예제 #1
0
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'
예제 #2
0
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'