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.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.pcerr.message.pcerr.message.error.type.SessionCase;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
36 import com.google.common.annotations.VisibleForTesting;
37 import com.google.common.base.Preconditions;
40 * Abstract PCEP session negotiator. Takes care of basic handshake without implementing a specific policy. Policies need
41 * to be provided by a specific subclass.
43 public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegotiator<Message, PCEPSessionImpl> {
45 * Unified KeepWait and OpenWait timer expiration, in seconds.
47 public static final int FAIL_TIMER_VALUE = 60;
50 * PCEP session negotiation state transitions are described in RFC5440. Simplification the two timers (KeepWait and
51 * OpenWait) are merged into a FailTimer, as they are mutually exclusive, have the same timeout value and their
52 * action is to terminate negotiation. This timer is restarted between state transitions and runs in all states
53 * except Idle and Finished.
58 * Negotiation has not begun. It will be activated once we are asked to provide our initial proposal, at which
59 * point we move into OpenWait state.
63 * Waiting for the peer's OPEN message.
67 * Waiting for the peer's KEEPALIVE message.
71 * Negotiation has completed.
76 private static final Logger LOG = LoggerFactory.getLogger(AbstractPCEPSessionNegotiator.class);
78 private final Timer timer;
81 private State state = State.Idle;
84 private Timeout failTimer;
87 private Open localPrefs;
90 private Open remotePrefs;
92 private volatile boolean localOK, openRetry, remoteOK;
94 private final Keepalive keepalive = new KeepaliveBuilder().setKeepaliveMessage(new KeepaliveMessageBuilder().build()).build();
96 protected AbstractPCEPSessionNegotiator(final Timer timer, final Promise<PCEPSessionImpl> promise, final Channel channel) {
97 super(promise, channel);
98 this.timer = Preconditions.checkNotNull(timer);
102 * Get the initial session parameters proposal.
104 * @return Session parameters proposal.
106 protected abstract Open getInitialProposal();
109 * Get the revised session parameters proposal based on the feedback the peer has provided to us.
111 * @param suggestion Peer-provided suggested session parameters
112 * @return Session parameters proposal, or null if peers session parameters preclude us from suggesting anything
114 protected abstract Open getRevisedProposal(Open suggestion);
117 * Check whether a peer-provided session parameters proposal is acceptable.
119 * @param proposal peer-proposed session parameters
120 * @return true if the proposal is acceptable, false otherwise
122 protected abstract boolean isProposalAcceptable(Open proposal);
125 * Given a peer-provided session parameters proposal which we found unacceptable, provide a counter-proposal. The
126 * requirement is that the isProposalAcceptable() method has to return true when presented with this proposal.
128 * @param proposal unacceptable peer proposal
129 * @return our counter-proposal, or null if there is no way to negotiate an acceptable proposal
131 protected abstract Open getCounterProposal(Open proposal);
134 * Create the protocol session.
136 * @param timer Timer which the session can use for its various functions.
137 * @param channel Underlying channel.
138 * @param sessionId Assigned session ID.
139 * @param localPrefs Session preferences proposed by us and accepted by the peer.
140 * @param remotePrefs Session preferences proposed by the peer and accepted by us.
141 * @return New protocol session.
143 protected abstract PCEPSessionImpl createSession(Timer timer, Channel channel, Open localPrefs, Open remotePrefs);
146 * Sends PCEP Error Message with one PCEPError.
150 private void sendErrorMessage(final PCEPErrors value) {
152 this.channel.writeAndFlush(Util.createErrorMessage(value, null));
155 private void scheduleFailTimer() {
156 final Object lock = this;
158 this.failTimer = this.timer.newTimeout(new TimerTask() {
160 public void run(final Timeout timeout) {
161 synchronized (lock) {
162 // This closes the race between timer expiring and new timer
163 // being armed while it waits for the lock.
164 if (AbstractPCEPSessionNegotiator.this.failTimer == timeout) {
165 switch (AbstractPCEPSessionNegotiator.this.state) {
170 sendErrorMessage(PCEPErrors.NO_MSG_BEFORE_EXP_KEEPWAIT);
171 negotiationFailed(new TimeoutException("KeepWait timer expired"));
172 AbstractPCEPSessionNegotiator.this.state = State.Finished;
175 sendErrorMessage(PCEPErrors.NO_OPEN_BEFORE_EXP_OPENWAIT);
176 negotiationFailed(new TimeoutException("OpenWait timer expired"));
177 AbstractPCEPSessionNegotiator.this.state = State.Finished;
183 }, FAIL_TIMER_VALUE, TimeUnit.SECONDS);
187 protected final synchronized void startNegotiation() {
188 Preconditions.checkState(this.state == State.Idle);
189 this.localPrefs = getInitialProposal();
190 final OpenMessage m = new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.OpenBuilder().setOpenMessage(
191 new OpenMessageBuilder().setOpen(this.localPrefs).build()).build();
192 this.channel.writeAndFlush(m);
193 this.state = State.OpenWait;
196 LOG.debug("Channel {} started sent proposal {}", this.channel, this.localPrefs);
200 protected final synchronized void handleMessage(final Message msg) {
201 this.failTimer.cancel();
203 LOG.debug("Channel {} handling message in state {}", this.channel, this.state);
205 switch (this.state) {
208 throw new IllegalStateException("Unexpected handleMessage in state " + this.state);
210 if (msg instanceof Keepalive) {
213 LOG.info("Channel {} completed negotiation", this.channel);
214 negotiationSuccessful(createSession(this.timer, this.channel, this.localPrefs, this.remotePrefs));
215 this.state = State.Finished;
218 this.state = State.OpenWait;
219 LOG.debug("Channel {} moved to OpenWait state with localOK=1", this.channel);
223 } else if (msg instanceof Pcerr) {
224 final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcerr.message.PcerrMessage err = ((Pcerr) msg).getPcerrMessage();
225 this.localPrefs = getRevisedProposal(((SessionCase) err.getErrorType()).getSession().getOpen());
226 if (this.localPrefs == null) {
227 sendErrorMessage(PCEPErrors.PCERR_NON_ACC_SESSION_CHAR);
228 negotiationFailed(new RuntimeException("Peer suggested unacceptable retry proposal"));
229 this.state = State.Finished;
232 this.channel.writeAndFlush(new OpenBuilder().setOpenMessage(new OpenMessageBuilder().setOpen(this.localPrefs).build()).build());
233 if (!this.remoteOK) {
234 this.state = State.OpenWait;
242 if (msg instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Open) {
243 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();
244 final Open open = o.getOpen();
245 if (isProposalAcceptable(open)) {
246 this.channel.writeAndFlush(this.keepalive);
247 this.remotePrefs = open;
248 this.remoteOK = true;
250 negotiationSuccessful(createSession(this.timer, this.channel, this.localPrefs, this.remotePrefs));
251 this.state = State.Finished;
254 this.state = State.KeepWait;
255 LOG.debug("Channel {} moved to KeepWait state with remoteOK=1", this.channel);
260 if (this.openRetry) {
261 sendErrorMessage(PCEPErrors.SECOND_OPEN_MSG);
262 negotiationFailed(new RuntimeException("OPEN renegotiation failed"));
263 this.state = State.Finished;
267 final Open newPrefs = getCounterProposal(open);
268 if (newPrefs == null) {
269 sendErrorMessage(PCEPErrors.NON_ACC_NON_NEG_SESSION_CHAR);
270 negotiationFailed(new RuntimeException("Peer sent unacceptable session parameters"));
271 this.state = State.Finished;
275 this.channel.writeAndFlush(Util.createErrorMessage(PCEPErrors.NON_ACC_NEG_SESSION_CHAR, newPrefs));
277 this.openRetry = true;
278 this.state = this.localOK ? State.OpenWait : State.KeepWait;
286 LOG.warn("Channel {} in state {} received unexpected message {}", this.channel, this.state, msg);
287 sendErrorMessage(PCEPErrors.NON_OR_INVALID_OPEN_MSG);
288 negotiationFailed(new Exception("Illegal message encountered"));
289 this.state = State.Finished;
292 public synchronized State getState() {