netconf-testtool, autoconfiguration through restconf and topology model 65/35065/9
authormiroslav.kovac <miroslav.kovac@pantheon.tech>
Fri, 19 Feb 2016 11:36:30 +0000 (12:36 +0100)
committermiroslav.kovac <miroslav.kovac@pantheon.tech>
Thu, 17 Mar 2016 16:15:47 +0000 (17:15 +0100)
It starts all the devices, creates a restconf request and
sends PUT request to odl which sends us some response.
It also gives us time of how long did it take to configure
all the devices. All this can be done synchronously or
asynchronously.

Change-Id: I3c9bfc23b538d934809ddf3664fe024af65ec6ef
Signed-off-by: miroslav.kovac <miroslav.kovac@pantheon.tech>
netconf/tools/netconf-testtool/pom.xml
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/Execution.java [new file with mode: 0644]
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/Main.java
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/NetconfDeviceSimulator.java
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/ScaleUtil.java
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/TesttoolParameters.java [new file with mode: 0644]
netconf/tools/netconf-testtool/src/main/resources/config-template.xml [new file with mode: 0644]

index fa1e85ed1e25d0674ed8255b13e24d953323b4a7..3655b6beaea921b000597b029cfe0c0dbc9175c7 100644 (file)
                                     </excludes>
                                 </filter>
                             </filters>
-                            <artifactSet>
-                                <excludes>
-                                    <exclude>com.ning</exclude>
-                                </excludes>
-                            </artifactSet>
-                            <transformers>
-                                <transformer
-                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
-                                    <mainClass>org.opendaylight.netconf.test.tool.Main</mainClass>
-                                </transformer>
-                            </transformers>
-                            <shadedArtifactAttached>true</shadedArtifactAttached>
-                            <shadedClassifierName>executable</shadedClassifierName>
-                        </configuration>
-                    </execution>
+                              <transformers>
+                                  <transformer
+                                          implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                                      <mainClass>org.opendaylight.netconf.test.tool.Main</mainClass>
+                                  </transformer>
+                              </transformers>
+                              <shadedArtifactAttached>true</shadedArtifactAttached>
+                              <shadedClassifierName>executable</shadedClassifierName>
+                          </configuration>
+                      </execution>
 
