From 4e33bae3a75e833a95207ae9d8a99006aaccf360 Mon Sep 17 00:00:00 2001 From: Maros Marsalek Date: Sat, 18 Apr 2015 19:44:46 +0200 Subject: [PATCH] Netconf testtool stress client Change-Id: I4bd351fe17e596e9153a48bf233432b8c8b19ea9 Signed-off-by: Maros Marsalek --- .../client/NetconfClientDispatcherImpl.java | 4 + ...NetconfClientSessionNegotiatorFactory.java | 33 ++- .../netconf/netconf-testtool/edit.txt | 7 + opendaylight/netconf/netconf-testtool/pom.xml | 33 +++ .../client/stress/AsyncExecutionStrategy.java | 101 +++++++ .../stress/ConfigurableClientDispatcher.java | 59 ++++ .../tool/client/stress/ExecutionStrategy.java | 16 ++ .../test/tool/client/stress/Parameters.java | 153 +++++++++++ .../test/tool/client/stress/StressClient.java | 256 ++++++++++++++++++ .../client/stress/SyncExecutionStrategy.java | 105 +++++++ opendaylight/netconf/pom.xml | 2 +- 11 files changed, 765 insertions(+), 4 deletions(-) create mode 100644 opendaylight/netconf/netconf-testtool/edit.txt create mode 100644 opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/AsyncExecutionStrategy.java create mode 100644 opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/ConfigurableClientDispatcher.java create mode 100644 opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/ExecutionStrategy.java create mode 100644 opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/Parameters.java create mode 100644 opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/StressClient.java create mode 100644 opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/SyncExecutionStrategy.java diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientDispatcherImpl.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientDispatcherImpl.java index 5d584f3b98..039900327b 100644 --- a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientDispatcherImpl.java +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientDispatcherImpl.java @@ -32,6 +32,10 @@ public class NetconfClientDispatcherImpl extends AbstractDispatcher createClient(final NetconfClientConfiguration clientConfiguration) { switch (clientConfiguration.getProtocol()) { diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiatorFactory.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiatorFactory.java index ac13729d88..4c5fd1d1ec 100644 --- a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiatorFactory.java +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiatorFactory.java @@ -33,11 +33,22 @@ import org.slf4j.LoggerFactory; public class NetconfClientSessionNegotiatorFactory implements SessionNegotiatorFactory { - public static final Set CLIENT_CAPABILITIES = ImmutableSet.of( + public static final Set EXI_CLIENT_CAPABILITIES = ImmutableSet.of( XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0, XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_1, XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_EXI_1_0); + public static final Set LEGACY_EXI_CLIENT_CAPABILITIES = ImmutableSet.of( + XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0, + XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_EXI_1_0); + + public static final Set DEFAULT_CLIENT_CAPABILITIES = ImmutableSet.of( + XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0, + XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_1); + + public static final Set LEGACY_FRAMING_CLIENT_CAPABILITIES = ImmutableSet.of( + XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0); + private static final Logger LOG = LoggerFactory.getLogger(NetconfClientSessionNegotiatorFactory.class); private static final String START_EXI_MESSAGE_ID = "default-start-exi"; private static final EXIOptions DEFAULT_OPTIONS; @@ -61,19 +72,35 @@ public class NetconfClientSessionNegotiatorFactory implements SessionNegotiatorF DEFAULT_OPTIONS = opts; } + private final Set clientCapabilities; + public NetconfClientSessionNegotiatorFactory(final Timer timer, final Optional additionalHeader, final long connectionTimeoutMillis) { this(timer, additionalHeader, connectionTimeoutMillis, DEFAULT_OPTIONS); } + public NetconfClientSessionNegotiatorFactory(final Timer timer, + final Optional additionalHeader, + final long connectionTimeoutMillis, final Set capabilities) { + this(timer, additionalHeader, connectionTimeoutMillis, DEFAULT_OPTIONS, capabilities); + + } + public NetconfClientSessionNegotiatorFactory(final Timer timer, final Optional additionalHeader, final long connectionTimeoutMillis, final EXIOptions exiOptions) { + this(timer, additionalHeader, connectionTimeoutMillis, exiOptions, EXI_CLIENT_CAPABILITIES); + } + + public NetconfClientSessionNegotiatorFactory(final Timer timer, + final Optional additionalHeader, + final long connectionTimeoutMillis, final EXIOptions exiOptions, final Set capabilities) { this.timer = Preconditions.checkNotNull(timer); this.additionalHeader = additionalHeader; this.connectionTimeoutMillis = connectionTimeoutMillis; this.options = exiOptions; + this.clientCapabilities = capabilities; } @Override @@ -84,9 +111,9 @@ public class NetconfClientSessionNegotiatorFactory implements SessionNegotiatorF NetconfMessage startExiMessage = NetconfStartExiMessage.create(options, START_EXI_MESSAGE_ID); NetconfHelloMessage helloMessage = null; try { - helloMessage = NetconfHelloMessage.createClientHello(CLIENT_CAPABILITIES, additionalHeader); + helloMessage = NetconfHelloMessage.createClientHello(clientCapabilities, additionalHeader); } catch (NetconfDocumentedException e) { - LOG.error("Unable to create client hello message with capabilities {} and additional handler {}",CLIENT_CAPABILITIES,additionalHeader); + LOG.error("Unable to create client hello message with capabilities {} and additional handler {}", clientCapabilities,additionalHeader); throw new IllegalStateException(e); } diff --git a/opendaylight/netconf/netconf-testtool/edit.txt b/opendaylight/netconf/netconf-testtool/edit.txt new file mode 100644 index 0000000000..1e7bbb6312 --- /dev/null +++ b/opendaylight/netconf/netconf-testtool/edit.txt @@ -0,0 +1,7 @@ + + +prefix:threadfactory-naming +name{MSG_ID} +remote-connector-processing-executor + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-testtool/pom.xml b/opendaylight/netconf/netconf-testtool/pom.xml index 6548d87b49..f15dcc3417 100644 --- a/opendaylight/netconf/netconf-testtool/pom.xml +++ b/opendaylight/netconf/netconf-testtool/pom.xml @@ -59,6 +59,10 @@ org.opendaylight.controller netconf-connector-config + + org.opendaylight.controller + sal-netconf-connector + org.opendaylight.controller logback-config @@ -164,6 +168,35 @@ executable + + + stress-client + + shade + + package + + stress-client + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + org.opendaylight.controller.netconf.test.tool.client.stress.StressClient + + + true + executable + + diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/AsyncExecutionStrategy.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/AsyncExecutionStrategy.java new file mode 100644 index 0000000000..7b60a17827 --- /dev/null +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/AsyncExecutionStrategy.java @@ -0,0 +1,101 @@ +/* + * 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.controller.netconf.test.tool.client.stress; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCommunicator; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class AsyncExecutionStrategy implements ExecutionStrategy { + private static final Logger LOG = LoggerFactory.getLogger(AsyncExecutionStrategy.class); + + private final Parameters params; + private final List preparedMessages; + private final NetconfDeviceCommunicator sessionListener; + private final List editBatches; + + public AsyncExecutionStrategy(final Parameters params, final List editConfigMsgs, final NetconfDeviceCommunicator sessionListener) { + this.params = params; + this.preparedMessages = editConfigMsgs; + this.sessionListener = sessionListener; + this.editBatches = countEditBatchSizes(params); + } + + private static List countEditBatchSizes(final Parameters params) { + final List editBatches = Lists.newArrayList(); + if (params.editBatchSize != params.editCount) { + final int fullBatches = params.editCount / params.editBatchSize; + for (int i = 0; i < fullBatches; i++) { + editBatches.add(params.editBatchSize); + } + + if (params.editCount % params.editBatchSize != 0) { + editBatches.add(params.editCount % params.editBatchSize); + } + } else { + editBatches.add(params.editBatchSize); + } + return editBatches; + } + + @Override + public void invoke() { + final AtomicInteger responseCounter = new AtomicInteger(0); + final List>> futures = Lists.newArrayList(); + + int batchI = 0; + for (final Integer editBatch : editBatches) { + for (int i = 0; i < editBatch; i++) { + final int msgId = i + (batchI * params.editBatchSize); + final NetconfMessage msg = preparedMessages.get(msgId); + LOG.debug("Sending message {}", msgId); + if(LOG.isDebugEnabled()) { + LOG.debug("Sending message {}", XmlUtil.toString(msg.getDocument())); + } + final ListenableFuture> netconfMessageFuture = + sessionListener.sendRequest(msg, StressClient.EDIT_QNAME); + futures.add(netconfMessageFuture); + } + batchI++; + LOG.info("Batch {} with size {} sent. Committing", batchI, editBatch); + futures.add(sessionListener.sendRequest(StressClient.COMMIT_MSG, StressClient.COMMIT_QNAME)); + } + + LOG.info("All batches sent. Waiting for responses"); + // Wait for every future + for (final ListenableFuture> future : futures) { + try { + final RpcResult netconfMessageRpcResult = future.get(params.msgTimeout, TimeUnit.SECONDS); + if(netconfMessageRpcResult.isSuccessful()) { + responseCounter.incrementAndGet(); + LOG.debug("Received response {}", responseCounter.get()); + } else { + LOG.warn("Request failed {}", netconfMessageRpcResult); + } + } catch (final InterruptedException e) { + throw new RuntimeException(e); + } catch (final ExecutionException | TimeoutException e) { + throw new RuntimeException("Request not finished", e); + } + } + + Preconditions.checkState(responseCounter.get() == params.editCount + editBatches.size(), "Not all responses were received, only %s from %s", responseCounter.get(), params.editCount + editBatches.size()); + } +} diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/ConfigurableClientDispatcher.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/ConfigurableClientDispatcher.java new file mode 100644 index 0000000000..2d96d8f0aa --- /dev/null +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/ConfigurableClientDispatcher.java @@ -0,0 +1,59 @@ +/* + * 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.controller.netconf.test.tool.client.stress; + +import io.netty.channel.EventLoopGroup; +import io.netty.util.Timer; +import java.util.Set; +import org.opendaylight.controller.netconf.client.NetconfClientDispatcherImpl; +import org.opendaylight.controller.netconf.client.NetconfClientSessionNegotiatorFactory; +import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration; + +public class ConfigurableClientDispatcher extends NetconfClientDispatcherImpl { + + private final Set capabilities; + + private ConfigurableClientDispatcher(final EventLoopGroup bossGroup, final EventLoopGroup workerGroup, final Timer timer, final Set capabilities) { + super(bossGroup, workerGroup, timer); + this.capabilities = capabilities; + } + + /** + * EXI + chunked framing + */ + public static ConfigurableClientDispatcher createChunkedExi(final EventLoopGroup bossGroup, final EventLoopGroup workerGroup, final Timer timer) { + return new ConfigurableClientDispatcher(bossGroup, workerGroup, timer, NetconfClientSessionNegotiatorFactory.EXI_CLIENT_CAPABILITIES); + } + + /** + * EXI + ]]>]]> framing + */ + public static ConfigurableClientDispatcher createLegacyExi(final EventLoopGroup bossGroup, final EventLoopGroup workerGroup, final Timer timer) { + return new ConfigurableClientDispatcher(bossGroup, workerGroup, timer, NetconfClientSessionNegotiatorFactory.LEGACY_EXI_CLIENT_CAPABILITIES); + } + + /** + * Chunked framing + */ + public static ConfigurableClientDispatcher createChunked(final EventLoopGroup bossGroup, final EventLoopGroup workerGroup, final Timer timer) { + return new ConfigurableClientDispatcher(bossGroup, workerGroup, timer, NetconfClientSessionNegotiatorFactory.DEFAULT_CLIENT_CAPABILITIES); + } + + /** + * ]]>]]> framing + */ + public static ConfigurableClientDispatcher createLegacy(final EventLoopGroup bossGroup, final EventLoopGroup workerGroup, final Timer timer) { + return new ConfigurableClientDispatcher(bossGroup, workerGroup, timer, NetconfClientSessionNegotiatorFactory.LEGACY_FRAMING_CLIENT_CAPABILITIES); + } + + @Override + protected NetconfClientSessionNegotiatorFactory getNegotiatorFactory(final NetconfClientConfiguration cfg) { + return new NetconfClientSessionNegotiatorFactory(getTimer(), cfg.getAdditionalHeader(), cfg.getConnectionTimeoutMillis(), capabilities); + } +} diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/ExecutionStrategy.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/ExecutionStrategy.java new file mode 100644 index 0000000000..dc2ddf1d69 --- /dev/null +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/ExecutionStrategy.java @@ -0,0 +1,16 @@ +/* + * 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.controller.netconf.test.tool.client.stress; + +/** + * Created by mmarsale on 18.4.2015. + */ +public interface ExecutionStrategy { + void invoke(); +} diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/Parameters.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/Parameters.java new file mode 100644 index 0000000000..6648bd4b52 --- /dev/null +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/Parameters.java @@ -0,0 +1,153 @@ +/* + * 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.controller.netconf.test.tool.client.stress; + +import com.google.common.base.Preconditions; +import java.io.File; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import net.sourceforge.argparse4j.ArgumentParsers; +import net.sourceforge.argparse4j.annotation.Arg; +import net.sourceforge.argparse4j.inf.ArgumentParser; + +public class Parameters { + + @Arg(dest = "ip") + public String ip; + + @Arg(dest = "port") + public int port; + + @Arg(dest = "edit-count") + public int editCount; + + @Arg(dest = "edit-content") + public File editContent; + + @Arg(dest = "edit-batch-size") + public int editBatchSize; + + @Arg(dest = "debug") + public boolean debug; + + @Arg(dest = "legacy-framing") + public boolean legacyFraming; + + @Arg(dest = "exi") + public boolean exi; + + @Arg(dest = "async") + public boolean async; + + @Arg(dest = "ssh") + public boolean ssh; + + @Arg(dest = "msg-timeout") + public long msgTimeout; + + static ArgumentParser getParser() { + final ArgumentParser parser = ArgumentParsers.newArgumentParser("netconf stress client"); + + parser.description("Netconf stress client"); + + parser.addArgument("--ip") + .type(String.class) + .setDefault("127.0.0.1") + .type(String.class) + .help("Netconf server IP") + .dest("ip"); + + parser.addArgument("--port") + .type(Integer.class) + .setDefault(2830) + .type(Integer.class) + .help("Netconf server port") + .dest("port"); + + parser.addArgument("--edits") + .type(Integer.class) + .setDefault(50000) + .type(Integer.class) + .help("Netconf edit rpcs to be sent") + .dest("edit-count"); + + parser.addArgument("--edit-content") + .type(File.class) + .setDefault(new File("edit.txt")) + .type(File.class) + .dest("edit-content"); + + parser.addArgument("--edit-batch-size") + .type(Integer.class) + .required(false) + .setDefault(-1) + .type(Integer.class) + .dest("edit-batch-size"); + + parser.addArgument("--debug") + .type(Boolean.class) + .setDefault(false) + .help("Whether to use debug log level instead of INFO") + .dest("debug"); + + parser.addArgument("--legacy-framing") + .type(Boolean.class) + .setDefault(false) + .dest("legacy-framing"); + + parser.addArgument("--exi") + .type(Boolean.class) + .setDefault(false) + .dest("exi"); + + parser.addArgument("--async-requests") + .type(Boolean.class) + .setDefault(true) + .dest("async"); + + parser.addArgument("--msg-timeout") + .type(Integer.class) + .setDefault(60) + .dest("msg-timeout"); + + parser.addArgument("--ssh") + .type(Boolean.class) + .setDefault(false) + .dest("ssh"); + + // TODO add get-config option instead of edit + commit + // TODO different edit config content + + return parser; + } + + void validate() { + Preconditions.checkArgument(port > 0, "Port =< 0"); + Preconditions.checkArgument(editCount > 0, "Edit count =< 0"); + if (editBatchSize == -1) { + editBatchSize = editCount; + } else { + Preconditions.checkArgument(editBatchSize <= editCount, "Edit count =< 0"); + } + + Preconditions.checkArgument(editContent.exists(), "Edit content file missing"); + Preconditions.checkArgument(editContent.isDirectory() == false, "Edit content file is a dir"); + Preconditions.checkArgument(editContent.canRead(), "Edit content file is unreadable"); + // TODO validate + } + + public InetSocketAddress getInetAddress() { + try { + return new InetSocketAddress(InetAddress.getByName(ip), port); + } catch (final UnknownHostException e) { + throw new IllegalArgumentException("Unknown ip", e); + } + } +} diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/StressClient.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/StressClient.java new file mode 100644 index 0000000000..fe0a0bcd52 --- /dev/null +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/StressClient.java @@ -0,0 +1,256 @@ +/* + * 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.controller.netconf.test.tool.client.stress; + +import ch.qos.logback.classic.Level; +import com.google.common.base.Charsets; +import com.google.common.base.Stopwatch; +import com.google.common.collect.Lists; +import com.google.common.io.Files; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timer; +import io.netty.util.concurrent.GlobalEventExecutor; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import net.sourceforge.argparse4j.inf.ArgumentParser; +import net.sourceforge.argparse4j.inf.ArgumentParserException; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.client.NetconfClientDispatcherImpl; +import org.opendaylight.controller.netconf.client.NetconfClientSession; +import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration; +import org.opendaylight.controller.netconf.client.conf.NetconfClientConfigurationBuilder; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.controller.sal.connect.api.RemoteDevice; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCommunicator; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.protocol.framework.NeverReconnectStrategy; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.CommitInput; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.EditConfigInput; +import org.opendaylight.yangtools.yang.common.QName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +public final class StressClient { + + private static final Logger LOG = LoggerFactory.getLogger(StressClient.class); + + static final QName COMMIT_QNAME = QName.create(CommitInput.QNAME, "commit"); + public static final NetconfMessage COMMIT_MSG; + + static { + try { + COMMIT_MSG = new NetconfMessage(XmlUtil.readXmlToDocument("\n" + + " \n" + + "")); + } catch (SAXException | IOException e) { + throw new ExceptionInInitializerError(e); + } + } + + static final QName EDIT_QNAME = QName.create(EditConfigInput.QNAME, "edit-config"); + static final org.w3c.dom.Document editBlueprint; + + static { + try { + editBlueprint = XmlUtil.readXmlToDocument( + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""); + } catch (SAXException | IOException e) { + throw new ExceptionInInitializerError(e); + } + } + + private static final String MSG_ID_PLACEHOLDER = "{MSG_ID}"; + private static final String MSG_ID_PLACEHOLDER_REGEX = "\\{MSG_ID\\}"; + + public static void main(final String[] args) { + final Parameters params = parseArgs(args, Parameters.getParser()); + params.validate(); + + // TODO remove + try { + Thread.sleep(10000); + } catch (final InterruptedException e) { +// e.printStackTrace(); + } + + 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); + + LOG.info("Preparing messages"); + // Prepare all msgs up front + final List preparedMessages = Lists.newArrayListWithCapacity(params.editCount); + + final String editContentString; + boolean needsModification = false; + try { + editContentString = Files.toString(params.editContent, Charsets.UTF_8); + if(editContentString.contains(MSG_ID_PLACEHOLDER)) { + needsModification = true; + }; + } catch (IOException e) { + throw new IllegalArgumentException("Cannot read content of " + params.editContent); + } + + for (int i = 0; i < params.editCount; i++) { + final Document msg = XmlUtil.createDocumentCopy(editBlueprint); + msg.getDocumentElement().setAttribute("message-id", Integer.toString(i)); + final NetconfMessage netconfMessage = new NetconfMessage(msg); + + final Element editContentElement; + try { + // Insert message id where needed + final String specificEditContent = needsModification ? + editContentString.replaceAll(MSG_ID_PLACEHOLDER_REGEX, Integer.toString(i)) : + editContentString; + + editContentElement = XmlUtil.readXmlToElement(specificEditContent); + final Node config = ((Element) msg.getDocumentElement().getElementsByTagName("edit-config").item(0)). + getElementsByTagName("config").item(0); + config.appendChild(msg.importNode(editContentElement, true)); + } catch (final IOException | SAXException e) { + throw new IllegalArgumentException("Edit content file is unreadable", e); + } + + preparedMessages.add(netconfMessage); + + } + + + final NioEventLoopGroup nioGroup = new NioEventLoopGroup(); + final Timer timer = new HashedWheelTimer(); + + final NetconfClientDispatcherImpl netconfClientDispatcher = configureClientDispatcher(params, nioGroup, timer); + + final NetconfDeviceCommunicator sessionListener = getSessionListener(params.getInetAddress()); + + final NetconfClientConfiguration cfg = getNetconfClientConfiguration(params, sessionListener); + + LOG.info("Connecting to netconf server {}:{}", params.ip, params.port); + final NetconfClientSession netconfClientSession; + try { + netconfClientSession = netconfClientDispatcher.createClient(cfg).get(); + } catch (final InterruptedException e) { + throw new RuntimeException(e); + } catch (final ExecutionException e) { + throw new RuntimeException("Unable to connect", e); + } + + LOG.info("Starting stress test"); + final Stopwatch started = Stopwatch.createStarted(); + getExecutionStrategy(params, preparedMessages, sessionListener).invoke(); + started.stop(); + + LOG.info("FINISHED. Execution time: {}", started); + LOG.info("Requests per second: {}", (params.editCount * 1000.0 / started.elapsed(TimeUnit.MILLISECONDS))); + + // Cleanup + netconfClientSession.close(); + timer.stop(); + try { + nioGroup.shutdownGracefully().get(20L, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + LOG.warn("Unable to close executor properly", e); + } + } + + private static ExecutionStrategy getExecutionStrategy(final Parameters params, final List preparedMessages, final NetconfDeviceCommunicator sessionListener) { + if(params.async) { + return new AsyncExecutionStrategy(params, preparedMessages, sessionListener); + } else { + return new SyncExecutionStrategy(params, preparedMessages, sessionListener); + } + } + + private static NetconfClientDispatcherImpl configureClientDispatcher(final Parameters params, final NioEventLoopGroup nioGroup, final Timer timer) { + final NetconfClientDispatcherImpl netconfClientDispatcher; + if(params.exi) { + if(params.legacyFraming) { + netconfClientDispatcher= ConfigurableClientDispatcher.createLegacyExi(nioGroup, nioGroup, timer); + } else { + netconfClientDispatcher = ConfigurableClientDispatcher.createChunkedExi(nioGroup, nioGroup, timer); + } + } else { + if(params.legacyFraming) { + netconfClientDispatcher = ConfigurableClientDispatcher.createLegacy(nioGroup, nioGroup, timer); + } else { + netconfClientDispatcher = ConfigurableClientDispatcher.createChunked(nioGroup, nioGroup, timer); + } + } + return netconfClientDispatcher; + } + + private static NetconfClientConfiguration getNetconfClientConfiguration(final Parameters params, final NetconfDeviceCommunicator sessionListener) { + final NetconfClientConfigurationBuilder netconfClientConfigurationBuilder = NetconfClientConfigurationBuilder.create(); + netconfClientConfigurationBuilder.withSessionListener(sessionListener); + netconfClientConfigurationBuilder.withAddress(params.getInetAddress()); + netconfClientConfigurationBuilder.withProtocol(params.ssh ? NetconfClientConfiguration.NetconfClientProtocol.SSH : NetconfClientConfiguration.NetconfClientProtocol.TCP); + netconfClientConfigurationBuilder.withConnectionTimeoutMillis(20000L); + netconfClientConfigurationBuilder.withReconnectStrategy(new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE, 5000)); + return netconfClientConfigurationBuilder.build(); + } + + static NetconfDeviceCommunicator getSessionListener(final InetSocketAddress inetAddress) { + final RemoteDevice loggingRemoteDevice = new LoggingRemoteDevice(); + return new NetconfDeviceCommunicator(new RemoteDeviceId("secure-test", inetAddress), loggingRemoteDevice); + } + + private static Parameters parseArgs(final String[] args, final ArgumentParser parser) { + final Parameters opt = new Parameters(); + try { + parser.parseArgs(args, opt); + return opt; + } catch (final ArgumentParserException e) { + parser.handleError(e); + } + + System.exit(1); + return null; + } + + + private static class LoggingRemoteDevice implements RemoteDevice { + @Override + public void onRemoteSessionUp(final NetconfSessionPreferences remoteSessionCapabilities, final NetconfDeviceCommunicator netconfDeviceCommunicator) { + LOG.info("Session established"); + } + + @Override + public void onRemoteSessionDown() { + LOG.info("Session down"); + } + + @Override + public void onRemoteSessionFailed(final Throwable throwable) { + LOG.info("Session failed"); + } + + @Override + public void onNotification(final NetconfMessage notification) { + LOG.info("Notification received: {}", notification.toString()); + } + } + +} diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/SyncExecutionStrategy.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/SyncExecutionStrategy.java new file mode 100644 index 0000000000..34142a7f2a --- /dev/null +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/client/stress/SyncExecutionStrategy.java @@ -0,0 +1,105 @@ +/* + * 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.controller.netconf.test.tool.client.stress; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCommunicator; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// TODO reuse code from org.opendaylight.controller.netconf.test.tool.client.stress.AsyncExecutionStrategy +class SyncExecutionStrategy implements ExecutionStrategy { + private static final Logger LOG = LoggerFactory.getLogger(SyncExecutionStrategy.class); + + private final Parameters params; + private final List preparedMessages; + private final NetconfDeviceCommunicator sessionListener; + private final List editBatches; + + public SyncExecutionStrategy(final Parameters params, final List preparedMessages, final NetconfDeviceCommunicator sessionListener) { + this.params = params; + this.preparedMessages = preparedMessages; + this.sessionListener = sessionListener; + editBatches = countEditBatchSizes(params); + } + + private static List countEditBatchSizes(final Parameters params) { + final List editBatches = Lists.newArrayList(); + if (params.editBatchSize != params.editCount) { + final int fullBatches = params.editCount / params.editBatchSize; + for (int i = 0; i < fullBatches; i++) { + editBatches.add(params.editBatchSize); + } + + if (params.editCount % params.editBatchSize != 0) { + editBatches.add(params.editCount % params.editBatchSize); + } + } else { + editBatches.add(params.editBatchSize); + } + return editBatches; + } + + public void invoke() { + final AtomicInteger responseCounter = new AtomicInteger(0); + + int batchI = 0; + for (final Integer editBatch : editBatches) { + for (int i = 0; i < editBatch; i++) { + final int msgId = i + (batchI * params.editBatchSize); + final NetconfMessage msg = preparedMessages.get(msgId); + LOG.debug("Sending message {}", msgId); + if(LOG.isDebugEnabled()) { + LOG.debug("Sending message {}", XmlUtil.toString(msg.getDocument())); + } + final ListenableFuture> netconfMessageFuture = + sessionListener.sendRequest(msg, StressClient.EDIT_QNAME); + // Wait for response + waitForResponse(responseCounter, netconfMessageFuture); + + } + batchI++; + LOG.info("Batch {} with size {} sent. Committing", batchI, editBatch); + + // Commit batch sync + waitForResponse(responseCounter, + sessionListener.sendRequest(StressClient.COMMIT_MSG, StressClient.COMMIT_QNAME)); + } + + Preconditions.checkState(responseCounter.get() == params.editCount + editBatches.size(), "Not all responses were received, only %s from %s", responseCounter.get(), params.editCount + editBatches.size()); + } + + private void waitForResponse(AtomicInteger responseCounter, final ListenableFuture> netconfMessageFuture) { + try { + final RpcResult netconfMessageRpcResult = + netconfMessageFuture.get(params.msgTimeout, TimeUnit.SECONDS); + if (netconfMessageRpcResult.isSuccessful()) { + responseCounter.incrementAndGet(); + LOG.debug("Received response {}", responseCounter.get()); + } else { + LOG.warn("Request failed {}", netconfMessageRpcResult); + } + + } catch (final InterruptedException e) { + throw new RuntimeException(e); + } catch (final ExecutionException | TimeoutException e) { + throw new RuntimeException("Request not finished", e); + } + } +} diff --git a/opendaylight/netconf/pom.xml b/opendaylight/netconf/pom.xml index a990b5c6cb..ab92128dbd 100644 --- a/opendaylight/netconf/pom.xml +++ b/opendaylight/netconf/pom.xml @@ -123,7 +123,7 @@ true ${project.basedir} **\/*.java,**\/*.xml,**\/*.ini,**\/*.sh,**\/*.bat,**\/*.yang - **\/target\/,**\/bin\/,**\/target-ide\/,**\/${jmxGeneratorPath}\/,**\/${salGeneratorPath}\/,**\/netconf\/test\/tool\/Main.java + **\/target\/,**\/bin\/,**\/target-ide\/,**\/${jmxGeneratorPath}\/,**\/${salGeneratorPath}\/,**\/netconf\/test\/tool\/Main.java, **\/netconf\/test\/tool\/client\/stress\/StressClient.java -- 2.36.6