From: Maros Marsalek Date: Wed, 19 Mar 2014 14:58:03 +0000 (+0100) Subject: BUG-472 Initial EXI encoder/decoder implementation in Netconf X-Git-Tag: autorelease-tag-v20140601202136_82eb3f9~217 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=59a26f11e6f07d6b36aa820562ad00e367dcaa3b BUG-472 Initial EXI encoder/decoder implementation in Netconf according to http://tools.ietf.org/html/draft-varga-netconf-exi-capability-02 Change-Id: I861c126a8df1b26f477aa354ed1f8dba59d651cd Signed-off-by: Martin Bobak Signed-off-by: Maros Marsalek --- diff --git a/opendaylight/md-sal/pom.xml b/opendaylight/md-sal/pom.xml index 631f1118c5..43ea9f1621 100644 --- a/opendaylight/md-sal/pom.xml +++ b/opendaylight/md-sal/pom.xml @@ -52,7 +52,7 @@ sal-rest-connector sal-netconf-connector - + inventory-manager statistics-manager topology-manager @@ -67,7 +67,7 @@ sal-remoterpc-connector/implementation - + @@ -167,14 +167,13 @@ org.opendaylight.yangtools binding-generator-impl - ${yangtools.version} + ${yangtools.version} org.opendaylight.yangtools yang-parser-impl - ${yangtools.version} + ${yangtools.version} - org.mockito diff --git a/opendaylight/md-sal/sal-binding-it/pom.xml b/opendaylight/md-sal/sal-binding-it/pom.xml index 520935ca90..654ad4a1cc 100644 --- a/opendaylight/md-sal/sal-binding-it/pom.xml +++ b/opendaylight/md-sal/sal-binding-it/pom.xml @@ -245,5 +245,13 @@ antlr4-runtime-osgi-nohead 4.0 + + org.opendaylight.controller.thirdparty + nagasena + + + org.opendaylight.controller.thirdparty + nagasena-rta + diff --git a/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTest.java b/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTest.java index 019fc0eb73..3d78f94861 100644 --- a/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTest.java +++ b/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTest.java @@ -69,7 +69,10 @@ public abstract class AbstractTest { // + "/src/test/resources/logback.xml"), mavenBundle("org.slf4j", "log4j-over-slf4j").versionAsInProject(), // mavenBundle("ch.qos.logback", "logback-core").versionAsInProject(), // - mavenBundle("ch.qos.logback", "logback-classic").versionAsInProject(), // + mavenBundle("ch.qos.logback", "logback-classic").versionAsInProject(), + mavenBundle("org.opendaylight.controller.thirdparty", "nagasena").versionAsInProject(), + mavenBundle("org.opendaylight.controller.thirdparty", "nagasena-rta").versionAsInProject(), + // systemProperty("osgi.bundles.defaultStartLevel").value("4"), systemPackages("sun.nio.ch"), diff --git a/opendaylight/md-sal/samples/toaster-it/pom.xml b/opendaylight/md-sal/samples/toaster-it/pom.xml index d61393c225..8ca098c3d9 100644 --- a/opendaylight/md-sal/samples/toaster-it/pom.xml +++ b/opendaylight/md-sal/samples/toaster-it/pom.xml @@ -43,6 +43,7 @@ sample-toaster-consumer 1.1-SNAPSHOT + org.opendaylight.controller.samples sample-toaster-provider @@ -81,5 +82,13 @@ 3.8.1.v20120830-144521 test + + org.opendaylight.controller.thirdparty + nagasena + + + org.opendaylight.controller.thirdparty + nagasena-rta + diff --git a/opendaylight/md-sal/samples/toaster-it/src/test/java/org/opendaylight/controller/sample/toaster/it/ToasterTest.java b/opendaylight/md-sal/samples/toaster-it/src/test/java/org/opendaylight/controller/sample/toaster/it/ToasterTest.java index 000783bd07..add523157f 100644 --- a/opendaylight/md-sal/samples/toaster-it/src/test/java/org/opendaylight/controller/sample/toaster/it/ToasterTest.java +++ b/opendaylight/md-sal/samples/toaster-it/src/test/java/org/opendaylight/controller/sample/toaster/it/ToasterTest.java @@ -7,20 +7,9 @@ */ package org.opendaylight.controller.sample.toaster.it; -import static org.junit.Assert.assertEquals; -import static org.opendaylight.controller.test.sal.binding.it.TestHelper.*; -import static org.ops4j.pax.exam.CoreOptions.*; - -import javax.inject.Inject; -import javax.management.JMX; -import javax.management.MBeanServer; -import javax.management.ObjectName; - import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; -import org.opendaylight.controller.config.yang.config.toaster_consumer.impl.ToasterConsumerRuntimeMXBean; -import org.opendaylight.controller.config.yang.config.toaster_provider.impl.ToasterProviderRuntimeMXBean; import org.opendaylight.controller.sample.toaster.provider.api.ToastConsumer; import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.HashBrown; import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.WhiteBread; @@ -31,8 +20,23 @@ import org.ops4j.pax.exam.options.DefaultCompositeOption; import org.ops4j.pax.exam.util.Filter; import org.ops4j.pax.exam.util.PathUtils; +import javax.inject.Inject; +import javax.management.MBeanServer; +import javax.management.ObjectName; import java.lang.management.ManagementFactory; +import static org.junit.Assert.assertEquals; +import static org.opendaylight.controller.test.sal.binding.it.TestHelper.baseModelBundles; +import static org.opendaylight.controller.test.sal.binding.it.TestHelper.bindingAwareSalBundles; +import static org.opendaylight.controller.test.sal.binding.it.TestHelper.configMinumumBundles; +import static org.opendaylight.controller.test.sal.binding.it.TestHelper.flowCapableModelBundles; +import static org.opendaylight.controller.test.sal.binding.it.TestHelper.junitAndMockitoBundles; +import static org.opendaylight.controller.test.sal.binding.it.TestHelper.mdSalCoreBundles; +import static org.ops4j.pax.exam.CoreOptions.mavenBundle; +import static org.ops4j.pax.exam.CoreOptions.options; +import static org.ops4j.pax.exam.CoreOptions.systemPackages; +import static org.ops4j.pax.exam.CoreOptions.systemProperty; + @RunWith(PaxExam.class) public class ToasterTest { @@ -72,7 +76,9 @@ public class ToasterTest { return new DefaultCompositeOption( mavenBundle("org.opendaylight.controller.samples", "sample-toaster-provider").versionAsInProject(), mavenBundle("org.opendaylight.controller.samples", "sample-toaster-consumer").versionAsInProject(), - mavenBundle("org.opendaylight.controller.samples", "sample-toaster").versionAsInProject() + mavenBundle("org.opendaylight.controller.samples", "sample-toaster").versionAsInProject(), + mavenBundle("org.opendaylight.controller.thirdparty", "nagasena").versionAsInProject(), + mavenBundle("org.opendaylight.controller.thirdparty", "nagasena-rta").versionAsInProject() ); } diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceImpl.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceImpl.java index 95659ddf91..c6248df41b 100644 --- a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceImpl.java +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceImpl.java @@ -8,10 +8,11 @@ package org.opendaylight.controller.netconf.confignetconfconnector.osgi; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; -import com.google.common.collect.Sets; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + import org.opendaylight.controller.config.api.LookupRegistry; import org.opendaylight.controller.config.util.ConfigRegistryJMXClient; import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry; @@ -22,10 +23,10 @@ import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; import org.opendaylight.yangtools.yang.model.api.Module; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Sets; /** * Manages life cycle of {@link YangStoreSnapshot}. @@ -141,6 +142,11 @@ public class NetconfOperationServiceImpl implements NetconfOperationService { public Optional> getLocation() { return Optional.absent(); } + + @Override + public String toString() { + return capability; + } } private static class YangStoreCapability extends BasicCapability { diff --git a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java index 9770cc5bee..cd38d3babe 100644 --- a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java +++ b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java @@ -74,7 +74,7 @@ import org.opendaylight.controller.config.yang.test.impl.NetconfTestImplModuleMX import org.opendaylight.controller.config.yang.test.impl.Peers; import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; -import org.opendaylight.controller.netconf.api.NetconfOperationRouter; +import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationRouter; import org.opendaylight.controller.netconf.confignetconfconnector.operations.Commit; import org.opendaylight.controller.netconf.confignetconfconnector.operations.DiscardChanges; import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.EditConfig; diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/AbstractNetconfSession.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/AbstractNetconfSession.java deleted file mode 100644 index f85d9b9f30..0000000000 --- a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/AbstractNetconfSession.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.controller.netconf.api; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; - -import java.io.IOException; - -import org.opendaylight.protocol.framework.AbstractProtocolSession; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public abstract class AbstractNetconfSession> extends AbstractProtocolSession implements NetconfSession { - private static final Logger logger = LoggerFactory.getLogger(AbstractNetconfSession.class); - private final L sessionListener; - private final long sessionId; - private boolean up = false; - - private final Channel channel; - - protected AbstractNetconfSession(L sessionListener, Channel channel, long sessionId) { - this.sessionListener = sessionListener; - this.channel = channel; - this.sessionId = sessionId; - logger.debug("Session {} created", sessionId); - } - - protected abstract S thisInstance(); - - @Override - public void close() { - channel.close(); - up = false; - sessionListener.onSessionTerminated(thisInstance(), new NetconfTerminationReason("Session closed")); - } - - @Override - protected void handleMessage(NetconfMessage netconfMessage) { - logger.debug("handling incoming message"); - sessionListener.onMessage(thisInstance(), netconfMessage); - } - - @Override - public ChannelFuture sendMessage(NetconfMessage netconfMessage) { - return channel.writeAndFlush(netconfMessage); - } - - @Override - protected void endOfInput() { - logger.debug("Session {} end of input detected while session was in state {}", toString(), isUp() ? "up" - : "initialized"); - if (isUp()) { - this.sessionListener.onSessionDown(thisInstance(), new IOException("End of input detected. Close the session.")); - } - } - - @Override - protected void sessionUp() { - logger.debug("Session {} up", toString()); - sessionListener.onSessionUp(thisInstance()); - this.up = true; - } - - @Override - public String toString() { - final StringBuffer sb = new StringBuffer("ServerNetconfSession{"); - sb.append("sessionId=").append(sessionId); - sb.append('}'); - return sb.toString(); - } - - public final boolean isUp() { - return up; - } - - public final long getSessionId() { - return sessionId; - } -} - diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfClientSessionPreferences.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfClientSessionPreferences.java new file mode 100644 index 0000000000..58242c725f --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfClientSessionPreferences.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.api; + +/** + * The only input for the start of a NETCONF session is hello-message. + */ +public final class NetconfClientSessionPreferences extends NetconfSessionPreferences { + + private final NetconfMessage startExiMessage; + + public NetconfClientSessionPreferences(final NetconfMessage helloMessage, + final NetconfMessage startExiMessage) { + super(helloMessage); + this.startExiMessage = startExiMessage; + } + + /** + * @return the startExiMessage + */ + public NetconfMessage getStartExiMessage() { + return startExiMessage; + } +} diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfServerSessionPreferences.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfServerSessionPreferences.java index d56213cf16..d63a43e7eb 100644 --- a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfServerSessionPreferences.java +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfServerSessionPreferences.java @@ -15,7 +15,8 @@ public final class NetconfServerSessionPreferences extends NetconfSessionPrefere private final long sessionId; - public NetconfServerSessionPreferences(final NetconfMessage helloMessage, long sessionId) { + public NetconfServerSessionPreferences(final NetconfMessage helloMessage, + long sessionId) { super(helloMessage); this.sessionId = sessionId; } diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSession.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSession.java index e52e71ceea..0bd54979f8 100644 --- a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSession.java +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSession.java @@ -12,5 +12,7 @@ import io.netty.channel.ChannelFuture; import org.opendaylight.protocol.framework.ProtocolSession; public interface NetconfSession extends ProtocolSession { + ChannelFuture sendMessage(NetconfMessage message); + } diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSessionPreferences.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSessionPreferences.java index be3040802c..0f3717abec 100644 --- a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSessionPreferences.java +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSessionPreferences.java @@ -9,6 +9,7 @@ package org.opendaylight.controller.netconf.api; public class NetconfSessionPreferences { + private final NetconfMessage helloMessage; public NetconfSessionPreferences(final NetconfMessage helloMessage) { @@ -21,4 +22,5 @@ public class NetconfSessionPreferences { public NetconfMessage getHelloMessage() { return this.helloMessage; } + } diff --git a/opendaylight/netconf/netconf-client/pom.xml b/opendaylight/netconf/netconf-client/pom.xml index 25ed0e7ab1..9c11cb1af7 100644 --- a/opendaylight/netconf/netconf-client/pom.xml +++ b/opendaylight/netconf/netconf-client/pom.xml @@ -60,6 +60,7 @@ org.opendaylight.controller.netconf.util, org.opendaylight.controller.netconf.util.*, org.opendaylight.protocol.framework, + org.openexi.*, org.slf4j, org.w3c.dom, org.xml.sax, diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientDispatcher.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientDispatcher.java index 43664b3233..20da6aa869 100644 --- a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientDispatcher.java +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientDispatcher.java @@ -8,15 +8,12 @@ package org.opendaylight.controller.netconf.client; +import com.google.common.base.Optional; import io.netty.channel.EventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.util.HashedWheelTimer; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Promise; - -import java.io.Closeable; -import java.net.InetSocketAddress; - import org.opendaylight.controller.netconf.util.AbstractChannelInitializer; import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader; import org.opendaylight.protocol.framework.AbstractDispatcher; @@ -26,7 +23,8 @@ import org.opendaylight.protocol.framework.SessionListenerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Optional; +import java.io.Closeable; +import java.net.InetSocketAddress; public class NetconfClientDispatcher extends AbstractDispatcher implements Closeable { diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSession.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSession.java index 2d07dd5833..ad50fedf6b 100644 --- a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSession.java +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSession.java @@ -9,13 +9,17 @@ package org.opendaylight.controller.netconf.client; import io.netty.channel.Channel; - -import java.util.Collection; - -import org.opendaylight.controller.netconf.api.AbstractNetconfSession; +import org.opendaylight.controller.netconf.util.AbstractNetconfSession; +import org.opendaylight.controller.netconf.util.handler.NetconfEXICodec; +import org.opendaylight.controller.netconf.util.handler.NetconfEXIToMessageDecoder; +import org.opendaylight.controller.netconf.util.handler.NetconfMessageToEXIEncoder; +import org.opendaylight.controller.netconf.util.handler.NetconfMessageToXMLEncoder; +import org.opendaylight.controller.netconf.util.handler.NetconfXMLToMessageDecoder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Collection; + public final class NetconfClientSession extends AbstractNetconfSession { private static final Logger logger = LoggerFactory.getLogger(NetconfClientSession.class); @@ -23,7 +27,7 @@ public final class NetconfClientSession extends AbstractNetconfSession capabilities) { - super(sessionListener,channel,sessionId); + super(sessionListener, channel, sessionId); this.capabilities = capabilities; logger.debug("Client Session {} created", toString()); } @@ -32,8 +36,23 @@ public final class NetconfClientSession extends AbstractNetconfSession { + AbstractNetconfSessionNegotiator +{ + private static final Logger logger = LoggerFactory.getLogger(NetconfClientSessionNegotiator.class); + + private static final XPathExpression sessionIdXPath = XMLNetconfUtil + .compileXPath("/netconf:hello/netconf:session-id"); + + private static final String EXI_1_0_CAPABILITY_MARKER = "exi:1.0"; - protected NetconfClientSessionNegotiator(NetconfSessionPreferences sessionPreferences, - Promise promise, Channel channel, Timer timer, NetconfClientSessionListener sessionListener, - long connectionTimeoutMillis) { + protected NetconfClientSessionNegotiator(NetconfClientSessionPreferences sessionPreferences, + Promise promise, + Channel channel, + Timer timer, + NetconfClientSessionListener sessionListener, + long connectionTimeoutMillis) { super(sessionPreferences, promise, channel, timer, sessionListener, connectionTimeoutMillis); } - private static Collection getCapabilities(Document doc) { - XmlElement responseElement = XmlElement.fromDomDocument(doc); - XmlElement capabilitiesElement = responseElement - .getOnlyChildElementWithSameNamespace(XmlNetconfConstants.CAPABILITIES); - List caps = capabilitiesElement.getChildElements(XmlNetconfConstants.CAPABILITY); - return Collections2.transform(caps, new Function() { + @Override + protected void handleMessage(NetconfHelloMessage netconfMessage) { + NetconfClientSession session = super.getSessionForHelloMessage(netconfMessage); + + if (shouldUseExi(netconfMessage.getDocument())){ + logger.info("Netconf session: {} should use exi.", session); + tryToStartExi(session); + } else { + logger.info("Netconf session {} isn't capable using exi.", session); + negotiationSuccessful(session); + } + } + + private boolean shouldUseExi(Document doc) { + return containsExi10Capability(doc) + && containsExi10Capability(sessionPreferences.getHelloMessage().getDocument()); + } + + private boolean containsExi10Capability(final Document doc) { + final NodeList nList = doc.getElementsByTagName(XmlNetconfConstants.CAPABILITY); + for (int i = 0; i < nList.getLength(); i++) { + if (nList.item(i).getTextContent().contains(EXI_1_0_CAPABILITY_MARKER)) { + return true; + } + } + return false; + } - @Nullable + private void tryToStartExi(final NetconfClientSession session) { + final NetconfMessage startExi = sessionPreferences.getStartExiMessage(); + session.sendMessage(startExi).addListener(new ChannelFutureListener() { @Override - public String apply(@Nullable XmlElement input) { - // Trim possible leading/tailing whitespace - return input.getTextContent().trim(); + public void operationComplete(final ChannelFuture f) { + if (!f.isSuccess()) { + logger.warn("Failed to send start-exi message {} on session {}", startExi, session, f.cause()); + } else { + logger.trace("Start-exi message {} sent to socket on session {}", startExi, session); + NetconfClientSessionNegotiator.this.channel.pipeline().addAfter( + AbstractChannelInitializer.NETCONF_MESSAGE_DECODER, ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER, + new ExiConfirmationInboundHandler(session)); + } } }); } - private static final XPathExpression sessionIdXPath = XMLNetconfUtil - .compileXPath("/netconf:hello/netconf:session-id"); - private long extractSessionId(Document doc) { final Node sessionIdNode = (Node) XmlUtil.evaluateXPath(sessionIdXPath, doc, XPathConstants.NODE); String textContent = sessionIdNode.getTextContent(); @@ -73,6 +111,52 @@ public class NetconfClientSessionNegotiator extends @Override protected NetconfClientSession getSession(NetconfClientSessionListener sessionListener, Channel channel, NetconfHelloMessage message) { return new NetconfClientSession(sessionListener, channel, extractSessionId(message.getDocument()), - getCapabilities(message.getDocument())); + NetconfMessageUtil.extractCapabilitiesFromHello(message.getDocument())); + } + + /** + * Handler to process response for start-exi message + */ + private final class ExiConfirmationInboundHandler extends ChannelInboundHandlerAdapter { + private static final String EXI_CONFIRMED_HANDLER = "exiConfirmedHandler"; + + private final NetconfClientSession session; + + ExiConfirmationInboundHandler(NetconfClientSession session) { + this.session = session; + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + ctx.pipeline().remove(ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER); + + NetconfMessage netconfMessage = (NetconfMessage) msg; + + // Ok response to start-exi, try to add exi handlers + if (NetconfMessageUtil.isOKMessage(netconfMessage)) { + logger.trace("Positive response on start-exi call received on session {}", session); + try { + session.startExiCommunication(sessionPreferences.getStartExiMessage()); + } catch (RuntimeException e) { + // Unable to add exi, continue without exi + logger.warn("Unable to start exi communication, Communication will continue without exi on session {}", session, e); + } + + // Error response + } else if(NetconfMessageUtil.isErrorMessage(netconfMessage)) { + logger.warn( + "Error response to start-exi message {}, Communication will continue without exi on session {}", + XmlUtil.toString(netconfMessage.getDocument()), session); + + // Unexpected response to start-exi, throwing message away, continue without exi + } else { + logger.warn( + "Unexpected response to start-exi message, should be ok, was {}, " + + "Communication will continue without exi and response message will be thrown away on session {}", + XmlUtil.toString(netconfMessage.getDocument()), session); + } + + negotiationSuccessful(session); + } } } diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiatorFactory.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiatorFactory.java index cff214401c..07e088e117 100644 --- a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiatorFactory.java +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiatorFactory.java @@ -8,62 +8,79 @@ package org.opendaylight.controller.netconf.client; -import io.netty.channel.Channel; -import io.netty.util.Timer; -import io.netty.util.concurrent.Promise; - -import java.io.IOException; -import java.io.InputStream; - +import org.opendaylight.controller.netconf.api.NetconfClientSessionPreferences; import org.opendaylight.controller.netconf.api.NetconfMessage; -import org.opendaylight.controller.netconf.api.NetconfSessionPreferences; import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage; import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader; -import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.controller.netconf.util.messages.NetconfStartExiMessage; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; import org.opendaylight.protocol.framework.SessionListenerFactory; import org.opendaylight.protocol.framework.SessionNegotiator; import org.opendaylight.protocol.framework.SessionNegotiatorFactory; -import org.xml.sax.SAXException; +import org.openexi.proc.common.AlignmentType; +import org.openexi.proc.common.EXIOptions; +import org.openexi.proc.common.EXIOptionsException; import com.google.common.base.Optional; import com.google.common.base.Preconditions; +import com.google.common.collect.Sets; + +import io.netty.channel.Channel; +import io.netty.util.Timer; +import io.netty.util.concurrent.Promise; public class NetconfClientSessionNegotiatorFactory implements SessionNegotiatorFactory { + public static final java.util.Set CLIENT_CAPABILITIES = Sets.newHashSet( + XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, + XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_1, + XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_EXI_1_0); + + private static final String START_EXI_MESSAGE_ID = "default-start-exi"; + private final Optional additionalHeader; private final long connectionTimeoutMillis; private final Timer timer; + private final EXIOptions options; + + public NetconfClientSessionNegotiatorFactory(Timer timer, + Optional additionalHeader, + long connectionTimeoutMillis) { + this(timer, additionalHeader, connectionTimeoutMillis, DEFAULT_OPTIONS); + } - public NetconfClientSessionNegotiatorFactory(Timer timer, Optional additionalHeader, long connectionTimeoutMillis) { + public NetconfClientSessionNegotiatorFactory(Timer timer, + Optional additionalHeader, + long connectionTimeoutMillis, EXIOptions exiOptions) { this.timer = Preconditions.checkNotNull(timer); this.additionalHeader = additionalHeader; this.connectionTimeoutMillis = connectionTimeoutMillis; - } - - private static NetconfMessage loadHelloMessageTemplate() { - final String helloMessagePath = "/client_hello.xml"; - try (InputStream is = NetconfClientSessionNegotiatorFactory.class.getResourceAsStream(helloMessagePath)) { - Preconditions.checkState(is != null, "Input stream from %s was null", helloMessagePath); - return new NetconfMessage(XmlUtil.readXmlToDocument(is)); - } catch (SAXException | IOException e) { - throw new RuntimeException("Unable to load hello message", e); - } + this.options = exiOptions; } @Override - public SessionNegotiator getSessionNegotiator(SessionListenerFactory sessionListenerFactory, Channel channel, + public SessionNegotiator getSessionNegotiator(SessionListenerFactory sessionListenerFactory, + Channel channel, Promise promise) { - // Hello message needs to be recreated every time - NetconfMessage helloMessage = loadHelloMessageTemplate(); - if(this.additionalHeader.isPresent()) { - helloMessage = new NetconfHelloMessage(helloMessage.getDocument(), additionalHeader.get()); - } else { - helloMessage = new NetconfHelloMessage(helloMessage.getDocument()); - } + NetconfMessage startExiMessage = NetconfStartExiMessage.create(options, START_EXI_MESSAGE_ID); + NetconfHelloMessage helloMessage = NetconfHelloMessage.createClientHello(CLIENT_CAPABILITIES, additionalHeader); - NetconfSessionPreferences proposal = new NetconfSessionPreferences(helloMessage); + NetconfClientSessionPreferences proposal = new NetconfClientSessionPreferences(helloMessage,startExiMessage); return new NetconfClientSessionNegotiator(proposal, promise, channel, timer, - sessionListenerFactory.getSessionListener(), connectionTimeoutMillis); + sessionListenerFactory.getSessionListener(),connectionTimeoutMillis); + } + + private static final EXIOptions DEFAULT_OPTIONS = new EXIOptions(); + static { + try { + DEFAULT_OPTIONS.setPreserveDTD(true); + DEFAULT_OPTIONS.setPreserveNS(true); + DEFAULT_OPTIONS.setPreserveLexicalValues(true); + DEFAULT_OPTIONS.setAlignmentType(AlignmentType.preCompress); + } catch (EXIOptionsException e) { + // Should not happen since DEFAULT_OPTIONS are still the same + throw new IllegalStateException("Unable to create EXI DEFAULT_OPTIONS"); + } } } diff --git a/opendaylight/netconf/netconf-client/src/main/resources/client_hello.xml b/opendaylight/netconf/netconf-client/src/main/resources/client_hello.xml deleted file mode 100644 index 46f6f76412..0000000000 --- a/opendaylight/netconf/netconf-client/src/main/resources/client_hello.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - urn:ietf:params:netconf:base:1.0 - urn:ietf:params:netconf:base:1.1 - - diff --git a/opendaylight/netconf/netconf-impl/pom.xml b/opendaylight/netconf/netconf-impl/pom.xml index 4e78b2e4d6..ffc15d878a 100644 --- a/opendaylight/netconf/netconf-impl/pom.xml +++ b/opendaylight/netconf/netconf-impl/pom.xml @@ -149,6 +149,7 @@ org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.schemas, org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.extension.rev131210, org.opendaylight.yangtools.yang.binding, + org.openexi.*, diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSession.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSession.java index 9ddc64f1a1..280375d918 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSession.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSession.java @@ -8,15 +8,18 @@ package org.opendaylight.controller.netconf.impl; -import io.netty.channel.Channel; - import java.text.SimpleDateFormat; import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.opendaylight.controller.netconf.api.AbstractNetconfSession; import org.opendaylight.controller.netconf.api.monitoring.NetconfManagementSession; +import org.opendaylight.controller.netconf.util.AbstractNetconfSession; +import org.opendaylight.controller.netconf.util.handler.NetconfEXICodec; +import org.opendaylight.controller.netconf.util.handler.NetconfEXIToMessageDecoder; +import org.opendaylight.controller.netconf.util.handler.NetconfMessageToEXIEncoder; +import org.opendaylight.controller.netconf.util.handler.NetconfMessageToXMLEncoder; +import org.opendaylight.controller.netconf.util.handler.NetconfXMLToMessageDecoder; import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.DomainName; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Host; @@ -34,6 +37,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; +import io.netty.channel.Channel; public final class NetconfServerSession extends AbstractNetconfSession implements NetconfManagementSession { @@ -124,4 +128,16 @@ public final class NetconfServerSession extends AbstractNetconfSession { - public static final String MESSAGE_ID = "message-id"; static final Logger logger = LoggerFactory.getLogger(NetconfServerSessionListener.class); private final SessionMonitoringService monitoringService; @@ -110,18 +111,17 @@ public class NetconfServerSessionListener implements NetconfSessionListener { - public static final String SERVER_HELLO_XML_LOCATION = "/server_hello.xml"; + private static final Set DEFAULT_CAPABILITIES = Sets.newHashSet( + XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, + XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_EXI_1_0); private final Timer timer; - private static final Document helloMessageTemplate = loadHelloMessageTemplate(); private final SessionIdProvider idProvider; private final NetconfOperationProvider netconfOperationProvider; private final long connectionTimeoutMillis; @@ -63,14 +54,6 @@ public class NetconfServerSessionNegotiatorFactory implements SessionNegotiatorF this.monitoringService = monitoringService; } - private static Document loadHelloMessageTemplate() { - InputStream resourceAsStream = NetconfServerSessionNegotiatorFactory.class - .getResourceAsStream(SERVER_HELLO_XML_LOCATION); - Preconditions.checkNotNull(resourceAsStream, "Unable to load server hello message blueprint from %s", - SERVER_HELLO_XML_LOCATION); - return NetconfUtil.createMessage(resourceAsStream).getDocument(); - } - /** * * @param defunctSessionListenerFactory will not be taken into account as session listener factory can @@ -99,32 +82,8 @@ public class NetconfServerSessionNegotiatorFactory implements SessionNegotiatorF sessionListenerFactory.getSessionListener(), connectionTimeoutMillis); } - private static final XPathExpression sessionIdXPath = XMLNetconfUtil - .compileXPath("/netconf:hello/netconf:session-id"); - private static final XPathExpression capabilitiesXPath = XMLNetconfUtil - .compileXPath("/netconf:hello/netconf:capabilities"); - private NetconfHelloMessage createHelloMessage(long sessionId, CapabilityProvider capabilityProvider) { - Document helloMessageTemplate = getHelloTemplateClone(); - - // change session ID - final Node sessionIdNode = (Node) XmlUtil.evaluateXPath(sessionIdXPath, helloMessageTemplate, - XPathConstants.NODE); - sessionIdNode.setTextContent(String.valueOf(sessionId)); - - // add capabilities from yang store - final Element capabilitiesElement = (Element) XmlUtil.evaluateXPath(capabilitiesXPath, helloMessageTemplate, - XPathConstants.NODE); - - for (String capability : capabilityProvider.getCapabilities()) { - final Element capabilityElement = XmlUtil.createElement(helloMessageTemplate, XmlNetconfConstants.CAPABILITY, Optional.absent()); - capabilityElement.setTextContent(capability); - capabilitiesElement.appendChild(capabilityElement); - } - return new NetconfHelloMessage(helloMessageTemplate); + return NetconfHelloMessage.createServerHello(Sets.union(capabilityProvider.getCapabilities(), DEFAULT_CAPABILITIES), sessionId); } - private synchronized Document getHelloTemplateClone() { - return (Document) helloMessageTemplate.cloneNode(true); - } } diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCommit.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCommit.java index ee0c6ce3ef..1a9f5d7393 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCommit.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCommit.java @@ -12,7 +12,7 @@ import java.io.InputStream; import java.util.Map; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; -import org.opendaylight.controller.netconf.api.NetconfOperationRouter; +import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationRouter; import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer; import org.opendaylight.controller.netconf.impl.mapping.CapabilityProvider; import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; diff --git a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/DefaultNetconfOperation.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultNetconfOperation.java similarity index 63% rename from opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/DefaultNetconfOperation.java rename to opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultNetconfOperation.java index 314eb3834f..13d4ab290b 100644 --- a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/DefaultNetconfOperation.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultNetconfOperation.java @@ -1,14 +1,14 @@ -/* - * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.controller.netconf.mapping.api; - -import org.opendaylight.controller.netconf.api.NetconfSession; - -public interface DefaultNetconfOperation { - void setNetconfSession(NetconfSession s); -} +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.netconf.impl.mapping.operations; + +import org.opendaylight.controller.netconf.impl.NetconfServerSession; + +public interface DefaultNetconfOperation { + void setNetconfSession(NetconfServerSession s); +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultStartExi.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultStartExi.java index a2befbadd2..b9b30a5eaa 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultStartExi.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultStartExi.java @@ -1,77 +1,71 @@ -/* - * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.controller.netconf.impl.mapping.operations; - -import org.opendaylight.controller.netconf.api.NetconfDocumentedException; -import org.opendaylight.controller.netconf.api.NetconfSession; -import org.opendaylight.controller.netconf.mapping.api.DefaultNetconfOperation; -import org.opendaylight.controller.netconf.util.mapping.AbstractSingletonNetconfOperation; -import org.opendaylight.controller.netconf.util.xml.XmlElement; -import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; -import org.opendaylight.controller.netconf.util.xml.XmlUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import com.google.common.base.Optional; - -public class DefaultStartExi extends AbstractSingletonNetconfOperation implements DefaultNetconfOperation { - - public static final String START_EXI = "start-exi"; - - private NetconfSession netconfSession; - - private static final Logger logger = LoggerFactory.getLogger(DefaultStartExi.class); - - public DefaultStartExi(String netconfSessionIdForReporting) { - super(netconfSessionIdForReporting); - } - - @Override - protected String getOperationName() { - return START_EXI; - } - - @Override - protected Element handleWithNoSubsequentOperations(Document document, XmlElement operationElement) throws NetconfDocumentedException { - - Element getSchemaResult = XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.of(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0)); - - throw new UnsupportedOperationException("Not implemented"); - - /* - try { - ExiParameters exiParams = new ExiParameters(); - exiParams.setParametersFromXmlElement(operationElement); - - netconfSession.addExiDecoder(ExiDecoderHandler.HANDLER_NAME, new ExiDecoderHandler(exiParams)); - netconfSession.addExiEncoderAfterMessageSent(ExiEncoderHandler.HANDLER_NAME,new ExiEncoderHandler(exiParams)); - - } catch (EXIException e) { - getSchemaResult = document - .createElement(XmlNetconfConstants.RPC_ERROR); - } - - logger.trace("{} operation successful", START_EXI); - logger.debug("received start-exi message {} ", XmlUtil.toString(document)); - return getSchemaResult; - */ - } - - @Override - public void setNetconfSession(NetconfSession s) { - netconfSession = s; - } - - public NetconfSession getNetconfSession() { - return netconfSession; - } - - -} +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.netconf.impl.mapping.operations; + +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.impl.NetconfServerSession; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution; +import org.opendaylight.controller.netconf.util.mapping.AbstractSingletonNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +public class DefaultStartExi extends AbstractSingletonNetconfOperation implements DefaultNetconfOperation { + public static final String START_EXI = "start-exi"; + + private static final Logger logger = LoggerFactory.getLogger(DefaultStartExi.class); + private NetconfServerSession netconfSession; + + public DefaultStartExi(String netconfSessionIdForReporting) { + super(netconfSessionIdForReporting); + } + + @Override + public Document handle(Document message, + NetconfOperationChainedExecution subsequentOperation) throws NetconfDocumentedException { + logger.debug("Received start-exi message {} ", XmlUtil.toString(message)); + + try { + netconfSession.startExiCommunication(new NetconfMessage(message)); + } catch (IllegalArgumentException e) { + throw new NetconfDocumentedException("Failed to parse EXI parameters", ErrorType.protocol, + ErrorTag.operation_failed, ErrorSeverity.error); + } + + return super.handle(message, subsequentOperation); + } + + @Override + protected Element handleWithNoSubsequentOperations(Document document, XmlElement operationElement) throws NetconfDocumentedException { + Element getSchemaResult = document.createElementNS( XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, XmlNetconfConstants.OK); + logger.trace("{} operation successful", START_EXI); + return getSchemaResult; + } + + @Override + protected String getOperationName() { + return START_EXI; + } + + @Override + protected String getOperationNamespace() { + return XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_EXI_1_0; + } + + @Override + public void setNetconfSession(NetconfServerSession s) { + netconfSession = s; + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultStopExi.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultStopExi.java index 836f826912..22caeff071 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultStopExi.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultStopExi.java @@ -1,62 +1,58 @@ -/* - * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.controller.netconf.impl.mapping.operations; - -import org.opendaylight.controller.netconf.api.NetconfDocumentedException; -import org.opendaylight.controller.netconf.api.NetconfSession; -import org.opendaylight.controller.netconf.mapping.api.DefaultNetconfOperation; -import org.opendaylight.controller.netconf.util.mapping.AbstractSingletonNetconfOperation; -import org.opendaylight.controller.netconf.util.xml.XmlElement; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -public class DefaultStopExi extends AbstractSingletonNetconfOperation implements DefaultNetconfOperation { - - public static final String STOP_EXI = "stop-exi"; - private NetconfSession netconfSession; - - private static final Logger logger = LoggerFactory - .getLogger(DefaultStartExi.class); - - public DefaultStopExi(String netconfSessionIdForReporting) { - super(netconfSessionIdForReporting); - } - - @Override - protected String getOperationName() { - return STOP_EXI; - } - - @Override - protected Element handleWithNoSubsequentOperations(Document document, XmlElement operationElement) - throws NetconfDocumentedException { - throw new UnsupportedOperationException("Not implemented"); - /* - netconfSession.remove(ExiDecoderHandler.class); - netconfSession.removeAfterMessageSent(ExiEncoderHandler.HANDLER_NAME); - - Element getSchemaResult = document.createElement(XmlNetconfConstants.OK); - XmlUtil.addNamespaceAttr(getSchemaResult, - XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); - logger.trace("{} operation successful", STOP_EXI); - logger.debug("received stop-exi message {} ", XmlUtil.toString(document)); - return getSchemaResult; - */ - } - - @Override - public void setNetconfSession(NetconfSession s) { - this.netconfSession = s; - } - - public NetconfSession getNetconfSession() { - return netconfSession; - } -} +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.netconf.impl.mapping.operations; + +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.impl.NetconfServerSession; +import org.opendaylight.controller.netconf.util.mapping.AbstractSingletonNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class DefaultStopExi extends AbstractSingletonNetconfOperation implements DefaultNetconfOperation { + + public static final String STOP_EXI = "stop-exi"; + private NetconfServerSession netconfSession; + + private static final Logger logger = LoggerFactory + .getLogger(DefaultStartExi.class); + + public DefaultStopExi(String netconfSessionIdForReporting) { + super(netconfSessionIdForReporting); + } + + @Override + protected Element handleWithNoSubsequentOperations(Document document, XmlElement operationElement) throws NetconfDocumentedException { + logger.debug("Received stop-exi message {} ", XmlUtil.toString(operationElement)); + + netconfSession.stopExiCommunication(); + + Element getSchemaResult = document.createElementNS( XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, XmlNetconfConstants.OK); + logger.trace("{} operation successful", STOP_EXI); + return getSchemaResult; + } + + @Override + protected String getOperationName() { + return STOP_EXI; + } + + @Override + protected String getOperationNamespace() { + return XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_EXI_1_0; + } + + @Override + public void setNetconfSession(NetconfServerSession s) { + this.netconfSession = s; + } +} diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfOperationRouter.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationRouter.java similarity index 62% rename from opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfOperationRouter.java rename to opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationRouter.java index dcd2ffad55..16cca1fee7 100644 --- a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfOperationRouter.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationRouter.java @@ -6,13 +6,15 @@ * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.controller.netconf.api; +package org.opendaylight.controller.netconf.impl.osgi; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.impl.NetconfServerSession; import org.w3c.dom.Document; public interface NetconfOperationRouter extends AutoCloseable { - Document onNetconfMessage(Document message, NetconfSession session) + Document onNetconfMessage(Document message, NetconfServerSession session) throws NetconfDocumentedException; diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationRouterImpl.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationRouterImpl.java index 80ba8388ef..bb4c76a4b8 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationRouterImpl.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationRouterImpl.java @@ -11,16 +11,15 @@ import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; -import org.opendaylight.controller.netconf.api.NetconfOperationRouter; -import org.opendaylight.controller.netconf.api.NetconfSession; import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer; +import org.opendaylight.controller.netconf.impl.NetconfServerSession; import org.opendaylight.controller.netconf.impl.mapping.CapabilityProvider; import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultCloseSession; import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultCommit; import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultGetSchema; import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultStartExi; import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultStopExi; -import org.opendaylight.controller.netconf.mapping.api.DefaultNetconfOperation; +import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultNetconfOperation; import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution; @@ -94,7 +93,7 @@ public class NetconfOperationRouterImpl implements NetconfOperationRouter { @Override public synchronized Document onNetconfMessage(Document message, - NetconfSession session) throws NetconfDocumentedException { + NetconfServerSession session) throws NetconfDocumentedException { Preconditions.checkNotNull(allNetconfOperations, "Operation router was not initialized properly"); NetconfOperationExecution netconfOperationExecution; @@ -154,20 +153,20 @@ public class NetconfOperationRouterImpl implements NetconfOperationRouter { } private NetconfOperationExecution getNetconfOperationWithHighestPriority( - Document message, NetconfSession session) { + Document message, NetconfServerSession session) { TreeMap sortedByPriority = getSortedNetconfOperationsWithCanHandle( message, session); Preconditions.checkArgument(sortedByPriority.isEmpty() == false, - "No %s available to handleWithNoSubsequentOperations message %s", NetconfOperation.class.getName(), + "No %s available to handle message %s", NetconfOperation.class.getName(), XmlUtil.toString(message)); return NetconfOperationExecution.createExecutionChain(sortedByPriority, sortedByPriority.lastKey()); } private TreeMap getSortedNetconfOperationsWithCanHandle(Document message, - NetconfSession session) { + NetconfServerSession session) { TreeMap sortedPriority = Maps.newTreeMap(); for (NetconfOperation netconfOperation : allNetconfOperations) { diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/util/DeserializerExceptionHandler.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/util/DeserializerExceptionHandler.java index fca3e8bc1b..31c4d4f57e 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/util/DeserializerExceptionHandler.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/util/DeserializerExceptionHandler.java @@ -36,7 +36,7 @@ public final class @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - logger.warn("An exception occured during message handling", cause); + logger.warn("An exception occurred during message handling", cause); handleDeserializerException(ctx, cause); } diff --git a/opendaylight/netconf/netconf-impl/src/main/resources/server_hello.xml b/opendaylight/netconf/netconf-impl/src/main/resources/server_hello.xml deleted file mode 100644 index 6a3f911cd4..0000000000 --- a/opendaylight/netconf/netconf-impl/src/main/resources/server_hello.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - urn:ietf:params:netconf:base:1.0 - - 1 - diff --git a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java index ae4a9bf4b2..d169858d35 100644 --- a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java +++ b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java @@ -22,16 +22,16 @@ import org.junit.Test; import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver; import org.opendaylight.controller.config.spi.ModuleFactory; import org.opendaylight.controller.config.util.ConfigTransactionJMXClient; +import org.opendaylight.controller.netconf.client.test.TestingNetconfClient; +import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreException; import org.opendaylight.controller.config.yang.test.impl.DepTestImplModuleFactory; import org.opendaylight.controller.config.yang.test.impl.NetconfTestImplModuleFactory; import org.opendaylight.controller.config.yang.test.impl.NetconfTestImplModuleMXBean; import org.opendaylight.controller.config.yang.test.impl.TestImplModuleFactory; import org.opendaylight.controller.netconf.StubUserManager; import org.opendaylight.controller.netconf.api.NetconfMessage; -import org.opendaylight.controller.netconf.client.test.TestingNetconfClient; import org.opendaylight.controller.netconf.client.NetconfClientDispatcher; import org.opendaylight.controller.netconf.confignetconfconnector.osgi.NetconfOperationServiceFactoryImpl; -import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreException; import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer; import org.opendaylight.controller.netconf.impl.NetconfServerDispatcher; import org.opendaylight.controller.netconf.impl.osgi.NetconfMonitoringServiceImpl; @@ -68,9 +68,9 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import static java.util.Collections.emptyList; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -148,6 +148,7 @@ public class NetconfITTest extends AbstractNetconfConfigTest { } static Collection getBasicYangs() throws IOException { + List paths = Arrays.asList("/META-INF/yang/config.yang", "/META-INF/yang/rpc-context.yang", "/META-INF/yang/config-test.yang", "/META-INF/yang/config-test-impl.yang", "/META-INF/yang/test-types.yang", "/META-INF/yang/ietf-inet-types.yang"); @@ -281,34 +282,6 @@ public class NetconfITTest extends AbstractNetconfConfigTest { } } - /* - @Test - public void testStartExi() throws Exception { - try (TestingNetconfClient netconfClient = createSession(tcpAddress, "1")) { - - - Document rpcReply = netconfClient.sendMessage(this.startExi) - .getDocument(); - assertIsOK(rpcReply); - - ExiParameters exiParams = new ExiParameters(); - exiParams.setParametersFromXmlElement(XmlElement.fromDomDocument(this.startExi.getDocument())); - - netconfClient.getClientSession().addExiDecoder(ExiDecoderHandler.HANDLER_NAME, new ExiDecoderHandler(exiParams)); - netconfClient.getClientSession().addExiEncoder(ExiEncoderHandler.HANDLER_NAME, new ExiEncoderHandler(exiParams)); - - rpcReply = netconfClient.sendMessage(this.editConfig) - .getDocument(); - assertIsOK(rpcReply); - - rpcReply = netconfClient.sendMessage(this.stopExi) - .getDocument(); - assertIsOK(rpcReply); - - } - } - */ - @Test public void testCloseSession() throws Exception { try (TestingNetconfClient netconfClient = createSession(tcpAddress, "1")) { @@ -448,5 +421,4 @@ public class NetconfITTest extends AbstractNetconfConfigTest { }.join(); } - } diff --git a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfMonitoringITTest.java b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfMonitoringITTest.java index f581342a9c..92caea17d5 100644 --- a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfMonitoringITTest.java +++ b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfMonitoringITTest.java @@ -10,7 +10,6 @@ package org.opendaylight.controller.netconf.it; import com.google.common.base.Charsets; import com.google.common.base.Optional; import com.google.common.collect.Sets; -import static org.opendaylight.controller.netconf.util.test.XmlUnitUtil.assertContainsElementWithText; import io.netty.channel.ChannelFuture; import junit.framework.Assert; import org.junit.Before; @@ -18,20 +17,20 @@ import org.junit.Test; import org.mockito.Mock; import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver; import org.opendaylight.controller.config.spi.ModuleFactory; +import org.opendaylight.controller.netconf.client.test.TestingNetconfClient; +import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreException; import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.netconf.api.monitoring.NetconfManagementSession; -import org.opendaylight.controller.netconf.client.test.TestingNetconfClient; import org.opendaylight.controller.netconf.client.NetconfClientDispatcher; import org.opendaylight.controller.netconf.confignetconfconnector.osgi.NetconfOperationServiceFactoryImpl; -import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreException; import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer; import org.opendaylight.controller.netconf.impl.NetconfServerDispatcher; import org.opendaylight.controller.netconf.impl.osgi.NetconfMonitoringServiceImpl; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider; import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFactoryListenerImpl; import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceSnapshotImpl; import org.opendaylight.controller.netconf.impl.osgi.SessionMonitoringService; import org.opendaylight.controller.netconf.mapping.api.Capability; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; import org.opendaylight.controller.netconf.monitoring.osgi.NetconfMonitoringActivator; import org.opendaylight.controller.netconf.monitoring.osgi.NetconfMonitoringOperationService; @@ -55,6 +54,7 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.opendaylight.controller.netconf.util.test.XmlUnitUtil.assertContainsElementWithText; public class NetconfMonitoringITTest extends AbstractNetconfConfigTest { @@ -131,7 +131,7 @@ public class NetconfMonitoringITTest extends AbstractNetconfConfigTest { } - @Test(timeout = 5 * 10000) + @Test(timeout = 13 * 10000) public void testClientHelloWithAuth() throws Exception { String fileName = "netconfMessages/client_hello_with_auth.xml"; String hello = XmlFileLoader.fileToString(fileName); diff --git a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/pax/IdentityRefNetconfTest.java b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/pax/IdentityRefNetconfTest.java index 266b245b7f..96a9effcfc 100644 --- a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/pax/IdentityRefNetconfTest.java +++ b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/pax/IdentityRefNetconfTest.java @@ -100,7 +100,11 @@ public class IdentityRefNetconfTest { mavenBundle("org.slf4j", "slf4j-api").versionAsInProject(), mavenBundle("org.slf4j", "log4j-over-slf4j").versionAsInProject(), mavenBundle("ch.qos.logback", "logback-core").versionAsInProject(), - mavenBundle("ch.qos.logback", "logback-classic").versionAsInProject()); + mavenBundle("ch.qos.logback", "logback-classic").versionAsInProject(), + mavenBundle("org.opendaylight.controller.thirdparty", "nagasena").versionAsInProject(), + mavenBundle("org.opendaylight.controller.thirdparty", "nagasena-rta").versionAsInProject()); + + } private Option testingModules() { diff --git a/opendaylight/netconf/netconf-util/pom.xml b/opendaylight/netconf/netconf-util/pom.xml index e6cd3daad2..af8e4fbc39 100644 --- a/opendaylight/netconf/netconf-util/pom.xml +++ b/opendaylight/netconf/netconf-util/pom.xml @@ -58,6 +58,10 @@ org.opendaylight.controller.thirdparty nagasena + + org.opendaylight.controller.thirdparty + nagasena-rta + @@ -106,6 +110,7 @@ org.xml.sax, org.xml.sax.helpers, org.opendaylight.controller.config.api, + org.openexi.*, diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractNetconfSession.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractNetconfSession.java new file mode 100644 index 0000000000..270af3505f --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractNetconfSession.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.netconf.util; + +import java.io.IOException; + +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.NetconfTerminationReason; +import org.opendaylight.controller.netconf.util.handler.NetconfEXICodec; +import org.opendaylight.controller.netconf.util.xml.EXIParameters; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.protocol.framework.AbstractProtocolSession; +import org.openexi.proc.common.EXIOptionsException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; + +public abstract class AbstractNetconfSession> extends AbstractProtocolSession implements NetconfSession, NetconfExiSession { + private static final Logger logger = LoggerFactory.getLogger(AbstractNetconfSession.class); + private final L sessionListener; + private final long sessionId; + private boolean up = false; + + private ChannelHandler delayedEncoder; + + private final Channel channel; + + protected AbstractNetconfSession(final L sessionListener, final Channel channel, final long sessionId) { + this.sessionListener = sessionListener; + this.channel = channel; + this.sessionId = sessionId; + logger.debug("Session {} created", sessionId); + } + + protected abstract S thisInstance(); + + @Override + public void close() { + channel.close(); + up = false; + sessionListener.onSessionTerminated(thisInstance(), new NetconfTerminationReason("Session closed")); + } + + @Override + protected void handleMessage(final NetconfMessage netconfMessage) { + logger.debug("handling incoming message"); + sessionListener.onMessage(thisInstance(), netconfMessage); + } + + @Override + public ChannelFuture sendMessage(final NetconfMessage netconfMessage) { + final ChannelFuture future = channel.writeAndFlush(netconfMessage); + if (delayedEncoder !=null) { + replaceMessageEncoder(delayedEncoder); + delayedEncoder = null; + } + + return future; + } + + @Override + protected void endOfInput() { + logger.debug("Session {} end of input detected while session was in state {}", toString(), isUp() ? "up" + : "initialized"); + if (isUp()) { + this.sessionListener.onSessionDown(thisInstance(), new IOException("End of input detected. Close the session.")); + } + } + + @Override + protected void sessionUp() { + logger.debug("Session {} up", toString()); + sessionListener.onSessionUp(thisInstance()); + this.up = true; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer(getClass().getSimpleName() + "{"); + sb.append("sessionId=").append(sessionId); + sb.append('}'); + return sb.toString(); + } + + protected T removeHandler(final Class handlerType) { + return this.channel.pipeline().remove(handlerType); + } + + protected void replaceMessageDecoder(final ChannelHandler handler) { + replaceChannelHandler(AbstractChannelInitializer.NETCONF_MESSAGE_DECODER, handler); + } + + protected void replaceMessageEncoder(final ChannelHandler handler) { + replaceChannelHandler(AbstractChannelInitializer.NETCONF_MESSAGE_ENCODER, handler); + } + + protected void replaceMessageEncoderAfterNextMessage(final ChannelHandler handler) { + this.delayedEncoder = handler; + } + + protected void replaceChannelHandler(final String handlerName, final ChannelHandler handler) { + channel.pipeline().replace(handlerName, handlerName, handler); + } + + @Override + public final void startExiCommunication(final NetconfMessage startExiMessage) { + final EXIParameters exiParams; + try { + exiParams = EXIParameters.fromXmlElement(XmlElement.fromDomDocument(startExiMessage.getDocument())); + } catch (final EXIOptionsException e) { + logger.warn("Unable to parse EXI parameters from {} om session {}", XmlUtil.toString(startExiMessage.getDocument()), this, e); + throw new IllegalArgumentException(e); + } + final NetconfEXICodec exiCodec = new NetconfEXICodec(exiParams.getOptions()); + addExiHandlers(exiCodec); + logger.debug("EXI handlers added to pipeline on session {}", this); + } + + protected abstract void addExiHandlers(NetconfEXICodec exiCodec); + + public final boolean isUp() { + return up; + } + + public final long getSessionId() { + return sessionId; + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractNetconfSessionNegotiator.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractNetconfSessionNegotiator.java index 71f08339c8..724b45bc08 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractNetconfSessionNegotiator.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractNetconfSessionNegotiator.java @@ -8,9 +8,12 @@ package org.opendaylight.controller.netconf.util; +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; @@ -18,11 +21,6 @@ import io.netty.util.TimerTask; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.Promise; - -import java.util.concurrent.TimeUnit; - -import io.netty.channel.ChannelInboundHandlerAdapter; -import org.opendaylight.controller.netconf.api.AbstractNetconfSession; import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.netconf.api.NetconfSessionListener; import org.opendaylight.controller.netconf.api.NetconfSessionPreferences; @@ -39,16 +37,16 @@ import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.NodeList; -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; +import java.util.concurrent.TimeUnit; public abstract class AbstractNetconfSessionNegotiator

