BUG-3335 Disable keepalives in netconf testtool
[controller.git] / opendaylight / netconf / netconf-testtool / src / main / java / org / opendaylight / controller / netconf / test / tool / Main.java
1 /*
2  * Copyright (c) 2014 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.controller.netconf.test.tool;
10
11 import static com.google.common.base.Preconditions.checkArgument;
12 import static com.google.common.base.Preconditions.checkNotNull;
13
14 import ch.qos.logback.classic.Level;
15 import com.google.common.base.Charsets;
16 import com.google.common.base.Preconditions;
17 import com.google.common.collect.Lists;
18 import com.google.common.io.ByteStreams;
19 import com.google.common.io.CharStreams;
20 import com.google.common.io.Files;
21 import java.io.File;
22 import java.io.FileFilter;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.InputStreamReader;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.List;
29 import java.util.concurrent.TimeUnit;
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.controller.netconf.util.xml.XmlElement;
35 import org.opendaylight.controller.netconf.util.xml.XmlUtil;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38 import org.w3c.dom.Document;
39 import org.w3c.dom.Element;
40 import org.w3c.dom.Node;
41 import org.w3c.dom.NodeList;
42 import org.xml.sax.SAXException;
43
44 public final class Main {
45
46     private static final Logger LOG = LoggerFactory.getLogger(Main.class);
47
48     public static class Params {
49
50         @Arg(dest = "schemas-dir")
51         public File schemasDir;
52
53         @Arg(dest = "devices-count")
54         public int deviceCount;
55
56         @Arg(dest = "starting-port")
57         public int startingPort;
58
59         @Arg(dest = "generate-config-connection-timeout")
60         public int generateConfigsTimeout;
61
62         @Arg(dest = "generate-config-address")
63         public String generateConfigsAddress;
64
65         @Arg(dest = "distro-folder")
66         public File distroFolder;
67
68         @Arg(dest = "generate-configs-batch-size")
69         public int generateConfigBatchSize;
70
71         @Arg(dest = "ssh")
72         public boolean ssh;
73
74         @Arg(dest = "exi")
75         public boolean exi;
76
77         @Arg(dest = "debug")
78         public boolean debug;
79
80         @Arg(dest = "notification-file")
81         public File notificationFile;
82
83         static ArgumentParser getParser() {
84             final ArgumentParser parser = ArgumentParsers.newArgumentParser("netconf testool");
85
86             parser.description("Netconf device simulator. Detailed info can be found at https://wiki.opendaylight.org/view/OpenDaylight_Controller:Netconf:Testtool#Building_testtool");
87
88             parser.addArgument("--device-count")
89                     .type(Integer.class)
90                     .setDefault(1)
91                     .type(Integer.class)
92                     .help("Number of simulated netconf devices to spin")
93                     .dest("devices-count");
94
95             parser.addArgument("--schemas-dir")
96                     .type(File.class)
97                     .help("Directory containing yang schemas to describe simulated devices. Some schemas e.g. netconf monitoring and inet types are included by default")
98                     .dest("schemas-dir");
99
100             parser.addArgument("--notification-file")
101                     .type(File.class)
102                     .help("Xml file containing notifications that should be sent to clients after create subscription is called")
103                     .dest("notification-file");
104
105             parser.addArgument("--starting-port")
106                     .type(Integer.class)
107                     .setDefault(17830)
108                     .help("First port for simulated device. Each other device will have previous+1 port number")
109                     .dest("starting-port");
110
111             parser.addArgument("--generate-config-connection-timeout")
112                     .type(Integer.class)
113                     .setDefault((int)TimeUnit.MINUTES.toMillis(30))
114                     .help("Timeout to be generated in initial config files")
115                     .dest("generate-config-connection-timeout");
116
117             parser.addArgument("--generate-config-address")
118                     .type(String.class)
119                     .setDefault("127.0.0.1")
120                     .help("Address to be placed in generated configs")
121                     .dest("generate-config-address");
122
123             parser.addArgument("--generate-configs-batch-size")
124                     .type(Integer.class)
125                     .setDefault(4000)
126                     .help("Number of connector configs per generated file")
127                     .dest("generate-configs-batch-size");
128
129             parser.addArgument("--distribution-folder")
130                     .type(File.class)
131                     .help("Directory where the karaf distribution for controller is located")
132                     .dest("distro-folder");
133
134             parser.addArgument("--ssh")
135                     .type(Boolean.class)
136                     .setDefault(true)
137                     .help("Whether to use ssh for transport or just pure tcp")
138                     .dest("ssh");
139
140             parser.addArgument("--exi")
141                     .type(Boolean.class)
142                     .setDefault(true)
143                     .help("Whether to use exi to transport xml content")
144                     .dest("exi");
145
146             parser.addArgument("--debug")
147                     .type(Boolean.class)
148                     .setDefault(false)
149                     .help("Whether to use debug log level instead of INFO")
150                     .dest("debug");
151
152             return parser;
153         }
154
155         void validate() {
156             checkArgument(deviceCount > 0, "Device count has to be > 0");
157             checkArgument(startingPort > 1023, "Starting port has to be > 1023");
158
159             if(schemasDir != null) {
160                 checkArgument(schemasDir.exists(), "Schemas dir has to exist");
161                 checkArgument(schemasDir.isDirectory(), "Schemas dir has to be a directory");
162                 checkArgument(schemasDir.canRead(), "Schemas dir has to be readable");
163             }
164         }
165     }
166
167     public static void main(final String[] args) {
168         final Params params = parseArgs(args, Params.getParser());
169         params.validate();
170
171         final ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
172         root.setLevel(params.debug ? Level.DEBUG : Level.INFO);
173
174         final NetconfDeviceSimulator netconfDeviceSimulator = new NetconfDeviceSimulator();
175         try {
176             final List<Integer> openDevices = netconfDeviceSimulator.start(params);
177             if (openDevices.size() == 0) {
178                 LOG.error("Failed to start any simulated devices, exiting...");
179                 System.exit(1);
180             }
181             if(params.distroFolder != null) {
182                 final ConfigGenerator configGenerator = new ConfigGenerator(params.distroFolder, openDevices);
183                 final List<File> generated = configGenerator.generate(params.ssh, params.generateConfigBatchSize, params.generateConfigsTimeout, params.generateConfigsAddress);
184                 configGenerator.updateFeatureFile(generated);
185                 configGenerator.changeLoadOrder();
186             }
187         } catch (final Exception e) {
188             LOG.error("Unhandled exception", e);
189             netconfDeviceSimulator.close();
190             System.exit(1);
191         }
192
193         // Block main thread
194         synchronized (netconfDeviceSimulator) {
195             try {
196                 netconfDeviceSimulator.wait();
197             } catch (final InterruptedException e) {
198                 throw new RuntimeException(e);
199             }
200         }
201     }
202
203     private static Params parseArgs(final String[] args, final ArgumentParser parser) {
204         final Params opt = new Params();
205         try {
206             parser.parseArgs(args, opt);
207             return opt;
208         } catch (final ArgumentParserException e) {
209             parser.handleError(e);
210         }
211
212         System.exit(1);
213         return null;
214     }
215
216     private static class ConfigGenerator {
217         public static final String NETCONF_CONNECTOR_XML = "/99-netconf-connector-simulated.xml";
218         public static final String SIM_DEVICE_SUFFIX = "-sim-device";
219
220         private static final String SIM_DEVICE_CFG_PREFIX = "simulated-devices_";
221         private static final String ETC_KARAF_PATH = "etc/";
222         private static final String ETC_OPENDAYLIGHT_KARAF_PATH = ETC_KARAF_PATH + "opendaylight/karaf/";
223
224         public static final String NETCONF_CONNECTOR_ALL_FEATURE = "odl-netconf-connector-all";
225         private static final String ORG_OPS4J_PAX_URL_MVN_CFG = "org.ops4j.pax.url.mvn.cfg";
226
227         private final File configDir;
228         private final List<Integer> openDevices;
229         private final List<File> ncFeatureFiles;
230         private final File etcDir;
231         private final File loadOrderCfgFile;
232
233         public ConfigGenerator(final File directory, final List<Integer> openDevices) {
234             this.configDir = new File(directory, ETC_OPENDAYLIGHT_KARAF_PATH);
235             this.etcDir = new File(directory, ETC_KARAF_PATH);
236             this.loadOrderCfgFile = new File(etcDir, ORG_OPS4J_PAX_URL_MVN_CFG);
237             this.ncFeatureFiles = getFeatureFile(directory, "features-netconf-connector", "xml");
238             this.openDevices = openDevices;
239         }
240
241         public List<File> generate(final boolean useSsh, final int batchSize, final int generateConfigsTimeout, final String address) {
242             if(configDir.exists() == false) {
243                 Preconditions.checkState(configDir.mkdirs(), "Unable to create directory " + configDir);
244             }
245
246             for (final File file : configDir.listFiles(new FileFilter() {
247                 @Override
248                 public boolean accept(final File pathname) {
249                     return !pathname.isDirectory() && pathname.getName().startsWith(SIM_DEVICE_CFG_PREFIX);
250                 }
251             })) {
252                 Preconditions.checkState(file.delete(), "Unable to clean previous generated file %s", file);
253             }
254
255             try(InputStream stream = Main.class.getResourceAsStream(NETCONF_CONNECTOR_XML)) {
256                 checkNotNull(stream, "Cannot load %s", NETCONF_CONNECTOR_XML);
257                 String configBlueprint = CharStreams.toString(new InputStreamReader(stream, Charsets.UTF_8));
258
259                 final String before = configBlueprint.substring(0, configBlueprint.indexOf("<module>"));
260                 final String middleBlueprint = configBlueprint.substring(configBlueprint.indexOf("<module>"), configBlueprint.indexOf("</module>"));
261                 final String after = configBlueprint.substring(configBlueprint.indexOf("</module>") + "</module>".length());
262
263                 int connectorCount = 0;
264                 Integer batchStart = null;
265                 StringBuilder b = new StringBuilder();
266                 b.append(before);
267
268                 final List<File> generatedConfigs = Lists.newArrayList();
269
270                 for (final Integer openDevice : openDevices) {
271                     if(batchStart == null) {
272                         batchStart = openDevice;
273                     }
274
275                     final String name = String.valueOf(openDevice) + SIM_DEVICE_SUFFIX;
276                     String configContent = String.format(middleBlueprint, name, address, String.valueOf(openDevice), String.valueOf(!useSsh));
277                     configContent = String.format("%s%s%d%s\n%s\n", configContent, "<connection-timeout-millis>", generateConfigsTimeout, "</connection-timeout-millis>", "</module>");
278
279                     b.append(configContent);
280                     connectorCount++;
281                     if(connectorCount == batchSize) {
282                         b.append(after);
283                         final File to = new File(configDir, String.format(SIM_DEVICE_CFG_PREFIX + "%d-%d.xml", batchStart, openDevice));
284                         generatedConfigs.add(to);
285                         Files.write(b.toString(), to, Charsets.UTF_8);
286                         connectorCount = 0;
287                         b = new StringBuilder();
288                         b.append(before);
289                         batchStart = null;
290                     }
291                 }
292
293                 // Write remaining
294                 if(connectorCount != 0) {
295                     b.append(after);
296                     final File to = new File(configDir, String.format(SIM_DEVICE_CFG_PREFIX + "%d-%d.xml", batchStart, openDevices.get(openDevices.size() - 1)));
297                     generatedConfigs.add(to);
298                     Files.write(b.toString(), to, Charsets.UTF_8);
299                 }
300
301                 LOG.info("Config files generated in {}", configDir);
302                 return generatedConfigs;
303             } catch (final IOException e) {
304                 throw new RuntimeException("Unable to generate config files", e);
305             }
306         }
307
308
309         public void updateFeatureFile(final List<File> generated) {
310             // TODO karaf core contains jaxb for feature files, use that for
311             // modification
312             try {
313                 for (final File featureFile : ncFeatureFiles) {
314                     final Document document = XmlUtil.readXmlToDocument(Files
315                             .toString(featureFile, Charsets.UTF_8));
316                     final NodeList childNodes = document.getDocumentElement().getChildNodes();
317
318                     for (int i = 0; i < childNodes.getLength(); i++) {
319                         final Node item = childNodes.item(i);
320                         if (item instanceof Element == false) {
321                             continue;
322                         }
323                         if (item.getLocalName().equals("feature") == false) {
324                             continue;
325                         }
326
327                         if (NETCONF_CONNECTOR_ALL_FEATURE
328                                 .equals(((Element) item).getAttribute("name"))) {
329                             final Element ncAllFeatureDefinition = (Element) item;
330                             // Clean previous generated files
331                             for (final XmlElement configfile : XmlElement
332                                     .fromDomElement(ncAllFeatureDefinition)
333                                     .getChildElements("configfile")) {
334                                 ncAllFeatureDefinition.removeChild(configfile.getDomElement());
335                             }
336                             for (final File file : generated) {
337                                 final Element configfile = document.createElement("configfile");
338                                 configfile.setTextContent("file:"
339                                         + ETC_OPENDAYLIGHT_KARAF_PATH
340                                         + file.getName());
341                                 configfile.setAttribute(
342                                         "finalname",
343                                         ETC_OPENDAYLIGHT_KARAF_PATH
344                                                 + file.getName());
345                                 ncAllFeatureDefinition.appendChild(configfile);
346                             }
347                         }
348                     }
349
350                     Files.write(XmlUtil.toString(document), featureFile,Charsets.UTF_8);
351                     LOG.info("Feature file {} updated", featureFile);
352                 }
353             } catch (final IOException e) {
354                 throw new RuntimeException("Unable to load features file as a resource");
355             } catch (final SAXException e) {
356                 throw new RuntimeException("Unable to parse features file");
357             }
358         }
359
360
361         private static List<File> getFeatureFile(final File distroFolder, final String featureName, final String suffix) {
362             checkExistingDir(distroFolder, String.format("Folder %s does not exist", distroFolder));
363
364             final File systemDir = checkExistingDir(new File(distroFolder, "system"), String.format("Folder %s does not contain a karaf distro, folder system is missing", distroFolder));
365             final File netconfConnectorFeaturesParentDir = checkExistingDir(new File(systemDir, "org/opendaylight/controller/" + featureName), String.format("Karaf distro in %s does not contain netconf-connector features", distroFolder));
366
367             // Find newest version for features
368             final File newestVersionDir = Collections.max(
369                     Lists.newArrayList(netconfConnectorFeaturesParentDir.listFiles(new FileFilter() {
370                         @Override
371                         public boolean accept(final File pathname) {
372                             return pathname.isDirectory();
373                         }
374                     })), new Comparator<File>() {
375                         @Override
376                         public int compare(final File o1, final File o2) {
377                             return o1.getName().compareTo(o2.getName());
378                         }
379                     });
380
381             return Lists.newArrayList(newestVersionDir.listFiles(new FileFilter() {
382                 @Override
383                 public boolean accept(final File pathname) {
384                     return pathname.getName().contains(featureName)
385                             && Files.getFileExtension(pathname.getName()).equals(suffix);
386                 }
387             }));
388         }
389
390         private static File checkExistingDir(final File folder, final String msg) {
391             Preconditions.checkArgument(folder.exists(), msg);
392             Preconditions.checkArgument(folder.isDirectory(), msg);
393             return folder;
394         }
395
396         public void changeLoadOrder() {
397             try {
398                 Files.write(ByteStreams.toByteArray(getClass().getResourceAsStream("/" +ORG_OPS4J_PAX_URL_MVN_CFG)), loadOrderCfgFile);
399                 LOG.info("Load order changed to prefer local bundles/features by rewriting file {}", loadOrderCfgFile);
400             } catch (IOException e) {
401                 throw new RuntimeException("Unable to rewrite features file " + loadOrderCfgFile, e);
402             }
403         }
404     }
405 }