blob: ca04a7c27964631bc6d24a73b8fcc06963a79853 [file] [log] [blame]
Thomas Vachuskaf9c84362015-04-15 11:20:45 -07001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onlab.stc;
17
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070018import com.google.common.collect.ImmutableList;
Thomas Vachuska7c5b6532015-11-02 14:50:33 -080019import com.google.common.io.Files;
Thomas Vachuska750ab042015-06-17 10:42:15 -070020import org.eclipse.jetty.server.Server;
21import org.eclipse.jetty.servlet.ServletHandler;
22import org.eclipse.jetty.util.log.Logger;
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070023import org.onlab.stc.Coordinator.Status;
24
Thomas Vachuska7c5b6532015-11-02 14:50:33 -080025import java.io.File;
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070026import java.io.FileInputStream;
27import java.io.FileNotFoundException;
Thomas Vachuska7c5b6532015-11-02 14:50:33 -080028import java.io.IOException;
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070029import java.text.SimpleDateFormat;
30import java.util.Date;
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070031import java.util.List;
32import java.util.Objects;
Thomas Vachuska29e82172015-08-27 11:13:59 -070033import java.util.Set;
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070034
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070035import static java.lang.System.currentTimeMillis;
36import static org.onlab.stc.Coordinator.Status.*;
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070037import static org.onlab.stc.Coordinator.print;
38
39/**
40 * Main program for executing system test coordinator.
41 */
42public final class Main {
43
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070044 private static final String NONE = "\u001B[0m";
45 private static final String GRAY = "\u001B[30;1m";
46 private static final String RED = "\u001B[31;1m";
47 private static final String GREEN = "\u001B[32;1m";
48 private static final String BLUE = "\u001B[36m";
49
Thomas Vachuskad542cc42015-09-11 16:15:36 -070050 private static final String SUCCESS_SUMMARY =
51 "%s %sPassed! %d steps succeeded%s";
Thomas Vachuskad6c965d2015-09-08 20:48:58 -070052 private static final String MIXED_SUMMARY =
53 "%s%d steps succeeded; %s%d steps failed; %s%d steps skipped%s";
Thomas Vachuskad542cc42015-09-11 16:15:36 -070054 private static final String FAILURE_SUMMARY = "%s %sFailed! " + MIXED_SUMMARY;
55 private static final String ABORTED_SUMMARY = "%s %sAborted! " + MIXED_SUMMARY;
Thomas Vachuskad6c965d2015-09-08 20:48:58 -070056
57 private boolean isReported = false;
Thomas Vachuska29e82172015-08-27 11:13:59 -070058
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070059 private enum Command {
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070060 LIST, RUN, RUN_RANGE, HELP
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070061 }
62
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070063 private final String scenarioFile;
64
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070065 private Command command = Command.HELP;
66 private String runFromPatterns = "";
67 private String runToPatterns = "";
68
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070069 private Coordinator coordinator;
Thomas Vachuska7c5b6532015-11-02 14:50:33 -080070 private Compiler compiler;
Thomas Vachuskab51b8bc2015-07-27 08:37:12 -070071 private Monitor monitor;
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070072 private Listener delegate = new Listener();
73
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070074 private static boolean useColor = Objects.equals("true", System.getenv("stcColor"));
Thomas Vachuska7c5b6532015-11-02 14:50:33 -080075 private static boolean dumpLogs = Objects.equals("true", System.getenv("stcDumpLogs"));
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070076
77 // usage: stc [<scenario-file>] [run]
78 // usage: stc [<scenario-file>] run [from <from-patterns>] [to <to-patterns>]]
79 // usage: stc [<scenario-file>] list
80
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070081 // Public construction forbidden
82 private Main(String[] args) {
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070083 this.scenarioFile = args[0];
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070084
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070085 if (args.length <= 1 || args.length == 2 && args[1].equals("run")) {
86 command = Command.RUN;
87 } else if (args.length == 2 && args[1].equals("list")) {
88 command = Command.LIST;
89 } else if (args.length >= 4 && args[1].equals("run")) {
90 int i = 2;
91 if (args[i].equals("from")) {
92 command = Command.RUN_RANGE;
93 runFromPatterns = args[i + 1];
94 i += 2;
95 }
96
97 if (args.length >= i + 2 && args[i].equals("to")) {
98 command = Command.RUN_RANGE;
99 runToPatterns = args[i + 1];
100 }
101 }
102 }
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700103
104 /**
105 * Main entry point for coordinating test scenario execution.
106 *
107 * @param args command-line arguments
108 */
109 public static void main(String[] args) {
110 Main main = new Main(args);
111 main.run();
112 }
113
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700114 // Runs the scenario processing
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700115 private void run() {
116 try {
117 // Load scenario
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700118 Scenario scenario = Scenario.loadScenario(new FileInputStream(scenarioFile));
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700119
120 // Elaborate scenario
Thomas Vachuska7c5b6532015-11-02 14:50:33 -0800121 compiler = new Compiler(scenario);
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700122 compiler.compile();
123
Thomas Vachuskab51b8bc2015-07-27 08:37:12 -0700124 // Setup the process flow coordinator
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700125 coordinator = new Coordinator(scenario, compiler.processFlow(),
126 compiler.logDir());
127 coordinator.addListener(delegate);
Thomas Vachuska750ab042015-06-17 10:42:15 -0700128
Thomas Vachuskab51b8bc2015-07-27 08:37:12 -0700129 // Prepare the GUI monitor
130 monitor = new Monitor(coordinator, compiler);
131 startMonitorServer(monitor);
Thomas Vachuska750ab042015-06-17 10:42:15 -0700132
Thomas Vachuskab51b8bc2015-07-27 08:37:12 -0700133 // Execute process flow
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700134 processCommand();
135
136 } catch (FileNotFoundException e) {
137 print("Unable to find scenario file %s", scenarioFile);
138 }
139 }
140
Thomas Vachuska750ab042015-06-17 10:42:15 -0700141 // Initiates a web-server for the monitor GUI.
Thomas Vachuskab51b8bc2015-07-27 08:37:12 -0700142 private static void startMonitorServer(Monitor monitor) {
Thomas Vachuska750ab042015-06-17 10:42:15 -0700143 org.eclipse.jetty.util.log.Log.setLog(new NullLogger());
144 Server server = new Server(9999);
145 ServletHandler handler = new ServletHandler();
146 server.setHandler(handler);
Thomas Vachuskab51b8bc2015-07-27 08:37:12 -0700147 MonitorWebSocketServlet.setMonitor(monitor);
Thomas Vachuska750ab042015-06-17 10:42:15 -0700148 handler.addServletWithMapping(MonitorWebSocketServlet.class, "/*");
149 try {
150 server.start();
151 } catch (Exception e) {
152 e.printStackTrace();
153 }
154 }
155
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700156 // Processes the appropriate command
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700157 private void processCommand() {
158 switch (command) {
159 case RUN:
160 processRun();
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700161 break;
162 case LIST:
163 processList();
164 break;
165 case RUN_RANGE:
166 processRunRange();
167 break;
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700168 default:
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700169 print("Unsupported command %s", command);
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700170 }
171 }
172
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700173 // Processes the scenario 'run' command.
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700174 private void processRun() {
Thomas Vachuska1b403a52015-08-26 11:30:48 -0700175 coordinator.reset();
176 runCoordinator();
177 }
178
179 // Processes the scenario 'run' command for range of steps.
180 private void processRunRange() {
181 coordinator.reset(list(runFromPatterns), list(runToPatterns));
182 runCoordinator();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700183 }
184
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700185 // Processes the scenario 'list' command.
186 private void processList() {
187 coordinator.getRecords()
Thomas Vachuska0ec6ff42015-07-17 11:00:02 -0700188 .forEach(event -> logStatus(event.time(), event.name(), event.status(), event.command()));
Thomas Vachuskad542cc42015-09-11 16:15:36 -0700189 printSummary(0, false);
Thomas Vachuska1b403a52015-08-26 11:30:48 -0700190 System.exit(0);
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700191 }
192
Thomas Vachuska1b403a52015-08-26 11:30:48 -0700193 // Runs the coordinator and waits for it to finish.
194 private void runCoordinator() {
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700195 try {
Thomas Vachuskad6c965d2015-09-08 20:48:58 -0700196 Runtime.getRuntime().addShutdownHook(new ShutdownHook());
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700197 coordinator.start();
198 int exitCode = coordinator.waitFor();
199 pause(100); // allow stdout to flush
Thomas Vachuskad6c965d2015-09-08 20:48:58 -0700200 printSummary(exitCode, false);
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700201 System.exit(exitCode);
202 } catch (InterruptedException e) {
203 print("Unable to execute scenario %s", scenarioFile);
204 }
205 }
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700206
Thomas Vachuskad6c965d2015-09-08 20:48:58 -0700207 private synchronized void printSummary(int exitCode, boolean isAborted) {
208 if (!isReported) {
209 isReported = true;
210 Set<Step> steps = coordinator.getSteps();
Thomas Vachuskad542cc42015-09-11 16:15:36 -0700211 String duration = formatDuration((int) (coordinator.duration() / 1_000));
Thomas Vachuskad6c965d2015-09-08 20:48:58 -0700212 int count = steps.size();
213 if (exitCode == 0) {
Thomas Vachuskad542cc42015-09-11 16:15:36 -0700214 print(SUCCESS_SUMMARY, duration, color(SUCCEEDED), count, color(null));
Thomas Vachuskad6c965d2015-09-08 20:48:58 -0700215 } else {
216 long success = steps.stream().filter(s -> coordinator.getStatus(s) == SUCCEEDED).count();
217 long failed = steps.stream().filter(s -> coordinator.getStatus(s) == FAILED).count();
218 long skipped = steps.stream().filter(s -> coordinator.getStatus(s) == SKIPPED).count();
Thomas Vachuskad542cc42015-09-11 16:15:36 -0700219 print(isAborted ? ABORTED_SUMMARY : FAILURE_SUMMARY, duration,
Thomas Vachuskad6c965d2015-09-08 20:48:58 -0700220 color(FAILED), color(SUCCEEDED), success,
221 color(FAILED), failed, color(SKIPPED), skipped, color(null));
222 }
Thomas Vachuska29e82172015-08-27 11:13:59 -0700223 }
224 }
225
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700226 /**
227 * Internal delegate to monitor the process execution.
228 */
Thomas Vachuska7c5b6532015-11-02 14:50:33 -0800229 private class Listener implements StepProcessListener {
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700230 @Override
Thomas Vachuskab51b8bc2015-07-27 08:37:12 -0700231 public void onStart(Step step, String command) {
232 logStatus(currentTimeMillis(), step.name(), IN_PROGRESS, command);
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700233 }
234
235 @Override
Thomas Vachuska86439372015-06-05 09:21:32 -0700236 public void onCompletion(Step step, Status status) {
Thomas Vachuska0ec6ff42015-07-17 11:00:02 -0700237 logStatus(currentTimeMillis(), step.name(), status, null);
Thomas Vachuska7c5b6532015-11-02 14:50:33 -0800238 if (dumpLogs && !(step instanceof Group) && status == FAILED) {
239 dumpLogs(step);
240 }
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700241 }
242
243 @Override
244 public void onOutput(Step step, String line) {
245 }
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700246 }
247
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700248 // Logs the step status.
Thomas Vachuska0ec6ff42015-07-17 11:00:02 -0700249 private static void logStatus(long time, String name, Status status, String cmd) {
250 if (cmd != null) {
251 print("%s %s%s %s%s -- %s", time(time), color(status), name, action(status), color(null), cmd);
252 } else {
253 print("%s %s%s %s%s", time(time), color(status), name, action(status), color(null));
254 }
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700255 }
256
Thomas Vachuska7c5b6532015-11-02 14:50:33 -0800257 // Dumps the step logs to standard output.
258 private void dumpLogs(Step step) {
259 File logFile = new File(compiler.logDir(), step.name() + ".log");
260 try {
261 print(">>>>>");
262 Files.copy(logFile, System.out);
263 print("<<<<<");
264 } catch (IOException e) {
265 print("Unable to dump log file %s", logFile.getName());
266 }
267 }
268
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700269 // Produces a description of event using the specified step status.
270 private static String action(Status status) {
271 return status == IN_PROGRESS ? "started" :
272 (status == SUCCEEDED ? "completed" :
273 (status == FAILED ? "failed" :
274 (status == SKIPPED ? "skipped" : "waiting")));
275 }
276
277 // Produces an ANSI escape code for color using the specified step status.
278 private static String color(Status status) {
279 if (!useColor) {
280 return "";
281 }
282 return status == null ? NONE :
283 (status == IN_PROGRESS ? BLUE :
284 (status == SUCCEEDED ? GREEN :
285 (status == FAILED ? RED : GRAY)));
286 }
287
288 // Produces a list from the specified comma-separated string.
289 private static List<String> list(String patterns) {
290 return ImmutableList.copyOf(patterns.split(","));
291 }
292
293 // Produces a formatted time stamp.
294 private static String time(long time) {
295 return new SimpleDateFormat("YYYY-MM-dd HH:mm:ss").format(new Date(time));
296 }
297
298 // Pauses for the specified number of millis.
299 private static void pause(int ms) {
300 try {
301 Thread.sleep(ms);
302 } catch (InterruptedException e) {
303 print("Interrupted!");
304 }
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700305 }
306
Thomas Vachuskad542cc42015-09-11 16:15:36 -0700307 // Formats time duration
308 private static String formatDuration(int totalSeconds) {
309 int seconds = totalSeconds % 60;
310 int totalMinutes = totalSeconds / 60;
311 int minutes = totalMinutes % 60;
312 int hours = totalMinutes / 60;
313 return hours > 0 ?
314 String.format("%d:%02d:%02d", hours, minutes, seconds) :
315 String.format("%d:%02d", minutes, seconds);
316 }
317
Thomas Vachuskad6c965d2015-09-08 20:48:58 -0700318 // Shutdown hook to report status even when aborted.
319 private class ShutdownHook extends Thread {
320 @Override
321 public void run() {
322 printSummary(1, true);
323 }
324 }
325
Thomas Vachuska750ab042015-06-17 10:42:15 -0700326 // Logger to quiet Jetty down
327 private static class NullLogger implements Logger {
328 @Override
329 public String getName() {
330 return "quiet";
331 }
332
333 @Override
334 public void warn(String msg, Object... args) {
335 }
336
337 @Override
338 public void warn(Throwable thrown) {
339 }
340
341 @Override
342 public void warn(String msg, Throwable thrown) {
343 }
344
345 @Override
346 public void info(String msg, Object... args) {
347 }
348
349 @Override
350 public void info(Throwable thrown) {
351 }
352
353 @Override
354 public void info(String msg, Throwable thrown) {
355 }
356
357 @Override
358 public boolean isDebugEnabled() {
359 return false;
360 }
361
362 @Override
363 public void setDebugEnabled(boolean enabled) {
364 }
365
366 @Override
367 public void debug(String msg, Object... args) {
368 }
369
370 @Override
371 public void debug(Throwable thrown) {
372 }
373
374 @Override
375 public void debug(String msg, Throwable thrown) {
376 }
377
378 @Override
379 public Logger getLogger(String name) {
380 return this;
381 }
382
383 @Override
384 public void ignore(Throwable ignored) {
385 }
386 }
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700387}