From: Tony Tkacik Date: Fri, 6 Jun 2014 08:19:54 +0000 (+0000) Subject: Merge "Checkstyle maven plugin check - md-sal" X-Git-Tag: release/helium~701 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=8115c4f148589ee00ec6aebc13eaf10f9975c83b;hp=8be2db7972de31d125f34e4beed82433f202f4bd;p=controller.git Merge "Checkstyle maven plugin check - md-sal" --- diff --git a/features/base/src/main/resources/features.xml b/features/base/src/main/resources/features.xml index cc112052cc..23051f5a9a 100644 --- a/features/base/src/main/resources/features.xml +++ b/features/base/src/main/resources/features.xml @@ -1,10 +1,7 @@ - + - + http transaction base-felix-dm @@ -21,41 +18,46 @@ mvn:org.opendaylight.controller/dummy-console/1.1.0-SNAPSHOT - + mvn:org.osgi/org.osgi.compendium/${osgi.compendium.version} mvn:org.apache.felix/org.apache.felix.dependencymanager/${felix.dependencymanager.version} mvn:org.apache.felix/org.apache.felix.dependencymanager.shell/${felix.dependencymanager.shell.version} - + mvn:org.apache.aries/org.apache.aries.util/1.1.0 mvn:org.apache.aries.spifly/org.apache.aries.spifly.dynamic.bundle/${spifly.version} mvn:org.ow2.asm/asm-all/4.0 - - wrap:mvn:io.netty/netty-buffer/${netty.version} - wrap:mvn:io.netty/netty-codec/${netty.version} - wrap:mvn:io.netty/netty-transport/${netty.version} - wrap:mvn:io.netty/netty-common/${netty.version} - wrap:mvn:io.netty/netty-handler/${netty.version} - wrap:mvn:io.netty/netty-codec-http/${netty.version} - mvn:org.opendaylight.controller.thirdparty/ganymed/1.1-SNAPSHOT - - - base-gemini-web - mvn:org.opendaylight.controller.thirdparty/com.sun.jersey.jersey-servlet/${jersey.version} - mvn:com.sun.jersey/jersey-server/${jersey.version} - mvn:com.sun.jersey/jersey-core/${jersey.version} - mvn:com.sun.jersey/jersey-client/${jersey.version} - mvn:com.sun.jersey/jersey-servlet/${jersey.version} + + wrap:mvn:io.netty/netty-buffer/${netty.version} + wrap:mvn:io.netty/netty-codec/${netty.version} + wrap:mvn:io.netty/netty-transport/${netty.version} + wrap:mvn:io.netty/netty-common/${netty.version} + wrap:mvn:io.netty/netty-handler/${netty.version} + wrap:mvn:io.netty/netty-codec-http/${netty.version} + mvn:org.opendaylight.controller.thirdparty/ganymed/1.1-SNAPSHOT + + + base-gemini-web + mvn:org.opendaylight.controller.thirdparty/com.sun.jersey.jersey-servlet/${jersey.version} + mvn:com.sun.jersey/jersey-server/${jersey.version} + mvn:com.sun.jersey/jersey-core/${jersey.version} + mvn:com.sun.jersey/jersey-client/${jersey.version} + mvn:com.sun.jersey/jersey-servlet/${jersey.version} + mvn:javax.ws.rs/javax.ws.rs-api/2.0 + + http + mvn:com.eclipsesource.jaxrs/jersey-all/${jersey2.version} + mvn:com.eclipsesource.jaxrs/publisher/${jersey2.publisher.version} + mvn:javax.ws.rs/javax.ws.rs-api/${jsr311.v2.api.version} + mvn:javax.annotation/javax.annotation-api/${javax.annotation.version} + mvn:com.fasterxml.jackson.core/jackson-annotations/${jackson.version} mvn:com.fasterxml.jackson.core/jackson-core/${jackson.version} mvn:com.fasterxml.jackson.core/jackson-databind/${jackson.version} mvn:org.codehaus.jettison/jettison/${jettison.version} - mvn:javax.ws.rs/jsr311-api/${jsr311.api.version} mvn:com.fasterxml.jackson.module/jackson-module-jaxb-annotations/${jackson.version} mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/${jackson.version} mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/${jackson.version} @@ -66,8 +68,7 @@ mvn:org.slf4j/slf4j-simple/1.7.2 mvn:org.slf4j/slf4j-api/1.7.2 - + mvn:com.google.guava/guava/${guava.version} mvn:org.javassist/javassist/${javassist.version} mvn:commons-io/commons-io/${commons.io.version} diff --git a/opendaylight/commons/opendaylight/pom.xml b/opendaylight/commons/opendaylight/pom.xml index c166f668cc..077b452f0a 100644 --- a/opendaylight/commons/opendaylight/pom.xml +++ b/opendaylight/commons/opendaylight/pom.xml @@ -88,14 +88,18 @@ 1.7 1.7 3.17.1-GA + 1.2 1.17 1.17 + 4.0 + 2.8 1.3.3 src/main/yang-gen-config 1.1.4 2.0.1 1.1.1 + 2.0 4.8.1 3.0.1 1.0.9 @@ -107,7 +111,7 @@ 1.1-SNAPSHOT 1.9.5 0.2.5-SNAPSHOT - 4.0.17.Final + 4.0.19.Final 0.0.3-SNAPSHOT 0.4.2-SNAPSHOT 0.4.2-SNAPSHOT @@ -946,6 +950,11 @@ ${netconf.version} test-jar + + org.opendaylight.controller + netconf-tcp + ${netconf.version} + org.opendaylight.controller netconf-util diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractDispatcher.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractDispatcher.java index 916ef9a88b..fef2c71969 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractDispatcher.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractDispatcher.java @@ -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, L extends SessionListener> implements Closeable { - protected interface PipelineInitializer> { + + protected interface ChannelPipelineInitializer> { /** * 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, 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 promise); + void initializeChannel(CH channel, Promise promise); + } + + protected interface PipelineInitializer> extends ChannelPipelineInitializer { + } @@ -76,25 +82,43 @@ public abstract class AbstractDispatcher, L extends * @return ChannelFuture representing the binding process */ protected ChannelFuture createServer(final InetSocketAddress address, final PipelineInitializer 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 ChannelFuture createServer(SocketAddress address, Class channelClass, + final ChannelPipelineInitializer initializer) { final ServerBootstrap b = new ServerBootstrap(); - b.childHandler(new ChannelInitializer() { + b.childHandler(new ChannelInitializer() { @Override - protected void initChannel(final SocketChannel ch) { + protected void initChannel(final CH ch) { initializer.initializeChannel(ch, new DefaultPromise(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); } diff --git a/opendaylight/config/logback-config/src/test/java/org/opendaylight/controller/config/yang/logback/config/LogbackModuleTest.java b/opendaylight/config/logback-config/src/test/java/org/opendaylight/controller/config/yang/logback/config/LogbackModuleTest.java index d9c9dada62..75323d256e 100644 --- a/opendaylight/config/logback-config/src/test/java/org/opendaylight/controller/config/yang/logback/config/LogbackModuleTest.java +++ b/opendaylight/config/logback-config/src/test/java/org/opendaylight/controller/config/yang/logback/config/LogbackModuleTest.java @@ -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()); diff --git a/opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/fixed/FixedThreadPoolConfigBeanTest.java b/opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/fixed/FixedThreadPoolConfigBeanTest.java index c95661d9c9..62b295be8d 100644 --- a/opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/fixed/FixedThreadPoolConfigBeanTest.java +++ b/opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/fixed/FixedThreadPoolConfigBeanTest.java @@ -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 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); diff --git a/opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/scheduled/ScheduledThreadPoolConfigBeanTest.java b/opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/scheduled/ScheduledThreadPoolConfigBeanTest.java index ef06e43d2f..0fc2fd6eb3 100644 --- a/opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/scheduled/ScheduledThreadPoolConfigBeanTest.java +++ b/opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/scheduled/ScheduledThreadPoolConfigBeanTest.java @@ -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 index 0000000000..e91da89970 --- /dev/null +++ b/opendaylight/distribution/opendaylight-karaf/src/main/resources/etc/jre.properties @@ -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 index 0000000000..ca8c83c380 --- /dev/null +++ b/opendaylight/distribution/opendaylight-karaf/src/main/resources/etc/startup.properties @@ -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 diff --git a/opendaylight/distribution/opendaylight/pom.xml b/opendaylight/distribution/opendaylight/pom.xml index 5b44bb7569..3802370aca 100644 --- a/opendaylight/distribution/opendaylight/pom.xml +++ b/opendaylight/distribution/opendaylight/pom.xml @@ -902,6 +902,10 @@ org.opendaylight.controller netconf-ssh + + org.opendaylight.controller + netconf-tcp + org.opendaylight.controller netconf-util diff --git a/opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini b/opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini index f15f8f7404..f05afbb346 100644 --- a/opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini +++ b/opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini @@ -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 diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/SingletonHolder.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/SingletonHolder.java index 8276446766..1ec4aa2d30 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/SingletonHolder.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/SingletonHolder.java @@ -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 queue = new LinkedBlockingQueue(queueSize) { - private static final long serialVersionUID = 1L; + final BlockingQueue delegate = new LinkedBlockingQueue<>(queueSize); + final BlockingQueue queue = new ForwardingBlockingQueue() { + @Override + protected BlockingQueue 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 index 0000000000..8a190c115f --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/AbstractDOMStoreTransaction.java @@ -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 diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/InMemoryDOMDataStore.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/InMemoryDOMDataStore.java index 87c68596ef..2495146aa6 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/InMemoryDOMDataStore.java +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/InMemoryDOMDataStore.java @@ -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, 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, 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, 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, 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, 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>> 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>> 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 index 0000000000..315293fbe7 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedReadTransaction.java @@ -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>> 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 index 0000000000..4abc80229f --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedReadWriteTransaction.java @@ -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>> 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 index 0000000000..717fb11987 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedWriteTransaction.java @@ -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 diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/json/test/CnSnToJsonBasicDataTypesTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/json/test/CnSnToJsonBasicDataTypesTest.java index 312365585e..4e32e7058c 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/json/test/CnSnToJsonBasicDataTypesTest.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/json/test/CnSnToJsonBasicDataTypesTest.java @@ -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 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 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() ); } } } diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/xml/test/CnSnToXmlTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/xml/test/CnSnToXmlTest.java index fc54795fcc..155ee9d590 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/xml/test/CnSnToXmlTest.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/xml/test/CnSnToXmlTest.java @@ -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 value"); } @@ -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"); } @@ -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"); } @@ -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"); } @@ -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"); } @@ -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"); } @@ -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"); } @@ -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"); } @@ -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"); } @@ -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 + ">"); } @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 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 + ">two one"); } @Test public void snAsYangEnumerationToXmlTest() { + EnumTypeDefinition.EnumPair mockEnum = mock( EnumTypeDefinition.EnumPair.class ); + when( mockEnum.getName() ).thenReturn( "enum2" ); + List 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.absent() ) ) + .deserialize("enum2"), elName), "<" + elName + ">enum2"); } @@ -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"); serializeToXml( - prepareCnStructForYangData(TypeDefinitionAwareCodec.BOOLEAN_DEFAULT_CODEC.deserialize("true"), elName), + prepareCnStructForYangData(TypeDefinitionAwareCodec.from(BooleanType.getInstance()).deserialize("true"), elName), "<" + elName + ">true"); } @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 bitList = Lists.newArrayList( mockBit1, mockBit2 ); + + List> types = Lists.>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"); String bits = "first second"; serializeToXml( - prepareCnStructForYangData(TypeDefinitionAwareCodec.UNION_DEFAULT_CODEC.deserialize(bits), elName), "<" + prepareCnStructForYangData(TypeDefinitionAwareCodec.from(unionType).deserialize(bits), elName), "<" + elName + ">first second"); String bool = "str"; serializeToXml( - prepareCnStructForYangData(TypeDefinitionAwareCodec.UNION_DEFAULT_CODEC.deserialize(bool), elName), "<" + prepareCnStructForYangData(TypeDefinitionAwareCodec.from(unionType).deserialize(bool), elName), "<" + elName + ">str"); } - 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( diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CodecsExceptionsCatchingTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CodecsExceptionsCatchingTest.java index 51687e2a12..307abebdd7 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CodecsExceptionsCatchingTest.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CodecsExceptionsCatchingTest.java @@ -56,6 +56,6 @@ public class CodecsExceptionsCatchingTest extends JerseyTest { Response response = target("/config/number:cont").request(MediaType.APPLICATION_XML).put( Entity.entity("3f", 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 index 0000000000..110c3237de --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/cnsn-to-json/simple-data-types/xml/bad-data.xml @@ -0,0 +1,3 @@ + + invalid + \ 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/data.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/cnsn-to-json/simple-data-types/xml/data.xml index 56872a337d..f73ce1b65c 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/resources/cnsn-to-json/simple-data-types/xml/data.xml +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/cnsn-to-json/simple-data-types/xml/data.xml @@ -29,15 +29,14 @@ 55 true true - false + 10 bt1 33 false - 44 - 21 - + b1 + zero x:iden \ No newline at end of file diff --git a/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/NodeStatisticsHandler.java b/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/NodeStatisticsHandler.java index dbcbab982a..7d9cc7ecbd 100644 --- a/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/NodeStatisticsHandler.java +++ b/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/NodeStatisticsHandler.java @@ -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()); } diff --git a/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/StatisticsRequestScheduler.java b/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/StatisticsRequestScheduler.java index 9ebfd6fd62..bea43ef68a 100644 --- a/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/StatisticsRequestScheduler.java +++ b/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/StatisticsRequestScheduler.java @@ -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> 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); } diff --git a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/ReplaceEditConfigStrategyTest.java b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/ReplaceEditConfigStrategyTest.java index 78a2043e20..22a3f10547 100644 --- a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/ReplaceEditConfigStrategyTest.java +++ b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/ReplaceEditConfigStrategyTest.java @@ -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)); } diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/SshClientChannelInitializer.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/SshClientChannelInitializer.java index 799674487f..829ac304bd 100644 --- a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/SshClientChannelInitializer.java +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/SshClientChannelInitializer.java @@ -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 promise) { + public void initialize(final Channel ch, final Promise promise) { try { final Invoker invoker = Invoker.subsystem("netconf"); ch.pipeline().addFirst(new SshHandler(authenticationHandler, invoker)); @@ -42,7 +42,7 @@ final class SshClientChannelInitializer extends AbstractChannelInitializer promise) { ch.pipeline().addAfter(NETCONF_MESSAGE_DECODER, AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR, negotiatorFactory.getSessionNegotiator(new SessionListenerFactory() { diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/TcpClientChannelInitializer.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/TcpClientChannelInitializer.java index 4a0a089fae..ee8f8baf01 100644 --- a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/TcpClientChannelInitializer.java +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/TcpClientChannelInitializer.java @@ -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 promise) { - super.initialize(ch, promise); - } - - @Override - protected void initializeSessionNegotiator(final SocketChannel ch, final Promise promise) { + protected void initializeSessionNegotiator(final Channel ch, final Promise promise) { ch.pipeline().addAfter(NETCONF_MESSAGE_DECODER, AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR, negotiatorFactory.getSessionNegotiator(new SessionListenerFactory() { @Override diff --git a/opendaylight/netconf/netconf-client/src/test/java/org/opendaylight/controller/netconf/client/test/TestingNetconfClient.java b/opendaylight/netconf/netconf-client/src/test/java/org/opendaylight/controller/netconf/client/test/TestingNetconfClient.java index 60d8f3044a..afa17532d5 100644 --- a/opendaylight/netconf/netconf-client/src/test/java/org/opendaylight/controller/netconf/client/test/TestingNetconfClient.java +++ b/opendaylight/netconf/netconf-client/src/test/java/org/opendaylight/controller/netconf/client/test/TestingNetconfClient.java @@ -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 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(); + } } diff --git a/opendaylight/netconf/netconf-impl/pom.xml b/opendaylight/netconf/netconf-impl/pom.xml index 1d94517152..c60506ef44 100644 --- a/opendaylight/netconf/netconf-impl/pom.xml +++ b/opendaylight/netconf/netconf-impl/pom.xml @@ -109,6 +109,7 @@ org.apache.felix maven-bundle-plugin + 2.3.7 org.opendaylight.controller.netconf.impl.osgi.NetconfImplActivator @@ -121,6 +122,7 @@ io.netty.buffer, io.netty.handler.codec, io.netty.channel.nio, + io.netty.channel.local, javax.annotation, javax.management, javax.net.ssl, diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcher.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcher.java index de3dee1443..4dfb749818 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcher.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcher.java @@ -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() { @@ -37,6 +43,15 @@ public class NetconfServerDispatcher extends AbstractDispatcher() { + @Override + public void initializeChannel(final LocalChannel ch, final Promise promise) { + initializer.initialize(ch, promise); + } + }); + } + public static class ServerChannelInitializer extends AbstractChannelInitializer { public static final String DESERIALIZER_EX_HANDLER_KEY = "deserializerExHandler"; @@ -50,16 +65,15 @@ public class NetconfServerDispatcher extends AbstractDispatcher promise) { + protected void initializeSessionNegotiator(Channel ch, Promise promise) { ch.pipeline().addAfter(DESERIALIZER_EX_HANDLER_KEY, AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR, negotiatorFactory.getSessionNegotiator(null, ch, promise)); } } - } diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivator.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivator.java index 7130dc3501..6ab62ef29a 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivator.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivator.java @@ -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 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 dic = new Hashtable<>(); + private NetconfMonitoringServiceImpl startMonitoringService(BundleContext context, NetconfOperationServiceFactoryListenerImpl factoriesListener) { + NetconfMonitoringServiceImpl netconfMonitoringServiceImpl = new NetconfMonitoringServiceImpl(factoriesListener); + Dictionary dic = new Hashtable<>(); regMonitoring = context.registerService(NetconfMonitoringService.class, netconfMonitoringServiceImpl, dic); return netconfMonitoringServiceImpl; diff --git a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java index 140284e4ee..0969bd92a5 100644 --- a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java +++ b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java @@ -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 { diff --git a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java index fd43f67c05..60a5207daa 100644 --- a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java +++ b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java @@ -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.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()); } } } diff --git a/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/AbstractChannelInitializer.java b/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/AbstractChannelInitializer.java index e88bf53ae0..7897666ddc 100644 --- a/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/AbstractChannelInitializer.java +++ b/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/AbstractChannelInitializer.java @@ -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 { public static final String NETCONF_MESSAGE_FRAME_ENCODER = "frameEncoder"; public static final String NETCONF_SESSION_NEGOTIATOR = "negotiator"; - public void initialize(SocketChannel ch, Promise promise) { + public void initialize(Channel ch, Promise 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 { 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 { * Insert session negotiator into the pipeline. It must be inserted after message decoder * identified by {@link AbstractChannelInitializer#NETCONF_MESSAGE_DECODER}, (or any other custom decoder processor) */ - protected abstract void initializeSessionNegotiator(SocketChannel ch, Promise promise); + protected abstract void initializeSessionNegotiator(Channel ch, Promise promise); } diff --git a/opendaylight/netconf/netconf-ssh/pom.xml b/opendaylight/netconf/netconf-ssh/pom.xml index 622881352e..cbd3efc57f 100644 --- a/opendaylight/netconf/netconf-ssh/pom.xml +++ b/opendaylight/netconf/netconf-ssh/pom.xml @@ -56,21 +56,25 @@ org.apache.felix maven-bundle-plugin + 2.3.7 org.opendaylight.controller.netconf.ssh.osgi.NetconfSSHActivator 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 + 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 diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/NetconfSSHServer.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/NetconfSSHServer.java index c6974d4982..08bf9836b2 100644 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/NetconfSSHServer.java +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/NetconfSSHServer.java @@ -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"); } } diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProvider.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProvider.java index 2e9a0b9d8b..5d39dd1eb8 100644 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProvider.java +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProvider.java @@ -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 customizer = new ServiceTrackerCustomizer() { + @Override + public IUserManager addingService(final ServiceReference reference) { + logger.trace("Service {} added", reference); + nullableUserManager = bundleContext.getService(reference); + return nullableUserManager; + } + + @Override + public void modifiedService(final ServiceReference reference, final IUserManager service) { + logger.trace("Replacing modified service {} in netconf SSH.", reference); + nullableUserManager = service; + } + + @Override + public void removedService(final ServiceReference 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 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 index fad0f79a4e..0000000000 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProviderInterface.java +++ /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); -} diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/PEMGenerator.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/PEMGenerator.java index 348fe006f3..53ab8219ee 100644 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/PEMGenerator.java +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/PEMGenerator.java @@ -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(); } } + } diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/NetconfSSHActivator.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/NetconfSSHActivator.java index d74308cfad..a26843fae1 100644 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/NetconfSSHActivator.java +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/NetconfSSHActivator.java @@ -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 customizer = new ServiceTrackerCustomizer(){ - @Override - public IUserManager addingService(final ServiceReference 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 reference, final IUserManager service) { - logger.trace("Replacing modified service {} in netconf SSH.", reference); - server.addUserManagerService(service); - } - @Override - public void removedService(final ServiceReference 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 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 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 index 0000000000..d999d378d9 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/Handshaker.java @@ -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 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 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() { + @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 { + 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 index c53a625ad0..0000000000 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/IOThread.java +++ /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 index 04639cb36f..0000000000 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/SocketThread.java +++ /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 index 298f91ce8d..0000000000 --- a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/KeyGeneratorTest.java +++ /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 index 663a0b4a82..0000000000 --- a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/SSHServerTest.java +++ /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 index 0000000000..5d0c71aa62 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoClient.java @@ -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() { + @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 index 0000000000..81182a580e --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoClientHandler.java @@ -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 index 0000000000..ec89d75f29 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoServer.java @@ -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() { + @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 index 0000000000..1286ec6875 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoServerHandler.java @@ -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 index 0000000000..8f2c50278d --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/ProxyServer.java @@ -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() { + @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 index 0000000000..ecab21256e --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/ProxyServerHandler.java @@ -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() { + @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 index 0000000000..4e32e82e89 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java @@ -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 index 0000000000..5e368bc566 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/ssh/authentication/SSHServerTest.java @@ -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 index 0000000000..324c234330 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/test/resources/logback-test.xml @@ -0,0 +1,13 @@ + + + + + %date{"yyyy-MM-dd HH:mm:ss.SSS z"} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/opendaylight/netconf/netconf-tcp/pom.xml b/opendaylight/netconf/netconf-tcp/pom.xml new file mode 100644 index 0000000000..65da6e987e --- /dev/null +++ b/opendaylight/netconf/netconf-tcp/pom.xml @@ -0,0 +1,63 @@ + + + + 4.0.0 + + org.opendaylight.controller + netconf-subsystem + 0.2.5-SNAPSHOT + ../ + + netconf-tcp + bundle + ${project.artifactId} + + + + ${project.groupId} + netconf-api + + + ${project.groupId} + netconf-util + + + commons-io + commons-io + + + org.slf4j + slf4j-api + + + org.opendaylight.yangtools + mockito-configuration + test + + + + + + + org.apache.felix + maven-bundle-plugin + 2.3.7 + + + org.opendaylight.controller.netconf.tcp.osgi.NetconfTCPActivator + 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 + + + + + + + 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 index 0000000000..2e0022cb07 --- /dev/null +++ b/opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/netty/ProxyServer.java @@ -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() { + @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 index 0000000000..fa8892853b --- /dev/null +++ b/opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/netty/ProxyServerHandler.java @@ -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() { + @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 index 0000000000..bc94e596d7 --- /dev/null +++ b/opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/osgi/NetconfTCPActivator.java @@ -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 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(); + } + } +} diff --git a/opendaylight/netconf/netconf-util/pom.xml b/opendaylight/netconf/netconf-util/pom.xml index dcbdcabbba..d9d957c663 100644 --- a/opendaylight/netconf/netconf-util/pom.xml +++ b/opendaylight/netconf/netconf-util/pom.xml @@ -53,13 +53,14 @@ org.apache.felix maven-bundle-plugin + 2.3.7 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 + org.osgi.framework, org.slf4j, org.w3c.dom, org.xml.sax,io.netty.channel.local org.opendaylight.controller.netconf.util.* diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/osgi/NetconfConfigUtil.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/osgi/NetconfConfigUtil.java index 0993b8ad0c..333fea3493 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/osgi/NetconfConfigUtil.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/osgi/NetconfConfigUtil.java @@ -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 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 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 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 extractNetconfServerAddress(final BundleContext context, + public static Optional extractNetconfServerAddress(final BundleContext context, final InfixProp infixProp) { - final Optional address = getProperty(context, PREFIX_PROP + infixProp + ADDRESS_SUFFIX_PROP); + final Optional address = getProperty(context, getNetconfServerAddressKey(infixProp)); final Optional 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 extractNetconfClientAddress(final BundleContext context, - final InfixProp infixProp) { - final Optional address = getProperty(context, - PREFIX_PROP + infixProp + CLIENT_PROP + ADDRESS_SUFFIX_PROP); - final Optional 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 getProperty(final BundleContext context, final String propKey) { String value = context.getProperty(propKey); if (value != null && value.isEmpty()) { diff --git a/opendaylight/netconf/pom.xml b/opendaylight/netconf/pom.xml index 013cfbee52..d26fcf987e 100644 --- a/opendaylight/netconf/pom.xml +++ b/opendaylight/netconf/pom.xml @@ -27,6 +27,7 @@ netconf-mapping-api netconf-client netconf-ssh + netconf-tcp netconf-monitoring ietf-netconf-monitoring ietf-netconf-monitoring-extension diff --git a/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/packet/TCP.java b/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/packet/TCP.java index 4b2badd92d..8253ac46d3 100644 --- a/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/packet/TCP.java +++ b/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/packet/TCP.java @@ -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))); + } + } diff --git a/opendaylight/sal/api/src/test/java/org/opendaylight/controller/sal/packet/TCPTest.java b/opendaylight/sal/api/src/test/java/org/opendaylight/controller/sal/packet/TCPTest.java index 48679c33f2..3e18aedfdb 100644 --- a/opendaylight/sal/api/src/test/java/org/opendaylight/controller/sal/packet/TCPTest.java +++ b/opendaylight/sal/api/src/test/java/org/opendaylight/controller/sal/packet/TCPTest.java @@ -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); + } }