def main(arglist=None, direct_parser=None): """ Main program """ argget = argparse.ArgumentParser( description= 'tool for testing services against an interoperability profile, version {}' .format(tool_version)) # config argget.add_argument('-c', '--config', type=str, help='config file') # tool argget.add_argument('--desc', type=str, default='No desc', help='sysdescription for identifying logs') argget.add_argument( '--payload', type=str, help= 'mode to validate payloads [Tree, Single, SingleFile, TreeFile] followed by resource/filepath', nargs=2) argget.add_argument('-v', action='store_const', const=True, default=None, help='verbose log output to stdout (parameter-only)') argget.add_argument('--logdir', type=str, default='./logs', help='directory for log files') argget.add_argument( '--debug_logging', action="store_const", const=logging.DEBUG, default=logging.INFO, help= 'Output debug statements to text log, otherwise it only uses INFO (parameter-only)' ) argget.add_argument('--verbose_checks', action="store_const", const=VERBO_NUM, default=logging.INFO, help='Show all checks in logging (parameter-only)') argget.add_argument('--nooemcheck', action='store_const', const=True, default=None, help='Don\'t check OEM items') argget.add_argument('--csv_report', action='store_true', help='print a csv report at the end of the log') # service argget.add_argument('-i', '--ip', type=str, help='ip to test on [host:port]') argget.add_argument('-u', '--user', type=str, help='user for basic auth') argget.add_argument('-p', '--passwd', type=str, help='pass for basic auth') argget.add_argument( '--linklimit', type=str, help= 'Limit the amount of links in collections, formatted TypeName:## TypeName:## ..., default LogEntry:20 ', nargs='*') argget.add_argument( '--sample', type=int, help= 'sample this number of members from large collections for validation; default is to validate all members' ) argget.add_argument('--timeout', type=int, help='requests timeout in seconds') argget.add_argument('--nochkcert', action='store_const', const=True, default=None, help='ignore check for certificate') argget.add_argument('--nossl', action='store_const', const=True, default=None, help='use http instead of https') argget.add_argument('--forceauth', action='store_const', const=True, default=None, help='force authentication on unsecure connections') argget.add_argument('--authtype', type=str, help='authorization type (None|Basic|Session|Token)') argget.add_argument( '--localonly', action='store_const', const=True, default=None, help='only use locally stored schema on your harddrive') argget.add_argument('--preferonline', action='store_const', const=True, default=None, help='use online schema') argget.add_argument('--service', action='store_const', const=True, default=None, help='only use uris within the service') argget.add_argument( '--ca_bundle', type=str, help='path to Certificate Authority bundle file or directory') argget.add_argument('--token', type=str, help='bearer token for authtype Token') argget.add_argument('--http_proxy', type=str, help='URL for the HTTP proxy') argget.add_argument('--https_proxy', type=str, help='URL for the HTTPS proxy') argget.add_argument( '--cache', type=str, help= 'cache mode [Off, Fallback, Prefer] followed by directory to fallback or override problem service JSON payloads', nargs=2) argget.add_argument('--uri_check', action='store_const', const=True, default=None, help='Check for URI if schema supports it') argget.add_argument( '--version_check', type=str, help= 'Change default tool configuration based on the version provided (default use target version)' ) # metadata argget.add_argument('--schemadir', type=str, help='directory for local schema files') argget.add_argument( '--schema_pack', type=str, help= 'Deploy DMTF schema from zip distribution, for use with --localonly (Specify url or type "latest", overwrites current schema)' ) argget.add_argument( '--suffix', type=str, help='suffix of local schema files (for version differences)') # Config information unique to Interop Validator argget.add_argument( 'profile', type=str, default='sample.json', help='interop profile with which to validate service against') argget.add_argument( '--schema', type=str, default=None, help='schema with which to validate interop profile against') argget.add_argument('--warnrecommended', action='store_true', help='warn on recommended instead of pass') # todo: write patches argget.add_argument( '--writecheck', action='store_true', help='(unimplemented) specify to allow WriteRequirement checks') args = argget.parse_args(arglist) # set up config rst.ch.setLevel(args.verbose_checks if not args.v else logging.DEBUG) if direct_parser is not None: try: cdict = rst.convertConfigParserToDict(direct_parser) config, default_list = rst.setConfig(cdict) except Exception as ex: rsvLogger.debug('Exception caught while parsing configuration', exc_info=1) rsvLogger.error('Unable to parse configuration: {}'.format( repr(ex))) return 1, None, 'Config Parser Exception' elif args.config is None and args.ip is None: rsvLogger.info('No ip or config specified.') argget.print_help() return 1, None, 'Config Incomplete' else: try: config, default_list = rst.setByArgparse(args) except Exception as ex: rsvLogger.debug('Exception caught while parsing configuration', exc_info=1) rsvLogger.error('Unable to parse configuration: {}'.format( repr(ex))) return 1, None, 'Config Exception' # Set interop config items config['WarnRecommended'] = rst.config.get('warnrecommended', args.warnrecommended) commonInterop.config['WarnRecommended'] = config['WarnRecommended'] config['WriteCheck'] = rst.config.get('writecheck', args.writecheck) commonInterop.config['WriteCheck'] = config['WriteCheck'] config['profile'] = args.profile config['schema'] = args.schema # Setup schema store if config['schema_pack'] is not None and config['schema_pack'] != '': httpprox = config['httpproxy'] httpsprox = config['httpsproxy'] proxies = {} proxies['http'] = httpprox if httpprox != "" else None proxies['https'] = httpsprox if httpsprox != "" else None setup_schema_pack(config['schema_pack'], config['metadatafilepath'], proxies, config['timeout']) # Logging config logpath = config['logpath'] startTick = datetime.now() if not os.path.isdir(logpath): os.makedirs(logpath) fmt = logging.Formatter('%(levelname)s - %(message)s') fh = logging.FileHandler( datetime.strftime( startTick, os.path.join(logpath, "InteropLog_%m_%d_%Y_%H%M%S.txt"))) fh.setLevel(min(args.debug_logging, args.verbose_checks)) fh.setFormatter(fmt) rsvLogger.addHandler(fh) # Then start service rsvLogger.info( "Redfish Interop Validator, version {}".format(tool_version)) try: currentService = rst.startService(config, default_list) except Exception as ex: rsvLogger.debug('Exception caught while creating Service', exc_info=1) rsvLogger.error("Service could not be started: {}".format(ex)) return 1, None, 'Service Exception' metadata = currentService.metadata sysDescription, ConfigURI = (config['systeminfo'], config['targetip']) # start printing rsvLogger.info('ConfigURI: ' + ConfigURI) rsvLogger.info('System Info: ' + sysDescription) rsvLogger.info('Profile:' + config['profile']) rsvLogger.info('\n'.join([ '{}: {}'.format(x, config[x]) for x in sorted( list(config.keys() - set(['systeminfo', 'targetip', 'password', 'description']))) if config[x] not in ['', None] ])) rsvLogger.info('Start time: ' + startTick.strftime('%x - %X')) # Interop Profile handling profile = schema = None success = True with open(args.profile) as f: profile = json.loads(f.read()) if args.schema is not None: with open(args.schema) as f: schema = json.loads(f.read()) success = checkProfileAgainstSchema(profile, schema) if not success: rsvLogger.info("Profile did not conform to the given schema...") return 1 # Combine profiles profiles = getProfiles(profile, './') rsvLogger.info('\nProfile Hashes: ') for profile in profiles: profileName = profile.get('ProfileName') rsvLogger.info('profile: {}, dict md5 hash: {}'.format( profileName, hashProfile(profile))) # Start main status_code = 1 jsonData = None if rst.config.get('payloadmode') not in [ 'Tree', 'Single', 'SingleFile', 'TreeFile', 'Default' ]: rst.config['payloadmode'] = 'Default' rsvLogger.error('PayloadMode or path invalid, using Default behavior') if 'File' in rst.config.get('payloadmode'): if rst.config.get('payloadfilepath') is not None and os.path.isfile( rst.config.get('payloadfilepath')): with open(rst.config.get('payloadfilepath')) as f: jsonData = json.load(f) f.close() else: rsvLogger.error('File not found {}'.format( rst.config.get('payloadfilepath'))) return 1 results = None for profile in profiles: profileName = profile.get('ProfileName') if 'Single' in rst.config.get('payloadmode'): success, counts, resultsNew, xlinks, topobj = validateSingleURI( rst.config.get('payloadfilepath'), profile, 'Target', expectedJson=jsonData) elif 'Tree' in rst.config.get('payloadmode'): success, counts, resultsNew, xlinks, topobj = validateURITree( rst.config.get('payloadfilepath'), 'Target', profile, expectedJson=jsonData) else: success, counts, resultsNew, xlinks, topobj = validateURITree( '/redfish/v1/', 'ServiceRoot', profile, expectedJson=jsonData) if results is None: results = resultsNew else: for item in resultsNew: innerCounts = results[item]['counts'] innerCounts.update(resultsNew[item]['counts']) if item in results: for x in resultsNew[item]['messages']: x.name = profileName + ' -- ' + x.name results[item]['messages'].extend( resultsNew[item]['messages']) #resultsNew = {profileName+key: resultsNew[key] for key in resultsNew if key in results} #results.update(resultsNew) finalCounts = Counter() nowTick = datetime.now() rsvLogger.info('Elapsed time: {}'.format( str(nowTick - startTick).rsplit('.', 1)[0])) finalCounts.update(metadata.get_counter()) for item in results: innerCounts = results[item]['counts'] # detect if there are error messages for this resource, but no failure counts; if so, add one to the innerCounts counters_all_pass = True for countType in sorted(innerCounts.keys()): if innerCounts.get(countType) == 0: continue if any(x in countType for x in ['problem', 'fail', 'bad', 'exception']): counters_all_pass = False if 'fail' in countType or 'exception' in countType: rsvLogger.error('{} {} errors in {}'.format( innerCounts[countType], countType, results[item]['uri'])) innerCounts[countType] += 0 error_messages_present = False if results[item]['errors'] is not None and len( results[item]['errors']) > 0: error_messages_present = True if results[item]['warns'] is not None and len( results[item]['warns']) > 0: innerCounts['warningPresent'] = 1 if counters_all_pass and error_messages_present: innerCounts['failErrorPresent'] = 1 finalCounts.update(results[item]['counts']) fails = 0 for key in [key for key in finalCounts.keys()]: if finalCounts[key] == 0: del finalCounts[key] continue if any(x in key for x in ['problem', 'fail', 'bad', 'exception']): fails += finalCounts[key] rsvLogger.info('Rendering HTML Log') html_str = renderHtml(results, finalCounts, tool_version, startTick, nowTick, args.csv_report) lastResultsPage = datetime.strftime( startTick, os.path.join(logpath, "InteropHtmlLog%m_%d_%Y_%H%M%S.html")) writeHtml(html_str, lastResultsPage) success = success and not (fails > 0) rsvLogger.info(finalCounts) if not success: rsvLogger.info( "Validation has failed: {} problems found".format(fails)) else: rsvLogger.info("Validation has succeeded.") status_code = 0 return status_code, lastResultsPage, 'Validation done'
def main(arglist=None, direct_parser=None): """ Main program """ argget = argparse.ArgumentParser( description= 'tool to test a service against a collection of Schema, version {}'. format(tool_version)) # config argget.add_argument('-c', '--config', type=str, help='config file') # tool argget.add_argument('--desc', type=str, default='No desc', help='sysdescription for identifying logs') argget.add_argument( '--payload', type=str, help= 'mode to validate payloads [Tree, Single, SingleFile, TreeFile] followed by resource/filepath', nargs=2) argget.add_argument('-v', action='store_const', const=True, default=None, help='verbose log output to stdout (parameter-only)') argget.add_argument('--logdir', type=str, default='./logs', help='directory for log files') argget.add_argument( '--debug_logging', action="store_const", const=True, default=None, help= 'Output debug statements to text log, otherwise it only uses INFO (parameter-only)' ) argget.add_argument('--verbose_checks', action="store_const", const=True, default=None, help='Show all checks in logging (parameter-only)') argget.add_argument('--nooemcheck', action='store_const', const=True, default=None, help='Don\'t check OEM items') argget.add_argument('--csv_report', action='store_true', help='print a csv report at the end of the log') # service argget.add_argument('-i', '--ip', type=str, help='ip to test on [host:port]') argget.add_argument('-u', '--user', type=str, help='user for basic auth') argget.add_argument('-p', '--passwd', type=str, help='pass for basic auth') argget.add_argument( '--linklimit', type=str, help= 'Limit the amount of links in collections, formatted TypeName:## TypeName:## ..., default LogEntry:20 ', nargs='*') argget.add_argument( '--sample', type=int, help= 'sample this number of members from large collections for validation; default is to validate all members' ) argget.add_argument('--timeout', type=int, help='requests timeout in seconds') argget.add_argument('--nochkcert', action='store_const', const=True, default=None, help='ignore check for certificate') argget.add_argument('--nossl', action='store_const', const=True, default=None, help='use http instead of https') argget.add_argument('--forceauth', action='store_const', const=True, default=None, help='force authentication on unsecure connections') argget.add_argument('--authtype', type=str, help='authorization type (None|Basic|Session|Token)') argget.add_argument( '--localonly', action='store_const', const=True, default=None, help='only use locally stored schema on your harddrive') argget.add_argument('--preferonline', action='store_const', const=True, default=None, help='use online schema') argget.add_argument('--service', action='store_const', const=True, default=None, help='only use uris within the service') argget.add_argument( '--ca_bundle', type=str, help='path to Certificate Authority bundle file or directory') argget.add_argument('--token', type=str, help='bearer token for authtype Token') argget.add_argument('--http_proxy', type=str, help='URL for the HTTP proxy') argget.add_argument('--https_proxy', type=str, help='URL for the HTTPS proxy') argget.add_argument( '--cache', type=str, help= 'cache mode [Off, Fallback, Prefer] followed by directory to fallback or override problem service JSON payloads', nargs=2) argget.add_argument('--uri_check', action='store_const', const=True, default=None, help='Check for URI if schema supports it') argget.add_argument( '--version_check', type=str, help= 'Change default tool configuration based on the version provided (default use target version)' ) # metadata argget.add_argument('--schemadir', type=str, help='directory for local schema files') argget.add_argument( '--schema_pack', type=str, help= 'Deploy DMTF schema from zip distribution, for use with --localonly (Specify url or type "latest", overwrites current schema)' ) argget.add_argument( '--suffix', type=str, help='suffix of local schema files (for version differences)') args = argget.parse_args(arglist) # set up config rst.ch.setLevel(VERBO_NUM if args.verbose_checks else logging. INFO if not args.v else logging.DEBUG) if direct_parser is not None: try: cdict = rst.convertConfigParserToDict(direct_parser) config, default_list = rst.setConfig(cdict) except Exception as ex: rsvLogger.debug('Exception caught while parsing configuration', exc_info=1) rsvLogger.error('Unable to parse configuration: {}'.format( repr(ex))) return 1, None, 'Config Parser Exception' elif args.config is None and args.ip is None: rsvLogger.info('No ip or config specified.') argget.print_help() return 1, None, 'Config Incomplete' else: try: config, default_list = rst.setByArgparse(args) except Exception as ex: rsvLogger.debug('Exception caught while parsing configuration', exc_info=1) rsvLogger.error('Unable to parse configuration: {}'.format( repr(ex))) return 1, None, 'Config Exception' # Setup schema store if config['schema_pack'] is not None and config['schema_pack'] != '': httpprox = config['httpproxy'] httpsprox = config['httpsproxy'] proxies = {} proxies['http'] = httpprox if httpprox != "" else None proxies['https'] = httpsprox if httpsprox != "" else None setup_schema_pack(config['schema_pack'], config['metadatafilepath'], proxies, config['timeout']) # Logging config logpath = config['logpath'] schemadir = config['metadatafilepath'] startTick = datetime.now() if not os.path.isdir(logpath): os.makedirs(logpath) if not os.path.isdir(schemadir) and not config['preferonline']: rsvLogger.info( 'First run suggested to create and own local schema files, please download manually or use --schema_pack latest' ) rsvLogger.info( 'Alternatively, use the option --prefer_online to skip local schema file checks' ) rsvLogger.info( 'The tool will, by default, attempt to download and store XML files to relieve traffic from DMTF/service' ) elif config['preferonline']: rsvLogger.info( 'Using option PreferOnline, retrieving solely from online sources may be slow...' ) fmt = logging.Formatter('%(levelname)s - %(message)s') fh = logging.FileHandler( datetime.strftime( startTick, os.path.join(logpath, "ConformanceLog_%m_%d_%Y_%H%M%S.txt"))) fh.setLevel( min(logging.INFO if not args.debug_logging else logging.DEBUG, logging.INFO if not args.verbose_checks else VERBO_NUM)) fh.setFormatter(fmt) rsvLogger.addHandler(fh) # Then start service rsvLogger.info( "Redfish Service Validator, version {}".format(tool_version)) try: currentService = rst.startService(config, default_list) except Exception as ex: rsvLogger.debug('Exception caught while creating Service', exc_info=1) rsvLogger.error("Service could not be started: {}".format(ex)) return 1, None, 'Service Exception' metadata = currentService.metadata sysDescription, ConfigURI = (config['systeminfo'], config['targetip']) # start printing config details, remove redundant/private info from print rsvLogger.info('ConfigURI: ' + ConfigURI) rsvLogger.info('System Info: ' + sysDescription) rsvLogger.info('\n'.join([ '{}: {}'.format(x, config[x]) for x in sorted( list(config.keys() - set(['systeminfo', 'targetip', 'password', 'description']))) if config[x] not in ['', None] ])) rsvLogger.info('Start time: ' + startTick.strftime('%x - %X')) # Start main status_code = 1 jsonData = None # Determine runner pmode, ppath = config.get('payloadmode', 'Default'), config.get('payloadfilepath') if pmode not in ['Tree', 'Single', 'SingleFile', 'TreeFile', 'Default']: pmode = 'Default' rsvLogger.error('PayloadMode or path invalid, using Default behavior') if 'File' in pmode: if ppath is not None and os.path.isfile(ppath): with open(ppath) as f: jsonData = json.load(f) f.close() else: rsvLogger.error('File not found: {}'.format(ppath)) return 1, None, 'File not found: {}'.format(ppath) try: if 'Single' in pmode: success, counts, results, xlinks, topobj = validateSingleURI( ppath, 'Target', expectedJson=jsonData) elif 'Tree' in pmode: success, counts, results, xlinks, topobj = validateURITree( ppath, 'Target', expectedJson=jsonData) else: success, counts, results, xlinks, topobj = validateURITree( '/redfish/v1/', 'ServiceRoot', expectedJson=jsonData) except AuthenticationError as e: # log authentication error and terminate program rsvLogger.error('{}'.format(e)) return 1, None, 'Failed to authenticate with the service' currentService.close() rsvLogger.debug('Metadata: Namespaces referenced in service: {}'.format( metadata.get_service_namespaces())) rsvLogger.debug('Metadata: Namespaces missing from $metadata: {}'.format( metadata.get_missing_namespaces())) finalCounts = Counter() nowTick = datetime.now() rsvLogger.info('Elapsed time: {}'.format( str(nowTick - startTick).rsplit('.', 1)[0])) error_lines, finalCounts = count_errors(results) for line in error_lines: rsvLogger.error(line) finalCounts.update(metadata.get_counter()) fails = 0 for key in [key for key in finalCounts.keys()]: if finalCounts[key] == 0: del finalCounts[key] continue if any(x in key for x in ['problem', 'fail', 'bad', 'exception']): fails += finalCounts[key] html_str = renderHtml(results, tool_version, startTick, nowTick, currentService, args.csv_report) lastResultsPage = datetime.strftime( startTick, os.path.join(logpath, "ConformanceHtmlLog_%m_%d_%Y_%H%M%S.html")) writeHtml(html_str, lastResultsPage) success = success and not (fails > 0) rsvLogger.info(finalCounts) # dump cache info to debug log rsvLogger.debug('getSchemaDetails() -> {}'.format( rst.rfSchema.getSchemaDetails.cache_info())) rsvLogger.debug('callResourceURI() -> {}'.format( currentService.callResourceURI.cache_info())) if not success: rsvLogger.error( "Validation has failed: {} problems found".format(fails)) else: rsvLogger.info("Validation has succeeded.") status_code = 0 return status_code, lastResultsPage, 'Validation done'