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.base.Throwables;
13 import com.google.common.io.CharStreams;
14 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
15 import java.io.BufferedReader;
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;
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;
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";
49 private static final long TIMEOUT = 20L;
50 private static final long RETRY_DELAY = 10L;
51 private static final int DEVICE_STEP = 1000;
53 private static ch.qos.logback.classic.Logger root;
54 private static Logger resultsLog;
59 @SuppressWarnings("checkstyle:illegalCatch")
60 public static void main(final String[] args) {
61 final TesttoolParameters params = TesttoolParameters.parseArgs(args, TesttoolParameters.getParser());
65 // cleanup at the start in case controller was already running
66 final Runtime runtime = Runtime.getRuntime();
67 cleanup(runtime, params);
70 root.warn("Starting scale test with {} devices", params.deviceCount);
71 final ScheduledFuture<?> timeoutGuardFuture = EXECUTOR.schedule(new TimeoutGuard(), TIMEOUT,
73 final Configuration configuration = new ConfigurationBuilder().from(params).build();
74 final NetconfDeviceSimulator netconfDeviceSimulator = new NetconfDeviceSimulator(configuration);
76 final List<Integer> openDevices = netconfDeviceSimulator.start();
77 if (openDevices.size() == 0) {
78 root.error("Failed to start any simulated devices, exiting...");
82 if (params.distroFolder == null) {
83 root.error("Distro folder is not set, exiting...");
87 root.warn(params.distroFolder.getAbsolutePath());
89 runtime.exec(params.distroFolder.getAbsolutePath() + "/bin/start");
92 final Process list = runtime.exec(params.distroFolder.getAbsolutePath()
93 + "/bin/client feature:list");
96 } catch (InterruptedException e) {
97 root.warn("Failed to sleep", e);
99 status = CharStreams.toString(new BufferedReader(new InputStreamReader(list.getErrorStream())));
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");
107 CharStreams.toString(new BufferedReader(new InputStreamReader(featureInstall.getInputStream()))));
109 CharStreams.toString(new BufferedReader(new InputStreamReader(featureInstall.getErrorStream()))));
111 } catch (IOException e) {
112 root.error("Failed to start karaf", e);
116 waitNetconfTopologyReady(params);
117 final Execution ex = new Execution(openDevices, params);
120 root.warn("Karaf started, starting stopwatch");
124 EXECUTOR.schedule(new ScaleVerifyCallable(params), RETRY_DELAY, TimeUnit.SECONDS);
125 root.warn("First callable scheduled");
127 root.warn("semaphore released");
128 } catch (InterruptedException e) {
129 throw new IllegalStateException("Interrupted while waiting for semaphore", e);
132 timeoutGuardFuture.cancel(false);
133 params.deviceCount += DEVICE_STEP;
134 netconfDeviceSimulator.close();
137 cleanup(runtime, params);
141 private static void setUpLoggers(final TesttoolParameters params) {
142 System.setProperty("log_file_name", "scale-util.log");
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");
149 private static void cleanup(final Runtime runtime, final TesttoolParameters params) {
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);
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 = "";
164 final Process pgrep = runtime.exec("pgrep -f org.apache.karaf.main.Main");
166 controllerPid = CharStreams.toString(new BufferedReader(new InputStreamReader(pgrep.getInputStream())));
167 root.warn(controllerPid);
168 runtime.exec("kill -9 " + controllerPid);
170 Thread.sleep(10000L);
171 } while (!controllerPid.isEmpty());
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()) {
180 } else if (!f.delete()) {
181 root.warn("Failed to delete {}", f);
185 if (!folder.delete()) {
186 root.warn("Failed to delete {}", folder);
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...");
197 root.warn("Received status code {}, going to sleep...", response.statusCode());
201 } catch (InterruptedException e) {
202 throw new IllegalStateException("Sleep interrupted", e);
204 response = requestNetconfTopology(params);
206 root.info("Returned status code {}, Netconf topology is accessible", response.statusCode());
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() {
214 protected PasswordAuthentication getPasswordAuthentication() {
215 return new PasswordAuthentication(params.controllerAuthUsername,
216 params.controllerAuthPassword.toCharArray());
220 final HttpRequest request = HttpRequest.newBuilder(URI.create(String.format(RESTCONF_URL, params.controllerIp,
221 params.controllerPort)))
223 .header("Content-Type", "application/json")
224 .header("Accept", "application/json")
227 return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
228 } catch (IOException e) {
229 root.warn(e.getMessage());
231 } catch (InterruptedException e) {
232 throw new IllegalStateException("Interrupted while waiting for response", e);
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");
240 private final HttpClient httpClient;
241 private final HttpRequest request;
243 private final int deviceCount;
245 ScaleVerifyCallable(final TesttoolParameters params) {
246 deviceCount = params.deviceCount;
247 httpClient = HttpClient.newBuilder()
248 .authenticator(new Authenticator() {
250 protected PasswordAuthentication getPasswordAuthentication() {
251 return new PasswordAuthentication(params.controllerAuthUsername,
252 params.controllerAuthPassword.toCharArray());
256 request = HttpRequest.newBuilder(URI.create(String.format(RESTCONF_URL, params.controllerIp,
257 params.controllerPort)))
259 .header("Content-Type", "application/xml")
260 .header("Accept", "application/xml")
265 public Void call() throws Exception {
266 LOG.info("Checking number of connected devices.");
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);
273 final String body = response.body();
274 final Matcher matcher = PATTERN.matcher(body);
276 while (matcher.find()) {
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);
285 resultsLog.info("All {} of {} devices connected in {}", count, deviceCount, STOPWATCH);
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);
297 private static class TimeoutGuard implements Callable<Void> {
300 resultsLog.warn("Timeout for scale test reached after: {} ..aborting", STOPWATCH);
301 root.warn("Timeout for scale test reached after: {} ..aborting", STOPWATCH);
307 public static class LoggingWrapperExecutor extends ScheduledThreadPoolExecutor {
308 public LoggingWrapperExecutor(final int corePoolSize) {
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);
317 private static class LogOnExceptionCallable<T> implements Callable<T> {
318 private final Callable<T> theCallable;
320 LogOnExceptionCallable(final Callable<T> theCallable) {
321 this.theCallable = theCallable;
325 @SuppressWarnings("checkstyle:illegalCatch")
328 return theCallable.call();
329 } catch (Exception e) {
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);