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