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 com.google.common.annotations.VisibleForTesting;
11 import com.google.common.base.Preconditions;
12 import io.netty.channel.Channel;
13 import io.netty.util.concurrent.Promise;
14 import java.util.concurrent.Future;
15 import java.util.concurrent.TimeUnit;
16 import java.util.concurrent.TimeoutException;
17 import org.opendaylight.protocol.framework.AbstractSessionNegotiator;
18 import org.opendaylight.protocol.pcep.spi.PCEPErrors;
19 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Keepalive;
20 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.KeepaliveBuilder;
21 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.OpenBuilder;
22 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Pcerr;
23 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.Message;
24 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.OpenMessage;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.keepalive.message.KeepaliveMessageBuilder;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.message.OpenMessageBuilder;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.object.Open;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcep.error.object.ErrorObject;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcerr.message.pcerr.message.error.type.SessionCase;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
34 * Abstract PCEP session negotiator. Takes care of basic handshake without implementing a specific policy. Policies need
35 * to be provided by a specific subclass.
37 public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegotiator<Message, PCEPSessionImpl> {
39 * Unified KeepWait and OpenWait timer expiration, in seconds.
41 public static final int FAIL_TIMER_VALUE = 60;
44 * PCEP session negotiation state transitions are described in RFC5440. Simplification the two timers (KeepWait and
45 * OpenWait) are merged into a FailTimer, as they are mutually exclusive, have the same timeout value and their
46 * action is to terminate negotiation. This timer is restarted between state transitions and runs in all states
47 * except Idle and Finished.
52 * Negotiation has not begun. It will be activated once we are asked to provide our initial proposal, at which
53 * point we move into OpenWait state.
57 * Waiting for the peer's OPEN message.
61 * Waiting for the peer's KEEPALIVE message.
65 * Negotiation has completed.
70 private static final Logger LOG = LoggerFactory.getLogger(AbstractPCEPSessionNegotiator.class);
71 private static final Keepalive KEEPALIVE = new KeepaliveBuilder().setKeepaliveMessage(new KeepaliveMessageBuilder().build()).build();
73 private volatile boolean localOK, openRetry, remoteOK;
74 private volatile State state = State.IDLE;
75 private Future<?> failTimer;
76 private Open localPrefs;
77 private Open remotePrefs;
79 protected AbstractPCEPSessionNegotiator(final Promise<PCEPSessionImpl> promise, final Channel channel) {
80 super(promise, channel);
84 * Get the initial session parameters proposal.
86 * @return Session parameters proposal.
88 protected abstract Open getInitialProposal();
91 * Get the revised session parameters proposal based on the feedback the peer has provided to us.
93 * @param suggestion Peer-provided suggested session parameters
94 * @return Session parameters proposal, or null if peers session parameters preclude us from suggesting anything
96 protected abstract Open getRevisedProposal(Open suggestion);
99 * Check whether a peer-provided session parameters proposal is acceptable.
101 * @param proposal peer-proposed session parameters
102 * @return true if the proposal is acceptable, false otherwise
104 protected abstract boolean isProposalAcceptable(Open proposal);
107 * Given a peer-provided session parameters proposal which we found unacceptable, provide a counter-proposal. The
108 * requirement is that the isProposalAcceptable() method has to return true when presented with this proposal.
110 * @param proposal unacceptable peer proposal
111 * @return our counter-proposal, or null if there is no way to negotiate an acceptable proposal
113 protected abstract Open getCounterProposal(Open proposal);
116 * Create the protocol session.
118 * @param channel Underlying channel.
119 * @param localPrefs Session preferences proposed by us and accepted by the peer.
120 * @param remotePrefs Session preferences proposed by the peer and accepted by us.
121 * @return New protocol session.
123 protected abstract PCEPSessionImpl createSession(Channel channel, Open localPrefs, Open remotePrefs);
126 * Sends PCEP Error Message with one PCEPError.
130 private void sendErrorMessage(final PCEPErrors value) {
132 this.sendMessage(Util.createErrorMessage(value, null));
135 private void scheduleFailTimer() {
136 this.failTimer = this.channel.eventLoop().schedule(new Runnable() {
139 switch (AbstractPCEPSessionNegotiator.this.state) {
144 sendErrorMessage(PCEPErrors.NO_MSG_BEFORE_EXP_KEEPWAIT);
145 negotiationFailed(new TimeoutException("KeepWait timer expired"));
146 AbstractPCEPSessionNegotiator.this.state = State.FINISHED;
149 sendErrorMessage(PCEPErrors.NO_OPEN_BEFORE_EXP_OPENWAIT);
150 negotiationFailed(new TimeoutException("OpenWait timer expired"));
151 AbstractPCEPSessionNegotiator.this.state = State.FINISHED;
157 }, FAIL_TIMER_VALUE, TimeUnit.SECONDS);
161 protected final void startNegotiation() {
162 Preconditions.checkState(this.state == State.IDLE);
163 this.localPrefs = getInitialProposal();
164 final OpenMessage m = new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.OpenBuilder().setOpenMessage(
165 new OpenMessageBuilder().setOpen(this.localPrefs).build()).build();
167 this.state = State.OPEN_WAIT;
170 LOG.info("PCEP session with {} started, sent proposal {}", this.channel, this.localPrefs);
173 private boolean handleMessageKeepWait(final Message msg) {
174 if (msg instanceof Keepalive) {
177 LOG.info("PCEP peer {} completed negotiation", this.channel);
178 negotiationSuccessful(createSession(this.channel, this.localPrefs, this.remotePrefs));
179 this.state = State.FINISHED;
182 this.state = State.OPEN_WAIT;
183 LOG.debug("Channel {} moved to OpenWait state with localOK=1", this.channel);
186 } else if (msg instanceof Pcerr) {
187 final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcerr.message.PcerrMessage err = ((Pcerr) msg).getPcerrMessage();
188 if (err.getErrorType() == null) {
189 final ErrorObject obj = err.getErrors().get(0).getErrorObject();
190 LOG.warn("Unexpected error received from PCC: type {} value {}", obj.getType(), obj.getValue());
191 negotiationFailed(new IllegalStateException("Unexpected error received from PCC."));
192 this.state = State.IDLE;
195 this.localPrefs = getRevisedProposal(((SessionCase) err.getErrorType()).getSession().getOpen());
196 if (this.localPrefs == null) {
197 sendErrorMessage(PCEPErrors.PCERR_NON_ACC_SESSION_CHAR);
198 negotiationFailed(new IllegalStateException("Peer suggested unacceptable retry proposal"));
199 this.state = State.FINISHED;
202 this.sendMessage(new OpenBuilder().setOpenMessage(new OpenMessageBuilder().setOpen(this.localPrefs).build()).build());
203 if (!this.remoteOK) {
204 this.state = State.OPEN_WAIT;
212 private boolean handleMessageOpenWait(final Message msg) {
213 if (!(msg instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Open)) {
216 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();
217 final Open open = o.getOpen();
218 if (isProposalAcceptable(open)) {
219 this.sendMessage(KEEPALIVE);
220 this.remotePrefs = open;
221 this.remoteOK = true;
223 negotiationSuccessful(createSession(this.channel, this.localPrefs, this.remotePrefs));
224 LOG.info("PCEP peer {} completed negotiation", this.channel);
225 this.state = State.FINISHED;
228 this.state = State.KEEP_WAIT;
229 LOG.debug("Channel {} moved to KeepWait state with remoteOK=1", this.channel);
233 if (this.openRetry) {
234 sendErrorMessage(PCEPErrors.SECOND_OPEN_MSG);
235 negotiationFailed(new IllegalStateException("OPEN renegotiation failed"));
236 this.state = State.FINISHED;
239 final Open newPrefs = getCounterProposal(open);
240 if (newPrefs == null) {
241 sendErrorMessage(PCEPErrors.NON_ACC_NON_NEG_SESSION_CHAR);
242 negotiationFailed(new IllegalStateException("Peer sent unacceptable session parameters"));
243 this.state = State.FINISHED;
246 this.sendMessage(Util.createErrorMessage(PCEPErrors.NON_ACC_NEG_SESSION_CHAR, newPrefs));
247 this.openRetry = true;
248 this.state = this.localOK ? State.OPEN_WAIT : State.KEEP_WAIT;
254 protected final void handleMessage(final Message msg) {
255 this.failTimer.cancel(false);
257 LOG.debug("Channel {} handling message {} in state {}", this.channel, msg, this.state);
259 switch (this.state) {
262 throw new IllegalStateException("Unexpected handleMessage in state " + this.state);
264 if (handleMessageKeepWait(msg)) {
269 if (handleMessageOpenWait(msg)) {
276 LOG.warn("Channel {} in state {} received unexpected message {}", this.channel, this.state, msg);
277 sendErrorMessage(PCEPErrors.NON_OR_INVALID_OPEN_MSG);
278 negotiationFailed(new Exception("Illegal message encountered"));
279 this.state = State.FINISHED;