From: Moiz Raja Date: Wed, 29 Oct 2014 03:05:09 +0000 (+0000) Subject: Merge "BUG 2221 : Add metering to ShardTransaction actor" X-Git-Tag: release/lithium~963 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=b5167b9bc04f2792b275cfe0eac78c0f5eb9442d;hp=818258c2370c687de93edc887b32019d25c34095 Merge "BUG 2221 : Add metering to ShardTransaction actor" --- diff --git a/features/base/pom.xml b/features/base/pom.xml index b7ab3ca757..cd84eeaf33 100644 --- a/features/base/pom.xml +++ b/features/base/pom.xml @@ -31,10 +31,6 @@ org.opendaylight.controller karaf-tomcat-security - - org.opendaylight.controller.thirdparty - ganymed - com.fasterxml.jackson.core jackson-annotations diff --git a/features/base/src/main/resources/features.xml b/features/base/src/main/resources/features.xml index c324f6cea6..d6802acd0e 100644 --- a/features/base/src/main/resources/features.xml +++ b/features/base/src/main/resources/features.xml @@ -36,7 +36,6 @@ 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.2.0-SNAPSHOT odl-base-gemini-web @@ -80,6 +79,7 @@ mvn:eclipselink/javax.persistence/2.0.4.v201112161009 mvn:eclipselink/javax.resource/1.5.0.v200906010428 + mvn:org.eclipse.persistence/org.eclipse.persistence.antlr/2.5.0 mvn:org.eclipse.persistence/org.eclipse.persistence.moxy/2.5.0 mvn:org.eclipse.persistence/org.eclipse.persistence.core/2.5.0 diff --git a/features/netconf-connector/pom.xml b/features/netconf-connector/pom.xml index 03d6fed605..c69ee197be 100644 --- a/features/netconf-connector/pom.xml +++ b/features/netconf-connector/pom.xml @@ -162,6 +162,7 @@ Optional TODO: Remove TODO comments. --> + org.opendaylight.yangtools features-test diff --git a/features/netconf/pom.xml b/features/netconf/pom.xml index a944bb4dec..028c16b02f 100644 --- a/features/netconf/pom.xml +++ b/features/netconf/pom.xml @@ -74,10 +74,6 @@ org.opendaylight.controller netconf-netty-util - - org.opendaylight.controller.thirdparty - ganymed - org.apache.sshd sshd-core diff --git a/features/netconf/src/main/resources/features.xml b/features/netconf/src/main/resources/features.xml index 444f20865b..fb668ae15a 100644 --- a/features/netconf/src/main/resources/features.xml +++ b/features/netconf/src/main/resources/features.xml @@ -57,7 +57,6 @@ odl-netconf-mapping-api odl-netconf-util mvn:org.opendaylight.controller/netconf-netty-util/${project.version} - mvn:org.opendaylight.controller.thirdparty/ganymed/${ganymed.version} mvn:org.apache.sshd/sshd-core/${sshd-core.version} mvn:org.openexi/nagasena/${exi.nagasena.version} mvn:io.netty/netty-codec/${netty.version} diff --git a/opendaylight/commons/opendaylight/pom.xml b/opendaylight/commons/opendaylight/pom.xml index ffb9ef746d..198d17a79a 100644 --- a/opendaylight/commons/opendaylight/pom.xml +++ b/opendaylight/commons/opendaylight/pom.xml @@ -106,7 +106,6 @@ 0.5.0-SNAPSHOT 0.5.0-SNAPSHOT 0.7.0-SNAPSHOT - 1.2.0-SNAPSHOT 0.6.0-SNAPSHOT 0.6.0-SNAPSHOT 0.5.0-SNAPSHOT @@ -180,7 +179,7 @@ java target/code-coverage/jacoco.exec target/code-coverage/jacoco-it.exec - org.openflow.openflowj,net.sf.jung2,org.opendaylight.controller.protobuff.messages,ch.ethz.ssh2 + org.openflow.openflowj,net.sf.jung2,org.opendaylight.controller.protobuff.messages Sonar way with Findbugs 1.0.0 1.2.1 @@ -218,21 +217,14 @@ + - ${project.groupId} - ietf-netconf-monitoring - ${netconf.version} - - - ${project.groupId} - ietf-netconf-monitoring-extension - ${netconf.version} - - - ${project.groupId} - netconf-netty-util + org.opendaylight.controller + netconf-artifacts ${netconf.version} + pom + import org.apache.sshd @@ -863,11 +855,6 @@ config-manager ${config.version} - - org.opendaylight.controller - config-netconf-connector - ${netconf.version} - org.opendaylight.controller config-persister-api @@ -894,11 +881,6 @@ config-persister-feature-adapter ${config.version} - - org.opendaylight.controller - config-persister-impl - ${netconf.version} - org.opendaylight.controller @@ -1070,94 +1052,6 @@ ${dummy-console.version} - - - org.opendaylight.controller - netconf-api - ${netconf.version} - - - org.opendaylight.controller - netconf-client - ${netconf.version} - - - org.opendaylight.controller - netconf-client - ${netconf.version} - test-jar - - - - - org.opendaylight.controller - netconf-config-dispatcher - ${netconf.version} - - - org.opendaylight.controller - netconf-impl - ${netconf.version} - - - org.opendaylight.controller - netconf-impl - ${netconf.version} - test-jar - - - org.opendaylight.controller - netconf-mapping-api - ${netconf.version} - - - org.opendaylight.controller - netconf-monitoring - ${netconf.version} - - - org.opendaylight.controller - netconf-netty-util - ${netconf.version} - test-jar - - - org.opendaylight.controller - netconf-auth - ${netconf.version} - - - org.opendaylight.controller - netconf-usermanager - ${netconf.version} - - - org.opendaylight.controller - netconf-ssh - ${netconf.version} - - - org.opendaylight.controller - netconf-ssh - ${netconf.version} - test-jar - - - org.opendaylight.controller - netconf-tcp - ${netconf.version} - - - org.opendaylight.controller - netconf-util - ${netconf.version} - - - org.opendaylight.controller - netconf-util - ${netconf.version} - test-jar - org.opendaylight.controller netty-config-api @@ -1350,16 +1244,6 @@ md-sal-config ${mdsal.version} - - org.opendaylight.controller - netconf-config - ${netconf.version} - - - org.opendaylight.controller - netconf-connector-config - ${netconf.version} - org.opendaylight.controller sal-rest-docgen @@ -1691,11 +1575,7 @@ com.sun.jersey.jersey-servlet ${jersey-servlet.version} - - org.opendaylight.controller.thirdparty - ganymed - ${ganymed.version} - + org.opendaylight.controller.thirdparty @@ -1712,174 +1592,16 @@ org.openflow.openflowj 1.0.2 - - org.opendaylight.yangtools - binding-generator-impl - ${yangtools.version} - - - org.opendaylight.yangtools - binding-data-codec - ${yangtools.version} - - - org.opendaylight.yangtools - binding-generator-spi - ${yangtools.version} - - - org.opendaylight.yangtools - binding-generator-util - ${yangtools.version} - - - org.opendaylight.yangtools - binding-type-provider - ${yangtools.version} - - - org.opendaylight.yangtools - concepts - ${yangtools.version} - - - org.opendaylight.yangtools - object-cache-api - ${yangtools.version} - - - org.opendaylight.yangtools - object-cache-guava - ${yangtools.version} - - - org.opendaylight.yangtools - restconf-client-api - ${yangtools.version} - - - org.opendaylight.yangtools - restconf-client-impl - ${yangtools.version} - - - org.opendaylight.yangtools - util - ${yangtools.version} - - - org.opendaylight.yangtools - yang-data-composite-node - ${yangtools.version} - - - org.opendaylight.yangtools - yang-data-codec-gson - ${yangtools.version} - - - - org.opendaylight.yangtools - yang-binding - ${yangtools.version} - - - org.opendaylight.yangtools - yang-common - ${yangtools.version} - - - org.opendaylight.yangtools - yang-data-api - ${yangtools.version} - - - org.opendaylight.yangtools - yang-data-impl - ${yangtools.version} - + org.opendaylight.yangtools - yang-data-util + yangtools-artifacts ${yangtools.version} + pom + import - - org.opendaylight.yangtools - yang-maven-plugin-spi - ${yangtools.version} - - - org.opendaylight.yangtools - yang-model-api - ${yangtools.version} - - - org.opendaylight.yangtools - yang-model-util - ${yangtools.version} - - - org.opendaylight.yangtools - yang-parser-api - ${yangtools.version} - - - org.opendaylight.yangtools - yang-parser-impl - ${yangtools.version} - - - - org.opendaylight.yangtools.model - ietf-inet-types - ${ietf-inet-types.version} - - - org.opendaylight.yangtools.model - ietf-restconf - ${ietf-restconf.version} - - - org.opendaylight.yangtools.model - ietf-topology - ${ietf-topology.version} - - - org.opendaylight.yangtools.model - ietf-topology-l3-unicast-igp - ${ietf-topology.version} - - - org.opendaylight.yangtools.model - ietf-yang-types - ${ietf-yang-types.version} - - - org.opendaylight.yangtools.model - ietf-yang-types-20130715 - 2013.07.15.7-SNAPSHOT - - - org.opendaylight.yangtools.model - opendaylight-l2-types - ${opendaylight-l2-types.version} - - - org.opendaylight.yangtools.model - yang-ext - ${yang-ext.version} - - - org.opendaylight.yangtools.thirdparty - antlr4-runtime-osgi-nohead - 4.0 - - - org.opendaylight.yangtools.thirdparty - xtend-lib-osgi - ${xtend.version} - + org.openexi nagasena @@ -1950,12 +1672,6 @@ ${mdsal.version} test - - org.opendaylight.yangtools - mockito-configuration - ${yangtools.version} - test - org.opendaylight.controller features-config @@ -1972,14 +1688,6 @@ xml runtime - - org.opendaylight.controller - features-netconf - ${netconf.version} - features - xml - runtime - org.opendaylight.controller features-config-persister diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectPromise.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectPromise.java index ea87afa48d..98a2c2cca1 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectPromise.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectPromise.java @@ -98,10 +98,8 @@ final class ReconnectPromise, L extends SessionList return; } - // Check if initial connection was fully finished. If the session was dropped during negotiation, reconnect will not happen. - // Session can be dropped during negotiation on purpose by the client side and would make no sense to initiate reconnect if (promise.isInitialConnectFinished() == false) { - return; + LOG.debug("Connection to {} was dropped during negotiation, reattempting", promise.address); } LOG.debug("Reconnecting after connection to {} was dropped", promise.address); diff --git a/opendaylight/commons/protocol-framework/src/test/java/org/opendaylight/protocol/framework/ServerTest.java b/opendaylight/commons/protocol-framework/src/test/java/org/opendaylight/protocol/framework/ServerTest.java index 63026e384c..fc38888de3 100644 --- a/opendaylight/commons/protocol-framework/src/test/java/org/opendaylight/protocol/framework/ServerTest.java +++ b/opendaylight/commons/protocol-framework/src/test/java/org/opendaylight/protocol/framework/ServerTest.java @@ -250,52 +250,6 @@ public class ServerTest { assertFalse(session.isSuccess()); } - @Test - public void testNegotiationFailedNoReconnect() throws Exception { - final Promise p = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); - - this.dispatcher = getServerDispatcher(p); - - this.server = this.dispatcher.createServer(this.serverAddress, new SessionListenerFactory() { - @Override - public SimpleSessionListener getSessionListener() { - return new SimpleSessionListener(); - } - }); - - this.server.get(); - - this.clientDispatcher = new SimpleDispatcher(new SessionNegotiatorFactory() { - @Override - public SessionNegotiator getSessionNegotiator(final SessionListenerFactory factory, - final Channel channel, final Promise promise) { - - return new SimpleSessionNegotiator(promise, channel) { - @Override - protected void startNegotiation() throws Exception { - negotiationFailed(new IllegalStateException("Negotiation failed")); - } - }; - } - }, new DefaultPromise(GlobalEventExecutor.INSTANCE), eventLoopGroup); - - final ReconnectStrategyFactory reconnectStrategyFactory = mock(ReconnectStrategyFactory.class); - final ReconnectStrategy reconnectStrategy = getMockedReconnectStrategy(); - doReturn(reconnectStrategy).when(reconnectStrategyFactory).createReconnectStrategy(); - - this.clientDispatcher.createReconnectingClient(this.serverAddress, - reconnectStrategyFactory, new SessionListenerFactory() { - @Override - public SimpleSessionListener getSessionListener() { - return new SimpleSessionListener(); - } - }); - - - // Only one strategy should be created for initial connect, no more = no reconnects - verify(reconnectStrategyFactory, times(1)).createReconnectStrategy(); - } - private SimpleDispatcher getClientDispatcher() { return new SimpleDispatcher(new SessionNegotiatorFactory() { @Override diff --git a/opendaylight/config/config-api/src/main/yang/config.yang b/opendaylight/config/config-api/src/main/yang/config.yang index 5d6c11fbee..e46d327ece 100644 --- a/opendaylight/config/config-api/src/main/yang/config.yang +++ b/opendaylight/config/config-api/src/main/yang/config.yang @@ -140,7 +140,7 @@ module config { "Top level container encapsulating configuration of all modules."; list module { - key "name"; + key "type name"; leaf name { description "Unique module instance name"; type string; diff --git a/opendaylight/config/shutdown-impl/src/main/java/org/opendaylight/controller/config/yang/shutdown/impl/ShutdownModuleFactory.java b/opendaylight/config/shutdown-impl/src/main/java/org/opendaylight/controller/config/yang/shutdown/impl/ShutdownModuleFactory.java index 4df9b036f1..1994e21a6d 100644 --- a/opendaylight/config/shutdown-impl/src/main/java/org/opendaylight/controller/config/yang/shutdown/impl/ShutdownModuleFactory.java +++ b/opendaylight/config/shutdown-impl/src/main/java/org/opendaylight/controller/config/yang/shutdown/impl/ShutdownModuleFactory.java @@ -5,26 +5,16 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -/** - * Generated file - - * Generated from: yang module name: shutdown-impl yang module local name: shutdown - * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator - * Generated at: Wed Dec 18 14:02:06 CET 2013 - * - * Do not modify this file unless it is present under src/main directory - */ package org.opendaylight.controller.config.yang.shutdown.impl; +import java.util.Arrays; +import java.util.Set; import org.opendaylight.controller.config.api.DependencyResolver; import org.opendaylight.controller.config.api.DependencyResolverFactory; import org.opendaylight.controller.config.api.ModuleIdentifier; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; -import java.util.Arrays; -import java.util.Set; - public class ShutdownModuleFactory extends AbstractShutdownModuleFactory { public ShutdownModule instantiateModule(String instanceName, DependencyResolver dependencyResolver, diff --git a/opendaylight/config/shutdown-impl/src/main/java/org/opendaylight/controller/config/yang/shutdown/impl/ShutdownServiceImpl.java b/opendaylight/config/shutdown-impl/src/main/java/org/opendaylight/controller/config/yang/shutdown/impl/ShutdownServiceImpl.java index 4abbd3b36f..7d97fcd964 100644 --- a/opendaylight/config/shutdown-impl/src/main/java/org/opendaylight/controller/config/yang/shutdown/impl/ShutdownServiceImpl.java +++ b/opendaylight/config/shutdown-impl/src/main/java/org/opendaylight/controller/config/yang/shutdown/impl/ShutdownServiceImpl.java @@ -8,15 +8,14 @@ package org.opendaylight.controller.config.yang.shutdown.impl; import com.google.common.base.Optional; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; import org.opendaylight.controller.config.shutdown.ShutdownService; import org.osgi.framework.Bundle; import org.osgi.framework.BundleException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadInfo; - public class ShutdownServiceImpl implements ShutdownService, AutoCloseable { private final ShutdownService impl; private final ShutdownRuntimeRegistration registration; @@ -42,7 +41,7 @@ public class ShutdownServiceImpl implements ShutdownService, AutoCloseable { } class Impl implements ShutdownService { - private static final Logger logger = LoggerFactory.getLogger(Impl.class); + private static final Logger LOG = LoggerFactory.getLogger(Impl.class); private final String secret; private final Bundle systemBundle; @@ -53,27 +52,27 @@ class Impl implements ShutdownService { @Override public void shutdown(String inputSecret, Long maxWaitTime, Optional reason) { - logger.warn("Shutdown issued with secret {} and reason {}", inputSecret, reason); + LOG.warn("Shutdown issued with secret {} and reason {}", inputSecret, reason); try { Thread.sleep(1000); // prevent brute force attack } catch (InterruptedException e) { Thread.currentThread().interrupt(); - logger.warn("Shutdown process interrupted", e); + LOG.warn("Shutdown process interrupted", e); } if (this.secret.equals(inputSecret)) { - logger.info("Server is shutting down"); + LOG.info("Server is shutting down"); // actual work: Thread stopSystemBundleThread = new StopSystemBundleThread(systemBundle); stopSystemBundleThread.start(); if (maxWaitTime != null && maxWaitTime > 0) { Thread systemExitThread = new CallSystemExitThread(maxWaitTime); - logger.debug("Scheduling {}", systemExitThread); + LOG.debug("Scheduling {}", systemExitThread); systemExitThread.start(); } // end } else { - logger.warn("Unauthorized attempt to shut down server"); + LOG.warn("Unauthorized attempt to shut down server"); throw new IllegalArgumentException("Invalid secret"); } } @@ -81,7 +80,7 @@ class Impl implements ShutdownService { } class StopSystemBundleThread extends Thread { - private static final Logger logger = LoggerFactory.getLogger(StopSystemBundleThread.class); + private static final Logger LOG = LoggerFactory.getLogger(StopSystemBundleThread.class); private final Bundle systemBundle; StopSystemBundleThread(Bundle systemBundle) { @@ -94,18 +93,18 @@ class StopSystemBundleThread extends Thread { try { // wait so that JMX response is received Thread.sleep(1000); - logger.debug("Stopping system bundle"); + LOG.debug("Stopping system bundle"); systemBundle.stop(); } catch (BundleException e) { - logger.warn("Can not stop OSGi server", e); + LOG.warn("Can not stop OSGi server", e); } catch (InterruptedException e) { - logger.warn("Shutdown process interrupted", e); + LOG.warn("Shutdown process interrupted", e); } } } class CallSystemExitThread extends Thread { - private static final Logger logger = LoggerFactory.getLogger(CallSystemExitThread.class); + private static final Logger LOG = LoggerFactory.getLogger(CallSystemExitThread.class); private final long maxWaitTime; CallSystemExitThread(long maxWaitTime) { super("call-system-exit-daemon"); @@ -128,7 +127,7 @@ class CallSystemExitThread extends Thread { try { // wait specified time Thread.sleep(maxWaitTime); - logger.error("Since some threads are still running, server is going to shut down via System.exit(1) !"); + LOG.error("Since some threads are still running, server is going to shut down via System.exit(1) !"); // do a thread dump ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true, true); StringBuffer sb = new StringBuffer(); @@ -136,10 +135,10 @@ class CallSystemExitThread extends Thread { sb.append(info); sb.append("\n"); } - logger.warn("Thread dump:{}", sb); + LOG.warn("Thread dump:{}", sb); System.exit(1); } catch (InterruptedException e) { - logger.warn("Interrupted, not going to call System.exit(1)"); + LOG.warn("Interrupted, not going to call System.exit(1)"); } } } diff --git a/opendaylight/distribution/opendaylight/pom.xml b/opendaylight/distribution/opendaylight/pom.xml index e30ff05bf0..f6ecb44fa1 100644 --- a/opendaylight/distribution/opendaylight/pom.xml +++ b/opendaylight/distribution/opendaylight/pom.xml @@ -1152,10 +1152,6 @@ sample-toaster-provider ${mdsal.version} - - org.opendaylight.controller.thirdparty - ganymed - org.apache.sshd sshd-core @@ -1163,7 +1159,6 @@ org.opendaylight.yangtools binding-generator-api - ${yangtools.version} org.opendaylight.yangtools @@ -1184,7 +1179,6 @@ org.opendaylight.yangtools binding-model-api - ${yangtools.version} org.opendaylight.yangtools diff --git a/opendaylight/md-sal/compatibility/sal-compatibility/src/main/java/org/opendaylight/controller/sal/compatibility/InventoryAndReadAdapter.java b/opendaylight/md-sal/compatibility/sal-compatibility/src/main/java/org/opendaylight/controller/sal/compatibility/InventoryAndReadAdapter.java index bbb6673a8e..560d8a1d3f 100644 --- a/opendaylight/md-sal/compatibility/sal-compatibility/src/main/java/org/opendaylight/controller/sal/compatibility/InventoryAndReadAdapter.java +++ b/opendaylight/md-sal/compatibility/sal-compatibility/src/main/java/org/opendaylight/controller/sal/compatibility/InventoryAndReadAdapter.java @@ -7,16 +7,10 @@ */ package org.opendaylight.controller.sal.compatibility; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CopyOnWriteArrayList; +import com.google.common.base.Optional; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.Iterables; import org.opendaylight.controller.md.sal.binding.util.TypeSafeDataReader; import org.opendaylight.controller.sal.binding.api.data.DataBrokerService; @@ -46,6 +40,9 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.ta import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.AggregateFlowStatisticsUpdate; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.FlowStatisticsData; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.FlowsStatisticsUpdate; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetAllFlowStatisticsFromFlowTableInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetAllFlowStatisticsFromFlowTableInputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetAllFlowStatisticsFromFlowTableOutput; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetFlowStatisticsFromFlowTableInputBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.OpendaylightFlowStatisticsListener; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.OpendaylightFlowStatisticsService; @@ -59,6 +56,8 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.table.statistics.rev13 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.table.statistics.rev131215.flow.table.statistics.FlowTableStatistics; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.topology.discovery.rev130819.FlowTopologyDiscoveryService; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.topology.discovery.rev130819.Link; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev131103.TransactionAware; +import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev131103.TransactionId; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRemoved; @@ -81,21 +80,36 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.port.statistics.rev131214.N import org.opendaylight.yang.gen.v1.urn.opendaylight.port.statistics.rev131214.OpendaylightPortStatisticsListener; import org.opendaylight.yang.gen.v1.urn.opendaylight.port.statistics.rev131214.OpendaylightPortStatisticsService; import org.opendaylight.yang.gen.v1.urn.opendaylight.port.statistics.rev131214.node.connector.statistics.and.port.number.map.NodeConnectorStatisticsAndPortNumberMap; +import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.TableId; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.common.RpcResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.Iterables; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; public class InventoryAndReadAdapter implements IPluginInReadService, IPluginInInventoryService, OpendaylightFlowStatisticsListener, OpendaylightFlowTableStatisticsListener, OpendaylightPortStatisticsListener { private static final Logger LOG = LoggerFactory.getLogger(InventoryAndReadAdapter.class); private static final short OPENFLOWV10_TABLE_ID = 0; + private static final int SLEEP_FOR_NOTIFICATIONS_MILLIS = 500; private final InventoryNotificationProvider inventoryNotificationProvider = new InventoryNotificationProvider(); private final Map> nodeToNodeConnectorsMap = new ConcurrentHashMap<>(); private List inventoryPublisher = new CopyOnWriteArrayList<>(); private List statisticsPublisher = new CopyOnWriteArrayList<>(); + private Cache> txCache; private OpendaylightFlowTableStatisticsService flowTableStatisticsService; private OpendaylightPortStatisticsService nodeConnectorStatisticsService; @@ -171,6 +185,7 @@ public class InventoryAndReadAdapter implements IPluginInReadService, IPluginInI public void startAdapter() { inventoryNotificationProvider.setDataProviderService(getDataProviderService()); inventoryNotificationProvider.setInventoryPublisher(getInventoryPublisher()); + txCache = CacheBuilder.newBuilder().expireAfterWrite(60L, TimeUnit.SECONDS).maximumSize(10000).build(); // inventoryNotificationProvider.start(); } @@ -251,22 +266,97 @@ public class InventoryAndReadAdapter implements IPluginInReadService, IPluginInI @Override public List readAllFlow(final Node node, final boolean cached) { - final ArrayList output = new ArrayList<>(); - final Table table = readOperationalTable(node, OPENFLOWV10_TABLE_ID); - if (table != null) { - final List flows = table.getFlow(); - LOG.trace("Number of flows installed in table 0 of node {} : {}", node, flows.size()); + final ArrayList ret= new ArrayList<>(); + if (cached) { + final Table table = readOperationalTable(node, OPENFLOWV10_TABLE_ID); + if (table != null) { + final List flows = table.getFlow(); + LOG.trace("Number of flows installed in table 0 of node {} : {}", node, flows.size()); + + for (final Flow flow : flows) { + final FlowStatisticsData statsFromDataStore = flow.getAugmentation(FlowStatisticsData.class); + if (statsFromDataStore != null) { + final FlowOnNode it = new FlowOnNode(ToSalConversionsUtils.toFlow(flow, node)); + ret.add(addFlowStats(it, statsFromDataStore.getFlowStatistics())); + } + } + } + } else { + LOG.debug("readAllFlow cached:{}", cached); + GetAllFlowStatisticsFromFlowTableInput input = + new GetAllFlowStatisticsFromFlowTableInputBuilder() + .setNode(NodeMapping.toNodeRef(node)) + .setTableId(new TableId(OPENFLOWV10_TABLE_ID)) + .build(); + + Future> future = + getFlowStatisticsService().getAllFlowStatisticsFromFlowTable(input); - for (final Flow flow : flows) { - final FlowStatisticsData statsFromDataStore = flow.getAugmentation(FlowStatisticsData.class); - if (statsFromDataStore != null) { - final FlowOnNode it = new FlowOnNode(ToSalConversionsUtils.toFlow(flow, node)); - output.add(addFlowStats(it, statsFromDataStore.getFlowStatistics())); + RpcResult result = null; + try { + // having a blocking call is fine here, as we need to join + // the notifications and return the result + result = future.get(); + } catch (Exception e) { + LOG.error("Exception in getAllFlowStatisticsFromFlowTable ", e); + return ret; + } + + GetAllFlowStatisticsFromFlowTableOutput output = result.getResult(); + if (output == null) { + return ret; + } + + TransactionId transactionId = output.getTransactionId(); + String cacheKey = buildCacheKey(transactionId, NodeMapping.toNodeId(node)); + LOG.info("readAllFlow transactionId:{} cacheKey:{}", transactionId, cacheKey); + + // insert an entry in tempcache, will get updated when notification is received + txCache.put(cacheKey, new TransactionNotificationList( + transactionId, node.getNodeIDString())); + + TransactionNotificationList txnList = + (TransactionNotificationList) txCache.getIfPresent(cacheKey); + + // this loop would not be infinite as the cache will remove an entry + // after defined time if not written to + while (txnList != null && !txnList.areAllNotificationsGathered()) { + LOG.debug("readAllFlow waiting for notification..."); + waitForNotification(); + txnList = (TransactionNotificationList) txCache.getIfPresent(cacheKey); + } + + if (txnList == null) { + return ret; + } + + List notifications = txnList.getNotifications(); + for (FlowsStatisticsUpdate flowsStatisticsUpdate : notifications) { + List flowAndStatisticsMapList = flowsStatisticsUpdate.getFlowAndStatisticsMapList(); + if (flowAndStatisticsMapList != null) { + for (FlowAndStatisticsMapList flowAndStatistics : flowAndStatisticsMapList) { + final FlowOnNode it = new FlowOnNode(ToSalConversionsUtils.toFlow(flowAndStatistics, node)); + ret.add(addFlowStats(it, flowAndStatistics)); + } } } } + return ret; + } + + private String buildCacheKey(final TransactionId id, final NodeId nodeId) { + return String.valueOf(id.getValue()) + "-" + nodeId.getValue(); + } - return output; + private void waitForNotification() { + try { + // going for a simple sleep approach,as wait-notify on a monitor would require + // us to maintain monitors per txn-node combo + Thread.sleep(SLEEP_FOR_NOTIFICATIONS_MILLIS); + LOG.trace("statCollector is waking up from a wait stat Response sleep"); + } catch (final InterruptedException e) { + LOG.warn("statCollector has been interrupted waiting stat Response sleep", e); + } } @Override @@ -623,6 +713,8 @@ public class InventoryAndReadAdapter implements IPluginInReadService, IPluginInI for (final IPluginOutReadService statsPublisher : getStatisticsPublisher()) { statsPublisher.nodeFlowStatisticsUpdated(aDNode, adsalFlowsStatistics); } + + updateTransactionCache(notification, notification.getId(), !notification.isMoreReplies()); } /** @@ -778,4 +870,48 @@ public class InventoryAndReadAdapter implements IPluginInReadService, IPluginInI private List removeNodeConnectors(final InstanceIdentifier nodeIdentifier) { return this.nodeToNodeConnectorsMap.remove(Iterables.get(nodeIdentifier.getPathArguments(), 1)); } + + private void updateTransactionCache(T notification, NodeId nodeId, boolean lastNotification) { + + String cacheKey = buildCacheKey(notification.getTransactionId(), nodeId); + TransactionNotificationList txnList = (TransactionNotificationList) txCache.getIfPresent(cacheKey); + final Optional> optional = Optional.>fromNullable(txnList); + if (optional.isPresent()) { + LOG.info("updateTransactionCache cacheKey:{}, lastNotification:{}, txnList-present:{}", cacheKey, lastNotification, optional.isPresent()); + TransactionNotificationList txn = optional.get(); + txn.addNotification(notification); + txn.setAllNotificationsGathered(lastNotification); + } + } + + private class TransactionNotificationList { + private TransactionId id; + private String nId; + private List notifications; + private boolean allNotificationsGathered; + + public TransactionNotificationList(TransactionId id, String nId) { + this.nId = nId; + this.id = id; + notifications = new ArrayList(); + } + + public void addNotification(T notification) { + notifications.add(notification); + } + + public void setAllNotificationsGathered(boolean allNotificationsGathered) { + this.allNotificationsGathered = allNotificationsGathered; + } + + public boolean areAllNotificationsGathered() { + return allNotificationsGathered; + } + + public List getNotifications() { + return notifications; + } + + } + } diff --git a/opendaylight/md-sal/compatibility/sal-compatibility/src/main/java/org/opendaylight/controller/sal/compatibility/NodeMapping.java b/opendaylight/md-sal/compatibility/sal-compatibility/src/main/java/org/opendaylight/controller/sal/compatibility/NodeMapping.java index bcb2367e7a..2bc3e60309 100644 --- a/opendaylight/md-sal/compatibility/sal-compatibility/src/main/java/org/opendaylight/controller/sal/compatibility/NodeMapping.java +++ b/opendaylight/md-sal/compatibility/sal-compatibility/src/main/java/org/opendaylight/controller/sal/compatibility/NodeMapping.java @@ -10,11 +10,6 @@ package org.opendaylight.controller.sal.compatibility; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Objects; import com.google.common.base.Preconditions; -import java.math.BigInteger; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.regex.Pattern; import org.opendaylight.controller.sal.common.util.Arguments; import org.opendaylight.controller.sal.core.AdvertisedBandwidth; import org.opendaylight.controller.sal.core.Bandwidth; @@ -65,6 +60,12 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.math.BigInteger; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.regex.Pattern; + public final class NodeMapping { private static final Logger LOG = LoggerFactory @@ -167,7 +168,7 @@ public final class NodeMapping { * @param aDNode * @return */ - private static NodeId toNodeId(org.opendaylight.controller.sal.core.Node aDNode) { + public static NodeId toNodeId(org.opendaylight.controller.sal.core.Node aDNode) { String targetPrefix = null; if (NodeIDType.OPENFLOW.equals(aDNode.getType())) { targetPrefix = OPENFLOW_ID_PREFIX; diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ExampleActor.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ExampleActor.java index 04df7785ad..97b912ef74 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ExampleActor.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ExampleActor.java @@ -100,7 +100,7 @@ public class ExampleActor extends RaftActor { try { bs = fromObject(state); } catch (Exception e) { - LOG.error("Exception in creating snapshot", e); + LOG.error(e, "Exception in creating snapshot"); } getSelf().tell(new CaptureSnapshotReply(bs), null); } @@ -110,10 +110,10 @@ public class ExampleActor extends RaftActor { try { state.putAll((HashMap) toObject(snapshot)); } catch (Exception e) { - LOG.error("Exception in applying snapshot", e); + LOG.error(e, "Exception in applying snapshot"); } if(LOG.isDebugEnabled()) { - LOG.debug("Snapshot applied to state :" + ((HashMap) state).size()); + LOG.debug("Snapshot applied to state : {}", ((HashMap) state).size()); } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActor.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActor.java index 64fa749604..66a46ef3bd 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActor.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActor.java @@ -29,9 +29,7 @@ import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshot; import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshotReply; import org.opendaylight.controller.cluster.raft.base.messages.Replicate; import org.opendaylight.controller.cluster.raft.base.messages.SendHeartBeat; -import org.opendaylight.controller.cluster.raft.behaviors.Candidate; import org.opendaylight.controller.cluster.raft.behaviors.Follower; -import org.opendaylight.controller.cluster.raft.behaviors.Leader; import org.opendaylight.controller.cluster.raft.behaviors.RaftActorBehavior; import org.opendaylight.controller.cluster.raft.client.messages.AddRaftPeer; import org.opendaylight.controller.cluster.raft.client.messages.FindLeader; @@ -159,7 +157,9 @@ public abstract class RaftActor extends UntypedPersistentActor { } private void onRecoveredSnapshot(SnapshotOffer offer) { - LOG.debug("SnapshotOffer called.."); + if(LOG.isDebugEnabled()) { + LOG.debug("SnapshotOffer called.."); + } initRecoveryTimer(); @@ -250,7 +250,7 @@ public abstract class RaftActor extends UntypedPersistentActor { replicatedLog.lastIndex(), replicatedLog.snapshotIndex, replicatedLog.snapshotTerm, replicatedLog.size()); - currentBehavior = switchBehavior(RaftState.Follower); + currentBehavior = new Follower(context); onStateChanged(); } @@ -355,14 +355,13 @@ public abstract class RaftActor extends UntypedPersistentActor { if (!(message instanceof AppendEntriesMessages.AppendEntries) && !(message instanceof AppendEntriesReply) && !(message instanceof SendHeartBeat)) { if(LOG.isDebugEnabled()) { - LOG.debug("onReceiveCommand: message:" + message.getClass()); + LOG.debug("onReceiveCommand: message: {}", message.getClass()); } } - RaftState state = - currentBehavior.handleMessage(getSender(), message); RaftActorBehavior oldBehavior = currentBehavior; - currentBehavior = switchBehavior(state); + currentBehavior = currentBehavior.handleMessage(getSender(), message); + if(oldBehavior != currentBehavior){ onStateChanged(); } @@ -569,38 +568,6 @@ public abstract class RaftActor extends UntypedPersistentActor { protected void onLeaderChanged(String oldLeader, String newLeader){}; - private RaftActorBehavior switchBehavior(RaftState state) { - if (currentBehavior != null) { - if (currentBehavior.state() == state) { - return currentBehavior; - } - LOG.info("Switching from state " + currentBehavior.state() + " to " - + state); - - try { - currentBehavior.close(); - } catch (Exception e) { - LOG.error(e, - "Failed to close behavior : " + currentBehavior.state()); - } - - } else { - LOG.info("Switching behavior to " + state); - } - RaftActorBehavior behavior = null; - if (state == RaftState.Candidate) { - behavior = new Candidate(context); - } else if (state == RaftState.Follower) { - behavior = new Follower(context); - } else { - behavior = new Leader(context); - } - - - - return behavior; - } - private void trimPersistentData(long sequenceNumber) { // Trim akka snapshots // FIXME : Not sure how exactly the SnapshotSelectionCriteria is applied @@ -622,8 +589,8 @@ public abstract class RaftActor extends UntypedPersistentActor { } String peerAddress = context.getPeerAddress(leaderId); if(LOG.isDebugEnabled()) { - LOG.debug("getLeaderAddress leaderId = " + leaderId + " peerAddress = " - + peerAddress); + LOG.debug("getLeaderAddress leaderId = {} peerAddress = {}", + leaderId, peerAddress); } return peerAddress; @@ -697,8 +664,11 @@ public abstract class RaftActor extends UntypedPersistentActor { public void appendAndPersist(final ActorRef clientActor, final String identifier, final ReplicatedLogEntry replicatedLogEntry) { - context.getLogger().debug( - "Append log entry and persist {} ", replicatedLogEntry); + + if(LOG.isDebugEnabled()) { + LOG.debug("Append log entry and persist {} ", replicatedLogEntry); + } + // FIXME : By adding the replicated log entry to the in-memory journal we are not truly ensuring durability of the logs journal.add(replicatedLogEntry); diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractRaftActorBehavior.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractRaftActorBehavior.java index b1560a5648..eed74bba82 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractRaftActorBehavior.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractRaftActorBehavior.java @@ -10,9 +10,9 @@ package org.opendaylight.controller.cluster.raft.behaviors; import akka.actor.ActorRef; import akka.actor.Cancellable; +import akka.event.LoggingAdapter; import org.opendaylight.controller.cluster.raft.ClientRequestTracker; import org.opendaylight.controller.cluster.raft.RaftActorContext; -import org.opendaylight.controller.cluster.raft.RaftState; import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry; import org.opendaylight.controller.cluster.raft.SerializationUtils; import org.opendaylight.controller.cluster.raft.base.messages.ApplyLogEntries; @@ -44,6 +44,11 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { */ protected final RaftActorContext context; + /** + * + */ + protected final LoggingAdapter LOG; + /** * */ @@ -57,6 +62,7 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { protected AbstractRaftActorBehavior(RaftActorContext context) { this.context = context; + this.LOG = context.getLogger(); } /** @@ -71,7 +77,7 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { * @param appendEntries The AppendEntries message * @return */ - protected abstract RaftState handleAppendEntries(ActorRef sender, + protected abstract RaftActorBehavior handleAppendEntries(ActorRef sender, AppendEntries appendEntries); @@ -83,19 +89,21 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { * @param appendEntries * @return */ - protected RaftState appendEntries(ActorRef sender, + protected RaftActorBehavior appendEntries(ActorRef sender, AppendEntries appendEntries) { // 1. Reply false if term < currentTerm (§5.1) if (appendEntries.getTerm() < currentTerm()) { - context.getLogger().debug( - "Cannot append entries because sender term " + appendEntries - .getTerm() + " is less than " + currentTerm()); + if(LOG.isDebugEnabled()) { + LOG.debug("Cannot append entries because sender term {} is less than {}", + appendEntries.getTerm(), currentTerm()); + } + sender.tell( new AppendEntriesReply(context.getId(), currentTerm(), false, lastIndex(), lastTerm()), actor() ); - return state(); + return this; } @@ -114,7 +122,7 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { * @param appendEntriesReply The AppendEntriesReply message * @return */ - protected abstract RaftState handleAppendEntriesReply(ActorRef sender, + protected abstract RaftActorBehavior handleAppendEntriesReply(ActorRef sender, AppendEntriesReply appendEntriesReply); /** @@ -125,11 +133,12 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { * @param requestVote * @return */ - protected RaftState requestVote(ActorRef sender, + protected RaftActorBehavior requestVote(ActorRef sender, RequestVote requestVote) { - - context.getLogger().debug(requestVote.toString()); + if(LOG.isDebugEnabled()) { + LOG.debug(requestVote.toString()); + } boolean grantVote = false; @@ -167,7 +176,7 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { sender.tell(new RequestVoteReply(currentTerm(), grantVote), actor()); - return state(); + return this; } /** @@ -182,7 +191,7 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { * @param requestVoteReply The RequestVoteReply message * @return */ - protected abstract RaftState handleRequestVoteReply(ActorRef sender, + protected abstract RaftActorBehavior handleRequestVoteReply(ActorRef sender, RequestVoteReply requestVoteReply); /** @@ -341,12 +350,14 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { } else { //if one index is not present in the log, no point in looping // around as the rest wont be present either - context.getLogger().warning( - "Missing index {} from log. Cannot apply state. Ignoring {} to {}", i, i, index ); + LOG.warning( + "Missing index {} from log. Cannot apply state. Ignoring {} to {}", i, i, index); break; } } - context.getLogger().debug("Setting last applied to {}", newLastApplied); + if(LOG.isDebugEnabled()) { + LOG.debug("Setting last applied to {}", newLastApplied); + } context.setLastApplied(newLastApplied); // send a message to persist a ApplyLogEntries marker message into akka's persistent journal @@ -361,7 +372,7 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { } @Override - public RaftState handleMessage(ActorRef sender, Object message) { + public RaftActorBehavior handleMessage(ActorRef sender, Object message) { if (message instanceof AppendEntries) { return appendEntries(sender, (AppendEntries) message); } else if (message instanceof AppendEntriesReply) { @@ -371,10 +382,21 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { } else if (message instanceof RequestVoteReply) { return handleRequestVoteReply(sender, (RequestVoteReply) message); } - return state(); + return this; } @Override public String getLeaderId() { return leaderId; } + + protected RaftActorBehavior switchBehavior(RaftActorBehavior behavior) { + LOG.info("Switching from behavior {} to {}", this.state(), behavior.state()); + try { + close(); + } catch (Exception e) { + LOG.error(e, "Failed to close behavior : {}", this.state()); + } + + return behavior; + } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Candidate.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Candidate.java index bb1927ef23..4a3e2c5d66 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Candidate.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Candidate.java @@ -52,7 +52,9 @@ public class Candidate extends AbstractRaftActorBehavior { peers = context.getPeerAddresses().keySet(); - context.getLogger().debug("Election:Candidate has following peers:"+ peers); + if(LOG.isDebugEnabled()) { + LOG.debug("Election:Candidate has following peers: {}", peers); + } if(peers.size() > 0) { // Votes are required from a majority of the peers including self. @@ -78,21 +80,23 @@ public class Candidate extends AbstractRaftActorBehavior { scheduleElection(electionDuration()); } - @Override protected RaftState handleAppendEntries(ActorRef sender, + @Override protected RaftActorBehavior handleAppendEntries(ActorRef sender, AppendEntries appendEntries) { - context.getLogger().debug(appendEntries.toString()); + if(LOG.isDebugEnabled()) { + LOG.debug(appendEntries.toString()); + } - return state(); + return this; } - @Override protected RaftState handleAppendEntriesReply(ActorRef sender, + @Override protected RaftActorBehavior handleAppendEntriesReply(ActorRef sender, AppendEntriesReply appendEntriesReply) { - return state(); + return this; } - @Override protected RaftState handleRequestVoteReply(ActorRef sender, + @Override protected RaftActorBehavior handleRequestVoteReply(ActorRef sender, RequestVoteReply requestVoteReply) { if (requestVoteReply.isVoteGranted()) { @@ -100,10 +104,10 @@ public class Candidate extends AbstractRaftActorBehavior { } if (voteCount >= votesRequired) { - return RaftState.Leader; + return switchBehavior(new Leader(context)); } - return state(); + return this; } @Override public RaftState state() { @@ -111,7 +115,7 @@ public class Candidate extends AbstractRaftActorBehavior { } @Override - public RaftState handleMessage(ActorRef sender, Object originalMessage) { + public RaftActorBehavior handleMessage(ActorRef sender, Object originalMessage) { Object message = fromSerializableMessage(originalMessage); @@ -119,14 +123,17 @@ public class Candidate extends AbstractRaftActorBehavior { RaftRPC rpc = (RaftRPC) message; - context.getLogger().debug("RaftRPC message received {} my term is {}", rpc.toString(), context.getTermInformation().getCurrentTerm()); + if(LOG.isDebugEnabled()) { + LOG.debug("RaftRPC message received {} my term is {}", rpc, context.getTermInformation().getCurrentTerm()); + } // If RPC request or response contains term T > currentTerm: // set currentTerm = T, convert to follower (§5.1) // This applies to all RPC messages and responses if (rpc.getTerm() > context.getTermInformation().getCurrentTerm()) { context.getTermInformation().updateAndPersist(rpc.getTerm(), null); - return RaftState.Follower; + + return switchBehavior(new Follower(context)); } } @@ -137,11 +144,12 @@ public class Candidate extends AbstractRaftActorBehavior { // ourselves the leader. This gives enough time for a leader // who we do not know about (as a peer) // to send a message to the candidate - return RaftState.Leader; + + return switchBehavior(new Leader(context)); } startNewTerm(); scheduleElection(electionDuration()); - return state(); + return this; } return super.handleMessage(sender, message); @@ -159,7 +167,9 @@ public class Candidate extends AbstractRaftActorBehavior { context.getTermInformation().updateAndPersist(currentTerm + 1, context.getId()); - context.getLogger().debug("Starting new term " + (currentTerm + 1)); + if(LOG.isDebugEnabled()) { + LOG.debug("Starting new term {}", (currentTerm + 1)); + } // Request for a vote // TODO: Retry request for vote if replies do not arrive in a reasonable diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Follower.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Follower.java index 1cfdf9dba8..7ada8b31c5 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Follower.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Follower.java @@ -9,7 +9,6 @@ package org.opendaylight.controller.cluster.raft.behaviors; import akka.actor.ActorRef; -import akka.event.LoggingAdapter; import com.google.protobuf.ByteString; import org.opendaylight.controller.cluster.raft.RaftActorContext; import org.opendaylight.controller.cluster.raft.RaftState; @@ -39,17 +38,13 @@ import java.util.ArrayList; public class Follower extends AbstractRaftActorBehavior { private ByteString snapshotChunksCollected = ByteString.EMPTY; - private final LoggingAdapter LOG; - public Follower(RaftActorContext context) { super(context); - LOG = context.getLogger(); - scheduleElection(electionDuration()); } - @Override protected RaftState handleAppendEntries(ActorRef sender, + @Override protected RaftActorBehavior handleAppendEntries(ActorRef sender, AppendEntries appendEntries) { if(appendEntries.getEntries() != null && appendEntries.getEntries().size() > 0) { @@ -133,15 +128,14 @@ public class Follower extends AbstractRaftActorBehavior { new AppendEntriesReply(context.getId(), currentTerm(), false, lastIndex(), lastTerm()), actor() ); - return state(); + return this; } if (appendEntries.getEntries() != null && appendEntries.getEntries().size() > 0) { if(LOG.isDebugEnabled()) { LOG.debug( - "Number of entries to be appended = " + appendEntries - .getEntries().size() + "Number of entries to be appended = {}", appendEntries.getEntries().size() ); } @@ -168,8 +162,7 @@ public class Follower extends AbstractRaftActorBehavior { if(LOG.isDebugEnabled()) { LOG.debug( - "Removing entries from log starting at " - + matchEntry.getIndex() + "Removing entries from log starting at {}", matchEntry.getIndex() ); } @@ -181,9 +174,7 @@ public class Follower extends AbstractRaftActorBehavior { } if(LOG.isDebugEnabled()) { - context.getLogger().debug( - "After cleanup entries to be added from = " + (addEntriesFrom - + lastIndex()) + LOG.debug("After cleanup entries to be added from = {}", (addEntriesFrom + lastIndex()) ); } @@ -191,17 +182,14 @@ public class Follower extends AbstractRaftActorBehavior { for (int i = addEntriesFrom; i < appendEntries.getEntries().size(); i++) { - context.getLogger().info( - "Append entry to log " + appendEntries.getEntries().get( - i).getData() - .toString() - ); - context.getReplicatedLog() - .appendAndPersist(appendEntries.getEntries().get(i)); + if(LOG.isDebugEnabled()) { + LOG.debug("Append entry to log {}", appendEntries.getEntries().get(i).getData()); + } + context.getReplicatedLog().appendAndPersist(appendEntries.getEntries().get(i)); } if(LOG.isDebugEnabled()) { - LOG.debug("Log size is now " + context.getReplicatedLog().size()); + LOG.debug("Log size is now {}", context.getReplicatedLog().size()); } } @@ -216,7 +204,7 @@ public class Follower extends AbstractRaftActorBehavior { if (prevCommitIndex != context.getCommitIndex()) { if(LOG.isDebugEnabled()) { - LOG.debug("Commit index set to " + context.getCommitIndex()); + LOG.debug("Commit index set to {}", context.getCommitIndex()); } } @@ -239,24 +227,24 @@ public class Follower extends AbstractRaftActorBehavior { sender.tell(new AppendEntriesReply(context.getId(), currentTerm(), true, lastIndex(), lastTerm()), actor()); - return state(); + return this; } - @Override protected RaftState handleAppendEntriesReply(ActorRef sender, + @Override protected RaftActorBehavior handleAppendEntriesReply(ActorRef sender, AppendEntriesReply appendEntriesReply) { - return state(); + return this; } - @Override protected RaftState handleRequestVoteReply(ActorRef sender, + @Override protected RaftActorBehavior handleRequestVoteReply(ActorRef sender, RequestVoteReply requestVoteReply) { - return state(); + return this; } @Override public RaftState state() { return RaftState.Follower; } - @Override public RaftState handleMessage(ActorRef sender, Object originalMessage) { + @Override public RaftActorBehavior handleMessage(ActorRef sender, Object originalMessage) { Object message = fromSerializableMessage(originalMessage); @@ -271,7 +259,7 @@ public class Follower extends AbstractRaftActorBehavior { } if (message instanceof ElectionTimeout) { - return RaftState.Candidate; + return switchBehavior(new Candidate(context)); } else if (message instanceof InstallSnapshot) { InstallSnapshot installSnapshot = (InstallSnapshot) message; @@ -297,8 +285,10 @@ public class Follower extends AbstractRaftActorBehavior { // this is the last chunk, create a snapshot object and apply snapshotChunksCollected = snapshotChunksCollected.concat(installSnapshot.getData()); - context.getLogger().debug("Last chunk received: snapshotChunksCollected.size:{}", - snapshotChunksCollected.size()); + if(LOG.isDebugEnabled()) { + LOG.debug("Last chunk received: snapshotChunksCollected.size:{}", + snapshotChunksCollected.size()); + } Snapshot snapshot = Snapshot.create(snapshotChunksCollected.toByteArray(), new ArrayList(), @@ -324,7 +314,7 @@ public class Follower extends AbstractRaftActorBehavior { true), actor()); } catch (Exception e) { - context.getLogger().error("Exception in InstallSnapshot of follower", e); + LOG.error(e, "Exception in InstallSnapshot of follower:"); //send reply with success as false. The chunk will be sent again on failure sender.tell(new InstallSnapshotReply(currentTerm(), context.getId(), installSnapshot.getChunkIndex(), false), actor()); diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Leader.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Leader.java index ff8a2256d3..9edba85865 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Leader.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Leader.java @@ -11,7 +11,6 @@ package org.opendaylight.controller.cluster.raft.behaviors; import akka.actor.ActorRef; import akka.actor.ActorSelection; import akka.actor.Cancellable; -import akka.event.LoggingAdapter; import com.google.common.base.Preconditions; import com.google.protobuf.ByteString; import org.opendaylight.controller.cluster.raft.ClientRequestTracker; @@ -81,13 +80,9 @@ public class Leader extends AbstractRaftActorBehavior { private final int minReplicationCount; - private final LoggingAdapter LOG; - public Leader(RaftActorContext context) { super(context); - LOG = context.getLogger(); - followers = context.getPeerAddresses().keySet(); for (String followerId : followers) { @@ -100,7 +95,7 @@ public class Leader extends AbstractRaftActorBehavior { } if(LOG.isDebugEnabled()) { - LOG.debug("Election:Leader has following peers:" + followers); + LOG.debug("Election:Leader has following peers: {}", followers); } if (followers.size() > 0) { @@ -123,17 +118,17 @@ public class Leader extends AbstractRaftActorBehavior { } - @Override protected RaftState handleAppendEntries(ActorRef sender, + @Override protected RaftActorBehavior handleAppendEntries(ActorRef sender, AppendEntries appendEntries) { if(LOG.isDebugEnabled()) { LOG.debug(appendEntries.toString()); } - return state(); + return this; } - @Override protected RaftState handleAppendEntriesReply(ActorRef sender, + @Override protected RaftActorBehavior handleAppendEntriesReply(ActorRef sender, AppendEntriesReply appendEntriesReply) { if(! appendEntriesReply.isSuccess()) { @@ -149,7 +144,7 @@ public class Leader extends AbstractRaftActorBehavior { if(followerLogInformation == null){ LOG.error("Unknown follower {}", followerId); - return state(); + return this; } if (appendEntriesReply.isSuccess()) { @@ -199,7 +194,7 @@ public class Leader extends AbstractRaftActorBehavior { applyLogToStateMachine(context.getCommitIndex()); } - return state(); + return this; } protected ClientRequestTracker removeClientRequestTracker(long logIndex) { @@ -222,16 +217,16 @@ public class Leader extends AbstractRaftActorBehavior { return null; } - @Override protected RaftState handleRequestVoteReply(ActorRef sender, + @Override protected RaftActorBehavior handleRequestVoteReply(ActorRef sender, RequestVoteReply requestVoteReply) { - return state(); + return this; } @Override public RaftState state() { return RaftState.Leader; } - @Override public RaftState handleMessage(ActorRef sender, Object originalMessage) { + @Override public RaftActorBehavior handleMessage(ActorRef sender, Object originalMessage) { Preconditions.checkNotNull(sender, "sender should not be null"); Object message = fromSerializableMessage(originalMessage); @@ -243,13 +238,15 @@ public class Leader extends AbstractRaftActorBehavior { // This applies to all RPC messages and responses if (rpc.getTerm() > context.getTermInformation().getCurrentTerm()) { context.getTermInformation().updateAndPersist(rpc.getTerm(), null); - return RaftState.Follower; + + return switchBehavior(new Follower(context)); } } try { if (message instanceof SendHeartBeat) { - return sendHeartBeat(); + sendHeartBeat(); + return this; } else if(message instanceof SendInstallSnapshot) { installSnapshotIfNeeded(); } else if (message instanceof Replicate) { @@ -321,7 +318,7 @@ public class Leader extends AbstractRaftActorBehavior { long logIndex = replicate.getReplicatedLogEntry().getIndex(); if(LOG.isDebugEnabled()) { - LOG.debug("Replicate message " + logIndex); + LOG.debug("Replicate message {}", logIndex); } // Create a tracker entry we will use this later to notify the @@ -445,7 +442,7 @@ public class Leader extends AbstractRaftActorBehavior { followerActor.path(), mapFollowerToSnapshot.get(followerId).getChunkIndex(), mapFollowerToSnapshot.get(followerId).getTotalChunks()); } catch (IOException e) { - LOG.error("InstallSnapshot failed for Leader.", e); + LOG.error(e, "InstallSnapshot failed for Leader."); } } @@ -467,11 +464,10 @@ public class Leader extends AbstractRaftActorBehavior { return nextChunk; } - private RaftState sendHeartBeat() { + private void sendHeartBeat() { if (followers.size() > 0) { sendAppendEntries(); } - return state(); } private void stopHeartBeat() { diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/RaftActorBehavior.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/RaftActorBehavior.java index ca2d916ecf..064cd8b88c 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/RaftActorBehavior.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/RaftActorBehavior.java @@ -25,17 +25,18 @@ import org.opendaylight.controller.cluster.raft.RaftState; * differently. */ public interface RaftActorBehavior extends AutoCloseable{ + /** * Handle a message. If the processing of the message warrants a state - * change then a new state should be returned otherwise this method should - * return the state for the current behavior. + * change then a new behavior should be returned otherwise this method should + * return the current behavior. * * @param sender The sender of the message * @param message A message that needs to be processed * - * @return The new state or self (this) + * @return The new behavior or current behavior */ - RaftState handleMessage(ActorRef sender, Object message); + RaftActorBehavior handleMessage(ActorRef sender, Object message); /** * The state associated with a given behavior diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorTest.java index 22f374319c..c15c9198bd 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorTest.java @@ -181,7 +181,7 @@ public class RaftActorTest extends AbstractActorTest { return true; } }.from(raftActor.path().toString()) - .message("Switching from state Candidate to Leader") + .message("Switching from behavior Candidate to Leader") .occurrences(1).exec(); diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractRaftActorBehaviorTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractRaftActorBehaviorTest.java index 8068dfbcff..3893018008 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractRaftActorBehaviorTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractRaftActorBehaviorTest.java @@ -7,7 +7,6 @@ import org.junit.Test; import org.opendaylight.controller.cluster.raft.AbstractActorTest; import org.opendaylight.controller.cluster.raft.MockRaftActorContext; import org.opendaylight.controller.cluster.raft.RaftActorContext; -import org.opendaylight.controller.cluster.raft.RaftState; import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry; import org.opendaylight.controller.cluster.raft.SerializationUtils; import org.opendaylight.controller.cluster.raft.messages.AppendEntries; @@ -22,6 +21,7 @@ import java.util.ArrayList; import java.util.List; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest { @@ -79,12 +79,12 @@ public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest { RaftActorBehavior behavior = createBehavior(context); // Send an unknown message so that the state of the RaftActor remains unchanged - RaftState expected = behavior.handleMessage(getRef(), "unknown"); + RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown"); - RaftState raftState = + RaftActorBehavior raftBehavior = behavior.handleMessage(getRef(), appendEntries); - assertEquals(expected, raftState); + assertEquals(expected, raftBehavior); // Also expect an AppendEntriesReply to be sent where success is false final Boolean out = new ExpectMsg(duration("1 seconds"), @@ -145,12 +145,12 @@ public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest { } // Send an unknown message so that the state of the RaftActor remains unchanged - RaftState expected = behavior.handleMessage(getRef(), "unknown"); + RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown"); - RaftState raftState = + RaftActorBehavior raftBehavior = behavior.handleMessage(getRef(), appendEntries); - assertEquals(expected, raftState); + assertEquals(expected, raftBehavior); assertEquals(1, log.size()); @@ -174,11 +174,11 @@ public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest { RaftActorBehavior behavior = createBehavior( createActorContext(behaviorActor)); - RaftState raftState = behavior.handleMessage(getTestActor(), + RaftActorBehavior raftBehavior = behavior.handleMessage(getTestActor(), new RequestVote(1000, "test", 10000, 999)); - if(behavior.state() != RaftState.Follower){ - assertEquals(RaftState.Follower, raftState); + if(!(behavior instanceof Follower)){ + assertTrue(raftBehavior instanceof Follower); } else { final Boolean out = @@ -228,11 +228,11 @@ public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest { RaftActorBehavior behavior = createBehavior(actorContext); - RaftState raftState = behavior.handleMessage(getTestActor(), + RaftActorBehavior raftBehavior = behavior.handleMessage(getTestActor(), new RequestVote(1000, "test", 10000, 999)); - if(behavior.state() != RaftState.Follower){ - assertEquals(RaftState.Follower, raftState); + if(!(behavior instanceof Follower)){ + assertTrue(raftBehavior instanceof Follower); } else { final Boolean out = new ExpectMsg(duration("1 seconds"), @@ -309,10 +309,10 @@ public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest { setLastLogEntry( (MockRaftActorContext) actorContext, 0, 0, p); - RaftState raftState = createBehavior(actorContext) + RaftActorBehavior raftBehavior = createBehavior(actorContext) .handleMessage(actorRef, rpc); - assertEquals(RaftState.Follower, raftState); + assertTrue(raftBehavior instanceof Follower); } protected MockRaftActorContext.SimpleReplicatedLog setLastLogEntry( diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/CandidateTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/CandidateTest.java index d478b17555..a8d47e2c60 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/CandidateTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/CandidateTest.java @@ -9,7 +9,6 @@ import org.junit.Test; import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl; import org.opendaylight.controller.cluster.raft.MockRaftActorContext; import org.opendaylight.controller.cluster.raft.RaftActorContext; -import org.opendaylight.controller.cluster.raft.RaftState; import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout; import org.opendaylight.controller.cluster.raft.messages.AppendEntries; import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply; @@ -109,10 +108,10 @@ public class CandidateTest extends AbstractRaftActorBehaviorTest { Candidate candidate = new Candidate(raftActorContext); - RaftState raftState = + RaftActorBehavior raftBehavior = candidate.handleMessage(candidateActor, new ElectionTimeout()); - Assert.assertEquals(RaftState.Leader, raftState); + Assert.assertTrue(raftBehavior instanceof Leader); } @Test @@ -123,10 +122,10 @@ public class CandidateTest extends AbstractRaftActorBehaviorTest { Candidate candidate = new Candidate(raftActorContext); - RaftState raftState = + RaftActorBehavior raftBehavior = candidate.handleMessage(candidateActor, new ElectionTimeout()); - Assert.assertEquals(RaftState.Candidate, raftState); + Assert.assertTrue(raftBehavior instanceof Candidate); } @Test @@ -137,9 +136,9 @@ public class CandidateTest extends AbstractRaftActorBehaviorTest { Candidate candidate = new Candidate(raftActorContext); - RaftState stateOnFirstVote = candidate.handleMessage(peerActor1, new RequestVoteReply(0, true)); + RaftActorBehavior behaviorOnFirstVote = candidate.handleMessage(peerActor1, new RequestVoteReply(0, true)); - Assert.assertEquals(RaftState.Leader, stateOnFirstVote); + Assert.assertTrue(behaviorOnFirstVote instanceof Leader); } @@ -151,12 +150,12 @@ public class CandidateTest extends AbstractRaftActorBehaviorTest { Candidate candidate = new Candidate(raftActorContext); - RaftState stateOnFirstVote = candidate.handleMessage(peerActor1, new RequestVoteReply(0, true)); + RaftActorBehavior behaviorOnFirstVote = candidate.handleMessage(peerActor1, new RequestVoteReply(0, true)); - RaftState stateOnSecondVote = candidate.handleMessage(peerActor2, new RequestVoteReply(0, true)); + RaftActorBehavior behaviorOnSecondVote = candidate.handleMessage(peerActor2, new RequestVoteReply(0, true)); - Assert.assertEquals(RaftState.Candidate, stateOnFirstVote); - Assert.assertEquals(RaftState.Leader, stateOnSecondVote); + Assert.assertTrue(behaviorOnFirstVote instanceof Candidate); + Assert.assertTrue(behaviorOnSecondVote instanceof Leader); } diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/FollowerTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/FollowerTest.java index a72a7c4332..edeab11e2a 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/FollowerTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/FollowerTest.java @@ -9,7 +9,6 @@ import org.junit.Test; import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl; import org.opendaylight.controller.cluster.raft.MockRaftActorContext; import org.opendaylight.controller.cluster.raft.RaftActorContext; -import org.opendaylight.controller.cluster.raft.RaftState; import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry; import org.opendaylight.controller.cluster.raft.base.messages.ApplySnapshot; import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout; @@ -85,10 +84,10 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { Follower follower = new Follower(raftActorContext); - RaftState raftState = + RaftActorBehavior raftBehavior = follower.handleMessage(followerActor, new ElectionTimeout()); - Assert.assertEquals(RaftState.Candidate, raftState); + Assert.assertTrue(raftBehavior instanceof Candidate); } @Test @@ -187,7 +186,7 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { AppendEntries appendEntries = new AppendEntries(2, "leader-1", 100, 1, entries, 101); - RaftState raftState = + RaftActorBehavior raftBehavior = createBehavior(context).handleMessage(getRef(), appendEntries); assertEquals(101L, context.getLastApplied()); @@ -226,12 +225,12 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { RaftActorBehavior behavior = createBehavior(context); // Send an unknown message so that the state of the RaftActor remains unchanged - RaftState expected = behavior.handleMessage(getRef(), "unknown"); + RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown"); - RaftState raftState = + RaftActorBehavior raftBehavior = behavior.handleMessage(getRef(), appendEntries); - assertEquals(expected, raftState); + assertEquals(expected, raftBehavior); // Also expect an AppendEntriesReply to be sent where success is false final Boolean out = new ExpectMsg(duration("1 seconds"), @@ -302,12 +301,12 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { RaftActorBehavior behavior = createBehavior(context); // Send an unknown message so that the state of the RaftActor remains unchanged - RaftState expected = behavior.handleMessage(getRef(), "unknown"); + RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown"); - RaftState raftState = + RaftActorBehavior raftBehavior = behavior.handleMessage(getRef(), appendEntries); - assertEquals(expected, raftState); + assertEquals(expected, raftBehavior); assertEquals(5, log.last().getIndex() + 1); assertNotNull(log.get(3)); assertNotNull(log.get(4)); @@ -382,12 +381,12 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { RaftActorBehavior behavior = createBehavior(context); // Send an unknown message so that the state of the RaftActor remains unchanged - RaftState expected = behavior.handleMessage(getRef(), "unknown"); + RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown"); - RaftState raftState = + RaftActorBehavior raftBehavior = behavior.handleMessage(getRef(), appendEntries); - assertEquals(expected, raftState); + assertEquals(expected, raftBehavior); // The entry at index 2 will be found out-of-sync with the leader // and will be removed diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/LeaderTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/LeaderTest.java index 19af64790f..48543d7de2 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/LeaderTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/LeaderTest.java @@ -12,7 +12,6 @@ import org.opendaylight.controller.cluster.raft.FollowerLogInformation; import org.opendaylight.controller.cluster.raft.FollowerLogInformationImpl; import org.opendaylight.controller.cluster.raft.MockRaftActorContext; import org.opendaylight.controller.cluster.raft.RaftActorContext; -import org.opendaylight.controller.cluster.raft.RaftState; import org.opendaylight.controller.cluster.raft.ReplicatedLogImplEntry; import org.opendaylight.controller.cluster.raft.SerializationUtils; import org.opendaylight.controller.cluster.raft.base.messages.ApplyState; @@ -54,8 +53,8 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { // handle message should return the Leader state when it receives an // unknown message - RaftState state = leader.handleMessage(senderActor, "foo"); - Assert.assertEquals(RaftState.Leader, state); + RaftActorBehavior behavior = leader.handleMessage(senderActor, "foo"); + Assert.assertTrue(behavior instanceof Leader); }}; } @@ -125,7 +124,7 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { actorContext.setPeerAddresses(peerAddresses); Leader leader = new Leader(actorContext); - RaftState raftState = leader + RaftActorBehavior raftBehavior = leader .handleMessage(senderActor, new Replicate(null, null, new MockRaftActorContext.MockReplicatedLogEntry(1, 100, @@ -133,7 +132,7 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { )); // State should not change - assertEquals(RaftState.Leader, raftState); + assertTrue(raftBehavior instanceof Leader); final String out = new ExpectMsg(duration("1 seconds"), "match hint") { @@ -179,11 +178,11 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { .build()); Leader leader = new Leader(actorContext); - RaftState raftState = leader + RaftActorBehavior raftBehavior = leader .handleMessage(senderActor, new Replicate(null, "state-id",actorContext.getReplicatedLog().get(1))); // State should not change - assertEquals(RaftState.Leader, raftState); + assertTrue(raftBehavior instanceof Leader); assertEquals(1, actorContext.getCommitIndex()); @@ -258,10 +257,10 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { new MockRaftActorContext.MockPayload("D")); // this should invoke a sendinstallsnapshot as followersLastIndex < snapshotIndex - RaftState raftState = leader.handleMessage( + RaftActorBehavior raftBehavior = leader.handleMessage( senderActor, new Replicate(null, "state-id", entry)); - assertEquals(RaftState.Leader, raftState); + assertTrue(raftBehavior instanceof Leader); // we might receive some heartbeat messages, so wait till we SendInstallSnapshot Boolean[] matches = new ReceiveWhile(Boolean.class, duration("2 seconds")) { @@ -333,9 +332,9 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { new ReplicatedLogImplEntry(newEntryIndex, currentTerm, new MockRaftActorContext.MockPayload("D")); - RaftState raftState = leader.handleMessage(senderActor, new SendInstallSnapshot()); + RaftActorBehavior raftBehavior = leader.handleMessage(senderActor, new SendInstallSnapshot()); - assertEquals(RaftState.Leader, raftState); + assertTrue(raftBehavior instanceof Leader); // check if installsnapshot gets called with the correct values. final String out = @@ -419,11 +418,11 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { //clears leaders log actorContext.getReplicatedLog().removeFrom(0); - RaftState raftState = leader.handleMessage(senderActor, + RaftActorBehavior raftBehavior = leader.handleMessage(senderActor, new InstallSnapshotReply(currentTerm, followerActor.path().toString(), leader.getFollowerToSnapshot().getChunkIndex(), true)); - assertEquals(RaftState.Leader, raftState); + assertTrue(raftBehavior instanceof Leader); assertEquals(leader.mapFollowerToSnapshot.size(), 0); assertEquals(leader.followerToLog.size(), 1); diff --git a/opendaylight/md-sal/sal-binding-it/src/main/java/org/opendaylight/controller/test/sal/binding/it/TestHelper.java b/opendaylight/md-sal/sal-binding-it/src/main/java/org/opendaylight/controller/test/sal/binding/it/TestHelper.java index efd35ccfa6..f7313f4ce7 100644 --- a/opendaylight/md-sal/sal-binding-it/src/main/java/org/opendaylight/controller/test/sal/binding/it/TestHelper.java +++ b/opendaylight/md-sal/sal-binding-it/src/main/java/org/opendaylight/controller/test/sal/binding/it/TestHelper.java @@ -76,7 +76,6 @@ public class TestHelper { mavenBundle("org.apache.sshd", "sshd-core").versionAsInProject(), // mavenBundle("org.openexi", "nagasena").versionAsInProject(), // mavenBundle("org.openexi", "nagasena-rta").versionAsInProject(), // - mavenBundle(CONTROLLER + ".thirdparty", "ganymed").versionAsInProject(), // mavenBundle(CONTROLLER, "netconf-mapping-api").versionAsInProject(), // mavenBundle(CONTROLLER, "config-persister-impl").versionAsInProject(), // diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/NodeToNormalizedNodeBuilder.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/NodeToNormalizedNodeBuilder.java deleted file mode 100644 index 03d632b61f..0000000000 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/NodeToNormalizedNodeBuilder.java +++ /dev/null @@ -1,856 +0,0 @@ -/* - * - * 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.cluster.datastore.node; - -import com.google.common.base.Preconditions; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import org.opendaylight.controller.cluster.datastore.node.utils.NodeIdentifierFactory; -import org.opendaylight.controller.protobuff.messages.common.NormalizedNodeMessages.Node; -import org.opendaylight.yangtools.concepts.Identifiable; -import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; -import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; -import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; -import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; -import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; -import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; -import org.opendaylight.yangtools.yang.data.api.schema.MapNode; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer; -import org.opendaylight.yangtools.yang.data.impl.schema.Builders; -import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; -import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder; -import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder; -import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; -import org.opendaylight.yangtools.yang.model.api.AugmentationTarget; -import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; -import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; -import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; -import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; -import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; -import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; -import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import static com.google.common.base.Preconditions.checkArgument; - -/** - * NormalizedNodeBuilder is a builder that walks through a tree like structure and constructs a - * NormalizedNode from it. - *

- * A large part of this code has been copied over from a similar class in sal-common-impl which was - * originally supposed to convert a CompositeNode to NormalizedNode - * - * @param - */ -public abstract class NodeToNormalizedNodeBuilder - implements Identifiable { - - private final T identifier; - - protected static final Logger logger = LoggerFactory - .getLogger(NodeToNormalizedNodeBuilder.class); - - @Override - public T getIdentifier() { - return identifier; - } - - ; - - protected NodeToNormalizedNodeBuilder(final T identifier) { - super(); - this.identifier = identifier; - - } - - /** - * @return Should return true if the node that this operation corresponds to is a mixin - */ - public boolean isMixin() { - return false; - } - - - /** - * @return Should return true if the node that this operation corresponds to has a 'key' - * associated with it. This is typically true for a list-item or leaf-list entry in yang - */ - public boolean isKeyedEntry() { - return false; - } - - protected Set getQNameIdentifiers() { - return Collections.singleton(identifier.getNodeType()); - } - - public abstract NodeToNormalizedNodeBuilder getChild( - final PathArgument child); - - public abstract NodeToNormalizedNodeBuilder getChild(QName child); - - public abstract NormalizedNode normalize(QName nodeType, Node node); - - - - private static abstract class SimpleTypeNormalization - extends NodeToNormalizedNodeBuilder { - - protected SimpleTypeNormalization(final T identifier) { - super(identifier); - } - - @Override - public NormalizedNode normalize(final QName nodeType, - final Node node) { - checkArgument(node != null); - return normalizeImpl(nodeType, node); - } - - protected abstract NormalizedNode normalizeImpl(QName nodeType, - Node node); - - @Override - public NodeToNormalizedNodeBuilder getChild( - final PathArgument child) { - return null; - } - - @Override - public NodeToNormalizedNodeBuilder getChild(final QName child) { - return null; - } - - @Override - public NormalizedNode createDefault( - final PathArgument currentArg) { - // TODO Auto-generated method stub - return null; - } - - } - - - private static final class LeafNormalization extends - SimpleTypeNormalization { - - private final LeafSchemaNode schema; - - protected LeafNormalization(final LeafSchemaNode schema, final NodeIdentifier identifier) { - super(identifier); - this.schema = schema; - } - - @Override - protected NormalizedNode normalizeImpl(final QName nodeType, - final Node node) { - Object value = NodeValueCodec.toTypeSafeValue(this.schema, this.schema.getType(), node); - return ImmutableNodes.leafNode(nodeType, value); - - } - - } - - - private static final class LeafListEntryNormalization extends - SimpleTypeNormalization { - - private final LeafListSchemaNode schema; - - public LeafListEntryNormalization(final LeafListSchemaNode potential) { - super(new NodeWithValue(potential.getQName(), null)); - this.schema = potential; - } - - @Override - protected NormalizedNode normalizeImpl(final QName nodeType, - final Node node) { - final Object data = node.getValue(); - if (data == null) { - Preconditions.checkArgument(false, - "No data available in leaf list entry for " + nodeType); - } - - Object value = NodeValueCodec.toTypeSafeValue(this.schema, this.schema.getType(), node); - - NodeWithValue nodeId = new NodeWithValue(nodeType, value); - return Builders.leafSetEntryBuilder().withNodeIdentifier(nodeId) - .withValue(value).build(); - } - - - @Override - public boolean isKeyedEntry() { - return true; - } - } - - - private static abstract class NodeToNormalizationNodeOperation - extends NodeToNormalizedNodeBuilder { - - protected NodeToNormalizationNodeOperation(final T identifier) { - super(identifier); - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - @Override - public final NormalizedNodeContainer normalize( - final QName nodeType, final Node node) { - checkArgument(node != null); - - if (!node.getType().equals(AugmentationNode.class.getSimpleName()) - && !node.getType().equals(ContainerNode.class.getSimpleName()) - && !node.getType().equals(MapNode.class.getSimpleName())) { - checkArgument(nodeType != null); - } - - NormalizedNodeContainerBuilder builder = createBuilder(node); - - Set> usedMixins = new HashSet<>(); - - logNode(node); - - if (node.getChildCount() == 0 && ( - node.getType().equals(LeafSetEntryNode.class.getSimpleName()) - || node.getType().equals(LeafNode.class.getSimpleName()))) { - PathArgument childPathArgument = - NodeIdentifierFactory.getArgument(node.getPath()); - - final NormalizedNode child; - if (childPathArgument instanceof NodeWithValue) { - final NodeWithValue nodeWithValue = - new NodeWithValue(childPathArgument.getNodeType(), - node.getValue()); - child = - Builders.leafSetEntryBuilder() - .withNodeIdentifier(nodeWithValue) - .withValue(node.getValue()).build(); - } else { - child = - ImmutableNodes.leafNode(childPathArgument.getNodeType(), - node.getValue()); - } - builder.addChild(child); - } - - final List children = node.getChildList(); - for (Node nodeChild : children) { - - PathArgument childPathArgument = - NodeIdentifierFactory.getArgument(nodeChild.getPath()); - - QName childNodeType = null; - NodeToNormalizedNodeBuilder childOp = null; - - if (childPathArgument instanceof AugmentationIdentifier) { - childOp = getChild(childPathArgument); - checkArgument(childOp instanceof AugmentationNormalization, childPathArgument); - } else { - childNodeType = childPathArgument.getNodeType(); - childOp = getChild(childNodeType); - } - // We skip unknown nodes if this node is mixin since - // it's nodes and parent nodes are interleaved - if (childOp == null && isMixin()) { - continue; - } else if (childOp == null) { - logger.error( - "childOp is null and this operation is not a mixin : this = {}", - this.toString()); - } - - checkArgument(childOp != null, - "Node %s is not allowed inside %s", - childNodeType, getIdentifier()); - - if (childOp.isMixin()) { - if (usedMixins.contains(childOp)) { - // We already run / processed that mixin, so to avoid - // duplicate we are - // skipping next nodes. - continue; - } - // builder.addChild(childOp.normalize(nodeType, treeCacheNode)); - final NormalizedNode childNode = - childOp.normalize(childNodeType, nodeChild); - if (childNode != null) - builder.addChild(childNode); - usedMixins.add(childOp); - } else { - final NormalizedNode childNode = - childOp.normalize(childNodeType, nodeChild); - if (childNode != null) - builder.addChild(childNode); - } - } - - - try { - return (NormalizedNodeContainer) builder.build(); - } catch (Exception e) { - return null; - } - - } - - private void logNode(Node node) { - //let us find out the type of the node - logger.debug("We got a {} , with identifier {} with {} children", - node.getType(), node.getPath(), - node.getChildList()); - } - - @SuppressWarnings("rawtypes") - protected abstract NormalizedNodeContainerBuilder createBuilder( - final Node node); - - } - - - private static abstract class DataContainerNormalizationOperation - extends NodeToNormalizationNodeOperation { - - private final DataNodeContainer schema; - private final Map> byQName; - private final Map> byArg; - - protected DataContainerNormalizationOperation(final T identifier, - final DataNodeContainer schema) { - super(identifier); - this.schema = schema; - this.byArg = new ConcurrentHashMap<>(); - this.byQName = new ConcurrentHashMap<>(); - } - - @Override - public NodeToNormalizedNodeBuilder getChild( - final PathArgument child) { - NodeToNormalizedNodeBuilder potential = byArg.get(child); - if (potential != null) { - return potential; - } - potential = fromSchema(schema, child); - return register(potential); - } - - @Override - public NodeToNormalizedNodeBuilder getChild(final QName child) { - if (child == null) { - return null; - } - - NodeToNormalizedNodeBuilder potential = byQName.get(child); - if (potential != null) { - return potential; - } - potential = fromSchemaAndPathArgument(schema, child); - return register(potential); - } - - private NodeToNormalizedNodeBuilder register( - final NodeToNormalizedNodeBuilder potential) { - if (potential != null) { - byArg.put(potential.getIdentifier(), potential); - for (QName qName : potential.getQNameIdentifiers()) { - byQName.put(qName, potential); - } - } - return potential; - } - - } - - - private static final class ListItemNormalization extends - DataContainerNormalizationOperation { - - private final List keyDefinition; - private final ListSchemaNode schemaNode; - - protected ListItemNormalization( - final NodeIdentifierWithPredicates identifier, - final ListSchemaNode schema) { - super(identifier, schema); - this.schemaNode = schema; - keyDefinition = schema.getKeyDefinition(); - } - - @Override - protected NormalizedNodeContainerBuilder createBuilder( - final Node node) { - NodeIdentifierWithPredicates nodeIdentifierWithPredicates = - (NodeIdentifierWithPredicates) NodeIdentifierFactory - .createPathArgument(node - .getPath(), schemaNode); - return Builders.mapEntryBuilder() - .withNodeIdentifier( - nodeIdentifierWithPredicates - ); - } - - @Override - public NormalizedNode createDefault( - final PathArgument currentArg) { - DataContainerNodeAttrBuilder - builder = - Builders.mapEntryBuilder().withNodeIdentifier( - (NodeIdentifierWithPredicates) currentArg); - for (Entry keyValue : ((NodeIdentifierWithPredicates) currentArg) - .getKeyValues().entrySet()) { - if (keyValue.getValue() == null) { - throw new NullPointerException( - "Null value found for path : " - + currentArg); - } - builder.addChild(Builders.leafBuilder() - // - .withNodeIdentifier(new NodeIdentifier(keyValue.getKey())) - .withValue(keyValue.getValue()).build()); - } - return builder.build(); - } - - - @Override - public boolean isKeyedEntry() { - return true; - } - } - - - private static final class ContainerNormalization extends - DataContainerNormalizationOperation { - - protected ContainerNormalization(final ContainerSchemaNode schema) { - super(new NodeIdentifier(schema.getQName()), schema); - } - - @Override - protected NormalizedNodeContainerBuilder createBuilder( - final Node node) { - return Builders.containerBuilder() - .withNodeIdentifier(getIdentifier()); - } - - @Override - public NormalizedNode createDefault( - final PathArgument currentArg) { - return Builders.containerBuilder() - .withNodeIdentifier((NodeIdentifier) currentArg).build(); - } - - } - - - private static abstract class MixinNormalizationOp - extends NodeToNormalizationNodeOperation { - - protected MixinNormalizationOp(final T identifier) { - super(identifier); - } - - @Override - public final boolean isMixin() { - return true; - } - - } - - - private static final class LeafListMixinNormalization extends - MixinNormalizationOp { - - private final NodeToNormalizedNodeBuilder innerOp; - - public LeafListMixinNormalization(final LeafListSchemaNode potential) { - super(new NodeIdentifier(potential.getQName())); - innerOp = new LeafListEntryNormalization(potential); - } - - @Override - protected NormalizedNodeContainerBuilder createBuilder( - final Node node) { - return Builders.leafSetBuilder() - .withNodeIdentifier(getIdentifier()); - } - - @Override - public NormalizedNode createDefault( - final PathArgument currentArg) { - return Builders.leafSetBuilder().withNodeIdentifier(getIdentifier()) - .build(); - } - - @Override - public NodeToNormalizedNodeBuilder getChild( - final PathArgument child) { - if (child instanceof NodeWithValue) { - return innerOp; - } - return null; - } - - @Override - public NodeToNormalizedNodeBuilder getChild(final QName child) { - if (getIdentifier().getNodeType().equals(child)) { - return innerOp; - } - return null; - } - - } - - - private static final class AugmentationNormalization extends - MixinNormalizationOp { - - private final Map> byQName; - private final Map> byArg; - - public AugmentationNormalization(final AugmentationSchema augmentation, - final DataNodeContainer schema) { - super(augmentationIdentifierFrom(augmentation)); - - ImmutableMap.Builder> - byQNameBuilder = - ImmutableMap.builder(); - ImmutableMap.Builder> - byArgBuilder = - ImmutableMap.builder(); - - for (DataSchemaNode augNode : augmentation.getChildNodes()) { - DataSchemaNode resolvedNode = - schema.getDataChildByName(augNode.getQName()); - NodeToNormalizedNodeBuilder resolvedOp = - fromDataSchemaNode(resolvedNode); - byArgBuilder.put(resolvedOp.getIdentifier(), resolvedOp); - for (QName resQName : resolvedOp.getQNameIdentifiers()) { - byQNameBuilder.put(resQName, resolvedOp); - } - } - byQName = byQNameBuilder.build(); - byArg = byArgBuilder.build(); - - } - - @Override - public NodeToNormalizedNodeBuilder getChild( - final PathArgument child) { - return byArg.get(child); - } - - @Override - public NodeToNormalizedNodeBuilder getChild(final QName child) { - return byQName.get(child); - } - - @Override - protected Set getQNameIdentifiers() { - return getIdentifier().getPossibleChildNames(); - } - - @SuppressWarnings("rawtypes") - @Override - protected NormalizedNodeContainerBuilder createBuilder( - final Node node) { - return Builders.augmentationBuilder() - .withNodeIdentifier(getIdentifier()); - } - - @Override - public NormalizedNode createDefault( - final PathArgument currentArg) { - return Builders.augmentationBuilder() - .withNodeIdentifier(getIdentifier()) - .build(); - } - - } - - - private static final class ListMixinNormalization extends - MixinNormalizationOp { - - private final ListItemNormalization innerNode; - - public ListMixinNormalization(final ListSchemaNode list) { - super(new NodeIdentifier(list.getQName())); - this.innerNode = - new ListItemNormalization(new NodeIdentifierWithPredicates( - list.getQName(), Collections.emptyMap()), - list); - } - - @SuppressWarnings("rawtypes") - @Override - protected NormalizedNodeContainerBuilder createBuilder( - final Node node) { - return Builders.mapBuilder().withNodeIdentifier(getIdentifier()); - } - - @Override - public NormalizedNode createDefault( - final PathArgument currentArg) { - return Builders.mapBuilder().withNodeIdentifier(getIdentifier()) - .build(); - } - - @Override - public NodeToNormalizedNodeBuilder getChild( - final PathArgument child) { - if (child.getNodeType().equals(getIdentifier().getNodeType())) { - return innerNode; - } - return null; - } - - @Override - public NodeToNormalizedNodeBuilder getChild(final QName child) { - if (getIdentifier().getNodeType().equals(child)) { - return innerNode; - } - return null; - } - - } - - - private static class ChoiceNodeNormalization extends - MixinNormalizationOp { - - private final ImmutableMap> - byQName; - private final ImmutableMap> - byArg; - - protected ChoiceNodeNormalization( - final org.opendaylight.yangtools.yang.model.api.ChoiceNode schema) { - super(new NodeIdentifier(schema.getQName())); - ImmutableMap.Builder> - byQNameBuilder = - ImmutableMap.builder(); - ImmutableMap.Builder> - byArgBuilder = - ImmutableMap.builder(); - - for (ChoiceCaseNode caze : schema.getCases()) { - for (DataSchemaNode cazeChild : caze.getChildNodes()) { - NodeToNormalizedNodeBuilder childOp = - fromDataSchemaNode(cazeChild); - byArgBuilder.put(childOp.getIdentifier(), childOp); - for (QName qname : childOp.getQNameIdentifiers()) { - byQNameBuilder.put(qname, childOp); - } - } - } - byQName = byQNameBuilder.build(); - byArg = byArgBuilder.build(); - } - - @Override - public NodeToNormalizedNodeBuilder getChild( - final PathArgument child) { - return byArg.get(child); - } - - @Override - public NodeToNormalizedNodeBuilder getChild(final QName child) { - return byQName.get(child); - } - - @Override - protected NormalizedNodeContainerBuilder createBuilder( - final Node node) { - return Builders.choiceBuilder().withNodeIdentifier(getIdentifier()); - } - - @Override - public NormalizedNode createDefault( - final PathArgument currentArg) { - return Builders.choiceBuilder().withNodeIdentifier(getIdentifier()) - .build(); - } - } - - /** - * Find an appropriate NormalizedNodeBuilder using both the schema and the - * Path Argument - * - * @param schema - * @param child - * @return - */ - public static NodeToNormalizedNodeBuilder fromSchemaAndPathArgument( - final DataNodeContainer schema, final QName child) { - DataSchemaNode potential = schema.getDataChildByName(child); - if (potential == null) { - Iterable - choices = - FluentIterable.from(schema.getChildNodes()).filter( - org.opendaylight.yangtools.yang.model.api.ChoiceNode.class); - potential = findChoice(choices, child); - } - if (potential == null) { - if (logger.isTraceEnabled()) { - logger.trace("BAD CHILD = {}", child.toString()); - } - } - - checkArgument(potential != null, - "Supplied QName %s is not valid according to schema %s", child, - schema); - - // If the schema in an instance of DataSchemaNode and the potential - // is augmenting something then there is a chance that this may be - // and augmentation node - if ((schema instanceof DataSchemaNode) - && potential.isAugmenting()) { - - AugmentationNormalization augmentation = - fromAugmentation(schema, (AugmentationTarget) schema, - potential); - - // If an augmentation normalization (builder) is not found then - // we fall through to the regular processing - if(augmentation != null){ - return augmentation; - } - } - return fromDataSchemaNode(potential); - } - - /** - * Given a bunch of choice nodes and a the name of child find a choice node for that child which - * has a non-null value - * - * @param choices - * @param child - * @return - */ - private static org.opendaylight.yangtools.yang.model.api.ChoiceNode findChoice( - final Iterable choices, - final QName child) { - org.opendaylight.yangtools.yang.model.api.ChoiceNode foundChoice = null; - choiceLoop: - for (org.opendaylight.yangtools.yang.model.api.ChoiceNode choice : choices) { - for (ChoiceCaseNode caze : choice.getCases()) { - if (caze.getDataChildByName(child) != null) { - foundChoice = choice; - break choiceLoop; - } - } - } - return foundChoice; - } - - - /** - * Create an AugmentationIdentifier based on the AugmentationSchema - * - * @param augmentation - * @return - */ - public static AugmentationIdentifier augmentationIdentifierFrom( - final AugmentationSchema augmentation) { - ImmutableSet.Builder potentialChildren = ImmutableSet.builder(); - for (DataSchemaNode child : augmentation.getChildNodes()) { - potentialChildren.add(child.getQName()); - } - return new AugmentationIdentifier(potentialChildren.build()); - } - - /** - * Create an AugmentationNormalization based on the schema of the DataContainer, the - * AugmentationTarget and the potential schema node - * - * @param schema - * @param augments - * @param potential - * @return - */ - private static AugmentationNormalization fromAugmentation( - final DataNodeContainer schema, final AugmentationTarget augments, - final DataSchemaNode potential) { - AugmentationSchema augmentation = null; - for (AugmentationSchema aug : augments.getAvailableAugmentations()) { - DataSchemaNode child = aug.getDataChildByName(potential.getQName()); - if (child != null) { - augmentation = aug; - break; - } - - } - if (augmentation != null) { - return new AugmentationNormalization(augmentation, schema); - } else { - return null; - } - } - - /** - * @param schema - * @param child - * @return - */ - private static NodeToNormalizedNodeBuilder fromSchema( - final DataNodeContainer schema, final PathArgument child) { - if (child instanceof AugmentationIdentifier) { - QName childQName = ((AugmentationIdentifier) child) - .getPossibleChildNames().iterator().next(); - - return fromSchemaAndPathArgument(schema, childQName); - } - return fromSchemaAndPathArgument(schema, child.getNodeType()); - } - - public static NodeToNormalizedNodeBuilder fromDataSchemaNode( - final DataSchemaNode potential) { - if (potential instanceof ContainerSchemaNode) { - return new ContainerNormalization((ContainerSchemaNode) potential); - } else if (potential instanceof ListSchemaNode) { - return new ListMixinNormalization((ListSchemaNode) potential); - } else if (potential instanceof LeafSchemaNode) { - return new LeafNormalization((LeafSchemaNode) potential, - new NodeIdentifier(potential.getQName())); - } else if (potential instanceof org.opendaylight.yangtools.yang.model.api.ChoiceNode) { - return new ChoiceNodeNormalization( - (org.opendaylight.yangtools.yang.model.api.ChoiceNode) potential); - } else if (potential instanceof LeafListSchemaNode) { - return new LeafListMixinNormalization( - (LeafListSchemaNode) potential); - } - return null; - } - - public static NodeToNormalizedNodeBuilder from(final SchemaContext ctx) { - return new ContainerNormalization(ctx); - } - - public abstract NormalizedNode createDefault(PathArgument currentArg); - -} diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/NodeValueCodec.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/NodeValueCodec.java deleted file mode 100644 index b6dbefbbac..0000000000 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/NodeValueCodec.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * - * 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.cluster.datastore.node; - -import org.opendaylight.controller.cluster.datastore.node.utils.QNameFactory; -import org.opendaylight.controller.cluster.datastore.util.InstanceIdentifierUtils; -import org.opendaylight.controller.protobuff.messages.common.NormalizedNodeMessages; -import org.opendaylight.yangtools.yang.data.api.codec.BitsCodec; -import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec; -import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; -import org.opendaylight.yangtools.yang.model.api.TypeDefinition; -import org.opendaylight.yangtools.yang.model.util.IdentityrefType; -import org.opendaylight.yangtools.yang.model.util.InstanceIdentifierType; -import org.opendaylight.yangtools.yang.model.util.Leafref; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class NodeValueCodec { - protected static final Logger logger = LoggerFactory - .getLogger(NodeValueCodec.class); - - public static Object toTypeSafeValue(DataSchemaNode schema, TypeDefinition type, NormalizedNodeMessages.Node node){ - - String value = node.getValue(); - - if(schema != null && value != null){ - TypeDefinition baseType = type; - - while (baseType.getBaseType() != null) { - baseType = baseType.getBaseType(); - } - - TypeDefinitionAwareCodec> codec = - TypeDefinitionAwareCodec.from(type); - - if(codec instanceof BitsCodec){ - if(value.contains("[]")){ - value = ""; - } else { - value = value.replace("[", ""); - value = value.replace("]", ""); - value = value.replace(",", " "); - } - } - - if (codec != null) { - return codec.deserialize(value); - } else if(baseType instanceof Leafref) { - return value; - } else if(baseType instanceof IdentityrefType) { - return QNameFactory.create(value); - } else if(baseType instanceof InstanceIdentifierType) { - return InstanceIdentifierUtils.fromSerializable(node.getInstanceIdentifierValue()); - } else { - logger.error("Could not figure out how to transform value " + value + " for schemaType " + type); - } - } - - return value; - } -} diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/NormalizedNodeToProtocolBufferNode.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/NormalizedNodeToProtocolBufferNode.java deleted file mode 100644 index 68d3c590e0..0000000000 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/NormalizedNodeToProtocolBufferNode.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * - * 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.cluster.datastore.node; - -import com.google.common.base.Preconditions; -import org.opendaylight.controller.cluster.datastore.util.InstanceIdentifierUtils; -import org.opendaylight.controller.protobuff.messages.common.NormalizedNodeMessages; -import org.opendaylight.controller.protobuff.messages.common.NormalizedNodeMessages.Node; -import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; -import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; -import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; -import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; -import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode; -import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; -import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; -import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; -import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; -import org.opendaylight.yangtools.yang.data.api.schema.MapNode; -import org.opendaylight.yangtools.yang.data.api.schema.MixinNode; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer; -import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode; - -import java.util.Map; - -/** - * NormalizedNodeToProtocolBufferNode walks the NormalizedNode tree converting it to the - * NormalizedMessage.Node - *

- * {@link org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode } is a tree like structure that provides a generic structure for a yang data - * model - */ -public class NormalizedNodeToProtocolBufferNode { - - - private final Node.Builder builderRoot; - private NormalizedNodeMessages.Container container; - - public NormalizedNodeToProtocolBufferNode() { - - builderRoot = Node.newBuilder(); - } - - public void encode(String parentPath, NormalizedNode normalizedNode) { - if (parentPath == null) { - parentPath = ""; - } - - NormalizedNodeMessages.Container.Builder containerBuilder = - NormalizedNodeMessages.Container.newBuilder(); - - if (normalizedNode != null) { - - navigateNormalizedNode(0, parentPath, normalizedNode, builderRoot); - // here we need to put back the Node Tree in Container - - container = - containerBuilder.setParentPath(parentPath).setNormalizedNode( - builderRoot.build()).build(); - } else { - //this can happen when an attempt was made to read from datastore and normalized node was null. - container = containerBuilder.setParentPath(parentPath).build(); - - } - - } - - - private void navigateDataContainerNode(int level, final String parentPath, - final DataContainerNode dataContainerNode, - Node.Builder builderParent) { - - String newParentPath = - parentPath + "/" + dataContainerNode.getIdentifier().toString(); - String type = getDataContainerType(dataContainerNode).getSimpleName(); - builderParent.setPath(dataContainerNode.getIdentifier().toString()) - .setType(type); - - final Iterable> - value = - dataContainerNode.getValue(); - for (NormalizedNode node : value) { - Node.Builder builderChild = Node.newBuilder(); - if (node instanceof MixinNode - && node instanceof NormalizedNodeContainer) { - - navigateNormalizedNodeContainerMixin(level, newParentPath, - (NormalizedNodeContainer) node, builderChild); - } else { - navigateNormalizedNode(level, newParentPath, node, - builderChild); - } - builderParent.addChild(builderChild); - } - } - - private Class getDataContainerType( - NormalizedNodeContainer dataContainerNode) { - if (dataContainerNode instanceof ChoiceNode) { - return ChoiceNode.class; - } else if (dataContainerNode instanceof AugmentationNode) { - return AugmentationNode.class; - } else if (dataContainerNode instanceof ContainerNode) { - return ContainerNode.class; - } else if (dataContainerNode instanceof MapEntryNode) { - return MapEntryNode.class; - } else if (dataContainerNode instanceof UnkeyedListEntryNode) { - return UnkeyedListEntryNode.class; - } else if (dataContainerNode instanceof MapNode) { - return MapNode.class; - } else if (dataContainerNode instanceof LeafSetNode) { - return LeafSetNode.class; - } - throw new IllegalArgumentException( - "could not find the data container node type " - + dataContainerNode.toString() - ); - } - - private void navigateNormalizedNodeContainerMixin(int level, - final String parentPath, - NormalizedNodeContainer node, Node.Builder builderParent) { - String newParentPath = - parentPath + "/" + node.getIdentifier().toString(); - - builderParent.setPath(node.getIdentifier().toString()).setType( - this.getDataContainerType(node).getSimpleName()); - final Iterable> value = node.getValue(); - for (NormalizedNode normalizedNode : value) { - // child node builder - Node.Builder builderChild = Node.newBuilder(); - if (normalizedNode instanceof MixinNode - && normalizedNode instanceof NormalizedNodeContainer) { - navigateNormalizedNodeContainerMixin(level + 1, newParentPath, - (NormalizedNodeContainer) normalizedNode, builderChild); - } else { - navigateNormalizedNode(level, newParentPath, normalizedNode, - builderChild); - } - builderParent.addChild(builderChild); - - } - - - - } - - - private void navigateNormalizedNode(int level, - String parentPath, NormalizedNode normalizedNode, - Node.Builder builderParent) { - - if (normalizedNode instanceof DataContainerNode) { - - final DataContainerNode dataContainerNode = - (DataContainerNode) normalizedNode; - - navigateDataContainerNode(level + 1, parentPath, dataContainerNode, - builderParent); - } else if (normalizedNode instanceof MixinNode - && normalizedNode instanceof NormalizedNodeContainer) { - - navigateNormalizedNodeContainerMixin(level, parentPath, - (NormalizedNodeContainer) normalizedNode, - builderParent); - } else { - if (normalizedNode instanceof LeafNode) { - buildLeafNode(parentPath, normalizedNode, builderParent); - } else if (normalizedNode instanceof LeafSetEntryNode) { - buildLeafSetEntryNode(parentPath, normalizedNode, - builderParent); - } - - } - - } - - private void buildLeafSetEntryNode(String parentPath, - NormalizedNode normalizedNode, - Node.Builder builderParent) { - String path = - parentPath + "/" + normalizedNode.getIdentifier().toString(); - LeafSetEntryNode leafSetEntryNode = (LeafSetEntryNode) normalizedNode; - Map attributes = leafSetEntryNode.getAttributes(); - if (!attributes.isEmpty()) { - NormalizedNodeMessages.Attribute.Builder builder = null; - for (Map.Entry attribute : attributes.entrySet()) { - builder = NormalizedNodeMessages.Attribute.newBuilder(); - - builder - .setName(attribute.getKey().toString()) - .setValue(normalizedNode.getValue().toString()); - - builderParent.addAttributes(builder.build()); - } - } - buildNodeValue(normalizedNode, builderParent); - } - - private void buildLeafNode(String parentPath, - NormalizedNode normalizedNode, - Node.Builder builderParent) { - Preconditions.checkNotNull(parentPath); - Preconditions.checkNotNull(normalizedNode); - String path = - parentPath + "/" + normalizedNode.getIdentifier().toString(); - LeafNode leafNode = (LeafNode) normalizedNode; - Map attributes = leafNode.getAttributes(); - if (!attributes.isEmpty()) { - NormalizedNodeMessages.Attribute.Builder builder = null; - for (Map.Entry attribute : attributes.entrySet()) { - builder = NormalizedNodeMessages.Attribute.newBuilder(); - builder - .setName(attribute.getKey().toString()) - .setValue(attribute.getValue().toString()); - - builderParent.addAttributes(builder.build()); - } - } - - Object value = normalizedNode.getValue(); - if (value == null) { - builderParent - .setPath(normalizedNode.getIdentifier().toString()) - .setType(LeafNode.class.getSimpleName()) - .setValueType(String.class.getSimpleName()) - .setValue(""); - } else { - buildNodeValue(normalizedNode, builderParent); - } - } - - private void buildNodeValue(NormalizedNode normalizedNode, - Node.Builder builderParent) { - - Object value = normalizedNode.getValue(); - - builderParent - .setPath(normalizedNode.getIdentifier().toString()) - .setType(LeafNode.class.getSimpleName()) - .setValueType((value.getClass().getSimpleName())) - .setValue(value.toString()); - - if(value.getClass().equals(YangInstanceIdentifier.class)){ - builderParent.setInstanceIdentifierValue( - InstanceIdentifierUtils - .toSerializable((YangInstanceIdentifier) value)); - } - } - - public NormalizedNodeMessages.Container getContainer() { - return container; - } -} diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/NodeIdentifierFactory.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/NodeIdentifierFactory.java index ea3986f4a0..da61e6de73 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/NodeIdentifierFactory.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/NodeIdentifierFactory.java @@ -32,20 +32,6 @@ public class NodeIdentifierFactory { return value; } - public static YangInstanceIdentifier.PathArgument getArgument(String id, DataSchemaNode schemaNode){ - YangInstanceIdentifier.PathArgument value = cache.get(id); - if(value == null){ - synchronized (cache){ - value = cache.get(id); - if(value == null) { - value = createPathArgument(id, schemaNode); - cache.put(id, value); - } - } - } - return value; - } - public static YangInstanceIdentifier.PathArgument createPathArgument(String id, DataSchemaNode schemaNode){ final NodeIdentifierWithPredicatesGenerator nodeIdentifierWithPredicatesGenerator = new NodeIdentifierWithPredicatesGenerator(id, schemaNode); diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NodeTypes.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NodeTypes.java new file mode 100644 index 0000000000..3ff6efbb05 --- /dev/null +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NodeTypes.java @@ -0,0 +1,29 @@ +/* + * + * 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.cluster.datastore.node.utils.stream; + +public class NodeTypes { + + public static final byte LEAF_NODE = 1; + public static final byte LEAF_SET = 2; + public static final byte LEAF_SET_ENTRY_NODE = 3; + public static final byte CONTAINER_NODE = 4; + public static final byte UNKEYED_LIST = 5; + public static final byte UNKEYED_LIST_ITEM = 6; + public static final byte MAP_NODE = 7; + public static final byte MAP_ENTRY_NODE = 8; + public static final byte ORDERED_MAP_NODE = 9; + public static final byte CHOICE_NODE = 10; + public static final byte AUGMENTATION_NODE = 11; + public static final byte ANY_XML_NODE = 12; + public static final byte END_NODE = 13; + +} diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeInputStreamReader.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeInputStreamReader.java new file mode 100644 index 0000000000..bdc52bca68 --- /dev/null +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeInputStreamReader.java @@ -0,0 +1,391 @@ +/* + * + * 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.cluster.datastore.node.utils.stream; + +import com.google.common.base.Preconditions; +import org.opendaylight.controller.cluster.datastore.node.utils.QNameFactory; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; +import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode; +import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeAttrBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * NormalizedNodeInputStreamReader reads the byte stream and constructs the normalized node including its children nodes. + * This process goes in recursive manner, where each NodeTypes object signifies the start of the object, except END_NODE. + * If a node can have children, then that node's end is calculated based on appearance of END_NODE. + * + */ + +public class NormalizedNodeInputStreamReader implements NormalizedNodeStreamReader { + + private DataInputStream reader; + + private static final Logger LOG = LoggerFactory.getLogger(NormalizedNodeInputStreamReader.class); + + private Map codedStringMap = new HashMap<>(); + private static final String REVISION_ARG = "?revision="; + + public NormalizedNodeInputStreamReader(InputStream stream) throws IOException { + Preconditions.checkNotNull(stream); + reader = new DataInputStream(stream); + } + + + public NormalizedNode readNormalizedNode() throws IOException { + NormalizedNode node = null; + + // each node should start with a byte + byte nodeType = reader.readByte(); + + if(nodeType == NodeTypes.END_NODE) { + LOG.debug("End node reached. return"); + return null; + } + else if(nodeType == NodeTypes.AUGMENTATION_NODE) { + LOG.debug("Reading augmentation node. will create augmentation identifier"); + + YangInstanceIdentifier.AugmentationIdentifier identifier = + new YangInstanceIdentifier.AugmentationIdentifier(readQNameSet()); + DataContainerNodeBuilder augmentationBuilder = + Builders.augmentationBuilder().withNodeIdentifier(identifier); + augmentationBuilder = addDataContainerChildren(augmentationBuilder); + node = augmentationBuilder.build(); + + } else { + QName qName = readQName(); + + if(nodeType == NodeTypes.LEAF_SET_ENTRY_NODE) { + LOG.debug("Reading leaf set entry node. Will create NodeWithValue instance identifier"); + + // Read the object value + Object value = readObject(); + + YangInstanceIdentifier.NodeWithValue nodeWithValue = new YangInstanceIdentifier.NodeWithValue(qName, value); + node = Builders.leafSetEntryBuilder().withNodeIdentifier(nodeWithValue).withValue(value).build(); + + } else if(nodeType == NodeTypes.MAP_ENTRY_NODE) { + LOG.debug("Reading map entry node. Will create node identifier with predicates."); + + YangInstanceIdentifier.NodeIdentifierWithPredicates nodeIdentifier = + new YangInstanceIdentifier.NodeIdentifierWithPredicates(qName, readKeyValueMap()); + DataContainerNodeAttrBuilder mapEntryBuilder + = Builders.mapEntryBuilder().withNodeIdentifier(nodeIdentifier); + + mapEntryBuilder = (DataContainerNodeAttrBuilder)addDataContainerChildren(mapEntryBuilder); + node = mapEntryBuilder.build(); + + } else { + LOG.debug("Creating standard node identifier. "); + YangInstanceIdentifier.NodeIdentifier identifier = new YangInstanceIdentifier.NodeIdentifier(qName); + node = readNodeIdentifierDependentNode(nodeType, identifier); + + } + } + return node; + } + + private NormalizedNode readNodeIdentifierDependentNode(byte nodeType, YangInstanceIdentifier.NodeIdentifier identifier) + throws IOException { + + switch(nodeType) { + case NodeTypes.LEAF_NODE : + LOG.debug("Read leaf node"); + // Read the object value + NormalizedNodeAttrBuilder leafBuilder = Builders.leafBuilder(); + return leafBuilder.withNodeIdentifier(identifier).withValue(readObject()).build(); + + case NodeTypes.ANY_XML_NODE : + LOG.debug("Read xml node"); + Node value = (Node) readObject(); + return Builders.anyXmlBuilder().withValue(value).build(); + + case NodeTypes.MAP_NODE : + LOG.debug("Read map node"); + CollectionNodeBuilder mapBuilder = Builders.mapBuilder().withNodeIdentifier(identifier); + mapBuilder = addMapNodeChildren(mapBuilder); + return mapBuilder.build(); + + case NodeTypes.CHOICE_NODE : + LOG.debug("Read choice node"); + DataContainerNodeBuilder choiceBuilder = + Builders.choiceBuilder().withNodeIdentifier(identifier); + choiceBuilder = addDataContainerChildren(choiceBuilder); + return choiceBuilder.build(); + + case NodeTypes.ORDERED_MAP_NODE : + LOG.debug("Reading ordered map node"); + CollectionNodeBuilder orderedMapBuilder = + Builders.orderedMapBuilder().withNodeIdentifier(identifier); + orderedMapBuilder = addMapNodeChildren(orderedMapBuilder); + return orderedMapBuilder.build(); + + case NodeTypes.UNKEYED_LIST : + LOG.debug("Read unkeyed list node"); + CollectionNodeBuilder unkeyedListBuilder = + Builders.unkeyedListBuilder().withNodeIdentifier(identifier); + unkeyedListBuilder = addUnkeyedListChildren(unkeyedListBuilder); + return unkeyedListBuilder.build(); + + case NodeTypes.UNKEYED_LIST_ITEM : + LOG.debug("Read unkeyed list item node"); + DataContainerNodeAttrBuilder unkeyedListEntryBuilder + = Builders.unkeyedListEntryBuilder().withNodeIdentifier(identifier); + + unkeyedListEntryBuilder = (DataContainerNodeAttrBuilder) + addDataContainerChildren(unkeyedListEntryBuilder); + return unkeyedListEntryBuilder.build(); + + case NodeTypes.CONTAINER_NODE : + LOG.debug("Read container node"); + DataContainerNodeAttrBuilder containerBuilder = + Builders.containerBuilder().withNodeIdentifier(identifier); + + containerBuilder = (DataContainerNodeAttrBuilder) + addDataContainerChildren(containerBuilder); + return containerBuilder.build(); + + case NodeTypes.LEAF_SET : + LOG.debug("Read leaf set node"); + ListNodeBuilder> leafSetBuilder = + Builders.leafSetBuilder().withNodeIdentifier(identifier); + leafSetBuilder = addLeafSetChildren(leafSetBuilder); + return leafSetBuilder.build(); + + default : + return null; + } + } + + private QName readQName() throws IOException { + // Read in the same sequence of writing + String localName = readCodedString(); + String namespace = readCodedString(); + String revision = readCodedString(); + String qName; + // Not using stringbuilder as compiler optimizes string concatenation of + + if(revision != null){ + qName = "(" + namespace+ REVISION_ARG + revision + ")" +localName; + } else { + qName = "(" + namespace + ")" +localName; + } + + return QNameFactory.create(qName); + } + + + private String readCodedString() throws IOException { + boolean readFromMap = reader.readBoolean(); + if(readFromMap) { + return codedStringMap.get(reader.readInt()); + } else { + String value = reader.readUTF(); + if(value != null) { + codedStringMap.put(Integer.valueOf(codedStringMap.size()), value); + } + return value; + } + } + + private Set readQNameSet() throws IOException{ + // Read the children count + int count = reader.readInt(); + Set children = new HashSet<>(count); + for(int i = 0; i readKeyValueMap() throws IOException { + int count = reader.readInt(); + Map keyValueMap = new HashMap<>(count); + + for(int i = 0; i pathArguments = new ArrayList<>(size); + + for(int i=0; i readObjSet() throws IOException { + int count = reader.readInt(); + Set children = new HashSet<>(count); + for(int i = 0; i> addLeafSetChildren(ListNodeBuilder> builder) + throws IOException { + + LOG.debug("Reading children of leaf set"); + LeafSetEntryNode child = (LeafSetEntryNode)readNormalizedNode(); + + while(child != null) { + builder.withChild(child); + child = (LeafSetEntryNode)readNormalizedNode(); + } + return builder; + } + + private CollectionNodeBuilder addUnkeyedListChildren( + CollectionNodeBuilder builder) + throws IOException{ + + LOG.debug("Reading children of unkeyed list"); + UnkeyedListEntryNode child = (UnkeyedListEntryNode)readNormalizedNode(); + + while(child != null) { + builder.withChild(child); + child = (UnkeyedListEntryNode)readNormalizedNode(); + } + return builder; + } + + private DataContainerNodeBuilder addDataContainerChildren(DataContainerNodeBuilder builder) + throws IOException { + LOG.debug("Reading data container (leaf nodes) nodes"); + + DataContainerChild child = + (DataContainerChild) readNormalizedNode(); + + while(child != null) { + builder.withChild(child); + child = + (DataContainerChild) readNormalizedNode(); + } + return builder; + } + + + private CollectionNodeBuilder addMapNodeChildren(CollectionNodeBuilder builder) + throws IOException { + LOG.debug("Reading map node children"); + MapEntryNode child = (MapEntryNode)readNormalizedNode(); + + while(child != null){ + builder.withChild(child); + child = (MapEntryNode)readNormalizedNode(); + } + + return builder; + } + + + @Override + public void close() throws IOException { + reader.close(); + } + +} diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeOutputStreamWriter.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeOutputStreamWriter.java new file mode 100644 index 0000000000..05a47a0401 --- /dev/null +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeOutputStreamWriter.java @@ -0,0 +1,343 @@ +/* + * + * 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.cluster.datastore.node.utils.stream; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * NormalizedNodeOutputStreamWriter will be used by distributed datastore to send normalized node in + * a stream. + * A stream writer wrapper around this class will write node objects to stream in recursive manner. + * for example - If you have a ContainerNode which has a two LeafNode as children, then + * you will first call {@link #startContainerNode(YangInstanceIdentifier.NodeIdentifier, int)}, then will call + * {@link #leafNode(YangInstanceIdentifier.NodeIdentifier, Object)} twice and then, {@link #endNode()} to end + * container node. + * + * Based on the each node, the node type is also written to the stream, that helps in reconstructing the object, + * while reading. + * + * + */ + +public class NormalizedNodeOutputStreamWriter implements NormalizedNodeStreamWriter{ + + private DataOutputStream writer; + + private static final Logger LOG = LoggerFactory.getLogger(NormalizedNodeOutputStreamWriter.class); + + private Map stringCodeMap = new HashMap<>(); + + public NormalizedNodeOutputStreamWriter(OutputStream stream) throws IOException { + Preconditions.checkNotNull(stream); + writer = new DataOutputStream(stream); + } + + @Override + public void leafNode(YangInstanceIdentifier.NodeIdentifier name, Object value) throws IOException, IllegalArgumentException { + Preconditions.checkNotNull(name, "Node identifier should not be null"); + LOG.debug("Writing a new leaf node"); + startNode(name.getNodeType(), NodeTypes.LEAF_NODE); + + writeObject(value); + } + + @Override + public void startLeafSet(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException { + Preconditions.checkNotNull(name, "Node identifier should not be null"); + LOG.debug("Starting a new leaf set"); + + startNode(name.getNodeType(), NodeTypes.LEAF_SET); + } + + @Override + public void leafSetEntryNode(YangInstanceIdentifier.NodeWithValue name, Object value) throws IOException, IllegalArgumentException { + Preconditions.checkNotNull(name, "Node identifier should not be null"); + + LOG.debug("Writing a new leaf set entry node"); + startNode(name.getNodeType(), NodeTypes.LEAF_SET_ENTRY_NODE); + + writeObject(value); + } + + @Override + public void startContainerNode(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException { + Preconditions.checkNotNull(name, "Node identifier should not be null"); + + LOG.debug("Starting a new container node"); + + startNode(name.getNodeType(), NodeTypes.CONTAINER_NODE); + } + + @Override + public void startUnkeyedList(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException { + Preconditions.checkNotNull(name, "Node identifier should not be null"); + LOG.debug("Starting a new unkeyed list"); + + startNode(name.getNodeType(), NodeTypes.UNKEYED_LIST); + } + + @Override + public void startUnkeyedListItem(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) throws IOException, IllegalStateException { + Preconditions.checkNotNull(name, "Node identifier should not be null"); + LOG.debug("Starting a new unkeyed list item"); + + startNode(name.getNodeType(), NodeTypes.UNKEYED_LIST_ITEM); + } + + @Override + public void startMapNode(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException { + Preconditions.checkNotNull(name, "Node identifier should not be null"); + LOG.debug("Starting a new map node"); + + startNode(name.getNodeType(), NodeTypes.MAP_NODE); + } + + @Override + public void startMapEntryNode(YangInstanceIdentifier.NodeIdentifierWithPredicates identifier, int childSizeHint) throws IOException, IllegalArgumentException { + Preconditions.checkNotNull(identifier, "Node identifier should not be null"); + LOG.debug("Starting a new map entry node"); + startNode(identifier.getNodeType(), NodeTypes.MAP_ENTRY_NODE); + + writeKeyValueMap(identifier.getKeyValues()); + + } + + @Override + public void startOrderedMapNode(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException { + Preconditions.checkNotNull(name, "Node identifier should not be null"); + LOG.debug("Starting a new ordered map node"); + + startNode(name.getNodeType(), NodeTypes.ORDERED_MAP_NODE); + } + + @Override + public void startChoiceNode(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException { + Preconditions.checkNotNull(name, "Node identifier should not be null"); + LOG.debug("Starting a new choice node"); + + startNode(name.getNodeType(), NodeTypes.CHOICE_NODE); + } + + @Override + public void startAugmentationNode(YangInstanceIdentifier.AugmentationIdentifier identifier) throws IOException, IllegalArgumentException { + Preconditions.checkNotNull(identifier, "Node identifier should not be null"); + LOG.debug("Starting a new augmentation node"); + + writer.writeByte(NodeTypes.AUGMENTATION_NODE); + writeQNameSet(identifier.getPossibleChildNames()); + } + + @Override + public void anyxmlNode(YangInstanceIdentifier.NodeIdentifier name, Object value) throws IOException, IllegalArgumentException { + Preconditions.checkNotNull(name, "Node identifier should not be null"); + LOG.debug("Writing a new xml node"); + + startNode(name.getNodeType(), NodeTypes.ANY_XML_NODE); + + writeObject(value); + } + + @Override + public void endNode() throws IOException, IllegalStateException { + LOG.debug("Ending the node"); + + writer.writeByte(NodeTypes.END_NODE); + } + + @Override + public void close() throws IOException { + writer.close(); + } + + @Override + public void flush() throws IOException { + writer.flush(); + } + + private void startNode(final QName qName, byte nodeType) throws IOException { + + Preconditions.checkNotNull(qName, "QName of node identifier should not be null."); + // First write the type of node + writer.writeByte(nodeType); + // Write Start Tag + writeQName(qName); + } + + private void writeQName(QName qName) throws IOException { + + writeCodedString(qName.getLocalName()); + writeCodedString(qName.getNamespace().toString()); + writeCodedString(qName.getFormattedRevision()); + } + + private void writeCodedString(String key) throws IOException { + Integer value = stringCodeMap.get(key); + + if(value != null) { + writer.writeBoolean(true); + writer.writeInt(value); + } else { + if(key != null) { + stringCodeMap.put(key, Integer.valueOf(stringCodeMap.size())); + } + writer.writeBoolean(false); + writer.writeUTF(key); + } + } + + private void writeObjSet(Set set) throws IOException { + if(!set.isEmpty()){ + writer.writeInt(set.size()); + for(Object o : set){ + if(o instanceof String){ + writeCodedString(o.toString()); + } else { + throw new IllegalArgumentException("Expected value type to be String but was : " + + o.toString()); + } + } + } else { + writer.writeInt(0); + } + } + + private void writeYangInstanceIdentifier(YangInstanceIdentifier identifier) throws IOException { + Iterable pathArguments = identifier.getPathArguments(); + int size = Iterables.size(pathArguments); + writer.writeInt(size); + + for(YangInstanceIdentifier.PathArgument pathArgument : pathArguments) { + writePathArgument(pathArgument); + } + } + + private void writePathArgument(YangInstanceIdentifier.PathArgument pathArgument) throws IOException { + + byte type = PathArgumentTypes.getSerializablePathArgumentType(pathArgument); + + writer.writeByte(type); + + switch(type) { + case PathArgumentTypes.NODE_IDENTIFIER : + + YangInstanceIdentifier.NodeIdentifier nodeIdentifier = + (YangInstanceIdentifier.NodeIdentifier) pathArgument; + + writeQName(nodeIdentifier.getNodeType()); + break; + + case PathArgumentTypes.NODE_IDENTIFIER_WITH_PREDICATES: + + YangInstanceIdentifier.NodeIdentifierWithPredicates nodeIdentifierWithPredicates = + (YangInstanceIdentifier.NodeIdentifierWithPredicates) pathArgument; + writeQName(nodeIdentifierWithPredicates.getNodeType()); + + writeKeyValueMap(nodeIdentifierWithPredicates.getKeyValues()); + break; + + case PathArgumentTypes.NODE_IDENTIFIER_WITH_VALUE : + + YangInstanceIdentifier.NodeWithValue nodeWithValue = + (YangInstanceIdentifier.NodeWithValue) pathArgument; + + writeQName(nodeWithValue.getNodeType()); + writeObject(nodeWithValue.getValue()); + break; + + case PathArgumentTypes.AUGMENTATION_IDENTIFIER : + + YangInstanceIdentifier.AugmentationIdentifier augmentationIdentifier = + (YangInstanceIdentifier.AugmentationIdentifier) pathArgument; + + // No Qname in augmentation identifier + writeQNameSet(augmentationIdentifier.getPossibleChildNames()); + break; + default : + throw new IllegalStateException("Unknown node identifier type is found : " + pathArgument.getClass().toString() ); + } + } + + private void writeKeyValueMap(Map keyValueMap) throws IOException { + if(keyValueMap != null && !keyValueMap.isEmpty()) { + writer.writeInt(keyValueMap.size()); + Set qNameSet = keyValueMap.keySet(); + + for(QName qName : qNameSet) { + writeQName(qName); + writeObject(keyValueMap.get(qName)); + } + } else { + writer.writeInt(0); + } + } + + private void writeQNameSet(Set children) throws IOException { + // Write each child's qname separately, if list is empty send count as 0 + if(children != null && !children.isEmpty()) { + writer.writeInt(children.size()); + for(QName qName : children) { + writeQName(qName); + } + } else { + LOG.debug("augmentation node does not have any child"); + writer.writeInt(0); + } + } + + private void writeObject(Object value) throws IOException { + + byte type = ValueTypes.getSerializableType(value); + // Write object type first + writer.writeByte(type); + + switch(type) { + case ValueTypes.BOOL_TYPE: + writer.writeBoolean((Boolean) value); + break; + case ValueTypes.QNAME_TYPE: + writeQName((QName) value); + break; + case ValueTypes.INT_TYPE: + writer.writeInt((Integer) value); + break; + case ValueTypes.BYTE_TYPE: + writer.writeByte((Byte) value); + break; + case ValueTypes.LONG_TYPE: + writer.writeLong((Long) value); + break; + case ValueTypes.SHORT_TYPE: + writer.writeShort((Short) value); + break; + case ValueTypes.BITS_TYPE: + writeObjSet((Set) value); + break; + case ValueTypes.YANG_IDENTIFIER_TYPE: + writeYangInstanceIdentifier((YangInstanceIdentifier) value); + break; + default: + writer.writeUTF(value.toString()); + break; + } + } +} diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeStreamReader.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeStreamReader.java new file mode 100644 index 0000000000..c619afd7ee --- /dev/null +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeStreamReader.java @@ -0,0 +1,23 @@ +/* + * + * 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.cluster.datastore.node.utils.stream; + + +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +import java.io.IOException; + + +public interface NormalizedNodeStreamReader extends AutoCloseable { + + NormalizedNode readNormalizedNode() throws IOException; +} diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeStreamWriter.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeStreamWriter.java new file mode 100644 index 0000000000..af95b61423 --- /dev/null +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeStreamWriter.java @@ -0,0 +1,219 @@ + +/* + * 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.cluster.datastore.node.utils.stream; + +import java.io.Closeable; +import java.io.Flushable; +import java.io.IOException; + +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; + +/** + * Event Stream Writer based on Normalized Node tree representation + * + *

Writing Event Stream

+ * + *
    + *
  • container - Container node representation, start event is + * emitted using {@link #startContainerNode(YangInstanceIdentifier.NodeIdentifier, int)} + * and node end event is + * emitted using {@link #endNode()}. Container node is implementing + * {@link org.opendaylight.yangtools.yang.binding.DataObject} interface. + * + *
  • list - YANG list statement has two representation in event + * stream - unkeyed list and map. Unkeyed list is YANG list which did not + * specify key.
  • + * + *
      + *
    • Map - Map start event is emitted using + * {@link #startMapNode(YangInstanceIdentifier.NodeIdentifier, int)} + * and is ended using {@link #endNode()}. Each map entry start is emitted using + * {@link #startMapEntryNode(YangInstanceIdentifier.NodeIdentifierWithPredicates, int)} + * with Map of keys + * and finished using {@link #endNode()}.
    • + * + *
    • UnkeyedList - Unkeyed list represent list without keys, + * unkeyed list start is emitted using + * {@link #startUnkeyedList(YangInstanceIdentifier.NodeIdentifier, int)} list + * end is emitted using {@link #endNode()}. Each list item is emitted using + * {@link #startUnkeyedListItem(YangInstanceIdentifier.NodeIdentifier, int)} + * and ended using {@link #endNode()}.
    • + *
    + * + *
  • leaf - Leaf node event is emitted using + * {@link #leafNode(YangInstanceIdentifier.NodeIdentifier, Object)}. + * {@link #endNode()} MUST NOT BE emitted for + * leaf node.
  • + * + *
  • leaf-list - Leaf list start is emitted using + * {@link #startLeafSet(YangInstanceIdentifier.NodeIdentifier, int)}. + * Leaf list end is emitted using + * {@link #endNode()}. Leaf list entries are emitted using + * {@link #leafSetEntryNode(YangInstanceIdentifier.NodeWithValue name, Object). + * + *
  • anyxml - Anyxml node event is emitted using + * {@link #leafNode(YangInstanceIdentifier.NodeIdentifier, Object)}. {@link #endNode()} MUST NOT BE emitted + * for anyxml node.
  • + * + * + *
  • choice Choice node event is emmited by + * {@link #startChoiceNode(YangInstanceIdentifier.NodeIdentifier, int)} event and + * finished by invoking {@link #endNode()} + *
  • + * augment - Represents augmentation, augmentation node is started + * by invoking {@link #startAugmentationNode(YangInstanceIdentifier.AugmentationIdentifier)} and + * finished by invoking {@link #endNode()}.
  • + * + *
+ * + *

Implementation notes

+ * + *

+ * Implementations of this interface must not hold user suppled objects + * and resources needlessly. + * + */ + +public interface NormalizedNodeStreamWriter extends Closeable, Flushable { + + public final int UNKNOWN_SIZE = -1; + + /** + * Write the leaf node identifier and value to the stream. + * @param name + * @param value + * @throws IOException + * @throws IllegalArgumentException + */ + void leafNode(YangInstanceIdentifier.NodeIdentifier name, Object value) + throws IOException, IllegalArgumentException; + + /** + * Start writing leaf Set node. You must call {@link #endNode()} once you are done writing all of its children. + * @param name + * @param childSizeHint is the estimated children count. Usage is optional in implementation. + * @throws IOException + * @throws IllegalArgumentException + */ + void startLeafSet(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) + throws IOException, IllegalArgumentException; + + /** + * Write the leaf Set Entry Node object to the stream with identifier and value. + * @param name + * @param value + * @throws IOException + * @throws IllegalArgumentException + */ + void leafSetEntryNode(YangInstanceIdentifier.NodeWithValue name, Object value) + throws IOException, IllegalArgumentException; + + /** + * Start writing container node. You must call {@link #endNode()} once you are done writing all of its children. + * @param name + * @param childSizeHint is the estimated children count. Usage is optional in implementation. + * @throws IOException + * @throws IllegalArgumentException + */ + void startContainerNode(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) + throws IOException, IllegalArgumentException; + + /** + * Start writing unkeyed list node. You must call {@link #endNode()} once you are done writing all of its children. + * @param name + * @param childSizeHint is the estimated children count. Usage is optional in implementation. + * @throws IOException + * @throws IllegalArgumentException + */ + void startUnkeyedList(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) + throws IOException, IllegalArgumentException; + + /** + * Start writing unkeyed list item. You must call {@link #endNode()} once you are done writing all of its children. + * @param name + * @param childSizeHint is the estimated children count. Usage is optional in implementation. + * @throws IOException + * @throws IllegalStateException + */ + void startUnkeyedListItem(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) + throws IOException, IllegalStateException; + + /** + * Start writing map node. You must call {@link #endNode()} once you are done writing all of its children. + * @param name + * @param childSizeHint is the estimated children count. Usage is optional in implementation. + * @throws IOException + * @throws IllegalArgumentException + */ + void startMapNode(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) + throws IOException, IllegalArgumentException; + + /** + * Start writing map entry node. You must call {@link #endNode()} once you are done writing all of its children. + * @param identifier + * @param childSizeHint is the estimated children count. Usage is optional in implementation. + * @throws IOException + * @throws IllegalArgumentException + */ + void startMapEntryNode(YangInstanceIdentifier.NodeIdentifierWithPredicates identifier, int childSizeHint) + throws IOException, IllegalArgumentException; + + /** + * Start writing ordered map node. You must call {@link #endNode()} once you are done writing all of its children. + * @param name + * @param childSizeHint is the estimated children count. Usage is optional in implementation. + * @throws IOException + * @throws IllegalArgumentException + */ + void startOrderedMapNode(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) + throws IOException, IllegalArgumentException; + + /** + * Start writing choice node. You must call {@link #endNode()} once you are done writing all of its children. + * @param name + * @param childSizeHint is the estimated children count. Usage is optional in implementation. + * @throws IOException + * @throws IllegalArgumentException + */ + void startChoiceNode(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) + throws IOException, IllegalArgumentException; + + /** + * Start writing augmentation node. You must call {@link #endNode()} once you are done writing all of its children. + * @param identifier + * @throws IOException + * @throws IllegalArgumentException + */ + void startAugmentationNode(YangInstanceIdentifier.AugmentationIdentifier identifier) + throws IOException, IllegalArgumentException; + + /** + * Write any xml node identifier and value to the stream + * @param name + * @param value + * @throws IOException + * @throws IllegalArgumentException + */ + void anyxmlNode(YangInstanceIdentifier.NodeIdentifier name, Object value) + throws IOException, IllegalArgumentException; + + /** + * This method should be used to add end symbol/identifier of node in the stream. + * @throws IOException + * @throws IllegalStateException + */ + void endNode() throws IOException, IllegalStateException; + + @Override + void close() throws IOException; + + @Override + void flush() throws IOException; +} diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/PathArgumentTypes.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/PathArgumentTypes.java new file mode 100644 index 0000000000..b01beb8c77 --- /dev/null +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/PathArgumentTypes.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.cluster.datastore.node.utils.stream; + +import com.google.common.collect.ImmutableMap; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; + +import java.util.Map; + +public class PathArgumentTypes { + public static final byte AUGMENTATION_IDENTIFIER = 1; + public static final byte NODE_IDENTIFIER = 2; + public static final byte NODE_IDENTIFIER_WITH_VALUE = 3; + public static final byte NODE_IDENTIFIER_WITH_PREDICATES = 4; + + private static Map, Byte> CLASS_TO_ENUM_MAP = + ImmutableMap., Byte>builder(). + put(YangInstanceIdentifier.AugmentationIdentifier.class, AUGMENTATION_IDENTIFIER). + put(YangInstanceIdentifier.NodeIdentifier.class, NODE_IDENTIFIER). + put(YangInstanceIdentifier.NodeIdentifierWithPredicates.class, NODE_IDENTIFIER_WITH_PREDICATES). + put(YangInstanceIdentifier.NodeWithValue.class, NODE_IDENTIFIER_WITH_VALUE).build(); + + public static byte getSerializablePathArgumentType(YangInstanceIdentifier.PathArgument pathArgument){ + + Byte type = CLASS_TO_ENUM_MAP.get(pathArgument.getClass()); + if(type == null) { + throw new IllegalArgumentException("Unknown type of PathArgument = " + pathArgument); + } + + return type; + } + +} diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/ValueTypes.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/ValueTypes.java new file mode 100644 index 0000000000..6035e3c644 --- /dev/null +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/ValueTypes.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.cluster.datastore.node.utils.stream; + +import com.google.common.base.Preconditions; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class ValueTypes { + public static final byte SHORT_TYPE = 1; + public static final byte BYTE_TYPE = 2; + public static final byte INT_TYPE = 3; + public static final byte LONG_TYPE = 4; + public static final byte BOOL_TYPE = 5; + public static final byte QNAME_TYPE = 6; + public static final byte BITS_TYPE = 7; + public static final byte YANG_IDENTIFIER_TYPE = 8; + public static final byte STRING_TYPE = 9; + public static final byte BIG_INTEGER_TYPE = 10; + public static final byte BIG_DECIMAL_TYPE = 11; + + private static Map types = new HashMap<>(); + + static { + types.put(String.class, Byte.valueOf(STRING_TYPE)); + types.put(Byte.class, Byte.valueOf(BYTE_TYPE)); + types.put(Integer.class, Byte.valueOf(INT_TYPE)); + types.put(Long.class, Byte.valueOf(LONG_TYPE)); + types.put(Boolean.class, Byte.valueOf(BOOL_TYPE)); + types.put(QName.class, Byte.valueOf(QNAME_TYPE)); + types.put(Set.class, Byte.valueOf(BITS_TYPE)); + types.put(YangInstanceIdentifier.class, Byte.valueOf(YANG_IDENTIFIER_TYPE)); + types.put(Short.class, Byte.valueOf(SHORT_TYPE)); + types.put(BigInteger.class, Byte.valueOf(BIG_INTEGER_TYPE)); + types.put(BigDecimal.class, Byte.valueOf(BIG_DECIMAL_TYPE)); + } + + public static final byte getSerializableType(Object node){ + Preconditions.checkNotNull(node, "node should not be null"); + + Byte type = types.get(node.getClass()); + if(type != null) { + return type; + } else if(node instanceof Set){ + return BITS_TYPE; + } + + throw new IllegalArgumentException("Unknown value type " + node.getClass().getSimpleName()); + } +} diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/util/EncoderDecoderUtil.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/util/EncoderDecoderUtil.java deleted file mode 100644 index 90f5cf3396..0000000000 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/util/EncoderDecoderUtil.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * - * 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.cluster.datastore.util; - -import com.google.common.base.Preconditions; -import com.google.common.collect.Lists; -import org.opendaylight.controller.protobuff.messages.common.SimpleNormalizedNodeMessage; -import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; -import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; -import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; -import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; -import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; -import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; -import org.opendaylight.yangtools.yang.data.api.schema.MapNode; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlDocumentUtils; -import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.DomUtils; -import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory; -import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.serializer.DomFromNormalizedNodeSerializerFactory; -import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; -import org.opendaylight.yangtools.yang.model.api.ChoiceNode; -import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; -import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; -import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; -import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; -import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; -import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; -import org.opendaylight.yangtools.yang.model.api.Module; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.TransformerFactoryConfigurationError; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; -import java.io.ByteArrayInputStream; -import java.io.StringWriter; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -/* - * - * EncoderDecoderUtil helps in wrapping the NormalizedNode into a SimpleNormalizedNode - * protobuf message containing the XML representation of the NormalizeNode - * - * @author: syedbahm - */ -public class EncoderDecoderUtil { - static DocumentBuilderFactory factory; - - private static DomFromNormalizedNodeSerializerFactory serializerFactory = - DomFromNormalizedNodeSerializerFactory - .getInstance(XmlDocumentUtils.getDocument(), - DomUtils.defaultValueCodecProvider()); - - private static DomToNormalizedNodeParserFactory parserFactory = - DomToNormalizedNodeParserFactory - .getInstance(DomUtils.defaultValueCodecProvider()); - - static { - factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - factory.setCoalescing(true); - factory.setIgnoringElementContentWhitespace(true); - factory.setIgnoringComments(true); - } - - private static DataSchemaNode findChildNode(Collection children, - String name) { - List containers = Lists.newArrayList(); - - for (DataSchemaNode dataSchemaNode : children) { - if (dataSchemaNode.getQName().getLocalName().equals(name)) - return dataSchemaNode; - if (dataSchemaNode instanceof DataNodeContainer) { - containers.add((DataNodeContainer) dataSchemaNode); - } else if (dataSchemaNode instanceof ChoiceNode) { - containers.addAll(((ChoiceNode) dataSchemaNode).getCases()); - } - } - - for (DataNodeContainer container : containers) { - DataSchemaNode retVal = - findChildNode(container.getChildNodes(), name); - if (retVal != null) { - return retVal; - } - } - - return null; - } - - private static DataSchemaNode getSchemaNode(SchemaContext context, - QName qname) { - - for (Module module : context - .findModuleByNamespace(qname.getNamespace())) { - // we will take the first child as the start of the - if (module.getChildNodes() != null || !module.getChildNodes() - .isEmpty()) { - - DataSchemaNode found = - findChildNode(module.getChildNodes(), qname.getLocalName()); - return found; - } - } - return null; - } - - private static String toString(Element xml) { - try { - Transformer transformer = - TransformerFactory.newInstance().newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - - StreamResult result = new StreamResult(new StringWriter()); - DOMSource source = new DOMSource(xml); - transformer.transform(source, result); - - return result.getWriter().toString(); - } catch (IllegalArgumentException | TransformerFactoryConfigurationError - | TransformerException e) { - throw new RuntimeException("Unable to serialize xml element " + xml, - e); - } - } - - private static String toString(Iterable xmlIterable) { - try { - Transformer transformer = - TransformerFactory.newInstance().newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - - StreamResult result = new StreamResult(new StringWriter()); - Iterator iterator = xmlIterable.iterator(); - DOMSource source; - if(iterator.hasNext()) { - source = new DOMSource((org.w3c.dom.Node) iterator.next()); - transformer.transform(source, result); - transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); - } - - while(iterator.hasNext()) { - source = new DOMSource((org.w3c.dom.Node) iterator.next()); - transformer.transform(source, result); - } - System.out.println(result.getWriter().toString()); - return result.getWriter().toString(); - } catch (IllegalArgumentException | TransformerFactoryConfigurationError - | TransformerException e) { - throw new RuntimeException("Unable to serialize xml element(s) " + xmlIterable.toString(), - e); - } - } - - private static Iterable serialize(DataSchemaNode schemaNode, NormalizedNode normalizedNode){ - if(schemaNode instanceof ContainerSchemaNode){ //1 - return serializerFactory - .getContainerNodeSerializer() - .serialize((ContainerSchemaNode) schemaNode, - (ContainerNode) normalizedNode); - } else if(schemaNode instanceof ChoiceNode){ //2 - return serializerFactory - .getChoiceNodeSerializer() - .serialize((ChoiceNode) schemaNode, - (org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode) normalizedNode); - } else if(schemaNode instanceof LeafSchemaNode){ //3 - return serializerFactory - .getLeafNodeSerializer() - .serialize((LeafSchemaNode) schemaNode, (LeafNode) normalizedNode); - } else if(schemaNode instanceof ListSchemaNode){ //4 - return serializerFactory - .getMapNodeSerializer() - .serialize((ListSchemaNode) schemaNode, (MapNode) normalizedNode); - } else if(schemaNode instanceof LeafListSchemaNode){ //5 - return serializerFactory - .getLeafSetNodeSerializer() - .serialize((LeafListSchemaNode) schemaNode, (LeafSetNode) normalizedNode); - } else if(schemaNode instanceof AugmentationSchema){//6 - return serializerFactory - .getAugmentationNodeSerializer() - .serialize((AugmentationSchema) schemaNode, (AugmentationNode) normalizedNode); - } else if(schemaNode instanceof ListSchemaNode && normalizedNode instanceof LeafSetEntryNode){ //7 - return serializerFactory - .getLeafSetEntryNodeSerializer() - .serialize((LeafListSchemaNode) schemaNode, (LeafSetEntryNode) normalizedNode); - } else if(schemaNode instanceof ListSchemaNode){ //8 - return serializerFactory - .getMapEntryNodeSerializer() - .serialize((ListSchemaNode) schemaNode, (MapEntryNode) normalizedNode); - } - - - - throw new UnsupportedOperationException(schemaNode.getClass().toString()); - } - - private static NormalizedNode parse(Document doc, DataSchemaNode schemaNode){ - if(schemaNode instanceof ContainerSchemaNode){ - return parserFactory - .getContainerNodeParser() - .parse(Collections.singletonList(doc.getDocumentElement()), - (ContainerSchemaNode) schemaNode); - - } else if(schemaNode instanceof ChoiceNode){ - return parserFactory - .getChoiceNodeParser() - .parse(Collections.singletonList(doc.getDocumentElement()), - (ChoiceNode) schemaNode); - } else if(schemaNode instanceof LeafNode){ - return parserFactory - .getLeafNodeParser() - .parse(Collections.singletonList(doc.getDocumentElement()), - (LeafSchemaNode) schemaNode); - } else if(schemaNode instanceof ListSchemaNode){ - return parserFactory - .getMapNodeParser() - .parse(Collections.singletonList(doc.getDocumentElement()), - (ListSchemaNode) schemaNode); - } else if(schemaNode instanceof LeafListSchemaNode){ - return parserFactory - .getLeafSetNodeParser() - .parse(Collections.singletonList(doc.getDocumentElement()), - (LeafListSchemaNode) schemaNode); - } else if(schemaNode instanceof AugmentationSchema){ - return parserFactory - .getAugmentationNodeParser() - .parse(Collections.singletonList(doc.getDocumentElement()), - (AugmentationSchema) schemaNode); - } else if(schemaNode instanceof ListSchemaNode){ - return parserFactory - .getMapEntryNodeParser() - .parse(Collections.singletonList(doc.getDocumentElement()), - (ListSchemaNode) schemaNode); - - } - - throw new UnsupportedOperationException(schemaNode.getClass().toString()); - } - - - /** - * Helps in generation of NormalizedNodeXml message for the supplied NormalizedNode - * - * @param sc --SchemaContext - * @param normalizedNode -- Normalized Node to be encoded - * @return SimpleNormalizedNodeMessage.NormalizedNodeXml - */ - public static SimpleNormalizedNodeMessage.NormalizedNodeXml encode( - SchemaContext sc, NormalizedNode normalizedNode) { - - Preconditions.checkArgument(sc != null, "Schema context found null"); - - Preconditions.checkArgument(normalizedNode != null, - "normalized node found null"); - - DataSchemaNode schemaNode = getSchemaNode(sc, - normalizedNode.getIdentifier() - .getNodeType() - ); - - Preconditions.checkState(schemaNode != null, - "Couldn't find schema node for " + normalizedNode.getIdentifier()); - - Iterable els = serialize(schemaNode, normalizedNode); - - String xmlString = toString(els.iterator().next()); - SimpleNormalizedNodeMessage.NormalizedNodeXml.Builder builder = - SimpleNormalizedNodeMessage.NormalizedNodeXml.newBuilder(); - builder.setXmlString(xmlString); - builder - .setNodeIdentifier(normalizedNode.getIdentifier() - .getNodeType().toString()); - return builder.build(); - - } - - /** - * Utilizes the SimpleNormalizedNodeMessage.NormalizedNodeXml to convert into NormalizedNode - * - * @param sc -- schema context - * @param normalizedNodeXml -- containing the normalized Node XML - * @return NormalizedNode return - * @throws Exception - */ - - public static NormalizedNode decode(SchemaContext sc, - SimpleNormalizedNodeMessage.NormalizedNodeXml normalizedNodeXml) - throws Exception { - - Preconditions - .checkArgument(sc != null, "schema context seems to be null"); - - Preconditions.checkArgument(normalizedNodeXml != null, - "SimpleNormalizedNodeMessage.NormalizedNodeXml found to be null"); - QName qname = QName.create(normalizedNodeXml.getNodeIdentifier()); - - // here we will try to get back the NormalizedNode - DataSchemaNode schemaNode = getSchemaNode(sc, qname); - - // now we need to read the XML - Document doc = - factory.newDocumentBuilder().parse( - new ByteArrayInputStream( - normalizedNodeXml.getXmlString().getBytes( - "utf-8")) - ); - - doc.getDocumentElement().normalize(); - - - return parse(doc, schemaNode); - } - - - -} diff --git a/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeStreamReaderWriterTest.java b/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeStreamReaderWriterTest.java new file mode 100644 index 0000000000..052f609e92 --- /dev/null +++ b/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeStreamReaderWriterTest.java @@ -0,0 +1,65 @@ +/* + * + * 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.cluster.datastore.node.utils.stream; + + +import org.apache.commons.lang.SerializationUtils; +import org.junit.Assert; +import org.junit.Test; +import org.opendaylight.controller.cluster.datastore.util.TestModel; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import static org.junit.Assert.fail; + +public class NormalizedNodeStreamReaderWriterTest { + + final NormalizedNode input = TestModel.createTestContainer(); + + @Test + public void testNormalizedNodeStreamReaderWriter() { + + byte[] byteData = null; + + try(ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + NormalizedNodeStreamWriter writer = new NormalizedNodeOutputStreamWriter(byteArrayOutputStream)) { + + NormalizedNodeWriter normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter(writer); + normalizedNodeWriter.write(input); + byteData = byteArrayOutputStream.toByteArray(); + + } catch (IOException e) { + fail("Writing to OutputStream failed :" + e.toString()); + } + + try(NormalizedNodeInputStreamReader reader = new NormalizedNodeInputStreamReader(new ByteArrayInputStream(byteData))) { + + NormalizedNode node = reader.readNormalizedNode(); + Assert.assertEquals(input, node); + + } catch (IOException e) { + fail("Reading from InputStream failed :" + e.toString()); + } + } + + @Test + public void testWithSerializable() { + SampleNormalizedNodeSerializable serializable = new SampleNormalizedNodeSerializable(input); + SampleNormalizedNodeSerializable clone = (SampleNormalizedNodeSerializable)SerializationUtils.clone(serializable); + + Assert.assertEquals(input, clone.getInput()); + + } + +} diff --git a/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeWriter.java b/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeWriter.java new file mode 100644 index 0000000000..845038e3fd --- /dev/null +++ b/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeWriter.java @@ -0,0 +1,190 @@ +/* + * 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.cluster.datastore.node.utils.stream; + +import com.google.common.base.Preconditions; +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; +import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; +import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode; +import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode; + +import java.io.Closeable; +import java.io.Flushable; +import java.io.IOException; +import java.util.Collection; + +import static org.opendaylight.controller.cluster.datastore.node.utils.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE; + + +/** + * This class is used only for testing purpose for now, we may use similar logic while integrating + * with cluster + */ + +public class NormalizedNodeWriter implements Closeable, Flushable { + private final NormalizedNodeStreamWriter writer; + + private NormalizedNodeWriter(final NormalizedNodeStreamWriter writer) { + this.writer = Preconditions.checkNotNull(writer); + } + + protected final NormalizedNodeStreamWriter getWriter() { + return writer; + } + + /** + * Create a new writer backed by a {@link org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter}. + * + * @param writer Back-end writer + * @return A new instance. + */ + public static NormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer) { + return new NormalizedNodeWriter(writer); + } + + + /** + * Iterate over the provided {@link org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode} and emit write + * events to the encapsulated {@link org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter}. + * + * @param node Node + * @return + * @throws java.io.IOException when thrown from the backing writer. + */ + public final NormalizedNodeWriter write(final NormalizedNode node) throws IOException { + if (wasProcessedAsComplexNode(node)) { + return this; + } + + if (wasProcessAsSimpleNode(node)) { + return this; + } + + throw new IllegalStateException("It wasn't possible to serialize node " + node); + } + + @Override + public void flush() throws IOException { + writer.flush(); + } + + @Override + public void close() throws IOException { + writer.flush(); + writer.close(); + } + + /** + * Emit a best guess of a hint for a particular set of children. It evaluates the + * iterable to see if the size can be easily gotten to. If it is, we hint at the + * real number of child nodes. Otherwise we emit UNKNOWN_SIZE. + * + * @param children Child nodes + * @return Best estimate of the collection size required to hold all the children. + */ + static final int childSizeHint(final Iterable children) { + return (children instanceof Collection) ? ((Collection) children).size() : UNKNOWN_SIZE; + } + + private boolean wasProcessAsSimpleNode(final NormalizedNode node) throws IOException { + if (node instanceof LeafSetEntryNode) { + final LeafSetEntryNode nodeAsLeafList = (LeafSetEntryNode)node; + writer.leafSetEntryNode(nodeAsLeafList.getIdentifier(), nodeAsLeafList.getValue()); + return true; + } else if (node instanceof LeafNode) { + final LeafNode nodeAsLeaf = (LeafNode)node; + writer.leafNode(nodeAsLeaf.getIdentifier(), nodeAsLeaf.getValue()); + return true; + } else if (node instanceof AnyXmlNode) { + final AnyXmlNode anyXmlNode = (AnyXmlNode)node; + writer.anyxmlNode(anyXmlNode.getIdentifier(), anyXmlNode.getValue()); + return true; + } + + return false; + } + + /** + * Emit events for all children and then emit an endNode() event. + * + * @param children Child iterable + * @return True + * @throws java.io.IOException when the writer reports it + */ + protected final boolean writeChildren(final Iterable> children) throws IOException { + for (NormalizedNode child : children) { + write(child); + } + + writer.endNode(); + return true; + } + + protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException { + writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue())); + return writeChildren(node.getValue()); + } + + private boolean wasProcessedAsComplexNode(final NormalizedNode node) throws IOException { + if (node instanceof ContainerNode) { + final ContainerNode n = (ContainerNode) node; + writer.startContainerNode(n.getIdentifier(), childSizeHint(n.getValue())); + return writeChildren(n.getValue()); + } + if (node instanceof MapEntryNode) { + return writeMapEntryNode((MapEntryNode) node); + } + if (node instanceof UnkeyedListEntryNode) { + final UnkeyedListEntryNode n = (UnkeyedListEntryNode) node; + writer.startUnkeyedListItem(n.getIdentifier(), childSizeHint(n.getValue())); + return writeChildren(n.getValue()); + } + if (node instanceof ChoiceNode) { + final ChoiceNode n = (ChoiceNode) node; + writer.startChoiceNode(n.getIdentifier(), childSizeHint(n.getValue())); + return writeChildren(n.getValue()); + } + if (node instanceof AugmentationNode) { + final AugmentationNode n = (AugmentationNode) node; + writer.startAugmentationNode(n.getIdentifier()); + return writeChildren(n.getValue()); + } + if (node instanceof UnkeyedListNode) { + final UnkeyedListNode n = (UnkeyedListNode) node; + writer.startUnkeyedList(n.getIdentifier(), childSizeHint(n.getValue())); + return writeChildren(n.getValue()); + } + if (node instanceof OrderedMapNode) { + final OrderedMapNode n = (OrderedMapNode) node; + writer.startOrderedMapNode(n.getIdentifier(), childSizeHint(n.getValue())); + return writeChildren(n.getValue()); + } + if (node instanceof MapNode) { + final MapNode n = (MapNode) node; + writer.startMapNode(n.getIdentifier(), childSizeHint(n.getValue())); + return writeChildren(n.getValue()); + } + if (node instanceof LeafSetNode) { + //covers also OrderedLeafSetNode for which doesn't exist start* method + final LeafSetNode n = (LeafSetNode) node; + writer.startLeafSet(n.getIdentifier(), childSizeHint(n.getValue())); + return writeChildren(n.getValue()); + } + + return false; + } +} diff --git a/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/SampleNormalizedNodeSerializable.java b/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/SampleNormalizedNodeSerializable.java new file mode 100644 index 0000000000..33d48a5278 --- /dev/null +++ b/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/SampleNormalizedNodeSerializable.java @@ -0,0 +1,44 @@ +/* + * 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.cluster.datastore.node.utils.stream; + + +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.net.URISyntaxException; + +public class SampleNormalizedNodeSerializable implements Serializable { + + private NormalizedNode input; + + public SampleNormalizedNodeSerializable(NormalizedNode input) { + this.input = input; + } + + public NormalizedNode getInput() { + return input; + } + + private void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException, URISyntaxException { + NormalizedNodeStreamReader reader = new NormalizedNodeInputStreamReader(stream); + this.input = reader.readNormalizedNode(); + } + + private void writeObject(final ObjectOutputStream stream) throws IOException { + NormalizedNodeStreamWriter writer = new NormalizedNodeOutputStreamWriter(stream); + NormalizedNodeWriter normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter(writer); + + normalizedNodeWriter.write(this.input); + } + +} diff --git a/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/util/NormalizedNodeXmlConverterTest.java b/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/util/NormalizedNodeXmlConverterTest.java deleted file mode 100644 index 853b3e264b..0000000000 --- a/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/util/NormalizedNodeXmlConverterTest.java +++ /dev/null @@ -1,483 +0,0 @@ -/* - * - * 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.cluster.datastore.util; - -import com.google.common.base.Preconditions; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import org.custommonkey.xmlunit.Diff; -import org.custommonkey.xmlunit.XMLUnit; -import org.junit.Test; -import org.opendaylight.controller.protobuff.messages.common.SimpleNormalizedNodeMessage; -import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; -import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; -import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; -import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; -import org.opendaylight.yangtools.yang.data.api.schema.MapNode; -import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlDocumentUtils; -import org.opendaylight.yangtools.yang.data.impl.schema.Builders; -import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder; -import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder; -import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder; -import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeBuilder; -import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.DomUtils; -import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory; -import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.serializer.DomFromNormalizedNodeSerializerFactory; -import org.opendaylight.yangtools.yang.model.api.ChoiceNode; -import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; -import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; -import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; -import org.opendaylight.yangtools.yang.model.api.Module; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.TransformerFactoryConfigurationError; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.StringWriter; -import java.net.URI; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Set; - - -/** - * Two of the testcases in the yangtools/yang-data-impl are leveraged (with modification) to create - * the serialization of NormalizedNode using the ProtocolBuffer - * - * @syedbahm - * - */ - - -public class NormalizedNodeXmlConverterTest { - private static final Logger logger = LoggerFactory - .getLogger(NormalizedNodeXmlConverterTest.class); - public static final String NAMESPACE = - "urn:opendaylight:params:xml:ns:yang:controller:test"; - private static Date revision; - private ContainerNode expectedNode; - private ContainerSchemaNode containerNode; - private String xmlPath; - - static { - try { - revision = new SimpleDateFormat("yyyy-MM-dd").parse("2014-03-13"); - } catch (ParseException e) { - throw new RuntimeException(e); - } - } - - public static DataSchemaNode getSchemaNode(final SchemaContext context, - final String moduleName, final String childNodeName) { - for (Module module : context.getModules()) { - if (module.getName().equals(moduleName)) { - DataSchemaNode found = - findChildNode(module.getChildNodes(), childNodeName); - Preconditions.checkState(found != null, "Unable to find %s", - childNodeName); - return found; - } - } - throw new IllegalStateException("Unable to find child node " - + childNodeName); - } - - static DataSchemaNode findChildNode( - final Collection children, final String name) { - List containers = Lists.newArrayList(); - - for (DataSchemaNode dataSchemaNode : children) { - if (dataSchemaNode.getQName().getLocalName().equals(name)) { - return dataSchemaNode; - } - if (dataSchemaNode instanceof DataNodeContainer) { - containers.add((DataNodeContainer) dataSchemaNode); - } else if (dataSchemaNode instanceof ChoiceNode) { - containers.addAll(((ChoiceNode) dataSchemaNode).getCases()); - } - } - - for (DataNodeContainer container : containers) { - DataSchemaNode retVal = findChildNode(container.getChildNodes(), name); - if (retVal != null) { - return retVal; - } - } - - return null; - } - - public static YangInstanceIdentifier.NodeIdentifier getNodeIdentifier( - final String localName) { - return new YangInstanceIdentifier.NodeIdentifier(QName.create( - URI.create(NAMESPACE), revision, localName)); - } - - public static YangInstanceIdentifier.AugmentationIdentifier getAugmentIdentifier( - final String... childNames) { - Set qn = Sets.newHashSet(); - - for (String childName : childNames) { - qn.add(getNodeIdentifier(childName).getNodeType()); - } - - return new YangInstanceIdentifier.AugmentationIdentifier(qn); - } - - - public static ContainerNode augmentChoiceExpectedNode() { - - DataContainerNodeBuilder b = - Builders.containerBuilder(); - b.withNodeIdentifier(getNodeIdentifier("container")); - - b.withChild(Builders - .choiceBuilder() - .withNodeIdentifier(getNodeIdentifier("ch2")) - .withChild( - Builders.leafBuilder() - .withNodeIdentifier(getNodeIdentifier("c2Leaf")).withValue("2") - .build()) - .withChild( - Builders - .choiceBuilder() - .withNodeIdentifier(getNodeIdentifier("c2DeepChoice")) - .withChild( - Builders - .leafBuilder() - .withNodeIdentifier( - getNodeIdentifier("c2DeepChoiceCase1Leaf2")) - .withValue("2").build()).build()).build()); - - b.withChild(Builders - .choiceBuilder() - .withNodeIdentifier(getNodeIdentifier("ch3")) - .withChild( - Builders.leafBuilder() - .withNodeIdentifier(getNodeIdentifier("c3Leaf")).withValue("3") - .build()).build()); - - b.withChild(Builders - .augmentationBuilder() - .withNodeIdentifier(getAugmentIdentifier("augLeaf")) - .withChild( - Builders.leafBuilder() - .withNodeIdentifier(getNodeIdentifier("augLeaf")) - .withValue("augment").build()).build()); - - b.withChild(Builders - .augmentationBuilder() - .withNodeIdentifier(getAugmentIdentifier("ch")) - .withChild( - Builders - .choiceBuilder() - .withNodeIdentifier(getNodeIdentifier("ch")) - .withChild( - Builders.leafBuilder() - .withNodeIdentifier(getNodeIdentifier("c1Leaf")) - .withValue("1").build()) - .withChild( - Builders - .augmentationBuilder() - .withNodeIdentifier( - getAugmentIdentifier("c1Leaf_AnotherAugment", - "deepChoice")) - .withChild( - Builders - .leafBuilder() - .withNodeIdentifier( - getNodeIdentifier("c1Leaf_AnotherAugment")) - .withValue("1").build()) - .withChild( - Builders - .choiceBuilder() - .withNodeIdentifier( - getNodeIdentifier("deepChoice")) - .withChild( - Builders - .leafBuilder() - .withNodeIdentifier( - getNodeIdentifier("deepLeafc1")) - .withValue("1").build()).build()) - .build()).build()).build()); - - return b.build(); - } - - - - public void init(final String yangPath, final String xmlPath, - final ContainerNode expectedNode) throws Exception { - SchemaContext schema = parseTestSchema(yangPath); - this.xmlPath = xmlPath; - this.containerNode = - (ContainerSchemaNode) getSchemaNode(schema, "test", "container"); - this.expectedNode = expectedNode; - } - - SchemaContext parseTestSchema(final String yangPath) throws Exception { - - YangParserImpl yangParserImpl = new YangParserImpl(); - InputStream stream = - NormalizedNodeXmlConverterTest.class.getResourceAsStream(yangPath); - ArrayList al = new ArrayList(); - al.add(stream); - Set modules = yangParserImpl.parseYangModelsFromStreams(al); - return yangParserImpl.resolveSchemaContext(modules); - - } - - - @Test - public void testConversionWithAugmentChoice() throws Exception { - init("/augment_choice.yang", "/augment_choice.xml", - augmentChoiceExpectedNode()); - Document doc = loadDocument(xmlPath); - - ContainerNode built = - DomToNormalizedNodeParserFactory - .getInstance(DomUtils.defaultValueCodecProvider()) - .getContainerNodeParser() - .parse(Collections.singletonList(doc.getDocumentElement()), - containerNode); - - if (expectedNode != null) { - junit.framework.Assert.assertEquals(expectedNode, built); - } - - logger.info("{}", built); - - Iterable els = - DomFromNormalizedNodeSerializerFactory - .getInstance(XmlDocumentUtils.getDocument(), - DomUtils.defaultValueCodecProvider()) - .getContainerNodeSerializer().serialize(containerNode, built); - - Element el = els.iterator().next(); - - XMLUnit.setIgnoreWhitespace(true); - XMLUnit.setIgnoreComments(true); - - System.out.println(toString(doc.getDocumentElement())); - System.out.println(toString(el)); - - new Diff(XMLUnit.buildControlDocument(toString(doc.getDocumentElement())), - XMLUnit.buildTestDocument(toString(el))).similar(); - } - - private static ContainerNode listLeafListWithAttributes() { - DataContainerNodeBuilder b = - Builders.containerBuilder(); - b.withNodeIdentifier(getNodeIdentifier("container")); - - CollectionNodeBuilder listBuilder = - Builders.mapBuilder().withNodeIdentifier(getNodeIdentifier("list")); - - Map predicates = Maps.newHashMap(); - predicates.put(getNodeIdentifier("uint32InList").getNodeType(), 3L); - - DataContainerNodeBuilder list1Builder = - Builders.mapEntryBuilder().withNodeIdentifier( - new YangInstanceIdentifier.NodeIdentifierWithPredicates( - getNodeIdentifier("list").getNodeType(), predicates)); - NormalizedNodeBuilder> uint32InListBuilder = - Builders.leafBuilder().withNodeIdentifier( - getNodeIdentifier("uint32InList")); - - list1Builder.withChild(uint32InListBuilder.withValue(3L).build()); - - listBuilder.withChild(list1Builder.build()); - b.withChild(listBuilder.build()); - - NormalizedNodeBuilder> booleanBuilder = - Builders.leafBuilder().withNodeIdentifier(getNodeIdentifier("boolean")); - booleanBuilder.withValue(false); - b.withChild(booleanBuilder.build()); - - ListNodeBuilder> leafListBuilder = - Builders.leafSetBuilder().withNodeIdentifier( - getNodeIdentifier("leafList")); - - NormalizedNodeBuilder> leafList1Builder = - Builders.leafSetEntryBuilder().withNodeIdentifier( - new YangInstanceIdentifier.NodeWithValue(getNodeIdentifier( - "leafList").getNodeType(), "a")); - - leafList1Builder.withValue("a"); - - leafListBuilder.withChild(leafList1Builder.build()); - b.withChild(leafListBuilder.build()); - - return b.build(); - } - - - @Test - public void testConversionWithAttributes() throws Exception { - init("/test.yang", "/simple_xml_with_attributes.xml", - listLeafListWithAttributes()); - Document doc = loadDocument(xmlPath); - - ContainerNode built = - DomToNormalizedNodeParserFactory - .getInstance(DomUtils.defaultValueCodecProvider()) - .getContainerNodeParser() - .parse(Collections.singletonList(doc.getDocumentElement()), - containerNode); - - if (expectedNode != null) { - junit.framework.Assert.assertEquals(expectedNode, built); - } - - logger.info("{}", built); - - Iterable els = - DomFromNormalizedNodeSerializerFactory - .getInstance(XmlDocumentUtils.getDocument(), - DomUtils.defaultValueCodecProvider()) - .getContainerNodeSerializer().serialize(containerNode, built); - - Element el = els.iterator().next(); - - XMLUnit.setIgnoreWhitespace(true); - XMLUnit.setIgnoreComments(true); - - System.out.println(toString(doc.getDocumentElement())); - System.out.println(toString(el)); - - new Diff(XMLUnit.buildControlDocument(toString(doc.getDocumentElement())), - XMLUnit.buildTestDocument(toString(el))).similar(); - } - - - private Document loadDocument(final String xmlPath) throws Exception { - InputStream resourceAsStream = - NormalizedNodeXmlConverterTest.class.getResourceAsStream(xmlPath); - - Document currentConfigElement = readXmlToDocument(resourceAsStream); - Preconditions.checkNotNull(currentConfigElement); - return currentConfigElement; - } - - private static final DocumentBuilderFactory BUILDERFACTORY; - - static { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - factory.setCoalescing(true); - factory.setIgnoringElementContentWhitespace(true); - factory.setIgnoringComments(true); - BUILDERFACTORY = factory; - } - - private Document readXmlToDocument(final InputStream xmlContent) - throws IOException, SAXException { - DocumentBuilder dBuilder; - try { - dBuilder = BUILDERFACTORY.newDocumentBuilder(); - } catch (ParserConfigurationException e) { - throw new RuntimeException("Failed to parse XML document", e); - } - Document doc = dBuilder.parse(xmlContent); - - doc.getDocumentElement().normalize(); - return doc; - } - - public static String toString(final Element xml) { - try { - Transformer transformer = - TransformerFactory.newInstance().newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - - StreamResult result = new StreamResult(new StringWriter()); - DOMSource source = new DOMSource(xml); - transformer.transform(source, result); - - return result.getWriter().toString(); - } catch (IllegalArgumentException | TransformerFactoryConfigurationError - | TransformerException e) { - throw new RuntimeException("Unable to serialize xml element " + xml, e); - } - } - - @Test - public void testConversionToNormalizedXml() throws Exception { - SimpleNormalizedNodeMessage.NormalizedNodeXml nnXml = - EncoderDecoderUtil.encode(parseTestSchema("/augment_choice.yang"), - augmentChoiceExpectedNode()); - Document expectedDoc = loadDocument("/augment_choice.xml"); - Document convertedDoc = - EncoderDecoderUtil.factory.newDocumentBuilder().parse( - new ByteArrayInputStream(nnXml.getXmlString().getBytes("utf-8"))); - System.out.println(toString(convertedDoc.getDocumentElement())); - XMLUnit.setIgnoreWhitespace(true); - XMLUnit.setIgnoreComments(true); - new Diff(XMLUnit.buildControlDocument(toString(expectedDoc - .getDocumentElement())), - XMLUnit.buildTestDocument(toString(convertedDoc.getDocumentElement()))) - .similar(); - System.out.println(toString(expectedDoc.getDocumentElement())); - - } - - - @Test - public void testConversionFromXmlToNormalizedNode() throws Exception { - SimpleNormalizedNodeMessage.NormalizedNodeXml nnXml = - EncoderDecoderUtil.encode(parseTestSchema("/test.yang"), - listLeafListWithAttributes()); - Document expectedDoc = loadDocument("/simple_xml_with_attributes.xml"); - Document convertedDoc = - EncoderDecoderUtil.factory.newDocumentBuilder().parse( - new ByteArrayInputStream(nnXml.getXmlString().getBytes("utf-8"))); - System.out.println(toString(convertedDoc.getDocumentElement())); - XMLUnit.setIgnoreWhitespace(true); - XMLUnit.setIgnoreComments(true); - new Diff(XMLUnit.buildControlDocument(toString(expectedDoc - .getDocumentElement())), - XMLUnit.buildTestDocument(toString(convertedDoc.getDocumentElement()))) - .similar(); - System.out.println(toString(expectedDoc.getDocumentElement())); - - // now we will try to convert xml back to normalize node. - ContainerNode cn = - (ContainerNode) EncoderDecoderUtil.decode( - parseTestSchema("/test.yang"), nnXml); - junit.framework.Assert.assertEquals(listLeafListWithAttributes(), cn); - - } - -} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ClusterWrapper.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ClusterWrapper.java index 2eac2400b5..58d805b2b5 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ClusterWrapper.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ClusterWrapper.java @@ -13,4 +13,5 @@ import akka.actor.ActorRef; public interface ClusterWrapper { void subscribeToMemberEvents(ActorRef actorRef); String getCurrentMemberName(); + String getSelfAddress(); } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ClusterWrapperImpl.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ClusterWrapperImpl.java index 8910137ec4..857510ad4b 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ClusterWrapperImpl.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ClusterWrapperImpl.java @@ -17,6 +17,7 @@ import com.google.common.base.Preconditions; public class ClusterWrapperImpl implements ClusterWrapper { private final Cluster cluster; private final String currentMemberName; + private final String selfAddress; public ClusterWrapperImpl(ActorSystem actorSystem){ Preconditions.checkNotNull(actorSystem, "actorSystem should not be null"); @@ -31,6 +32,7 @@ public class ClusterWrapperImpl implements ClusterWrapper { ); currentMemberName = (String) cluster.getSelfRoles().toArray()[0]; + selfAddress = cluster.selfAddress().toString(); } @@ -45,4 +47,8 @@ public class ClusterWrapperImpl implements ClusterWrapper { public String getCurrentMemberName() { return currentMemberName; } + + public String getSelfAddress() { + return selfAddress; + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListener.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListener.java index a498826e98..9a77e4d568 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListener.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListener.java @@ -19,8 +19,12 @@ import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class DataChangeListener extends AbstractUntypedActor { + private static final Logger LOG = LoggerFactory.getLogger(DataChangeListener.class); + private final AsyncDataChangeListener> listener; private boolean notificationsEnabled = false; @@ -29,7 +33,8 @@ public class DataChangeListener extends AbstractUntypedActor { this.listener = Preconditions.checkNotNull(listener, "listener should not be null"); } - @Override public void handleReceive(Object message) throws Exception { + @Override + public void handleReceive(Object message) throws Exception { if(message instanceof DataChanged){ dataChanged(message); } else if(message instanceof EnableNotification){ @@ -39,18 +44,24 @@ public class DataChangeListener extends AbstractUntypedActor { private void enableNotification(EnableNotification message) { notificationsEnabled = message.isEnabled(); + LOG.debug("{} notifications for listener {}", (notificationsEnabled ? "Enabled" : "Disabled"), + listener); } private void dataChanged(Object message) { // Do nothing if notifications are not enabled - if(!notificationsEnabled){ + if(!notificationsEnabled) { + LOG.debug("Notifications not enabled for listener {} - dropping change notification", + listener); return; } DataChanged reply = (DataChanged) message; - AsyncDataChangeEvent> - change = reply.getChange(); + AsyncDataChangeEvent> change = reply.getChange(); + + LOG.debug("Sending change notification {} to listener {}", change, listener); + this.listener.onDataChanged(change); // It seems the sender is never null but it doesn't hurt to check. If the caller passes in diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationProxy.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationProxy.java index acf630e2e9..b2ae060c3d 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationProxy.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationProxy.java @@ -8,14 +8,27 @@ package org.opendaylight.controller.cluster.datastore; +import java.util.concurrent.TimeUnit; import akka.actor.ActorRef; import akka.actor.ActorSelection; import akka.actor.PoisonPill; +import akka.dispatch.OnComplete; +import akka.util.Timeout; +import org.opendaylight.controller.cluster.datastore.exceptions.LocalShardNotFoundException; import org.opendaylight.controller.cluster.datastore.messages.CloseDataChangeListenerRegistration; +import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListener; +import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListenerReply; +import org.opendaylight.controller.cluster.datastore.utils.ActorContext; +import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker; +import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener; import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.google.common.annotations.VisibleForTesting; +import scala.concurrent.Future; /** * ListenerRegistrationProxy acts as a proxy for a ListenerRegistration that was done on a remote shard @@ -24,25 +37,36 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; * The ListenerRegistrationProxy talks to a remote ListenerRegistration actor. *

*/ +@SuppressWarnings("rawtypes") public class DataChangeListenerRegistrationProxy implements ListenerRegistration { + + private static final Logger LOG = LoggerFactory.getLogger(DataChangeListenerRegistrationProxy.class); + + public static final Timeout REGISTER_TIMEOUT = new Timeout(5, TimeUnit.MINUTES); + private volatile ActorSelection listenerRegistrationActor; - private final AsyncDataChangeListener listener; - private final ActorRef dataChangeListenerActor; + private final AsyncDataChangeListener> listener; + private ActorRef dataChangeListenerActor; + private final String shardName; + private final ActorContext actorContext; private boolean closed = false; public >> - DataChangeListenerRegistrationProxy( - ActorSelection listenerRegistrationActor, - L listener, ActorRef dataChangeListenerActor) { - this.listenerRegistrationActor = listenerRegistrationActor; + DataChangeListenerRegistrationProxy ( + String shardName, ActorContext actorContext, L listener) { + this.shardName = shardName; + this.actorContext = actorContext; this.listener = listener; - this.dataChangeListenerActor = dataChangeListenerActor; } - public >> - DataChangeListenerRegistrationProxy( - L listener, ActorRef dataChangeListenerActor) { - this(null, listener, dataChangeListenerActor); + @VisibleForTesting + ActorSelection getListenerRegistrationActor() { + return listenerRegistrationActor; + } + + @VisibleForTesting + ActorRef getDataChangeListenerActor() { + return dataChangeListenerActor; } @Override @@ -50,7 +74,11 @@ public class DataChangeListenerRegistrationProxy implements ListenerRegistration return listener; } - public void setListenerRegistrationActor(ActorSelection listenerRegistrationActor) { + private void setListenerRegistrationActor(ActorSelection listenerRegistrationActor) { + if(listenerRegistrationActor == null) { + return; + } + boolean sendCloseMessage = false; synchronized(this) { if(closed) { @@ -59,16 +87,55 @@ public class DataChangeListenerRegistrationProxy implements ListenerRegistration this.listenerRegistrationActor = listenerRegistrationActor; } } + if(sendCloseMessage) { listenerRegistrationActor.tell(new CloseDataChangeListenerRegistration().toSerializable(), null); } + } - this.listenerRegistrationActor = listenerRegistrationActor; + public void init(final YangInstanceIdentifier path, final AsyncDataBroker.DataChangeScope scope) { + + dataChangeListenerActor = actorContext.getActorSystem().actorOf( + DataChangeListener.props(listener)); + + Future findFuture = actorContext.findLocalShardAsync(shardName, REGISTER_TIMEOUT); + findFuture.onComplete(new OnComplete() { + @Override + public void onComplete(Throwable failure, ActorRef shard) { + if(failure instanceof LocalShardNotFoundException) { + LOG.debug("No local shard found for {} - DataChangeListener {} at path {} " + + "cannot be registered", shardName, listener, path); + } else if(failure != null) { + LOG.error("Failed to find local shard {} - DataChangeListener {} at path {} " + + "cannot be registered: {}", shardName, listener, path, failure); + } else { + doRegistration(shard, path, scope); + } + } + }, actorContext.getActorSystem().dispatcher()); } - public ActorSelection getListenerRegistrationActor() { - return listenerRegistrationActor; + private void doRegistration(ActorRef shard, final YangInstanceIdentifier path, + DataChangeScope scope) { + + Future future = actorContext.executeOperationAsync(shard, + new RegisterChangeListener(path, dataChangeListenerActor.path(), scope), + REGISTER_TIMEOUT); + + future.onComplete(new OnComplete(){ + @Override + public void onComplete(Throwable failure, Object result) { + if(failure != null) { + LOG.error("Failed to register DataChangeListener {} at path {}", + listener, path.toString(), failure); + } else { + RegisterChangeListenerReply reply = (RegisterChangeListenerReply) result; + setListenerRegistrationActor(actorContext.actorSelection( + reply.getListenerRegistrationPath())); + } + } + }, actorContext.getActorSystem().dispatcher()); } @Override @@ -79,11 +146,16 @@ public class DataChangeListenerRegistrationProxy implements ListenerRegistration sendCloseMessage = !closed && listenerRegistrationActor != null; closed = true; } + if(sendCloseMessage) { - listenerRegistrationActor.tell(new - CloseDataChangeListenerRegistration().toSerializable(), null); + listenerRegistrationActor.tell(new CloseDataChangeListenerRegistration().toSerializable(), + ActorRef.noSender()); + listenerRegistrationActor = null; } - dataChangeListenerActor.tell(PoisonPill.getInstance(), null); + if(dataChangeListenerActor != null) { + dataChangeListenerActor.tell(PoisonPill.getInstance(), ActorRef.noSender()); + dataChangeListenerActor = null; + } } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DistributedDataStore.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DistributedDataStore.java index f6c31aab04..2c73807dca 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DistributedDataStore.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DistributedDataStore.java @@ -8,16 +8,10 @@ package org.opendaylight.controller.cluster.datastore; -import akka.actor.ActorRef; import akka.actor.ActorSystem; -import akka.dispatch.OnComplete; -import akka.util.Timeout; -import com.google.common.base.Optional; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import org.opendaylight.controller.cluster.datastore.identifiers.ShardManagerIdentifier; -import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListener; -import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListenerReply; import org.opendaylight.controller.cluster.datastore.shardstrategy.ShardStrategyFactory; import org.opendaylight.controller.cluster.datastore.utils.ActorContext; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker; @@ -34,7 +28,6 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import scala.concurrent.Future; /** * @@ -83,39 +76,11 @@ public class DistributedDataStore implements DOMStore, SchemaContextListener, Au String shardName = ShardStrategyFactory.getStrategy(path).findShard(path); - Optional shard = actorContext.findLocalShard(shardName); - - //if shard is NOT local - if (!shard.isPresent()) { - LOG.debug("No local shard for shardName {} was found so returning a noop registration", shardName); - return new NoOpDataChangeListenerRegistration(listener); - } - //if shard is local - ActorRef dataChangeListenerActor = actorContext.getActorSystem().actorOf(DataChangeListener.props(listener)); - Future future = actorContext.executeOperationAsync(shard.get(), - new RegisterChangeListener(path, dataChangeListenerActor.path(), scope), - new Timeout(actorContext.getOperationDuration().$times(REGISTER_DATA_CHANGE_LISTENER_TIMEOUT_FACTOR))); - final DataChangeListenerRegistrationProxy listenerRegistrationProxy = - new DataChangeListenerRegistrationProxy(listener, dataChangeListenerActor); - - future.onComplete(new OnComplete() { - - @Override - public void onComplete(Throwable failure, Object result) - throws Throwable { - if (failure != null) { - LOG.error("Failed to register listener at path " + path.toString(), failure); - return; - } - RegisterChangeListenerReply reply = (RegisterChangeListenerReply) result; - listenerRegistrationProxy.setListenerRegistrationActor(actorContext - .actorSelection(reply.getListenerRegistrationPath())); - } - }, actorContext.getActorSystem().dispatcher()); + new DataChangeListenerRegistrationProxy(shardName, actorContext, listener); + listenerRegistrationProxy.init(path, scope); return listenerRegistrationProxy; - } @Override diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/NoOpCohort.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/NoOpCohort.java deleted file mode 100644 index eb28159025..0000000000 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/NoOpCohort.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.cluster.datastore; - -import akka.actor.UntypedActor; -import org.opendaylight.controller.cluster.datastore.messages.AbortTransaction; -import org.opendaylight.controller.cluster.datastore.messages.AbortTransactionReply; -import org.opendaylight.controller.cluster.datastore.messages.CanCommitTransaction; -import org.opendaylight.controller.cluster.datastore.messages.CanCommitTransactionReply; -import org.opendaylight.controller.cluster.datastore.messages.CommitTransaction; -import org.opendaylight.controller.cluster.datastore.messages.CommitTransactionReply; -import org.opendaylight.controller.cluster.datastore.messages.PreCommitTransaction; -import org.opendaylight.controller.cluster.datastore.messages.PreCommitTransactionReply; - -public class NoOpCohort extends UntypedActor { - - @Override public void onReceive(Object message) throws Exception { - if (message.getClass().equals(CanCommitTransaction.SERIALIZABLE_CLASS)) { - getSender().tell(new CanCommitTransactionReply(false).toSerializable(), getSelf()); - } else if (message.getClass().equals(PreCommitTransaction.SERIALIZABLE_CLASS)) { - getSender().tell( - new PreCommitTransactionReply().toSerializable(), - getSelf()); - } else if (message.getClass().equals(CommitTransaction.SERIALIZABLE_CLASS)) { - getSender().tell(new CommitTransactionReply().toSerializable(), getSelf()); - } else if (message.getClass().equals(AbortTransaction.SERIALIZABLE_CLASS)) { - getSender().tell(new AbortTransactionReply().toSerializable(), getSelf()); - } else { - throw new Exception ("Not recognized message received,message="+message); - } - - } -} - diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/NoOpDataChangeListenerRegistration.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/NoOpDataChangeListenerRegistration.java deleted file mode 100644 index 14af31e898..0000000000 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/NoOpDataChangeListenerRegistration.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.cluster.datastore; - -import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener; -import org.opendaylight.yangtools.concepts.ListenerRegistration; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; - -/** - * When a consumer registers a data change listener and no local shard is - * available to register that listener with then we return an instance of - * NoOpDataChangeListenerRegistration - * - *

- * - * The NoOpDataChangeListenerRegistration as it's name suggests does - * nothing when an operation is invoked on it - */ -public class NoOpDataChangeListenerRegistration - implements ListenerRegistration { - - private final AsyncDataChangeListener> - listener; - - public >> NoOpDataChangeListenerRegistration( - AsyncDataChangeListener> listener) { - - this.listener = listener; - } - - @Override - public AsyncDataChangeListener> getInstance() { - return listener; - } - - @Override public void close() { - - } -} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java index fef7e22873..789d51a19f 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java @@ -34,9 +34,9 @@ import org.opendaylight.controller.cluster.datastore.identifiers.ShardIdentifier import org.opendaylight.controller.cluster.datastore.identifiers.ShardTransactionIdentifier; import org.opendaylight.controller.cluster.datastore.jmx.mbeans.shard.ShardMBeanFactory; import org.opendaylight.controller.cluster.datastore.jmx.mbeans.shard.ShardStats; -import org.opendaylight.controller.cluster.datastore.messages.ActorInitialized; import org.opendaylight.controller.cluster.datastore.messages.AbortTransaction; import org.opendaylight.controller.cluster.datastore.messages.AbortTransactionReply; +import org.opendaylight.controller.cluster.datastore.messages.ActorInitialized; import org.opendaylight.controller.cluster.datastore.messages.CanCommitTransaction; import org.opendaylight.controller.cluster.datastore.messages.CloseTransactionChain; import org.opendaylight.controller.cluster.datastore.messages.CommitTransaction; @@ -74,14 +74,14 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import scala.concurrent.duration.Duration; import scala.concurrent.duration.FiniteDuration; -import java.util.ArrayList; + +import javax.annotation.Nonnull; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import javax.annotation.Nonnull; /** * A Shard represents a portion of the logical data tree
@@ -112,7 +112,10 @@ public class Shard extends RaftActor { private final ShardStats shardMBean; - private final List dataChangeListeners = new ArrayList<>(); + private final List dataChangeListeners = Lists.newArrayList(); + + private final List delayedListenerRegistrations = + Lists.newArrayList(); private final DatastoreContext datastoreContext; @@ -216,6 +219,10 @@ public class Shard extends RaftActor { if (message instanceof RecoveryFailure){ LOG.error(((RecoveryFailure) message).cause(), "Recovery failed because of this cause"); + + // Even though recovery failed, we still need to finish our recovery, eg send the + // ActorInitialized message and start the txCommitTimeoutCheckSchedule. + onRecoveryComplete(); } else { super.onReceiveRecover(message); } @@ -383,8 +390,11 @@ public class Shard extends RaftActor { ready.getModification()); // Return our actor path as we'll handle the three phase commit. - getSender().tell(new ReadyTransactionReply(Serialization.serializedActorPath(self())). - toSerializable(), getSelf()); + ReadyTransactionReply readyTransactionReply = + new ReadyTransactionReply(Serialization.serializedActorPath(self())); + getSender().tell( + ready.isReturnSerialized() ? readyTransactionReply.toSerializable() : readyTransactionReply, + getSelf()); } private void handleAbortTransaction(AbortTransaction abort) { @@ -568,53 +578,60 @@ public class Shard extends RaftActor { store.onGlobalContextUpdated(message.getSchemaContext()); } - @VisibleForTesting void updateSchemaContext(SchemaContext schemaContext) { + @VisibleForTesting + void updateSchemaContext(SchemaContext schemaContext) { store.onGlobalContextUpdated(schemaContext); } - private void registerChangeListener( - RegisterChangeListener registerChangeListener) { + private void registerChangeListener(RegisterChangeListener registerChangeListener) { - if(LOG.isDebugEnabled()) { - LOG.debug("registerDataChangeListener for {}", registerChangeListener - .getPath()); + LOG.debug("registerDataChangeListener for {}", registerChangeListener.getPath()); + + ListenerRegistration>> registration; + if(isLeader()) { + registration = doChangeListenerRegistration(registerChangeListener); + } else { + LOG.debug("Shard is not the leader - delaying registration"); + + DelayedListenerRegistration delayedReg = + new DelayedListenerRegistration(registerChangeListener); + delayedListenerRegistrations.add(delayedReg); + registration = delayedReg; } + ActorRef listenerRegistration = getContext().actorOf( + DataChangeListenerRegistration.props(registration)); + + LOG.debug("registerDataChangeListener sending reply, listenerRegistrationPath = {} ", + listenerRegistration.path()); - ActorSelection dataChangeListenerPath = getContext() - .system().actorSelection( - registerChangeListener.getDataChangeListenerPath()); + getSender().tell(new RegisterChangeListenerReply(listenerRegistration.path()),getSelf()); + } + private ListenerRegistration>> doChangeListenerRegistration( + RegisterChangeListener registerChangeListener) { + + ActorSelection dataChangeListenerPath = getContext().system().actorSelection( + registerChangeListener.getDataChangeListenerPath()); // Notify the listener if notifications should be enabled or not // If this shard is the leader then it will enable notifications else // it will not - dataChangeListenerPath - .tell(new EnableNotification(isLeader()), getSelf()); + dataChangeListenerPath.tell(new EnableNotification(true), getSelf()); // Now store a reference to the data change listener so it can be notified // at a later point if notifications should be enabled or disabled dataChangeListeners.add(dataChangeListenerPath); - AsyncDataChangeListener> - listener = new DataChangeListenerProxy(schemaContext, dataChangeListenerPath); + AsyncDataChangeListener> listener = + new DataChangeListenerProxy(schemaContext, dataChangeListenerPath); - ListenerRegistration>> - registration = store.registerChangeListener(registerChangeListener.getPath(), - listener, registerChangeListener.getScope()); - ActorRef listenerRegistration = - getContext().actorOf( - DataChangeListenerRegistration.props(registration)); + LOG.debug("Registering for path {}", registerChangeListener.getPath()); - if(LOG.isDebugEnabled()) { - LOG.debug( - "registerDataChangeListener sending reply, listenerRegistrationPath = {} " - , listenerRegistration.path().toString()); - } - - getSender() - .tell(new RegisterChangeListenerReply(listenerRegistration.path()), - getSelf()); + return store.registerChangeListener(registerChangeListener.getPath(), listener, + registerChangeListener.getScope()); } private boolean isMetricsCaptureEnabled(){ @@ -695,12 +712,15 @@ public class Shard extends RaftActor { //notify shard manager getContext().parent().tell(new ActorInitialized(), getSelf()); - // Schedule a message to be periodically sent to check if the current in-progress - // transaction should be expired and aborted. - FiniteDuration period = Duration.create(transactionCommitTimeout / 3, TimeUnit.MILLISECONDS); - txCommitTimeoutCheckSchedule = getContext().system().scheduler().schedule( - period, period, getSelf(), - TX_COMMIT_TIMEOUT_CHECK_MESSAGE, getContext().dispatcher(), ActorRef.noSender()); + // Being paranoid here - this method should only be called once but just in case... + if(txCommitTimeoutCheckSchedule == null) { + // Schedule a message to be periodically sent to check if the current in-progress + // transaction should be expired and aborted. + FiniteDuration period = Duration.create(transactionCommitTimeout / 3, TimeUnit.MILLISECONDS); + txCommitTimeoutCheckSchedule = getContext().system().scheduler().schedule( + period, period, getSelf(), + TX_COMMIT_TIMEOUT_CHECK_MESSAGE, getContext().dispatcher(), ActorRef.noSender()); + } } @Override @@ -787,17 +807,28 @@ public class Shard extends RaftActor { } } - @Override protected void onStateChanged() { + @Override + protected void onStateChanged() { + boolean isLeader = isLeader(); for (ActorSelection dataChangeListener : dataChangeListeners) { - dataChangeListener - .tell(new EnableNotification(isLeader()), getSelf()); + dataChangeListener.tell(new EnableNotification(isLeader), getSelf()); + } + + if(isLeader) { + for(DelayedListenerRegistration reg: delayedListenerRegistrations) { + if(!reg.isClosed()) { + reg.setDelegate(doChangeListenerRegistration(reg.getRegisterChangeListener())); + } + } + + delayedListenerRegistrations.clear(); } shardMBean.setRaftState(getRaftState().name()); shardMBean.setCurrentTerm(getCurrentTerm()); // If this actor is no longer the leader close all the transaction chains - if(!isLeader()){ + if(!isLeader){ for(Map.Entry entry : transactionChains.entrySet()){ if(LOG.isDebugEnabled()) { LOG.debug( @@ -851,4 +882,45 @@ public class Shard extends RaftActor { ShardStats getShardMBean() { return shardMBean; } + + private static class DelayedListenerRegistration implements + ListenerRegistration>> { + + private volatile boolean closed; + + private final RegisterChangeListener registerChangeListener; + + private volatile ListenerRegistration>> delegate; + + DelayedListenerRegistration(RegisterChangeListener registerChangeListener) { + this.registerChangeListener = registerChangeListener; + } + + void setDelegate( ListenerRegistration>> registration) { + this.delegate = registration; + } + + boolean isClosed() { + return closed; + } + + RegisterChangeListener getRegisterChangeListener() { + return registerChangeListener; + } + + @Override + public AsyncDataChangeListener> getInstance() { + return delegate != null ? delegate.getInstance() : null; + } + + @Override + public void close() { + closed = true; + if(delegate != null) { + delegate.close(); + } + } + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardManager.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardManager.java index 157f1cb377..e861165c6b 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardManager.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardManager.java @@ -25,6 +25,7 @@ import akka.persistence.RecoveryFailure; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; +import com.google.common.collect.Lists; import org.opendaylight.controller.cluster.common.actor.AbstractUntypedPersistentActorWithMetering; import org.opendaylight.controller.cluster.datastore.identifiers.ShardIdentifier; import org.opendaylight.controller.cluster.datastore.identifiers.ShardManagerIdentifier; @@ -163,7 +164,7 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { LOG.debug("Initializing shard [{}]", shardName); ShardInformation shardInformation = localShards.get(shardName); if (shardInformation != null) { - shardInformation.setShardInitialized(true); + shardInformation.setActorInitialized(); } } @@ -192,7 +193,7 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { return; } - sendResponse(shardInformation, new Supplier() { + sendResponse(shardInformation, message.isWaitUntilInitialized(), new Supplier() { @Override public Object get() { return new LocalShardFound(shardInformation.getActor()); @@ -200,9 +201,22 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { }); } - private void sendResponse(ShardInformation shardInformation, Supplier messageSupplier) { - if (shardInformation.getActor() == null || !shardInformation.isShardInitialized()) { - getSender().tell(new ActorNotInitialized(), getSelf()); + private void sendResponse(ShardInformation shardInformation, boolean waitUntilInitialized, + final Supplier messageSupplier) { + if (!shardInformation.isShardInitialized()) { + if(waitUntilInitialized) { + final ActorRef sender = getSender(); + final ActorRef self = self(); + shardInformation.addRunnableOnInitialized(new Runnable() { + @Override + public void run() { + sender.tell(messageSupplier.get(), self); + } + }); + } else { + getSender().tell(new ActorNotInitialized(), getSelf()); + } + return; } @@ -277,7 +291,7 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { // First see if the there is a local replica for the shard final ShardInformation info = localShards.get(shardName); if (info != null) { - sendResponse(info, new Supplier() { + sendResponse(info, message.isWaitUntilInitialized(), new Supplier() { @Override public Object get() { return new PrimaryFound(info.getActorPath().toString()).toSerializable(); @@ -422,7 +436,11 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { private ActorRef actor; private ActorPath actorPath; private final Map peerAddresses; - private boolean shardInitialized = false; // flag that determines if the actor is ready for business + + // flag that determines if the actor is ready for business + private boolean actorInitialized = false; + + private final List runnablesOnInitialized = Lists.newArrayList(); private ShardInformation(String shardName, ShardIdentifier shardId, Map peerAddresses) { @@ -474,11 +492,21 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { } boolean isShardInitialized() { - return shardInitialized; + return getActor() != null && actorInitialized; + } + + void setActorInitialized() { + this.actorInitialized = true; + + for(Runnable runnable: runnablesOnInitialized) { + runnable.run(); + } + + runnablesOnInitialized.clear(); } - void setShardInitialized(boolean shardInitialized) { - this.shardInitialized = shardInitialized; + void addRunnableOnInitialized(Runnable runnable) { + runnablesOnInitialized.add(runnable); } } @@ -505,8 +533,6 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { } static class SchemaContextModules implements Serializable { - private static final long serialVersionUID = 1L; - private final Set modules; SchemaContextModules(Set modules){ diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadTransaction.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadTransaction.java index 29f22b28f4..d12e9997bb 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadTransaction.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadTransaction.java @@ -11,7 +11,6 @@ package org.opendaylight.controller.cluster.datastore; import akka.actor.ActorRef; - import org.opendaylight.controller.cluster.datastore.jmx.mbeans.shard.ShardStats; import org.opendaylight.controller.cluster.datastore.messages.DataExists; import org.opendaylight.controller.cluster.datastore.messages.ReadData; @@ -34,10 +33,18 @@ public class ShardReadTransaction extends ShardTransaction { @Override public void handleReceive(Object message) throws Exception { - if(ReadData.SERIALIZABLE_CLASS.equals(message.getClass())) { - readData(transaction, ReadData.fromSerializable(message)); + if(message instanceof ReadData) { + readData(transaction, (ReadData) message, !SERIALIZED_REPLY); + + } else if (message instanceof DataExists) { + dataExists(transaction, (DataExists) message, !SERIALIZED_REPLY); + + } else if(ReadData.SERIALIZABLE_CLASS.equals(message.getClass())) { + readData(transaction, ReadData.fromSerializable(message), SERIALIZED_REPLY); + } else if(DataExists.SERIALIZABLE_CLASS.equals(message.getClass())) { - dataExists(transaction, DataExists.fromSerializable(message)); + dataExists(transaction, DataExists.fromSerializable(message), SERIALIZED_REPLY); + } else { super.handleReceive(message); } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadWriteTransaction.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadWriteTransaction.java index 2e174ebf56..b1fd02d217 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadWriteTransaction.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadWriteTransaction.java @@ -33,10 +33,18 @@ public class ShardReadWriteTransaction extends ShardWriteTransaction { @Override public void handleReceive(Object message) throws Exception { - if(ReadData.SERIALIZABLE_CLASS.equals(message.getClass())) { - readData(transaction, ReadData.fromSerializable(message)); + if (message instanceof ReadData) { + readData(transaction, (ReadData) message, !SERIALIZED_REPLY); + + } else if (message instanceof DataExists) { + dataExists(transaction, (DataExists) message, !SERIALIZED_REPLY); + + } else if(ReadData.SERIALIZABLE_CLASS.equals(message.getClass())) { + readData(transaction, ReadData.fromSerializable(message), SERIALIZED_REPLY); + } else if(DataExists.SERIALIZABLE_CLASS.equals(message.getClass())) { - dataExists(transaction, DataExists.fromSerializable(message)); + dataExists(transaction, DataExists.fromSerializable(message), SERIALIZED_REPLY); + } else { super.handleReceive(message); } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransaction.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransaction.java index bb676e3757..32de47f451 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransaction.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransaction.java @@ -61,6 +61,7 @@ public abstract class ShardTransaction extends AbstractUntypedActorWithMetering private final SchemaContext schemaContext; private final ShardStats shardStats; private final String transactionID; + protected static final boolean SERIALIZED_REPLY = true; protected ShardTransaction(ActorRef shardActor, SchemaContext schemaContext, ShardStats shardStats, String transactionID) { @@ -116,23 +117,24 @@ public abstract class ShardTransaction extends AbstractUntypedActorWithMetering getSelf().tell(PoisonPill.getInstance(), getSelf()); } - protected void readData(DOMStoreReadTransaction transaction,ReadData message) { + protected void readData(DOMStoreReadTransaction transaction, ReadData message, final boolean returnSerialized) { final ActorRef sender = getSender(); final ActorRef self = getSelf(); final YangInstanceIdentifier path = message.getPath(); final CheckedFuture>, ReadFailedException> future = transaction.read(path); + future.addListener(new Runnable() { @Override public void run() { try { Optional> optional = future.checkedGet(); - if (optional.isPresent()) { - sender.tell(new ReadDataReply(schemaContext,optional.get()).toSerializable(), self); - } else { - sender.tell(new ReadDataReply(schemaContext,null).toSerializable(), self); - } + ReadDataReply readDataReply = new ReadDataReply(schemaContext, optional.orNull()); + + sender.tell((returnSerialized ? readDataReply.toSerializable(): + readDataReply), self); + } catch (Exception e) { shardStats.incrementFailedReadTransactionsCount(); sender.tell(new akka.actor.Status.Failure(e), self); @@ -142,12 +144,15 @@ public abstract class ShardTransaction extends AbstractUntypedActorWithMetering }, getContext().dispatcher()); } - protected void dataExists(DOMStoreReadTransaction transaction, DataExists message) { + protected void dataExists(DOMStoreReadTransaction transaction, DataExists message, + final boolean returnSerialized) { final YangInstanceIdentifier path = message.getPath(); try { Boolean exists = transaction.exists(path).checkedGet(); - getSender().tell(new DataExistsReply(exists).toSerializable(), getSelf()); + DataExistsReply dataExistsReply = new DataExistsReply(exists); + getSender().tell(returnSerialized ? dataExistsReply.toSerializable() : + dataExistsReply, getSelf()); } catch (ReadFailedException e) { getSender().tell(new akka.actor.Status.Failure(e),getSelf()); } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardWriteTransaction.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardWriteTransaction.java index e993e4b55c..21c210daf2 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardWriteTransaction.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardWriteTransaction.java @@ -53,14 +53,31 @@ public class ShardWriteTransaction extends ShardTransaction { @Override public void handleReceive(Object message) throws Exception { - if(WriteData.SERIALIZABLE_CLASS.equals(message.getClass())) { - writeData(transaction, WriteData.fromSerializable(message, getSchemaContext())); + + if (message instanceof WriteData) { + writeData(transaction, (WriteData) message, !SERIALIZED_REPLY); + + } else if (message instanceof MergeData) { + mergeData(transaction, (MergeData) message, !SERIALIZED_REPLY); + + } else if (message instanceof DeleteData) { + deleteData(transaction, (DeleteData) message, !SERIALIZED_REPLY); + + } else if (message instanceof ReadyTransaction) { + readyTransaction(transaction, new ReadyTransaction(), !SERIALIZED_REPLY); + + } else if(WriteData.SERIALIZABLE_CLASS.equals(message.getClass())) { + writeData(transaction, WriteData.fromSerializable(message, getSchemaContext()), SERIALIZED_REPLY); + } else if(MergeData.SERIALIZABLE_CLASS.equals(message.getClass())) { - mergeData(transaction, MergeData.fromSerializable(message, getSchemaContext())); + mergeData(transaction, MergeData.fromSerializable(message, getSchemaContext()), SERIALIZED_REPLY); + } else if(DeleteData.SERIALIZABLE_CLASS.equals(message.getClass())) { - deleteData(transaction, DeleteData.fromSerializable(message)); + deleteData(transaction, DeleteData.fromSerializable(message), SERIALIZED_REPLY); + } else if(ReadyTransaction.SERIALIZABLE_CLASS.equals(message.getClass())) { - readyTransaction(transaction, new ReadyTransaction()); + readyTransaction(transaction, new ReadyTransaction(), SERIALIZED_REPLY); + } else if (message instanceof GetCompositedModification) { // This is here for testing only getSender().tell(new GetCompositeModificationReply( @@ -70,7 +87,7 @@ public class ShardWriteTransaction extends ShardTransaction { } } - private void writeData(DOMStoreWriteTransaction transaction, WriteData message) { + private void writeData(DOMStoreWriteTransaction transaction, WriteData message, boolean returnSerialized) { modification.addModification( new WriteModification(message.getPath(), message.getData(), getSchemaContext())); if(LOG.isDebugEnabled()) { @@ -78,13 +95,15 @@ public class ShardWriteTransaction extends ShardTransaction { } try { transaction.write(message.getPath(), message.getData()); - getSender().tell(new WriteDataReply().toSerializable(), getSelf()); + WriteDataReply writeDataReply = new WriteDataReply(); + getSender().tell(returnSerialized ? writeDataReply.toSerializable() : writeDataReply, + getSelf()); }catch(Exception e){ getSender().tell(new akka.actor.Status.Failure(e), getSelf()); } } - private void mergeData(DOMStoreWriteTransaction transaction, MergeData message) { + private void mergeData(DOMStoreWriteTransaction transaction, MergeData message, boolean returnSerialized) { modification.addModification( new MergeModification(message.getPath(), message.getData(), getSchemaContext())); if(LOG.isDebugEnabled()) { @@ -92,29 +111,34 @@ public class ShardWriteTransaction extends ShardTransaction { } try { transaction.merge(message.getPath(), message.getData()); - getSender().tell(new MergeDataReply().toSerializable(), getSelf()); + MergeDataReply mergeDataReply = new MergeDataReply(); + getSender().tell(returnSerialized ? mergeDataReply.toSerializable() : mergeDataReply , + getSelf()); }catch(Exception e){ getSender().tell(new akka.actor.Status.Failure(e), getSelf()); } } - private void deleteData(DOMStoreWriteTransaction transaction, DeleteData message) { + private void deleteData(DOMStoreWriteTransaction transaction, DeleteData message, boolean returnSerialized) { if(LOG.isDebugEnabled()) { LOG.debug("deleteData at path : " + message.getPath().toString()); } modification.addModification(new DeleteModification(message.getPath())); try { transaction.delete(message.getPath()); - getSender().tell(new DeleteDataReply().toSerializable(), getSelf()); + DeleteDataReply deleteDataReply = new DeleteDataReply(); + getSender().tell(returnSerialized ? deleteDataReply.toSerializable() : deleteDataReply, + getSelf()); }catch(Exception e){ getSender().tell(new akka.actor.Status.Failure(e), getSelf()); } } - private void readyTransaction(DOMStoreWriteTransaction transaction, ReadyTransaction message) { + private void readyTransaction(DOMStoreWriteTransaction transaction, ReadyTransaction message, boolean returnSerialized) { DOMStoreThreePhaseCommitCohort cohort = transaction.ready(); - getShardActor().forward(new ForwardedReadyTransaction(getTransactionID(), cohort, modification), + getShardActor().forward(new ForwardedReadyTransaction( + getTransactionID(), cohort, modification, returnSerialized), getContext()); } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionProxy.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionProxy.java index ec198510d3..715f48c349 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionProxy.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionProxy.java @@ -157,7 +157,7 @@ public class TransactionProxy implements DOMStoreReadWriteTransaction { for(ActorSelection actor : remoteTransactionActors) { LOG.trace("Sending CloseTransaction to {}", actor); actorContext.sendOperationAsync(actor, - new CloseTransaction().toSerializable()); + new CloseTransaction().toSerializable()); } } } @@ -385,8 +385,8 @@ public class TransactionProxy implements DOMStoreReadWriteTransaction { } Object response = actorContext.executeOperation(primaryShard.get(), - new CreateTransaction(identifier.toString(), this.transactionType.ordinal(), - getTransactionChainId()).toSerializable()); + new CreateTransaction(identifier.toString(), this.transactionType.ordinal(), + getTransactionChainId()).toSerializable()); if (response.getClass().equals(CreateTransactionReply.SERIALIZABLE_CLASS)) { CreateTransactionReply reply = CreateTransactionReply.fromSerializable(response); @@ -408,8 +408,12 @@ public class TransactionProxy implements DOMStoreReadWriteTransaction { remoteTransactionActorsMB.set(true); } + // TxActor is always created where the leader of the shard is. + // Check if TxActor is created in the same node + boolean isTxActorLocal = actorContext.isLocalPath(transactionPath); + transactionContext = new TransactionContextImpl(shardName, transactionPath, - transactionActor, identifier, actorContext, schemaContext); + transactionActor, identifier, actorContext, schemaContext, isTxActorLocal); remoteTransactionPaths.put(shardName, transactionContext); } else { @@ -483,15 +487,17 @@ public class TransactionProxy implements DOMStoreReadWriteTransaction { private final SchemaContext schemaContext; private final String actorPath; private final ActorSelection actor; + private final boolean isTxActorLocal; private TransactionContextImpl(String shardName, String actorPath, ActorSelection actor, TransactionIdentifier identifier, ActorContext actorContext, - SchemaContext schemaContext) { + SchemaContext schemaContext, boolean isTxActorLocal) { super(shardName, identifier); this.actorPath = actorPath; this.actor = actor; this.actorContext = actorContext; this.schemaContext = schemaContext; + this.isTxActorLocal = isTxActorLocal; } private ActorSelection getActor() { @@ -514,8 +520,9 @@ public class TransactionProxy implements DOMStoreReadWriteTransaction { } // Send the ReadyTransaction message to the Tx actor. + ReadyTransaction readyTransaction = new ReadyTransaction(); final Future replyFuture = actorContext.executeOperationAsync(getActor(), - new ReadyTransaction().toSerializable()); + isTxActorLocal ? readyTransaction : readyTransaction.toSerializable()); // Combine all the previously recorded put/merge/delete operation reply Futures and the // ReadyTransactionReply Future into one Future. If any one fails then the combined @@ -549,15 +556,15 @@ public class TransactionProxy implements DOMStoreReadWriteTransaction { // Note the Future get call here won't block as it's complete. Object serializedReadyReply = replyFuture.value().get().get(); - if(serializedReadyReply.getClass().equals( - ReadyTransactionReply.SERIALIZABLE_CLASS)) { - ReadyTransactionReply reply = ReadyTransactionReply.fromSerializable( - serializedReadyReply); + if (serializedReadyReply instanceof ReadyTransactionReply) { + return actorContext.actorSelection(((ReadyTransactionReply)serializedReadyReply).getCohortPath()); + } else if(serializedReadyReply.getClass().equals(ReadyTransactionReply.SERIALIZABLE_CLASS)) { + ReadyTransactionReply reply = ReadyTransactionReply.fromSerializable(serializedReadyReply); return actorContext.actorSelection(reply.getCohortPath()); + } else { // Throwing an exception here will fail the Future. - throw new IllegalArgumentException(String.format("Invalid reply type {}", serializedReadyReply.getClass())); } @@ -570,8 +577,10 @@ public class TransactionProxy implements DOMStoreReadWriteTransaction { if(LOG.isDebugEnabled()) { LOG.debug("Tx {} deleteData called path = {}", identifier, path); } + + DeleteData deleteData = new DeleteData(path); recordedOperationFutures.add(actorContext.executeOperationAsync(getActor(), - new DeleteData(path).toSerializable())); + isTxActorLocal ? deleteData : deleteData.toSerializable())); } @Override @@ -579,8 +588,10 @@ public class TransactionProxy implements DOMStoreReadWriteTransaction { if(LOG.isDebugEnabled()) { LOG.debug("Tx {} mergeData called path = {}", identifier, path); } + + MergeData mergeData = new MergeData(path, data, schemaContext); recordedOperationFutures.add(actorContext.executeOperationAsync(getActor(), - new MergeData(path, data, schemaContext).toSerializable())); + isTxActorLocal ? mergeData : mergeData.toSerializable())); } @Override @@ -588,8 +599,10 @@ public class TransactionProxy implements DOMStoreReadWriteTransaction { if(LOG.isDebugEnabled()) { LOG.debug("Tx {} writeData called path = {}", identifier, path); } + + WriteData writeData = new WriteData(path, data, schemaContext); recordedOperationFutures.add(actorContext.executeOperationAsync(getActor(), - new WriteData(path, data, schemaContext).toSerializable())); + isTxActorLocal ? writeData : writeData.toSerializable())); } @Override @@ -619,6 +632,7 @@ public class TransactionProxy implements DOMStoreReadWriteTransaction { Future> combinedFutures = akka.dispatch.Futures.sequence( Lists.newArrayList(recordedOperationFutures), actorContext.getActorSystem().dispatcher()); + OnComplete> onComplete = new OnComplete>() { @Override public void onComplete(Throwable failure, Iterable notUsed) @@ -663,25 +677,27 @@ public class TransactionProxy implements DOMStoreReadWriteTransaction { if(LOG.isDebugEnabled()) { LOG.debug("Tx {} read operation succeeded", identifier, failure); } - if (readResponse.getClass().equals(ReadDataReply.SERIALIZABLE_CLASS)) { - ReadDataReply reply = ReadDataReply.fromSerializable(schemaContext, - path, readResponse); - if (reply.getNormalizedNode() == null) { - returnFuture.set(Optional.>absent()); - } else { - returnFuture.set(Optional.>of( - reply.getNormalizedNode())); - } + + if (readResponse instanceof ReadDataReply) { + ReadDataReply reply = (ReadDataReply) readResponse; + returnFuture.set(Optional.>fromNullable(reply.getNormalizedNode())); + + } else if (readResponse.getClass().equals(ReadDataReply.SERIALIZABLE_CLASS)) { + ReadDataReply reply = ReadDataReply.fromSerializable(schemaContext, path, readResponse); + returnFuture.set(Optional.>fromNullable(reply.getNormalizedNode())); + } else { returnFuture.setException(new ReadFailedException( - "Invalid response reading data for path " + path)); + "Invalid response reading data for path " + path)); } } } }; + ReadData readData = new ReadData(path); Future readFuture = actorContext.executeOperationAsync(getActor(), - new ReadData(path).toSerializable()); + isTxActorLocal ? readData : readData.toSerializable()); + readFuture.onComplete(onComplete, actorContext.getActorSystem().dispatcher()); } @@ -756,9 +772,13 @@ public class TransactionProxy implements DOMStoreReadWriteTransaction { if(LOG.isDebugEnabled()) { LOG.debug("Tx {} dataExists operation succeeded", identifier, failure); } - if (response.getClass().equals(DataExistsReply.SERIALIZABLE_CLASS)) { - returnFuture.set(Boolean.valueOf(DataExistsReply. - fromSerializable(response).exists())); + + if (response instanceof DataExistsReply) { + returnFuture.set(Boolean.valueOf(((DataExistsReply) response).exists())); + + } else if (response.getClass().equals(DataExistsReply.SERIALIZABLE_CLASS)) { + returnFuture.set(Boolean.valueOf(DataExistsReply.fromSerializable(response).exists())); + } else { returnFuture.setException(new ReadFailedException( "Invalid response checking exists for path " + path)); @@ -767,8 +787,10 @@ public class TransactionProxy implements DOMStoreReadWriteTransaction { } }; + DataExists dataExists = new DataExists(path); Future future = actorContext.executeOperationAsync(getActor(), - new DataExists(path).toSerializable()); + isTxActorLocal ? dataExists : dataExists.toSerializable()); + future.onComplete(onComplete, actorContext.getActorSystem().dispatcher()); } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/exceptions/LocalShardNotFoundException.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/exceptions/LocalShardNotFoundException.java new file mode 100644 index 0000000000..7a976b86ed --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/exceptions/LocalShardNotFoundException.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2014 Brocade Communications 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.cluster.datastore.exceptions; + +/** + * Exception thrown when attempting to find a local shard but it doesn't exist. + * + * @author Thomas Pantelis + */ +public class LocalShardNotFoundException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public LocalShardNotFoundException(String message){ + super(message); + } +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/FindLocalShard.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/FindLocalShard.java index c415db6efe..b6560a8347 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/FindLocalShard.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/FindLocalShard.java @@ -14,12 +14,18 @@ package org.opendaylight.controller.cluster.datastore.messages; */ public class FindLocalShard { private final String shardName; + private final boolean waitUntilInitialized; - public FindLocalShard(String shardName) { + public FindLocalShard(String shardName, boolean waitUntilInitialized) { this.shardName = shardName; + this.waitUntilInitialized = waitUntilInitialized; } public String getShardName() { return shardName; } + + public boolean isWaitUntilInitialized() { + return waitUntilInitialized; + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/FindPrimary.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/FindPrimary.java index f5a6a34841..a34330bcf6 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/FindPrimary.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/FindPrimary.java @@ -15,26 +15,33 @@ import com.google.common.base.Preconditions; * */ public class FindPrimary implements SerializableMessage{ - public static final Class SERIALIZABLE_CLASS = FindPrimary.class; + public static final Class SERIALIZABLE_CLASS = FindPrimary.class; + private final String shardName; + private final boolean waitUntilInitialized; - public FindPrimary(String shardName){ + public FindPrimary(String shardName, boolean waitUntilInitialized){ Preconditions.checkNotNull(shardName, "shardName should not be null"); this.shardName = shardName; + this.waitUntilInitialized = waitUntilInitialized; } public String getShardName() { return shardName; } - @Override - public Object toSerializable() { - return this; - } + public boolean isWaitUntilInitialized() { + return waitUntilInitialized; + } - public static FindPrimary fromSerializable(Object message){ - return (FindPrimary) message; - } + @Override + public Object toSerializable() { + return this; + } + + public static FindPrimary fromSerializable(Object message){ + return (FindPrimary) message; + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/ForwardedReadyTransaction.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/ForwardedReadyTransaction.java index 4f8ea51f78..180108f218 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/ForwardedReadyTransaction.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/ForwardedReadyTransaction.java @@ -19,12 +19,15 @@ public class ForwardedReadyTransaction { private final String transactionID; private final DOMStoreThreePhaseCommitCohort cohort; private final Modification modification; + private final boolean returnSerialized; public ForwardedReadyTransaction(String transactionID, DOMStoreThreePhaseCommitCohort cohort, - Modification modification) { + Modification modification, boolean returnSerialized) { this.transactionID = transactionID; this.cohort = cohort; this.modification = modification; + this.returnSerialized = returnSerialized; + } public String getTransactionID() { @@ -38,4 +41,8 @@ public class ForwardedReadyTransaction { public Modification getModification() { return modification; } + + public boolean isReturnSerialized() { + return returnSerialized; + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/ActorContext.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/ActorContext.java index d8af09c86b..0a1e80b0cb 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/ActorContext.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/ActorContext.java @@ -13,17 +13,21 @@ import akka.actor.ActorRef; import akka.actor.ActorSelection; import akka.actor.ActorSystem; import akka.actor.PoisonPill; +import akka.dispatch.Mapper; import akka.util.Timeout; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import org.opendaylight.controller.cluster.datastore.ClusterWrapper; import org.opendaylight.controller.cluster.datastore.Configuration; +import org.opendaylight.controller.cluster.datastore.exceptions.LocalShardNotFoundException; import org.opendaylight.controller.cluster.datastore.exceptions.NotInitializedException; import org.opendaylight.controller.cluster.datastore.exceptions.TimeoutException; +import org.opendaylight.controller.cluster.datastore.exceptions.UnknownMessageException; import org.opendaylight.controller.cluster.datastore.messages.ActorNotInitialized; import org.opendaylight.controller.cluster.datastore.messages.FindLocalShard; import org.opendaylight.controller.cluster.datastore.messages.FindPrimary; import org.opendaylight.controller.cluster.datastore.messages.LocalShardFound; +import org.opendaylight.controller.cluster.datastore.messages.LocalShardNotFound; import org.opendaylight.controller.cluster.datastore.messages.PrimaryFound; import org.opendaylight.controller.cluster.datastore.messages.UpdateSchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaContext; @@ -33,9 +37,7 @@ import scala.concurrent.Await; import scala.concurrent.Future; import scala.concurrent.duration.Duration; import scala.concurrent.duration.FiniteDuration; - import java.util.concurrent.TimeUnit; - import static akka.pattern.Patterns.ask; /** @@ -117,14 +119,14 @@ public class ActorContext { } /** - * Finds a local shard given it's shard name and return it's ActorRef + * Finds a local shard given its shard name and return it's ActorRef * * @param shardName the name of the local shard that needs to be found * @return a reference to a local shard actor which represents the shard * specified by the shardName */ public Optional findLocalShard(String shardName) { - Object result = executeOperation(shardManager, new FindLocalShard(shardName)); + Object result = executeOperation(shardManager, new FindLocalShard(shardName, false)); if (result instanceof LocalShardFound) { LocalShardFound found = (LocalShardFound) result; @@ -135,9 +137,40 @@ public class ActorContext { return Optional.absent(); } + /** + * Finds a local shard async given its shard name and return a Future from which to obtain the + * ActorRef. + * + * @param shardName the name of the local shard that needs to be found + */ + public Future findLocalShardAsync( final String shardName, Timeout timeout) { + Future future = executeOperationAsync(shardManager, + new FindLocalShard(shardName, true), timeout); + + return future.map(new Mapper() { + @Override + public ActorRef checkedApply(Object response) throws Throwable { + if(response instanceof LocalShardFound) { + LocalShardFound found = (LocalShardFound)response; + LOG.debug("Local shard found {}", found.getPath()); + return found.getPath(); + } else if(response instanceof ActorNotInitialized) { + throw new NotInitializedException( + String.format("Found local shard for %s but it's not initialized yet.", + shardName)); + } else if(response instanceof LocalShardNotFound) { + throw new LocalShardNotFoundException( + String.format("Local shard for %s does not exist.", shardName)); + } + + throw new UnknownMessageException(String.format( + "FindLocalShard returned unkown response: %s", response)); + } + }, getActorSystem().dispatcher()); + } private String findPrimaryPathOrNull(String shardName) { - Object result = executeOperation(shardManager, new FindPrimary(shardName).toSerializable()); + Object result = executeOperation(shardManager, new FindPrimary(shardName, false).toSerializable()); if (result.getClass().equals(PrimaryFound.SERIALIZABLE_CLASS)) { PrimaryFound found = PrimaryFound.fromSerializable(result); @@ -237,6 +270,10 @@ public class ActorContext { actorSystem.shutdown(); } + public ClusterWrapper getClusterWrapper() { + return clusterWrapper; + } + public String getCurrentMemberName(){ return clusterWrapper.getCurrentMemberName(); } @@ -262,4 +299,30 @@ public class ActorContext { public FiniteDuration getOperationDuration() { return operationDuration; } + + public boolean isLocalPath(String path) { + String selfAddress = clusterWrapper.getSelfAddress(); + if (path == null || selfAddress == null) { + return false; + } + + int atIndex1 = path.indexOf("@"); + int atIndex2 = selfAddress.indexOf("@"); + + if (atIndex1 == -1 || atIndex2 == -1) { + return false; + } + + int slashIndex1 = path.indexOf("/", atIndex1); + int slashIndex2 = selfAddress.indexOf("/", atIndex2); + + if (slashIndex1 == -1 || slashIndex2 == -1) { + return false; + } + + String hostPort1 = path.substring(atIndex1, slashIndex1); + String hostPort2 = selfAddress.substring(atIndex2, slashIndex2); + + return hostPort1.equals(hostPort2); + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationProxyTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationProxyTest.java index aaf080bdf7..c27993f97b 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationProxyTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationProxyTest.java @@ -1,108 +1,258 @@ +/* + * Copyright (c) 2014 Brocade Communications 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.cluster.datastore; +import java.util.concurrent.TimeUnit; import akka.actor.ActorRef; +import akka.actor.ActorSystem; import akka.actor.Props; -import junit.framework.Assert; +import akka.actor.Terminated; +import akka.dispatch.ExecutionContexts; +import akka.dispatch.Futures; +import akka.testkit.JavaTestKit; +import akka.util.Timeout; +import org.junit.Assert; import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.opendaylight.controller.cluster.datastore.messages.ActorNotInitialized; import org.opendaylight.controller.cluster.datastore.messages.CloseDataChangeListenerRegistration; +import org.opendaylight.controller.cluster.datastore.messages.FindLocalShard; +import org.opendaylight.controller.cluster.datastore.messages.LocalShardFound; +import org.opendaylight.controller.cluster.datastore.messages.LocalShardNotFound; +import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListener; +import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListenerReply; import org.opendaylight.controller.cluster.datastore.utils.ActorContext; import org.opendaylight.controller.cluster.datastore.utils.DoNothingActor; -import org.opendaylight.controller.cluster.datastore.utils.MessageCollectorActor; -import org.opendaylight.controller.cluster.datastore.utils.MockClusterWrapper; -import org.opendaylight.controller.cluster.datastore.utils.MockConfiguration; -import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent; +import org.opendaylight.controller.md.cluster.datastore.model.TestModel; +import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker; +import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.Uninterruptibles; +import scala.concurrent.ExecutionContextExecutor; +import scala.concurrent.Future; +import scala.concurrent.duration.FiniteDuration; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; -import java.util.List; +/** + * Unit tests for DataChangeListenerRegistrationProxy. + * + * @author Thomas Pantelis + */ +public class DataChangeListenerRegistrationProxyTest extends AbstractActorTest { -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertNotNull; -import static junit.framework.TestCase.assertTrue; + @SuppressWarnings("unchecked") + private final AsyncDataChangeListener> mockListener = + Mockito.mock(AsyncDataChangeListener.class); -public class DataChangeListenerRegistrationProxyTest extends AbstractActorTest{ + @Test + public void testGetInstance() throws Exception { + DataChangeListenerRegistrationProxy proxy = new DataChangeListenerRegistrationProxy( + "shard", Mockito.mock(ActorContext.class), mockListener); + + Assert.assertEquals(mockListener, proxy.getInstance()); + } + + @SuppressWarnings("unchecked") + @Test(timeout=10000) + public void testSuccessfulRegistration() { + new JavaTestKit(getSystem()) {{ + ActorContext actorContext = new ActorContext(getSystem(), getRef(), + mock(ClusterWrapper.class), mock(Configuration.class)); + + final DataChangeListenerRegistrationProxy proxy = new DataChangeListenerRegistrationProxy( + "shard-1", actorContext, mockListener); + + final YangInstanceIdentifier path = YangInstanceIdentifier.of(TestModel.TEST_QNAME); + final DataChangeScope scope = AsyncDataBroker.DataChangeScope.ONE; + new Thread() { + @Override + public void run() { + proxy.init(path, scope); + } + + }.start(); + + FiniteDuration timeout = duration("5 seconds"); + FindLocalShard findLocalShard = expectMsgClass(timeout, FindLocalShard.class); + Assert.assertEquals("getShardName", "shard-1", findLocalShard.getShardName()); + + reply(new LocalShardFound(getRef())); + + RegisterChangeListener registerMsg = expectMsgClass(timeout, RegisterChangeListener.class); + Assert.assertEquals("getPath", path, registerMsg.getPath()); + Assert.assertEquals("getScope", scope, registerMsg.getScope()); + + reply(new RegisterChangeListenerReply(getRef().path())); + + for(int i = 0; (i < 20 * 5) && proxy.getListenerRegistrationActor() == null; i++) { + Uninterruptibles.sleepUninterruptibly(50, TimeUnit.MILLISECONDS); + } + + Assert.assertEquals("getListenerRegistrationActor", getSystem().actorSelection(getRef().path()), + proxy.getListenerRegistrationActor()); + + watch(proxy.getDataChangeListenerActor()); - private ActorRef dataChangeListenerActor = getSystem().actorOf(Props.create(DoNothingActor.class)); + proxy.close(); - private static class MockDataChangeListener implements - AsyncDataChangeListener> { + // The listener registration actor should get a Close message + expectMsgClass(timeout, CloseDataChangeListenerRegistration.SERIALIZABLE_CLASS); - @Override public void onDataChanged( - AsyncDataChangeEvent> change) { - throw new UnsupportedOperationException("onDataChanged"); - } + // The DataChangeListener actor should be terminated + expectMsgClass(timeout, Terminated.class); + + proxy.close(); + + expectNoMsg(); + }}; } - @Test - public void testGetInstance() throws Exception { - final Props props = Props.create(MessageCollectorActor.class); - final ActorRef actorRef = getSystem().actorOf(props); + @Test(timeout=10000) + public void testLocalShardNotFound() { + new JavaTestKit(getSystem()) {{ + ActorContext actorContext = new ActorContext(getSystem(), getRef(), + mock(ClusterWrapper.class), mock(Configuration.class)); - MockDataChangeListener listener = - new MockDataChangeListener(); - DataChangeListenerRegistrationProxy proxy = - new DataChangeListenerRegistrationProxy( - getSystem().actorSelection(actorRef.path()), - listener, dataChangeListenerActor); + final DataChangeListenerRegistrationProxy proxy = new DataChangeListenerRegistrationProxy( + "shard-1", actorContext, mockListener); - Assert.assertEquals(listener, proxy.getInstance()); + final YangInstanceIdentifier path = YangInstanceIdentifier.of(TestModel.TEST_QNAME); + final DataChangeScope scope = AsyncDataBroker.DataChangeScope.ONE; + new Thread() { + @Override + public void run() { + proxy.init(path, scope); + } + }.start(); + + FiniteDuration timeout = duration("5 seconds"); + FindLocalShard findLocalShard = expectMsgClass(timeout, FindLocalShard.class); + Assert.assertEquals("getShardName", "shard-1", findLocalShard.getShardName()); + + reply(new LocalShardNotFound("shard-1")); + + expectNoMsg(duration("1 seconds")); + }}; } - @Test - public void testClose() throws Exception { - final Props props = Props.create(MessageCollectorActor.class); - final ActorRef actorRef = getSystem().actorOf(props); + @Test(timeout=10000) + public void testLocalShardNotInitialized() { + new JavaTestKit(getSystem()) {{ + ActorContext actorContext = new ActorContext(getSystem(), getRef(), + mock(ClusterWrapper.class), mock(Configuration.class)); - DataChangeListenerRegistrationProxy proxy = - new DataChangeListenerRegistrationProxy( - getSystem().actorSelection(actorRef.path()), - new MockDataChangeListener(), dataChangeListenerActor); + final DataChangeListenerRegistrationProxy proxy = new DataChangeListenerRegistrationProxy( + "shard-1", actorContext, mockListener); - proxy.close(); + final YangInstanceIdentifier path = YangInstanceIdentifier.of(TestModel.TEST_QNAME); + final DataChangeScope scope = AsyncDataBroker.DataChangeScope.ONE; + new Thread() { + @Override + public void run() { + proxy.init(path, scope); + } + + }.start(); + + FiniteDuration timeout = duration("5 seconds"); + FindLocalShard findLocalShard = expectMsgClass(timeout, FindLocalShard.class); + Assert.assertEquals("getShardName", "shard-1", findLocalShard.getShardName()); + + reply(new ActorNotInitialized()); + + new Within(duration("1 seconds")) { + @Override + protected void run() { + expectNoMsg(); + } + }; + }}; + } + + @Test + public void testFailedRegistration() { + new JavaTestKit(getSystem()) {{ + ActorSystem mockActorSystem = mock(ActorSystem.class); - //Check if it was received by the remote actor - ActorContext - testContext = new ActorContext(getSystem(), getSystem().actorOf(Props.create(DoNothingActor.class)),new MockClusterWrapper(), new MockConfiguration()); - Object messages = testContext - .executeOperation(actorRef, "messages"); + ActorRef mockActor = getSystem().actorOf(Props.create(DoNothingActor.class), + "testFailedRegistration"); + doReturn(mockActor).when(mockActorSystem).actorOf(any(Props.class)); + ExecutionContextExecutor executor = ExecutionContexts.fromExecutor( + MoreExecutors.sameThreadExecutor()); + doReturn(executor).when(mockActorSystem).dispatcher(); - assertNotNull(messages); + ActorContext actorContext = mock(ActorContext.class); - assertTrue(messages instanceof List); + String shardName = "shard-1"; + final DataChangeListenerRegistrationProxy proxy = new DataChangeListenerRegistrationProxy( + shardName, actorContext, mockListener); - List listMessages = (List) messages; + doReturn(mockActorSystem).when(actorContext).getActorSystem(); + doReturn(duration("5 seconds")).when(actorContext).getOperationDuration(); + doReturn(Futures.successful(getRef())).when(actorContext).findLocalShardAsync(eq(shardName), + any(Timeout.class)); + doReturn(Futures.failed(new RuntimeException("mock"))). + when(actorContext).executeOperationAsync(any(ActorRef.class), + any(Object.class), any(Timeout.class)); - assertEquals(1, listMessages.size()); + proxy.init(YangInstanceIdentifier.of(TestModel.TEST_QNAME), + AsyncDataBroker.DataChangeScope.ONE); - assertTrue(listMessages.get(0).getClass() - .equals(CloseDataChangeListenerRegistration.SERIALIZABLE_CLASS)); + Assert.assertEquals("getListenerRegistrationActor", null, + proxy.getListenerRegistrationActor()); + }}; } + @SuppressWarnings("unchecked") @Test - public void testCloseWhenRegistrationIsNull() throws Exception { - final Props props = Props.create(MessageCollectorActor.class); - final ActorRef actorRef = getSystem().actorOf(props); + public void testCloseBeforeRegistration() { + new JavaTestKit(getSystem()) {{ + ActorContext actorContext = mock(ActorContext.class); - DataChangeListenerRegistrationProxy proxy = - new DataChangeListenerRegistrationProxy( - new MockDataChangeListener(), dataChangeListenerActor); + String shardName = "shard-1"; + final DataChangeListenerRegistrationProxy proxy = new DataChangeListenerRegistrationProxy( + shardName, actorContext, mockListener); - proxy.close(); + doReturn(getSystem()).when(actorContext).getActorSystem(); + doReturn(getSystem().actorSelection(getRef().path())). + when(actorContext).actorSelection(getRef().path()); + doReturn(duration("5 seconds")).when(actorContext).getOperationDuration(); + doReturn(Futures.successful(getRef())).when(actorContext).findLocalShardAsync(eq(shardName), + any(Timeout.class)); - //Check if it was received by the remote actor - ActorContext - testContext = new ActorContext(getSystem(), getSystem().actorOf(Props.create(DoNothingActor.class)),new MockClusterWrapper(), new MockConfiguration()); - Object messages = testContext - .executeOperation(actorRef, "messages"); + Answer> answer = new Answer>() { + @Override + public Future answer(InvocationOnMock invocation) { + proxy.close(); + return Futures.successful((Object)new RegisterChangeListenerReply(getRef().path())); + } + }; - assertNotNull(messages); + doAnswer(answer).when(actorContext).executeOperationAsync(any(ActorRef.class), + any(Object.class), any(Timeout.class)); - assertTrue(messages instanceof List); + proxy.init(YangInstanceIdentifier.of(TestModel.TEST_QNAME), + AsyncDataBroker.DataChangeScope.ONE); - List listMessages = (List) messages; + expectMsgClass(duration("5 seconds"), CloseDataChangeListenerRegistration.SERIALIZABLE_CLASS); - assertEquals(0, listMessages.size()); + Assert.assertEquals("getListenerRegistrationActor", null, + proxy.getListenerRegistrationActor()); + }}; } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreIntegrationTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreIntegrationTest.java index 395021d361..1cc7ae8ad0 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreIntegrationTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreIntegrationTest.java @@ -10,15 +10,18 @@ import static org.junit.Assert.assertNotNull; import org.junit.Test; import org.opendaylight.controller.cluster.datastore.shardstrategy.ShardStrategyFactory; import org.opendaylight.controller.cluster.datastore.utils.MockClusterWrapper; +import org.opendaylight.controller.cluster.datastore.utils.MockDataChangeListener; import org.opendaylight.controller.md.cluster.datastore.model.CarsModel; import org.opendaylight.controller.md.cluster.datastore.model.PeopleModel; import org.opendaylight.controller.md.cluster.datastore.model.SchemaContextHelper; import org.opendaylight.controller.md.cluster.datastore.model.TestModel; +import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope; 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.DOMStoreTransactionChain; import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction; +import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; @@ -204,6 +207,45 @@ public class DistributedDataStoreIntegrationTest extends AbstractActorTest { }}; } + @Test + public void testChangeListenerRegistration() throws Exception{ + new IntegrationTestKit(getSystem()) {{ + DistributedDataStore dataStore = + setupDistributedDataStore("testChangeListenerRegistration", "test-1"); + + MockDataChangeListener listener = new MockDataChangeListener(3); + + ListenerRegistration + listenerReg = dataStore.registerChangeListener(TestModel.TEST_PATH, listener, + DataChangeScope.SUBTREE); + + assertNotNull("registerChangeListener returned null", listenerReg); + + testWriteTransaction(dataStore, TestModel.TEST_PATH, + ImmutableNodes.containerNode(TestModel.TEST_QNAME)); + + testWriteTransaction(dataStore, TestModel.OUTER_LIST_PATH, + ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME).build()); + + YangInstanceIdentifier listPath = YangInstanceIdentifier.builder(TestModel.OUTER_LIST_PATH). + nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1).build(); + testWriteTransaction(dataStore, listPath, + ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1)); + + listener.waitForChangeEvents(TestModel.TEST_PATH, TestModel.OUTER_LIST_PATH, listPath ); + + listenerReg.close(); + + testWriteTransaction(dataStore, YangInstanceIdentifier.builder(TestModel.OUTER_LIST_PATH). + nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2).build(), + ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2)); + + listener.expectNoMoreChanges("Received unexpected change after close"); + + cleanup(dataStore); + }}; + } + class IntegrationTestKit extends ShardTestKit { IntegrationTestKit(ActorSystem actorSystem) { diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreTest.java deleted file mode 100644 index 00243ea5d1..0000000000 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreTest.java +++ /dev/null @@ -1,244 +0,0 @@ -package org.opendaylight.controller.cluster.datastore; - -import akka.actor.ActorPath; -import akka.actor.ActorRef; -import akka.actor.ActorSelection; -import akka.actor.ActorSystem; -import akka.actor.Props; -import akka.dispatch.ExecutionContexts; -import akka.dispatch.Futures; -import akka.util.Timeout; -import com.google.common.base.Optional; -import com.google.common.util.concurrent.MoreExecutors; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListenerReply; -import org.opendaylight.controller.cluster.datastore.shardstrategy.ShardStrategyFactory; -import org.opendaylight.controller.cluster.datastore.utils.ActorContext; -import org.opendaylight.controller.cluster.datastore.utils.DoNothingActor; -import org.opendaylight.controller.cluster.datastore.utils.MockActorContext; -import org.opendaylight.controller.cluster.datastore.utils.MockConfiguration; -import org.opendaylight.controller.md.cluster.datastore.model.TestModel; -import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker; -import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent; -import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener; -import org.opendaylight.controller.protobuff.messages.transaction.ShardTransactionMessages.CreateTransactionReply; -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.DOMStoreTransactionChain; -import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction; -import org.opendaylight.yangtools.concepts.ListenerRegistration; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import scala.concurrent.ExecutionContextExecutor; -import scala.concurrent.Future; -import scala.concurrent.duration.FiniteDuration; -import java.util.concurrent.TimeUnit; -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertNull; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class DistributedDataStoreTest extends AbstractActorTest{ - - private DistributedDataStore distributedDataStore; - private MockActorContext mockActorContext; - private ActorRef doNothingActorRef; - - @Before - public void setUp() throws Exception { - ShardStrategyFactory.setConfiguration(new MockConfiguration()); - final Props props = Props.create(DoNothingActor.class); - - doNothingActorRef = getSystem().actorOf(props); - - mockActorContext = new MockActorContext(getSystem(), doNothingActorRef); - distributedDataStore = new DistributedDataStore(mockActorContext); - distributedDataStore.onGlobalContextUpdated( - TestModel.createTestContext()); - - // Make CreateTransactionReply as the default response. Will need to be - // tuned if a specific test requires some other response - mockActorContext.setExecuteShardOperationResponse( - CreateTransactionReply.newBuilder() - .setTransactionActorPath(doNothingActorRef.path().toString()) - .setTransactionId("txn-1 ") - .build()); - } - - @After - public void tearDown() throws Exception { - - } - - @SuppressWarnings("resource") - @Test - public void testConstructor(){ - ActorSystem actorSystem = mock(ActorSystem.class); - - new DistributedDataStore(actorSystem, "config", - mock(ClusterWrapper.class), mock(Configuration.class), - DatastoreContext.newBuilder().build()); - - verify(actorSystem).actorOf(any(Props.class), eq("shardmanager-config")); - } - - @Test - public void testRegisterChangeListenerWhenShardIsNotLocal() throws Exception { - - ListenerRegistration registration = - distributedDataStore.registerChangeListener(TestModel.TEST_PATH, new AsyncDataChangeListener>() { - @Override - public void onDataChanged(AsyncDataChangeEvent> change) { - throw new UnsupportedOperationException("onDataChanged"); - } - }, AsyncDataBroker.DataChangeScope.BASE); - - // Since we do not expect the shard to be local registration will return a NoOpRegistration - assertTrue(registration instanceof NoOpDataChangeListenerRegistration); - - assertNotNull(registration); - } - - @Test - public void testRegisterChangeListenerWhenShardIsLocal() throws Exception { - ActorContext actorContext = mock(ActorContext.class); - - distributedDataStore = new DistributedDataStore(actorContext); - distributedDataStore.onGlobalContextUpdated(TestModel.createTestContext()); - - Future future = mock(Future.class); - when(actorContext.getOperationDuration()).thenReturn(FiniteDuration.apply(5, TimeUnit.SECONDS)); - when(actorContext.getActorSystem()).thenReturn(getSystem()); - when(actorContext.findLocalShard(anyString())).thenReturn(Optional.of(doNothingActorRef)); - when(actorContext - .executeOperationAsync(eq(doNothingActorRef), anyObject(), any(Timeout.class))).thenReturn(future); - - ListenerRegistration registration = - distributedDataStore.registerChangeListener(TestModel.TEST_PATH, - mock(AsyncDataChangeListener.class), - AsyncDataBroker.DataChangeScope.BASE); - - assertNotNull(registration); - - assertEquals(DataChangeListenerRegistrationProxy.class, registration.getClass()); - } - - @Test - public void testRegisterChangeListenerWhenSuccessfulReplyReceived() throws Exception { - ActorContext actorContext = mock(ActorContext.class); - - distributedDataStore = new DistributedDataStore(actorContext); - distributedDataStore.onGlobalContextUpdated( - TestModel.createTestContext()); - - ExecutionContextExecutor executor = ExecutionContexts.fromExecutor(MoreExecutors.sameThreadExecutor()); - - // Make Future successful - Future f = Futures.successful(new RegisterChangeListenerReply(doNothingActorRef.path())); - - // Setup the mocks - ActorSystem actorSystem = mock(ActorSystem.class); - ActorSelection actorSelection = mock(ActorSelection.class); - - when(actorContext.getOperationDuration()).thenReturn(FiniteDuration.apply(5, TimeUnit.SECONDS)); - when(actorSystem.dispatcher()).thenReturn(executor); - when(actorSystem.actorOf(any(Props.class))).thenReturn(doNothingActorRef); - when(actorContext.getActorSystem()).thenReturn(actorSystem); - when(actorContext.findLocalShard(anyString())).thenReturn(Optional.of(doNothingActorRef)); - when(actorContext - .executeOperationAsync(eq(doNothingActorRef), anyObject(), any(Timeout.class))).thenReturn(f); - when(actorContext.actorSelection(any(ActorPath.class))).thenReturn(actorSelection); - - ListenerRegistration registration = - distributedDataStore.registerChangeListener(TestModel.TEST_PATH, - mock(AsyncDataChangeListener.class), - AsyncDataBroker.DataChangeScope.BASE); - - assertNotNull(registration); - - assertEquals(DataChangeListenerRegistrationProxy.class, registration.getClass()); - - ActorSelection listenerRegistrationActor = - ((DataChangeListenerRegistrationProxy) registration).getListenerRegistrationActor(); - - assertNotNull(listenerRegistrationActor); - - assertEquals(actorSelection, listenerRegistrationActor); - } - - @Test - public void testRegisterChangeListenerWhenSuccessfulReplyFailed() throws Exception { - ActorContext actorContext = mock(ActorContext.class); - - distributedDataStore = new DistributedDataStore(actorContext); - distributedDataStore.onGlobalContextUpdated( - TestModel.createTestContext()); - - ExecutionContextExecutor executor = ExecutionContexts.fromExecutor(MoreExecutors.sameThreadExecutor()); - - // Make Future fail - Future f = Futures.failed(new IllegalArgumentException()); - - // Setup the mocks - ActorSystem actorSystem = mock(ActorSystem.class); - ActorSelection actorSelection = mock(ActorSelection.class); - - when(actorContext.getOperationDuration()).thenReturn(FiniteDuration.apply(5, TimeUnit.SECONDS)); - when(actorSystem.dispatcher()).thenReturn(executor); - when(actorSystem.actorOf(any(Props.class))).thenReturn(doNothingActorRef); - when(actorContext.getActorSystem()).thenReturn(actorSystem); - when(actorContext.findLocalShard(anyString())).thenReturn(Optional.of(doNothingActorRef)); - when(actorContext - .executeOperationAsync(eq(doNothingActorRef), anyObject(), any(Timeout.class))).thenReturn(f); - when(actorContext.actorSelection(any(ActorPath.class))).thenReturn(actorSelection); - - ListenerRegistration registration = - distributedDataStore.registerChangeListener(TestModel.TEST_PATH, - mock(AsyncDataChangeListener.class), - AsyncDataBroker.DataChangeScope.BASE); - - assertNotNull(registration); - - assertEquals(DataChangeListenerRegistrationProxy.class, registration.getClass()); - - ActorSelection listenerRegistrationActor = - ((DataChangeListenerRegistrationProxy) registration).getListenerRegistrationActor(); - - assertNull(listenerRegistrationActor); - - } - - - @Test - public void testCreateTransactionChain() throws Exception { - final DOMStoreTransactionChain transactionChain = distributedDataStore.createTransactionChain(); - assertNotNull(transactionChain); - } - - @Test - public void testNewReadOnlyTransaction() throws Exception { - final DOMStoreReadTransaction transaction = distributedDataStore.newReadOnlyTransaction(); - assertNotNull(transaction); - } - - @Test - public void testNewWriteOnlyTransaction() throws Exception { - final DOMStoreWriteTransaction transaction = distributedDataStore.newWriteOnlyTransaction(); - assertNotNull(transaction); - } - - @Test - public void testNewReadWriteTransaction() throws Exception { - final DOMStoreReadWriteTransaction transaction = distributedDataStore.newReadWriteTransaction(); - assertNotNull(transaction); - } -} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardManagerTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardManagerTest.java index 5022d97997..c04dcf1534 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardManagerTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardManagerTest.java @@ -2,9 +2,11 @@ package org.opendaylight.controller.cluster.datastore; import akka.actor.ActorRef; import akka.actor.Props; +import akka.pattern.Patterns; import akka.persistence.RecoveryCompleted; import akka.testkit.JavaTestKit; import akka.testkit.TestActorRef; +import akka.util.Timeout; import akka.japi.Creator; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; @@ -29,6 +31,8 @@ import org.opendaylight.controller.cluster.datastore.utils.MockConfiguration; import org.opendaylight.controller.md.cluster.datastore.model.TestModel; import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier; import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import scala.concurrent.Await; +import scala.concurrent.Future; import java.net.URI; import java.util.Collection; import java.util.HashSet; @@ -76,7 +80,7 @@ public class ShardManagerTest extends AbstractActorTest { shardManager.tell(new UpdateSchemaContext(TestModel.createTestContext()), getRef()); - shardManager.tell(new FindPrimary("non-existent").toSerializable(), getRef()); + shardManager.tell(new FindPrimary("non-existent", false).toSerializable(), getRef()); expectMsgEquals(duration("5 seconds"), new PrimaryNotFound("non-existent").toSerializable()); @@ -91,25 +95,44 @@ public class ShardManagerTest extends AbstractActorTest { shardManager.tell(new UpdateSchemaContext(TestModel.createTestContext()), getRef()); shardManager.tell(new ActorInitialized(), mockShardActor); - shardManager.tell(new FindPrimary(Shard.DEFAULT_NAME).toSerializable(), getRef()); + shardManager.tell(new FindPrimary(Shard.DEFAULT_NAME, false).toSerializable(), getRef()); expectMsgClass(duration("5 seconds"), PrimaryFound.SERIALIZABLE_CLASS); }}; } @Test - public void testOnReceiveFindPrimaryForNotInitialzedShard() throws Exception { + public void testOnReceiveFindPrimaryForNotInitializedShard() throws Exception { new JavaTestKit(getSystem()) {{ final ActorRef shardManager = getSystem().actorOf(newShardMgrProps()); shardManager.tell(new UpdateSchemaContext(TestModel.createTestContext()), getRef()); - shardManager.tell(new FindPrimary(Shard.DEFAULT_NAME).toSerializable(), getRef()); + shardManager.tell(new FindPrimary(Shard.DEFAULT_NAME, false).toSerializable(), getRef()); expectMsgClass(duration("5 seconds"), ActorNotInitialized.class); }}; } + @Test + public void testOnReceiveFindPrimaryWaitForShardInitialized() throws Exception { + new JavaTestKit(getSystem()) {{ + final ActorRef shardManager = getSystem().actorOf(newShardMgrProps()); + + shardManager.tell(new UpdateSchemaContext(TestModel.createTestContext()), getRef()); + + // We're passing waitUntilInitialized = true to FindPrimary so the response should be + // delayed until we send ActorInitialized. + Future future = Patterns.ask(shardManager, new FindPrimary(Shard.DEFAULT_NAME, true), + new Timeout(5, TimeUnit.SECONDS)); + + shardManager.tell(new ActorInitialized(), mockShardActor); + + Object resp = Await.result(future, duration("5 seconds")); + assertTrue("Expected: PrimaryFound, Actual: " + resp, resp instanceof PrimaryFound); + }}; + } + @Test public void testOnReceiveFindLocalShardForNonExistentShard() throws Exception { new JavaTestKit(getSystem()) {{ @@ -117,7 +140,7 @@ public class ShardManagerTest extends AbstractActorTest { shardManager.tell(new UpdateSchemaContext(TestModel.createTestContext()), getRef()); - shardManager.tell(new FindLocalShard("non-existent"), getRef()); + shardManager.tell(new FindLocalShard("non-existent", false), getRef()); LocalShardNotFound notFound = expectMsgClass(duration("5 seconds"), LocalShardNotFound.class); @@ -133,7 +156,7 @@ public class ShardManagerTest extends AbstractActorTest { shardManager.tell(new UpdateSchemaContext(TestModel.createTestContext()), getRef()); shardManager.tell(new ActorInitialized(), mockShardActor); - shardManager.tell(new FindLocalShard(Shard.DEFAULT_NAME), getRef()); + shardManager.tell(new FindLocalShard(Shard.DEFAULT_NAME, false), getRef()); LocalShardFound found = expectMsgClass(duration("5 seconds"), LocalShardFound.class); @@ -148,14 +171,32 @@ public class ShardManagerTest extends AbstractActorTest { final ActorRef shardManager = getSystem().actorOf(newShardMgrProps()); shardManager.tell(new UpdateSchemaContext(TestModel.createTestContext()), getRef()); - //shardManager.tell(new ActorInitialized(), mockShardActor); - shardManager.tell(new FindLocalShard(Shard.DEFAULT_NAME), getRef()); + shardManager.tell(new FindLocalShard(Shard.DEFAULT_NAME, false), getRef()); expectMsgClass(duration("5 seconds"), ActorNotInitialized.class); }}; } + @Test + public void testOnReceiveFindLocalShardWaitForShardInitialized() throws Exception { + new JavaTestKit(getSystem()) {{ + final ActorRef shardManager = getSystem().actorOf(newShardMgrProps()); + + shardManager.tell(new UpdateSchemaContext(TestModel.createTestContext()), getRef()); + + // We're passing waitUntilInitialized = true to FindLocalShard so the response should be + // delayed until we send ActorInitialized. + Future future = Patterns.ask(shardManager, new FindLocalShard(Shard.DEFAULT_NAME, true), + new Timeout(5, TimeUnit.SECONDS)); + + shardManager.tell(new ActorInitialized(), mockShardActor); + + Object resp = Await.result(future, duration("5 seconds")); + assertTrue("Expected: LocalShardFound, Actual: " + resp, resp instanceof LocalShardFound); + }}; + } + @Test public void testOnReceiveMemberUp() throws Exception { new JavaTestKit(getSystem()) {{ @@ -163,7 +204,7 @@ public class ShardManagerTest extends AbstractActorTest { MockClusterWrapper.sendMemberUp(shardManager, "member-2", getRef().path().toString()); - shardManager.tell(new FindPrimary("astronauts").toSerializable(), getRef()); + shardManager.tell(new FindPrimary("astronauts", false).toSerializable(), getRef()); PrimaryFound found = PrimaryFound.fromSerializable(expectMsgClass(duration("5 seconds"), PrimaryFound.SERIALIZABLE_CLASS)); @@ -180,13 +221,13 @@ public class ShardManagerTest extends AbstractActorTest { MockClusterWrapper.sendMemberUp(shardManager, "member-2", getRef().path().toString()); - shardManager.tell(new FindPrimary("astronauts").toSerializable(), getRef()); + shardManager.tell(new FindPrimary("astronauts", false).toSerializable(), getRef()); expectMsgClass(duration("5 seconds"), PrimaryFound.SERIALIZABLE_CLASS); MockClusterWrapper.sendMemberRemoved(shardManager, "member-2", getRef().path().toString()); - shardManager.tell(new FindPrimary("astronauts").toSerializable(), getRef()); + shardManager.tell(new FindPrimary("astronauts", false).toSerializable(), getRef()); expectMsgClass(duration("5 seconds"), PrimaryNotFound.SERIALIZABLE_CLASS); }}; diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTest.java index 9b4f77b7c4..03a18ea6c3 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTest.java @@ -1,12 +1,12 @@ package org.opendaylight.controller.cluster.datastore; import akka.actor.ActorRef; +import akka.actor.PoisonPill; import akka.actor.Props; import akka.dispatch.Dispatchers; import akka.dispatch.OnComplete; import akka.japi.Creator; import akka.pattern.Patterns; -import akka.testkit.JavaTestKit; import akka.testkit.TestActorRef; import akka.util.Timeout; import com.google.common.base.Function; @@ -15,6 +15,7 @@ import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.Uninterruptibles; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -29,7 +30,6 @@ import org.opendaylight.controller.cluster.datastore.messages.CanCommitTransacti import org.opendaylight.controller.cluster.datastore.messages.CommitTransaction; import org.opendaylight.controller.cluster.datastore.messages.CommitTransactionReply; import org.opendaylight.controller.cluster.datastore.messages.CreateTransaction; -import org.opendaylight.controller.cluster.datastore.messages.EnableNotification; import org.opendaylight.controller.cluster.datastore.messages.ForwardedReadyTransaction; import org.opendaylight.controller.cluster.datastore.messages.PeerAddressResolved; import org.opendaylight.controller.cluster.datastore.messages.ReadyTransactionReply; @@ -43,6 +43,7 @@ import org.opendaylight.controller.cluster.datastore.modification.WriteModificat import org.opendaylight.controller.cluster.datastore.node.NormalizedNodeToNodeCodec; import org.opendaylight.controller.cluster.datastore.utils.InMemoryJournal; import org.opendaylight.controller.cluster.datastore.utils.InMemorySnapshotStore; +import org.opendaylight.controller.cluster.datastore.utils.MockDataChangeListener; import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry; import org.opendaylight.controller.cluster.raft.ReplicatedLogImplEntry; import org.opendaylight.controller.cluster.raft.Snapshot; @@ -50,6 +51,9 @@ import org.opendaylight.controller.cluster.raft.base.messages.ApplyLogEntries; import org.opendaylight.controller.cluster.raft.base.messages.ApplySnapshot; import org.opendaylight.controller.cluster.raft.base.messages.ApplyState; import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshot; +import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout; +import org.opendaylight.controller.cluster.raft.client.messages.FindLeader; +import org.opendaylight.controller.cluster.raft.client.messages.FindLeaderReply; import org.opendaylight.controller.cluster.raft.protobuff.client.messages.CompositeModificationPayload; import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload; import org.opendaylight.controller.md.cluster.datastore.model.SchemaContextHelper; @@ -78,6 +82,7 @@ import scala.concurrent.duration.FiniteDuration; import java.io.IOException; import java.util.Collections; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -86,6 +91,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import static org.junit.Assert.assertEquals; 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 static org.mockito.Mockito.mock; @@ -97,17 +103,14 @@ public class ShardTest extends AbstractActorTest { private static final SchemaContext SCHEMA_CONTEXT = TestModel.createTestContext(); - private static final ShardIdentifier IDENTIFIER = ShardIdentifier.builder().memberName("member-1") - .shardName("inventory").type("config").build(); - private static final AtomicInteger NEXT_SHARD_NUM = new AtomicInteger(); - private static String shardName() { - return "shard" + NEXT_SHARD_NUM.getAndIncrement(); - } + private final ShardIdentifier shardID = ShardIdentifier.builder().memberName("member-1") + .shardName("inventory").type("config" + NEXT_SHARD_NUM.getAndIncrement()).build(); private DatastoreContext dataStoreContext = DatastoreContext.newBuilder(). - shardJournalRecoveryLogBatchSize(3).shardSnapshotBatchCount(5000).build(); + shardJournalRecoveryLogBatchSize(3).shardSnapshotBatchCount(5000). + shardHeartbeatIntervalInMillis(100).build(); @Before public void setUp() { @@ -124,40 +127,149 @@ public class ShardTest extends AbstractActorTest { } private Props newShardProps() { - return Shard.props(IDENTIFIER, Collections.emptyMap(), + return Shard.props(shardID, Collections.emptyMap(), dataStoreContext, SCHEMA_CONTEXT); } @Test - public void testOnReceiveRegisterListener() throws Exception { - new JavaTestKit(getSystem()) {{ - ActorRef subject = getSystem().actorOf(newShardProps(), "testRegisterChangeListener"); + public void testRegisterChangeListener() throws Exception { + new ShardTestKit(getSystem()) {{ + TestActorRef shard = TestActorRef.create(getSystem(), + newShardProps(), "testRegisterChangeListener"); - subject.tell(new UpdateSchemaContext(SchemaContextHelper.full()), getRef()); + waitUntilLeader(shard); - subject.tell(new RegisterChangeListener(TestModel.TEST_PATH, - getRef().path(), AsyncDataBroker.DataChangeScope.BASE), getRef()); + shard.tell(new UpdateSchemaContext(SchemaContextHelper.full()), ActorRef.noSender()); - EnableNotification enable = expectMsgClass(duration("3 seconds"), EnableNotification.class); - assertEquals("isEnabled", false, enable.isEnabled()); + MockDataChangeListener listener = new MockDataChangeListener(1); + ActorRef dclActor = getSystem().actorOf(DataChangeListener.props(listener), + "testRegisterChangeListener-DataChangeListener"); + + shard.tell(new RegisterChangeListener(TestModel.TEST_PATH, + dclActor.path(), AsyncDataBroker.DataChangeScope.BASE), getRef()); RegisterChangeListenerReply reply = expectMsgClass(duration("3 seconds"), RegisterChangeListenerReply.class); - assertTrue(reply.getListenerRegistrationPath().toString().matches( + String replyPath = reply.getListenerRegistrationPath().toString(); + assertTrue("Incorrect reply path: " + replyPath, replyPath.matches( "akka:\\/\\/test\\/user\\/testRegisterChangeListener\\/\\$.*")); + + YangInstanceIdentifier path = TestModel.TEST_PATH; + writeToStore(shard, path, ImmutableNodes.containerNode(TestModel.TEST_QNAME)); + + listener.waitForChangeEvents(path); + + dclActor.tell(PoisonPill.getInstance(), ActorRef.noSender()); + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); + }}; + } + + @SuppressWarnings("serial") + @Test + public void testChangeListenerNotifiedWhenNotTheLeaderOnRegistration() throws Exception { + // This test tests the timing window in which a change listener is registered before the + // shard becomes the leader. We verify that the listener is registered and notified of the + // existing data when the shard becomes the leader. + new ShardTestKit(getSystem()) {{ + // For this test, we want to send the RegisterChangeListener message after the shard + // has recovered from persistence and before it becomes the leader. So we subclass + // Shard to override onReceiveCommand and, when the first ElectionTimeout is received, + // we know that the shard has been initialized to a follower and has started the + // election process. The following 2 CountDownLatches are used to coordinate the + // ElectionTimeout with the sending of the RegisterChangeListener message. + final CountDownLatch onFirstElectionTimeout = new CountDownLatch(1); + final CountDownLatch onChangeListenerRegistered = new CountDownLatch(1); + Creator creator = new Creator() { + boolean firstElectionTimeout = true; + + @Override + public Shard create() throws Exception { + return new Shard(shardID, Collections.emptyMap(), + dataStoreContext, SCHEMA_CONTEXT) { + @Override + public void onReceiveCommand(final Object message) { + if(message instanceof ElectionTimeout && firstElectionTimeout) { + // Got the first ElectionTimeout. We don't forward it to the + // base Shard yet until we've sent the RegisterChangeListener + // message. So we signal the onFirstElectionTimeout latch to tell + // the main thread to send the RegisterChangeListener message and + // start a thread to wait on the onChangeListenerRegistered latch, + // which the main thread signals after it has sent the message. + // After the onChangeListenerRegistered is triggered, we send the + // original ElectionTimeout message to proceed with the election. + firstElectionTimeout = false; + final ActorRef self = getSelf(); + new Thread() { + @Override + public void run() { + Uninterruptibles.awaitUninterruptibly( + onChangeListenerRegistered, 5, TimeUnit.SECONDS); + self.tell(message, self); + } + }.start(); + + onFirstElectionTimeout.countDown(); + } else { + super.onReceiveCommand(message); + } + } + }; + } + }; + + MockDataChangeListener listener = new MockDataChangeListener(1); + ActorRef dclActor = getSystem().actorOf(DataChangeListener.props(listener), + "testRegisterChangeListenerWhenNotLeaderInitially-DataChangeListener"); + + TestActorRef shard = TestActorRef.create(getSystem(), + Props.create(new DelegatingShardCreator(creator)), + "testRegisterChangeListenerWhenNotLeaderInitially"); + + // Write initial data into the in-memory store. + YangInstanceIdentifier path = TestModel.TEST_PATH; + writeToStore(shard, path, ImmutableNodes.containerNode(TestModel.TEST_QNAME)); + + // Wait until the shard receives the first ElectionTimeout message. + assertEquals("Got first ElectionTimeout", true, + onFirstElectionTimeout.await(5, TimeUnit.SECONDS)); + + // Now send the RegisterChangeListener and wait for the reply. + shard.tell(new RegisterChangeListener(path, dclActor.path(), + AsyncDataBroker.DataChangeScope.SUBTREE), getRef()); + + RegisterChangeListenerReply reply = expectMsgClass(duration("5 seconds"), + RegisterChangeListenerReply.class); + assertNotNull("getListenerRegistrationPath", reply.getListenerRegistrationPath()); + + // Sanity check - verify the shard is not the leader yet. + shard.tell(new FindLeader(), getRef()); + FindLeaderReply findLeadeReply = + expectMsgClass(duration("5 seconds"), FindLeaderReply.class); + assertNull("Expected the shard not to be the leader", findLeadeReply.getLeaderActor()); + + // Signal the onChangeListenerRegistered latch to tell the thread above to proceed + // with the election process. + onChangeListenerRegistered.countDown(); + + // Wait for the shard to become the leader and notify our listener with the existing + // data in the store. + listener.waitForChangeEvents(path); + + dclActor.tell(PoisonPill.getInstance(), ActorRef.noSender()); + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); }}; } @Test public void testCreateTransaction(){ new ShardTestKit(getSystem()) {{ - ActorRef subject = getSystem().actorOf(newShardProps(), "testCreateTransaction"); + ActorRef shard = getSystem().actorOf(newShardProps(), "testCreateTransaction"); - waitUntilLeader(subject); + waitUntilLeader(shard); - subject.tell(new UpdateSchemaContext(TestModel.createTestContext()), getRef()); + shard.tell(new UpdateSchemaContext(TestModel.createTestContext()), getRef()); - subject.tell(new CreateTransaction("txn-1", + shard.tell(new CreateTransaction("txn-1", TransactionProxy.TransactionType.READ_ONLY.ordinal() ).toSerializable(), getRef()); CreateTransactionReply reply = expectMsgClass(duration("3 seconds"), @@ -166,18 +278,19 @@ public class ShardTest extends AbstractActorTest { String path = reply.getTransactionActorPath().toString(); assertTrue("Unexpected transaction path " + path, path.contains("akka://test/user/testCreateTransaction/shard-txn-1")); - expectNoMsg(); + + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); }}; } @Test public void testCreateTransactionOnChain(){ new ShardTestKit(getSystem()) {{ - final ActorRef subject = getSystem().actorOf(newShardProps(), "testCreateTransactionOnChain"); + final ActorRef shard = getSystem().actorOf(newShardProps(), "testCreateTransactionOnChain"); - waitUntilLeader(subject); + waitUntilLeader(shard); - subject.tell(new CreateTransaction("txn-1", + shard.tell(new CreateTransaction("txn-1", TransactionProxy.TransactionType.READ_ONLY.ordinal() , "foobar").toSerializable(), getRef()); @@ -187,47 +300,69 @@ public class ShardTest extends AbstractActorTest { String path = reply.getTransactionActorPath().toString(); assertTrue("Unexpected transaction path " + path, path.contains("akka://test/user/testCreateTransactionOnChain/shard-txn-1")); - expectNoMsg(); + + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); }}; } @Test public void testPeerAddressResolved(){ - new JavaTestKit(getSystem()) {{ - final ShardIdentifier identifier = - ShardIdentifier.builder().memberName("member-1") - .shardName("inventory").type("config").build(); + new ShardTestKit(getSystem()) {{ + final CountDownLatch recoveryComplete = new CountDownLatch(1); + class TestShard extends Shard { + TestShard() { + super(shardID, Collections.singletonMap(shardID, null), + dataStoreContext, SCHEMA_CONTEXT); + } - Props props = Shard.props(identifier, - Collections.singletonMap(identifier, null), - dataStoreContext, SCHEMA_CONTEXT); - final ActorRef subject = getSystem().actorOf(props, "testPeerAddressResolved"); + Map getPeerAddresses() { + return getRaftActorContext().getPeerAddresses(); + } - new Within(duration("3 seconds")) { @Override - protected void run() { + protected void onRecoveryComplete() { + try { + super.onRecoveryComplete(); + } finally { + recoveryComplete.countDown(); + } + } + } - subject.tell( - new PeerAddressResolved(identifier, "akka://foobar"), - getRef()); + final TestActorRef shard = TestActorRef.create(getSystem(), + Props.create(new DelegatingShardCreator(new Creator() { + @Override + public TestShard create() throws Exception { + return new TestShard(); + } + })), "testPeerAddressResolved"); - expectNoMsg(); - } - }; + //waitUntilLeader(shard); + assertEquals("Recovery complete", true, + Uninterruptibles.awaitUninterruptibly(recoveryComplete, 5, TimeUnit.SECONDS)); + + String address = "akka://foobar"; + shard.underlyingActor().onReceiveCommand(new PeerAddressResolved(shardID, address)); + + assertEquals("getPeerAddresses", address, + ((TestShard)shard.underlyingActor()).getPeerAddresses().get(shardID.toString())); + + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); }}; } @Test public void testApplySnapshot() throws ExecutionException, InterruptedException { - TestActorRef ref = TestActorRef.create(getSystem(), newShardProps()); + TestActorRef shard = TestActorRef.create(getSystem(), newShardProps(), + "testApplySnapshot"); NormalizedNodeToNodeCodec codec = new NormalizedNodeToNodeCodec(SCHEMA_CONTEXT); - writeToStore(ref, TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME)); + writeToStore(shard, TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME)); YangInstanceIdentifier root = YangInstanceIdentifier.builder().build(); - NormalizedNode expected = readStore(ref, root); + NormalizedNode expected = readStore(shard, root); NormalizedNodeMessages.Container encode = codec.encode(expected); @@ -235,17 +370,19 @@ public class ShardTest extends AbstractActorTest { encode.getNormalizedNode().toByteString().toByteArray(), Collections.emptyList(), 1, 2, 3, 4)); - ref.underlyingActor().onReceiveCommand(applySnapshot); + shard.underlyingActor().onReceiveCommand(applySnapshot); - NormalizedNode actual = readStore(ref, root); + NormalizedNode actual = readStore(shard, root); assertEquals(expected, actual); + + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); } @Test public void testApplyState() throws Exception { - TestActorRef shard = TestActorRef.create(getSystem(), newShardProps()); + TestActorRef shard = TestActorRef.create(getSystem(), newShardProps(), "testApplyState"); NormalizedNode node = ImmutableNodes.containerNode(TestModel.TEST_QNAME); @@ -259,6 +396,8 @@ public class ShardTest extends AbstractActorTest { NormalizedNode actual = readStore(shard, TestModel.TEST_PATH); assertEquals("Applied state", node, actual); + + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); } @SuppressWarnings("serial") @@ -279,7 +418,7 @@ public class ShardTest extends AbstractActorTest { DOMStoreReadTransaction readTx = testStore.newReadOnlyTransaction(); NormalizedNode root = readTx.read(YangInstanceIdentifier.builder().build()).get().get(); - InMemorySnapshotStore.addSnapshot(IDENTIFIER.toString(), Snapshot.create( + InMemorySnapshotStore.addSnapshot(shardID.toString(), Snapshot.create( new NormalizedNodeToNodeCodec(SCHEMA_CONTEXT).encode( root). getNormalizedNode().toByteString().toByteArray(), @@ -287,7 +426,7 @@ public class ShardTest extends AbstractActorTest { // Set up the InMemoryJournal. - InMemoryJournal.addEntry(IDENTIFIER.toString(), 0, new ReplicatedLogImplEntry(0, 1, newPayload( + InMemoryJournal.addEntry(shardID.toString(), 0, new ReplicatedLogImplEntry(0, 1, newPayload( new WriteModification(TestModel.OUTER_LIST_PATH, ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME).build(), SCHEMA_CONTEXT)))); @@ -301,11 +440,11 @@ public class ShardTest extends AbstractActorTest { Modification mod = new MergeModification(path, ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, i), SCHEMA_CONTEXT); - InMemoryJournal.addEntry(IDENTIFIER.toString(), i, new ReplicatedLogImplEntry(i, 1, + InMemoryJournal.addEntry(shardID.toString(), i, new ReplicatedLogImplEntry(i, 1, newPayload(mod))); } - InMemoryJournal.addEntry(IDENTIFIER.toString(), nListEntries + 1, + InMemoryJournal.addEntry(shardID.toString(), nListEntries + 1, new ApplyLogEntries(nListEntries)); // Create the actor and wait for recovery complete. @@ -315,7 +454,7 @@ public class ShardTest extends AbstractActorTest { Creator creator = new Creator() { @Override public Shard create() throws Exception { - return new Shard(IDENTIFIER, Collections.emptyMap(), + return new Shard(shardID, Collections.emptyMap(), dataStoreContext, SCHEMA_CONTEXT) { @Override protected void onRecoveryComplete() { @@ -363,6 +502,8 @@ public class ShardTest extends AbstractActorTest { shard.underlyingActor().getShardMBean().getCommitIndex()); assertEquals("Last applied", nListEntries, shard.underlyingActor().getShardMBean().getLastApplied()); + + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); } private CompositeModificationPayload newPayload(Modification... mods) { @@ -433,7 +574,8 @@ public class ShardTest extends AbstractActorTest { System.setProperty("shard.persistent", "true"); new ShardTestKit(getSystem()) {{ final TestActorRef shard = TestActorRef.create(getSystem(), - newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName()); + newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), + "testConcurrentThreePhaseCommits"); waitUntilLeader(shard); @@ -468,7 +610,7 @@ public class ShardTest extends AbstractActorTest { // Simulate the ForwardedReadyTransaction message for the first Tx that would be sent // by the ShardTransaction. - shard.tell(new ForwardedReadyTransaction(transactionID1, cohort1, modification1), getRef()); + shard.tell(new ForwardedReadyTransaction(transactionID1, cohort1, modification1, true), getRef()); ReadyTransactionReply readyReply = ReadyTransactionReply.fromSerializable( expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS)); assertEquals("Cohort path", shard.path().toString(), readyReply.getCohortPath()); @@ -482,10 +624,10 @@ public class ShardTest extends AbstractActorTest { // Send the ForwardedReadyTransaction for the next 2 Tx's. - shard.tell(new ForwardedReadyTransaction(transactionID2, cohort2, modification2), getRef()); + shard.tell(new ForwardedReadyTransaction(transactionID2, cohort2, modification2, true), getRef()); expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); - shard.tell(new ForwardedReadyTransaction(transactionID3, cohort3, modification3), getRef()); + shard.tell(new ForwardedReadyTransaction(transactionID3, cohort3, modification3, true), getRef()); expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); // Send the CanCommitTransaction message for the next 2 Tx's. These should get queued and @@ -518,7 +660,6 @@ public class ShardTest extends AbstractActorTest { @Override public void onComplete(Throwable error, Object resp) { if(error != null) { - System.out.println(new java.util.Date()+": "+getClass().getSimpleName() + " failure: "+error); caughtEx.set(new AssertionError(getClass().getSimpleName() + " failure", error)); } else { try { @@ -606,7 +747,17 @@ public class ShardTest extends AbstractActorTest { assertTrue("Missing leaf " + TestModel.ID_QNAME.getLocalName(), idLeaf.isPresent()); assertEquals(TestModel.ID_QNAME.getLocalName() + " value", 1, idLeaf.get().getValue()); + for(int i = 0; i < 20 * 5; i++) { + long lastLogIndex = shard.underlyingActor().getShardMBean().getLastLogIndex(); + if(lastLogIndex == 2) { + break; + } + Uninterruptibles.sleepUninterruptibly(50, TimeUnit.MILLISECONDS); + } + assertEquals("Last log index", 2, shard.underlyingActor().getShardMBean().getLastLogIndex()); + + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); }}; } @@ -614,7 +765,8 @@ public class ShardTest extends AbstractActorTest { public void testCommitPhaseFailure() throws Throwable { new ShardTestKit(getSystem()) {{ final TestActorRef shard = TestActorRef.create(getSystem(), - newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName()); + newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), + "testCommitPhaseFailure"); waitUntilLeader(shard); @@ -639,10 +791,10 @@ public class ShardTest extends AbstractActorTest { // Simulate the ForwardedReadyTransaction messages that would be sent // by the ShardTransaction. - shard.tell(new ForwardedReadyTransaction(transactionID1, cohort1, modification1), getRef()); + shard.tell(new ForwardedReadyTransaction(transactionID1, cohort1, modification1, true), getRef()); expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); - shard.tell(new ForwardedReadyTransaction(transactionID2, cohort2, modification2), getRef()); + shard.tell(new ForwardedReadyTransaction(transactionID2, cohort2, modification2, true), getRef()); expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); // Send the CanCommitTransaction message for the first Tx. @@ -681,6 +833,8 @@ public class ShardTest extends AbstractActorTest { inOrder.verify(cohort1).preCommit(); inOrder.verify(cohort1).commit(); inOrder.verify(cohort2).canCommit(); + + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); }}; } @@ -688,7 +842,8 @@ public class ShardTest extends AbstractActorTest { public void testPreCommitPhaseFailure() throws Throwable { new ShardTestKit(getSystem()) {{ final TestActorRef shard = TestActorRef.create(getSystem(), - newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName()); + newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), + "testPreCommitPhaseFailure"); waitUntilLeader(shard); @@ -703,7 +858,7 @@ public class ShardTest extends AbstractActorTest { // Simulate the ForwardedReadyTransaction messages that would be sent // by the ShardTransaction. - shard.tell(new ForwardedReadyTransaction(transactionID, cohort, modification), getRef()); + shard.tell(new ForwardedReadyTransaction(transactionID, cohort, modification, true), getRef()); expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); // Send the CanCommitTransaction message. @@ -722,6 +877,8 @@ public class ShardTest extends AbstractActorTest { InOrder inOrder = inOrder(cohort); inOrder.verify(cohort).canCommit(); inOrder.verify(cohort).preCommit(); + + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); }}; } @@ -729,7 +886,8 @@ public class ShardTest extends AbstractActorTest { public void testCanCommitPhaseFailure() throws Throwable { new ShardTestKit(getSystem()) {{ final TestActorRef shard = TestActorRef.create(getSystem(), - newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName()); + newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), + "testCanCommitPhaseFailure"); waitUntilLeader(shard); @@ -743,13 +901,15 @@ public class ShardTest extends AbstractActorTest { // Simulate the ForwardedReadyTransaction messages that would be sent // by the ShardTransaction. - shard.tell(new ForwardedReadyTransaction(transactionID, cohort, modification), getRef()); + shard.tell(new ForwardedReadyTransaction(transactionID, cohort, modification, true), getRef()); expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); // Send the CanCommitTransaction message. shard.tell(new CanCommitTransaction(transactionID).toSerializable(), getRef()); expectMsgClass(duration, akka.actor.Status.Failure.class); + + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); }}; } @@ -758,7 +918,8 @@ public class ShardTest extends AbstractActorTest { System.setProperty("shard.persistent", "true"); new ShardTestKit(getSystem()) {{ final TestActorRef shard = TestActorRef.create(getSystem(), - newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName()); + newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), + "testAbortBeforeFinishCommit"); waitUntilLeader(shard); @@ -793,7 +954,7 @@ public class ShardTest extends AbstractActorTest { TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME), modification, preCommit); - shard.tell(new ForwardedReadyTransaction(transactionID, cohort, modification), getRef()); + shard.tell(new ForwardedReadyTransaction(transactionID, cohort, modification, true), getRef()); expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); shard.tell(new CanCommitTransaction(transactionID).toSerializable(), getRef()); @@ -810,6 +971,8 @@ public class ShardTest extends AbstractActorTest { NormalizedNode node = readStore(shard, TestModel.TEST_PATH); assertNotNull(TestModel.TEST_QNAME.getLocalName() + " not found", node); + + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); }}; } @@ -819,7 +982,8 @@ public class ShardTest extends AbstractActorTest { new ShardTestKit(getSystem()) {{ final TestActorRef shard = TestActorRef.create(getSystem(), - newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName()); + newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), + "testTransactionCommitTimeout"); waitUntilLeader(shard); @@ -854,10 +1018,10 @@ public class ShardTest extends AbstractActorTest { // Ready the Tx's - shard.tell(new ForwardedReadyTransaction(transactionID1, cohort1, modification1), getRef()); + shard.tell(new ForwardedReadyTransaction(transactionID1, cohort1, modification1, true), getRef()); expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); - shard.tell(new ForwardedReadyTransaction(transactionID2, cohort2, modification2), getRef()); + shard.tell(new ForwardedReadyTransaction(transactionID2, cohort2, modification2, true), getRef()); expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); // canCommit 1st Tx. We don't send the commit so it should timeout. @@ -877,6 +1041,8 @@ public class ShardTest extends AbstractActorTest { NormalizedNode node = readStore(shard, listNodePath); assertNotNull(listNodePath + " not found", node); + + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); }}; } @@ -886,7 +1052,8 @@ public class ShardTest extends AbstractActorTest { new ShardTestKit(getSystem()) {{ final TestActorRef shard = TestActorRef.create(getSystem(), - newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName()); + newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), + "testTransactionCommitQueueCapacityExceeded"); waitUntilLeader(shard); @@ -913,13 +1080,13 @@ public class ShardTest extends AbstractActorTest { // Ready the Tx's - shard.tell(new ForwardedReadyTransaction(transactionID1, cohort1, modification1), getRef()); + shard.tell(new ForwardedReadyTransaction(transactionID1, cohort1, modification1, true), getRef()); expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); - shard.tell(new ForwardedReadyTransaction(transactionID2, cohort2, modification2), getRef()); + shard.tell(new ForwardedReadyTransaction(transactionID2, cohort2, modification2, true), getRef()); expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); - shard.tell(new ForwardedReadyTransaction(transactionID3, cohort3, modification3), getRef()); + shard.tell(new ForwardedReadyTransaction(transactionID3, cohort3, modification3, true), getRef()); expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); // canCommit 1st Tx. @@ -935,6 +1102,8 @@ public class ShardTest extends AbstractActorTest { shard.tell(new CanCommitTransaction(transactionID3).toSerializable(), getRef()); expectMsgClass(duration, akka.actor.Status.Failure.class); + + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); }}; } @@ -942,10 +1111,13 @@ public class ShardTest extends AbstractActorTest { public void testCanCommitBeforeReadyFailure() throws Throwable { new ShardTestKit(getSystem()) {{ final TestActorRef shard = TestActorRef.create(getSystem(), - newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName()); + newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), + "testCanCommitBeforeReadyFailure"); shard.tell(new CanCommitTransaction("tx").toSerializable(), getRef()); expectMsgClass(duration("5 seconds"), akka.actor.Status.Failure.class); + + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); }}; } @@ -953,7 +1125,8 @@ public class ShardTest extends AbstractActorTest { public void testAbortTransaction() throws Throwable { new ShardTestKit(getSystem()) {{ final TestActorRef shard = TestActorRef.create(getSystem(), - newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName()); + newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), + "testAbortTransaction"); waitUntilLeader(shard); @@ -976,10 +1149,10 @@ public class ShardTest extends AbstractActorTest { // Simulate the ForwardedReadyTransaction messages that would be sent // by the ShardTransaction. - shard.tell(new ForwardedReadyTransaction(transactionID1, cohort1, modification1), getRef()); + shard.tell(new ForwardedReadyTransaction(transactionID1, cohort1, modification1, true), getRef()); expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); - shard.tell(new ForwardedReadyTransaction(transactionID2, cohort2, modification2), getRef()); + shard.tell(new ForwardedReadyTransaction(transactionID2, cohort2, modification2, true), getRef()); expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); // Send the CanCommitTransaction message for the first Tx. @@ -1016,6 +1189,8 @@ public class ShardTest extends AbstractActorTest { InOrder inOrder = inOrder(cohort1, cohort2); inOrder.verify(cohort1).canCommit(); inOrder.verify(cohort2).canCommit(); + + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); }}; } @@ -1026,7 +1201,7 @@ public class ShardTest extends AbstractActorTest { Creator creator = new Creator() { @Override public Shard create() throws Exception { - return new Shard(IDENTIFIER, Collections.emptyMap(), + return new Shard(shardID, Collections.emptyMap(), dataStoreContext, SCHEMA_CONTEXT) { @Override public void saveSnapshot(Object snapshot) { @@ -1050,6 +1225,8 @@ public class ShardTest extends AbstractActorTest { shard.tell(new CaptureSnapshot(-1,-1,-1,-1), getRef()); assertEquals("Snapshot saved", true, latch.get().await(5, TimeUnit.SECONDS)); + + shard.tell(PoisonPill.getInstance(), ActorRef.noSender()); }}; } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTestKit.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTestKit.java index 9a0e8f9e18..d08258a2a0 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTestKit.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTestKit.java @@ -8,6 +8,7 @@ package org.opendaylight.controller.cluster.datastore; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.junit.Assert; import org.opendaylight.controller.cluster.raft.client.messages.FindLeader; import org.opendaylight.controller.cluster.raft.client.messages.FindLeaderReply; @@ -15,6 +16,7 @@ import com.google.common.util.concurrent.Uninterruptibles; import scala.concurrent.Await; import scala.concurrent.Future; import scala.concurrent.duration.Duration; +import scala.concurrent.duration.FiniteDuration; import akka.actor.ActorRef; import akka.actor.ActorSystem; import akka.pattern.Patterns; @@ -45,17 +47,21 @@ class ShardTestKit extends JavaTestKit { } protected void waitUntilLeader(ActorRef shard) { + FiniteDuration duration = Duration.create(100, TimeUnit.MILLISECONDS); for(int i = 0; i < 20 * 5; i++) { - Future future = Patterns.ask(shard, new FindLeader(), new Timeout(5, TimeUnit.SECONDS)); + Future future = Patterns.ask(shard, new FindLeader(), new Timeout(duration)); try { - FindLeaderReply resp = (FindLeaderReply)Await.result(future, Duration.create(5, TimeUnit.SECONDS)); + FindLeaderReply resp = (FindLeaderReply)Await.result(future, duration); if(resp.getLeaderActor() != null) { return; } - } catch (Exception e) { + } catch(TimeoutException e) { + } catch(Exception e) { + System.err.println("FindLeader threw ex"); e.printStackTrace(); } + Uninterruptibles.sleepUninterruptibly(50, TimeUnit.MILLISECONDS); } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTransactionTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTransactionTest.java index 8ce8f4d4b5..711f3d7a72 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTransactionTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTransactionTest.java @@ -9,6 +9,7 @@ import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import org.junit.BeforeClass; import org.junit.Test; +import org.opendaylight.controller.cluster.datastore.ShardWriteTransaction.GetCompositeModificationReply; import org.opendaylight.controller.cluster.datastore.exceptions.UnknownMessageException; import org.opendaylight.controller.cluster.datastore.identifiers.ShardIdentifier; import org.opendaylight.controller.cluster.datastore.jmx.mbeans.shard.ShardStats; @@ -33,6 +34,7 @@ import org.opendaylight.controller.cluster.datastore.modification.Modification; import org.opendaylight.controller.cluster.datastore.modification.WriteModification; import org.opendaylight.controller.md.cluster.datastore.model.TestModel; import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStore; +import org.opendaylight.controller.protobuff.messages.transaction.ShardTransactionMessages; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; import org.opendaylight.yangtools.yang.model.api.SchemaContext; @@ -40,6 +42,8 @@ import scala.concurrent.duration.Duration; import java.util.Collections; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class ShardTransactionTest extends AbstractActorTest { @@ -73,41 +77,35 @@ public class ShardTransactionTest extends AbstractActorTest { public void testOnReceiveReadData() throws Exception { new JavaTestKit(getSystem()) {{ final ActorRef shard = createShard(); - final Props props = ShardTransaction.props(store.newReadOnlyTransaction(), shard, + Props props = ShardTransaction.props(store.newReadOnlyTransaction(), shard, testSchemaContext, datastoreContext, shardStats, "txn"); - final ActorRef subject = getSystem().actorOf(props, "testReadData"); - new Within(duration("1 seconds")) { - @Override - protected void run() { - - subject.tell( - new ReadData(YangInstanceIdentifier.builder().build()).toSerializable(), - getRef()); - - final String out = new ExpectMsg(duration("1 seconds"), "match hint") { - // do not put code outside this method, will run afterwards - @Override - protected String match(Object in) { - if (in.getClass().equals(ReadDataReply.SERIALIZABLE_CLASS)) { - if (ReadDataReply.fromSerializable(testSchemaContext,YangInstanceIdentifier.builder().build(), in) - .getNormalizedNode()!= null) { - return "match"; - } - return null; - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message - - assertEquals("match", out); - - expectNoMsg(); - } + testOnReceiveReadData(getSystem().actorOf(props, "testReadDataRO")); + + props = ShardTransaction.props(store.newReadWriteTransaction(), shard, + testSchemaContext, datastoreContext, shardStats, "txn"); + + testOnReceiveReadData(getSystem().actorOf(props, "testReadDataRW")); + } + + private void testOnReceiveReadData(final ActorRef subject) { + //serialized read + subject.tell(new ReadData(YangInstanceIdentifier.builder().build()).toSerializable(), + getRef()); + + ShardTransactionMessages.ReadDataReply replySerialized = + expectMsgClass(duration("5 seconds"), ReadDataReply.SERIALIZABLE_CLASS); + + assertNotNull(ReadDataReply.fromSerializable( + testSchemaContext,YangInstanceIdentifier.builder().build(), replySerialized) + .getNormalizedNode()); + + // unserialized read + subject.tell(new ReadData(YangInstanceIdentifier.builder().build()),getRef()); + ReadDataReply reply = expectMsgClass(duration("5 seconds"), ReadDataReply.class); - }; + assertNotNull(reply.getNormalizedNode()); }}; } @@ -115,42 +113,35 @@ public class ShardTransactionTest extends AbstractActorTest { public void testOnReceiveReadDataWhenDataNotFound() throws Exception { new JavaTestKit(getSystem()) {{ final ActorRef shard = createShard(); - final Props props = ShardTransaction.props( store.newReadOnlyTransaction(), shard, + Props props = ShardTransaction.props( store.newReadOnlyTransaction(), shard, testSchemaContext, datastoreContext, shardStats, "txn"); - final ActorRef subject = getSystem().actorOf(props, "testReadDataWhenDataNotFound"); - new Within(duration("1 seconds")) { - @Override - protected void run() { - - subject.tell( - new ReadData(TestModel.TEST_PATH).toSerializable(), - getRef()); - - final String out = new ExpectMsg(duration("1 seconds"), "match hint") { - // do not put code outside this method, will run afterwards - @Override - protected String match(Object in) { - if (in.getClass().equals(ReadDataReply.SERIALIZABLE_CLASS)) { - if (ReadDataReply.fromSerializable(testSchemaContext,TestModel.TEST_PATH, in) - .getNormalizedNode() - == null) { - return "match"; - } - return null; - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message - - assertEquals("match", out); - - expectNoMsg(); - } + testOnReceiveReadDataWhenDataNotFound(getSystem().actorOf( + props, "testReadDataWhenDataNotFoundRO")); + + props = ShardTransaction.props( store.newReadWriteTransaction(), shard, + testSchemaContext, datastoreContext, shardStats, "txn"); + + testOnReceiveReadDataWhenDataNotFound(getSystem().actorOf( + props, "testReadDataWhenDataNotFoundRW")); + } + + private void testOnReceiveReadDataWhenDataNotFound(final ActorRef subject) { + // serialized read + subject.tell(new ReadData(TestModel.TEST_PATH).toSerializable(), getRef()); + + ShardTransactionMessages.ReadDataReply replySerialized = + expectMsgClass(duration("5 seconds"), ReadDataReply.SERIALIZABLE_CLASS); + + assertTrue(ReadDataReply.fromSerializable( + testSchemaContext, TestModel.TEST_PATH, replySerialized).getNormalizedNode() == null); + // unserialized read + subject.tell(new ReadData(TestModel.TEST_PATH),getRef()); - }; + ReadDataReply reply = expectMsgClass(duration("5 seconds"), ReadDataReply.class); + + assertTrue(reply.getNormalizedNode() == null); }}; } @@ -158,41 +149,32 @@ public class ShardTransactionTest extends AbstractActorTest { public void testOnReceiveDataExistsPositive() throws Exception { new JavaTestKit(getSystem()) {{ final ActorRef shard = createShard(); - final Props props = ShardTransaction.props(store.newReadOnlyTransaction(), shard, + Props props = ShardTransaction.props(store.newReadOnlyTransaction(), shard, testSchemaContext, datastoreContext, shardStats, "txn"); - final ActorRef subject = getSystem().actorOf(props, "testDataExistsPositive"); - new Within(duration("1 seconds")) { - @Override - protected void run() { - - subject.tell( - new DataExists(YangInstanceIdentifier.builder().build()).toSerializable(), - getRef()); - - final String out = new ExpectMsg(duration("1 seconds"), "match hint") { - // do not put code outside this method, will run afterwards - @Override - protected String match(Object in) { - if (in.getClass().equals(DataExistsReply.SERIALIZABLE_CLASS)) { - if (DataExistsReply.fromSerializable(in) - .exists()) { - return "match"; - } - return null; - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message - - assertEquals("match", out); - - expectNoMsg(); - } + testOnReceiveDataExistsPositive(getSystem().actorOf(props, "testDataExistsPositiveRO")); + + props = ShardTransaction.props(store.newReadWriteTransaction(), shard, + testSchemaContext, datastoreContext, shardStats, "txn"); + + testOnReceiveDataExistsPositive(getSystem().actorOf(props, "testDataExistsPositiveRW")); + } + + private void testOnReceiveDataExistsPositive(final ActorRef subject) { + subject.tell(new DataExists(YangInstanceIdentifier.builder().build()).toSerializable(), + getRef()); + + ShardTransactionMessages.DataExistsReply replySerialized = + expectMsgClass(duration("5 seconds"), ShardTransactionMessages.DataExistsReply.class); + assertTrue(DataExistsReply.fromSerializable(replySerialized).exists()); - }; + // unserialized read + subject.tell(new DataExists(YangInstanceIdentifier.builder().build()),getRef()); + + DataExistsReply reply = expectMsgClass(duration("5 seconds"), DataExistsReply.class); + + assertTrue(reply.exists()); }}; } @@ -200,76 +182,44 @@ public class ShardTransactionTest extends AbstractActorTest { public void testOnReceiveDataExistsNegative() throws Exception { new JavaTestKit(getSystem()) {{ final ActorRef shard = createShard(); - final Props props = ShardTransaction.props(store.newReadOnlyTransaction(), shard, + Props props = ShardTransaction.props(store.newReadOnlyTransaction(), shard, testSchemaContext, datastoreContext, shardStats, "txn"); - final ActorRef subject = getSystem().actorOf(props, "testDataExistsNegative"); - new Within(duration("1 seconds")) { - @Override - protected void run() { - - subject.tell( - new DataExists(TestModel.TEST_PATH).toSerializable(), - getRef()); - - final String out = new ExpectMsg(duration("1 seconds"), "match hint") { - // do not put code outside this method, will run afterwards - @Override - protected String match(Object in) { - if (in.getClass().equals(DataExistsReply.SERIALIZABLE_CLASS)) { - if (!DataExistsReply.fromSerializable(in) - .exists()) { - return "match"; - } - return null; - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message - - assertEquals("match", out); - - expectNoMsg(); - } + testOnReceiveDataExistsNegative(getSystem().actorOf(props, "testDataExistsNegativeRO")); + + props = ShardTransaction.props(store.newReadWriteTransaction(), shard, + testSchemaContext, datastoreContext, shardStats, "txn"); + + testOnReceiveDataExistsNegative(getSystem().actorOf(props, "testDataExistsNegativeRW")); + } + + private void testOnReceiveDataExistsNegative(final ActorRef subject) { + subject.tell(new DataExists(TestModel.TEST_PATH).toSerializable(), getRef()); + ShardTransactionMessages.DataExistsReply replySerialized = + expectMsgClass(duration("5 seconds"), ShardTransactionMessages.DataExistsReply.class); - }; + assertFalse(DataExistsReply.fromSerializable(replySerialized).exists()); + + // unserialized read + subject.tell(new DataExists(TestModel.TEST_PATH),getRef()); + + DataExistsReply reply = expectMsgClass(duration("5 seconds"), DataExistsReply.class); + + assertFalse(reply.exists()); }}; } private void assertModification(final ActorRef subject, final Class modificationType) { new JavaTestKit(getSystem()) {{ - new Within(duration("3 seconds")) { - @Override - protected void run() { - subject - .tell(new ShardWriteTransaction.GetCompositedModification(), - getRef()); - - final CompositeModification compositeModification = - new ExpectMsg(duration("3 seconds"), "match hint") { - // do not put code outside this method, will run afterwards - @Override - protected CompositeModification match(Object in) { - if (in instanceof ShardWriteTransaction.GetCompositeModificationReply) { - return ((ShardWriteTransaction.GetCompositeModificationReply) in) - .getModification(); - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message - - assertTrue( - compositeModification.getModifications().size() == 1); - assertEquals(modificationType, - compositeModification.getModifications().get(0) - .getClass()); + subject.tell(new ShardWriteTransaction.GetCompositedModification(), getRef()); - } - }; + CompositeModification compositeModification = expectMsgClass(duration("3 seconds"), + GetCompositeModificationReply.class).getModification(); + + assertTrue(compositeModification.getModifications().size() == 1); + assertEquals(modificationType, compositeModification.getModifications().get(0).getClass()); }}; } @@ -282,34 +232,22 @@ public class ShardTransactionTest extends AbstractActorTest { final ActorRef subject = getSystem().actorOf(props, "testWriteData"); - new Within(duration("1 seconds")) { - @Override - protected void run() { - - subject.tell(new WriteData(TestModel.TEST_PATH, - ImmutableNodes.containerNode(TestModel.TEST_QNAME), TestModel.createTestContext()).toSerializable(), - getRef()); - - final String out = new ExpectMsg(duration("1 seconds"), "match hint") { - // do not put code outside this method, will run afterwards - @Override - protected String match(Object in) { - if (in.getClass().equals(WriteDataReply.SERIALIZABLE_CLASS)) { - return "match"; - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message - - assertEquals("match", out); - - assertModification(subject, WriteModification.class); - expectNoMsg(); - } + subject.tell(new WriteData(TestModel.TEST_PATH, + ImmutableNodes.containerNode(TestModel.TEST_QNAME), TestModel.createTestContext()).toSerializable(), + getRef()); + + ShardTransactionMessages.WriteDataReply replySerialized = + expectMsgClass(duration("5 seconds"), ShardTransactionMessages.WriteDataReply.class); + assertModification(subject, WriteModification.class); - }; + //unserialized write + subject.tell(new WriteData(TestModel.TEST_PATH, + ImmutableNodes.containerNode(TestModel.TEST_QNAME), + TestModel.createTestContext()), + getRef()); + + expectMsgClass(duration("5 seconds"), WriteDataReply.class); }}; } @@ -322,35 +260,21 @@ public class ShardTransactionTest extends AbstractActorTest { final ActorRef subject = getSystem().actorOf(props, "testMergeData"); - new Within(duration("1 seconds")) { - @Override - protected void run() { - - subject.tell(new MergeData(TestModel.TEST_PATH, - ImmutableNodes.containerNode(TestModel.TEST_QNAME), testSchemaContext).toSerializable(), - getRef()); - - final String out = new ExpectMsg(duration("500 milliseconds"), "match hint") { - // do not put code outside this method, will run afterwards - @Override - protected String match(Object in) { - if (in.getClass().equals(MergeDataReply.SERIALIZABLE_CLASS)) { - return "match"; - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message + subject.tell(new MergeData(TestModel.TEST_PATH, + ImmutableNodes.containerNode(TestModel.TEST_QNAME), testSchemaContext).toSerializable(), + getRef()); - assertEquals("match", out); + ShardTransactionMessages.MergeDataReply replySerialized = + expectMsgClass(duration("5 seconds"), ShardTransactionMessages.MergeDataReply.class); - assertModification(subject, MergeModification.class); - - expectNoMsg(); - } + assertModification(subject, MergeModification.class); + //unserialized merge + subject.tell(new MergeData(TestModel.TEST_PATH, + ImmutableNodes.containerNode(TestModel.TEST_QNAME), testSchemaContext), + getRef()); - }; + expectMsgClass(duration("5 seconds"), MergeDataReply.class); }}; } @@ -363,32 +287,17 @@ public class ShardTransactionTest extends AbstractActorTest { final ActorRef subject = getSystem().actorOf(props, "testDeleteData"); - new Within(duration("1 seconds")) { - @Override - protected void run() { - - subject.tell(new DeleteData(TestModel.TEST_PATH).toSerializable(), getRef()); - - final String out = new ExpectMsg(duration("1 seconds"), "match hint") { - // do not put code outside this method, will run afterwards - @Override - protected String match(Object in) { - if (in.getClass().equals(DeleteDataReply.SERIALIZABLE_CLASS)) { - return "match"; - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message - - assertEquals("match", out); - - assertModification(subject, DeleteModification.class); - expectNoMsg(); - } + subject.tell(new DeleteData(TestModel.TEST_PATH).toSerializable(), getRef()); + ShardTransactionMessages.DeleteDataReply replySerialized = + expectMsgClass(duration("5 seconds"), ShardTransactionMessages.DeleteDataReply.class); - }; + assertModification(subject, DeleteModification.class); + + //unserialized merge + subject.tell(new DeleteData(TestModel.TEST_PATH), getRef()); + + expectMsgClass(duration("5 seconds"), DeleteDataReply.class); }}; } @@ -402,83 +311,41 @@ public class ShardTransactionTest extends AbstractActorTest { final ActorRef subject = getSystem().actorOf(props, "testReadyTransaction"); - new Within(duration("1 seconds")) { - @Override - protected void run() { - - subject.tell(new ReadyTransaction().toSerializable(), getRef()); + subject.tell(new ReadyTransaction().toSerializable(), getRef()); - final String out = new ExpectMsg(duration("1 seconds"), "match hint") { - // do not put code outside this method, will run afterwards - @Override - protected String match(Object in) { - if (in.getClass().equals(ReadyTransactionReply.SERIALIZABLE_CLASS)) { - return "match"; - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message - - assertEquals("match", out); + expectMsgClass(duration("5 seconds"), ReadyTransactionReply.SERIALIZABLE_CLASS); + }}; - expectNoMsg(); - } + // test + new JavaTestKit(getSystem()) {{ + final ActorRef shard = createShard(); + final Props props = ShardTransaction.props( store.newReadWriteTransaction(), shard, + testSchemaContext, datastoreContext, shardStats, "txn"); + final ActorRef subject = + getSystem().actorOf(props, "testReadyTransaction2"); + subject.tell(new ReadyTransaction(), getRef()); - }; + expectMsgClass(duration("5 seconds"), ReadyTransactionReply.class); }}; } + @SuppressWarnings("unchecked") @Test public void testOnReceiveCloseTransaction() throws Exception { new JavaTestKit(getSystem()) {{ final ActorRef shard = createShard(); final Props props = ShardTransaction.props(store.newReadWriteTransaction(), shard, testSchemaContext, datastoreContext, shardStats, "txn"); - final ActorRef subject = - getSystem().actorOf(props, "testCloseTransaction"); + final ActorRef subject = getSystem().actorOf(props, "testCloseTransaction"); watch(subject); - new Within(duration("6 seconds")) { - @Override - protected void run() { - - subject.tell(new CloseTransaction().toSerializable(), getRef()); - - final String out = new ExpectMsg(duration("3 seconds"), "match hint") { - // do not put code outside this method, will run afterwards - @Override - protected String match(Object in) { - System.out.println("!!!IN match 1: "+(in!=null?in.getClass():"NULL")); - if (in.getClass().equals(CloseTransactionReply.SERIALIZABLE_CLASS)) { - return "match"; - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message - - assertEquals("match", out); - - final String termination = new ExpectMsg(duration("3 seconds"), "match hint") { - // do not put code outside this method, will run afterwards - @Override - protected String match(Object in) { - System.out.println("!!!IN match 2: "+(in!=null?in.getClass():"NULL")); - if (in instanceof Terminated) { - return "match"; - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message - - assertEquals("match", termination); - } - }; + subject.tell(new CloseTransaction().toSerializable(), getRef()); + + expectMsgClass(duration("3 seconds"), CloseTransactionReply.SERIALIZABLE_CLASS); + expectMsgClass(duration("3 seconds"), Terminated.class); }}; } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionProxyTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionProxyTest.java index 592337f93f..f2b849122a 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionProxyTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionProxyTest.java @@ -1,12 +1,12 @@ package org.opendaylight.controller.cluster.datastore; -import com.google.common.util.concurrent.CheckedFuture; - import akka.actor.ActorRef; import akka.actor.ActorSelection; +import akka.actor.ActorSystem; import akka.actor.Props; import akka.dispatch.Futures; import com.google.common.base.Optional; +import com.google.common.util.concurrent.CheckedFuture; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentMatcher; @@ -44,10 +44,8 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext; import scala.concurrent.Await; import scala.concurrent.Future; import scala.concurrent.duration.Duration; - import java.util.List; import java.util.concurrent.TimeUnit; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -83,6 +81,9 @@ public class TransactionProxyTest extends AbstractActorTest { private SchemaContext schemaContext; + @Mock + private ClusterWrapper mockClusterWrapper; + String memberName = "mock-member"; @Before @@ -94,6 +95,7 @@ public class TransactionProxyTest extends AbstractActorTest { doReturn(getSystem()).when(mockActorContext).getActorSystem(); doReturn(memberName).when(mockActorContext).getCurrentMemberName(); doReturn(schemaContext).when(mockActorContext).getSchemaContext(); + doReturn(mockClusterWrapper).when(mockActorContext).getClusterWrapper(); ShardStrategyFactory.setConfiguration(configuration); } @@ -112,7 +114,7 @@ public class TransactionProxyTest extends AbstractActorTest { return argThat(matcher); } - private DataExists eqDataExists() { + private DataExists eqSerializedDataExists() { ArgumentMatcher matcher = new ArgumentMatcher() { @Override public boolean matches(Object argument) { @@ -124,7 +126,19 @@ public class TransactionProxyTest extends AbstractActorTest { return argThat(matcher); } - private ReadData eqReadData() { + private DataExists eqDataExists() { + ArgumentMatcher matcher = new ArgumentMatcher() { + @Override + public boolean matches(Object argument) { + return (argument instanceof DataExists) && + ((DataExists)argument).getPath().equals(TestModel.TEST_PATH); + } + }; + + return argThat(matcher); + } + + private ReadData eqSerializedReadData() { ArgumentMatcher matcher = new ArgumentMatcher() { @Override public boolean matches(Object argument) { @@ -136,7 +150,19 @@ public class TransactionProxyTest extends AbstractActorTest { return argThat(matcher); } - private WriteData eqWriteData(final NormalizedNode nodeToWrite) { + private ReadData eqReadData() { + ArgumentMatcher matcher = new ArgumentMatcher() { + @Override + public boolean matches(Object argument) { + return (argument instanceof ReadData) && + ((ReadData)argument).getPath().equals(TestModel.TEST_PATH); + } + }; + + return argThat(matcher); + } + + private WriteData eqSerializedWriteData(final NormalizedNode nodeToWrite) { ArgumentMatcher matcher = new ArgumentMatcher() { @Override public boolean matches(Object argument) { @@ -153,7 +179,23 @@ public class TransactionProxyTest extends AbstractActorTest { return argThat(matcher); } - private MergeData eqMergeData(final NormalizedNode nodeToWrite) { + private WriteData eqWriteData(final NormalizedNode nodeToWrite) { + ArgumentMatcher matcher = new ArgumentMatcher() { + @Override + public boolean matches(Object argument) { + if(argument instanceof WriteData) { + WriteData obj = (WriteData) argument; + return obj.getPath().equals(TestModel.TEST_PATH) && + obj.getData().equals(nodeToWrite); + } + return false; + } + }; + + return argThat(matcher); + } + + private MergeData eqSerializedMergeData(final NormalizedNode nodeToWrite) { ArgumentMatcher matcher = new ArgumentMatcher() { @Override public boolean matches(Object argument) { @@ -170,7 +212,24 @@ public class TransactionProxyTest extends AbstractActorTest { return argThat(matcher); } - private DeleteData eqDeleteData() { + private MergeData eqMergeData(final NormalizedNode nodeToWrite) { + ArgumentMatcher matcher = new ArgumentMatcher() { + @Override + public boolean matches(Object argument) { + if(argument instanceof MergeData) { + MergeData obj = ((MergeData) argument); + return obj.getPath().equals(TestModel.TEST_PATH) && + obj.getData().equals(nodeToWrite); + } + + return false; + } + }; + + return argThat(matcher); + } + + private DeleteData eqSerializedDeleteData() { ArgumentMatcher matcher = new ArgumentMatcher() { @Override public boolean matches(Object argument) { @@ -182,30 +241,67 @@ public class TransactionProxyTest extends AbstractActorTest { return argThat(matcher); } - private Future readyTxReply(String path) { + private DeleteData eqDeleteData() { + ArgumentMatcher matcher = new ArgumentMatcher() { + @Override + public boolean matches(Object argument) { + return argument instanceof DeleteData && + ((DeleteData)argument).getPath().equals(TestModel.TEST_PATH); + } + }; + + return argThat(matcher); + } + + private Future readySerializedTxReply(String path) { return Futures.successful((Object)new ReadyTransactionReply(path).toSerializable()); } - private Future readDataReply(NormalizedNode data) { + private Future readyTxReply(String path) { + return Futures.successful((Object)new ReadyTransactionReply(path)); + } + + + private Future readSerializedDataReply(NormalizedNode data) { return Futures.successful(new ReadDataReply(schemaContext, data).toSerializable()); } - private Future dataExistsReply(boolean exists) { + private Future readDataReply(NormalizedNode data) { + return Futures.successful(new ReadDataReply(schemaContext, data)); + } + + private Future dataExistsSerializedReply(boolean exists) { return Futures.successful(new DataExistsReply(exists).toSerializable()); } - private Future writeDataReply() { + private Future dataExistsReply(boolean exists) { + return Futures.successful(new DataExistsReply(exists)); + } + + private Future writeSerializedDataReply() { return Futures.successful(new WriteDataReply().toSerializable()); } - private Future mergeDataReply() { + private Future writeDataReply() { + return Futures.successful(new WriteDataReply()); + } + + private Future mergeSerializedDataReply() { return Futures.successful(new MergeDataReply().toSerializable()); } - private Future deleteDataReply() { + private Future mergeDataReply() { + return Futures.successful(new MergeDataReply()); + } + + private Future deleteSerializedDataReply() { return Futures.successful(new DeleteDataReply().toSerializable()); } + private Future deleteDataReply() { + return Futures.successful(new DeleteDataReply()); + } + private ActorSelection actorSelection(ActorRef actorRef) { return getSystem().actorSelection(actorRef.path()); } @@ -216,17 +312,20 @@ public class TransactionProxyTest extends AbstractActorTest { .setTransactionId("txn-1").build(); } - private ActorRef setupActorContextWithInitialCreateTransaction(TransactionType type) { - ActorRef actorRef = getSystem().actorOf(Props.create(DoNothingActor.class)); - doReturn(getSystem().actorSelection(actorRef.path())). + private ActorRef setupActorContextWithInitialCreateTransaction(ActorSystem actorSystem, TransactionType type) { + ActorRef actorRef = actorSystem.actorOf(Props.create(DoNothingActor.class)); + doReturn(actorSystem.actorSelection(actorRef.path())). when(mockActorContext).actorSelection(actorRef.path().toString()); - doReturn(Optional.of(getSystem().actorSelection(actorRef.path()))). + doReturn(Optional.of(actorSystem.actorSelection(actorRef.path()))). when(mockActorContext).findPrimaryShard(eq(DefaultShardStrategy.DEFAULT_SHARD)); doReturn(createTransactionReply(actorRef)).when(mockActorContext). - executeOperation(eq(getSystem().actorSelection(actorRef.path())), + executeOperation(eq(actorSystem.actorSelection(actorRef.path())), eqCreateTransaction(memberName, type)); + + doReturn(false).when(mockActorContext).isLocalPath(actorRef.path().toString()); + return actorRef; } @@ -243,13 +342,13 @@ public class TransactionProxyTest extends AbstractActorTest { @Test public void testRead() throws Exception { - ActorRef actorRef = setupActorContextWithInitialCreateTransaction(READ_ONLY); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_ONLY); TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, READ_ONLY); - doReturn(readDataReply(null)).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqReadData()); + doReturn(readSerializedDataReply(null)).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedReadData()); Optional> readOptional = transactionProxy.read( TestModel.TEST_PATH).get(5, TimeUnit.SECONDS); @@ -258,8 +357,8 @@ public class TransactionProxyTest extends AbstractActorTest { NormalizedNode expectedNode = ImmutableNodes.containerNode(TestModel.TEST_QNAME); - doReturn(readDataReply(expectedNode)).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqReadData()); + doReturn(readSerializedDataReply(expectedNode)).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedReadData()); readOptional = transactionProxy.read(TestModel.TEST_PATH).get(5, TimeUnit.SECONDS); @@ -270,7 +369,7 @@ public class TransactionProxyTest extends AbstractActorTest { @Test(expected = ReadFailedException.class) public void testReadWithInvalidReplyMessageType() throws Exception { - setupActorContextWithInitialCreateTransaction(READ_ONLY); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_ONLY); doReturn(Futures.successful(new Object())).when(mockActorContext). executeOperationAsync(any(ActorSelection.class), any()); @@ -283,7 +382,7 @@ public class TransactionProxyTest extends AbstractActorTest { @Test(expected = TestException.class) public void testReadWithAsyncRemoteOperatonFailure() throws Throwable { - setupActorContextWithInitialCreateTransaction(READ_ONLY); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_ONLY); doReturn(Futures.failed(new TestException())).when(mockActorContext). executeOperationAsync(any(ActorSelection.class), any()); @@ -338,18 +437,18 @@ public class TransactionProxyTest extends AbstractActorTest { @Test(expected = TestException.class) public void testReadWithPriorRecordingOperationFailure() throws Throwable { - ActorRef actorRef = setupActorContextWithInitialCreateTransaction(READ_WRITE); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_WRITE); NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); - doReturn(writeDataReply()).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqWriteData(nodeToWrite)); + doReturn(writeSerializedDataReply()).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedWriteData(nodeToWrite)); doReturn(Futures.failed(new TestException())).when(mockActorContext). - executeOperationAsync(eq(actorSelection(actorRef)), eqDeleteData()); + executeOperationAsync(eq(actorSelection(actorRef)), eqSerializedDeleteData()); - doReturn(readDataReply(null)).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqReadData()); + doReturn(readSerializedDataReply(null)).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedReadData()); TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, READ_WRITE); @@ -362,21 +461,21 @@ public class TransactionProxyTest extends AbstractActorTest { propagateReadFailedExceptionCause(transactionProxy.read(TestModel.TEST_PATH)); } finally { verify(mockActorContext, times(0)).executeOperationAsync( - eq(actorSelection(actorRef)), eqReadData()); + eq(actorSelection(actorRef)), eqSerializedReadData()); } } @Test public void testReadWithPriorRecordingOperationSuccessful() throws Throwable { - ActorRef actorRef = setupActorContextWithInitialCreateTransaction(READ_WRITE); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_WRITE); NormalizedNode expectedNode = ImmutableNodes.containerNode(TestModel.TEST_QNAME); - doReturn(writeDataReply()).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqWriteData(expectedNode)); + doReturn(writeSerializedDataReply()).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedWriteData(expectedNode)); - doReturn(readDataReply(expectedNode)).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqReadData()); + doReturn(readSerializedDataReply(expectedNode)).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedReadData()); TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, READ_WRITE); @@ -402,20 +501,20 @@ public class TransactionProxyTest extends AbstractActorTest { @Test public void testExists() throws Exception { - ActorRef actorRef = setupActorContextWithInitialCreateTransaction(READ_ONLY); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_ONLY); TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, READ_ONLY); - doReturn(dataExistsReply(false)).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqDataExists()); + doReturn(dataExistsSerializedReply(false)).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedDataExists()); Boolean exists = transactionProxy.exists(TestModel.TEST_PATH).checkedGet(); assertEquals("Exists response", false, exists); - doReturn(dataExistsReply(true)).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqDataExists()); + doReturn(dataExistsSerializedReply(true)).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedDataExists()); exists = transactionProxy.exists(TestModel.TEST_PATH).checkedGet(); @@ -434,7 +533,7 @@ public class TransactionProxyTest extends AbstractActorTest { @Test(expected = ReadFailedException.class) public void testExistsWithInvalidReplyMessageType() throws Exception { - setupActorContextWithInitialCreateTransaction(READ_ONLY); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_ONLY); doReturn(Futures.successful(new Object())).when(mockActorContext). executeOperationAsync(any(ActorSelection.class), any()); @@ -447,7 +546,7 @@ public class TransactionProxyTest extends AbstractActorTest { @Test(expected = TestException.class) public void testExistsWithAsyncRemoteOperatonFailure() throws Throwable { - setupActorContextWithInitialCreateTransaction(READ_ONLY); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_ONLY); doReturn(Futures.failed(new TestException())).when(mockActorContext). executeOperationAsync(any(ActorSelection.class), any()); @@ -460,18 +559,18 @@ public class TransactionProxyTest extends AbstractActorTest { @Test(expected = TestException.class) public void testExistsWithPriorRecordingOperationFailure() throws Throwable { - ActorRef actorRef = setupActorContextWithInitialCreateTransaction(READ_WRITE); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_WRITE); NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); - doReturn(writeDataReply()).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqWriteData(nodeToWrite)); + doReturn(writeSerializedDataReply()).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedWriteData(nodeToWrite)); doReturn(Futures.failed(new TestException())).when(mockActorContext). - executeOperationAsync(eq(actorSelection(actorRef)), eqDeleteData()); + executeOperationAsync(eq(actorSelection(actorRef)), eqSerializedDeleteData()); - doReturn(dataExistsReply(false)).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqDataExists()); + doReturn(dataExistsSerializedReply(false)).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedDataExists()); TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, READ_WRITE); @@ -484,21 +583,21 @@ public class TransactionProxyTest extends AbstractActorTest { propagateReadFailedExceptionCause(transactionProxy.exists(TestModel.TEST_PATH)); } finally { verify(mockActorContext, times(0)).executeOperationAsync( - eq(actorSelection(actorRef)), eqDataExists()); + eq(actorSelection(actorRef)), eqSerializedDataExists()); } } @Test public void testExistsWithPriorRecordingOperationSuccessful() throws Throwable { - ActorRef actorRef = setupActorContextWithInitialCreateTransaction(READ_WRITE); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_WRITE); NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); - doReturn(writeDataReply()).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqWriteData(nodeToWrite)); + doReturn(writeSerializedDataReply()).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedWriteData(nodeToWrite)); - doReturn(dataExistsReply(true)).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqDataExists()); + doReturn(dataExistsSerializedReply(true)).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedDataExists()); TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, READ_WRITE); @@ -544,12 +643,12 @@ public class TransactionProxyTest extends AbstractActorTest { @Test public void testWrite() throws Exception { - ActorRef actorRef = setupActorContextWithInitialCreateTransaction(WRITE_ONLY); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), WRITE_ONLY); NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); - doReturn(writeDataReply()).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqWriteData(nodeToWrite)); + doReturn(writeSerializedDataReply()).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedWriteData(nodeToWrite)); TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, WRITE_ONLY); @@ -557,7 +656,7 @@ public class TransactionProxyTest extends AbstractActorTest { transactionProxy.write(TestModel.TEST_PATH, nodeToWrite); verify(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqWriteData(nodeToWrite)); + eq(actorSelection(actorRef)), eqSerializedWriteData(nodeToWrite)); verifyRecordingOperationFutures(transactionProxy.getRecordedOperationFutures(), WriteDataReply.SERIALIZABLE_CLASS); @@ -587,12 +686,12 @@ public class TransactionProxyTest extends AbstractActorTest { @Test public void testMerge() throws Exception { - ActorRef actorRef = setupActorContextWithInitialCreateTransaction(WRITE_ONLY); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), WRITE_ONLY); NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); - doReturn(mergeDataReply()).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqMergeData(nodeToWrite)); + doReturn(mergeSerializedDataReply()).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedMergeData(nodeToWrite)); TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, WRITE_ONLY); @@ -600,7 +699,7 @@ public class TransactionProxyTest extends AbstractActorTest { transactionProxy.merge(TestModel.TEST_PATH, nodeToWrite); verify(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqMergeData(nodeToWrite)); + eq(actorSelection(actorRef)), eqSerializedMergeData(nodeToWrite)); verifyRecordingOperationFutures(transactionProxy.getRecordedOperationFutures(), MergeDataReply.SERIALIZABLE_CLASS); @@ -608,10 +707,10 @@ public class TransactionProxyTest extends AbstractActorTest { @Test public void testDelete() throws Exception { - ActorRef actorRef = setupActorContextWithInitialCreateTransaction(WRITE_ONLY); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), WRITE_ONLY); - doReturn(deleteDataReply()).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqDeleteData()); + doReturn(deleteSerializedDataReply()).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedDeleteData()); TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, WRITE_ONLY); @@ -619,7 +718,7 @@ public class TransactionProxyTest extends AbstractActorTest { transactionProxy.delete(TestModel.TEST_PATH); verify(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqDeleteData()); + eq(actorSelection(actorRef)), eqSerializedDeleteData()); verifyRecordingOperationFutures(transactionProxy.getRecordedOperationFutures(), DeleteDataReply.SERIALIZABLE_CLASS); @@ -637,7 +736,7 @@ public class TransactionProxyTest extends AbstractActorTest { Object expReply = expReplies[i++]; if(expReply instanceof ActorSelection) { ActorSelection actual = Await.result(future, Duration.create(5, TimeUnit.SECONDS)); - assertEquals("Cohort actor path", (ActorSelection) expReply, actual); + assertEquals("Cohort actor path", expReply, actual); } else { // Expecting exception. try { @@ -653,17 +752,17 @@ public class TransactionProxyTest extends AbstractActorTest { @SuppressWarnings("unchecked") @Test public void testReady() throws Exception { - ActorRef actorRef = setupActorContextWithInitialCreateTransaction(READ_WRITE); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_WRITE); NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); - doReturn(readDataReply(null)).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqReadData()); + doReturn(readSerializedDataReply(null)).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedReadData()); - doReturn(writeDataReply()).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqWriteData(nodeToWrite)); + doReturn(writeSerializedDataReply()).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedWriteData(nodeToWrite)); - doReturn(readyTxReply(actorRef.path().toString())).when(mockActorContext).executeOperationAsync( + doReturn(readySerializedTxReply(actorRef.path().toString())).when(mockActorContext).executeOperationAsync( eq(actorSelection(actorRef)), isA(ReadyTransaction.SERIALIZABLE_CLASS)); TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, @@ -688,19 +787,21 @@ public class TransactionProxyTest extends AbstractActorTest { @SuppressWarnings("unchecked") @Test public void testReadyWithRecordingOperationFailure() throws Exception { - ActorRef actorRef = setupActorContextWithInitialCreateTransaction(WRITE_ONLY); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), WRITE_ONLY); NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); - doReturn(mergeDataReply()).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqMergeData(nodeToWrite)); + doReturn(mergeSerializedDataReply()).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedMergeData(nodeToWrite)); doReturn(Futures.failed(new TestException())).when(mockActorContext). - executeOperationAsync(eq(actorSelection(actorRef)), eqWriteData(nodeToWrite)); + executeOperationAsync(eq(actorSelection(actorRef)), eqSerializedWriteData(nodeToWrite)); - doReturn(readyTxReply(actorRef.path().toString())).when(mockActorContext).executeOperationAsync( + doReturn(readySerializedTxReply(actorRef.path().toString())).when(mockActorContext).executeOperationAsync( eq(actorSelection(actorRef)), isA(ReadyTransaction.SERIALIZABLE_CLASS)); + doReturn(false).when(mockActorContext).isLocalPath(actorRef.path().toString()); + TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, WRITE_ONLY); @@ -723,12 +824,12 @@ public class TransactionProxyTest extends AbstractActorTest { @SuppressWarnings("unchecked") @Test public void testReadyWithReplyFailure() throws Exception { - ActorRef actorRef = setupActorContextWithInitialCreateTransaction(WRITE_ONLY); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), WRITE_ONLY); NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); - doReturn(mergeDataReply()).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqMergeData(nodeToWrite)); + doReturn(mergeSerializedDataReply()).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedMergeData(nodeToWrite)); doReturn(Futures.failed(new TestException())).when(mockActorContext). executeOperationAsync(eq(actorSelection(actorRef)), @@ -781,12 +882,12 @@ public class TransactionProxyTest extends AbstractActorTest { @SuppressWarnings("unchecked") @Test public void testReadyWithInvalidReplyMessageType() throws Exception { - ActorRef actorRef = setupActorContextWithInitialCreateTransaction(WRITE_ONLY); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), WRITE_ONLY); NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); - doReturn(writeDataReply()).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqWriteData(nodeToWrite)); + doReturn(writeSerializedDataReply()).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedWriteData(nodeToWrite)); doReturn(Futures.successful(new Object())).when(mockActorContext). executeOperationAsync(eq(actorSelection(actorRef)), @@ -808,7 +909,7 @@ public class TransactionProxyTest extends AbstractActorTest { @Test public void testGetIdentifier() { - setupActorContextWithInitialCreateTransaction(READ_ONLY); + setupActorContextWithInitialCreateTransaction(getSystem(), READ_ONLY); TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, TransactionProxy.TransactionType.READ_ONLY); @@ -820,10 +921,10 @@ public class TransactionProxyTest extends AbstractActorTest { @SuppressWarnings("unchecked") @Test public void testClose() throws Exception{ - ActorRef actorRef = setupActorContextWithInitialCreateTransaction(READ_WRITE); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_WRITE); - doReturn(readDataReply(null)).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), eqReadData()); + doReturn(readSerializedDataReply(null)).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedReadData()); TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, READ_WRITE); @@ -835,4 +936,140 @@ public class TransactionProxyTest extends AbstractActorTest { verify(mockActorContext).sendOperationAsync( eq(actorSelection(actorRef)), isA(CloseTransaction.SERIALIZABLE_CLASS)); } + + + /** + * Method to test a local Tx actor. The Tx paths are matched to decide if the + * Tx actor is local or not. This is done by mocking the Tx actor path + * and the caller paths and ensuring that the paths have the remote-address format + * + * Note: Since the default akka provider for test is not a RemoteActorRefProvider, + * the paths returned for the actors for all the tests are not qualified remote paths. + * Hence are treated as non-local/remote actors. In short, all tests except + * few below run for remote actors + * + * @throws Exception + */ + @Test + public void testLocalTxActorRead() throws Exception { + ActorSystem actorSystem = getSystem(); + ActorRef shardActorRef = actorSystem.actorOf(Props.create(DoNothingActor.class)); + + doReturn(actorSystem.actorSelection(shardActorRef.path())). + when(mockActorContext).actorSelection(shardActorRef.path().toString()); + + doReturn(Optional.of(actorSystem.actorSelection(shardActorRef.path()))). + when(mockActorContext).findPrimaryShard(eq(DefaultShardStrategy.DEFAULT_SHARD)); + + String actorPath = "akka.tcp://system@127.0.0.1:2550/user/tx-actor"; + CreateTransactionReply createTransactionReply = CreateTransactionReply.newBuilder() + .setTransactionId("txn-1") + .setTransactionActorPath(actorPath) + .build(); + + doReturn(createTransactionReply).when(mockActorContext). + executeOperation(eq(actorSystem.actorSelection(shardActorRef.path())), + eqCreateTransaction(memberName, READ_ONLY)); + + doReturn(true).when(mockActorContext).isLocalPath(actorPath); + + TransactionProxy transactionProxy = new TransactionProxy(mockActorContext,READ_ONLY); + + // negative test case with null as the reply + doReturn(readDataReply(null)).when(mockActorContext).executeOperationAsync( + any(ActorSelection.class), eqReadData()); + + Optional> readOptional = transactionProxy.read( + TestModel.TEST_PATH).get(5, TimeUnit.SECONDS); + + assertEquals("NormalizedNode isPresent", false, readOptional.isPresent()); + + // test case with node as read data reply + NormalizedNode expectedNode = ImmutableNodes.containerNode(TestModel.TEST_QNAME); + + doReturn(readDataReply(expectedNode)).when(mockActorContext).executeOperationAsync( + any(ActorSelection.class), eqReadData()); + + readOptional = transactionProxy.read(TestModel.TEST_PATH).get(5, TimeUnit.SECONDS); + + assertEquals("NormalizedNode isPresent", true, readOptional.isPresent()); + + assertEquals("Response NormalizedNode", expectedNode, readOptional.get()); + + // test for local data exists + doReturn(dataExistsReply(true)).when(mockActorContext).executeOperationAsync( + any(ActorSelection.class), eqDataExists()); + + boolean exists = transactionProxy.exists(TestModel.TEST_PATH).checkedGet(); + + assertEquals("Exists response", true, exists); + } + + @Test + public void testLocalTxActorWrite() throws Exception { + ActorSystem actorSystem = getSystem(); + ActorRef shardActorRef = actorSystem.actorOf(Props.create(DoNothingActor.class)); + + doReturn(actorSystem.actorSelection(shardActorRef.path())). + when(mockActorContext).actorSelection(shardActorRef.path().toString()); + + doReturn(Optional.of(actorSystem.actorSelection(shardActorRef.path()))). + when(mockActorContext).findPrimaryShard(eq(DefaultShardStrategy.DEFAULT_SHARD)); + + String actorPath = "akka.tcp://system@127.0.0.1:2550/user/tx-actor"; + CreateTransactionReply createTransactionReply = CreateTransactionReply.newBuilder() + .setTransactionId("txn-1") + .setTransactionActorPath(actorPath) + .build(); + + doReturn(createTransactionReply).when(mockActorContext). + executeOperation(eq(actorSystem.actorSelection(shardActorRef.path())), + eqCreateTransaction(memberName, WRITE_ONLY)); + + doReturn(true).when(mockActorContext).isLocalPath(actorPath); + + NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); + + doReturn(writeDataReply()).when(mockActorContext).executeOperationAsync( + any(ActorSelection.class), eqWriteData(nodeToWrite)); + + TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, WRITE_ONLY); + transactionProxy.write(TestModel.TEST_PATH, nodeToWrite); + + verify(mockActorContext).executeOperationAsync( + any(ActorSelection.class), eqWriteData(nodeToWrite)); + + //testing local merge + doReturn(mergeDataReply()).when(mockActorContext).executeOperationAsync( + any(ActorSelection.class), eqMergeData(nodeToWrite)); + + transactionProxy.merge(TestModel.TEST_PATH, nodeToWrite); + + verify(mockActorContext).executeOperationAsync( + any(ActorSelection.class), eqMergeData(nodeToWrite)); + + + //testing local delete + doReturn(deleteDataReply()).when(mockActorContext).executeOperationAsync( + any(ActorSelection.class), eqDeleteData()); + + transactionProxy.delete(TestModel.TEST_PATH); + + verify(mockActorContext).executeOperationAsync(any(ActorSelection.class), eqDeleteData()); + + verifyRecordingOperationFutures(transactionProxy.getRecordedOperationFutures(), + WriteDataReply.class, MergeDataReply.class, DeleteDataReply.class); + + // testing ready + doReturn(readyTxReply(shardActorRef.path().toString())).when(mockActorContext).executeOperationAsync( + any(ActorSelection.class), isA(ReadyTransaction.class)); + + DOMStoreThreePhaseCommitCohort ready = transactionProxy.ready(); + + assertTrue(ready instanceof ThreePhaseCommitCohortProxy); + + ThreePhaseCommitCohortProxy proxy = (ThreePhaseCommitCohortProxy) ready; + + verifyCohortFutures(proxy, getSystem().actorSelection(shardActorRef.path())); + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/ActorContextTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/ActorContextTest.java index 8426b03a37..60f9a2d9dc 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/ActorContextTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/ActorContextTest.java @@ -7,7 +7,6 @@ import akka.actor.UntypedActor; import akka.japi.Creator; import akka.testkit.JavaTestKit; import com.google.common.base.Optional; - import org.junit.Test; import org.opendaylight.controller.cluster.datastore.AbstractActorTest; import org.opendaylight.controller.cluster.datastore.ClusterWrapper; @@ -18,9 +17,7 @@ import org.opendaylight.controller.cluster.datastore.messages.LocalShardNotFound import scala.concurrent.Await; import scala.concurrent.Future; import scala.concurrent.duration.Duration; - import java.util.concurrent.TimeUnit; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; @@ -99,23 +96,15 @@ public class ActorContextTest extends AbstractActorTest{ @Test public void testFindLocalShardWithShardNotFound(){ new JavaTestKit(getSystem()) {{ + ActorRef shardManagerActorRef = getSystem() + .actorOf(MockShardManager.props(false, null)); - new Within(duration("1 seconds")) { - @Override - protected void run() { - - ActorRef shardManagerActorRef = getSystem() - .actorOf(MockShardManager.props(false, null)); - - ActorContext actorContext = - new ActorContext(getSystem(), shardManagerActorRef , mock(ClusterWrapper.class), + ActorContext actorContext = + new ActorContext(getSystem(), shardManagerActorRef , mock(ClusterWrapper.class), mock(Configuration.class)); - Optional out = actorContext.findLocalShard("default"); - assertTrue(!out.isPresent()); - expectNoMsg(); - } - }; + Optional out = actorContext.findLocalShard("default"); + assertTrue(!out.isPresent()); }}; } @@ -123,63 +112,74 @@ public class ActorContextTest extends AbstractActorTest{ @Test public void testExecuteRemoteOperation() { new JavaTestKit(getSystem()) {{ + ActorRef shardActorRef = getSystem().actorOf(Props.create(EchoActor.class)); - new Within(duration("3 seconds")) { - @Override - protected void run() { - - ActorRef shardActorRef = getSystem().actorOf(Props.create(EchoActor.class)); - - ActorRef shardManagerActorRef = getSystem() - .actorOf(MockShardManager.props(true, shardActorRef)); + ActorRef shardManagerActorRef = getSystem() + .actorOf(MockShardManager.props(true, shardActorRef)); - ActorContext actorContext = - new ActorContext(getSystem(), shardManagerActorRef , mock(ClusterWrapper.class), + ActorContext actorContext = + new ActorContext(getSystem(), shardManagerActorRef , mock(ClusterWrapper.class), mock(Configuration.class)); - ActorSelection actor = actorContext.actorSelection(shardActorRef.path()); - - Object out = actorContext.executeOperation(actor, "hello"); + ActorSelection actor = actorContext.actorSelection(shardActorRef.path()); - assertEquals("hello", out); + Object out = actorContext.executeOperation(actor, "hello"); - expectNoMsg(); - } - }; + assertEquals("hello", out); }}; } @Test public void testExecuteRemoteOperationAsync() { new JavaTestKit(getSystem()) {{ + ActorRef shardActorRef = getSystem().actorOf(Props.create(EchoActor.class)); - new Within(duration("3 seconds")) { - @Override - protected void run() { + ActorRef shardManagerActorRef = getSystem() + .actorOf(MockShardManager.props(true, shardActorRef)); - ActorRef shardActorRef = getSystem().actorOf(Props.create(EchoActor.class)); + ActorContext actorContext = + new ActorContext(getSystem(), shardManagerActorRef , mock(ClusterWrapper.class), + mock(Configuration.class)); - ActorRef shardManagerActorRef = getSystem() - .actorOf(MockShardManager.props(true, shardActorRef)); + ActorSelection actor = actorContext.actorSelection(shardActorRef.path()); - ActorContext actorContext = - new ActorContext(getSystem(), shardManagerActorRef , mock(ClusterWrapper.class), - mock(Configuration.class)); + Future future = actorContext.executeOperationAsync(actor, "hello"); - ActorSelection actor = actorContext.actorSelection(shardActorRef.path()); + try { + Object result = Await.result(future, Duration.create(3, TimeUnit.SECONDS)); + assertEquals("Result", "hello", result); + } catch(Exception e) { + throw new AssertionError(e); + } + }}; + } - Future future = actorContext.executeOperationAsync(actor, "hello"); + @Test + public void testIsLocalPath() { + MockClusterWrapper clusterWrapper = new MockClusterWrapper(); + ActorContext actorContext = + new ActorContext(getSystem(), null, clusterWrapper, mock(Configuration.class)); - try { - Object result = Await.result(future, Duration.create(3, TimeUnit.SECONDS)); - assertEquals("Result", "hello", result); - } catch(Exception e) { - throw new AssertionError(e); - } + clusterWrapper.setSelfAddress(""); + assertEquals(false, actorContext.isLocalPath(null)); + assertEquals(false, actorContext.isLocalPath("")); - expectNoMsg(); - } - }; - }}; + clusterWrapper.setSelfAddress(null); + assertEquals(false, actorContext.isLocalPath("")); + + clusterWrapper.setSelfAddress("akka://test/user/$b"); + assertEquals(false, actorContext.isLocalPath("akka://test/user/$a")); + + clusterWrapper.setSelfAddress("akka.tcp://system@127.0.0.1:2550/"); + assertEquals(true, actorContext.isLocalPath("akka.tcp://system@127.0.0.1:2550/")); + + clusterWrapper.setSelfAddress("akka.tcp://system@127.0.0.1:2550"); + assertEquals(false, actorContext.isLocalPath("akka.tcp://system@127.0.0.1:2550/")); + + clusterWrapper.setSelfAddress("akka.tcp://system@128.0.0.1:2550/"); + assertEquals(false, actorContext.isLocalPath("akka.tcp://system@127.0.0.1:2550/")); + + clusterWrapper.setSelfAddress("akka.tcp://system@127.0.0.1:2551/"); + assertEquals(false, actorContext.isLocalPath("akka.tcp://system@127.0.0.1:2550/")); } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockClusterWrapper.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockClusterWrapper.java index 803aa03b7c..b80506d17d 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockClusterWrapper.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockClusterWrapper.java @@ -15,19 +15,31 @@ import akka.cluster.MemberStatus; import akka.cluster.UniqueAddress; import org.opendaylight.controller.cluster.datastore.ClusterWrapper; import scala.collection.JavaConversions; - import java.util.HashSet; import java.util.Set; public class MockClusterWrapper implements ClusterWrapper{ - @Override public void subscribeToMemberEvents(ActorRef actorRef) { + private String selfAddress = "akka.tcp://test@127.0.0.1:2550/user/member-1-shard-test-config"; + + @Override + public void subscribeToMemberEvents(ActorRef actorRef) { } - @Override public String getCurrentMemberName() { + @Override + public String getCurrentMemberName() { return "member-1"; } + @Override + public String getSelfAddress() { + return selfAddress; + } + + public void setSelfAddress(String selfAddress) { + this.selfAddress = selfAddress; + } + public static void sendMemberUp(ActorRef to, String memberName, String address){ to.tell(createMemberUp(memberName, address), null); } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockDataChangeListener.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockDataChangeListener.java new file mode 100644 index 0000000000..f2f49d1bf3 --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockDataChangeListener.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014 Brocade Communications 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.cluster.datastore.utils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent; +import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Uninterruptibles; + +/** + * A mock DataChangeListener implementation. + * + * @author Thomas Pantelis + */ +public class MockDataChangeListener implements + AsyncDataChangeListener> { + + private final List>> + changeList = Lists.newArrayList(); + private final CountDownLatch changeLatch; + private final int expChangeEventCount; + + public MockDataChangeListener(int expChangeEventCount) { + changeLatch = new CountDownLatch(expChangeEventCount); + this.expChangeEventCount = expChangeEventCount; + } + + @Override + public void onDataChanged(AsyncDataChangeEvent> change) { + changeList.add(change); + changeLatch.countDown(); + } + + public void waitForChangeEvents(YangInstanceIdentifier... expPaths) { + assertEquals("Change notifications complete", true, + Uninterruptibles.awaitUninterruptibly(changeLatch, 5, TimeUnit.SECONDS)); + + for(int i = 0; i < expPaths.length; i++) { + assertTrue(String.format("Change %d does not contain %s", (i+1), expPaths[i]), + changeList.get(i).getCreatedData().containsKey(expPaths[i])); + } + } + + public void expectNoMoreChanges(String assertMsg) { + Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS); + assertEquals(assertMsg, expChangeEventCount, changeList.size()); + } +} diff --git a/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/broker/spi/rpc/RpcRoutingStrategy.java b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/broker/spi/rpc/RpcRoutingStrategy.java index 81203c55fe..6c8f37b66b 100644 --- a/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/broker/spi/rpc/RpcRoutingStrategy.java +++ b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/broker/spi/rpc/RpcRoutingStrategy.java @@ -7,6 +7,8 @@ */ package org.opendaylight.controller.md.sal.dom.broker.spi.rpc; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; import org.opendaylight.yangtools.concepts.Identifiable; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; @@ -14,17 +16,14 @@ import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.RpcDefinition; import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode; -import com.google.common.base.Optional; - public abstract class RpcRoutingStrategy implements Identifiable { + private static final QName CONTEXT_REFERENCE = QName.cachedReference(QName.create("urn:opendaylight:yang:extension:yang-ext", + "2013-07-09", "context-reference")); private final QName identifier; - private static final QName CONTEXT_REFERENCE = QName.create("urn:opendaylight:yang:extension:yang-ext", - "2013-07-09", "context-reference"); private RpcRoutingStrategy(final QName identifier) { - super(); - this.identifier = identifier; + this.identifier = Preconditions.checkNotNull(identifier); } /** @@ -47,7 +46,7 @@ public abstract class RpcRoutingStrategy implements Identifiable { public abstract QName getContext(); @Override - public QName getIdentifier() { + public final QName getIdentifier() { return identifier; } @@ -64,14 +63,14 @@ public abstract class RpcRoutingStrategy implements Identifiable { for (DataSchemaNode schemaNode : input.getChildNodes()) { Optional context = getRoutingContext(schemaNode); if (context.isPresent()) { - return createRoutedStrategy(rpc, context.get(), schemaNode.getQName()); + return new RoutedRpcStrategy(rpc.getQName(), context.get(), schemaNode.getQName()); } } } - return createGlobalStrategy(rpc); + return new GlobalRpcStrategy(rpc.getQName()); } - public static Optional getRoutingContext(final DataSchemaNode schemaNode) { + public static Optional getRoutingContext(final DataSchemaNode schemaNode) { for (UnknownSchemaNode extension : schemaNode.getUnknownSchemaNodes()) { if (CONTEXT_REFERENCE.equals(extension.getNodeType())) { return Optional.fromNullable(extension.getQName()); @@ -80,26 +79,14 @@ public abstract class RpcRoutingStrategy implements Identifiable { return Optional.absent(); } - private static RpcRoutingStrategy createRoutedStrategy(final RpcDefinition rpc, final QName context, final QName leafNode) { - return new RoutedRpcStrategy(rpc.getQName(), context, leafNode); - } - - - - private static RpcRoutingStrategy createGlobalStrategy(final RpcDefinition rpc) { - GlobalRpcStrategy ret = new GlobalRpcStrategy(rpc.getQName()); - return ret; - } - - private static class RoutedRpcStrategy extends RpcRoutingStrategy { - - final QName context; + private static final class RoutedRpcStrategy extends RpcRoutingStrategy { + private final QName context; private final QName leaf; private RoutedRpcStrategy(final QName identifier, final QName ctx, final QName leaf) { super(identifier); - this.context = ctx; - this.leaf = leaf; + this.context = Preconditions.checkNotNull(ctx); + this.leaf = Preconditions.checkNotNull(leaf); } @Override @@ -118,7 +105,7 @@ public abstract class RpcRoutingStrategy implements Identifiable { } } - private static class GlobalRpcStrategy extends RpcRoutingStrategy { + private static final class GlobalRpcStrategy extends RpcRoutingStrategy { public GlobalRpcStrategy(final QName identifier) { super(identifier); @@ -131,12 +118,12 @@ public abstract class RpcRoutingStrategy implements Identifiable { @Override public QName getContext() { - throw new UnsupportedOperationException("Not routed strategy does not have context."); + throw new UnsupportedOperationException("Non-routed strategy does not have a context"); } @Override public QName getLeaf() { - throw new UnsupportedOperationException("Not routed strategy does not have context."); + throw new UnsupportedOperationException("Non-routed strategy does not have a context"); } } } \ No newline at end of file diff --git a/opendaylight/md-sal/sal-dom-xsql/src/main/java/org/opendaylight/controller/md/sal/dom/xsql/XSQLAdapter.java b/opendaylight/md-sal/sal-dom-xsql/src/main/java/org/opendaylight/controller/md/sal/dom/xsql/XSQLAdapter.java index beab6d2fb1..ecea744d14 100644 --- a/opendaylight/md-sal/sal-dom-xsql/src/main/java/org/opendaylight/controller/md/sal/dom/xsql/XSQLAdapter.java +++ b/opendaylight/md-sal/sal-dom-xsql/src/main/java/org/opendaylight/controller/md/sal/dom/xsql/XSQLAdapter.java @@ -30,6 +30,8 @@ public class XSQLAdapter extends Thread implements SchemaContextListener { private static final int SLEEP = 10000; private static XSQLAdapter a = new XSQLAdapter(); private static PrintStream l = null; + private static String tmpDir = null; + private static File xqlLog = null; public boolean stopped = false; private List elementHosts = new ArrayList(); private String username; @@ -79,6 +81,12 @@ public class XSQLAdapter extends Thread implements SchemaContextListener { return a; } + public static File getXQLLogfile() { + tmpDir = System.getProperty("java.io.tmpdir"); + xqlLog = new File(tmpDir + "/xql.log"); + return xqlLog; + } + public static void main(String args[]) { XSQLAdapter adapter = new XSQLAdapter(); adapter.start(); @@ -90,7 +98,7 @@ public class XSQLAdapter extends Thread implements SchemaContextListener { synchronized (XSQLAdapter.class) { if (l == null) { l = new PrintStream( - new FileOutputStream("/tmp/xql.log")); + new FileOutputStream(getXQLLogfile())); } } } @@ -108,7 +116,7 @@ public class XSQLAdapter extends Thread implements SchemaContextListener { synchronized (XSQLAdapter.class) { if (l == null) { l = new PrintStream( - new FileOutputStream("/tmp/xql.log")); + new FileOutputStream(getXQLLogfile())); } } } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.java index 3988a495cb..95fb9a4826 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.java @@ -7,9 +7,18 @@ */ package org.opendaylight.controller.sal.restconf.impl; +import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION; +import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.OPERATIONAL; + import com.google.common.base.Optional; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.ListenableFuture; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import javax.ws.rs.core.Response.Status; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; @@ -37,16 +46,6 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.ws.rs.core.Response.Status; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION; -import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.OPERATIONAL; - public class BrokerFacade { private final static Logger LOG = LoggerFactory.getLogger(BrokerFacade.class); @@ -261,15 +260,13 @@ public class BrokerFacade { try { - CheckedFuture future = - rwTx.exists(store, currentPath); + CheckedFuture future = rwTx.exists(store, currentPath); exists = future.checkedGet(); } catch (ReadFailedException e) { LOG.error("Failed to read pre-existing data from store {} path {}", store, currentPath, e); throw new IllegalStateException("Failed to read pre-existing data", e); } - if (!exists && iterator.hasNext()) { rwTx.merge(store, currentPath, currentOp.createDefault(currentArg)); } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java index a95a64b2c2..cd860efab7 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java @@ -9,9 +9,12 @@ package org.opendaylight.controller.sal.restconf.impl; import com.google.common.base.Objects; +import com.google.common.base.Optional; import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; import com.google.common.base.Splitter; import com.google.common.base.Strings; +import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -63,6 +66,7 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.InstanceI import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.ModifiedNodeDoesNotExistException; import org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.parser.CnSnToNormalizedNodeParserFactory; import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; import org.opendaylight.yangtools.yang.data.impl.NodeFactory; @@ -976,9 +980,13 @@ public class RestconfImpl implements RestconfService { broker.commitConfigurationDataDelete(normalizedII).get(); } } catch (Exception e) { - throw new RestconfDocumentedException("Error creating data", e); + final Optional searchedException = Iterables.tryFind(Throwables.getCausalChain(e), + Predicates.instanceOf(ModifiedNodeDoesNotExistException.class)); + if (searchedException.isPresent()) { + throw new RestconfDocumentedException("Data specified for deleting doesn't exist.", ErrorType.APPLICATION, ErrorTag.DATA_MISSING); + } + throw new RestconfDocumentedException("Error while deleting data", e); } - return Response.status(Status.OK).build(); } diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java index 6b25830240..f533a6360a 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java @@ -8,9 +8,21 @@ package org.opendaylight.controller.sal.restconf.impl.test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + import com.google.common.base.Optional; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.Futures; +import java.util.concurrent.Future; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -43,19 +55,6 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdent import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.impl.schema.Builders; -import java.util.concurrent.Future; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - /** * Unit tests for BrokerFacade. * @@ -234,6 +233,9 @@ public class BrokerFacadeTest { when(wTransaction.submit()).thenReturn(expFuture); + NormalizedNode dummyNode2 = createDummyNode("dummy:namespace2", "2014-07-01", "dummy local name2"); + + CheckedFuture actualFuture = brokerFacade .commitConfigurationDataDelete(instanceID); diff --git a/opendaylight/md-sal/samples/toaster-provider/src/main/java/org/opendaylight/controller/sample/toaster/provider/OpendaylightToaster.java b/opendaylight/md-sal/samples/toaster-provider/src/main/java/org/opendaylight/controller/sample/toaster/provider/OpendaylightToaster.java index b7518e094d..332d375282 100644 --- a/opendaylight/md-sal/samples/toaster-provider/src/main/java/org/opendaylight/controller/sample/toaster/provider/OpendaylightToaster.java +++ b/opendaylight/md-sal/samples/toaster-provider/src/main/java/org/opendaylight/controller/sample/toaster/provider/OpendaylightToaster.java @@ -141,6 +141,8 @@ public class OpendaylightToaster implements ToasterService, ToasterProviderRunti { darknessFactor.set( darkness ); } + + LOG.info("onDataChanged - new Toaster config: {}", toaster); } } diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/runtime/InstanceRuntime.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/runtime/InstanceRuntime.java index 44227bb4d8..350b0aa247 100644 --- a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/runtime/InstanceRuntime.java +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/runtime/InstanceRuntime.java @@ -107,7 +107,7 @@ public class InstanceRuntime { String elementName = jmxToYangChildRbeMapping.get(childMappingEntry.getKey()); - Element innerXml = XmlUtil.createElement(document, elementName, Optional.absent()); + Element innerXml = XmlUtil.createElement(document, elementName, Optional.of(namespace)); childMappingEntry.getValue().toXml(objectName, innerChildRbeOns, document, runtimeInstanceIndex, innerXml, namespace); xml.appendChild(innerXml); diff --git a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java index e41b174b66..641881cf9e 100644 --- a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java +++ b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java @@ -662,7 +662,7 @@ public class NetconfMappingTest extends AbstractConfigTest { assertEquals(8 * 4, getElementsSize(response, "inner-inner-running-data")); assertEquals(8 * 4, getElementsSize(response, "deep3")); assertEquals(8 * 4 * 2, getElementsSize(response, "list-of-strings")); - assertEquals(8, getElementsSize(response, "inner-running-data-additional")); + assertEquals(8, getElementsSize(response, "inner-running-data-additional", "urn:opendaylight:params:xml:ns:yang:controller:test:impl")); assertEquals(8, getElementsSize(response, "deep4")); // TODO assert keys @@ -693,6 +693,10 @@ public class NetconfMappingTest extends AbstractConfigTest { return response.getElementsByTagName(elementName).getLength(); } + private int getElementsSize(Document response, String elementName, String namespace) { + return response.getElementsByTagNameNS(namespace, elementName).getLength(); + } + private Document executeOp(final NetconfOperation op, final String filename) throws ParserConfigurationException, SAXException, IOException, NetconfDocumentedException { diff --git a/opendaylight/netconf/netconf-artifacts/pom.xml b/opendaylight/netconf/netconf-artifacts/pom.xml new file mode 100644 index 0000000000..eb3cac18df --- /dev/null +++ b/opendaylight/netconf/netconf-artifacts/pom.xml @@ -0,0 +1,164 @@ + + + + + + 4.0.0 + org.opendaylight.controller + netconf-artifacts + 0.3.0-SNAPSHOT + pom + + + + + ${project.groupId} + netconf-config-dispatcher + ${project.version} + + + ${project.groupId} + config-netconf-connector + ${project.version} + + + ${project.groupId} + config-persister-impl + ${project.version} + + + ${project.groupId} + netconf-api + ${project.version} + + + ${project.groupId} + netconf-auth + ${project.version} + + + ${project.groupId} + netconf-cli + ${project.version} + + + ${project.groupId} + netconf-client + ${project.version} + + + ${project.groupId} + netconf-config + ${project.version} + + + ${project.groupId} + netconf-connector-config + ${project.version} + + + ${project.groupId} + netconf-impl + ${project.version} + + + ${project.groupId} + netconf-mapping-api + ${project.version} + + + ${project.groupId} + netconf-monitoring + ${project.version} + + + ${project.groupId} + netconf-netty-util + ${project.version} + + + ${project.groupId} + netconf-ssh + ${project.version} + + + ${project.groupId} + netconf-tcp + ${project.version} + + + ${project.groupId} + netconf-testtool + ${project.version} + + + ${project.groupId} + netconf-usermanager + ${project.version} + + + ${project.groupId} + netconf-util + ${project.version} + + + + ${project.groupId} + ietf-netconf-monitoring + ${project.version} + + + ${project.groupId} + ietf-netconf-monitoring-extension + ${project.version} + + + + ${project.groupId} + netconf-client + ${project.version} + test-jar + + + ${project.groupId} + netconf-impl + ${project.version} + test-jar + + + ${project.groupId} + netconf-netty-util + ${project.version} + test-jar + + + ${project.groupId} + netconf-ssh + ${project.version} + test-jar + + + ${project.groupId} + netconf-util + ${project.version} + test-jar + + + + ${project.groupId} + features-netconf + ${project.version} + features + xml + runtime + + + + + diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/SubtreeFilter.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/SubtreeFilter.java index 6e81584133..42a8bae448 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/SubtreeFilter.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/SubtreeFilter.java @@ -10,6 +10,7 @@ package org.opendaylight.controller.netconf.impl; import com.google.common.base.Optional; import java.io.IOException; +import java.util.Map; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; import org.opendaylight.controller.netconf.util.mapping.AbstractNetconfOperation.OperationNameAndNamespace; @@ -73,7 +74,7 @@ public class SubtreeFilter { return result; } - private static void addSubtree(XmlElement filter, XmlElement src, XmlElement dst) { + private static void addSubtree(XmlElement filter, XmlElement src, XmlElement dst) throws NetconfDocumentedException { for (XmlElement srcChild : src.getChildElements()) { for (XmlElement filterChild : filter.getChildElements()) { addSubtree2(filterChild, srcChild, dst); @@ -81,7 +82,7 @@ public class SubtreeFilter { } } - private static MatchingResult addSubtree2(XmlElement filter, XmlElement src, XmlElement dstParent) { + private static MatchingResult addSubtree2(XmlElement filter, XmlElement src, XmlElement dstParent) throws NetconfDocumentedException { Document document = dstParent.getDomElement().getOwnerDocument(); MatchingResult matches = matches(src, filter); if (matches != MatchingResult.NO_MATCH && matches != MatchingResult.CONTENT_MISMATCH) { @@ -123,7 +124,7 @@ public class SubtreeFilter { * Shallow compare src node to filter: tag name and namespace must match. * If filter node has no children and has text content, it also must match. */ - private static MatchingResult matches(XmlElement src, XmlElement filter) { + private static MatchingResult matches(XmlElement src, XmlElement filter) throws NetconfDocumentedException { boolean tagMatch = src.getName().equals(filter.getName()) && src.getNamespaceOptionally().equals(filter.getNamespaceOptionally()); MatchingResult result = null; @@ -131,7 +132,7 @@ public class SubtreeFilter { // match text content Optional maybeText = filter.getOnlyTextContentOptionally(); if (maybeText.isPresent()) { - if (maybeText.equals(src.getOnlyTextContentOptionally())) { + if (maybeText.equals(src.getOnlyTextContentOptionally()) || prefixedContentMatches(filter, src)) { result = MatchingResult.CONTENT_MATCH; } else { result = MatchingResult.CONTENT_MISMATCH; @@ -159,10 +160,30 @@ public class SubtreeFilter { if (result == null) { result = MatchingResult.NO_MATCH; } - logger.debug("Matching {} to {} resulted in {}", src, filter, tagMatch); + logger.debug("Matching {} to {} resulted in {}", src, filter, result); return result; } + private static boolean prefixedContentMatches(final XmlElement filter, final XmlElement src) throws NetconfDocumentedException { + final Map.Entry prefixToNamespaceOfFilter = filter.findNamespaceOfTextContent(); + final Map.Entry prefixToNamespaceOfSrc = src.findNamespaceOfTextContent(); + + final String prefix = prefixToNamespaceOfFilter.getKey(); + // If this is not a prefixed content, we do not need to continue since content do not match + if(prefix.equals(XmlElement.DEFAULT_NAMESPACE_PREFIX)) { + return false; + } + // Namespace mismatch + if(!prefixToNamespaceOfFilter.getValue().equals(prefixToNamespaceOfSrc.getValue())) { + return false; + } + + final String unprefixedFilterContent = filter.getTextContent().substring(prefix.length()); + final String unprefixedSrcCOntnet = src.getTextContent().substring(prefix.length()); + // Finally compare unprefixed content + return unprefixedFilterContent.equals(unprefixedSrcCOntnet); + } + enum MatchingResult { NO_MATCH, TAG_MATCH, CONTENT_MATCH, CONTENT_MISMATCH } diff --git a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/SubtreeFilterTest.java b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/SubtreeFilterTest.java index b11834386e..5d9470750e 100644 --- a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/SubtreeFilterTest.java +++ b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/SubtreeFilterTest.java @@ -36,7 +36,7 @@ public class SubtreeFilterTest { @Parameters public static Collection data() { List result = new ArrayList<>(); - for (int i = 0; i <= 8; i++) { + for (int i = 0; i <= 9; i++) { result.add(new Object[]{i}); } return result; diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/subtree/9/post-filter.xml b/opendaylight/netconf/netconf-impl/src/test/resources/subtree/9/post-filter.xml new file mode 100644 index 0000000000..afe9655326 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/subtree/9/post-filter.xml @@ -0,0 +1,14 @@ + + + + + + fred + x:admin + Fred Flintstone + + + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/subtree/9/pre-filter.xml b/opendaylight/netconf/netconf-impl/src/test/resources/subtree/9/pre-filter.xml new file mode 100644 index 0000000000..eca3241f05 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/subtree/9/pre-filter.xml @@ -0,0 +1,40 @@ + + + + + + root + superuser + Charlie Root + + 1 + 1 + + + + fred + x:admin + Fred Flintstone + + 2 + 2 + + + + barney + admin + Barney Rubble + + 2 + 3 + + + + + + admin + + + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/subtree/9/request.xml b/opendaylight/netconf/netconf-impl/src/test/resources/subtree/9/request.xml new file mode 100644 index 0000000000..47da0feec1 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/subtree/9/request.xml @@ -0,0 +1,19 @@ + + + + + + + + + + fred + a:admin + + + + + + + \ No newline at end of file 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 1adcd7e491..f96f557619 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 @@ -21,14 +21,23 @@ import com.google.common.collect.Lists; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import io.netty.channel.EventLoopGroup; import io.netty.channel.local.LocalAddress; +import io.netty.channel.nio.NioEventLoopGroup; import io.netty.util.concurrent.GlobalEventExecutor; import java.io.IOException; import java.net.InetSocketAddress; +import java.nio.file.Files; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; +import org.apache.sshd.server.PasswordAuthenticator; +import org.apache.sshd.server.keyprovider.PEMGeneratorHostKeyProvider; +import org.apache.sshd.server.session.ServerSession; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -40,13 +49,12 @@ import org.opendaylight.controller.netconf.client.NetconfClientDispatcher; import org.opendaylight.controller.netconf.client.NetconfClientDispatcherImpl; import org.opendaylight.controller.netconf.client.NetconfClientSessionListener; import org.opendaylight.controller.netconf.client.SimpleNetconfClientSessionListener; +import org.opendaylight.controller.netconf.client.TestingNetconfClient; import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration; import org.opendaylight.controller.netconf.client.conf.NetconfClientConfigurationBuilder; -import org.opendaylight.controller.netconf.client.TestingNetconfClient; import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler; import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.LoginPassword; -import org.opendaylight.controller.netconf.ssh.NetconfSSHServer; -import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator; +import org.opendaylight.controller.netconf.ssh.SshProxyServer; import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil; import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil; import org.opendaylight.controller.netconf.util.xml.XmlUtil; @@ -68,19 +76,32 @@ public class NetconfITSecureTest extends AbstractNetconfConfigTest { public static final String USERNAME = "user"; public static final String PASSWORD = "pwd"; - private NetconfSSHServer sshServer; + private SshProxyServer sshProxyServer; + + private ExecutorService nioExec; + private EventLoopGroup clientGroup; + private ScheduledExecutorService minaTimerEx; @Before public void setUp() throws Exception { - final char[] pem = PEMGenerator.generate().toCharArray(); - sshServer = NetconfSSHServer.start(TLS_ADDRESS.getPort(), NetconfConfigUtil.getNetconfLocalAddress(), getNettyThreadgroup(), pem); - sshServer.setAuthProvider(getAuthProvider()); + nioExec = Executors.newFixedThreadPool(1); + clientGroup = new NioEventLoopGroup(); + minaTimerEx = Executors.newScheduledThreadPool(1); + sshProxyServer = new SshProxyServer(minaTimerEx, clientGroup, nioExec); + sshProxyServer.bind(TLS_ADDRESS, NetconfConfigUtil.getNetconfLocalAddress(), new PasswordAuthenticator() { + @Override + public boolean authenticate(final String username, final String password, final ServerSession session) { + return true; + } + }, new PEMGeneratorHostKeyProvider(Files.createTempFile("prefix", "suffix").toAbsolutePath().toString())); } @After public void tearDown() throws Exception { - sshServer.close(); - sshServer.join(); + sshProxyServer.close(); + clientGroup.shutdownGracefully().await(); + minaTimerEx.shutdownNow(); + nioExec.shutdownNow(); } @Test diff --git a/opendaylight/netconf/netconf-netty-util/pom.xml b/opendaylight/netconf/netconf-netty-util/pom.xml index e2afcc42f5..a9c1e8336d 100644 --- a/opendaylight/netconf/netconf-netty-util/pom.xml +++ b/opendaylight/netconf/netconf-netty-util/pom.xml @@ -48,10 +48,6 @@ org.opendaylight.controller protocol-framework - - org.opendaylight.controller.thirdparty - ganymed - org.apache.sshd sshd-core @@ -89,7 +85,7 @@ maven-bundle-plugin - org.apache.sshd.*, ch.ethz.ssh2, com.google.common.base, com.google.common.collect, io.netty.buffer, + org.apache.sshd.*, com.google.common.base, com.google.common.collect, io.netty.buffer, io.netty.channel, io.netty.channel.socket, io.netty.handler.codec, io.netty.handler.ssl, io.netty.util, io.netty.util.concurrent, javax.xml.transform, javax.xml.transform.dom, javax.xml.transform.sax, javax.xml.transform.stream, org.opendaylight.controller.netconf.api, diff --git a/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandler.java b/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandler.java index 3bd7232023..fa7d0900ed 100644 --- a/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandler.java +++ b/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandler.java @@ -8,12 +8,9 @@ package org.opendaylight.controller.netconf.nettyutil.handler.ssh.client; -import com.google.common.base.Preconditions; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelOutboundHandlerAdapter; -import io.netty.channel.ChannelPromise; import java.io.IOException; import java.net.SocketAddress; + import org.apache.sshd.ClientChannel; import org.apache.sshd.ClientSession; import org.apache.sshd.SshClient; @@ -26,6 +23,13 @@ import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication. import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Preconditions; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; +import io.netty.channel.ChannelPromise; + /** * Netty SSH handler class. Acts as interface between Netty and SSH library. */ @@ -47,7 +51,7 @@ public class AsyncSshHandler extends ChannelOutboundHandlerAdapter { private final AuthenticationHandler authenticationHandler; private final SshClient sshClient; - private AsyncSshHanderReader sshReadAsyncListener; + private AsyncSshHandlerReader sshReadAsyncListener; private AsyncSshHandlerWriter sshWriteAsyncHandler; private ClientChannel channel; @@ -138,7 +142,20 @@ public class AsyncSshHandler extends ChannelOutboundHandlerAdapter { connectPromise.setSuccess(); connectPromise = null; - sshReadAsyncListener = new AsyncSshHanderReader(this, ctx, channel.getAsyncOut()); + // TODO we should also read from error stream and at least log from that + + sshReadAsyncListener = new AsyncSshHandlerReader(new AutoCloseable() { + @Override + public void close() throws Exception { + AsyncSshHandler.this.disconnect(ctx, ctx.newPromise()); + } + }, new AsyncSshHandlerReader.ReadMsgHandler() { + @Override + public void onMessageRead(final ByteBuf msg) { + ctx.fireChannelRead(msg); + } + }, channel.toString(), channel.getAsyncOut()); + // if readAsyncListener receives immediate close, it will close this handler and closing this handler sets channel variable to null if(channel != null) { sshWriteAsyncHandler = new AsyncSshHandlerWriter(channel.getAsyncIn()); diff --git a/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHanderReader.java b/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandlerReader.java similarity index 66% rename from opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHanderReader.java rename to opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandlerReader.java index 73a24f27b2..ada15583cd 100644 --- a/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHanderReader.java +++ b/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandlerReader.java @@ -8,9 +8,8 @@ package org.opendaylight.controller.netconf.nettyutil.handler.ssh.client; +import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelOutboundHandler; import org.apache.sshd.common.future.SshFutureListener; import org.apache.sshd.common.io.IoInputStream; import org.apache.sshd.common.io.IoReadFuture; @@ -22,22 +21,24 @@ import org.slf4j.LoggerFactory; * Listener on async input stream from SSH session. * This listeners schedules reads in a loop until the session is closed or read fails. */ -final class AsyncSshHanderReader implements SshFutureListener, AutoCloseable { +public final class AsyncSshHandlerReader implements SshFutureListener, AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(AsyncSshHandler.class); private static final int BUFFER_SIZE = 8192; - private final ChannelOutboundHandler asyncSshHandler; - private final ChannelHandlerContext ctx; + private final AutoCloseable connectionClosedCallback; + private final ReadMsgHandler readHandler; + private final String channelId; private IoInputStream asyncOut; private Buffer buf; private IoReadFuture currentReadFuture; - public AsyncSshHanderReader(final ChannelOutboundHandler asyncSshHandler, final ChannelHandlerContext ctx, final IoInputStream asyncOut) { - this.asyncSshHandler = asyncSshHandler; - this.ctx = ctx; + public AsyncSshHandlerReader(final AutoCloseable connectionClosedCallback, final ReadMsgHandler readHandler, final String channelId, final IoInputStream asyncOut) { + this.connectionClosedCallback = connectionClosedCallback; + this.readHandler = readHandler; + this.channelId = channelId; this.asyncOut = asyncOut; buf = new Buffer(BUFFER_SIZE); asyncOut.read(buf).addListener(this); @@ -48,16 +49,20 @@ final class AsyncSshHanderReader implements SshFutureListener, Aut if(future.getException() != null) { if(asyncOut.isClosed() || asyncOut.isClosing()) { // Ssh dropped - logger.debug("Ssh session dropped on channel: {}", ctx.channel(), future.getException()); + logger.debug("Ssh session dropped on channel: {}", channelId, future.getException()); } else { - logger.warn("Exception while reading from SSH remote on channel {}", ctx.channel(), future.getException()); + logger.warn("Exception while reading from SSH remote on channel {}", channelId, future.getException()); } invokeDisconnect(); return; } if (future.getRead() > 0) { - ctx.fireChannelRead(Unpooled.wrappedBuffer(buf.array(), 0, future.getRead())); + final ByteBuf msg = Unpooled.wrappedBuffer(buf.array(), 0, future.getRead()); + if(logger.isTraceEnabled()) { + logger.trace("Reading message on channel: {}, message: {}", channelId, AsyncSshHandlerWriter.byteBufToString(msg)); + } + readHandler.onMessageRead(msg); // Schedule next read buf = new Buffer(BUFFER_SIZE); @@ -68,7 +73,7 @@ final class AsyncSshHanderReader implements SshFutureListener, Aut private void invokeDisconnect() { try { - asyncSshHandler.disconnect(ctx, ctx.newPromise()); + connectionClosedCallback.close(); } catch (final Exception e) { // This should not happen throw new IllegalStateException(e); @@ -80,8 +85,14 @@ final class AsyncSshHanderReader implements SshFutureListener, Aut // Remove self as listener on close to prevent reading from closed input if(currentReadFuture != null) { currentReadFuture.removeListener(this); + currentReadFuture = null; } asyncOut = null; } + + public interface ReadMsgHandler { + + void onMessageRead(ByteBuf msg); + } } diff --git a/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandlerWriter.java b/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandlerWriter.java index eace0ac7ea..8e639bd47c 100644 --- a/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandlerWriter.java +++ b/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandlerWriter.java @@ -28,7 +28,7 @@ import org.slf4j.LoggerFactory; * Async Ssh writer. Takes messages(byte arrays) and sends them encrypted to remote server. * Also handles pending writes by caching requests until pending state is over. */ -final class AsyncSshHandlerWriter implements AutoCloseable { +public final class AsyncSshHandlerWriter implements AutoCloseable { private static final Logger logger = LoggerFactory .getLogger(AsyncSshHandlerWriter.class); @@ -116,7 +116,7 @@ final class AsyncSshHandlerWriter implements AutoCloseable { writeWithPendingDetection(pendingWrite.ctx, pendingWrite.promise, msg); } - private static String byteBufToString(final ByteBuf msg) { + public static String byteBufToString(final ByteBuf msg) { msg.resetReaderIndex(); final String s = msg.toString(Charsets.UTF_8); msg.resetReaderIndex(); diff --git a/opendaylight/netconf/netconf-netty-util/src/test/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandlerTest.java b/opendaylight/netconf/netconf-netty-util/src/test/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandlerTest.java index d0fc43d04a..212eabb290 100644 --- a/opendaylight/netconf/netconf-netty-util/src/test/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandlerTest.java +++ b/opendaylight/netconf/netconf-netty-util/src/test/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandlerTest.java @@ -459,6 +459,8 @@ public class AsyncSshHandlerTest { private ChannelSubsystem getMockedSubsystemChannel(final IoInputStream asyncOut, final IoOutputStream asyncIn) throws IOException { final ChannelSubsystem subsystemChannel = mock(ChannelSubsystem.class); + doReturn("subsystemChannel").when(subsystemChannel).toString(); + doNothing().when(subsystemChannel).setStreaming(any(ClientChannel.Streaming.class)); final OpenFuture openFuture = mock(OpenFuture.class); diff --git a/opendaylight/netconf/netconf-ssh/pom.xml b/opendaylight/netconf/netconf-ssh/pom.xml index 221626b741..e0c7dba4fa 100644 --- a/opendaylight/netconf/netconf-ssh/pom.xml +++ b/opendaylight/netconf/netconf-ssh/pom.xml @@ -36,10 +36,6 @@ org.bouncycastle bcprov-jdk15on - - org.opendaylight.controller.thirdparty - ganymed - org.apache.sshd sshd-core @@ -60,7 +56,6 @@ org.opendaylight.controller netconf-netty-util - test org.opendaylight.controller 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 deleted file mode 100644 index 86206a7d5c..0000000000 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/NetconfSSHServer.java +++ /dev/null @@ -1,134 +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; - -import com.google.common.base.Preconditions; -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.auth.AuthProvider; -import org.opendaylight.controller.netconf.ssh.threads.Handshaker; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Optional; - -import io.netty.channel.EventLoopGroup; -import io.netty.channel.local.LocalAddress; - -/** - * 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 extends Thread implements AutoCloseable { - - private static final Logger logger = LoggerFactory.getLogger(NetconfSSHServer.class); - private static final AtomicLong sessionIdCounter = new AtomicLong(); - - private final ServerSocket serverSocket; - private final LocalAddress localAddress; - private final EventLoopGroup bossGroup; - private Optional authProvider = Optional.absent(); - private final ExecutorService handshakeExecutor; - private final char[] pem; - private volatile boolean up; - - private NetconfSSHServer(final int serverPort, final LocalAddress localAddress, final EventLoopGroup bossGroup, final char[] pem) throws IOException { - super(NetconfSSHServer.class.getSimpleName()); - this.bossGroup = bossGroup; - this.pem = pem; - 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.localAddress = localAddress; - this.up = true; - handshakeExecutor = Executors.newFixedThreadPool(10); - } - - public static NetconfSSHServer start(final int serverPort, final LocalAddress localAddress, final EventLoopGroup bossGroup, final char[] pemArray) throws IOException { - final NetconfSSHServer netconfSSHServer = new NetconfSSHServer(serverPort, localAddress, bossGroup, pemArray); - netconfSSHServer.start(); - return netconfSSHServer; - } - - public synchronized AuthProvider getAuthProvider() { - Preconditions.checkState(authProvider.isPresent(), "AuthenticationProvider is not set up, cannot authenticate user"); - return authProvider.get(); - } - - public synchronized void setAuthProvider(final AuthProvider authProvider) { - if(this.authProvider != null) { - logger.debug("Changing auth provider to {}", authProvider); - } - this.authProvider = Optional.fromNullable(authProvider); - } - - @Override - public void close() throws IOException { - up = false; - logger.trace("Closing SSH server socket."); - serverSocket.close(); - bossGroup.shutdownGracefully(); - logger.trace("SSH server socket closed."); - } - - @VisibleForTesting - public InetSocketAddress getLocalSocketAddress() { - return (InetSocketAddress) serverSocket.getLocalSocketAddress(); - } - - @Override - public void run() { - while (up) { - Socket acceptedSocket = null; - try { - acceptedSocket = serverSocket.accept(); - } catch (final IOException e) { - if (up == false) { - logger.trace("Exiting server thread", e); - } else { - logger.warn("Exception occurred during socket.accept", e); - } - } - if (acceptedSocket != null) { - try { - final Handshaker task = new Handshaker(acceptedSocket, localAddress, sessionIdCounter.incrementAndGet(), getAuthProvider(), bossGroup, pem); - handshakeExecutor.submit(task); - } catch (final IOException e) { - logger.warn("Cannot set PEMHostKey, closing connection", e); - closeSocket(acceptedSocket); - } catch (final IllegalStateException e) { - logger.warn("Cannot accept connection, closing", e); - closeSocket(acceptedSocket); - } - } - } - logger.debug("Server thread is exiting"); - } - - private void closeSocket(final Socket acceptedSocket) { - try { - acceptedSocket.close(); - } catch (final IOException e) { - logger.warn("Ignoring exception while closing socket", e); - } - } - -} diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/RemoteNetconfCommand.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/RemoteNetconfCommand.java new file mode 100644 index 0000000000..e642e073a3 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/RemoteNetconfCommand.java @@ -0,0 +1,191 @@ +/* + * 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.ssh; + +import com.google.common.base.Preconditions; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +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.util.concurrent.GenericFutureListener; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.io.IoInputStream; +import org.apache.sshd.common.io.IoOutputStream; +import org.apache.sshd.server.AsyncCommand; +import org.apache.sshd.server.Command; +import org.apache.sshd.server.Environment; +import org.apache.sshd.server.ExitCallback; +import org.apache.sshd.server.SessionAware; +import org.apache.sshd.server.session.ServerSession; +import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This command handles all netconf related rpc and forwards to delegate server. + * Uses netty to make a local connection to delegate server. + * + * Command is Apache Mina SSH terminology for objects handling ssh data. + */ +public class RemoteNetconfCommand implements AsyncCommand, SessionAware { + + private static final Logger logger = LoggerFactory.getLogger(RemoteNetconfCommand.class); + + private final EventLoopGroup clientEventGroup; + private final LocalAddress localAddress; + + private IoInputStream in; + private IoOutputStream out; + private ExitCallback callback; + private NetconfHelloMessageAdditionalHeader netconfHelloMessageAdditionalHeader; + + private Channel clientChannel; + private ChannelFuture clientChannelFuture; + + public RemoteNetconfCommand(final EventLoopGroup clientEventGroup, final LocalAddress localAddress) { + this.clientEventGroup = clientEventGroup; + this.localAddress = localAddress; + } + + @Override + public void setIoInputStream(final IoInputStream in) { + this.in = in; + } + + @Override + public void setIoOutputStream(final IoOutputStream out) { + this.out = out; + } + + @Override + public void setIoErrorStream(final IoOutputStream err) { + // TODO do we want to use error stream in some way ? + } + + @Override + public void setInputStream(final InputStream in) { + throw new UnsupportedOperationException("Synchronous IO is unsupported"); + } + + @Override + public void setOutputStream(final OutputStream out) { + throw new UnsupportedOperationException("Synchronous IO is unsupported"); + + } + + @Override + public void setErrorStream(final OutputStream err) { + throw new UnsupportedOperationException("Synchronous IO is unsupported"); + + } + + @Override + public void setExitCallback(final ExitCallback callback) { + this.callback = callback; + } + + @Override + public void start(final Environment env) throws IOException { + logger.trace("Establishing internal connection to netconf server for client: {}", getClientAddress()); + + final Bootstrap clientBootstrap = new Bootstrap(); + clientBootstrap.group(clientEventGroup).channel(LocalChannel.class); + + clientBootstrap + .handler(new ChannelInitializer() { + @Override + public void initChannel(final LocalChannel ch) throws Exception { + ch.pipeline().addLast(new SshProxyClientHandler(in, out, netconfHelloMessageAdditionalHeader, callback)); + } + }); + clientChannelFuture = clientBootstrap.connect(localAddress); + clientChannelFuture.addListener(new GenericFutureListener() { + + @Override + public void operationComplete(final ChannelFuture future) throws Exception { + if(future.isSuccess()) { + clientChannel = clientChannelFuture.channel(); + } else { + logger.warn("Unable to establish internal connection to netconf server for client: {}", getClientAddress()); + Preconditions.checkNotNull(callback, "Exit callback must be set"); + callback.onExit(1, "Unable to establish internal connection to netconf server for client: "+ getClientAddress()); + } + } + }); + } + + @Override + public void destroy() { + logger.trace("Releasing internal connection to netconf server for client: {} on channel: {}", + getClientAddress(), clientChannel); + + clientChannelFuture.cancel(true); + if(clientChannel != null) { + clientChannel.close().addListener(new GenericFutureListener() { + + @Override + public void operationComplete(final ChannelFuture future) throws Exception { + if (future.isSuccess() == false) { + logger.warn("Unable to release internal connection to netconf server on channel: {}", clientChannel); + } + } + }); + } + } + + private String getClientAddress() { + return netconfHelloMessageAdditionalHeader.getAddress(); + } + + @Override + public void setSession(final ServerSession session) { + final SocketAddress remoteAddress = session.getIoSession().getRemoteAddress(); + String hostName = ""; + String port = ""; + if(remoteAddress instanceof InetSocketAddress) { + hostName = ((InetSocketAddress) remoteAddress).getAddress().getHostAddress(); + port = Integer.toString(((InetSocketAddress) remoteAddress).getPort()); + } + netconfHelloMessageAdditionalHeader = new NetconfHelloMessageAdditionalHeader( + session.getUsername(), hostName, port, "ssh", "client"); + } + + public static class NetconfCommandFactory implements NamedFactory { + + public static final String NETCONF = "netconf"; + + private final EventLoopGroup clientBootstrap; + private final LocalAddress localAddress; + + public NetconfCommandFactory(final EventLoopGroup clientBootstrap, final LocalAddress localAddress) { + + this.clientBootstrap = clientBootstrap; + this.localAddress = localAddress; + } + + @Override + public String getName() { + return NETCONF; + } + + @Override + public RemoteNetconfCommand create() { + return new RemoteNetconfCommand(clientBootstrap, localAddress); + } + } + +} diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/SshProxyClientHandler.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/SshProxyClientHandler.java new file mode 100644 index 0000000000..2b2b3b3e81 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/SshProxyClientHandler.java @@ -0,0 +1,99 @@ +/* + * 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.ssh; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import org.apache.sshd.common.io.IoInputStream; +import org.apache.sshd.common.io.IoOutputStream; +import org.apache.sshd.server.ExitCallback; +import org.opendaylight.controller.netconf.nettyutil.handler.ssh.client.AsyncSshHandlerReader; +import org.opendaylight.controller.netconf.nettyutil.handler.ssh.client.AsyncSshHandlerWriter; +import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Netty handler that reads SSH from remote client and writes to delegate server and reads from delegate server and writes to remote client + */ +final class SshProxyClientHandler extends ChannelInboundHandlerAdapter { + + private static final Logger logger = LoggerFactory.getLogger(SshProxyClientHandler.class); + + private final IoInputStream in; + private final IoOutputStream out; + + private AsyncSshHandlerReader asyncSshHandlerReader; + private AsyncSshHandlerWriter asyncSshHandlerWriter; + + private final NetconfHelloMessageAdditionalHeader netconfHelloMessageAdditionalHeader; + private final ExitCallback callback; + + public SshProxyClientHandler(final IoInputStream in, final IoOutputStream out, + final NetconfHelloMessageAdditionalHeader netconfHelloMessageAdditionalHeader, + final ExitCallback callback) { + this.in = in; + this.out = out; + this.netconfHelloMessageAdditionalHeader = netconfHelloMessageAdditionalHeader; + this.callback = callback; + } + + @Override + public void channelActive(final ChannelHandlerContext ctx) throws Exception { + writeAdditionalHeader(ctx); + + asyncSshHandlerWriter = new AsyncSshHandlerWriter(out); + asyncSshHandlerReader = new AsyncSshHandlerReader(new AutoCloseable() { + @Override + public void close() throws Exception { + // Close both sessions (delegate server and remote client) + ctx.fireChannelInactive(); + ctx.disconnect(); + ctx.close(); + asyncSshHandlerReader.close(); + asyncSshHandlerWriter.close(); + } + }, new AsyncSshHandlerReader.ReadMsgHandler() { + @Override + public void onMessageRead(final ByteBuf msg) { + if(logger.isTraceEnabled()) { + logger.trace("Forwarding message for client: {} on channel: {}, message: {}", + netconfHelloMessageAdditionalHeader.getAddress(), ctx.channel(), AsyncSshHandlerWriter.byteBufToString(msg)); + } + // Just forward to delegate + ctx.writeAndFlush(msg); + } + }, "ssh" + netconfHelloMessageAdditionalHeader.getAddress(), in); + + + super.channelActive(ctx); + } + + private void writeAdditionalHeader(final ChannelHandlerContext ctx) { + ctx.writeAndFlush(Unpooled.copiedBuffer(netconfHelloMessageAdditionalHeader.toFormattedString().getBytes())); + } + + @Override + public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { + asyncSshHandlerWriter.write(ctx, msg, ctx.newPromise()); + } + + @Override + public void channelInactive(final ChannelHandlerContext ctx) throws Exception { + logger.debug("Internal connection to netconf server was dropped for client: {} on channel: ", + netconfHelloMessageAdditionalHeader.getAddress(), ctx.channel()); + callback.onExit(1, "Internal connection to netconf server was dropped for client: " + + netconfHelloMessageAdditionalHeader.getAddress() + " on channel: " + ctx.channel()); + super.channelInactive(ctx); + } + + +} diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/SshProxyServer.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/SshProxyServer.java new file mode 100644 index 0000000000..0b85cf2653 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/SshProxyServer.java @@ -0,0 +1,134 @@ +/* + * 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.ssh; + +import com.google.common.collect.Lists; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.local.LocalAddress; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.channels.AsynchronousChannelGroup; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import org.apache.sshd.SshServer; +import org.apache.sshd.common.FactoryManager; +import org.apache.sshd.common.KeyPairProvider; +import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.RuntimeSshException; +import org.apache.sshd.common.io.IoAcceptor; +import org.apache.sshd.common.io.IoConnector; +import org.apache.sshd.common.io.IoHandler; +import org.apache.sshd.common.io.IoServiceFactory; +import org.apache.sshd.common.io.IoServiceFactoryFactory; +import org.apache.sshd.common.io.nio2.Nio2Acceptor; +import org.apache.sshd.common.io.nio2.Nio2Connector; +import org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory; +import org.apache.sshd.common.util.CloseableUtils; +import org.apache.sshd.server.Command; +import org.apache.sshd.server.PasswordAuthenticator; + +/** + * Proxy SSH server that just delegates decrypted content to a delegate server within same VM. + * Implemented using Apache Mina SSH lib. + */ +public class SshProxyServer implements AutoCloseable { + + private final SshServer sshServer; + private final ScheduledExecutorService minaTimerExecutor; + private final EventLoopGroup clientGroup; + private final IoServiceFactoryFactory nioServiceWithPoolFactoryFactory; + + public SshProxyServer(final ScheduledExecutorService minaTimerExecutor, final EventLoopGroup clientGroup, final ExecutorService nioExecutor) { + this.minaTimerExecutor = minaTimerExecutor; + this.clientGroup = clientGroup; + this.nioServiceWithPoolFactoryFactory = new NioServiceWithPoolFactory.NioServiceWithPoolFactoryFactory(nioExecutor); + this.sshServer = SshServer.setUpDefaultServer(); + } + + public void bind(final InetSocketAddress bindingAddress, final LocalAddress localAddress, final PasswordAuthenticator authenticator, final KeyPairProvider keyPairProvider) throws IOException { + sshServer.setHost(bindingAddress.getHostString()); + sshServer.setPort(bindingAddress.getPort()); + + sshServer.setPasswordAuthenticator(authenticator); + sshServer.setKeyPairProvider(keyPairProvider); + + sshServer.setIoServiceFactoryFactory(nioServiceWithPoolFactoryFactory); + sshServer.setScheduledExecutorService(minaTimerExecutor); + + final RemoteNetconfCommand.NetconfCommandFactory netconfCommandFactory = + new RemoteNetconfCommand.NetconfCommandFactory(clientGroup, localAddress); + sshServer.setSubsystemFactories(Lists.>newArrayList(netconfCommandFactory)); + sshServer.start(); + } + + @Override + public void close() { + try { + sshServer.stop(true); + } catch (final InterruptedException e) { + throw new RuntimeException("Interrupted while stopping sshServer", e); + } finally { + sshServer.close(true); + } + } + + /** + * Based on Nio2ServiceFactory with one addition: injectable executor + */ + private static final class NioServiceWithPoolFactory extends CloseableUtils.AbstractCloseable implements IoServiceFactory { + + private final FactoryManager manager; + private final AsynchronousChannelGroup group; + + public NioServiceWithPoolFactory(final FactoryManager manager, final ExecutorService executor) { + this.manager = manager; + try { + group = AsynchronousChannelGroup.withThreadPool(executor); + } catch (final IOException e) { + throw new RuntimeSshException(e); + } + } + + public IoConnector createConnector(final IoHandler handler) { + return new Nio2Connector(manager, handler, group); + } + + public IoAcceptor createAcceptor(final IoHandler handler) { + return new Nio2Acceptor(manager, handler, group); + } + + @Override + protected void doCloseImmediately() { + try { + group.shutdownNow(); + group.awaitTermination(5, TimeUnit.SECONDS); + } catch (final Exception e) { + log.debug("Exception caught while closing channel group", e); + } finally { + super.doCloseImmediately(); + } + } + + private static final class NioServiceWithPoolFactoryFactory extends Nio2ServiceFactoryFactory { + + private final ExecutorService nioExecutor; + + private NioServiceWithPoolFactoryFactory(final ExecutorService nioExecutor) { + this.nioExecutor = nioExecutor; + } + + @Override + public IoServiceFactory create(final FactoryManager manager) { + return new NioServiceWithPoolFactory(manager, nioExecutor); + } + } + } + +} 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 deleted file mode 100644 index 53ab8219ee..0000000000 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/PEMGenerator.java +++ /dev/null @@ -1,90 +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 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; - -import java.io.File; -import java.io.IOException; -import java.io.StringWriter; -import java.security.Key; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.SecureRandom; - -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(); - 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)) { - pemWriter.writeObject(key); - } - return writer.toString(); - } - } - -} diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/AuthProviderTracker.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/AuthProviderTracker.java new file mode 100644 index 0000000000..97e611c0d2 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/AuthProviderTracker.java @@ -0,0 +1,91 @@ +/* + * 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.ssh.osgi; + +import com.google.common.base.Preconditions; +import org.apache.sshd.server.PasswordAuthenticator; +import org.apache.sshd.server.session.ServerSession; +import org.opendaylight.controller.netconf.auth.AuthConstants; +import org.opendaylight.controller.netconf.auth.AuthProvider; +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; + +final class AuthProviderTracker implements ServiceTrackerCustomizer, PasswordAuthenticator { + private static final Logger logger = LoggerFactory.getLogger(AuthProviderTracker.class); + + private final BundleContext bundleContext; + + private Integer maxPreference; + private final ServiceTracker listenerTracker; + private AuthProvider authProvider; + + public AuthProviderTracker(final BundleContext bundleContext) { + this.bundleContext = bundleContext; + listenerTracker = new ServiceTracker<>(bundleContext, AuthProvider.class, this); + listenerTracker.open(); + } + + @Override + public AuthProvider addingService(final ServiceReference reference) { + logger.trace("Service {} added", reference); + final AuthProvider authService = bundleContext.getService(reference); + final Integer newServicePreference = getPreference(reference); + if(isBetter(newServicePreference)) { + maxPreference = newServicePreference; + this.authProvider = authService; + } + return authService; + } + + private Integer getPreference(final ServiceReference reference) { + final Object preferenceProperty = reference.getProperty(AuthConstants.SERVICE_PREFERENCE_KEY); + return preferenceProperty == null ? Integer.MIN_VALUE : Integer.valueOf(preferenceProperty.toString()); + } + + private boolean isBetter(final Integer newServicePreference) { + Preconditions.checkNotNull(newServicePreference); + if(maxPreference == null) { + return true; + } + + return newServicePreference > maxPreference; + } + + @Override + public void modifiedService(final ServiceReference reference, final AuthProvider service) { + final AuthProvider authService = bundleContext.getService(reference); + final Integer newServicePreference = getPreference(reference); + if(isBetter(newServicePreference)) { + logger.trace("Replacing modified service {} in netconf SSH.", reference); + this.authProvider = authService; + } + } + + @Override + public void removedService(final ServiceReference reference, final AuthProvider service) { + logger.trace("Removing service {} from netconf SSH. " + + "SSH won't authenticate users until AuthProvider service will be started.", reference); + maxPreference = null; + this.authProvider = null; + } + + public void stop() { + listenerTracker.close(); + // sshThread should finish normally since sshServer.close stops processing + } + + @Override + public boolean authenticate(final String username, final String password, final ServerSession session) { + return authProvider == null ? false : authProvider.authenticated(username, password); + } +} 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 0d0f95c3cb..b871d19db8 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 @@ -9,51 +9,51 @@ package org.opendaylight.controller.netconf.ssh.osgi; import static com.google.common.base.Preconditions.checkState; -import com.google.common.base.Preconditions; -import java.io.File; +import com.google.common.base.Optional; +import com.google.common.base.Strings; +import io.netty.channel.local.LocalAddress; +import io.netty.channel.nio.NioEventLoopGroup; import java.io.IOException; import java.net.InetSocketAddress; - +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; import org.apache.commons.io.FilenameUtils; -import org.opendaylight.controller.netconf.auth.AuthConstants; -import org.opendaylight.controller.netconf.auth.AuthProvider; -import org.opendaylight.controller.netconf.ssh.NetconfSSHServer; -import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator; +import org.apache.sshd.common.util.ThreadUtils; +import org.apache.sshd.server.keyprovider.PEMGeneratorHostKeyProvider; +import org.opendaylight.controller.netconf.ssh.SshProxyServer; 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.osgi.framework.ServiceReference; -import org.osgi.util.tracker.ServiceTracker; -import org.osgi.util.tracker.ServiceTrackerCustomizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Optional; -import com.google.common.base.Strings; - -import io.netty.channel.EventLoopGroup; -import io.netty.channel.local.LocalAddress; -import io.netty.channel.nio.NioEventLoopGroup; - -/** - * 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.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 { private static final Logger logger = LoggerFactory.getLogger(NetconfSSHActivator.class); - private static AuthProviderTracker authProviderTracker; - private NetconfSSHServer server; + private static final java.lang.String ALGORITHM = "RSA"; + private static final int KEY_SIZE = 4096; + public static final int POOL_SIZE = 8; + + private ScheduledExecutorService minaTimerExecutor; + private NioEventLoopGroup clientGroup; + private ExecutorService nioExecutor; + private AuthProviderTracker authProviderTracker; + + private SshProxyServer server; @Override public void start(final BundleContext bundleContext) throws IOException { + minaTimerExecutor = Executors.newScheduledThreadPool(POOL_SIZE, new ThreadFactory() { + @Override + public Thread newThread(final Runnable r) { + return new Thread(r, "netconf-ssh-server-mina-timers"); + } + }); + clientGroup = new NioEventLoopGroup(); + nioExecutor = ThreadUtils.newFixedThreadPool("netconf-ssh-server-nio-group", POOL_SIZE); server = startSSHServer(bundleContext); } @@ -66,11 +66,22 @@ public class NetconfSSHActivator implements BundleActivator { if(authProviderTracker != null) { authProviderTracker.stop(); } + + if(nioExecutor!=null) { + nioExecutor.shutdownNow(); + } + + if(clientGroup != null) { + clientGroup.shutdownGracefully(); + } + + if(minaTimerExecutor != null) { + minaTimerExecutor.shutdownNow(); + } } - private static NetconfSSHServer startSSHServer(final BundleContext bundleContext) throws IOException { - final Optional maybeSshSocketAddress = NetconfConfigUtil.extractNetconfServerAddress(bundleContext, - InfixProp.ssh); + private SshProxyServer startSSHServer(final BundleContext bundleContext) throws IOException { + final Optional maybeSshSocketAddress = NetconfConfigUtil.extractNetconfServerAddress(bundleContext, InfixProp.ssh); if (maybeSshSocketAddress.isPresent() == false) { logger.trace("SSH bridge not configured"); @@ -82,92 +93,15 @@ public class NetconfSSHActivator implements BundleActivator { final LocalAddress localAddress = NetconfConfigUtil.getNetconfLocalAddress(); - final String path = FilenameUtils.separatorsToSystem(NetconfConfigUtil.getPrivateKeyPath(bundleContext)); - checkState(!Strings.isNullOrEmpty(path), "Path to ssh private key is blank. Reconfigure %s", NetconfConfigUtil.getPrivateKeyKey()); - final String privateKeyPEMString = PEMGenerator.readOrGeneratePK(new File(path)); - - final EventLoopGroup bossGroup = new NioEventLoopGroup(); - final NetconfSSHServer server = NetconfSSHServer.start(sshSocketAddress.getPort(), localAddress, bossGroup, privateKeyPEMString.toCharArray()); - - authProviderTracker = new AuthProviderTracker(bundleContext, server); + authProviderTracker = new AuthProviderTracker(bundleContext); - return server; - } + final String path = FilenameUtils.separatorsToSystem(NetconfConfigUtil.getPrivateKeyPath(bundleContext)); + checkState(!Strings.isNullOrEmpty(path), "Path to ssh private key is blank. Reconfigure %s", + NetconfConfigUtil.getPrivateKeyKey()); - private static Thread runNetconfSshThread(final NetconfSSHServer server) { - final Thread serverThread = new Thread(server, "netconf SSH server thread"); - serverThread.setDaemon(true); - serverThread.start(); - logger.trace("Netconf SSH bridge up and running."); - return serverThread; + final SshProxyServer sshProxyServer = new SshProxyServer(minaTimerExecutor, clientGroup, nioExecutor); + sshProxyServer.bind(sshSocketAddress, localAddress, authProviderTracker, new PEMGeneratorHostKeyProvider(path, ALGORITHM, KEY_SIZE)); + return sshProxyServer; } - private static class AuthProviderTracker implements ServiceTrackerCustomizer { - private final BundleContext bundleContext; - private final NetconfSSHServer server; - - private Integer maxPreference; - private Thread sshThread; - private final ServiceTracker listenerTracker; - - public AuthProviderTracker(final BundleContext bundleContext, final NetconfSSHServer server) { - this.bundleContext = bundleContext; - this.server = server; - listenerTracker = new ServiceTracker<>(bundleContext, AuthProvider.class, this); - listenerTracker.open(); - } - - @Override - public AuthProvider addingService(final ServiceReference reference) { - logger.trace("Service {} added", reference); - final AuthProvider authService = bundleContext.getService(reference); - final Integer newServicePreference = getPreference(reference); - if(isBetter(newServicePreference)) { - maxPreference = newServicePreference; - server.setAuthProvider(authService); - if(sshThread == null) { - sshThread = runNetconfSshThread(server); - } - } - return authService; - } - - private Integer getPreference(final ServiceReference reference) { - final Object preferenceProperty = reference.getProperty(AuthConstants.SERVICE_PREFERENCE_KEY); - return preferenceProperty == null ? Integer.MIN_VALUE : Integer.valueOf(preferenceProperty.toString()); - } - - private boolean isBetter(final Integer newServicePreference) { - Preconditions.checkNotNull(newServicePreference); - if(maxPreference == null) { - return true; - } - - return newServicePreference > maxPreference; - } - - @Override - public void modifiedService(final ServiceReference reference, final AuthProvider service) { - final AuthProvider authService = bundleContext.getService(reference); - final Integer newServicePreference = getPreference(reference); - if(isBetter(newServicePreference)) { - logger.trace("Replacing modified service {} in netconf SSH.", reference); - server.setAuthProvider(authService); - } - } - - @Override - public void removedService(final ServiceReference reference, final AuthProvider service) { - logger.trace("Removing service {} from netconf SSH. " + - "SSH won't authenticate users until AuthProvider service will be started.", reference); - maxPreference = null; - server.setAuthProvider(null); - } - - public void stop() { - listenerTracker.close(); - // sshThread should finish normally since sshServer.close stops processing - } - - } } 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 deleted file mode 100644 index eec6c3a097..0000000000 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/Handshaker.java +++ /dev/null @@ -1,415 +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 static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - -import java.io.BufferedOutputStream; -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.auth.AuthProvider; -import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -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; - -/** - * 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, final char[] pem) 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(pem, 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("{} is started", session); - try { - // TODO this should be guarded with a timer to prevent resource exhaustion - ganymedConnection.connect(); - } catch (IOException e) { - logger.debug("{} connection error", session, e); - } - logger.trace("{} 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 BufferedOutputStream remoteOutputStream; - private final String session; - private ChannelHandlerContext channelHandlerContext; - - public SSHClientHandler(AutoCloseable remoteConnection, OutputStream remoteOutputStream, - String session) { - this.remoteConnection = remoteConnection; - this.remoteOutputStream = new BufferedOutputStream(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) throws IOException { - 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(); - - // write additional header before polling thread is started - // polling thread could process and forward data before additional header is written - // This will result into unexpected state: hello message without additional header and the next message with additional header - channel.writeAndFlush(Unpooled.copiedBuffer(additionalHeader.getBytes())); - - 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(); - } 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/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java index eb2b644cbc..62ce587237 100644 --- 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 @@ -11,9 +11,6 @@ package org.opendaylight.controller.netconf.netty; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import com.google.common.base.Stopwatch; import io.netty.bootstrap.Bootstrap; @@ -23,16 +20,21 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.util.HashedWheelTimer; import java.net.InetSocketAddress; +import java.nio.file.Files; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import org.junit.After; -import org.junit.Before; +import org.apache.sshd.server.PasswordAuthenticator; +import org.apache.sshd.server.keyprovider.PEMGeneratorHostKeyProvider; +import org.apache.sshd.server.session.ServerSession; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; -import org.opendaylight.controller.netconf.auth.AuthProvider; import org.opendaylight.controller.netconf.netty.EchoClientHandler.State; import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.LoginPassword; import org.opendaylight.controller.netconf.nettyutil.handler.ssh.client.AsyncSshHandler; -import org.opendaylight.controller.netconf.ssh.NetconfSSHServer; -import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator; +import org.opendaylight.controller.netconf.ssh.SshProxyServer; import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,68 +42,77 @@ import org.slf4j.LoggerFactory; public class SSHTest { public static final Logger logger = LoggerFactory.getLogger(SSHTest.class); public static final String AHOJ = "ahoj\n"; - private EventLoopGroup nettyGroup; - HashedWheelTimer hashedWheelTimer; - @Before - public void setUp() throws Exception { + private static EventLoopGroup nettyGroup; + private static HashedWheelTimer hashedWheelTimer; + private static ExecutorService nioExec; + private static ScheduledExecutorService minaTimerEx; + + @BeforeClass + public static void setUp() throws Exception { hashedWheelTimer = new HashedWheelTimer(); nettyGroup = new NioEventLoopGroup(); + nioExec = Executors.newFixedThreadPool(1); + minaTimerEx = Executors.newScheduledThreadPool(1); } - @After - public void tearDown() throws Exception { + @AfterClass + public static void tearDown() throws Exception { hashedWheelTimer.stop(); - nettyGroup.shutdownGracefully(); + nettyGroup.shutdownGracefully().await(); + minaTimerEx.shutdownNow(); + nioExec.shutdownNow(); } @Test public void test() throws Exception { new Thread(new EchoServer(), "EchoServer").start(); - AuthProvider authProvider = mock(AuthProvider.class); - doReturn(true).when(authProvider).authenticated(anyString(), anyString()); - doReturn("auth").when(authProvider).toString(); - - NetconfSSHServer netconfSSHServer = NetconfSSHServer.start(10831, NetconfConfigUtil.getNetconfLocalAddress(), - new NioEventLoopGroup(), PEMGenerator.generate().toCharArray()); - netconfSSHServer.setAuthProvider(authProvider); - InetSocketAddress address = netconfSSHServer.getLocalSocketAddress(); + final InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 10831); + final SshProxyServer sshProxyServer = new SshProxyServer(minaTimerEx, nettyGroup, nioExec); + sshProxyServer.bind(addr, NetconfConfigUtil.getNetconfLocalAddress(), + new PasswordAuthenticator() { + @Override + public boolean authenticate(final String username, final String password, final ServerSession session) { + return true; + } + }, new PEMGeneratorHostKeyProvider(Files.createTempFile("prefix", "suffix").toAbsolutePath().toString())); - final EchoClientHandler echoClientHandler = connectClient(new InetSocketAddress("localhost", address.getPort())); + final EchoClientHandler echoClientHandler = connectClient(addr); Stopwatch stopwatch = new Stopwatch().start(); - while(echoClientHandler.isConnected() == false && stopwatch.elapsed(TimeUnit.SECONDS) < 5) { - Thread.sleep(100); + while(echoClientHandler.isConnected() == false && stopwatch.elapsed(TimeUnit.SECONDS) < 30) { + Thread.sleep(500); } assertTrue(echoClientHandler.isConnected()); logger.info("connected, writing to client"); echoClientHandler.write(AHOJ); + // check that server sent back the same string stopwatch = stopwatch.reset().start(); - while (echoClientHandler.read().endsWith(AHOJ) == false && stopwatch.elapsed(TimeUnit.SECONDS) < 5) { - Thread.sleep(100); + while (echoClientHandler.read().endsWith(AHOJ) == false && stopwatch.elapsed(TimeUnit.SECONDS) < 30) { + Thread.sleep(500); } + try { - String read = echoClientHandler.read(); + final String read = echoClientHandler.read(); assertTrue(read + " should end with " + AHOJ, read.endsWith(AHOJ)); } finally { logger.info("Closing socket"); - netconfSSHServer.close(); - netconfSSHServer.join(); + sshProxyServer.close(); } } - public EchoClientHandler connectClient(InetSocketAddress address) { + public EchoClientHandler connectClient(final InetSocketAddress address) { final EchoClientHandler echoClientHandler = new EchoClientHandler(); - ChannelInitializer channelInitializer = new ChannelInitializer() { + final ChannelInitializer channelInitializer = new ChannelInitializer() { @Override - public void initChannel(NioSocketChannel ch) throws Exception { + public void initChannel(final NioSocketChannel ch) throws Exception { ch.pipeline().addFirst(AsyncSshHandler.createForNetconfSubsystem(new LoginPassword("a", "a"))); ch.pipeline().addLast(echoClientHandler); } }; - Bootstrap b = new Bootstrap(); + final Bootstrap b = new Bootstrap(); b.group(nettyGroup) .channel(NioSocketChannel.class) @@ -114,9 +125,9 @@ public class SSHTest { @Test public void testClientWithoutServer() throws Exception { - InetSocketAddress address = new InetSocketAddress(12345); + final InetSocketAddress address = new InetSocketAddress(12345); final EchoClientHandler echoClientHandler = connectClient(address); - Stopwatch stopwatch = new Stopwatch().start(); + final Stopwatch stopwatch = new Stopwatch().start(); while(echoClientHandler.getState() == State.CONNECTING && stopwatch.elapsed(TimeUnit.SECONDS) < 5) { Thread.sleep(100); } 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 index 1151abcdf2..9cd0c9bcea 100644 --- 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 @@ -12,19 +12,26 @@ 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 java.nio.file.Files; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import org.apache.sshd.ClientSession; +import org.apache.sshd.SshClient; +import org.apache.sshd.client.future.AuthFuture; +import org.apache.sshd.client.future.ConnectFuture; +import org.apache.sshd.server.PasswordAuthenticator; +import org.apache.sshd.server.keyprovider.PEMGeneratorHostKeyProvider; +import org.apache.sshd.server.session.ServerSession; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.opendaylight.controller.netconf.auth.AuthProvider; -import org.opendaylight.controller.netconf.ssh.NetconfSSHServer; +import org.opendaylight.controller.netconf.ssh.SshProxyServer; import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceListener; @@ -39,13 +46,15 @@ public class SSHServerTest { 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; + + private SshProxyServer server; @Mock private BundleContext mockedContext; - + private final ExecutorService nioExec = Executors.newFixedThreadPool(1); + private final EventLoopGroup clientGroup = new NioEventLoopGroup(); + private final ScheduledExecutorService minaTimerEx = Executors.newScheduledThreadPool(1); @Before public void setUp() throws Exception { @@ -55,42 +64,39 @@ public class SSHServerTest { doReturn(new ServiceReference[0]).when(mockedContext).getServiceReferences(anyString(), anyString()); logger.info("Creating SSH server"); - String pem; - try (InputStream is = getClass().getResourceAsStream("/RSA.pk")) { - pem = IOUtils.toString(is); - } - - EventLoopGroup bossGroup = new NioEventLoopGroup(); - NetconfSSHServer server = NetconfSSHServer.start(PORT, NetconfConfigUtil.getNetconfLocalAddress(), - bossGroup, pem.toCharArray()); - server.setAuthProvider(new AuthProvider() { - @Override - public boolean authenticated(final String username, final String password) { - return true; - } - }); - - sshServerThread = new Thread(server); - sshServerThread.setDaemon(true); - sshServerThread.start(); - logger.info("SSH server on " + PORT); + final InetSocketAddress addr = InetSocketAddress.createUnresolved(HOST, PORT); + server = new SshProxyServer(minaTimerEx, clientGroup, nioExec); + server.bind(addr, NetconfConfigUtil.getNetconfLocalAddress(), + new PasswordAuthenticator() { + @Override + public boolean authenticate(final String username, final String password, final ServerSession session) { + return true; + } + }, new PEMGeneratorHostKeyProvider(Files.createTempFile("prefix", "suffix").toAbsolutePath().toString())); + logger.info("SSH server started on " + PORT); } @Test - public void connect() { + public void connect() throws Exception { + final SshClient sshClient = SshClient.setUpDefaultClient(); + sshClient.start(); 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); + final ConnectFuture connect = sshClient.connect(USER, HOST, PORT); + connect.await(30, TimeUnit.SECONDS); + org.junit.Assert.assertTrue(connect.isConnected()); + final ClientSession session = connect.getSession(); + session.addPasswordIdentity(PASSWORD); + final AuthFuture auth = session.auth(); + auth.await(30, TimeUnit.SECONDS); + org.junit.Assert.assertTrue(auth.isSuccess()); + } finally { + sshClient.close(true); + server.close(); + clientGroup.shutdownGracefully().await(); + minaTimerEx.shutdownNow(); + nioExec.shutdownNow(); } - } } diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/NetconfDeviceSimulator.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/NetconfDeviceSimulator.java index d0939a288f..e8ba769da5 100644 --- a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/NetconfDeviceSimulator.java +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/NetconfDeviceSimulator.java @@ -19,13 +19,13 @@ import com.google.common.collect.Sets; import com.google.common.io.CharStreams; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.local.LocalAddress; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.util.HashedWheelTimer; import java.io.Closeable; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -34,6 +34,8 @@ import java.net.Inet4Address; import java.net.InetSocketAddress; import java.net.URI; import java.net.UnknownHostException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.AbstractMap; import java.util.Date; import java.util.HashMap; @@ -42,8 +44,15 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ParseTreeWalker; +import org.apache.sshd.common.util.ThreadUtils; +import org.apache.sshd.server.PasswordAuthenticator; +import org.apache.sshd.server.keyprovider.PEMGeneratorHostKeyProvider; +import org.apache.sshd.server.session.ServerSession; import org.opendaylight.controller.netconf.api.monitoring.NetconfManagementSession; import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer; @@ -58,8 +67,7 @@ import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceSnapshot; import org.opendaylight.controller.netconf.monitoring.osgi.NetconfMonitoringOperationService; -import org.opendaylight.controller.netconf.ssh.NetconfSSHServer; -import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator; +import org.opendaylight.controller.netconf.ssh.SshProxyServer; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException; import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation; @@ -85,14 +93,22 @@ public class NetconfDeviceSimulator implements Closeable { private final NioEventLoopGroup nettyThreadgroup; private final HashedWheelTimer hashedWheelTimer; private final List devicesChannels = Lists.newArrayList(); + private final List sshWrappers = Lists.newArrayList(); + private final ScheduledExecutorService minaTimerExecutor; + private final ExecutorService nioExecutor; public NetconfDeviceSimulator() { - this(new NioEventLoopGroup(), new HashedWheelTimer()); + // TODO make pool size configurable + this(new NioEventLoopGroup(), new HashedWheelTimer(), + Executors.newScheduledThreadPool(8, new ThreadFactoryBuilder().setNameFormat("netconf-ssh-server-mina-timers-%d").build()), + ThreadUtils.newFixedThreadPool("netconf-ssh-server-nio-group", 8)); } - public NetconfDeviceSimulator(final NioEventLoopGroup eventExecutors, final HashedWheelTimer hashedWheelTimer) { + private NetconfDeviceSimulator(final NioEventLoopGroup eventExecutors, final HashedWheelTimer hashedWheelTimer, final ScheduledExecutorService minaTimerExecutor, final ExecutorService nioExecutor) { this.nettyThreadgroup = eventExecutors; this.hashedWheelTimer = hashedWheelTimer; + this.minaTimerExecutor = minaTimerExecutor; + this.nioExecutor = nioExecutor; } private NetconfServerDispatcher createDispatcher(final Map moduleBuilders, final boolean exi, final int generateConfigsTimeout) { @@ -162,17 +178,31 @@ public class NetconfDeviceSimulator implements Closeable { int currentPort = params.startingPort; final List openDevices = Lists.newArrayList(); + + // Generate key to temp folder + final PEMGeneratorHostKeyProvider keyPairProvider = getPemGeneratorHostKeyProvider(); + for (int i = 0; i < params.deviceCount; i++) { final InetSocketAddress address = getAddress(currentPort); final ChannelFuture server; if(params.ssh) { + final InetSocketAddress bindingAddress = InetSocketAddress.createUnresolved("0.0.0.0", currentPort); final LocalAddress tcpLocalAddress = new LocalAddress(address.toString()); server = dispatcher.createLocalServer(tcpLocalAddress); try { - final NetconfSSHServer sshServer = NetconfSSHServer.start(currentPort, tcpLocalAddress, nettyThreadgroup, getPemArray()); - sshServer.setAuthProvider(new AcceptingAuthProvider()); + final SshProxyServer sshServer = new SshProxyServer(minaTimerExecutor, nettyThreadgroup, nioExecutor); + sshServer.bind(bindingAddress, tcpLocalAddress, + new PasswordAuthenticator() { + @Override + public boolean authenticate(final String username, final String password, final ServerSession session) { + // All connections are accepted + return true; + } + }, keyPairProvider); + + sshWrappers.add(sshServer); } catch (final Exception e) { LOG.warn("Cannot start simulated device on {}, skipping", address, e); // Close local server and continue @@ -225,10 +255,12 @@ public class NetconfDeviceSimulator implements Closeable { return openDevices; } - private char[] getPemArray() { + private PEMGeneratorHostKeyProvider getPemGeneratorHostKeyProvider() { try { - return PEMGenerator.readOrGeneratePK(new File("PK")).toCharArray(); + final Path tempFile = Files.createTempFile("tempKeyNetconfTest", "suffix"); + return new PEMGeneratorHostKeyProvider(tempFile.toAbsolutePath().toString()); } catch (final IOException e) { + LOG.error("Unable to generate PEM key", e); throw new RuntimeException(e); } } @@ -317,10 +349,15 @@ public class NetconfDeviceSimulator implements Closeable { @Override public void close() { + for (final SshProxyServer sshWrapper : sshWrappers) { + sshWrapper.close(); + } for (final Channel deviceCh : devicesChannels) { deviceCh.close(); } nettyThreadgroup.shutdownGracefully(); + minaTimerExecutor.shutdownNow(); + nioExecutor.shutdownNow(); // close Everything } diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlElement.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlElement.java index 78efe7e972..3c63204881 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlElement.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlElement.java @@ -37,6 +37,8 @@ import org.xml.sax.SAXException; public final class XmlElement { + public static final String DEFAULT_NAMESPACE_PREFIX = ""; + private final Element element; private static final Logger logger = LoggerFactory.getLogger(XmlElement.class); @@ -72,16 +74,16 @@ public final class XmlElement { return xmlElement; } - private static Map extractNamespaces(Element typeElement) throws NetconfDocumentedException { + private Map extractNamespaces() throws NetconfDocumentedException { Map namespaces = new HashMap<>(); - NamedNodeMap attributes = typeElement.getAttributes(); + NamedNodeMap attributes = element.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { Node attribute = attributes.item(i); String attribKey = attribute.getNodeName(); if (attribKey.startsWith(XmlUtil.XMLNS_ATTRIBUTE_KEY)) { String prefix; if (attribKey.equals(XmlUtil.XMLNS_ATTRIBUTE_KEY)) { - prefix = ""; + prefix = DEFAULT_NAMESPACE_PREFIX; } else { if (!attribKey.startsWith(XmlUtil.XMLNS_ATTRIBUTE_KEY + ":")){ throw new NetconfDocumentedException("Attribute doesn't start with :", @@ -94,6 +96,15 @@ public final class XmlElement { namespaces.put(prefix, attribute.getNodeValue()); } } + + // namespace does not have to be defined on this element but inherited + if(!namespaces.containsKey(DEFAULT_NAMESPACE_PREFIX)) { + Optional namespaceOptionally = getNamespaceOptionally(); + if(namespaceOptionally.isPresent()) { + namespaces.put(DEFAULT_NAMESPACE_PREFIX, namespaceOptionally.get()); + } + } + return namespaces; } @@ -132,7 +143,7 @@ public final class XmlElement { } public String getName() { - if (element.getLocalName()!=null && !element.getLocalName().equals("")){ + if (element.getLocalName()!=null && !element.getLocalName().equals(DEFAULT_NAMESPACE_PREFIX)){ return element.getLocalName(); } return element.getTagName(); @@ -327,7 +338,7 @@ public final class XmlElement { public String getTextContent() throws NetconfDocumentedException { NodeList childNodes = element.getChildNodes(); if (childNodes.getLength() == 0) { - return ""; + return DEFAULT_NAMESPACE_PREFIX; } for(int i = 0; i < childNodes.getLength(); i++) { Node textChild = childNodes.item(i); @@ -356,7 +367,7 @@ public final class XmlElement { public String getNamespaceAttribute() throws MissingNameSpaceException { String attribute = element.getAttribute(XmlUtil.XMLNS_ATTRIBUTE_KEY); - if (attribute == null || attribute.equals("")){ + if (attribute == null || attribute.equals(DEFAULT_NAMESPACE_PREFIX)){ throw new MissingNameSpaceException(String.format("Element %s must specify namespace", toString()), NetconfDocumentedException.ErrorType.application, @@ -415,14 +426,14 @@ public final class XmlElement { * is found value will be null. */ public Map.Entry findNamespaceOfTextContent() throws NetconfDocumentedException { - Map namespaces = extractNamespaces(element); + Map namespaces = extractNamespaces(); String textContent = getTextContent(); int indexOfColon = textContent.indexOf(':'); String prefix; if (indexOfColon > -1) { prefix = textContent.substring(0, indexOfColon); } else { - prefix = ""; + prefix = DEFAULT_NAMESPACE_PREFIX; } if (!namespaces.containsKey(prefix)) { throw new IllegalArgumentException("Cannot find namespace for " + XmlUtil.toString(element) + ". Prefix from content is " diff --git a/opendaylight/netconf/pom.xml b/opendaylight/netconf/pom.xml index 361f4cfa08..b392c5b672 100644 --- a/opendaylight/netconf/pom.xml +++ b/opendaylight/netconf/pom.xml @@ -38,6 +38,8 @@ netconf-auth netconf-usermanager netconf-testtool + + netconf-artifacts diff --git a/pom.xml b/pom.xml index bb8ad1dbeb..53ef5601bb 100644 --- a/pom.xml +++ b/pom.xml @@ -53,8 +53,7 @@ - third-party/ganymed - + third-party/commons/thirdparty diff --git a/third-party/ganymed/pom.xml b/third-party/ganymed/pom.xml deleted file mode 100644 index 676e2a24f1..0000000000 --- a/third-party/ganymed/pom.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - 4.0.0 - - - org.opendaylight.controller - commons.thirdparty - 1.2.0-SNAPSHOT - ../commons/thirdparty - - - org.opendaylight.controller.thirdparty - ganymed - 1.2.0-SNAPSHOT - bundle - - - - org.osgi - org.osgi.core - 5.0.0 - - - ch.ethz.ganymed - ganymed-ssh2 - 261 - - - - - - - org.apache.felix - maven-bundle-plugin - true - - - ch.ethz.ssh2.* - ganymed-ssh2;scope=compile - true - - - - - org.apache.maven.plugins - maven-enforcer-plugin - ${enforcer.version} - - - enforce-no-snapshots - - enforce - - - - - - ch.ethz.ganymed:ganymed-ssh2:* - - - ch.ethz.ganymed:ganymed-ssh2:[261] - - - - - - - - - - - - diff --git a/third-party/ganymed/src/main/java/ch/ethz/ssh2/Connection.java b/third-party/ganymed/src/main/java/ch/ethz/ssh2/Connection.java deleted file mode 100644 index aa13c40d9d..0000000000 --- a/third-party/ganymed/src/main/java/ch/ethz/ssh2/Connection.java +++ /dev/null @@ -1,1424 +0,0 @@ -/* - * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. - * Please refer to the LICENSE.txt for licensing details. - */ - -package ch.ethz.ssh2; - -import java.io.CharArrayWriter; -import java.io.File; -import java.net.Socket; -import java.io.FileReader; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.SocketTimeoutException; -import java.security.SecureRandom; -import java.util.List; -import java.util.Vector; - -import ch.ethz.ssh2.auth.AuthenticationManager; -import ch.ethz.ssh2.channel.ChannelManager; -import ch.ethz.ssh2.crypto.CryptoWishList; -import ch.ethz.ssh2.crypto.cipher.BlockCipherFactory; -import ch.ethz.ssh2.crypto.digest.MAC; -import ch.ethz.ssh2.packets.PacketIgnore; -import ch.ethz.ssh2.transport.KexManager; -import ch.ethz.ssh2.transport.TransportManager; -import ch.ethz.ssh2.util.TimeoutService; -import ch.ethz.ssh2.util.TimeoutService.TimeoutToken; - -/** - * A Connection is used to establish an encrypted TCP/IP - * connection to a SSH-2 server. - *

- * Typically, one - *

    - *
  1. creates a {@link #Connection(String) Connection} object.
  2. - *
  3. calls the {@link #connect() connect()} method.
  4. - *
  5. calls some of the authentication methods (e.g., {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()}).
  6. - *
  7. calls one or several times the {@link #openSession() openSession()} method.
  8. - *
  9. finally, one must close the connection and release resources with the {@link #close() close()} method.
  10. - *
- * - * @author Christian Plattner - * @version $Id: Connection.java 69 2013-08-09 06:39:56Z dkocher@sudo.ch $ - */ - -public class Connection -{ - /** - * The identifier presented to the SSH-2 server. This is the same - * as the "softwareversion" defined in RFC 4253. - *

- * NOTE: As per the RFC, the "softwareversion" string MUST consist of printable - * US-ASCII characters, with the exception of whitespace characters and the minus sign (-). - */ - private String softwareversion = String.format("Ganymed_%s", Version.getSpecification()); - - /* Will be used to generate all random data needed for the current connection. - * Note: SecureRandom.nextBytes() is thread safe. - */ - - private SecureRandom generator; - - private Socket precreatedSocket; - - public Connection(Socket socket) { - this.precreatedSocket = socket; - this.hostname = socket.getInetAddress().getHostName(); - this.port = socket.getPort(); - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @return The list of supported cipher algorithms by this implementation. - */ - public static synchronized String[] getAvailableCiphers() - { - return BlockCipherFactory.getDefaultCipherList(); - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @return The list of supported MAC algorthims by this implementation. - */ - public static synchronized String[] getAvailableMACs() - { - return MAC.getMacList(); - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @return The list of supported server host key algorthims by this implementation. - */ - public static synchronized String[] getAvailableServerHostKeyAlgorithms() - { - return KexManager.getDefaultServerHostkeyAlgorithmList(); - } - - private AuthenticationManager am; - - private boolean authenticated = false; - private ChannelManager cm; - - private CryptoWishList cryptoWishList = new CryptoWishList(); - - private DHGexParameters dhgexpara = new DHGexParameters(); - - private final String hostname; - - private final int port; - - private TransportManager tm; - - private boolean tcpNoDelay = false; - - private ProxyData proxyData = null; - - private List connectionMonitors = new Vector(); - - /** - * Prepares a fresh Connection object which can then be used - * to establish a connection to the specified SSH-2 server. - *

- * Same as {@link #Connection(String, int) Connection(hostname, 22)}. - * - * @param hostname the hostname of the SSH-2 server. - */ - public Connection(String hostname) - { - this(hostname, 22); - } - - /** - * Prepares a fresh Connection object which can then be used - * to establish a connection to the specified SSH-2 server. - * - * @param hostname - * the host where we later want to connect to. - * @param port - * port on the server, normally 22. - */ - public Connection(String hostname, int port) - { - this.hostname = hostname; - this.port = port; - } - - /** - * Prepares a fresh Connection object which can then be used - * to establish a connection to the specified SSH-2 server. - * - * @param hostname - * the host where we later want to connect to. - * @param port - * port on the server, normally 22. - * @param softwareversion - * Allows you to set a custom "softwareversion" string as defined in RFC 4253. - * NOTE: As per the RFC, the "softwareversion" string MUST consist of printable - * US-ASCII characters, with the exception of whitespace characters and the minus sign (-). - */ - public Connection(String hostname, int port, String softwareversion) - { - this.hostname = hostname; - this.port = port; - this.softwareversion = softwareversion; - } - - /** - * After a successful connect, one has to authenticate oneself. This method - * is based on DSA (it uses DSA to sign a challenge sent by the server). - *

- * If the authentication phase is complete, true will be - * returned. If the server does not accept the request (or if further - * authentication steps are needed), false is returned and - * one can retry either by using this or any other authentication method - * (use the getRemainingAuthMethods method to get a list of - * the remaining possible methods). - * - * @param user - * A String holding the username. - * @param pem - * A String containing the DSA private key of the - * user in OpenSSH key format (PEM, you can't miss the - * "-----BEGIN DSA PRIVATE KEY-----" tag). The string may contain - * linefeeds. - * @param password - * If the PEM string is 3DES encrypted ("DES-EDE3-CBC"), then you - * must specify the password. Otherwise, this argument will be - * ignored and can be set to null. - * - * @return whether the connection is now authenticated. - * @throws IOException - * - * @deprecated You should use one of the {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()} - * methods, this method is just a wrapper for it and will - * disappear in future builds. - * - */ - public synchronized boolean authenticateWithDSA(String user, String pem, String password) throws IOException - { - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - if (user == null) - throw new IllegalArgumentException("user argument is null"); - - if (pem == null) - throw new IllegalArgumentException("pem argument is null"); - - authenticated = am.authenticatePublicKey(user, pem.toCharArray(), password, getOrCreateSecureRND()); - - return authenticated; - } - - /** - * A wrapper that calls {@link #authenticateWithKeyboardInteractive(String, String[], InteractiveCallback) - * authenticateWithKeyboardInteractivewith} a null submethod list. - * - * @param user - * A String holding the username. - * @param cb - * An InteractiveCallback which will be used to - * determine the responses to the questions asked by the server. - * @return whether the connection is now authenticated. - * @throws IOException - */ - public synchronized boolean authenticateWithKeyboardInteractive(String user, InteractiveCallback cb) - throws IOException - { - return authenticateWithKeyboardInteractive(user, null, cb); - } - - /** - * After a successful connect, one has to authenticate oneself. This method - * is based on "keyboard-interactive", specified in - * draft-ietf-secsh-auth-kbdinteract-XX. Basically, you have to define a - * callback object which will be feeded with challenges generated by the - * server. Answers are then sent back to the server. It is possible that the - * callback will be called several times during the invocation of this - * method (e.g., if the server replies to the callback's answer(s) with - * another challenge...) - *

- * If the authentication phase is complete, true will be - * returned. If the server does not accept the request (or if further - * authentication steps are needed), false is returned and - * one can retry either by using this or any other authentication method - * (use the getRemainingAuthMethods method to get a list of - * the remaining possible methods). - *

- * Note: some SSH servers advertise "keyboard-interactive", however, any - * interactive request will be denied (without having sent any challenge to - * the client). - * - * @param user - * A String holding the username. - * @param submethods - * An array of submethod names, see - * draft-ietf-secsh-auth-kbdinteract-XX. May be null - * to indicate an empty list. - * @param cb - * An InteractiveCallback which will be used to - * determine the responses to the questions asked by the server. - * - * @return whether the connection is now authenticated. - * @throws IOException - */ - public synchronized boolean authenticateWithKeyboardInteractive(String user, String[] submethods, - InteractiveCallback cb) throws IOException - { - if (cb == null) - throw new IllegalArgumentException("Callback may not ne NULL!"); - - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - if (user == null) - throw new IllegalArgumentException("user argument is null"); - - authenticated = am.authenticateInteractive(user, submethods, cb); - - return authenticated; - } - - /** - * After a successful connect, one has to authenticate oneself. This method - * sends username and password to the server. - *

- * If the authentication phase is complete, true will be - * returned. If the server does not accept the request (or if further - * authentication steps are needed), false is returned and - * one can retry either by using this or any other authentication method - * (use the getRemainingAuthMethods method to get a list of - * the remaining possible methods). - *

- * Note: if this method fails, then please double-check that it is actually - * offered by the server (use {@link #getRemainingAuthMethods(String) getRemainingAuthMethods()}. - *

- * Often, password authentication is disabled, but users are not aware of it. - * Many servers only offer "publickey" and "keyboard-interactive". However, - * even though "keyboard-interactive" *feels* like password authentication - * (e.g., when using the putty or openssh clients) it is *not* the same mechanism. - * - * @param user - * @param password - * @return if the connection is now authenticated. - * @throws IOException - */ - public synchronized boolean authenticateWithPassword(String user, String password) throws IOException - { - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - if (user == null) - throw new IllegalArgumentException("user argument is null"); - - if (password == null) - throw new IllegalArgumentException("password argument is null"); - - authenticated = am.authenticatePassword(user, password); - - return authenticated; - } - - /** - * After a successful connect, one has to authenticate oneself. - * This method can be used to explicitly use the special "none" - * authentication method (where only a username has to be specified). - *

- * Note 1: The "none" method may always be tried by clients, however as by - * the specs, the server will not explicitly announce it. In other words, - * the "none" token will never show up in the list returned by - * {@link #getRemainingAuthMethods(String)}. - *

- * Note 2: no matter which one of the authenticateWithXXX() methods - * you call, the library will always issue exactly one initial "none" - * authentication request to retrieve the initially allowed list of - * authentication methods by the server. Please read RFC 4252 for the - * details. - *

- * If the authentication phase is complete, true will be - * returned. If further authentication steps are needed, false - * is returned and one can retry by any other authentication method - * (use the getRemainingAuthMethods method to get a list of - * the remaining possible methods). - * - * @param user - * @return if the connection is now authenticated. - * @throws IOException - */ - public synchronized boolean authenticateWithNone(String user) throws IOException - { - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - if (user == null) - throw new IllegalArgumentException("user argument is null"); - - /* Trigger the sending of the PacketUserauthRequestNone packet */ - /* (if not already done) */ - - authenticated = am.authenticateNone(user); - - return authenticated; - } - - /** - * After a successful connect, one has to authenticate oneself. - * The authentication method "publickey" works by signing a challenge - * sent by the server. The signature is either DSA or RSA based - it - * just depends on the type of private key you specify, either a DSA - * or RSA private key in PEM format. And yes, this is may seem to be a - * little confusing, the method is called "publickey" in the SSH-2 protocol - * specification, however since we need to generate a signature, you - * actually have to supply a private key =). - *

- * The private key contained in the PEM file may also be encrypted ("Proc-Type: 4,ENCRYPTED"). - * The library supports DES-CBC and DES-EDE3-CBC encryption, as well - * as the more exotic PEM encrpytions AES-128-CBC, AES-192-CBC and AES-256-CBC. - *

- * If the authentication phase is complete, true will be - * returned. If the server does not accept the request (or if further - * authentication steps are needed), false is returned and - * one can retry either by using this or any other authentication method - * (use the getRemainingAuthMethods method to get a list of - * the remaining possible methods). - *

- * NOTE PUTTY USERS: Event though your key file may start with "-----BEGIN..." - * it is not in the expected format. You have to convert it to the OpenSSH - * key format by using the "puttygen" tool (can be downloaded from the Putty - * website). Simply load your key and then use the "Conversions/Export OpenSSH key" - * functionality to get a proper PEM file. - * - * @param user - * A String holding the username. - * @param pemPrivateKey - * A char[] containing a DSA or RSA private key of the - * user in OpenSSH key format (PEM, you can't miss the - * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE KEY-----" - * tag). The char array may contain linebreaks/linefeeds. - * @param password - * If the PEM structure is encrypted ("Proc-Type: 4,ENCRYPTED") then - * you must specify a password. Otherwise, this argument will be ignored - * and can be set to null. - * - * @return whether the connection is now authenticated. - * @throws IOException - */ - public synchronized boolean authenticateWithPublicKey(String user, char[] pemPrivateKey, String password) - throws IOException - { - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - if (user == null) - throw new IllegalArgumentException("user argument is null"); - - if (pemPrivateKey == null) - throw new IllegalArgumentException("pemPrivateKey argument is null"); - - authenticated = am.authenticatePublicKey(user, pemPrivateKey, password, getOrCreateSecureRND()); - - return authenticated; - } - - /** - * A convenience wrapper function which reads in a private key (PEM format, either DSA or RSA) - * and then calls authenticateWithPublicKey(String, char[], String). - *

- * NOTE PUTTY USERS: Event though your key file may start with "-----BEGIN..." - * it is not in the expected format. You have to convert it to the OpenSSH - * key format by using the "puttygen" tool (can be downloaded from the Putty - * website). Simply load your key and then use the "Conversions/Export OpenSSH key" - * functionality to get a proper PEM file. - * - * @param user - * A String holding the username. - * @param pemFile - * A File object pointing to a file containing a DSA or RSA - * private key of the user in OpenSSH key format (PEM, you can't miss the - * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE KEY-----" - * tag). - * @param password - * If the PEM file is encrypted then you must specify the password. - * Otherwise, this argument will be ignored and can be set to null. - * - * @return whether the connection is now authenticated. - * @throws IOException - */ - public synchronized boolean authenticateWithPublicKey(String user, File pemFile, String password) - throws IOException - { - if (pemFile == null) - throw new IllegalArgumentException("pemFile argument is null"); - - char[] buff = new char[256]; - - CharArrayWriter cw = new CharArrayWriter(); - - FileReader fr = new FileReader(pemFile); - - while (true) - { - int len = fr.read(buff); - if (len < 0) - break; - cw.write(buff, 0, len); - } - - fr.close(); - - return authenticateWithPublicKey(user, cw.toCharArray(), password); - } - - /** - * Add a {@link ConnectionMonitor} to this connection. Can be invoked at any time, - * but it is best to add connection monitors before invoking - * connect() to avoid glitches (e.g., you add a connection monitor after - * a successful connect(), but the connection has died in the mean time. Then, - * your connection monitor won't be notified.) - *

- * You can add as many monitors as you like. If a monitor has already been added, then - * this method does nothing. - * - * @see ConnectionMonitor - * - * @param cmon An object implementing the {@link ConnectionMonitor} interface. - */ - public synchronized void addConnectionMonitor(ConnectionMonitor cmon) - { - if (cmon == null) - throw new IllegalArgumentException("cmon argument is null"); - - if (!connectionMonitors.contains(cmon)) - { - connectionMonitors.add(cmon); - - if (tm != null) - tm.setConnectionMonitors(connectionMonitors); - } - } - - /** - * Remove a {@link ConnectionMonitor} from this connection. - * - * @param cmon - * @return whether the monitor could be removed - */ - public synchronized boolean removeConnectionMonitor(ConnectionMonitor cmon) - { - if (cmon == null) - throw new IllegalArgumentException("cmon argument is null"); - - boolean existed = connectionMonitors.remove(cmon); - - if (tm != null) - tm.setConnectionMonitors(connectionMonitors); - - return existed; - } - - /** - * Close the connection to the SSH-2 server. All assigned sessions will be - * closed, too. Can be called at any time. Don't forget to call this once - * you don't need a connection anymore - otherwise the receiver thread may - * run forever. - */ - public synchronized void close() - { - Throwable t = new Throwable("Closed due to user request."); - close(t, false); - } - - public synchronized void close(Throwable t, boolean hard) - { - if (cm != null) - cm.closeAllChannels(); - - if (tm != null) - { - tm.close(t, hard == false); - tm = null; - } - am = null; - cm = null; - authenticated = false; - } - - /** - * Same as {@link #connect(ServerHostKeyVerifier, int, int) connect(null, 0, 0)}. - * - * @return see comments for the {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} method. - * @throws IOException - */ - public synchronized ConnectionInfo connect() throws IOException - { - return connect(null, 0, 0); - } - - /** - * Same as {@link #connect(ServerHostKeyVerifier, int, int) connect(verifier, 0, 0)}. - * - * @return see comments for the {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} method. - * @throws IOException - */ - public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier) throws IOException - { - return connect(verifier, 0, 0); - } - - /** - * Connect to the SSH-2 server and, as soon as the server has presented its - * host key, use the {@link ServerHostKeyVerifier#verifyServerHostKey(String, - * int, String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} - * method of the verifier to ask for permission to proceed. - * If verifier is null, then any host key will be - * accepted - this is NOT recommended, since it makes man-in-the-middle attackes - * VERY easy (somebody could put a proxy SSH server between you and the real server). - *

- * Note: The verifier will be called before doing any crypto calculations - * (i.e., diffie-hellman). Therefore, if you don't like the presented host key then - * no CPU cycles are wasted (and the evil server has less information about us). - *

- * However, it is still possible that the server presented a fake host key: the server - * cheated (typically a sign for a man-in-the-middle attack) and is not able to generate - * a signature that matches its host key. Don't worry, the library will detect such - * a scenario later when checking the signature (the signature cannot be checked before - * having completed the diffie-hellman exchange). - *

- * Note 2: The {@link ServerHostKeyVerifier#verifyServerHostKey(String, - * int, String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method - * will *NOT* be called from the current thread, the call is being made from a - * background thread (there is a background dispatcher thread for every - * established connection). - *

- * Note 3: This method will block as long as the key exchange of the underlying connection - * has not been completed (and you have not specified any timeouts). - *

- * Note 4: If you want to re-use a connection object that was successfully connected, - * then you must call the {@link #close()} method before invoking connect() again. - * - * @param verifier - * An object that implements the - * {@link ServerHostKeyVerifier} interface. Pass null - * to accept any server host key - NOT recommended. - * - * @param connectTimeout - * Connect the underlying TCP socket to the server with the given timeout - * value (non-negative, in milliseconds). Zero means no timeout. If a proxy is being - * used (see {@link #setProxyData(ProxyData)}), then this timeout is used for the - * connection establishment to the proxy. - * - * @param kexTimeout - * Timeout for complete connection establishment (non-negative, - * in milliseconds). Zero means no timeout. The timeout counts from the - * moment you invoke the connect() method and is cancelled as soon as the - * first key-exchange round has finished. It is possible that - * the timeout event will be fired during the invocation of the - * verifier callback, but it will only have an effect after - * the verifier returns. - * - * @return A {@link ConnectionInfo} object containing the details of - * the established connection. - * - * @throws IOException - * If any problem occurs, e.g., the server's host key is not - * accepted by the verifier or there is problem during - * the initial crypto setup (e.g., the signature sent by the server is wrong). - *

- * In case of a timeout (either connectTimeout or kexTimeout) - * a SocketTimeoutException is thrown. - *

- * An exception may also be thrown if the connection was already successfully - * connected (no matter if the connection broke in the mean time) and you invoke - * connect() again without having called {@link #close()} first. - *

- * If a HTTP proxy is being used and the proxy refuses the connection, - * then a {@link HTTPProxyException} may be thrown, which - * contains the details returned by the proxy. If the proxy is buggy and does - * not return a proper HTTP response, then a normal IOException is thrown instead. - */ - public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier, int connectTimeout, int kexTimeout) - throws IOException - { - final class TimeoutState - { - boolean isCancelled = false; - boolean timeoutSocketClosed = false; - } - - if (tm != null) - throw new IOException("Connection to " + hostname + " is already in connected state!"); - - if (connectTimeout < 0) - throw new IllegalArgumentException("connectTimeout must be non-negative!"); - - if (kexTimeout < 0) - throw new IllegalArgumentException("kexTimeout must be non-negative!"); - - final TimeoutState state = new TimeoutState(); - - tm = new TransportManager(); - tm.setSoTimeout(connectTimeout); - tm.setConnectionMonitors(connectionMonitors); - - /* Make sure that the runnable below will observe the new value of "tm" - * and "state" (the runnable will be executed in a different thread, which - * may be already running, that is why we need a memory barrier here). - * See also the comment in Channel.java if you - * are interested in the details. - * - * OKOK, this is paranoid since adding the runnable to the todo list - * of the TimeoutService will ensure that all writes have been flushed - * before the Runnable reads anything - * (there is a synchronized block in TimeoutService.addTimeoutHandler). - */ - - synchronized (tm) - { - /* We could actually synchronize on anything. */ - } - - try - { - TimeoutToken token = null; - - if (kexTimeout > 0) - { - final Runnable timeoutHandler = new Runnable() - { - public void run() - { - synchronized (state) - { - if (state.isCancelled) - return; - state.timeoutSocketClosed = true; - tm.close(new SocketTimeoutException("The connect timeout expired"), false); - } - } - }; - - long timeoutHorizont = System.currentTimeMillis() + kexTimeout; - - token = TimeoutService.addTimeoutHandler(timeoutHorizont, timeoutHandler); - } - - try - { - - if (precreatedSocket != null) { - tm.clientInit(precreatedSocket, softwareversion, cryptoWishList, verifier, dhgexpara, - getOrCreateSecureRND()); - } else { - tm.clientInit(hostname, port, softwareversion, cryptoWishList, verifier, dhgexpara, connectTimeout, - getOrCreateSecureRND(), proxyData); - } - } - catch (SocketTimeoutException se) - { - throw (SocketTimeoutException) new SocketTimeoutException( - "The connect() operation on the socket timed out.").initCause(se); - } - - tm.setTcpNoDelay(tcpNoDelay); - - /* Wait until first KEX has finished */ - - ConnectionInfo ci = tm.getConnectionInfo(1); - - /* Now try to cancel the timeout, if needed */ - - if (token != null) - { - TimeoutService.cancelTimeoutHandler(token); - - /* Were we too late? */ - - synchronized (state) - { - if (state.timeoutSocketClosed) - throw new IOException("This exception will be replaced by the one below =)"); - /* Just in case the "cancelTimeoutHandler" invocation came just a little bit - * too late but the handler did not enter the semaphore yet - we can - * still stop it. - */ - state.isCancelled = true; - } - } - - return ci; - } - catch (SocketTimeoutException ste) - { - throw ste; - } - catch (IOException e1) - { - /* This will also invoke any registered connection monitors */ - close(new Throwable("There was a problem during connect."), false); - - synchronized (state) - { - /* Show a clean exception, not something like "the socket is closed!?!" */ - if (state.timeoutSocketClosed) - throw new SocketTimeoutException("The kexTimeout (" + kexTimeout + " ms) expired."); - } - - /* Do not wrap a HTTPProxyException */ - if (e1 instanceof HTTPProxyException) - throw e1; - - throw (IOException) new IOException("There was a problem while connecting to " + hostname + ":" + port) - .initCause(e1); - } - } - - /** - * Creates a new {@link LocalPortForwarder}. - * A LocalPortForwarder forwards TCP/IP connections that arrive at a local - * port via the secure tunnel to another host (which may or may not be - * identical to the remote SSH-2 server). - *

- * This method must only be called after one has passed successfully the authentication step. - * There is no limit on the number of concurrent forwardings. - * - * @param local_port the local port the LocalPortForwarder shall bind to. - * @param host_to_connect target address (IP or hostname) - * @param port_to_connect target port - * @return A {@link LocalPortForwarder} object. - * @throws IOException - */ - public synchronized LocalPortForwarder createLocalPortForwarder(int local_port, String host_to_connect, - int port_to_connect) throws IOException - { - if (tm == null) - throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); - - return new LocalPortForwarder(cm, local_port, host_to_connect, port_to_connect); - } - - /** - * Creates a new {@link LocalPortForwarder}. - * A LocalPortForwarder forwards TCP/IP connections that arrive at a local - * port via the secure tunnel to another host (which may or may not be - * identical to the remote SSH-2 server). - *

- * This method must only be called after one has passed successfully the authentication step. - * There is no limit on the number of concurrent forwardings. - * - * @param addr specifies the InetSocketAddress where the local socket shall be bound to. - * @param host_to_connect target address (IP or hostname) - * @param port_to_connect target port - * @return A {@link LocalPortForwarder} object. - * @throws IOException - */ - public synchronized LocalPortForwarder createLocalPortForwarder(InetSocketAddress addr, String host_to_connect, - int port_to_connect) throws IOException - { - if (tm == null) - throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); - - return new LocalPortForwarder(cm, addr, host_to_connect, port_to_connect); - } - - /** - * Creates a new {@link LocalStreamForwarder}. - * A LocalStreamForwarder manages an Input/Outputstream pair - * that is being forwarded via the secure tunnel into a TCP/IP connection to another host - * (which may or may not be identical to the remote SSH-2 server). - * - * @param host_to_connect - * @param port_to_connect - * @return A {@link LocalStreamForwarder} object. - * @throws IOException - */ - public synchronized LocalStreamForwarder createLocalStreamForwarder(String host_to_connect, int port_to_connect) - throws IOException - { - if (tm == null) - throw new IllegalStateException("Cannot forward, you need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("Cannot forward, connection is not authenticated."); - - return new LocalStreamForwarder(cm, host_to_connect, port_to_connect); - } - - /** - * Create a very basic {@link SCPClient} that can be used to copy - * files from/to the SSH-2 server. - *

- * Works only after one has passed successfully the authentication step. - * There is no limit on the number of concurrent SCP clients. - *

- * Note: This factory method will probably disappear in the future. - * - * @return A {@link SCPClient} object. - * @throws IOException - */ - public synchronized SCPClient createSCPClient() throws IOException - { - if (tm == null) - throw new IllegalStateException("Cannot create SCP client, you need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("Cannot create SCP client, connection is not authenticated."); - - return new SCPClient(this); - } - - /** - * Force an asynchronous key re-exchange (the call does not block). The - * latest values set for MAC, Cipher and DH group exchange parameters will - * be used. If a key exchange is currently in progress, then this method has - * the only effect that the so far specified parameters will be used for the - * next (server driven) key exchange. - *

- * Note: This implementation will never start a key exchange (other than the initial one) - * unless you or the SSH-2 server ask for it. - * - * @throws IOException - * In case of any failure behind the scenes. - */ - public synchronized void forceKeyExchange() throws IOException - { - if (tm == null) - throw new IllegalStateException("You need to establish a connection first."); - - tm.forceKeyExchange(cryptoWishList, dhgexpara, null, null); - } - - /** - * Returns the hostname that was passed to the constructor. - * - * @return the hostname - */ - public synchronized String getHostname() - { - return hostname; - } - - /** - * Returns the port that was passed to the constructor. - * - * @return the TCP port - */ - public synchronized int getPort() - { - return port; - } - - /** - * Returns a {@link ConnectionInfo} object containing the details of - * the connection. Can be called as soon as the connection has been - * established (successfully connected). - * - * @return A {@link ConnectionInfo} object. - * @throws IOException - * In case of any failure behind the scenes. - */ - public synchronized ConnectionInfo getConnectionInfo() throws IOException - { - if (tm == null) - throw new IllegalStateException( - "Cannot get details of connection, you need to establish a connection first."); - return tm.getConnectionInfo(1); - } - - /** - * After a successful connect, one has to authenticate oneself. This method - * can be used to tell which authentication methods are supported by the - * server at a certain stage of the authentication process (for the given - * username). - *

- * Note 1: the username will only be used if no authentication step was done - * so far (it will be used to ask the server for a list of possible - * authentication methods by sending the initial "none" request). Otherwise, - * this method ignores the user name and returns a cached method list - * (which is based on the information contained in the last negative server response). - *

- * Note 2: the server may return method names that are not supported by this - * implementation. - *

- * After a successful authentication, this method must not be called - * anymore. - * - * @param user - * A String holding the username. - * - * @return a (possibly emtpy) array holding authentication method names. - * @throws IOException - */ - public synchronized String[] getRemainingAuthMethods(String user) throws IOException - { - if (user == null) - throw new IllegalArgumentException("user argument may not be NULL!"); - - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - return am.getRemainingMethods(user); - } - - /** - * Determines if the authentication phase is complete. Can be called at any - * time. - * - * @return true if no further authentication steps are - * needed. - */ - public synchronized boolean isAuthenticationComplete() - { - return authenticated; - } - - /** - * Returns true if there was at least one failed authentication request and - * the last failed authentication request was marked with "partial success" - * by the server. This is only needed in the rare case of SSH-2 server setups - * that cannot be satisfied with a single successful authentication request - * (i.e., multiple authentication steps are needed.) - *

- * If you are interested in the details, then have a look at RFC4252. - * - * @return if the there was a failed authentication step and the last one - * was marked as a "partial success". - */ - public synchronized boolean isAuthenticationPartialSuccess() - { - if (am == null) - return false; - - return am.getPartialSuccess(); - } - - /** - * Checks if a specified authentication method is available. This method is - * actually just a wrapper for {@link #getRemainingAuthMethods(String) - * getRemainingAuthMethods()}. - * - * @param user - * A String holding the username. - * @param method - * An authentication method name (e.g., "publickey", "password", - * "keyboard-interactive") as specified by the SSH-2 standard. - * @return if the specified authentication method is currently available. - * @throws IOException - */ - public synchronized boolean isAuthMethodAvailable(String user, String method) throws IOException - { - if (method == null) - throw new IllegalArgumentException("method argument may not be NULL!"); - - String methods[] = getRemainingAuthMethods(user); - - for (int i = 0; i < methods.length; i++) - { - if (methods[i].compareTo(method) == 0) - return true; - } - - return false; - } - - private SecureRandom getOrCreateSecureRND() - { - if (generator == null) - generator = new SecureRandom(); - - return generator; - } - - /** - * Open a new {@link Session} on this connection. Works only after one has passed - * successfully the authentication step. There is no limit on the number of - * concurrent sessions. - * - * @return A {@link Session} object. - * @throws IOException - */ - public synchronized Session openSession() throws IOException - { - if (tm == null) - throw new IllegalStateException("Cannot open session, you need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("Cannot open session, connection is not authenticated."); - - return new Session(cm, getOrCreateSecureRND()); - } - - /** - * Send an SSH_MSG_IGNORE packet. This method will generate a random data attribute - * (length between 0 (invlusive) and 16 (exclusive) bytes, contents are random bytes). - *

- * This method must only be called once the connection is established. - * - * @throws IOException - */ - public synchronized void sendIgnorePacket() throws IOException - { - SecureRandom rnd = getOrCreateSecureRND(); - - byte[] data = new byte[rnd.nextInt(16)]; - rnd.nextBytes(data); - - sendIgnorePacket(data); - } - - /** - * Send an SSH_MSG_IGNORE packet with the given data attribute. - *

- * This method must only be called once the connection is established. - * - * @throws IOException - */ - public synchronized void sendIgnorePacket(byte[] data) throws IOException - { - if (data == null) - throw new IllegalArgumentException("data argument must not be null."); - - if (tm == null) - throw new IllegalStateException( - "Cannot send SSH_MSG_IGNORE packet, you need to establish a connection first."); - - PacketIgnore pi = new PacketIgnore(); - pi.setData(data); - - tm.sendMessage(pi.getPayload()); - } - - /** - * Removes duplicates from a String array, keeps only first occurence - * of each element. Does not destroy order of elements; can handle nulls. - * Uses a very efficient O(N^2) algorithm =) - * - * @param list a String array. - * @return a cleaned String array. - */ - private String[] removeDuplicates(String[] list) - { - if ((list == null) || (list.length < 2)) - return list; - - String[] list2 = new String[list.length]; - - int count = 0; - - for (int i = 0; i < list.length; i++) - { - boolean duplicate = false; - - String element = list[i]; - - for (int j = 0; j < count; j++) - { - if (((element == null) && (list2[j] == null)) || ((element != null) && (element.equals(list2[j])))) - { - duplicate = true; - break; - } - } - - if (duplicate) - continue; - - list2[count++] = list[i]; - } - - if (count == list2.length) - return list2; - - String[] tmp = new String[count]; - System.arraycopy(list2, 0, tmp, 0, count); - - return tmp; - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @param ciphers - */ - public synchronized void setClient2ServerCiphers(String[] ciphers) - { - if ((ciphers == null) || (ciphers.length == 0)) - throw new IllegalArgumentException(); - ciphers = removeDuplicates(ciphers); - BlockCipherFactory.checkCipherList(ciphers); - cryptoWishList.c2s_enc_algos = ciphers; - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @param macs - */ - public synchronized void setClient2ServerMACs(String[] macs) - { - if ((macs == null) || (macs.length == 0)) - throw new IllegalArgumentException(); - macs = removeDuplicates(macs); - MAC.checkMacList(macs); - cryptoWishList.c2s_mac_algos = macs; - } - - /** - * Sets the parameters for the diffie-hellman group exchange. Unless you - * know what you are doing, you will never need this. Default values are - * defined in the {@link DHGexParameters} class. - * - * @param dgp {@link DHGexParameters}, non null. - * - */ - public synchronized void setDHGexParameters(DHGexParameters dgp) - { - if (dgp == null) - throw new IllegalArgumentException(); - - dhgexpara = dgp; - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @param ciphers - */ - public synchronized void setServer2ClientCiphers(String[] ciphers) - { - if ((ciphers == null) || (ciphers.length == 0)) - throw new IllegalArgumentException(); - ciphers = removeDuplicates(ciphers); - BlockCipherFactory.checkCipherList(ciphers); - cryptoWishList.s2c_enc_algos = ciphers; - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @param macs - */ - public synchronized void setServer2ClientMACs(String[] macs) - { - if ((macs == null) || (macs.length == 0)) - throw new IllegalArgumentException(); - - macs = removeDuplicates(macs); - MAC.checkMacList(macs); - cryptoWishList.s2c_mac_algos = macs; - } - - /** - * Define the set of allowed server host key algorithms to be used for - * the following key exchange operations. - *

- * Unless you know what you are doing, you will never need this. - * - * @param algos An array of allowed server host key algorithms. - * SSH-2 defines ssh-dss and ssh-rsa. - * The entries of the array must be ordered after preference, i.e., - * the entry at index 0 is the most preferred one. You must specify - * at least one entry. - */ - public synchronized void setServerHostKeyAlgorithms(String[] algos) - { - if ((algos == null) || (algos.length == 0)) - throw new IllegalArgumentException(); - - algos = removeDuplicates(algos); - KexManager.checkServerHostkeyAlgorithmsList(algos); - cryptoWishList.serverHostKeyAlgorithms = algos; - } - - /** - * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) on the underlying socket. - *

- * Can be called at any time. If the connection has not yet been established - * then the passed value will be stored and set after the socket has been set up. - * The default value that will be used is false. - * - * @param enable the argument passed to the Socket.setTCPNoDelay() method. - * @throws IOException - */ - public synchronized void setTCPNoDelay(boolean enable) throws IOException - { - tcpNoDelay = enable; - - if (tm != null) - tm.setTcpNoDelay(enable); - } - - /** - * Used to tell the library that the connection shall be established through a proxy server. - * It only makes sense to call this method before calling the {@link #connect() connect()} - * method. - *

- * At the moment, only HTTP proxies are supported. - *

- * Note: This method can be called any number of times. The {@link #connect() connect()} - * method will use the value set in the last preceding invocation of this method. - * - * @see HTTPProxyData - * - * @param proxyData Connection information about the proxy. If null, then - * no proxy will be used (non surprisingly, this is also the default). - */ - public synchronized void setProxyData(ProxyData proxyData) - { - this.proxyData = proxyData; - } - - /** - * Request a remote port forwarding. - * If successful, then forwarded connections will be redirected to the given target address. - * You can cancle a requested remote port forwarding by calling - * {@link #cancelRemotePortForwarding(int) cancelRemotePortForwarding()}. - *

- * A call of this method will block until the peer either agreed or disagreed to your request- - *

- * Note 1: this method typically fails if you - *

    - *
  • pass a port number for which the used remote user has not enough permissions (i.e., port - * < 1024)
  • - *
  • or pass a port number that is already in use on the remote server
  • - *
  • or if remote port forwarding is disabled on the server.
  • - *
- *

- * Note 2: (from the openssh man page): By default, the listening socket on the server will be - * bound to the loopback interface only. This may be overriden by specifying a bind address. - * Specifying a remote bind address will only succeed if the server's GatewayPorts option - * is enabled (see sshd_config(5)). - * - * @param bindAddress address to bind to on the server: - *

    - *
  • "" means that connections are to be accepted on all protocol families - * supported by the SSH implementation
  • - *
  • "0.0.0.0" means to listen on all IPv4 addresses
  • - *
  • "::" means to listen on all IPv6 addresses
  • - *
  • "localhost" means to listen on all protocol families supported by the SSH - * implementation on loopback addresses only, [RFC3330] and RFC3513]
  • - *
  • "127.0.0.1" and "::1" indicate listening on the loopback interfaces for - * IPv4 and IPv6 respectively
  • - *
- * @param bindPort port number to bind on the server (must be > 0) - * @param targetAddress the target address (IP or hostname) - * @param targetPort the target port - * @throws IOException - */ - public synchronized void requestRemotePortForwarding(String bindAddress, int bindPort, String targetAddress, - int targetPort) throws IOException - { - if (tm == null) - throw new IllegalStateException("You need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("The connection is not authenticated."); - - if ((bindAddress == null) || (targetAddress == null) || (bindPort <= 0) || (targetPort <= 0)) - throw new IllegalArgumentException(); - - cm.requestGlobalForward(bindAddress, bindPort, targetAddress, targetPort); - } - - /** - * Cancel an earlier requested remote port forwarding. - * Currently active forwardings will not be affected (e.g., disrupted). - * Note that further connection forwarding requests may be received until - * this method has returned. - * - * @param bindPort the allocated port number on the server - * @throws IOException if the remote side refuses the cancel request or another low - * level error occurs (e.g., the underlying connection is closed) - */ - public synchronized void cancelRemotePortForwarding(int bindPort) throws IOException - { - if (tm == null) - throw new IllegalStateException("You need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("The connection is not authenticated."); - - cm.requestCancelGlobalForward(bindPort); - } - - /** - * Provide your own instance of SecureRandom. Can be used, e.g., if you - * want to seed the used SecureRandom generator manually. - *

- * The SecureRandom instance is used during key exchanges, public key authentication, - * x11 cookie generation and the like. - * - * @param rnd a SecureRandom instance - */ - public synchronized void setSecureRandom(SecureRandom rnd) - { - if (rnd == null) - throw new IllegalArgumentException(); - - this.generator = rnd; - } -} diff --git a/third-party/ganymed/src/main/java/ch/ethz/ssh2/channel/ChannelManager.java b/third-party/ganymed/src/main/java/ch/ethz/ssh2/channel/ChannelManager.java deleted file mode 100644 index 8fec9a0f54..0000000000 --- a/third-party/ganymed/src/main/java/ch/ethz/ssh2/channel/ChannelManager.java +++ /dev/null @@ -1,1845 +0,0 @@ -/* - * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. - * Please refer to the LICENSE.txt for licensing details. - */ - -package ch.ethz.ssh2.channel; - -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Vector; - -import ch.ethz.ssh2.ChannelCondition; -import ch.ethz.ssh2.PtySettings; -import ch.ethz.ssh2.ServerConnectionCallback; -import ch.ethz.ssh2.ServerSessionCallback; -import ch.ethz.ssh2.log.Logger; -import ch.ethz.ssh2.packets.PacketChannelFailure; -import ch.ethz.ssh2.packets.PacketChannelOpenConfirmation; -import ch.ethz.ssh2.packets.PacketChannelOpenFailure; -import ch.ethz.ssh2.packets.PacketChannelSuccess; -import ch.ethz.ssh2.packets.PacketGlobalCancelForwardRequest; -import ch.ethz.ssh2.packets.PacketGlobalForwardRequest; -import ch.ethz.ssh2.packets.PacketOpenDirectTCPIPChannel; -import ch.ethz.ssh2.packets.PacketOpenSessionChannel; -import ch.ethz.ssh2.packets.PacketSessionExecCommand; -import ch.ethz.ssh2.packets.PacketSessionPtyRequest; -import ch.ethz.ssh2.packets.PacketSessionStartShell; -import ch.ethz.ssh2.packets.PacketSessionSubsystemRequest; -import ch.ethz.ssh2.packets.PacketSessionX11Request; -import ch.ethz.ssh2.packets.Packets; -import ch.ethz.ssh2.packets.TypesReader; -import ch.ethz.ssh2.server.ServerConnectionState; -import ch.ethz.ssh2.transport.MessageHandler; -import ch.ethz.ssh2.transport.TransportManager; - -/** - * ChannelManager. Please read the comments in Channel.java. - *

- * Besides the crypto part, this is the core of the library. - * - * @author Christian Plattner - * @version $Id: ChannelManager.java 48 2013-08-01 12:22:33Z cleondris@gmail.com $ - */ -public class ChannelManager implements MessageHandler -{ - private static final Logger log = Logger.getLogger(ChannelManager.class); - - private final ServerConnectionState server_state; - private final TransportManager tm; - - private final HashMap x11_magic_cookies = new HashMap(); - - private final List channels = new Vector(); - private int nextLocalChannel = 100; - private boolean shutdown = false; - private int globalSuccessCounter = 0; - private int globalFailedCounter = 0; - - private final HashMap remoteForwardings = new HashMap(); - - private final List listenerThreads = new Vector(); - - private boolean listenerThreadsAllowed = true; - - /** - * Constructor for client-mode. - * @param tm - */ - public ChannelManager(TransportManager tm) - { - this.server_state = null; - this.tm = tm; - tm.registerMessageHandler(this, 80, 100); - } - - /** - * Constructor for server-mode. - * @param state - */ - public ChannelManager(ServerConnectionState state) - { - this.server_state = state; - this.tm = state.tm; - tm.registerMessageHandler(this, 80, 100); - } - - private Channel getChannel(int id) - { - synchronized (channels) - { - for (Channel c : channels) - { - if (c.localID == id) - return c; - } - } - return null; - } - - private void removeChannel(int id) - { - synchronized (channels) - { - for (Channel c : channels) - { - if (c.localID == id) - { - channels.remove(c); - break; - } - } - } - } - - private int addChannel(Channel c) - { - synchronized (channels) - { - channels.add(c); - return nextLocalChannel++; - } - } - - private void waitUntilChannelOpen(Channel c) throws IOException - { - boolean wasInterrupted = false; - - synchronized (c) - { - while (c.state == Channel.STATE_OPENING) - { - try - { - c.wait(); - } - catch (InterruptedException ignore) - { - wasInterrupted = true; - } - } - - if (c.state != Channel.STATE_OPEN) - { - removeChannel(c.localID); - - String detail = c.getReasonClosed(); - - if (detail == null) - detail = "state: " + c.state; - - throw new IOException("Could not open channel (" + detail + ")"); - } - } - - if (wasInterrupted) - Thread.currentThread().interrupt(); - } - - private void waitForGlobalSuccessOrFailure() throws IOException - { - boolean wasInterrupted = false; - - try - { - synchronized (channels) - { - while ((globalSuccessCounter == 0) && (globalFailedCounter == 0)) - { - if (shutdown) - { - throw new IOException("The connection is being shutdown"); - } - - try - { - channels.wait(); - } - catch (InterruptedException ignore) - { - wasInterrupted = true; - } - } - - if (globalFailedCounter != 0) - { - throw new IOException("The server denied the request (did you enable port forwarding?)"); - } - - if (globalSuccessCounter == 0) - { - throw new IOException("Illegal state."); - } - } - } - finally - { - if (wasInterrupted) - Thread.currentThread().interrupt(); - } - } - - private void waitForChannelSuccessOrFailure(Channel c) throws IOException - { - boolean wasInterrupted = false; - - try - { - synchronized (c) - { - while ((c.successCounter == 0) && (c.failedCounter == 0)) - { - if (c.state != Channel.STATE_OPEN) - { - String detail = c.getReasonClosed(); - - if (detail == null) - detail = "state: " + c.state; - - throw new IOException("This SSH2 channel is not open (" + detail + ")"); - } - - try - { - c.wait(); - } - catch (InterruptedException ignore) - { - wasInterrupted = true; - } - } - - if (c.failedCounter != 0) - { - throw new IOException("The server denied the request."); - } - } - } - finally - { - if (wasInterrupted) - Thread.currentThread().interrupt(); - } - } - - public void registerX11Cookie(String hexFakeCookie, X11ServerData data) - { - synchronized (x11_magic_cookies) - { - x11_magic_cookies.put(hexFakeCookie, data); - } - } - - public void unRegisterX11Cookie(String hexFakeCookie, boolean killChannels) - { - if (hexFakeCookie == null) - throw new IllegalStateException("hexFakeCookie may not be null"); - - synchronized (x11_magic_cookies) - { - x11_magic_cookies.remove(hexFakeCookie); - } - - if (killChannels == false) - return; - - log.debug("Closing all X11 channels for the given fake cookie"); - - List channel_copy = new Vector(); - - synchronized (channels) - { - channel_copy.addAll(channels); - } - - for (Channel c : channel_copy) - { - synchronized (c) - { - if (hexFakeCookie.equals(c.hexX11FakeCookie) == false) - continue; - } - - try - { - closeChannel(c, "Closing X11 channel since the corresponding session is closing", true); - } - catch (IOException ignored) - { - } - } - } - - public X11ServerData checkX11Cookie(String hexFakeCookie) - { - synchronized (x11_magic_cookies) - { - if (hexFakeCookie != null) - return x11_magic_cookies.get(hexFakeCookie); - } - return null; - } - - public void closeAllChannels() - { - log.debug("Closing all channels"); - - List channel_copy = new Vector(); - - synchronized (channels) - { - channel_copy.addAll(channels); - } - - for (Channel c : channel_copy) - { - try - { - closeChannel(c, "Closing all channels", true); - } - catch (IOException ignored) - { - } - } - } - - public void closeChannel(Channel c, String reason, boolean force) throws IOException - { - byte msg[] = new byte[5]; - - synchronized (c) - { - if (force) - { - c.state = Channel.STATE_CLOSED; - c.EOF = true; - } - - c.setReasonClosed(reason); - - msg[0] = Packets.SSH_MSG_CHANNEL_CLOSE; - msg[1] = (byte) (c.remoteID >> 24); - msg[2] = (byte) (c.remoteID >> 16); - msg[3] = (byte) (c.remoteID >> 8); - msg[4] = (byte) (c.remoteID); - - c.notifyAll(); - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent == true) - return; - tm.sendMessage(msg); - c.closeMessageSent = true; - } - - log.debug("Sent SSH_MSG_CHANNEL_CLOSE (channel " + c.localID + ")"); - } - - public void sendEOF(Channel c) throws IOException - { - byte[] msg = new byte[5]; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - return; - - msg[0] = Packets.SSH_MSG_CHANNEL_EOF; - msg[1] = (byte) (c.remoteID >> 24); - msg[2] = (byte) (c.remoteID >> 16); - msg[3] = (byte) (c.remoteID >> 8); - msg[4] = (byte) (c.remoteID); - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent == true) - return; - tm.sendMessage(msg); - } - - - log.debug("Sent EOF (Channel " + c.localID + "/" + c.remoteID + ")"); - } - - public void sendOpenConfirmation(Channel c) throws IOException - { - PacketChannelOpenConfirmation pcoc = null; - - synchronized (c) - { - if (c.state != Channel.STATE_OPENING) - return; - - c.state = Channel.STATE_OPEN; - - pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, c.localWindow, c.localMaxPacketSize); - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent == true) - return; - tm.sendMessage(pcoc.getPayload()); - } - } - - public void sendData(Channel c, byte[] buffer, int pos, int len) throws IOException - { - boolean wasInterrupted = false; - - try - { - while (len > 0) - { - int thislen = 0; - byte[] msg; - - synchronized (c) - { - while (true) - { - if (c.state == Channel.STATE_CLOSED) - throw new ChannelClosedException("SSH channel is closed. (" + c.getReasonClosed() + ")"); - - if (c.state != Channel.STATE_OPEN) - throw new ChannelClosedException("SSH channel in strange state. (" + c.state + ")"); - - if (c.remoteWindow != 0) - break; - - try - { - c.wait(); - } - catch (InterruptedException ignore) - { - wasInterrupted = true; - } - } - - /* len > 0, no sign extension can happen when comparing */ - - thislen = (c.remoteWindow >= len) ? len : (int) c.remoteWindow; - - int estimatedMaxDataLen = c.remoteMaxPacketSize - (tm.getPacketOverheadEstimate() + 9); - - /* The worst case scenario =) a true bottleneck */ - - if (estimatedMaxDataLen <= 0) - { - estimatedMaxDataLen = 1; - } - - if (thislen > estimatedMaxDataLen) - thislen = estimatedMaxDataLen; - - c.remoteWindow -= thislen; - - msg = new byte[1 + 8 + thislen]; - - msg[0] = Packets.SSH_MSG_CHANNEL_DATA; - msg[1] = (byte) (c.remoteID >> 24); - msg[2] = (byte) (c.remoteID >> 16); - msg[3] = (byte) (c.remoteID >> 8); - msg[4] = (byte) (c.remoteID); - msg[5] = (byte) (thislen >> 24); - msg[6] = (byte) (thislen >> 16); - msg[7] = (byte) (thislen >> 8); - msg[8] = (byte) (thislen); - - System.arraycopy(buffer, pos, msg, 9, thislen); - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent == true) - throw new ChannelClosedException("SSH channel is closed. (" + c.getReasonClosed() + ")"); - - tm.sendMessage(msg); - } - - pos += thislen; - len -= thislen; - } - } - finally - { - if (wasInterrupted) - Thread.currentThread().interrupt(); - } - } - - public int requestGlobalForward(String bindAddress, int bindPort, String targetAddress, int targetPort) - throws IOException - { - RemoteForwardingData rfd = new RemoteForwardingData(); - - rfd.bindAddress = bindAddress; - rfd.bindPort = bindPort; - rfd.targetAddress = targetAddress; - rfd.targetPort = targetPort; - - synchronized (remoteForwardings) - { - Integer key = new Integer(bindPort); - - if (remoteForwardings.get(key) != null) - { - throw new IOException("There is already a forwarding for remote port " + bindPort); - } - - remoteForwardings.put(key, rfd); - } - - synchronized (channels) - { - globalSuccessCounter = globalFailedCounter = 0; - } - - PacketGlobalForwardRequest pgf = new PacketGlobalForwardRequest(true, bindAddress, bindPort); - tm.sendMessage(pgf.getPayload()); - - log.debug("Requesting a remote forwarding ('" + bindAddress + "', " + bindPort + ")"); - - try - { - waitForGlobalSuccessOrFailure(); - } - catch (IOException e) - { - synchronized (remoteForwardings) - { - remoteForwardings.remove(rfd); - } - throw e; - } - - return bindPort; - } - - public void requestCancelGlobalForward(int bindPort) throws IOException - { - RemoteForwardingData rfd = null; - - synchronized (remoteForwardings) - { - rfd = remoteForwardings.get(new Integer(bindPort)); - - if (rfd == null) - throw new IOException("Sorry, there is no known remote forwarding for remote port " + bindPort); - } - - synchronized (channels) - { - globalSuccessCounter = globalFailedCounter = 0; - } - - PacketGlobalCancelForwardRequest pgcf = new PacketGlobalCancelForwardRequest(true, rfd.bindAddress, - rfd.bindPort); - tm.sendMessage(pgcf.getPayload()); - - log.debug("Requesting cancelation of remote forward ('" + rfd.bindAddress + "', " + rfd.bindPort + ")"); - - waitForGlobalSuccessOrFailure(); - - /* Only now we are sure that no more forwarded connections will arrive */ - - synchronized (remoteForwardings) - { - remoteForwardings.remove(rfd); - } - } - - public void registerThread(IChannelWorkerThread thr) throws IOException - { - synchronized (listenerThreads) - { - if (listenerThreadsAllowed == false) - throw new IOException("Too late, this connection is closed."); - listenerThreads.add(thr); - } - } - - public Channel openDirectTCPIPChannel(String host_to_connect, int port_to_connect, String originator_IP_address, - int originator_port) throws IOException - { - Channel c = new Channel(this); - - synchronized (c) - { - c.localID = addChannel(c); - // end of synchronized block forces writing out to main memory - } - - PacketOpenDirectTCPIPChannel dtc = new PacketOpenDirectTCPIPChannel(c.localID, c.localWindow, - c.localMaxPacketSize, host_to_connect, port_to_connect, originator_IP_address, originator_port); - - tm.sendMessage(dtc.getPayload()); - - waitUntilChannelOpen(c); - - return c; - } - - public Channel openSessionChannel() throws IOException - { - Channel c = new Channel(this); - - synchronized (c) - { - c.localID = addChannel(c); - // end of synchronized block forces the writing out to main memory - } - - log.debug("Sending SSH_MSG_CHANNEL_OPEN (Channel " + c.localID + ")"); - - PacketOpenSessionChannel smo = new PacketOpenSessionChannel(c.localID, c.localWindow, c.localMaxPacketSize); - tm.sendMessage(smo.getPayload()); - - waitUntilChannelOpen(c); - - return c; - } - - public void requestPTY(Channel c, String term, int term_width_characters, int term_height_characters, - int term_width_pixels, int term_height_pixels, byte[] terminal_modes) throws IOException - { - PacketSessionPtyRequest spr; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")"); - - spr = new PacketSessionPtyRequest(c.remoteID, true, term, term_width_characters, term_height_characters, - term_width_pixels, term_height_pixels, terminal_modes); - - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent) - throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")"); - tm.sendMessage(spr.getPayload()); - } - - try - { - waitForChannelSuccessOrFailure(c); - } - catch (IOException e) - { - throw (IOException) new IOException("PTY request failed").initCause(e); - } - } - - public void requestX11(Channel c, boolean singleConnection, String x11AuthenticationProtocol, - String x11AuthenticationCookie, int x11ScreenNumber) throws IOException - { - PacketSessionX11Request psr; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); - - psr = new PacketSessionX11Request(c.remoteID, true, singleConnection, x11AuthenticationProtocol, - x11AuthenticationCookie, x11ScreenNumber); - - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent) - throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); - tm.sendMessage(psr.getPayload()); - } - - log.debug("Requesting X11 forwarding (Channel " + c.localID + "/" + c.remoteID + ")"); - - try - { - waitForChannelSuccessOrFailure(c); - } - catch (IOException e) - { - throw (IOException) new IOException("The X11 request failed.").initCause(e); - } - } - - public void requestSubSystem(Channel c, String subSystemName) throws IOException - { - PacketSessionSubsystemRequest ssr; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")"); - - ssr = new PacketSessionSubsystemRequest(c.remoteID, true, subSystemName); - - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent) - throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")"); - tm.sendMessage(ssr.getPayload()); - } - - try - { - waitForChannelSuccessOrFailure(c); - } - catch (IOException e) - { - throw (IOException) new IOException("The subsystem request failed.").initCause(e); - } - } - - public void requestExecCommand(Channel c, String cmd) throws IOException - { - this.requestExecCommand(c, cmd, null); - } - - /** - * @param charsetName The charset used to convert between Java Unicode Strings and byte encodings - */ - public void requestExecCommand(Channel c, String cmd, String charsetName) throws IOException - { - PacketSessionExecCommand sm; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")"); - - sm = new PacketSessionExecCommand(c.remoteID, true, cmd); - - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent) - throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")"); - tm.sendMessage(sm.getPayload(charsetName)); - } - - log.debug("Executing command (channel " + c.localID + ", '" + cmd + "')"); - - try - { - waitForChannelSuccessOrFailure(c); - } - catch (IOException e) - { - throw (IOException) new IOException("The execute request failed.").initCause(e); - } - } - - public void requestShell(Channel c) throws IOException - { - PacketSessionStartShell sm; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")"); - - sm = new PacketSessionStartShell(c.remoteID, true); - - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent) - throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")"); - tm.sendMessage(sm.getPayload()); - } - - try - { - waitForChannelSuccessOrFailure(c); - } - catch (IOException e) - { - throw (IOException) new IOException("The shell request failed.").initCause(e); - } - } - - public void msgChannelExtendedData(byte[] msg, int msglen) throws IOException - { - if (msglen <= 13) - throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - int dataType = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); - int len = ((msg[9] & 0xff) << 24) | ((msg[10] & 0xff) << 16) | ((msg[11] & 0xff) << 8) | (msg[12] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_EXTENDED_DATA message for non-existent channel " + id); - - if (dataType != Packets.SSH_EXTENDED_DATA_STDERR) - throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has unknown type (" + dataType + ")"); - - if (len != (msglen - 13)) - throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong len (calculated " + (msglen - 13) - + ", got " + len + ")"); - - log.debug("Got SSH_MSG_CHANNEL_EXTENDED_DATA (channel " + id + ", " + len + ")"); - - synchronized (c) - { - if (c.state == Channel.STATE_CLOSED) - return; // ignore - - if (c.state != Channel.STATE_OPEN) - throw new IOException("Got SSH_MSG_CHANNEL_EXTENDED_DATA, but channel is not in correct state (" - + c.state + ")"); - - if (c.localWindow < len) - throw new IOException("Remote sent too much data, does not fit into window."); - - c.localWindow -= len; - - System.arraycopy(msg, 13, c.stderrBuffer, c.stderrWritepos, len); - c.stderrWritepos += len; - - c.notifyAll(); - } - } - - /** - * Wait until for a condition. - * - * @param c Channel - * @param timeout in ms, 0 means no timeout. - * @param condition_mask minimum event mask (at least one of the conditions must be fulfilled) - * @return all current events - */ - public int waitForCondition(Channel c, long timeout, int condition_mask) - { - boolean wasInterrupted = false; - - try - { - long end_time = 0; - boolean end_time_set = false; - - synchronized (c) - { - while (true) - { - int current_cond = 0; - - int stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; - int stderrAvail = c.stderrWritepos - c.stderrReadpos; - - if (stdoutAvail > 0) - current_cond = current_cond | ChannelCondition.STDOUT_DATA; - - if (stderrAvail > 0) - current_cond = current_cond | ChannelCondition.STDERR_DATA; - - if (c.EOF) - current_cond = current_cond | ChannelCondition.EOF; - - if (c.getExitStatus() != null) - current_cond = current_cond | ChannelCondition.EXIT_STATUS; - - if (c.getExitSignal() != null) - current_cond = current_cond | ChannelCondition.EXIT_SIGNAL; - - if (c.state == Channel.STATE_CLOSED) - return current_cond | ChannelCondition.CLOSED | ChannelCondition.EOF; - - if ((current_cond & condition_mask) != 0) - return current_cond; - - if (timeout > 0) - { - if (!end_time_set) - { - end_time = System.currentTimeMillis() + timeout; - end_time_set = true; - } - else - { - timeout = end_time - System.currentTimeMillis(); - - if (timeout <= 0) - return current_cond | ChannelCondition.TIMEOUT; - } - } - - try - { - if (timeout > 0) - c.wait(timeout); - else - c.wait(); - } - catch (InterruptedException e) - { - wasInterrupted = true; - } - } - } - } - finally - { - if (wasInterrupted) - Thread.currentThread().interrupt(); - } - } - - public int getAvailable(Channel c, boolean extended) throws IOException - { - synchronized (c) - { - int avail; - - if (extended) - avail = c.stderrWritepos - c.stderrReadpos; - else - avail = c.stdoutWritepos - c.stdoutReadpos; - - return ((avail > 0) ? avail : (c.EOF ? -1 : 0)); - } - } - - public int getChannelData(Channel c, boolean extended, byte[] target, int off, int len) throws IOException - { - boolean wasInterrupted = false; - - try - { - int copylen = 0; - int increment = 0; - int remoteID = 0; - int localID = 0; - - synchronized (c) - { - int stdoutAvail = 0; - int stderrAvail = 0; - - while (true) - { - /* - * Data available? We have to return remaining data even if the - * channel is already closed. - */ - - stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; - stderrAvail = c.stderrWritepos - c.stderrReadpos; - - if ((!extended) && (stdoutAvail != 0)) - break; - - if ((extended) && (stderrAvail != 0)) - break; - - /* Do not wait if more data will never arrive (EOF or CLOSED) */ - - if ((c.EOF) || (c.state != Channel.STATE_OPEN)) - return -1; - - try - { - c.wait(); - } - catch (InterruptedException ignore) - { - wasInterrupted = true; - } - } - - /* OK, there is some data. Return it. */ - - if (!extended) - { - copylen = (stdoutAvail > len) ? len : stdoutAvail; - System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, target, off, copylen); - c.stdoutReadpos += copylen; - - if (c.stdoutReadpos != c.stdoutWritepos) - - System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, c.stdoutBuffer, 0, c.stdoutWritepos - - c.stdoutReadpos); - - c.stdoutWritepos -= c.stdoutReadpos; - c.stdoutReadpos = 0; - } - else - { - copylen = (stderrAvail > len) ? len : stderrAvail; - System.arraycopy(c.stderrBuffer, c.stderrReadpos, target, off, copylen); - c.stderrReadpos += copylen; - - if (c.stderrReadpos != c.stderrWritepos) - - System.arraycopy(c.stderrBuffer, c.stderrReadpos, c.stderrBuffer, 0, c.stderrWritepos - - c.stderrReadpos); - - c.stderrWritepos -= c.stderrReadpos; - c.stderrReadpos = 0; - } - - if (c.state != Channel.STATE_OPEN) - return copylen; - - if (c.localWindow < ((Channel.CHANNEL_BUFFER_SIZE + 1) / 2)) - { - int minFreeSpace = Math.min(Channel.CHANNEL_BUFFER_SIZE - c.stdoutWritepos, - Channel.CHANNEL_BUFFER_SIZE - c.stderrWritepos); - - increment = minFreeSpace - c.localWindow; - c.localWindow = minFreeSpace; - } - - remoteID = c.remoteID; /* read while holding the lock */ - localID = c.localID; /* read while holding the lock */ - } - - /* - * If a consumer reads stdout and stdin in parallel, we may end up with - * sending two msgWindowAdjust messages. Luckily, it - * does not matter in which order they arrive at the server. - */ - - if (increment > 0) - { - log.debug("Sending SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + localID + ", " + increment + ")"); - - synchronized (c.channelSendLock) - { - byte[] msg = c.msgWindowAdjust; - - msg[0] = Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST; - msg[1] = (byte) (remoteID >> 24); - msg[2] = (byte) (remoteID >> 16); - msg[3] = (byte) (remoteID >> 8); - msg[4] = (byte) (remoteID); - msg[5] = (byte) (increment >> 24); - msg[6] = (byte) (increment >> 16); - msg[7] = (byte) (increment >> 8); - msg[8] = (byte) (increment); - - if (c.closeMessageSent == false) - tm.sendMessage(msg); - } - } - - return copylen; - } - finally - { - if (wasInterrupted) - Thread.currentThread().interrupt(); - } - - } - - public void msgChannelData(byte[] msg, int msglen) throws IOException - { - if (msglen <= 9) - throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - int len = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_DATA message for non-existent channel " + id); - - if (len != (msglen - 9)) - throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong len (calculated " + (msglen - 9) + ", got " - + len + ")"); - - log.debug("Got SSH_MSG_CHANNEL_DATA (channel " + id + ", " + len + ")"); - - synchronized (c) - { - if (c.state == Channel.STATE_CLOSED) - return; // ignore - - if (c.state != Channel.STATE_OPEN) - throw new IOException("Got SSH_MSG_CHANNEL_DATA, but channel is not in correct state (" + c.state + ")"); - - if (c.localWindow < len) - throw new IOException("Remote sent too much data, does not fit into window."); - - c.localWindow -= len; - - System.arraycopy(msg, 9, c.stdoutBuffer, c.stdoutWritepos, len); - c.stdoutWritepos += len; - - c.notifyAll(); - } - } - - public void msgChannelWindowAdjust(byte[] msg, int msglen) throws IOException - { - if (msglen != 9) - throw new IOException("SSH_MSG_CHANNEL_WINDOW_ADJUST message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - int windowChange = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_WINDOW_ADJUST message for non-existent channel " + id); - - synchronized (c) - { - final long huge = 0xFFFFffffL; /* 2^32 - 1 */ - - c.remoteWindow += (windowChange & huge); /* avoid sign extension */ - - /* TODO - is this a good heuristic? */ - - if ((c.remoteWindow > huge)) - c.remoteWindow = huge; - - c.notifyAll(); - } - - - log.debug("Got SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + id + ", " + windowChange + ")"); - } - - public void msgChannelOpen(byte[] msg, int msglen) throws IOException - { - TypesReader tr = new TypesReader(msg, 0, msglen); - - tr.readByte(); // skip packet type - String channelType = tr.readString(); - int remoteID = tr.readUINT32(); /* sender channel */ - int remoteWindow = tr.readUINT32(); /* initial window size */ - int remoteMaxPacketSize = tr.readUINT32(); /* maximum packet size */ - - if ("x11".equals(channelType)) - { - synchronized (x11_magic_cookies) - { - /* If we did not request X11 forwarding, then simply ignore this bogus request. */ - - if (x11_magic_cookies.size() == 0) - { - PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, - Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "X11 forwarding not activated", ""); - - tm.sendAsynchronousMessage(pcof.getPayload()); - - log.warning("Unexpected X11 request, denying it!"); - - return; - } - } - - String remoteOriginatorAddress = tr.readString(); - int remoteOriginatorPort = tr.readUINT32(); - - Channel c = new Channel(this); - - synchronized (c) - { - c.remoteID = remoteID; - c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */ - c.remoteMaxPacketSize = remoteMaxPacketSize; - c.localID = addChannel(c); - } - - /* - * The open confirmation message will be sent from another thread - */ - - RemoteX11AcceptThread rxat = new RemoteX11AcceptThread(c, remoteOriginatorAddress, remoteOriginatorPort); - rxat.setDaemon(true); - rxat.start(); - - return; - } - - if ("forwarded-tcpip".equals(channelType)) - { - String remoteConnectedAddress = tr.readString(); /* address that was connected */ - int remoteConnectedPort = tr.readUINT32(); /* port that was connected */ - String remoteOriginatorAddress = tr.readString(); /* originator IP address */ - int remoteOriginatorPort = tr.readUINT32(); /* originator port */ - - RemoteForwardingData rfd = null; - - synchronized (remoteForwardings) - { - rfd = remoteForwardings.get(new Integer(remoteConnectedPort)); - } - - if (rfd == null) - { - PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, - Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, - "No thanks, unknown port in forwarded-tcpip request", ""); - - /* Always try to be polite. */ - - tm.sendAsynchronousMessage(pcof.getPayload()); - - log.debug("Unexpected forwarded-tcpip request, denying it!"); - - return; - } - - Channel c = new Channel(this); - - synchronized (c) - { - c.remoteID = remoteID; - c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ - c.remoteMaxPacketSize = remoteMaxPacketSize; - c.localID = addChannel(c); - } - - /* - * The open confirmation message will be sent from another thread. - */ - - RemoteAcceptThread rat = new RemoteAcceptThread(c, remoteConnectedAddress, remoteConnectedPort, - remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort); - - rat.setDaemon(true); - rat.start(); - - return; - } - - if ((server_state != null) && ("session".equals(channelType))) - { - ServerConnectionCallback cb = null; - - synchronized (server_state) - { - cb = server_state.cb_conn; - } - - if (cb == null) - { - tm.sendAsynchronousMessage(new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, - "Sessions are currently not enabled", "en").getPayload()); - - return; - } - - final Channel c = new Channel(this); - - synchronized (c) - { - c.remoteID = remoteID; - c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ - c.remoteMaxPacketSize = remoteMaxPacketSize; - c.localID = addChannel(c); - c.state = Channel.STATE_OPEN; - c.ss = new ServerSessionImpl(c); - } - - PacketChannelOpenConfirmation pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, - c.localWindow, c.localMaxPacketSize); - - tm.sendAsynchronousMessage(pcoc.getPayload()); - - c.ss.sscb = cb.acceptSession(c.ss); - - return; - } - - /* Tell the server that we have no idea what it is talking about */ - - PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_UNKNOWN_CHANNEL_TYPE, - "Unknown channel type", ""); - - tm.sendAsynchronousMessage(pcof.getPayload()); - - - log.warning("The peer tried to open an unsupported channel type (" + channelType + ")"); - } - - /* Starts the given runnable in a foreground (non-daemon) thread */ - private void runAsync(Runnable r) - { - Thread t = new Thread(r); - t.start(); - } - - public void msgChannelRequest(byte[] msg, int msglen) throws IOException - { - TypesReader tr = new TypesReader(msg, 0, msglen); - - tr.readByte(); // skip packet type - int id = tr.readUINT32(); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id); - - ServerSessionImpl server_session = null; - - if (server_state != null) - { - synchronized (c) - { - server_session = c.ss; - } - } - - String type = tr.readString("US-ASCII"); - boolean wantReply = tr.readBoolean(); - - log.debug("Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')"); - - if (type.equals("exit-status")) - { - if (wantReply != false) - throw new IOException( - "Badly formatted SSH_MSG_CHANNEL_REQUEST exit-status message, 'want reply' is true"); - - int exit_status = tr.readUINT32(); - - if (tr.remain() != 0) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); - - synchronized (c) - { - c.exit_status = new Integer(exit_status); - c.notifyAll(); - } - - log.debug("Got EXIT STATUS (channel " + id + ", status " + exit_status + ")"); - - return; - } - - if ((server_state == null) && (type.equals("exit-signal"))) - { - if (wantReply != false) - throw new IOException( - "Badly formatted SSH_MSG_CHANNEL_REQUEST exit-signal message, 'want reply' is true"); - - String signame = tr.readString("US-ASCII"); - tr.readBoolean(); - tr.readString(); - tr.readString(); - - if (tr.remain() != 0) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); - - synchronized (c) - { - c.exit_signal = signame; - c.notifyAll(); - } - - log.debug("Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")"); - - return; - } - - if ((server_session != null) && (type.equals("pty-req"))) - { - PtySettings pty = new PtySettings(); - - pty.term = tr.readString(); - pty.term_width_characters = tr.readUINT32(); - pty.term_height_characters = tr.readUINT32(); - pty.term_width_pixels = tr.readUINT32(); - pty.term_height_pixels = tr.readUINT32(); - pty.terminal_modes = tr.readByteString(); - - if (tr.remain() != 0) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); - - Runnable run_after_sending_success = null; - - ServerSessionCallback sscb = server_session.getServerSessionCallback(); - - if (sscb != null) - run_after_sending_success = sscb.requestPtyReq(server_session, pty); - - if (wantReply) - { - if (run_after_sending_success != null) - { - tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); - } - else - { - tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); - } - } - - if (run_after_sending_success != null) - { - runAsync(run_after_sending_success); - } - - return; - } - - if ((server_session != null) && (type.equals("subsystem"))) - { - String command = tr.readString(); - if (tr.remain() != 0) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); - - Runnable run_after_sending_success = null; - ServerSessionCallback sscb = server_session.getServerSessionCallback(); - - if (sscb != null) - run_after_sending_success = sscb.requestSubsystem(server_session, command); - - if (wantReply) - { - if (run_after_sending_success != null) - { - tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); - } - else - { - tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); - } - } - - if (run_after_sending_success != null) - { - runAsync(run_after_sending_success); - } - - return; - } - - if ((server_session != null) && (type.equals("shell"))) - { - if (tr.remain() != 0) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); - - Runnable run_after_sending_success = null; - ServerSessionCallback sscb = server_session.getServerSessionCallback(); - - if (sscb != null) - run_after_sending_success = sscb.requestShell(server_session); - - if (wantReply) - { - if (run_after_sending_success != null) - { - tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); - } - else - { - tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); - } - } - - if (run_after_sending_success != null) - { - runAsync(run_after_sending_success); - } - - return; - } - - if ((server_session != null) && (type.equals("exec"))) - { - String command = tr.readString(); - - if (tr.remain() != 0) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); - - Runnable run_after_sending_success = null; - ServerSessionCallback sscb = server_session.getServerSessionCallback(); - - if (sscb != null) - run_after_sending_success = sscb.requestExec(server_session, command); - - if (wantReply) - { - if (run_after_sending_success != null) - { - tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); - } - else - { - tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); - } - } - - if (run_after_sending_success != null) - { - runAsync(run_after_sending_success); - } - - return; - } - - /* We simply ignore unknown channel requests, however, if the server wants a reply, - * then we signal that we have no idea what it is about. - */ - - if (wantReply) - { - tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); - } - - log.debug("Channel request '" + type + "' is not known, ignoring it"); - } - - public void msgChannelEOF(byte[] msg, int msglen) throws IOException - { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_EOF message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_EOF message for non-existent channel " + id); - - synchronized (c) - { - c.EOF = true; - c.notifyAll(); - } - - log.debug("Got SSH_MSG_CHANNEL_EOF (channel " + id + ")"); - } - - public void msgChannelClose(byte[] msg, int msglen) throws IOException - { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_CLOSE message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_CLOSE message for non-existent channel " + id); - - synchronized (c) - { - c.EOF = true; - c.state = Channel.STATE_CLOSED; - c.setReasonClosed("Close requested by remote"); - c.closeMessageRecv = true; - - removeChannel(c.localID); - - c.notifyAll(); - } - - log.debug("Got SSH_MSG_CHANNEL_CLOSE (channel " + id + ")"); - } - - public void msgChannelSuccess(byte[] msg, int msglen) throws IOException - { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_SUCCESS message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_SUCCESS message for non-existent channel " + id); - - synchronized (c) - { - c.successCounter++; - c.notifyAll(); - } - - log.debug("Got SSH_MSG_CHANNEL_SUCCESS (channel " + id + ")"); - } - - public void msgChannelFailure(byte[] msg, int msglen) throws IOException - { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_FAILURE message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id); - - synchronized (c) - { - c.failedCounter++; - c.notifyAll(); - } - - log.debug("Got SSH_MSG_CHANNEL_FAILURE (channel " + id + ")"); - } - - public void msgChannelOpenConfirmation(byte[] msg, int msglen) throws IOException - { - PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg, 0, msglen); - - Channel c = getChannel(sm.recipientChannelID); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for non-existent channel " - + sm.recipientChannelID); - - synchronized (c) - { - if (c.state != Channel.STATE_OPENING) - throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for channel " - + sm.recipientChannelID); - - c.remoteID = sm.senderChannelID; - c.remoteWindow = sm.initialWindowSize & 0xFFFFffffL; /* convert UINT32 to long */ - c.remoteMaxPacketSize = sm.maxPacketSize; - c.state = Channel.STATE_OPEN; - c.notifyAll(); - } - - log.debug("Got SSH_MSG_CHANNEL_OPEN_CONFIRMATION (channel " + sm.recipientChannelID + " / remote: " - + sm.senderChannelID + ")"); - } - - public void msgChannelOpenFailure(byte[] msg, int msglen) throws IOException - { - if (msglen < 5) - throw new IOException("SSH_MSG_CHANNEL_OPEN_FAILURE message has wrong size (" + msglen + ")"); - - TypesReader tr = new TypesReader(msg, 0, msglen); - - tr.readByte(); // skip packet type - int id = tr.readUINT32(); /* sender channel */ - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE message for non-existent channel " + id); - - int reasonCode = tr.readUINT32(); - String description = tr.readString("UTF-8"); - - String reasonCodeSymbolicName = null; - - switch (reasonCode) - { - case 1: - reasonCodeSymbolicName = "SSH_OPEN_ADMINISTRATIVELY_PROHIBITED"; - break; - case 2: - reasonCodeSymbolicName = "SSH_OPEN_CONNECT_FAILED"; - break; - case 3: - reasonCodeSymbolicName = "SSH_OPEN_UNKNOWN_CHANNEL_TYPE"; - break; - case 4: - reasonCodeSymbolicName = "SSH_OPEN_RESOURCE_SHORTAGE"; - break; - default: - reasonCodeSymbolicName = "UNKNOWN REASON CODE (" + reasonCode + ")"; - } - - StringBuilder descriptionBuffer = new StringBuilder(); - descriptionBuffer.append(description); - - for (int i = 0; i < descriptionBuffer.length(); i++) - { - char cc = descriptionBuffer.charAt(i); - - if ((cc >= 32) && (cc <= 126)) - continue; - descriptionBuffer.setCharAt(i, '\uFFFD'); - } - - synchronized (c) - { - c.EOF = true; - c.state = Channel.STATE_CLOSED; - c.setReasonClosed("The server refused to open the channel (" + reasonCodeSymbolicName + ", '" - + descriptionBuffer.toString() + "')"); - c.notifyAll(); - } - - log.debug("Got SSH_MSG_CHANNEL_OPEN_FAILURE (channel " + id + ")"); - } - - public void msgGlobalRequest(byte[] msg, int msglen) throws IOException - { - /* Currently we do not support any kind of global request */ - - TypesReader tr = new TypesReader(msg, 0, msglen); - - tr.readByte(); // skip packet type - String requestName = tr.readString(); - boolean wantReply = tr.readBoolean(); - - if (wantReply) - { - byte[] reply_failure = new byte[1]; - reply_failure[0] = Packets.SSH_MSG_REQUEST_FAILURE; - - tm.sendAsynchronousMessage(reply_failure); - } - - /* We do not clean up the requestName String - that is OK for debug */ - - log.debug("Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")"); - } - - public void msgGlobalSuccess() throws IOException - { - synchronized (channels) - { - globalSuccessCounter++; - channels.notifyAll(); - } - - log.debug("Got SSH_MSG_REQUEST_SUCCESS"); - } - - public void msgGlobalFailure() throws IOException - { - synchronized (channels) - { - globalFailedCounter++; - channels.notifyAll(); - } - - log.debug("Got SSH_MSG_REQUEST_FAILURE"); - } - - public void handleMessage(byte[] msg, int msglen) throws IOException - { - if (msg == null) - { - - log.debug("HandleMessage: got shutdown"); - - synchronized (listenerThreads) - { - for (IChannelWorkerThread lat : listenerThreads) - { - lat.stopWorking(); - } - listenerThreadsAllowed = false; - } - - synchronized (channels) - { - shutdown = true; - - for (Channel c : channels) - { - synchronized (c) - { - c.EOF = true; - c.state = Channel.STATE_CLOSED; - c.setReasonClosed("The connection is being shutdown"); - c.closeMessageRecv = true; /* - * You never know, perhaps - * we are waiting for a - * pending close message - * from the server... - */ - c.notifyAll(); - } - } - - channels.clear(); - channels.notifyAll(); /* Notify global response waiters */ - return; - } - } - - switch (msg[0]) - { - case Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION: - msgChannelOpenConfirmation(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST: - msgChannelWindowAdjust(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_DATA: - msgChannelData(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_EXTENDED_DATA: - msgChannelExtendedData(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_REQUEST: - msgChannelRequest(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_EOF: - msgChannelEOF(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_OPEN: - msgChannelOpen(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_CLOSE: - msgChannelClose(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_SUCCESS: - msgChannelSuccess(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_FAILURE: - msgChannelFailure(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_OPEN_FAILURE: - msgChannelOpenFailure(msg, msglen); - break; - case Packets.SSH_MSG_GLOBAL_REQUEST: - msgGlobalRequest(msg, msglen); - break; - case Packets.SSH_MSG_REQUEST_SUCCESS: - msgGlobalSuccess(); - break; - case Packets.SSH_MSG_REQUEST_FAILURE: - msgGlobalFailure(); - break; - default: - throw new IOException("Cannot handle unknown channel message " + (msg[0] & 0xff)); - } - } -} diff --git a/third-party/ganymed/src/main/java/ch/ethz/ssh2/transport/TransportManager.java b/third-party/ganymed/src/main/java/ch/ethz/ssh2/transport/TransportManager.java deleted file mode 100644 index 963267082b..0000000000 --- a/third-party/ganymed/src/main/java/ch/ethz/ssh2/transport/TransportManager.java +++ /dev/null @@ -1,990 +0,0 @@ -/* - * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. - * Please refer to the LICENSE.txt for licensing details. - */ - -package ch.ethz.ssh2.transport; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; -import java.security.SecureRandom; -import java.util.List; -import java.util.Vector; - -import ch.ethz.ssh2.ConnectionInfo; -import ch.ethz.ssh2.ConnectionMonitor; -import ch.ethz.ssh2.DHGexParameters; -import ch.ethz.ssh2.HTTPProxyData; -import ch.ethz.ssh2.HTTPProxyException; -import ch.ethz.ssh2.ProxyData; -import ch.ethz.ssh2.ServerHostKeyVerifier; -import ch.ethz.ssh2.crypto.Base64; -import ch.ethz.ssh2.crypto.CryptoWishList; -import ch.ethz.ssh2.crypto.cipher.BlockCipher; -import ch.ethz.ssh2.crypto.digest.MAC; -import ch.ethz.ssh2.log.Logger; -import ch.ethz.ssh2.packets.PacketDisconnect; -import ch.ethz.ssh2.packets.Packets; -import ch.ethz.ssh2.packets.TypesReader; -import ch.ethz.ssh2.server.ServerConnectionState; -import ch.ethz.ssh2.signature.DSAPrivateKey; -import ch.ethz.ssh2.signature.RSAPrivateKey; -import ch.ethz.ssh2.util.StringEncoder; -import ch.ethz.ssh2.util.Tokenizer; - -/* - * Yes, the "standard" is a big mess. On one side, the say that arbitary channel - * packets are allowed during kex exchange, on the other side we need to blindly - * ignore the next _packet_ if the KEX guess was wrong. Where do we know from that - * the next packet is not a channel data packet? Yes, we could check if it is in - * the KEX range. But the standard says nothing about this. The OpenSSH guys - * block local "normal" traffic during KEX. That's fine - however, they assume - * that the other side is doing the same. During re-key, if they receive traffic - * other than KEX, they become horribly irritated and kill the connection. Since - * we are very likely going to communicate with OpenSSH servers, we have to play - * the same game - even though we could do better. - * - * btw: having stdout and stderr on the same channel, with a shared window, is - * also a VERY good idea... =( - */ - -/** - * TransportManager. - * - * @author Christian Plattner - * @version $Id: TransportManager.java 47 2013-07-31 23:59:52Z cleondris@gmail.com $ - */ -public class TransportManager -{ - private static final Logger log = Logger.getLogger(TransportManager.class); - - private static class HandlerEntry - { - MessageHandler mh; - int low; - int high; - } - - private final List asynchronousQueue = new Vector(); - private Thread asynchronousThread = null; - private boolean asynchronousPending = false; - - class AsynchronousEntry - { - public byte[] msg; - public Runnable run; - - public AsynchronousEntry(byte[] msg, Runnable run) - { - this.msg = msg; - this.run = run; - } - } - - class AsynchronousWorker extends Thread - { - @Override - public void run() - { - while (true) - { - AsynchronousEntry item = null; - - synchronized (asynchronousQueue) - { - if (asynchronousQueue.size() == 0) - { - /* Only now we may reset the flag, since we are sure that all queued items - * have been sent (there is a slight delay between de-queuing and sending, - * this is why we need this flag! See code below. Sending takes place outside - * of this lock, this is why a test for size()==0 (from another thread) does not ensure - * that all messages have been sent. - */ - - asynchronousPending = false; - - /* Notify any senders that they can proceed, all async messages have been delivered */ - - asynchronousQueue.notifyAll(); - - /* After the queue is empty for about 2 seconds, stop this thread */ - - try - { - asynchronousQueue.wait(2000); - } - catch (InterruptedException ignore) - { - } - - if (asynchronousQueue.size() == 0) - { - asynchronousThread = null; - return; - } - } - - item = asynchronousQueue.remove(0); - } - - /* The following invocation may throw an IOException. - * There is no point in handling it - it simply means - * that the connection has a problem and we should stop - * sending asynchronously messages. We do not need to signal that - * we have exited (asynchronousThread = null): further - * messages in the queue cannot be sent by this or any - * other thread. - * Other threads will sooner or later (when receiving or - * sending the next message) get the same IOException and - * get to the same conclusion. - */ - - try - { - sendMessageImmediate(item.msg); - } - catch (IOException e) - { - return; - } - - if (item.run != null) - { - try - { - item.run.run(); - } - catch (Exception ignore) - { - } - - } - } - } - } - - private Socket sock = new Socket(); - - private final Object connectionSemaphore = new Object(); - - private boolean flagKexOngoing = false; - private boolean connectionClosed = false; - - private Throwable reasonClosedCause = null; - - private TransportConnection tc; - private KexManager km; - - private final List messageHandlers = new Vector(); - - private Thread receiveThread; - - private List connectionMonitors = new Vector(); - private boolean monitorsWereInformed = false; - - /** - * There were reports that there are JDKs which use - * the resolver even though one supplies a dotted IP - * address in the Socket constructor. That is why we - * try to generate the InetAdress "by hand". - * - * @param host - * @return the InetAddress - * @throws UnknownHostException - */ - private static InetAddress createInetAddress(String host) throws UnknownHostException - { - /* Check if it is a dotted IP4 address */ - - InetAddress addr = parseIPv4Address(host); - - if (addr != null) - { - return addr; - } - - return InetAddress.getByName(host); - } - - private static InetAddress parseIPv4Address(String host) throws UnknownHostException - { - if (host == null) - { - return null; - } - - String[] quad = Tokenizer.parseTokens(host, '.'); - - if ((quad == null) || (quad.length != 4)) - { - return null; - } - - byte[] addr = new byte[4]; - - for (int i = 0; i < 4; i++) - { - int part = 0; - - if ((quad[i].length() == 0) || (quad[i].length() > 3)) - { - return null; - } - - for (int k = 0; k < quad[i].length(); k++) - { - char c = quad[i].charAt(k); - - /* No, Character.isDigit is not the same */ - if ((c < '0') || (c > '9')) - { - return null; - } - - part = part * 10 + (c - '0'); - } - - if (part > 255) /* 300.1.2.3 is invalid =) */ - { - return null; - } - - addr[i] = (byte) part; - } - - return InetAddress.getByAddress(host, addr); - } - - public int getPacketOverheadEstimate() - { - return tc.getPacketOverheadEstimate(); - } - - public void setTcpNoDelay(boolean state) throws IOException - { - sock.setTcpNoDelay(state); - } - - public void setSoTimeout(int timeout) throws IOException - { - sock.setSoTimeout(timeout); - } - - public ConnectionInfo getConnectionInfo(int kexNumber) throws IOException - { - return km.getOrWaitForConnectionInfo(kexNumber); - } - - public Throwable getReasonClosedCause() - { - synchronized (connectionSemaphore) - { - return reasonClosedCause; - } - } - - public byte[] getSessionIdentifier() - { - return km.sessionId; - } - - public void close(Throwable cause, boolean useDisconnectPacket) - { - if (useDisconnectPacket == false) - { - /* OK, hard shutdown - do not aquire the semaphore, - * perhaps somebody is inside (and waits until the remote - * side is ready to accept new data). */ - - try - { - sock.close(); - } - catch (IOException ignore) - { - } - - /* OK, whoever tried to send data, should now agree that - * there is no point in further waiting =) - * It is safe now to aquire the semaphore. - */ - } - - synchronized (connectionSemaphore) - { - if (connectionClosed == false) - { - if (useDisconnectPacket == true) - { - try - { - byte[] msg = new PacketDisconnect(Packets.SSH_DISCONNECT_BY_APPLICATION, cause.getMessage(), "") - .getPayload(); - if (tc != null) - { - tc.sendMessage(msg); - } - } - catch (IOException ignore) - { - } - - try - { - sock.close(); - } - catch (IOException ignore) - { - } - } - - connectionClosed = true; - reasonClosedCause = cause; /* may be null */ - } - connectionSemaphore.notifyAll(); - } - - /* No check if we need to inform the monitors */ - - List monitors = new Vector(); - - synchronized (this) - { - /* Short term lock to protect "connectionMonitors" - * and "monitorsWereInformed" - * (they may be modified concurrently) - */ - - if (monitorsWereInformed == false) - { - monitorsWereInformed = true; - monitors.addAll(connectionMonitors); - } - } - - for (ConnectionMonitor cmon : monitors) - { - try - { - cmon.connectionLost(reasonClosedCause); - } - catch (Exception ignore) - { - } - } - } - - private static Socket establishConnection(String hostname, int port, ProxyData proxyData, int connectTimeout) - throws IOException - { - /* See the comment for createInetAddress() */ - - if (proxyData == null) - { - InetAddress addr = createInetAddress(hostname); - Socket s = new Socket(); - s.connect(new InetSocketAddress(addr, port), connectTimeout); - return s; - } - - if (proxyData instanceof HTTPProxyData) - { - HTTPProxyData pd = (HTTPProxyData) proxyData; - - /* At the moment, we only support HTTP proxies */ - - InetAddress addr = createInetAddress(pd.proxyHost); - Socket s = new Socket(); - s.connect(new InetSocketAddress(addr, pd.proxyPort), connectTimeout); - - /* OK, now tell the proxy where we actually want to connect to */ - - StringBuilder sb = new StringBuilder(); - - sb.append("CONNECT "); - sb.append(hostname); - sb.append(':'); - sb.append(port); - sb.append(" HTTP/1.0\r\n"); - - if ((pd.proxyUser != null) && (pd.proxyPass != null)) - { - String credentials = pd.proxyUser + ":" + pd.proxyPass; - char[] encoded = Base64.encode(StringEncoder.GetBytes(credentials)); - sb.append("Proxy-Authorization: Basic "); - sb.append(encoded); - sb.append("\r\n"); - } - - if (pd.requestHeaderLines != null) - { - for (int i = 0; i < pd.requestHeaderLines.length; i++) - { - if (pd.requestHeaderLines[i] != null) - { - sb.append(pd.requestHeaderLines[i]); - sb.append("\r\n"); - } - } - } - - sb.append("\r\n"); - - OutputStream out = s.getOutputStream(); - - out.write(StringEncoder.GetBytes(sb.toString())); - out.flush(); - - /* Now parse the HTTP response */ - - byte[] buffer = new byte[1024]; - InputStream in = s.getInputStream(); - - int len = ClientServerHello.readLineRN(in, buffer); - - String httpReponse = StringEncoder.GetString(buffer, 0, len); - - if (httpReponse.startsWith("HTTP/") == false) - { - throw new IOException("The proxy did not send back a valid HTTP response."); - } - - /* "HTTP/1.X XYZ X" => 14 characters minimum */ - - if ((httpReponse.length() < 14) || (httpReponse.charAt(8) != ' ') || (httpReponse.charAt(12) != ' ')) - { - throw new IOException("The proxy did not send back a valid HTTP response."); - } - - int errorCode = 0; - - try - { - errorCode = Integer.parseInt(httpReponse.substring(9, 12)); - } - catch (NumberFormatException ignore) - { - throw new IOException("The proxy did not send back a valid HTTP response."); - } - - if ((errorCode < 0) || (errorCode > 999)) - { - throw new IOException("The proxy did not send back a valid HTTP response."); - } - - if (errorCode != 200) - { - throw new HTTPProxyException(httpReponse.substring(13), errorCode); - } - - /* OK, read until empty line */ - - while (true) - { - len = ClientServerHello.readLineRN(in, buffer); - if (len == 0) - { - break; - } - } - return s; - } - - throw new IOException("Unsupported ProxyData"); - } - - private void startReceiver() throws IOException - { - receiveThread = new Thread(new Runnable() - { - public void run() - { - try - { - receiveLoop(); - } - catch (Exception e) - { - close(e, false); - - log.warning("Receive thread: error in receiveLoop: " + e.getMessage()); - } - - if (log.isDebugEnabled()) - { - log.debug("Receive thread: back from receiveLoop"); - } - - /* Tell all handlers that it is time to say goodbye */ - - if (km != null) - { - try - { - km.handleMessage(null, 0); - } - catch (IOException ignored) - { - } - } - - for (HandlerEntry he : messageHandlers) - { - try - { - he.mh.handleMessage(null, 0); - } - catch (Exception ignore) - { - } - } - } - }); - - receiveThread.setDaemon(true); - receiveThread.start(); - } - - public void clientInit(Socket socket, String softwareversion, CryptoWishList cwl, - ServerHostKeyVerifier verifier, DHGexParameters dhgex, SecureRandom rnd) throws IOException - { - /* First, establish the TCP connection to the SSH-2 server */ - - sock = socket; - - /* Parse the server line and say hello - important: this information is later needed for the - * key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object - * for later use. - */ - - ClientServerHello csh = ClientServerHello.clientHello(softwareversion, sock.getInputStream(), - sock.getOutputStream()); - - tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), rnd); - String hostname = sock.getInetAddress().getHostName(); - int port = sock.getPort(); - - km = new ClientKexManager(this, csh, cwl, hostname, port, verifier, rnd); - km.initiateKEX(cwl, dhgex, null, null); - - startReceiver(); - } - - public void clientInit(String hostname, int port, String softwareversion, CryptoWishList cwl, - ServerHostKeyVerifier verifier, DHGexParameters dhgex, int connectTimeout, SecureRandom rnd, - ProxyData proxyData) throws IOException - { - /* First, establish the TCP connection to the SSH-2 server */ - - sock = establishConnection(hostname, port, proxyData, connectTimeout); - - /* Parse the server line and say hello - important: this information is later needed for the - * key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object - * for later use. - */ - - ClientServerHello csh = ClientServerHello.clientHello(softwareversion, sock.getInputStream(), - sock.getOutputStream()); - - tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), rnd); - - km = new ClientKexManager(this, csh, cwl, hostname, port, verifier, rnd); - km.initiateKEX(cwl, dhgex, null, null); - - startReceiver(); - } - - public void serverInit(ServerConnectionState state) throws IOException - { - /* TCP connection is already established */ - - this.sock = state.s; - - /* Parse the client line and say hello - important: this information is later needed for the - * key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object - * for later use. - */ - - state.csh = ClientServerHello.serverHello(state.softwareversion, sock.getInputStream(), sock.getOutputStream()); - - tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), state.generator); - - km = new ServerKexManager(state); - km.initiateKEX(state.next_cryptoWishList, null, state.next_dsa_key, state.next_rsa_key); - - startReceiver(); - } - - public void registerMessageHandler(MessageHandler mh, int low, int high) - { - HandlerEntry he = new HandlerEntry(); - he.mh = mh; - he.low = low; - he.high = high; - - synchronized (messageHandlers) - { - messageHandlers.add(he); - } - } - - public void removeMessageHandler(MessageHandler mh, int low, int high) - { - synchronized (messageHandlers) - { - for (int i = 0; i < messageHandlers.size(); i++) - { - HandlerEntry he = messageHandlers.get(i); - if ((he.mh == mh) && (he.low == low) && (he.high == high)) - { - messageHandlers.remove(i); - break; - } - } - } - } - - public void sendKexMessage(byte[] msg) throws IOException - { - synchronized (connectionSemaphore) - { - if (connectionClosed) - { - throw (IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause); - } - - flagKexOngoing = true; - - try - { - tc.sendMessage(msg); - } - catch (IOException e) - { - close(e, false); - throw e; - } - } - } - - public void kexFinished() throws IOException - { - synchronized (connectionSemaphore) - { - flagKexOngoing = false; - connectionSemaphore.notifyAll(); - } - } - - /** - * - * @param cwl - * @param dhgex - * @param dsa may be null if this is a client connection - * @param rsa may be null if this is a client connection - * @throws IOException - */ - public void forceKeyExchange(CryptoWishList cwl, DHGexParameters dhgex, DSAPrivateKey dsa, RSAPrivateKey rsa) - throws IOException - { - synchronized (connectionSemaphore) - { - if (connectionClosed) - /* Inform the caller that there is no point in triggering a new kex */ - throw (IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause); - } - - km.initiateKEX(cwl, dhgex, dsa, rsa); - } - - public void changeRecvCipher(BlockCipher bc, MAC mac) - { - tc.changeRecvCipher(bc, mac); - } - - public void changeSendCipher(BlockCipher bc, MAC mac) - { - tc.changeSendCipher(bc, mac); - } - - public void sendAsynchronousMessage(byte[] msg) throws IOException - { - sendAsynchronousMessage(msg, null); - } - - public void sendAsynchronousMessage(byte[] msg, Runnable run) throws IOException - { - synchronized (asynchronousQueue) - { - asynchronousQueue.add(new AsynchronousEntry(msg, run)); - asynchronousPending = true; - - /* This limit should be flexible enough. We need this, otherwise the peer - * can flood us with global requests (and other stuff where we have to reply - * with an asynchronous message) and (if the server just sends data and does not - * read what we send) this will probably put us in a low memory situation - * (our send queue would grow and grow and...) */ - - if (asynchronousQueue.size() > 100) - { - throw new IOException("Error: the peer is not consuming our asynchronous replies."); - } - - /* Check if we have an asynchronous sending thread */ - - if (asynchronousThread == null) - { - asynchronousThread = new AsynchronousWorker(); - asynchronousThread.setDaemon(true); - asynchronousThread.start(); - - /* The thread will stop after 2 seconds of inactivity (i.e., empty queue) */ - } - - asynchronousQueue.notifyAll(); - } - } - - public void setConnectionMonitors(List monitors) - { - synchronized (this) - { - connectionMonitors = new Vector(); - connectionMonitors.addAll(monitors); - } - } - - /** - * True if no response message expected. - */ - private boolean idle; - - /** - * Send a message but ensure that all queued messages are being sent first. - * - * @param msg - * @throws IOException - */ - public void sendMessage(byte[] msg) throws IOException - { - synchronized (asynchronousQueue) - { - while (asynchronousPending) - { - try - { - asynchronousQueue.wait(1000); - } - catch (InterruptedException e) - { - } - } - } - - sendMessageImmediate(msg); - } - - /** - * Send message, ignore queued async messages that have not been delivered yet. - * Will be called directly from the asynchronousThread thread. - * - * @param msg - * @throws IOException - */ - public void sendMessageImmediate(byte[] msg) throws IOException - { - if (Thread.currentThread() == receiveThread) - { - throw new IOException("Assertion error: sendMessage may never be invoked by the receiver thread!"); - } - - boolean wasInterrupted = false; - - try - { - synchronized (connectionSemaphore) - { - while (true) - { - if (connectionClosed) - { - throw (IOException) new IOException("Sorry, this connection is closed.") - .initCause(reasonClosedCause); - } - - if (flagKexOngoing == false) - { - break; - } - - try - { - connectionSemaphore.wait(); - } - catch (InterruptedException e) - { - wasInterrupted = true; - } - } - - try - { - tc.sendMessage(msg); - idle = false; - } - catch (IOException e) - { - close(e, false); - throw e; - } - } - } - finally - { - if (wasInterrupted) - Thread.currentThread().interrupt(); - } - } - - public void receiveLoop() throws IOException - { - byte[] msg = new byte[35000]; - - while (true) - { - int msglen; - try - { - msglen = tc.receiveMessage(msg, 0, msg.length); - } - catch (SocketTimeoutException e) - { - // Timeout in read - if (idle) - { - log.debug("Ignoring socket timeout"); - continue; - } - throw e; - } - idle = true; - - int type = msg[0] & 0xff; - - if (type == Packets.SSH_MSG_IGNORE) - { - continue; - } - - if (type == Packets.SSH_MSG_DEBUG) - { - if (log.isDebugEnabled()) - { - TypesReader tr = new TypesReader(msg, 0, msglen); - tr.readByte(); - tr.readBoolean(); - StringBuilder debugMessageBuffer = new StringBuilder(); - debugMessageBuffer.append(tr.readString("UTF-8")); - - for (int i = 0; i < debugMessageBuffer.length(); i++) - { - char c = debugMessageBuffer.charAt(i); - - if ((c >= 32) && (c <= 126)) - { - continue; - } - debugMessageBuffer.setCharAt(i, '\uFFFD'); - } - - log.debug("DEBUG Message from remote: '" + debugMessageBuffer.toString() + "'"); - } - continue; - } - - if (type == Packets.SSH_MSG_UNIMPLEMENTED) - { - throw new IOException("Peer sent UNIMPLEMENTED message, that should not happen."); - } - - if (type == Packets.SSH_MSG_DISCONNECT) - { - TypesReader tr = new TypesReader(msg, 0, msglen); - tr.readByte(); - int reason_code = tr.readUINT32(); - StringBuilder reasonBuffer = new StringBuilder(); - reasonBuffer.append(tr.readString("UTF-8")); - - /* - * Do not get fooled by servers that send abnormal long error - * messages - */ - - if (reasonBuffer.length() > 255) - { - reasonBuffer.setLength(255); - reasonBuffer.setCharAt(254, '.'); - reasonBuffer.setCharAt(253, '.'); - reasonBuffer.setCharAt(252, '.'); - } - - /* - * Also, check that the server did not send characters that may - * screw up the receiver -> restrict to reasonable US-ASCII - * subset -> "printable characters" (ASCII 32 - 126). Replace - * all others with 0xFFFD (UNICODE replacement character). - */ - - for (int i = 0; i < reasonBuffer.length(); i++) - { - char c = reasonBuffer.charAt(i); - - if ((c >= 32) && (c <= 126)) - { - continue; - } - reasonBuffer.setCharAt(i, '\uFFFD'); - } - - throw new IOException("Peer sent DISCONNECT message (reason code " + reason_code + "): " - + reasonBuffer.toString()); - } - - /* - * Is it a KEX Packet? - */ - - if ((type == Packets.SSH_MSG_KEXINIT) || (type == Packets.SSH_MSG_NEWKEYS) - || ((type >= 30) && (type <= 49))) - { - km.handleMessage(msg, msglen); - continue; - } - - MessageHandler mh = null; - - for (int i = 0; i < messageHandlers.size(); i++) - { - HandlerEntry he = messageHandlers.get(i); - if ((he.low <= type) && (type <= he.high)) - { - mh = he.mh; - break; - } - } - - if (mh == null) - { - throw new IOException("Unexpected SSH message (type " + type + ")"); - } - - mh.handleMessage(msg, msglen); - } - } -}