Merge "Checkstyle plugin check - config & netconf"
authorTony Tkacik <ttkacik@cisco.com>
Fri, 6 Jun 2014 08:19:33 +0000 (08:19 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Fri, 6 Jun 2014 08:19:33 +0000 (08:19 +0000)
62 files changed:
features/base/src/main/resources/features.xml
opendaylight/commons/opendaylight/pom.xml
opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractDispatcher.java
opendaylight/config/logback-config/src/test/java/org/opendaylight/controller/config/yang/logback/config/LogbackModuleTest.java
opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/fixed/FixedThreadPoolConfigBeanTest.java
opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/scheduled/ScheduledThreadPoolConfigBeanTest.java
opendaylight/distribution/opendaylight-karaf/src/main/resources/etc/jre.properties [new file with mode: 0644]
opendaylight/distribution/opendaylight-karaf/src/main/resources/etc/startup.properties [new file with mode: 0644]
opendaylight/distribution/opendaylight/pom.xml
opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/SingletonHolder.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/AbstractDOMStoreTransaction.java [new file with mode: 0644]
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/InMemoryDOMDataStore.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedReadTransaction.java [new file with mode: 0644]
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedReadWriteTransaction.java [new file with mode: 0644]
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedWriteTransaction.java [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/json/test/CnSnToJsonBasicDataTypesTest.java
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/xml/test/CnSnToXmlTest.java
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CodecsExceptionsCatchingTest.java
opendaylight/md-sal/sal-rest-connector/src/test/resources/cnsn-to-json/simple-data-types/xml/bad-data.xml [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/resources/cnsn-to-json/simple-data-types/xml/data.xml
opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/NodeStatisticsHandler.java
opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/StatisticsRequestScheduler.java
opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/ReplaceEditConfigStrategyTest.java
opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/SshClientChannelInitializer.java
opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/TcpClientChannelInitializer.java
opendaylight/netconf/netconf-client/src/test/java/org/opendaylight/controller/netconf/client/test/TestingNetconfClient.java
opendaylight/netconf/netconf-impl/pom.xml
opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcher.java
opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivator.java
opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java
opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java
opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/AbstractChannelInitializer.java
opendaylight/netconf/netconf-ssh/pom.xml
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/NetconfSSHServer.java
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProvider.java
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProviderInterface.java [deleted file]
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/PEMGenerator.java
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/NetconfSSHActivator.java
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/Handshaker.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/IOThread.java [deleted file]
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/SocketThread.java [deleted file]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/KeyGeneratorTest.java [deleted file]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/SSHServerTest.java [deleted file]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoClient.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoClientHandler.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoServer.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoServerHandler.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/ProxyServer.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/ProxyServerHandler.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/ssh/authentication/SSHServerTest.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/resources/logback-test.xml [new file with mode: 0644]
opendaylight/netconf/netconf-tcp/pom.xml [new file with mode: 0644]
opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/netty/ProxyServer.java [new file with mode: 0644]
opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/netty/ProxyServerHandler.java [new file with mode: 0644]
opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/osgi/NetconfTCPActivator.java [new file with mode: 0644]
opendaylight/netconf/netconf-util/pom.xml
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/osgi/NetconfConfigUtil.java
opendaylight/netconf/pom.xml
opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/packet/TCP.java
opendaylight/sal/api/src/test/java/org/opendaylight/controller/sal/packet/TCPTest.java

index cc112052cc4f9acfab09a030b30caaebd7e878ef..23051f5a9ab88ba7a7f82dc75b9ad2abfbf0355c 100644 (file)
@@ -1,10 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<features name="base-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.2.0"
-   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-   xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.2.0 http://karaf.apache.org/xmlns/features/v1.2.0">
+<features name="base-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.2.0 http://karaf.apache.org/xmlns/features/v1.2.0">
 
-   <feature name="base-all" description="OpenDaylight Controller"
-      version="${project.version}">
+   <feature name="base-all" description="OpenDaylight Controller" version="${project.version}">
       <feature>http</feature>
       <feature>transaction</feature>
       <feature>base-felix-dm</feature>
    <feature name="base-dummy-console" description="Temporary Dummy Console" version="1.1.0-SNAPSHOT">
       <bundle>mvn:org.opendaylight.controller/dummy-console/1.1.0-SNAPSHOT</bundle>
    </feature>
-   <feature name="base-felix-dm" description="Felix Dependency Manager"
-      version="${felix.dependencymanager.version}">
+   <feature name="base-felix-dm" description="Felix Dependency Manager" version="${felix.dependencymanager.version}">
       <bundle start-level="35">mvn:org.osgi/org.osgi.compendium/${osgi.compendium.version}</bundle>
       <bundle start-level="35">mvn:org.apache.felix/org.apache.felix.dependencymanager/${felix.dependencymanager.version}</bundle>
       <bundle start-level="35">mvn:org.apache.felix/org.apache.felix.dependencymanager.shell/${felix.dependencymanager.shell.version}</bundle>
    </feature>
-   <feature name="base-aries-spi-fly" description="Aries SPI Fly"
-      version="${spifly.version}">
+   <feature name="base-aries-spi-fly" description="Aries SPI Fly" version="${spifly.version}">
       <bundle start-level="35">mvn:org.apache.aries/org.apache.aries.util/1.1.0</bundle>
       <bundle start-level="35">mvn:org.apache.aries.spifly/org.apache.aries.spifly.dynamic.bundle/${spifly.version}</bundle>
       <bundle start-level="35">mvn:org.ow2.asm/asm-all/4.0</bundle>
    </feature>
-     <feature name='base-netty' version='${netty.version}'>
-        <bundle>wrap:mvn:io.netty/netty-buffer/${netty.version}</bundle>
-        <bundle>wrap:mvn:io.netty/netty-codec/${netty.version}</bundle>
-        <bundle>wrap:mvn:io.netty/netty-transport/${netty.version}</bundle>
-        <bundle>wrap:mvn:io.netty/netty-common/${netty.version}</bundle>
-        <bundle>wrap:mvn:io.netty/netty-handler/${netty.version}</bundle>
-        <bundle>wrap:mvn:io.netty/netty-codec-http/${netty.version}</bundle>
-        <bundle>mvn:org.opendaylight.controller.thirdparty/ganymed/1.1-SNAPSHOT</bundle>
-    </feature>
-    <feature name="base-jersey" description="Jersey" version="${jersey.version}">
-        <feature>base-gemini-web</feature>
-        <bundle>mvn:org.opendaylight.controller.thirdparty/com.sun.jersey.jersey-servlet/${jersey.version}</bundle>
-        <bundle>mvn:com.sun.jersey/jersey-server/${jersey.version}</bundle>
-        <bundle>mvn:com.sun.jersey/jersey-core/${jersey.version}</bundle>
-        <bundle>mvn:com.sun.jersey/jersey-client/${jersey.version}</bundle>
-        <bundle>mvn:com.sun.jersey/jersey-servlet/${jersey.version}</bundle>
+   <feature name='base-netty' version='${netty.version}'>
+      <bundle>wrap:mvn:io.netty/netty-buffer/${netty.version}</bundle>
+      <bundle>wrap:mvn:io.netty/netty-codec/${netty.version}</bundle>
+      <bundle>wrap:mvn:io.netty/netty-transport/${netty.version}</bundle>
+      <bundle>wrap:mvn:io.netty/netty-common/${netty.version}</bundle>
+      <bundle>wrap:mvn:io.netty/netty-handler/${netty.version}</bundle>
+      <bundle>wrap:mvn:io.netty/netty-codec-http/${netty.version}</bundle>
+      <bundle>mvn:org.opendaylight.controller.thirdparty/ganymed/1.1-SNAPSHOT</bundle>
+   </feature>
+   <feature name="base-jersey" description="Jersey" version="${jersey.version}">
+      <feature>base-gemini-web</feature>
+      <bundle>mvn:org.opendaylight.controller.thirdparty/com.sun.jersey.jersey-servlet/${jersey.version}</bundle>
+      <bundle>mvn:com.sun.jersey/jersey-server/${jersey.version}</bundle>
+      <bundle>mvn:com.sun.jersey/jersey-core/${jersey.version}</bundle>
+      <bundle>mvn:com.sun.jersey/jersey-client/${jersey.version}</bundle>
+      <bundle>mvn:com.sun.jersey/jersey-servlet/${jersey.version}</bundle>
+      <bundle start="true" start-level="35">mvn:javax.ws.rs/javax.ws.rs-api/2.0</bundle>
    </feature>
+   <feature name="base-jersey2-osgi" description="OSGi friendly Jersey" version="${jersey2.publisher.version}">
+      <feature>http</feature>
+      <bundle>mvn:com.eclipsesource.jaxrs/jersey-all/${jersey2.version}</bundle>
+      <bundle>mvn:com.eclipsesource.jaxrs/publisher/${jersey2.publisher.version}</bundle>
+      <bundle start="true" start-level="35">mvn:javax.ws.rs/javax.ws.rs-api/${jsr311.v2.api.version}</bundle>
+      <bundle>mvn:javax.annotation/javax.annotation-api/${javax.annotation.version}</bundle>
+    </feature>
    <feature name="base-jackson" description="Jackson JAX-RS" version="${jackson.version}">
       <bundle start="true" start-level="35">mvn:com.fasterxml.jackson.core/jackson-annotations/${jackson.version}</bundle>
       <bundle start="true" start-level="35">mvn:com.fasterxml.jackson.core/jackson-core/${jackson.version}</bundle>
       <bundle start="true" start-level="35">mvn:com.fasterxml.jackson.core/jackson-databind/${jackson.version}</bundle>
       <bundle start="true" start-level="35">mvn:org.codehaus.jettison/jettison/${jettison.version}</bundle>
-      <bundle start="true" start-level="35">mvn:javax.ws.rs/jsr311-api/${jsr311.api.version}</bundle>
       <bundle start="true" start-level="35">mvn:com.fasterxml.jackson.module/jackson-module-jaxb-annotations/${jackson.version}</bundle>
       <bundle start="true" start-level="35">mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/${jackson.version}</bundle>
       <bundle start="true" start-level="35">mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/${jackson.version}</bundle>
@@ -66,8 +68,7 @@
       <bundle start-level="35">mvn:org.slf4j/slf4j-simple/1.7.2</bundle>
       <bundle start="true" start-level="35">mvn:org.slf4j/slf4j-api/1.7.2</bundle>
    </feature>
-   <feature name="base-apache-commons" description="Apache Commons Libraries"
-      version="${project.version}">
+   <feature name="base-apache-commons" description="Apache Commons Libraries" version="${project.version}">
       <bundle start="true" start-level="35">mvn:com.google.guava/guava/${guava.version}</bundle>
       <bundle start="true" start-level="35">mvn:org.javassist/javassist/${javassist.version}</bundle>
       <bundle start="true" start-level="35">mvn:commons-io/commons-io/${commons.io.version}</bundle>
index c166f668ccd48e9419e79536c7b39523b7f04238..077b452f0ac96623c4b9f445d91445f9cae57cd1 100644 (file)
     <java.version.source>1.7</java.version.source>
     <java.version.target>1.7</java.version.target>
     <javassist.version>3.17.1-GA</javassist.version>
+    <javax.annotation.version>1.2</javax.annotation.version>
     <!-- Third party version -->
     <jersey-servlet.version>1.17</jersey-servlet.version>
     <jersey.version>1.17</jersey.version>
+    <jersey2.publisher.version>4.0</jersey2.publisher.version>
+    <jersey2.version>2.8</jersey2.version>
     <jettison.version>1.3.3</jettison.version>
     <jmxGeneratorPath>src/main/yang-gen-config</jmxGeneratorPath>
     <jolokia.version>1.1.4</jolokia.version>
     <jsr305.api.version>2.0.1</jsr305.api.version>
     <jsr311.api.version>1.1.1</jsr311.api.version>
+    <jsr311.v2.api.version>2.0</jsr311.v2.api.version>
     <junit.version>4.8.1</junit.version>
     <karaf.version>3.0.1</karaf.version>
     <logback.version>1.0.9</logback.version>
     <mdsal.version>1.1-SNAPSHOT</mdsal.version>
     <mockito.version>1.9.5</mockito.version>
     <netconf.version>0.2.5-SNAPSHOT</netconf.version>
-    <netty.version>4.0.17.Final</netty.version>
+    <netty.version>4.0.19.Final</netty.version>
     <networkconfig.bridgedomain.northbound.version>0.0.3-SNAPSHOT</networkconfig.bridgedomain.northbound.version>
     <networkconfig.neutron.implementation.version>0.4.2-SNAPSHOT</networkconfig.neutron.implementation.version>
     <networkconfig.neutron.northbound.version>0.4.2-SNAPSHOT</networkconfig.neutron.northbound.version>
         <version>${netconf.version}</version>
         <type>test-jar</type>
       </dependency>
+      <dependency>
+        <groupId>org.opendaylight.controller</groupId>
+        <artifactId>netconf-tcp</artifactId>
+        <version>${netconf.version}</version>
+      </dependency>
       <dependency>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>netconf-util</artifactId>
index 916ef9a88befa87ac8b5c17902ec6d970f23807d..fef2c7196948c007705b4444a5c9445065618648 100644 (file)
@@ -7,12 +7,16 @@
  */
 package org.opendaylight.protocol.framework;
 
+import com.google.common.base.Preconditions;
 import io.netty.bootstrap.Bootstrap;
 import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelOption;
 import io.netty.channel.EventLoopGroup;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.local.LocalServerChannel;
 import io.netty.channel.socket.SocketChannel;
 import io.netty.channel.socket.nio.NioServerSocketChannel;
 import io.netty.channel.socket.nio.NioSocketChannel;
@@ -21,22 +25,20 @@ import io.netty.util.concurrent.EventExecutor;
 import io.netty.util.concurrent.Future;
 import io.netty.util.concurrent.GlobalEventExecutor;
 import io.netty.util.concurrent.Promise;
-
 import java.io.Closeable;
 import java.net.InetSocketAddress;
-
+import java.net.SocketAddress;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Preconditions;
-
 /**
  * Dispatcher class for creating servers and clients. The idea is to first create servers and clients and the run the
  * start method that will handle sockets in different thread.
  */
 public abstract class AbstractDispatcher<S extends ProtocolSession<?>, L extends SessionListener<?, ?, ?>> implements Closeable {
 
-    protected interface PipelineInitializer<S extends ProtocolSession<?>> {
+
+    protected interface ChannelPipelineInitializer<CH extends Channel, S extends ProtocolSession<?>> {
         /**
          * Initializes channel by specifying the handlers in its pipeline. Handlers are protocol specific, therefore this
          * method needs to be implemented in protocol specific Dispatchers.
@@ -44,7 +46,11 @@ public abstract class AbstractDispatcher<S extends ProtocolSession<?>, L extends
          * @param channel whose pipeline should be defined, also to be passed to {@link SessionNegotiatorFactory}
          * @param promise to be passed to {@link SessionNegotiatorFactory}
          */
-        void initializeChannel(SocketChannel channel, Promise<S> promise);
+        void initializeChannel(CH channel, Promise<S> promise);
+    }
+
+    protected interface PipelineInitializer<S extends ProtocolSession<?>> extends ChannelPipelineInitializer<SocketChannel, S> {
+
     }
 
 
@@ -76,25 +82,43 @@ public abstract class AbstractDispatcher<S extends ProtocolSession<?>, L extends
      * @return ChannelFuture representing the binding process
      */
     protected ChannelFuture createServer(final InetSocketAddress address, final PipelineInitializer<S> initializer) {
+        return createServer(address, NioServerSocketChannel.class, initializer);
+    }
+
+    /**
+     * Creates server. Each server needs factories to pass their instances to client sessions.
+     *
+     * @param address address to which the server should be bound
+     * @param channelClass The {@link Class} which is used to create {@link Channel} instances from.
+     * @param initializer instance of PipelineInitializer used to initialize the channel pipeline
+     *
+     * @return ChannelFuture representing the binding process
+     */
+    protected <CH extends Channel> ChannelFuture createServer(SocketAddress address, Class<? extends ServerChannel> channelClass,
+                                                              final ChannelPipelineInitializer<CH, S> initializer) {
         final ServerBootstrap b = new ServerBootstrap();
-        b.childHandler(new ChannelInitializer<SocketChannel>() {
+        b.childHandler(new ChannelInitializer<CH>() {
 
             @Override
-            protected void initChannel(final SocketChannel ch) {
+            protected void initChannel(final CH ch) {
                 initializer.initializeChannel(ch, new DefaultPromise<S>(executor));
             }
         });
 
         b.option(ChannelOption.SO_BACKLOG, 128);
-        b.childOption(ChannelOption.SO_KEEPALIVE, true);
+        if (LocalServerChannel.class.equals(channelClass) == false) {
+            // makes no sense for LocalServer and produces warning
+            b.childOption(ChannelOption.SO_KEEPALIVE, true);
+        }
         customizeBootstrap(b);
 
         if (b.group() == null) {
             b.group(bossGroup, workerGroup);
         }
         try {
-            b.channel(NioServerSocketChannel.class);
+            b.channel(channelClass);
         } catch (IllegalStateException e) {
+            // FIXME: if this is ok, document why
             LOG.trace("Not overriding channelFactory on bootstrap {}", b, e);
         }
 
index d9c9dada6202be0a4eac69d138329cb5f69c745f..75323d256e73de143a35c75bbfe9df1faf806d24 100644 (file)
@@ -7,6 +7,14 @@
  */
 package org.opendaylight.controller.config.yang.logback.config;
 
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.matchers.JUnitMatchers.containsString;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import javax.management.ObjectName;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -16,15 +24,6 @@ import org.opendaylight.controller.config.manager.impl.AbstractConfigTest;
 import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver;
 import org.opendaylight.controller.config.util.ConfigTransactionJMXClient;
 
-import javax.management.ObjectName;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-import static org.junit.matchers.JUnitMatchers.containsString;
-
 public class LogbackModuleTest extends AbstractConfigTest {
 
     private static final String INSTANCE_NAME = "singleton";
@@ -89,7 +88,7 @@ public class LogbackModuleTest extends AbstractConfigTest {
         assertBeanCount(1, factory.getImplementationName());
 
         ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction();
-        transaction.destroyConfigBean(factory.getImplementationName(), INSTANCE_NAME);
+        transaction.destroyModule(factory.getImplementationName(), INSTANCE_NAME);
         CommitStatus status = transaction.commit();
 
         assertBeanCount(0, factory.getImplementationName());
index c95661d9c95b7470ca45156b56c6bc2df3fe7fe6..62b295be8d1c53e8b648f7c352ab9b0c5244e875 100644 (file)
@@ -7,6 +7,19 @@
  */
 package org.opendaylight.controller.config.threadpool.fixed;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.matchers.JUnitMatchers.containsString;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.util.ArrayList;
+import java.util.List;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectName;
 import org.junit.Before;
 import org.junit.Test;
 import org.opendaylight.controller.config.api.ConflictingVersionException;
@@ -19,16 +32,11 @@ import org.opendaylight.controller.config.yang.threadpool.impl.NamingThreadFacto
 import org.opendaylight.controller.config.yang.threadpool.impl.NamingThreadFactoryModuleMXBean;
 import org.opendaylight.controller.config.yang.threadpool.impl.fixed.FixedThreadPoolModuleFactory;
 import org.opendaylight.controller.config.yang.threadpool.impl.fixed.FixedThreadPoolModuleMXBean;
-
-import javax.management.InstanceAlreadyExistsException;
-import javax.management.InstanceNotFoundException;
-import javax.management.ObjectName;
-
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-import static org.junit.matchers.JUnitMatchers.containsString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class FixedThreadPoolConfigBeanTest extends AbstractConfigTest {
+    private static final Logger logger = LoggerFactory.getLogger(FixedThreadPoolConfigBeanTest.class);
 
     private FixedThreadPoolModuleFactory factory;
     private final String nameInstance = "fixedInstance";
@@ -36,7 +44,7 @@ public class FixedThreadPoolConfigBeanTest extends AbstractConfigTest {
     @Before
     public void setUp() {
         factory = new FixedThreadPoolModuleFactory();
-        super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(mockedContext,factory,
+        super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(mockedContext, factory,
                 new NamingThreadFactoryModuleFactory()));
     }
 
@@ -44,7 +52,7 @@ public class FixedThreadPoolConfigBeanTest extends AbstractConfigTest {
     public void testCreateBean() throws InstanceAlreadyExistsException, ValidationException,
             ConflictingVersionException {
         ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction();
-        createFixed(transaction, nameInstance, 2);
+        createFixed(transaction, nameInstance, 2, nameInstance);
 
         transaction.validateConfig();
         CommitStatus status = transaction.commit();
@@ -57,7 +65,7 @@ public class FixedThreadPoolConfigBeanTest extends AbstractConfigTest {
     public void testReusingOldInstance() throws InstanceAlreadyExistsException, ConflictingVersionException,
             ValidationException {
         ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction();
-        createFixed(transaction, nameInstance, 4);
+        createFixed(transaction, nameInstance, 4, nameInstance);
 
         transaction.validateConfig();
         transaction.commit();
@@ -75,12 +83,12 @@ public class FixedThreadPoolConfigBeanTest extends AbstractConfigTest {
     public void testNegative() throws ConflictingVersionException, ValidationException, InstanceAlreadyExistsException {
         ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction();
 
-        createFixed(transaction, nameInstance, 5);
+        createFixed(transaction, nameInstance, 5, nameInstance);
         transaction.commit();
 
         transaction = configRegistryClient.createTransaction();
         try {
-            createFixed(transaction, nameInstance, 0);
+            createFixed(transaction, nameInstance, 0, nameInstance);
             fail();
         } catch (InstanceAlreadyExistsException e) {
             assertThat(
@@ -89,26 +97,56 @@ public class FixedThreadPoolConfigBeanTest extends AbstractConfigTest {
         }
     }
 
+    private int countThreadsByPrefix(String prefix) {
+        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
+        int result = 0;
+        List<String> names = new ArrayList<>();
+        for (ThreadInfo threadInfo : threadMXBean.dumpAllThreads(false, false)) {
+            names.add(threadInfo.getThreadName());
+            if (threadInfo.getThreadName().startsWith(prefix)) {
+                result++;
+            }
+        }
+        logger.info("Current threads {}", names);
+        return result;
+    }
+
     @Test
     public void testDestroy() throws InstanceAlreadyExistsException, ValidationException, ConflictingVersionException,
-            InstanceNotFoundException {
+            InstanceNotFoundException, InterruptedException {
+
+        String prefix = org.apache.commons.lang3.RandomStringUtils.randomAlphabetic(10);
+
+        int numberOfThreads = 100;
+        int threadCount1 = countThreadsByPrefix(prefix);
+        assertEquals(0, threadCount1);
         ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction();
-        createFixed(transaction, nameInstance, 1);
 
+        createFixed(transaction, nameInstance, numberOfThreads, prefix);
         transaction.commit();
+        int threadCount2 = countThreadsByPrefix(prefix);
+        assertEquals(numberOfThreads, threadCount2);
 
         transaction = configRegistryClient.createTransaction();
-        transaction.destroyConfigBean(factory.getImplementationName(), nameInstance);
+        transaction.destroyModule(factory.getImplementationName(), nameInstance);
         CommitStatus status = transaction.commit();
 
         assertBeanCount(0, factory.getImplementationName());
         assertStatus(status, 0, 0, 1);
+
+        for (int i = 0; i < 60; i++) {
+            if (countThreadsByPrefix(prefix) == 0) {
+                return;
+            }
+            Thread.sleep(1000);
+        }
+        assertEquals(0, countThreadsByPrefix(prefix));
     }
 
     @Test
     public void testValidationException() throws InstanceAlreadyExistsException {
         ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction();
-        createFixed(transaction, nameInstance, -1);
+        createFixed(transaction, nameInstance, -1, nameInstance);
         try {
             transaction.validateConfig();
             fail();
@@ -117,7 +155,7 @@ public class FixedThreadPoolConfigBeanTest extends AbstractConfigTest {
         }
     }
 
-    private ObjectName createFixed(ConfigTransactionJMXClient transaction, String name, int numberOfThreads)
+    private ObjectName createFixed(ConfigTransactionJMXClient transaction, String name, int numberOfThreads, String prefix)
             throws InstanceAlreadyExistsException {
         ObjectName nameCreated = transaction.createModule(factory.getImplementationName(), name);
         FixedThreadPoolModuleMXBean mxBean = transaction.newMXBeanProxy(nameCreated, FixedThreadPoolModuleMXBean.class);
@@ -126,7 +164,7 @@ public class FixedThreadPoolConfigBeanTest extends AbstractConfigTest {
         ObjectName threadFactoryON = transaction.createModule(NamingThreadFactoryModuleFactory.NAME, "naming");
         NamingThreadFactoryModuleMXBean namingThreadFactoryModuleMXBean = transaction.newMXBeanProxy(threadFactoryON,
                 NamingThreadFactoryModuleMXBean.class);
-        namingThreadFactoryModuleMXBean.setNamePrefix("prefix");
+        namingThreadFactoryModuleMXBean.setNamePrefix(prefix);
 
         mxBean.setThreadFactory(threadFactoryON);
 
index ef06e43d2f291b87f994d04612c0f9f5488ea86b..0fc2fd6eb3d0c5aa9c0bd4d0c4d45611417f4887 100644 (file)
@@ -7,6 +7,15 @@
  */
 package org.opendaylight.controller.config.threadpool.scheduled;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.matchers.JUnitMatchers.containsString;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectName;
 import org.junit.Before;
 import org.junit.Test;
 import org.opendaylight.controller.config.api.ConflictingVersionException;
@@ -20,16 +29,6 @@ import org.opendaylight.controller.config.yang.threadpool.impl.NamingThreadFacto
 import org.opendaylight.controller.config.yang.threadpool.impl.scheduled.ScheduledThreadPoolModuleFactory;
 import org.opendaylight.controller.config.yang.threadpool.impl.scheduled.ScheduledThreadPoolModuleMXBean;
 
-import javax.management.InstanceAlreadyExistsException;
-import javax.management.InstanceNotFoundException;
-import javax.management.ObjectName;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.matchers.JUnitMatchers.containsString;
-
 public class ScheduledThreadPoolConfigBeanTest extends AbstractConfigTest {
 
     private ScheduledThreadPoolModuleFactory factory;
@@ -103,7 +102,7 @@ public class ScheduledThreadPoolConfigBeanTest extends AbstractConfigTest {
         transaction.commit();
 
         transaction = configRegistryClient.createTransaction();
-        transaction.destroyConfigBean(factory.getImplementationName(), instanceName);
+        transaction.destroyModule(factory.getImplementationName(), instanceName);
         CommitStatus status = transaction.commit();
 
         assertBeanCount(0, factory.getImplementationName());
diff --git a/opendaylight/distribution/opendaylight-karaf/src/main/resources/etc/jre.properties b/opendaylight/distribution/opendaylight-karaf/src/main/resources/etc/jre.properties
new file mode 100644 (file)
index 0000000..e91da89
--- /dev/null
@@ -0,0 +1,503 @@
+################################################################################
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You under the Apache License, Version 2.0
+#    (the "License"); you may not use this file except in compliance with
+#    the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+################################################################################
+
+#
+# Java platform package export properties.
+#
+
+# Standard package set.  Note that:
+#   - javax.transaction* is exported with a mandatory attribute
+jre-1.6= \
+ javax.accessibility, \
+ javax.activation;version="1.1", \
+ javax.activity, \
+ javax.crypto, \
+ javax.crypto.interfaces, \
+ javax.crypto.spec, \
+ javax.imageio, \
+ javax.imageio.event, \
+ javax.imageio.metadata, \
+ javax.imageio.plugins.bmp, \
+ javax.imageio.plugins.jpeg, \
+ javax.imageio.spi, \
+ javax.imageio.stream, \
+ javax.jws, \
+ javax.jws.soap, \
+ javax.lang.model, \
+ javax.lang.model.element, \
+ javax.lang.model.type, \
+ javax.lang.model.util, \
+ javax.management, \
+ javax.management.loading, \
+ javax.management.modelmbean, \
+ javax.management.monitor, \
+ javax.management.openmbean, \
+ javax.management.relation, \
+ javax.management.remote, \
+ javax.management.remote.rmi, \
+ javax.management.timer, \
+ javax.naming, \
+ javax.naming.directory, \
+ javax.naming.event, \
+ javax.naming.ldap, \
+ javax.naming.spi, \
+ javax.net, \
+ javax.net.ssl, \
+ javax.print, \
+ javax.print.attribute, \
+ javax.print.attribute.standard, \
+ javax.print.event, \
+ javax.rmi, \
+ javax.rmi.CORBA, \
+ javax.rmi.ssl, \
+ javax.script, \
+ javax.security.auth, \
+ javax.security.auth.callback, \
+ javax.security.auth.kerberos, \
+ javax.security.auth.login, \
+ javax.security.auth.spi, \
+ javax.security.auth.x500, \
+ javax.security.cert, \
+ javax.security.sasl, \
+ javax.sound.midi, \
+ javax.sound.midi.spi, \
+ javax.sound.sampled, \
+ javax.sound.sampled.spi, \
+ javax.sql, \
+ javax.sql.rowset, \
+ javax.sql.rowset.serial, \
+ javax.sql.rowset.spi, \
+ javax.swing, \
+ javax.swing.border, \
+ javax.swing.colorchooser, \
+ javax.swing.event, \
+ javax.swing.filechooser, \
+ javax.swing.plaf, \
+ javax.swing.plaf.basic, \
+ javax.swing.plaf.metal, \
+ javax.swing.plaf.multi, \
+ javax.swing.plaf.synth, \
+ javax.swing.table, \
+ javax.swing.text, \
+ javax.swing.text.html, \
+ javax.swing.text.html.parser, \
+ javax.swing.text.rtf, \
+ javax.swing.tree, \
+ javax.swing.undo, \
+ javax.tools, \
+ javax.transaction; javax.transaction.xa; partial=true; mandatory:=partial, \
+ javax.xml, \
+ javax.xml.bind;version="2.2.1", \
+ javax.xml.bind.annotation;version="2.2.1", \
+ javax.xml.bind.annotation.adapters;version="2.2.1", \
+ javax.xml.bind.attachment;version="2.2.1", \
+ javax.xml.bind.helpers;version="2.2.1", \
+ javax.xml.bind.util;version="2.2.1", \
+ javax.xml.crypto, \
+ javax.xml.crypto.dom, \
+ javax.xml.crypto.dsig, \
+ javax.xml.crypto.dsig.dom, \
+ javax.xml.crypto.dsig.keyinfo, \
+ javax.xml.crypto.dsig.spec, \
+ javax.xml.datatype, \
+ javax.xml.namespace, \
+ javax.xml.parsers, \
+ javax.xml.soap;version="1.3", \
+ javax.xml.stream;version="1.2", \
+ javax.xml.stream.events;version="1.2", \
+ javax.xml.stream.util;version="1.2", \
+ javax.xml.transform, \
+ javax.xml.transform.dom, \
+ javax.xml.transform.sax, \
+ javax.xml.transform.stax, \
+ javax.xml.transform.stream, \
+ javax.xml.validation, \
+ javax.xml.ws;version="2.2", \
+ javax.xml.ws.handler;version="2.2", \
+ javax.xml.ws.handler.soap;version="2.2", \
+ javax.xml.ws.http;version="2.2", \
+ javax.xml.ws.soap;version="2.2", \
+ javax.xml.ws.spi;version="2.2", \
+ javax.xml.ws.wsaddressing;version="2.2", \
+ javax.xml.ws.spi.http;version="2.2", \
+ javax.xml.xpath, \
+ org.ietf.jgss, \
+ org.omg.CORBA, \
+ org.omg.CORBA_2_3, \
+ org.omg.CORBA_2_3.portable, \
+ org.omg.CORBA.DynAnyPackage, \
+ org.omg.CORBA.ORBPackage, \
+ org.omg.CORBA.portable, \
+ org.omg.CORBA.TypeCodePackage, \
+ org.omg.CosNaming, \
+ org.omg.CosNaming.NamingContextExtPackage, \
+ org.omg.CosNaming.NamingContextPackage, \
+ org.omg.Dynamic, \
+ org.omg.DynamicAny, \
+ org.omg.DynamicAny.DynAnyFactoryPackage, \
+ org.omg.DynamicAny.DynAnyPackage, \
+ org.omg.IOP, \
+ org.omg.IOP.CodecFactoryPackage, \
+ org.omg.IOP.CodecPackage, \
+ org.omg.Messaging, \
+ org.omg.PortableInterceptor, \
+ org.omg.PortableInterceptor.ORBInitInfoPackage, \
+ org.omg.PortableServer, \
+ org.omg.PortableServer.CurrentPackage, \
+ org.omg.PortableServer.POAManagerPackage, \
+ org.omg.PortableServer.POAPackage, \
+ org.omg.PortableServer.portable, \
+ org.omg.PortableServer.ServantLocatorPackage, \
+ org.omg.SendingContext, \
+ org.omg.stub.java.rmi, \
+ org.omg.stub.javax.management.remote.rmi, \
+ org.w3c.dom, \
+ org.w3c.dom.bootstrap, \
+ org.w3c.dom.css, \
+ org.w3c.dom.events, \
+ org.w3c.dom.html, \
+ org.w3c.dom.ls, \
+ org.w3c.dom.ranges, \
+ org.w3c.dom.stylesheets, \
+ org.w3c.dom.traversal, \
+ org.w3c.dom.views, \
+ org.w3c.dom.xpath, \
+ org.xml.sax, \
+ org.xml.sax.ext, \
+ org.xml.sax.helpers
+
+# Standard package set.  Note that:
+#   - javax.transaction* is exported with a mandatory attribute
+jre-1.7= \
+ javax.accessibility, \
+ javax.activation;version="1.1", \
+ javax.activity, \
+ javax.crypto, \
+ javax.crypto.interfaces, \
+ javax.crypto.spec, \
+ javax.imageio, \
+ javax.imageio.event, \
+ javax.imageio.metadata, \
+ javax.imageio.plugins.bmp, \
+ javax.imageio.plugins.jpeg, \
+ javax.imageio.spi, \
+ javax.imageio.stream, \
+ javax.jws, \
+ javax.jws.soap, \
+ javax.lang.model, \
+ javax.lang.model.element, \
+ javax.lang.model.type, \
+ javax.lang.model.util, \
+ javax.management, \
+ javax.management.loading, \
+ javax.management.modelmbean, \
+ javax.management.monitor, \
+ javax.management.openmbean, \
+ javax.management.relation, \
+ javax.management.remote, \
+ javax.management.remote.rmi, \
+ javax.management.timer, \
+ javax.naming, \
+ javax.naming.directory, \
+ javax.naming.event, \
+ javax.naming.ldap, \
+ javax.naming.spi, \
+ javax.net, \
+ javax.net.ssl, \
+ javax.print, \
+ javax.print.attribute, \
+ javax.print.attribute.standard, \
+ javax.print.event, \
+ javax.rmi, \
+ javax.rmi.CORBA, \
+ javax.rmi.ssl, \
+ javax.script, \
+ javax.security.auth, \
+ javax.security.auth.callback, \
+ javax.security.auth.kerberos, \
+ javax.security.auth.login, \
+ javax.security.auth.spi, \
+ javax.security.auth.x500, \
+ javax.security.cert, \
+ javax.security.sasl, \
+ javax.sound.midi, \
+ javax.sound.midi.spi, \
+ javax.sound.sampled, \
+ javax.sound.sampled.spi, \
+ javax.sql, \
+ javax.sql.rowset, \
+ javax.sql.rowset.serial, \
+ javax.sql.rowset.spi, \
+ javax.swing, \
+ javax.swing.border, \
+ javax.swing.colorchooser, \
+ javax.swing.event, \
+ javax.swing.filechooser, \
+ javax.swing.plaf, \
+ javax.swing.plaf.basic, \
+ javax.swing.plaf.metal, \
+ javax.swing.plaf.multi, \
+ javax.swing.plaf.synth, \
+ javax.swing.table, \
+ javax.swing.text, \
+ javax.swing.text.html, \
+ javax.swing.text.html.parser, \
+ javax.swing.text.rtf, \
+ javax.swing.tree, \
+ javax.swing.undo, \
+ javax.tools, \
+ javax.transaction; javax.transaction.xa; partial=true; mandatory:=partial, \
+ javax.xml, \
+ javax.xml.bind;version="2.2.1", \
+ javax.xml.bind.annotation;version="2.2.1", \
+ javax.xml.bind.annotation.adapters;version="2.2.1", \
+ javax.xml.bind.attachment;version="2.2.1", \
+ javax.xml.bind.helpers;version="2.2.1", \
+ javax.xml.bind.util;version="2.2.1", \
+ javax.xml.crypto, \
+ javax.xml.crypto.dom, \
+ javax.xml.crypto.dsig, \
+ javax.xml.crypto.dsig.dom, \
+ javax.xml.crypto.dsig.keyinfo, \
+ javax.xml.crypto.dsig.spec, \
+ javax.xml.datatype, \
+ javax.xml.namespace, \
+ javax.xml.parsers, \
+ javax.xml.soap;version="1.3", \
+ javax.xml.stream;version="1.2", \
+ javax.xml.stream.events;version="1.2", \
+ javax.xml.stream.util;version="1.2", \
+ javax.xml.transform, \
+ javax.xml.transform.dom, \
+ javax.xml.transform.sax, \
+ javax.xml.transform.stax, \
+ javax.xml.transform.stream, \
+ javax.xml.validation, \
+ javax.xml.ws;version="2.2", \
+ javax.xml.ws.handler;version="2.2", \
+ javax.xml.ws.handler.soap;version="2.2", \
+ javax.xml.ws.http;version="2.2", \
+ javax.xml.ws.soap;version="2.2", \
+ javax.xml.ws.spi;version="2.2", \
+ javax.xml.ws.wsaddressing;version="2.2", \
+ javax.xml.ws.spi.http;version="2.2", \
+ javax.xml.xpath, \
+ org.ietf.jgss, \
+ org.omg.CORBA, \
+ org.omg.CORBA_2_3, \
+ org.omg.CORBA_2_3.portable, \
+ org.omg.CORBA.DynAnyPackage, \
+ org.omg.CORBA.ORBPackage, \
+ org.omg.CORBA.portable, \
+ org.omg.CORBA.TypeCodePackage, \
+ org.omg.CosNaming, \
+ org.omg.CosNaming.NamingContextExtPackage, \
+ org.omg.CosNaming.NamingContextPackage, \
+ org.omg.Dynamic, \
+ org.omg.DynamicAny, \
+ org.omg.DynamicAny.DynAnyFactoryPackage, \
+ org.omg.DynamicAny.DynAnyPackage, \
+ org.omg.IOP, \
+ org.omg.IOP.CodecFactoryPackage, \
+ org.omg.IOP.CodecPackage, \
+ org.omg.Messaging, \
+ org.omg.PortableInterceptor, \
+ org.omg.PortableInterceptor.ORBInitInfoPackage, \
+ org.omg.PortableServer, \
+ org.omg.PortableServer.CurrentPackage, \
+ org.omg.PortableServer.POAManagerPackage, \
+ org.omg.PortableServer.POAPackage, \
+ org.omg.PortableServer.portable, \
+ org.omg.PortableServer.ServantLocatorPackage, \
+ org.omg.SendingContext, \
+ org.omg.stub.java.rmi, \
+ org.omg.stub.javax.management.remote.rmi, \
+ org.w3c.dom, \
+ org.w3c.dom.bootstrap, \
+ org.w3c.dom.css, \
+ org.w3c.dom.events, \
+ org.w3c.dom.html, \
+ org.w3c.dom.ls, \
+ org.w3c.dom.ranges, \
+ org.w3c.dom.stylesheets, \
+ org.w3c.dom.traversal, \
+ org.w3c.dom.views, \
+ org.w3c.dom.xpath, \
+ org.xml.sax, \
+ org.xml.sax.ext, \
+ org.xml.sax.helpers
+
+jre-1.8= \
+ javax.accessibility, \
+ javax.activation;version="1.1", \
+ javax.activity, \
+ javax.crypto, \
+ javax.crypto.interfaces, \
+ javax.crypto.spec, \
+ javax.imageio, \
+ javax.imageio.event, \
+ javax.imageio.metadata, \
+ javax.imageio.plugins.bmp, \
+ javax.imageio.plugins.jpeg, \
+ javax.imageio.spi, \
+ javax.imageio.stream, \
+ javax.jws, \
+ javax.jws.soap, \
+ javax.lang.model, \
+ javax.lang.model.element, \
+ javax.lang.model.type, \
+ javax.lang.model.util, \
+ javax.management, \
+ javax.management.loading, \
+ javax.management.modelmbean, \
+ javax.management.monitor, \
+ javax.management.openmbean, \
+ javax.management.relation, \
+ javax.management.remote, \
+ javax.management.remote.rmi, \
+ javax.management.timer, \
+ javax.naming, \
+ javax.naming.directory, \
+ javax.naming.event, \
+ javax.naming.ldap, \
+ javax.naming.spi, \
+ javax.net, \
+ javax.net.ssl, \
+ javax.print, \
+ javax.print.attribute, \
+ javax.print.attribute.standard, \
+ javax.print.event, \
+ javax.rmi, \
+ javax.rmi.CORBA, \
+ javax.rmi.ssl, \
+ javax.script, \
+ javax.security.auth, \
+ javax.security.auth.callback, \
+ javax.security.auth.kerberos, \
+ javax.security.auth.login, \
+ javax.security.auth.spi, \
+ javax.security.auth.x500, \
+ javax.security.cert, \
+ javax.security.sasl, \
+ javax.sound.midi, \
+ javax.sound.midi.spi, \
+ javax.sound.sampled, \
+ javax.sound.sampled.spi, \
+ javax.sql, \
+ javax.sql.rowset, \
+ javax.sql.rowset.serial, \
+ javax.sql.rowset.spi, \
+ javax.swing, \
+ javax.swing.border, \
+ javax.swing.colorchooser, \
+ javax.swing.event, \
+ javax.swing.filechooser, \
+ javax.swing.plaf, \
+ javax.swing.plaf.basic, \
+ javax.swing.plaf.metal, \
+ javax.swing.plaf.multi, \
+ javax.swing.plaf.synth, \
+ javax.swing.table, \
+ javax.swing.text, \
+ javax.swing.text.html, \
+ javax.swing.text.html.parser, \
+ javax.swing.text.rtf, \
+ javax.swing.tree, \
+ javax.swing.undo, \
+ javax.tools, \
+ javax.transaction; javax.transaction.xa; partial=true; mandatory:=partial, \
+ javax.xml, \
+ javax.xml.bind;version="2.2.1", \
+ javax.xml.bind.annotation;version="2.2.1", \
+ javax.xml.bind.annotation.adapters;version="2.2.1", \
+ javax.xml.bind.attachment;version="2.2.1", \
+ javax.xml.bind.helpers;version="2.2.1", \
+ javax.xml.bind.util;version="2.2.1", \
+ javax.xml.crypto, \
+ javax.xml.crypto.dom, \
+ javax.xml.crypto.dsig, \
+ javax.xml.crypto.dsig.dom, \
+ javax.xml.crypto.dsig.keyinfo, \
+ javax.xml.crypto.dsig.spec, \
+ javax.xml.datatype, \
+ javax.xml.namespace, \
+ javax.xml.parsers, \
+ javax.xml.soap;version="1.3", \
+ javax.xml.stream;version="1.2", \
+ javax.xml.stream.events;version="1.2", \
+ javax.xml.stream.util;version="1.2", \
+ javax.xml.transform, \
+ javax.xml.transform.dom, \
+ javax.xml.transform.sax, \
+ javax.xml.transform.stax, \
+ javax.xml.transform.stream, \
+ javax.xml.validation, \
+ javax.xml.ws;version="2.2", \
+ javax.xml.ws.handler;version="2.2", \
+ javax.xml.ws.handler.soap;version="2.2", \
+ javax.xml.ws.http;version="2.2", \
+ javax.xml.ws.soap;version="2.2", \
+ javax.xml.ws.spi;version="2.2", \
+ javax.xml.ws.wsaddressing;version="2.2", \
+ javax.xml.ws.spi.http;version="2.2", \
+ javax.xml.xpath, \
+ org.ietf.jgss, \
+ org.omg.CORBA, \
+ org.omg.CORBA_2_3, \
+ org.omg.CORBA_2_3.portable, \
+ org.omg.CORBA.DynAnyPackage, \
+ org.omg.CORBA.ORBPackage, \
+ org.omg.CORBA.portable, \
+ org.omg.CORBA.TypeCodePackage, \
+ org.omg.CosNaming, \
+ org.omg.CosNaming.NamingContextExtPackage, \
+ org.omg.CosNaming.NamingContextPackage, \
+ org.omg.Dynamic, \
+ org.omg.DynamicAny, \
+ org.omg.DynamicAny.DynAnyFactoryPackage, \
+ org.omg.DynamicAny.DynAnyPackage, \
+ org.omg.IOP, \
+ org.omg.IOP.CodecFactoryPackage, \
+ org.omg.IOP.CodecPackage, \
+ org.omg.Messaging, \
+ org.omg.PortableInterceptor, \
+ org.omg.PortableInterceptor.ORBInitInfoPackage, \
+ org.omg.PortableServer, \
+ org.omg.PortableServer.CurrentPackage, \
+ org.omg.PortableServer.POAManagerPackage, \
+ org.omg.PortableServer.POAPackage, \
+ org.omg.PortableServer.portable, \
+ org.omg.PortableServer.ServantLocatorPackage, \
+ org.omg.SendingContext, \
+ org.omg.stub.java.rmi, \
+ org.omg.stub.javax.management.remote.rmi, \
+ org.w3c.dom, \
+ org.w3c.dom.bootstrap, \
+ org.w3c.dom.css, \
+ org.w3c.dom.events, \
+ org.w3c.dom.html, \
+ org.w3c.dom.ls, \
+ org.w3c.dom.ranges, \
+ org.w3c.dom.stylesheets, \
+ org.w3c.dom.traversal, \
+ org.w3c.dom.views, \
+ org.w3c.dom.xpath, \
+ org.xml.sax, \
+ org.xml.sax.ext, \
+ org.xml.sax.helpers
diff --git a/opendaylight/distribution/opendaylight-karaf/src/main/resources/etc/startup.properties b/opendaylight/distribution/opendaylight-karaf/src/main/resources/etc/startup.properties
new file mode 100644 (file)
index 0000000..ca8c83c
--- /dev/null
@@ -0,0 +1,53 @@
+#Bundles to be started on startup, with startlevel
+
+# feature: framework version: 3.0.1
+mvn\:org.ops4j.base/ops4j-base-lang/1.4.0 = 5
+mvn\:biz.aQute.bnd/bndlib/2.2.0 = 5
+mvn\:org.ops4j.pax.swissbox/pax-swissbox-bnd/1.7.0 = 5
+mvn\:org.ops4j.pax.url/pax-url-maven-commons/1.6.0 = 5
+mvn\:org.ops4j.pax.url/pax-url-aether/1.6.0 = 5
+mvn\:org.ops4j.pax.url/pax-url-wrap/1.6.0 = 5
+mvn\:javax.annotation/javax.annotation-api/1.2 = 5
+mvn\:org.ops4j.pax.logging/pax-logging-api/1.7.2 = 8
+mvn\:org.ops4j.pax.logging/pax-logging-service/1.7.2 = 8
+mvn\:org.apache.karaf.service/org.apache.karaf.service.guard/3.0.1 = 10
+mvn\:org.apache.felix/org.apache.felix.configadmin/1.6.0 = 10
+mvn\:org.apache.felix/org.apache.felix.fileinstall/3.2.8 = 11
+mvn\:org.ow2.asm/asm-all/4.1 = 12
+mvn\:org.apache.aries/org.apache.aries.util/1.1.0 = 20
+mvn\:org.apache.aries.proxy/org.apache.aries.proxy.api/1.0.0 = 20
+mvn\:org.apache.aries.proxy/org.apache.aries.proxy.impl/1.0.2 = 20
+mvn\:org.apache.aries.blueprint/org.apache.aries.blueprint.api/1.0.0 = 20
+mvn\:org.apache.aries.blueprint/org.apache.aries.blueprint.cm/1.0.3 = 20
+mvn\:org.apache.aries.blueprint/org.apache.aries.blueprint.core.compatibility/1.0.0 = 20
+mvn\:org.apache.aries.blueprint/org.apache.aries.blueprint.core/1.4.0 = 20
+mvn\:org.apache.karaf.deployer/org.apache.karaf.deployer.spring/3.0.1 = 24
+mvn\:org.apache.karaf.deployer/org.apache.karaf.deployer.blueprint/3.0.1 = 24
+mvn\:org.apache.karaf.deployer/org.apache.karaf.deployer.wrap/3.0.1 = 24
+mvn\:org.apache.karaf.region/org.apache.karaf.region.core/3.0.1 = 25
+mvn\:org.apache.karaf.features/org.apache.karaf.features.core/3.0.1 = 25
+mvn\:org.apache.karaf.deployer/org.apache.karaf.deployer.features/3.0.1 = 26
+mvn\:jline/jline/2.11 = 30
+mvn\:org.jledit/core/0.2.1 = 30
+mvn\:org.fusesource.jansi/jansi/1.11 = 30
+mvn\:org.ops4j.base/ops4j-base-util-property/1.4.0 = 30
+mvn\:org.ops4j.base/ops4j-base-util-xml/1.4.0 = 30
+mvn\:org.ops4j.base/ops4j-base-util-collections/1.4.0 = 30
+mvn\:org.ops4j.pax.url/pax-url-commons/1.6.0 = 30
+mvn\:org.ops4j.pax.swissbox/pax-swissbox-property/1.7.0 = 30
+mvn\:org.ops4j.base/ops4j-base-net/1.4.0 = 30
+mvn\:org.ops4j.base/ops4j-base-monitors/1.4.0 = 30
+mvn\:org.apache.karaf.features/org.apache.karaf.features.command/3.0.1 = 30
+mvn\:org.apache.karaf.shell/org.apache.karaf.shell.console/3.0.1 = 30
+mvn\:org.apache.karaf.jaas/org.apache.karaf.jaas.modules/3.0.1 = 30
+mvn\:org.apache.karaf.jaas/org.apache.karaf.jaas.config/3.0.1 = 30
+mvn\:org.apache.karaf.jaas/org.apache.karaf.jaas.boot/3.0.1 = 30
+mvn\:org.apache.sshd/sshd-core/0.9.0 = 30
+mvn\:org.apache.karaf.bundle/org.apache.karaf.bundle.command/3.0.1 = 30
+mvn\:org.apache.karaf.shell/org.apache.karaf.shell.table/3.0.1 = 30
+mvn\:org.apache.karaf.bundle/org.apache.karaf.bundle.core/3.0.1 = 30
+mvn\:org.apache.karaf.shell/org.apache.karaf.shell.help/3.0.1 = 30
+mvn\:org.apache.karaf.system/org.apache.karaf.system.core/3.0.1 = 30
+mvn\:org.apache.karaf.system/org.apache.karaf.system.command/3.0.1 = 30
+mvn\:org.apache.karaf.shell/org.apache.karaf.shell.commands/3.0.1 = 30
+mvn\:org.apache.aries.quiesce/org.apache.aries.quiesce.api/1.0.0 = 30
index 5b44bb7569b9c3df2bce4c6ef76c4741e7541061..3802370aca3e8a7726d27d7fab1ca67d6eeb1944 100644 (file)
           <groupId>org.opendaylight.controller</groupId>
           <artifactId>netconf-ssh</artifactId>
         </dependency>
+        <dependency>
+          <groupId>org.opendaylight.controller</groupId>
+          <artifactId>netconf-tcp</artifactId>
+        </dependency>
         <dependency>
           <groupId>org.opendaylight.controller</groupId>
           <artifactId>netconf-util</artifactId>
index f15f8f7404d1420d0efc361551c99b0355d60170..f05afbb346f7f157162b313981874c4d3d6bdd40 100644 (file)
@@ -14,13 +14,11 @@ osgi.bundles=\
 
 # Netconf startup configuration
 
-# Netconf tcp address:port is optional with default value 127.0.0.1:8383
+# Netconf tcp address:port is optional
 #netconf.tcp.address=127.0.0.1
-#netconf.tcp.port=8384
-
-#netconf.tcp.client.address=127.0.0.1
-#netconf.tcp.client.port=8384
+#netconf.tcp.port=8383
 
+# Netconf tcp address:port is optional
 netconf.ssh.address=0.0.0.0
 netconf.ssh.port=1830
 netconf.ssh.pk.path = ./configuration/RSA.pk
index 827644676665a1d1576abdbd2fe5caf2d8113a66..1ec4aa2d30bc9da7307891a41050bfc33e55642a 100644 (file)
@@ -11,6 +11,7 @@ import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.RejectedExecutionHandler;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
@@ -24,6 +25,7 @@ import org.opendaylight.controller.sal.binding.spi.NotificationInvokerFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.util.concurrent.ForwardingBlockingQueue;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
@@ -62,41 +64,49 @@ public class SingletonHolder {
                 try {
                     queueSize = Integer.parseInt(queueValue);
                     logger.trace("Queue size was set to {}", queueSize);
-                }catch(NumberFormatException e) {
+                } catch (NumberFormatException e) {
                     logger.warn("Cannot parse {} as set by {}, using default {}", queueValue,
                             NOTIFICATION_QUEUE_SIZE_PROPERTY, queueSize);
                 }
             }
+
             // Overriding the queue:
             // ThreadPoolExecutor would not create new threads if the queue is not full, thus adding
             // occurs in RejectedExecutionHandler.
             // This impl saturates threadpool first, then queue. When both are full caller will get blocked.
-            BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(queueSize) {
-                private static final long serialVersionUID = 1L;
+            final BlockingQueue<Runnable> delegate = new LinkedBlockingQueue<>(queueSize);
+            final BlockingQueue<Runnable> queue = new ForwardingBlockingQueue<Runnable>() {
+                @Override
+                protected BlockingQueue<Runnable> delegate() {
+                    return delegate;
+                }
 
                 @Override
-                public boolean offer(Runnable r) {
-                    // ThreadPoolExecutor will spawn a new thread after core size is reached only if the queue.offer returns false.
+                public boolean offer(final Runnable r) {
+                    // ThreadPoolExecutor will spawn a new thread after core size is reached only
+                    // if the queue.offer returns false.
                     return false;
                 }
             };
 
-            ThreadFactory factory = new ThreadFactoryBuilder().setDaemon(true).setNameFormat("md-sal-binding-notification-%d").build();
+            final ThreadFactory factory = new ThreadFactoryBuilder()
+            .setDaemon(true)
+            .setNameFormat("md-sal-binding-notification-%d")
+            .build();
 
-            ThreadPoolExecutor executor = new ThreadPoolExecutor(CORE_NOTIFICATION_THREADS, MAX_NOTIFICATION_THREADS,
-                    NOTIFICATION_THREAD_LIFE, TimeUnit.SECONDS, queue , factory,
+            final ThreadPoolExecutor executor = new ThreadPoolExecutor(CORE_NOTIFICATION_THREADS, MAX_NOTIFICATION_THREADS,
+                    NOTIFICATION_THREAD_LIFE, TimeUnit.SECONDS, queue, factory,
                     new RejectedExecutionHandler() {
-                        // if the max threads are met, then it will raise a rejectedExecution. We then push to the queue.
-                        @Override
-                        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
-                            try {
-                                executor.getQueue().put(r);
-                            } catch (InterruptedException e) {
-                                Thread.currentThread().interrupt();// set interrupt flag after clearing
-                                throw new IllegalStateException(e);
-                            }
-                        }
-                    });
+                // if the max threads are met, then it will raise a rejectedExecution. We then push to the queue.
+                @Override
+                public void rejectedExecution(final Runnable r, final ThreadPoolExecutor executor) {
+                    try {
+                        executor.getQueue().put(r);
+                    } catch (InterruptedException e) {
+                        throw new RejectedExecutionException("Interrupted while waiting on the queue", e);
+                    }
+                }
+            });
 
             NOTIFICATION_EXECUTOR = MoreExecutors.listeningDecorator(executor);
         }
diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/AbstractDOMStoreTransaction.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/AbstractDOMStoreTransaction.java
new file mode 100644 (file)
index 0000000..8a190c1
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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.md.sal.dom.store.impl;
+
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreTransaction;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.base.Preconditions;
+
+/**
+ * Abstract DOM Store Transaction
+ *
+ * Convenience super implementation of DOM Store transaction which provides
+ * common implementation of {@link #toString()} and {@link #getIdentifier()}.
+ *
+ *
+ */
+abstract class AbstractDOMStoreTransaction implements DOMStoreTransaction {
+    private final Object identifier;
+
+    protected AbstractDOMStoreTransaction(final Object identifier) {
+        this.identifier = Preconditions.checkNotNull(identifier,"Identifier must not be null.");
+    }
+
+    @Override
+    public final Object getIdentifier() {
+        return identifier;
+    }
+
+    @Override
+    public final String toString() {
+        return addToStringAttributes(Objects.toStringHelper(this)).toString();
+    }
+
+    /**
+     * Add class-specific toString attributes.
+     *
+     * @param toStringHelper
+     *            ToStringHelper instance
+     * @return ToStringHelper instance which was passed in
+     */
+    protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
+        return toStringHelper.add("id", identifier);
+    }
+}
\ No newline at end of file
index 87c68596efa60a668a7afe005d482eefac1f6ad7..2495146aa64d290460ab83c1c681a781e760f720 100644 (file)
@@ -7,7 +7,6 @@
  */
 package org.opendaylight.controller.md.sal.dom.store.impl;
 
-import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 
 import java.util.Collections;
@@ -16,6 +15,7 @@ import java.util.concurrent.atomic.AtomicLong;
 
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
+import org.opendaylight.controller.md.sal.dom.store.impl.SnapshotBackedWriteTransaction.TransactionReadyPrototype;
 import org.opendaylight.controller.md.sal.dom.store.impl.tree.DataPreconditionFailedException;
 import org.opendaylight.controller.md.sal.dom.store.impl.tree.DataTree;
 import org.opendaylight.controller.md.sal.dom.store.impl.tree.DataTreeCandidate;
@@ -27,7 +27,6 @@ import org.opendaylight.controller.sal.core.spi.data.DOMStore;
 import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadTransaction;
 import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
 import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort;
-import org.opendaylight.controller.sal.core.spi.data.DOMStoreTransaction;
 import org.opendaylight.controller.sal.core.spi.data.DOMStoreTransactionChain;
 import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction;
 import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
@@ -40,15 +39,22 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Objects;
-import com.google.common.base.Objects.ToStringHelper;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 
-public class InMemoryDOMDataStore implements DOMStore, Identifiable<String>, SchemaContextListener {
+/**
+ * In-memory DOM Data Store
+ *
+ * Implementation of {@link DOMStore} which uses {@link DataTree}
+ * and other classes such as {@link SnapshotBackedWriteTransaction}.
+ * {@link SnapshotBackedReadTransaction} and {@link ResolveDataChangeEventsTask}
+ * to implement {@link DOMStore} contract.
+ *
+ */
+public class InMemoryDOMDataStore implements DOMStore, Identifiable<String>, SchemaContextListener, TransactionReadyPrototype {
     private static final Logger LOG = LoggerFactory.getLogger(InMemoryDOMDataStore.class);
     private final DataTree dataTree = InMemoryDataTreeFactory.getInstance().create();
     private final ListenerTree listenerTree = ListenerTree.create();
@@ -83,7 +89,7 @@ public class InMemoryDOMDataStore implements DOMStore, Identifiable<String>, Sch
 
     @Override
     public DOMStoreTransactionChain createTransactionChain() {
-        throw new UnsupportedOperationException("Not implemented yet.");
+        return new DOMStoreTransactionChainImpl();
     }
 
     @Override
@@ -130,7 +136,8 @@ public class InMemoryDOMDataStore implements DOMStore, Identifiable<String>, Sch
         };
     }
 
-    private synchronized DOMStoreThreePhaseCommitCohort submit(final SnapshotBackedWriteTransaction writeTx) {
+    @Override
+    public synchronized DOMStoreThreePhaseCommitCohort ready(final SnapshotBackedWriteTransaction writeTx) {
         LOG.debug("Tx: {} is submitted. Modifications: {}", writeTx.getIdentifier(), writeTx.getMutatedView());
         return new ThreePhaseCommitImpl(writeTx);
     }
@@ -139,162 +146,61 @@ public class InMemoryDOMDataStore implements DOMStore, Identifiable<String>, Sch
         return name + "-" + txCounter.getAndIncrement();
     }
 
-    private static abstract class AbstractDOMStoreTransaction implements DOMStoreTransaction {
-        private final Object identifier;
-
-        protected AbstractDOMStoreTransaction(final Object identifier) {
-            this.identifier = identifier;
-        }
-
-        @Override
-        public final Object getIdentifier() {
-            return identifier;
-        }
-
-        @Override
-        public final String toString() {
-            return addToStringAttributes(Objects.toStringHelper(this)).toString();
-        }
-
-        /**
-         * Add class-specific toString attributes.
-         *
-         * @param toStringHelper
-         *            ToStringHelper instance
-         * @return ToStringHelper instance which was passed in
-         */
-        protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
-            return toStringHelper.add("id", identifier);
-        }
-    }
-
-    private static final class SnapshotBackedReadTransaction extends AbstractDOMStoreTransaction implements
-    DOMStoreReadTransaction {
-        private DataTreeSnapshot stableSnapshot;
+    private class DOMStoreTransactionChainImpl implements DOMStoreTransactionChain, TransactionReadyPrototype {
 
-        public SnapshotBackedReadTransaction(final Object identifier, final DataTreeSnapshot snapshot) {
-            super(identifier);
-            this.stableSnapshot = Preconditions.checkNotNull(snapshot);
-            LOG.debug("ReadOnly Tx: {} allocated with snapshot {}", identifier, snapshot);
-        }
-
-        @Override
-        public void close() {
-            LOG.debug("Store transaction: {} : Closed", getIdentifier());
-            stableSnapshot = null;
-        }
+        private SnapshotBackedWriteTransaction previousOutstandingTx;
 
         @Override
-        public ListenableFuture<Optional<NormalizedNode<?, ?>>> read(final InstanceIdentifier path) {
-            checkNotNull(path, "Path must not be null.");
-            checkState(stableSnapshot != null, "Transaction is closed");
-            return Futures.immediateFuture(stableSnapshot.readNode(path));
-        }
-    }
-
-    private static class SnapshotBackedWriteTransaction extends AbstractDOMStoreTransaction implements
-    DOMStoreWriteTransaction {
-        private DataTreeModification mutableTree;
-        private InMemoryDOMDataStore store;
-        private boolean ready = false;
-
-        public SnapshotBackedWriteTransaction(final Object identifier, final DataTreeSnapshot snapshot,
-                final InMemoryDOMDataStore store) {
-            super(identifier);
-            mutableTree = snapshot.newModification();
-            this.store = store;
-            LOG.debug("Write Tx: {} allocated with snapshot {}", identifier, snapshot);
-        }
-
-        @Override
-        public void close() {
-            LOG.debug("Store transaction: {} : Closed", getIdentifier());
-            this.mutableTree = null;
-            this.store = null;
-        }
-
-        @Override
-        public void write(final InstanceIdentifier path, final NormalizedNode<?, ?> data) {
-            checkNotReady();
-            try {
-                LOG.trace("Tx: {} Write: {}:{}", getIdentifier(), path, data);
-                mutableTree.write(path, data);
-                // FIXME: Add checked exception
-            } catch (Exception e) {
-                LOG.error("Tx: {}, failed to write {}:{} in {}", getIdentifier(), path, data, mutableTree, e);
+        public synchronized DOMStoreReadTransaction newReadOnlyTransaction() {
+            final DataTreeSnapshot snapshot;
+            if(previousOutstandingTx != null) {
+                checkState(previousOutstandingTx.isReady(), "Previous transaction in chain must be ready.");
+                snapshot = previousOutstandingTx.getMutatedView();
+            } else {
+                snapshot = dataTree.takeSnapshot();
             }
+            return new SnapshotBackedReadTransaction(nextIdentifier(), snapshot);
         }
 
         @Override
-        public void merge(final InstanceIdentifier path, final NormalizedNode<?, ?> data) {
-            checkNotReady();
-            try {
-                LOG.trace("Tx: {} Merge: {}:{}", getIdentifier(), path, data);
-                mutableTree.merge(path, data);
-                // FIXME: Add checked exception
-            } catch (Exception e) {
-                LOG.error("Tx: {}, failed to write {}:{} in {}", getIdentifier(), path, data, mutableTree, e);
+        public synchronized DOMStoreReadWriteTransaction newReadWriteTransaction() {
+            final DataTreeSnapshot snapshot;
+            if(previousOutstandingTx != null) {
+                checkState(previousOutstandingTx.isReady(), "Previous transaction in chain must be ready.");
+                snapshot = previousOutstandingTx.getMutatedView();
+            } else {
+                snapshot = dataTree.takeSnapshot().newModification();
             }
+            SnapshotBackedReadWriteTransaction ret = new SnapshotBackedReadWriteTransaction(nextIdentifier(), snapshot,this);
+            return ret;
         }
 
         @Override
-        public void delete(final InstanceIdentifier path) {
-            checkNotReady();
-            try {
-                LOG.trace("Tx: {} Delete: {}", getIdentifier(), path);
-                mutableTree.delete(path);
-                // FIXME: Add checked exception
-            } catch (Exception e) {
-                LOG.error("Tx: {}, failed to delete {} in {}", getIdentifier(), path, mutableTree, e);
+        public synchronized DOMStoreWriteTransaction newWriteOnlyTransaction() {
+            final DataTreeSnapshot snapshot;
+            if(previousOutstandingTx != null) {
+                checkState(previousOutstandingTx.isReady(), "Previous transaction in chain must be ready.");
+                snapshot = previousOutstandingTx.getMutatedView();
+            } else {
+                snapshot = dataTree.takeSnapshot().newModification();
             }
-        }
-
-        protected final boolean isReady() {
-            return ready;
-        }
-
-        protected final void checkNotReady() {
-            checkState(!ready, "Transaction %s is ready. No further modifications allowed.", getIdentifier());
+            SnapshotBackedWriteTransaction ret =new SnapshotBackedWriteTransaction(nextIdentifier(), snapshot,this);
+            return ret;
         }
 
         @Override
-        public synchronized DOMStoreThreePhaseCommitCohort ready() {
-            checkState(!ready, "Transaction %s is already ready.", getIdentifier());
-            ready = true;
-
-            LOG.debug("Store transaction: {} : Ready", getIdentifier());
-            mutableTree.ready();
-            return store.submit(this);
-        }
-
-        protected DataTreeModification getMutatedView() {
-            return mutableTree;
+        public DOMStoreThreePhaseCommitCohort ready(final SnapshotBackedWriteTransaction tx) {
+            DOMStoreThreePhaseCommitCohort storeCohort = InMemoryDOMDataStore.this.ready(tx);
+            // FIXME: We probably want to add Transaction Chain cohort
+            return storeCohort;
         }
 
         @Override
-        protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
-            return toStringHelper.add("ready", isReady());
-        }
-    }
-
-    private static class SnapshotBackedReadWriteTransaction extends SnapshotBackedWriteTransaction implements
-    DOMStoreReadWriteTransaction {
+        public void close() {
+            // TODO Auto-generated method stub
 
-        protected SnapshotBackedReadWriteTransaction(final Object identifier, final DataTreeSnapshot snapshot,
-                final InMemoryDOMDataStore store) {
-            super(identifier, snapshot, store);
         }
 
-        @Override
-        public ListenableFuture<Optional<NormalizedNode<?, ?>>> read(final InstanceIdentifier path) {
-            LOG.trace("Tx: {} Read: {}", getIdentifier(), path);
-            try {
-                return Futures.immediateFuture(getMutatedView().readNode(path));
-            } catch (Exception e) {
-                LOG.error("Tx: {} Failed Read of {}", getIdentifier(), path, e);
-                throw e;
-            }
-        }
     }
 
     private class ThreePhaseCommitImpl implements DOMStoreThreePhaseCommitCohort {
diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedReadTransaction.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedReadTransaction.java
new file mode 100644 (file)
index 0000000..315293f
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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.md.sal.dom.store.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.DataTreeSnapshot;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadTransaction;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ *
+ * Implementation of read-only transaction backed by {@link DataTreeSnapshot}
+ *
+ * Implementation of read-only transaction backed by {@link DataTreeSnapshot}
+ * which delegates most of its calls to similar methods provided by underlying snapshot.
+ *
+ */
+final class SnapshotBackedReadTransaction extends AbstractDOMStoreTransaction implements
+DOMStoreReadTransaction {
+    private static final Logger LOG = LoggerFactory.getLogger(SnapshotBackedReadTransaction.class);
+    private DataTreeSnapshot stableSnapshot;
+
+    public SnapshotBackedReadTransaction(final Object identifier, final DataTreeSnapshot snapshot) {
+        super(identifier);
+        this.stableSnapshot = Preconditions.checkNotNull(snapshot);
+        LOG.debug("ReadOnly Tx: {} allocated with snapshot {}", identifier, snapshot);
+    }
+
+    @Override
+    public void close() {
+        LOG.debug("Store transaction: {} : Closed", getIdentifier());
+        stableSnapshot = null;
+    }
+
+    @Override
+    public ListenableFuture<Optional<NormalizedNode<?, ?>>> read(final InstanceIdentifier path) {
+        checkNotNull(path, "Path must not be null.");
+        checkState(stableSnapshot != null, "Transaction is closed");
+        return Futures.immediateFuture(stableSnapshot.readNode(path));
+    }
+}
\ No newline at end of file
diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedReadWriteTransaction.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedReadWriteTransaction.java
new file mode 100644 (file)
index 0000000..4abc802
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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.md.sal.dom.store.impl;
+
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.DataTreeSnapshot;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Implementation of Read-Write transaction which is backed by {@link DataTreeSnapshot}
+ * and executed according to {@link TransactionReadyPrototype}.
+ *
+ */
+class SnapshotBackedReadWriteTransaction extends SnapshotBackedWriteTransaction implements
+DOMStoreReadWriteTransaction {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SnapshotBackedReadWriteTransaction.class);
+
+    /**
+     * Creates new read-write transaction.
+     *
+     * @param identifier transaction Identifier
+     * @param snapshot Snapshot which will be modified.
+     * @param readyImpl Implementation of ready method.
+     */
+    protected SnapshotBackedReadWriteTransaction(final Object identifier, final DataTreeSnapshot snapshot,
+            final TransactionReadyPrototype store) {
+        super(identifier, snapshot, store);
+    }
+
+    @Override
+    public ListenableFuture<Optional<NormalizedNode<?, ?>>> read(final InstanceIdentifier path) {
+        LOG.debug("Tx: {} Read: {}", getIdentifier(), path);
+        try {
+            return Futures.immediateFuture(getMutatedView().readNode(path));
+        } catch (Exception e) {
+            LOG.error("Tx: {} Failed Read of {}", getIdentifier(), path, e);
+            throw e;
+        }
+    }
+}
\ No newline at end of file
diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedWriteTransaction.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedWriteTransaction.java
new file mode 100644 (file)
index 0000000..717fb11
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * 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.md.sal.dom.store.impl;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.DataTreeModification;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.DataTreeSnapshot;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+
+/**
+ * Implementation of Write transaction which is backed by
+ * {@link DataTreeSnapshot} and executed according to
+ * {@link TransactionReadyPrototype}.
+ * 
+ */
+class SnapshotBackedWriteTransaction extends AbstractDOMStoreTransaction implements DOMStoreWriteTransaction {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SnapshotBackedWriteTransaction.class);
+    private DataTreeModification mutableTree;
+    private boolean ready = false;
+    private TransactionReadyPrototype readyImpl;
+
+    /**
+     * Creates new write-only transaction.
+     * 
+     * @param identifier
+     *            transaction Identifier
+     * @param snapshot
+     *            Snapshot which will be modified.
+     * @param readyImpl
+     *            Implementation of ready method.
+     */
+    public SnapshotBackedWriteTransaction(final Object identifier, final DataTreeSnapshot snapshot,
+            final TransactionReadyPrototype readyImpl) {
+        super(identifier);
+        mutableTree = snapshot.newModification();
+        this.readyImpl = Preconditions.checkNotNull(readyImpl, "readyImpl must not be null.");
+        LOG.debug("Write Tx: {} allocated with snapshot {}", identifier, snapshot);
+    }
+
+    @Override
+    public void close() {
+        LOG.debug("Store transaction: {} : Closed", getIdentifier());
+        this.mutableTree = null;
+        this.readyImpl = null;
+    }
+
+    @Override
+    public void write(final InstanceIdentifier path, final NormalizedNode<?, ?> data) {
+        checkNotReady();
+        try {
+            LOG.debug("Tx: {} Write: {}:{}", getIdentifier(), path, data);
+            mutableTree.write(path, data);
+            // FIXME: Add checked exception
+        } catch (Exception e) {
+            LOG.error("Tx: {}, failed to write {}:{} in {}", getIdentifier(), path, data, mutableTree, e);
+            // Rethrow original ones if they are subclasses of RuntimeException
+            // or Error
+            Throwables.propagateIfPossible(e);
+            // FIXME: Introduce proper checked exception
+            throw new IllegalArgumentException("Illegal input data.", e);
+        }
+    }
+
+    @Override
+    public void merge(final InstanceIdentifier path, final NormalizedNode<?, ?> data) {
+        checkNotReady();
+        try {
+            LOG.debug("Tx: {} Merge: {}:{}", getIdentifier(), path, data);
+            mutableTree.merge(path, data);
+            // FIXME: Add checked exception
+        } catch (Exception e) {
+            LOG.error("Tx: {}, failed to write {}:{} in {}", getIdentifier(), path, data, mutableTree, e);
+            // Rethrow original ones if they are subclasses of RuntimeException
+            // or Error
+            Throwables.propagateIfPossible(e);
+            // FIXME: Introduce proper checked exception
+            throw new IllegalArgumentException("Illegal input data.", e);
+        }
+    }
+
+    @Override
+    public void delete(final InstanceIdentifier path) {
+        checkNotReady();
+        try {
+            LOG.debug("Tx: {} Delete: {}", getIdentifier(), path);
+            mutableTree.delete(path);
+            // FIXME: Add checked exception
+        } catch (Exception e) {
+            LOG.error("Tx: {}, failed to delete {} in {}", getIdentifier(), path, mutableTree, e);
+            // Rethrow original ones if they are subclasses of RuntimeException
+            // or Error
+            Throwables.propagateIfPossible(e);
+            // FIXME: Introduce proper checked exception
+            throw new IllegalArgumentException("Illegal path to delete.", e);
+        }
+    }
+
+    protected final boolean isReady() {
+        return ready;
+    }
+
+    protected final void checkNotReady() {
+        checkState(!ready, "Transaction %s is ready. No further modifications allowed.", getIdentifier());
+    }
+
+    @Override
+    public synchronized DOMStoreThreePhaseCommitCohort ready() {
+        checkState(!ready, "Transaction %s is already ready.", getIdentifier());
+        ready = true;
+        LOG.debug("Store transaction: {} : Ready", getIdentifier());
+        mutableTree.ready();
+        return readyImpl.ready(this);
+    }
+
+    protected DataTreeModification getMutatedView() {
+        return mutableTree;
+    }
+
+    @Override
+    protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
+        return toStringHelper.add("ready", isReady());
+    }
+
+    /**
+     * Prototype implementation of
+     * {@link #ready(SnapshotBackedWriteTransaction)}
+     * 
+     * This class is intended to be implemented by Transaction factories
+     * responsible for allocation of {@link SnapshotBackedWriteTransaction} and
+     * providing underlying logic for applying implementation.
+     * 
+     */
+    public static interface TransactionReadyPrototype {
+
+        /**
+         * Returns a commit coordinator associated with supplied transactions.
+         * 
+         * This call must not fail.
+         * 
+         * @param tx
+         *            Transaction on which ready was invoked.
+         * @return DOMStoreThreePhaseCommitCohort associated with transaction
+         */
+        DOMStoreThreePhaseCommitCohort ready(SnapshotBackedWriteTransaction tx);
+    }
+}
\ No newline at end of file
index 312365585eaa149aaa05125acc4530840451c8e9..4e32e7058ca6cce63f9a38760764b0b0c137ddf7 100644 (file)
@@ -11,49 +11,127 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.IOException;
 import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import javax.ws.rs.WebApplicationException;
+import java.util.Map;
 
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.opendaylight.controller.sal.rest.impl.StructuredDataToJsonProvider;
 import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider;
+import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
 import org.opendaylight.controller.sal.restconf.impl.test.TestUtils;
 import org.opendaylight.controller.sal.restconf.impl.test.YangAndXmlAndDataSchemaLoader;
 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
 
+import com.google.common.collect.Maps;
 import com.google.gson.stream.JsonReader;
 import com.google.gson.stream.JsonToken;
 
 public class CnSnToJsonBasicDataTypesTest extends YangAndXmlAndDataSchemaLoader {
 
+    static abstract class LeafVerifier {
+
+        Object expectedValue;
+        JsonToken expectedToken;
+
+        LeafVerifier( Object expectedValue, JsonToken expectedToken ) {
+            this.expectedValue = expectedValue;
+            this.expectedToken = expectedToken;
+        }
+
+        abstract Object getActualValue( JsonReader reader ) throws IOException;
+
+        void verify( JsonReader reader, String keyName ) throws IOException {
+            assertEquals( "Json value for key " + keyName, expectedValue, getActualValue( reader ) );
+        }
+
+        JsonToken expectedTokenType() {
+            return expectedToken;
+        }
+    }
+
+    static class BooleanVerifier extends LeafVerifier {
+
+        public BooleanVerifier( boolean expected ) {
+            super( expected, JsonToken.BOOLEAN );
+        }
+
+        @Override
+        Object getActualValue( JsonReader reader ) throws IOException {
+            return reader.nextBoolean();
+        }
+    }
+
+    static class NumberVerifier extends LeafVerifier {
+
+        public NumberVerifier( Number expected ) {
+            super( expected, JsonToken.NUMBER );
+        }
+
+        @Override
+        Object getActualValue( JsonReader reader ) throws IOException {
+            if( expectedValue instanceof Double ) {
+                return reader.nextDouble();
+            }
+            else if( expectedValue instanceof Long ) {
+                return reader.nextLong();
+            }
+            else if( expectedValue instanceof Integer ) {
+                return reader.nextInt();
+            }
+
+            return null;
+        }
+    }
+
+    static class StringVerifier extends LeafVerifier {
+
+        StringVerifier( String expected ) {
+            super( expected, JsonToken.STRING );
+        }
+
+        @Override
+        Object getActualValue( JsonReader reader ) throws IOException {
+            return reader.nextString();
+        }
+    }
+
+    static class EmptyVerifier extends LeafVerifier {
+
+        EmptyVerifier() {
+            super( null, null );
+        }
+
+        @Override
+        Object getActualValue( JsonReader reader ) throws IOException {
+            reader.beginArray();
+            reader.nextNull();
+            reader.endArray();
+            return null;
+        }
+
+    }
+
     @BeforeClass
     public static void initialize() {
         dataLoad("/cnsn-to-json/simple-data-types");
     }
 
     @Test
-    public void simpleYangDataTest() {
+    public void simpleYangDataTest() throws Exception {
 
         CompositeNode compositeNode = TestUtils.readInputToCnSn("/cnsn-to-json/simple-data-types/xml/data.xml",
                 XmlToCompositeNodeProvider.INSTANCE);
 
-        String jsonOutput = null;
-
         TestUtils.normalizeCompositeNode(compositeNode, modules, "simple-data-types:cont");
 
-        try {
-            jsonOutput = TestUtils.writeCompNodeWithSchemaContextToOutput(compositeNode, modules, dataSchemaNode,
+        String jsonOutput = TestUtils.writeCompNodeWithSchemaContextToOutput(compositeNode, modules, dataSchemaNode,
                     StructuredDataToJsonProvider.INSTANCE);
-        } catch (WebApplicationException | IOException e) {
-        }
+
         assertNotNull(jsonOutput);
 
         verifyJsonOutput(jsonOutput);
@@ -88,167 +166,84 @@ public class CnSnToJsonBasicDataTypesTest extends YangAndXmlAndDataSchemaLoader
 
     private void jsonReadContElements(JsonReader jReader) throws IOException {
         jReader.beginObject();
-        List<String> loadedLfs = new ArrayList<>();
-        boolean enumChecked = false;
-        boolean bitsChecked = false;
-        boolean lfdecimal6Checked = false;
-        boolean lfdecimal4Checked = false;
-        boolean lfdecimal3Checked = false;
-        boolean lfdecimal2Checked = false;
-        boolean lfdecimal1Checked = false;
-        boolean lfbool1Checked = false;
-        boolean lfbool2Checked = false;
-        boolean lfstrChecked = false;
-        boolean lfbinaryChecked = false;
-        boolean lfemptyChecked = false;
-        boolean lfstr1Checked = false;
-        boolean lfidentityrefChecked = false;
+
+        Map<String,LeafVerifier> expectedMap = Maps.newHashMap();
+        expectedMap.put( "lfnint8Min", new NumberVerifier( Integer.valueOf( -128 ) ) );
+        expectedMap.put( "lfnint8Max", new NumberVerifier( Integer.valueOf( 127 ) ) );
+        expectedMap.put( "lfnint16Min", new NumberVerifier( Integer.valueOf( -32768 ) ) );
+        expectedMap.put( "lfnint16Max", new NumberVerifier( Integer.valueOf( 32767 ) ) );
+        expectedMap.put( "lfnint32Min", new NumberVerifier( Integer.valueOf( -2147483648 ) ) );
+        expectedMap.put( "lfnint32Max", new NumberVerifier( Long.valueOf( 2147483647 ) ) );
+        expectedMap.put( "lfnint64Min", new NumberVerifier( Long.valueOf( -9223372036854775808L ) ) );
+        expectedMap.put( "lfnint64Max", new NumberVerifier( Long.valueOf( 9223372036854775807L ) ) );
+        expectedMap.put( "lfnuint8Max", new NumberVerifier( Integer.valueOf( 255 ) ) );
+        expectedMap.put( "lfnuint16Max", new NumberVerifier( Integer.valueOf( 65535 ) ) );
+        expectedMap.put( "lfnuint32Max", new NumberVerifier( Long.valueOf( 4294967295L ) ) );
+        expectedMap.put( "lfstr", new StringVerifier( "lfstr" ) );
+        expectedMap.put( "lfstr1", new StringVerifier( "" ) );
+        expectedMap.put( "lfbool1", new BooleanVerifier( true ) );
+        expectedMap.put( "lfbool2", new BooleanVerifier( false ) );
+        expectedMap.put( "lfbool3", new BooleanVerifier( false ) );
+        expectedMap.put( "lfdecimal1", new NumberVerifier( new Double( 43.32 ) ) );
+        expectedMap.put( "lfdecimal2", new NumberVerifier( new Double( -0.43 ) ) );
+        expectedMap.put( "lfdecimal3", new NumberVerifier( new Double( 43 ) ) );
+        expectedMap.put( "lfdecimal4", new NumberVerifier( new Double( 43E3 ) ) );
+        expectedMap.put( "lfdecimal6", new NumberVerifier( new Double( 33.12345 ) ) );
+        expectedMap.put( "lfenum", new StringVerifier( "enum3" ) );
+        expectedMap.put( "lfbits", new StringVerifier( "bit3 bit2" ) );
+        expectedMap.put( "lfbinary", new StringVerifier( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ) );
+        expectedMap.put( "lfunion1", new StringVerifier( "324" ) );
+        expectedMap.put( "lfunion2", new StringVerifier( "33.3" ) );
+        expectedMap.put( "lfunion3", new StringVerifier( "55" ) );
+        expectedMap.put( "lfunion4", new StringVerifier( "true" ) );
+        expectedMap.put( "lfunion5", new StringVerifier( "true" ) );
+        expectedMap.put( "lfunion6", new StringVerifier( "10" ) );
+        expectedMap.put( "lfunion7", new StringVerifier( "" ) );
+        expectedMap.put( "lfunion8", new StringVerifier( "" ) );
+        expectedMap.put( "lfunion9", new StringVerifier( "" ) );
+        expectedMap.put( "lfunion10", new StringVerifier( "bt1" ) );
+        expectedMap.put( "lfunion11", new StringVerifier( "33" ) );
+        expectedMap.put( "lfunion12", new StringVerifier( "false" ) );
+        expectedMap.put( "lfunion13", new StringVerifier( "b1" ) );
+        expectedMap.put( "lfunion14", new StringVerifier( "zero" ) );
+        expectedMap.put( "lfempty", new EmptyVerifier() );
+        expectedMap.put( "identityref1", new StringVerifier( "simple-data-types:iden" ) );
 
         while (jReader.hasNext()) {
             String keyName = jReader.nextName();
-            JsonToken peek = null;
-            try {
-                peek = jReader.peek();
-            } catch (IOException e) {
-                assertTrue("Key " + keyName + " has incorrect value for specifed type", false);
-            }
+            JsonToken peek = jReader.peek();
 
-            if (keyName.startsWith("lfnint") || keyName.startsWith("lfnuint")) {
-                assertEquals("Key " + keyName + " has incorrect type", JsonToken.NUMBER, peek);
-                try {
-                    jReader.nextLong();
-                } catch (NumberFormatException e) {
-                    assertTrue("Key " + keyName + " has incorrect value - " + e.getMessage(), false);
-                }
-                loadedLfs.add(keyName.substring(3));
-            } else if (keyName.equals("identityref1")) {
-                assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-                assertEquals("simple-data-types:iden", jReader.nextString());
-                lfidentityrefChecked = true;
-            } else if (keyName.equals("lfstr")) {
-                assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-                assertEquals("lfstr", jReader.nextString());
-                lfstrChecked = true;
-            } else if (keyName.equals("lfstr1")) {
-                assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-                assertEquals("", jReader.nextString());
-                lfstr1Checked = true;
-            } else if (keyName.equals("lfbool1")) {
-                assertEquals("Key " + keyName + " has incorrect type", JsonToken.BOOLEAN, peek);
-                assertEquals(true, jReader.nextBoolean());
-                lfbool1Checked = true;
-            } else if (keyName.equals("lfbool2")) {
-                assertEquals("Key " + keyName + " has incorrect type", JsonToken.BOOLEAN, peek);
-                assertEquals(false, jReader.nextBoolean());
-                lfbool2Checked = true;
-            } else if (keyName.equals("lfbool3")) {
-                assertEquals("Key " + keyName + " has incorrect type", JsonToken.BOOLEAN, peek);
-                assertEquals(false, jReader.nextBoolean());
-            } else if (keyName.equals("lfdecimal1")) {
-                assertEquals("Key " + keyName + " has incorrect type", JsonToken.NUMBER, peek);
-                assertEquals(new Double(43.32), (Double) jReader.nextDouble());
-                lfdecimal1Checked = true;
-            } else if (keyName.equals("lfdecimal2")) {
-                assertEquals("Key " + keyName + " has incorrect type", JsonToken.NUMBER, peek);
-                assertEquals(new Double(-0.43), (Double) jReader.nextDouble());
-                lfdecimal2Checked = true;
-            } else if (keyName.equals("lfdecimal3")) {
-                assertEquals("Key " + keyName + " has incorrect type", JsonToken.NUMBER, peek);
-                assertEquals(new Double(43), (Double) jReader.nextDouble());
-                lfdecimal3Checked = true;
-            } else if (keyName.equals("lfdecimal4")) {
-                assertEquals("Key " + keyName + " has incorrect type", JsonToken.NUMBER, peek);
-                assertEquals(new Double(43E3), (Double) jReader.nextDouble());
-                lfdecimal4Checked = true;
-            } else if (keyName.equals("lfdecimal6")) {
-                assertEquals("Key " + keyName + " has incorrect type", JsonToken.NUMBER, peek);
-                assertEquals(new Double(33.12345), (Double) jReader.nextDouble());
-                lfdecimal6Checked = true;
-            } else if (keyName.equals("lfenum")) {
-                assertEquals("enum3", jReader.nextString());
-                enumChecked = true;
-            } else if (keyName.equals("lfbits")) {
-                assertEquals("bit3 bit2", jReader.nextString());
-                bitsChecked = true;
-            } else if (keyName.equals("lfbinary")) {
-                assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", jReader.nextString());
-                lfbinaryChecked = true;
-            } else if (keyName.equals("lfempty")) {
-                jReader.beginArray();
-                jReader.nextNull();
-                jReader.endArray();
-                lfemptyChecked = true;
-            } else if (keyName.startsWith("lfunion")) {
-                checkLfUnion(jReader, keyName, peek);
-            } else {
-                assertTrue("Key " + keyName + " doesn't exists in yang file.", false);
+            LeafVerifier verifier = expectedMap.remove( keyName );
+            assertNotNull( "Found unexpected leaf: " + keyName , verifier );
+
+            JsonToken expToken = verifier.expectedTokenType();
+            if( expToken != null ) {
+                assertEquals( "Json token type for key " + keyName, expToken, peek );
             }
 
+            verifier.verify( jReader, keyName );;
+        }
+
+        if( !expectedMap.isEmpty() ) {
+            fail( "Missing leaf nodes in Json output: " +expectedMap.keySet() );
         }
-        Collections.sort(loadedLfs);
-        String expectedLfsStr = "[int16Max, int16Min, int32Max, int32Min, int64Max, int64Min, int8Max, int8Min, uint16Max, uint32Max, uint8Max]";
-        String actualLfsStr = loadedLfs.toString();
-        assertEquals("Some leaves are missing", expectedLfsStr, actualLfsStr);
-        assertTrue("Enum wasn't checked", enumChecked);
-        assertTrue("Bits wasn't checked", bitsChecked);
-        assertTrue("Decimal1 wasn't checked", lfdecimal1Checked);
-        assertTrue("Decimal2 wasn't checked", lfdecimal2Checked);
-        assertTrue("Decimal3 wasn't checked", lfdecimal3Checked);
-        assertTrue("Decimal4 wasn't checked", lfdecimal4Checked);
-        assertTrue("Decimal5 wasn't checked", lfdecimal6Checked);
-        assertTrue("lfbool1 wasn't checked", lfbool1Checked);
-        assertTrue("lfbool2 wasn't checked", lfbool2Checked);
-        assertTrue("lfstr wasn't checked", lfstrChecked);
-        assertTrue("lfstr1 wasn't checked", lfstr1Checked);
-        assertTrue("lfbinary wasn't checked", lfbinaryChecked);
-        assertTrue("lfempty wasn't checked", lfemptyChecked);
-        assertTrue("lfidentityref wasn't checked", lfidentityrefChecked);
+
         jReader.endObject();
     }
 
-    private void checkLfUnion(JsonReader jReader, String keyName, JsonToken peek) throws IOException {
-        if (keyName.equals("lfunion1")) {
-            assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-            assertEquals("324", jReader.nextString());
-        } else if (keyName.equals("lfunion2")) {
-            assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-            assertEquals("33.3", jReader.nextString());
-        } else if (keyName.equals("lfunion3")) {
-            assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-            assertEquals("55", jReader.nextString());
-        } else if (keyName.equals("lfunion4")) {
-            assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-            assertEquals("true", jReader.nextString());
-        } else if (keyName.equals("lfunion5")) {
-            assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-            assertEquals("true", jReader.nextString());
-        } else if (keyName.equals("lfunion6")) {
-            assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-            assertEquals("false", jReader.nextString());
-        } else if (keyName.equals("lfunion7")) {
-            assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-            assertEquals("", jReader.nextString());
-        } else if (keyName.equals("lfunion8")) {
-            assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-            assertEquals("", jReader.nextString());
-        } else if (keyName.equals("lfunion9")) {
-            assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-            assertEquals("", jReader.nextString());
-        } else if (keyName.equals("lfunion10")) {
-            assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-            assertEquals("bt1", jReader.nextString());
-        } else if (keyName.equals("lfunion11")) {
-            assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-            assertEquals("33", jReader.nextString());
-        } else if (keyName.equals("lfunion12")) {
-            assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-            assertEquals("false", jReader.nextString());
-        } else if (keyName.equals("lfunion13")) {
-            assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-            assertEquals("44", jReader.nextString());
-        } else if (keyName.equals("lfunion14")) {
-            assertEquals("Key " + keyName + " has incorrect type", JsonToken.STRING, peek);
-            assertEquals("21", jReader.nextString());
+    @Test
+    public void testBadData() throws Exception {
+
+        try {
+            CompositeNode compositeNode = TestUtils.readInputToCnSn(
+                                               "/cnsn-to-json/simple-data-types/xml/bad-data.xml",
+                                               XmlToCompositeNodeProvider.INSTANCE);
+
+            TestUtils.normalizeCompositeNode(compositeNode, modules, "simple-data-types:cont");
+            fail( "Expected RestconfDocumentedException" );
+        }
+        catch( RestconfDocumentedException e ) {
+            assertEquals( "getErrorTag", ErrorTag.INVALID_VALUE, e.getErrors().get( 0 ).getErrorTag() );
         }
     }
 }
index fc54795fcce5a468cb0d6e780f561a3052fe2eeb..155ee9d5908ab7ac6c285278542e976f11374d9a 100644 (file)
@@ -11,12 +11,16 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
+import java.util.List;
 
 import javax.ws.rs.WebApplicationException;
 import javax.xml.transform.TransformerFactoryConfigurationError;
 
 import org.junit.BeforeClass;
 import org.junit.Test;
+
+import static org.mockito.Mockito.*;
+
 import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider;
 import org.opendaylight.controller.sal.restconf.impl.test.TestUtils;
 import org.opendaylight.controller.sal.restconf.impl.test.YangAndXmlAndDataSchemaLoader;
@@ -26,6 +30,29 @@ import org.opendaylight.yangtools.yang.data.api.MutableCompositeNode;
 import org.opendaylight.yangtools.yang.data.api.MutableSimpleNode;
 import org.opendaylight.yangtools.yang.data.impl.NodeFactory;
 import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
+import org.opendaylight.yangtools.yang.model.util.BinaryType;
+import org.opendaylight.yangtools.yang.model.util.BitsType;
+import org.opendaylight.yangtools.yang.model.util.BooleanType;
+import org.opendaylight.yangtools.yang.model.util.EmptyType;
+import org.opendaylight.yangtools.yang.model.util.EnumerationType;
+import org.opendaylight.yangtools.yang.model.util.Int16;
+import org.opendaylight.yangtools.yang.model.util.Int32;
+import org.opendaylight.yangtools.yang.model.util.Int64;
+import org.opendaylight.yangtools.yang.model.util.Int8;
+import org.opendaylight.yangtools.yang.model.util.StringType;
+import org.opendaylight.yangtools.yang.model.util.Uint16;
+import org.opendaylight.yangtools.yang.model.util.Uint32;
+import org.opendaylight.yangtools.yang.model.util.Uint64;
+import org.opendaylight.yangtools.yang.model.util.Uint8;
+import org.opendaylight.yangtools.yang.model.util.UnionType;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Lists;
 
 /**
  *
@@ -65,7 +92,7 @@ public class CnSnToXmlTest extends YangAndXmlAndDataSchemaLoader {
     @Test
     public void snAsYangStringToXmlTest() {
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.STRING_DEFAULT_CODEC.deserialize("lfStr value"),
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(StringType.getInstance()).deserialize("lfStr value"),
                         "lfStr"), "<lfStr>lfStr value</lfStr>");
     }
 
@@ -73,7 +100,7 @@ public class CnSnToXmlTest extends YangAndXmlAndDataSchemaLoader {
     public void snAsYangInt8ToXmlTest() {
         String elName = "lfInt8";
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.INT8_DEFAULT_CODEC.deserialize("14"), elName), "<"
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(Int8.getInstance()).deserialize("14"), elName), "<"
                         + elName + ">14</" + elName + ">");
     }
 
@@ -81,7 +108,7 @@ public class CnSnToXmlTest extends YangAndXmlAndDataSchemaLoader {
     public void snAsYangInt16ToXmlTest() {
         String elName = "lfInt16";
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.INT16_DEFAULT_CODEC.deserialize("3000"), elName),
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(Int16.getInstance()).deserialize("3000"), elName),
                 "<" + elName + ">3000</" + elName + ">");
     }
 
@@ -89,7 +116,7 @@ public class CnSnToXmlTest extends YangAndXmlAndDataSchemaLoader {
     public void snAsYangInt32ToXmlTest() {
         String elName = "lfInt32";
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.INT32_DEFAULT_CODEC.deserialize("201234"), elName),
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(Int32.getInstance()).deserialize("201234"), elName),
                 "<" + elName + ">201234</" + elName + ">");
     }
 
@@ -97,7 +124,7 @@ public class CnSnToXmlTest extends YangAndXmlAndDataSchemaLoader {
     public void snAsYangInt64ToXmlTest() {
         String elName = "lfInt64";
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.INT64_DEFAULT_CODEC.deserialize("5123456789"),
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(Int64.getInstance()).deserialize("5123456789"),
                         elName), "<" + elName + ">5123456789</" + elName + ">");
     }
 
@@ -105,7 +132,7 @@ public class CnSnToXmlTest extends YangAndXmlAndDataSchemaLoader {
     public void snAsYangUint8ToXmlTest() {
         String elName = "lfUint8";
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.UINT8_DEFAULT_CODEC.deserialize("200"), elName),
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(Uint8.getInstance()).deserialize("200"), elName),
                 "<" + elName + ">200</" + elName + ">");
     }
 
@@ -113,7 +140,7 @@ public class CnSnToXmlTest extends YangAndXmlAndDataSchemaLoader {
     public void snAsYangUint16ToXmlTest() {
         String elName = "lfUint16";
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.UINT16_DEFAULT_CODEC.deserialize("4000"), elName),
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(Uint16.getInstance()).deserialize("4000"), elName),
                 "<" + elName + ">4000</" + elName + ">");
     }
 
@@ -121,7 +148,7 @@ public class CnSnToXmlTest extends YangAndXmlAndDataSchemaLoader {
     public void snAsYangUint32ToXmlTest() {
         String elName = "lfUint32";
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.UINT32_DEFAULT_CODEC.deserialize("4123456789"),
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(Uint32.getInstance()).deserialize("4123456789"),
                         elName), "<" + elName + ">4123456789</" + elName + ">");
     }
 
@@ -129,7 +156,7 @@ public class CnSnToXmlTest extends YangAndXmlAndDataSchemaLoader {
     public void snAsYangUint64ToXmlTest() {
         String elName = "lfUint64";
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.UINT64_DEFAULT_CODEC.deserialize("5123456789"),
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(Uint64.getInstance()).deserialize("5123456789"),
                         elName), "<" + elName + ">5123456789</" + elName + ">");
     }
 
@@ -138,25 +165,40 @@ public class CnSnToXmlTest extends YangAndXmlAndDataSchemaLoader {
         String elName = "lfBinary";
         serializeToXml(
                 prepareCnStructForYangData(
-                        TypeDefinitionAwareCodec.BINARY_DEFAULT_CODEC
-                        .deserialize("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567"),
+                        TypeDefinitionAwareCodec.from(BinaryType.getInstance())
+                                .deserialize("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567"),
                         elName), "<" + elName + ">ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567</"
-                                + elName + ">");
+                        + elName + ">");
     }
 
     @Test
     public void snAsYangBitsToXmlTest() {
+        BitsTypeDefinition.Bit mockBit1 = mock( BitsTypeDefinition.Bit.class );
+        when( mockBit1.getName() ).thenReturn( "one" );
+        BitsTypeDefinition.Bit mockBit2 = mock( BitsTypeDefinition.Bit.class );
+        when( mockBit2.getName() ).thenReturn( "two" );
+        List<BitsTypeDefinition.Bit> bitList = Lists.newArrayList( mockBit1, mockBit2 );
+
         String elName = "lfBits";
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.BITS_DEFAULT_CODEC.deserialize("one two"), elName),
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(
+                                BitsType.create( mock( SchemaPath.class ), bitList ) )
+                                                               .deserialize("one two"), elName),
                 "<" + elName + ">one two</" + elName + ">", "<" + elName + ">two one</" + elName + ">");
     }
 
     @Test
     public void snAsYangEnumerationToXmlTest() {
+        EnumTypeDefinition.EnumPair mockEnum = mock( EnumTypeDefinition.EnumPair.class );
+        when( mockEnum.getName() ).thenReturn( "enum2" );
+        List<EnumPair> enumList = Lists.newArrayList( mockEnum );
+
         String elName = "lfEnumeration";
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.ENUMERATION_DEFAULT_CODEC.deserialize("enum2"),
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(
+                    EnumerationType.create( mock( SchemaPath.class ), enumList,
+                                            Optional.<EnumTypeDefinition.EnumPair>absent() ) )
+                                                                                    .deserialize("enum2"),
                         elName), "<" + elName + ">enum2</" + elName + ">");
     }
 
@@ -164,7 +206,7 @@ public class CnSnToXmlTest extends YangAndXmlAndDataSchemaLoader {
     public void snAsYangEmptyToXmlTest() {
         String elName = "lfEmpty";
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.EMPTY_DEFAULT_CODEC.deserialize(null), elName), "<"
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(EmptyType.getInstance()).deserialize(null), elName), "<"
                         + elName + "/>");
     }
 
@@ -172,33 +214,46 @@ public class CnSnToXmlTest extends YangAndXmlAndDataSchemaLoader {
     public void snAsYangBooleanToXmlTest() {
         String elName = "lfBoolean";
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.BOOLEAN_DEFAULT_CODEC.deserialize("str"), elName),
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(BooleanType.getInstance()).deserialize("str"), elName),
                 "<" + elName + ">false</" + elName + ">");
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.BOOLEAN_DEFAULT_CODEC.deserialize("true"), elName),
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(BooleanType.getInstance()).deserialize("true"), elName),
                 "<" + elName + ">true</" + elName + ">");
     }
 
     @Test
     public void snAsYangUnionToXmlTest() {
+
+        BitsTypeDefinition.Bit mockBit1 = mock( BitsTypeDefinition.Bit.class );
+        when( mockBit1.getName() ).thenReturn( "first" );
+        BitsTypeDefinition.Bit mockBit2 = mock( BitsTypeDefinition.Bit.class );
+        when( mockBit1.getName() ).thenReturn( "second" );
+        List<BitsTypeDefinition.Bit> bitList = Lists.newArrayList( mockBit1, mockBit2 );
+
+        List<TypeDefinition<?>> types = Lists.<TypeDefinition<?>>newArrayList(
+                                                            Int8.getInstance(),
+                                                            BitsType.create( mock( SchemaPath.class ) , bitList ),
+                                                            BooleanType.getInstance() );
+        UnionType unionType = UnionType.create( types );
+
         String elName = "lfUnion";
         String int8 = "15";
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.UNION_DEFAULT_CODEC.deserialize(int8), elName), "<"
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(unionType).deserialize(int8), elName), "<"
                         + elName + ">15</" + elName + ">");
 
         String bits = "first second";
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.UNION_DEFAULT_CODEC.deserialize(bits), elName), "<"
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(unionType).deserialize(bits), elName), "<"
                         + elName + ">first second</" + elName + ">");
 
         String bool = "str";
         serializeToXml(
-                prepareCnStructForYangData(TypeDefinitionAwareCodec.UNION_DEFAULT_CODEC.deserialize(bool), elName), "<"
+                prepareCnStructForYangData(TypeDefinitionAwareCodec.from(unionType).deserialize(bool), elName), "<"
                         + elName + ">str</" + elName + ">");
     }
 
-    private void serializeToXml(final CompositeNode compositeNode, final String... xmlRepresentation)
+    private void serializeToXml(CompositeNode compositeNode, String... xmlRepresentation)
             throws TransformerFactoryConfigurationError {
         String xmlString = "";
         try {
@@ -220,7 +275,7 @@ public class CnSnToXmlTest extends YangAndXmlAndDataSchemaLoader {
 
     }
 
-    private CompositeNode prepareIdentityrefData(final String prefix, final boolean valueAsQName) {
+    private CompositeNode prepareIdentityrefData(String prefix, boolean valueAsQName) {
         MutableCompositeNode cont = NodeFactory.createMutableCompositeNode(
                 TestUtils.buildQName("cont", "basic:module", "2013-12-2"), null, null, ModifyAction.CREATE, null);
         MutableCompositeNode cont1 = NodeFactory.createMutableCompositeNode(
index 51687e2a1285601e4efff6c3737001a9f12488f3..307abebdd7b1a37ebfb3cc1d716048614c3e44cc 100644 (file)
@@ -56,6 +56,6 @@ public class CodecsExceptionsCatchingTest extends JerseyTest {
         Response response = target("/config/number:cont").request(MediaType.APPLICATION_XML).put(
                 Entity.entity("<cont xmlns=\"number\"><lf>3f</lf></cont>", MediaType.APPLICATION_XML));
         String exceptionMessage = response.readEntity(String.class);
-        assertTrue(exceptionMessage.contains("Incorrect lexical representation of Integer value: 3f"));
+        assertTrue(exceptionMessage.contains("invalid-value"));
     }
 }
\ No newline at end of file
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/cnsn-to-json/simple-data-types/xml/bad-data.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/cnsn-to-json/simple-data-types/xml/bad-data.xml
new file mode 100644 (file)
index 0000000..110c323
--- /dev/null
@@ -0,0 +1,3 @@
+<cont>
+    <lfnint8Min>invalid</lfnint8Min>
+</cont>
\ No newline at end of file
index 56872a337d0124be4789789ef8f58427ab378278..f73ce1b65c38a154a8ae2067318c1b261e012c36 100644 (file)
        <lfunion3>55</lfunion3>
        <lfunion4>true</lfunion4>
        <lfunion5>true</lfunion5>
-       <lfunion6>false</lfunion6>
+       <lfunion6>10</lfunion6>
        <lfunion7></lfunion7>
        <lfunion8></lfunion8>
        <lfunion9></lfunion9>
        <lfunion10>bt1</lfunion10>
        <lfunion11>33</lfunion11>
        <lfunion12>false</lfunion12>
-       <lfunion13>44</lfunion13>
-       <lfunion14>21</lfunion14>
-       <lfempty />
+       <lfunion13>b1</lfunion13>
+       <lfunion14>zero</lfunion14>
        <identityref1 xmlns:x="simple:data:types">x:iden</identityref1>
 </cont>
\ No newline at end of file
index dbcbab982a9aec997e37a2fb09e763bb3f3c5f96..7d9cc7ecbd789634e96ddbf32153e534d594dbc4 100644 (file)
@@ -313,6 +313,9 @@ public final class NodeStatisticsHandler implements AutoCloseable, FlowCapableCo
         meterStats.close();
         queueStats.close();
 
+        //Clean up queued statistics request from scheduler queue 
+        srScheduler.removeRequestsFromSchedulerQueue(this.getNodeRef());
+
         logger.debug("Statistics handler for {} shut down", targetNodeKey.getId());
     }
 
index 9ebfd6fd62f7b2ab8933d86a03a788b3154d2eb5..bea43ef68a05c1000d7a7d904d0c36f6fccc1b4a 100644 (file)
@@ -18,6 +18,7 @@ import java.util.concurrent.TimeUnit;
 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
 import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction;
 import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction.DataTransactionListener;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -62,6 +63,19 @@ public class StatisticsRequestScheduler implements DataTransactionListener {
         requestQueue.put(statsRequest, null);
     }
     
+    public void removeRequestsFromSchedulerQueue(NodeRef node){
+        AbstractStatsTracker stats = null;
+        synchronized(requestQueue){
+            Iterator<Map.Entry<AbstractStatsTracker, Integer>> nodesItr = requestQueue.entrySet().iterator();
+            while(nodesItr.hasNext()){
+                stats = nodesItr.next().getKey();
+                if(stats.getNodeRef().equals(node)){
+                    nodesItr.remove();
+                }
+            }
+        }
+
+    }
     public AbstractStatsTracker getNextRequestFromSchedulerQueue(){
         //Remove first element
         AbstractStatsTracker stats = null;
@@ -79,10 +93,7 @@ public class StatisticsRequestScheduler implements DataTransactionListener {
 
     private void requestStatistics(){
         AbstractStatsTracker stats = this.getNextRequestFromSchedulerQueue();
-        if(stats != null) {
-            stats.request();
-            stats.increaseRequestCounter();
-        }
+        sendStatsRequest(stats);
     }
     @Override
     public void onStatusUpdated(DataModificationTransaction transaction, TransactionStatus status) {
@@ -106,12 +117,19 @@ public class StatisticsRequestScheduler implements DataTransactionListener {
                 break;
             }
         }
+        sendStatsRequest(stats);
+    }
+    
+    private void sendStatsRequest(AbstractStatsTracker stats){
         if(stats != null){
-            stats.request();
-            stats.increaseRequestCounter();
+            try{
+                stats.request();
+                stats.increaseRequestCounter();
+            }catch(Exception e){
+                srsLogger.warn("Statistics request was not sent successfully. Reason : {}",e.getMessage());
+            }
         }
     }
-    
     public void start(){
         timer.schedule(task, 0, REQUEST_MONITOR_INTERVAL);
     }
index 78a2043e202413786f1000954c10289ce5395696..22a3f105477fbc300739a579b17767a640660406 100644 (file)
@@ -8,7 +8,17 @@
 
 package org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig;
 
+import static org.mockito.Matchers.any;
+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.mockito.Mockito.verify;
+
 import com.google.common.collect.Sets;
+import java.util.Map;
+import javax.management.Attribute;
+import javax.management.ObjectName;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
@@ -16,17 +26,6 @@ import org.mockito.MockitoAnnotations;
 import org.opendaylight.controller.config.util.ConfigTransactionClient;
 import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeConfigElement;
 
-import javax.management.Attribute;
-import javax.management.ObjectName;
-import java.util.Map;
-
-import static org.mockito.Matchers.any;
-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.mockito.Mockito.verify;
-
 public class ReplaceEditConfigStrategyTest {
 
     @Mock
@@ -35,7 +34,7 @@ public class ReplaceEditConfigStrategyTest {
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        doNothing().when(ta).destroyConfigBean(anyString(), anyString());
+        doNothing().when(ta).destroyModule(anyString(), anyString());
         doReturn(mockON()).when(ta).lookupConfigBean(anyString(), anyString());
         doNothing().when(ta).setAttribute(any(ObjectName.class), anyString(), any(Attribute.class));
     }
index 799674487f4de5d936067b51b3f9ae392caee897..829ac304bd667f680d813e32ff58ed9f9b0b6f37 100644 (file)
@@ -7,7 +7,7 @@
  */
 package org.opendaylight.controller.netconf.client;
 
-import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.Channel;
 import io.netty.util.concurrent.Promise;
 import java.io.IOException;
 import org.opendaylight.controller.netconf.nettyutil.AbstractChannelInitializer;
@@ -31,7 +31,7 @@ final class SshClientChannelInitializer extends AbstractChannelInitializer<Netco
     }
 
     @Override
-    public void initialize(final SocketChannel ch, final Promise<NetconfClientSession> promise) {
+    public void initialize(final Channel ch, final Promise<NetconfClientSession> promise) {
         try {
             final Invoker invoker = Invoker.subsystem("netconf");
             ch.pipeline().addFirst(new SshHandler(authenticationHandler, invoker));
@@ -42,7 +42,7 @@ final class SshClientChannelInitializer extends AbstractChannelInitializer<Netco
     }
 
     @Override
-    protected void initializeSessionNegotiator(final SocketChannel ch,
+    protected void initializeSessionNegotiator(final Channel ch,
                                                final Promise<NetconfClientSession> promise) {
         ch.pipeline().addAfter(NETCONF_MESSAGE_DECODER,  AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR,
                 negotiatorFactory.getSessionNegotiator(new SessionListenerFactory<NetconfClientSessionListener>() {
index 4a0a089fae97e1ff3d1c8b969f6da077e3584cb7..ee8f8baf0151c4cdeb820878ca9c440e60c9b64b 100644 (file)
@@ -7,7 +7,7 @@
  */
 package org.opendaylight.controller.netconf.client;
 
-import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.Channel;
 import io.netty.util.concurrent.Promise;
 import org.opendaylight.controller.netconf.nettyutil.AbstractChannelInitializer;
 import org.opendaylight.protocol.framework.SessionListenerFactory;
@@ -24,12 +24,7 @@ class TcpClientChannelInitializer extends AbstractChannelInitializer<NetconfClie
     }
 
     @Override
-    public void initialize(final SocketChannel ch, final Promise<NetconfClientSession> promise) {
-        super.initialize(ch, promise);
-    }
-
-    @Override
-    protected void initializeSessionNegotiator(final SocketChannel ch, final Promise<NetconfClientSession> promise) {
+    protected void initializeSessionNegotiator(final Channel ch, final Promise<NetconfClientSession> promise) {
         ch.pipeline().addAfter(NETCONF_MESSAGE_DECODER, AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR,
                 negotiatorFactory.getSessionNegotiator(new SessionListenerFactory<NetconfClientSessionListener>() {
                     @Override
index 60d8f3044aee5667ea04858377d31b109ca88ef0..afa17532d55ce6628b1659db90e666acf1e97250 100644 (file)
@@ -8,24 +8,35 @@
 
 package org.opendaylight.controller.netconf.client.test;
 
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Sets;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.util.HashedWheelTimer;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GlobalEventExecutor;
 import java.io.Closeable;
 import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
 import java.util.Set;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-
 import org.opendaylight.controller.netconf.api.NetconfMessage;
 import org.opendaylight.controller.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.controller.netconf.client.NetconfClientDispatcherImpl;
 import org.opendaylight.controller.netconf.client.NetconfClientSession;
 import org.opendaylight.controller.netconf.client.NetconfClientSessionListener;
 import org.opendaylight.controller.netconf.client.SimpleNetconfClientSessionListener;
 import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Sets;
-import io.netty.util.concurrent.Future;
+import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol;
+import org.opendaylight.controller.netconf.client.conf.NetconfClientConfigurationBuilder;
+import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
+import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.LoginPassword;
+import org.opendaylight.protocol.framework.NeverReconnectStrategy;
 
 
 /**
@@ -95,4 +106,29 @@ public class TestingNetconfClient implements Closeable {
         Preconditions.checkState(clientSession != null, "Client was not initialized successfully");
         return Sets.newHashSet(clientSession.getServerCapabilities());
     }
+
+    public static void main(String[] args) throws Exception {
+        HashedWheelTimer hashedWheelTimer = new HashedWheelTimer();
+        NioEventLoopGroup nettyGroup = new NioEventLoopGroup();
+        NetconfClientDispatcherImpl netconfClientDispatcher = new NetconfClientDispatcherImpl(nettyGroup, nettyGroup, hashedWheelTimer);
+        LoginPassword authHandler = new LoginPassword("admin", "admin");
+        TestingNetconfClient client = new TestingNetconfClient("client", netconfClientDispatcher, getClientConfig("127.0.0.1", 1830, true, Optional.of(authHandler)));
+        System.out.println(client.getCapabilities());
+    }
+
+    private static NetconfClientConfiguration getClientConfig(String host ,int port, boolean ssh, Optional<? extends AuthenticationHandler> maybeAuthHandler) throws UnknownHostException {
+        InetSocketAddress netconfAddress = new InetSocketAddress(InetAddress.getByName(host), port);
+        final NetconfClientConfigurationBuilder b = NetconfClientConfigurationBuilder.create();
+        b.withAddress(netconfAddress);
+        b.withSessionListener(new SimpleNetconfClientSessionListener());
+        b.withReconnectStrategy(new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE,
+                NetconfClientConfigurationBuilder.DEFAULT_CONNECTION_TIMEOUT_MILLIS));
+        if (ssh) {
+            b.withProtocol(NetconfClientProtocol.SSH);
+            b.withAuthHandler(maybeAuthHandler.get());
+        } else {
+            b.withProtocol(NetconfClientProtocol.TCP);
+        }
+        return b.build();
+    }
 }
index 1d94517152f45215cc7481a963a290a2870640d2..c60506ef447d821d5458dbb399c30a1ab0e94325 100644 (file)
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.7</version>
         <configuration>
           <instructions>
             <Bundle-Activator>org.opendaylight.controller.netconf.impl.osgi.NetconfImplActivator</Bundle-Activator>
                             io.netty.buffer,
                             io.netty.handler.codec,
                             io.netty.channel.nio,
+                            io.netty.channel.local,
                             javax.annotation,
                             javax.management,
                             javax.net.ssl,
index de3dee14437b804fd18819bb20302a962fd213e2..4dfb7498184af4e7a71f7c40744821e258e00966 100644 (file)
@@ -8,8 +8,13 @@
 
 package org.opendaylight.controller.netconf.impl;
 
+import com.google.common.annotations.VisibleForTesting;
+import io.netty.channel.Channel;
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import io.netty.channel.local.LocalServerChannel;
 import io.netty.channel.socket.SocketChannel;
 import io.netty.util.concurrent.Promise;
 import java.net.InetSocketAddress;
@@ -27,6 +32,7 @@ public class NetconfServerDispatcher extends AbstractDispatcher<NetconfServerSes
         this.initializer = serverChannelInitializer;
     }
 
+    @VisibleForTesting
     public ChannelFuture createServer(InetSocketAddress address) {
 
         return super.createServer(address, new PipelineInitializer<NetconfServerSession>() {
@@ -37,6 +43,15 @@ public class NetconfServerDispatcher extends AbstractDispatcher<NetconfServerSes
         });
     }
 
+    public ChannelFuture createLocalServer(LocalAddress address) {
+        return super.createServer(address, LocalServerChannel.class, new ChannelPipelineInitializer<LocalChannel, NetconfServerSession>() {
+            @Override
+            public void initializeChannel(final LocalChannel ch, final Promise<NetconfServerSession> promise) {
+                initializer.initialize(ch, promise);
+            }
+        });
+    }
+
     public static class ServerChannelInitializer extends AbstractChannelInitializer<NetconfServerSession> {
 
         public static final String DESERIALIZER_EX_HANDLER_KEY = "deserializerExHandler";
@@ -50,16 +65,15 @@ public class NetconfServerDispatcher extends AbstractDispatcher<NetconfServerSes
         }
 
         @Override
-        protected void initializeMessageDecoder(SocketChannel ch) {
+        protected void initializeMessageDecoder(Channel ch) {
             super.initializeMessageDecoder(ch);
             ch.pipeline().addLast(DESERIALIZER_EX_HANDLER_KEY, new DeserializerExceptionHandler());
         }
 
         @Override
-        protected void initializeSessionNegotiator(SocketChannel ch, Promise<NetconfServerSession> promise) {
+        protected void initializeSessionNegotiator(Channel ch, Promise<NetconfServerSession> promise) {
             ch.pipeline().addAfter(DESERIALIZER_EX_HANDLER_KEY, AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR,
                     negotiatorFactory.getSessionNegotiator(null, ch, promise));
         }
     }
-
 }
index 7130dc350134578372348ce829bda08243a5303c..6ab62ef29a82037cd12e01c5f71d88e34a32a5bd 100644 (file)
@@ -7,12 +7,13 @@
  */
 package org.opendaylight.controller.netconf.impl.osgi;
 
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.util.HashedWheelTimer;
 import java.lang.management.ManagementFactory;
-import java.net.InetSocketAddress;
 import java.util.Dictionary;
 import java.util.Hashtable;
 import java.util.concurrent.TimeUnit;
-
 import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService;
 import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer;
 import org.opendaylight.controller.netconf.impl.NetconfServerDispatcher;
@@ -26,9 +27,6 @@ import org.osgi.framework.ServiceRegistration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import io.netty.channel.nio.NioEventLoopGroup;
-import io.netty.util.HashedWheelTimer;
-
 public class NetconfImplActivator implements BundleActivator {
 
     private static final Logger logger = LoggerFactory.getLogger(NetconfImplActivator.class);
@@ -40,17 +38,16 @@ public class NetconfImplActivator implements BundleActivator {
     private ServiceRegistration<NetconfMonitoringService> regMonitoring;
 
     @Override
-    public void start(final BundleContext context) {
-        final InetSocketAddress address = NetconfConfigUtil.extractTCPNetconfServerAddress(context,
-                NetconfConfigUtil.DEFAULT_NETCONF_TCP_ADDRESS);
-        final NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl();
+    public void start(final BundleContext context)  {
+
+        NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl();
         startOperationServiceFactoryTracker(context, factoriesListener);
 
-        final SessionIdProvider idProvider = new SessionIdProvider();
+        SessionIdProvider idProvider = new SessionIdProvider();
         timer = new HashedWheelTimer();
-
         long connectionTimeoutMillis = NetconfConfigUtil.extractTimeoutMillis(context);
 
+
         commitNot = new DefaultCommitNotificationProducer(ManagementFactory.getPlatformMBeanServer());
 
         SessionMonitoringService monitoringService = startMonitoringService(context, factoriesListener);
@@ -62,24 +59,24 @@ public class NetconfImplActivator implements BundleActivator {
 
         NetconfServerDispatcher.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcher.ServerChannelInitializer(
                 serverNegotiatorFactory);
+        NetconfServerDispatcher dispatch = new NetconfServerDispatcher(serverChannelInitializer, eventLoopGroup, eventLoopGroup);
 
-        NetconfServerDispatcher dispatch = new NetconfServerDispatcher(serverChannelInitializer, eventLoopGroup,
-                eventLoopGroup);
-
-        logger.info("Starting TCP netconf server at {}", address);
-        dispatch.createServer(address);
+        LocalAddress address = NetconfConfigUtil.getNetconfLocalAddress();
+        logger.trace("Starting local netconf server at {}", address);
+        dispatch.createLocalServer(address);
 
         context.registerService(NetconfOperationProvider.class, factoriesListener, null);
+
     }
 
-    private void startOperationServiceFactoryTracker(final BundleContext context, final NetconfOperationServiceFactoryListenerImpl factoriesListener) {
+    private void startOperationServiceFactoryTracker(BundleContext context, NetconfOperationServiceFactoryListenerImpl factoriesListener) {
         factoriesTracker = new NetconfOperationServiceFactoryTracker(context, factoriesListener);
         factoriesTracker.open();
     }
 
-    private NetconfMonitoringServiceImpl startMonitoringService(final BundleContext context, final NetconfOperationServiceFactoryListenerImpl factoriesListener) {
-        final NetconfMonitoringServiceImpl netconfMonitoringServiceImpl = new NetconfMonitoringServiceImpl(factoriesListener);
-        final Dictionary<String, ?> dic = new Hashtable<>();
+    private NetconfMonitoringServiceImpl startMonitoringService(BundleContext context, NetconfOperationServiceFactoryListenerImpl factoriesListener) {
+        NetconfMonitoringServiceImpl netconfMonitoringServiceImpl = new NetconfMonitoringServiceImpl(factoriesListener);
+        Dictionary<String, ?> dic = new Hashtable<>();
         regMonitoring = context.registerService(NetconfMonitoringService.class, netconfMonitoringServiceImpl, dic);
 
         return netconfMonitoringServiceImpl;
index 140284e4ee1946ab2cc579fba0cf08362440e580..0969bd92a59a7cb6132a2b3b6cb5ad713af5077e 100644 (file)
@@ -16,13 +16,13 @@ import static org.mockito.Mockito.mock;
 
 import ch.ethz.ssh2.Connection;
 import io.netty.channel.ChannelFuture;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
 import io.netty.util.concurrent.GlobalEventExecutor;
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.management.ManagementFactory;
 import java.net.InetSocketAddress;
-import java.nio.file.Files;
 import java.util.Collection;
 import java.util.List;
 import junit.framework.Assert;
@@ -50,16 +50,14 @@ import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
 import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
 import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator;
 import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
 import org.opendaylight.controller.netconf.util.test.XmlFileLoader;
 import org.opendaylight.controller.netconf.util.xml.XmlUtil;
-import org.opendaylight.controller.sal.authorization.AuthResultEnum;
-import org.opendaylight.controller.usermanager.IUserManager;
 import org.opendaylight.protocol.framework.NeverReconnectStrategy;
 
 public class NetconfITSecureTest extends AbstractNetconfConfigTest {
 
     private static final InetSocketAddress tlsAddress = new InetSocketAddress("127.0.0.1", 12024);
-    private static final InetSocketAddress tcpAddress = new InetSocketAddress("127.0.0.1", 12023);
 
     private DefaultCommitNotificationProducer commitNot;
     private NetconfSSHServer sshServer;
@@ -79,13 +77,10 @@ public class NetconfITSecureTest extends AbstractNetconfConfigTest {
 
 
         final NetconfServerDispatcher dispatchS = createDispatcher(factoriesListener);
-        ChannelFuture s = dispatchS.createServer(tcpAddress);
+        ChannelFuture s = dispatchS.createLocalServer(NetconfConfigUtil.getNetconfLocalAddress());
         s.await();
-
-        sshServer = NetconfSSHServer.start(tlsAddress.getPort(), tcpAddress, getAuthProvider());
-        Thread thread = new Thread(sshServer);
-        thread.setDaemon(true);
-        thread.start();
+        EventLoopGroup bossGroup  = new NioEventLoopGroup();
+        sshServer = NetconfSSHServer.start(tlsAddress.getPort(), NetconfConfigUtil.getNetconfLocalAddress(), getAuthProvider(), bossGroup);
     }
 
     private NetconfServerDispatcher createDispatcher(NetconfOperationServiceFactoryListenerImpl factoriesListener) {
@@ -140,13 +135,10 @@ public class NetconfITSecureTest extends AbstractNetconfConfigTest {
     }
 
     public AuthProvider getAuthProvider() throws Exception {
-        final IUserManager userManager = mock(IUserManager.class);
-        doReturn(AuthResultEnum.AUTH_ACCEPT).when(userManager).authenticate(anyString(), anyString());
-
-        final File privateKeyFile = Files.createTempFile("tmp-netconf-test", "pk").toFile();
-        privateKeyFile.deleteOnExit();
-        String privateKeyPEMString = PEMGenerator.generateTo(privateKeyFile);
-        return new AuthProvider(userManager, privateKeyPEMString);
+        AuthProvider mock = mock(AuthProvider.class);
+        doReturn(true).when(mock).authenticated(anyString(), anyString());
+        doReturn(PEMGenerator.generate().toCharArray()).when(mock).getPEMAsCharArray();
+        return mock;
     }
 
     public AuthenticationHandler getAuthHandler() throws IOException {
index fd43f67c056407c15dc0692d938b4fad7c30586b..60a5207daa2e74b54cbc5a1a2b1ec9dd423a3f2b 100644 (file)
@@ -8,7 +8,6 @@
 
 package org.opendaylight.controller.netconf.it;
 
-import static java.util.Collections.emptyList;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
@@ -17,6 +16,10 @@ import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
+import com.google.common.base.Throwables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import io.netty.channel.ChannelFuture;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.management.ManagementFactory;
@@ -29,10 +32,8 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
-
 import javax.management.ObjectName;
 import javax.xml.parsers.ParserConfigurationException;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
@@ -66,32 +67,20 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controll
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.test.types.rev131127.TestIdentity2;
 import org.opendaylight.yangtools.yang.data.impl.codec.CodecRegistry;
 import org.opendaylight.yangtools.yang.data.impl.codec.IdentityCodec;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
 import org.xml.sax.SAXException;
 
-import com.google.common.base.Throwables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import io.netty.channel.ChannelFuture;
-
 public class NetconfITTest extends AbstractNetconfConfigTest {
 
     // TODO refactor, pull common code up to AbstractNetconfITTest
 
-    private static final Logger logger = LoggerFactory.getLogger(NetconfITTest.class);
-
     private static final InetSocketAddress tcpAddress = new InetSocketAddress("127.0.0.1", 12023);
-    private static final InetSocketAddress sshAddress = new InetSocketAddress("127.0.0.1", 10830);
-    private static final String USERNAME = "netconf";
-    private static final String PASSWORD = "netconf";
 
-    private NetconfMessage getConfig, getConfigCandidate, editConfig,
-            closeSession, startExi, stopExi;
+
+    private NetconfMessage getConfig, getConfigCandidate, editConfig, closeSession;
     private DefaultCommitNotificationProducer commitNot;
     private NetconfServerDispatcher dispatch;
 
@@ -139,10 +128,6 @@ public class NetconfITTest extends AbstractNetconfConfigTest {
         this.editConfig = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/edit_config.xml");
         this.getConfig = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/getConfig.xml");
         this.getConfigCandidate = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/getConfig_candidate.xml");
-        this.startExi = XmlFileLoader
-                .xmlFileToNetconfMessage("netconfMessages/startExi.xml");
-        this.stopExi = XmlFileLoader
-                .xmlFileToNetconfMessage("netconfMessages/stopExi.xml");
         this.closeSession = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/closeSession.xml");
     }
 
@@ -166,7 +151,7 @@ public class NetconfITTest extends AbstractNetconfConfigTest {
                 yangDependencies.add(resourceAsStream);
             }
         }
-        assertEquals("Some yang files were not found", emptyList(), failedToFind);
+        assertEquals("Some yang files were not found", Collections.<String>emptyList(), failedToFind);
         return yangDependencies;
     }
 
@@ -198,6 +183,7 @@ public class NetconfITTest extends AbstractNetconfConfigTest {
     public void testTwoSessions() throws Exception {
         try (TestingNetconfClient netconfClient = new TestingNetconfClient("1", clientDispatcher, getClientConfiguration(tcpAddress, 10000)))  {
             try (TestingNetconfClient netconfClient2 = new TestingNetconfClient("2", clientDispatcher, getClientConfiguration(tcpAddress, 10000))) {
+                assertNotNull(netconfClient2.getCapabilities());
             }
         }
     }
index e88bf53ae0a7ccfc95d091521e0587a55fc1ef71..7897666ddc6763cff64f8e7f6dd225f411c9eb54 100644 (file)
@@ -8,7 +8,7 @@
 
 package org.opendaylight.controller.netconf.nettyutil;
 
-import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.Channel;
 import io.netty.util.concurrent.Promise;
 import org.opendaylight.controller.netconf.api.NetconfSession;
 import org.opendaylight.controller.netconf.nettyutil.handler.FramingMechanismHandlerFactory;
@@ -25,7 +25,7 @@ public abstract class AbstractChannelInitializer<S extends NetconfSession> {
     public static final String NETCONF_MESSAGE_FRAME_ENCODER = "frameEncoder";
     public static final String NETCONF_SESSION_NEGOTIATOR = "negotiator";
 
-    public void initialize(SocketChannel ch, Promise<S> promise) {
+    public void initialize(Channel ch, Promise<S> promise) {
         ch.pipeline().addLast(NETCONF_MESSAGE_AGGREGATOR, new NetconfEOMAggregator());
         initializeMessageDecoder(ch);
         ch.pipeline().addLast(NETCONF_MESSAGE_FRAME_ENCODER, FramingMechanismHandlerFactory.createHandler(FramingMechanism.EOM));
@@ -34,13 +34,13 @@ public abstract class AbstractChannelInitializer<S extends NetconfSession> {
         initializeSessionNegotiator(ch, promise);
     }
 
-    protected void initializeMessageEncoder(SocketChannel ch) {
+    protected void initializeMessageEncoder(Channel 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) {
+    protected void initializeMessageDecoder(Channel 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());
@@ -50,6 +50,6 @@ public abstract class AbstractChannelInitializer<S extends NetconfSession> {
      * 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<S> promise);
+    protected abstract void initializeSessionNegotiator(Channel ch, Promise<S> promise);
 
 }
index 622881352eef102119eed81a74f32ee0db2ed76a..cbd3efc57f7bc0abfd27e77bec63334087265fab 100644 (file)
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.7</version>
         <configuration>
           <instructions>
             <Bundle-Activator>org.opendaylight.controller.netconf.ssh.osgi.NetconfSSHActivator</Bundle-Activator>
             <Import-Package>com.google.common.base,
-                            ch.ethz.ssh2,
-                            ch.ethz.ssh2.signature,
-                            org.apache.commons.io,
-                            org.opendaylight.controller.netconf.util.osgi,
-                            org.opendaylight.controller.usermanager,
-                            org.opendaylight.controller.sal.authorization,
-                            org.opendaylight.controller.sal.utils,
-                            org.osgi.framework,
-                            org.osgi.util.tracker,
-                            org.slf4j,
-                            org.bouncycastle.openssl</Import-Package>
+              ch.ethz.ssh2,
+              ch.ethz.ssh2.signature,
+              org.apache.commons.io,
+              org.opendaylight.controller.netconf.util.osgi,
+              org.opendaylight.controller.usermanager,
+              org.opendaylight.controller.sal.authorization,
+              org.opendaylight.controller.sal.utils,
+              org.osgi.framework,
+              org.osgi.util.tracker,
+              org.slf4j,
+              org.bouncycastle.openssl,
+              io.netty.bootstrap, io.netty.buffer, io.netty.channel, io.netty.channel.local, io.netty.channel.nio,
+              io.netty.handler.stream, io.netty.util.concurrent, org.apache.commons.lang3,
+              org.opendaylight.controller.netconf.util.messages</Import-Package>
           </instructions>
         </configuration>
       </plugin>
index c6974d4982db051f1cb6a66d3f67394ee2d57817..08bf9836b22135a295f2e54d68fa6c73ffeadf57 100644 (file)
@@ -7,79 +7,94 @@
  */
 package org.opendaylight.controller.netconf.ssh;
 
-import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
-import org.opendaylight.controller.netconf.ssh.threads.SocketThread;
-import org.opendaylight.controller.usermanager.IUserManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.annotation.concurrent.ThreadSafe;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
 import java.io.IOException;
-import java.net.InetSocketAddress;
 import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicLong;
+import javax.annotation.concurrent.ThreadSafe;
+import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
+import org.opendaylight.controller.netconf.ssh.threads.Handshaker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+/**
+ * Thread that accepts client connections. Accepted socket is forwarded to {@link org.opendaylight.controller.netconf.ssh.threads.Handshaker},
+ * which is executed in {@link #handshakeExecutor}.
+ */
 @ThreadSafe
-public final class NetconfSSHServer implements Runnable {
+public final class NetconfSSHServer extends Thread implements AutoCloseable {
 
-    private ServerSocket ss = null;
-    private static final Logger logger =  LoggerFactory.getLogger(NetconfSSHServer.class);
-    private static final AtomicLong sesssionId = new AtomicLong();
-    private final InetSocketAddress clientAddress;
-    private final AuthProvider authProvider;
-    private volatile boolean up = false;
+    private static final Logger logger = LoggerFactory.getLogger(NetconfSSHServer.class);
+    private static final AtomicLong sessionIdCounter = new AtomicLong();
 
-    private NetconfSSHServer(int serverPort,InetSocketAddress clientAddress, AuthProvider authProvider) throws IllegalStateException, IOException {
+    private final ServerSocket serverSocket;
+    private final LocalAddress localAddress;
+    private final EventLoopGroup bossGroup;
+    private final AuthProvider authProvider;
+    private final ExecutorService handshakeExecutor;
+    private volatile boolean up;
 
-        logger.trace("Creating SSH server socket on port {}",serverPort);
-        this.ss = new ServerSocket(serverPort);
-        if (!ss.isBound()){
-            throw new IllegalStateException("Socket can't be bound to requested port :"+serverPort);
+    private NetconfSSHServer(int serverPort, LocalAddress localAddress, AuthProvider authProvider, EventLoopGroup bossGroup) throws IOException {
+        super(NetconfSSHServer.class.getSimpleName());
+        this.bossGroup = bossGroup;
+        logger.trace("Creating SSH server socket on port {}", serverPort);
+        this.serverSocket = new ServerSocket(serverPort);
+        if (serverSocket.isBound() == false) {
+            throw new IllegalStateException("Socket can't be bound to requested port :" + serverPort);
         }
         logger.trace("Server socket created.");
-        this.clientAddress = clientAddress;
+        this.localAddress = localAddress;
         this.authProvider = authProvider;
         this.up = true;
+        handshakeExecutor = Executors.newFixedThreadPool(10);
     }
 
-    public static NetconfSSHServer start(int serverPort, InetSocketAddress clientAddress,AuthProvider authProvider) throws IllegalStateException, IOException {
-        return new NetconfSSHServer(serverPort, clientAddress,authProvider);
+    public static NetconfSSHServer start(int serverPort, LocalAddress localAddress, AuthProvider authProvider, EventLoopGroup bossGroup) throws IOException {
+        NetconfSSHServer netconfSSHServer = new NetconfSSHServer(serverPort, localAddress, authProvider, bossGroup);
+        netconfSSHServer.start();
+        return netconfSSHServer;
     }
 
-    public void stop() throws IOException {
+    @Override
+    public void close() throws IOException {
         up = false;
         logger.trace("Closing SSH server socket.");
-        ss.close();
+        serverSocket.close();
+        bossGroup.shutdownGracefully();
         logger.trace("SSH server socket closed.");
     }
 
-    public void removeUserManagerService(){
-        this.authProvider.removeUserManagerService();
-    }
-
-    public void addUserManagerService(IUserManager userManagerService){
-        this.authProvider.addUserManagerService(userManagerService);
-    }
-    public boolean isUp(){
-        return this.up;
-    }
     @Override
     public void run() {
         while (up) {
-            logger.trace("Starting new socket thread.");
+            Socket acceptedSocket = null;
             try {
-                SocketThread.start(ss.accept(), clientAddress, sesssionId.incrementAndGet(), authProvider);
-            }
-            catch (IOException e) {
-                if( up ) {
-                    logger.error("Exception occurred during socket thread initialization", e);
+                acceptedSocket = serverSocket.accept();
+            } catch (IOException e) {
+                if (up == false) {
+                    logger.trace("Exiting server thread", e);
+                } else {
+                    logger.warn("Exception occurred during socket.accept", e);
                 }
-                else {
-                    // We're shutting down so an exception is expected as the socket's been closed.
-                    // Log to debug.
-                    logger.debug("Shutting down - got expected exception: " + e);
+            }
+            if (acceptedSocket != null) {
+                try {
+                    Handshaker task = new Handshaker(acceptedSocket, localAddress, sessionIdCounter.incrementAndGet(), authProvider, bossGroup);
+                    handshakeExecutor.submit(task);
+                } catch (IOException e) {
+                    logger.warn("Cannot set PEMHostKey, closing connection", e);
+                    try {
+                        acceptedSocket.close();
+                    } catch (IOException e1) {
+                        logger.warn("Ignoring exception while closing socket", e);
+                    }
                 }
             }
         }
+        logger.debug("Server thread is exiting");
     }
 }
index 2e9a0b9d8bbd256154ff82689e85e556b60c9e90..5d39dd1eb8adad115e030c0136cbae702d2bde65 100644 (file)
@@ -7,41 +7,75 @@
  */
 package org.opendaylight.controller.netconf.ssh.authentication;
 
-import java.io.IOException;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
 import org.opendaylight.controller.sal.authorization.AuthResultEnum;
 import org.opendaylight.controller.usermanager.IUserManager;
-import static com.google.common.base.Preconditions.checkNotNull;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-public class AuthProvider implements AuthProviderInterface {
+public class AuthProvider {
+    private static final Logger logger = LoggerFactory.getLogger(AuthProvider.class);
 
-    private IUserManager um;
     private final String pem;
+    private IUserManager nullableUserManager;
 
-    public AuthProvider(IUserManager ium, String pemCertificate) throws IllegalArgumentException, IOException {
+    public AuthProvider(String pemCertificate, final BundleContext bundleContext) {
         checkNotNull(pemCertificate, "Parameter 'pemCertificate' is null");
-        checkNotNull(ium, "No user manager service available.");
-        this.um = ium;
         pem = pemCertificate;
+
+        ServiceTrackerCustomizer<IUserManager, IUserManager> customizer = new ServiceTrackerCustomizer<IUserManager, IUserManager>() {
+            @Override
+            public IUserManager addingService(final ServiceReference<IUserManager> reference) {
+                logger.trace("Service {} added", reference);
+                nullableUserManager = bundleContext.getService(reference);
+                return nullableUserManager;
+            }
+
+            @Override
+            public void modifiedService(final ServiceReference<IUserManager> reference, final IUserManager service) {
+                logger.trace("Replacing modified service {} in netconf SSH.", reference);
+                nullableUserManager = service;
+            }
+
+            @Override
+            public void removedService(final ServiceReference<IUserManager> reference, final IUserManager service) {
+                logger.trace("Removing service {} from netconf SSH. " +
+                        "SSH won't authenticate users until IUserManager service will be started.", reference);
+                synchronized (AuthProvider.this) {
+                    nullableUserManager = null;
+                }
+            }
+        };
+        ServiceTracker<IUserManager, IUserManager> listenerTracker = new ServiceTracker<>(bundleContext, IUserManager.class, customizer);
+        listenerTracker.open();
     }
 
-    @Override
-    public boolean authenticated(String username, String password) {
-        AuthResultEnum authResult = this.um.authenticate(username, password);
+    /**
+     * Authenticate user. This implementation tracks IUserManager and delegates the decision to it. If the service is not
+     * available, IllegalStateException is thrown.
+     */
+    public synchronized boolean authenticated(String username, String password) {
+        if (nullableUserManager == null) {
+            logger.warn("Cannot authenticate user '{}', user manager service is missing", username);
+            throw new IllegalStateException("User manager service is not available");
+        }
+        AuthResultEnum authResult = nullableUserManager.authenticate(username, password);
+        logger.debug("Authentication result for user '{}' : {}", username, authResult);
         return authResult.equals(AuthResultEnum.AUTH_ACCEPT) || authResult.equals(AuthResultEnum.AUTH_ACCEPT_LOC);
     }
 
-    @Override
     public char[] getPEMAsCharArray() {
         return pem.toCharArray();
     }
 
-    @Override
-    public void removeUserManagerService() {
-        this.um = null;
-    }
-
-    @Override
-    public void addUserManagerService(IUserManager userManagerService) {
-        this.um = userManagerService;
+    @VisibleForTesting
+    void setNullableUserManager(IUserManager nullableUserManager) {
+        this.nullableUserManager = nullableUserManager;
     }
 }
diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProviderInterface.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProviderInterface.java
deleted file mode 100644 (file)
index fad0f79..0000000
+++ /dev/null
@@ -1,19 +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.ssh.authentication;
-
-import org.opendaylight.controller.usermanager.IUserManager;
-
-public interface AuthProviderInterface {
-
-    public boolean authenticated(String username, String password) throws IllegalStateException;
-    public char[] getPEMAsCharArray() throws Exception;
-    public void removeUserManagerService();
-    public void addUserManagerService(IUserManager userManagerService);
-}
index 348fe006f3a7d4cfefcb8c502a9115da422a0a61..53ab8219ee9c2a9f98e144f8c125f45b86edb002 100644 (file)
@@ -8,8 +8,11 @@
 
 package org.opendaylight.controller.netconf.ssh.authentication;
 
+import com.google.common.annotations.VisibleForTesting;
+import java.io.FileInputStream;
 import java.security.NoSuchAlgorithmException;
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
 import org.bouncycastle.openssl.PEMWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -26,17 +29,55 @@ public class PEMGenerator {
     private static final Logger logger = LoggerFactory.getLogger(PEMGenerator.class);
     private static final int KEY_SIZE = 4096;
 
+
+    public static String readOrGeneratePK(File privateKeyFile) throws IOException {
+        if (privateKeyFile.exists() == false) {
+            // generate & save to file
+            try {
+                return generateTo(privateKeyFile);
+            } catch (Exception e) {
+                logger.error("Exception occurred while generating PEM string to {}", privateKeyFile, e);
+                throw new IllegalStateException("Error generating RSA key from file " + privateKeyFile);
+            }
+        } else {
+            // read from file
+            try (FileInputStream fis = new FileInputStream(privateKeyFile)) {
+                return IOUtils.toString(fis);
+            } catch (final IOException e) {
+                logger.error("Error reading RSA key from file {}", privateKeyFile, e);
+                throw new IOException("Error reading RSA key from file " + privateKeyFile, e);
+            }
+        }
+    }
+
+    /**
+     * Generate private key to a file and return its content as string.
+     *
+     * @param privateFile path where private key should be generated
+     * @return String representation of private key
+     * @throws IOException
+     * @throws NoSuchAlgorithmException
+     */
+    @VisibleForTesting
     public static String generateTo(File privateFile) throws IOException, NoSuchAlgorithmException {
+        logger.info("Generating private key to {}", privateFile.getAbsolutePath());
+        String privatePEM = generate();
+        FileUtils.write(privateFile, privatePEM);
+        return privatePEM;
+    }
+
+    @VisibleForTesting
+    public static String generate() throws NoSuchAlgorithmException, IOException {
         KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
         SecureRandom sr = new SecureRandom();
         keyGen.initialize(KEY_SIZE, sr);
         KeyPair keypair = keyGen.generateKeyPair();
-        logger.info("Generating private key to {}", privateFile.getAbsolutePath());
-        String privatePEM = toString(keypair.getPrivate());
-        FileUtils.write(privateFile, privatePEM);
-        return privatePEM;
+        return toString(keypair.getPrivate());
     }
 
+    /**
+     * Get string representation of a key.
+     */
     private static String toString(Key key) throws IOException {
         try (StringWriter writer = new StringWriter()) {
             try (PEMWriter pemWriter = new PEMWriter(writer)) {
@@ -45,4 +86,5 @@ public class PEMGenerator {
             return writer.toString();
         }
     }
+
 }
index d74308cfadbae8e658e58f9b189d93baaea83c2e..a26843fae17a97621b48221a932eb9b823a6ce83 100644 (file)
@@ -7,24 +7,24 @@
  */
 package org.opendaylight.controller.netconf.ssh.osgi;
 
-import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
 
+import com.google.common.base.Optional;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.nio.NioEventLoopGroup;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
 import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
 import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator;
 import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
-import org.opendaylight.controller.usermanager.IUserManager;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil.InfixProp;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.ServiceTracker;
-import org.osgi.util.tracker.ServiceTrackerCustomizer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -32,112 +32,56 @@ import org.slf4j.LoggerFactory;
  * Activator for netconf SSH bundle which creates SSH bridge between netconf client and netconf server. Activator
  * starts SSH Server in its own thread. This thread is closed when activator calls stop() method. Server opens socket
  * and listens for client connections. Each client connection creation is handled in separate
- * {@link org.opendaylight.controller.netconf.ssh.threads.SocketThread} thread.
+ * {@link org.opendaylight.controller.netconf.ssh.threads.Handshaker} thread.
  * This thread creates two additional threads {@link org.opendaylight.controller.netconf.ssh.threads.IOThread}
  * forwarding data from/to client.IOThread closes servers session and server connection when it gets -1 on input stream.
  * {@link org.opendaylight.controller.netconf.ssh.threads.IOThread}'s run method waits for -1 on input stream to finish.
  * All threads are daemons.
- **/
-public class NetconfSSHActivator implements BundleActivator{
+ */
+public class NetconfSSHActivator implements BundleActivator {
+    private static final Logger logger = LoggerFactory.getLogger(NetconfSSHActivator.class);
 
     private NetconfSSHServer server;
-    private static final Logger logger =  LoggerFactory.getLogger(NetconfSSHActivator.class);
-    private IUserManager iUserManager;
-    private BundleContext context = null;
-
-    private ServiceTrackerCustomizer<IUserManager, IUserManager> customizer = new ServiceTrackerCustomizer<IUserManager, IUserManager>(){
-        @Override
-        public IUserManager addingService(final ServiceReference<IUserManager> reference) {
-            logger.trace("Service {} added, let there be SSH bridge.", reference);
-            iUserManager =  context.getService(reference);
-            try {
-                onUserManagerFound(iUserManager);
-            } catch (final Exception e) {
-                logger.trace("Can't start SSH server due to {}",e);
-            }
-            return iUserManager;
-        }
-        @Override
-        public void modifiedService(final ServiceReference<IUserManager> reference, final IUserManager service) {
-            logger.trace("Replacing modified service {} in netconf SSH.", reference);
-            server.addUserManagerService(service);
-        }
-        @Override
-        public void removedService(final ServiceReference<IUserManager> reference, final IUserManager service) {
-            logger.trace("Removing service {} from netconf SSH. " +
-                    "SSH won't authenticate users until IUserManager service will be started.", reference);
-            removeUserManagerService();
-        }
-    };
-
 
     @Override
-    public void start(final BundleContext context) {
-        this.context = context;
-        listenForManagerService();
+    public void start(final BundleContext bundleContext) throws IOException {
+        server = startSSHServer(bundleContext);
     }
 
     @Override
     public void stop(BundleContext context) throws IOException {
-        if (server != null){
-            server.stop();
-            logger.trace("Netconf SSH bridge is down ...");
+        if (server != null) {
+            server.close();
         }
     }
-    private void startSSHServer() throws IOException {
-        checkNotNull(this.iUserManager, "No user manager service available.");
-        logger.trace("Starting netconf SSH  bridge.");
-        final InetSocketAddress sshSocketAddress = NetconfConfigUtil.extractSSHNetconfAddress(context,
-                NetconfConfigUtil.DEFAULT_NETCONF_SSH_ADDRESS);
-        final InetSocketAddress tcpSocketAddress = NetconfConfigUtil.extractTCPNetconfClientAddress(context,
-               NetconfConfigUtil.DEFAULT_NETCONF_TCP_ADDRESS);
 
-        String path =  FilenameUtils.separatorsToSystem(NetconfConfigUtil.getPrivateKeyPath(context));
+    private static NetconfSSHServer startSSHServer(BundleContext bundleContext) throws IOException {
+        Optional<InetSocketAddress> maybeSshSocketAddress = NetconfConfigUtil.extractNetconfServerAddress(bundleContext,
+                InfixProp.ssh);
 
-        if (path.isEmpty()) {
-            throw new IllegalStateException("Missing netconf.ssh.pk.path key in configuration file.");
+        if (maybeSshSocketAddress.isPresent() == false) {
+            logger.trace("SSH bridge not configured");
+            return null;
         }
+        InetSocketAddress sshSocketAddress = maybeSshSocketAddress.get();
+        logger.trace("Starting netconf SSH  bridge at {}", sshSocketAddress);
 
-        final File privateKeyFile = new File(path);
-        final String privateKeyPEMString;
-        if (privateKeyFile.exists() == false) {
-            // generate & save to file
-            try {
-                privateKeyPEMString = PEMGenerator.generateTo(privateKeyFile);
-            } catch (Exception e) {
-                logger.error("Exception occurred while generating PEM string {}", e);
-                throw new IllegalStateException("Error generating RSA key from file " + path);
-            }
-        } else {
-            // read from file
-            try (FileInputStream fis = new FileInputStream(path)) {
-                privateKeyPEMString = IOUtils.toString(fis);
-            } catch (final IOException e) {
-                logger.error("Error reading RSA key from file '{}'", path);
-                throw new IOException("Error reading RSA key from file " + path, e);
-            }
-        }
-        final AuthProvider authProvider = new AuthProvider(iUserManager, privateKeyPEMString);
-        this.server = NetconfSSHServer.start(sshSocketAddress.getPort(), tcpSocketAddress, authProvider);
+        LocalAddress localAddress = NetconfConfigUtil.getNetconfLocalAddress();
+
+        String path = FilenameUtils.separatorsToSystem(NetconfConfigUtil.getPrivateKeyPath(bundleContext));
+        checkState(StringUtils.isNotBlank(path), "Path to ssh private key is blank. Reconfigure %s", NetconfConfigUtil.getPrivateKeyKey());
+        String privateKeyPEMString = PEMGenerator.readOrGeneratePK(new File(path));
+
+        final AuthProvider authProvider = new AuthProvider(privateKeyPEMString, bundleContext);
+        EventLoopGroup bossGroup  = new NioEventLoopGroup();
+        NetconfSSHServer server = NetconfSSHServer.start(sshSocketAddress.getPort(), localAddress, authProvider, bossGroup);
 
         final Thread serverThread = new Thread(server, "netconf SSH server thread");
         serverThread.setDaemon(true);
         serverThread.start();
         logger.trace("Netconf SSH  bridge up and running.");
+        return server;
     }
 
-    private void onUserManagerFound(final IUserManager userManager) throws Exception{
-        if (server!=null && server.isUp()){
-           server.addUserManagerService(userManager);
-        } else {
-           startSSHServer();
-        }
-    }
-    private void removeUserManagerService(){
-        this.server.removeUserManagerService();
-    }
-    private void listenForManagerService(){
-        final ServiceTracker<IUserManager, IUserManager> listenerTracker = new ServiceTracker<>(context, IUserManager.class,customizer);
-        listenerTracker.open();
-    }
+
 }
diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/Handshaker.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/Handshaker.java
new file mode 100644 (file)
index 0000000..d999d37
--- /dev/null
@@ -0,0 +1,406 @@
+/*
+ * 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.ssh.threads;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import ch.ethz.ssh2.AuthenticationResult;
+import ch.ethz.ssh2.PtySettings;
+import ch.ethz.ssh2.ServerAuthenticationCallback;
+import ch.ethz.ssh2.ServerConnection;
+import ch.ethz.ssh2.ServerConnectionCallback;
+import ch.ethz.ssh2.ServerSession;
+import ch.ethz.ssh2.ServerSessionCallback;
+import ch.ethz.ssh2.SimpleServerSessionCallback;
+import com.google.common.base.Supplier;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufProcessor;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import io.netty.handler.stream.ChunkedStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import javax.annotation.concurrent.NotThreadSafe;
+import javax.annotation.concurrent.ThreadSafe;
+import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * One instance represents per connection, responsible for ssh handshake.
+ * Once auth succeeds and correct subsystem is chosen, backend connection with
+ * netty netconf server is made. This task finishes right after negotiation is done.
+ */
+@ThreadSafe
+public class Handshaker implements Runnable {
+    private static final Logger logger = LoggerFactory.getLogger(Handshaker.class);
+
+    private final ServerConnection ganymedConnection;
+    private final String session;
+
+
+    public Handshaker(Socket socket, LocalAddress localAddress, long sessionId, AuthProvider authProvider,
+                      EventLoopGroup bossGroup) throws IOException {
+
+        this.session = "Session " + sessionId;
+
+        String remoteAddressWithPort = socket.getRemoteSocketAddress().toString().replace("/", "");
+        logger.debug("{} started with {}", session, remoteAddressWithPort);
+        String remoteAddress, remotePort;
+        if (remoteAddressWithPort.contains(":")) {
+            String[] split = remoteAddressWithPort.split(":");
+            remoteAddress = split[0];
+            remotePort = split[1];
+        } else {
+            remoteAddress = remoteAddressWithPort;
+            remotePort = "";
+        }
+        ServerAuthenticationCallbackImpl serverAuthenticationCallback = new ServerAuthenticationCallbackImpl(
+                authProvider, session);
+
+        ganymedConnection = new ServerConnection(socket);
+
+        ServerConnectionCallbackImpl serverConnectionCallback = new ServerConnectionCallbackImpl(
+                serverAuthenticationCallback, remoteAddress, remotePort, session,
+                getGanymedAutoCloseable(ganymedConnection), localAddress, bossGroup);
+
+        // initialize ganymed
+        ganymedConnection.setPEMHostKey(authProvider.getPEMAsCharArray(), null);
+        ganymedConnection.setAuthenticationCallback(serverAuthenticationCallback);
+        ganymedConnection.setServerConnectionCallback(serverConnectionCallback);
+    }
+
+
+    private static AutoCloseable getGanymedAutoCloseable(final ServerConnection ganymedConnection) {
+        return new AutoCloseable() {
+            @Override
+            public void close() throws Exception {
+                ganymedConnection.close();
+            }
+        };
+    }
+
+    @Override
+    public void run() {
+        // let ganymed process handshake
+        logger.trace("{} SocketThread is started", session);
+        try {
+            // TODO this should be guarded with a timer to prevent resource exhaustion
+            ganymedConnection.connect();
+        } catch (IOException e) {
+            logger.warn("{} SocketThread error ", session, e);
+        }
+        logger.trace("{} SocketThread is exiting", session);
+    }
+}
+
+/**
+ * Netty client handler that forwards bytes from backed server to supplied output stream.
+ * When backend server closes the connection, remoteConnection.close() is called to tear
+ * down ssh connection.
+ */
+class SSHClientHandler extends ChannelInboundHandlerAdapter {
+    private static final Logger logger = LoggerFactory.getLogger(SSHClientHandler.class);
+    private final AutoCloseable remoteConnection;
+    private final OutputStream remoteOutputStream;
+    private final String session;
+    private ChannelHandlerContext channelHandlerContext;
+
+    public SSHClientHandler(AutoCloseable remoteConnection, OutputStream remoteOutputStream,
+                            String session) {
+        this.remoteConnection = remoteConnection;
+        this.remoteOutputStream = remoteOutputStream;
+        this.session = session;
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) {
+        this.channelHandlerContext = ctx;
+        logger.debug("{} Client active", session);
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) {
+        ByteBuf bb = (ByteBuf) msg;
+        // we can block the server here so that slow client does not cause memory pressure
+        try {
+            bb.forEachByte(new ByteBufProcessor() {
+                @Override
+                public boolean process(byte value) throws Exception {
+                    remoteOutputStream.write(value);
+                    return true;
+                }
+            });
+        } finally {
+            bb.release();
+        }
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) throws IOException {
+        logger.trace("{} Flushing", session);
+        remoteOutputStream.flush();
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        // Close the connection when an exception is raised.
+        logger.warn("{} Unexpected exception from downstream", session, cause);
+        ctx.close();
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+        logger.trace("{} channelInactive() called, closing remote client ctx", session);
+        remoteConnection.close();//this should close socket and all threads created for this client
+        this.channelHandlerContext = null;
+    }
+
+    public ChannelHandlerContext getChannelHandlerContext() {
+        return checkNotNull(channelHandlerContext, "Channel is not active");
+    }
+}
+
+/**
+ * Ganymed handler that gets unencrypted input and output streams, connects them to netty.
+ * Checks that 'netconf' subsystem is chosen by user.
+ * Launches new ClientInputStreamPoolingThread thread once session is established.
+ * Writes custom header to netty server, to inform it about IP address and username.
+ */
+class ServerConnectionCallbackImpl implements ServerConnectionCallback {
+    private static final Logger logger = LoggerFactory.getLogger(ServerConnectionCallbackImpl.class);
+    public static final String NETCONF_SUBSYSTEM = "netconf";
+
+    private final Supplier<String> currentUserSupplier;
+    private final String remoteAddress;
+    private final String remotePort;
+    private final String session;
+    private final AutoCloseable ganymedConnection;
+    private final LocalAddress localAddress;
+    private final EventLoopGroup bossGroup;
+
+    ServerConnectionCallbackImpl(Supplier<String> currentUserSupplier, String remoteAddress, String remotePort, String session,
+                                 AutoCloseable ganymedConnection, LocalAddress localAddress, EventLoopGroup bossGroup) {
+        this.currentUserSupplier = currentUserSupplier;
+        this.remoteAddress = remoteAddress;
+        this.remotePort = remotePort;
+        this.session = session;
+        this.ganymedConnection = ganymedConnection;
+        // initialize netty local connection
+        this.localAddress = localAddress;
+        this.bossGroup = bossGroup;
+    }
+
+    private static ChannelFuture initializeNettyConnection(LocalAddress localAddress, EventLoopGroup bossGroup,
+                                                           final SSHClientHandler sshClientHandler) {
+        Bootstrap clientBootstrap = new Bootstrap();
+        clientBootstrap.group(bossGroup).channel(LocalChannel.class);
+
+        clientBootstrap.handler(new ChannelInitializer<LocalChannel>() {
+            @Override
+            public void initChannel(LocalChannel ch) throws Exception {
+                ch.pipeline().addLast(sshClientHandler);
+            }
+        });
+        // asynchronously initialize local connection to netconf server
+        return clientBootstrap.connect(localAddress);
+    }
+
+    @Override
+    public ServerSessionCallback acceptSession(final ServerSession serverSession) {
+        String currentUser = currentUserSupplier.get();
+        final String additionalHeader = new NetconfHelloMessageAdditionalHeader(currentUser, remoteAddress,
+                remotePort, "ssh", "client").toFormattedString();
+
+
+        return new SimpleServerSessionCallback() {
+            @Override
+            public Runnable requestSubsystem(final ServerSession ss, final String subsystem) throws IOException {
+                return new Runnable() {
+                    @Override
+                    public void run() {
+                        if (NETCONF_SUBSYSTEM.equals(subsystem)) {
+                            // connect
+                            final SSHClientHandler sshClientHandler = new SSHClientHandler(ganymedConnection, ss.getStdin(), session);
+                            ChannelFuture clientChannelFuture = initializeNettyConnection(localAddress, bossGroup, sshClientHandler);
+                            // get channel
+                            final Channel channel = clientChannelFuture.awaitUninterruptibly().channel();
+                            new ClientInputStreamPoolingThread(session, ss.getStdout(), channel, new AutoCloseable() {
+                                @Override
+                                public void close() throws Exception {
+                                    logger.trace("Closing both ganymed and local connection");
+                                    try {
+                                        ganymedConnection.close();
+                                    } catch (Exception e) {
+                                        logger.warn("Ignoring exception while closing ganymed", e);
+                                    }
+                                    try {
+                                        channel.close();
+                                    } catch (Exception e) {
+                                        logger.warn("Ignoring exception while closing channel", e);
+                                    }
+                                }
+                            }, sshClientHandler.getChannelHandlerContext()).start();
+
+                            // write additional header
+                            channel.writeAndFlush(Unpooled.copiedBuffer(additionalHeader.getBytes()));
+                        } else {
+                            logger.debug("{} Wrong subsystem requested:'{}', closing ssh session", serverSession, subsystem);
+                            String reason = "Only netconf subsystem is supported, requested:" + subsystem;
+                            closeSession(ss, reason);
+                        }
+                    }
+                };
+            }
+
+            public void closeSession(ServerSession ss, String reason) {
+                logger.trace("{} Closing session - {}", serverSession, reason);
+                try {
+                    ss.getStdin().write(reason.getBytes());
+                } catch (IOException e) {
+                    logger.warn("{} Exception while closing session", serverSession, e);
+                }
+                ss.close();
+            }
+
+            @Override
+            public Runnable requestPtyReq(final ServerSession ss, final PtySettings pty) throws IOException {
+                return new Runnable() {
+                    @Override
+                    public void run() {
+                        closeSession(ss, "PTY request not supported");
+                    }
+                };
+            }
+
+            @Override
+            public Runnable requestShell(final ServerSession ss) throws IOException {
+                return new Runnable() {
+                    @Override
+                    public void run() {
+                        closeSession(ss, "Shell not supported");
+                    }
+                };
+            }
+        };
+    }
+}
+
+/**
+ * Only thread that is required during ssh session, forwards client's input to netty.
+ * When user closes connection, onEndOfInput.close() is called to tear down the local channel.
+ */
+class ClientInputStreamPoolingThread extends Thread {
+    private static final Logger logger = LoggerFactory.getLogger(ClientInputStreamPoolingThread.class);
+
+    private final InputStream fromClientIS;
+    private final Channel serverChannel;
+    private final AutoCloseable onEndOfInput;
+    private final ChannelHandlerContext channelHandlerContext;
+
+    ClientInputStreamPoolingThread(String session, InputStream fromClientIS, Channel serverChannel, AutoCloseable onEndOfInput,
+                                   ChannelHandlerContext channelHandlerContext) {
+        super(ClientInputStreamPoolingThread.class.getSimpleName() + " " + session);
+        this.fromClientIS = fromClientIS;
+        this.serverChannel = serverChannel;
+        this.onEndOfInput = onEndOfInput;
+        this.channelHandlerContext = channelHandlerContext;
+    }
+
+    @Override
+    public void run() {
+        ChunkedStream chunkedStream = new ChunkedStream(fromClientIS);
+        try {
+            ByteBuf byteBuf;
+            while ((byteBuf = chunkedStream.readChunk(channelHandlerContext/*only needed for ByteBuf alloc */)) != null) {
+                serverChannel.writeAndFlush(byteBuf);
+            }
+        } catch (Exception e) {
+            logger.warn("Exception", e);
+        } finally {
+            logger.trace("End of input");
+            // tear down connection
+            try {
+                onEndOfInput.close();
+            } catch (Exception e) {
+                logger.warn("Ignoring exception while closing socket", e);
+            }
+        }
+    }
+}
+
+/**
+ * Authentication handler for ganymed.
+ * Provides current user name after authenticating using supplied AuthProvider.
+ */
+@NotThreadSafe
+class ServerAuthenticationCallbackImpl implements ServerAuthenticationCallback, Supplier<String> {
+    private static final Logger logger = LoggerFactory.getLogger(ServerAuthenticationCallbackImpl.class);
+    private final AuthProvider authProvider;
+    private final String session;
+    private String currentUser;
+
+    ServerAuthenticationCallbackImpl(AuthProvider authProvider, String session) {
+        this.authProvider = authProvider;
+        this.session = session;
+    }
+
+    @Override
+    public String initAuthentication(ServerConnection sc) {
+        logger.trace("{} Established connection", session);
+        return "Established connection" + "\r\n";
+    }
+
+    @Override
+    public String[] getRemainingAuthMethods(ServerConnection sc) {
+        return new String[]{ServerAuthenticationCallback.METHOD_PASSWORD};
+    }
+
+    @Override
+    public AuthenticationResult authenticateWithNone(ServerConnection sc, String username) {
+        return AuthenticationResult.FAILURE;
+    }
+
+    @Override
+    public AuthenticationResult authenticateWithPassword(ServerConnection sc, String username, String password) {
+        checkState(currentUser == null);
+        try {
+            if (authProvider.authenticated(username, password)) {
+                currentUser = username;
+                logger.trace("{} user {} authenticated", session, currentUser);
+                return AuthenticationResult.SUCCESS;
+            }
+        } catch (Exception e) {
+            logger.warn("{} Authentication failed", session, e);
+        }
+        return AuthenticationResult.FAILURE;
+    }
+
+    @Override
+    public AuthenticationResult authenticateWithPublicKey(ServerConnection sc, String username, String algorithm,
+                                                          byte[] publicKey, byte[] signature) {
+        return AuthenticationResult.FAILURE;
+    }
+
+    @Override
+    public String get() {
+        return currentUser;
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/IOThread.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/IOThread.java
deleted file mode 100644 (file)
index c53a625..0000000
+++ /dev/null
@@ -1,70 +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.ssh.threads;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import javax.annotation.concurrent.ThreadSafe;
-
-import org.apache.commons.io.IOUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import ch.ethz.ssh2.ServerConnection;
-import ch.ethz.ssh2.ServerSession;
-
-@ThreadSafe
-public class IOThread extends Thread {
-
-    private static final Logger logger =  LoggerFactory.getLogger(IOThread.class);
-
-    private final InputStream inputStream;
-    private final OutputStream outputStream;
-    private final ServerSession servSession;
-    private final ServerConnection servconnection;
-    private String customHeader;
-
-
-    public IOThread (InputStream is, OutputStream os, String id,ServerSession ss, ServerConnection conn){
-        this.inputStream = is;
-        this.outputStream = os;
-        this.servSession = ss;
-        this.servconnection = conn;
-        super.setName(id);
-        logger.trace("IOThread {} created", super.getName());
-    }
-
-    public IOThread (InputStream is, OutputStream os, String id,ServerSession ss, ServerConnection conn,String header){
-        this.inputStream = is;
-        this.outputStream = os;
-        this.servSession = ss;
-        this.servconnection = conn;
-        this.customHeader = header;
-        super.setName(id);
-        logger.trace("IOThread {} created", super.getName());
-    }
-
-    @Override
-    public void run() {
-        logger.trace("thread {} started", super.getName());
-        try {
-            if (this.customHeader!=null && !this.customHeader.equals("")){
-                this.outputStream.write(this.customHeader.getBytes());
-                logger.trace("adding {} header", this.customHeader);
-            }
-            IOUtils.copy(this.inputStream, this.outputStream);
-        } catch (Exception e) {
-            logger.error("inputstream -> outputstream copy error ",e);
-        }
-        logger.trace("closing server session");
-        servSession.close();
-        servconnection.close();
-        logger.trace("thread {} is closing",super.getName());
-    }
-}
diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/SocketThread.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/SocketThread.java
deleted file mode 100644 (file)
index 04639cb..0000000
+++ /dev/null
@@ -1,208 +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.ssh.threads;
-
-
-import ch.ethz.ssh2.AuthenticationResult;
-import ch.ethz.ssh2.PtySettings;
-import ch.ethz.ssh2.ServerAuthenticationCallback;
-import ch.ethz.ssh2.ServerConnection;
-import ch.ethz.ssh2.ServerConnectionCallback;
-import ch.ethz.ssh2.ServerSession;
-import ch.ethz.ssh2.ServerSessionCallback;
-import ch.ethz.ssh2.SimpleServerSessionCallback;
-import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.annotation.concurrent.ThreadSafe;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-
-@ThreadSafe
-public class SocketThread implements Runnable, ServerAuthenticationCallback, ServerConnectionCallback {
-    private static final Logger logger = LoggerFactory.getLogger(SocketThread.class);
-
-    private final Socket socket;
-    private final InetSocketAddress clientAddress;
-    private ServerConnection conn = null;
-    private final long sessionId;
-    private String currentUser;
-    private final String remoteAddressWithPort;
-    private final AuthProvider authProvider;
-
-
-    public static void start(Socket socket,
-                             InetSocketAddress clientAddress,
-                             long sessionId,
-                             AuthProvider authProvider) throws IOException {
-        Thread netconf_ssh_socket_thread = new Thread(new SocketThread(socket, clientAddress, sessionId, authProvider));
-        netconf_ssh_socket_thread.setDaemon(true);
-        netconf_ssh_socket_thread.start();
-    }
-
-    private SocketThread(Socket socket,
-                         InetSocketAddress clientAddress,
-                         long sessionId,
-                         AuthProvider authProvider) throws IOException {
-
-        this.socket = socket;
-        this.clientAddress = clientAddress;
-        this.sessionId = sessionId;
-        this.remoteAddressWithPort = socket.getRemoteSocketAddress().toString().replaceFirst("/", "");
-        this.authProvider = authProvider;
-
-    }
-
-    @Override
-    public void run() {
-        conn = new ServerConnection(socket);
-        try {
-            conn.setPEMHostKey(authProvider.getPEMAsCharArray(), "netconf");
-        } catch (Exception e) {
-            logger.warn("Server authentication setup failed.", e);
-        }
-        conn.setAuthenticationCallback(this);
-        conn.setServerConnectionCallback(this);
-        try {
-            conn.connect();
-        } catch (IOException e) {
-            logger.error("SocketThread error ", e);
-        }
-    }
-
-    @Override
-    public ServerSessionCallback acceptSession(final ServerSession session) {
-        SimpleServerSessionCallback cb = new SimpleServerSessionCallback() {
-            @Override
-            public Runnable requestSubsystem(final ServerSession ss, final String subsystem) throws IOException {
-                return new Runnable() {
-                    @Override
-                    public void run() {
-                        if (subsystem.equals("netconf")) {
-                            IOThread netconf_ssh_input = null;
-                            IOThread netconf_ssh_output = null;
-                            try {
-                                String hostName = clientAddress.getHostName();
-                                int portNumber = clientAddress.getPort();
-                                final Socket echoSocket = new Socket(hostName, portNumber);
-                                logger.trace("echo socket created");
-
-                                logger.trace("starting netconf_ssh_input thread");
-                                netconf_ssh_input = new IOThread(echoSocket.getInputStream(), ss.getStdin(), "input_thread_" + sessionId, ss, conn);
-                                netconf_ssh_input.setDaemon(false);
-                                netconf_ssh_input.start();
-
-                                logger.trace("starting netconf_ssh_output thread");
-                                final String customHeader = "[" + currentUser + ";" + remoteAddressWithPort + ";ssh;;;;;;]\n";
-                                netconf_ssh_output = new IOThread(ss.getStdout(), echoSocket.getOutputStream(), "output_thread_" + sessionId, ss, conn, customHeader);
-                                netconf_ssh_output.setDaemon(false);
-                                netconf_ssh_output.start();
-
-                            } catch (Exception t) {
-                                logger.error("SSH bridge could not create echo socket: {}", t.getMessage(), t);
-
-                                try {
-                                    if (netconf_ssh_input != null) {
-                                        netconf_ssh_input.join();
-                                    }
-                                } catch (InterruptedException e1) {
-                                    Thread.currentThread().interrupt();
-                                    logger.error("netconf_ssh_input join error ", e1);
-                                }
-
-                                try {
-                                    if (netconf_ssh_output != null) {
-                                        netconf_ssh_output.join();
-                                    }
-                                } catch (InterruptedException e2) {
-                                    Thread.currentThread().interrupt();
-                                    logger.error("netconf_ssh_output join error ", e2);
-                                }
-                            }
-                        } else {
-                            String reason = "Only netconf subsystem is supported, requested:" + subsystem;
-                            closeSession(ss, reason);
-                        }
-                    }
-                };
-            }
-
-            public void closeSession(ServerSession ss, String reason) {
-                logger.trace("Closing session - {}", reason);
-                try {
-                    ss.getStdin().write(reason.getBytes());
-                } catch (IOException e) {
-                    logger.debug("Exception while closing session", e);
-                }
-                ss.close();
-            }
-
-            @Override
-            public Runnable requestPtyReq(final ServerSession ss, final PtySettings pty) throws IOException {
-                return new Runnable() {
-                    @Override
-                    public void run() {
-                        closeSession(ss, "PTY request not supported");
-                    }
-                };
-            }
-
-            @Override
-            public Runnable requestShell(final ServerSession ss) throws IOException {
-                return new Runnable() {
-                    @Override
-                    public void run() {
-                        closeSession(ss, "Shell not supported");
-                    }
-                };
-            }
-        };
-
-        return cb;
-    }
-
-    @Override
-    public String initAuthentication(ServerConnection sc) {
-        logger.trace("Established connection with host {}", remoteAddressWithPort);
-        return "Established connection with host " + remoteAddressWithPort + "\r\n";
-    }
-
-    @Override
-    public String[] getRemainingAuthMethods(ServerConnection sc) {
-        return new String[]{ServerAuthenticationCallback.METHOD_PASSWORD};
-    }
-
-    @Override
-    public AuthenticationResult authenticateWithNone(ServerConnection sc, String username) {
-        return AuthenticationResult.FAILURE;
-    }
-
-    @Override
-    public AuthenticationResult authenticateWithPassword(ServerConnection sc, String username, String password) {
-
-        try {
-            if (authProvider.authenticated(username, password)) {
-                currentUser = username;
-                logger.trace("user {}@{} authenticated", currentUser, remoteAddressWithPort);
-                return AuthenticationResult.SUCCESS;
-            }
-        } catch (Exception e) {
-            logger.warn("Authentication failed due to :" + e.getLocalizedMessage());
-        }
-        return AuthenticationResult.FAILURE;
-    }
-
-    @Override
-    public AuthenticationResult authenticateWithPublicKey(ServerConnection sc, String username, String algorithm,
-                                                          byte[] publickey, byte[] signature) {
-        return AuthenticationResult.FAILURE;
-    }
-
-}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/KeyGeneratorTest.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/KeyGeneratorTest.java
deleted file mode 100644 (file)
index 298f91c..0000000
+++ /dev/null
@@ -1,65 +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;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
-import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
-import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator;
-import org.opendaylight.controller.usermanager.IUserManager;
-import org.opendaylight.controller.usermanager.UserConfig;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.Inet4Address;
-import java.net.InetSocketAddress;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doReturn;
-
-// This test is intended to be verified using ssh
-@Ignore
-public class KeyGeneratorTest {
-
-    @Mock
-    private IUserManager iUserManager;
-    File tempFile;
-
-    @Before
-    public void setUp() throws IOException {
-        MockitoAnnotations.initMocks(this);
-        doReturn(null).when(iUserManager).addLocalUser(any(UserConfig.class));
-        tempFile = File.createTempFile("odltest", ".tmp");
-        tempFile.deleteOnExit();
-    }
-
-    @After
-    public void tearDown() {
-        assertTrue(tempFile.delete());
-    }
-
-    @Test
-    public void test() throws Exception {
-        String pem = PEMGenerator.generateTo(tempFile);
-
-        AuthProvider authProvider = new AuthProvider(iUserManager, pem);
-        InetSocketAddress inetSocketAddress = new InetSocketAddress(Inet4Address.getLoopbackAddress().getHostAddress(), 8383);
-        NetconfSSHServer server = NetconfSSHServer.start(1830, inetSocketAddress, authProvider);
-
-        Thread serverThread = new  Thread(server,"netconf SSH server thread");
-        serverThread.start();
-        serverThread.join();
-    }
-}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/SSHServerTest.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/SSHServerTest.java
deleted file mode 100644 (file)
index 663a0b4..0000000
+++ /dev/null
@@ -1,68 +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;
-
-import ch.ethz.ssh2.Connection;
-import junit.framework.Assert;
-import org.apache.commons.io.IOUtils;
-import org.junit.Test;
-import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
-import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.InputStream;
-import java.net.InetSocketAddress;
-
-
-public class SSHServerTest {
-
-    private static final String USER = "netconf";
-    private static final String PASSWORD  = "netconf";
-    private static final String HOST = "127.0.0.1";
-    private static final int PORT = 1830;
-    private static final InetSocketAddress tcpAddress = new InetSocketAddress("127.0.0.1", 8383);
-    private static final Logger logger =  LoggerFactory.getLogger(SSHServerTest.class);
-    private Thread sshServerThread;
-
-
-
-
-    public void startSSHServer() throws Exception{
-        logger.info("Creating SSH server");
-        StubUserManager um = new StubUserManager(USER,PASSWORD);
-        String pem;
-        try(InputStream is = getClass().getResourceAsStream("/RSA.pk")) {
-            pem = IOUtils.toString(is);
-        }
-        AuthProvider ap = new AuthProvider(um, pem);
-        NetconfSSHServer server = NetconfSSHServer.start(PORT,tcpAddress,ap);
-        sshServerThread = new Thread(server);
-        sshServerThread.setDaemon(true);
-        sshServerThread.start();
-        logger.info("SSH server on");
-    }
-
-    @Test
-    public void connect(){
-        try {
-            this.startSSHServer();
-            Connection conn = new Connection(HOST,PORT);
-            Assert.assertNotNull(conn);
-            logger.info("connecting to SSH server");
-            conn.connect();
-            logger.info("authenticating ...");
-            boolean isAuthenticated = conn.authenticateWithPassword(USER,PASSWORD);
-            Assert.assertTrue(isAuthenticated);
-        } catch (Exception e) {
-            logger.error("Error while starting SSH server.", e);
-        }
-
-    }
-
-}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoClient.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoClient.java
new file mode 100644 (file)
index 0000000..5d0c71a
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * 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.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Sends one message when a connection is open and echoes back any received
+ * data to the server.  Simply put, the echo client initiates the ping-pong
+ * traffic between the echo client and server by sending the first message to
+ * the server.
+ */
+public class EchoClient implements Runnable {
+    private static final Logger logger = LoggerFactory.getLogger(EchoClient.class);
+
+    private final ChannelHandler clientHandler;
+
+
+    public EchoClient(ChannelHandler clientHandler) {
+        this.clientHandler = clientHandler;
+    }
+
+    public void run() {
+        // Configure the client.
+        EventLoopGroup group = new NioEventLoopGroup();
+        try {
+            Bootstrap b = new Bootstrap();
+            b.group(group)
+                    .channel(LocalChannel.class)
+                    .handler(new ChannelInitializer<LocalChannel>() {
+                        @Override
+                        public void initChannel(LocalChannel ch) throws Exception {
+                            ch.pipeline().addLast(clientHandler);
+                        }
+                    });
+
+            // Start the client.
+            LocalAddress localAddress = new LocalAddress("foo");
+            ChannelFuture f = b.connect(localAddress).sync();
+
+            // Wait until the connection is closed.
+            f.channel().closeFuture().sync();
+        } catch (Exception e) {
+            logger.error("Error in client", e);
+            throw new RuntimeException("Error in client", e);
+        } finally {
+            // Shut down the event loop to terminate all threads.
+            logger.info("Client is shutting down");
+            group.shutdownGracefully();
+        }
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoClientHandler.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoClientHandler.java
new file mode 100644 (file)
index 0000000..81182a5
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * 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.netty;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.base.Charsets;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handler implementation for the echo client.  It initiates the ping-pong
+ * traffic between the echo client and server by sending the first message to
+ * the server.
+ */
+public class EchoClientHandler extends ChannelInboundHandlerAdapter {
+    private static final Logger logger = LoggerFactory.getLogger(EchoClientHandler.class);
+
+    private ChannelHandlerContext ctx;
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) {
+        checkState(this.ctx == null);
+        logger.info("client active");
+        this.ctx = ctx;
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        ByteBuf bb = (ByteBuf) msg;
+        logger.info(">{}", bb.toString(Charsets.UTF_8));
+        bb.release();
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        // Close the connection when an exception is raised.
+        logger.warn("Unexpected exception from downstream.", cause);
+        checkState(this.ctx.equals(ctx));
+        ctx.close();
+        this.ctx = null;
+    }
+
+    public void write(String message) {
+        ByteBuf byteBuf = Unpooled.copiedBuffer(message.getBytes());
+        ctx.writeAndFlush(byteBuf);
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoServer.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoServer.java
new file mode 100644 (file)
index 0000000..ec89d75
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * 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.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import io.netty.channel.local.LocalServerChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Echoes back any received data from a client.
+ */
+public class EchoServer implements Runnable {
+    private static final Logger logger = LoggerFactory.getLogger(EchoServer.class);
+
+    public void run() {
+        // Configure the server.
+        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
+        EventLoopGroup workerGroup = new NioEventLoopGroup();
+        try {
+            ServerBootstrap b = new ServerBootstrap();
+            b.group(bossGroup, workerGroup)
+                    .channel(LocalServerChannel.class)
+                    .option(ChannelOption.SO_BACKLOG, 100)
+                    .handler(new LoggingHandler(LogLevel.INFO))
+                    .childHandler(new ChannelInitializer<LocalChannel>() {
+                        @Override
+                        public void initChannel(LocalChannel ch) throws Exception {
+                            ch.pipeline().addLast(new EchoServerHandler());
+                        }
+                    });
+
+            // Start the server.
+            LocalAddress localAddress = NetconfConfigUtil.getNetconfLocalAddress();
+            ChannelFuture f = b.bind(localAddress).sync();
+
+            // Wait until the server socket is closed.
+            f.channel().closeFuture().sync();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        } finally {
+            // Shut down all event loops to terminate all threads.
+            bossGroup.shutdownGracefully();
+            workerGroup.shutdownGracefully();
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        new Thread(new EchoServer()).start();
+        Thread.sleep(1000);
+        EchoClientHandler clientHandler = new EchoClientHandler();
+        EchoClient echoClient = new EchoClient(clientHandler);
+        new Thread(echoClient).start();
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+        do {
+            String message = reader.readLine();
+            if (message == null ||  "exit".equalsIgnoreCase(message)) {
+                break;
+            }
+            logger.debug("Got '{}'", message);
+            clientHandler.write(message);
+        } while (true);
+        System.exit(0);
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoServerHandler.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoServerHandler.java
new file mode 100644 (file)
index 0000000..1286ec6
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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.netty;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandler.Sharable;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handler implementation for the echo server.
+ */
+@Sharable
+public class EchoServerHandler extends ChannelInboundHandlerAdapter {
+
+    private static final Logger logger = LoggerFactory.getLogger(EchoServerHandler.class.getName());
+    private String fromLastNewLine = "";
+    private final Splitter splitter = Splitter.onPattern("\r?\n");
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+        logger.debug("sleep start");
+        Thread.sleep(1000);
+        logger.debug("sleep done");
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        ByteBuf byteBuf = (ByteBuf) msg;
+        String message = byteBuf.toString(Charsets.UTF_8);
+        logger.info("writing back '{}'", message);
+        ctx.write(msg);
+        fromLastNewLine += message;
+        for (String line : splitter.split(fromLastNewLine)) {
+            if ("quit".equals(line)) {
+                logger.info("closing server ctx");
+                ctx.flush();
+                ctx.close();
+                break;
+            }
+            fromLastNewLine = line; // last line should be preserved
+        }
+
+        // do not release byteBuf as it is handled back
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+        logger.debug("flushing");
+        ctx.flush();
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/ProxyServer.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/ProxyServer.java
new file mode 100644 (file)
index 0000000..8f2c502
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import java.net.InetSocketAddress;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
+
+public class ProxyServer implements Runnable {
+    private final ProxyHandlerFactory proxyHandlerFactory;
+
+    public ProxyServer(ProxyHandlerFactory proxyHandlerFactory) {
+        this.proxyHandlerFactory = proxyHandlerFactory;
+    }
+
+    public void run() {
+        // Configure the server.
+        final EventLoopGroup bossGroup = new NioEventLoopGroup();
+        EventLoopGroup workerGroup = new NioEventLoopGroup();
+        try {
+            final LocalAddress localAddress = NetconfConfigUtil.getNetconfLocalAddress();
+            ServerBootstrap serverBootstrap = new ServerBootstrap();
+            serverBootstrap.group(bossGroup, workerGroup)
+                    .channel(NioServerSocketChannel.class)
+                    .option(ChannelOption.SO_BACKLOG, 100)
+                    .handler(new LoggingHandler(LogLevel.INFO))
+                    .childHandler(new ChannelInitializer<SocketChannel>() {
+                        @Override
+                        public void initChannel(SocketChannel ch) throws Exception {
+                            ch.pipeline().addLast(proxyHandlerFactory.create(bossGroup, localAddress));
+                        }
+                    });
+
+            // Start the server.
+            InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8080);
+            ChannelFuture f = serverBootstrap.bind(address).sync();
+
+            // Wait until the server socket is closed.
+            f.channel().closeFuture().sync();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        } finally {
+            // Shut down all event loops to terminate all threads.
+            bossGroup.shutdownGracefully();
+            workerGroup.shutdownGracefully();
+        }
+    }
+    public static interface ProxyHandlerFactory {
+        ChannelHandler create(EventLoopGroup bossGroup, LocalAddress localAddress);
+    }
+
+    public static void main(String[] args) {
+        ProxyHandlerFactory proxyHandlerFactory = new ProxyHandlerFactory() {
+            @Override
+            public ChannelHandler create(EventLoopGroup bossGroup, LocalAddress localAddress) {
+                return new ProxyServerHandler(bossGroup, localAddress);
+            }
+        };
+        start(proxyHandlerFactory);
+    }
+
+    public static void start(ProxyHandlerFactory proxyHandlerFactory) {
+        new Thread(new EchoServer()).start();
+        new Thread(new ProxyServer(proxyHandlerFactory)).start();
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/ProxyServerHandler.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/ProxyServerHandler.java
new file mode 100644 (file)
index 0000000..ecab212
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * 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.netty;
+
+import com.google.common.base.Charsets;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ProxyServerHandler extends ChannelInboundHandlerAdapter {
+    private static final Logger logger = LoggerFactory.getLogger(ProxyServerHandler.class.getName());
+    private final Bootstrap clientBootstrap;
+    private final LocalAddress localAddress;
+
+
+    private Channel clientChannel;
+
+    public ProxyServerHandler(EventLoopGroup bossGroup, LocalAddress localAddress) {
+        clientBootstrap = new Bootstrap();
+        clientBootstrap.group(bossGroup).channel(LocalChannel.class);
+        this.localAddress = localAddress;
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext remoteCtx) {
+        final ProxyClientHandler clientHandler = new ProxyClientHandler(remoteCtx);
+        clientBootstrap.handler(new ChannelInitializer<LocalChannel>() {
+            @Override
+            public void initChannel(LocalChannel ch) throws Exception {
+                ch.pipeline().addLast(clientHandler);
+            }
+        });
+        ChannelFuture clientChannelFuture = clientBootstrap.connect(localAddress).awaitUninterruptibly();
+        clientChannel = clientChannelFuture.channel();
+        clientChannel.writeAndFlush(Unpooled.copiedBuffer("connected\n".getBytes()));
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) {
+        logger.info("channelInactive - closing client connection");
+        clientChannel.close();
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, final Object msg) {
+        logger.debug("Writing to client {}", msg);
+        clientChannel.write(msg);
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) {
+        logger.debug("flushing");
+        clientChannel.flush();
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        // Close the connection when an exception is raised.
+        logger.warn("Unexpected exception from downstream.", cause);
+        ctx.close();
+    }
+}
+
+class ProxyClientHandler extends ChannelInboundHandlerAdapter {
+    private static final Logger logger = LoggerFactory.getLogger(ProxyClientHandler.class);
+
+    private final ChannelHandlerContext remoteCtx;
+
+
+    public ProxyClientHandler(ChannelHandlerContext remoteCtx) {
+        this.remoteCtx = remoteCtx;
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) {
+        logger.info("client active");
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) {
+        ByteBuf bb = (ByteBuf) msg;
+        logger.info(">{}", bb.toString(Charsets.UTF_8));
+        remoteCtx.write(msg);
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) {
+        logger.debug("Flushing server ctx");
+        remoteCtx.flush();
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        // Close the connection when an exception is raised.
+        logger.warn("Unexpected exception from downstream", cause);
+        ctx.close();
+    }
+
+    // called both when local or remote connection dies
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) {
+        logger.debug("channelInactive() called, closing remote client ctx");
+        remoteCtx.close();
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java
new file mode 100644 (file)
index 0000000..4e32e82
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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.netty;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import io.netty.channel.nio.NioEventLoopGroup;
+import org.junit.Test;
+import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
+import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
+import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SSHTest {
+    public static final Logger logger = LoggerFactory.getLogger(SSHTest.class);
+
+    @Test
+    public void test() throws Exception {
+        new Thread(new EchoServer(), "EchoServer").start();
+        AuthProvider authProvider = mock(AuthProvider.class);
+        doReturn(PEMGenerator.generate().toCharArray()).when(authProvider).getPEMAsCharArray();
+        doReturn(true).when(authProvider).authenticated(anyString(), anyString());
+        NetconfSSHServer thread = NetconfSSHServer.start(1831, NetconfConfigUtil.getNetconfLocalAddress(), authProvider, new NioEventLoopGroup());
+        Thread.sleep(2000);
+        logger.info("Closing socket");
+        thread.close();
+        thread.join();
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/ssh/authentication/SSHServerTest.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/ssh/authentication/SSHServerTest.java
new file mode 100644 (file)
index 0000000..5e368bc
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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.ssh.authentication;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+
+import ch.ethz.ssh2.Connection;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import junit.framework.Assert;
+import org.apache.commons.io.IOUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.netconf.StubUserManager;
+import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class SSHServerTest {
+
+    private static final String USER = "netconf";
+    private static final String PASSWORD = "netconf";
+    private static final String HOST = "127.0.0.1";
+    private static final int PORT = 1830;
+    private static final InetSocketAddress tcpAddress = new InetSocketAddress("127.0.0.1", 8383);
+    private static final Logger logger = LoggerFactory.getLogger(SSHServerTest.class);
+    private Thread sshServerThread;
+
+    @Mock
+    private BundleContext mockedContext;
+
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        doReturn(null).when(mockedContext).createFilter(anyString());
+        doNothing().when(mockedContext).addServiceListener(any(ServiceListener.class), anyString());
+        doReturn(new ServiceReference[0]).when(mockedContext).getServiceReferences(anyString(), anyString());
+
+        logger.info("Creating SSH server");
+        StubUserManager um = new StubUserManager(USER, PASSWORD);
+        String pem;
+        try (InputStream is = getClass().getResourceAsStream("/RSA.pk")) {
+            pem = IOUtils.toString(is);
+        }
+        AuthProvider ap = new AuthProvider(pem, mockedContext);
+        ap.setNullableUserManager(um);
+        EventLoopGroup bossGroup = new NioEventLoopGroup();
+        NetconfSSHServer server = NetconfSSHServer.start(PORT, NetconfConfigUtil.getNetconfLocalAddress(),
+                ap, bossGroup);
+
+        sshServerThread = new Thread(server);
+        sshServerThread.setDaemon(true);
+        sshServerThread.start();
+        logger.info("SSH server on " + PORT);
+    }
+
+    @Test
+    public void connect() {
+        try {
+            Connection conn = new Connection(HOST, PORT);
+            Assert.assertNotNull(conn);
+            logger.info("connecting to SSH server");
+            conn.connect();
+            logger.info("authenticating ...");
+            boolean isAuthenticated = conn.authenticateWithPassword(USER, PASSWORD);
+            Assert.assertTrue(isAuthenticated);
+        } catch (Exception e) {
+            logger.error("Error while starting SSH server.", e);
+        }
+
+    }
+
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/resources/logback-test.xml b/opendaylight/netconf/netconf-ssh/src/test/resources/logback-test.xml
new file mode 100644 (file)
index 0000000..324c234
--- /dev/null
@@ -0,0 +1,13 @@
+<configuration>
+
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>%date{"yyyy-MM-dd HH:mm:ss.SSS z"} [%thread] %-5level %logger{36} - %msg%n</pattern>
+    </encoder>
+  </appender>
+
+  <root level="error">
+    <appender-ref ref="STDOUT" />
+  </root>
+  <logger name="org.opendaylight.controller.netconf" level="TRACE"/>
+</configuration>
diff --git a/opendaylight/netconf/netconf-tcp/pom.xml b/opendaylight/netconf/netconf-tcp/pom.xml
new file mode 100644 (file)
index 0000000..65da6e9
--- /dev/null
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>netconf-subsystem</artifactId>
+    <version>0.2.5-SNAPSHOT</version>
+    <relativePath>../</relativePath>
+  </parent>
+  <artifactId>netconf-tcp</artifactId>
+  <packaging>bundle</packaging>
+  <name>${project.artifactId}</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>netconf-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>netconf-util</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>mockito-configuration</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.7</version>
+        <configuration>
+          <instructions>
+            <Bundle-Activator>org.opendaylight.controller.netconf.tcp.osgi.NetconfTCPActivator</Bundle-Activator>
+            <Import-Package>com.google.common.base, io.netty.bootstrap, io.netty.channel, io.netty.channel.local,
+              io.netty.channel.nio, io.netty.channel.socket, io.netty.channel.socket.nio, io.netty.handler.logging,
+              io.netty.util.concurrent, org.opendaylight.controller.netconf.util.osgi, org.osgi.framework, org.slf4j</Import-Package>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
diff --git a/opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/netty/ProxyServer.java b/opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/netty/ProxyServer.java
new file mode 100644 (file)
index 0000000..2e0022c
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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.tcp.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import java.net.InetSocketAddress;
+
+public class ProxyServer implements AutoCloseable {
+    private final EventLoopGroup bossGroup = new NioEventLoopGroup();
+    private final EventLoopGroup workerGroup = new NioEventLoopGroup();
+    private final ChannelFuture channelFuture;
+
+    public ProxyServer(InetSocketAddress address, final LocalAddress localAddress) {
+        // Configure the server.
+        final Bootstrap clientBootstrap = new Bootstrap();
+        clientBootstrap.group(bossGroup).channel(LocalChannel.class);
+
+        ServerBootstrap serverBootstrap = new ServerBootstrap();
+        serverBootstrap.group(bossGroup, workerGroup)
+                .channel(NioServerSocketChannel.class)
+                .handler(new LoggingHandler(LogLevel.DEBUG))
+                .childHandler(new ChannelInitializer<SocketChannel>() {
+                    @Override
+                    public void initChannel(SocketChannel ch) throws Exception {
+                        ch.pipeline().addLast(new ProxyServerHandler(clientBootstrap, localAddress));
+                    }
+                });
+
+        // Start the server.
+        channelFuture = serverBootstrap.bind(address).syncUninterruptibly();
+    }
+
+    @Override
+    public void close() {
+        channelFuture.channel().close();
+        bossGroup.shutdownGracefully();
+        workerGroup.shutdownGracefully();
+    }
+}
diff --git a/opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/netty/ProxyServerHandler.java b/opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/netty/ProxyServerHandler.java
new file mode 100644 (file)
index 0000000..fa88928
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * 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.tcp.netty;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ProxyServerHandler extends ChannelInboundHandlerAdapter {
+    private static final Logger logger = LoggerFactory.getLogger(ProxyServerHandler.class.getName());
+    private final Bootstrap clientBootstrap;
+    private final LocalAddress localAddress;
+
+    private Channel clientChannel;
+
+    public ProxyServerHandler(Bootstrap clientBootstrap, LocalAddress localAddress) {
+        this.clientBootstrap = clientBootstrap;
+        this.localAddress = localAddress;
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext remoteCtx) {
+        final ProxyClientHandler clientHandler = new ProxyClientHandler(remoteCtx);
+        clientBootstrap.handler(new ChannelInitializer<LocalChannel>() {
+            @Override
+            public void initChannel(LocalChannel ch) throws Exception {
+                ch.pipeline().addLast(clientHandler);
+            }
+        });
+        ChannelFuture clientChannelFuture = clientBootstrap.connect(localAddress).awaitUninterruptibly();
+        clientChannel = clientChannelFuture.channel();
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) {
+        logger.trace("channelInactive - closing client channel");
+        clientChannel.close();
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, final Object msg) {
+        logger.trace("Writing to client channel");
+        clientChannel.write(msg);
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) {
+        logger.trace("Flushing client channel");
+        clientChannel.flush();
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        // Close the connection when an exception is raised.
+        logger.warn("Unexpected exception from downstream.", cause);
+        ctx.close();
+    }
+}
+
+class ProxyClientHandler extends ChannelInboundHandlerAdapter {
+    private static final Logger logger = LoggerFactory.getLogger(ProxyClientHandler.class);
+
+    private final ChannelHandlerContext remoteCtx;
+    private ChannelHandlerContext localCtx;
+
+    public ProxyClientHandler(ChannelHandlerContext remoteCtx) {
+        this.remoteCtx = remoteCtx;
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) {
+        checkState(this.localCtx == null);
+        logger.trace("Client channel active");
+        this.localCtx = ctx;
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) {
+        logger.trace("Forwarding message");
+        remoteCtx.write(msg);
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) {
+        logger.trace("Flushing remote ctx");
+        remoteCtx.flush();
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        // Close the connection when an exception is raised.
+        logger.warn("Unexpected exception from downstream", cause);
+        checkState(this.localCtx.equals(ctx));
+        ctx.close();
+    }
+
+    // called both when local or remote connection dies
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) {
+        logger.trace("channelInactive() called, closing remote client ctx");
+        remoteCtx.close();
+    }
+
+}
diff --git a/opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/osgi/NetconfTCPActivator.java b/opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/osgi/NetconfTCPActivator.java
new file mode 100644 (file)
index 0000000..bc94e59
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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.tcp.osgi;
+
+import com.google.common.base.Optional;
+import java.net.InetSocketAddress;
+import org.opendaylight.controller.netconf.tcp.netty.ProxyServer;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil.InfixProp;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Opens TCP port specified in config.ini, creates bridge between this port and local netconf server.
+ */
+public class NetconfTCPActivator implements BundleActivator {
+    private static final Logger logger = LoggerFactory.getLogger(NetconfTCPActivator.class);
+    private ProxyServer proxyServer;
+
+    @Override
+    public void start(BundleContext context) {
+        final Optional<InetSocketAddress> maybeAddress = NetconfConfigUtil.extractNetconfServerAddress(context, InfixProp.tcp);
+        if (maybeAddress.isPresent() == false) {
+            logger.debug("Netconf tcp server is not configured to start");
+            return;
+        }
+        InetSocketAddress address = maybeAddress.get();
+        if (address.getAddress().isAnyLocalAddress()) {
+            logger.warn("Unprotected netconf TCP address is configured to ANY local address. This is a security risk. " +
+                    "Consider changing {} to 127.0.0.1", NetconfConfigUtil.getNetconfServerAddressKey(InfixProp.tcp));
+        }
+        logger.info("Starting TCP netconf server at {}", address);
+        proxyServer = new ProxyServer(address, NetconfConfigUtil.getNetconfLocalAddress());
+    }
+
+    @Override
+    public void stop(BundleContext context) {
+        if (proxyServer != null) {
+            proxyServer.close();
+        }
+    }
+}
index dcbdcabbba2683b260b003624c189561a134de2a..d9d957c663766ccb847cc57a3cde920b19d6cfd2 100644 (file)
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.7</version>
         <configuration>
           <instructions>
             <Import-Package>com.google.common.base, com.google.common.collect, io.netty.channel,
               io.netty.util.concurrent, javax.annotation, javax.xml.namespace, javax.xml.parsers, javax.xml.transform,
               javax.xml.transform.dom, javax.xml.transform.stream, javax.xml.validation, javax.xml.xpath,
               org.opendaylight.controller.netconf.api, org.opendaylight.controller.netconf.mapping.api,
-              org.osgi.framework, org.slf4j, org.w3c.dom, org.xml.sax</Import-Package>
+              org.osgi.framework, org.slf4j, org.w3c.dom, org.xml.sax,io.netty.channel.local</Import-Package>
             <Export-Package>org.opendaylight.controller.netconf.util.*</Export-Package>
           </instructions>
         </configuration>
index 0993b8ad0c7038e5b86b0fee0e5cb4d30fd701f8..333fea3493172286fdba2c807eff105760741411 100644 (file)
@@ -9,36 +9,35 @@
 package org.opendaylight.controller.netconf.util.osgi;
 
 import com.google.common.base.Optional;
+import io.netty.channel.local.LocalAddress;
+import java.net.InetSocketAddress;
 import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.net.InetSocketAddress;
-
 public final class NetconfConfigUtil {
     private static final Logger logger = LoggerFactory.getLogger(NetconfConfigUtil.class);
 
-    public static final InetSocketAddress DEFAULT_NETCONF_TCP_ADDRESS
-            = new InetSocketAddress("127.0.0.1", 8383);
-    public static final InetSocketAddress DEFAULT_NETCONF_SSH_ADDRESS
-            = new InetSocketAddress("0.0.0.0", 1830);
-
     private static final String PREFIX_PROP = "netconf.";
 
     private NetconfConfigUtil() {
     }
 
-    private enum InfixProp {
+    public enum InfixProp {
         tcp, ssh
     }
 
     private static final String PORT_SUFFIX_PROP = ".port";
     private static final String ADDRESS_SUFFIX_PROP = ".address";
-    private static final String CLIENT_PROP = ".client";
     private static final String PRIVATE_KEY_PATH_PROP = ".pk.path";
 
     private static final String CONNECTION_TIMEOUT_MILLIS_PROP = "connectionTimeoutMillis";
     private static final long DEFAULT_TIMEOUT_MILLIS = 5000;
+    private static final LocalAddress netconfLocalAddress = new LocalAddress("netconf");
+
+    public static LocalAddress getNetconfLocalAddress() {
+        return netconfLocalAddress;
+    }
 
     public static long extractTimeoutMillis(final BundleContext bundleContext) {
         final String key = PREFIX_PROP + CONNECTION_TIMEOUT_MILLIS_PROP;
@@ -54,22 +53,6 @@ public final class NetconfConfigUtil {
         }
     }
 
-    public static InetSocketAddress extractTCPNetconfServerAddress(final BundleContext context, final InetSocketAddress defaultAddress) {
-        final Optional<InetSocketAddress> extracted = extractNetconfServerAddress(context, InfixProp.tcp);
-        final InetSocketAddress netconfTcpAddress = getNetconfAddress(defaultAddress, extracted, InfixProp.tcp);
-        logger.debug("Using {} as netconf tcp address", netconfTcpAddress);
-        if (netconfTcpAddress.getAddress().isAnyLocalAddress()) {
-            logger.warn("Unprotected netconf TCP address is configured to ANY local address. This is a security risk. " +
-                    "Consider changing {} to 127.0.0.1", PREFIX_PROP + InfixProp.tcp + ADDRESS_SUFFIX_PROP);
-        }
-        return netconfTcpAddress;
-    }
-
-    public static InetSocketAddress extractTCPNetconfClientAddress(final BundleContext context, final InetSocketAddress defaultAddress) {
-        final Optional<InetSocketAddress> extracted = extractNetconfClientAddress(context, InfixProp.tcp);
-        return getNetconfAddress(defaultAddress, extracted, InfixProp.tcp);
-    }
-
     /**
      * Get extracted address or default.
      *
@@ -93,15 +76,12 @@ public final class NetconfConfigUtil {
         return inetSocketAddress;
     }
 
-    public static InetSocketAddress extractSSHNetconfAddress(final BundleContext context, final InetSocketAddress defaultAddress) {
-        Optional<InetSocketAddress> extractedAddress = extractNetconfServerAddress(context, InfixProp.ssh);
-        InetSocketAddress netconfSSHAddress = getNetconfAddress(defaultAddress, extractedAddress, InfixProp.ssh);
-        logger.debug("Using {} as netconf SSH address", netconfSSHAddress);
-        return netconfSSHAddress;
+    public static String getPrivateKeyPath(final BundleContext context) {
+        return getPropertyValue(context, getPrivateKeyKey());
     }
 
-    public static String getPrivateKeyPath(final BundleContext context) {
-        return getPropertyValue(context, PREFIX_PROP + InfixProp.ssh + PRIVATE_KEY_PATH_PROP);
+    public static String getPrivateKeyKey() {
+        return PREFIX_PROP + InfixProp.ssh + PRIVATE_KEY_PATH_PROP;
     }
 
     private static String getPropertyValue(final BundleContext context, final String propertyName) {
@@ -112,16 +92,20 @@ public final class NetconfConfigUtil {
         return propertyValue;
     }
 
+    public static String getNetconfServerAddressKey(InfixProp infixProp) {
+        return PREFIX_PROP + infixProp + ADDRESS_SUFFIX_PROP;
+    }
+
     /**
      * @param context   from which properties are being read.
      * @param infixProp either tcp or ssh
      * @return value if address and port are present and valid, Optional.absent otherwise.
      * @throws IllegalStateException if address or port are invalid, or configuration is missing
      */
-    private static Optional<InetSocketAddress> extractNetconfServerAddress(final BundleContext context,
+    public static Optional<InetSocketAddress> extractNetconfServerAddress(final BundleContext context,
                                                                            final InfixProp infixProp) {
 
-        final Optional<String> address = getProperty(context, PREFIX_PROP + infixProp + ADDRESS_SUFFIX_PROP);
+        final Optional<String> address = getProperty(context, getNetconfServerAddressKey(infixProp));
         final Optional<String> port = getProperty(context, PREFIX_PROP + infixProp + PORT_SUFFIX_PROP);
 
         if (address.isPresent() && port.isPresent()) {
@@ -140,24 +124,6 @@ public final class NetconfConfigUtil {
         return new InetSocketAddress(address.get(), portNumber);
     }
 
-    private static Optional<InetSocketAddress> extractNetconfClientAddress(final BundleContext context,
-                                                                           final InfixProp infixProp) {
-        final Optional<String> address = getProperty(context,
-                PREFIX_PROP + infixProp + CLIENT_PROP + ADDRESS_SUFFIX_PROP);
-        final Optional<String> port = getProperty(context,
-                PREFIX_PROP + infixProp + CLIENT_PROP + PORT_SUFFIX_PROP);
-
-        if (address.isPresent() && port.isPresent()) {
-            try {
-                return Optional.of(parseAddress(address, port));
-            } catch (final RuntimeException e) {
-                logger.warn("Unable to parse client {} netconf address from {}:{}, fallback to server address",
-                        infixProp, address, port, e);
-            }
-        }
-        return extractNetconfServerAddress(context, infixProp);
-    }
-
     private static Optional<String> getProperty(final BundleContext context, final String propKey) {
         String value = context.getProperty(propKey);
         if (value != null && value.isEmpty()) {
index 013cfbee52d0a27858291c507bb9c7e5edf357b0..d26fcf987e5469388b5c01bac3a391b805a46536 100644 (file)
@@ -27,6 +27,7 @@
     <module>netconf-mapping-api</module>
     <module>netconf-client</module>
     <module>netconf-ssh</module>
+    <module>netconf-tcp</module>
     <module>netconf-monitoring</module>
     <module>ietf-netconf-monitoring</module>
     <module>ietf-netconf-monitoring-extension</module>
index 4b2badd92db8baa5cde25a3dbb8a7e2f40d9967a..8253ac46d3f5405ace57da8022be90a183645a68 100644 (file)
@@ -230,4 +230,12 @@ public class TCP extends Packet {
         return (BitBufferHelper.getShort(fieldValues.get(DESTPORT)));
     }
 
+    /**
+     * Get the stored checksum value of the TCP header
+     * @return short - the checksum
+     */
+    public short getChecksum() {
+        return (BitBufferHelper.getShort(fieldValues.get(CHECKSUM)));
+    }
+
 }
index 48679c33f287ced6dab0d97141acd4b40d077a4c..3e18aedfdb38b7238ef9a72c9d49bff151aec685 100644 (file)
@@ -104,4 +104,13 @@ public class TCPTest {
         Assert.assertTrue(urgentPointer[1] == 10);
 
     }
+
+    @Test
+    public void testGetChecksum() {
+        TCP tcp = new TCP();
+        byte[] udpChecksum = { 0, -56 };
+        tcp.hdrFieldsMap.put("Checksum", udpChecksum);
+        short checksum = tcp.getChecksum();
+        Assert.assertTrue(checksum == 200);
+    }
 }