package org.batfish.client; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.output.WriterOutputStream; import org.apache.commons.lang.exception.ExceptionUtils; import org.batfish.client.Settings.RunMode; import org.batfish.client.answer.LoadQuestionAnswerElement; import org.batfish.common.BatfishException; import org.batfish.common.BfConsts; import org.batfish.common.BatfishLogger; import org.batfish.common.Pair; import org.batfish.common.Task; import org.batfish.common.Task.Batch; import org.batfish.common.WorkItem; import org.batfish.common.CoordConsts.WorkStatusCode; import org.batfish.common.plugin.AbstractClient; import org.batfish.common.plugin.IClient; import org.batfish.common.util.BatfishObjectMapper; import org.batfish.common.util.CommonUtil; import org.batfish.common.util.ZipUtility; import org.batfish.datamodel.Configuration; import org.batfish.datamodel.answers.Answer; import org.batfish.datamodel.questions.IEnvironmentCreationQuestion; import org.batfish.datamodel.questions.Question; import org.batfish.datamodel.questions.Question.InstanceData; import org.batfish.datamodel.questions.Question.InstanceData.Variable; import org.codehaus.jettison.json.JSONArray; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; import org.codehaus.jettison.json.JSONTokener; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; import com.kjetland.jackson.jsonSchema.JsonSchemaGenerator; import jline.console.ConsoleReader; import jline.console.completer.Completer; import jline.console.history.FileHistory; public class Client extends AbstractClient implements IClient { private static final String DEFAULT_CONTAINER_PREFIX = "cp"; private static final String DEFAULT_DELTA_ENV_PREFIX = "env_"; private static final String DEFAULT_ENV_NAME = BfConsts.RELPATH_DEFAULT_ENVIRONMENT_NAME; private static final String DEFAULT_QUESTION_PREFIX = "q"; private static final String DEFAULT_TESTRIG_PREFIX = "tr_"; private static final String DIFF_NOT_READY_MSG = "Cannot ask differential question without first setting delta testrig/environment\n"; private static final String ENV_HOME = "HOME"; private static final String FLAG_FAILING_TEST = "-error"; private static final String HISTORY_FILE = ".batfishclient_history"; private static final int NUM_TRIES_WARNING_THRESHOLD = 5; private static final String STARTUP_FILE = ".batfishclientrc"; private Map<String, String> _additionalBatfishOptions; private final Map<String, String> _bfq; private String _currContainerName = null; private String _currDeltaEnv = null; private String _currDeltaTestrig; private String _currEnv = null; private String _currTestrig = null; private boolean _exit; private BatfishLogger _logger; @SuppressWarnings("unused") private BfCoordPoolHelper _poolHelper; private ConsoleReader _reader; private Settings _settings; private BfCoordWorkHelper _workHelper; public Client(Settings settings) { super(false, settings.getPluginDirs()); _additionalBatfishOptions = new HashMap<>(); _bfq = new TreeMap<>(); _settings = settings; switch (_settings.getRunMode()) { case batch: if (_settings.getBatchCommandFile() == null) { System.err.println( "org.batfish.client: Command file not specified while running in batch mode."); System.err.printf( "Use '-%s <cmdfile>' if you want batch mode, or '-%s interactive' if you want interactive mode\n", Settings.ARG_COMMAND_FILE, Settings.ARG_RUN_MODE); System.exit(1); } _logger = new BatfishLogger(_settings.getLogLevel(), false, _settings.getLogFile(), false, false); break; case gendatamodel: _logger = new BatfishLogger(_settings.getLogLevel(), false, _settings.getLogFile(), false, false); break; case genquestions: if (_settings.getQuestionsDir() == null) { System.err.println( "org.batfish.client: Out dir not specified while running in genquestions mode."); System.err.printf("Use '-%s <cmdfile>'\n", Settings.ARG_QUESTIONS_DIR); System.exit(1); } _logger = new BatfishLogger(_settings.getLogLevel(), false, _settings.getLogFile(), false, false); break; case interactive: try { _reader = new ConsoleReader(); Path historyPath = Paths.get(System.getenv(ENV_HOME), HISTORY_FILE); historyPath.toFile().createNewFile(); FileHistory history = new FileHistory(historyPath.toFile()); _reader.setHistory(history); _reader.setPrompt("batfish> "); _reader.setExpandEvents(false); List<Completer> completors = new LinkedList<>(); completors.add(new CommandCompleter()); for (Completer c : completors) { _reader.addCompleter(c); } PrintWriter pWriter = new PrintWriter(_reader.getOutput(), true); OutputStream os = new WriterOutputStream(pWriter); PrintStream ps = new PrintStream(os, true); _logger = new BatfishLogger(_settings.getLogLevel(), false, ps); } catch (Exception e) { System.err.printf("Could not initialize client: %s\n", e.getMessage()); e.printStackTrace(); System.exit(1); } break; default: System.err.println("org.batfish.client: Unknown run mode."); System.exit(1); } } public Client(String[] args) throws Exception { this(new Settings(args)); } private boolean addBatfishOption(String[] words, List<String> options, List<String> parameters) { String optionKey = parameters.get(0); String optionValue = String.join(" ", Arrays.copyOfRange(words, 2 + options.size(), words.length)); _additionalBatfishOptions.put(optionKey, optionValue); return true; } private boolean answer(String questionTemplateName, String paramsLine, boolean isDelta, FileWriter outWriter) { String questionName = DEFAULT_QUESTION_PREFIX + "_" + UUID.randomUUID().toString(); String questionContentUnmodified = _bfq .get(questionTemplateName.toLowerCase()); if (questionContentUnmodified == null) { throw new BatfishException("Invalid question template name: '" + questionTemplateName + "'"); } Map<String, String> parameters = parseParams(paramsLine); JSONObject questionJson; try { questionJson = new JSONObject(questionContentUnmodified); } catch (JSONException e) { throw new BatfishException("Question content is not valid JSON", e); } JSONObject instanceJson; try { instanceJson = questionJson.getJSONObject(Question.INSTANCE_VAR); } catch (JSONException e) { throw new BatfishException("Question is missing instance data", e); } String instanceDataStr = instanceJson.toString(); BatfishObjectMapper mapper = new BatfishObjectMapper(); InstanceData instanceData; try { instanceData = mapper.<InstanceData> readValue(instanceDataStr, new TypeReference<InstanceData>() { }); } catch (IOException e) { throw new BatfishException("Invalid instance data (JSON)", e); } Map<String, Variable> variables = instanceData.getVariables(); for (Entry<String, String> e : parameters.entrySet()) { String parameterName = e.getKey(); String parameterValue = e.getValue(); Variable variable = variables.get(parameterName); if (variable != null) { JsonNode value; try { value = mapper.readTree(parameterValue); } catch (IOException e1) { throw new BatfishException("Variable value is not valid JSON", e1); } variable.setValue(value); } else { throw new BatfishException("No variable named: '" + parameterName + "' in supplied question template"); } } String modifiedInstanceDataStr; try { modifiedInstanceDataStr = mapper.writeValueAsString(instanceData); JSONObject modifiedInstanceData = new JSONObject( modifiedInstanceDataStr); questionJson.put(Question.INSTANCE_VAR, modifiedInstanceData); } catch (JSONException | JsonProcessingException e) { throw new BatfishException("Could not process modified instance data", e); } String modifiedQuestionStr = questionJson.toString(); boolean questionJsonDifferential; // check whether question is valid modulo instance data try { questionJsonDifferential = questionJson.has(Question.DIFFERENTIAL_VAR) && questionJson.getBoolean(Question.DIFFERENTIAL_VAR); } catch (JSONException e) { throw new BatfishException( "Could not find whether question is explicitly differential", e); } if (questionJsonDifferential && (_currDeltaEnv == null || _currDeltaTestrig == null)) { _logger.output(DIFF_NOT_READY_MSG); return false; } Path questionFile = createTempFile(BfConsts.RELPATH_QUESTION_FILE, modifiedQuestionStr); questionFile.toFile().deleteOnExit(); // upload the question boolean resultUpload = _workHelper.uploadQuestion(_currContainerName, isDelta ? _currDeltaTestrig : _currTestrig, questionName, questionFile.toAbsolutePath().toString()); if (!resultUpload) { return false; } _logger.debug("Uploaded question. Answering now.\n"); // delete the temporary params file if (questionFile != null) { CommonUtil.delete(questionFile); } // answer the question WorkItem wItemAs = _workHelper.getWorkItemAnswerQuestion(questionName, _currContainerName, _currTestrig, _currEnv, _currDeltaTestrig, _currDeltaEnv, isDelta); return execute(wItemAs, outWriter); } private boolean answer(String[] words, FileWriter outWriter, List<String> options, List<String> parameters, boolean isDelta) { if (!isSetTestrig() || !isSetContainer(true) || (isDelta && !isSetDeltaEnvironment())) { return false; } String qTypeStr = parameters.get(0); String paramsLine = String.join(" ", Arrays.copyOfRange(words, 2 + options.size(), words.length)); return answer(qTypeStr, paramsLine, isDelta, outWriter); } private boolean answerFile(Path questionFile, boolean isDelta, FileWriter outWriter) { if (!Files.exists(questionFile)) { throw new BatfishException("Question file not found: " + questionFile); } String questionName = DEFAULT_QUESTION_PREFIX + "_" + UUID.randomUUID().toString(); // upload the question boolean resultUpload = _workHelper.uploadQuestion(_currContainerName, isDelta ? _currDeltaTestrig : _currTestrig, questionName, questionFile.toAbsolutePath().toString()); if (!resultUpload) { return false; } _logger.debug("Uploaded question. Answering now.\n"); // answer the question WorkItem wItemAs = _workHelper.getWorkItemAnswerQuestion(questionName, _currContainerName, _currTestrig, _currEnv, _currDeltaTestrig, _currDeltaEnv, isDelta); return execute(wItemAs, outWriter); } private boolean answerType(String questionType, String paramsLine, boolean isDelta, FileWriter outWriter) { Map<String, String> parameters = parseParams(paramsLine); String questionString; if (questionType.startsWith(QuestionHelper.MACRO_PREFIX)) { try { questionString = QuestionHelper.resolveMacro(questionType, paramsLine, _questions); } catch (BatfishException e) { _logger.errorf("Could not resolve macro: %s\n", e.getMessage()); return false; } } else { questionString = QuestionHelper.getQuestionString(questionType, _questions, false); } JSONObject questionJson; try { questionJson = new JSONObject(questionString); } catch (JSONException e) { throw new BatfishException( "Failed to convert unmodified question string to JSON", e); } for (Entry<String, String> e : parameters.entrySet()) { String parameterName = e.getKey(); String parameterValue = e.getValue(); Object parameterObj; try { parameterObj = new JSONTokener(parameterValue).nextValue(); questionJson.put(parameterName, parameterObj); } catch (JSONException e1) { throw new BatfishException("Failed to apply parameter: '" + parameterName + "' with value: '" + parameterValue + "' to question JSON", e1); } } String modifiedQuestionJson = questionJson.toString(); BatfishObjectMapper mapper = new BatfishObjectMapper( getCurrentClassLoader()); Question modifiedQuestion = null; try { modifiedQuestion = mapper.readValue(modifiedQuestionJson, Question.class); } catch (IOException e) { throw new BatfishException( "Modified question is no longer valid, likely due to invalid parameters", e); } if (modifiedQuestion.getDifferential() && (_currDeltaEnv == null || _currDeltaTestrig == null)) { _logger.output(DIFF_NOT_READY_MSG); return false; } // if no exception is thrown, then the modifiedQuestionJson is good Path questionFile = createTempFile("question", modifiedQuestionJson); questionFile.toFile().deleteOnExit(); boolean result = answerFile(questionFile, isDelta, outWriter); if (questionFile != null) { CommonUtil.delete(questionFile); } return result; } private boolean cat(String[] words) throws IOException, FileNotFoundException { String filename = words[1]; try (BufferedReader br = new BufferedReader(new FileReader(filename))) { String line = null; while ((line = br.readLine()) != null) { _logger.output(line + "\n"); } } return true; } private boolean checkApiKey() { String isValid = _workHelper.checkApiKey(); _logger.outputf("Api key validitiy: %s\n", isValid); return true; } private boolean clearScreen() throws IOException { _reader.clearScreen(); return false; } private Path createTempFile(String filePrefix, String content) { Path tempFilePath; try { tempFilePath = Files.createTempFile(filePrefix, null); } catch (IOException e) { throw new BatfishException("Failed to create temporary file", e); } File tempFile = tempFilePath.toFile(); tempFile.deleteOnExit(); _logger.debugf("Creating temporary %s file: %s\n", filePrefix, tempFilePath.toAbsolutePath().toString()); FileWriter writer; try { writer = new FileWriter(tempFile); writer.write(content + "\n"); writer.close(); } catch (IOException e) { throw new BatfishException("Failed to write content to temporary file", e); } return tempFilePath; } private boolean delAnalysis(FileWriter outWriter, List<String> options, List<String> parameters) { if (!isSetContainer(true)) { return false; } if (options.size() != 0 || parameters.size() != 1) { _logger.errorf("Invalid arguments: %s %s\n", options.toString(), parameters.toString()); printUsage(Command.DEL_ANALYSIS); return false; } String analysisName = parameters.get(0); boolean result = _workHelper.delAnalysis(_currContainerName, analysisName); logOutput(outWriter, "Result of deleting analysis " + analysisName + ": " + result + "\n"); return result; } private boolean delAnalysisQuestions(FileWriter outWriter, List<String> options, List<String> parameters) { if (!isSetContainer(true)) { return false; } if (options.size() != 0 || parameters.size() < 2) { _logger.errorf("Invalid arguments: %s %s\n", options.toString(), parameters.toString()); printUsage(Command.DEL_ANALYSIS_QUESTIONS); return false; } String analysisName = parameters.get(0); String delQuestionsStr = "[]"; try { JSONArray delQuestionsArray = new JSONArray(); for (int index = 1; index < parameters.size(); index++) { delQuestionsArray.put(parameters.get(index)); } delQuestionsStr = delQuestionsArray.toString(1); } catch (JSONException e) { throw new BatfishException("Failed to get JSONObject for analysis", e); } boolean result = _workHelper.configureAnalysis(_currContainerName, false, analysisName, null, delQuestionsStr); logOutput(outWriter, "Result of deleting analysis questions: " + result + "\n"); return result; } private boolean delBatfishOption(List<String> parameters) { String optionKey = parameters.get(0); if (!_additionalBatfishOptions.containsKey(optionKey)) { _logger.outputf("Batfish option %s does not exist\n", optionKey); return false; } _additionalBatfishOptions.remove(optionKey); return true; } private boolean delContainer(List<String> parameters) { String containerName = parameters.get(0); boolean result = _workHelper.delContainer(containerName); _logger.outputf("Result of deleting container: %s\n", result); return true; } private boolean delEnvironment(List<String> parameters) { if (!isSetTestrig() || !isSetContainer(true)) { return false; } String envName = parameters.get(0); boolean result = _workHelper.delEnvironment(_currContainerName, _currTestrig, envName); _logger.outputf("Result of deleting environment: %s\n", result); return true; } private boolean delQuestion(List<String> parameters) { if (!isSetTestrig() || !isSetContainer(true)) { return false; } String qName = parameters.get(0); boolean result = _workHelper.delQuestion(_currContainerName, _currTestrig, qName); _logger.outputf("Result of deleting question: %s\n", result); return true; } private boolean delTestrig(FileWriter outWriter, List<String> parameters) { if (!isSetContainer(true)) { return false; } String testrigName = parameters.get(0); boolean result = _workHelper.delTestrig(_currContainerName, testrigName); logOutput(outWriter, "Result of deleting testrig: " + result + "\n"); return true; } private boolean dir(List<String> parameters) { String dirname = (parameters.size() == 1) ? parameters.get(0) : "."; File currDirectory = new File(dirname); for (File file : currDirectory.listFiles()) { _logger.output(file.getName() + "\n"); } return true; } private boolean echo(String[] words) { _logger.outputf("%s\n", String.join(" ", Arrays.copyOfRange(words, 1, words.length))); return true; } private boolean execute(WorkItem wItem, FileWriter outWriter) { _logger.info("work-id is " + wItem.getId() + "\n"); wItem.addRequestParam(BfConsts.ARG_LOG_LEVEL, _settings.getBatfishLogLevel()); for (String option : _additionalBatfishOptions.keySet()) { wItem.addRequestParam(option, _additionalBatfishOptions.get(option)); } boolean queueWorkResult = _workHelper.queueWork(wItem); _logger.info("Queuing result: " + queueWorkResult + "\n"); if (!queueWorkResult) { return queueWorkResult; } Pair<WorkStatusCode, String> response = _workHelper .getWorkStatus(wItem.getId()); if (response == null) { return false; } WorkStatusCode status = response.getFirst(); while (status != WorkStatusCode.TERMINATEDABNORMALLY && status != WorkStatusCode.TERMINATEDNORMALLY && status != WorkStatusCode.ASSIGNMENTERROR) { printWorkStatusResponse(response); try { Thread.sleep(1 * 1000); } catch (InterruptedException e) { throw new BatfishException("Interrupted while waiting for response", e); } response = _workHelper.getWorkStatus(wItem.getId()); if (response == null) { return false; } status = response.getFirst(); } printWorkStatusResponse(response); // get the answer String ansFileName = wItem.getId() + BfConsts.SUFFIX_ANSWER_JSON_FILE; String downloadedAnsFile = _workHelper.getObject(wItem.getContainerName(), wItem.getTestrigName(), ansFileName); if (downloadedAnsFile == null) { _logger.errorf( "Failed to get answer file %s. Fix batfish and remove the statement below this line\n", ansFileName); // return false; } else { String answerString = CommonUtil .readFile(Paths.get(downloadedAnsFile)); // Check if we need to make things pretty // Don't if we are writing to FileWriter, because we need valid JSON in // that case String answerStringToPrint = answerString; if (outWriter == null && _settings.getPrettyPrintAnswers()) { ObjectMapper mapper = new BatfishObjectMapper( getCurrentClassLoader()); Answer answer; try { answer = mapper.readValue(answerString, Answer.class); } catch (IOException e) { throw new BatfishException( "Response does not appear to be valid JSON representation of " + Answer.class.getSimpleName()); } answerStringToPrint = answer.prettyPrint(); } logOutput(outWriter, answerStringToPrint); // tests serialization/deserialization when running in debug mode if (_logger.getLogLevel() >= BatfishLogger.LEVEL_DEBUG) { try { ObjectMapper mapper = new BatfishObjectMapper( getCurrentClassLoader()); Answer answer = mapper.readValue(answerString, Answer.class); String newAnswerString = mapper.writeValueAsString(answer); JsonNode tree = mapper.readTree(answerString); JsonNode newTree = mapper.readTree(newAnswerString); if (!CommonUtil.checkJsonEqual(tree, newTree)) { // if (!tree.equals(newTree)) { _logger.errorf( "Original and recovered Json are different. Recovered = %s\n", newAnswerString); } } catch (Exception e) { _logger.outputf("Could NOT deserialize Json to Answer: %s\n", e.getMessage()); } } } // get and print the log when in debugging mode if (_logger.getLogLevel() >= BatfishLogger.LEVEL_DEBUG) { _logger.output("---------------- Service Log --------------\n"); String logFileName = wItem.getId() + BfConsts.SUFFIX_LOG_FILE; String downloadedFileStr = _workHelper.getObject( wItem.getContainerName(), wItem.getTestrigName(), logFileName); if (downloadedFileStr == null) { _logger.errorf("Failed to get log file %s\n", logFileName); return false; } else { Path downloadedFile = Paths.get(downloadedFileStr); CommonUtil.outputFileLines(downloadedFile, _logger::output); } } if (response.getFirst() == WorkStatusCode.TERMINATEDNORMALLY) { return true; } else { // _logger.errorf("WorkItem failed: %s", wItem); return false; } } private boolean exit() { _exit = true; return true; } private void generateDatamodel() { try { ObjectMapper mapper = new BatfishObjectMapper(); JsonSchemaGenerator schemaGenNew = new JsonSchemaGenerator(mapper); JsonNode schemaNew = schemaGenNew .generateJsonSchema(Configuration.class); _logger.output(mapper.writeValueAsString(schemaNew)); // Reflections reflections = new Reflections("org.batfish.datamodel"); // Set<Class<? extends AnswerElement>> classes = // reflections.getSubTypesOf(AnswerElement.class); // _logger.outputf("Found %d classes that inherit %s\n", // classes.toArray().length, "AnswerElement"); // // File dmDir = Paths.get(_settings.getDatamodelDir()).toFile(); // if (!dmDir.exists()) { // if (!dmDir.mkdirs()) { // throw new BatfishException("Could not create directory: " + // dmDir.getAbsolutePath()); // } // } // // for (Class c : classes) { // String className = c.getCanonicalName() // .replaceAll("org\\.batfish\\.datamodel\\.", "") // .replaceAll("\\.", "-") // + ".json"; // _logger.outputf("%s --> %s\n", c, className); // Path file = Paths.get(dmDir.getAbsolutePath(), className); // try (PrintWriter out = new // PrintWriter(file.toAbsolutePath().toString())) { // ObjectMapper mapper = new BatfishObjectMapper(); // JsonSchemaGenerator schemaGen = new JsonSchemaGenerator(mapper); // JsonNode schema = schemaGen.generateJsonSchema(c); // String schemaString = mapper.writeValueAsString(schema); // out.println(schemaString); // } // } // JsonSchemaGenerator schemaGenNew = new JsonSchemaGenerator(mapper, // true, JsonSchemaConfig.vanillaJsonSchemaDraft4()); // JsonNode schemaNew = // schemaGenNew.generateJsonSchema(Configuration.class); // _logger.output(mapper.writeValueAsString(schemaNew)); // _logger.output("\n"); // JsonNode schemaNew2 = // schemaGenNew.generateJsonSchema(SchemaTest.Parent.class); // _logger.output(mapper.writeValueAsString(schemaNew2)); } catch (Exception e) { _logger.errorf("Could not generate data model: " + e.getMessage()); e.printStackTrace(); } } private boolean generateDataplane(FileWriter outWriter) throws Exception { if (!isSetTestrig() || !isSetContainer(true)) { return false; } // generate the data plane WorkItem wItemGenDp = _workHelper.getWorkItemGenerateDataPlane( _currContainerName, _currTestrig, _currEnv); return execute(wItemGenDp, outWriter); } private boolean generateDeltaDataplane(FileWriter outWriter) throws Exception { if (!isSetDeltaEnvironment() || !isSetTestrig() || !isSetContainer(true)) { return false; } WorkItem wItemGenDdp = _workHelper.getWorkItemGenerateDeltaDataPlane( _currContainerName, _currTestrig, _currEnv, _currDeltaTestrig, _currDeltaEnv); return execute(wItemGenDdp, outWriter); } private void generateQuestions() { File questionsDir = Paths.get(_settings.getQuestionsDir()).toFile(); if (!questionsDir.exists()) { if (!questionsDir.mkdirs()) { _logger.errorf("Could not create questions dir %s\n", _settings.getQuestionsDir()); System.exit(1); } } _questions.forEach((qName, supplier) -> { try { String questionString = QuestionHelper.getQuestionString(qName, _questions, true); String qFile = Paths .get(_settings.getQuestionsDir(), qName + ".json").toFile() .getAbsolutePath(); PrintWriter writer = new PrintWriter(qFile); writer.write(questionString); writer.close(); } catch (Exception e) { _logger.errorf("Could not write question %s: %s\n", qName, e.getMessage()); } }); } private boolean get(String[] words, FileWriter outWriter, List<String> options, List<String> parameters, boolean isDelta) throws Exception { if (!isSetTestrig() || !isSetContainer(true) || (isDelta && !isSetDeltaEnvironment())) { return false; } String qTypeStr = parameters.get(0).toLowerCase(); String paramsLine = String.join(" ", Arrays.copyOfRange(words, 2 + options.size(), words.length)); // TODO: make environment creation a command, not a question if (!qTypeStr.startsWith(QuestionHelper.MACRO_PREFIX) && qTypeStr.equals(IEnvironmentCreationQuestion.NAME)) { String deltaEnvName = DEFAULT_DELTA_ENV_PREFIX + UUID.randomUUID().toString(); String prefixString = (paramsLine.trim().length() > 0) ? " | " : ""; paramsLine += String.format("%s %s=%s", prefixString, IEnvironmentCreationQuestion.ENVIRONMENT_NAME_KEY, deltaEnvName); if (!answerType(qTypeStr, paramsLine, isDelta, outWriter)) { unsetTestrig(true); return false; } _currDeltaEnv = deltaEnvName; _currDeltaTestrig = _currTestrig; _logger.output("Active delta testrig->environment is set "); _logger.infof("to %s->%s\n", _currDeltaTestrig, _currDeltaEnv); _logger.output("\n"); return true; } else { return answerType(qTypeStr, paramsLine, isDelta, outWriter); } } private boolean getAnalysisAnswers(FileWriter outWriter, List<String> options, List<String> parameters, boolean delta, boolean differential) { if (!isSetTestrig() || !isSetContainer(true)) { return false; } if (options.size() != 0 || parameters.size() != 1) { _logger.errorf("Invalid arguments: %s %s\n", options.toString(), parameters.toString()); printUsage(Command.GET_ANALYSIS_ANSWERS); return false; } String analysisName = parameters.get(0); String baseTestrig; String baseEnvironment; String deltaTestrig; String deltaEnvironment; if (differential) { baseTestrig = _currTestrig; baseEnvironment = _currEnv; deltaTestrig = _currDeltaTestrig; deltaEnvironment = _currDeltaEnv; } else if (delta) { baseTestrig = _currDeltaTestrig; baseEnvironment = _currDeltaEnv; deltaTestrig = null; deltaEnvironment = null; } else { baseTestrig = _currTestrig; baseEnvironment = _currEnv; deltaTestrig = null; deltaEnvironment = null; } String answer = _workHelper.getAnalysisAnswers(_currContainerName, baseTestrig, baseEnvironment, deltaTestrig, deltaEnvironment, analysisName); if (answer == null) { return false; } logOutput(outWriter, answer + "\n"); return true; } private boolean getAnswer(FileWriter outWriter, List<String> parameters, boolean delta, boolean differential) { if (!isSetTestrig() || !isSetContainer(true)) { return false; } if (parameters.size() != 1) { _logger.error("Invalid arguments: " + parameters.toString()); printUsage(Command.GET_ANSWER); return false; } String questionName = parameters.get(0); String baseTestrig; String baseEnvironment; String deltaTestrig; String deltaEnvironment; if (differential) { baseTestrig = _currTestrig; baseEnvironment = _currEnv; deltaTestrig = _currDeltaTestrig; deltaEnvironment = _currDeltaEnv; } else if (delta) { baseTestrig = _currDeltaTestrig; baseEnvironment = _currDeltaEnv; deltaTestrig = null; deltaEnvironment = null; } else { baseTestrig = _currTestrig; baseEnvironment = _currEnv; deltaTestrig = null; deltaEnvironment = null; } String answerString = _workHelper.getAnswer(_currContainerName, baseTestrig, baseEnvironment, deltaTestrig, deltaEnvironment, questionName); String answerStringToPrint = answerString; if (outWriter == null && _settings.getPrettyPrintAnswers()) { ObjectMapper mapper = new BatfishObjectMapper(getCurrentClassLoader()); Answer answer; try { answer = mapper.readValue(answerString, Answer.class); } catch (IOException e) { throw new BatfishException( "Response does not appear to be valid JSON representation of " + Answer.class.getSimpleName()); } answerStringToPrint = answer.prettyPrint(); } logOutput(outWriter, answerStringToPrint + "\n"); return true; } private List<String> getCommandOptions(String[] words) { List<String> options = new LinkedList<>(); int currIndex = 1; while (currIndex < words.length && words[currIndex].startsWith("-")) { options.add(words[currIndex]); currIndex++; } return options; } private List<String> getCommandParameters(String[] words, int numOptions) { List<String> parameters = new LinkedList<>(); for (int index = numOptions + 1; index < words.length; index++) { parameters.add(words[index]); } return parameters; } @Override public BatfishLogger getLogger() { return _logger; } private boolean getQuestion(List<String> parameters) { if (!isSetTestrig() || !isSetContainer(true)) { return false; } String questionName = parameters.get(0); String questionFileName = String.format("%s/%s/%s", BfConsts.RELPATH_QUESTIONS_DIR, questionName, BfConsts.RELPATH_QUESTION_FILE); String downloadedQuestionFile = _workHelper.getObject(_currContainerName, _currTestrig, questionFileName); if (downloadedQuestionFile == null) { _logger.errorf("Failed to get question file %s\n", questionFileName); return false; } String questionString = CommonUtil .readFile(Paths.get(downloadedQuestionFile)); _logger.outputf("Question:\n%s\n", questionString); return true; } public Settings getSettings() { return _settings; } private boolean help(List<String> parameters) { if (parameters.size() == 1) { Command cmd = Command.fromName(parameters.get(0)); printUsage(cmd); } else { printUsage(); } return true; } private boolean initContainer(String[] words) { String containerPrefix = (words.length > 1) ? words[1] : DEFAULT_CONTAINER_PREFIX; _currContainerName = _workHelper.initContainer(containerPrefix); if (_currContainerName == null) { _logger.errorf("Could not init container\n"); return false; } _logger.output("Active container is set"); _logger.infof(" to %s\n", _currContainerName); _logger.output("\n"); return true; } private boolean initDeltaEnv(FileWriter outWriter, List<String> parameters) throws Exception { if (!isSetTestrig() || !isSetContainer(true)) { return false; } String deltaEnvLocation = parameters.get(0); String deltaEnvName = (parameters.size() > 1) ? parameters.get(1) : DEFAULT_DELTA_ENV_PREFIX + UUID.randomUUID().toString(); if (!uploadTestrigOrEnv(deltaEnvLocation, deltaEnvName, false)) { return false; } _currDeltaEnv = deltaEnvName; _currDeltaTestrig = _currTestrig; _logger.output("Active delta testrig->environment is set"); _logger.infof("to %s->%s\n", _currDeltaTestrig, _currDeltaEnv); _logger.output("\n"); WorkItem wItemGenDdp = _workHelper.getWorkItemCompileDeltaEnvironment( _currContainerName, _currDeltaTestrig, _currEnv, _currDeltaEnv); if (!execute(wItemGenDdp, outWriter)) { return false; } return true; } private void initHelpers() { switch (_settings.getRunMode()) { case batch: case interactive: break; case gendatamodel: case genquestions: default: return; } String workMgr = _settings.getCoordinatorHost() + ":" + _settings.getCoordinatorWorkPort(); String poolMgr = _settings.getCoordinatorHost() + ":" + _settings.getCoordinatorPoolPort(); _workHelper = new BfCoordWorkHelper(workMgr, _logger, _settings); _poolHelper = new BfCoordPoolHelper(poolMgr); int numTries = 0; while (true) { try { numTries++; boolean exceededNumTriesWarningThreshold = numTries > NUM_TRIES_WARNING_THRESHOLD; if (_workHelper.isReachable(exceededNumTriesWarningThreshold)) { // print this message only we might have printed unable to // connect message earlier if (exceededNumTriesWarningThreshold) { _logger.outputf("Connected to coordinator after %d tries\n", numTries); } break; } Thread.sleep(1 * 1000); // 1 second } catch (Exception e) { _logger.errorf( "Exeption while checking reachability to coordinator: ", e.getMessage()); System.exit(1); } } } private boolean initOrAddAnalysis(FileWriter outWriter, List<String> options, List<String> parameters, boolean newAnalysis) { if (!isSetContainer(true)) { return false; } if (options.size() != 0 || parameters.size() != 2) { _logger.errorf("Invalid arguments: %s %s", options.toString(), parameters.toString()); printUsage(Command.INIT_ANALYSIS); return false; } String analysisName = parameters.get(0); String questionsPathStr = parameters.get(1); Map<String, String> questionMap = new TreeMap<>(); if (!loadQuestions(null, questionsPathStr, questionMap)) { return false; } String analysisJsonString = "{}"; try { JSONObject jObject = new JSONObject(); for (String qName : questionMap.keySet()) { jObject.put(qName, new JSONObject(questionMap.get(qName))); } analysisJsonString = jObject.toString(1); } catch (JSONException e) { throw new BatfishException("Failed to get JSONObject for analysis", e); } Path analysisFile = createTempFile("analysis", analysisJsonString); boolean result = _workHelper.configureAnalysis(_currContainerName, newAnalysis, analysisName, analysisFile.toAbsolutePath().toString(), null); if (analysisFile != null) { CommonUtil.delete(analysisFile); } logOutput(outWriter, "Output of configuring analysis " + analysisName + ": " + result + "\n"); return result; } private boolean initTestrig(FileWriter outWriter, List<String> parameters, boolean doDelta) throws Exception { String testrigLocation = parameters.get(0); String testrigName = (parameters.size() > 1) ? parameters.get(1) : DEFAULT_TESTRIG_PREFIX + UUID.randomUUID().toString(); // initialize the container if it hasn't been init'd before if (!isSetContainer(false)) { _currContainerName = _workHelper .initContainer(DEFAULT_CONTAINER_PREFIX); if (_currContainerName == null) { _logger.errorf("Could not init container\n"); return false; } _logger.outputf("Init'ed and set active container"); _logger.infof(" to %s\n", _currContainerName); _logger.output("\n"); } if (!uploadTestrigOrEnv(testrigLocation, testrigName, true)) { unsetTestrig(doDelta); return false; } _logger.output("Uploaded testrig. Parsing now.\n"); WorkItem wItemParse = _workHelper.getWorkItemParse(_currContainerName, testrigName, false); if (!execute(wItemParse, outWriter)) { unsetTestrig(doDelta); return false; } if (!doDelta) { _currTestrig = testrigName; _currEnv = DEFAULT_ENV_NAME; _logger.infof("Base testrig is now %s\n", _currTestrig); } else { _currDeltaTestrig = testrigName; _currDeltaEnv = DEFAULT_ENV_NAME; _logger.infof("Delta testrig is now %s\n", _currDeltaTestrig); } return true; } private boolean isSetContainer(boolean printError) { if (!_settings.getSanityCheck()) { return true; } if (_currContainerName == null) { if (printError) { _logger.errorf("Active container is not set\n"); } return false; } return true; } private boolean isSetDeltaEnvironment() { if (!_settings.getSanityCheck()) { return true; } if (_currDeltaTestrig == null) { _logger.errorf("Active delta testrig is not set\n"); return false; } if (_currDeltaEnv == null) { _logger.errorf("Active delta environment is not set\n"); return false; } return true; } private boolean isSetTestrig() { if (!_settings.getSanityCheck()) { return true; } if (_currTestrig == null) { _logger.errorf("Active testrig is not set.\n"); _logger.errorf( "Specify testrig on command line (-%s <testrigdir>) or use command (%s <testrigdir>)\n", Settings.ARG_TESTRIG_DIR, Command.INIT_TESTRIG); return false; } return true; } private boolean listAnalyses(FileWriter outWriter, List<String> options, List<String> parameters) { if (!isSetContainer(true)) { return false; } if (options.size() != 0 || parameters.size() != 0) { _logger.errorf("Invalid arguments: %s %s\n", options.toString(), parameters.toString()); printUsage(Command.LIST_TESTRIGS); return false; } JSONObject analysisList = _workHelper.listAnalyses(_currContainerName); logOutput(outWriter, String.format("Found %d analyses\n", analysisList.length())); try { logOutput(outWriter, analysisList.toString(1)); } catch (JSONException e) { throw new BatfishException("Failed to print analysis list", e); } // if (analysisList != null) { // Iterator<?> aIterator = analysisList.keys(); // while (aIterator.hasNext()) { // String aName = (String) aIterator.next(); // _logger.outputf("Analysis: %s\n", aName); // // try { // JSONObject questionList = analysisList.getJSONObject(aName); // _logger.outputf("Found %d questions\n", questionList.length()); // // Iterator<?> qIterator = questionList.keys(); // while (qIterator.hasNext()) { // String qName = (String) qIterator.next(); // _logger.outputf(" Question: %s\n", qName); // // JSONObject questionJson = questionList.getJSONObject(qName); // _logger.outputf("%s\n", questionJson.toString(1)); // } // // } // catch (JSONException e) { // throw new BatfishException("Failed to process analysis list", e); // } // } // } return true; } private boolean listContainers() { String[] containerList = _workHelper.listContainers(); _logger.outputf("Containers: %s\n", Arrays.toString(containerList)); return true; } private boolean listEnvironments() { if (!isSetTestrig() || !isSetContainer(true)) { return false; } String[] environmentList = _workHelper .listEnvironments(_currContainerName, _currTestrig); _logger.outputf("Environments: %s\n", Arrays.toString(environmentList)); return true; } private boolean listQuestions() { if (!isSetTestrig() || !isSetContainer(true)) { return false; } String[] questionList = _workHelper.listQuestions(_currContainerName, _currTestrig); _logger.outputf("Questions: %s\n", Arrays.toString(questionList)); return true; } private boolean listTestrigs(FileWriter outWriter, List<String> options, List<String> parameters) { if (options.size() != 0 || parameters.size() != 0) { _logger.errorf("Invalid arguments: %s %s\n", options.toString(), parameters.toString()); printUsage(Command.LIST_TESTRIGS); return false; } Map<String, String> testrigs = _workHelper .listTestrigs(_currContainerName); if (testrigs != null) { for (String testrigName : testrigs.keySet()) { logOutput(outWriter, String.format("Testrig: %s\n%s\n", testrigName, testrigs.get(testrigName))); } } return true; } private String loadQuestion(Path file, Map<String, String> bfq) { String questionText = CommonUtil.readFile(file); try { JSONObject questionObj = new JSONObject(questionText); if (questionObj.has(Question.INSTANCE_VAR) && !questionObj.isNull(Question.INSTANCE_VAR)) { JSONObject instanceDataObj = questionObj .getJSONObject(Question.INSTANCE_VAR); String instanceDataStr = instanceDataObj.toString(); BatfishObjectMapper mapper = new BatfishObjectMapper( getCurrentClassLoader()); InstanceData instanceData = mapper.<InstanceData> readValue( instanceDataStr, new TypeReference<InstanceData>() { }); validateInstanceData(instanceData); String name = instanceData.getInstanceName(); bfq.put(name.toLowerCase(), questionText); return name; } else { throw new BatfishException("Question in file: '" + file.toString() + "' has no instance name"); } } catch (JSONException | IOException e) { throw new BatfishException("Failed to process question", e); } } private boolean loadQuestions(FileWriter outWriter, List<String> parameters, Map<String, String> bfq) { if (parameters.size() != 1) { _logger.error("Invalid arguments: " + parameters.toString()); printUsage(Command.LOAD_QUESTIONS); return false; } String questionsPathStr = parameters.get(0); return loadQuestions(outWriter, questionsPathStr, bfq); } private boolean loadQuestions(FileWriter outWriter, String questionsPathStr, Map<String, String> bfq) { Path questionsPath = Paths.get(questionsPathStr); int numLoaded = 0; Answer answer = new Answer(); LoadQuestionAnswerElement ae = new LoadQuestionAnswerElement(); answer.addAnswerElement(ae); SortedSet<Path> jsonQuestionFiles = new TreeSet<>(); try { Files.walkFileTree(questionsPath, Collections.emptySet(), 1, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { String filename = file.getFileName().toString(); if (filename.endsWith(".json")) { jsonQuestionFiles.add(file); } return FileVisitResult.CONTINUE; } }); } catch (IOException e) { throw new BatfishException("Failed to visit questions dir", e); } for (Path jsonQuestionFile : jsonQuestionFiles) { int numBefore = bfq.size(); String name = loadQuestion(jsonQuestionFile, bfq); int numAfter = bfq.size(); if (numBefore == numAfter) { ae.getReplaced().add(name); } else { ae.getAdded().add(name); } numLoaded++; } ae.setNumLoaded(numLoaded); ObjectMapper mapper = new BatfishObjectMapper(getCurrentClassLoader()); String answerStringToPrint; try { answerStringToPrint = mapper.writeValueAsString(answer); } catch (JsonProcessingException e) { throw new BatfishException("Could not write answer element as string", e); } if (outWriter == null && _settings.getPrettyPrintAnswers()) { answerStringToPrint = answer.prettyPrint(); } logOutput(outWriter, answerStringToPrint); return true; } private void logOutput(FileWriter outWriter, String message) { if (outWriter == null) { _logger.outputf(message); } else { try { outWriter.write(message); } catch (IOException e) { throw new BatfishException( "Failed to log output to outWriter", e); } } } private Map<String, String> parseParams(String paramsLine) { Map<String, String> parameters = new HashMap<>(); Pattern pattern = Pattern.compile("([\\w_]+)\\s*=\\s*(.+)"); String[] params = paramsLine.split("\\|"); _logger.debugf("Found %d parameters\n", params.length); for (String param : params) { Matcher matcher = pattern.matcher(param); while (matcher.find()) { String key = matcher.group(1).trim(); String value = matcher.group(2).trim(); _logger.debugf("key=%s value=%s\n", key, value); parameters.put(key, value); } } return parameters; } private void printUsage() { for (Command cmd : Command.getUsageMap().keySet()) { printUsage(cmd); } } private void printUsage(Command command) { Pair<String, String> usage = Command.getUsageMap().get(command); _logger.outputf("%s %s\n\t%s\n\n", command.commandName(), usage.getFirst(), usage.getSecond()); } private void printWorkStatusResponse(Pair<WorkStatusCode, String> response) { if (_logger.getLogLevel() >= BatfishLogger.LEVEL_INFO) { WorkStatusCode status = response.getFirst(); _logger.infof("status: %s\n", status); BatfishObjectMapper mapper = new BatfishObjectMapper(); Task task; try { task = mapper.readValue(response.getSecond(), Task.class); } catch (IOException e) { _logger.errorf("Could not deserialize task object: %s\n", e); return; } if (task == null) { _logger.infof(".... no task information\n"); return; } List<Batch> batches = task.getBatches(); // when log level is INFO, we only print the last batch // else print all for (int i = 0; i < batches.size(); i++) { if (i == batches.size() - 1 || status == WorkStatusCode.TERMINATEDNORMALLY || status == WorkStatusCode.TERMINATEDABNORMALLY) { _logger.infof(".... %s\n", batches.get(i).toString()); } else { _logger.debugf(".... %s\n", batches.get(i).toString()); } } if (status == WorkStatusCode.TERMINATEDNORMALLY || status == WorkStatusCode.TERMINATEDABNORMALLY) { _logger.infof(".... %s: %s\n", task.getTerminated().toString(), status); } } } private boolean processCommand(String command) { String line = command.trim(); if (line.length() == 0 || line.startsWith("#")) { return true; } _logger.debug("Doing command: " + line + "\n"); String[] words = line.split("\\s+"); if (words.length > 0) { if (!validCommandUsage(words)) { return false; } } return processCommand(words, null); } private boolean processCommand(String[] words, FileWriter outWriter) { try { List<String> options = getCommandOptions(words); List<String> parameters = getCommandParameters(words, options.size()); Command command; try { command = Command.fromName(words[0]); } catch (BatfishException e) { _logger.errorf("Command failed: %s\n", e.getMessage()); return false; } switch (command) { case ADD_ANALYSIS_QUESTIONS: return initOrAddAnalysis(outWriter, options, parameters, false); case ADD_BATFISH_OPTION: return addBatfishOption(words, options, parameters); case ANSWER: return answer(words, outWriter, options, parameters, false); case ANSWER_DELTA: return answer(words, outWriter, options, parameters, true); case CAT: return cat(words); case CHECK_API_KEY: return checkApiKey(); case CLEAR_SCREEN: return clearScreen(); case DEL_ANALYSIS: return delAnalysis(outWriter, options, parameters); case DEL_ANALYSIS_QUESTIONS: return delAnalysisQuestions(outWriter, options, parameters); case DEL_BATFISH_OPTION: return delBatfishOption(parameters); case DEL_CONTAINER: return delContainer(parameters); case DEL_ENVIRONMENT: return delEnvironment(parameters); case DEL_QUESTION: return delQuestion(parameters); case DEL_TESTRIG: return delTestrig(outWriter, parameters); case DIR: return dir(parameters); case ECHO: return echo(words); case GEN_DP: return generateDataplane(outWriter); case GEN_DELTA_DP: return generateDeltaDataplane(outWriter); case GET: return get(words, outWriter, options, parameters, false); case GET_DELTA: return get(words, outWriter, options, parameters, true); case GET_ANALYSIS_ANSWERS: return getAnalysisAnswers(outWriter, options, parameters, false, false); case GET_ANALYSIS_ANSWERS_DELTA: return getAnalysisAnswers(outWriter, options, parameters, true, false); case GET_ANALYSIS_ANSWERS_DIFFERENTIAL: return getAnalysisAnswers(outWriter, options, parameters, false, true); case GET_ANSWER: return getAnswer(outWriter, parameters, false, false); case GET_ANSWER_DELTA: return getAnswer(outWriter, parameters, true, false); case GET_ANSWER_DIFFERENTIAL: return getAnswer(outWriter, parameters, false, true); case GET_QUESTION: return getQuestion(parameters); case HELP: return help(parameters); case INIT_ANALYSIS: return initOrAddAnalysis(outWriter, options, parameters, true); case INIT_CONTAINER: return initContainer(words); case INIT_DELTA_ENV: return initDeltaEnv(outWriter, parameters); case INIT_DELTA_TESTRIG: return initTestrig(outWriter, parameters, true); case INIT_TESTRIG: return initTestrig(outWriter, parameters, false); case LIST_ANALYSES: return listAnalyses(outWriter, options, parameters); case LIST_CONTAINERS: return listContainers(); case LIST_ENVIRONMENTS: return listEnvironments(); case LIST_QUESTIONS: return listQuestions(); case LIST_TESTRIGS: return listTestrigs(outWriter, options, parameters); case LOAD_QUESTIONS: return loadQuestions(outWriter, parameters, _bfq); case PROMPT: return prompt(); case PWD: return pwd(); case REINIT_DELTA_TESTRIG: return reinitTestrig(outWriter, true); case RUN_ANALYSIS: return runAnalysis(outWriter, options, parameters, false, false); case RUN_ANALYSIS_DELTA: return runAnalysis(outWriter, options, parameters, true, false); case RUN_ANALYSIS_DIFFERENTIAL: return runAnalysis(outWriter, options, parameters, false, true); case REINIT_TESTRIG: return reinitTestrig(outWriter, false); case SET_BATFISH_LOGLEVEL: return setBatfishLogLevel(parameters); case SET_CONTAINER: return setContainer(parameters); case SET_DELTA_ENV: return setDeltaEnv(parameters); case SET_ENV: return setEnv(parameters); case SET_DELTA_TESTRIG: return setDeltaTestrig(parameters); case SET_LOGLEVEL: return setLogLevel(parameters); case SET_PRETTY_PRINT: return setPrettyPrint(parameters); case SET_TESTRIG: return setTestrig(parameters); case SHOW_API_KEY: return showApiKey(); case SHOW_BATFISH_LOGLEVEL: return showBatfishLogLevel(); case SHOW_BATFISH_OPTIONS: return showBatfishOptions(); case SHOW_CONTAINER: return showContainer(); case SHOW_COORDINATOR_HOST: return showCoordinatorHost(); case SHOW_DELTA_TESTRIG: return showDeltaTestrig(); case SHOW_LOGLEVEL: return showLogLevel(); case SHOW_TESTRIG: return showTestrig(); case TEST: return test(parameters); case UPLOAD_CUSTOM_OBJECT: return uploadCustomObject(parameters); case EXIT: case QUIT: return exit(); default: _logger.error("Unsupported command " + words[0] + "\n"); _logger.error("Type 'help' to see the list of valid commands\n"); return false; } } catch (Exception e) { e.printStackTrace(); return false; } } private boolean processCommands(List<String> commands) { for (String command : commands) { if (!processCommand(command)) { return false; } } return true; } private boolean prompt() throws IOException { if (_settings.getRunMode() == RunMode.interactive) { _logger.output("\n\n[Press enter to proceed]\n\n"); BufferedReader in = new BufferedReader( new InputStreamReader(System.in)); in.readLine(); } return true; } private boolean pwd() { final String dir = System.getProperty("user.dir"); _logger.output("working directory = " + dir + "\n"); return true; } private List<String> readCommands(Path startupFilePath) { List<String> commands = null; try { commands = Files.readAllLines(startupFilePath, StandardCharsets.US_ASCII); } catch (Exception e) { System.err.printf("Exception reading command file %s: %s\n", _settings.getBatchCommandFile(), e.getMessage()); System.exit(1); } return commands; } private boolean reinitTestrig(FileWriter outWriter, boolean isDelta) throws Exception { String testrig; if (!isDelta) { _logger.output("Reinitializing testrig. Parsing now.\n"); testrig = _currTestrig; } else { _logger.output("Reinitializing delta testrig. Parsing now.\n"); testrig = _currDeltaTestrig; } WorkItem wItemParse = _workHelper.getWorkItemParse(_currContainerName, testrig, isDelta); if (!execute(wItemParse, outWriter)) { return false; } return true; } public void run(List<String> initialCommands) { loadPlugins(); initHelpers(); _logger.debugf("Will use coordinator at %s://%s\n", (_settings.getUseSsl()) ? "https" : "http", _settings.getCoordinatorHost()); if (!processCommands(initialCommands)) { return; } // set container if specified if (_settings.getContainerId() != null) { if (!processCommand(Command.SET_CONTAINER.commandName() + " " + _settings.getContainerId())) { return; } } // set testrig if dir or id is specified if (_settings.getTestrigDir() != null) { if (_settings.getTestrigId() != null) { System.err.println( "org.batfish.client: Cannot supply both testrigDir and testrigId."); System.exit(1); } if (!processCommand(Command.INIT_TESTRIG.commandName() + " " + _settings.getTestrigDir())) { return; } } if (_settings.getTestrigId() != null) { if (!processCommand(Command.SET_TESTRIG.commandName() + " " + _settings.getTestrigId())) { return; } } switch (_settings.getRunMode()) { case batch: { runBatchFile(); break; } case gendatamodel: generateDatamodel(); break; case genquestions: generateQuestions(); break; case interactive: { runStartupFile(); runInteractive(); break; } default: System.err.println("org.batfish.client: Unknown run mode."); System.exit(1); } } private boolean runAnalysis(FileWriter outWriter, List<String> options, List<String> parameters, boolean delta, boolean differential) { if (!isSetContainer(true) || !isSetTestrig()) { return false; } if (options.size() != 0 || parameters.size() != 1) { _logger.errorf("Invalid arguments: %s %s", options.toString(), parameters.toString()); printUsage(Command.RUN_ANALYSIS); return false; } String analysisName = parameters.get(0); // answer the question WorkItem wItemAs = _workHelper.getWorkItemRunAnalysis(analysisName, _currContainerName, _currTestrig, _currEnv, _currDeltaTestrig, _currDeltaEnv, delta, differential); return execute(wItemAs, outWriter); } private void runBatchFile() { Path batchCommandFilePath = Paths.get(_settings.getBatchCommandFile()); List<String> commands = readCommands(batchCommandFilePath); boolean result = processCommands(commands); if (!result) { System.exit(1); } } private void runInteractive() { try { String rawLine; while (!_exit && (rawLine = _reader.readLine()) != null) { processCommand(rawLine); } } catch (Throwable t) { t.printStackTrace(); } finally { FileHistory history = (FileHistory) _reader.getHistory(); try { history.flush(); } catch (IOException e) { e.printStackTrace(); } } } private void runStartupFile() { Path startupFilePath = Paths.get(System.getenv(ENV_HOME), STARTUP_FILE); if (Files.exists(startupFilePath)) { List<String> commands = readCommands(startupFilePath); boolean result = processCommands(commands); if (!result) { System.exit(1); } } } private boolean setBatfishLogLevel(List<String> parameters) { String logLevelStr = parameters.get(0).toLowerCase(); if (!BatfishLogger.isValidLogLevel(logLevelStr)) { _logger.errorf("Undefined loglevel value: %s\n", logLevelStr); return false; } _settings.setBatfishLogLevel(logLevelStr); _logger.output("Changed batfish loglevel to " + logLevelStr + "\n"); return true; } private boolean setContainer(List<String> parameters) { _currContainerName = parameters.get(0); _logger.outputf("Active container is now set to %s\n", _currContainerName); return true; } private boolean setDeltaEnv(List<String> parameters) { _currDeltaEnv = parameters.get(0); if (_currDeltaTestrig == null) { _currDeltaTestrig = _currTestrig; } _logger.outputf("Active delta testrig->environment is now %s->%s\n", _currDeltaTestrig, _currDeltaEnv); return true; } private boolean setDeltaTestrig(List<String> parameters) { _currDeltaTestrig = parameters.get(0); _currDeltaEnv = (parameters.size() > 1) ? parameters.get(1) : DEFAULT_ENV_NAME; _logger.outputf("Delta testrig->env is now %s->%s\n", _currDeltaTestrig, _currDeltaEnv); return true; } private boolean setEnv(List<String> parameters) { if (!isSetTestrig()) { return false; } _currEnv = parameters.get(0); _logger.outputf("Base testrig->env is now %s->%s\n", _currTestrig, _currEnv); return true; } private boolean setLogLevel(List<String> parameters) { String logLevelStr = parameters.get(0).toLowerCase(); if (!BatfishLogger.isValidLogLevel(logLevelStr)) { _logger.errorf("Undefined loglevel value: %s\n", logLevelStr); return false; } _logger.setLogLevel(logLevelStr); _settings.setLogLevel(logLevelStr); _logger.output("Changed client loglevel to " + logLevelStr + "\n"); return true; } private boolean setPrettyPrint(List<String> parameters) { String ppStr = parameters.get(0).toLowerCase(); boolean prettyPrint = Boolean.parseBoolean(ppStr); _settings.setPrettyPrintAnswers(prettyPrint); _logger.output("Set pretty printing answers to " + ppStr + "\n"); return true; } private boolean setTestrig(List<String> parameters) { if (!isSetContainer(true)) { return false; } _currTestrig = parameters.get(0); _currEnv = (parameters.size() > 1) ? parameters.get(1) : DEFAULT_ENV_NAME; _logger.outputf("Base testrig->env is now %s->%s\n", _currTestrig, _currEnv); return true; } private boolean showApiKey() { _logger.outputf("Current API Key is %s\n", _settings.getApiKey()); return true; } private boolean showBatfishLogLevel() { _logger.outputf("Current batfish log level is %s\n", _settings.getBatfishLogLevel()); return true; } private boolean showBatfishOptions() { _logger.outputf("There are %d additional batfish options\n", _additionalBatfishOptions.size()); for (String option : _additionalBatfishOptions.keySet()) { _logger.outputf(" %s : %s \n", option, _additionalBatfishOptions.get(option)); } return true; } private boolean showContainer() { _logger.outputf("Current container is %s\n", _currContainerName); return true; } private boolean showCoordinatorHost() { _logger.outputf("Current coordinator host is %s\n", _settings.getCoordinatorHost()); return true; } private boolean showDeltaTestrig() { if (!isSetDeltaEnvironment()) { return false; } _logger.outputf("Delta testrig->environment is %s->%s\n", _currDeltaTestrig, _currDeltaEnv); return true; } private boolean showLogLevel() { _logger.outputf("Current client log level is %s\n", _logger.getLogLevelStr()); return true; } private boolean showTestrig() { if (!isSetTestrig()) { return false; } _logger.outputf("Base testrig->environment is %s->%s\n", _currTestrig, _currEnv); return true; } private boolean test(List<String> parameters) throws IOException { boolean failingTest = false; boolean missingReferenceFile = false; boolean testPassed = false; int testCommandIndex = 1; if (parameters.get(testCommandIndex).equals(FLAG_FAILING_TEST)) { testCommandIndex++; failingTest = true; } String referenceFileName = parameters.get(0); String[] testCommand = parameters .subList(testCommandIndex, parameters.size()) .toArray(new String[0]); _logger.debugf("Ref file is %s. \n", referenceFileName, parameters.size()); _logger.debugf("Test command is %s\n", Arrays.toString(testCommand)); File referenceFile = new File(referenceFileName); if (!referenceFile.exists()) { _logger.errorf("Reference file does not exist: %s\n", referenceFileName); missingReferenceFile = true; } File testoutFile = Files.createTempFile("test", "out").toFile(); testoutFile.deleteOnExit(); FileWriter testoutWriter = new FileWriter(testoutFile); boolean testCommandSucceeded = processCommand(testCommand, testoutWriter); testoutWriter.close(); if (!failingTest && testCommandSucceeded) { try { ObjectMapper mapper = new BatfishObjectMapper( getCurrentClassLoader()); // rewrite new answer string using local implementation String testOutput = CommonUtil .readFile(Paths.get(testoutFile.getAbsolutePath())); String testAnswerString = testOutput; try { Answer testAnswer = mapper.readValue(testOutput, Answer.class); testAnswerString = mapper.writeValueAsString(testAnswer); } catch (JsonParseException | UnrecognizedPropertyException e) { //not all outputs of process command are of Answer.class type //in that case, we use the exact string as initialized above for comparison } if (!missingReferenceFile) { String referenceOutput = CommonUtil .readFile(Paths.get(referenceFileName)); String referenceAnswerString = referenceOutput; // rewrite reference string using local implementation Answer referenceAnswer; try { referenceAnswer = mapper.readValue(referenceOutput, Answer.class); referenceAnswerString = mapper .writeValueAsString(referenceAnswer); } catch (JsonParseException | UnrecognizedPropertyException e) { //throw new BatfishException( // "Error reading reference output using current schema (reference output is likely obsolete)", // e); //not all outputs of process command are of Answer.class type //in that case, we use the exact string as initialized above for comparison } // due to options chosen in BatfishObjectMapper, if json // outputs were equal, then strings should be equal if (referenceAnswerString.equals(testAnswerString)) { testPassed = true; } } } catch (Exception e) { _logger.error("Exception in comparing test results: " + ExceptionUtils.getStackTrace(e)); } } else if (failingTest) { testPassed = !testCommandSucceeded; } StringBuilder sb = new StringBuilder(); sb.append("'" + testCommand[0]); for (int i = 1; i < testCommand.length; i++) { sb.append(" " + testCommand[i]); } sb.append("'"); String testCommandText = sb.toString(); String message = "Test: " + testCommandText + (failingTest ? " results in error as expected" : " matches " + referenceFileName) + (testPassed ? ": Pass\n" : ": Fail\n"); _logger.output(message); if (!failingTest) { if (!testPassed) { String outFileName = referenceFile + ".testout"; Files.move(Paths.get(testoutFile.getAbsolutePath()), Paths.get(referenceFile + ".testout"), StandardCopyOption.REPLACE_EXISTING); _logger.outputf("Copied output to %s\n", outFileName); } } return true; } private void unsetTestrig(boolean doDelta) { if (doDelta) { _currDeltaTestrig = null; _currDeltaEnv = null; _logger.info("Delta testrig and environment are now unset\n"); } else { _currTestrig = null; _currEnv = null; _logger.info("Base testrig and environment are now unset\n"); } } private boolean uploadCustomObject(List<String> parameters) { if (!isSetTestrig() || !isSetContainer(true)) { return false; } String objectName = parameters.get(0); String objectFile = parameters.get(1); // upload the object return _workHelper.uploadCustomObject(_currContainerName, _currTestrig, objectName, objectFile); } private boolean uploadTestrigOrEnv(String fileOrDir, String testrigOrEnvName, boolean isTestrig) throws Exception { File filePointer = new File(fileOrDir); String uploadFilename = fileOrDir; if (filePointer.isDirectory()) { File uploadFile = File.createTempFile("testrigOrEnv", "zip"); uploadFile.deleteOnExit(); uploadFilename = uploadFile.getAbsolutePath(); ZipUtility.zipFiles(filePointer.getAbsolutePath(), uploadFilename); } boolean result = (isTestrig) ? _workHelper.uploadTestrig(_currContainerName, testrigOrEnvName, uploadFilename) : _workHelper.uploadEnvironment(_currContainerName, _currTestrig, testrigOrEnvName, uploadFilename); // unequal means we must have created a temporary file if (uploadFilename != fileOrDir) { new File(uploadFilename).delete(); } return result; } private void validateInstanceData(InstanceData instanceData) { String description = instanceData.getDescription(); String q = "Question: '" + instanceData.getInstanceName() + "'"; if (description == null || description.length() == 0) { throw new BatfishException(q + " is missing question description"); } for (Entry<String, Variable> e : instanceData.getVariables().entrySet()) { String variableName = e.getKey(); Variable variable = e.getValue(); String v = "Variable: '" + variableName + "' in " + q; String variableDescription = variable.getDescription(); if (variableDescription == null || variableDescription.length() == 0) { throw new BatfishException(v + " is missing description"); } } } private boolean validCommandUsage(String[] words) { return true; } }