Merge "Make TCP netconf endpoint configurable"
[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.Stopwatch;
14 import com.google.common.io.Files;
15 import io.netty.channel.nio.NioEventLoopGroup;
16 import io.netty.util.HashedWheelTimer;
17 import io.netty.util.Timer;
18 import java.io.IOException;
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.concurrent.ExecutionException;
22 import java.util.concurrent.ExecutorService;
23 import java.util.concurrent.Executors;
24 import java.util.concurrent.Future;
25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.TimeoutException;
27 import net.sourceforge.argparse4j.inf.ArgumentParser;
28 import net.sourceforge.argparse4j.inf.ArgumentParserException;
29 import org.opendaylight.controller.netconf.api.NetconfMessage;
30 import org.opendaylight.controller.netconf.client.NetconfClientDispatcherImpl;
31 import org.opendaylight.controller.netconf.util.xml.XmlUtil;
32 import org.opendaylight.controller.sal.connect.api.RemoteDevice;
33 import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCommunicator;
34 import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences;
35 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.CommitInput;
36 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.EditConfigInput;
37 import org.opendaylight.yangtools.yang.common.QName;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40 import org.w3c.dom.Document;
41 import org.w3c.dom.Element;
42 import org.w3c.dom.Node;
43 import org.xml.sax.SAXException;
44
45 public final class StressClient {
46
47     private static final Logger LOG = LoggerFactory.getLogger(StressClient.class);
48
49     static final QName COMMIT_QNAME = QName.create(CommitInput.QNAME, "commit");
50     public static final NetconfMessage COMMIT_MSG;
51
52     static {
53         try {
54             COMMIT_MSG = new NetconfMessage(XmlUtil.readXmlToDocument("<rpc message-id=\"commit-batch\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n" +
55                     "    <commit/>\n" +
56                     "</rpc>"));
57         } catch (SAXException | IOException e) {
58             throw new ExceptionInInitializerError(e);
59         }
60     }
61
62     static final QName EDIT_QNAME = QName.create(EditConfigInput.QNAME, "edit-config");
63     static final org.w3c.dom.Document editBlueprint;
64
65     static {
66         try {
67             editBlueprint = XmlUtil.readXmlToDocument(
68                     "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n" +
69                             "    <edit-config xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n" +
70                             "        <target>\n" +
71                             "            <candidate/>\n" +
72                             "        </target>\n" +
73                             "        <default-operation>none</default-operation>" +
74                             "        <config/>\n" +
75                             "    </edit-config>\n" +
76                             "</rpc>");
77         } catch (SAXException | IOException e) {
78             throw new ExceptionInInitializerError(e);
79         }
80     }
81
82     private static final String MSG_ID_PLACEHOLDER_REGEX = "\\{MSG_ID\\}";
83     private static final String PHYS_ADDR_PLACEHOLDER = "{PHYS_ADDR}";
84
85     private static long macStart = 0xAABBCCDD0000L;
86
87     public static void main(final String[] args) {
88         final Parameters params = parseArgs(args, Parameters.getParser());
89         params.validate();
90
91         final ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
92         root.setLevel(params.debug ? Level.DEBUG : Level.INFO);
93
94         final int threadAmount = params.threadAmount;
95         LOG.info("thread amount: " + threadAmount);
96         final int requestsPerThread = params.editCount / params.threadAmount;
97         LOG.info("requestsPerThread: " + requestsPerThread);
98         final int leftoverRequests = params.editCount % params.threadAmount;
99         LOG.info("leftoverRequests: " + leftoverRequests);
100
101
102         LOG.info("Preparing messages");
103         // Prepare all msgs up front
104         final List<List<NetconfMessage>> allPreparedMessages = new ArrayList<>(threadAmount);
105         for (int i = 0; i < threadAmount; i++) {
106             if (i != threadAmount - 1) {
107                 allPreparedMessages.add(new ArrayList<NetconfMessage>(requestsPerThread));
108             } else {
109                 allPreparedMessages.add(new ArrayList<NetconfMessage>(requestsPerThread + leftoverRequests));
110             }
111         }
112
113
114         final String editContentString;
115         try {
116             editContentString = Files.toString(params.editContent, Charsets.UTF_8);
117         } catch (final IOException e) {
118             throw new IllegalArgumentException("Cannot read content of " + params.editContent);
119         }
120
121         for (int i = 0; i < threadAmount; i++) {
122             final List<NetconfMessage> preparedMessages = allPreparedMessages.get(i);
123             int padding = 0;
124             if (i == threadAmount - 1) {
125                 padding = leftoverRequests;
126             }
127             for (int j = 0; j < requestsPerThread + padding; j++) {
128                 LOG.debug("id: " + (i * requestsPerThread + j));
129                 preparedMessages.add(prepareMessage(i * requestsPerThread + j, editContentString));
130             }
131         }
132
133         final NioEventLoopGroup nioGroup = new NioEventLoopGroup();
134         final Timer timer = new HashedWheelTimer();
135
136         final NetconfClientDispatcherImpl netconfClientDispatcher = configureClientDispatcher(params, nioGroup, timer);
137
138         final List<StressClientCallable> callables = new ArrayList<>(threadAmount);
139         for (final List<NetconfMessage> messages : allPreparedMessages) {
140             callables.add(new StressClientCallable(params, netconfClientDispatcher, messages));
141         }
142
143         final ExecutorService executorService = Executors.newFixedThreadPool(threadAmount);
144
145         LOG.info("Starting stress test");
146         final Stopwatch started = Stopwatch.createStarted();
147         try {
148             final List<Future<Boolean>> futures = executorService.invokeAll(callables);
149             for (final Future<Boolean> future : futures) {
150                 try {
151                     future.get(4L, TimeUnit.MINUTES);
152                 } catch (ExecutionException | TimeoutException e) {
153                     throw new RuntimeException(e);
154                 }
155             }
156             executorService.shutdownNow();
157         } catch (final InterruptedException e) {
158             throw new RuntimeException("Unable to execute requests", e);
159         }
160         started.stop();
161
162         LOG.info("FINISHED. Execution time: {}", started);
163         LOG.info("Requests per second: {}", (params.editCount * 1000.0 / started.elapsed(TimeUnit.MILLISECONDS)));
164
165         // Cleanup
166         timer.stop();
167         try {
168             nioGroup.shutdownGracefully().get(20L, TimeUnit.SECONDS);
169         } catch (InterruptedException | ExecutionException | TimeoutException e) {
170             LOG.warn("Unable to close executor properly", e);
171         }
172     }
173
174     static NetconfMessage prepareMessage(final int id, final String editContentString) {
175         final Document msg = XmlUtil.createDocumentCopy(editBlueprint);
176         msg.getDocumentElement().setAttribute("message-id", Integer.toString(id));
177         final NetconfMessage netconfMessage = new NetconfMessage(msg);
178
179         final Element editContentElement;
180         try {
181             // Insert message id where needed
182             String specificEditContent = editContentString.replaceAll(MSG_ID_PLACEHOLDER_REGEX, Integer.toString(id));
183
184             final StringBuilder stringBuilder = new StringBuilder(specificEditContent);
185             int idx = stringBuilder.indexOf(PHYS_ADDR_PLACEHOLDER);
186             while (idx!= -1) {
187                 stringBuilder.replace(idx, idx + PHYS_ADDR_PLACEHOLDER.length(), getMac(macStart++));
188                 idx = stringBuilder.indexOf(PHYS_ADDR_PLACEHOLDER);
189             }
190             specificEditContent = stringBuilder.toString();
191
192             editContentElement = XmlUtil.readXmlToElement(specificEditContent);
193             final Node config = ((Element) msg.getDocumentElement().getElementsByTagName("edit-config").item(0)).
194                     getElementsByTagName("config").item(0);
195             config.appendChild(msg.importNode(editContentElement, true));
196         } catch (final IOException | SAXException e) {
197             throw new IllegalArgumentException("Edit content file is unreadable", e);
198         }
199
200         return netconfMessage;
201     }
202
203     private static NetconfClientDispatcherImpl configureClientDispatcher(final Parameters params, final NioEventLoopGroup nioGroup, final Timer timer) {
204         final NetconfClientDispatcherImpl netconfClientDispatcher;
205         if(params.exi) {
206             if(params.legacyFraming) {
207                 netconfClientDispatcher= ConfigurableClientDispatcher.createLegacyExi(nioGroup, nioGroup, timer);
208             } else {
209                 netconfClientDispatcher = ConfigurableClientDispatcher.createChunkedExi(nioGroup, nioGroup, timer);
210             }
211         } else {
212             if(params.legacyFraming) {
213                 netconfClientDispatcher = ConfigurableClientDispatcher.createLegacy(nioGroup, nioGroup, timer);
214             } else {
215                 netconfClientDispatcher = ConfigurableClientDispatcher.createChunked(nioGroup, nioGroup, timer);
216             }
217         }
218         return netconfClientDispatcher;
219     }
220
221     public static String getMac(long mac) {
222         StringBuilder m = new StringBuilder(Long.toString(mac, 16));
223
224         for (int i = m.length(); i < 12; i++) {
225             m.insert(0, "0");
226         }
227
228         for (int j = m.length() - 2; j >= 2; j-=2) {
229             m.insert(j, ":");
230         }
231
232         return m.toString();
233     }
234
235     private static Parameters parseArgs(final String[] args, final ArgumentParser parser) {
236         final Parameters opt = new Parameters();
237         try {
238             parser.parseArgs(args, opt);
239             return opt;
240         } catch (final ArgumentParserException e) {
241             parser.handleError(e);
242         }
243
244         System.exit(1);
245         return null;
246     }
247
248
249     static class LoggingRemoteDevice implements RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> {
250         @Override
251         public void onRemoteSessionUp(final NetconfSessionPreferences remoteSessionCapabilities, final NetconfDeviceCommunicator netconfDeviceCommunicator) {
252             LOG.info("Session established");
253         }
254
255         @Override
256         public void onRemoteSessionDown() {
257             LOG.info("Session down");
258         }
259
260         @Override
261         public void onRemoteSessionFailed(final Throwable throwable) {
262             LOG.info("Session failed");
263         }
264
265         @Override
266         public void onNotification(final NetconfMessage notification) {
267             LOG.info("Notification received: {}", notification.toString());
268         }
269     }
270
271 }