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.PCEPMessage;
24 import org.opendaylight.protocol.pcep.message.PCEPErrorMessage;
25 import org.opendaylight.protocol.pcep.message.PCEPKeepAliveMessage;
26 import org.opendaylight.protocol.pcep.message.PCEPOpenMessage;
27 import org.opendaylight.protocol.pcep.object.PCEPErrorObject;
28 import org.opendaylight.protocol.pcep.object.PCEPOpenObject;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
32 import com.google.common.base.Preconditions;
33 import com.google.common.collect.ImmutableList;
36 * Abstract PCEP session negotiator. Takes care of basic handshake without
37 * implementing a specific policy. Policies need to be provided by a specific
40 public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegotiator<PCEPMessage, 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.
48 * Simplification the two timers (KeepWait and OpenWait) are merged into
49 * a FailTimer, as they are mutually exclusive, have the same timeout
50 * value and their action is to terminate negotiation. This timer is
51 * restarted between state transitions and runs in all states except
56 * Negotiation has not begun. It will be activated once we are asked
57 * to provide our initial proposal, at which point we move into
62 * Waiting for the peer's OPEN message.
66 * Waiting for the peer's KEEPALIVE message.
70 * Negotiation has completed.
75 private static final Logger logger = LoggerFactory.getLogger(AbstractPCEPSessionNegotiator.class);
76 private final Timer timer;
79 private State state = State.Idle;
82 private Timeout failTimer;
85 private PCEPOpenObject localPrefs;
88 private PCEPOpenObject remotePrefs;
90 private volatile boolean localOK, openRetry, remoteOK;
92 protected AbstractPCEPSessionNegotiator(final Timer timer, final Promise<PCEPSessionImpl> promise, final Channel channel) {
93 super(promise, channel);
94 this.timer = Preconditions.checkNotNull(timer);
98 * Get the initial session parameters proposal.
99 * @return Session parameters proposal.
101 protected abstract PCEPOpenObject getInitialProposal();
104 * Get the revised session parameters proposal based on the feedback
105 * the peer has provided to us.
107 * @param suggestion Peer-provided suggested session parameters
108 * @return Session parameters proposal.
110 protected abstract PCEPOpenObject getRevisedProposal(PCEPOpenObject suggestion);
113 * Check whether a peer-provided session parameters proposal is acceptable.
115 * @param proposal peer-proposed session parameters
116 * @return true if the proposal is acceptable, false otherwise
118 protected abstract boolean isProposalAcceptable(PCEPOpenObject proposal);
121 * Given a peer-provided session parameters proposal which we found
122 * unacceptable, provide a counter-proposal. The requirement is that
123 * the isProposalAcceptable() method has to return true when presented
124 * with this proposal.
126 * @param proposal unacceptable peer proposal
127 * @return our counter-proposal, or null if there is no way to negotiate
128 * an acceptable proposal
130 protected abstract PCEPOpenObject getCounterProposal(PCEPOpenObject proposal);
133 * Create the protocol session.
135 * @param timer Timer which the session can use for its various functions.
136 * @param channel Underlying channel.
137 * @param sessionId Assigned session ID.
138 * @param localPrefs Session preferences proposed by us and accepted by the peer.
139 * @param remotePrefs Session preferences proposed by the peer and accepted by us.
140 * @return New protocol session.
142 protected abstract PCEPSessionImpl createSession(Timer timer, Channel channel,
143 PCEPOpenObject localPrefs, PCEPOpenObject remotePrefs);
146 * Sends PCEP Error Message with one PCEPError.
150 private void sendErrorMessage(final PCEPErrors value) {
151 channel.writeAndFlush(new PCEPErrorMessage(ImmutableList.of(new PCEPErrorObject(value))));
154 private void scheduleFailTimer() {
155 final Object lock = this;
157 failTimer = timer.newTimeout(new TimerTask() {
159 public void run(final Timeout timeout) throws Exception {
160 synchronized (lock) {
161 // This closes the race between timer expiring and new timer
162 // being armed while it waits for the lock.
163 if (failTimer == timeout) {
169 sendErrorMessage(PCEPErrors.NO_MSG_BEFORE_EXP_KEEPWAIT);
170 negotiationFailed(new TimeoutException("KeepWait timer expired"));
171 state = State.Finished;
174 sendErrorMessage(PCEPErrors.NO_OPEN_BEFORE_EXP_OPENWAIT);
175 negotiationFailed(new TimeoutException("OpenWait timer expired"));
176 state = State.Finished;
182 }, FAIL_TIMER_VALUE, TimeUnit.SECONDS);
186 final synchronized protected void startNegotiation() {
187 Preconditions.checkState(state == State.Idle);
188 localPrefs = getInitialProposal();
189 channel.writeAndFlush(new PCEPOpenMessage(localPrefs));
190 state = State.OpenWait;
193 logger.debug("Channel {} started sent proposal {}", channel, localPrefs);
197 final synchronized protected void handleMessage(final PCEPMessage msg) throws Exception {
200 logger.debug("Channel {} handling message in state {}", channel, msg);
205 throw new IllegalStateException("Unexpected handleMessage in state " + state);
207 if (msg instanceof PCEPKeepAliveMessage) {
210 negotiationSuccessful(createSession(timer, channel, localPrefs, remotePrefs));
211 state = State.Finished;
214 state = State.OpenWait;
215 logger.debug("Channel {} moved to OpenWait state with localOK=1", channel);
219 } else if (msg instanceof PCEPErrorMessage) {
220 final PCEPErrorMessage err = (PCEPErrorMessage) msg;
221 localPrefs = getRevisedProposal(err.getOpenObject());
222 if (localPrefs == null) {
223 sendErrorMessage(PCEPErrors.PCERR_NON_ACC_SESSION_CHAR);
224 negotiationFailed(new RuntimeException("Peer suggested unacceptable retry proposal"));
225 state = State.Finished;
230 state = State.OpenWait;
238 if (msg instanceof PCEPOpenMessage) {
239 final PCEPOpenObject open = ((PCEPOpenMessage) msg).getOpenObject();
240 if (isProposalAcceptable(open)) {
241 channel.writeAndFlush(new PCEPKeepAliveMessage());
245 negotiationSuccessful(createSession(timer, channel, localPrefs, remotePrefs));
246 state = State.Finished;
249 state = State.KeepWait;
250 logger.debug("Channel {} moved to KeepWait state with remoteOK=1", channel);
256 sendErrorMessage(PCEPErrors.SECOND_OPEN_MSG);
257 negotiationFailed(new RuntimeException("OPEN renegotiation failed"));
258 state = State.Finished;
262 final PCEPOpenObject newPrefs = getCounterProposal(open);
263 if (newPrefs == null) {
264 sendErrorMessage(PCEPErrors.NON_ACC_NON_NEG_SESSION_CHAR);
265 negotiationFailed(new RuntimeException("Peer sent unacceptable session parameters"));
266 state = State.Finished;
270 channel.writeAndFlush(
271 new PCEPErrorMessage(newPrefs, ImmutableList.of(
272 new PCEPErrorObject(PCEPErrors.NON_ACC_NEG_SESSION_CHAR)), null));
275 state = localOK ? State.OpenWait : State.KeepWait;
283 logger.warn("Channel {} in state {} received unexpected message {}", channel, state, msg);
284 sendErrorMessage(PCEPErrors.NON_OR_INVALID_OPEN_MSG);
285 negotiationFailed(new Exception("Illegal message encountered"));
286 state = State.Finished;