From 55ede46616f6b0e82f08fad5b168c9bd4f78fd4c Mon Sep 17 00:00:00 2001 From: Jozef Bacigal Date: Mon, 7 Mar 2016 14:29:07 +0100 Subject: [PATCH] Add test device connections utility Change-Id: I89933b0ce057679e959cf2ef576fe10c8a1cc122 Signed-off-by: Jozef Bacigal --- parent/pom.xml | 6 + simple-client/pom.xml | 4 + .../protocol/impl/clients/CallableClient.java | 119 ++++++++++ .../clients/ControllerConnectionTestTool.java | 207 ++++++++++++++++++ .../impl/clients/ScenarioFactory.java | 22 ++ .../impl/clients/ScenarioHandler.java | 22 +- .../impl/clients/WaitForMessageEvent.java | 8 +- 7 files changed, 383 insertions(+), 5 deletions(-) create mode 100644 simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/CallableClient.java create mode 100644 simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/ControllerConnectionTestTool.java diff --git a/parent/pom.xml b/parent/pom.xml index 00cfc5dc..5822d278 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -58,6 +58,7 @@ 1.5.0-SNAPSHOT 0.10.0-SNAPSHOT 1.1.0-SNAPSHOT + 0.7.0 @@ -90,6 +91,11 @@ import pom + + net.sourceforge.argparse4j + argparse4j + ${argparse4j.version} + diff --git a/simple-client/pom.xml b/simple-client/pom.xml index 5eb462b5..76dd3575 100644 --- a/simple-client/pom.xml +++ b/simple-client/pom.xml @@ -45,5 +45,9 @@ org.slf4j slf4j-log4j12 + + net.sourceforge.argparse4j + argparse4j + 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 index 00000000..24b1da3c --- /dev/null +++ b/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/CallableClient.java @@ -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, 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 isOnlineFuture; + private SettableFuture 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 getIsOnlineFuture() { + return isOnlineFuture; + } + + @Override + public SettableFuture 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 index 00000000..67367b82 --- /dev/null +++ b/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/ControllerConnectionTestTool.java @@ -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> 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> listenableFutures = new ArrayList<>(); + for (Callable booleanCallable : callableList) { + listenableFutures.add(listeningExecutorService.submit(booleanCallable)); + } + final ListenableFuture> summaryFuture = Futures.successfulAsList(listenableFutures); + List booleanList = summaryFuture.get(params.timeout, TimeUnit.SECONDS); + Futures.addCallback(summaryFuture, new FutureCallback>() { + @Override + public void onSuccess(@Nullable final List 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; + } + + +} diff --git a/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/ScenarioFactory.java b/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/ScenarioFactory.java index 5b4e669a..4e28c4cd 100644 --- a/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/ScenarioFactory.java +++ b/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/ScenarioFactory.java @@ -44,6 +44,28 @@ public final class ScenarioFactory { return stack; } + /** + * Creates stack with handshake needed messages. XID of messages: + *
    + *
  1. hello sent - 00000001 + *
  2. hello waiting - 00000021 + *
  3. featuresrequest waiting - 00000002 + *
  4. featuresreply sent - 00000002 + *
+ * @return stack filled with Handshake messages + */ + public static Deque createHandshakeScenarioWithBarrier() { + Deque 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: *
    diff --git a/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/ScenarioHandler.java b/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/ScenarioHandler.java index 44abb36d..9692cb8e 100644 --- a/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/ScenarioHandler.java +++ b/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/ScenarioHandler.java @@ -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 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; + } } diff --git a/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/WaitForMessageEvent.java b/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/WaitForMessageEvent.java index 59228e36..f57b738f 100644 --- a/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/WaitForMessageEvent.java +++ b/simple-client/src/main/java/org/opendaylight/openflowjava/protocol/impl/clients/WaitForMessageEvent.java @@ -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]; + } } } } -- 2.36.6