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
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
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