<groupId>org.opendaylight.controller</groupId>
<artifactId>karaf-tomcat-security</artifactId>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller.thirdparty</groupId>
- <artifactId>ganymed</artifactId>
- </dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<bundle>wrap:mvn:io.netty/netty-common/${netty.version}</bundle>
<bundle>wrap:mvn:io.netty/netty-handler/${netty.version}</bundle>
<bundle>wrap:mvn:io.netty/netty-codec-http/${netty.version}</bundle>
- <bundle>mvn:org.opendaylight.controller.thirdparty/ganymed/1.2.0-SNAPSHOT</bundle>
</feature>
<feature name="odl-base-jersey" description="Jersey" version="${jersey.version}">
<feature>odl-base-gemini-web</feature>
<feature name="odl-base-eclipselink-persistence" description="EclipseLink Persistence API" version="2.0.4.v201112161009">
<bundle start="true">mvn:eclipselink/javax.persistence/2.0.4.v201112161009</bundle>
<bundle start="true">mvn:eclipselink/javax.resource/1.5.0.v200906010428</bundle>
+ <bundle start="true">mvn:org.eclipse.persistence/org.eclipse.persistence.antlr/2.5.0</bundle>
<bundle start="true">mvn:org.eclipse.persistence/org.eclipse.persistence.moxy/2.5.0</bundle>
<bundle start="true">mvn:org.eclipse.persistence/org.eclipse.persistence.core/2.5.0</bundle>
</feature>
Optional TODO: Remove TODO comments.
-->
<!-- test to validate features.xml -->
+ <!--FIXME BUG-2195 When running single feature tests for netconf connector, features including ssh proxy server always fail (this behavior does not appear when running karaf distro directly)-->
<dependency>
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>features-test</artifactId>
<groupId>org.opendaylight.controller</groupId>
<artifactId>netconf-netty-util</artifactId>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller.thirdparty</groupId>
- <artifactId>ganymed</artifactId>
- </dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
<feature version='${project.version}'>odl-netconf-mapping-api</feature>
<feature version='${project.version}'>odl-netconf-util</feature>
<bundle>mvn:org.opendaylight.controller/netconf-netty-util/${project.version}</bundle>
- <bundle>mvn:org.opendaylight.controller.thirdparty/ganymed/${ganymed.version}</bundle>
<bundle>mvn:org.apache.sshd/sshd-core/${sshd-core.version}</bundle>
<bundle>mvn:org.openexi/nagasena/${exi.nagasena.version}</bundle>
<bundle>mvn:io.netty/netty-codec/${netty.version}</bundle>
<forwarding.staticrouting.northbound.version>0.5.0-SNAPSHOT</forwarding.staticrouting.northbound.version>
<forwardingrulesmanager.implementation.version>0.5.0-SNAPSHOT</forwardingrulesmanager.implementation.version>
<forwardingrulesmanager.version>0.7.0-SNAPSHOT</forwardingrulesmanager.version>
- <ganymed.version>1.2.0-SNAPSHOT</ganymed.version>
<hosttracker.api.version>0.6.0-SNAPSHOT</hosttracker.api.version>
<hosttracker.implementation.version>0.6.0-SNAPSHOT</hosttracker.implementation.version>
<hosttracker.northbound.version>0.5.0-SNAPSHOT</hosttracker.northbound.version>
<sonar.language>java</sonar.language>
<sonar.jacoco.reportPath>target/code-coverage/jacoco.exec</sonar.jacoco.reportPath>
<sonar.jacoco.itReportPath>target/code-coverage/jacoco-it.exec</sonar.jacoco.itReportPath>
- <sonar.skippedModules>org.openflow.openflowj,net.sf.jung2,org.opendaylight.controller.protobuff.messages,ch.ethz.ssh2</sonar.skippedModules>
+ <sonar.skippedModules>org.openflow.openflowj,net.sf.jung2,org.opendaylight.controller.protobuff.messages</sonar.skippedModules>
<sonar.profile>Sonar way with Findbugs</sonar.profile>
<spifly.version>1.0.0</spifly.version>
<spring-osgi.version>1.2.1</spring-osgi.version>
<dependencyManagement>
<dependencies>
+
<!-- project specific dependencies -->
<dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>ietf-netconf-monitoring</artifactId>
- <version>${netconf.version}</version>
- </dependency>
- <dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>ietf-netconf-monitoring-extension</artifactId>
- <version>${netconf.version}</version>
- </dependency>
- <dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>netconf-netty-util</artifactId>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>netconf-artifacts</artifactId>
<version>${netconf.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>config-manager</artifactId>
<version>${config.version}</version>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>config-netconf-connector</artifactId>
- <version>${netconf.version}</version>
- </dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>config-persister-api</artifactId>
<artifactId>config-persister-feature-adapter</artifactId>
<version>${config.version}</version>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>config-persister-impl</artifactId>
- <version>${netconf.version}</version>
- </dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<version>${dummy-console.version}</version>
</dependency>
- <!-- Netconf -->
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-api</artifactId>
- <version>${netconf.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-client</artifactId>
- <version>${netconf.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-client</artifactId>
- <version>${netconf.version}</version>
- <type>test-jar</type>
- </dependency>
-
- <!--Netconf config-->
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-config-dispatcher</artifactId>
- <version>${netconf.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-impl</artifactId>
- <version>${netconf.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-impl</artifactId>
- <version>${netconf.version}</version>
- <type>test-jar</type>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-mapping-api</artifactId>
- <version>${netconf.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-monitoring</artifactId>
- <version>${netconf.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-netty-util</artifactId>
- <version>${netconf.version}</version>
- <type>test-jar</type>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-auth</artifactId>
- <version>${netconf.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-usermanager</artifactId>
- <version>${netconf.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-ssh</artifactId>
- <version>${netconf.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-ssh</artifactId>
- <version>${netconf.version}</version>
- <type>test-jar</type>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-tcp</artifactId>
- <version>${netconf.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-util</artifactId>
- <version>${netconf.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-util</artifactId>
- <version>${netconf.version}</version>
- <type>test-jar</type>
- </dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>netty-config-api</artifactId>
<artifactId>md-sal-config</artifactId>
<version>${mdsal.version}</version>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-config</artifactId>
- <version>${netconf.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>netconf-connector-config</artifactId>
- <version>${netconf.version}</version>
- </dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>sal-rest-docgen</artifactId>
<artifactId>com.sun.jersey.jersey-servlet</artifactId>
<version>${jersey-servlet.version}</version>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller.thirdparty</groupId>
- <artifactId>ganymed</artifactId>
- <version>${ganymed.version}</version>
- </dependency>
+
<!-- Third parties from opendaylight released -->
<dependency>
<groupId>org.opendaylight.controller.thirdparty</groupId>
<artifactId>org.openflow.openflowj</artifactId>
<version>1.0.2</version>
</dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>binding-generator-impl</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>binding-data-codec</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>binding-generator-spi</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>binding-generator-util</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>binding-type-provider</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>concepts</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>object-cache-api</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>object-cache-guava</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>restconf-client-api</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>restconf-client-impl</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>util</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>yang-data-composite-node</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>yang-data-codec-gson</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <!-- yangtools dependencies -->
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>yang-binding</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>yang-common</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>yang-data-api</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>yang-data-impl</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
+ <!-- yangtools artifacts -->
<dependency>
<groupId>org.opendaylight.yangtools</groupId>
- <artifactId>yang-data-util</artifactId>
+ <artifactId>yangtools-artifacts</artifactId>
<version>${yangtools.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
</dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>yang-maven-plugin-spi</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>yang-model-api</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>yang-model-util</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>yang-parser-api</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>yang-parser-impl</artifactId>
- <version>${yangtools.version}</version>
- </dependency>
- <!-- yang model dependencies -->
- <dependency>
- <groupId>org.opendaylight.yangtools.model</groupId>
- <artifactId>ietf-inet-types</artifactId>
- <version>${ietf-inet-types.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools.model</groupId>
- <artifactId>ietf-restconf</artifactId>
- <version>${ietf-restconf.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools.model</groupId>
- <artifactId>ietf-topology</artifactId>
- <version>${ietf-topology.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools.model</groupId>
- <artifactId>ietf-topology-l3-unicast-igp</artifactId>
- <version>${ietf-topology.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools.model</groupId>
- <artifactId>ietf-yang-types</artifactId>
- <version>${ietf-yang-types.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools.model</groupId>
- <artifactId>ietf-yang-types-20130715</artifactId>
- <version>2013.07.15.7-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools.model</groupId>
- <artifactId>opendaylight-l2-types</artifactId>
- <version>${opendaylight-l2-types.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools.model</groupId>
- <artifactId>yang-ext</artifactId>
- <version>${yang-ext.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools.thirdparty</groupId>
- <artifactId>antlr4-runtime-osgi-nohead</artifactId>
- <version>4.0</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools.thirdparty</groupId>
- <artifactId>xtend-lib-osgi</artifactId>
- <version>${xtend.version}</version>
- </dependency>
+
<dependency>
<groupId>org.openexi</groupId>
<artifactId>nagasena</artifactId>
<version>${mdsal.version}</version>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools</groupId>
- <artifactId>mockito-configuration</artifactId>
- <version>${yangtools.version}</version>
- <scope>test</scope>
- </dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>features-config</artifactId>
<type>xml</type>
<scope>runtime</scope>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>features-netconf</artifactId>
- <version>${netconf.version}</version>
- <classifier>features</classifier>
- <type>xml</type>
- <scope>runtime</scope>
- </dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>features-config-persister</artifactId>
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);
assertFalse(session.isSuccess());
}
- @Test
- public void testNegotiationFailedNoReconnect() throws Exception {
- final Promise<Boolean> p = new DefaultPromise<>(GlobalEventExecutor.INSTANCE);
-
- this.dispatcher = getServerDispatcher(p);
-
- this.server = this.dispatcher.createServer(this.serverAddress, new SessionListenerFactory<SimpleSessionListener>() {
- @Override
- public SimpleSessionListener getSessionListener() {
- return new SimpleSessionListener();
- }
- });
-
- this.server.get();
-
- this.clientDispatcher = new SimpleDispatcher(new SessionNegotiatorFactory<SimpleMessage, SimpleSession, SimpleSessionListener>() {
- @Override
- public SessionNegotiator<SimpleSession> getSessionNegotiator(final SessionListenerFactory<SimpleSessionListener> factory,
- final Channel channel, final Promise<SimpleSession> promise) {
-
- return new SimpleSessionNegotiator(promise, channel) {
- @Override
- protected void startNegotiation() throws Exception {
- negotiationFailed(new IllegalStateException("Negotiation failed"));
- }
- };
- }
- }, new DefaultPromise<SimpleSession>(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<SimpleSessionListener>() {
- @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<SimpleMessage, SimpleSession, SimpleSessionListener>() {
@Override
"Top level container encapsulating configuration of all modules.";
list module {
- key "name";
+ key "type name";
leaf name {
description "Unique module instance name";
type string;
* 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,
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;
}
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;
@Override
public void shutdown(String inputSecret, Long maxWaitTime, Optional<String> 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");
}
}
}
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) {
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");
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();
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)");
}
}
}
<artifactId>sample-toaster-provider</artifactId>
<version>${mdsal.version}</version>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller.thirdparty</groupId>
- <artifactId>ganymed</artifactId>
- </dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
<dependency>
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>binding-generator-api</artifactId>
- <version>${yangtools.version}</version>
</dependency>
<dependency>
<groupId>org.opendaylight.yangtools</groupId>
<dependency>
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>binding-model-api</artifactId>
- <version>${yangtools.version}</version>
</dependency>
<dependency>
<groupId>org.opendaylight.yangtools</groupId>
*/
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;
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;
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;
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<PathArgument,List<PathArgument>> nodeToNodeConnectorsMap = new ConcurrentHashMap<>();
private List<IPluginOutInventoryService> inventoryPublisher = new CopyOnWriteArrayList<>();
private List<IPluginOutReadService> statisticsPublisher = new CopyOnWriteArrayList<>();
+ private Cache<String, TransactionNotificationList<? extends TransactionAware>> txCache;
private OpendaylightFlowTableStatisticsService flowTableStatisticsService;
private OpendaylightPortStatisticsService nodeConnectorStatisticsService;
public void startAdapter() {
inventoryNotificationProvider.setDataProviderService(getDataProviderService());
inventoryNotificationProvider.setInventoryPublisher(getInventoryPublisher());
+ txCache = CacheBuilder.newBuilder().expireAfterWrite(60L, TimeUnit.SECONDS).maximumSize(10000).build();
// inventoryNotificationProvider.start();
}
@Override
public List<FlowOnNode> readAllFlow(final Node node, final boolean cached) {
- final ArrayList<FlowOnNode> output = new ArrayList<>();
- final Table table = readOperationalTable(node, OPENFLOWV10_TABLE_ID);
- if (table != null) {
- final List<Flow> flows = table.getFlow();
- LOG.trace("Number of flows installed in table 0 of node {} : {}", node, flows.size());
+ final ArrayList<FlowOnNode> ret= new ArrayList<>();
+ if (cached) {
+ final Table table = readOperationalTable(node, OPENFLOWV10_TABLE_ID);
+ if (table != null) {
+ final List<Flow> 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<RpcResult<GetAllFlowStatisticsFromFlowTableOutput>> 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<GetAllFlowStatisticsFromFlowTableOutput> 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<FlowsStatisticsUpdate>(
+ transactionId, node.getNodeIDString()));
+
+ TransactionNotificationList<FlowsStatisticsUpdate> txnList =
+ (TransactionNotificationList<FlowsStatisticsUpdate>) 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<FlowsStatisticsUpdate>) txCache.getIfPresent(cacheKey);
+ }
+
+ if (txnList == null) {
+ return ret;
+ }
+
+ List<FlowsStatisticsUpdate> notifications = txnList.getNotifications();
+ for (FlowsStatisticsUpdate flowsStatisticsUpdate : notifications) {
+ List<FlowAndStatisticsMapList> 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
for (final IPluginOutReadService statsPublisher : getStatisticsPublisher()) {
statsPublisher.nodeFlowStatisticsUpdated(aDNode, adsalFlowsStatistics);
}
+
+ updateTransactionCache(notification, notification.getId(), !notification.isMoreReplies());
}
/**
private List<PathArgument> removeNodeConnectors(final InstanceIdentifier<? extends Object> nodeIdentifier) {
return this.nodeToNodeConnectorsMap.remove(Iterables.get(nodeIdentifier.getPathArguments(), 1));
}
+
+ private <T extends TransactionAware> void updateTransactionCache(T notification, NodeId nodeId, boolean lastNotification) {
+
+ String cacheKey = buildCacheKey(notification.getTransactionId(), nodeId);
+ TransactionNotificationList<T> txnList = (TransactionNotificationList<T>) txCache.getIfPresent(cacheKey);
+ final Optional<TransactionNotificationList<T>> optional = Optional.<TransactionNotificationList<T>>fromNullable(txnList);
+ if (optional.isPresent()) {
+ LOG.info("updateTransactionCache cacheKey:{}, lastNotification:{}, txnList-present:{}", cacheKey, lastNotification, optional.isPresent());
+ TransactionNotificationList<T> txn = optional.get();
+ txn.addNotification(notification);
+ txn.setAllNotificationsGathered(lastNotification);
+ }
+ }
+
+ private class TransactionNotificationList<T extends TransactionAware> {
+ private TransactionId id;
+ private String nId;
+ private List<T> notifications;
+ private boolean allNotificationsGathered;
+
+ public TransactionNotificationList(TransactionId id, String nId) {
+ this.nId = nId;
+ this.id = id;
+ notifications = new ArrayList<T>();
+ }
+
+ public void addNotification(T notification) {
+ notifications.add(notification);
+ }
+
+ public void setAllNotificationsGathered(boolean allNotificationsGathered) {
+ this.allNotificationsGathered = allNotificationsGathered;
+ }
+
+ public boolean areAllNotificationsGathered() {
+ return allNotificationsGathered;
+ }
+
+ public List<T> getNotifications() {
+ return notifications;
+ }
+
+ }
+
}
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;
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
* @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;
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);
}
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());
}
}
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;
}
private void onRecoveredSnapshot(SnapshotOffer offer) {
- LOG.debug("SnapshotOffer called..");
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("SnapshotOffer called..");
+ }
initRecoveryTimer();
replicatedLog.lastIndex(), replicatedLog.snapshotIndex,
replicatedLog.snapshotTerm, replicatedLog.size());
- currentBehavior = switchBehavior(RaftState.Follower);
+ currentBehavior = new Follower(context);
onStateChanged();
}
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();
}
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
}
String peerAddress = context.getPeerAddress(leaderId);
if(LOG.isDebugEnabled()) {
- LOG.debug("getLeaderAddress leaderId = " + leaderId + " peerAddress = "
- + peerAddress);
+ LOG.debug("getLeaderAddress leaderId = {} peerAddress = {}",
+ leaderId, peerAddress);
}
return peerAddress;
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);
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;
*/
protected final RaftActorContext context;
+ /**
+ *
+ */
+ protected final LoggingAdapter LOG;
+
/**
*
*/
protected AbstractRaftActorBehavior(RaftActorContext context) {
this.context = context;
+ this.LOG = context.getLogger();
}
/**
* @param appendEntries The AppendEntries message
* @return
*/
- protected abstract RaftState handleAppendEntries(ActorRef sender,
+ protected abstract RaftActorBehavior handleAppendEntries(ActorRef sender,
AppendEntries appendEntries);
* @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;
}
* @param appendEntriesReply The AppendEntriesReply message
* @return
*/
- protected abstract RaftState handleAppendEntriesReply(ActorRef sender,
+ protected abstract RaftActorBehavior handleAppendEntriesReply(ActorRef sender,
AppendEntriesReply appendEntriesReply);
/**
* @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;
sender.tell(new RequestVoteReply(currentTerm(), grantVote), actor());
- return state();
+ return this;
}
/**
* @param requestVoteReply The RequestVoteReply message
* @return
*/
- protected abstract RaftState handleRequestVoteReply(ActorRef sender,
+ protected abstract RaftActorBehavior handleRequestVoteReply(ActorRef sender,
RequestVoteReply requestVoteReply);
/**
} 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
}
@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) {
} 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;
+ }
}
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.
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()) {
}
if (voteCount >= votesRequired) {
- return RaftState.Leader;
+ return switchBehavior(new Leader(context));
}
- return state();
+ return this;
}
@Override public RaftState state() {
}
@Override
- public RaftState handleMessage(ActorRef sender, Object originalMessage) {
+ public RaftActorBehavior handleMessage(ActorRef sender, Object originalMessage) {
Object message = fromSerializableMessage(originalMessage);
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));
}
}
// 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);
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
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;
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) {
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()
);
}
if(LOG.isDebugEnabled()) {
LOG.debug(
- "Removing entries from log starting at "
- + matchEntry.getIndex()
+ "Removing entries from log starting at {}", matchEntry.getIndex()
);
}
}
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())
);
}
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());
}
}
if (prevCommitIndex != context.getCommitIndex()) {
if(LOG.isDebugEnabled()) {
- LOG.debug("Commit index set to " + context.getCommitIndex());
+ LOG.debug("Commit index set to {}", context.getCommitIndex());
}
}
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);
}
if (message instanceof ElectionTimeout) {
- return RaftState.Candidate;
+ return switchBehavior(new Candidate(context));
} else if (message instanceof InstallSnapshot) {
InstallSnapshot installSnapshot = (InstallSnapshot) message;
// 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<ReplicatedLogEntry>(),
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());
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;
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) {
}
if(LOG.isDebugEnabled()) {
- LOG.debug("Election:Leader has following peers:" + followers);
+ LOG.debug("Election:Leader has following peers: {}", followers);
}
if (followers.size() > 0) {
}
- @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()) {
if(followerLogInformation == null){
LOG.error("Unknown follower {}", followerId);
- return state();
+ return this;
}
if (appendEntriesReply.isSuccess()) {
applyLogToStateMachine(context.getCommitIndex());
}
- return state();
+ return this;
}
protected ClientRequestTracker removeClientRequestTracker(long logIndex) {
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);
// 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) {
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
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.");
}
}
return nextChunk;
}
- private RaftState sendHeartBeat() {
+ private void sendHeartBeat() {
if (followers.size() > 0) {
sendAppendEntries();
}
- return state();
}
private void stopHeartBeat() {
* 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
return true;
}
}.from(raftActor.path().toString())
- .message("Switching from state Candidate to Leader")
+ .message("Switching from behavior Candidate to Leader")
.occurrences(1).exec();
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;
import java.util.List;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
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<Boolean>(duration("1 seconds"),
}
// 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());
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 =
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<Boolean>(duration("1 seconds"),
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(
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;
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
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
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);
}
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);
}
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;
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
AppendEntries appendEntries =
new AppendEntries(2, "leader-1", 100, 1, entries, 101);
- RaftState raftState =
+ RaftActorBehavior raftBehavior =
createBehavior(context).handleMessage(getRef(), appendEntries);
assertEquals(101L, context.getLastApplied());
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<Boolean>(duration("1 seconds"),
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));
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
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;
// 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);
}};
}
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,
));
// State should not change
- assertEquals(RaftState.Leader, raftState);
+ assertTrue(raftBehavior instanceof Leader);
final String out =
new ExpectMsg<String>(duration("1 seconds"), "match hint") {
.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());
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>(Boolean.class, duration("2 seconds")) {
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 =
//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);
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(), //
+++ /dev/null
-/*
- *
- * 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.
- * <p/>
- * 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 <T>
- */
-public abstract class NodeToNormalizedNodeBuilder<T extends PathArgument>
- implements Identifiable<T> {
-
- 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<QName> 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<T extends PathArgument>
- extends NodeToNormalizedNodeBuilder<T> {
-
- 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<NodeIdentifier> {
-
- 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<NodeWithValue> {
-
- 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<T extends PathArgument>
- extends NodeToNormalizedNodeBuilder<T> {
-
- 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<NodeToNormalizedNodeBuilder<?>> 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<Node> 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<T extends PathArgument>
- extends NodeToNormalizationNodeOperation<T> {
-
- private final DataNodeContainer schema;
- private final Map<QName, NodeToNormalizedNodeBuilder<?>> byQName;
- private final Map<PathArgument, NodeToNormalizedNodeBuilder<?>> 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<NodeIdentifierWithPredicates> {
-
- private final List<QName> 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<NodeIdentifierWithPredicates, MapEntryNode>
- builder =
- Builders.mapEntryBuilder().withNodeIdentifier(
- (NodeIdentifierWithPredicates) currentArg);
- for (Entry<QName, Object> 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<NodeIdentifier> {
-
- 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<T extends PathArgument>
- extends NodeToNormalizationNodeOperation<T> {
-
- protected MixinNormalizationOp(final T identifier) {
- super(identifier);
- }
-
- @Override
- public final boolean isMixin() {
- return true;
- }
-
- }
-
-
- private static final class LeafListMixinNormalization extends
- MixinNormalizationOp<NodeIdentifier> {
-
- 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<AugmentationIdentifier> {
-
- private final Map<QName, NodeToNormalizedNodeBuilder<?>> byQName;
- private final Map<PathArgument, NodeToNormalizedNodeBuilder<?>> byArg;
-
- public AugmentationNormalization(final AugmentationSchema augmentation,
- final DataNodeContainer schema) {
- super(augmentationIdentifierFrom(augmentation));
-
- ImmutableMap.Builder<QName, NodeToNormalizedNodeBuilder<?>>
- byQNameBuilder =
- ImmutableMap.builder();
- ImmutableMap.Builder<PathArgument, NodeToNormalizedNodeBuilder<?>>
- 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<QName> 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<NodeIdentifier> {
-
- private final ListItemNormalization innerNode;
-
- public ListMixinNormalization(final ListSchemaNode list) {
- super(new NodeIdentifier(list.getQName()));
- this.innerNode =
- new ListItemNormalization(new NodeIdentifierWithPredicates(
- list.getQName(), Collections.<QName, Object>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<NodeIdentifier> {
-
- private final ImmutableMap<QName, NodeToNormalizedNodeBuilder<?>>
- byQName;
- private final ImmutableMap<PathArgument, NodeToNormalizedNodeBuilder<?>>
- byArg;
-
- protected ChoiceNodeNormalization(
- final org.opendaylight.yangtools.yang.model.api.ChoiceNode schema) {
- super(new NodeIdentifier(schema.getQName()));
- ImmutableMap.Builder<QName, NodeToNormalizedNodeBuilder<?>>
- byQNameBuilder =
- ImmutableMap.builder();
- ImmutableMap.Builder<PathArgument, NodeToNormalizedNodeBuilder<?>>
- 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<org.opendaylight.yangtools.yang.model.api.ChoiceNode>
- 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<org.opendaylight.yangtools.yang.model.api.ChoiceNode> 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<QName> 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);
-
-}
+++ /dev/null
-/*
- *
- * 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<Object, ? extends TypeDefinition<?>> 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;
- }
-}
+++ /dev/null
-/*
- *
- * 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
- * <p/>
- * {@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<DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>>
- 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<? extends NormalizedNode<?, ?>> 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<QName, String> attributes = leafSetEntryNode.getAttributes();
- if (!attributes.isEmpty()) {
- NormalizedNodeMessages.Attribute.Builder builder = null;
- for (Map.Entry<QName, String> 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<QName, String> attributes = leafNode.getAttributes();
- if (!attributes.isEmpty()) {
- NormalizedNodeMessages.Attribute.Builder builder = null;
- for (Map.Entry<QName, String> 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;
- }
-}
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);
--- /dev/null
+/*
+ *
+ * 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;
+
+}
--- /dev/null
+/*
+ *
+ * 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<Integer, String> 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<YangInstanceIdentifier.AugmentationIdentifier, AugmentationNode> 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<YangInstanceIdentifier.NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder
+ = Builders.mapEntryBuilder().withNodeIdentifier(nodeIdentifier);
+
+ mapEntryBuilder = (DataContainerNodeAttrBuilder<YangInstanceIdentifier.NodeIdentifierWithPredicates,
+ MapEntryNode>)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<MapEntryNode, MapNode> mapBuilder = Builders.mapBuilder().withNodeIdentifier(identifier);
+ mapBuilder = addMapNodeChildren(mapBuilder);
+ return mapBuilder.build();
+
+ case NodeTypes.CHOICE_NODE :
+ LOG.debug("Read choice node");
+ DataContainerNodeBuilder<YangInstanceIdentifier.NodeIdentifier, ChoiceNode> choiceBuilder =
+ Builders.choiceBuilder().withNodeIdentifier(identifier);
+ choiceBuilder = addDataContainerChildren(choiceBuilder);
+ return choiceBuilder.build();
+
+ case NodeTypes.ORDERED_MAP_NODE :
+ LOG.debug("Reading ordered map node");
+ CollectionNodeBuilder<MapEntryNode, OrderedMapNode> orderedMapBuilder =
+ Builders.orderedMapBuilder().withNodeIdentifier(identifier);
+ orderedMapBuilder = addMapNodeChildren(orderedMapBuilder);
+ return orderedMapBuilder.build();
+
+ case NodeTypes.UNKEYED_LIST :
+ LOG.debug("Read unkeyed list node");
+ CollectionNodeBuilder<UnkeyedListEntryNode, UnkeyedListNode> unkeyedListBuilder =
+ Builders.unkeyedListBuilder().withNodeIdentifier(identifier);
+ unkeyedListBuilder = addUnkeyedListChildren(unkeyedListBuilder);
+ return unkeyedListBuilder.build();
+
+ case NodeTypes.UNKEYED_LIST_ITEM :
+ LOG.debug("Read unkeyed list item node");
+ DataContainerNodeAttrBuilder<YangInstanceIdentifier.NodeIdentifier, UnkeyedListEntryNode> unkeyedListEntryBuilder
+ = Builders.unkeyedListEntryBuilder().withNodeIdentifier(identifier);
+
+ unkeyedListEntryBuilder = (DataContainerNodeAttrBuilder<YangInstanceIdentifier.NodeIdentifier, UnkeyedListEntryNode>)
+ addDataContainerChildren(unkeyedListEntryBuilder);
+ return unkeyedListEntryBuilder.build();
+
+ case NodeTypes.CONTAINER_NODE :
+ LOG.debug("Read container node");
+ DataContainerNodeAttrBuilder<YangInstanceIdentifier.NodeIdentifier, ContainerNode> containerBuilder =
+ Builders.containerBuilder().withNodeIdentifier(identifier);
+
+ containerBuilder = (DataContainerNodeAttrBuilder<YangInstanceIdentifier.NodeIdentifier, ContainerNode>)
+ addDataContainerChildren(containerBuilder);
+ return containerBuilder.build();
+
+ case NodeTypes.LEAF_SET :
+ LOG.debug("Read leaf set node");
+ ListNodeBuilder<Object, LeafSetEntryNode<Object>> 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<QName> readQNameSet() throws IOException{
+ // Read the children count
+ int count = reader.readInt();
+ Set<QName> children = new HashSet<>(count);
+ for(int i = 0; i<count; i++) {
+ children.add(readQName());
+ }
+ return children;
+ }
+
+ private Map<QName, Object> readKeyValueMap() throws IOException {
+ int count = reader.readInt();
+ Map<QName, Object> keyValueMap = new HashMap<>(count);
+
+ for(int i = 0; i<count; i++) {
+ keyValueMap.put(readQName(), readObject());
+ }
+
+ return keyValueMap;
+ }
+
+ private Object readObject() throws IOException {
+ byte objectType = reader.readByte();
+ switch(objectType) {
+ case ValueTypes.BITS_TYPE:
+ return readObjSet();
+
+ case ValueTypes.BOOL_TYPE :
+ return reader.readBoolean();
+
+ case ValueTypes.BYTE_TYPE :
+ return reader.readByte();
+
+ case ValueTypes.INT_TYPE :
+ return reader.readInt();
+
+ case ValueTypes.LONG_TYPE :
+ return reader.readLong();
+
+ case ValueTypes.QNAME_TYPE :
+ return readQName();
+
+ case ValueTypes.SHORT_TYPE :
+ return reader.readShort();
+
+ case ValueTypes.STRING_TYPE :
+ return reader.readUTF();
+
+ case ValueTypes.BIG_DECIMAL_TYPE :
+ return new BigDecimal(reader.readUTF());
+
+ case ValueTypes.BIG_INTEGER_TYPE :
+ return new BigInteger(reader.readUTF());
+
+ case ValueTypes.YANG_IDENTIFIER_TYPE :
+ int size = reader.readInt();
+
+ List<YangInstanceIdentifier.PathArgument> pathArguments = new ArrayList<>(size);
+
+ for(int i=0; i<size; i++) {
+ pathArguments.add(readPathArgument());
+ }
+ return YangInstanceIdentifier.create(pathArguments);
+
+ default :
+ return null;
+ }
+ }
+
+ private Set<String> readObjSet() throws IOException {
+ int count = reader.readInt();
+ Set<String> children = new HashSet<>(count);
+ for(int i = 0; i<count; i++) {
+ children.add(readCodedString());
+ }
+ return children;
+ }
+
+ private YangInstanceIdentifier.PathArgument readPathArgument() throws IOException {
+ // read Type
+ int type = reader.readByte();
+
+ switch(type) {
+
+ case PathArgumentTypes.AUGMENTATION_IDENTIFIER :
+ return new YangInstanceIdentifier.AugmentationIdentifier(readQNameSet());
+
+ case PathArgumentTypes.NODE_IDENTIFIER :
+ return new YangInstanceIdentifier.NodeIdentifier(readQName());
+
+ case PathArgumentTypes.NODE_IDENTIFIER_WITH_PREDICATES :
+ return new YangInstanceIdentifier.NodeIdentifierWithPredicates(readQName(), readKeyValueMap());
+
+ case PathArgumentTypes.NODE_IDENTIFIER_WITH_VALUE :
+ return new YangInstanceIdentifier.NodeWithValue(readQName(), readObject());
+
+ default :
+ return null;
+ }
+ }
+
+ private ListNodeBuilder<Object, LeafSetEntryNode<Object>> addLeafSetChildren(ListNodeBuilder<Object,
+ LeafSetEntryNode<Object>> builder)
+ throws IOException {
+
+ LOG.debug("Reading children of leaf set");
+ LeafSetEntryNode<Object> child = (LeafSetEntryNode<Object>)readNormalizedNode();
+
+ while(child != null) {
+ builder.withChild(child);
+ child = (LeafSetEntryNode<Object>)readNormalizedNode();
+ }
+ return builder;
+ }
+
+ private CollectionNodeBuilder<UnkeyedListEntryNode, UnkeyedListNode> addUnkeyedListChildren(
+ CollectionNodeBuilder<UnkeyedListEntryNode, UnkeyedListNode> 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<? extends YangInstanceIdentifier.PathArgument, ?> child =
+ (DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>) readNormalizedNode();
+
+ while(child != null) {
+ builder.withChild(child);
+ child =
+ (DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>) 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();
+ }
+
+}
--- /dev/null
+/*
+ *
+ * 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<String, Integer> 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<YangInstanceIdentifier.PathArgument> 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<QName, Object> keyValueMap) throws IOException {
+ if(keyValueMap != null && !keyValueMap.isEmpty()) {
+ writer.writeInt(keyValueMap.size());
+ Set<QName> qNameSet = keyValueMap.keySet();
+
+ for(QName qName : qNameSet) {
+ writeQName(qName);
+ writeObject(keyValueMap.get(qName));
+ }
+ } else {
+ writer.writeInt(0);
+ }
+ }
+
+ private void writeQNameSet(Set<QName> 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;
+ }
+ }
+}
--- /dev/null
+/*
+ *
+ * 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;
+}
--- /dev/null
+
+/*
+ * 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
+ *
+ * <h3>Writing Event Stream</h3>
+ *
+ * <ul>
+ * <li><code>container</code> - 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.
+ *
+ * <li><code>list</code> - YANG list statement has two representation in event
+ * stream - unkeyed list and map. Unkeyed list is YANG list which did not
+ * specify key.</li>
+ *
+ * <ul>
+ * <li><code>Map</code> - 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()}.</li>
+ *
+ * <li><code>UnkeyedList</code> - 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()}.</li>
+ * </ul>
+ *
+ * <li><code>leaf</code> - Leaf node event is emitted using
+ * {@link #leafNode(YangInstanceIdentifier.NodeIdentifier, Object)}.
+ * {@link #endNode()} MUST NOT BE emitted for
+ * leaf node.</li>
+ *
+ * <li><code>leaf-list</code> - 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).
+ *
+ * <li><code>anyxml - Anyxml node event is emitted using
+ * {@link #leafNode(YangInstanceIdentifier.NodeIdentifier, Object)}. {@link #endNode()} MUST NOT BE emitted
+ * for anyxml node.</code></li>
+ *
+ *
+ * <li><code>choice</code> Choice node event is emmited by
+ * {@link #startChoiceNode(YangInstanceIdentifier.NodeIdentifier, int)} event and
+ * finished by invoking {@link #endNode()}
+ * <li>
+ * <code>augment</code> - Represents augmentation, augmentation node is started
+ * by invoking {@link #startAugmentationNode(YangInstanceIdentifier.AugmentationIdentifier)} and
+ * finished by invoking {@link #endNode()}.</li>
+ *
+ * </ul>
+ *
+ * <h3>Implementation notes</h3>
+ *
+ * <p>
+ * 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;
+}
--- /dev/null
+/*
+ * 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<Class<?>, Byte> CLASS_TO_ENUM_MAP =
+ ImmutableMap.<Class<?>, 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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<Class, Byte> 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());
+ }
+}
+++ /dev/null
-/*
- *
- * 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;
-
-/*
- *
- * <code>EncoderDecoderUtil</code> 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<DataSchemaNode> children,
- String name) {
- List<DataNodeContainer> 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<Element> 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<Element> 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<Element> 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);
- }
-
-
-
-}
--- /dev/null
+/*
+ *
+ * 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());
+
+ }
+
+}
--- /dev/null
+/*
+ * 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<? extends NormalizedNode<?, ?>> 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;
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
+++ /dev/null
-/*
- *
- * 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<DataSchemaNode> children, final String name) {
- List<DataNodeContainer> 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<QName> qn = Sets.newHashSet();
-
- for (String childName : childNames) {
- qn.add(getNodeIdentifier(childName).getNodeType());
- }
-
- return new YangInstanceIdentifier.AugmentationIdentifier(qn);
- }
-
-
- public static ContainerNode augmentChoiceExpectedNode() {
-
- DataContainerNodeBuilder<YangInstanceIdentifier.NodeIdentifier, ContainerNode> 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<InputStream> al = new ArrayList<InputStream>();
- al.add(stream);
- Set<Module> 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<Element> 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<YangInstanceIdentifier.NodeIdentifier, ContainerNode> b =
- Builders.containerBuilder();
- b.withNodeIdentifier(getNodeIdentifier("container"));
-
- CollectionNodeBuilder<MapEntryNode, MapNode> listBuilder =
- Builders.mapBuilder().withNodeIdentifier(getNodeIdentifier("list"));
-
- Map<QName, Object> predicates = Maps.newHashMap();
- predicates.put(getNodeIdentifier("uint32InList").getNodeType(), 3L);
-
- DataContainerNodeBuilder<YangInstanceIdentifier.NodeIdentifierWithPredicates, MapEntryNode> list1Builder =
- Builders.mapEntryBuilder().withNodeIdentifier(
- new YangInstanceIdentifier.NodeIdentifierWithPredicates(
- getNodeIdentifier("list").getNodeType(), predicates));
- NormalizedNodeBuilder<YangInstanceIdentifier.NodeIdentifier, Object, LeafNode<Object>> uint32InListBuilder =
- Builders.leafBuilder().withNodeIdentifier(
- getNodeIdentifier("uint32InList"));
-
- list1Builder.withChild(uint32InListBuilder.withValue(3L).build());
-
- listBuilder.withChild(list1Builder.build());
- b.withChild(listBuilder.build());
-
- NormalizedNodeBuilder<YangInstanceIdentifier.NodeIdentifier, Object, LeafNode<Object>> booleanBuilder =
- Builders.leafBuilder().withNodeIdentifier(getNodeIdentifier("boolean"));
- booleanBuilder.withValue(false);
- b.withChild(booleanBuilder.build());
-
- ListNodeBuilder<Object, LeafSetEntryNode<Object>> leafListBuilder =
- Builders.leafSetBuilder().withNodeIdentifier(
- getNodeIdentifier("leafList"));
-
- NormalizedNodeBuilder<YangInstanceIdentifier.NodeWithValue, Object, LeafSetEntryNode<Object>> 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<Element> 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);
-
- }
-
-}
public interface ClusterWrapper {
void subscribeToMemberEvents(ActorRef actorRef);
String getCurrentMemberName();
+ String getSelfAddress();
}
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");
);
currentMemberName = (String) cluster.getSelfRoles().toArray()[0];
+ selfAddress = cluster.selfAddress().toString();
}
public String getCurrentMemberName() {
return currentMemberName;
}
+
+ public String getSelfAddress() {
+ return selfAddress;
+ }
}
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<YangInstanceIdentifier, NormalizedNode<?, ?>> listener;
private boolean notificationsEnabled = false;
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){
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<YangInstanceIdentifier, NormalizedNode<?, ?>>
- change = reply.getChange();
+ AsyncDataChangeEvent<YangInstanceIdentifier, NormalizedNode<?, ?>> 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
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
* The ListenerRegistrationProxy talks to a remote ListenerRegistration actor.
* </p>
*/
+@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<YangInstanceIdentifier, NormalizedNode<?, ?>> listener;
+ private ActorRef dataChangeListenerActor;
+ private final String shardName;
+ private final ActorContext actorContext;
private boolean closed = false;
public <L extends AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>
- 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 <L extends AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>
- DataChangeListenerRegistrationProxy(
- L listener, ActorRef dataChangeListenerActor) {
- this(null, listener, dataChangeListenerActor);
+ @VisibleForTesting
+ ActorSelection getListenerRegistrationActor() {
+ return listenerRegistrationActor;
+ }
+
+ @VisibleForTesting
+ ActorRef getDataChangeListenerActor() {
+ return dataChangeListenerActor;
}
@Override
return listener;
}
- public void setListenerRegistrationActor(ActorSelection listenerRegistrationActor) {
+ private void setListenerRegistrationActor(ActorSelection listenerRegistrationActor) {
+ if(listenerRegistrationActor == null) {
+ return;
+ }
+
boolean sendCloseMessage = false;
synchronized(this) {
if(closed) {
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<ActorRef> findFuture = actorContext.findLocalShardAsync(shardName, REGISTER_TIMEOUT);
+ findFuture.onComplete(new OnComplete<ActorRef>() {
+ @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<Object> future = actorContext.executeOperationAsync(shard,
+ new RegisterChangeListener(path, dataChangeListenerActor.path(), scope),
+ REGISTER_TIMEOUT);
+
+ future.onComplete(new OnComplete<Object>(){
+ @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
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;
+ }
}
}
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;
import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import scala.concurrent.Future;
/**
*
String shardName = ShardStrategyFactory.getStrategy(path).findShard(path);
- Optional<ActorRef> 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
+++ /dev/null
-/*
- * 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);
- }
-
- }
-}
-
+++ /dev/null
-/*
- * 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
- *
- * <p>
- *
- * The NoOpDataChangeListenerRegistration as it's name suggests does
- * nothing when an operation is invoked on it
- */
-public class NoOpDataChangeListenerRegistration
- implements ListenerRegistration {
-
- private final AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>
- listener;
-
- public <L extends AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>> NoOpDataChangeListenerRegistration(
- AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>> listener) {
-
- this.listener = listener;
- }
-
- @Override
- public AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>> getInstance() {
- return listener;
- }
-
- @Override public void close() {
-
- }
-}
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;
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 <br/>
private final ShardStats shardMBean;
- private final List<ActorSelection> dataChangeListeners = new ArrayList<>();
+ private final List<ActorSelection> dataChangeListeners = Lists.newArrayList();
+
+ private final List<DelayedListenerRegistration> delayedListenerRegistrations =
+ Lists.newArrayList();
private final DatastoreContext datastoreContext;
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);
}
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) {
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<AsyncDataChangeListener<YangInstanceIdentifier,
+ NormalizedNode<?, ?>>> 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<AsyncDataChangeListener<YangInstanceIdentifier,
+ NormalizedNode<?, ?>>> 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<YangInstanceIdentifier, NormalizedNode<?, ?>>
- listener = new DataChangeListenerProxy(schemaContext, dataChangeListenerPath);
+ AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>> listener =
+ new DataChangeListenerProxy(schemaContext, dataChangeListenerPath);
- ListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>
- 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(){
//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
}
}
- @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<String, DOMStoreTransactionChain> entry : transactionChains.entrySet()){
if(LOG.isDebugEnabled()) {
LOG.debug(
ShardStats getShardMBean() {
return shardMBean;
}
+
+ private static class DelayedListenerRegistration implements
+ ListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>> {
+
+ private volatile boolean closed;
+
+ private final RegisterChangeListener registerChangeListener;
+
+ private volatile ListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier,
+ NormalizedNode<?, ?>>> delegate;
+
+ DelayedListenerRegistration(RegisterChangeListener registerChangeListener) {
+ this.registerChangeListener = registerChangeListener;
+ }
+
+ void setDelegate( ListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier,
+ NormalizedNode<?, ?>>> registration) {
+ this.delegate = registration;
+ }
+
+ boolean isClosed() {
+ return closed;
+ }
+
+ RegisterChangeListener getRegisterChangeListener() {
+ return registerChangeListener;
+ }
+
+ @Override
+ public AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>> getInstance() {
+ return delegate != null ? delegate.getInstance() : null;
+ }
+
+ @Override
+ public void close() {
+ closed = true;
+ if(delegate != null) {
+ delegate.close();
+ }
+ }
+ }
}
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;
LOG.debug("Initializing shard [{}]", shardName);
ShardInformation shardInformation = localShards.get(shardName);
if (shardInformation != null) {
- shardInformation.setShardInitialized(true);
+ shardInformation.setActorInitialized();
}
}
return;
}
- sendResponse(shardInformation, new Supplier<Object>() {
+ sendResponse(shardInformation, message.isWaitUntilInitialized(), new Supplier<Object>() {
@Override
public Object get() {
return new LocalShardFound(shardInformation.getActor());
});
}
- private void sendResponse(ShardInformation shardInformation, Supplier<Object> messageSupplier) {
- if (shardInformation.getActor() == null || !shardInformation.isShardInitialized()) {
- getSender().tell(new ActorNotInitialized(), getSelf());
+ private void sendResponse(ShardInformation shardInformation, boolean waitUntilInitialized,
+ final Supplier<Object> 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;
}
// 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<Object>() {
+ sendResponse(info, message.isWaitUntilInitialized(), new Supplier<Object>() {
@Override
public Object get() {
return new PrimaryFound(info.getActorPath().toString()).toSerializable();
private ActorRef actor;
private ActorPath actorPath;
private final Map<ShardIdentifier, String> 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<Runnable> runnablesOnInitialized = Lists.newArrayList();
private ShardInformation(String shardName, ShardIdentifier shardId,
Map<ShardIdentifier, String> peerAddresses) {
}
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);
}
}
}
static class SchemaContextModules implements Serializable {
- private static final long serialVersionUID = 1L;
-
private final Set<String> modules;
SchemaContextModules(Set<String> modules){
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;
@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);
}
@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);
}
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) {
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<Optional<NormalizedNode<?, ?>>, ReadFailedException> future =
transaction.read(path);
+
future.addListener(new Runnable() {
@Override
public void run() {
try {
Optional<NormalizedNode<?, ?>> 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);
}, 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());
}
@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(
}
}
- 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()) {
}
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()) {
}
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());
}
for(ActorSelection actor : remoteTransactionActors) {
LOG.trace("Sending CloseTransaction to {}", actor);
actorContext.sendOperationAsync(actor,
- new CloseTransaction().toSerializable());
+ new CloseTransaction().toSerializable());
}
}
}
}
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);
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 {
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() {
}
// Send the ReadyTransaction message to the Tx actor.
+ ReadyTransaction readyTransaction = new ReadyTransaction();
final Future<Object> 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
// 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()));
}
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
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
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
Future<Iterable<Object>> combinedFutures = akka.dispatch.Futures.sequence(
Lists.newArrayList(recordedOperationFutures),
actorContext.getActorSystem().dispatcher());
+
OnComplete<Iterable<Object>> onComplete = new OnComplete<Iterable<Object>>() {
@Override
public void onComplete(Throwable failure, Iterable<Object> notUsed)
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.<NormalizedNode<?, ?>>absent());
- } else {
- returnFuture.set(Optional.<NormalizedNode<?, ?>>of(
- reply.getNormalizedNode()));
- }
+
+ if (readResponse instanceof ReadDataReply) {
+ ReadDataReply reply = (ReadDataReply) readResponse;
+ returnFuture.set(Optional.<NormalizedNode<?, ?>>fromNullable(reply.getNormalizedNode()));
+
+ } else if (readResponse.getClass().equals(ReadDataReply.SERIALIZABLE_CLASS)) {
+ ReadDataReply reply = ReadDataReply.fromSerializable(schemaContext, path, readResponse);
+ returnFuture.set(Optional.<NormalizedNode<?, ?>>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<Object> readFuture = actorContext.executeOperationAsync(getActor(),
- new ReadData(path).toSerializable());
+ isTxActorLocal ? readData : readData.toSerializable());
+
readFuture.onComplete(onComplete, actorContext.getActorSystem().dispatcher());
}
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));
}
};
+ DataExists dataExists = new DataExists(path);
Future<Object> future = actorContext.executeOperationAsync(getActor(),
- new DataExists(path).toSerializable());
+ isTxActorLocal ? dataExists : dataExists.toSerializable());
+
future.onComplete(onComplete, actorContext.getActorSystem().dispatcher());
}
}
--- /dev/null
+/*
+ * 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);
+ }
+}
*/
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;
+ }
}
*
*/
public class FindPrimary implements SerializableMessage{
- public static final Class SERIALIZABLE_CLASS = FindPrimary.class;
+ public static final Class<FindPrimary> 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;
+ }
}
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() {
public Modification getModification() {
return modification;
}
+
+ public boolean isReturnSerialized() {
+ return returnSerialized;
+ }
}
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;
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;
/**
}
/**
- * 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<ActorRef> 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;
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<ActorRef> findLocalShardAsync( final String shardName, Timeout timeout) {
+ Future<Object> future = executeOperationAsync(shardManager,
+ new FindLocalShard(shardName, true), timeout);
+
+ return future.map(new Mapper<Object, ActorRef>() {
+ @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);
actorSystem.shutdown();
}
+ public ClusterWrapper getClusterWrapper() {
+ return clusterWrapper;
+ }
+
public String getCurrentMemberName(){
return clusterWrapper.getCurrentMemberName();
}
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);
+ }
}
+/*
+ * 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<YangInstanceIdentifier, NormalizedNode<?, ?>> 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<YangInstanceIdentifier, NormalizedNode<?, ?>> {
+ // The listener registration actor should get a Close message
+ expectMsgClass(timeout, CloseDataChangeListenerRegistration.SERIALIZABLE_CLASS);
- @Override public void onDataChanged(
- AsyncDataChangeEvent<YangInstanceIdentifier, NormalizedNode<?, ?>> 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<Object> listMessages = (List<Object>) 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<Future<Object>> answer = new Answer<Future<Object>>() {
+ @Override
+ public Future<Object> 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<Object> listMessages = (List<Object>) messages;
+ expectMsgClass(duration("5 seconds"), CloseDataChangeListenerRegistration.SERIALIZABLE_CLASS);
- assertEquals(0, listMessages.size());
+ Assert.assertEquals("getListenerRegistrationActor", null,
+ proxy.getListenerRegistrationActor());
+ }};
}
}
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;
}};
}
+ @Test
+ public void testChangeListenerRegistration() throws Exception{
+ new IntegrationTestKit(getSystem()) {{
+ DistributedDataStore dataStore =
+ setupDistributedDataStore("testChangeListenerRegistration", "test-1");
+
+ MockDataChangeListener listener = new MockDataChangeListener(3);
+
+ ListenerRegistration<MockDataChangeListener>
+ 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) {
+++ /dev/null
-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<YangInstanceIdentifier, NormalizedNode<?, ?>>() {
- @Override
- public void onDataChanged(AsyncDataChangeEvent<YangInstanceIdentifier, NormalizedNode<?, ?>> 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);
- }
-}
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;
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;
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());
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<Object> 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()) {{
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);
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);
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<Object> 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()) {{
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));
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);
}};
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;
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;
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;
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;
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;
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;
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;
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() {
}
private Props newShardProps() {
- return Shard.props(IDENTIFIER, Collections.<ShardIdentifier,String>emptyMap(),
+ return Shard.props(shardID, Collections.<ShardIdentifier,String>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> 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<Shard> creator = new Creator<Shard>() {
+ boolean firstElectionTimeout = true;
+
+ @Override
+ public Shard create() throws Exception {
+ return new Shard(shardID, Collections.<ShardIdentifier,String>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> 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"),
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());
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.<ShardIdentifier, String>singletonMap(shardID, null),
+ dataStoreContext, SCHEMA_CONTEXT);
+ }
- Props props = Shard.props(identifier,
- Collections.<ShardIdentifier, String>singletonMap(identifier, null),
- dataStoreContext, SCHEMA_CONTEXT);
- final ActorRef subject = getSystem().actorOf(props, "testPeerAddressResolved");
+ Map<String, String> 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> shard = TestActorRef.create(getSystem(),
+ Props.create(new DelegatingShardCreator(new Creator<Shard>() {
+ @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<Shard> ref = TestActorRef.create(getSystem(), newShardProps());
+ TestActorRef<Shard> 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);
encode.getNormalizedNode().toByteString().toByteArray(),
Collections.<ReplicatedLogEntry>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> shard = TestActorRef.create(getSystem(), newShardProps());
+ TestActorRef<Shard> shard = TestActorRef.create(getSystem(), newShardProps(), "testApplyState");
NormalizedNode<?, ?> node = ImmutableNodes.containerNode(TestModel.TEST_QNAME);
NormalizedNode<?,?> actual = readStore(shard, TestModel.TEST_PATH);
assertEquals("Applied state", node, actual);
+
+ shard.tell(PoisonPill.getInstance(), ActorRef.noSender());
}
@SuppressWarnings("serial")
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(),
// 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))));
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.
Creator<Shard> creator = new Creator<Shard>() {
@Override
public Shard create() throws Exception {
- return new Shard(IDENTIFIER, Collections.<ShardIdentifier,String>emptyMap(),
+ return new Shard(shardID, Collections.<ShardIdentifier,String>emptyMap(),
dataStoreContext, SCHEMA_CONTEXT) {
@Override
protected void onRecoveryComplete() {
shard.underlyingActor().getShardMBean().getCommitIndex());
assertEquals("Last applied", nListEntries,
shard.underlyingActor().getShardMBean().getLastApplied());
+
+ shard.tell(PoisonPill.getInstance(), ActorRef.noSender());
}
private CompositeModificationPayload newPayload(Modification... mods) {
System.setProperty("shard.persistent", "true");
new ShardTestKit(getSystem()) {{
final TestActorRef<Shard> shard = TestActorRef.create(getSystem(),
- newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName());
+ newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()),
+ "testConcurrentThreePhaseCommits");
waitUntilLeader(shard);
// 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());
// 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
@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 {
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());
}};
}
public void testCommitPhaseFailure() throws Throwable {
new ShardTestKit(getSystem()) {{
final TestActorRef<Shard> shard = TestActorRef.create(getSystem(),
- newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName());
+ newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()),
+ "testCommitPhaseFailure");
waitUntilLeader(shard);
// 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.
inOrder.verify(cohort1).preCommit();
inOrder.verify(cohort1).commit();
inOrder.verify(cohort2).canCommit();
+
+ shard.tell(PoisonPill.getInstance(), ActorRef.noSender());
}};
}
public void testPreCommitPhaseFailure() throws Throwable {
new ShardTestKit(getSystem()) {{
final TestActorRef<Shard> shard = TestActorRef.create(getSystem(),
- newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName());
+ newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()),
+ "testPreCommitPhaseFailure");
waitUntilLeader(shard);
// 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.
InOrder inOrder = inOrder(cohort);
inOrder.verify(cohort).canCommit();
inOrder.verify(cohort).preCommit();
+
+ shard.tell(PoisonPill.getInstance(), ActorRef.noSender());
}};
}
public void testCanCommitPhaseFailure() throws Throwable {
new ShardTestKit(getSystem()) {{
final TestActorRef<Shard> shard = TestActorRef.create(getSystem(),
- newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName());
+ newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()),
+ "testCanCommitPhaseFailure");
waitUntilLeader(shard);
// 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());
}};
}
System.setProperty("shard.persistent", "true");
new ShardTestKit(getSystem()) {{
final TestActorRef<Shard> shard = TestActorRef.create(getSystem(),
- newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName());
+ newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()),
+ "testAbortBeforeFinishCommit");
waitUntilLeader(shard);
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());
NormalizedNode<?, ?> node = readStore(shard, TestModel.TEST_PATH);
assertNotNull(TestModel.TEST_QNAME.getLocalName() + " not found", node);
+
+ shard.tell(PoisonPill.getInstance(), ActorRef.noSender());
}};
}
new ShardTestKit(getSystem()) {{
final TestActorRef<Shard> shard = TestActorRef.create(getSystem(),
- newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName());
+ newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()),
+ "testTransactionCommitTimeout");
waitUntilLeader(shard);
// 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.
NormalizedNode<?, ?> node = readStore(shard, listNodePath);
assertNotNull(listNodePath + " not found", node);
+
+ shard.tell(PoisonPill.getInstance(), ActorRef.noSender());
}};
}
new ShardTestKit(getSystem()) {{
final TestActorRef<Shard> shard = TestActorRef.create(getSystem(),
- newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName());
+ newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()),
+ "testTransactionCommitQueueCapacityExceeded");
waitUntilLeader(shard);
// 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.
shard.tell(new CanCommitTransaction(transactionID3).toSerializable(), getRef());
expectMsgClass(duration, akka.actor.Status.Failure.class);
+
+ shard.tell(PoisonPill.getInstance(), ActorRef.noSender());
}};
}
public void testCanCommitBeforeReadyFailure() throws Throwable {
new ShardTestKit(getSystem()) {{
final TestActorRef<Shard> 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());
}};
}
public void testAbortTransaction() throws Throwable {
new ShardTestKit(getSystem()) {{
final TestActorRef<Shard> shard = TestActorRef.create(getSystem(),
- newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), shardName());
+ newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()),
+ "testAbortTransaction");
waitUntilLeader(shard);
// 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.
InOrder inOrder = inOrder(cohort1, cohort2);
inOrder.verify(cohort1).canCommit();
inOrder.verify(cohort2).canCommit();
+
+ shard.tell(PoisonPill.getInstance(), ActorRef.noSender());
}};
}
Creator<Shard> creator = new Creator<Shard>() {
@Override
public Shard create() throws Exception {
- return new Shard(IDENTIFIER, Collections.<ShardIdentifier,String>emptyMap(),
+ return new Shard(shardID, Collections.<ShardIdentifier,String>emptyMap(),
dataStoreContext, SCHEMA_CONTEXT) {
@Override
public void saveSnapshot(Object snapshot) {
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());
}};
}
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;
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;
}
protected void waitUntilLeader(ActorRef shard) {
+ FiniteDuration duration = Duration.create(100, TimeUnit.MILLISECONDS);
for(int i = 0; i < 20 * 5; i++) {
- Future<Object> future = Patterns.ask(shard, new FindLeader(), new Timeout(5, TimeUnit.SECONDS));
+ Future<Object> 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);
}
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;
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;
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 {
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<String>(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());
}};
}
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<String>(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);
}};
}
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<String>(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());
}};
}
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<String>(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<? extends Modification> modificationType) {
new JavaTestKit(getSystem()) {{
- new Within(duration("3 seconds")) {
- @Override
- protected void run() {
- subject
- .tell(new ShardWriteTransaction.GetCompositedModification(),
- getRef());
-
- final CompositeModification compositeModification =
- new ExpectMsg<CompositeModification>(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());
}};
}
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<String>(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);
}};
}
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<String>(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);
}};
}
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<String>(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);
}};
}
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<String>(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<String>(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<String>(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);
}};
}
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;
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;
private SchemaContext schemaContext;
+ @Mock
+ private ClusterWrapper mockClusterWrapper;
+
String memberName = "mock-member";
@Before
doReturn(getSystem()).when(mockActorContext).getActorSystem();
doReturn(memberName).when(mockActorContext).getCurrentMemberName();
doReturn(schemaContext).when(mockActorContext).getSchemaContext();
+ doReturn(mockClusterWrapper).when(mockActorContext).getClusterWrapper();
ShardStrategyFactory.setConfiguration(configuration);
}
return argThat(matcher);
}
- private DataExists eqDataExists() {
+ private DataExists eqSerializedDataExists() {
ArgumentMatcher<DataExists> matcher = new ArgumentMatcher<DataExists>() {
@Override
public boolean matches(Object argument) {
return argThat(matcher);
}
- private ReadData eqReadData() {
+ private DataExists eqDataExists() {
+ ArgumentMatcher<DataExists> matcher = new ArgumentMatcher<DataExists>() {
+ @Override
+ public boolean matches(Object argument) {
+ return (argument instanceof DataExists) &&
+ ((DataExists)argument).getPath().equals(TestModel.TEST_PATH);
+ }
+ };
+
+ return argThat(matcher);
+ }
+
+ private ReadData eqSerializedReadData() {
ArgumentMatcher<ReadData> matcher = new ArgumentMatcher<ReadData>() {
@Override
public boolean matches(Object argument) {
return argThat(matcher);
}
- private WriteData eqWriteData(final NormalizedNode<?, ?> nodeToWrite) {
+ private ReadData eqReadData() {
+ ArgumentMatcher<ReadData> matcher = new ArgumentMatcher<ReadData>() {
+ @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<WriteData> matcher = new ArgumentMatcher<WriteData>() {
@Override
public boolean matches(Object argument) {
return argThat(matcher);
}
- private MergeData eqMergeData(final NormalizedNode<?, ?> nodeToWrite) {
+ private WriteData eqWriteData(final NormalizedNode<?, ?> nodeToWrite) {
+ ArgumentMatcher<WriteData> matcher = new ArgumentMatcher<WriteData>() {
+ @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<MergeData> matcher = new ArgumentMatcher<MergeData>() {
@Override
public boolean matches(Object argument) {
return argThat(matcher);
}
- private DeleteData eqDeleteData() {
+ private MergeData eqMergeData(final NormalizedNode<?, ?> nodeToWrite) {
+ ArgumentMatcher<MergeData> matcher = new ArgumentMatcher<MergeData>() {
+ @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<DeleteData> matcher = new ArgumentMatcher<DeleteData>() {
@Override
public boolean matches(Object argument) {
return argThat(matcher);
}
- private Future<Object> readyTxReply(String path) {
+ private DeleteData eqDeleteData() {
+ ArgumentMatcher<DeleteData> matcher = new ArgumentMatcher<DeleteData>() {
+ @Override
+ public boolean matches(Object argument) {
+ return argument instanceof DeleteData &&
+ ((DeleteData)argument).getPath().equals(TestModel.TEST_PATH);
+ }
+ };
+
+ return argThat(matcher);
+ }
+
+ private Future<Object> readySerializedTxReply(String path) {
return Futures.successful((Object)new ReadyTransactionReply(path).toSerializable());
}
- private Future<Object> readDataReply(NormalizedNode<?, ?> data) {
+ private Future<Object> readyTxReply(String path) {
+ return Futures.successful((Object)new ReadyTransactionReply(path));
+ }
+
+
+ private Future<Object> readSerializedDataReply(NormalizedNode<?, ?> data) {
return Futures.successful(new ReadDataReply(schemaContext, data).toSerializable());
}
- private Future<Object> dataExistsReply(boolean exists) {
+ private Future<ReadDataReply> readDataReply(NormalizedNode<?, ?> data) {
+ return Futures.successful(new ReadDataReply(schemaContext, data));
+ }
+
+ private Future<Object> dataExistsSerializedReply(boolean exists) {
return Futures.successful(new DataExistsReply(exists).toSerializable());
}
- private Future<Object> writeDataReply() {
+ private Future<DataExistsReply> dataExistsReply(boolean exists) {
+ return Futures.successful(new DataExistsReply(exists));
+ }
+
+ private Future<Object> writeSerializedDataReply() {
return Futures.successful(new WriteDataReply().toSerializable());
}
- private Future<Object> mergeDataReply() {
+ private Future<WriteDataReply> writeDataReply() {
+ return Futures.successful(new WriteDataReply());
+ }
+
+ private Future<Object> mergeSerializedDataReply() {
return Futures.successful(new MergeDataReply().toSerializable());
}
- private Future<Object> deleteDataReply() {
+ private Future<MergeDataReply> mergeDataReply() {
+ return Futures.successful(new MergeDataReply());
+ }
+
+ private Future<Object> deleteSerializedDataReply() {
return Futures.successful(new DeleteDataReply().toSerializable());
}
+ private Future<DeleteDataReply> deleteDataReply() {
+ return Futures.successful(new DeleteDataReply());
+ }
+
private ActorSelection actorSelection(ActorRef actorRef) {
return getSystem().actorSelection(actorRef.path());
}
.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;
}
@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<NormalizedNode<?, ?>> readOptional = transactionProxy.read(
TestModel.TEST_PATH).get(5, TimeUnit.SECONDS);
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);
@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());
@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());
@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);
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);
@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();
@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());
@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());
@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);
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);
@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);
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);
@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);
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);
@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);
transactionProxy.delete(TestModel.TEST_PATH);
verify(mockActorContext).executeOperationAsync(
- eq(actorSelection(actorRef)), eqDeleteData());
+ eq(actorSelection(actorRef)), eqSerializedDeleteData());
verifyRecordingOperationFutures(transactionProxy.getRecordedOperationFutures(),
DeleteDataReply.SERIALIZABLE_CLASS);
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 {
@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,
@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);
@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)),
@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)),
@Test
public void testGetIdentifier() {
- setupActorContextWithInitialCreateTransaction(READ_ONLY);
+ setupActorContextWithInitialCreateTransaction(getSystem(), READ_ONLY);
TransactionProxy transactionProxy = new TransactionProxy(mockActorContext,
TransactionProxy.TransactionType.READ_ONLY);
@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);
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<NormalizedNode<?, ?>> 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()));
+ }
}
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;
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;
@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<ActorRef> out = actorContext.findLocalShard("default");
- assertTrue(!out.isPresent());
- expectNoMsg();
- }
- };
+ Optional<ActorRef> out = actorContext.findLocalShard("default");
+ assertTrue(!out.isPresent());
}};
}
@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<Object> 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<Object> 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/"));
}
}
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);
}
--- /dev/null
+/*
+ * 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<YangInstanceIdentifier, NormalizedNode<?, ?>> {
+
+ private final List<AsyncDataChangeEvent<YangInstanceIdentifier, NormalizedNode<?, ?>>>
+ 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<YangInstanceIdentifier, NormalizedNode<?, ?>> 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());
+ }
+}
*/
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;
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<QName> {
+ 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);
}
/**
public abstract QName getContext();
@Override
- public QName getIdentifier() {
+ public final QName getIdentifier() {
return identifier;
}
for (DataSchemaNode schemaNode : input.getChildNodes()) {
Optional<QName> 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<QName> getRoutingContext(final DataSchemaNode schemaNode) {
+ public static Optional<QName> getRoutingContext(final DataSchemaNode schemaNode) {
for (UnknownSchemaNode extension : schemaNode.getUnknownSchemaNodes()) {
if (CONTEXT_REFERENCE.equals(extension.getNodeType())) {
return Optional.fromNullable(extension.getQName());
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
}
}
- private static class GlobalRpcStrategy extends RpcRoutingStrategy {
+ private static final class GlobalRpcStrategy extends RpcRoutingStrategy {
public GlobalRpcStrategy(final QName identifier) {
super(identifier);
@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
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<String> elementHosts = new ArrayList<String>();
private String username;
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();
synchronized (XSQLAdapter.class) {
if (l == null) {
l = new PrintStream(
- new FileOutputStream("/tmp/xql.log"));
+ new FileOutputStream(getXQLLogfile()));
}
}
}
synchronized (XSQLAdapter.class) {
if (l == null) {
l = new PrintStream(
- new FileOutputStream("/tmp/xql.log"));
+ new FileOutputStream(getXQLLogfile()));
}
}
}
*/
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;
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);
try {
- CheckedFuture<Boolean, ReadFailedException> future =
- rwTx.exists(store, currentPath);
+ CheckedFuture<Boolean, ReadFailedException> 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));
}
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;
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;
broker.commitConfigurationDataDelete(normalizedII).get();
}
} catch (Exception e) {
- throw new RestconfDocumentedException("Error creating data", e);
+ final Optional<Throwable> 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();
}
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;
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.
*
when(wTransaction.submit()).thenReturn(expFuture);
+ NormalizedNode<?, ?> dummyNode2 = createDummyNode("dummy:namespace2", "2014-07-01", "dummy local name2");
+
+
CheckedFuture<Void, TransactionCommitFailedException> actualFuture = brokerFacade
.commitConfigurationDataDelete(instanceID);
{
darknessFactor.set( darkness );
}
+
+ LOG.info("onDataChanged - new Toaster config: {}", toaster);
}
}
String elementName = jmxToYangChildRbeMapping.get(childMappingEntry.getKey());
- Element innerXml = XmlUtil.createElement(document, elementName, Optional.<String>absent());
+ Element innerXml = XmlUtil.createElement(document, elementName, Optional.of(namespace));
childMappingEntry.getValue().toXml(objectName, innerChildRbeOns, document,
runtimeInstanceIndex, innerXml, namespace);
xml.appendChild(innerXml);
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
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 {
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ and is available at http://www.eclipse.org/legal/epl-v10.html
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>netconf-artifacts</artifactId>
+ <version>0.3.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-config-dispatcher</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-netconf-connector</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-persister-impl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-auth</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-cli</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-config</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-connector-config</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-impl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-mapping-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-monitoring</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-netty-util</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-ssh</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-tcp</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-testtool</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-usermanager</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-util</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>ietf-netconf-monitoring</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>ietf-netconf-monitoring-extension</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-client</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-impl</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-netty-util</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-ssh</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-util</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>features-netconf</artifactId>
+ <version>${project.version}</version>
+ <classifier>features</classifier>
+ <type>xml</type>
+ <scope>runtime</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+</project>
+
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;
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);
}
}
- 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) {
* 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;
// match text content
Optional<String> 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;
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<String, String> prefixToNamespaceOfFilter = filter.findNamespaceOfTextContent();
+ final Map.Entry<String, String> 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
}
@Parameters
public static Collection<Object[]> data() {
List<Object[]> result = new ArrayList<>();
- for (int i = 0; i <= 8; i++) {
+ for (int i = 0; i <= 9; i++) {
result.add(new Object[]{i});
}
return result;
--- /dev/null
+<rpc-reply message-id="5"
+ xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <data>
+ <top xmlns="http://example.com/schema/1.2/config">
+ <users>
+ <user>
+ <name>fred</name>
+ <type xmlns:x="http://java.sun.com/dtd/properties.dtd">x:admin</type>
+ <full-name>Fred Flintstone</full-name>
+ </user>
+ </users>
+ </top>
+ </data>
+</rpc-reply>
\ No newline at end of file
--- /dev/null
+<rpc-reply message-id="5" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <data>
+ <top xmlns="http://example.com/schema/1.2/config">
+ <users>
+ <user>
+ <name>root</name>
+ <type>superuser</type>
+ <full-name>Charlie Root</full-name>
+ <company-info>
+ <dept>1</dept>
+ <id>1</id>
+ </company-info>
+ </user>
+ <user>
+ <name>fred</name>
+ <type xmlns:x="http://java.sun.com/dtd/properties.dtd">x:admin</type>
+ <full-name>Fred Flintstone</full-name>
+ <company-info>
+ <dept>2</dept>
+ <id>2</id>
+ </company-info>
+ </user>
+ <user>
+ <name>barney</name>
+ <type>admin</type>
+ <full-name>Barney Rubble</full-name>
+ <company-info>
+ <dept>2</dept>
+ <id>3</id>
+ </company-info>
+ </user>
+ </users>
+ <groups>
+ <group>
+ <name>admin</name>
+ </group>
+ </groups>
+ </top>
+ </data>
+</rpc-reply>
\ No newline at end of file
--- /dev/null
+<rpc message-id="5"
+ xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <get-config>
+ <source>
+ <running/>
+ </source>
+ <filter type="subtree">
+ <top xmlns="http://example.com/schema/1.2/config">
+ <users>
+ <user>
+ <name>fred</name>
+ <type xmlns:a="http://java.sun.com/dtd/properties.dtd">a:admin</type>
+ <full-name/>
+ </user>
+ </users>
+ </top>
+ </filter>
+ </get-config>
+</rpc>
\ No newline at end of file
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;
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;
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
<groupId>org.opendaylight.controller</groupId>
<artifactId>protocol-framework</artifactId>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller.thirdparty</groupId>
- <artifactId>ganymed</artifactId>
- </dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
- <Import-Package>org.apache.sshd.*, ch.ethz.ssh2, com.google.common.base, com.google.common.collect, io.netty.buffer,
+ <Import-Package>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,
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;
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.
*/
private final AuthenticationHandler authenticationHandler;
private final SshClient sshClient;
- private AsyncSshHanderReader sshReadAsyncListener;
+ private AsyncSshHandlerReader sshReadAsyncListener;
private AsyncSshHandlerWriter sshWriteAsyncHandler;
private ClientChannel channel;
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());
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;
* 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<IoReadFuture>, AutoCloseable {
+public final class AsyncSshHandlerReader implements SshFutureListener<IoReadFuture>, 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);
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);
private void invokeDisconnect() {
try {
- asyncSshHandler.disconnect(ctx, ctx.newPromise());
+ connectionClosedCallback.close();
} catch (final Exception e) {
// This should not happen
throw new IllegalStateException(e);
// 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);
+ }
}
* 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);
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();
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);
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller.thirdparty</groupId>
- <artifactId>ganymed</artifactId>
- </dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>netconf-netty-util</artifactId>
- <scope>test</scope>
</dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
+++ /dev/null
-/*
- * 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> 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);
- }
- }
-
-}
--- /dev/null
+/*
+ * 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<LocalChannel>() {
+ @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<ChannelFuture>() {
+
+ @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<ChannelFuture>() {
+
+ @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<Command> {
+
+ 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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+
+}
--- /dev/null
+/*
+ * 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.<NamedFactory<Command>>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);
+ }
+ }
+ }
+
+}
+++ /dev/null
-/*
- * 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();
- }
- }
-
-}
--- /dev/null
+/*
+ * 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<AuthProvider, AuthProvider>, PasswordAuthenticator {
+ private static final Logger logger = LoggerFactory.getLogger(AuthProviderTracker.class);
+
+ private final BundleContext bundleContext;
+
+ private Integer maxPreference;
+ private final ServiceTracker<AuthProvider, AuthProvider> 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<AuthProvider> 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<AuthProvider> 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<AuthProvider> 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<AuthProvider> 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);
+ }
+}
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);
}
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<InetSocketAddress> maybeSshSocketAddress = NetconfConfigUtil.extractNetconfServerAddress(bundleContext,
- InfixProp.ssh);
+ private SshProxyServer startSSHServer(final BundleContext bundleContext) throws IOException {
+ final Optional<InetSocketAddress> maybeSshSocketAddress = NetconfConfigUtil.extractNetconfServerAddress(bundleContext, InfixProp.ssh);
if (maybeSshSocketAddress.isPresent() == false) {
logger.trace("SSH bridge not configured");
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<AuthProvider, AuthProvider> {
- private final BundleContext bundleContext;
- private final NetconfSSHServer server;
-
- private Integer maxPreference;
- private Thread sshThread;
- private final ServiceTracker<AuthProvider, AuthProvider> 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<AuthProvider> 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<AuthProvider> 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<AuthProvider> 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<AuthProvider> 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
- }
-
- }
}
+++ /dev/null
-/*
- * 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<String> currentUserSupplier;
- private final String remoteAddress;
- private final String remotePort;
- private final String session;
- private final AutoCloseable ganymedConnection;
- private final LocalAddress localAddress;
- private final EventLoopGroup bossGroup;
-
- ServerConnectionCallbackImpl(Supplier<String> currentUserSupplier, String remoteAddress, String remotePort, String session,
- AutoCloseable ganymedConnection, LocalAddress localAddress, EventLoopGroup bossGroup) {
- this.currentUserSupplier = currentUserSupplier;
- this.remoteAddress = remoteAddress;
- this.remotePort = remotePort;
- this.session = session;
- this.ganymedConnection = ganymedConnection;
- // initialize netty local connection
- this.localAddress = localAddress;
- this.bossGroup = bossGroup;
- }
-
- private static ChannelFuture initializeNettyConnection(LocalAddress localAddress, EventLoopGroup bossGroup,
- final SSHClientHandler sshClientHandler) {
- Bootstrap clientBootstrap = new Bootstrap();
- clientBootstrap.group(bossGroup).channel(LocalChannel.class);
-
- clientBootstrap.handler(new ChannelInitializer<LocalChannel>() {
- @Override
- public void initChannel(LocalChannel ch) throws Exception {
- ch.pipeline().addLast(sshClientHandler);
- }
- });
- // asynchronously initialize local connection to netconf server
- return clientBootstrap.connect(localAddress);
- }
-
- @Override
- public ServerSessionCallback acceptSession(final ServerSession serverSession) {
- String currentUser = currentUserSupplier.get();
- final String additionalHeader = new NetconfHelloMessageAdditionalHeader(currentUser, remoteAddress,
- remotePort, "ssh", "client").toFormattedString();
-
-
- return new SimpleServerSessionCallback() {
- @Override
- public Runnable requestSubsystem(final ServerSession ss, final String subsystem) throws IOException {
- return new Runnable() {
- @Override
- public void run() {
- if (NETCONF_SUBSYSTEM.equals(subsystem)) {
- // connect
- final SSHClientHandler sshClientHandler = new SSHClientHandler(ganymedConnection, ss.getStdin(), session);
- ChannelFuture clientChannelFuture = initializeNettyConnection(localAddress, bossGroup, sshClientHandler);
- // get channel
- final Channel channel = clientChannelFuture.awaitUninterruptibly().channel();
-
- // 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<String> {
- private static final Logger logger = LoggerFactory.getLogger(ServerAuthenticationCallbackImpl.class);
- private final AuthProvider authProvider;
- private final String session;
- private String currentUser;
-
- ServerAuthenticationCallbackImpl(AuthProvider authProvider, String session) {
- this.authProvider = authProvider;
- this.session = session;
- }
-
- @Override
- public String initAuthentication(ServerConnection sc) {
- logger.trace("{} Established connection", session);
- return "Established connection" + "\r\n";
- }
-
- @Override
- public String[] getRemainingAuthMethods(ServerConnection sc) {
- return new String[]{ServerAuthenticationCallback.METHOD_PASSWORD};
- }
-
- @Override
- public AuthenticationResult authenticateWithNone(ServerConnection sc, String username) {
- return AuthenticationResult.FAILURE;
- }
-
- @Override
- public AuthenticationResult authenticateWithPassword(ServerConnection sc, String username, String password) {
- checkState(currentUser == null);
- try {
- if (authProvider.authenticated(username, password)) {
- currentUser = username;
- logger.trace("{} user {} authenticated", session, currentUser);
- return AuthenticationResult.SUCCESS;
- }
- } catch (Exception e) {
- logger.warn("{} Authentication failed", session, e);
- }
- return AuthenticationResult.FAILURE;
- }
-
- @Override
- public AuthenticationResult authenticateWithPublicKey(ServerConnection sc, String username, String algorithm,
- byte[] publicKey, byte[] signature) {
- return AuthenticationResult.FAILURE;
- }
-
- @Override
- public String get() {
- return currentUser;
- }
-}
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;
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;
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<NioSocketChannel> channelInitializer = new ChannelInitializer<NioSocketChannel>() {
+ final ChannelInitializer<NioSocketChannel> channelInitializer = new ChannelInitializer<NioSocketChannel>() {
@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)
@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);
}
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;
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 {
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();
}
-
}
}
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;
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;
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;
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;
private final NioEventLoopGroup nettyThreadgroup;
private final HashedWheelTimer hashedWheelTimer;
private final List<Channel> devicesChannels = Lists.newArrayList();
+ private final List<SshProxyServer> 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<ModuleBuilder, String> moduleBuilders, final boolean exi, final int generateConfigsTimeout) {
int currentPort = params.startingPort;
final List<Integer> 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
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);
}
}
@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
}
public final class XmlElement {
+ public static final String DEFAULT_NAMESPACE_PREFIX = "";
+
private final Element element;
private static final Logger logger = LoggerFactory.getLogger(XmlElement.class);
return xmlElement;
}
- private static Map<String, String> extractNamespaces(Element typeElement) throws NetconfDocumentedException {
+ private Map<String, String> extractNamespaces() throws NetconfDocumentedException {
Map<String, String> 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 :",
namespaces.put(prefix, attribute.getNodeValue());
}
}
+
+ // namespace does not have to be defined on this element but inherited
+ if(!namespaces.containsKey(DEFAULT_NAMESPACE_PREFIX)) {
+ Optional<String> namespaceOptionally = getNamespaceOptionally();
+ if(namespaceOptionally.isPresent()) {
+ namespaces.put(DEFAULT_NAMESPACE_PREFIX, namespaceOptionally.get());
+ }
+ }
+
return namespaces;
}
}
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();
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);
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,
* is found value will be null.
*/
public Map.Entry<String/* prefix */, String/* namespace */> findNamespaceOfTextContent() throws NetconfDocumentedException {
- Map<String, String> namespaces = extractNamespaces(element);
+ Map<String, String> 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 "
<module>netconf-auth</module>
<module>netconf-usermanager</module>
<module>netconf-testtool</module>
+
+ <module>netconf-artifacts</module>
</modules>
<dependencies>
<!-- <module>third-party/net.sf.jung2</module> -->
<!-- <module>third-party/jersey-servlet</module> -->
<!-- <module>third-party/org.apache.catalina.filters.CorsFilter</module> -->
- <module>third-party/ganymed</module>
-
+
<module>third-party/commons/thirdparty</module>
<!-- SAL bundles -->
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
-
- <parent>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>commons.thirdparty</artifactId>
- <version>1.2.0-SNAPSHOT</version>
- <relativePath>../commons/thirdparty</relativePath>
- </parent>
-
- <groupId>org.opendaylight.controller.thirdparty</groupId>
- <artifactId>ganymed</artifactId>
- <version>1.2.0-SNAPSHOT</version>
- <packaging>bundle</packaging>
-
- <dependencies>
- <dependency>
- <groupId>org.osgi</groupId>
- <artifactId>org.osgi.core</artifactId>
- <version>5.0.0</version>
- </dependency>
- <dependency>
- <groupId>ch.ethz.ganymed</groupId>
- <artifactId>ganymed-ssh2</artifactId>
- <version>261</version>
- </dependency>
- </dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.felix</groupId>
- <artifactId>maven-bundle-plugin</artifactId>
- <extensions>true</extensions>
- <configuration>
- <instructions>
- <Export-Package>ch.ethz.ssh2.*</Export-Package>
- <Embed-Dependency>ganymed-ssh2;scope=compile</Embed-Dependency>
- <Embed-Transitive>true</Embed-Transitive>
- </instructions>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-enforcer-plugin</artifactId>
- <version>${enforcer.version}</version>
- <executions>
- <execution>
- <id>enforce-no-snapshots</id>
- <goals>
- <goal>enforce</goal>
- </goals>
- <configuration>
- <rules>
- <bannedDependencies>
- <excludes>
- <exclude>ch.ethz.ganymed:ganymed-ssh2:*</exclude>
- </excludes>
- <includes>
- <include>ch.ethz.ganymed:ganymed-ssh2:[261]</include>
- </includes>
- </bannedDependencies>
- </rules>
- </configuration>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
-</project>
-
-
+++ /dev/null
-/*
- * 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 <code>Connection</code> is used to establish an encrypted TCP/IP
- * connection to a SSH-2 server.
- * <p>
- * Typically, one
- * <ol>
- * <li>creates a {@link #Connection(String) Connection} object.</li>
- * <li>calls the {@link #connect() connect()} method.</li>
- * <li>calls some of the authentication methods (e.g., {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()}).</li>
- * <li>calls one or several times the {@link #openSession() openSession()} method.</li>
- * <li>finally, one must close the connection and release resources with the {@link #close() close()} method.</li>
- * </ol>
- *
- * @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.
- * <p/>
- * <b>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 (-).</b>
- */
- 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<ConnectionMonitor> connectionMonitors = new Vector<ConnectionMonitor>();
-
- /**
- * Prepares a fresh <code>Connection</code> object which can then be used
- * to establish a connection to the specified SSH-2 server.
- * <p>
- * 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 <code>Connection</code> 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 <code>Connection</code> 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.
- * <b>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 (-).</b>
- */
- 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).
- * <p>
- * If the authentication phase is complete, <code>true</code> will be
- * returned. If the server does not accept the request (or if further
- * authentication steps are needed), <code>false</code> is returned and
- * one can retry either by using this or any other authentication method
- * (use the <code>getRemainingAuthMethods</code> method to get a list of
- * the remaining possible methods).
- *
- * @param user
- * A <code>String</code> holding the username.
- * @param pem
- * A <code>String</code> 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 <code>null</code>.
- *
- * @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 <code>null</code> submethod list.
- *
- * @param user
- * A <code>String</code> holding the username.
- * @param cb
- * An <code>InteractiveCallback</code> 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...)
- * <p>
- * If the authentication phase is complete, <code>true</code> will be
- * returned. If the server does not accept the request (or if further
- * authentication steps are needed), <code>false</code> is returned and
- * one can retry either by using this or any other authentication method
- * (use the <code>getRemainingAuthMethods</code> method to get a list of
- * the remaining possible methods).
- * <p>
- * 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 <code>String</code> holding the username.
- * @param submethods
- * An array of submethod names, see
- * draft-ietf-secsh-auth-kbdinteract-XX. May be <code>null</code>
- * to indicate an empty list.
- * @param cb
- * An <code>InteractiveCallback</code> 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.
- * <p>
- * If the authentication phase is complete, <code>true</code> will be
- * returned. If the server does not accept the request (or if further
- * authentication steps are needed), <code>false</code> is returned and
- * one can retry either by using this or any other authentication method
- * (use the <code>getRemainingAuthMethods</code> method to get a list of
- * the remaining possible methods).
- * <p>
- * Note: if this method fails, then please double-check that it is actually
- * offered by the server (use {@link #getRemainingAuthMethods(String) getRemainingAuthMethods()}.
- * <p>
- * 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).
- * <p>
- * 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)}.
- * <p>
- * 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.
- * <p>
- * If the authentication phase is complete, <code>true</code> will be
- * returned. If further authentication steps are needed, <code>false</code>
- * is returned and one can retry by any other authentication method
- * (use the <code>getRemainingAuthMethods</code> 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 =).
- * <p>
- * 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.
- * <p>
- * If the authentication phase is complete, <code>true</code> will be
- * returned. If the server does not accept the request (or if further
- * authentication steps are needed), <code>false</code> is returned and
- * one can retry either by using this or any other authentication method
- * (use the <code>getRemainingAuthMethods</code> method to get a list of
- * the remaining possible methods).
- * <p>
- * 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 <code>String</code> holding the username.
- * @param pemPrivateKey
- * A <code>char[]</code> 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 <code>null</code>.
- *
- * @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 <code>authenticateWithPublicKey(String, char[], String)</code>.
- * <p>
- * 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 <code>String</code> holding the username.
- * @param pemFile
- * A <code>File</code> 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 <code>null</code>.
- *
- * @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
- * <code>connect()</code> 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.)
- * <p>
- * 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 <code>verifier</code> to ask for permission to proceed.
- * If <code>verifier</code> is <code>null</code>, 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).
- * <p>
- * 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).
- * <p>
- * 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).
- * <p>
- * 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).
- * <p>
- * 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).
- * <p>
- * 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 <code>connect()</code> again.
- *
- * @param verifier
- * An object that implements the
- * {@link ServerHostKeyVerifier} interface. Pass <code>null</code>
- * 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
- * <code>verifier</code> callback, but it will only have an effect after
- * the <code>verifier</code> 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 <code>verifier</code> or there is problem during
- * the initial crypto setup (e.g., the signature sent by the server is wrong).
- * <p>
- * In case of a timeout (either connectTimeout or kexTimeout)
- * a SocketTimeoutException is thrown.
- * <p>
- * 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
- * <code>connect()</code> again without having called {@link #close()} first.
- * <p>
- * 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 <code>LocalPortForwarder</code> 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).
- * <p>
- * 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 <code>LocalPortForwarder</code> 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).
- * <p>
- * 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 <code>LocalStreamForwarder</code> 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.
- * <p>
- * Works only after one has passed successfully the authentication step.
- * There is no limit on the number of concurrent SCP clients.
- * <p>
- * 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.
- * <p>
- * 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).
- * <p>
- * 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).
- * <p>
- * Note 2: the server may return method names that are not supported by this
- * implementation.
- * <p>
- * After a successful authentication, this method must not be called
- * anymore.
- *
- * @param user
- * A <code>String</code> 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 <code>true</code> 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.)
- * <p>
- * 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 <code>String</code> 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).
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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 <code>ssh-dss</code> and <code>ssh-rsa</code>.
- * 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.
- * <p>
- * 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 <code>false</code>.
- *
- * @param enable the argument passed to the <code>Socket.setTCPNoDelay()</code> 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.
- * <p>
- * At the moment, only HTTP proxies are supported.
- * <p>
- * 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 <code>null</code>, 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()}.
- * <p>
- * A call of this method will block until the peer either agreed or disagreed to your request-
- * <p>
- * Note 1: this method typically fails if you
- * <ul>
- * <li>pass a port number for which the used remote user has not enough permissions (i.e., port
- * < 1024)</li>
- * <li>or pass a port number that is already in use on the remote server</li>
- * <li>or if remote port forwarding is disabled on the server.</li>
- * </ul>
- * <p>
- * 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 <b>GatewayPorts</b> option
- * is enabled (see sshd_config(5)).
- *
- * @param bindAddress address to bind to on the server:
- * <ul>
- * <li>"" means that connections are to be accepted on all protocol families
- * supported by the SSH implementation</li>
- * <li>"0.0.0.0" means to listen on all IPv4 addresses</li>
- * <li>"::" means to listen on all IPv6 addresses</li>
- * <li>"localhost" means to listen on all protocol families supported by the SSH
- * implementation on loopback addresses only, [RFC3330] and RFC3513]</li>
- * <li>"127.0.0.1" and "::1" indicate listening on the loopback interfaces for
- * IPv4 and IPv6 respectively</li>
- * </ul>
- * @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.
- * <p>
- * 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;
- }
-}
+++ /dev/null
-/*
- * 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.
- * <p/>
- * 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<String, X11ServerData> x11_magic_cookies = new HashMap<String, X11ServerData>();
-
- private final List<Channel> channels = new Vector<Channel>();
- private int nextLocalChannel = 100;
- private boolean shutdown = false;
- private int globalSuccessCounter = 0;
- private int globalFailedCounter = 0;
-
- private final HashMap<Integer, RemoteForwardingData> remoteForwardings = new HashMap<Integer, RemoteForwardingData>();
-
- private final List<IChannelWorkerThread> listenerThreads = new Vector<IChannelWorkerThread>();
-
- 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> channel_copy = new Vector<Channel>();
-
- 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> channel_copy = new Vector<Channel>();
-
- 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));
- }
- }
-}
+++ /dev/null
-/*
- * 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<AsynchronousEntry> asynchronousQueue = new Vector<AsynchronousEntry>();
- 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<HandlerEntry> messageHandlers = new Vector<HandlerEntry>();
-
- private Thread receiveThread;
-
- private List<ConnectionMonitor> connectionMonitors = new Vector<ConnectionMonitor>();
- 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<ConnectionMonitor> monitors = new Vector<ConnectionMonitor>();
-
- 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<ConnectionMonitor> monitors)
- {
- synchronized (this)
- {
- connectionMonitors = new Vector<ConnectionMonitor>();
- 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);
- }
- }
-}