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