Fix scale util test tool
[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 package org.opendaylight.netconf.test.tool;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkState;
12
13 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
14 import java.io.BufferedReader;
15 import java.io.File;
16 import java.io.FileReader;
17 import java.io.IOException;
18 import java.lang.reflect.Field;
19 import java.nio.charset.StandardCharsets;
20 import java.nio.file.Files;
21 import java.nio.file.Paths;
22 import java.nio.file.StandardCopyOption;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.StringJoiner;
27 import java.util.concurrent.TimeUnit;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30 import net.sourceforge.argparse4j.ArgumentParsers;
31 import net.sourceforge.argparse4j.annotation.Arg;
32 import net.sourceforge.argparse4j.inf.ArgumentParser;
33 import net.sourceforge.argparse4j.inf.ArgumentParserException;
34 import org.opendaylight.yangtools.yang.common.YangConstants;
35
36 @SuppressFBWarnings({"DM_EXIT"})
37 public final class TesttoolParameters {
38     private static final Pattern YANG_FILENAME_PATTERN = Pattern
39             .compile("(?<name>.*)@(?<revision>\\d{4}-\\d{2}-\\d{2})\\.yang");
40     private static final Pattern REVISION_DATE_PATTERN = Pattern.compile("revision\\s+\"?(\\d{4}-\\d{2}-\\d{2})\"?");
41
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 = "controller-auth-username")
49     public String controllerAuthUsername;
50     @Arg(dest = "controller-auth-password")
51     public String controllerAuthPassword;
52     @Arg(dest = "controller-ip")
53     public String controllerIp;
54     @Arg(dest = "controller-port")
55     public Integer controllerPort;
56     @Arg(dest = "schemas-dir")
57     public File schemasDir;
58     @Arg(dest = "devices-count")
59     public int deviceCount;
60     @Arg(dest = "devices-per-port")
61     public int devicesPerPort;
62     @Arg(dest = "starting-port")
63     public int startingPort;
64     @Arg(dest = "generate-config-connection-timeout")
65     public int generateConfigsTimeout;
66     @Arg(dest = "generate-config-address")
67     public String generateConfigsAddress;
68     @Arg(dest = "distro-folder")
69     public File distroFolder;
70     @Arg(dest = "generate-configs-batch-size")
71     public int generateConfigBatchSize;
72     @Arg(dest = "ssh")
73     public boolean ssh;
74     @Arg(dest = "exi")
75     public boolean exi = true;
76     @Arg(dest = "debug")
77     public boolean debug;
78     @Arg(dest = "notification-file")
79     public File notificationFile;
80     @Arg(dest = "md-sal")
81     public boolean mdSal;
82     @Arg(dest = "initial-config-xml-file")
83     public File initialConfigXMLFile;
84     @Arg(dest = "time-out")
85     public long timeOut;
86     @Arg(dest = "ip")
87     public String ip;
88     @Arg(dest = "thread-pool-size")
89     public int threadPoolSize;
90     @Arg(dest = "rpc-config")
91     public File rpcConfig;
92
93     @SuppressWarnings("checkstyle:lineLength")
94     static ArgumentParser getParser() {
95         final ArgumentParser parser = ArgumentParsers.newArgumentParser("netconf testtool");
96
97         parser.description("netconf testtool");
98
99         parser.addArgument("--edit-content")
100                 .type(String.class)
101                 .dest("edit-content");
102
103         parser.addArgument("--async-requests")
104                 .type(Boolean.class)
105                 .setDefault(Boolean.FALSE)
106                 .dest("async");
107
108         parser.addArgument("--thread-amount")
109                 .type(Integer.class)
110                 .setDefault(1)
111                 .dest("thread-amount")
112                 .help("The number of threads to use for configuring devices.");
113
114         parser.addArgument("--throttle")
115                 .type(Integer.class)
116                 .setDefault(5000)
117                 .help("Maximum amount of async requests that can be open at a time, "
118                         + "with mutltiple threads this gets divided among all threads")
119                 .dest("throttle");
120
121         parser.addArgument("--controller-auth-username")
122                 .type(String.class)
123                 .setDefault("admin")
124                 .help("Username for HTTP basic authentication to destination controller.")
125                 .dest("controller-auth-username");
126
127         parser.addArgument("--controller-auth-password")
128                 .type(String.class)
129                 .setDefault("admin")
130                 .help("Password for HTTP basic authentication to destination controller.")
131                 .dest("controller-auth-password");
132
133         parser.addArgument("--controller-ip")
134                 .type(String.class)
135                 .setDefault("127.0.0.1")
136                 .help("Ip of controller if available it will be used for spawning netconf connectors via topology"
137                         + " configuration as a part of"
138                         + " URI(http://<controller-ip>:<controller-port>/rests/data/...)"
139                         + " otherwise it will just start simulated devices and skip the execution of PATCH requests")
140                 .dest("controller-ip");
141
142         parser.addArgument("--controller-port")
143                 .type(Integer.class)
144                 .setDefault(8181)
145                 .help("Port of controller if available it will be used for spawning netconf connectors via topology "
146                         + "configuration as a part of"
147                         + " URI(http://<controller-ip>:<controller-port>/rests/data/...) "
148                         + "otherwise it will just start simulated devices and skip the execution of PATCH requests")
149                 .dest("controller-port");
150
151         parser.addArgument("--device-count")
152                 .type(Integer.class)
153                 .setDefault(1)
154                 .help("Number of simulated netconf devices to spin. This is the number of actual ports open for the devices.")
155                 .dest("devices-count");
156
157         parser.addArgument("--devices-per-port")
158                 .type(Integer.class)
159                 .setDefault(1)
160                 .help("Amount of config files generated per port to spoof more devices than are actually running")
161                 .dest("devices-per-port");
162
163         parser.addArgument("--schemas-dir")
164                 .type(File.class)
165                 .help("Directory containing yang schemas to describe simulated devices. Some schemas e.g. netconf monitoring and inet types are included by default")
166                 .dest("schemas-dir");
167
168         parser.addArgument("--notification-file")
169                 .type(File.class)
170                 .help("Xml file containing notifications that should be sent to clients after create subscription is called")
171                 .dest("notification-file");
172
173         parser.addArgument("--initial-config-xml-file")
174                 .type(File.class)
175                 .help("Xml file containing initial simulatted configuration to be returned via get-config rpc")
176                 .dest("initial-config-xml-file");
177
178         parser.addArgument("--starting-port")
179                 .type(Integer.class)
180                 .setDefault(17830)
181                 .help("First port for simulated device. Each other device will have previous+1 port number")
182                 .dest("starting-port");
183
184         parser.addArgument("--generate-config-connection-timeout")
185                 .type(Integer.class)
186                 .setDefault((int) TimeUnit.MINUTES.toMillis(30))
187                 .help("Timeout to be generated in initial config files")
188                 .dest("generate-config-connection-timeout");
189
190         parser.addArgument("--generate-config-address")
191                 .type(String.class)
192                 .setDefault("127.0.0.1")
193                 .help("Address to be placed in generated configs")
194                 .dest("generate-config-address");
195
196         parser.addArgument("--generate-configs-batch-size")
197                 .type(Integer.class)
198                 .setDefault(1)
199                 .help("Number of connector configs per generated file")
200                 .dest("generate-configs-batch-size");
201
202         parser.addArgument("--distribution-folder")
203                 .type(File.class)
204                 .help("Directory where the karaf distribution for controller is located")
205                 .dest("distro-folder");
206
207         parser.addArgument("--ssh")
208                 .type(Boolean.class)
209                 .setDefault(Boolean.TRUE)
210                 .help("Whether to use ssh for transport or just pure tcp")
211                 .dest("ssh");
212
213         parser.addArgument("--exi")
214                 .type(Boolean.class)
215                 .setDefault(Boolean.TRUE)
216                 .help("Whether to use exi to transport xml content")
217                 .dest("exi");
218
219         parser.addArgument("--debug")
220                 .type(Boolean.class)
221                 .setDefault(Boolean.FALSE)
222                 .help("Whether to use debug log level instead of INFO")
223                 .dest("debug");
224
225         parser.addArgument("--md-sal")
226                 .type(Boolean.class)
227                 .setDefault(Boolean.FALSE)
228                 .help("Whether to use md-sal datastore instead of default simulated datastore.")
229                 .dest("md-sal");
230
231         parser.addArgument("--time-out")
232                 .type(long.class)
233                 .setDefault(20)
234                 .help("the maximum time in seconds for executing each PATCH request")
235                 .dest("time-out");
236
237         parser.addArgument("-ip")
238                 .type(String.class)
239                 .setDefault("0.0.0.0")
240                 .help("Ip address which will be used for creating a socket address."
241                         + "It can either be a machine name, such as "
242                         + "java.sun.com, or a textual representation of its IP address.")
243                 .dest("ip");
244
245         parser.addArgument("--thread-pool-size")
246                 .type(Integer.class)
247                 .setDefault(8)
248                 .help("The number of threads to keep in the pool, when creating a device simulator. Even if they are idle.")
249                 .dest("thread-pool-size");
250         parser.addArgument("--rpc-config")
251                 .type(File.class)
252                 .help("Rpc config file. It can be used to define custom rpc behavior, or override the default one."
253                     + "Usable for testing buggy device behavior.")
254                 .dest("rpc-config");
255
256         return parser;
257     }
258
259     static TesttoolParameters parseArgs(final String[] args, final ArgumentParser parser) {
260         final TesttoolParameters opt = new TesttoolParameters();
261         try {
262             parser.parseArgs(args, opt);
263             return opt;
264         } catch (final ArgumentParserException e) {
265             parser.handleError(e);
266         }
267
268         System.exit(1);
269         return null;
270     }
271
272     @SuppressWarnings("checkstyle:regexpSinglelineJava")
273     void validate() {
274         if (controllerIp != null) {
275             //FIXME Ip validation
276             checkArgument(controllerPort != null, "Controller port is missing");
277             //FIXME Is there specific bound
278             checkArgument(controllerPort >= 0, "Controller port should be non-negative integer");
279             checkArgument(controllerPort < 65354, "Controller port should be less than 65354");
280         } else {
281             checkArgument(controllerPort == null, "Controller ip is missing");
282         }
283
284         checkArgument(deviceCount > 0, "Device count has to be > 0");
285         checkArgument(startingPort > 1023, "Starting port has to be > 1023");
286         checkArgument(devicesPerPort > 0, "At least one device per port needed");
287
288         if (schemasDir != null) {
289             checkArgument(schemasDir.exists(), "Schemas dir has to exist");
290             checkArgument(schemasDir.isDirectory(), "Schemas dir has to be a directory");
291             checkArgument(schemasDir.canRead(), "Schemas dir has to be readable");
292
293             final File[] filesArray = schemasDir.listFiles();
294             final List<File> files = filesArray != null ? Arrays.asList(filesArray) : Collections.emptyList();
295             for (final File file : files) {
296                 final Matcher matcher = YANG_FILENAME_PATTERN.matcher(file.getName());
297                 if (!matcher.matches()) {
298                     try {
299                         final String correctName = correctedName(file);
300                         if (correctName != null) {
301                             Files.move(file.toPath(), Paths.get(correctName), StandardCopyOption.ATOMIC_MOVE);
302                         }
303                     } catch (final IOException e) {
304                         // print error to console (test tool is running from console)
305                         e.printStackTrace();
306                     }
307                 }
308             }
309         }
310         if (rpcConfig != null) {
311             checkArgument(rpcConfig.exists(), "Rpc config file has to exist");
312             checkArgument(!rpcConfig.isDirectory(), "Rpc config file can't be a directory");
313             checkArgument(rpcConfig.canRead(), "Rpc config file to be readable");
314         }
315     }
316
317     private static String correctedName(final File file) throws IOException {
318         try (BufferedReader reader = new BufferedReader(new FileReader(file, StandardCharsets.UTF_8))) {
319             String line = reader.readLine();
320             while (line != null && !REVISION_DATE_PATTERN.matcher(line).find()) {
321                 line = reader.readLine();
322             }
323             if (line != null) {
324                 final Matcher m = REVISION_DATE_PATTERN.matcher(line);
325                 checkState(m.find(), "Revision pattern %s did not match line %s", REVISION_DATE_PATTERN, line);
326                 String moduleName = file.getAbsolutePath();
327                 if (file.getName().endsWith(YangConstants.RFC6020_YANG_FILE_EXTENSION)) {
328                     moduleName = moduleName.substring(0, moduleName.length() - 5);
329                 }
330
331                 return moduleName + "@" + m.group(1) + YangConstants.RFC6020_YANG_FILE_EXTENSION;
332             }
333         }
334         return null;
335     }
336
337     @Override
338     public String toString() {
339         final List<Field> fields = Arrays.asList(this.getClass().getDeclaredFields());
340         final StringJoiner joiner = new StringJoiner(", \n", "TesttoolParameters{", "}\n");
341         fields.stream()
342                 .filter(field -> field.getAnnotation(Arg.class) != null)
343                 .map(this::getFieldString)
344                 .forEach(joiner::add);
345         return joiner.toString();
346     }
347
348     private String getFieldString(final Field field) {
349         try {
350             return field.getName() + "='" + field.get(this) + "'";
351         } catch (final IllegalAccessException e) {
352             return field.getName() + "= UNKNOWN";
353         }
354     }
355 }