2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.netconf.test.tool;
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;
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;
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;
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";
48 private static final long TIMEOUT = 20L;
49 private static final long RETRY_DELAY = 10L;
50 private static final int DEVICE_STEP = 1000;
52 private static ch.qos.logback.classic.Logger root;
53 private static Logger resultsLog;
58 @SuppressWarnings("checkstyle:illegalCatch")
59 public static void main(final String[] args) {
60 final TesttoolParameters params = TesttoolParameters.parseArgs(args, TesttoolParameters.getParser());
64 // cleanup at the start in case controller was already running
65 final Runtime runtime = Runtime.getRuntime();
66 cleanup(runtime, params);
69 root.warn("Starting scale test with {} devices", params.deviceCount);
70 final ScheduledFuture<?> timeoutGuardFuture = EXECUTOR.schedule(new TimeoutGuard(), TIMEOUT,
72 final Configuration configuration = new ConfigurationBuilder().from(params).build();
73 final NetconfDeviceSimulator netconfDeviceSimulator = new NetconfDeviceSimulator(configuration);
75 final List<Integer> openDevices = netconfDeviceSimulator.start();
76 if (openDevices.size() == 0) {
77 root.error("Failed to start any simulated devices, exiting...");
81 if (params.distroFolder == null) {
82 root.error("Distro folder is not set, exiting...");
86 root.warn(params.distroFolder.getAbsolutePath());
88 runtime.exec(params.distroFolder.getAbsolutePath() + "/bin/start");
91 final Process list = runtime.exec(params.distroFolder.getAbsolutePath()
92 + "/bin/client feature:list");
95 } catch (InterruptedException e) {
96 root.warn("Failed to sleep", e);
98 status = CharStreams.toString(new BufferedReader(new InputStreamReader(list.getErrorStream())));
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");
106 CharStreams.toString(new BufferedReader(new InputStreamReader(featureInstall.getInputStream()))));
108 CharStreams.toString(new BufferedReader(new InputStreamReader(featureInstall.getErrorStream()))));
110 } catch (IOException e) {
111 root.error("Failed to start karaf", e);
115 waitNetconfTopologyReady(params);
116 final Execution ex = new Execution(openDevices, params);
119 root.warn("Karaf started, starting stopwatch");
123 EXECUTOR.schedule(new ScaleVerifyCallable(params), RETRY_DELAY, TimeUnit.SECONDS);
124 root.warn("First callable scheduled");
126 root.warn("semaphore released");
127 } catch (InterruptedException e) {
128 throw new RuntimeException(e);
131 timeoutGuardFuture.cancel(false);
132 params.deviceCount += DEVICE_STEP;
133 netconfDeviceSimulator.close();
136 cleanup(runtime, params);
140 private static void setUpLoggers(final TesttoolParameters params) {
141 System.setProperty("log_file_name", "scale-util.log");
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");
148 private static void cleanup(final Runtime runtime, final TesttoolParameters params) {
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);
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 = "";
163 final Process pgrep = runtime.exec("pgrep -f org.apache.karaf.main.Main");
165 controllerPid = CharStreams.toString(new BufferedReader(new InputStreamReader(pgrep.getInputStream())));
166 root.warn(controllerPid);
167 runtime.exec("kill -9 " + controllerPid);
169 Thread.sleep(10000L);
170 } while (!controllerPid.isEmpty());
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()) {
181 root.warn("Failed to delete {}", f);
186 if (!folder.delete()) {
187 root.warn("Failed to delete {}", folder);
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...");
198 root.warn("Received status code {}, going to sleep...", response.statusCode());
202 response = requestNetconfTopology(params);
203 } catch (InterruptedException e) {
204 throw new RuntimeException(e);
207 root.info("Returned status code {}, Netconf topology is accessible", response.statusCode());
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() {
215 protected PasswordAuthentication getPasswordAuthentication() {
216 return new PasswordAuthentication(params.controllerAuthUsername,
217 params.controllerAuthPassword.toCharArray());
221 final HttpRequest request = HttpRequest.newBuilder(URI.create(String.format(RESTCONF_URL, params.controllerIp,
222 params.controllerPort)))
224 .header("Content-Type", "application/json")
225 .header("Accept", "application/json")
228 return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
229 } catch (IOException e) {
230 root.warn(e.getMessage());
232 } catch (InterruptedException e) {
233 throw new RuntimeException(e);
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");
241 private final HttpClient httpClient;
242 private final HttpRequest request;
244 private final int deviceCount;
246 ScaleVerifyCallable(final TesttoolParameters params) {
247 deviceCount = params.deviceCount;
248 httpClient = HttpClient.newBuilder()
249 .authenticator(new Authenticator() {
251 protected PasswordAuthentication getPasswordAuthentication() {
252 return new PasswordAuthentication(params.controllerAuthUsername,
253 params.controllerAuthPassword.toCharArray());
257 request = HttpRequest.newBuilder(URI.create(String.format(RESTCONF_URL, params.controllerIp,
258 params.controllerPort)))
260 .header("Content-Type", "application/xml")
261 .header("Accept", "application/xml")
266 public Void call() throws Exception {
267 LOG.info("Checking number of connected devices.");
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);
274 final String body = response.body();
275 final Matcher matcher = PATTERN.matcher(body);
277 while (matcher.find()) {
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);
286 resultsLog.info("All {} of {} devices connected in {}", count, deviceCount, STOPWATCH);
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);
298 private static class TimeoutGuard implements Callable<Void> {
301 resultsLog.warn("Timeout for scale test reached after: {} ..aborting", STOPWATCH);
302 root.warn("Timeout for scale test reached after: {} ..aborting", STOPWATCH);
308 @SuppressWarnings("checkstyle:illegalCatch")
309 public static class LoggingWrapperExecutor extends ScheduledThreadPoolExecutor {
310 public LoggingWrapperExecutor(final int corePoolSize) {
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);
319 private static class LogOnExceptionCallable<T> implements Callable<T> {
320 private final Callable<T> theCallable;
322 LogOnExceptionCallable(final Callable<T> theCallable) {
323 this.theCallable = theCallable;
329 return theCallable.call();
330 } catch (Exception e) {
332 root.warn("error in executing: " + theCallable + ". It will no longer be run!", e);
334 // rethrow so that the executor can do it's thing
335 throw new RuntimeException(e);