Add {PHYS_ADDR} attribute to netconf stress client tool
[controller.git] / opendaylight / netconf / netconf-testtool / src / main / java / org / opendaylight / controller / netconf / test / tool / client / stress / StressClient.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.controller.netconf.test.tool.client.stress;
10
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;
54
55 public final class StressClient {
56
57     private static final Logger LOG = LoggerFactory.getLogger(StressClient.class);
58
59     static final QName COMMIT_QNAME = QName.create(CommitInput.QNAME, "commit");
60     public static final NetconfMessage COMMIT_MSG;
61
62     static {
63         try {
64             COMMIT_MSG = new NetconfMessage(XmlUtil.readXmlToDocument("<rpc message-id=\"commit-batch\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n" +
65                     "    <commit/>\n" +
66                     "</rpc>"));
67         } catch (SAXException | IOException e) {
68             throw new ExceptionInInitializerError(e);
69         }
70     }
71
72     static final QName EDIT_QNAME = QName.create(EditConfigInput.QNAME, "edit-config");
73     static final org.w3c.dom.Document editBlueprint;
74
75     static {
76         try {
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" +
80                             "        <target>\n" +
81                             "            <candidate/>\n" +
82                             "        </target>\n" +
83                             "        <config/>\n" +
84                             "    </edit-config>\n" +
85                             "</rpc>");
86         } catch (SAXException | IOException e) {
87             throw new ExceptionInInitializerError(e);
88         }
89     }
90
91     private static final String MSG_ID_PLACEHOLDER_REGEX = "\\{MSG_ID\\}";
92     private static final String PHYS_ADDR_PLACEHOLDER_REGEX = "\\{PHYS_ADDR\\}";
93
94     public static void main(final String[] args) {
95         final Parameters params = parseArgs(args, Parameters.getParser());
96         params.validate();
97
98         // Wait 5 seconds to allow for debugging/profiling
99         try {
100             Thread.sleep(5000);
101         } catch (final InterruptedException e) {
102             throw new RuntimeException(e);
103         }
104
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);
107
108         LOG.info("Preparing messages");
109         // Prepare all msgs up front
110         final List<NetconfMessage> preparedMessages = Lists.newArrayListWithCapacity(params.editCount);
111
112         final String editContentString;
113         try {
114             editContentString = Files.toString(params.editContent, Charsets.UTF_8);
115         } catch (IOException e) {
116             throw new IllegalArgumentException("Cannot read content of " + params.editContent);
117         }
118
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);
123
124             final Element editContentElement;
125             try {
126                 // Insert message id where needed
127                 String specificEditContent =
128                         editContentString.replaceAll(MSG_ID_PLACEHOLDER_REGEX, Integer.toString(i));
129
130                 // Insert physical address where needed
131                 specificEditContent =
132                         specificEditContent.replaceAll(PHYS_ADDR_PLACEHOLDER_REGEX, getMac(i));
133
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);
140             }
141
142             preparedMessages.add(netconfMessage);
143
144         }
145
146
147         final NioEventLoopGroup nioGroup = new NioEventLoopGroup();
148         final Timer timer = new HashedWheelTimer();
149
150         final NetconfClientDispatcherImpl netconfClientDispatcher = configureClientDispatcher(params, nioGroup, timer);
151
152         final NetconfDeviceCommunicator sessionListener = getSessionListener(params.getInetAddress());
153
154         final NetconfClientConfiguration cfg = getNetconfClientConfiguration(params, sessionListener);
155
156         LOG.info("Connecting to netconf server {}:{}", params.ip, params.port);
157         final NetconfClientSession netconfClientSession;
158         try {
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);
164         }
165
166         LOG.info("Starting stress test");
167         final Stopwatch started = Stopwatch.createStarted();
168         getExecutionStrategy(params, preparedMessages, sessionListener).invoke();
169         started.stop();
170
171         LOG.info("FINISHED. Execution time: {}", started);
172         LOG.info("Requests per second: {}", (params.editCount * 1000.0 / started.elapsed(TimeUnit.MILLISECONDS)));
173
174         // Cleanup
175         netconfClientSession.close();
176         timer.stop();
177         try {
178             nioGroup.shutdownGracefully().get(20L, TimeUnit.SECONDS);
179         } catch (InterruptedException | ExecutionException | TimeoutException e) {
180             LOG.warn("Unable to close executor properly", e);
181         }
182     }
183
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);
187
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");
192         }
193         return Joiner.on(':').join(Iterables.concat(Iterables.transform(macGroups, new Function<String, String>() {
194             @Override
195             public String apply(final String input) {
196                 return input.length() == 1 ? input + "0" : input;
197             }
198         }), additionalGroups));
199     }
200
201     private static ExecutionStrategy getExecutionStrategy(final Parameters params, final List<NetconfMessage> preparedMessages, final NetconfDeviceCommunicator sessionListener) {
202         if(params.async) {
203             return new AsyncExecutionStrategy(params, preparedMessages, sessionListener);
204         } else {
205             return new SyncExecutionStrategy(params, preparedMessages, sessionListener);
206         }
207     }
208
209     private static NetconfClientDispatcherImpl configureClientDispatcher(final Parameters params, final NioEventLoopGroup nioGroup, final Timer timer) {
210         final NetconfClientDispatcherImpl netconfClientDispatcher;
211         if(params.exi) {
212             if(params.legacyFraming) {
213                 netconfClientDispatcher= ConfigurableClientDispatcher.createLegacyExi(nioGroup, nioGroup, timer);
214             } else {
215                 netconfClientDispatcher = ConfigurableClientDispatcher.createChunkedExi(nioGroup, nioGroup, timer);
216             }
217         } else {
218             if(params.legacyFraming) {
219                 netconfClientDispatcher = ConfigurableClientDispatcher.createLegacy(nioGroup, nioGroup, timer);
220             } else {
221                 netconfClientDispatcher = ConfigurableClientDispatcher.createChunked(nioGroup, nioGroup, timer);
222             }
223         }
224         return netconfClientDispatcher;
225     }
226
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) {
234                 @Override
235                 public String toFormattedString() {
236                     LOG.debug("Sending TCP header {}", header);
237                     return header;
238                 }
239             });
240         }
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();
245     }
246
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);
250     }
251
252     private static Parameters parseArgs(final String[] args, final ArgumentParser parser) {
253         final Parameters opt = new Parameters();
254         try {
255             parser.parseArgs(args, opt);
256             return opt;
257         } catch (final ArgumentParserException e) {
258             parser.handleError(e);
259         }
260
261         System.exit(1);
262         return null;
263     }
264
265
266     private static class LoggingRemoteDevice implements RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> {
267         @Override
268         public void onRemoteSessionUp(final NetconfSessionPreferences remoteSessionCapabilities, final NetconfDeviceCommunicator netconfDeviceCommunicator) {
269             LOG.info("Session established");
270         }
271
272         @Override
273         public void onRemoteSessionDown() {
274             LOG.info("Session down");
275         }
276
277         @Override
278         public void onRemoteSessionFailed(final Throwable throwable) {
279             LOG.info("Session failed");
280         }
281
282         @Override
283         public void onNotification(final NetconfMessage notification) {
284             LOG.info("Notification received: {}", notification.toString());
285         }
286     }
287
288 }