def validateSingleURI(URI, profile, uriName='', expectedType=None, expectedSchema=None, expectedJson=None, parent=None):
    """
    Validates a single URI that is given, returning its ResourceObject, counts and links
    """
    # rs-assertion: 9.4.1
    # Initial startup here
    counts = Counter()
    results, messages = {}, []

    ehandler, whandler = create_logging_capture(my_logger)

    results[uriName] = {'uri': URI,
                        'success': False,
                        'counts': counts,
                        'messages': messages,
                        'errors': '',
                        'warns': '',
                        'rtime': '',
                        'context': '',
                        'fulltype': '',
                        'rcode': 0,
                        'payload': {}}

    # check for @odata mandatory stuff
    # check for version numbering problems
    # check id if it's the same as URI
    # check @odata.context instead of local.  Realize that @odata is NOT a "property"

    # Attempt to get a list of properties
    if URI is None:
        if parent is not None:
            parentURI = parent.uri
        else:
            parentURI = 'MissingParent'
        URI = parentURI + '/Missing URI Link'
        my_logger.warning('Tool appears to be missing vital URI information, replacing URI w/: {}'.format(URI))
    # Generate dictionary of property info
    try:
        if expectedJson is None:
            success, jsondata, status, rtime = traverseInterop.callResourceURI(URI)
            results[uriName]['payload'] = jsondata
        else:
            results[uriName]['payload'] = expectedJson

        # # verify basic odata strings
        # if results[uriName]['payload'] is not None:
        #     successPayload, odataMessages = traverseInterop.ResourceObj.checkPayloadConformance(results[uriName]['payload'], URI)
        #     messages.extend(odataMessages)

        propResourceObj = traverseInterop.createResourceObject(
            uriName, URI, expectedJson, expectedType, expectedSchema, parent)
        if not propResourceObj:
            counts['problemResource'] += 1
            results[uriName]['warns'], results[uriName]['errors'] = get_my_capture(my_logger, whandler), get_my_capture(my_logger, ehandler)
            return False, counts, results, None, None
    except traverseInterop.AuthenticationError as e:
        raise  # re-raise exception
    except Exception as e:
        my_logger.debug('Exception caught while creating ResourceObj', exc_info=1)
        my_logger.error('Unable to gather property info for URI {}: {}'
                        .format(URI, repr(e)))
        counts['exceptionResource'] += 1
        results[uriName]['warns'], results[uriName]['errors'] = get_my_capture(my_logger, whandler), get_my_capture(my_logger, ehandler)
        return False, counts, results, None, None

    counts['passGet'] += 1

    # verify odata type
    objRes = profile.get('Resources')

    my_logger.verbose1("*** {}, {}".format(uriName, URI))
    uriName, SchemaFullType, jsondata = uriName, uriName, propResourceObj.jsondata
    SchemaType = getType(jsondata.get('@odata.type', 'NoType'))
    if SchemaType not in objRes:
        # my_logger.info('\nNo Such Type in sample {} {}, skipping'.format(URI, SchemaType))
        # Get all links available
        links = getURIsInProperty(jsondata, uriName)
        return True, counts, results, links, propResourceObj

    # my_logger.info("\n*** %s", URI)
    # my_logger.debug("\n*** %s, %s, %s", expectedType, expectedSchema is not None, expectedJson is not None)

    # verify odata_id properly resolves to its parent if holding fragment
    odata_id = propResourceObj.jsondata.get('@odata.id', '')
    if '#' in odata_id:
        if parent is not None:
            payload_resolve = traverseInterop.navigateJsonFragment(parent.jsondata, URI)
            if payload_resolve is None:
                my_logger.error('@odata.id of ReferenceableMember does not contain a valid JSON pointer for this payload: {}'.format(odata_id))
                counts['badOdataIdResolution'] += 1
            elif payload_resolve != propResourceObj.jsondata:
                my_logger.error('@odata.id of ReferenceableMember does not point to the correct object: {}'.format(odata_id))
                counts['badOdataIdResolution'] += 1
        else:
            my_logger.warning('No parent found with which to test @odata.id of ReferenceableMember')

    # if not successPayload:
    #     counts['failPayloadError'] += 1
    #     my_logger.error(str(URI) + ': payload error, @odata property non-conformant',)

    # if URI was sampled, get the notation text from traverseInterop.uri_sample_map
    sample_string = traverseInterop.uri_sample_map.get(URI)
    sample_string = sample_string + ', ' if sample_string is not None else ''

    results[uriName]['uri'] = (str(URI))
    results[uriName]['samplemapped'] = (str(sample_string))
    results[uriName]['rtime'] = propResourceObj.rtime
    results[uriName]['rcode'] = propResourceObj.status
    results[uriName]['payload'] = propResourceObj.jsondata
    results[uriName]['context'] = propResourceObj.context
    results[uriName]['fulltype'] = propResourceObj.typename
    results[uriName]['success'] = True

    my_logger.info('\n')
    my_logger.info("*** %s, %s", URI, SchemaType)
    my_logger.debug("*** %s, %s, %s", expectedType, expectedSchema is not None, expectedJson is not None)
    my_logger.info("\t Type (%s), GET SUCCESS (time: %s)", propResourceObj.typename, propResourceObj.rtime)
    objRes = objRes.get(SchemaType)
    try:
        propMessages, propCounts = interop.validateInteropResource(propResourceObj, objRes, jsondata)
        messages = messages.extend(propMessages)
        counts.update(propCounts)
        my_logger.info('{} of {} tests passed.'.format(counts['pass'] + counts['warn'], counts['totaltests']))
    except Exception:
        my_logger.exception("Something went wrong")
        my_logger.error(
            'Could not finish validation check on this payload')
        counts['exceptionProfilePayload'] += 1
    my_logger.info('%s, %s\n', SchemaFullType, counts)

    # Get all links available
    links = getURIsInProperty(propResourceObj.jsondata, uriName)

    results[uriName]['warns'], results[uriName]['errors'] = get_my_capture(my_logger, whandler), get_my_capture(my_logger, ehandler)

    pass_val = len(results[uriName]['errors']) == 0
    for key in counts:
        if any(x in key for x in ['problem', 'fail', 'bad', 'exception']):
            pass_val = False
            break
    my_logger.info("\t {}".format('PASS' if pass_val else' FAIL...'))

    return True, counts, results, links, propResourceObj
