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