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