def validateURITree(URI, profile, uriName, expectedType=None, expectedSchema=None, expectedJson=None, check_oem=True):
    """name
    Validates a Tree of URIs, traversing from the first given
    """
    allLinks = set()
    allLinks.add(URI)
    refLinks = list()

    # Resource level validation
    rcounts = Counter()
    rerror = StringIO()
    rmessages = []
    r_exists = {}

    resource_info = dict(profile.get('Resources'))

    # Validate top URI
    validateSuccess, counts, results, links, thisobj = \
        validateSingleURI(URI, profile, uriName, expectedType, expectedSchema, expectedJson)

    # parent first, then child execution
    # do top level root first, then do each child root, then their children...
    # hold refs for last (less recursion)
    if validateSuccess:
        serviceVersion = profile.get("Protocol")
        if serviceVersion is not None and uriName == 'ServiceRoot':
            serviceVersion = serviceVersion.get('MinVersion', '1.0.0')
            msg, m_success = interop.validateMinVersion(thisobj.jsondata.get("RedfishVersion", "0"), serviceVersion)
            rmessages.append(msg)

        currentLinks = [(l, links[l], thisobj) for l in links]
        # todo : churning a lot of links, causing possible slowdown even with set checks
        while len(currentLinks) > 0:
            newLinks = list()
            for linkName, link, parent in currentLinks:
                assert(isinstance(link, str))
                if link is None or link.rstrip('/') in allLinks:
                    continue
            
                if '#' in link:
                    # if link.rsplit('#', 1)[0] not in allLinks:
                    #     refLinks.append((linkName, link, parent))
                    continue

                if 'Oem' in linkName and not check_oem:
                    my_logger.info('Skipping Oem Link')
                    continue

                if refLinks is not currentLinks and ('Links' in linkName.split('.') or 'RelatedItem' in linkName.split('.') or 'Redundancy' in linkName.split('.')):
                    refLinks.append((linkName, link, parent))
                    continue

                # if autoExpand and linkType is not None:
                #     linkSuccess, linkCounts, linkResults, innerLinks, linkobj = \
                #         validateSingleURI(linkURI, profile, linkURI, linkType, linkSchema, innerJson, parent=parent)
                else:
                    linkSuccess, linkCounts, linkResults, innerLinks, linkobj = \
                        validateSingleURI(link, profile, linkName, parent=parent)

                allLinks.add(link.rstrip('/'))

                if not linkSuccess:
                    continue

                innerLinksTuple = [(l, innerLinks[l], linkobj) for l in innerLinks]
                newLinks.extend(innerLinksTuple)
                results.update(linkResults)
                SchemaType = getType(linkobj.jsondata.get('@odata.type', 'NoType'))

                r_exists[SchemaType] = True

            if refLinks is not currentLinks and len(newLinks) == 0 and len(refLinks) > 0:
                currentLinks = refLinks
            else:
                currentLinks = newLinks

    # interop service level checks
    finalResults = {}
    my_logger.info('Service Level Checks')
    if URI not in ["/redfish/v1", "/redfish/v1/"]:
        resultEnum = interop.sEnum.WARN
        my_logger.info("We are not validating root, warn only")
    else:
        resultEnum = interop.sEnum.FAIL

    # for item in resource_info:
    #     # thisobj does not exist if we didn't find the first resource
    #     if thisobj and item == getType(thisobj.typeobj.fulltype):
    #         continue

    #     exists = r_exists.get(item, False)

    #     if "ConditionalRequirements" in resource_info[item]:
    #         innerList = resource_info[item]["ConditionalRequirements"]
    #         for condreq in innerList:
    #             if interop.checkConditionalRequirementResourceLevel(r_exists, condreq, item):
    #                 my_logger.info(
    #                     'Service Conditional for {} applies'.format(item))
    #                 req = condreq.get("ReadRequirement", "Mandatory")
    #                 rmessages.append(
    #                     interop.msgInterop(item + '.Conditional.ReadRequirement',
    #                                              req, 'Must Exist' if req == "Mandatory" else 'Any', 'DNE' if not exists else 'Exists',
    #                                              resultEnum if not exists and req == "Mandatory" else interop.sEnum.PASS))
    #             else:
    #                 my_logger.info(
    #                     'Service Conditional for {} does not apply'.format(item))

    #     req = resource_info[item].get("ReadRequirement", "Mandatory")

    #     if not exists:
    #         rmessages.append(
    #             interop.msgInterop(item + '.ReadRequirement', req,
    #                                      'Must Exist' if req == "Mandatory" else 'Any', 'DNE',
    #                                      resultEnum if req == "Mandatory" else interop.sEnum.PASS))
    #     else:
    #         rmessages.append(
    #             interop.msgInterop(item + '.ReadRequirement', req,
    #                                      'Must Exist' if req == "Mandatory" else 'Any', 'Exists',
    #                                      interop.sEnum.PASS))

    for item in rmessages:
        if item.success == interop.sEnum.WARN:
            rcounts['warn'] += 1
        elif item.success == interop.sEnum.PASS:
            rcounts['pass'] += 1
        elif item.success == interop.sEnum.FAIL:
            rcounts['fail.{}'.format(item.name)] += 1

    finalResults['n/a'] = {'uri': "Service Level Requirements", 'success': rcounts.get('fail', 0) == 0,
                           'counts': rcounts,
                           'messages': rmessages, 'errors': rerror.getvalue(), 'warns': '',
                           'rtime': '', 'context': '', 'fulltype': ''}
    finalResults.update(results)
    rerror.close()

    return validateSuccess, counts, finalResults, refLinks, thisobj
