2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.protocol.pcep.impl;
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;
16 import java.util.concurrent.TimeUnit;
17 import java.util.concurrent.TimeoutException;
19 import javax.annotation.concurrent.GuardedBy;
21 import org.opendaylight.protocol.framework.AbstractSessionNegotiator;
22 import org.opendaylight.protocol.pcep.PCEPErrors;
23 import org.opendaylight.protocol.pcep.message.PCEPErrorMessage;
24 import org.opendaylight.protocol.pcep.message.PCEPOpenMessage;
25 import org.opendaylight.protocol.pcep.object.PCEPErrorObject;
26 import org.opendaylight.protocol.pcep.object.PCEPOpenObject;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.KeepaliveMessage;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.Message;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.keepalive.message.KeepaliveMessageBuilder;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
33 import com.google.common.base.Preconditions;
34 import com.google.common.collect.ImmutableList;
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.
40 public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegotiator<Message, PCEPSessionImpl> {
42 * Unified KeepWait and OpenWait timer expiration, in seconds.
44 public static final int FAIL_TIMER_VALUE = 60;
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.
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.
59 * Waiting for the peer's OPEN message.
63 * Waiting for the peer's KEEPALIVE message.
67 * Negotiation has completed.
72 private static final Logger logger = LoggerFactory.getLogger(AbstractPCEPSessionNegotiator.class);
73 private final Timer timer;
76 private State state = State.Idle;
79 private Timeout failTimer;
82 private PCEPOpenObject localPrefs;
85 private PCEPOpenObject remotePrefs;
87 private volatile boolean localOK, openRetry, remoteOK;
89 protected AbstractPCEPSessionNegotiator(final Timer timer, final Promise<PCEPSessionImpl> promise, final Channel channel) {
90 super(promise, channel);
91 this.timer = Preconditions.checkNotNull(timer);
95 * Get the initial session parameters proposal.
97 * @return Session parameters proposal.
99 protected abstract PCEPOpenObject getInitialProposal();
102 * Get the revised session parameters proposal based on the feedback the peer has provided to us.
104 * @param suggestion Peer-provided suggested session parameters
105 * @return Session parameters proposal.
107 protected abstract PCEPOpenObject getRevisedProposal(PCEPOpenObject suggestion);
110 * Check whether a peer-provided session parameters proposal is acceptable.
112 * @param proposal peer-proposed session parameters
113 * @return true if the proposal is acceptable, false otherwise
115 protected abstract boolean isProposalAcceptable(PCEPOpenObject proposal);
118 * Given a peer-provided session parameters proposal which we found unacceptable, provide a counter-proposal. The
119 * requirement is that the isProposalAcceptable() method has to return true when presented with this proposal.
121 * @param proposal unacceptable peer proposal
122 * @return our counter-proposal, or null if there is no way to negotiate an acceptable proposal
124 protected abstract PCEPOpenObject getCounterProposal(PCEPOpenObject proposal);
127 * Create the protocol session.
129 * @param timer Timer which the session can use for its various functions.
130 * @param channel Underlying channel.
131 * @param sessionId Assigned session ID.
132 * @param localPrefs Session preferences proposed by us and accepted by the peer.
133 * @param remotePrefs Session preferences proposed by the peer and accepted by us.
134 * @return New protocol session.
136 protected abstract PCEPSessionImpl createSession(Timer timer, Channel channel, PCEPOpenObject localPrefs, PCEPOpenObject remotePrefs);
139 * Sends PCEP Error Message with one PCEPError.
143 private void sendErrorMessage(final PCEPErrors value) {
144 this.channel.writeAndFlush(new PCEPErrorMessage(ImmutableList.of(new PCEPErrorObject(value))));
147 private void scheduleFailTimer() {
148 final Object lock = this;
150 this.failTimer = this.timer.newTimeout(new TimerTask() {
152 public void run(final Timeout timeout) throws Exception {
153 synchronized (lock) {
154 // This closes the race between timer expiring and new timer
155 // being armed while it waits for the lock.
156 if (AbstractPCEPSessionNegotiator.this.failTimer == timeout) {
157 switch (AbstractPCEPSessionNegotiator.this.state) {
162 sendErrorMessage(PCEPErrors.NO_MSG_BEFORE_EXP_KEEPWAIT);
163 negotiationFailed(new TimeoutException("KeepWait timer expired"));
164 AbstractPCEPSessionNegotiator.this.state = State.Finished;
167 sendErrorMessage(PCEPErrors.NO_OPEN_BEFORE_EXP_OPENWAIT);
168 negotiationFailed(new TimeoutException("OpenWait timer expired"));
169 AbstractPCEPSessionNegotiator.this.state = State.Finished;
175 }, FAIL_TIMER_VALUE, TimeUnit.SECONDS);
179 final synchronized protected void startNegotiation() {
180 Preconditions.checkState(this.state == State.Idle);
181 this.localPrefs = getInitialProposal();
182 this.channel.writeAndFlush(new PCEPOpenMessage(this.localPrefs));
183 this.state = State.OpenWait;
186 logger.debug("Channel {} started sent proposal {}", this.channel, this.localPrefs);
190 final synchronized protected void handleMessage(final Message msg) throws Exception {
191 this.failTimer.cancel();
193 logger.debug("Channel {} handling message in state {}", this.channel, this.state);
195 switch (this.state) {
198 throw new IllegalStateException("Unexpected handleMessage in state " + this.state);
200 if (msg instanceof KeepaliveMessage) {
203 negotiationSuccessful(createSession(this.timer, this.channel, this.localPrefs, this.remotePrefs));
204 this.state = State.Finished;
207 this.state = State.OpenWait;
208 logger.debug("Channel {} moved to OpenWait state with localOK=1", this.channel);
212 } else if (msg instanceof PCEPErrorMessage) {
213 final PCEPErrorMessage err = (PCEPErrorMessage) msg;
214 this.localPrefs = getRevisedProposal(err.getOpenObject());
215 if (this.localPrefs == null) {
216 sendErrorMessage(PCEPErrors.PCERR_NON_ACC_SESSION_CHAR);
217 negotiationFailed(new RuntimeException("Peer suggested unacceptable retry proposal"));
218 this.state = State.Finished;
222 if (!this.remoteOK) {
223 this.state = State.OpenWait;
231 if (msg instanceof PCEPOpenMessage) {
232 final PCEPOpenObject open = ((PCEPOpenMessage) msg).getOpenObject();
233 if (isProposalAcceptable(open)) {
234 this.channel.writeAndFlush(new KeepaliveMessageBuilder().build());
235 this.remotePrefs = open;
236 this.remoteOK = true;
238 negotiationSuccessful(createSession(this.timer, this.channel, this.localPrefs, this.remotePrefs));
239 this.state = State.Finished;
242 this.state = State.KeepWait;
243 logger.debug("Channel {} moved to KeepWait state with remoteOK=1", this.channel);
248 if (this.openRetry) {
249 sendErrorMessage(PCEPErrors.SECOND_OPEN_MSG);
250 negotiationFailed(new RuntimeException("OPEN renegotiation failed"));
251 this.state = State.Finished;
255 final PCEPOpenObject newPrefs = getCounterProposal(open);
256 if (newPrefs == null) {
257 sendErrorMessage(PCEPErrors.NON_ACC_NON_NEG_SESSION_CHAR);
258 negotiationFailed(new RuntimeException("Peer sent unacceptable session parameters"));
259 this.state = State.Finished;
263 this.channel.writeAndFlush(new PCEPErrorMessage(newPrefs, ImmutableList.of(new PCEPErrorObject(PCEPErrors.NON_ACC_NEG_SESSION_CHAR)), null));
265 this.openRetry = true;
266 this.state = this.localOK ? State.OpenWait : State.KeepWait;
274 logger.warn("Channel {} in state {} received unexpected message {}", this.channel, this.state, msg);
275 sendErrorMessage(PCEPErrors.NON_OR_INVALID_OPEN_MSG);
276 negotiationFailed(new Exception("Illegal message encountered"));
277 this.state = State.Finished;