From bc63a2842676c5c40e9ab9b807a78093020dd84a Mon Sep 17 00:00:00 2001 From: Jonathan Shook Date: Tue, 26 Jul 2022 01:15:06 -0500 Subject: [PATCH] improve unrecognized command error --- .../docsys/core/NBWebServerApp.java | 39 +++++--- .../java/io/nosqlbench/engine/cli/NBCLI.java | 96 ++++++++----------- .../engine/cli/NBCLICommandParser.java | 21 ++-- .../nosqlbench/engine/cli/NBCLIOptions.java | 61 ++++++++++-- .../engine/cli/TestNBCLIOptions.java | 2 +- .../resources/ScenarioExecutorEndpoint.java | 5 +- 6 files changed, 136 insertions(+), 88 deletions(-) diff --git a/docsys/src/main/java/io/nosqlbench/docsys/core/NBWebServerApp.java b/docsys/src/main/java/io/nosqlbench/docsys/core/NBWebServerApp.java index b455291fb..1c5058b5a 100644 --- a/docsys/src/main/java/io/nosqlbench/docsys/core/NBWebServerApp.java +++ b/docsys/src/main/java/io/nosqlbench/docsys/core/NBWebServerApp.java @@ -16,7 +16,9 @@ package io.nosqlbench.docsys.core; +import io.nosqlbench.api.spi.BundledApp; import io.nosqlbench.docsys.endpoints.DocsysMarkdownEndpoint; +import io.nosqlbench.nb.annotations.Service; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -27,24 +29,12 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.Arrays; -public class NBWebServerApp { +@Service(value=NBWebServerApp.class,selector="appserver") +public class NBWebServerApp implements BundledApp { private static final Logger logger = LogManager.getLogger(NBWebServerApp.class); public static void main(String[] args) { - if (args.length > 0 && args[0].contains("help")) { - showHelp(); - } else if (args.length > 0 && args[0].contains("generate")) { - try { - String[] genargs = Arrays.copyOfRange(args, 1, args.length); - logger.info("Generating with args [" + String.join("][", args) + "]"); - generate(genargs); - } catch (IOException e) { - logger.error("could not generate files with command " + String.join(" ", args)); - e.printStackTrace(); - } - } else { - runServer(args); - } + new NBWebServerApp().appMain(args); } private static boolean deleteDirectory(File directoryToBeDeleted) { @@ -152,4 +142,23 @@ public class NBWebServerApp { private static void listTopics() { } + + @Override + public int appMain(String[] args) { + if (args.length > 0 && args[0].contains("help")) { + showHelp(); + } else if (args.length > 0 && args[0].contains("generate")) { + try { + String[] genargs = Arrays.copyOfRange(args, 1, args.length); + logger.info("Generating with args [" + String.join("][", args) + "]"); + generate(genargs); + } catch (IOException e) { + logger.error("could not generate files with command " + String.join(" ", args)); + e.printStackTrace(); + } + } else { + runServer(args); + } + return 0; + } } diff --git a/engine-cli/src/main/java/io/nosqlbench/engine/cli/NBCLI.java b/engine-cli/src/main/java/io/nosqlbench/engine/cli/NBCLI.java index b6a44efe2..0f3eed593 100644 --- a/engine-cli/src/main/java/io/nosqlbench/engine/cli/NBCLI.java +++ b/engine-cli/src/main/java/io/nosqlbench/engine/cli/NBCLI.java @@ -16,14 +16,21 @@ package io.nosqlbench.engine.cli; -import io.nosqlbench.api.docsapi.docexporter.BundledMarkdownExporter; -import io.nosqlbench.docsys.core.NBWebServerApp; +import io.nosqlbench.api.annotations.Annotation; +import io.nosqlbench.api.annotations.Layer; +import io.nosqlbench.api.content.Content; +import io.nosqlbench.api.content.NBIO; +import io.nosqlbench.api.engine.metrics.ActivityMetrics; +import io.nosqlbench.api.errors.BasicError; +import io.nosqlbench.api.logging.NBLogLevel; +import io.nosqlbench.api.metadata.SessionNamer; +import io.nosqlbench.api.metadata.SystemId; +import io.nosqlbench.api.spi.BundledApp; import io.nosqlbench.engine.api.activityapi.cyclelog.outputs.cyclelog.CycleLogDumperUtility; import io.nosqlbench.engine.api.activityapi.cyclelog.outputs.cyclelog.CycleLogImporterUtility; import io.nosqlbench.engine.api.activityapi.input.InputType; import io.nosqlbench.engine.api.activityapi.output.OutputType; import io.nosqlbench.engine.api.activityconfig.rawyaml.RawStmtsLoader; -import io.nosqlbench.api.engine.metrics.ActivityMetrics; import io.nosqlbench.engine.core.annotation.Annotators; import io.nosqlbench.engine.core.lifecycle.*; import io.nosqlbench.engine.core.logging.LoggerConfig; @@ -35,16 +42,8 @@ import io.nosqlbench.engine.core.script.ScenariosExecutor; import io.nosqlbench.engine.core.script.ScriptParams; import io.nosqlbench.engine.docker.DockerMetricsManager; import io.nosqlbench.nb.annotations.Maturity; -import io.nosqlbench.api.annotations.Annotation; -import io.nosqlbench.api.annotations.Layer; -import io.nosqlbench.api.content.Content; -import io.nosqlbench.api.content.NBIO; -import io.nosqlbench.api.errors.BasicError; -import io.nosqlbench.api.logging.NBLogLevel; -import io.nosqlbench.api.markdown.exporter.MarkdownExporter; -import io.nosqlbench.api.metadata.SessionNamer; -import io.nosqlbench.api.metadata.SystemId; -import io.nosqlbench.virtdata.userlibs.apps.VirtDataMainApp; +import io.nosqlbench.nb.annotations.Service; +import io.nosqlbench.nb.annotations.ServiceSelector; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.ConfigurationFactory; @@ -53,15 +52,10 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.concurrent.locks.LockSupport; import java.util.function.Function; import java.util.stream.Collectors; @@ -178,6 +172,20 @@ public class NBCLI implements Function { logger.info("command-line: " + Arrays.stream(args).collect(Collectors.joining(" "))); logger.info("client-hardware: " + SystemId.getHostSummary()); + + // Invoke any bundled app which matches the name of the first non-option argument, if it exists. + // If it does not, continue with no fanfare. Let it drop through to other command resolution methods. + if (args.length>0 && args[0].matches("\\w[\\w\\d-_.]+")) { + ServiceSelector apploader = ServiceSelector.of(args[0], ServiceLoader.load(BundledApp.class)); + BundledApp app = apploader.get().orElse(null); + if (app!=null) { + String[] appargs = Arrays.copyOfRange(args, 1, args.length); + logger.info("invoking bundled app '" + args[0] + "' (" + app.getClass().getSimpleName() + ")."); + int result = app.appMain(appargs); + return result; + } + } + boolean dockerMetrics = globalOptions.wantsDockerMetrics(); String dockerMetricsAt = globalOptions.wantsDockerMetricsAt(); String reportGraphiteTo = globalOptions.wantsReportGraphiteTo(); @@ -226,40 +234,6 @@ public class NBCLI implements Function { annotatorsConfig = "[{type:'log',level:'info'}]"; } - if (args.length > 0 && args[0].toLowerCase().equals("cqlgen")) { - String exporterImpl = "io.nosqlbench.cqlgen.exporter.CGWorkloadExporter"; - String[] exporterArgs = Arrays.copyOfRange(args, 1, args.length); - try { - Class genclass = Class.forName(exporterImpl); - Method main = genclass.getMethod("main", new String[0].getClass()); - Object result = main.invoke(null, new Object[]{exporterArgs}); - } catch (ClassNotFoundException e) { - throw new RuntimeException("cql workload exporter implementation " + exporterImpl + " was not found in this runtime."); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - System.out.println("Error in app: " + e.toString()); - e.printStackTrace(); - throw new RuntimeException("error while invoking " + exporterImpl + ": " + e.toString(),e); - } - return EXIT_OK; - } - if (args.length > 0 && args[0].toLowerCase().equals("export-docs")) { - BundledMarkdownExporter.main(Arrays.copyOfRange(args,1,args.length)); - return EXIT_OK; - } - if (args.length > 0 && args[0].toLowerCase().equals("virtdata")) { - VirtDataMainApp.main(Arrays.copyOfRange(args, 1, args.length)); - return EXIT_OK; - } - if (args.length > 0 && args[0].toLowerCase().matches("docserver|appserver")) { - NBWebServerApp.main(Arrays.copyOfRange(args, 1, args.length)); - return EXIT_OK; - } - if (args.length > 0 && args[0].toLowerCase().equals(MarkdownExporter.APP_NAME) - ) { - MarkdownExporter.main(Arrays.copyOfRange(args, 1, args.length)); - return EXIT_OK; - } - NBCLIOptions options = new NBCLIOptions(args); logger = LogManager.getLogger("NBCLI"); @@ -282,6 +256,20 @@ public class NBCLI implements Function { return EXIT_OK; } + if (options.isWantsListApps()) { + ServiceLoader loader = ServiceLoader.load(BundledApp.class); + for (ServiceLoader.Provider provider : loader.stream().toList()) { + Class appType = provider.type(); + String name = appType.getAnnotation(Service.class).selector(); + System.out.println(String.format("%-40s %s",name,appType.getCanonicalName())); + } + return EXIT_OK; + } + + if (options.getWantsListCommands()) { + NBCLICommandParser.RESERVED_WORDS.forEach(System.out::println); + return EXIT_OK; + } if (options.wantsActivityTypes()) { new ActivityTypeLoader().getAllSelectors().forEach(System.out::println); return EXIT_OK; @@ -297,7 +285,7 @@ public class NBCLI implements Function { return EXIT_OK; } - if (options.wantsScriptList()) { + if (options.wantsListScripts()) { NBCLIScripts.printScripts(true, options.wantsIncludes()); return EXIT_OK; } diff --git a/engine-cli/src/main/java/io/nosqlbench/engine/cli/NBCLICommandParser.java b/engine-cli/src/main/java/io/nosqlbench/engine/cli/NBCLICommandParser.java index c7fecc56d..a19fe7b8d 100644 --- a/engine-cli/src/main/java/io/nosqlbench/engine/cli/NBCLICommandParser.java +++ b/engine-cli/src/main/java/io/nosqlbench/engine/cli/NBCLICommandParser.java @@ -16,14 +16,19 @@ package io.nosqlbench.engine.cli; -import io.nosqlbench.engine.api.scenarios.NBCLIScenarioParser; import io.nosqlbench.api.content.Content; import io.nosqlbench.api.content.NBIO; +import io.nosqlbench.engine.api.scenarios.NBCLIScenarioParser; -import java.security.InvalidParameterException; import java.util.*; +/** + * This parser will return a non-empty optional if there is no error. + * If the optional is empty, then it means some part of the command structure + * was not recognized. + */ public class NBCLICommandParser { + private static final String FRAGMENT = "fragment"; private static final String SCRIPT = "script"; private static final String START = "start"; @@ -37,21 +42,20 @@ public class NBCLICommandParser { public static final Set RESERVED_WORDS = new HashSet<>() {{ addAll( Arrays.asList( - SCRIPT, ACTIVITY, SCENARIO, RUN, START, - FRAGMENT, STOP, AWAIT, WAIT_MILLIS + FRAGMENT, SCRIPT, START, RUN, AWAIT, STOP, ACTIVITY, SCENARIO, WAIT_MILLIS ) ); }}; - public static void parse( + public static Optional> parse( LinkedList arglist, - LinkedList cmdList, String... includes ) { + List cmdList = new LinkedList<>(); PathCanonicalizer canonicalizer = new PathCanonicalizer(includes); while (arglist.peekFirst() != null) { String word = arglist.peekFirst(); - Cmd cmd = null; + Cmd cmd; switch (word) { case FRAGMENT: case SCRIPT: @@ -80,11 +84,12 @@ public class NBCLICommandParser { } else if (NBCLIScenarioParser.isFoundWorkload(word, includes)) { NBCLIScenarioParser.parseScenarioCommand(arglist, RESERVED_WORDS, includes); } else { - throw new InvalidParameterException("unrecognized option:" + word); + return Optional.empty(); } break; } } + return Optional.of(cmdList); } } diff --git a/engine-cli/src/main/java/io/nosqlbench/engine/cli/NBCLIOptions.java b/engine-cli/src/main/java/io/nosqlbench/engine/cli/NBCLIOptions.java index 003c65599..e1409b994 100644 --- a/engine-cli/src/main/java/io/nosqlbench/engine/cli/NBCLIOptions.java +++ b/engine-cli/src/main/java/io/nosqlbench/engine/cli/NBCLIOptions.java @@ -61,6 +61,7 @@ public class NBCLIOptions { // Discovery private static final String HELP = "--help"; + private static final String LIST_COMMANDS = "--list-commands"; private static final String LIST_METRICS = "--list-metrics"; private static final String LIST_DRIVERS = "--list-drivers"; private static final String LIST_ACTIVITY_TYPES = "--list-activity-types"; @@ -69,6 +70,7 @@ public class NBCLIOptions { private static final String LIST_SCENARIOS = "--list-scenarios"; private static final String LIST_INPUT_TYPES = "--list-input-types"; private static final String LIST_OUTPUT_TYPES = "--list-output-types"; + private static final String LIST_APPS = "--list-apps"; private static final String VERSION_COORDS = "--version-coords"; private static final String VERSION = "--version"; private static final String SHOW_SCRIPT = "--show-script"; @@ -128,7 +130,7 @@ public class NBCLIOptions { // private static final String DEFAULT_CONSOLE_LOGGING_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"; - private final LinkedList cmdList = new LinkedList<>(); + private final List cmdList = new ArrayList<>(); private int logsMax = 0; private boolean wantsVersionShort = false; private boolean wantsVersionCoords = false; @@ -160,8 +162,8 @@ public class NBCLIOptions { private Map logLevelsOverrides = new HashMap<>(); private boolean enableChart = false; private boolean dockerMetrics = false; - private boolean wantsScenariosList = false; - private boolean wantsScriptList = false; + private boolean wantsListScenarios = false; + private boolean wantsListScripts = false; private String wantsToCopyWorkload = null; private boolean wantsWorkloadsList = false; private final List wantsToIncludePaths = new ArrayList<>(); @@ -185,7 +187,16 @@ public class NBCLIOptions { private boolean enableAnsi = System.getenv("TERM")!=null && !System.getenv("TERM").isEmpty(); private Maturity minMaturity = Maturity.Unspecified; private String graphitelogLevel="info"; + private boolean wantsListCommands = false; + private boolean wantsListApps = false; + public boolean isWantsListApps() { + return wantsListApps; + } + + public boolean getWantsListCommands() { + return wantsListCommands; + } public String getAnnotatorsConfig() { return annotatorsConfig; } @@ -517,6 +528,10 @@ public class NBCLIOptions { arglist.removeFirst(); showScript = true; break; + case LIST_COMMANDS: + arglist.removeFirst(); + this.wantsListCommands = true; + break; case LIST_METRICS: arglist.removeFirst(); arglist.addFirst("start"); @@ -596,16 +611,20 @@ public class NBCLIOptions { break; case LIST_SCENARIOS: arglist.removeFirst(); - wantsScenariosList = true; + wantsListScenarios = true; break; case LIST_SCRIPTS: arglist.removeFirst(); - wantsScriptList = true; + wantsListScripts = true; break; case LIST_WORKLOADS: arglist.removeFirst(); wantsWorkloadsList = true; break; + case LIST_APPS: + arglist.removeFirst(); + wantsListApps= true; + break; case SCRIPT_FILE: arglist.removeFirst(); scriptFile = readWordOrThrow(arglist, "script file"); @@ -619,7 +638,31 @@ public class NBCLIOptions { } } arglist = nonincludes; - NBCLICommandParser.parse(arglist, cmdList); + Optional> commands = NBCLICommandParser.parse(arglist); + if (commands.isPresent()) { + this.cmdList.addAll(commands.get()); + } else { + String arg = arglist.peekFirst(); + Objects.requireNonNull(arg); + String helpmsg = """ + Could not recognize command 'ARG'. + This means that all of the following searches for a compatible command failed: + 1. commands: no scenario command named 'ARG' is known. (start, run, await, ...) + 2. scripts: no auto script named './scripts/auto/ARG.js' in the local filesystem. + 3. scripts: no auto script named 'scripts/auto/ARG.js' was found in the PROG binary. + 4. workloads: no workload file named ARG[.yaml] was found in the local filesystem, even in include paths INCLUDES. + 5. workloads: no workload file named ARG[.yaml] was bundled in PROG binary, even in include paths INCLUDES. + 6. apps: no application named ARG was bundled in PROG. + + You can discover available ways to invoke PROG by using the various --list-* commands: + [ --list-commands, --list-scripts, --list-workloads (and --list-scenarios), --list-apps ] + """ + .replaceAll("ARG",arg) + .replaceAll("PROG","nb5") + .replaceAll("INCLUDES", String.join(",",this.wantsIncludes())); + throw new BasicError(helpmsg); + + } } @@ -859,11 +902,11 @@ public class NBCLIOptions { } public boolean wantsScenariosList() { - return wantsScenariosList; + return wantsListScenarios; } - public boolean wantsScriptList() { - return wantsScriptList; + public boolean wantsListScripts() { + return wantsListScripts; } public boolean wantsToCopyResource() { diff --git a/engine-cli/src/test/java/io/nosqlbench/engine/cli/TestNBCLIOptions.java b/engine-cli/src/test/java/io/nosqlbench/engine/cli/TestNBCLIOptions.java index 96d0eb7b7..a57273c4f 100644 --- a/engine-cli/src/test/java/io/nosqlbench/engine/cli/TestNBCLIOptions.java +++ b/engine-cli/src/test/java/io/nosqlbench/engine/cli/TestNBCLIOptions.java @@ -205,7 +205,7 @@ public class TestNBCLIOptions { @Test public void listScripts() { NBCLIOptions opts = new NBCLIOptions(new String[]{ "--list-scripts"}); - assertThat(opts.wantsScriptList()).isTrue(); + assertThat(opts.wantsListScripts()).isTrue(); } @Test diff --git a/engine-rest/src/main/java/io/nosqlbench/engine/rest/resources/ScenarioExecutorEndpoint.java b/engine-rest/src/main/java/io/nosqlbench/engine/rest/resources/ScenarioExecutorEndpoint.java index fac00fc81..86497249a 100644 --- a/engine-rest/src/main/java/io/nosqlbench/engine/rest/resources/ScenarioExecutorEndpoint.java +++ b/engine-rest/src/main/java/io/nosqlbench/engine/rest/resources/ScenarioExecutorEndpoint.java @@ -122,7 +122,10 @@ public class ScenarioExecutorEndpoint implements WebServiceObject { } args = substituteFilenames(rq, args); - NBCLICommandParser.parse(args, cmdList, workspace.asIncludes()); + Optional> parsed = NBCLICommandParser.parse(args, workspace.asIncludes()); + if (!parsed.isPresent()) { + return Response.serverError().entity("Unable to render command stream from provided command spec.").build(); + } ScriptBuffer buffer = new BasicScriptBuffer(); buffer.add(cmdList.toArray(new Cmd[0]));