Esempio n. 3
0
def checkConditionalRequirement(propResourceObj, profile_entry,
                                rf_payload_tuple):
    """
    Returns boolean if profile_entry's conditional is true or false
    """
    my_logger.debug('Evaluating conditionalRequirements')
    if "SubordinateToResource" in profile_entry:
        isSubordinate = False
        # iterate through parents via resourceObj
        # list must be reversed to work backwards
        resourceParent = propResourceObj.parent
        for expectedParent in reversed(profile_entry["SubordinateToResource"]):
            if resourceParent is not None:
                parentType = getType(
                    resourceParent.jsondata.get('@odata.type', 'NoType'))
                isSubordinate = parentType == expectedParent
                my_logger.debug('\tsubordinance ' + str(parentType) + ' ' +
                                str(isSubordinate))
                resourceParent = resourceParent.parent
            else:
                my_logger.debug('no parent')
                isSubordinate = False
        return isSubordinate
    elif "CompareProperty" in profile_entry:
        # find property in json payload by working backwards thru objects
        # rf_payload tuple is designed just for this piece, since there is
        # no parent in dictionaries
        if profile_entry["CompareProperty"][0] == '/':
            comparePropNames = profile_entry["CompareProperty"].split('/')[1:]
        else:
            comparePropNames = [profile_entry["CompareProperty"]]
        if "CompareType" not in profile_entry:
            my_logger.error(
                "Invalid Profile - CompareType is required for CompareProperty but not found"
            )
            raise ValueError('CompareType missing with CompareProperty')
        if "CompareValues" not in profile_entry and profile_entry[
                'CompareType'] not in ['Absent', 'Present']:
            my_logger.error(
                "Invalid Profile - CompareValues is required for CompareProperty but not found"
            )
            raise ValueError('CompareValues missing with CompareProperty')
        if "CompareValues" in profile_entry and profile_entry[
                'CompareType'] in ['Absent', 'Present']:
            my_logger.warning(
                "Invalid Profile - CompareValues is not required for CompareProperty Absent or Present "
            )

        _, (rf_payload_item, _) = rf_payload_tuple

        compareProp = rf_payload_item.get(comparePropNames[0], 'DNE')
        if (compareProp != 'DNE') and len(comparePropNames) > 1:
            for comparePropName in comparePropNames[1:]:
                compareProp = compareProp.get(comparePropName, 'DNE')
                if compareProp == 'DNE':
                    break
        # compatability with old version, deprecate with versioning
        compareType = profile_entry.get("CompareType",
                                        profile_entry.get("Comparison"))
        return checkComparison(compareProp, compareType,
                               profile_entry.get("CompareValues", []))[1]
    else:
        my_logger.error("Invalid Profile - No conditional given")
        raise ValueError('No conditional given for Comparison')
