Add test device connections utility 26/47826/5
authorJozef Bacigal <jbacigal@cisco.com>
Mon, 7 Mar 2016 13:29:07 +0000 (14:29 +0100)
committerJozef Bacigal <jozef.bacigal@pantheon.tech>
Thu, 8 Dec 2016 08:19:02 +0000 (08:19 +0000)
Change-Id: I89933b0ce057679e959cf2ef576fe10c8a1cc122
Signed-off-by: Jozef Bacigal <jozef.bacigal@pantheon.tech>
parent/pom.xml
simple-client/pom.xml
simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/CallableClient.java [new file with mode: 0644]
simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/ControllerConnectionTestTool.java [new file with mode: 0644]
simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/ScenarioFactory.java
simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/ScenarioHandler.java
simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/WaitForMessageEvent.java

index 00cfc5dc52c836999e1418e907a583ad3e30b889..5822d278e22a1a403e213a3e31181381575cd295 100644 (file)
@@ -58,6 +58,7 @@
         <controller.mdsal.version>1.5.0-SNAPSHOT</controller.mdsal.version>
         <mdsal.model.version>0.10.0-SNAPSHOT</mdsal.model.version>
         <yangtools.version>1.1.0-SNAPSHOT</yangtools.version>
+        <argparse4j.version>0.7.0</argparse4j.version>
     </properties>
 
     <dependencyManagement>
               <scope>import</scope>
               <type>pom</type>
             </dependency>
