3b4f522f68b738f6973c3d7d4d32185279a51f3e
[netconf.git] / netconf / tools / netconf-testtool / src / main / java / org / opendaylight / netconf / test / tool / ScaleUtil.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 ch.qos.logback.classic.Level;
11 import com.google.common.base.Stopwatch;
12 import com.google.common.io.CharStreams;
13 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
14 import java.io.BufferedReader;
15 import java.io.File;
16 import java.io.IOException;
17 import java.io.InputStreamReader;
18 import java.net.Authenticator;
19 import java.net.ConnectException;
20 import java.net.PasswordAuthentication;
21 import java.net.URI;
22 import java.net.http.HttpClient;
23 import java.net.http.HttpRequest;
24 import java.net.http.HttpResponse;
25 import java.time.Duration;
26 import java.util.List;
27 import java.util.concurrent.Callable;
28 import java.util.concurrent.ScheduledExecutorService;
29 import java.util.concurrent.ScheduledFuture;
30 import java.util.concurrent.ScheduledThreadPoolExecutor;
31 import java.util.concurrent.Semaphore;
32 import java.util.concurrent.TimeUnit;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
35 import org.opendaylight.netconf.test.tool.config.Configuration;
36 import org.opendaylight.netconf.test.tool.config.ConfigurationBuilder;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 @SuppressFBWarnings({"DM_EXIT", "DM_DEFAULT_ENCODING", "SLF4J_LOGGER_SHOULD_BE_FINAL"})
41 public final class ScaleUtil {
42     private static final ScheduledExecutorService EXECUTOR = new LoggingWrapperExecutor(4);
43     private static final Semaphore SEMAPHORE = new Semaphore(0);
44     private static final Stopwatch STOPWATCH = Stopwatch.createUnstarted();
45     private static final String RESTCONF_URL = "http://%s:%d/rests/data/"
46             + "network-topology:network-topology?content=nonconfig";
47
48     private static final long TIMEOUT = 20L;
49     private static final long RETRY_DELAY = 10L;
50     private static final int DEVICE_STEP = 1000;
51
52     private static ch.qos.logback.classic.Logger root;
53     private static Logger resultsLog;
54
55     private ScaleUtil() {
56     }
57
58     @SuppressWarnings("checkstyle:illegalCatch")
59     public static void main(final String[] args) {
60         final TesttoolParameters params = TesttoolParameters.parseArgs(args, TesttoolParameters.getParser());
61
62         setUpLoggers(params);
63
64         // cleanup at the start in case controller was already running
65         final Runtime runtime = Runtime.getRuntime();
66         cleanup(runtime, params);
67
68         while (true) {
69             root.warn("Starting scale test with {} devices", params.deviceCount);
70             final ScheduledFuture<?> timeoutGuardFuture = EXECUTOR.schedule(new TimeoutGuard(), TIMEOUT,
71                 TimeUnit.MINUTES);
72             final Configuration configuration = new ConfigurationBuilder().from(params).build();
73             final NetconfDeviceSimulator netconfDeviceSimulator = new NetconfDeviceSimulator(configuration);
74
75             final List<Integer> openDevices = netconfDeviceSimulator.start();
76             if (openDevices.size() == 0) {
77                 root.error("Failed to start any simulated devices, exiting...");
78                 System.exit(1);
79             }
80
81             if (params.distroFolder == null) {
82                 root.error("Distro folder is not set, exiting...");
83                 System.exit(1);
84             }
85
86             root.warn(params.distroFolder.getAbsolutePath());
87             try {
88                 runtime.exec(params.distroFolder.getAbsolutePath() + "/bin/start");
89                 String status;
90                 do {
91                     final Process list = runtime.exec(params.distroFolder.getAbsolutePath()
92                         + "/bin/client feature:list");
93                     try {
94                         Thread.sleep(2000L);
95                     } catch (InterruptedException e) {
96                         root.warn("Failed to sleep", e);
97                     }
98                     status = CharStreams.toString(new BufferedReader(new InputStreamReader(list.getErrorStream())));
99                     root.warn(status);
100                 } while (status.startsWith("Failed to get the session"));
101                 root.warn("Doing feature install {}", params.distroFolder.getAbsolutePath()
102                     + "/bin/client feature:install odl-restconf-nb-rfc8040 odl-netconf-topology");
103                 final Process featureInstall = runtime.exec(params.distroFolder.getAbsolutePath()
104                     + "/bin/client feature:install odl-restconf-nb-rfc8040 odl-netconf-topology");
105                 root.warn(
106                     CharStreams.toString(new BufferedReader(new InputStreamReader(featureInstall.getInputStream()))));
107                 root.warn(
108                     CharStreams.toString(new BufferedReader(new InputStreamReader(featureInstall.getErrorStream()))));
109
110             } catch (IOException e) {
111                 root.error("Failed to start karaf", e);
112                 System.exit(1);
113             }
114
115             waitNetconfTopologyReady(params);
116             final Execution ex = new Execution(openDevices, params);
117             ex.call();
118
119             root.warn("Karaf started, starting stopwatch");
120             STOPWATCH.start();
121
122             try {
123                 EXECUTOR.schedule(new ScaleVerifyCallable(params), RETRY_DELAY, TimeUnit.SECONDS);
124                 root.warn("First callable scheduled");
125                 SEMAPHORE.acquire();
126                 root.warn("semaphore released");
127             } catch (InterruptedException e) {
128                 throw new RuntimeException(e);
129             }
130
131             timeoutGuardFuture.cancel(false);
132             params.deviceCount += DEVICE_STEP;
133             netconfDeviceSimulator.close();
134             STOPWATCH.reset();
135
136             cleanup(runtime, params);
137         }
138     }
139
140     private static void setUpLoggers(final TesttoolParameters params) {
141         System.setProperty("log_file_name", "scale-util.log");
142
143         root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
144         root.setLevel(params.debug ? Level.DEBUG : Level.INFO);
145         resultsLog = LoggerFactory.getLogger("results");
146     }
147
148     private static void cleanup(final Runtime runtime, final TesttoolParameters params) {
149         try {
150             stopKaraf(runtime, params);
151             deleteFolder(new File(params.distroFolder.getAbsoluteFile() + "/data"));
152         } catch (IOException | InterruptedException e) {
153             root.warn("Failed to stop karaf", e);
154             System.exit(1);
155         }
156     }
157
158     private static void stopKaraf(final Runtime runtime, final TesttoolParameters params)
159             throws IOException, InterruptedException {
160         root.info("Stopping karaf and sleeping for 10 sec..");
161         String controllerPid = "";
162         do {
163             final Process pgrep = runtime.exec("pgrep -f org.apache.karaf.main.Main");
164
165             controllerPid = CharStreams.toString(new BufferedReader(new InputStreamReader(pgrep.getInputStream())));
166             root.warn(controllerPid);
167             runtime.exec("kill -9 " + controllerPid);
168
169             Thread.sleep(10000L);
170         } while (!controllerPid.isEmpty());
171     }
172
173     private static void deleteFolder(final File folder) {
174         File[] files = folder.listFiles();
175         if (files != null) { //some JVMs return null for empty dirs
176             for (File f : files) {
177                 if (f.isDirectory()) {
178                     deleteFolder(f);
179                 } else {
180                     if (!f.delete()) {
181                         root.warn("Failed to delete {}", f);
182                     }
183                 }
184             }
185         }
186         if (!folder.delete()) {
187             root.warn("Failed to delete {}", folder);
188         }
189     }
190
191     private static void waitNetconfTopologyReady(final TesttoolParameters params) {
192         root.info("Wait for Netconf topology to be accessible via Restconf");
193         HttpResponse<String> response = requestNetconfTopology(params);
194         while (response == null || (response.statusCode() != 200 && response.statusCode() != 204)) {
195             if (response == null) {
196                 root.warn("Failed to get response from controller, going to sleep...");
197             } else {
198                 root.warn("Received status code {}, going to sleep...", response.statusCode());
199             }
200             try {
201                 Thread.sleep(1000L);
202                 response = requestNetconfTopology(params);
203             } catch (InterruptedException e) {
204                 throw new RuntimeException(e);
205             }
206         }
207         root.info("Returned status code {}, Netconf topology is accessible", response.statusCode());
208     }
209
210     private static HttpResponse<String> requestNetconfTopology(final TesttoolParameters params) {
211         final HttpClient httpClient = HttpClient.newBuilder()
212                 .connectTimeout(Duration.ofSeconds(Integer.MAX_VALUE))
213                 .authenticator(new Authenticator() {
214                     @Override
215                     protected PasswordAuthentication getPasswordAuthentication() {
216                         return new PasswordAuthentication(params.controllerAuthUsername,
217                                 params.controllerAuthPassword.toCharArray());
218                     }
219                 })
220                 .build();
221         final HttpRequest request = HttpRequest.newBuilder(URI.create(String.format(RESTCONF_URL, params.controllerIp,
222                         params.controllerPort)))
223                 .GET()
224                 .header("Content-Type", "application/json")
225                 .header("Accept", "application/json")
226                 .build();
227         try {
228             return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
229         } catch (IOException e) {
230             root.warn(e.getMessage());
231             return null;
232         } catch (InterruptedException e) {
233             throw new RuntimeException(e);
234         }
235     }
236
237     private static class ScaleVerifyCallable implements Callable<Void> {
238         private static final Logger LOG = LoggerFactory.getLogger(ScaleVerifyCallable.class);
239         private static final Pattern PATTERN = Pattern.compile("connected");
240
241         private final HttpClient httpClient;
242         private final HttpRequest request;
243
244         private final int deviceCount;
245
246         ScaleVerifyCallable(final TesttoolParameters params) {
247             deviceCount = params.deviceCount;
248             httpClient = HttpClient.newBuilder()
249                     .authenticator(new Authenticator() {
250                         @Override
251                         protected PasswordAuthentication getPasswordAuthentication() {
252                             return new PasswordAuthentication(params.controllerAuthUsername,
253                                 params.controllerAuthPassword.toCharArray());
254                         }
255                     })
256                     .build();
257             request = HttpRequest.newBuilder(URI.create(String.format(RESTCONF_URL, params.controllerIp,
258                             params.controllerPort)))
259                     .GET()
260                     .header("Content-Type", "application/xml")
261                     .header("Accept", "application/xml")
262                     .build();
263         }
264
265         @Override
266         public Void call() throws Exception {
267             LOG.info("Checking number of connected devices.");
268             try {
269                 final HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
270                 if (response.statusCode() != 200 && response.statusCode() != 204) {
271                     LOG.warn("Request failed, status code: {}", response.statusCode());
272                     EXECUTOR.schedule(this, RETRY_DELAY, TimeUnit.SECONDS);
273                 } else {
274                     final String body = response.body();
275                     final Matcher matcher = PATTERN.matcher(body);
276                     int count = 0;
277                     while (matcher.find()) {
278                         count++;
279                     }
280                     resultsLog.info("Currently connected devices : {} out of {}, time elapsed: {}",
281                         count, deviceCount, STOPWATCH);
282                     if (count != deviceCount) {
283                         EXECUTOR.schedule(this, RETRY_DELAY, TimeUnit.SECONDS);
284                     } else {
285                         STOPWATCH.stop();
286                         resultsLog.info("All {} of {} devices connected in {}", count, deviceCount, STOPWATCH);
287                         SEMAPHORE.release();
288                     }
289                 }
290             } catch (ConnectException e) {
291                 LOG.warn("Failed to connect to Restconf, is the controller running?", e);
292                 EXECUTOR.schedule(this, RETRY_DELAY, TimeUnit.SECONDS);
293             }
294             return null;
295         }
296     }
297
298     private static class TimeoutGuard implements Callable<Void> {
299         @Override
300         public Void call() {
301             resultsLog.warn("Timeout for scale test reached after: {} ..aborting", STOPWATCH);
302             root.warn("Timeout for scale test reached after: {} ..aborting", STOPWATCH);
303             System.exit(0);
304             return null;
305         }
306     }
307
308     @SuppressWarnings("checkstyle:illegalCatch")
309     public static class LoggingWrapperExecutor extends ScheduledThreadPoolExecutor {
310         public LoggingWrapperExecutor(final int corePoolSize) {
311             super(corePoolSize);
312         }
313
314         @Override
315         public <V> ScheduledFuture<V> schedule(final Callable<V> callable, final long delay, final TimeUnit unit) {
316             return super.schedule(new LogOnExceptionCallable<>(callable), delay, unit);
317         }
318
319         private static class LogOnExceptionCallable<T> implements Callable<T> {
320             private final Callable<T> theCallable;
321
322             LogOnExceptionCallable(final Callable<T> theCallable) {
323                 this.theCallable = theCallable;
324             }
325
326             @Override
327             public T call() {
328                 try {
329                     return theCallable.call();
330                 } catch (Exception e) {
331                     // log
332                     root.warn("error in executing: " + theCallable + ". It will no longer be run!", e);
333
334                     // rethrow so that the executor can do it's thing
335                     throw new RuntimeException(e);
336                 }
337             }
338         }
339     }
340 }