Esempio n. 4
0
def renderHtml(results, finalCounts, tool_version, startTick, nowTick, config):
    # Render html
    config_str = ', '.join(
        sorted(
            list(config.keys() -
                 set(['systeminfo', 'targetip', 'password', 'description']))))
    sysDescription, ConfigURI = (config['description'], config['ip'])
    logpath = config['logdir']

    # wrap html
    htmlPage = ''
    htmlStrTop = '<head><title>Conformance Test Summary</title>\
            <style>\
            .column {\
                float: left;\
                width: 45%;\
            }\
            .pass {background-color:#99EE99}\
            .fail {background-color:#EE9999}\
            .warn {background-color:#EEEE99}\
            .bluebg {background-color:#BDD6EE}\
            .button {padding: 12px; display: inline-block}\
            .center {text-align:center;}\
            .log {text-align:left; white-space:pre-wrap; word-wrap:break-word; font-size:smaller}\
            .title {background-color:#DDDDDD; border: 1pt solid; font-height: 30px; padding: 8px}\
            .titlesub {padding: 8px}\
            .titlerow {border: 2pt solid}\
            .results {transition: visibility 0s, opacity 0.5s linear; display: none; opacity: 0}\
            .resultsShow {display: block; opacity: 1}\
            body {background-color:lightgrey; border: 1pt solid; text-align:center; margin-left:auto; margin-right:auto}\
            th {text-align:center; background-color:beige; border: 1pt solid}\
            td {text-align:left; background-color:white; border: 1pt solid; word-wrap:break-word;}\
            table {width:90%; margin: 0px auto; table-layout:fixed;}\
            .titletable {width:100%}\
            </style>\
            </head>'

    htmlStrBodyHeader = ''
    # Logo and logname
    infos = [wrapTag('##### Redfish Conformance Test Report #####', 'h2')]
    infos.append(
        wrapTag(
            '<img align="center" alt="DMTF Redfish Logo" height="203" width="288"'
            'src="data:image/gif;base64,' + logo.logo + '">', 'h4'))
    infos.append(
        '<h4><a href="https://github.com/DMTF/Redfish-Interop-Validator">'
        'https://github.com/DMTF/Redfish-Interop-Validator</a></h4>')
    infos.append('Tool Version: {}'.format(tool_version))
    infos.append(startTick.strftime('%c'))
    infos.append('(Run time: {})'.format(
        str(nowTick - startTick).rsplit('.', 1)[0]))
    infos.append(
        '<h4>This tool is provided and maintained by the DMTF. '
        'For feedback, please open issues<br>in the tool\'s Github repository: '
        '<a href="https://github.com/DMTF/Redfish-Interop-Validator/issues">'
        'https://github.com/DMTF/Redfish-Interop-Validator/issues</a></h4>')

    htmlStrBodyHeader += tag.tr(tag.th(infoBlock(infos)))

    infos = {'System': ConfigURI, 'Description': sysDescription}
    htmlStrBodyHeader += tag.tr(tag.th(infoBlock(infos)))

    infos = {'Profile': config['profile'], 'Schema': config['schema']}
    htmlStrBodyHeader += tag.tr(tag.th(infoBlock(infos)))

    infos = {
        x: config[x]
        for x in config if x not in [
            'systeminfo', 'targetip', 'password', 'description', 'profile',
            'schema'
        ]
    }
    block = tag.tr(tag.th(infoBlock(infos, '|||')))
    for num, block in enumerate(block.split('|||'), 1):
        sep = '<br/>' if num % 4 == 0 else ',&ensp;'
        sep = '' if num == len(infos) else sep
        htmlStrBodyHeader += block + sep

    infos_left, infos_right = dict(), dict()
    for key in sorted(finalCounts.keys()):
        if finalCounts.get(key) == 0:
            continue
        if len(infos_left) <= len(infos_right):
            infos_left[key] = finalCounts[key]
        else:
            infos_right[key] = finalCounts[key]

    htmlStrCounts = (tag.div(infoBlock(infos_left), 'class=\'column log\'') +
                     tag.div(infoBlock(infos_right), 'class=\'column log\''))

    htmlStrBodyHeader += tag.tr(tag.td(htmlStrCounts))

    htmlStrTotal = '</div><div class="button warn" onClick="arr = document.getElementsByClassName(\'results\'); for (var i = 0; i < arr.length; i++){arr[i].className = \'results resultsShow\'};">Expand All</div>'
    htmlStrTotal += '</div><div class="button fail" onClick="arr = document.getElementsByClassName(\'results\'); for (var i = 0; i < arr.length; i++){arr[i].className = \'results\'};">Collapse All</div>'

    htmlStrBodyHeader += tag.tr(tag.td(htmlStrTotal))

    for cnt, item in enumerate(results):
        entry = []
        val = results[item]
        rtime = '(response time: {})'.format(val['rtime'])

        if len(val['messages']) == 0 and len(val['errors']) == 0:
            continue

        # uri block
        prop_type = val['fulltype']
        if prop_type is not None:
            namespace = getNamespace(prop_type)
            type_name = getType(prop_type)

        infos_a = [
            str(val.get(x)) for x in ['uri', 'samplemapped']
            if val.get(x) not in ['', None]
        ]
        infos_a.append(rtime)
        infos_a.append(type_name)
        uriTag = tag.tr(
            tag.th(infoBlock(infos_a, '&ensp;'), 'class="titlerow bluebg"'))
        entry.append(uriTag)

        # info block
        infos_b = [
            str(val.get(x)) for x in ['uri'] if val.get(x) not in ['', None]
        ]
        infos_b.append(rtime)
        infos_b.append(
            tag.div(
                'Show Results',
                attr=
                'class="button warn" onClick="document.getElementById(\'resNum{}\').classList.toggle(\'resultsShow\');"'
                .format(cnt)))
        buttonTag = tag.td(infoBlock(infos_b),
                           'class="title" style="width:30%"')

        infos_content = [
            str(val.get(x)) for x in ['context', 'origin', 'fulltype']
        ]
        infos_c = {
            y: x
            for x, y in zip(infos_content,
                            ['Context', 'File Origin', 'Resource Type'])
        }
        infosTag = tag.td(infoBlock(infos_c),
                          'class="titlesub log" style="width:40%"')

        success = val['success']
        if success:
            getTag = tag.td('GET Success', 'class="pass"')
        else:
            getTag = tag.td('GET Failure', 'class="fail"')

        countsTag = tag.td(
            infoBlock(val['counts'], split='', ffunc=applyInfoSuccessColor),
            'class="log"')

        rhead = ''.join([buttonTag, infosTag, getTag, countsTag])
        for x in [('tr', ), ('table', 'class=titletable'),
                  ('td', 'class=titlerow'), ('tr')]:
            rhead = wrapTag(''.join(rhead), *x)
        entry.append(rhead)

        # actual table
        rows = [(str(i.name), str(i.entry), str(i.expected), str(i.actual),
                 str(i.success.value)) for i in val['messages']]
        titles = ['Property Name', 'Value', 'Expected', 'Actual', 'Result']
        widths = ['15', '30', '30', '10', '15']
        tableHeader = tableBlock(rows, titles, widths, ffunc=applySuccessColor)

        #    lets wrap table and errors and warns into one single column table
        tableHeader = tag.tr(tag.td((tableHeader)))

        # warns and errors
        errors = val['errors']
        if len(errors) == 0:
            errors = 'No errors'
        infos = errors.split('\n')
        errorTags = tag.tr(tag.td(infoBlock(infos), 'class="fail log"'))

        warns = val['warns']
        if len(warns) == 0:
            warns = 'No warns'
        infos = warns.split('\n')
        warnTags = tag.tr(tag.td(infoBlock(infos), 'class="warn log"'))

        tableHeader += errorTags
        tableHeader += warnTags
        tableHeader = tag.table(tableHeader)
        tableHeader = tag.td(tableHeader,
                             'class="results" id=\'resNum{}\''.format(cnt))

        entry.append(tableHeader)

        # append
        htmlPage += ''.join([tag.tr(x) for x in entry])

    return wrapTag(
        wrapTag(htmlStrTop + wrapTag(htmlStrBodyHeader + htmlPage, 'table'),
                'body'), 'html')
