Example #1
0
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
    lc = setupLoggingCaptures()
    next(lc)

    # Start
    counts = Counter()
    results = OrderedDict()
    messages = []

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

    # check for @odata mandatory stuff
    # check for version numbering problems
    # check id if its 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 = '...'
        URI = parentURI + '...'
    if expectedJson is None:
        successGet, jsondata, status, rtime = rst.callResourceURI(URI)
    else:
        successGet, jsondata = True, expectedJson

    if jsondata is not None:
        successPayload, odataMessages = rst.ResourceObj.checkPayloadConformance(
            jsondata, URI)

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

    # Generate dictionary of property info
    try:
        propResourceObj = rst.createResourceObject(uriName, URI, expectedJson,
                                                   expectedType,
                                                   expectedSchema, parent)
        if not propResourceObj:
            counts['problemResource'] += 1
            results[uriName]['warns'], results[uriName]['errors'] = next(lc)
            return False, counts, results, None, None
    except AuthenticationError:
        raise  # re-raise exception
    except Exception:
        rsvLogger.exception("")
        counts['exceptionResource'] += 1
        results[uriName]['warns'], results[uriName]['errors'] = next(lc)
        return False, counts, results, None, None

    counts['passGet'] += 1

    # if URI was sampled, get the notation text from rst.uri_sample_map
    sample_string = rst.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]['context'] = propResourceObj.context
    results[uriName]['origin'] = propResourceObj.schemaObj.origin
    results[uriName]['fulltype'] = propResourceObj.typeobj.fulltype
    results[uriName]['success'] = True

    rsvLogger.debug("\t URI {}, Type ({}), GET SUCCESS (time: {})".format(
        URI, propResourceObj.typeobj.stype, propResourceObj.rtime))

    uriName, SchemaFullType, jsondata = propResourceObj.name, propResourceObj.typeobj.fulltype, propResourceObj.jsondata
    SchemaNamespace, SchemaType = rst.getNamespace(
        SchemaFullType), rst.getType(SchemaFullType)

    objRes = profile.get('Resources')

    if SchemaType not in objRes:
        rsvLogger.debug('\nNo Such Type in sample {} {}.{}, skipping'.format(
            URI, SchemaNamespace, SchemaType))
    else:
        rsvLogger.info("\n*** %s, %s", URI, SchemaType)
        rsvLogger.debug("\n*** %s, %s, %s", expectedType, expectedSchema
                        is not None, expectedJson is not None)
        objRes = objRes.get(SchemaType)
        try:
            propMessages, propCounts = commonInterop.validateInteropResource(
                propResourceObj, objRes, jsondata)
            messages = messages.extend(propMessages)
            counts.update(propCounts)
            rsvLogger.info('{} of {} tests passed.'.format(
                counts['pass'] + counts['warn'], counts['totaltests']))
        except Exception:
            rsvLogger.exception("Something went wrong")
            rsvLogger.error(
                'Could not finish validation check on this payload')
            counts['exceptionProfilePayload'] += 1
        rsvLogger.info('%s, %s\n', SchemaFullType, counts)

    # Get all links available
    results[uriName]['warns'], results[uriName]['errors'] = next(lc)

    rsvLogger.debug(propResourceObj.links)
    return True, counts, results, propResourceObj.links, propResourceObj