+            <dependency>
+                <groupId>net.sourceforge.argparse4j</groupId>
+                <artifactId>argparse4j</artifactId>
+                <version>${argparse4j.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
index 5eb462b50b4b4e2577a76a77e1f42180267b7da4..76dd357561e0b863de93d4e6c3eb20cb5f3ca844 100644 (file)
@@ -45,5 +45,9 @@
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-log4j12</artifactId>
         </dependency>
+        <dependency>
+            <groupId>net.sourceforge.argparse4j</groupId>
+            <artifactId>argparse4j</artifactId>
+        </dependency>
     </dependencies>
 </project>
diff --git a/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/CallableClient.java b/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/CallableClient.java
new file mode 100644 (file)
index 0000000..24b1da3
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2016 Pantheon Technologies s.r.o. 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.openflowjava.protocol.impl.clients;
+
+import java.net.InetAddress;
+import java.util.concurrent.Callable;
+
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.SettableFuture;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Callable client class, inspired by SimpleClient class
+ * Simulating device/switch connected to controller
+ * @author Jozef Bacigal
+ * Date: 4.3.2016.
+ */
+public class CallableClient implements Callable<Boolean>, OFClient {
+
+    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(CallableClient.class);
+
+    private int port = 6653;
+    private boolean securedClient = false;
+    private InetAddress ipAddress = null;
+    private String name = "Empty name";
+
+    private EventLoopGroup workerGroup;
+    private SettableFuture<Boolean> isOnlineFuture;
+    private SettableFuture<Boolean> scenarioDone;
+    private ScenarioHandler scenarioHandler = null;
+    private Bootstrap bootstrap = null;
+
+    public CallableClient(
+            final int port,
+            final boolean securedClient,
+            final InetAddress ipAddress,
+            final String name,
+            final ScenarioHandler scenarioHandler,
+            final Bootstrap bootstrap,
+            final EventLoopGroup eventExecutors) {
+
+        Preconditions.checkNotNull(ipAddress, "IP address cannot be null");
+        Preconditions.checkNotNull(scenarioHandler, "Scenario handler cannot be null");
+        this.port = port;
+        this.securedClient = securedClient;
+        this.ipAddress = ipAddress;
+        this.workerGroup = eventExecutors;
+        this.bootstrap = bootstrap;
+        this.name = name;
+        this.scenarioHandler = scenarioHandler;
+    }
+
+    @Override
+    public SettableFuture<Boolean> getIsOnlineFuture() {
+        return isOnlineFuture;
+    }
+
+    @Override
+    public SettableFuture<Boolean> getScenarioDone() {
+        return scenarioDone;
+    }
+
+    @Override
+    public void setScenarioHandler(final ScenarioHandler scenario) {
+        this.scenarioHandler = scenario;
+    }
+
+    @Override
+    public void setSecuredClient(final boolean securedClient) {
+        this.securedClient = securedClient;
+    }
+
+
+    @Override
+    public Boolean call() throws Exception {
+        Preconditions.checkNotNull(bootstrap);
+        Preconditions.checkNotNull(workerGroup);
+        LOG.info("Switch {} trying connect to controller", this.name);
+        SimpleClientInitializer clientInitializer = new SimpleClientInitializer(isOnlineFuture, securedClient);
+        clientInitializer.setScenario(scenarioHandler);
+        try {
+            bootstrap.group(workerGroup)
+                    .channel(NioSocketChannel.class)
+                    .handler(clientInitializer);
+
+            bootstrap.connect(ipAddress, port).sync();
+            synchronized (scenarioHandler) {
+                LOG.debug("WAITING FOR SCENARIO");
+                while (!scenarioHandler.isScenarioFinished()) {
+                    scenarioHandler.wait();
+                }
+            }
+        } catch (Exception ex) {
+            LOG.error(ex.getMessage(), ex);
+            return false;
+        }
+        if (scenarioHandler.isFinishedOK()) {
+            LOG.info("Device {} finished scenario OK", this.name);
+        } else {
+            LOG.error("Device {} finished scenario with error", this.name);
+        }
+        return scenarioHandler.isFinishedOK();
+
+    }
+
+    @Override
+    public void run() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/ControllerConnectionTestTool.java b/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/ControllerConnectionTestTool.java
new file mode 100644 (file)
index 0000000..67367b8
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2016 Pantheon Technologies s.r.o. 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.openflowjava.protocol.impl.clients;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import javax.annotation.Nullable;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+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.LoggerFactory;
+
+/**
+ * ControllerConnectionTestTool class, utilities for testing device's connect
+ * @author Jozef Bacigal
+ * Date: 4.3.2016.
+ */
+public class ControllerConnectionTestTool {
+
+    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(ControllerConnectionTestTool.class);
+
+    public static class Params {
+
+        @Arg(dest = "controller-ip")
+        public String controllerIP;
+
+        @Arg(dest = "devices-count")
+        public int deviceCount;
+
+        @Arg(dest = "ssl")
+        public boolean ssl;
+
+        @Arg(dest = "threads")
+        public int threads;
+
+        @Arg(dest = "port")
+        public int port;
+
+        @Arg(dest = "timeout")
+        public int timeout;
+
+        @Arg(dest = "freeze")
+        public int freeze;
+
+        @Arg(dest = "sleep")
+        public long sleep;
+
+        static ArgumentParser getParser() throws UnknownHostException {
+            final ArgumentParser parser = ArgumentParsers.newArgumentParser("openflowjava test-tool");
+
+            parser.description("Openflowjava switch -> controller connector simulator");
+
+            parser.addArgument("--device-count")
+                    .type(Integer.class)
+                    .setDefault(1)
+                    .help("Number of simulated switches. Has to be more than 0")
+                    .dest("devices-count");
+
+            parser.addArgument("--controller-ip")
+                    .type(String.class)
+                    .setDefault("127.0.0.1")
+                    .help("ODL controller ip address")
+                    .dest("controller-ip");
+
+            parser.addArgument("--ssl")
+                    .type(Boolean.class)
+                    .setDefault(false)
+                    .help("Use secured connection")
+                    .dest("ssl");
+
+            parser.addArgument("--threads")
+                    .type(Integer.class)
+                    .setDefault(1)
+                    .help("Number of threads: MAX 1024")
+                    .dest("threads");
+
+            parser.addArgument("--port")
+                    .type(Integer.class)
+                    .setDefault(6653)
+                    .help("Connection port")
+                    .dest("port");
+
+            parser.addArgument("--timeout")
+                    .type(Integer.class)
+                    .setDefault(60)
+                    .help("Timeout in seconds")
+                    .dest("timeout");
+
+            parser.addArgument("--scenarioTries")
+                    .type(Integer.class)
+                    .setDefault(3)
+                    .help("Number of tries in scenario, while waiting for response")
+                    .dest("freeze");
+
+            parser.addArgument("--timeBetweenScenario")
+                    .type(Long.class)
+                    .setDefault(100)
+                    .help("Waiting time in milliseconds between tries.")
+                    .dest("sleep");
+
+            return parser;
+        }
+
+        void validate() {
+            checkArgument(deviceCount > 0, "Switch count has to be > 0");
+            checkArgument(threads > 0 && threads < 1024, "Switch count has to be > 0 and < 1024");
+        }
+    }
+
+    public static void main(final String[] args) {
+
+        List<Callable<Boolean>> callableList = new ArrayList<>();
+        final EventLoopGroup workerGroup = new NioEventLoopGroup();
+
+        try {
+            final Params params = parseArgs(args, Params.getParser());
+            params.validate();
+
+            for(int loop=0;loop < params.deviceCount; loop++){
+
+                CallableClient cc = new CallableClient(
+                        params.port,
+                        params.ssl,
+                        InetAddress.getByName(params.controllerIP),
+                        "Switch no." + String.valueOf(loop),
+                        new ScenarioHandler(ScenarioFactory.createHandshakeScenarioWithBarrier(), params.freeze, params.sleep),
+                        new Bootstrap(),
+                        workerGroup);
+
+                callableList.add(cc);
+
+            }
+
+            ExecutorService executorService = Executors.newFixedThreadPool(params.threads);
+            final ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(executorService);
+
+            final List<ListenableFuture<Boolean>> listenableFutures = new ArrayList<>();
+            for (Callable<Boolean> booleanCallable : callableList) {
+               listenableFutures.add(listeningExecutorService.submit(booleanCallable));
+            }
+            final ListenableFuture<List<Boolean>> summaryFuture = Futures.successfulAsList(listenableFutures);
+            List<Boolean> booleanList = summaryFuture.get(params.timeout, TimeUnit.SECONDS);
+            Futures.addCallback(summaryFuture, new FutureCallback<List<Boolean>>() {
+                @Override
+                public void onSuccess(@Nullable final List<Boolean> booleanList) {
+                    LOG.info("Tests finished");
+                    workerGroup.shutdownGracefully();
+                    LOG.info("Summary:");
+                    int testsOK = 0;
+                    int testFailure = 0;
+                    for (Boolean aBoolean : booleanList) {
+                        if (aBoolean) {
+                            testsOK++;
+                        } else {
+                            testFailure++;
+                        }
+                    }
+                    LOG.info("Tests OK: {}", testsOK);
+                    LOG.info("Tests failure: {}", testFailure);
+                    System.exit(0);
+                }
+
+                @Override
+                public void onFailure(final Throwable throwable) {
+                    LOG.warn("Tests call failure");
+                    workerGroup.shutdownGracefully();
+                    System.exit(1);
+                }
+            });
+        } catch (Exception e) {
+            LOG.warn("Exception has been thrown: {}", e);
+            System.exit(1);
+        }
+    }
+
+    private static Params parseArgs(final String[] args, final ArgumentParser parser) throws ArgumentParserException {
+        final Params opt = new Params();
+        parser.parseArgs(args, opt);
+        return opt;
+    }
+
+
+}
index 5b4e669aa6d8867043fc35ce6f53d139f9f09790..4e28c4cd74f58ced75b1b9ceb01ef2426e26e0ae 100644 (file)
@@ -44,6 +44,28 @@ public final class ScenarioFactory {
         return stack;
     }
 
+    /**
+     * Creates stack with handshake needed messages. XID of messages:
+     * <ol>
+     *   <li> hello sent - 00000001
+     *   <li> hello waiting - 00000021
+     *   <li> featuresrequest waiting - 00000002
+     *   <li> featuresreply sent - 00000002
+     * </ol>
+     * @return stack filled with Handshake messages
+     */
+    public static Deque<ClientEvent> createHandshakeScenarioWithBarrier() {
+        Deque<ClientEvent> stack = new ArrayDeque<>();
+        stack.addFirst(new SendEvent(ByteBufUtils.hexStringToBytes("04 00 00 08 00 00 00 01")));
+        stack.addFirst(new WaitForMessageEvent(ByteBufUtils.hexStringToBytes("04 00 00 10 00 00 00 15 00 01 00 08 00 00 00 12"))); //Hello message 21
+        stack.addFirst(new WaitForMessageEvent(ByteBufUtils.hexStringToBytes("04 05 00 08 00 00 00 02")));
+        stack.addFirst(new SendEvent(ByteBufUtils.hexStringToBytes("04 06 00 20 00 00 00 02 "
+                + "00 01 02 03 04 05 06 07 00 01 02 03 01 00 00 00 00 01 02 03 00 01 02 03")));
+        stack.addFirst(new WaitForMessageEvent(ByteBufUtils.hexStringToBytes("04 14 00 08 00 00 00 00"))); //Barrier request
+        stack.addFirst(new SendEvent(ByteBufUtils.hexStringToBytes("04 15 00 08 00 00 00 04"))); //Barrier reply
+        return stack;
+    }
+
     /**
      * Creates stack with handshake needed messages. XID of messages:
      * <ol>
index 44abb36d0ab1a3595966ff1ed0bb144e8c84c884..9692cb8ebbdc805593bb3109378b02b6eb0319d0 100644 (file)
@@ -31,6 +31,9 @@ public class ScenarioHandler extends Thread {
     private ChannelHandlerContext ctx;
     private int eventNumber;
     private boolean scenarioFinished = false;
+    private int freeze = 2;
+    private long sleepBetweenTries = 100l;
+    private boolean finishedOK = true;
 
     /**
      *
@@ -41,6 +44,13 @@ public class ScenarioHandler extends Thread {
         ofMsg = new LinkedBlockingQueue<>();
     }
 
+    public ScenarioHandler(Deque<ClientEvent> scenario, int freeze, long sleepBetweenTries){
+        this.scenario = scenario;
+        ofMsg = new LinkedBlockingQueue<>();
+        this.sleepBetweenTries = sleepBetweenTries;
+        this.freeze = freeze;
+    }
+
     @Override
     public void run() {
         int freezeCounter = 0;
@@ -62,18 +72,22 @@ public class ScenarioHandler extends Thread {
                 event.setCtx(ctx);
             }
             if (peek.eventExecuted()) {
+                LOG.info("Scenario step finished OK, moving to next step.");
                 scenario.removeLast();
                 eventNumber++;
                 freezeCounter = 0;
+                finishedOK = true;
             } else {
                 freezeCounter++;
             }
-            if (freezeCounter > 2) {
+            if (freezeCounter > freeze) {
                 LOG.warn("Scenario frozen: {}", freezeCounter);
+                LOG.warn("Scenario step not finished NOT OK!", freezeCounter);
+                this.finishedOK = false;
                 break;
             }
             try {
-                sleep(100);
+                sleep(sleepBetweenTries);
             } catch (InterruptedException e) {
                 LOG.error(e.getMessage(), e);
             }
@@ -126,4 +140,8 @@ public class ScenarioHandler extends Thread {
     public boolean isScenarioFinished() {
         return scenarioFinished;
     }
+
+    public boolean isFinishedOK() {
+        return finishedOK;
+    }
 }
index 59228e3645a6ac4e6703f4ddd48008ab2a1963d5..f57b738f4032fa79e999d54d868f3c6dc0bab50b 100644 (file)
@@ -54,9 +54,11 @@ public class WaitForMessageEvent implements ClientEvent {
      * @param headerReceived header (first 8 bytes) of expected message
      */
     public void setHeaderReceived(byte[] headerReceived) {
-        this.headerReceived = new byte[headerReceived.length];
-        for (int i = 0; i < headerReceived.length; i++) {
-            this.headerReceived[i] = headerReceived[i];
+        if (headerReceived != null) {
+            this.headerReceived = new byte[headerReceived.length];
+            for (int i = 0; i < headerReceived.length; i++) {
+                this.headerReceived[i] = headerReceived[i];
+            }
         }
     }
 }