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