2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
9 package org.opendaylight.controller.netconf.test.tool.client.stress;
11 import ch.qos.logback.classic.Level;
12 import com.google.common.base.Charsets;
13 import com.google.common.base.Function;
14 import com.google.common.base.Joiner;
15 import com.google.common.base.Splitter;
16 import com.google.common.base.Stopwatch;
17 import com.google.common.collect.Iterables;
18 import com.google.common.collect.Lists;
19 import com.google.common.io.Files;
20 import io.netty.channel.nio.NioEventLoopGroup;
21 import io.netty.util.HashedWheelTimer;
22 import io.netty.util.Timer;
23 import io.netty.util.concurrent.GlobalEventExecutor;
24 import java.io.IOException;
25 import java.net.InetSocketAddress;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.concurrent.ExecutionException;
29 import java.util.concurrent.TimeUnit;
30 import java.util.concurrent.TimeoutException;
31 import net.sourceforge.argparse4j.inf.ArgumentParser;
32 import net.sourceforge.argparse4j.inf.ArgumentParserException;
33 import org.opendaylight.controller.netconf.api.NetconfMessage;
34 import org.opendaylight.controller.netconf.client.NetconfClientDispatcherImpl;
35 import org.opendaylight.controller.netconf.client.NetconfClientSession;
36 import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration;
37 import org.opendaylight.controller.netconf.client.conf.NetconfClientConfigurationBuilder;
38 import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
39 import org.opendaylight.controller.netconf.util.xml.XmlUtil;
40 import org.opendaylight.controller.sal.connect.api.RemoteDevice;
41 import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCommunicator;
42 import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences;
43 import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
44 import org.opendaylight.protocol.framework.NeverReconnectStrategy;
45 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.CommitInput;
46 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.EditConfigInput;
47 import org.opendaylight.yangtools.yang.common.QName;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50 import org.w3c.dom.Document;
51 import org.w3c.dom.Element;
52 import org.w3c.dom.Node;
53 import org.xml.sax.SAXException;
55 public final class StressClient {
57 private static final Logger LOG = LoggerFactory.getLogger(StressClient.class);
59 static final QName COMMIT_QNAME = QName.create(CommitInput.QNAME, "commit");
60 public static final NetconfMessage COMMIT_MSG;
64 COMMIT_MSG = new NetconfMessage(XmlUtil.readXmlToDocument("<rpc message-id=\"commit-batch\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n" +
67 } catch (SAXException | IOException e) {
68 throw new ExceptionInInitializerError(e);
72 static final QName EDIT_QNAME = QName.create(EditConfigInput.QNAME, "edit-config");
73 static final org.w3c.dom.Document editBlueprint;
77 editBlueprint = XmlUtil.readXmlToDocument(
78 "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n" +
79 " <edit-config xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n" +
86 } catch (SAXException | IOException e) {
87 throw new ExceptionInInitializerError(e);
91 private static final String MSG_ID_PLACEHOLDER_REGEX = "\\{MSG_ID\\}";
92 private static final String PHYS_ADDR_PLACEHOLDER_REGEX = "\\{PHYS_ADDR\\}";
94 public static void main(final String[] args) {
95 final Parameters params = parseArgs(args, Parameters.getParser());
98 // Wait 5 seconds to allow for debugging/profiling
101 } catch (final InterruptedException e) {
102 throw new RuntimeException(e);
105 final ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
106 root.setLevel(params.debug ? Level.DEBUG : Level.INFO);
108 LOG.info("Preparing messages");
109 // Prepare all msgs up front
110 final List<NetconfMessage> preparedMessages = Lists.newArrayListWithCapacity(params.editCount);
112 final String editContentString;
114 editContentString = Files.toString(params.editContent, Charsets.UTF_8);
115 } catch (IOException e) {
116 throw new IllegalArgumentException("Cannot read content of " + params.editContent);
119 for (int i = 0; i < params.editCount; i++) {
120 final Document msg = XmlUtil.createDocumentCopy(editBlueprint);
121 msg.getDocumentElement().setAttribute("message-id", Integer.toString(i));
122 final NetconfMessage netconfMessage = new NetconfMessage(msg);
124 final Element editContentElement;
126 // Insert message id where needed
127 String specificEditContent =
128 editContentString.replaceAll(MSG_ID_PLACEHOLDER_REGEX, Integer.toString(i));
130 // Insert physical address where needed
131 specificEditContent =
132 specificEditContent.replaceAll(PHYS_ADDR_PLACEHOLDER_REGEX, getMac(i));
134 editContentElement = XmlUtil.readXmlToElement(specificEditContent);
135 final Node config = ((Element) msg.getDocumentElement().getElementsByTagName("edit-config").item(0)).
136 getElementsByTagName("config").item(0);
137 config.appendChild(msg.importNode(editContentElement, true));
138 } catch (final IOException | SAXException e) {
139 throw new IllegalArgumentException("Edit content file is unreadable", e);
142 preparedMessages.add(netconfMessage);
147 final NioEventLoopGroup nioGroup = new NioEventLoopGroup();
148 final Timer timer = new HashedWheelTimer();
150 final NetconfClientDispatcherImpl netconfClientDispatcher = configureClientDispatcher(params, nioGroup, timer);
152 final NetconfDeviceCommunicator sessionListener = getSessionListener(params.getInetAddress());
154 final NetconfClientConfiguration cfg = getNetconfClientConfiguration(params, sessionListener);
156 LOG.info("Connecting to netconf server {}:{}", params.ip, params.port);
157 final NetconfClientSession netconfClientSession;
159 netconfClientSession = netconfClientDispatcher.createClient(cfg).get();
160 } catch (final InterruptedException e) {
161 throw new RuntimeException(e);
162 } catch (final ExecutionException e) {
163 throw new RuntimeException("Unable to connect", e);
166 LOG.info("Starting stress test");
167 final Stopwatch started = Stopwatch.createStarted();
168 getExecutionStrategy(params, preparedMessages, sessionListener).invoke();
171 LOG.info("FINISHED. Execution time: {}", started);
172 LOG.info("Requests per second: {}", (params.editCount * 1000.0 / started.elapsed(TimeUnit.MILLISECONDS)));
175 netconfClientSession.close();
178 nioGroup.shutdownGracefully().get(20L, TimeUnit.SECONDS);
179 } catch (InterruptedException | ExecutionException | TimeoutException e) {
180 LOG.warn("Unable to close executor properly", e);
184 private static String getMac(final int i) {
185 final String hex = Integer.toHexString(i);
186 final Iterable<String> macGroups = Splitter.fixedLength(2).split(hex);
188 final int additional = 6 - Iterables.size(macGroups);
189 final ArrayList<String> additionalGroups = Lists.newArrayListWithCapacity(additional);
190 for (int j = 0; j < additional; j++) {
191 additionalGroups.add("00");
193 return Joiner.on(':').join(Iterables.concat(Iterables.transform(macGroups, new Function<String, String>() {
195 public String apply(final String input) {
196 return input.length() == 1 ? input + "0" : input;
198 }), additionalGroups));
201 private static ExecutionStrategy getExecutionStrategy(final Parameters params, final List<NetconfMessage> preparedMessages, final NetconfDeviceCommunicator sessionListener) {
203 return new AsyncExecutionStrategy(params, preparedMessages, sessionListener);
205 return new SyncExecutionStrategy(params, preparedMessages, sessionListener);
209 private static NetconfClientDispatcherImpl configureClientDispatcher(final Parameters params, final NioEventLoopGroup nioGroup, final Timer timer) {
210 final NetconfClientDispatcherImpl netconfClientDispatcher;
212 if(params.legacyFraming) {
213 netconfClientDispatcher= ConfigurableClientDispatcher.createLegacyExi(nioGroup, nioGroup, timer);
215 netconfClientDispatcher = ConfigurableClientDispatcher.createChunkedExi(nioGroup, nioGroup, timer);
218 if(params.legacyFraming) {
219 netconfClientDispatcher = ConfigurableClientDispatcher.createLegacy(nioGroup, nioGroup, timer);
221 netconfClientDispatcher = ConfigurableClientDispatcher.createChunked(nioGroup, nioGroup, timer);
224 return netconfClientDispatcher;
227 private static NetconfClientConfiguration getNetconfClientConfiguration(final Parameters params, final NetconfDeviceCommunicator sessionListener) {
228 final NetconfClientConfigurationBuilder netconfClientConfigurationBuilder = NetconfClientConfigurationBuilder.create();
229 netconfClientConfigurationBuilder.withSessionListener(sessionListener);
230 netconfClientConfigurationBuilder.withAddress(params.getInetAddress());
231 if(params.tcpHeader != null) {
232 final String header = params.tcpHeader.replaceAll("\"", "").trim() + "\n";
233 netconfClientConfigurationBuilder.withAdditionalHeader(new NetconfHelloMessageAdditionalHeader(null, null, null, null, null) {
235 public String toFormattedString() {
236 LOG.debug("Sending TCP header {}", header);
241 netconfClientConfigurationBuilder.withProtocol(params.ssh ? NetconfClientConfiguration.NetconfClientProtocol.SSH : NetconfClientConfiguration.NetconfClientProtocol.TCP);
242 netconfClientConfigurationBuilder.withConnectionTimeoutMillis(20000L);
243 netconfClientConfigurationBuilder.withReconnectStrategy(new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE, 5000));
244 return netconfClientConfigurationBuilder.build();
247 static NetconfDeviceCommunicator getSessionListener(final InetSocketAddress inetAddress) {
248 final RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> loggingRemoteDevice = new LoggingRemoteDevice();
249 return new NetconfDeviceCommunicator(new RemoteDeviceId("secure-test", inetAddress), loggingRemoteDevice);
252 private static Parameters parseArgs(final String[] args, final ArgumentParser parser) {
253 final Parameters opt = new Parameters();
255 parser.parseArgs(args, opt);
257 } catch (final ArgumentParserException e) {
258 parser.handleError(e);
266 private static class LoggingRemoteDevice implements RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> {
268 public void onRemoteSessionUp(final NetconfSessionPreferences remoteSessionCapabilities, final NetconfDeviceCommunicator netconfDeviceCommunicator) {
269 LOG.info("Session established");
273 public void onRemoteSessionDown() {
274 LOG.info("Session down");
278 public void onRemoteSessionFailed(final Throwable throwable) {
279 LOG.info("Session failed");
283 public void onNotification(final NetconfMessage notification) {
284 LOG.info("Notification received: {}", notification.toString());