From: Ed Warnicke Date: Thu, 13 Feb 2014 11:44:11 +0000 (+0000) Subject: Merge "Refactor Additional header for netconf hello message." X-Git-Tag: autorelease-tag-v20140601202136_82eb3f9~460 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=aeabf761ca043e41eeca6333bc9deb94b1de9ed0;hp=474d4daf958336effcf4f419843d9d659a27bce5 Merge "Refactor Additional header for netconf hello message." --- diff --git a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/ConfigPusher.java b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/ConfigPusher.java index 1d48e9287b..460aec6ac6 100644 --- a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/ConfigPusher.java +++ b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/ConfigPusher.java @@ -8,8 +8,6 @@ package org.opendaylight.controller.netconf.persist.impl; -import io.netty.channel.EventLoopGroup; - import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; @@ -30,7 +28,7 @@ import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.netconf.client.NetconfClient; import org.opendaylight.controller.netconf.client.NetconfClientDispatcher; import org.opendaylight.controller.netconf.util.NetconfUtil; -import org.opendaylight.controller.netconf.util.messages.NetconfMessageAdditionalHeader; +import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader; import org.opendaylight.controller.netconf.util.xml.XmlElement; import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; import org.opendaylight.controller.netconf.util.xml.XmlUtil; @@ -40,12 +38,12 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; -import com.google.common.base.Optional; import com.google.common.base.Preconditions; +import io.netty.channel.EventLoopGroup; @Immutable public class ConfigPusher { - private static final Logger logger = LoggerFactory.getLogger(ConfigPersisterNotificationHandler.class); + private static final Logger logger = LoggerFactory.getLogger(ConfigPusher.class); private static final int NETCONF_SEND_ATTEMPT_MS_DELAY = 1000; private static final int NETCONF_SEND_ATTEMPTS = 20; @@ -134,8 +132,8 @@ public class ConfigPusher { final long deadlineNanos = pollingStartNanos + TimeUnit.MILLISECONDS.toNanos(maxWaitForCapabilitiesMillis); int attempt = 0; - String additionalHeader = NetconfMessageAdditionalHeader.toString("unknown", address.getAddress().getHostAddress(), - Integer.toString(address.getPort()), "tcp", Optional.of("persister")); + NetconfHelloMessageAdditionalHeader additionalHeader = new NetconfHelloMessageAdditionalHeader("unknown", address.getAddress().getHostAddress(), + Integer.toString(address.getPort()), "tcp", "persister"); Set latestCapabilities = null; while (System.nanoTime() < deadlineNanos) { diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfMessage.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfMessage.java index afca33328e..78586a3fec 100644 --- a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfMessage.java +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfMessage.java @@ -10,30 +10,18 @@ package org.opendaylight.controller.netconf.api; import org.w3c.dom.Document; -import com.google.common.base.Optional; - /** * NetconfMessage represents a wrapper around org.w3c.dom.Document. Needed for * implementing ProtocolMessage interface. */ -public final class NetconfMessage { - private final String additionalHeader; +public class NetconfMessage { private final Document doc; public NetconfMessage(final Document doc) { - this(doc, null); - } - - public NetconfMessage(Document doc, String additionalHeader) { this.doc = doc; - this.additionalHeader = additionalHeader; } public Document getDocument() { return this.doc; } - - public Optional getAdditionalHeader() { - return additionalHeader== null ? Optional.absent() : Optional.of(additionalHeader); - } } 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 bff2a54c58..43664b3233 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 @@ -18,6 +18,7 @@ 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; import org.opendaylight.protocol.framework.ReconnectStrategy; import org.opendaylight.protocol.framework.ReconnectStrategyFactory; @@ -31,19 +32,23 @@ public class NetconfClientDispatcher extends AbstractDispatcherabsent(), clientConnectionTimeoutMillis); + this.negotiatorFactory = new NetconfClientSessionNegotiatorFactory(timer, + Optional. absent(), clientConnectionTimeoutMillis); } - public NetconfClientDispatcher(EventLoopGroup bossGroup, EventLoopGroup workerGroup, String additionalHeader, long connectionTimeoutMillis) { + public NetconfClientDispatcher(EventLoopGroup bossGroup, EventLoopGroup workerGroup, + NetconfHelloMessageAdditionalHeader additionalHeader, long connectionTimeoutMillis) { super(bossGroup, workerGroup); timer = new HashedWheelTimer(); - this.negotatorFactory = new NetconfClientSessionNegotiatorFactory(timer, Optional.of(additionalHeader), connectionTimeoutMillis); + this.negotiatorFactory = new NetconfClientSessionNegotiatorFactory(timer, Optional.of(additionalHeader), + connectionTimeoutMillis); } public Future createClient(InetSocketAddress address, @@ -57,7 +62,7 @@ public class NetconfClientDispatcher extends AbstractDispatcher promise) { - new ClientChannelInitializer( negotatorFactory, sessionListener).initialize(ch, promise); + new ClientChannelInitializer(negotiatorFactory, sessionListener).initialize(ch, promise); } }); } @@ -65,7 +70,7 @@ public class NetconfClientDispatcher extends AbstractDispatcher createReconnectingClient(final InetSocketAddress address, final NetconfClientSessionListener listener, final ReconnectStrategyFactory connectStrategyFactory, final ReconnectStrategy reestablishStrategy) { - final ClientChannelInitializer init = new ClientChannelInitializer(negotatorFactory, listener); + final ClientChannelInitializer init = new ClientChannelInitializer(negotiatorFactory, listener); return super.createReconnectingClient(address, connectStrategyFactory, reestablishStrategy, new PipelineInitializer() { @@ -88,14 +93,20 @@ public class NetconfClientDispatcher extends AbstractDispatcher promise) { - ch.pipeline().addLast("negotiator", negotiatorFactory.getSessionNegotiator( - new SessionListenerFactory() { - @Override - public NetconfClientSessionListener getSessionListener() { - return sessionListener; - } - }, ch, promise)); + public void initialize(SocketChannel ch, Promise promise) { + super.initialize(ch,promise); + } + + @Override + protected void initializeSessionNegotiator(SocketChannel ch, Promise promise) { + ch.pipeline().addAfter(NETCONF_MESSAGE_DECODER, AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR, + negotiatorFactory.getSessionNegotiator( + new SessionListenerFactory() { + @Override + public NetconfClientSessionListener getSessionListener() { + return sessionListener; + } + }, ch, promise)); } } diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiator.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiator.java index 3c2e814d89..c742bafe5e 100644 --- a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiator.java +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiator.java @@ -8,10 +8,6 @@ package org.opendaylight.controller.netconf.client; -import io.netty.channel.Channel; -import io.netty.util.Timer; -import io.netty.util.concurrent.Promise; - import java.util.Collection; import java.util.List; @@ -19,9 +15,9 @@ import javax.annotation.Nullable; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; -import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.netconf.api.NetconfSessionPreferences; import org.opendaylight.controller.netconf.util.AbstractNetconfSessionNegotiator; +import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage; import org.opendaylight.controller.netconf.util.xml.XMLNetconfUtil; import org.opendaylight.controller.netconf.util.xml.XmlElement; import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; @@ -32,6 +28,10 @@ import org.w3c.dom.Node; import com.google.common.base.Function; import com.google.common.collect.Collections2; +import io.netty.channel.Channel; +import io.netty.util.Timer; +import io.netty.util.concurrent.Promise; + public class NetconfClientSessionNegotiator extends AbstractNetconfSessionNegotiator { @@ -71,7 +71,7 @@ public class NetconfClientSessionNegotiator extends } @Override - protected NetconfClientSession getSession(NetconfClientSessionListener sessionListener, Channel channel, NetconfMessage message) { + protected NetconfClientSession getSession(NetconfClientSessionListener sessionListener, Channel channel, NetconfHelloMessage message) { return new NetconfClientSession(sessionListener, channel, extractSessionId(message.getDocument()), getCapabilities(message.getDocument())); } 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 e678a601ff..bb372b3aff 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 @@ -17,6 +17,8 @@ import java.io.InputStream; 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.protocol.framework.SessionListenerFactory; import org.opendaylight.protocol.framework.SessionNegotiator; @@ -28,11 +30,11 @@ import com.google.common.base.Preconditions; public class NetconfClientSessionNegotiatorFactory implements SessionNegotiatorFactory { - private final Optional additionalHeader; + private final Optional additionalHeader; private final long connectionTimeoutMillis; private final Timer timer; - public NetconfClientSessionNegotiatorFactory(Timer timer, Optional additionalHeader, long connectionTimeoutMillis) { + public NetconfClientSessionNegotiatorFactory(Timer timer, Optional additionalHeader, long connectionTimeoutMillis) { this.timer = Preconditions.checkNotNull(timer); this.additionalHeader = additionalHeader; this.connectionTimeoutMillis = connectionTimeoutMillis; @@ -53,9 +55,12 @@ public class NetconfClientSessionNegotiatorFactory implements SessionNegotiatorF Promise promise) { // Hello message needs to be recreated every time NetconfMessage helloMessage = loadHelloMessageTemplate(); + if(this.additionalHeader.isPresent()) { - helloMessage = new NetconfMessage(helloMessage.getDocument(), additionalHeader.get()); - } + helloMessage = new NetconfHelloMessage(helloMessage.getDocument(), additionalHeader.get()); + } else + helloMessage = new NetconfHelloMessage(helloMessage.getDocument()); + NetconfSessionPreferences proposal = new NetconfSessionPreferences(helloMessage); return new NetconfClientSessionNegotiator(proposal, promise, channel, timer, sessionListenerFactory.getSessionListener(), connectionTimeoutMillis); diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfSshClientDispatcher.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfSshClientDispatcher.java index 0737279b04..5b82ff2215 100644 --- a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfSshClientDispatcher.java +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfSshClientDispatcher.java @@ -21,6 +21,7 @@ import org.opendaylight.controller.netconf.util.AbstractChannelInitializer; import org.opendaylight.controller.netconf.util.handler.ssh.SshHandler; import org.opendaylight.controller.netconf.util.handler.ssh.authentication.AuthenticationHandler; import org.opendaylight.controller.netconf.util.handler.ssh.client.Invoker; +import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader; import org.opendaylight.protocol.framework.ReconnectStrategy; import org.opendaylight.protocol.framework.ReconnectStrategyFactory; import org.opendaylight.protocol.framework.SessionListenerFactory; @@ -31,22 +32,24 @@ public class NetconfSshClientDispatcher extends NetconfClientDispatcher { private final AuthenticationHandler authHandler; private final HashedWheelTimer timer; - private final NetconfClientSessionNegotiatorFactory negotatorFactory; + private final NetconfClientSessionNegotiatorFactory negotiatorFactory; public NetconfSshClientDispatcher(AuthenticationHandler authHandler, EventLoopGroup bossGroup, EventLoopGroup workerGroup, long connectionTimeoutMillis) { super(bossGroup, workerGroup, connectionTimeoutMillis); this.authHandler = authHandler; this.timer = new HashedWheelTimer(); - this.negotatorFactory = new NetconfClientSessionNegotiatorFactory(timer, Optional.absent(), connectionTimeoutMillis); + this.negotiatorFactory = new NetconfClientSessionNegotiatorFactory(timer, + Optional. absent(), connectionTimeoutMillis); } public NetconfSshClientDispatcher(AuthenticationHandler authHandler, EventLoopGroup bossGroup, - EventLoopGroup workerGroup, String additionalHeader, long socketTimeoutMillis) { + EventLoopGroup workerGroup, NetconfHelloMessageAdditionalHeader additionalHeader, long socketTimeoutMillis) { super(bossGroup, workerGroup, additionalHeader, socketTimeoutMillis); this.authHandler = authHandler; this.timer = new HashedWheelTimer(); - this.negotatorFactory = new NetconfClientSessionNegotiatorFactory(timer, Optional.of(additionalHeader), socketTimeoutMillis); + this.negotiatorFactory = new NetconfClientSessionNegotiatorFactory(timer, Optional.of(additionalHeader), + socketTimeoutMillis); } @Override @@ -56,7 +59,7 @@ public class NetconfSshClientDispatcher extends NetconfClientDispatcher { @Override public void initializeChannel(SocketChannel arg0, Promise arg1) { - new NetconfSshClientInitializer(authHandler, negotatorFactory, sessionListener).initialize(arg0, arg1); + new NetconfSshClientInitializer(authHandler, negotiatorFactory, sessionListener).initialize(arg0, arg1); } }); @@ -66,7 +69,7 @@ public class NetconfSshClientDispatcher extends NetconfClientDispatcher { public Future createReconnectingClient(final InetSocketAddress address, final NetconfClientSessionListener listener, final ReconnectStrategyFactory connectStrategyFactory, final ReconnectStrategy reestablishStrategy) { - final NetconfSshClientInitializer init = new NetconfSshClientInitializer(authHandler, negotatorFactory, listener); + final NetconfSshClientInitializer init = new NetconfSshClientInitializer(authHandler, negotiatorFactory, listener); return super.createReconnectingClient(address, connectStrategyFactory, reestablishStrategy, new PipelineInitializer() { @@ -103,14 +106,15 @@ public class NetconfSshClientDispatcher extends NetconfClientDispatcher { } @Override - protected void initializeAfterDecoder(SocketChannel ch, Promise promise) { - ch.pipeline().addLast("negotiator", negotiatorFactory.getSessionNegotiator(new SessionListenerFactory() { + protected void initializeSessionNegotiator(SocketChannel ch, + Promise promise) { + ch.pipeline().addAfter(NETCONF_MESSAGE_DECODER, AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR, + negotiatorFactory.getSessionNegotiator(new SessionListenerFactory() { @Override public NetconfClientSessionListener getSessionListener() { return sessionListener; } }, ch, promise)); - } } } diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcher.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcher.java index bd39049c56..ee9009762e 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcher.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcher.java @@ -41,6 +41,8 @@ public class NetconfServerDispatcher extends AbstractDispatcher { + public static final String DESERIALIZER_EX_HANDLER_KEY = "deserializerExHandler"; + private final NetconfServerSessionNegotiatorFactory negotiatorFactory; private final NetconfServerSessionListenerFactory listenerFactory; @@ -51,9 +53,14 @@ public class NetconfServerDispatcher extends AbstractDispatcher promise) { - ch.pipeline().addLast("deserializerExHandler", new DeserializerExceptionHandler()); - ch.pipeline().addLast("negotiator", negotiatorFactory.getSessionNegotiator(listenerFactory, ch, promise)); + protected void initializeMessageDecoder(SocketChannel ch) { + super.initializeMessageDecoder(ch); + ch.pipeline().addLast(DESERIALIZER_EX_HANDLER_KEY, new DeserializerExceptionHandler()); + } + + @Override + protected void initializeSessionNegotiator(SocketChannel ch, Promise promise) { + ch.pipeline().addAfter(DESERIALIZER_EX_HANDLER_KEY, AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR, negotiatorFactory.getSessionNegotiator(listenerFactory, ch, promise)); } } 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 93d4e55410..9ddc64f1a1 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 @@ -17,6 +17,7 @@ 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.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; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.extension.rev131210.NetconfTcp; @@ -38,13 +39,13 @@ public final class NetconfServerSession extends AbstractNetconfSession { @@ -35,60 +35,21 @@ public class NetconfServerSessionNegotiator extends } @Override - protected NetconfServerSession getSession(NetconfServerSessionListener sessionListener, Channel channel, NetconfMessage message) { - Optional additionalHeader = message.getAdditionalHeader(); + protected NetconfServerSession getSession(NetconfServerSessionListener sessionListener, Channel channel, NetconfHelloMessage message) { + Optional additionalHeader = message.getAdditionalHeader(); - AdditionalHeader parsedHeader; + NetconfHelloMessageAdditionalHeader parsedHeader; if (additionalHeader.isPresent()) { - parsedHeader = AdditionalHeaderUtil.fromString(additionalHeader.get()); + parsedHeader = additionalHeader.get(); } else { - parsedHeader = new AdditionalHeader("unknown", ((InetSocketAddress)channel.localAddress()).getHostString(), + InetSocketAddress inetSocketAddress = (InetSocketAddress) channel.localAddress(); + parsedHeader = new NetconfHelloMessageAdditionalHeader("unknown", inetSocketAddress.getHostString(), Integer.toString(inetSocketAddress.getPort()), "tcp", "client"); } + logger.debug("Additional header from hello parsed as {} from {}", parsedHeader, additionalHeader); return new NetconfServerSession(sessionListener, channel, sessionPreferences.getSessionId(), parsedHeader); } - public static class AdditionalHeader { - - private final String username; - private final String address; - private final String transport; - private final String sessionIdentifier; - - public AdditionalHeader(String userName, String hostAddress, String transport, String sessionIdentifier) { - this.address = hostAddress; - this.username = userName; - this.transport = transport; - this.sessionIdentifier = sessionIdentifier; - } - - String getUsername() { - return username; - } - - String getAddress() { - return address; - } - - String getTransport() { - return transport; - } - - String getSessionType() { - return sessionIdentifier; - } - - @Override - public String toString() { - final StringBuffer sb = new StringBuffer("AdditionalHeader{"); - sb.append("username='").append(username).append('\''); - sb.append(", address='").append(address).append('\''); - sb.append(", transport='").append(transport).append('\''); - sb.append('}'); - return sb.toString(); - } - } - -} + } diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionNegotiatorFactory.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionNegotiatorFactory.java index 8086b748d7..e052f61cc9 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionNegotiatorFactory.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionNegotiatorFactory.java @@ -8,20 +8,15 @@ package org.opendaylight.controller.netconf.impl; +import com.google.common.base.Preconditions; import io.netty.channel.Channel; import io.netty.util.Timer; import io.netty.util.concurrent.Promise; - -import java.io.InputStream; - -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpression; - -import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.netconf.api.NetconfServerSessionPreferences; import org.opendaylight.controller.netconf.impl.mapping.CapabilityProvider; import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFactoryListener; import org.opendaylight.controller.netconf.util.NetconfUtil; +import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage; import org.opendaylight.controller.netconf.util.xml.XMLNetconfUtil; import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; import org.opendaylight.controller.netconf.util.xml.XmlUtil; @@ -32,9 +27,11 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; -import com.google.common.base.Preconditions; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import java.io.InputStream; -public class NetconfServerSessionNegotiatorFactory implements SessionNegotiatorFactory { +public class NetconfServerSessionNegotiatorFactory implements SessionNegotiatorFactory { public static final String SERVER_HELLO_XML_LOCATION = "/server_hello.xml"; @@ -77,7 +74,7 @@ public class NetconfServerSessionNegotiatorFactory implements SessionNegotiatorF private static final XPathExpression capabilitiesXPath = XMLNetconfUtil .compileXPath("/netconf:hello/netconf:capabilities"); - private NetconfMessage createHelloMessage(long sessionId) { + private NetconfHelloMessage createHelloMessage(long sessionId) { Document helloMessageTemplate = getHelloTemplateClone(); // change session ID @@ -96,7 +93,7 @@ public class NetconfServerSessionNegotiatorFactory implements SessionNegotiatorF capabilityElement.setTextContent(capability); capabilitiesElement.appendChild(capabilityElement); } - return new NetconfMessage(helloMessageTemplate); + return new NetconfHelloMessage(helloMessageTemplate); } private synchronized Document getHelloTemplateClone() { diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/util/AdditionalHeaderUtil.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/util/AdditionalHeaderUtil.java deleted file mode 100644 index 5c630dd343..0000000000 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/util/AdditionalHeaderUtil.java +++ /dev/null @@ -1,41 +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.impl.util; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.opendaylight.controller.netconf.impl.NetconfServerSessionNegotiator.AdditionalHeader; - -import com.google.common.base.Preconditions; - -public class AdditionalHeaderUtil { - - private static final Pattern pattern = Pattern - .compile("\\[(?[^;]+);(?
[0-9\\.]+)[:/](?[0-9]+);(?[a-z]+)[^\\]]+\\]"); - private static final Pattern customHeaderPattern = Pattern - .compile("\\[(?[^;]+);(?
[0-9\\.]+)[:/](?[0-9]+);(?[a-z]+);(?[a-z]+)[^\\]]+\\]"); - - public static AdditionalHeader fromString(String additionalHeader) { - additionalHeader = additionalHeader.trim(); - Matcher matcher = pattern.matcher(additionalHeader); - Matcher matcher2 = customHeaderPattern.matcher(additionalHeader); - Preconditions.checkArgument(matcher.matches(), "Additional header in wrong format %s, expected %s", - additionalHeader, pattern); - String username = matcher.group("username"); - String address = matcher.group("address"); - String transport = matcher.group("transport"); - String sessionIdentifier = "client"; - if (matcher2.matches()) { - sessionIdentifier = matcher2.group("sessionIdentifier"); - } - return new AdditionalHeader(username, address, transport, sessionIdentifier); - } - -} diff --git a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/AdditionalHeaderParserTest.java b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/AdditionalHeaderParserTest.java index 97d9a98b57..c2dcd67921 100644 --- a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/AdditionalHeaderParserTest.java +++ b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/AdditionalHeaderParserTest.java @@ -10,15 +10,15 @@ package org.opendaylight.controller.netconf.impl; import junit.framework.Assert; import org.junit.Test; -import org.opendaylight.controller.netconf.impl.util.AdditionalHeaderUtil; +import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader; public class AdditionalHeaderParserTest { @Test public void testParsing() throws Exception { String s = "[netconf;10.12.0.102:48528;ssh;;;;;;]"; - NetconfServerSessionNegotiator.AdditionalHeader header = AdditionalHeaderUtil.fromString(s); - Assert.assertEquals("netconf", header.getUsername()); + NetconfHelloMessageAdditionalHeader header = NetconfHelloMessageAdditionalHeader.fromString(s); + Assert.assertEquals("netconf", header.getUserName()); Assert.assertEquals("10.12.0.102", header.getAddress()); Assert.assertEquals("ssh", header.getTransport()); } @@ -26,8 +26,8 @@ public class AdditionalHeaderParserTest { @Test public void testParsing2() throws Exception { String s = "[tomas;10.0.0.0/10000;tcp;1000;1000;;/home/tomas;;]"; - NetconfServerSessionNegotiator.AdditionalHeader header = AdditionalHeaderUtil.fromString(s); - Assert.assertEquals("tomas", header.getUsername()); + NetconfHelloMessageAdditionalHeader header = NetconfHelloMessageAdditionalHeader.fromString(s); + Assert.assertEquals("tomas", header.getUserName()); Assert.assertEquals("10.0.0.0", header.getAddress()); Assert.assertEquals("tcp", header.getTransport()); } @@ -35,6 +35,6 @@ public class AdditionalHeaderParserTest { @Test(expected = IllegalArgumentException.class) public void testParsingNoUsername() throws Exception { String s = "[10.12.0.102:48528;ssh;;;;;;]"; - AdditionalHeaderUtil.fromString(s); + NetconfHelloMessageAdditionalHeader.fromString(s); } } diff --git a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/ConcurrentClientsTest.java b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/ConcurrentClientsTest.java index 0ef2c285e4..c0d52ad85e 100644 --- a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/ConcurrentClientsTest.java +++ b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/ConcurrentClientsTest.java @@ -37,6 +37,7 @@ import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationFilter; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; +import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader; import org.opendaylight.controller.netconf.util.test.XmlFileLoader; import org.opendaylight.controller.netconf.util.xml.XmlUtil; import org.slf4j.Logger; @@ -100,7 +101,8 @@ public class ConcurrentClientsTest { } nettyGroup = new NioEventLoopGroup(); - netconfClientDispatcher = new NetconfClientDispatcher( nettyGroup, nettyGroup, 5000); + NetconfHelloMessageAdditionalHeader additionalHeader = new NetconfHelloMessageAdditionalHeader("uname", "10.10.10.1", "830", "tcp", "client"); + netconfClientDispatcher = new NetconfClientDispatcher( nettyGroup, nettyGroup, additionalHeader, 5000); NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl(); factoriesListener.onAddNetconfOperationServiceFactory(mockOpF()); diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractChannelInitializer.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractChannelInitializer.java index 7068de8526..0910d9403a 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractChannelInitializer.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractChannelInitializer.java @@ -13,21 +13,44 @@ import io.netty.util.concurrent.Promise; import org.opendaylight.controller.netconf.api.NetconfSession; import org.opendaylight.controller.netconf.util.handler.FramingMechanismHandlerFactory; +import org.opendaylight.controller.netconf.util.handler.NetconfHelloMessageToXMLEncoder; import org.opendaylight.controller.netconf.util.handler.NetconfMessageAggregator; -import org.opendaylight.controller.netconf.util.handler.NetconfMessageToXMLEncoder; -import org.opendaylight.controller.netconf.util.handler.NetconfXMLToMessageDecoder; +import org.opendaylight.controller.netconf.util.handler.NetconfXMLToHelloMessageDecoder; import org.opendaylight.controller.netconf.util.messages.FramingMechanism; public abstract class AbstractChannelInitializer { - public void initialize(SocketChannel ch, Promise promise){ - ch.pipeline().addLast("aggregator", new NetconfMessageAggregator(FramingMechanism.EOM)); - ch.pipeline().addLast(new NetconfXMLToMessageDecoder()); - initializeAfterDecoder(ch, promise); - ch.pipeline().addLast("frameEncoder", FramingMechanismHandlerFactory.createHandler(FramingMechanism.EOM)); - ch.pipeline().addLast(new NetconfMessageToXMLEncoder()); + public static final String NETCONF_MESSAGE_DECODER = "netconfMessageDecoder"; + public static final String NETCONF_MESSAGE_AGGREGATOR = "aggregator"; + public static final String NETCONF_MESSAGE_ENCODER = "netconfMessageEncoder"; + public static final String NETCONF_MESSAGE_FRAME_ENCODER = "frameEncoder"; + public static final String NETCONF_SESSION_NEGOTIATOR = "negotiator"; + + public void initialize(SocketChannel ch, Promise promise) { + ch.pipeline().addLast(NETCONF_MESSAGE_AGGREGATOR, new NetconfMessageAggregator(FramingMechanism.EOM)); + initializeMessageDecoder(ch); + ch.pipeline().addLast(NETCONF_MESSAGE_FRAME_ENCODER, FramingMechanismHandlerFactory.createHandler(FramingMechanism.EOM)); + initializeMessageEncoder(ch); + + initializeSessionNegotiator(ch, promise); + } + + protected void initializeMessageEncoder(SocketChannel ch) { + // Special encoding handler for hello message to include additional header if available, + // it is thrown away after successful negotiation + ch.pipeline().addLast(NETCONF_MESSAGE_ENCODER, new NetconfHelloMessageToXMLEncoder()); + } + + protected void initializeMessageDecoder(SocketChannel ch) { + // Special decoding handler for hello message to parse additional header if available, + // it is thrown away after successful negotiation + ch.pipeline().addLast(NETCONF_MESSAGE_DECODER, new NetconfXMLToHelloMessageDecoder()); } - protected abstract void initializeAfterDecoder(SocketChannel ch, Promise promise); + /** + * Insert session negotiator into the pipeline. It must be inserted after message decoder + * identified by {@link AbstractChannelInitializer#NETCONF_MESSAGE_DECODER}, (or any other custom decoder processor) + */ + protected abstract void initializeSessionNegotiator(SocketChannel ch, Promise promise); } 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 9c35c7225f..9986b82bd8 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,17 +8,6 @@ package org.opendaylight.controller.netconf.util; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.ssl.SslHandler; -import io.netty.util.Timeout; -import io.netty.util.Timer; -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 org.opendaylight.controller.netconf.api.AbstractNetconfSession; @@ -26,11 +15,12 @@ import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.netconf.api.NetconfSessionListener; import org.opendaylight.controller.netconf.api.NetconfSessionPreferences; import org.opendaylight.controller.netconf.util.handler.FramingMechanismHandlerFactory; +import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage; import org.opendaylight.controller.netconf.util.handler.NetconfMessageAggregator; import org.opendaylight.controller.netconf.util.handler.NetconfMessageChunkDecoder; +import org.opendaylight.controller.netconf.util.handler.NetconfMessageToXMLEncoder; +import org.opendaylight.controller.netconf.util.handler.NetconfXMLToMessageDecoder; import org.opendaylight.controller.netconf.util.messages.FramingMechanism; -import org.opendaylight.controller.netconf.util.xml.XmlElement; -import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; import org.opendaylight.controller.netconf.util.xml.XmlUtil; import org.opendaylight.protocol.framework.AbstractSessionNegotiator; import org.slf4j.Logger; @@ -41,11 +31,23 @@ import org.w3c.dom.NodeList; 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.handler.ssl.SslHandler; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import io.netty.util.concurrent.Promise; + public abstract class AbstractNetconfSessionNegotiator

, L extends NetconfSessionListener> -extends AbstractSessionNegotiator { +extends AbstractSessionNegotiator { private static final Logger logger = LoggerFactory.getLogger(AbstractNetconfSessionNegotiator.class); public static final String NAME_OF_EXCEPTION_HANDLER = "lastExceptionHandler"; + public static final String CHUNK_DECODER_CHANNEL_HANDLER_KEY = "chunkDecoder"; protected final P sessionPreferences; @@ -147,20 +149,21 @@ extends AbstractSessionNegotiator { } @Override - protected void handleMessage(NetconfMessage netconfMessage) { + protected void handleMessage(NetconfHelloMessage netconfMessage) { final Document doc = netconfMessage.getDocument(); - if (isHelloMessage(doc)) { - if (containsBase11Capability(doc) - && containsBase11Capability(sessionPreferences.getHelloMessage().getDocument())) { - channel.pipeline().replace("frameEncoder", "frameEncoder", - FramingMechanismHandlerFactory.createHandler(FramingMechanism.CHUNK)); - channel.pipeline().replace("aggregator", "aggregator", - new NetconfMessageAggregator(FramingMechanism.CHUNK)); - channel.pipeline().addAfter("aggregator", "chunkDecoder", new NetconfMessageChunkDecoder()); + // Only Hello message should arrive during negotiation + if (netconfMessage instanceof NetconfHelloMessage) { + + replaceHelloMessageHandlers(); + + if (shouldUseChunkFraming(doc)) { + insertChunkFramingToPipeline(); } + changeState(State.ESTABLISHED); - S session = getSession(sessionListener, channel, netconfMessage); + S session = getSession(sessionListener, channel, (NetconfHelloMessage)netconfMessage); + negotiationSuccessful(session); } else { final IllegalStateException cause = new IllegalStateException( @@ -170,19 +173,37 @@ extends AbstractSessionNegotiator { } } - protected abstract S getSession(L sessionListener, Channel channel, NetconfMessage message); + /** + * Insert chunk framing handlers into the pipeline + */ + private void insertChunkFramingToPipeline() { + replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_FRAME_ENCODER, + FramingMechanismHandlerFactory.createHandler(FramingMechanism.CHUNK)); + replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_AGGREGATOR, + new NetconfMessageAggregator(FramingMechanism.CHUNK)); + channel.pipeline().addAfter(AbstractChannelInitializer.NETCONF_MESSAGE_AGGREGATOR, + CHUNK_DECODER_CHANNEL_HANDLER_KEY, new NetconfMessageChunkDecoder()); + } + + private boolean shouldUseChunkFraming(Document doc) { + return containsBase11Capability(doc) + && containsBase11Capability(sessionPreferences.getHelloMessage().getDocument()); + } - private boolean isHelloMessage(Document doc) { - try { - XmlElement.fromDomElementWithExpected(doc.getDocumentElement(), "hello", - XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + /** + * Remove special handlers for hello message. Insert regular netconf xml message (en|de)coders. + */ + private void replaceHelloMessageHandlers() { + replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_DECODER, new NetconfXMLToMessageDecoder()); + replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_ENCODER, new NetconfMessageToXMLEncoder()); + } - } catch (IllegalArgumentException | IllegalStateException e) { - return false; - } - return true; + private static ChannelHandler replaceChannelHandler(Channel channel, String handlerKey, ChannelHandler decoder) { + return channel.pipeline().replace(handlerKey, handlerKey, decoder); } + protected abstract S getSession(L sessionListener, Channel channel, NetconfHelloMessage message); + private 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, diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfHelloMessageToXMLEncoder.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfHelloMessageToXMLEncoder.java new file mode 100644 index 0000000000..a87d175d78 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfHelloMessageToXMLEncoder.java @@ -0,0 +1,64 @@ +/* + * 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.handler; + +import java.nio.ByteBuffer; + +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage; +import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader; + +import com.google.common.base.Charsets; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; + +/** + * Customized NetconfMessageToXMLEncoder that serializes additional header with + * session metadata along with + * {@link org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage} + * . Used by netconf clients to send information about the user, ip address, + * protocol etc. + *

+ * Hello message with header example: + *

+ * + *

+ * {@code
+ * [tomas;10.0.0.0/10000;tcp;1000;1000;;/home/tomas;;]
+ * 
+ * 
+ * urn:ietf:params:netconf:base:1.0
+ * 
+ * 
+ * }
+ * 
+ */ +public final class NetconfHelloMessageToXMLEncoder extends NetconfMessageToXMLEncoder { + + @Override + protected ByteBuffer encodeMessage(NetconfMessage msg) { + Preconditions.checkState(msg instanceof NetconfHelloMessage, "Netconf message of type %s expected, was %s", + NetconfHelloMessage.class, msg.getClass()); + Optional headerOptional = ((NetconfHelloMessage) msg) + .getAdditionalHeader(); + + // If additional header present, serialize it along with netconf hello + // message + if (headerOptional.isPresent()) { + byte[] bytesFromHeader = headerOptional.get().toFormattedString().getBytes(Charsets.UTF_8); + byte[] bytesFromMessage = xmlToString(msg.getDocument()).getBytes(Charsets.UTF_8); + + ByteBuffer byteBuffer = ByteBuffer.allocate(bytesFromHeader.length + bytesFromMessage.length) + .put(bytesFromHeader).put(bytesFromMessage); + byteBuffer.flip(); + return byteBuffer; + } + + return super.encodeMessage(msg); + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfMessageToXMLEncoder.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfMessageToXMLEncoder.java index 31a4225fc8..df0f7ef46a 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfMessageToXMLEncoder.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfMessageToXMLEncoder.java @@ -24,7 +24,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; import com.google.common.base.Optional; -public final class NetconfMessageToXMLEncoder extends MessageToByteEncoder { +public class NetconfMessageToXMLEncoder extends MessageToByteEncoder { private static final Logger LOG = LoggerFactory.getLogger(NetconfMessageToXMLEncoder.class); private final Optional clientId; @@ -47,21 +47,17 @@ public final class NetconfMessageToXMLEncoder extends MessageToByteEncoder POSSIBLE_ENDS = ImmutableList.of( + new byte[] { ']', '\n' }, + new byte[] { ']', '\r', '\n' }); + private static final List POSSIBLE_STARTS = ImmutableList.of( + new byte[] { '[' }, + new byte[] { '\r', '\n', '[' }, + new byte[] { '\n', '[' }); + + private String additionalHeaderCache; + + @Override + protected byte[] preprocessMessageBytes(byte[] bytes) { + // Extract bytes containing header with additional metadata + + if (startsWithAdditionalHeader(bytes)) { + // Auth information containing username, ip address... extracted for monitoring + int endOfAuthHeader = getAdditionalHeaderEndIndex(bytes); + if (endOfAuthHeader > -1) { + byte[] additionalHeaderBytes = Arrays.copyOfRange(bytes, 0, endOfAuthHeader + 2); + additionalHeaderCache = additionalHeaderToString(additionalHeaderBytes); + bytes = Arrays.copyOfRange(bytes, endOfAuthHeader + 2, bytes.length); + } + } + + return bytes; + } + + @Override + protected void cleanUpAfterDecode() { + additionalHeaderCache = null; + } + + @Override + protected NetconfMessage buildNetconfMessage(Document doc) { + return new NetconfHelloMessage(doc, additionalHeaderCache == null ? null + : NetconfHelloMessageAdditionalHeader.fromString(additionalHeaderCache)); + } + + private int getAdditionalHeaderEndIndex(byte[] bytes) { + for (byte[] possibleEnd : POSSIBLE_ENDS) { + int idx = findByteSequence(bytes, possibleEnd); + + if (idx != -1) { + return idx; + } + } + + return -1; + } + + private static int findByteSequence(final byte[] bytes, final byte[] sequence) { + if (bytes.length < sequence.length) { + throw new IllegalArgumentException("Sequence to be found is longer than the given byte array."); + } + if (bytes.length == sequence.length) { + if (Arrays.equals(bytes, sequence)) { + return 0; + } else { + return -1; + } + } + int j = 0; + for (int i = 0; i < bytes.length; i++) { + if (bytes[i] == sequence[j]) { + j++; + if (j == sequence.length) { + return i - j + 1; + } + } else { + j = 0; + } + } + return -1; + } + + private boolean startsWithAdditionalHeader(byte[] bytes) { + for (byte[] possibleStart : POSSIBLE_STARTS) { + int i = 0; + for (byte b : possibleStart) { + if(bytes[i++] != b) + break; + + if(i == possibleStart.length) + return true; + } + } + + return false; + } + + private String additionalHeaderToString(byte[] bytes) { + return Charsets.UTF_8.decode(ByteBuffer.wrap(bytes)).toString(); + } + +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfXMLToMessageDecoder.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfXMLToMessageDecoder.java index 2eefb91724..b697edfb05 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfXMLToMessageDecoder.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/NetconfXMLToMessageDecoder.java @@ -7,15 +7,8 @@ */ package org.opendaylight.controller.netconf.util.handler; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageDecoder; - import java.io.ByteArrayInputStream; -import java.io.IOException; import java.nio.ByteBuffer; -import java.util.Arrays; import java.util.List; import org.opendaylight.controller.netconf.api.NetconfDeserializerException; @@ -24,22 +17,17 @@ import org.opendaylight.controller.netconf.util.xml.XmlUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; -import org.xml.sax.SAXException; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; -import com.google.common.collect.ImmutableList; -public final class NetconfXMLToMessageDecoder extends ByteToMessageDecoder { - private static final Logger LOG = LoggerFactory.getLogger(NetconfXMLToMessageDecoder.class); +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; - private static final List POSSIBLE_ENDS = ImmutableList.of( - new byte[] { ']', '\n' }, - new byte[] { ']', '\r', '\n' }); - private static final List POSSIBLE_STARTS = ImmutableList.of( - new byte[] { '[' }, - new byte[] { '\r', '\n', '[' }, - new byte[] { '\n', '[' }); +public class NetconfXMLToMessageDecoder extends ByteToMessageDecoder { + private static final Logger LOG = LoggerFactory.getLogger(NetconfXMLToMessageDecoder.class); @Override @VisibleForTesting @@ -57,93 +45,35 @@ public final class NetconfXMLToMessageDecoder extends ByteToMessageDecoder { logMessage(bytes); - String additionalHeader = null; - - // FIXME: this has to be moved into the negotiator and explained as to what the heck - // is going on. This is definitely not specified in NETCONF and has no place here. It - // requires reading all data and incurs inefficiency by being unable to pipe the ByteBuf - // directly into the XML decoder. - if (startsWithAdditionalHeader(bytes)) { - // Auth information containing username, ip address... extracted for monitoring - int endOfAuthHeader = getAdditionalHeaderEndIndex(bytes); - if (endOfAuthHeader > -1) { - byte[] additionalHeaderBytes = Arrays.copyOfRange(bytes, 0, endOfAuthHeader + 2); - additionalHeader = additionalHeaderToString(additionalHeaderBytes); - bytes = Arrays.copyOfRange(bytes, endOfAuthHeader + 2, bytes.length); - } - } + bytes = preprocessMessageBytes(bytes); NetconfMessage message; try { Document doc = XmlUtil.readXmlToDocument(new ByteArrayInputStream(bytes)); - message = new NetconfMessage(doc, additionalHeader); - } catch (final SAXException | IOException | IllegalStateException e) { + message = buildNetconfMessage(doc); + } catch (Exception e) { throw new NetconfDeserializerException("Could not parse message from " + new String(bytes), e); } out.add(message); } finally { in.discardReadBytes(); + cleanUpAfterDecode(); } } - private int getAdditionalHeaderEndIndex(byte[] bytes) { - for (byte[] possibleEnd : POSSIBLE_ENDS) { - int idx = findByteSequence(bytes, possibleEnd); - - if (idx != -1) { - return idx; - } - } + protected void cleanUpAfterDecode() {} - return -1; + protected NetconfMessage buildNetconfMessage(Document doc) { + return new NetconfMessage(doc); } - private static int findByteSequence(final byte[] bytes, final byte[] sequence) { - if (bytes.length < sequence.length) { - throw new IllegalArgumentException("Sequence to be found is longer than the given byte array."); - } - if (bytes.length == sequence.length) { - if (Arrays.equals(bytes, sequence)) { - return 0; - } else { - return -1; - } - } - int j = 0; - for (int i = 0; i < bytes.length; i++) { - if (bytes[i] == sequence[j]) { - j++; - if (j == sequence.length) { - return i - j + 1; - } - } else { - j = 0; - } - } - return -1; + protected byte[] preprocessMessageBytes(byte[] bytes) { + return bytes; } - private boolean startsWithAdditionalHeader(byte[] bytes) { - for (byte[] possibleStart : POSSIBLE_STARTS) { - int i = 0; - for (byte b : possibleStart) { - if(bytes[i] != b) - break; - - return true; - } - } - - return false; - }; - private void logMessage(byte[] bytes) { String s = Charsets.UTF_8.decode(ByteBuffer.wrap(bytes)).toString(); LOG.debug("Parsing message \n{}", s); } - private String additionalHeaderToString(byte[] bytes) { - return Charsets.UTF_8.decode(ByteBuffer.wrap(bytes)).toString(); - } - } diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfHelloMessage.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfHelloMessage.java new file mode 100644 index 0000000000..249f894340 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfHelloMessage.java @@ -0,0 +1,53 @@ +/* + * 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 org.opendaylight.controller.netconf.api.NetconfMessage; +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.w3c.dom.Document; + +import com.google.common.base.Optional; + +/** + * NetconfMessage that can carry additional header with session metadata. See {@link org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader} + */ +public final class NetconfHelloMessage extends NetconfMessage { + + public static final String HELLO_TAG = "hello"; + + private final NetconfHelloMessageAdditionalHeader additionalHeader; + + public NetconfHelloMessage(Document doc, NetconfHelloMessageAdditionalHeader additionalHeader) { + super(doc); + checkHelloMessage(doc); + this.additionalHeader = additionalHeader; + } + + public NetconfHelloMessage(Document doc) { + this(doc, null); + } + + public Optional getAdditionalHeader() { + return additionalHeader== null ? Optional.absent() : Optional.of(additionalHeader); + } + + private static void checkHelloMessage(Document doc) { + try { + XmlElement.fromDomElementWithExpected(doc.getDocumentElement(), HELLO_TAG, + XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + + } catch (IllegalArgumentException | IllegalStateException e) { + throw new IllegalArgumentException(String.format( + "Hello message invalid format, should contain %s tag from namespace %s, but is: %s", HELLO_TAG, + XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, XmlUtil.toString(doc)), e); + } + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfHelloMessageAdditionalHeader.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfHelloMessageAdditionalHeader.java new file mode 100644 index 0000000000..f3ca30d2c4 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfHelloMessageAdditionalHeader.java @@ -0,0 +1,121 @@ +/* + * 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 com.google.common.base.Preconditions; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Additional header can be used with hello message to carry information about + * session's connection. Provided information can be reported via netconf + * monitoring. + *
+ * It has pattern "[username; host-address:port; transport; session-identifier;]"
+ * username - name of account on a remote
+ * host-address - client's IP address
+ * port - port number
+ * transport - tcp, ssh
+ * session-identifier - persister, client
+ * Session-identifier is optional, others mandatory.
+ * 
+ * This header is inserted in front of a netconf hello message followed by a newline. + */ +public class NetconfHelloMessageAdditionalHeader { + + private static final String SC = ";"; + + private final String userName; + private final String hostAddress; + private final String port; + private final String transport; + private final String sessionIdentifier; + + public NetconfHelloMessageAdditionalHeader(String userName, String hostAddress, String port, String transport, String sessionIdentifier) { + this.userName = userName; + this.hostAddress = hostAddress; + this.port = port; + this.transport = transport; + this.sessionIdentifier = sessionIdentifier; + } + + public String getUserName() { + return userName; + } + + public String getAddress() { + return hostAddress; + } + + public String getPort() { + return port; + } + + public String getTransport() { + return transport; + } + + public String getSessionIdentifier() { + return sessionIdentifier; + } + + /** + * Format additional header into a string suitable as a prefix for netconf hello message + */ + public String toFormattedString() { + Preconditions.checkNotNull(userName); + Preconditions.checkNotNull(hostAddress); + Preconditions.checkNotNull(port); + Preconditions.checkNotNull(transport); + Preconditions.checkNotNull(sessionIdentifier); + return "[" + userName + SC + hostAddress + ":" + port + SC + transport + SC + sessionIdentifier + SC + "]" + + System.lineSeparator(); + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("NetconfHelloMessageAdditionalHeader{"); + sb.append("userName='").append(userName).append('\''); + sb.append(", hostAddress='").append(hostAddress).append('\''); + sb.append(", port='").append(port).append('\''); + sb.append(", transport='").append(transport).append('\''); + sb.append(", sessionIdentifier='").append(sessionIdentifier).append('\''); + sb.append('}'); + return sb.toString(); + } + + // TODO IPv6 + private static final Pattern pattern = Pattern + .compile("\\[(?[^;]+);(?
[0-9\\.]+)[:/](?[0-9]+);(?[a-z]+)[^\\]]+\\]"); + private static final Pattern customHeaderPattern = Pattern + .compile("\\[(?[^;]+);(?
[0-9\\.]+)[:/](?[0-9]+);(?[a-z]+);(?[a-z]+)[^\\]]+\\]"); + + /** + * Parse additional header from a formatted string + */ + public static NetconfHelloMessageAdditionalHeader fromString(String additionalHeader) { + additionalHeader = additionalHeader.trim(); + Matcher matcher = pattern.matcher(additionalHeader); + Matcher matcher2 = customHeaderPattern.matcher(additionalHeader); + Preconditions.checkArgument(matcher.matches(), "Additional header in wrong format %s, expected %s", + additionalHeader, pattern); + + String username = matcher.group("username"); + String address = matcher.group("address"); + String port = matcher.group("port"); + String transport = matcher.group("transport"); + String sessionIdentifier = "client"; + if (matcher2.matches()) { + sessionIdentifier = matcher2.group("sessionIdentifier"); + } + return new NetconfHelloMessageAdditionalHeader(username, address, port, transport, sessionIdentifier); + } + +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageAdditionalHeader.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageAdditionalHeader.java deleted file mode 100644 index 457e226e01..0000000000 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageAdditionalHeader.java +++ /dev/null @@ -1,42 +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.util.messages; - -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; - -/** - * Additional header can be used with hello message to carry information about - * session's connection. Provided information can be reported via netconf - * monitoring. - *
- * It has pattern "[username; host-address:port; transport; session-identifier;]"
- * username - name of account on a remote
- * host-address - client's IP address
- * port - port number
- * transport - tcp, ssh
- * session-identifier - persister, client
- * Session-identifier is optional, others mandatory.
- * 
- */ -public class NetconfMessageAdditionalHeader { - - private static final String SC = ";"; - - public static String toString(String userName, String hostAddress, String port, String transport, - Optional sessionIdentifier) { - Preconditions.checkNotNull(userName); - Preconditions.checkNotNull(hostAddress); - Preconditions.checkNotNull(port); - Preconditions.checkNotNull(transport); - String identifier = sessionIdentifier.isPresent() ? sessionIdentifier.get() : ""; - return "[" + userName + SC + hostAddress + ":" + port + SC + transport + SC + identifier + SC + "]" - + System.lineSeparator(); - } -} diff --git a/opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageFactoryTest.java b/opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageFactoryTest.java index c405d9bcb5..6b7bffcd86 100644 --- a/opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageFactoryTest.java +++ b/opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageFactoryTest.java @@ -15,6 +15,7 @@ import java.util.ArrayList; import java.util.List; import org.junit.Test; +import org.opendaylight.controller.netconf.util.handler.NetconfXMLToHelloMessageDecoder; import org.opendaylight.controller.netconf.util.handler.NetconfXMLToMessageDecoder; import com.google.common.io.Files; @@ -22,7 +23,7 @@ import com.google.common.io.Files; public class NetconfMessageFactoryTest { @Test public void testAuth() throws Exception { - NetconfXMLToMessageDecoder parser = new NetconfXMLToMessageDecoder(); + NetconfXMLToMessageDecoder parser = new NetconfXMLToHelloMessageDecoder(); File authHelloFile = new File(getClass().getResource("/netconfMessages/client_hello_with_auth.xml").getFile()); final List out = new ArrayList<>();