Example #2
0
def getPropertyDetails(schemaObj,
                       propertyOwner,
                       propertyName,
                       val,
                       topVersion=None,
                       customType=None,
                       parent=None,
                       top_of_resource=None):
    """
    Get dictionary of tag attributes for properties given, including basetypes.

    param arg1: soup data
    param arg2: references
    ...
    """

    propEntry = dict()
    propEntry['val'] = val
    if val == 'n/a':
        val = None
    OwnerNamespace, OwnerType = getNamespace(propertyOwner), getType(
        propertyOwner)
    rst.traverseLogger.debug('___')
    rst.traverseLogger.debug('{}, {}:{}'.format(OwnerNamespace, propertyOwner,
                                                propertyName))

    soup, refs = schemaObj.soup, schemaObj.refs

    if customType is None:
        # Get Schema of the Owner that owns this prop
        ownerSchema = soup.find('Schema', attrs={'Namespace': OwnerNamespace})

        if ownerSchema is None:
            rst.traverseLogger.warning(
                "getPropertyDetails: Schema could not be acquired,  {}".format(
                    OwnerNamespace))
            return None

        # Get Entity of Owner, then the property of the Property we're targeting
        ownerEntity = ownerSchema.find(['EntityType', 'ComplexType'],
                                       attrs={'Name': OwnerType},
                                       recursive=False)

        # check if this property is a nav property
        # Checks if this prop is an annotation
        success, propertySoup, propertyRefs, propertyFullType = True, soup, refs, OwnerType

        if '@' not in propertyName:
            propEntry['isTerm'] = False  # not an @ annotation
            propertyTag = ownerEntity.find(['NavigationProperty', 'Property'],
                                           attrs={'Name': propertyName},
                                           recursive=False)

            # start adding attrs and props together
            propertyInnerTags = propertyTag.find_all(recursive=False)
            for tag in propertyInnerTags:
                if (not tag.get('Term')):
                    rst.traverseLogger.warn(tag,
                                            'does not contain a Term name')
                elif (tag.get('Term') == 'Redfish.Revisions'):
                    propEntry[tag['Term']] = tag.find_all('Record')
                else:
                    propEntry[tag['Term']] = tag.attrs
            propertyFullType = propertyTag.get('Type')
        else:
            propEntry['isTerm'] = True
            ownerEntity = ownerSchema.find(['Term'],
                                           attrs={'Name': OwnerType},
                                           recursive=False)
            if ownerEntity is None:
                ownerEntity = ownerSchema.find(['EntityType', 'ComplexType'],
                                               attrs={'Name': OwnerType},
                                               recursive=False)
            propertyTag = ownerEntity
            propertyFullType = propertyTag.get('Type', propertyOwner)

        propEntry['isNav'] = propertyTag.name == 'NavigationProperty'
        propEntry['attrs'] = propertyTag.attrs
        rst.traverseLogger.debug(propEntry)

        propEntry['realtype'] = 'none'

    else:
        propertyFullType = customType
        propEntry['realtype'] = 'none'
        propEntry['attrs'] = dict()
        propEntry['attrs']['Type'] = customType
        serviceRefs = rst.currentService.metadata.get_service_refs()
        serviceSchemaSoup = rst.currentService.metadata.get_soup()
        success, propertySoup, propertyRefs, propertyFullType = True, serviceSchemaSoup, serviceRefs, customType

    # find the real type of this, by inheritance
    while propertyFullType is not None:
        rst.traverseLogger.debug("HASTYPE")
        PropertyNamespace, PropertyType = getNamespace(
            propertyFullType), getType(propertyFullType)

        rst.traverseLogger.debug('{}, {}'.format(PropertyNamespace,
                                                 propertyFullType))

        # Type='Collection(Edm.String)'
        # If collection, check its inside type
        if re.match('Collection\(.*\)', propertyFullType) is not None:
            if val is not None and not isinstance(val, list):
                raise TypeError(
                    'This collection is not a List: {}'.format(val))
            propertyFullType = propertyFullType.replace('Collection(',
                                                        "").replace(')', "")
            propEntry['isCollection'] = propertyFullType
            continue
        else:
            if val is not None and isinstance(
                    val, list) and propEntry.get('isCollection') is None:
                raise TypeError('This item should not be a List')

        # If basic, just pass itself
        if 'Edm' in propertyFullType:
            propEntry['realtype'] = propertyFullType
            break

        # get proper soup, check if this Namespace is the same as its Owner, otherwise find its SchemaXml
        if PropertyNamespace.split('.')[0] != OwnerNamespace.split('.')[0]:
            schemaObj = schemaObj.getSchemaFromReference(PropertyNamespace)
            success = schemaObj is not None
            if success:
                uri = schemaObj.origin
                propertySoup = schemaObj.soup
                propertyRefs = schemaObj.refs
        else:
            success, propertySoup, uri = True, soup, 'of parent'

        if not success:
            rst.traverseLogger.warning(
                "getPropertyDetails: Could not acquire appropriate Schema for this item, {} {} {}"
                .format(propertyOwner, PropertyNamespace, propertyName))
            return propEntry

        # traverse tags to find the type
        propertySchema = propertySoup.find(
            'Schema', attrs={'Namespace': PropertyNamespace})
        if propertySchema is None:
            rst.traverseLogger.warning(
                'Schema element with Namespace attribute of {} not found in schema file {}'
                .format(PropertyNamespace, uri))
            break
        propertyTypeTag = propertySchema.find(
            ['EnumType', 'ComplexType', 'EntityType', 'TypeDefinition'],
            attrs={'Name': PropertyType},
            recursive=False)
        nameOfTag = propertyTypeTag.name if propertyTypeTag is not None else 'None'

        # perform more logic for each type
        if nameOfTag == 'TypeDefinition':  # Basic type
            # This piece of code is rather simple UNLESS this is an "enumeration"
            #   this is a unique deprecated enum, labeled as Edm.String

            propertyFullType = propertyTypeTag.get('UnderlyingType')
            isEnum = propertyTypeTag.find(
                'Annotation',
                attrs={'Term': 'Redfish.Enumeration'},
                recursive=False)

            if propertyFullType == 'Edm.String' and isEnum is not None:
                propEntry['realtype'] = 'deprecatedEnum'
                propEntry['typeprops'] = list()
                memberList = isEnum.find('Collection').find_all(
                    'PropertyValue')

                for member in memberList:
                    propEntry['typeprops'].append(member.get('String'))
                rst.traverseLogger.debug("{}".format(propEntry['typeprops']))
                break
            else:
                continue

        elif nameOfTag == 'ComplexType':  # go deeper into this type
            rst.traverseLogger.debug("go deeper in type")

            # We need to find the highest existence of this type vs topVersion schema
            # not ideal, but works for this solution
            success, baseSoup, baseRefs, baseType = True, propertySoup, propertyRefs, propertyFullType

            # If we're outside of our normal Soup, then do something different, otherwise elif
            if PropertyNamespace.split('.')[0] != OwnerNamespace.split(
                    '.')[0] and not customType:
                typelist = []
                schlist = []
                for schema in baseSoup.find_all('Schema'):
                    if schema.find('ComplexType', attrs={'Name': PropertyType
                                                         }) is None:
                        continue
                    newNamespace = schema.get('Namespace')
                    typelist.append(newNamespace)
                    schlist.append(schema)
                for item, schema in reversed(sorted(zip(typelist, schlist))):
                    rst.traverseLogger.debug(
                        "Working backwards: {}   {}".format(
                            item, getType(baseType)))
                    baseType = item + '.' + getType(baseType)
                    break
            elif topVersion is not None and (topVersion > OwnerNamespace):
                currentVersion = topVersion
                currentSchema = baseSoup.find(
                    'Schema', attrs={'Namespace': currentVersion})
                # Working backwards from topVersion schematag,
                #   created expectedType, check if currentTypeTag exists
                #   if it does, use our new expectedType, else continue down parent types
                #   until we exhaust all schematags in file
                while currentSchema is not None:
                    expectedType = currentVersion + '.' + PropertyType
                    currentTypeTag = currentSchema.find(
                        'ComplexType', attrs={'Name': PropertyType})
                    if currentTypeTag is not None:
                        baseType = expectedType
                        rst.traverseLogger.debug('new type: ' + baseType)
                        break
                    else:
                        nextEntity = currentSchema.find(
                            ['EntityType', 'ComplexType'],
                            attrs={'Name': OwnerType})
                        if nextEntity is None:
                            baseType = schemaObj.getHighestType(
                                baseType, topVersion)
                            break
                        nextType = nextEntity.get('BaseType')
                        currentVersion = getNamespace(nextType)
                        currentSchema = baseSoup.find(
                            'Schema', attrs={'Namespace': currentVersion})
                        continue
            propEntry['realtype'] = 'complex'
            if propEntry.get('isCollection') is None:
                propEntry['typeprops'] = rst.createResourceObject(
                    propertyName,
                    'complex',
                    val,
                    context=schemaObj.context,
                    typename=baseType,
                    isComplex=True,
                    topVersion=topVersion,
                    top_of_resource=top_of_resource)
            else:
                val = val if val is not None else []
                propEntry['typeprops'] = [
                    rst.createResourceObject(propertyName,
                                             'complex',
                                             item,
                                             context=schemaObj.context,
                                             typename=baseType,
                                             isComplex=True,
                                             topVersion=topVersion,
                                             top_of_resource=top_of_resource)
                    for item in val
                ]
            break

        elif nameOfTag == 'EnumType':  # If enum, get all members
            propEntry['realtype'] = 'enum'
            propEntry['typeprops'] = list()
            for MemberName in propertyTypeTag.find_all('Member'):
                propEntry['typeprops'].append(MemberName['Name'])
            break

        elif nameOfTag == 'EntityType':  # If entity, do nothing special (it's a reference link)
            propEntry['realtype'] = 'entity'
            if val is not None:
                if propEntry.get('isCollection') is None:
                    val = [val]
                val = val if val is not None else []
                for innerVal in val:
                    linkURI = innerVal.get('@odata.id')
                    autoExpand = propEntry.get('OData.AutoExpand', None) is not None or\
                        propEntry.get('OData.AutoExpand'.lower(), None) is not None
                    linkType = propertyFullType
                    linkSchema = propertyFullType
                    innerJson = innerVal
                    propEntry[
                        'typeprops'] = linkURI, autoExpand, linkType, linkSchema, innerJson
            else:
                propEntry['typeprops'] = None
            rst.traverseLogger.debug("typeEntityTag found {}".format(
                propertyTypeTag['Name']))
            break

        else:
            rst.traverseLogger.error(
                'Type {} not found under namespace {} in schema {}'.format(
                    PropertyType, PropertyNamespace, uri))
            break

    return propEntry
