import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.Timeout;
import io.netty.util.Timer;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.Promise;
import org.opendaylight.controller.netconf.api.NetconfMessage;
-import org.opendaylight.controller.netconf.api.NetconfSession;
+import org.opendaylight.controller.netconf.api.NetconfSessionListener;
import org.opendaylight.controller.netconf.api.NetconfSessionPreferences;
-import org.opendaylight.controller.netconf.util.xml.XmlElement;
-import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants;
+import org.opendaylight.controller.netconf.util.handler.FramingMechanismHandlerFactory;
+import org.opendaylight.controller.netconf.util.handler.NetconfChunkAggregator;
+import org.opendaylight.controller.netconf.util.handler.NetconfMessageToXMLEncoder;
+import org.opendaylight.controller.netconf.util.handler.NetconfXMLToMessageDecoder;
+import org.opendaylight.controller.netconf.util.messages.FramingMechanism;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage;
import org.opendaylight.controller.netconf.util.xml.XmlUtil;
import org.opendaylight.protocol.framework.AbstractSessionNegotiator;
-import org.opendaylight.protocol.framework.SessionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
import java.util.concurrent.TimeUnit;
-public abstract class AbstractNetconfSessionNegotiator<P extends NetconfSessionPreferences, S extends NetconfSession>
- extends AbstractSessionNegotiator<NetconfMessage, S> {
+public abstract class AbstractNetconfSessionNegotiator<P extends NetconfSessionPreferences, S extends AbstractNetconfSession<S, L>, L extends NetconfSessionListener<S>>
+extends AbstractSessionNegotiator<NetconfHelloMessage, S> {
- // TODO what time ?
- private static final long INITIAL_HOLDTIMER = 1;
private static final Logger logger = LoggerFactory.getLogger(AbstractNetconfSessionNegotiator.class);
+ public static final String NAME_OF_EXCEPTION_HANDLER = "lastExceptionHandler";
+
protected final P sessionPreferences;
- private final SessionListener sessionListener;
+ private final L sessionListener;
+ private Timeout timeout;
/**
* Possible states for Finite State Machine
*/
- private enum State {
+ protected enum State {
IDLE, OPEN_WAIT, FAILED, ESTABLISHED
}
private State state = State.IDLE;
private final Timer timer;
+ private final long connectionTimeoutMillis;
+ // TODO shrink constructor
protected AbstractNetconfSessionNegotiator(P sessionPreferences, Promise<S> promise, Channel channel, Timer timer,
- SessionListener sessionListener) {
+ L sessionListener, long connectionTimeoutMillis) {
super(promise, channel);
this.sessionPreferences = sessionPreferences;
this.timer = timer;
this.sessionListener = sessionListener;
+ this.connectionTimeoutMillis = connectionTimeoutMillis;
}
@Override
- protected void startNegotiation() throws Exception {
+ protected void startNegotiation() {
final Optional<SslHandler> sslHandler = getSslHandler(channel);
if (sslHandler.isPresent()) {
Future<Channel> future = sslHandler.get().handshakeFuture();
future.addListener(new GenericFutureListener<Future<? super Channel>>() {
@Override
- public void operationComplete(Future<? super Channel> future) throws Exception {
+ public void operationComplete(Future<? super Channel> future) {
Preconditions.checkState(future.isSuccess(), "Ssl handshake was not successful");
logger.debug("Ssl handshake complete");
start();
}
});
- } else
+ } else {
start();
+ }
}
private static Optional<SslHandler> getSslHandler(Channel channel) {
return sslHandler == null ? Optional.<SslHandler> absent() : Optional.of(sslHandler);
}
+ public P getSessionPreferences() {
+ return sessionPreferences;
+ }
+
private void start() {
final NetconfMessage helloMessage = this.sessionPreferences.getHelloMessage();
logger.debug("Session negotiation started with hello message {}", XmlUtil.toString(helloMessage.getDocument()));
- sendMessage(helloMessage);
- changeState(State.OPEN_WAIT);
+ channel.pipeline().addLast(NAME_OF_EXCEPTION_HANDLER, new ExceptionHandlingInboundChannelHandler());
- this.timer.newTimeout(new TimerTask() {
+ timeout = this.timer.newTimeout(new TimerTask() {
@Override
- public void run(final Timeout timeout) throws Exception {
+ public void run(final Timeout timeout) {
synchronized (this) {
if (state != State.ESTABLISHED) {
+ logger.debug("Connection timeout after {}, session is in state {}", timeout, state);
final IllegalStateException cause = new IllegalStateException(
"Session was not established after " + timeout);
negotiationFailed(cause);
changeState(State.FAILED);
+ } else if(channel.isOpen()) {
+ channel.pipeline().remove(NAME_OF_EXCEPTION_HANDLER);
}
}
}
- }, INITIAL_HOLDTIMER, TimeUnit.MINUTES);
- }
+ }, connectionTimeoutMillis, TimeUnit.MILLISECONDS);
- private void sendMessage(NetconfMessage message) {
- this.channel.writeAndFlush(message);
+ // FIXME, make sessionPreferences return HelloMessage, move NetconfHelloMessage to API
+ sendMessage((NetconfHelloMessage)helloMessage);
+ changeState(State.OPEN_WAIT);
+ }
+ private void cancelTimeout() {
+ if(timeout!=null) {
+ timeout.cancel();
+ }
}
@Override
- protected void handleMessage(NetconfMessage netconfMessage) {
+ protected void handleMessage(NetconfHelloMessage netconfMessage) {
+ S session = getSessionForHelloMessage(netconfMessage);
+ negotiationSuccessful(session);
+ }
+
+ protected final S getSessionForHelloMessage(NetconfHelloMessage netconfMessage) {
+ Preconditions.checkNotNull(netconfMessage, "netconfMessage");
+
final Document doc = netconfMessage.getDocument();
- if (isHelloMessage(doc)) {
- changeState(State.ESTABLISHED);
- S session = getSession(sessionListener, channel, doc);
- negotiationSuccessful(session);
- } else {
- final IllegalStateException cause = new IllegalStateException(
- "Received message was not hello as expected, but was " + XmlUtil.toString(doc));
- negotiationFailed(cause);
+ replaceHelloMessageHandlers();
+
+ if (shouldUseChunkFraming(doc)) {
+ insertChunkFramingToPipeline();
}
+
+ changeState(State.ESTABLISHED);
+ return getSession(sessionListener, channel, netconfMessage);
+ }
+
+ /**
+ * Insert chunk framing handlers into the pipeline
+ */
+ protected void insertChunkFramingToPipeline() {
+ replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_FRAME_ENCODER,
+ FramingMechanismHandlerFactory.createHandler(FramingMechanism.CHUNK));
+ replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_AGGREGATOR,
+ new NetconfChunkAggregator());
}
- protected abstract S getSession(SessionListener sessionListener, Channel channel, Document doc);
+ protected boolean shouldUseChunkFraming(Document doc) {
+ return containsBase11Capability(doc)
+ && containsBase11Capability(sessionPreferences.getHelloMessage().getDocument());
+ }
- private boolean isHelloMessage(Document doc) {
- try {
- XmlElement.fromDomElementWithExpected(doc.getDocumentElement(), "hello",
- XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
+ /**
+ * Remove special handlers for hello message. Insert regular netconf xml message (en|de)coders.
+ */
+ protected void replaceHelloMessageHandlers() {
+ replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_DECODER, new NetconfXMLToMessageDecoder());
+ replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_ENCODER, new NetconfMessageToXMLEncoder());
+ }
- } catch (IllegalArgumentException | IllegalStateException e) {
- return false;
- }
- return true;
+ private static ChannelHandler replaceChannelHandler(Channel channel, String handlerKey, ChannelHandler decoder) {
+ return channel.pipeline().replace(handlerKey, handlerKey, decoder);
}
- private void changeState(final State newState) {
+ protected abstract S getSession(L sessionListener, Channel channel, NetconfHelloMessage message);
+
+ protected synchronized void changeState(final State newState) {
logger.debug("Changing state from : {} to : {}", state, newState);
Preconditions.checkState(isStateChangePermitted(state, newState), "Cannot change state from %s to %s", state,
newState);
this.state = newState;
}
+ private boolean containsBase11Capability(final Document doc) {
+ final NodeList nList = doc.getElementsByTagName("capability");
+ for (int i = 0; i < nList.getLength(); i++) {
+ if (nList.item(i).getTextContent().contains("base:1.1")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static boolean isStateChangePermitted(State state, State newState) {
- if (state == State.IDLE && newState == State.OPEN_WAIT)
+ if (state == State.IDLE && newState == State.OPEN_WAIT) {
return true;
- if (state == State.OPEN_WAIT && newState == State.ESTABLISHED)
+ }
+ if (state == State.OPEN_WAIT && newState == State.ESTABLISHED) {
return true;
- if (state == State.OPEN_WAIT && newState == State.FAILED)
+ }
+ if (state == State.OPEN_WAIT && newState == State.FAILED) {
return true;
-
+ }
+ logger.debug("Transition from {} to {} is not allowed", state, newState);
return false;
}
+
+ /**
+ * Handler to catch exceptions in pipeline during negotiation
+ */
+ private final class ExceptionHandlingInboundChannelHandler extends ChannelInboundHandlerAdapter {
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+ logger.warn("An exception occurred during negotiation on channel {}", channel.localAddress(), cause);
+ cancelTimeout();
+ negotiationFailed(cause);
+ changeState(State.FAILED);
+ }
+ }
}