Merge "BUG-2255 : adjusted BGP configuration to use pingpong data broker."
[bgpcep.git] / pcep / impl / src / main / java / org / opendaylight / protocol / pcep / impl / AbstractPCEPSessionNegotiator.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.protocol.pcep.impl;
9
10 import com.google.common.annotations.VisibleForTesting;
11 import com.google.common.base.Preconditions;
12 import io.netty.channel.Channel;
13 import io.netty.util.concurrent.Promise;
14 import java.util.concurrent.Future;
15 import java.util.concurrent.TimeUnit;
16 import java.util.concurrent.TimeoutException;
17 import org.opendaylight.protocol.framework.AbstractSessionNegotiator;
18 import org.opendaylight.protocol.pcep.spi.PCEPErrors;
19 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Keepalive;
20 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.KeepaliveBuilder;
21 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.OpenBuilder;
22 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Pcerr;
23 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.Message;
24 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.OpenMessage;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.keepalive.message.KeepaliveMessageBuilder;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.message.OpenMessageBuilder;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.object.Open;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcep.error.object.ErrorObject;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcerr.message.pcerr.message.error.type.SessionCase;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 /**
34  * Abstract PCEP session negotiator. Takes care of basic handshake without implementing a specific policy. Policies need
35  * to be provided by a specific subclass.
36  */
37 public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegotiator<Message, PCEPSessionImpl> {
38     /**
39      * Unified KeepWait and OpenWait timer expiration, in seconds.
40      */
41     public static final int FAIL_TIMER_VALUE = 60;
42
43     /**
44      * PCEP session negotiation state transitions are described in RFC5440. Simplification the two timers (KeepWait and
45      * OpenWait) are merged into a FailTimer, as they are mutually exclusive, have the same timeout value and their
46      * action is to terminate negotiation. This timer is restarted between state transitions and runs in all states
47      * except Idle and Finished.
48      */
49     @VisibleForTesting
50     public enum State {
51         /**
52          * Negotiation has not begun. It will be activated once we are asked to provide our initial proposal, at which
53          * point we move into OpenWait state.
54          */
55         IDLE,
56         /**
57          * Waiting for the peer's OPEN message.
58          */
59         OPEN_WAIT,
60         /**
61          * Waiting for the peer's KEEPALIVE message.
62          */
63         KEEP_WAIT,
64         /**
65          * Negotiation has completed.
66          */
67         FINISHED,
68     }
69
70     private static final Logger LOG = LoggerFactory.getLogger(AbstractPCEPSessionNegotiator.class);
71     private static final Keepalive KEEPALIVE = new KeepaliveBuilder().setKeepaliveMessage(new KeepaliveMessageBuilder().build()).build();
72
73     private volatile boolean localOK, openRetry, remoteOK;
74     private volatile State state = State.IDLE;
75     private Future<?> failTimer;
76     private Open localPrefs;
77     private Open remotePrefs;
78
79     protected AbstractPCEPSessionNegotiator(final Promise<PCEPSessionImpl> promise, final Channel channel) {
80         super(promise, channel);
81     }
82
83     /**
84      * Get the initial session parameters proposal.
85      *
86      * @return Session parameters proposal.
87      */
88     protected abstract Open getInitialProposal();
89
90     /**
91      * Get the revised session parameters proposal based on the feedback the peer has provided to us.
92      *
93      * @param suggestion Peer-provided suggested session parameters
94      * @return Session parameters proposal, or null if peers session parameters preclude us from suggesting anything
95      */
96     protected abstract Open getRevisedProposal(Open suggestion);
97
98     /**
99      * Check whether a peer-provided session parameters proposal is acceptable.
100      *
101      * @param proposal peer-proposed session parameters
102      * @return true if the proposal is acceptable, false otherwise
103      */
104     protected abstract boolean isProposalAcceptable(Open proposal);
105
106     /**
107      * Given a peer-provided session parameters proposal which we found unacceptable, provide a counter-proposal. The
108      * requirement is that the isProposalAcceptable() method has to return true when presented with this proposal.
109      *
110      * @param proposal unacceptable peer proposal
111      * @return our counter-proposal, or null if there is no way to negotiate an acceptable proposal
112      */
113     protected abstract Open getCounterProposal(Open proposal);
114
115     /**
116      * Create the protocol session.
117      *
118      * @param channel Underlying channel.
119      * @param sessionId Assigned session ID.
120      * @param localPrefs Session preferences proposed by us and accepted by the peer.
121      * @param remotePrefs Session preferences proposed by the peer and accepted by us.
122      * @return New protocol session.
123      */
124     protected abstract PCEPSessionImpl createSession(Channel channel, Open localPrefs, Open remotePrefs);
125
126     /**
127      * Sends PCEP Error Message with one PCEPError.
128      *
129      * @param value
130      */
131     private void sendErrorMessage(final PCEPErrors value) {
132
133         this.sendMessage(Util.createErrorMessage(value, null));
134     }
135
136     private void scheduleFailTimer() {
137         this.failTimer = this.channel.eventLoop().schedule(new Runnable() {
138             @Override
139             public void run() {
140                 switch (AbstractPCEPSessionNegotiator.this.state) {
141                 case FINISHED:
142                 case IDLE:
143                     break;
144                 case KEEP_WAIT:
145                     sendErrorMessage(PCEPErrors.NO_MSG_BEFORE_EXP_KEEPWAIT);
146                     negotiationFailed(new TimeoutException("KeepWait timer expired"));
147                     AbstractPCEPSessionNegotiator.this.state = State.FINISHED;
148                     break;
149                 case OPEN_WAIT:
150                     sendErrorMessage(PCEPErrors.NO_OPEN_BEFORE_EXP_OPENWAIT);
151                     negotiationFailed(new TimeoutException("OpenWait timer expired"));
152                     AbstractPCEPSessionNegotiator.this.state = State.FINISHED;
153                     break;
154                 default:
155                     break;
156                 }
157             }
158         }, FAIL_TIMER_VALUE, TimeUnit.SECONDS);
159     }
160
161     @Override
162     protected final void startNegotiation() {
163         Preconditions.checkState(this.state == State.IDLE);
164         this.localPrefs = getInitialProposal();
165         final OpenMessage m = new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.OpenBuilder().setOpenMessage(
166                 new OpenMessageBuilder().setOpen(this.localPrefs).build()).build();
167         this.sendMessage(m);
168         this.state = State.OPEN_WAIT;
169         scheduleFailTimer();
170
171         LOG.info("PCEP session with {} started, sent proposal {}", this.channel, this.localPrefs);
172     }
173
174     private boolean handleMessageKeepWait(final Message msg) {
175         if (msg instanceof Keepalive) {
176             this.localOK = true;
177             if (this.remoteOK) {
178                 LOG.info("PCEP peer {} completed negotiation", this.channel);
179                 negotiationSuccessful(createSession(this.channel, this.localPrefs, this.remotePrefs));
180                 this.state = State.FINISHED;
181             } else {
182                 scheduleFailTimer();
183                 this.state = State.OPEN_WAIT;
184                 LOG.debug("Channel {} moved to OpenWait state with localOK=1", this.channel);
185             }
186             return true;
187         } else if (msg instanceof Pcerr) {
188             final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcerr.message.PcerrMessage err = ((Pcerr) msg).getPcerrMessage();
189             if (err.getErrorType() == null) {
190                 final ErrorObject obj = err.getErrors().get(0).getErrorObject();
191                 LOG.warn("Unexpected error received from PCC: type {} value {}", obj.getType(), obj.getValue());
192                 negotiationFailed(new IllegalStateException("Unexpected error received from PCC."));
193                 this.state = State.IDLE;
194                 return true;
195             }
196             this.localPrefs = getRevisedProposal(((SessionCase) err.getErrorType()).getSession().getOpen());
197             if (this.localPrefs == null) {
198                 sendErrorMessage(PCEPErrors.PCERR_NON_ACC_SESSION_CHAR);
199                 negotiationFailed(new IllegalStateException("Peer suggested unacceptable retry proposal"));
200                 this.state = State.FINISHED;
201                 return true;
202             }
203             this.sendMessage(new OpenBuilder().setOpenMessage(new OpenMessageBuilder().setOpen(this.localPrefs).build()).build());
204             if (!this.remoteOK) {
205                 this.state = State.OPEN_WAIT;
206             }
207             scheduleFailTimer();
208             return true;
209         }
210         return false;
211     }
212
213     private boolean handleMessageOpenWait(final Message msg) {
214         if (msg instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Open) {
215             final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.message.OpenMessage o = ((org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Open) msg).getOpenMessage();
216             final Open open = o.getOpen();
217             if (isProposalAcceptable(open)) {
218                 this.sendMessage(KEEPALIVE);
219                 this.remotePrefs = open;
220                 this.remoteOK = true;
221                 if (this.localOK) {
222                     negotiationSuccessful(createSession(this.channel, this.localPrefs, this.remotePrefs));
223                     LOG.info("PCEP peer {} completed negotiation", this.channel);
224                     this.state = State.FINISHED;
225                 } else {
226                     scheduleFailTimer();
227                     this.state = State.KEEP_WAIT;
228                     LOG.debug("Channel {} moved to KeepWait state with remoteOK=1", this.channel);
229                 }
230                 return true;
231             }
232
233             if (this.openRetry) {
234                 sendErrorMessage(PCEPErrors.SECOND_OPEN_MSG);
235                 negotiationFailed(new IllegalStateException("OPEN renegotiation failed"));
236                 this.state = State.FINISHED;
237                 return true;
238             }
239
240             final Open newPrefs = getCounterProposal(open);
241             if (newPrefs == null) {
242                 sendErrorMessage(PCEPErrors.NON_ACC_NON_NEG_SESSION_CHAR);
243                 negotiationFailed(new IllegalStateException("Peer sent unacceptable session parameters"));
244                 this.state = State.FINISHED;
245                 return true;
246             }
247
248             this.sendMessage(Util.createErrorMessage(PCEPErrors.NON_ACC_NEG_SESSION_CHAR, newPrefs));
249
250             this.openRetry = true;
251             this.state = this.localOK ? State.OPEN_WAIT : State.KEEP_WAIT;
252             scheduleFailTimer();
253             return true;
254         }
255         return false;
256     }
257
258     @Override
259     protected final void handleMessage(final Message msg) {
260         this.failTimer.cancel(false);
261
262         LOG.debug("Channel {} handling message {} in state {}", this.channel, msg, this.state);
263
264         switch (this.state) {
265         case FINISHED:
266         case IDLE:
267             throw new IllegalStateException("Unexpected handleMessage in state " + this.state);
268         case KEEP_WAIT:
269             if (handleMessageKeepWait(msg)) {
270                 return;
271             }
272             break;
273         case OPEN_WAIT:
274             if (handleMessageOpenWait(msg)) {
275                 return;
276             }
277             break;
278         default:
279             break;
280         }
281         LOG.warn("Channel {} in state {} received unexpected message {}", this.channel, this.state, msg);
282         sendErrorMessage(PCEPErrors.NON_OR_INVALID_OPEN_MSG);
283         negotiationFailed(new Exception("Illegal message encountered"));
284         this.state = State.FINISHED;
285     }
286
287     @VisibleForTesting
288     State getState() {
289         return this.state;
290     }
291 }