blob: 09b894566645b6224811167703550a0e7f05bd46 [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 Vachuska750ab042015-06-17 10:42:15 -070019import org.eclipse.jetty.server.Server;
20import org.eclipse.jetty.servlet.ServletHandler;
21import org.eclipse.jetty.util.log.Logger;
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070022import org.onlab.stc.Coordinator.Status;
23
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070024import java.io.FileInputStream;
25import java.io.FileNotFoundException;
26import java.text.SimpleDateFormat;
27import java.util.Date;
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070028import java.util.List;
29import java.util.Objects;
Thomas Vachuska29e82172015-08-27 11:13:59 -070030import java.util.Set;
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070031
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070032import static java.lang.System.currentTimeMillis;
33import static org.onlab.stc.Coordinator.Status.*;
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070034import static org.onlab.stc.Coordinator.print;
35
36/**
37 * Main program for executing system test coordinator.
38 */
39public final class Main {
40
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070041 private static final String NONE = "\u001B[0m";
42 private static final String GRAY = "\u001B[30;1m";
43 private static final String RED = "\u001B[31;1m";
44 private static final String GREEN = "\u001B[32;1m";
45 private static final String BLUE = "\u001B[36m";
46
Thomas Vachuska29e82172015-08-27 11:13:59 -070047 private static final String SUCCESS_SUMMARY = "%sPassed! %d steps succeeded%s";
Thomas Vachuskad6c965d2015-09-08 20:48:58 -070048 private static final String MIXED_SUMMARY =
49 "%s%d steps succeeded; %s%d steps failed; %s%d steps skipped%s";
50 private static final String FAILURE_SUMMARY = "%sFailed! " + MIXED_SUMMARY;
51 private static final String ABORTED_SUMMARY = "%sAborted! " + MIXED_SUMMARY;
52
53 private boolean isReported = false;
Thomas Vachuska29e82172015-08-27 11:13:59 -070054
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070055 private enum Command {
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070056 LIST, RUN, RUN_RANGE, HELP
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070057 }
58
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070059 private final String scenarioFile;
60
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070061 private Command command = Command.HELP;
62 private String runFromPatterns = "";
63 private String runToPatterns = "";
64
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070065 private Coordinator coordinator;
Thomas Vachuskab51b8bc2015-07-27 08:37:12 -070066 private Monitor monitor;
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070067 private Listener delegate = new Listener();
68
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070069 private static boolean useColor = Objects.equals("true", System.getenv("stcColor"));
70
71 // usage: stc [<scenario-file>] [run]
72 // usage: stc [<scenario-file>] run [from <from-patterns>] [to <to-patterns>]]
73 // usage: stc [<scenario-file>] list
74
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070075 // Public construction forbidden
76 private Main(String[] args) {
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070077 this.scenarioFile = args[0];
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070078
Thomas Vachuska50ec1af2015-06-02 00:42:52 -070079 if (args.length <= 1 || args.length == 2 && args[1].equals("run")) {
80 command = Command.RUN;
81 } else if (args.length == 2 && args[1].equals("list")) {
82 command = Command.LIST;
83 } else if (args.length >= 4 && args[1].equals("run")) {
84 int i = 2;
85 if (args[i].equals("from")) {
86 command = Command.RUN_RANGE;
87 runFromPatterns = args[i + 1];
88 i += 2;
89 }
90
91 if (args.length >= i + 2 && args[i].equals("to")) {
92 command = Command.RUN_RANGE;
93 runToPatterns = args[i + 1];
94 }
95 }
96 }
Thomas Vachuskaf9c84362015-04-15 11:20:45 -070097
98 /**
99 * Main entry point for coordinating test scenario execution.
100 *
101 * @param args command-line arguments
102 */
103 public static void main(String[] args) {
104 Main main = new Main(args);
105 main.run();
106 }
107
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700108 // Runs the scenario processing
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700109 private void run() {
110 try {
111 // Load scenario
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700112 Scenario scenario = Scenario.loadScenario(new FileInputStream(scenarioFile));
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700113
114 // Elaborate scenario
115 Compiler compiler = new Compiler(scenario);
116 compiler.compile();
117
Thomas Vachuskab51b8bc2015-07-27 08:37:12 -0700118 // Setup the process flow coordinator
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700119 coordinator = new Coordinator(scenario, compiler.processFlow(),
120 compiler.logDir());
121 coordinator.addListener(delegate);
Thomas Vachuska750ab042015-06-17 10:42:15 -0700122
Thomas Vachuskab51b8bc2015-07-27 08:37:12 -0700123 // Prepare the GUI monitor
124 monitor = new Monitor(coordinator, compiler);
125 startMonitorServer(monitor);
Thomas Vachuska750ab042015-06-17 10:42:15 -0700126
Thomas Vachuskab51b8bc2015-07-27 08:37:12 -0700127 // Execute process flow
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700128 processCommand();
129
130 } catch (FileNotFoundException e) {
131 print("Unable to find scenario file %s", scenarioFile);
132 }
133 }
134
Thomas Vachuska750ab042015-06-17 10:42:15 -0700135 // Initiates a web-server for the monitor GUI.
Thomas Vachuskab51b8bc2015-07-27 08:37:12 -0700136 private static void startMonitorServer(Monitor monitor) {
Thomas Vachuska750ab042015-06-17 10:42:15 -0700137 org.eclipse.jetty.util.log.Log.setLog(new NullLogger());
138 Server server = new Server(9999);
139 ServletHandler handler = new ServletHandler();
140 server.setHandler(handler);
Thomas Vachuskab51b8bc2015-07-27 08:37:12 -0700141 MonitorWebSocketServlet.setMonitor(monitor);
Thomas Vachuska750ab042015-06-17 10:42:15 -0700142 handler.addServletWithMapping(MonitorWebSocketServlet.class, "/*");
143 try {
144 server.start();
145 } catch (Exception e) {
146 e.printStackTrace();
147 }
148 }
149
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700150 // Processes the appropriate command
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700151 private void processCommand() {
152 switch (command) {
153 case RUN:
154 processRun();
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700155 break;
156 case LIST:
157 processList();
158 break;
159 case RUN_RANGE:
160 processRunRange();
161 break;
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700162 default:
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700163 print("Unsupported command %s", command);
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700164 }
165 }
166
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700167 // Processes the scenario 'run' command.
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700168 private void processRun() {
Thomas Vachuska1b403a52015-08-26 11:30:48 -0700169 coordinator.reset();
170 runCoordinator();
171 }
172
173 // Processes the scenario 'run' command for range of steps.
174 private void processRunRange() {
175 coordinator.reset(list(runFromPatterns), list(runToPatterns));
176 runCoordinator();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700177 }
178
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700179 // Processes the scenario 'list' command.
180 private void processList() {
181 coordinator.getRecords()
Thomas Vachuska0ec6ff42015-07-17 11:00:02 -0700182 .forEach(event -> logStatus(event.time(), event.name(), event.status(), event.command()));
Thomas Vachuska1b403a52015-08-26 11:30:48 -0700183 System.exit(0);
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700184 }
185
Thomas Vachuska1b403a52015-08-26 11:30:48 -0700186 // Runs the coordinator and waits for it to finish.
187 private void runCoordinator() {
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700188 try {
Thomas Vachuskad6c965d2015-09-08 20:48:58 -0700189 Runtime.getRuntime().addShutdownHook(new ShutdownHook());
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700190 coordinator.start();
191 int exitCode = coordinator.waitFor();
192 pause(100); // allow stdout to flush
Thomas Vachuskad6c965d2015-09-08 20:48:58 -0700193 printSummary(exitCode, false);
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700194 System.exit(exitCode);
195 } catch (InterruptedException e) {
196 print("Unable to execute scenario %s", scenarioFile);
197 }
198 }
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700199
Thomas Vachuskad6c965d2015-09-08 20:48:58 -0700200 private synchronized void printSummary(int exitCode, boolean isAborted) {
201 if (!isReported) {
202 isReported = true;
203 Set<Step> steps = coordinator.getSteps();
204 int count = steps.size();
205 if (exitCode == 0) {
206 print(SUCCESS_SUMMARY, color(SUCCEEDED), count, color(null));
207 } else {
208 long success = steps.stream().filter(s -> coordinator.getStatus(s) == SUCCEEDED).count();
209 long failed = steps.stream().filter(s -> coordinator.getStatus(s) == FAILED).count();
210 long skipped = steps.stream().filter(s -> coordinator.getStatus(s) == SKIPPED).count();
211 print(isAborted ? ABORTED_SUMMARY : FAILURE_SUMMARY,
212 color(FAILED), color(SUCCEEDED), success,
213 color(FAILED), failed, color(SKIPPED), skipped, color(null));
214 }
Thomas Vachuska29e82172015-08-27 11:13:59 -0700215 }
216 }
217
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700218 /**
219 * Internal delegate to monitor the process execution.
220 */
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700221 private static class Listener implements StepProcessListener {
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700222 @Override
Thomas Vachuskab51b8bc2015-07-27 08:37:12 -0700223 public void onStart(Step step, String command) {
224 logStatus(currentTimeMillis(), step.name(), IN_PROGRESS, command);
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700225 }
226
227 @Override
Thomas Vachuska86439372015-06-05 09:21:32 -0700228 public void onCompletion(Step step, Status status) {
Thomas Vachuska0ec6ff42015-07-17 11:00:02 -0700229 logStatus(currentTimeMillis(), step.name(), status, null);
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700230 }
231
232 @Override
233 public void onOutput(Step step, String line) {
234 }
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700235 }
236
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700237 // Logs the step status.
Thomas Vachuska0ec6ff42015-07-17 11:00:02 -0700238 private static void logStatus(long time, String name, Status status, String cmd) {
239 if (cmd != null) {
240 print("%s %s%s %s%s -- %s", time(time), color(status), name, action(status), color(null), cmd);
241 } else {
242 print("%s %s%s %s%s", time(time), color(status), name, action(status), color(null));
243 }
Thomas Vachuska50ec1af2015-06-02 00:42:52 -0700244 }
245
246 // Produces a description of event using the specified step status.
247 private static String action(Status status) {
248 return status == IN_PROGRESS ? "started" :
249 (status == SUCCEEDED ? "completed" :
250 (status == FAILED ? "failed" :
251 (status == SKIPPED ? "skipped" : "waiting")));
252 }
253
254 // Produces an ANSI escape code for color using the specified step status.
255 private static String color(Status status) {
256 if (!useColor) {
257 return "";
258 }
259 return status == null ? NONE :
260 (status == IN_PROGRESS ? BLUE :
261 (status == SUCCEEDED ? GREEN :
262 (status == FAILED ? RED : GRAY)));
263 }
264
265 // Produces a list from the specified comma-separated string.
266 private static List<String> list(String patterns) {
267 return ImmutableList.copyOf(patterns.split(","));
268 }
269
270 // Produces a formatted time stamp.
271 private static String time(long time) {
272 return new SimpleDateFormat("YYYY-MM-dd HH:mm:ss").format(new Date(time));
273 }
274
275 // Pauses for the specified number of millis.
276 private static void pause(int ms) {
277 try {
278 Thread.sleep(ms);
279 } catch (InterruptedException e) {
280 print("Interrupted!");
281 }
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700282 }
283
Thomas Vachuskad6c965d2015-09-08 20:48:58 -0700284 // Shutdown hook to report status even when aborted.
285 private class ShutdownHook extends Thread {
286 @Override
287 public void run() {
288 printSummary(1, true);
289 }
290 }
291
Thomas Vachuska750ab042015-06-17 10:42:15 -0700292 // Logger to quiet Jetty down
293 private static class NullLogger implements Logger {
294 @Override
295 public String getName() {
296 return "quiet";
297 }
298
299 @Override
300 public void warn(String msg, Object... args) {
301 }
302
303 @Override
304 public void warn(Throwable thrown) {
305 }
306
307 @Override
308 public void warn(String msg, Throwable thrown) {
309 }
310
311 @Override
312 public void info(String msg, Object... args) {
313 }
314
315 @Override
316 public void info(Throwable thrown) {
317 }
318
319 @Override
320 public void info(String msg, Throwable thrown) {
321 }
322
323 @Override
324 public boolean isDebugEnabled() {
325 return false;
326 }
327
328 @Override
329 public void setDebugEnabled(boolean enabled) {
330 }
331
332 @Override
333 public void debug(String msg, Object... args) {
334 }
335
336 @Override
337 public void debug(Throwable thrown) {
338 }
339
340 @Override
341 public void debug(String msg, Throwable thrown) {
342 }
343
344 @Override
345 public Logger getLogger(String name) {
346 return this;
347 }
348
349 @Override
350 public void ignore(Throwable ignored) {
351 }
352 }
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700353}