Example #3
0
def validateSingleURI(URI,
                      uriName='',
                      expectedType=None,
                      expectedSchema=None,
                      expectedJson=None,
                      parent=None):
    # rs-assertion: 9.4.1
    # Initial startup here
    lc = setupLoggingCaptures()
    next(lc)
    # Start
    rsvLogger.verboseout("\n*** %s, %s", uriName, URI)
    rsvLogger.info("\n*** %s", URI)
    rsvLogger.debug("\n*** %s, %s, %s", expectedType, expectedSchema
                    is not None, expectedJson is not None)
    counts = Counter()
    results = OrderedDict()
    messages = OrderedDict()

    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 its 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'
        rsvLogger.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 = rst.callResourceURI(URI)
            results[uriName]['payload'] = jsondata
        else:
            results[uriName]['payload'] = expectedJson

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

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

    # verify odata_id properly resolves to its parent if holding fragment
    odata_id = propResourceObj.jsondata.get('@odata.id', 'void')
    if '#' in odata_id:
        if parent is not None:
            payload_resolve = rst.navigateJsonFragment(parent.jsondata, URI)
            if payload_resolve is None:
                rsvLogger.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:
                rsvLogger.error(
                    '@odata.id of ReferenceableMember does not point to the correct object: {}'
                    .format(odata_id))
                counts['badOdataIdResolution'] += 1
        else:
            rsvLogger.warn(
                'No parent found with which to test @odata.id of ReferenceableMember'
            )

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

    # if URI was sampled, get the notation text from rst.uri_sample_map
    sample_string = rst.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]['origin'] = propResourceObj.schemaObj.origin
    results[uriName]['fulltype'] = propResourceObj.typename
    results[uriName]['success'] = True

    rsvLogger.info("\t Type (%s), GET SUCCESS (time: %s)",
                   propResourceObj.typename, propResourceObj.rtime)

    # If this is an AttributeRegistry, load it for later use
    if isinstance(propResourceObj.jsondata, dict):
        odata_type = propResourceObj.jsondata.get('@odata.type')
        if odata_type is not None:
            namespace = odata_type.split('.')[0]
            type_name = odata_type.split('.')[-1]
            if namespace == '#AttributeRegistry' and type_name == 'AttributeRegistry':
                loadAttributeRegDict(odata_type, propResourceObj.jsondata)

    for prop in propResourceObj.getResourceProperties():
        try:
            if not prop.valid and not prop.exists:
                continue
            propMessages, propCounts = checkPropertyConformance(
                propResourceObj.schemaObj,
                prop.name,
                prop,
                propResourceObj.jsondata,
                parentURI=URI)
            if '@Redfish.Copyright' in propMessages and 'MessageRegistry' not in propResourceObj.typeobj.fulltype:
                modified_entry = list(propMessages['@Redfish.Copyright'])
                modified_entry[-1] = 'FAIL'
                propMessages['@Redfish.Copyright'] = tuple(modified_entry)
                rsvLogger.error(
                    '@Redfish.Copyright is only allowed for mockups, and should not be allowed in official implementations'
                )
            if prop.payloadName != prop.propChild:
                propCounts['invalidName'] += 1
                for propMsg in propMessages:
                    modified_entry = list(propMessages[propMsg])
                    modified_entry[-1] = 'Invalid'
                    propMessages[propMsg] = tuple(modified_entry)
            if not prop.valid:
                rsvLogger.error(
                    'Verifying property that does not belong to this version: {}'
                    .format(prop.name))
                for propMsg in propMessages:
                    propCounts['invalidEntry'] += 1
                    modified_entry = list(propMessages[propMsg])
                    modified_entry[-1] = 'Invalid'
                    propMessages[propMsg] = tuple(modified_entry)

            messages.update(propMessages)
            counts.update(propCounts)
        except AuthenticationError as e:
            raise  # re-raise exception
        except Exception as ex:
            rsvLogger.debug('Exception caught while validating single URI',
                            exc_info=1)
            rsvLogger.error(
                '{}: Could not finish check on this property ({})'.format(
                    prop.name, str(ex)))
            counts['exceptionPropCheck'] += 1

    uriName, SchemaFullType, jsonData = propResourceObj.name, propResourceObj.typeobj.fulltype, propResourceObj.jsondata
    SchemaNamespace, SchemaType = rst.getNamespace(
        SchemaFullType), rst.getType(SchemaFullType)

    # List all items checked and unchecked
    # current logic does not check inside complex types
    fmt = '%-30s%30s'
    rsvLogger.verboseout('%s, %s, %s', uriName, SchemaNamespace, SchemaType)

    for key in jsonData:
        item = jsonData[key]
        rsvLogger.verboseout(fmt % (key, messages[key][3] if key in messages
                                    else 'Exists, no schema check'))

    allowAdditional = propResourceObj.typeobj.additional
    for key in [
            k for k in jsonData
            if k not in messages and k not in propResourceObj.unknownProperties
    ] + propResourceObj.unknownProperties:
        # note: extra messages for "unchecked" properties
        if not allowAdditional:
            rsvLogger.error(
                '{} not defined in schema {} (check version, spelling and casing)'
                .format(key, SchemaNamespace))
            counts['failAdditional'] += 1
            messages[key] = (displayValue(item), '-', '-', 'FAIL')
        else:
            rsvLogger.warn(
                '{} not defined in schema {} (check version, spelling and casing)'
                .format(key, SchemaNamespace))
            counts['unverifiedAdditional'] += 1
            messages[key] = (displayValue(item), '-', '-', 'Additional')

    for key in messages:
        if key not in jsonData:
            rsvLogger.verboseout(fmt % (key, messages[key][3]))

    results[uriName]['warns'], results[uriName]['errors'] = next(lc)

    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
    rsvLogger.info("\t {}".format('PASS' if pass_val else ' FAIL...'))

    rsvLogger.verboseout('%s, %s', SchemaFullType, counts)

    # Get all links available

    rsvLogger.debug(propResourceObj.links)

    return True, counts, results, propResourceObj.links, propResourceObj