Fix testtool device registration
[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                 .help("Ip of controller if available it will be used for spawning netconf connectors via topology"
136                         + " configuration as a part of"
137                         + " URI(http://<controller-ip>:<controller-port>/restconf/config/...)"
138                         + " otherwise it will just start simulated devices and skip the execution of PATCH requests")
139                 .dest("controller-ip");
140
141         parser.addArgument("--controller-port")
142                 .type(Integer.class)
143                 .help("Port of controller if available it will be used for spawning netconf connectors via topology "
144                         + "configuration as a part of"
145                         + " URI(http://<controller-ip>:<controller-port>/restconf/config/...) "
146                         + "otherwise it will just start simulated devices and skip the execution of PATCH requests")
147                 .dest("controller-port");
148
149         parser.addArgument("--device-count")
150                 .type(Integer.class)
151                 .setDefault(1)
152                 .help("Number of simulated netconf devices to spin. This is the number of actual ports open for the devices.")
153                 .dest("devices-count");
154
155         parser.addArgument("--devices-per-port")
156                 .type(Integer.class)
157                 .setDefault(1)
158                 .help("Amount of config files generated per port to spoof more devices than are actually running")
159                 .dest("devices-per-port");
160
161         parser.addArgument("--schemas-dir")
162                 .type(File.class)
163                 .help("Directory containing yang schemas to describe simulated devices. Some schemas e.g. netconf monitoring and inet types are included by default")
164                 .dest("schemas-dir");
165
166         parser.addArgument("--notification-file")
167                 .type(File.class)
168                 .help("Xml file containing notifications that should be sent to clients after create subscription is called")
169                 .dest("notification-file");
170
171         parser.addArgument("--initial-config-xml-file")
172                 .type(File.class)
173                 .help("Xml file containing initial simulatted configuration to be returned via get-config rpc")
174                 .dest("initial-config-xml-file");
175
176         parser.addArgument("--starting-port")
177                 .type(Integer.class)
178                 .setDefault(17830)
179                 .help("First port for simulated device. Each other device will have previous+1 port number")
180                 .dest("starting-port");
181
182         parser.addArgument("--generate-config-connection-timeout")
183                 .type(Integer.class)
184                 .setDefault((int) TimeUnit.MINUTES.toMillis(30))
185                 .help("Timeout to be generated in initial config files")
186                 .dest("generate-config-connection-timeout");
187
188         parser.addArgument("--generate-config-address")
189                 .type(String.class)
190                 .setDefault("127.0.0.1")
191                 .help("Address to be placed in generated configs")
192                 .dest("generate-config-address");
193
194         parser.addArgument("--generate-configs-batch-size")
195                 .type(Integer.class)
196                 .setDefault(1)
197                 .help("Number of connector configs per generated file")
198                 .dest("generate-configs-batch-size");
199
200         parser.addArgument("--distribution-folder")
201                 .type(File.class)
202                 .help("Directory where the karaf distribution for controller is located")
203                 .dest("distro-folder");
204
205         parser.addArgument("--ssh")
206                 .type(Boolean.class)
207                 .setDefault(Boolean.TRUE)
208                 .help("Whether to use ssh for transport or just pure tcp")
209                 .dest("ssh");
210
211         parser.addArgument("--exi")
212                 .type(Boolean.class)
213                 .setDefault(Boolean.TRUE)
214                 .help("Whether to use exi to transport xml content")
215                 .dest("exi");
216
217         parser.addArgument("--debug")
218                 .type(Boolean.class)
219                 .setDefault(Boolean.FALSE)
220                 .help("Whether to use debug log level instead of INFO")
221                 .dest("debug");
222
223         parser.addArgument("--md-sal")
224                 .type(Boolean.class)
225                 .setDefault(Boolean.FALSE)
226                 .help("Whether to use md-sal datastore instead of default simulated datastore.")
227                 .dest("md-sal");
228
229         parser.addArgument("--time-out")
230                 .type(long.class)
231                 .setDefault(20)
232                 .help("the maximum time in seconds for executing each PATCH request")
233                 .dest("time-out");
234
235         parser.addArgument("-ip")
236                 .type(String.class)
237                 .setDefault("0.0.0.0")
238                 .help("Ip address which will be used for creating a socket address."
239                         + "It can either be a machine name, such as "
240                         + "java.sun.com, or a textual representation of its IP address.")
241                 .dest("ip");
242
243         parser.addArgument("--thread-pool-size")
244                 .type(Integer.class)
245                 .setDefault(8)
246                 .help("The number of threads to keep in the pool, when creating a device simulator. Even if they are idle.")
247                 .dest("thread-pool-size");
248         parser.addArgument("--rpc-config")
249                 .type(File.class)
250                 .help("Rpc config file. It can be used to define custom rpc behavior, or override the default one."
251                     + "Usable for testing buggy device behavior.")
252                 .dest("rpc-config");
253
254         return parser;
255     }
256
257     static TesttoolParameters parseArgs(final String[] args, final ArgumentParser parser) {
258         final TesttoolParameters opt = new TesttoolParameters();
259         try {
260             parser.parseArgs(args, opt);
261             return opt;
262         } catch (final ArgumentParserException e) {
263             parser.handleError(e);
264         }
265
266         System.exit(1);
267         return null;
268     }
269
270     @SuppressWarnings("checkstyle:regexpSinglelineJava")
271     void validate() {
272         if (controllerIp != null) {
273             //FIXME Ip validation
274             checkArgument(controllerPort != null, "Controller port is missing");
275             //FIXME Is there specific bound
276             checkArgument(controllerPort >= 0, "Controller port should be non-negative integer");
277             checkArgument(controllerPort < 65354, "Controller port should be less than 65354");
278         } else {
279             checkArgument(controllerPort == null, "Controller ip is missing");
280         }
281
282         checkArgument(deviceCount > 0, "Device count has to be > 0");
283         checkArgument(startingPort > 1023, "Starting port has to be > 1023");
284         checkArgument(devicesPerPort > 0, "At least one device per port needed");
285
286         if (schemasDir != null) {
287             checkArgument(schemasDir.exists(), "Schemas dir has to exist");
288             checkArgument(schemasDir.isDirectory(), "Schemas dir has to be a directory");
289             checkArgument(schemasDir.canRead(), "Schemas dir has to be readable");
290
291             final File[] filesArray = schemasDir.listFiles();
292             final List<File> files = filesArray != null ? Arrays.asList(filesArray) : Collections.emptyList();
293             for (final File file : files) {
294                 final Matcher matcher = YANG_FILENAME_PATTERN.matcher(file.getName());
295                 if (!matcher.matches()) {
296                     try {
297                         final String correctName = correctedName(file);
298                         if (correctName != null) {
299                             Files.move(file.toPath(), Paths.get(correctName), StandardCopyOption.ATOMIC_MOVE);
300                         }
301                     } catch (final IOException e) {
302                         // print error to console (test tool is running from console)
303                         e.printStackTrace();
304                     }
305                 }
306             }
307         }
308         if (rpcConfig != null) {
309             checkArgument(rpcConfig.exists(), "Rpc config file has to exist");
310             checkArgument(!rpcConfig.isDirectory(), "Rpc config file can't be a directory");
311             checkArgument(rpcConfig.canRead(), "Rpc config file to be readable");
312         }
313     }
314
315     private static String correctedName(final File file) throws IOException {
316         try (BufferedReader reader = new BufferedReader(new FileReader(file, StandardCharsets.UTF_8))) {
317             String line = reader.readLine();
318             while (line != null && !REVISION_DATE_PATTERN.matcher(line).find()) {
319                 line = reader.readLine();
320             }
321             if (line != null) {
322                 final Matcher m = REVISION_DATE_PATTERN.matcher(line);
323                 checkState(m.find(), "Revision pattern %s did not match line %s", REVISION_DATE_PATTERN, line);
324                 String moduleName = file.getAbsolutePath();
325                 if (file.getName().endsWith(YangConstants.RFC6020_YANG_FILE_EXTENSION)) {
326                     moduleName = moduleName.substring(0, moduleName.length() - 5);
327                 }
328
329                 return moduleName + "@" + m.group(1) + YangConstants.RFC6020_YANG_FILE_EXTENSION;
330             }
331         }
332         return null;
333     }
334
335     @Override
336     public String toString() {
337         final List<Field> fields = Arrays.asList(this.getClass().getDeclaredFields());
338         final StringJoiner joiner = new StringJoiner(", \n", "TesttoolParameters{", "}\n");
339         fields.stream()
340                 .filter(field -> field.getAnnotation(Arg.class) != null)
341                 .map(this::getFieldString)
342                 .forEach(joiner::add);
343         return joiner.toString();
344     }
345
346     private String getFieldString(final Field field) {
347         try {
348             return field.getName() + "='" + field.get(this) + "'";
349         } catch (final IllegalAccessException e) {
350             return field.getName() + "= UNKNOWN";
351         }
352     }
353 }