-                    <execution>
-                        <id>stress-client</id>
-                        <goals>
-                            <goal>shade</goal>
-                        </goals>
-                        <phase>package</phase>
-                        <configuration>
-                            <shadedArtifactId>stress-client</shadedArtifactId>
-                            <filters>
-                                <filter>
-                                    <artifact>*:*</artifact>
-                                    <excludes>
-                                        <exclude>META-INF/*.SF</exclude>
-                                        <exclude>META-INF/*.DSA</exclude>
-                                        <exclude>META-INF/*.RSA</exclude>
-                                        <exclude>org.opendaylight.netconf.test.tool.client.http</exclude>
-                                        <exclude>org.opendaylight.netconf.test.tool.rpc</exclude>
-                                        <exclude>AcceptingAuthProvider</exclude>
-                                        <exclude>org.opendaylight.netconf.test.tool.DummyMonitoringService</exclude>
-                                        <exclude>org.opendaylight.netconf.test.tool.FakeCapability</exclude>
-                                        <exclude>org.opendaylight.netconf.test.tool.Main</exclude>
-                                        <exclude>org.opendaylight.netconf.test.tool.NetconfDeviceSimulator</exclude>
-                                    </excludes>
-                                </filter>
-                            </filters>
-                            <artifactSet>
-                                <excludes>
-                                    <exclude>org.bouncycastle:*</exclude>
-                                </excludes>
-                            </artifactSet>
-                            <transformers>
-                                <transformer
-                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
-                                    <manifestEntries>
-                                        <Main-Class>org.opendaylight.netconf.test.tool.client.stress.StressClient</Main-Class>
-                                        <Class-Path>. lib lib/bcprov-jdk15on.jar lib/bcpkix-jdk15on.jar</Class-Path>
-                                    </manifestEntries>
-                                </transformer>
-                            </transformers>
-                            <shadedArtifactAttached>true</shadedArtifactAttached>
-                            <shadedClassifierName>stress-client</shadedClassifierName>
-                        </configuration>
-                    </execution>
+                      <execution>
+                          <id>stress-client</id>
+                          <goals>
+                              <goal>shade</goal>
+                          </goals>
+                          <phase>package</phase>
+                          <configuration>
+                              <shadedArtifactId>stress-client</shadedArtifactId>
+                              <filters>
+                                  <filter>
+                                      <artifact>*:*</artifact>
+                                      <excludes>
+                                          <exclude>META-INF/*.SF</exclude>
+                                          <exclude>META-INF/*.DSA</exclude>
+                                          <exclude>META-INF/*.RSA</exclude>
+                                          <exclude>org.opendaylight.netconf.test.tool.client.http</exclude>
+                                          <exclude>org.opendaylight.netconf.test.tool.rpc</exclude>
+                                          <exclude>AcceptingAuthProvider</exclude>
+                                          <exclude>org.opendaylight.netconf.test.tool.DummyMonitoringService</exclude>
+                                          <exclude>org.opendaylight.netconf.test.tool.FakeCapability</exclude>
+                                          <exclude>org.opendaylight.netconf.test.tool.Main</exclude>
+                                          <exclude>org.opendaylight.netconf.test.tool.NetconfDeviceSimulator</exclude>
+                                      </excludes>
+                                  </filter>
+                              </filters>
+                              <artifactSet>
+                                  <excludes>
+                                     <exclude>org.bouncycastle:*</exclude>
+                                  </excludes>
+                              </artifactSet>
+                              <transformers>
+                                  <transformer
+                                          implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                                      <manifestEntries>
+                                          <Main-Class>org.opendaylight.netconf.test.tool.client.stress.StressClient</Main-Class>
+                                          <Class-Path>. lib lib/bcprov-jdk15on.jar lib/bcpkix-jdk15on.jar</Class-Path>
+                                      </manifestEntries>
+                                  </transformer>
+                              </transformers>
+                              <shadedArtifactAttached>true</shadedArtifactAttached>
+                              <shadedClassifierName>stress-client</shadedClassifierName>
+                          </configuration>
+                      </execution>
 
-                    <execution>
-                        <id>restconf-perf-client</id>
-                        <goals>
-                            <goal>shade</goal>
-                        </goals>
-                        <phase>package</phase>
-                        <configuration>
-                            <shadedArtifactId>rest-perf-client</shadedArtifactId>
-                            <filters>
-                                <filter>
-                                    <artifact>*:*</artifact>
-                                    <excludes>
-                                        <exclude>META-INF/*.SF</exclude>
-                                        <exclude>META-INF/*.DSA</exclude>
-                                        <exclude>META-INF/*.RSA</exclude>
-                                        <exclude>org.opendaylight.netconf.test.tool.rpc</exclude>
-                                        <exclude>AcceptingAuthProvider</exclude>
-                                        <exclude>org.opendaylight.netconf.test.tool.DummyMonitoringService</exclude>
-                                        <exclude>org.opendaylight.netconf.test.tool.FakeCapability</exclude>
-                                        <exclude>org.opendaylight.netconf.test.tool.Main</exclude>
-                                        <exclude>org.opendaylight.netconf.test.tool.NetconfDeviceSimulator</exclude>
-                                    </excludes>
-                                </filter>
-                            </filters>
-                            <artifactSet>
-                                <excludes>
-                                    <exclude>org.bouncycastle:*</exclude>
-                                    <exclude>com.google:*</exclude>
-                                    <exclude>org.opendaylight.yangtools</exclude>
-                                    <exclude>org.opendaylight.yang</exclude>
-                                </excludes>
-                            </artifactSet>
-                            <transformers>
-                                <transformer
-                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
-                                    <mainClass>org.opendaylight.netconf.test.tool.client.http.perf.RestPerfClient</mainClass>
-                                </transformer>
-                            </transformers>
-                            <shadedArtifactAttached>true</shadedArtifactAttached>
-                            <shadedClassifierName>rest-perf-client</shadedClassifierName>
-                        </configuration>
-                    </execution>
+                      <execution>
+                          <id>restconf-perf-client</id>
+                          <goals>
+                              <goal>shade</goal>
+                          </goals>
+                          <phase>package</phase>
+                          <configuration>
+                              <shadedArtifactId>rest-perf-client</shadedArtifactId>
+                              <filters>
+                                  <filter>
+                                      <artifact>*:*</artifact>
+                                      <excludes>
+                                          <exclude>META-INF/*.SF</exclude>
+                                          <exclude>META-INF/*.DSA</exclude>
+                                          <exclude>META-INF/*.RSA</exclude>
+                                          <exclude>org.opendaylight.netconf.test.tool.rpc</exclude>
+                                          <exclude>AcceptingAuthProvider</exclude>
+                                          <exclude>org.opendaylight.netconf.test.tool.DummyMonitoringService</exclude>
+                                          <exclude>org.opendaylight.netconf.test.tool.FakeCapability</exclude>
+                                          <exclude>org.opendaylight.netconf.test.tool.Main</exclude>
+                                          <exclude>org.opendaylight.netconf.test.tool.NetconfDeviceSimulator</exclude>
+                                      </excludes>
+                                  </filter>
+                              </filters>
+                              <artifactSet>
+                                  <excludes>
+                                      <exclude>org.bouncycastle:*</exclude>
+                                      <exclude>com.google:*</exclude>
+                                      <exclude>org.opendaylight.yangtools</exclude>
+                                      <exclude>org.opendaylight.yang</exclude>
+                                  </excludes>
+                              </artifactSet>
+                              <transformers>
+                                  <transformer
+                                          implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                                      <mainClass>org.opendaylight.netconf.test.tool.client.http.perf.RestPerfClient</mainClass>
+                                  </transformer>
+                              </transformers>
+                              <shadedArtifactAttached>true</shadedArtifactAttached>
+                              <shadedClassifierName>rest-perf-client</shadedClassifierName>
+                          </configuration>
+                      </execution>
 
                     <execution>
                         <id>scale-util</id>
                             <shadedClassifierName>scale-util</shadedClassifierName>
                         </configuration>
                     </execution>
-                </executions>
-            </plugin>
-            <plugin>
-                <artifactId>maven-assembly-plugin</artifactId>
-                <configuration>
-                    <descriptors>
-                        <descriptor>src/main/assembly/stress-client.xml</descriptor>
-                    </descriptors>
-                    <finalName>stress-client-${project.version}-package</finalName>
-                </configuration>
-                <executions>
-                    <execution>
-                        <id>make-assembly</id>
-                        <phase>package</phase>
-                        <goals>
-                            <goal>single</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
-        </plugins>
-    </build>
+                  </executions>
+              </plugin>
+              <plugin>
+                  <artifactId>maven-assembly-plugin</artifactId>
+                  <configuration>
+                      <descriptors>
+                          <descriptor>src/main/assembly/stress-client.xml</descriptor>
+                      </descriptors>
+                      <finalName>stress-client-${project.version}-package</finalName>
+                  </configuration>
+                  <executions>
+                      <execution>
+                          <id>make-assembly</id>
+                          <phase>package</phase>
+                          <goals>
+                              <goal>single</goal>
+                          </goals>
+                      </execution>
+                  </executions>
+              </plugin>
+          </plugins>
+      </build>
 
-</project>
+  </project>
diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/Execution.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/Execution.java
new file mode 100644 (file)
index 0000000..5fc8d9b
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2016 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.netconf.test.tool;
+
+import com.ning.http.client.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Semaphore;
+
+public class Execution implements Callable<Void> {
+
+    private final ArrayList<Request> payloads;
+    private final AsyncHttpClient asyncHttpClient;
+    private static final Logger LOG = LoggerFactory.getLogger(Execution.class);
+    private final boolean invokeAsync;
+    private final Semaphore semaphore;
+    private final int throttle;
+
+    static final class DestToPayload {
+
+        private final String destination;
+        private final String payload;
+
+        public DestToPayload(String destination, String payload) {
+            this.destination = destination;
+            this.payload = payload;
+        }
+
+        public String getDestination() {
+            return destination;
+        }
+
+        public String getPayload() {
+            return payload;
+        }
+    }
+
+    public Execution(TesttoolParameters params, ArrayList<DestToPayload> payloads) {
+        this.invokeAsync = params.async;
+        this.throttle = params.throttle / params.threadAmount;
+
+        if (params.async && params.threadAmount > 1) {
+            LOG.info("Throttling per thread: {}", this.throttle);
+        }
+        this.semaphore = new Semaphore(this.throttle);
+
+        this.asyncHttpClient = new AsyncHttpClient(new AsyncHttpClientConfig.Builder()
+                .setConnectTimeout(Integer.MAX_VALUE)
+                .setRequestTimeout(Integer.MAX_VALUE)
+                .setAllowPoolingConnections(true)
+                .build());
+
+        this.payloads = new ArrayList<>();
+        for (DestToPayload payload : payloads) {
+            AsyncHttpClient.BoundRequestBuilder requestBuilder = asyncHttpClient.preparePut(payload.getDestination())
+                    .addHeader("Content-Type", "application/xml")
+                    .addHeader("Accept", "application/xml")
+                    .setBody(payload.getPayload())
+                    .setRequestTimeout(Integer.MAX_VALUE);
+
+            if (params.auth != null) {
+                requestBuilder.setRealm(new Realm.RealmBuilder()
+                        .setScheme(Realm.AuthScheme.BASIC)
+                        .setPrincipal(params.auth.get(0))
+                        .setPassword(params.auth.get(1))
+                        .setMethodName("PUT")
+                        .setUsePreemptiveAuth(true)
+                        .build());
+            }
+            this.payloads.add(requestBuilder.build());
+        }
+    }
+
+    private void invokeSync() {
+        LOG.info("Begin sending sync requests");
+        for (Request request : payloads) {
+            try {
+                Response response = asyncHttpClient.executeRequest(request).get();
+                if (response.getStatusCode() != 200 && response.getStatusCode() != 204) {
+                    LOG.warn("Status code: {}", response.getStatusCode());
+                    LOG.warn("url: {}", request.getUrl());
+                    LOG.warn(response.getResponseBody());
+                }
+            } catch (InterruptedException | ExecutionException | IOException e) {
+                LOG.warn(e.toString());
+            }
+        }
+        LOG.info("End sending sync requests");
+    }
+
+    private void invokeAsync() {
+        final ArrayList<ListenableFuture<Response>> futures = new ArrayList<>();
+        LOG.info("Begin sending async requests");
+
+        for (final Request request : payloads) {
+            try {
+                semaphore.acquire();
+            } catch (InterruptedException e) {
+                LOG.warn("Semaphore acquire interrupted");
+            }
+            futures.add(asyncHttpClient.executeRequest(request, new AsyncCompletionHandler<Response>() {
+                @Override
+                public STATE onStatusReceived(HttpResponseStatus status) throws Exception {
+                    super.onStatusReceived(status);
+                    if (status.getStatusCode() != 200 && status.getStatusCode() != 204) {
+                        LOG.warn("Request failed, status code: {}", status.getStatusCode() + status.getStatusText());
+                        LOG.warn("request: {}", request.toString());
+                    }
+                    return STATE.CONTINUE;
+                }
+
+                @Override
+                public Response onCompleted(Response response) throws Exception {
+                    semaphore.release();
+                    return response;
+                }
+            }));
+        }
+        LOG.info("Requests sent, waiting for responses");
+
+        try {
+            semaphore.acquire(this.throttle);
+        } catch (InterruptedException e) {
+            LOG.warn("Semaphore acquire interrupted");
+        }
+
+        LOG.info("Responses received, ending...");
+    }
+
+    @Override
+    public Void call() throws Exception {
+        if (invokeAsync) {
+            this.invokeAsync();
+        } else {
+            this.invokeSync();
+        }
+        return null;
+    }
+}
index 1488b0545d954ff4cd6ca4f0ef64dea6336e32d6..e82127b68b480538d8efcced8ae78eadc4e16b5f 100644 (file)
@@ -8,12 +8,12 @@
 
 package org.opendaylight.netconf.test.tool;
 
-import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import ch.qos.logback.classic.Level;
 import com.google.common.base.Charsets;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
 import com.google.common.collect.Lists;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.CharStreams;
@@ -23,14 +23,15 @@ import java.io.FileFilter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
-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.opendaylight.controller.config.util.xml.XmlElement;
 import org.opendaylight.controller.config.util.xml.XmlUtil;
 import org.slf4j.Logger;
@@ -41,159 +42,14 @@ import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.xml.sax.SAXException;
 
+
 public final class Main {
 
     private static final Logger LOG = LoggerFactory.getLogger(Main.class);
 
-    public static class Params {
-
-        @Arg(dest = "schemas-dir")
-        public File schemasDir;
-
-        @Arg(dest = "devices-count")
-        public int deviceCount;
-
-        @Arg(dest = "devices-per-port")
-        public int devicesPerPort;
-
-        @Arg(dest = "starting-port")
-        public int startingPort;
-
-        @Arg(dest = "generate-config-connection-timeout")
-        public int generateConfigsTimeout;
-
-        @Arg(dest = "generate-config-address")
-        public String generateConfigsAddress;
-
-        @Arg(dest = "distro-folder")
-        public File distroFolder;
-
-        @Arg(dest = "generate-configs-batch-size")
-        public int generateConfigBatchSize;
-
-        @Arg(dest = "ssh")
-        public boolean ssh;
-
-        @Arg(dest = "exi")
-        public boolean exi;
-
-        @Arg(dest = "debug")
-        public boolean debug;
-
-        @Arg(dest = "notification-file")
-        public File notificationFile;
-
-        @Arg(dest = "md-sal")
-        public boolean mdSal;
-
-        @Arg(dest = "initial-config-xml-file")
-        public File initialConfigXMLFile;
-
-        static ArgumentParser getParser() {
-            final ArgumentParser parser = ArgumentParsers.newArgumentParser("netconf testool");
-
-            parser.description("Netconf device simulator. Detailed info can be found at https://wiki.opendaylight.org/view/OpenDaylight_Controller:Netconf:Testtool#Building_testtool");
-
-            parser.addArgument("--device-count")
-                    .type(Integer.class)
-                    .setDefault(1)
-                    .help("Number of simulated netconf devices to spin. This is the number of actual ports open for the devices.")
-                    .dest("devices-count");
-
-            parser.addArgument("--devices-per-port")
-                    .type(Integer.class)
-                    .setDefault(1)
-                    .help("Amount of config files generated per port to spoof more devices then are actually running")
-                    .dest("devices-per-port");
-
-            parser.addArgument("--schemas-dir")
-                    .type(File.class)
-                    .help("Directory containing yang schemas to describe simulated devices. Some schemas e.g. netconf monitoring and inet types are included by default")
-                    .dest("schemas-dir");
-
-            parser.addArgument("--notification-file")
-                    .type(File.class)
-                    .help("Xml file containing notifications that should be sent to clients after create subscription is called")
-                    .dest("notification-file");
-
-            parser.addArgument("--initial-config-xml-file")
-                    .type(File.class)
-                    .help("Xml file containing initial simulatted configuration to be returned via get-config rpc")
-                    .dest("initial-config-xml-file");
-
-            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-config-connection-timeout")
-                    .type(Integer.class)
-                    .setDefault((int)TimeUnit.MINUTES.toMillis(30))
-                    .help("Timeout to be generated in initial config files")
-                    .dest("generate-config-connection-timeout");
-
-            parser.addArgument("--generate-config-address")
-                    .type(String.class)
-                    .setDefault("127.0.0.1")
-                    .help("Address to be placed in generated configs")
-                    .dest("generate-config-address");
-
-            parser.addArgument("--generate-configs-batch-size")
-                    .type(Integer.class)
-                    .setDefault(4000)
-                    .help("Number of connector configs per generated file")
-                    .dest("generate-configs-batch-size");
-
-            parser.addArgument("--distribution-folder")
-                    .type(File.class)
-                    .help("Directory where the karaf distribution for controller is located")
-                    .dest("distro-folder");
-
-            parser.addArgument("--ssh")
-                    .type(Boolean.class)
-                    .setDefault(true)
-                    .help("Whether to use ssh for transport or just pure tcp")
-                    .dest("ssh");
-
-            parser.addArgument("--exi")
-                    .type(Boolean.class)
-                    .setDefault(true)
-                    .help("Whether to use exi to transport xml content")
-                    .dest("exi");
-
-            parser.addArgument("--debug")
-                    .type(Boolean.class)
-                    .setDefault(false)
-                    .help("Whether to use debug log level instead of INFO")
-                    .dest("debug");
-
-            parser.addArgument("--md-sal")
-                    .type(Boolean.class)
-                    .setDefault(false)
-                    .help("Whether to use md-sal datastore instead of default simulated datastore.")
-                    .dest("md-sal");
-
-            return parser;
-        }
-
-        void validate() {
-            checkArgument(deviceCount > 0, "Device count has to be > 0");
-            checkArgument(startingPort > 1023, "Starting port has to be > 1023");
-            checkArgument(devicesPerPort > 0, "Atleast one device per port needed");
-
-            if(schemasDir != null) {
-                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());
+        final TesttoolParameters params = TesttoolParameters.parseArgs(args, TesttoolParameters.getParser());
         params.validate();
-
         final ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
         root.setLevel(params.debug ? Level.DEBUG : Level.INFO);
 
@@ -204,7 +60,33 @@ public final class Main {
                 LOG.error("Failed to start any simulated devices, exiting...");
                 System.exit(1);
             }
-            if(params.distroFolder != null) {
+            if (params.controllerDestination != null) {
+                final ArrayList<ArrayList<Execution.DestToPayload>> allThreadsPayloads = params.getThreadsPayloads(openDevices);
+                final ArrayList<Execution> executions = new ArrayList<>();
+                for (ArrayList<Execution.DestToPayload> payloads : allThreadsPayloads) {
+                    executions.add(new Execution(params, payloads));
+                }
+                final ExecutorService executorService = Executors.newFixedThreadPool(params.threadAmount);
+                final Stopwatch time = Stopwatch.createStarted();
+                List<Future<Void>> futures = executorService.invokeAll(executions, params.timeOut, TimeUnit.SECONDS);
+                int threadNum = 0;
+                for(Future<Void> future : futures){
+                    threadNum++;
+                    if (future.isCancelled()) {
+                        LOG.info("{}. thread timed out.",threadNum);
+                    } else {
+                        try {
+                            future.get();
+                        } catch (final ExecutionException e) {
+                            LOG.info("{}. thread failed.", threadNum, e);
+                        }
+                    }
+                }
+                time.stop();
+                LOG.info("Time spent with configuration of devices: {}.",time);
+            }
+
+            if (params.distroFolder != null) {
                 final ConfigGenerator configGenerator = new ConfigGenerator(params.distroFolder, openDevices);
                 final List<File> generated = configGenerator.generate(
                         params.ssh, params.generateConfigBatchSize,
@@ -229,19 +111,6 @@ public final class Main {
         }
     }
 
-    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;
-    }
-
     static class ConfigGenerator {
         public static final String NETCONF_CONNECTOR_XML = "/99-netconf-connector-simulated.xml";
         public static final String SIM_DEVICE_SUFFIX = "-sim-device";
@@ -270,7 +139,7 @@ public final class Main {
         public List<File> generate(final boolean useSsh, final int batchSize,
                                    final int generateConfigsTimeout, final String address,
                                    final int devicesPerPort) {
-            if(configDir.exists() == false) {
+            if (configDir.exists() == false) {
                 Preconditions.checkState(configDir.mkdirs(), "Unable to create directory " + configDir);
             }
 
@@ -283,7 +152,7 @@ public final class Main {
                 Preconditions.checkState(file.delete(), "Unable to clean previous generated file %s", file);
             }
 
-            try(InputStream stream = Main.class.getResourceAsStream(NETCONF_CONNECTOR_XML)) {
+            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));
 
@@ -299,7 +168,7 @@ public final class Main {
                 final List<File> generatedConfigs = Lists.newArrayList();
 
                 for (final Integer openDevice : openDevices) {
-                    if(batchStart == null) {
+                    if (batchStart == null) {
                         batchStart = openDevice;
                     }
 
@@ -310,7 +179,7 @@ public final class Main {
 
                         b.append(configContent);
                         connectorCount++;
-                        if(connectorCount == batchSize) {
+                        if (connectorCount == batchSize) {
                             b.append(after);
                             final File to = new File(configDir, String.format(SIM_DEVICE_CFG_PREFIX + "%d-%d.xml", batchStart, openDevice));
                             generatedConfigs.add(to);
@@ -324,7 +193,7 @@ public final class Main {
                 }
 
                 // Write remaining
-                if(connectorCount != 0) {
+                if (connectorCount != 0) {
                     b.append(after);
                     final File to = new File(configDir, String.format(SIM_DEVICE_CFG_PREFIX + "%d-%d.xml", batchStart, openDevices.get(openDevices.size() - 1)));
                     generatedConfigs.add(to);
@@ -380,7 +249,7 @@ public final class Main {
                         }
                     }
 
-                    Files.write(XmlUtil.toString(document), featureFile,Charsets.UTF_8);
+                    Files.write(XmlUtil.toString(document), featureFile, Charsets.UTF_8);
                     LOG.info("Feature file {} updated", featureFile);
                 }
             } catch (final IOException e) {
@@ -433,7 +302,7 @@ public final class Main {
 
         public void changeLoadOrder() {
             try {
-                Files.write(ByteStreams.toByteArray(getClass().getResourceAsStream("/" +ORG_OPS4J_PAX_URL_MVN_CFG)), loadOrderCfgFile);
+                Files.write(ByteStreams.toByteArray(getClass().getResourceAsStream("/" + ORG_OPS4J_PAX_URL_MVN_CFG)), loadOrderCfgFile);
                 LOG.info("Load order changed to prefer local bundles/features by rewriting file {}", loadOrderCfgFile);
             } catch (IOException e) {
                 throw new RuntimeException("Unable to rewrite features file " + loadOrderCfgFile, e);
index 3233ea57a46c178723420118748659d6d2710c67..23d6750896140857b0dc10b9e6b3d36939edc0e3 100644 (file)
@@ -146,7 +146,7 @@ public class NetconfDeviceSimulator implements Closeable {
         return new NetconfServerDispatcherImpl(serverChannelInitializer, nettyThreadgroup, nettyThreadgroup);
     }
 
-    public List<Integer> start(final Main.Params params) {
+    public List<Integer> start(final TesttoolParameters params) {
         LOG.info("Starting {}, {} simulated devices starting on port {}", params.deviceCount, params.ssh ? "SSH" : "TCP", params.startingPort);
 
         final SharedSchemaRepository schemaRepo = new SharedSchemaRepository("netconf-simulator");
@@ -267,7 +267,7 @@ public class NetconfDeviceSimulator implements Closeable {
         }
     }
 
-    private Set<Capability> parseSchemasToModuleCapabilities(final Main.Params params, final SharedSchemaRepository consumer) {
+    private Set<Capability> parseSchemasToModuleCapabilities(final TesttoolParameters params, final SharedSchemaRepository consumer) {
         final Set<SourceIdentifier> loadedSources = Sets.newHashSet();
 
         consumer.registerSchemaSourceListener(TextToASTTransformer.create(consumer, consumer));
index 6b5667c932e7a4b24b55024a72bfec9837e42cb4..adce359f088e7a3d706fe2ba8589ecfe45978940 100644 (file)
@@ -9,14 +9,12 @@
 package org.opendaylight.netconf.test.tool;
 
 import ch.qos.logback.classic.Level;
-import com.google.common.base.Charsets;
 import com.google.common.base.Stopwatch;
 import com.google.common.io.CharStreams;
 import com.ning.http.client.AsyncHttpClient;
 import com.ning.http.client.AsyncHttpClientConfig.Builder;
 import com.ning.http.client.Request;
 import com.ning.http.client.Response;
-import java.io.BufferedInputStream;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
@@ -34,7 +32,6 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import net.sourceforge.argparse4j.inf.ArgumentParser;
 import net.sourceforge.argparse4j.inf.ArgumentParserException;
-import org.opendaylight.netconf.test.tool.Main.Params;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -54,7 +51,7 @@ public class ScaleUtil {
     private static final Semaphore semaphore = new Semaphore(0);
 
     public static void main(final String[] args) {
-        final Params params = parseArgs(args, Params.getParser());
+        final TesttoolParameters params = TesttoolParameters.parseArgs(args, TesttoolParameters.getParser());
 
         root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
         root.setLevel(params.debug ? Level.DEBUG : Level.INFO);
@@ -133,7 +130,7 @@ public class ScaleUtil {
         }
     }
 
-    private static void cleanup(final Runtime runtime, final Params params) {
+    private static void cleanup(final Runtime runtime, final TesttoolParameters params) {
         try {
             stopKaraf(runtime, params);
             deleteFolder(new File(params.distroFolder.getAbsoluteFile() + "/data"));
@@ -144,7 +141,7 @@ public class ScaleUtil {
         }
     }
 
-    private static void stopKaraf(final Runtime runtime, final Params params) throws IOException, InterruptedException {
+    private static void stopKaraf(final Runtime runtime, final TesttoolParameters params) throws IOException, InterruptedException {
         root.info("Stopping karaf and sleeping for 10 sec..");
         String controllerPid = "";
         do {
@@ -174,8 +171,8 @@ public class ScaleUtil {
         folder.delete();
     }
 
-    private static Params parseArgs(final String[] args, final ArgumentParser parser) {
-        final Params parameters = new Params();
+    private static TesttoolParameters parseArgs(final String[] args, final ArgumentParser parser) {
+        final TesttoolParameters parameters = new TesttoolParameters();
         try {
             parser.parseArgs(args, parameters);
             return parameters;
diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/TesttoolParameters.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/TesttoolParameters.java
new file mode 100644 (file)
index 0000000..eb63632
--- /dev/null
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2015 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.netconf.test.tool;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.io.CharStreams;
+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.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+import net.sourceforge.argparse4j.ArgumentParsers;
+import net.sourceforge.argparse4j.annotation.Arg;
+import net.sourceforge.argparse4j.inf.ArgumentParser;
+import net.sourceforge.argparse4j.inf.ArgumentParserException;
+
+public class TesttoolParameters {
+
+    private static final String HOST_KEY = "{HOST}";
+    private static final String PORT_KEY = "{PORT}";
+    private static final String SSH = "{SSH}";
+    private static final String ADDRESS_PORT = "{ADDRESS:PORT}";
+    private static final String dest = "http://{ADDRESS:PORT}/restconf/config/network-topology:network-topology/topology/topology-netconf/node/{PORT}-sim-device";
+
+    private static final String RESOURCE = "/config-template.xml";
+    private InputStream stream;
+
+    @Arg(dest = "edit-content")
+    public File editContent;
+
+    @Arg(dest = "async")
+    public boolean async;
+
+    @Arg(dest = "thread-amount")
+    public int threadAmount;
+
+    @Arg(dest = "throttle")
+    public int throttle;
+
+    @Arg(dest = "auth")
+    public ArrayList<String> auth;
+
+    @Arg(dest = "controller-destination")
+    public String controllerDestination;
+
+    @Arg(dest = "schemas-dir")
+    public File schemasDir;
+
+    @Arg(dest = "devices-count")
+    public int deviceCount;
+
+    @Arg(dest = "devices-per-port")
+    public int devicesPerPort;
+
+    @Arg(dest = "starting-port")
+    public int startingPort;
+
+    @Arg(dest = "generate-config-connection-timeout")
+    public int generateConfigsTimeout;
+
+    @Arg(dest = "generate-config-address")
+    public String generateConfigsAddress;
+
+    @Arg(dest = "distro-folder")
+    public File distroFolder;
+
+    @Arg(dest = "generate-configs-batch-size")
+    public int generateConfigBatchSize;
+
+    @Arg(dest = "ssh")
+    public boolean ssh;
+
+    @Arg(dest = "exi")
+    public boolean exi;
+
+    @Arg(dest = "debug")
+    public boolean debug;
+
+    @Arg(dest = "notification-file")
+    public File notificationFile;
+
+    @Arg(dest = "md-sal")
+    public boolean mdSal;
+
+    @Arg(dest = "initial-config-xml-file")
+    public File initialConfigXMLFile;
+
+    @Arg(dest = "time-out")
+    public long timeOut;
+
+    static ArgumentParser getParser() {
+        final ArgumentParser parser = ArgumentParsers.newArgumentParser("netconf testtool");
+
+        parser.description("netconf testtool");
+
+        parser.addArgument("--edit-content")
+                .type(String.class)
+                .dest("edit-content");
+
+        parser.addArgument("--async-requests")
+                .type(Boolean.class)
+                .setDefault(false)
+                .dest("async");
+
+        parser.addArgument("--thread-amount")
+                .type(Integer.class)
+                .setDefault(1)
+                .dest("thread-amount");
+
+        parser.addArgument("--throttle")
+                .type(Integer.class)
+                .setDefault(5000)
+                .help("Maximum amount of async requests that can be open at a time, " +
+                        "with mutltiple threads this gets divided among all threads")
+                .dest("throttle");
+
+        parser.addArgument("--auth")
+                .nargs(2)
+                .help("Username and password for HTTP basic authentication in order username password.")
+                .dest("auth");
+
+        parser.addArgument("--controller-destination")
+                .type(String.class)
+                .help("Ip address and port of controller. Must be in following format <ip>:<port> "+
+                      "if available it will be used for spawning netconf connectors via topology configuration as "+
+                      "a part of URI. Example (http://<controller destination>/restconf/config/network-topology:network-topology/topology/topology-netconf/node/<node-id>)"+
+                      "otherwise it will just start simulated devices and skip the execution of PUT requests")
+                .dest("controller-destination");
+
+        parser.addArgument("--device-count")
+                .type(Integer.class)
+                .setDefault(1)
+                .help("Number of simulated netconf devices to spin. This is the number of actual ports open for the devices.")
+                .dest("devices-count");
+
+        parser.addArgument("--devices-per-port")
+                .type(Integer.class)
+                .setDefault(1)
+                .help("Amount of config files generated per port to spoof more devices then are actually running")
+                .dest("devices-per-port");
+
+        parser.addArgument("--schemas-dir")
+                .type(File.class)
+                .help("Directory containing yang schemas to describe simulated devices. Some schemas e.g. netconf monitoring and inet types are included by default")
+                .dest("schemas-dir");
+
+        parser.addArgument("--notification-file")
+                .type(File.class)
+                .help("Xml file containing notifications that should be sent to clients after create subscription is called")
+                .dest("notification-file");
+
+        parser.addArgument("--initial-config-xml-file")
+                .type(File.class)
+                .help("Xml file containing initial simulatted configuration to be returned via get-config rpc")
+                .dest("initial-config-xml-file");
+
+        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-config-connection-timeout")
+                .type(Integer.class)
+                .setDefault((int) TimeUnit.MINUTES.toMillis(30))
+                .help("Timeout to be generated in initial config files")
+                .dest("generate-config-connection-timeout");
+
+        parser.addArgument("--generate-config-address")
+                .type(String.class)
+                .setDefault("127.0.0.1")
+                .help("Address to be placed in generated configs")
+                .dest("generate-config-address");
+
+        parser.addArgument("--generate-configs-batch-size")
+                .type(Integer.class)
+                .setDefault(4000)
+                .help("Number of connector configs per generated file")
+                .dest("generate-configs-batch-size");
+
+        parser.addArgument("--distribution-folder")
+                .type(File.class)
+                .help("Directory where the karaf distribution for controller is located")
+                .dest("distro-folder");
+
+        parser.addArgument("--ssh")
+                .type(Boolean.class)
+                .setDefault(true)
+                .help("Whether to use ssh for transport or just pure tcp")
+                .dest("ssh");
+
+        parser.addArgument("--exi")
+                .type(Boolean.class)
+                .setDefault(true)
+                .help("Whether to use exi to transport xml content")
+                .dest("exi");
+
+        parser.addArgument("--debug")
+                .type(Boolean.class)
+                .setDefault(false)
+                .help("Whether to use debug log level instead of INFO")
+                .dest("debug");
+
+        parser.addArgument("--md-sal")
+                .type(Boolean.class)
+                .setDefault(false)
+                .help("Whether to use md-sal datastore instead of default simulated datastore.")
+                .dest("md-sal");
+
+        parser.addArgument("--time-out")
+                .type(long.class)
+                .setDefault(20)
+                .help("the maximum time in seconds for executing each PUT request")
+                .dest("time-out");
+
+        return parser;
+    }
+
+    public static TesttoolParameters parseArgs(final String[] args, final ArgumentParser parser) {
+        final TesttoolParameters opt = new TesttoolParameters();
+        try {
+            parser.parseArgs(args, opt);
+            return opt;
+        } catch (final ArgumentParserException e) {
+            parser.handleError(e);
+        }
+
+        System.exit(1);
+        return null;
+    }
+
+    void validate() {
+        if (editContent == null) {
+            stream = TesttoolParameters.class.getResourceAsStream(RESOURCE);
+        } else {
+            Preconditions.checkArgument(!editContent.isDirectory(), "Edit content file is a dir");
+            Preconditions.checkArgument(editContent.canRead(), "Edit content file is unreadable");
+        }
+
+        if (controllerDestination != null) {
+            Preconditions.checkArgument(controllerDestination.contains(":"), "Controller Destination needs to be in a following format <ip>:<port>");
+            String[] parts = controllerDestination.split(Pattern.quote(":"));
+            Preconditions.checkArgument(Integer.parseInt(parts[1]) > 0, "Port =< 0");
+        }
+
+        checkArgument(deviceCount > 0, "Device count has to be > 0");
+        checkArgument(startingPort > 1023, "Starting port has to be > 1023");
+        checkArgument(devicesPerPort > 0, "Atleast one device per port needed");
+
+        if (schemasDir != null) {
+            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 ArrayList<ArrayList<Execution.DestToPayload>> getThreadsPayloads(List<Integer> openDevices) {
+        final String editContentString;
+        try {
+            if(stream == null)
+            {
+                editContentString = Files.toString(editContent, Charsets.UTF_8);
+            } else {
+                editContentString = CharStreams.toString(new InputStreamReader(stream, Charsets.UTF_8));
+            }
+        } catch (final IOException e) {
+            throw new IllegalArgumentException("Cannot read content of " + editContent);
+        }
+
+        final ArrayList<ArrayList<Execution.DestToPayload>> allThreadsPayloads = new ArrayList<>();
+        for (int i = 0; i < threadAmount; i++) {
+            final ArrayList<Execution.DestToPayload> payloads = new ArrayList<>();
+            for (int j = 0; j < openDevices.size(); j++) {
+                final StringBuilder destBuilder = new StringBuilder(dest);
+                destBuilder.replace(destBuilder.indexOf(ADDRESS_PORT), destBuilder.indexOf(ADDRESS_PORT) + ADDRESS_PORT.length(), controllerDestination)
+                        .replace(destBuilder.indexOf(PORT_KEY), destBuilder.indexOf(PORT_KEY) + PORT_KEY.length(), Integer.toString(openDevices.get(j)));
+                payloads.add(new Execution.DestToPayload(destBuilder.toString(), prepareMessage(openDevices.get(j), editContentString)));
+            }
+            allThreadsPayloads.add(payloads);
+        }
+
+        return allThreadsPayloads;
+    }
+
+    private String prepareMessage(final int openDevice, final String editContentString) {
+        StringBuilder messageBuilder = new StringBuilder(editContentString);
+
+        if (editContentString.contains(HOST_KEY)) {
+            messageBuilder.replace(messageBuilder.indexOf(HOST_KEY), messageBuilder.indexOf(HOST_KEY) + HOST_KEY.length(), generateConfigsAddress);
+        }
+        if (editContentString.contains(PORT_KEY)) {
+            while (messageBuilder.indexOf(PORT_KEY) != -1)
+                messageBuilder.replace(messageBuilder.indexOf(PORT_KEY), messageBuilder.indexOf(PORT_KEY) + PORT_KEY.length(), Integer.toString(openDevice));
+        }
+        if (editContentString.contains(SSH)) {
+            messageBuilder.replace(messageBuilder.indexOf(SSH), messageBuilder.indexOf(SSH) + SSH.length(), Boolean.toString(ssh));
+        }
+        return messageBuilder.toString();
+    }
+}
diff --git a/netconf/tools/netconf-testtool/src/main/resources/config-template.xml b/netconf/tools/netconf-testtool/src/main/resources/config-template.xml
new file mode 100644 (file)
index 0000000..ea237a7
--- /dev/null
@@ -0,0 +1,9 @@
+<node xmlns="urn:TBD:params:xml:ns:yang:network-topology">
+    <node-id>{PORT}-sim-device</node-id>
+    <host xmlns="urn:opendaylight:netconf-node-topology">{HOST}</host>
+    <port xmlns="urn:opendaylight:netconf-node-topology">{PORT}</port>
+    <username xmlns="urn:opendaylight:netconf-node-topology">admin</username>
+    <password xmlns="urn:opendaylight:netconf-node-topology">admin</password>
+    <tcp-only xmlns="urn:opendaylight:netconf-node-topology">{SSH}</tcp-only>
+    <keepalive-delay xmlns="urn:opendaylight:netconf-node-topology">0</keepalive-delay>
+</node>
\ No newline at end of file