Add a batching system in Netconf testtool
[netconf.git] / netconf / tools / netconf-testtool / src / main / java / org / opendaylight / netconf / test / tool / TesttoolParameters.java
1 /*
2  * Copyright (c) 2016 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.netconf.test.tool;
10
11 import static com.google.common.base.Preconditions.checkArgument;
12
13 import com.google.common.base.Charsets;
14 import com.google.common.base.Preconditions;
15 import com.google.common.io.CharStreams;
16 import com.google.common.io.Files;
17 import java.io.File;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.io.InputStreamReader;
21 import java.util.ArrayList;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.concurrent.TimeUnit;
25 import java.util.regex.Pattern;
26 import net.sourceforge.argparse4j.ArgumentParsers;
27 import net.sourceforge.argparse4j.annotation.Arg;
28 import net.sourceforge.argparse4j.inf.ArgumentParser;
29 import net.sourceforge.argparse4j.inf.ArgumentParserException;
30
31 public class TesttoolParameters {
32
33     private static final String HOST_KEY = "{HOST}";
34     private static final String PORT_KEY = "{PORT}";
35     private static final String SSH = "{SSH}";
36     private static final String ADDRESS_PORT = "{ADDRESS:PORT}";
37     private static final String dest = "http://{ADDRESS:PORT}/restconf/config/network-topology:network-topology/topology/topology-netconf/";
38
39     private static final String RESOURCE = "/config-template.json";
40     @Arg(dest = "edit-content")
41     public File editContent;
42     @Arg(dest = "async")
43     public boolean async;
44     @Arg(dest = "thread-amount")
45     public int threadAmount;
46     @Arg(dest = "throttle")
47     public int throttle;
48     @Arg(dest = "auth")
49     public ArrayList<String> auth;
50     @Arg(dest = "controller-destination")
51     public String controllerDestination;
52     @Arg(dest = "schemas-dir")
53     public File schemasDir;
54     @Arg(dest = "devices-count")
55     public int deviceCount;
56     @Arg(dest = "devices-per-port")
57     public int devicesPerPort;
58     @Arg(dest = "starting-port")
59     public int startingPort;
60     @Arg(dest = "generate-config-connection-timeout")
61     public int generateConfigsTimeout;
62     @Arg(dest = "generate-config-address")
63     public String generateConfigsAddress;
64     @Arg(dest = "distro-folder")
65     public File distroFolder;
66     @Arg(dest = "generate-configs-batch-size")
67     public int generateConfigBatchSize;
68     @Arg(dest = "ssh")
69     public boolean ssh;
70     @Arg(dest = "exi")
71     public boolean exi;
72     @Arg(dest = "debug")
73     public boolean debug;
74     @Arg(dest = "notification-file")
75     public File notificationFile;
76     @Arg(dest = "md-sal")
77     public boolean mdSal;
78     @Arg(dest = "initial-config-xml-file")
79     public File initialConfigXMLFile;
80     @Arg(dest = "time-out")
81     public long timeOut;
82     private InputStream stream;
83
84     static ArgumentParser getParser() {
85         final ArgumentParser parser = ArgumentParsers.newArgumentParser("netconf testtool");
86
87         parser.description("netconf testtool");
88
89         parser.addArgument("--edit-content")
90                 .type(String.class)
91                 .dest("edit-content");
92
93         parser.addArgument("--async-requests")
94                 .type(Boolean.class)
95                 .setDefault(false)
96                 .dest("async");
97
98         parser.addArgument("--thread-amount")
99                 .type(Integer.class)
100                 .setDefault(1)
101                 .dest("thread-amount");
102
103         parser.addArgument("--throttle")
104                 .type(Integer.class)
105                 .setDefault(5000)
106                 .help("Maximum amount of async requests that can be open at a time, " +
107                         "with mutltiple threads this gets divided among all threads")
108                 .dest("throttle");
109
110         parser.addArgument("--auth")
111                 .nargs(2)
112                 .help("Username and password for HTTP basic authentication in order username password.")
113                 .dest("auth");
114
115         parser.addArgument("--controller-destination")
116                 .type(String.class)
117                 .help("Ip address and port of controller. Must be in following format <ip>:<port> " +
118                         "if available it will be used for spawning netconf connectors via topology configuration as " +
119                         "a part of URI. Example (http://<controller destination>/restconf/config/network-topology:network-topology/topology/topology-netconf/node/<node-id>)" +
120                         "otherwise it will just start simulated devices and skip the execution of PUT requests")
121                 .dest("controller-destination");
122
123         parser.addArgument("--device-count")
124                 .type(Integer.class)
125                 .setDefault(1)
126                 .help("Number of simulated netconf devices to spin. This is the number of actual ports open for the devices.")
127                 .dest("devices-count");
128
129         parser.addArgument("--devices-per-port")
130                 .type(Integer.class)
131                 .setDefault(1)
132                 .help("Amount of config files generated per port to spoof more devices then are actually running")
133                 .dest("devices-per-port");
134
135         parser.addArgument("--schemas-dir")
136                 .type(File.class)
137                 .help("Directory containing yang schemas to describe simulated devices. Some schemas e.g. netconf monitoring and inet types are included by default")
138                 .dest("schemas-dir");
139
140         parser.addArgument("--notification-file")
141                 .type(File.class)
142                 .help("Xml file containing notifications that should be sent to clients after create subscription is called")
143                 .dest("notification-file");
144
145         parser.addArgument("--initial-config-xml-file")
146                 .type(File.class)
147                 .help("Xml file containing initial simulatted configuration to be returned via get-config rpc")
148                 .dest("initial-config-xml-file");
149
150         parser.addArgument("--starting-port")
151                 .type(Integer.class)
152                 .setDefault(17830)
153                 .help("First port for simulated device. Each other device will have previous+1 port number")
154                 .dest("starting-port");
155
156         parser.addArgument("--generate-config-connection-timeout")
157                 .type(Integer.class)
158                 .setDefault((int) TimeUnit.MINUTES.toMillis(30))
159                 .help("Timeout to be generated in initial config files")
160                 .dest("generate-config-connection-timeout");
161
162         parser.addArgument("--generate-config-address")
163                 .type(String.class)
164                 .setDefault("127.0.0.1")
165                 .help("Address to be placed in generated configs")
166                 .dest("generate-config-address");
167
168         parser.addArgument("--generate-configs-batch-size")
169                 .type(Integer.class)
170                 .setDefault(1)
171                 .help("Number of connector configs per generated file")
172                 .dest("generate-configs-batch-size");
173
174         parser.addArgument("--distribution-folder")
175                 .type(File.class)
176                 .help("Directory where the karaf distribution for controller is located")
177                 .dest("distro-folder");
178
179         parser.addArgument("--ssh")
180                 .type(Boolean.class)
181                 .setDefault(true)
182                 .help("Whether to use ssh for transport or just pure tcp")
183                 .dest("ssh");
184
185         parser.addArgument("--exi")
186                 .type(Boolean.class)
187                 .setDefault(true)
188                 .help("Whether to use exi to transport xml content")
189                 .dest("exi");
190
191         parser.addArgument("--debug")
192                 .type(Boolean.class)
193                 .setDefault(false)
194                 .help("Whether to use debug log level instead of INFO")
195                 .dest("debug");
196
197         parser.addArgument("--md-sal")
198                 .type(Boolean.class)
199                 .setDefault(false)
200                 .help("Whether to use md-sal datastore instead of default simulated datastore.")
201                 .dest("md-sal");
202
203         parser.addArgument("--time-out")
204                 .type(long.class)
205                 .setDefault(20)
206                 .help("the maximum time in seconds for executing each PUT request")
207                 .dest("time-out");
208
209         return parser;
210     }
211
212     public static TesttoolParameters parseArgs(final String[] args, final ArgumentParser parser) {
213         final TesttoolParameters opt = new TesttoolParameters();
214         try {
215             parser.parseArgs(args, opt);
216             return opt;
217         } catch (final ArgumentParserException e) {
218             parser.handleError(e);
219         }
220
221         System.exit(1);
222         return null;
223     }
224
225     private static String modifyMessage(final StringBuilder payloadBuilder, final int payloadPosition, final int size) {
226         if (size == 1) {
227             return payloadBuilder.toString();
228         }
229
230         if (payloadPosition == 0) {
231             payloadBuilder.insert(payloadBuilder.toString().indexOf('{', 2), "[");
232             payloadBuilder.replace(payloadBuilder.length() - 1, payloadBuilder.length(), ",");
233         } else if (payloadPosition + 1 == size) {
234             payloadBuilder.delete(0, payloadBuilder.toString().indexOf(':') + 1);
235             payloadBuilder.insert(payloadBuilder.toString().indexOf('}', 2) + 1, "]");
236         } else {
237             payloadBuilder.delete(0, payloadBuilder.toString().indexOf(':') + 1);
238             payloadBuilder.replace(payloadBuilder.length() - 2, payloadBuilder.length() - 1, ",");
239             payloadBuilder.deleteCharAt(payloadBuilder.toString().lastIndexOf('}'));
240         }
241         return payloadBuilder.toString();
242     }
243
244     void validate() {
245         if (editContent == null) {
246             stream = TesttoolParameters.class.getResourceAsStream(RESOURCE);
247         } else {
248             Preconditions.checkArgument(!editContent.isDirectory(), "Edit content file is a dir");
249             Preconditions.checkArgument(editContent.canRead(), "Edit content file is unreadable");
250         }
251
252         if (controllerDestination != null) {
253             Preconditions.checkArgument(controllerDestination.contains(":"), "Controller Destination needs to be in a following format <ip>:<port>");
254             String[] parts = controllerDestination.split(Pattern.quote(":"));
255             Preconditions.checkArgument(Integer.parseInt(parts[1]) > 0, "Port =< 0");
256         }
257
258         checkArgument(deviceCount > 0, "Device count has to be > 0");
259         checkArgument(startingPort > 1023, "Starting port has to be > 1023");
260         checkArgument(devicesPerPort > 0, "Atleast one device per port needed");
261
262         if (schemasDir != null) {
263             checkArgument(schemasDir.exists(), "Schemas dir has to exist");
264             checkArgument(schemasDir.isDirectory(), "Schemas dir has to be a directory");
265             checkArgument(schemasDir.canRead(), "Schemas dir has to be readable");
266         }
267     }
268
269     public ArrayList<ArrayList<Execution.DestToPayload>> getThreadsPayloads(final List<Integer> openDevices) {
270         final String editContentString;
271         try {
272             if (stream == null) {
273                 editContentString = Files.toString(editContent, Charsets.UTF_8);
274             } else {
275                 editContentString = CharStreams.toString(new InputStreamReader(stream, Charsets.UTF_8));
276             }
277         } catch (final IOException e) {
278             throw new IllegalArgumentException("Cannot read content of " + editContent);
279         }
280
281         int from, to;
282         Iterator<Integer> iterator;
283
284         final ArrayList<ArrayList<Execution.DestToPayload>> allThreadsPayloads = new ArrayList<>();
285         if (generateConfigBatchSize > 1) {
286
287             final int batchedRequests = openDevices.size() / generateConfigBatchSize;
288             final int batchedRequestsPerThread = batchedRequests / threadAmount;
289             final int leftoverBatchedRequests = (batchedRequests) % threadAmount;
290             final int leftoverRequests = openDevices.size() - (batchedRequests * generateConfigBatchSize);
291
292             final StringBuilder destBuilder = new StringBuilder(dest);
293             destBuilder.replace(destBuilder.indexOf(ADDRESS_PORT), destBuilder.indexOf(ADDRESS_PORT) + ADDRESS_PORT.length(), controllerDestination);
294
295             for (int l = 0; l < threadAmount; l++) {
296                 from = l * (batchedRequests * batchedRequestsPerThread);
297                 to = from + (batchedRequests * batchedRequestsPerThread);
298                 iterator = openDevices.subList(from, to).iterator();
299                 allThreadsPayloads.add(createBatchedPayloads(batchedRequestsPerThread, iterator, editContentString, destBuilder.toString()));
300             }
301             ArrayList<Execution.DestToPayload> payloads = null;
302             if (leftoverBatchedRequests > 0) {
303                 from = threadAmount * (batchedRequests * batchedRequestsPerThread);
304                 to = from + (batchedRequests * batchedRequestsPerThread);
305                 iterator = openDevices.subList(from, to).iterator();
306                 payloads = createBatchedPayloads(leftoverBatchedRequests, iterator, editContentString, destBuilder.toString());
307             }
308             String payload = "";
309
310             for (int j = 0; j < leftoverRequests; j++) {
311                 from = openDevices.size() - leftoverRequests;
312                 to = openDevices.size();
313                 iterator = openDevices.subList(from, to).iterator();
314                 final StringBuilder payloadBuilder = new StringBuilder(prepareMessage(iterator.next(), editContentString));
315                 payload += modifyMessage(payloadBuilder, j, leftoverRequests);
316             }
317             if (leftoverRequests > 0 || leftoverBatchedRequests > 0) {
318
319                 if (payloads != null) {
320                     payloads.add(new Execution.DestToPayload(destBuilder.toString(), payload));
321                 }
322                 allThreadsPayloads.add(payloads);
323             }
324         } else {
325             final int requestPerThreads = openDevices.size() / threadAmount;
326             final int leftoverRequests = openDevices.size() % threadAmount;
327
328             for (int i = 0; i < threadAmount; i++) {
329                 from = i * requestPerThreads;
330                 to = from + requestPerThreads;
331                 iterator = openDevices.subList(from, to).iterator();
332                 allThreadsPayloads.add(createPayloads(iterator, editContentString));
333             }
334
335             if (leftoverRequests > 0) {
336                 from = (threadAmount) * requestPerThreads;
337                 to = from + leftoverRequests;
338                 iterator = openDevices.subList(from, to).iterator();
339                 allThreadsPayloads.add(createPayloads(iterator, editContentString));
340             }
341         }
342         return allThreadsPayloads;
343     }
344
345     private String prepareMessage(final int openDevice, final String editContentString) {
346         StringBuilder messageBuilder = new StringBuilder(editContentString);
347
348         if (editContentString.contains(HOST_KEY)) {
349             messageBuilder.replace(messageBuilder.indexOf(HOST_KEY), messageBuilder.indexOf(HOST_KEY) + HOST_KEY.length(), generateConfigsAddress);
350         }
351         if (editContentString.contains(PORT_KEY)) {
352             while (messageBuilder.indexOf(PORT_KEY) != -1)
353                 messageBuilder.replace(messageBuilder.indexOf(PORT_KEY), messageBuilder.indexOf(PORT_KEY) + PORT_KEY.length(), Integer.toString(openDevice));
354         }
355         if (editContentString.contains(SSH)) {
356             messageBuilder.replace(messageBuilder.indexOf(SSH), messageBuilder.indexOf(SSH) + SSH.length(), Boolean.toString(ssh));
357         }
358         return messageBuilder.toString();
359     }
360
361     private ArrayList<Execution.DestToPayload> createPayloads(final Iterator<Integer> openDevices, final String editContentString) {
362         final ArrayList<Execution.DestToPayload> payloads = new ArrayList<>();
363
364         while (openDevices.hasNext()) {
365             final StringBuilder destBuilder = new StringBuilder(dest);
366             destBuilder.replace(destBuilder.indexOf(ADDRESS_PORT), destBuilder.indexOf(ADDRESS_PORT) + ADDRESS_PORT.length(), controllerDestination);
367             payloads.add(new Execution.DestToPayload(destBuilder.toString(), prepareMessage(openDevices.next(), editContentString)));
368         }
369         return payloads;
370     }
371
372     private ArrayList<Execution.DestToPayload> createBatchedPayloads(final int batchedRequestsCount, final Iterator<Integer> openDevices, final String editContentString,
373                                                                      final String destination) {
374         final ArrayList<Execution.DestToPayload> payloads = new ArrayList<>();
375
376         for (int i = 0; i < batchedRequestsCount; i++) {
377             String payload = "";
378             for (int j = 0; j < generateConfigBatchSize; j++) {
379                 final StringBuilder payloadBuilder = new StringBuilder(prepareMessage(openDevices.next(), editContentString));
380                 payload += modifyMessage(payloadBuilder, j, generateConfigBatchSize);
381             }
382             payloads.add(new Execution.DestToPayload(destination, payload));
383         }
384         return payloads;
385     }
386 }