--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ ~
+ ~ This program and the accompanying materials are made available under the
+ ~ terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ ~ and is available at http://www.eclipse.org/legal/epl-v10.html
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>netconf-subsystem</artifactId>
+ <version>0.2.5-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>netconf-testtool</artifactId>
+ <name>${project.artifactId}</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>net.sourceforge.argparse4j</groupId>
+ <artifactId>argparse4j</artifactId>
+ <version>0.4.3</version>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-netty-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>commons.logback_settings</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-netconf-connector</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>netconf-connector-config</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>logback-config</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>mockito-configuration</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>xmlunit</groupId>
+ <artifactId>xmlunit</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-impl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-mapping-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-monitoring</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-ssh</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netty-config-api</artifactId>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <configuration></configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <phase>package</phase>
+ <configuration>
+ <!-- TODO investigate why jar fails without this filter-->
+ <filters>
+ <filter>
+ <artifact>*:*</artifact>
+ <excludes>
+ <exclude>META-INF/*.SF</exclude>
+ <exclude>META-INF/*.DSA</exclude>
+ <exclude>META-INF/*.RSA</exclude>
+ </excludes>
+ </filter>
+ </filters>
+ <transformers>
+ <transformer
+ implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <mainClass>org.opendaylight.controller.netconf.test.tool.Main</mainClass>
+ </transformer>
+ </transformers>
+ <shadedArtifactAttached>true</shadedArtifactAttached>
+ <shadedClassifierName>executable</shadedClassifierName>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /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.test.tool;
+
+import java.io.File;
+import java.io.IOException;
+import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
+import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator;
+
+class AcceptingAuthProvider implements AuthProvider {
+ private final String privateKeyPEMString;
+
+ public AcceptingAuthProvider() {
+ try {
+ this.privateKeyPEMString = PEMGenerator.readOrGeneratePK(new File("PK"));
+ } catch (final IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public synchronized boolean authenticated(final String username, final String password) {
+ return true;
+ }
+
+ @Override
+ public char[] getPEMAsCharArray() {
+ return privateKeyPEMString.toCharArray();
+ }
+}
--- /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.test.tool;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.io.Files;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+
+import net.sourceforge.argparse4j.ArgumentParsers;
+import net.sourceforge.argparse4j.annotation.Arg;
+import net.sourceforge.argparse4j.inf.ArgumentParser;
+import net.sourceforge.argparse4j.inf.ArgumentParserException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.CharStreams;
+
+public final class Main {
+
+ // TODO add logback config
+
+ // TODO make exi configurable
+
+ private static final Logger LOG = LoggerFactory.getLogger(Main.class);
+
+ static class Params {
+
+ @Arg(dest = "schemas-dir")
+ public File schemasDir;
+
+ @Arg(dest = "devices-count")
+ public int deviceCount;
+
+ @Arg(dest = "starting-port")
+ public int startingPort;
+
+ @Arg(dest = "generate-configs-dir")
+ public File generateConfigsDir;
+
+ @Arg(dest = "generate-configs-batch-size")
+ public int generateConfigBatchSize;
+
+ @Arg(dest = "ssh")
+ public boolean ssh;
+
+ static ArgumentParser getParser() {
+ final ArgumentParser parser = ArgumentParsers.newArgumentParser("netconf testool");
+ parser.addArgument("--devices-count")
+ .type(Integer.class)
+ .setDefault(1)
+ .type(Integer.class)
+ .help("Number of simulated netconf devices to spin")
+ .dest("devices-count");
+
+ parser.addArgument("--schemas-dir")
+ .type(File.class)
+ .required(true)
+ .help("Directory containing yang schemas to describe simulated devices")
+ .dest("schemas-dir");
+
+ parser.addArgument("--starting-port")
+ .type(Integer.class)
+ .setDefault(17830)
+ .help("First port for simulated device. Each other device will have previous+1 port number")
+ .dest("starting-port");
+
+ parser.addArgument("--generate-configs-batch-size")
+ .type(Integer.class)
+ .setDefault(100)
+ .help("Number of connector configs per generated file")
+ .dest("generate-configs-batch-size");
+
+ parser.addArgument("--generate-configs-dir")
+ .type(File.class)
+ .help("Directory where initial config files for ODL distribution should be generated")
+ .dest("generate-configs-dir");
+
+ parser.addArgument("--ssh")
+ .type(Boolean.class)
+ .setDefault(true)
+ .help("Whether to use ssh for transport or just pure tcp")
+ .dest("ssh");
+
+ return parser;
+ }
+
+ void validate() {
+ checkArgument(deviceCount > 0, "Device count has to be > 0");
+ checkArgument(startingPort > 1024, "Starting port has to be > 1024");
+
+ checkArgument(schemasDir.exists(), "Schemas dir has to exist");
+ checkArgument(schemasDir.isDirectory(), "Schemas dir has to be a directory");
+ checkArgument(schemasDir.canRead(), "Schemas dir has to be readable");
+ }
+ }
+
+ public static void main(final String[] args) {
+ final Params params = parseArgs(args, Params.getParser());
+ params.validate();
+
+ final NetconfDeviceSimulator netconfDeviceSimulator = new NetconfDeviceSimulator();
+ try {
+ final List<Integer> openDevices = netconfDeviceSimulator.start(params);
+ if(params.generateConfigsDir != null) {
+ new ConfigGenerator(params.generateConfigsDir, openDevices).generate(params.ssh, params.generateConfigBatchSize);
+ }
+ } catch (final Exception e) {
+ LOG.error("Unhandled exception", e);
+ netconfDeviceSimulator.close();
+ System.exit(1);
+ }
+
+ // Block main thread
+ synchronized (netconfDeviceSimulator) {
+ try {
+ netconfDeviceSimulator.wait();
+ } catch (final InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+
+ private static Params parseArgs(final String[] args, final ArgumentParser parser) {
+ final Params opt = new Params();
+ try {
+ parser.parseArgs(args, opt);
+ return opt;
+ } catch (final ArgumentParserException e) {
+ parser.handleError(e);
+ }
+
+ System.exit(1);
+ return null;
+ }
+
+ private static class ConfigGenerator {
+ public static final String NETCONF_CONNECTOR_XML = "/initial/99-netconf-connector.xml";
+ public static final String NETCONF_CONNECTOR_NAME = "controller-config";
+ public static final String NETCONF_CONNECTOR_PORT = "1830";
+ public static final String NETCONF_USE_SSH = "false";
+ public static final String SIM_DEVICE_SUFFIX = "-sim-device";
+
+ private final File directory;
+ private final List<Integer> openDevices;
+
+ public ConfigGenerator(final File directory, final List<Integer> openDevices) {
+ this.directory = directory;
+ this.openDevices = openDevices;
+ }
+
+ public void generate(final boolean useSsh, final int batchSize) {
+ if(directory.exists() == false) {
+ checkState(directory.mkdirs(), "Unable to create folder %s" + directory);
+ }
+
+ try(InputStream stream = Main.class.getResourceAsStream(NETCONF_CONNECTOR_XML)) {
+ checkNotNull(stream, "Cannot load %s", NETCONF_CONNECTOR_XML);
+ String configBlueprint = CharStreams.toString(new InputStreamReader(stream, Charsets.UTF_8));
+
+ // TODO make address configurable
+ checkState(configBlueprint.contains(NETCONF_CONNECTOR_NAME));
+ checkState(configBlueprint.contains(NETCONF_CONNECTOR_PORT));
+ checkState(configBlueprint.contains(NETCONF_USE_SSH));
+ configBlueprint = configBlueprint.replace(NETCONF_CONNECTOR_NAME, "%s");
+ configBlueprint = configBlueprint.replace(NETCONF_CONNECTOR_PORT, "%s");
+ configBlueprint = configBlueprint.replace(NETCONF_USE_SSH, "%s");
+
+ final String before = configBlueprint.substring(0, configBlueprint.indexOf("<module>"));
+ final String middleBlueprint = configBlueprint.substring(configBlueprint.indexOf("<module>"), configBlueprint.indexOf("</module>") + "</module>".length());
+ final String after = configBlueprint.substring(configBlueprint.indexOf("</module>") + "</module>".length());
+
+ int connectorCount = 0;
+ Integer batchStart = null;
+ StringBuilder b = new StringBuilder();
+ b.append(before);
+
+ for (final Integer openDevice : openDevices) {
+ if(batchStart == null) {
+ batchStart = openDevice;
+ }
+
+ final String name = String.valueOf(openDevice) + SIM_DEVICE_SUFFIX;
+ final String configContent = String.format(middleBlueprint, name, String.valueOf(openDevice), String.valueOf(!useSsh));
+ b.append(configContent);
+ connectorCount++;
+ if(connectorCount == batchSize) {
+ b.append(after);
+ Files.write(b.toString(), new File(directory, String.format("simulated-devices_%d-%d.xml", batchStart, openDevice)), Charsets.UTF_8);
+ connectorCount = 0;
+ b = new StringBuilder();
+ b.append(before);
+ batchStart = null;
+ }
+ }
+
+ // Write remaining
+ if(connectorCount != 0) {
+ b.append(after);
+ Files.write(b.toString(), new File(directory, String.format("simulated-devices_%d-%d.xml", batchStart, openDevices.get(openDevices.size() - 1))), Charsets.UTF_8);
+ }
+
+ LOG.info("Config files generated in {}", directory);
+ } catch (final IOException e) {
+ throw new RuntimeException("Unable to generate config files", 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.test.tool;
+
+import com.google.common.base.Optional;
+import java.util.Date;
+import java.util.List;
+import org.opendaylight.controller.netconf.confignetconfconnector.util.Util;
+import org.opendaylight.controller.netconf.mapping.api.Capability;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.parser.builder.impl.ModuleBuilder;
+
+final class ModuleBuilderCapability implements Capability {
+ private static final Date NO_REVISION = new Date(0);
+ private final ModuleBuilder input;
+ private final Optional<String> content;
+
+ public ModuleBuilderCapability(final ModuleBuilder input, final String inputStream) {
+ this.input = input;
+ this.content = Optional.of(inputStream);
+ }
+
+ @Override
+ public String getCapabilityUri() {
+ // FIXME capabilities in Netconf-impl need to check for NO REVISION
+ final String withoutRevision = getModuleNamespace().get() + "?module=" + getModuleName().get();
+ return hasRevision() ? withoutRevision + "&revision=" + Util.writeDate(input.getRevision()) : withoutRevision;
+ }
+
+ @Override
+ public Optional<String> getModuleNamespace() {
+ return Optional.of(input.getNamespace().toString());
+ }
+
+ @Override
+ public Optional<String> getModuleName() {
+ return Optional.of(input.getName());
+ }
+
+ @Override
+ public Optional<String> getRevision() {
+ return Optional.of(hasRevision() ? QName.formattedRevision(input.getRevision()) : "");
+ }
+
+ private boolean hasRevision() {
+ return !input.getRevision().equals(NO_REVISION);
+ }
+
+ @Override
+ public Optional<String> getCapabilitySchema() {
+ return content;
+ }
+
+ @Override
+ public Optional<List<String>> getLocation() {
+ return Optional.absent();
+ }
+}
--- /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.test.tool;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.CharStreams;
+import com.google.common.util.concurrent.CheckedFuture;
+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.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.management.ManagementFactory;
+import java.net.Inet4Address;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.UnknownHostException;
+import java.util.AbstractMap;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.ExecutionException;
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.tree.ParseTreeWalker;
+import org.opendaylight.controller.netconf.api.monitoring.NetconfManagementSession;
+import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer;
+import org.opendaylight.controller.netconf.impl.NetconfServerDispatcher;
+import org.opendaylight.controller.netconf.impl.NetconfServerSessionNegotiatorFactory;
+import org.opendaylight.controller.netconf.impl.SessionIdProvider;
+import org.opendaylight.controller.netconf.impl.osgi.NetconfMonitoringServiceImpl;
+import org.opendaylight.controller.netconf.impl.osgi.SessionMonitoringService;
+import org.opendaylight.controller.netconf.mapping.api.Capability;
+import org.opendaylight.controller.netconf.mapping.api.NetconfOperation;
+import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider;
+import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService;
+import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceSnapshot;
+import org.opendaylight.controller.netconf.monitoring.osgi.NetconfMonitoringOperationService;
+import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
+import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
+import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
+import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceListener;
+import org.opendaylight.yangtools.yang.model.repo.util.FilesystemSchemaSourceCache;
+import org.opendaylight.yangtools.yang.parser.builder.impl.BuilderUtils;
+import org.opendaylight.yangtools.yang.parser.builder.impl.ModuleBuilder;
+import org.opendaylight.yangtools.yang.parser.impl.YangParserListenerImpl;
+import org.opendaylight.yangtools.yang.parser.repo.SharedSchemaRepository;
+import org.opendaylight.yangtools.yang.parser.util.ASTSchemaSource;
+import org.opendaylight.yangtools.yang.parser.util.TextToASTTransformer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NetconfDeviceSimulator implements Closeable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(NetconfDeviceSimulator.class);
+
+ public static final int CONNECTION_TIMEOUT_MILLIS = 20000;
+
+ private final NioEventLoopGroup nettyThreadgroup;
+ private final HashedWheelTimer hashedWheelTimer;
+ private final List<Channel> devicesChannels = Lists.newArrayList();
+
+ public NetconfDeviceSimulator() {
+ this(new NioEventLoopGroup(), new HashedWheelTimer());
+ }
+
+ public NetconfDeviceSimulator(final NioEventLoopGroup eventExecutors, final HashedWheelTimer hashedWheelTimer) {
+ this.nettyThreadgroup = eventExecutors;
+ this.hashedWheelTimer = hashedWheelTimer;
+ }
+
+ private NetconfServerDispatcher createDispatcher(final Map<ModuleBuilder, String> moduleBuilders) {
+
+ final Set<Capability> capabilities = Sets.newHashSet(Collections2.transform(moduleBuilders.keySet(), new Function<ModuleBuilder, Capability>() {
+ @Override
+ public Capability apply(final ModuleBuilder input) {
+ return new ModuleBuilderCapability(input, moduleBuilders.get(input));
+ }
+ }));
+
+ final SessionIdProvider idProvider = new SessionIdProvider();
+
+ final SimulatedOperationProvider simulatedOperationProvider = new SimulatedOperationProvider(idProvider, capabilities);
+ final NetconfMonitoringOperationService monitoringService = new NetconfMonitoringOperationService(new NetconfMonitoringServiceImpl(simulatedOperationProvider));
+ simulatedOperationProvider.addService(monitoringService);
+
+ final DefaultCommitNotificationProducer commitNotifier = new DefaultCommitNotificationProducer(ManagementFactory.getPlatformMBeanServer());
+
+ final NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactory(
+ hashedWheelTimer, simulatedOperationProvider, idProvider, CONNECTION_TIMEOUT_MILLIS, commitNotifier, new LoggingMonitoringService());
+
+ final NetconfServerDispatcher.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcher.ServerChannelInitializer(
+ serverNegotiatorFactory);
+ return new NetconfServerDispatcher(serverChannelInitializer, nettyThreadgroup, nettyThreadgroup);
+ }
+
+ private Map<ModuleBuilder, String> toModuleBuilders(final Map<SourceIdentifier, Map.Entry<ASTSchemaSource, YangTextSchemaSource>> sources) {
+ final Map<SourceIdentifier, ParserRuleContext> asts = Maps.transformValues(sources, new Function<Map.Entry<ASTSchemaSource, YangTextSchemaSource>, ParserRuleContext>() {
+ @Override
+ public ParserRuleContext apply(final Map.Entry<ASTSchemaSource, YangTextSchemaSource> input) {
+ return input.getKey().getAST();
+ }
+ });
+ final Map<String, TreeMap<Date, URI>> namespaceContext = BuilderUtils.createYangNamespaceContext(
+ asts.values(), Optional.<SchemaContext>absent());
+
+ final ParseTreeWalker walker = new ParseTreeWalker();
+ final Map<ModuleBuilder, String> sourceToBuilder = new HashMap<>();
+
+ for (final Map.Entry<SourceIdentifier, ParserRuleContext> entry : asts.entrySet()) {
+ final ModuleBuilder moduleBuilder = YangParserListenerImpl.create(namespaceContext, entry.getKey().getName(),
+ walker, entry.getValue()).getModuleBuilder();
+
+ try(InputStreamReader stream = new InputStreamReader(sources.get(entry.getKey()).getValue().openStream(), Charsets.UTF_8)) {
+ sourceToBuilder.put(moduleBuilder, CharStreams.toString(stream));
+ } catch (final IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ return sourceToBuilder;
+ }
+
+
+ public List<Integer> start(final Main.Params params) {
+ final Map<ModuleBuilder, String> moduleBuilders = parseSchemasToModuleBuilders(params);
+
+ final NetconfServerDispatcher dispatcher = createDispatcher(moduleBuilders);
+
+ int currentPort = params.startingPort;
+
+ final List<Integer> openDevices = Lists.newArrayList();
+ for (int i = 0; i < params.deviceCount; i++) {
+ final InetSocketAddress address = getAddress(currentPort);
+
+ final ChannelFuture server;
+ if(params.ssh) {
+ final LocalAddress tcpLocalAddress = new LocalAddress(address.toString());
+
+ server = dispatcher.createLocalServer(tcpLocalAddress);
+ try {
+ NetconfSSHServer.start(currentPort, tcpLocalAddress, new AcceptingAuthProvider(), nettyThreadgroup);
+ } catch (final Exception e) {
+ LOG.warn("Cannot start simulated device on {}, skipping", address, e);
+ // Close local server and continue
+ server.cancel(true);
+ if(server.isDone()) {
+ server.channel().close();
+ }
+ continue;
+ } finally {
+ currentPort++;
+ }
+
+ try {
+ server.get();
+ } catch (final InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (final ExecutionException e) {
+ LOG.warn("Cannot start ssh simulated device on {}, skipping", address, e);
+ continue;
+ }
+
+ LOG.debug("Simulated SSH device started on {}", address);
+
+ } else {
+ server = dispatcher.createServer(address);
+ currentPort++;
+
+ try {
+ server.get();
+ } catch (final InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (final ExecutionException e) {
+ LOG.warn("Cannot start tcp simulated device on {}, skipping", address, e);
+ continue;
+ }
+
+ LOG.debug("Simulated TCP device started on {}", address);
+ }
+
+ devicesChannels.add(server.channel());
+ openDevices.add(currentPort - 1);
+
+ }
+
+ if(openDevices.size() == params.deviceCount) {
+ LOG.info("All simulated devices started successfully from port {} to {}", params.startingPort, currentPort);
+ } else {
+ LOG.warn("Not all simulated devices started successfully. Started devices ar on ports {}", openDevices);
+ }
+
+ return openDevices;
+ }
+
+ private Map<ModuleBuilder, String> parseSchemasToModuleBuilders(final Main.Params params) {
+ final SharedSchemaRepository consumer = new SharedSchemaRepository("netconf-simulator");
+ consumer.registerSchemaSourceListener(TextToASTTransformer.create(consumer, consumer));
+
+ final Set<SourceIdentifier> loadedSources = Sets.newHashSet();
+
+ consumer.registerSchemaSourceListener(new SchemaSourceListener() {
+ @Override
+ public void schemaSourceEncountered(final SchemaSourceRepresentation schemaSourceRepresentation) {}
+
+ @Override
+ public void schemaSourceRegistered(final Iterable<PotentialSchemaSource<?>> potentialSchemaSources) {
+ for (final PotentialSchemaSource<?> potentialSchemaSource : potentialSchemaSources) {
+ loadedSources.add(potentialSchemaSource.getSourceIdentifier());
+ }
+ }
+
+ @Override
+ public void schemaSourceUnregistered(final PotentialSchemaSource<?> potentialSchemaSource) {}
+ });
+
+ final FilesystemSchemaSourceCache<YangTextSchemaSource> cache = new FilesystemSchemaSourceCache<>(consumer, YangTextSchemaSource.class, params.schemasDir);
+ consumer.registerSchemaSourceListener(cache);
+
+ final Map<SourceIdentifier, Map.Entry<ASTSchemaSource, YangTextSchemaSource>> asts = Maps.newHashMap();
+ for (final SourceIdentifier loadedSource : loadedSources) {
+ try {
+ final CheckedFuture<ASTSchemaSource, SchemaSourceException> ast = consumer.getSchemaSource(loadedSource, ASTSchemaSource.class);
+ final CheckedFuture<YangTextSchemaSource, SchemaSourceException> text = consumer.getSchemaSource(loadedSource, YangTextSchemaSource.class);
+ asts.put(loadedSource, new AbstractMap.SimpleEntry<>(ast.get(), text.get()));
+ } catch (final InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (final ExecutionException e) {
+ throw new RuntimeException("Cannot parse schema context", e);
+ }
+ }
+ return toModuleBuilders(asts);
+ }
+
+ private static InetSocketAddress getAddress(final int port) {
+ try {
+ // TODO make address configurable
+ return new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), port);
+ } catch (final UnknownHostException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void close() {
+ for (final Channel deviceCh : devicesChannels) {
+ deviceCh.close();
+ }
+ nettyThreadgroup.shutdownGracefully();
+ // close Everything
+ }
+
+ private static class SimulatedOperationProvider implements NetconfOperationProvider {
+ private final SessionIdProvider idProvider;
+ private final Set<NetconfOperationService> netconfOperationServices;
+
+
+ public SimulatedOperationProvider(final SessionIdProvider idProvider, final Set<Capability> caps) {
+ this.idProvider = idProvider;
+ final SimulatedOperationService simulatedOperationService = new SimulatedOperationService(caps, idProvider.getCurrentSessionId());
+ this.netconfOperationServices = Sets.<NetconfOperationService>newHashSet(simulatedOperationService);
+ }
+
+ @Override
+ public NetconfOperationServiceSnapshot openSnapshot(final String sessionIdForReporting) {
+ return new SimulatedServiceSnapshot(idProvider, netconfOperationServices);
+ }
+
+ public void addService(final NetconfOperationService monitoringService) {
+ netconfOperationServices.add(monitoringService);
+ }
+
+ private static class SimulatedServiceSnapshot implements NetconfOperationServiceSnapshot {
+ private final SessionIdProvider idProvider;
+ private final Set<NetconfOperationService> netconfOperationServices;
+
+ public SimulatedServiceSnapshot(final SessionIdProvider idProvider, final Set<NetconfOperationService> netconfOperationServices) {
+ this.idProvider = idProvider;
+ this.netconfOperationServices = netconfOperationServices;
+ }
+
+ @Override
+ public String getNetconfSessionIdForReporting() {
+ return String.valueOf(idProvider.getCurrentSessionId());
+ }
+
+ @Override
+ public Set<NetconfOperationService> getServices() {
+ return netconfOperationServices;
+ }
+
+ @Override
+ public void close() throws Exception {}
+ }
+
+ static class SimulatedOperationService implements NetconfOperationService {
+ private final Set<Capability> capabilities;
+ private static SimulatedGet sGet;
+
+ public SimulatedOperationService(final Set<Capability> capabilities, final long currentSessionId) {
+ this.capabilities = capabilities;
+ sGet = new SimulatedGet(String.valueOf(currentSessionId));
+ }
+
+ @Override
+ public Set<Capability> getCapabilities() {
+ return capabilities;
+ }
+
+ @Override
+ public Set<NetconfOperation> getNetconfOperations() {
+ return Sets.<NetconfOperation>newHashSet(sGet);
+ }
+
+ @Override
+ public void close() {
+ }
+
+ }
+ }
+
+ private class LoggingMonitoringService implements SessionMonitoringService {
+ @Override
+ public void onSessionUp(final NetconfManagementSession session) {
+ LOG.debug("Session {} established", session);
+ }
+
+ @Override
+ public void onSessionDown(final NetconfManagementSession session) {
+ LOG.debug("Session {} down", session);
+ }
+ }
+
+}
--- /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.test.tool;
+
+import com.google.common.base.Optional;
+import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
+import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants;
+import org.opendaylight.controller.netconf.confignetconfconnector.operations.AbstractConfigNetconfOperation;
+import org.opendaylight.controller.netconf.util.xml.XmlElement;
+import org.opendaylight.controller.netconf.util.xml.XmlUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+class SimulatedGet extends AbstractConfigNetconfOperation {
+
+ SimulatedGet(final String netconfSessionIdForReporting) {
+ super(null, netconfSessionIdForReporting);
+ }
+
+ @Override
+ protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws NetconfDocumentedException {
+ return XmlUtil.createElement(document, XmlNetconfConstants.DATA_KEY, Optional.<String>absent());
+ }
+
+ @Override
+ protected String getOperationName() {
+ return XmlNetconfConstants.GET;
+ }
+}
<module>netconf-it</module>
</modules>
</profile>
+
+ <profile>
+ <id>testtool</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ </activation>
+ <modules>
+ <module>netconf-testtool</module>
+ </modules>
+ </profile>
</profiles>
</project>