, L extends NetconfSessionListener> extends AbstractSessionNegotiator { private static final Logger logger = LoggerFactory.getLogger(AbstractNetconfSessionNegotiator.class); + public static final String NAME_OF_EXCEPTION_HANDLER = "lastExceptionHandler"; - private final P sessionPreferences; + protected final P sessionPreferences; private final L sessionListener; private Timeout timeout; @@ -56,7 +54,7 @@ extends AbstractSessionNegotiator { /** * Possible states for Finite State Machine */ - private enum State { + protected enum State { IDLE, OPEN_WAIT, FAILED, ESTABLISHED } @@ -64,6 +62,7 @@ extends AbstractSessionNegotiator { private final Timer timer; private final long connectionTimeoutMillis; + // TODO shrink constructor protected AbstractNetconfSessionNegotiator(P sessionPreferences, Promise promise, Channel channel, Timer timer, L sessionListener, long connectionTimeoutMillis) { super(promise, channel); @@ -127,7 +126,6 @@ extends AbstractSessionNegotiator { sendMessage((NetconfHelloMessage)helloMessage); changeState(State.OPEN_WAIT); } - private void cancelTimeout() { if(timeout!=null) { timeout.cancel(); @@ -136,7 +134,12 @@ extends AbstractSessionNegotiator { @Override protected void handleMessage(NetconfHelloMessage netconfMessage) { - Preconditions.checkNotNull(netconfMessage != null, "netconfMessage"); + S session = getSessionForHelloMessage(netconfMessage); + negotiationSuccessful(session); + } + + protected final S getSessionForHelloMessage(NetconfHelloMessage netconfMessage) { + Preconditions.checkNotNull(netconfMessage, "netconfMessage"); final Document doc = netconfMessage.getDocument(); @@ -147,22 +150,20 @@ extends AbstractSessionNegotiator { } changeState(State.ESTABLISHED); - S session = getSession(sessionListener, channel, netconfMessage); - - negotiationSuccessful(session); + return getSession(sessionListener, channel, netconfMessage); } /** * Insert chunk framing handlers into the pipeline */ - private void insertChunkFramingToPipeline() { + protected void insertChunkFramingToPipeline() { replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_FRAME_ENCODER, FramingMechanismHandlerFactory.createHandler(FramingMechanism.CHUNK)); replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_AGGREGATOR, new NetconfChunkAggregator()); } - private boolean shouldUseChunkFraming(Document doc) { + protected boolean shouldUseChunkFraming(Document doc) { return containsBase11Capability(doc) && containsBase11Capability(sessionPreferences.getHelloMessage().getDocument()); } @@ -170,7 +171,7 @@ extends AbstractSessionNegotiator { /** * Remove special handlers for hello message. Insert regular netconf xml message (en|de)coders. */ - private void replaceHelloMessageHandlers() { + protected void replaceHelloMessageHandlers() { replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_DECODER, new NetconfXMLToMessageDecoder()); replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_ENCODER, new NetconfMessageToXMLEncoder()); } @@ -181,7 +182,7 @@ extends AbstractSessionNegotiator { protected abstract S getSession(L sessionListener, Channel channel, NetconfHelloMessage message); - private synchronized void changeState(final State newState) { + 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); @@ -208,7 +209,6 @@ extends AbstractSessionNegotiator { if (state == State.OPEN_WAIT && newState == State.FAILED) { return true; } - logger.debug("Transition from {} to {} is not allowed", state, newState); return false; } @@ -217,7 +217,6 @@ extends AbstractSessionNegotiator { * 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); diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/NetconfExiSession.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/NetconfExiSession.java new file mode 100644 index 0000000000..2a20ba2f61 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/NetconfExiSession.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.netconf.util; + +import org.opendaylight.controller.netconf.api.NetconfMessage; + +/** + * Session capable of exi communication according to http://tools.ietf.org/html/draft-varga-netconf-exi-capability-02 + */ +public interface NetconfExiSession { + + /** + * Start exi communication with parameters included in start-exi message + */ + void startExiCommunication(NetconfMessage startExiMessage); + + /** + * Stop exi communication, initiated by stop-exi message + */ + void stopExiCommunication(); +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfEXICodec.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfEXICodec.java index c28db18901..a6e2e52ea5 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfEXICodec.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfEXICodec.java @@ -1,7 +1,7 @@ package org.opendaylight.controller.netconf.util.handler; +import com.google.common.base.Preconditions; import org.openexi.proc.HeaderOptionsOutputType; -import org.openexi.proc.common.AlignmentType; import org.openexi.proc.common.EXIOptions; import org.openexi.proc.common.EXIOptionsException; import org.openexi.proc.common.GrammarOptions; @@ -9,20 +9,16 @@ import org.openexi.proc.grammars.GrammarCache; import org.openexi.sax.EXIReader; import org.openexi.sax.Transmogrifier; -import com.google.common.base.Preconditions; - -final class NetconfEXICodec { +public final class NetconfEXICodec { /** * NETCONF is XML environment, so the use of EXI cookie is not really needed. Adding it * decreases efficiency of encoding by adding human-readable 4 bytes "EXI$" to the head * of the stream. This is really useful, so let's output it now. */ private static final boolean OUTPUT_EXI_COOKIE = true; - private final AlignmentType alignmentType; private final EXIOptions exiOptions; - public NetconfEXICodec(final AlignmentType alignmentType, final EXIOptions exiOptions) { - this.alignmentType = Preconditions.checkNotNull(alignmentType); + public NetconfEXICodec(final EXIOptions exiOptions) { this.exiOptions = Preconditions.checkNotNull(exiOptions); } @@ -53,7 +49,7 @@ final class NetconfEXICodec { Transmogrifier getTransmogrifier() throws EXIOptionsException { final Transmogrifier transmogrifier = new Transmogrifier(); - transmogrifier.setAlignmentType(alignmentType); + transmogrifier.setAlignmentType(exiOptions.getAlignmentType()); transmogrifier.setBlockSize(exiOptions.getBlockSize()); transmogrifier.setGrammarCache(getGrammarCache()); transmogrifier.setOutputCookie(OUTPUT_EXI_COOKIE); diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfEXIToMessageDecoder.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfEXIToMessageDecoder.java index e4c14abeaa..cbfbfe1c05 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfEXIToMessageDecoder.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfEXIToMessageDecoder.java @@ -7,34 +7,34 @@ */ package org.opendaylight.controller.netconf.util.handler; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufInputStream; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageDecoder; - import java.io.InputStream; import java.util.List; +import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import org.opendaylight.controller.netconf.api.NetconfMessage; import org.openexi.sax.EXIReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.xml.sax.InputSource; import com.google.common.base.Preconditions; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; + public final class NetconfEXIToMessageDecoder extends ByteToMessageDecoder { - private static final SAXTransformerFactory saxTransformerFactory = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); - // FIXME: is this needed? - // private static final SAXParserFactory saxParserFactory; - // static { - // saxParserFactory = SAXParserFactory.newInstance(); - // saxParserFactory.setNamespaceAware(true); - // } + private static final Logger LOG = LoggerFactory.getLogger(NetconfEXIToMessageDecoder.class); + +// private static final SAXTransformerFactory saxTransformerFactory = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); private final NetconfEXICodec codec; @@ -43,23 +43,37 @@ public final class NetconfEXIToMessageDecoder extends ByteToMessageDecoder { } @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final List out) throws Exception { /* * Note that we could loop here and process all the messages, but we can't do that. * The reason is operation, which has the contract of immediately stopping * the use of EXI, which means the next message needs to be decoded not by us, but rather * by the XML decoder. */ - final DOMResult result = new DOMResult(); + // If empty Byte buffer is passed to r.parse, EOFException is thrown + + if (in.readableBytes() == 0) { + LOG.debug("No more content in incoming buffer."); + return; + } + + LOG.trace("Received to decode: {}", ByteBufUtil.hexDump(in)); + final EXIReader r = codec.getReader(); - final TransformerHandler transformerHandler = saxTransformerFactory.newTransformerHandler(); - transformerHandler.setResult(result); - r.setContentHandler(transformerHandler); + final SAXTransformerFactory transformerFactory + = (SAXTransformerFactory) TransformerFactory.newInstance(); + final TransformerHandler handler = transformerFactory.newTransformerHandler(); + r.setContentHandler(handler); + + final DOMResult domResult = new DOMResult(); + handler.setResult(domResult); + try (final InputStream is = new ByteBufInputStream(in)) { r.parse(new InputSource(is)); } - out.add(new NetconfMessage((Document) result.getNode())); + + out.add(new NetconfMessage((Document) domResult.getNode())); } } diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfMessageToEXIEncoder.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfMessageToEXIEncoder.java index 70d9c2bf3a..5edec0d1bd 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfMessageToEXIEncoder.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfMessageToEXIEncoder.java @@ -7,26 +7,28 @@ */ package org.opendaylight.controller.netconf.util.handler; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufOutputStream; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToByteEncoder; - +import java.io.ByteArrayInputStream; import java.io.OutputStream; -import javax.xml.transform.Transformer; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.sax.SAXResult; -import javax.xml.transform.sax.SAXTransformerFactory; - import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; import org.openexi.sax.Transmogrifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.InputSource; import com.google.common.base.Preconditions; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + public final class NetconfMessageToEXIEncoder extends MessageToByteEncoder { - private static final SAXTransformerFactory saxTransformerFactory = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); + private static final Logger LOG = LoggerFactory.getLogger(NetconfMessageToEXIEncoder.class); + + //private static final SAXTransformerFactory saxTransformerFactory = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); private final NetconfEXICodec codec; public NetconfMessageToEXIEncoder(final NetconfEXICodec codec) { @@ -34,13 +36,17 @@ public final class NetconfMessageToEXIEncoder extends MessageToByteEncoder capabilities, + Optional additionalHeaderOptional) { + Document doc = createHelloMessageDoc(capabilities); + return additionalHeaderOptional.isPresent() ? new NetconfHelloMessage(doc, additionalHeaderOptional.get()) + : new NetconfHelloMessage(doc); + } + + private static Document createHelloMessageDoc(Iterable capabilities) { + Document doc = XmlUtil.newDocument(); + Element helloElement = doc.createElementNS(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, + HELLO_TAG); + Element capabilitiesElement = doc.createElement(XmlNetconfConstants.CAPABILITIES); + + for (String capability : Sets.newHashSet(capabilities)) { + Element capElement = doc.createElement(XmlNetconfConstants.CAPABILITY); + capElement.setTextContent(capability); + capabilitiesElement.appendChild(capElement); + } + + helloElement.appendChild(capabilitiesElement); + + doc.appendChild(helloElement); + return doc; + } + + public static NetconfHelloMessage createServerHello(Set capabilities, long sessionId) { + Document doc = createHelloMessageDoc(capabilities); + Element sessionIdElement = doc.createElement(XmlNetconfConstants.SESSION_ID); + sessionIdElement.setTextContent(Long.toString(sessionId)); + doc.getDocumentElement().appendChild(sessionIdElement); + return new NetconfHelloMessage(doc); + } } diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageUtil.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageUtil.java index 91eb86908b..e4d97cf65a 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageUtil.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageUtil.java @@ -8,11 +8,17 @@ package org.opendaylight.controller.netconf.util.messages; +import com.google.common.base.Function; +import com.google.common.collect.Collections2; import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.netconf.util.xml.XmlElement; import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; import org.w3c.dom.Document; +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.List; + public final class NetconfMessageUtil { private NetconfMessageUtil() {} @@ -26,10 +32,13 @@ public final class NetconfMessageUtil { } public static boolean isOKMessage(XmlElement xmlElement) { + if(xmlElement.getChildElements().size() != 1) { + return false; + } return xmlElement.getOnlyChildElement().getName().equals(XmlNetconfConstants.OK); } - public static boolean isErrorMEssage(NetconfMessage message) { + public static boolean isErrorMessage(NetconfMessage message) { return isErrorMessage(message.getDocument()); } @@ -38,7 +47,26 @@ public final class NetconfMessageUtil { } public static boolean isErrorMessage(XmlElement xmlElement) { + if(xmlElement.getChildElements().size() != 1) { + return false; + } return xmlElement.getOnlyChildElement().getName().equals(XmlNetconfConstants.RPC_ERROR); + } + + public static Collection extractCapabilitiesFromHello(Document doc) { + XmlElement responseElement = XmlElement.fromDomDocument(doc); + XmlElement capabilitiesElement = responseElement + .getOnlyChildElementWithSameNamespace(XmlNetconfConstants.CAPABILITIES); + List caps = capabilitiesElement.getChildElements(XmlNetconfConstants.CAPABILITY); + return Collections2.transform(caps, new Function() { + + @Nullable + @Override + public String apply(@Nullable XmlElement input) { + // Trim possible leading/tailing whitespace + return input.getTextContent().trim(); + } + }); } } diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfStartExiMessage.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfStartExiMessage.java new file mode 100644 index 0000000000..4fe6adc0a5 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfStartExiMessage.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.util.messages; + +import java.util.List; + +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.openexi.proc.common.EXIOptions; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import com.google.common.collect.Lists; + +/** + * Start-exi netconf message. + */ +public final class NetconfStartExiMessage extends NetconfMessage { + + public static final String START_EXI = "start-exi"; + public static final String ALIGNMENT_KEY = "alignment"; + public static final String FIDELITY_KEY = "fidelity"; + public static final String COMMENTS_KEY = "comments"; + public static final String DTD_KEY = "dtd"; + public static final String LEXICAL_VALUES_KEY = "lexical-values"; + public static final String PIS_KEY = "pis"; + public static final String PREFIXES_KEY = "prefixes"; + + private NetconfStartExiMessage(Document doc) { + super(doc); + } + + public static NetconfStartExiMessage create(EXIOptions exiOptions, String messageId) { + Document doc = XmlUtil.newDocument(); + Element rpcElement = doc.createElementNS(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, + XmlNetconfConstants.RPC_KEY); + rpcElement.setAttributeNS(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, + XmlNetconfConstants.MESSAGE_ID, messageId); + + // TODO draft http://tools.ietf.org/html/draft-varga-netconf-exi-capability-02#section-3.5.1 has no namespace for start-exi element in xml + Element startExiElement = doc.createElementNS(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_EXI_1_0, + START_EXI); + + addAlignemnt(exiOptions, doc, startExiElement); + addFidelity(exiOptions, doc, startExiElement); + + rpcElement.appendChild(startExiElement); + + doc.appendChild(rpcElement); + return new NetconfStartExiMessage(doc); + } + + private static void addFidelity(EXIOptions exiOptions, Document doc, Element startExiElement) { + List fidelityElements = Lists.newArrayList(); + createFidelityElement(doc, fidelityElements, exiOptions.getPreserveComments(), COMMENTS_KEY); + createFidelityElement(doc, fidelityElements, exiOptions.getPreserveDTD(), DTD_KEY); + createFidelityElement(doc, fidelityElements, exiOptions.getPreserveLexicalValues(), LEXICAL_VALUES_KEY); + createFidelityElement(doc, fidelityElements, exiOptions.getPreservePIs(), PIS_KEY); + createFidelityElement(doc, fidelityElements, exiOptions.getPreserveNS(), PREFIXES_KEY); + + if (fidelityElements.isEmpty() == false) { + Element fidelityElement = doc.createElementNS( + XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_EXI_1_0, FIDELITY_KEY); + for (Element element : fidelityElements) { + fidelityElement.appendChild(element); + } + startExiElement.appendChild(fidelityElement); + } + } + + private static void addAlignemnt(EXIOptions exiOptions, Document doc, Element startExiElement) { + Element alignmentElement = doc.createElementNS(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_EXI_1_0, + ALIGNMENT_KEY); + alignmentElement.setTextContent(exiOptions.getAlignmentType().toString()); + startExiElement.appendChild(alignmentElement); + } + + private static void createFidelityElement(Document doc, List fidelityElements, boolean fidelity, String fidelityName) { + + if (fidelity) { + fidelityElements.add(doc.createElementNS(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_EXI_1_0, + fidelityName)); + } + + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/EXIParameters.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/EXIParameters.java index 2c8a16cbe1..593b77f193 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/EXIParameters.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/EXIParameters.java @@ -7,6 +7,7 @@ */ package org.opendaylight.controller.netconf.util.xml; +import org.opendaylight.controller.netconf.api.NetconfMessage; import org.openexi.proc.common.AlignmentType; import org.openexi.proc.common.EXIOptions; import org.openexi.proc.common.EXIOptionsException; @@ -38,7 +39,11 @@ public final class EXIParameters { this.options = Preconditions.checkNotNull(options); } - public static EXIParameters forXmlElement(final XmlElement root) throws EXIOptionsException { + public static EXIParameters fromNetconfMessage(final NetconfMessage root) throws EXIOptionsException { + return fromXmlElement(XmlElement.fromDomDocument(root.getDocument())); + } + + public static EXIParameters fromXmlElement(final XmlElement root) throws EXIOptionsException { final EXIOptions options = new EXIOptions(); options.setAlignmentType(AlignmentType.bitPacked); @@ -73,24 +78,25 @@ public final class EXIParameters { } if (root.getElementsByTagName(EXI_PARAMETER_SCHEMA).getLength() > 0) { +/* + GrammarFactory grammarFactory = GrammarFactory.newInstance(); + if (operationElement + .getElementsByTagName(EXI_PARAMETER_SCHEMA_NONE) + .getLength() > 0) { + this.grammars = grammarFactory.createSchemaLessGrammars(); + } + + if (operationElement.getElementsByTagName( + EXI_PARAMETER_SCHEMA_BUILT_IN).getLength() > 0) { + this.grammars = grammarFactory.createXSDTypesOnlyGrammars(); + } - // GrammarFactory grammarFactory = GrammarFactory.newInstance(); - // if (operationElement - // .getElementsByTagName(EXI_PARAMETER_SCHEMA_NONE) - // .getLength() > 0) { - // this.grammars = grammarFactory.createSchemaLessGrammars(); - // } - // - // if (operationElement.getElementsByTagName( - // EXI_PARAMETER_SCHEMA_BUILT_IN).getLength() > 0) { - // this.grammars = grammarFactory.createXSDTypesOnlyGrammars(); - // } - // - // if (operationElement.getElementsByTagName( - // EXI_PARAMETER_SCHEMA_BASE_1_1).getLength() > 0) { - // this.grammars = grammarFactory - // .createGrammars(NETCONF_XSD_LOCATION); - // } + if (operationElement.getElementsByTagName( + EXI_PARAMETER_SCHEMA_BASE_1_1).getLength() > 0) { + this.grammars = grammarFactory + .createGrammars(NETCONF_XSD_LOCATION); + } +*/ } diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlNetconfConstants.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlNetconfConstants.java index c9f3a8a53b..d0be73843b 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlNetconfConstants.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlNetconfConstants.java @@ -9,6 +9,8 @@ package org.opendaylight.controller.netconf.util.xml; public final class XmlNetconfConstants { + + private XmlNetconfConstants() {} public static final String MOUNTPOINTS = "mountpoints"; @@ -37,8 +39,15 @@ public final class XmlNetconfConstants { public static final String PREFIX = "prefix"; + public static final String MESSAGE_ID = "message-id"; + public static final String SESSION_ID = "session-id"; + // public static final String URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0 = "urn:ietf:params:xml:ns:netconf:base:1.0"; + public static final String URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_1 = "urn:ietf:params:xml:ns:netconf:base:1.1"; + public static final String URN_IETF_PARAMS_XML_NS_NETCONF_EXI_1_0 = "urn:ietf:params:xml:ns:netconf:exi:1.0"; + + public static final String URN_IETF_PARAMS_NETCONF_CAPABILITY_EXI_1_0 = "urn:ietf:params:netconf:capability:exi:1.0"; public static final String URN_IETF_PARAMS_XML_NS_YANG_IETF_NETCONF_MONITORING = "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring"; // TODO where to store namespace of config ? public static final String URN_OPENDAYLIGHT_PARAMS_XML_NS_YANG_CONTROLLER_CONFIG = "urn:opendaylight:params:xml:ns:yang:controller:config"; diff --git a/opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/EXILibTest.java b/opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/EXILibTest.java new file mode 100644 index 0000000000..360e8126ae --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/EXILibTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.netconf.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.StringWriter; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerFactoryConfigurationError; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stream.StreamResult; + +import org.junit.Ignore; +import org.junit.Test; +import org.openexi.proc.common.AlignmentType; +import org.openexi.proc.common.GrammarOptions; +import org.openexi.proc.grammars.GrammarCache; +import org.openexi.sax.EXIReader; +import org.openexi.sax.Transmogrifier; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.InputSource; + +/** + * This test case tests nagasena library used for exi encode/decode. + * + * This library does not work correctly, since it is impossible to encode and then decode DOM xml. + * Encoding DOM using sax Transformer produces invalid xml, that cannot be decoded (Problem seems to be the namespace handling). + * + */ +@Ignore +public class EXILibTest { + + public static final AlignmentType ALIGNMENT_TYPE = AlignmentType.preCompress; + + @Test + public void testExiLibWithSaxTransformer() throws Exception { + final byte[] encode = encodeEXI(getDom2()); + final byte[] encodeWithTransformer = encodeEXITransformer(getDom2()); + + // System.err.println(Arrays.toString(encode)); + // System.err.println(Arrays.toString(encodeWithTransformer)); + + // This works fine (encoded from string) + decodeEXI(encode); + // Error, encoded from Dom with Transformer cannot be decoded, Exception is thrown + // + // either: + // org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces. + // + // or: + // java.lang.NullPointerException + // + // depends on GrammarOptions.addNS(go); option set + decodeEXI(encodeWithTransformer); + } + + private static final SAXTransformerFactory saxTransformerFactory = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); + + public static byte[] encodeEXITransformer(final Element xml) throws Exception { + final Transmogrifier transmogrifier = new Transmogrifier(); + + transmogrifier.setAlignmentType(ALIGNMENT_TYPE); + + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + + transmogrifier.setGrammarCache(getGrammarCache()); + + transmogrifier.setOutputStream(out); + + final Transformer transformer = saxTransformerFactory.newTransformer(); + transformer.transform(new DOMSource(xml), new SAXResult(transmogrifier.getSAXTransmogrifier())); + + return out.toByteArray(); + } + + public static byte[] encodeEXI(final Element xml) throws Exception { + final Transmogrifier transmogrifier = new Transmogrifier(); + + transmogrifier.setAlignmentType(ALIGNMENT_TYPE); + + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + + transmogrifier.setGrammarCache(getGrammarCache()); + + transmogrifier.setOutputStream(out); + + transmogrifier.encode(new InputSource(new ByteArrayInputStream(toString(xml, false).getBytes()))); + + out.flush(); + + return out.toByteArray(); + } + + private static GrammarCache getGrammarCache() { + short go = GrammarOptions.DEFAULT_OPTIONS; + + // This option on or off, nagasena still fails +// go = GrammarOptions.addNS(go); + + return new GrammarCache(null, go); + } + + public static Document decodeEXI(final byte[] input) throws Exception { + + final GrammarCache grammarCache; + final DOMResult domResult = new DOMResult(); + + try(ByteArrayInputStream in = new ByteArrayInputStream(input)) { + + final EXIReader reader = new EXIReader(); + + reader.setAlignmentType(ALIGNMENT_TYPE); + grammarCache = getGrammarCache(); + + reader.setGrammarCache(grammarCache); + + final SAXTransformerFactory transformerFactory + = (SAXTransformerFactory) TransformerFactory.newInstance(); + final TransformerHandler handler = transformerFactory.newTransformerHandler(); + handler.setResult(domResult); + + reader.setContentHandler(handler); + + reader.parse(new InputSource(in)); + } + + return (Document) domResult.getNode(); + } + + public static Element getDom() { + final Element dom; + + final Document d = newDocument(); + + dom = d.createElement("rpc"); + dom.setAttribute("xmlns", "a.b.c"); + dom.setAttribute("message-id", "id"); + dom.appendChild(d.createElement("inner")); + + return dom; + } + + public static Element getDom2() { + final Element dom; + + final Document d = newDocument(); + + dom = d.createElementNS("a.b.c", "rpc"); + dom.setAttribute("message-id", "id"); + dom.appendChild(d.createElement("inner")); + + return dom; + } + + private static final DocumentBuilderFactory BUILDERFACTORY; + + static { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setCoalescing(true); + factory.setIgnoringElementContentWhitespace(true); + factory.setIgnoringComments(true); + BUILDERFACTORY = factory; + } + + private static Document newDocument() { + try { + final DocumentBuilder builder = BUILDERFACTORY.newDocumentBuilder(); + return builder.newDocument(); + } catch (final ParserConfigurationException e) { + throw new RuntimeException("Failed to create document", e); + } + } + + private static String toString(final Element xml, final boolean addXmlDeclaration) { + try { + final Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, addXmlDeclaration ? "no" : "yes"); + + final StreamResult result = new StreamResult(new StringWriter()); + final DOMSource source = new DOMSource(xml); + transformer.transform(source, result); + + return result.getWriter().toString(); + } catch (IllegalArgumentException | TransformerFactoryConfigurationError | TransformerException e) { + throw new RuntimeException("Unable to serialize xml element " + xml, e); + } + } +} diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/startExi.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/startExi.xml index 7e44f74830..e7a483e405 100644 --- a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/startExi.xml +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/startExi.xml @@ -1,9 +1,9 @@ - - -pre-compression - - - - - + + +pre-compression + + + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/stopExi.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/stopExi.xml index d131ce7bbd..6c0524a4fc 100644 --- a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/stopExi.xml +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/stopExi.xml @@ -1,3 +1,3 @@ - - + + \ No newline at end of file