Esempio n. 5
0
def checkComparison(val, compareType, target):
    """
    Validate a given comparison option, given a value and a target set
    """
    my_logger.verbose1('Testing a comparison \n\t' +
                       str((val, compareType, target)))
    vallist = val if isinstance(val, list) else [val]
    paramPass = False
    if compareType is None:
        my_logger.error('CompareType not available in payload')
    if compareType == "AnyOf":
        for item in vallist:
            paramPass = item in target
            if paramPass:
                break
            else:
                continue

    if compareType == "AllOf":
        alltarget = set()
        for item in vallist:
            paramPass = item in target and item not in alltarget
            if paramPass:
                alltarget.add(item)
                if len(alltarget) == len(target):
                    break
            else:
                continue
        paramPass = len(alltarget) == len(target)
    if compareType == "LinkToResource":
        vallink = val.get('@odata.id')
        success, rf_payload, code, elapsed = callResourceURI(vallink)
        if success:
            ourType = rf_payload.get('@odata.type')
            if ourType is not None:
                SchemaType = getType(ourType)
                paramPass = SchemaType in target
            else:
                paramPass = False
        else:
            paramPass = False

    if compareType == "Absent":
        paramPass = val == 'DNE'
    if compareType == "Present":
        paramPass = val != 'DNE'

    if isinstance(target, list):
        if compareType == "Equal":
            paramPass = val in target
        elif compareType == "NotEqual":
            paramPass = val not in target
        else:
            for value in target:
                if compareType == "GreaterThan":
                    paramPass = val > value
                if compareType == "GreaterThanOrEqual":
                    paramPass = val >= value
                if compareType == "LessThan":
                    paramPass = val < value
                if compareType == "LessThanOrEqual":
                    paramPass = val <= value
                if paramPass is False:
                    break
    my_logger.debug('\tpass ' + str(paramPass))
    return msgInterop('Comparison', target, compareType, val, paramPass),\
        paramPass