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