2 * Copyright (c) 2016 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.netconf.test.tool;
11 import static com.google.common.base.Preconditions.checkArgument;
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;
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;
31 public class TesttoolParameters {
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/";
39 private static final String RESOURCE = "/config-template.json";
40 @Arg(dest = "edit-content")
41 public File editContent;
44 @Arg(dest = "thread-amount")
45 public int threadAmount;
46 @Arg(dest = "throttle")
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;
74 @Arg(dest = "notification-file")
75 public File notificationFile;
78 @Arg(dest = "initial-config-xml-file")
79 public File initialConfigXMLFile;
80 @Arg(dest = "time-out")
82 private InputStream stream;
87 @Arg(dest = "thread-pool-size")
88 public int threadPoolSize;
90 static ArgumentParser getParser() {
91 final ArgumentParser parser = ArgumentParsers.newArgumentParser("netconf testtool");
93 parser.description("netconf testtool");
95 parser.addArgument("--edit-content")
97 .dest("edit-content");
99 parser.addArgument("--async-requests")
104 parser.addArgument("--thread-amount")
107 .dest("thread-amount")
108 .help("The number of threads to use for configuring devices.");
110 parser.addArgument("--throttle")
113 .help("Maximum amount of async requests that can be open at a time, " +
114 "with mutltiple threads this gets divided among all threads")
117 parser.addArgument("--auth")
119 .help("Username and password for HTTP basic authentication in order username password.")
122 parser.addArgument("--controller-destination")
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");
130 parser.addArgument("--device-count")
133 .help("Number of simulated netconf devices to spin. This is the number of actual ports open for the devices.")
134 .dest("devices-count");
136 parser.addArgument("--devices-per-port")
139 .help("Amount of config files generated per port to spoof more devices then are actually running")
140 .dest("devices-per-port");
142 parser.addArgument("--schemas-dir")
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");
147 parser.addArgument("--notification-file")
149 .help("Xml file containing notifications that should be sent to clients after create subscription is called")
150 .dest("notification-file");
152 parser.addArgument("--initial-config-xml-file")
154 .help("Xml file containing initial simulatted configuration to be returned via get-config rpc")
155 .dest("initial-config-xml-file");
157 parser.addArgument("--starting-port")
160 .help("First port for simulated device. Each other device will have previous+1 port number")
161 .dest("starting-port");
163 parser.addArgument("--generate-config-connection-timeout")
165 .setDefault((int) TimeUnit.MINUTES.toMillis(30))
166 .help("Timeout to be generated in initial config files")
167 .dest("generate-config-connection-timeout");
169 parser.addArgument("--generate-config-address")
171 .setDefault("127.0.0.1")
172 .help("Address to be placed in generated configs")
173 .dest("generate-config-address");
175 parser.addArgument("--generate-configs-batch-size")
178 .help("Number of connector configs per generated file")
179 .dest("generate-configs-batch-size");
181 parser.addArgument("--distribution-folder")
183 .help("Directory where the karaf distribution for controller is located")
184 .dest("distro-folder");
186 parser.addArgument("--ssh")
189 .help("Whether to use ssh for transport or just pure tcp")
192 parser.addArgument("--exi")
195 .help("Whether to use exi to transport xml content")
198 parser.addArgument("--debug")
201 .help("Whether to use debug log level instead of INFO")
204 parser.addArgument("--md-sal")
207 .help("Whether to use md-sal datastore instead of default simulated datastore.")
210 parser.addArgument("--time-out")
213 .help("the maximum time in seconds for executing each PUT request")
216 parser.addArgument("-ip")
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.")
224 parser.addArgument("--thread-pool-size")
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");
233 public static TesttoolParameters parseArgs(final String[] args, final ArgumentParser parser) {
234 final TesttoolParameters opt = new TesttoolParameters();
236 parser.parseArgs(args, opt);
238 } catch (final ArgumentParserException e) {
239 parser.handleError(e);
246 private static String modifyMessage(final StringBuilder payloadBuilder, final int payloadPosition, final int size) {
248 return payloadBuilder.toString();
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, "]");
258 payloadBuilder.delete(0, payloadBuilder.toString().indexOf(':') + 1);
259 payloadBuilder.replace(payloadBuilder.length() - 2, payloadBuilder.length() - 1, ",");
260 payloadBuilder.deleteCharAt(payloadBuilder.toString().lastIndexOf('}'));
262 return payloadBuilder.toString();
266 if (editContent == null) {
267 stream = TesttoolParameters.class.getResourceAsStream(RESOURCE);
269 Preconditions.checkArgument(!editContent.isDirectory(), "Edit content file is a dir");
270 Preconditions.checkArgument(editContent.canRead(), "Edit content file is unreadable");
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");
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");
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");
290 public ArrayList<ArrayList<Execution.DestToPayload>> getThreadsPayloads(final List<Integer> openDevices) {
291 final String editContentString;
293 if (stream == null) {
294 editContentString = Files.toString(editContent, Charsets.UTF_8);
296 editContentString = CharStreams.toString(new InputStreamReader(stream, Charsets.UTF_8));
298 } catch (final IOException e) {
299 throw new IllegalArgumentException("Cannot read content of " + editContent);
303 Iterator<Integer> iterator;
305 final ArrayList<ArrayList<Execution.DestToPayload>> allThreadsPayloads = new ArrayList<>();
306 if (generateConfigBatchSize > 1) {
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);
313 final StringBuilder destBuilder = new StringBuilder(dest);
314 destBuilder.replace(destBuilder.indexOf(ADDRESS_PORT), destBuilder.indexOf(ADDRESS_PORT) + ADDRESS_PORT.length(), controllerDestination);
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()));
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());
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);
338 if (leftoverRequests > 0 || leftoverBatchedRequests > 0) {
340 if (payloads != null) {
341 payloads.add(new Execution.DestToPayload(destBuilder.toString(), payload));
343 allThreadsPayloads.add(payloads);
346 final int requestPerThreads = openDevices.size() / threadAmount;
347 final int leftoverRequests = openDevices.size() % threadAmount;
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));
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));
363 return allThreadsPayloads;
366 private String prepareMessage(final int openDevice, final String editContentString) {
367 StringBuilder messageBuilder = new StringBuilder(editContentString);
369 if (editContentString.contains(HOST_KEY)) {
370 messageBuilder.replace(messageBuilder.indexOf(HOST_KEY), messageBuilder.indexOf(HOST_KEY) + HOST_KEY.length(), generateConfigsAddress);
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));
376 if (editContentString.contains(SSH)) {
377 messageBuilder.replace(messageBuilder.indexOf(SSH), messageBuilder.indexOf(SSH) + SSH.length(), Boolean.toString(ssh));
379 return messageBuilder.toString();
382 private ArrayList<Execution.DestToPayload> createPayloads(final Iterator<Integer> openDevices, final String editContentString) {
383 final ArrayList<Execution.DestToPayload> payloads = new ArrayList<>();
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)));
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<>();
397 for (int i = 0; i < batchedRequestsCount; i++) {
399 for (int j = 0; j < generateConfigBatchSize; j++) {
400 final StringBuilder payloadBuilder = new StringBuilder(prepareMessage(openDevices.next(), editContentString));
401 payload += modifyMessage(payloadBuilder, j, generateConfigBatchSize);
403 payloads.add(new Execution.DestToPayload(destination, payload));