Alon Bar-Lev has posted comments on this change. Change subject: extensions test tool: logger ......................................................................
Patch Set 18: (34 comments) GOOD! Looks very nice. Some more minor comments... :) But I can see the end. https://gerrit.ovirt.org/#/c/37886/18/backend/manager/extension-tool/src/main/java/org/ovirt/engine/exttool/core/ExtensionsToolExecutor.java File backend/manager/extension-tool/src/main/java/org/ovirt/engine/exttool/core/ExtensionsToolExecutor.java: Line 35: ExtensionsToolExecutor.class.getResourceAsStream("arguments.properties"), Line 36: "core" Line 37: ); Line 38: Map<String,Object> argMap = parser.parse(args); Line 39: setupLogger(argMap); so how do we set the logger of the parser :)))) we should probably add a system property to do this as well, move the setupLogger to the earliest point and set the level based on system property, then reset the level based on the arguments. Line 40: if(argMap.containsKey("help")) { Line 41: logger.info( Line 42: String.format( Line 43: "usage: %s%n%n%s%n%n%s%n%n%s", Line 37: ); Line 38: Map<String,Object> argMap = parser.parse(args); Line 39: setupLogger(argMap); Line 40: if(argMap.containsKey("help")) { Line 41: logger.info( this is a good question, should we use logger to print usage and version, or plain stdout. I think that this should go into stdout, as you do want to redirect it and do not add the logger headers such as timestamp, level etc... and you do not want to be effected if logger is redirected to file only. Line 42: String.format( Line 43: "usage: %s%n%n%s%n%n%s%n%n%s", Line 44: parser.getPrefixUsage().replace("@PROGRAM_NAME@", PROGRAM_NAME), Line 45: parser.getPrefixHelp(), Line 42: String.format( Line 43: "usage: %s%n%n%s%n%n%s%n%n%s", Line 44: parser.getPrefixUsage().replace("@PROGRAM_NAME@", PROGRAM_NAME), Line 45: parser.getPrefixHelp(), Line 46: parser.getPrefixUnderlinetext(), all the above can be get out of parser.getUsage(), then all you need to do is replace the substitutions and output the module list. Line 47: getModules(moduleServices) Line 48: ) Line 49: ); Line 50: throw new ExitException(); Line 57: System.getProperty("org.ovirt.engine.exttool.core.packageVersion"), Line 58: System.getProperty("org.ovirt.engine.exttool.core.packageDisplayName") Line 59: ) Line 60: ); Line 61: throw new ExitException(); you can use ExitException(0, "version") so in debug we know why we exited, same to any other exit. Line 62: } Line 63: List<String> moduleOthers = (List<String>) argMap.get("__other__"); Line 64: if (moduleOthers.size() < 1) { Line 65: logger.error(String.format("Please provide module.%n%1$s", getModules(moduleServices))); Line 61: throw new ExitException(); Line 62: } Line 63: List<String> moduleOthers = (List<String>) argMap.get("__other__"); Line 64: if (moduleOthers.size() < 1) { Line 65: logger.error(String.format("Please provide module.%n%1$s", getModules(moduleServices))); please use {} and not String.format when you use logger. module list should be obtain using --help, there is no point in doing this here. but if you want, it is ok, but... it should probably be: ERROR Please provide module name. Available modules: x, y, z or: ERROR Module name is required INFO Available module names: x, y, z please avoid using new line within log messages, as it is very difficult to parse it later. Line 66: throw new ExitException(1); Line 67: } Line 68: String module = moduleOthers.get(0); Line 69: ModuleService moduleService = moduleServices.get(module); Line 64: if (moduleOthers.size() < 1) { Line 65: logger.error(String.format("Please provide module.%n%1$s", getModules(moduleServices))); Line 66: throw new ExitException(1); Line 67: } Line 68: String module = moduleOthers.get(0); maybe if module is "help" we can print usage as well. Line 69: ModuleService moduleService = moduleServices.get(module); Line 70: if (moduleService == null) { Line 71: logger.error(String.format("No such '%1$s' module exists.", module)); Line 72: throw new ExitException(1); Line 70: if (moduleService == null) { Line 71: logger.error(String.format("No such '%1$s' module exists.", module)); Line 72: throw new ExitException(1); Line 73: } Line 74: loadExtensions(moduleService, argMap); loadExtensions after module parse argument? so we avoid doing anything before syntax is correct? otherwise there is no sense in splitting the parse arguments and run. Line 75: moduleService.parseArguments(moduleOthers); Line 76: moduleService.run(); Line 77: } catch(ExitException e) { Line 78: logger.debug(e.getMessage(), e); Line 80: } catch (Throwable t) { Line 81: String message = t.getMessage() != null ? t.getMessage() : t.getClass().getName(); Line 82: logger.error(message); Line 83: logger.debug(message, t); Line 84: } please debug the exitStatus. Line 85: System.exit(exitStatus); Line 86: } Line 87: Line 88: private static void loadExtensions(ModuleService moduleService, Map<String, Object> argMap) { Line 93: if(files == null) { Line 94: files = listFiles( Line 95: ((String) argMap.get("extensions-dir")).replace( Line 96: "@ENGINE_ETC@", Line 97: System.getProperty("org.ovirt.engine.exttool.core.engineEtc") if you put the program name as static, why not align? not important which, but please be consistent. Line 98: ), Line 99: "properties" Line 100: ); Line 101: } Line 144: File dir = new File(directory); Line 145: List<File> propFiles = new ArrayList<>(); Line 146: if (!dir.exists()) { Line 147: logger.warn("Directory {} doesn't exists.", directory); Line 148: return propFiles; please avoid returning in middle of functions. Line 149: } Line 150: File[] dirFiles = dir.listFiles(); Line 151: if (dirFiles != null) { Line 152: for (File file : dirFiles) { Line 146: if (!dir.exists()) { Line 147: logger.warn("Directory {} doesn't exists.", directory); Line 148: return propFiles; Line 149: } Line 150: File[] dirFiles = dir.listFiles(); I think this will return null if directory cannot be found, so no need to add the above check. Line 151: if (dirFiles != null) { Line 152: for (File file : dirFiles) { Line 153: if (file.getName().endsWith(suffix)) { Line 154: propFiles.add(file); Line 162: private static String getModules(Map<String, ModuleService> modules) { Line 163: StringBuilder sb = new StringBuilder(String.format("Available Modules:%n")); Line 164: for(ModuleService entry : modules.values()) { Line 165: sb.append( Line 166: String.format(" %-10s - %s", entry.getName(), entry.getDescription()) I guess you want a new line after each entry? now I also see... if you want to include it in errors and such, you do not want the description, if you want to include in usage you do need... xxx ERROR Please provide module name. Available modules: x, y, z xxx --help Usage: .... Available Modules: x description y description Line 167: ); Line 168: } Line 169: return sb.toString(); Line 170: } https://gerrit.ovirt.org/#/c/37886/18/backend/manager/extension-tool/src/main/java/org/ovirt/engine/exttool/logger/LoggerServiceImpl.java File backend/manager/extension-tool/src/main/java/org/ovirt/engine/exttool/logger/LoggerServiceImpl.java: Line 63: public void parseArguments(List<String> moduleArgs) throws Exception { Line 64: moduleArgs.remove(0); Line 65: ParametersParser parser = new ParametersParser( Line 66: getClass().getResourceAsStream("arguments.properties"), Line 67: getName() I think that the prefix of the module core arguments should be the same for all modules within their properties file. it will be easier to distinguish it from the actions. not that important. Line 68: ); Line 69: args.putAll(parser.parse(moduleArgs)); Line 70: if(args.containsKey("help")) { Line 71: printUsage(parser); Line 65: ParametersParser parser = new ParametersParser( Line 66: getClass().getResourceAsStream("arguments.properties"), Line 67: getName() Line 68: ); Line 69: args.putAll(parser.parse(moduleArgs)); why do you need to copy args? what am I missing? logically you should be able to use this reference returned from parser forever. Line 70: if(args.containsKey("help")) { Line 71: printUsage(parser); Line 72: throw new ExitException(); Line 73: } Line 71: printUsage(parser); Line 72: throw new ExitException(); Line 73: } Line 74: Line 75: List<String> actionOthers = (List<String>)args.get("__other__"); I guess "__other__" can be a static constaint within parser :) Line 76: String action = actionOthers.remove(0); Line 77: actionMethod = actions.get(action); Line 78: if(actionMethod == null) { Line 79: throw new IllegalArgumentException( Line 81: ); Line 82: } Line 83: Line 84: parser = new ParametersParser( Line 85: getClass().getResourceAsStream("arguments.properties"), hmmm... just realized, all over, this should be: try(InputStream is = getClass().getResourceAsStream("arguments.properties")) { parser = ... } Line 86: action Line 87: ); Line 88: args.putAll(parser.parse(actionOthers)); Line 89: if(args.containsKey("help")) { Line 84: parser = new ParametersParser( Line 85: getClass().getResourceAsStream("arguments.properties"), Line 86: action Line 87: ); Line 88: args.putAll(parser.parse(actionOthers)); again, I am unsure why you copy. Line 89: if(args.containsKey("help")) { Line 90: printUsage(parser); Line 91: throw new ExitException(); Line 92: } Line 104: parser.getPrefixUsage().replace("@PROGRAM_NAME@", (String)getContext().get(PROGRAM_NAME)), Line 105: parser.getPrefixHelp(), Line 106: parser.getPrefixUnderlinetext() Line 107: ) Line 108: ); should go to stdout, well, I would have put the stdin/stdout/stderr in context by core, but not that important for now. and again, all this can be acquire from the parser, so if we add new fields it will be available to all. all you need to do is print parser.getUsage().replace().replace().replace()... Line 109: } Line 110: Line 111: private void logrecord() { Line 112: ExtensionProxy proxy = ((Map<String, ExtensionProxy>) context.get(EXTENSIONS_MAP)).get( https://gerrit.ovirt.org/#/c/37886/18/backend/manager/extension-tool/src/main/resources/org/ovirt/engine/exttool/core/arguments.properties File backend/manager/extension-tool/src/main/resources/org/ovirt/engine/exttool/core/arguments.properties: Line 19: core.arg.help.name = help Line 20: core.arg.help.help = show test tool help Line 21: core.arg.version.name = version Line 22: core.arg.version.help = show version of test tool Line 23: core.help.underlinetext = You can use this tool to test your extension. no need for this underlinetext. but we should cleanup the variable names... first, help is either help or usage, but should be consistent. xxxx [options] xxxx <-- help.usage this program is bla bla <-- help.header --option xxx <-- generated from properties Examples: <-- help.footer xxx --debug aaa in our case: @PROGRAM_NAME@ [options] module [options] oVirt Engine Extension API test tool Options: .... Available modules: logger - bla bla bla Line 24: core.help.footer = oVirt Engine Extension API test tool https://gerrit.ovirt.org/#/c/37886/18/backend/manager/extension-tool/src/main/resources/org/ovirt/engine/exttool/logger/arguments.properties File backend/manager/extension-tool/src/main/resources/org/ovirt/engine/exttool/logger/arguments.properties: Line 1: logger.help.usage.header = @PROGRAM_NAME@ logger [options] action [options] Line 2: logger.help.underlinetext = You can test your logger extension Logger interface test module Line 3: logger.arg.help.name = help Line 4: logger.arg.help.help = show help for logger module Line 5: log-record.arg.logger-name.name = logger-name Line 6: log-record.arg.logger-name.mandatory = true https://gerrit.ovirt.org/#/c/37886/18/backend/manager/modules/uutils/src/main/java/org/ovirt/engine/core/uutils/cli/parser/ParametersParser.java File backend/manager/modules/uutils/src/main/java/org/ovirt/engine/core/uutils/cli/parser/ParametersParser.java: Line 65: } Line 66: if(!arg.startsWith(LONG_PREFIX)) { Line 67: others.add(arg); Line 68: break; Line 69: } consider switching these... String arg = args.get(0); if (!arg.startsWith(LONG_PREFIX)) { break; } args.remove(0); if (arg.equals(LONG_PREFIX)) { break; } then you do not need to add back what you removed. Line 70: Line 71: String[] argVal = parseArgument(arg.substring(2)); Line 72: String key = argVal[0]; Line 73: String value = argVal[1]; Line 74: Line 75: ParserArgument parserArgument = arguments.get(key); Line 76: if(parserArgument == null) { Line 77: throw new ExitException( Line 78: String.format("Argument '%1$s' is not valid", arg), Invalid argument '%s'? Line 79: 1 Line 80: ); Line 81: } Line 82: if ( Line 92: } Line 93: } Line 94: if(parserArgument.getType() == ArgumentType.has_argument && value == null) { Line 95: throw new ExitException( Line 96: String.format("Argument's '%1$s' value is required", key), Value is required but missing for argument '%s'? Line 97: 1 Line 98: ); Line 99: } Line 100: Object convertedValue = null; Line 102: Matcher m = parserArgument.getMatcher().matcher(value); Line 103: if (!m.find()) { Line 104: throw new ExitException( Line 105: String.format( Line 106: "Argument's '%1$s' value '%2$s' don't match pattern %3$s", key, value, m.pattern() Pattern for argument '%s' does not match, pattern is '%s', value is '%s'? Line 107: ), Line 108: 1 Line 109: ); Line 110: } Line 109: ); Line 110: } Line 111: convertedValue = Util.getObjectValueByString(parserArgument.getConvert(), value); Line 112: } Line 113: Object o = argMap.get(key); can't this be in the else? Line 114: if(!parserArgument.isMutivalue()) { Line 115: argMap.put(key, convertedValue); Line 116: } else { Line 117: if(o == null) { Line 119: objects.add(convertedValue); Line 120: argMap.put(key, objects); Line 121: } else { Line 122: ((List<Object>)o).add(convertedValue); Line 123: } hmmm.... } else { Collection c = (Collection)argMap.get(key); if (c == null) { c = new ArrayList(); argMap.put(key, c); } c.add(convertedValue); } Line 124: } Line 125: } Line 126: mandatory.removeAll(argMap.keySet()); Line 127: if(!mandatory.isEmpty() && !argMap.containsKey("help")) { Line 122: ((List<Object>)o).add(convertedValue); Line 123: } Line 124: } Line 125: } Line 126: mandatory.removeAll(argMap.keySet()); better to copy so that you can call parse() twice? Line 127: if(!mandatory.isEmpty() && !argMap.containsKey("help")) { Line 128: throw new ExitException( Line 129: String.format("Argument(s) '%1$s' required", StringUtils.join(mandatory, ", ")), Line 130: 1 Line 128: throw new ExitException( Line 129: String.format("Argument(s) '%1$s' required", StringUtils.join(mandatory, ", ")), Line 130: 1 Line 131: ); Line 132: } help should not be any interest of the parser. if you do, please add: prefix.arg.xxx.help = true the actual parameter name should not leak here. but it is ok to issue error if --help is given as well... no reason to have this logic. Line 133: others.addAll(args); Line 134: argMap.put("__other__", others); Line 135: Line 136: return argMap; Line 130: 1 Line 131: ); Line 132: } Line 133: others.addAll(args); Line 134: argMap.put("__other__", others); please add public static constant for this within parser and use it all over... Line 135: Line 136: return argMap; Line 137: } Line 138: Line 182: getArgAttrValue(arg, "convert"), Line 183: true, Line 184: Thread.currentThread().getContextClassLoader() Line 185: ) Line 186: ); why can't the argument read it-self out of arg? one way is to convert the properties into map and send it: ParserArgument argument = new ParserArgument(propertiesToMap(arg)); another can be sending the prefix and the entire properties: ParserArgument argument = new ParserArgument(props, prefix); last can use reflection... but this is bad. anyway, the logic of what argument is can be in argument... including how to read it self from the properties. Line 187: } catch (ClassNotFoundException ex) { Line 188: throw new IllegalArgumentException( Line 189: String.format("Can't convert argument: '%1$s'. Please check convert class in properties file.", arg) Line 190: ); Line 257: ); Line 258: } Line 259: Line 260: public String getPrefixHelp() { Line 261: StringBuilder help = new StringBuilder("Options:\n"); please be consistent with %n and \n, %n can be \r\n when required. Line 262: for(String arg : getUsageArguments()) { Line 263: ParserArgument argument = this.arguments.get(arg); Line 264: help.append( Line 265: String.format( Line 272: Line 273: return help.toString(); Line 274: } Line 275: Line 276: public static Properties loadProperties(InputStream resource) { not sure this should be static now Line 277: try ( Line 278: Reader is = new InputStreamReader(resource, Charset.forName("UTF-8")); Line 279: ) { Line 280: Properties prop = new Properties(); Line 284: throw new RuntimeException(ioe); Line 285: } Line 286: } Line 287: Line 288: public static Properties loadProperties(Class<?> clazz, String resource) { I see you do not use this anywhere, so remove? Line 289: return loadProperties(clazz.getResourceAsStream(resource)); Line 290: } https://gerrit.ovirt.org/#/c/37886/18/backend/manager/modules/uutils/src/main/resources/org/ovirt/engine/core/uutils/cli/parser/defaults.properties File backend/manager/modules/uutils/src/main/resources/org/ovirt/engine/core/uutils/cli/parser/defaults.properties: Line 6: arg.metavar = STRING Line 7: arg.multivalue = false Line 8: help.underlinetext = Line 9: help.usage.header = Line 10: help.usage.footer = new line? -- To view, visit https://gerrit.ovirt.org/37886 To unsubscribe, visit https://gerrit.ovirt.org/settings Gerrit-MessageType: comment Gerrit-Change-Id: Ie06113c5d56a49e58d557c851f9ff00b9a9ca409 Gerrit-PatchSet: 18 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Ondřej Macháček <machacek.on...@gmail.com> Gerrit-Reviewer: Alon Bar-Lev <alo...@redhat.com> Gerrit-Reviewer: Ondra Machacek <omach...@redhat.com> Gerrit-Reviewer: Ondřej Macháček <machacek.on...@gmail.com> Gerrit-Reviewer: automat...@ovirt.org Gerrit-Reviewer: mooli tayer <mta...@redhat.com> Gerrit-Reviewer: oVirt Jenkins CI Server Gerrit-HasComments: Yes _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches