def checkComparison(val, compareType, target): """ Validate a given comparison option, given a value and a target set """ rsvLogger.info('Testing a comparison \n\t' + str((val, compareType, target))) vallist = val if isinstance(val, list) else [val] paramPass = False if compareType is None: rsvLogger.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, decoded, code, elapsed = rst.callResourceURI(vallink) if success: ourType = decoded.get('@odata.type') if ourType is not None: SchemaType = rst.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 len(target) >= 1: target = target[0] else: target = 'DNE' if target != 'DNE': if compareType == "Equal": paramPass = val == target if compareType == "NotEqual": paramPass = val != target if compareType == "GreaterThan": paramPass = val > target if compareType == "GreaterThanOrEqual": paramPass = val >= target if compareType == "LessThan": paramPass = val < target if compareType == "LessThanOrEqual": paramPass = val <= target rsvLogger.info('\tpass ' + str(paramPass)) if not paramPass: rsvLogger.error('\tNoPass') return msgInterop('Comparison', target, compareType, val, paramPass),\ paramPass
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 validateURITree(URI, uriName, profile, expectedType=None, expectedSchema=None, expectedJson=None): """ Validates a Tree of URIs, traversing from the first given """ traverseLogger = rst.getLogger() 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: serviceVersion = serviceVersion.get('MinVersion', '1.0.0') msg, m_success = commonInterop.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: linkURI, autoExpand, linkType, linkSchema, innerJson = link if linkURI is None: continue if linkURI.rstrip( '/') in allLinks or linkType == 'Resource.Item': 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(linkURI, profile, linkURI, linkType, linkSchema, parent=parent) allLinks.add(linkURI.rstrip('/')) if not linkSuccess: continue innerLinksTuple = [(l, innerLinks[l], linkobj) for l in innerLinks] newLinks.extend(innerLinksTuple) results.update(linkResults) SchemaType = rst.getType(linkobj.typeobj.fulltype) 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 = OrderedDict() traverseLogger.info('Service Level Checks') if URI not in ["/redfish/v1", "/redfish/v1/"]: resultEnum = commonInterop.sEnum.WARN traverseLogger.info("We are not validating root, warn only") else: resultEnum = commonInterop.sEnum.FAIL for item in resource_info: # thisobj does not exist if we didn't find the first resource if thisobj and item == rst.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 commonInterop.checkConditionalRequirementResourceLevel( r_exists, condreq, item): traverseLogger.info( 'Service Conditional for {} applies'.format(item)) req = condreq.get("ReadRequirement", "Mandatory") rmessages.append( commonInterop.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 commonInterop.sEnum.PASS)) else: traverseLogger.info( 'Service Conditional for {} does not apply'.format( item)) req = resource_info[item].get("ReadRequirement", "Mandatory") if not exists: rmessages.append( commonInterop.msgInterop( item + '.ReadRequirement', req, 'Must Exist' if req == "Mandatory" else 'Any', 'DNE', resultEnum if req == "Mandatory" else commonInterop.sEnum.PASS)) else: rmessages.append( commonInterop.msgInterop( item + '.ReadRequirement', req, 'Must Exist' if req == "Mandatory" else 'Any', 'Exists', commonInterop.sEnum.PASS)) for item in rmessages: if item.success == commonInterop.sEnum.WARN: rcounts['warn'] += 1 elif item.success == commonInterop.sEnum.PASS: rcounts['pass'] += 1 elif item.success == commonInterop.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
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
def validateURITree(URI, uriName, profile, expectedType=None, expectedSchema=None, expectedJson=None): """ Validates a Tree of URIs, traversing from the first given """ traverseLogger = rst.getLogger() allLinks = set() allLinks.add(URI) refLinks = list() # Resource level validation rcounts = Counter() rmessages = [] rerror = StringIO() objRes = 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: serviceVersion = serviceVersion.get('MinVersion', '1.0.0') msg, mpss = commonInterop.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: linkURI, autoExpand, linkType, linkSchema, innerJson = link if linkURI in allLinks or linkType == 'Resource.Item': 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(linkURI, profile, linkURI, linkType, linkSchema, parent=parent) allLinks.add(linkURI) if not linkSuccess: continue innerLinksTuple = [(l, innerLinks[l], linkobj) for l in innerLinks] newLinks.extend(innerLinksTuple) results.update(linkResults) SchemaType = rst.getType(linkobj.typeobj.fulltype) # Check schema level for requirements if SchemaType in objRes: traverseLogger.info( "Checking service requirement for {}".format( SchemaType)) req = objRes[SchemaType].get("ReadRequirement", "Mandatory") msg, pss = commonInterop.validateRequirement(req, None) if pss and not objRes[SchemaType].get('mark', False): rmessages.append(msg) msg.name = SchemaType + '.' + msg.name objRes[SchemaType]['mark'] = True if "ConditionalRequirements" in objRes[SchemaType]: innerList = objRes[SchemaType][ "ConditionalRequirements"] newList = list() for condreq in innerList: condtrue = commonInterop.checkConditionalRequirement( linkobj, condreq, (linkobj.jsondata, None), None) if condtrue: msg, cpss = commonInterop.validateRequirement( condreq.get("ReadRequirement", "Mandatory"), None) if cpss: rmessages.append(msg) msg.name = SchemaType + '.Conditional.' + msg.name else: newList.append(condreq) else: newList.append(condreq) objRes[SchemaType]["ConditionalRequirements"] = newList if refLinks is not currentLinks and len( newLinks) == 0 and len(refLinks) > 0: currentLinks = refLinks else: currentLinks = newLinks # interop service level checks finalResults = OrderedDict() if URI != "/redfish/v1": resultEnum = commonInterop.sEnum.WARN traverseLogger.info("We are not validating root, warn only") else: resultEnum = commonInterop.sEnum.FAIL for left in objRes: if not objRes[left].get('mark', False): req = objRes[left].get("ReadRequirement", "Mandatory") rmessages.append( commonInterop.msgInterop( left + '.ReadRequirement', req, 'Must Exist' if req == "Mandatory" else 'Any', 'DNE', resultEnum)) if "ConditionalRequirements" in objRes[left]: innerList = objRes[left]["ConditionalRequirements"] for condreq in innerList: req = condreq.get("ReadRequirement", "Mandatory") rmessages.append( commonInterop.msgInterop( left + '.Conditional.ReadRequirement', req, 'Must Exist' if req == "Mandatory" else 'Any', 'DNE', resultEnum)) for item in rmessages: if item.success == commonInterop.sEnum.WARN: rcounts['warn'] += 1 elif item.success == commonInterop.sEnum.PASS: rcounts['pass'] += 1 elif item.success == commonInterop.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':''} print(len(allLinks)) finalResults.update(results) rerror.close() return validateSuccess, counts, finalResults, refLinks, thisobj
def checkPropertyConformance(schemaObj, PropertyName, prop, decoded, ParentItem=None, parentURI=""): """checkPropertyConformance Given a dictionary of properties, check the validitiy of each item, and return a list of counted properties :param schemaObj: :param PropertyName: :param PropertyItem: :param decoded: :param ParentItem: :param parentURI: """ resultList = OrderedDict() counts = Counter() rsvLogger.verboseout(PropertyName) item = prop.payloadName propValue = prop.val rsvLogger.verboseout("\tvalue: {} {}".format(propValue, type(propValue))) propExists = not (propValue == 'n/a') if ParentItem is not None: item = ParentItem + '.' + item PropertyDict = prop.propDict if PropertyDict is None: if not propExists: rsvLogger.verboseout('{}: Item is skipped, no schema'.format(item)) counts['skipNoSchema'] += 1 return { item: ('-', '-', 'Yes' if propExists else 'No', 'NoSchema') }, counts else: rsvLogger.error( '{}: Item is present, but no schema found'.format(item)) counts['failNoSchema'] += 1 return { item: ('-', '-', 'Yes' if propExists else 'No', 'FAIL') }, counts propAttr = PropertyDict['attrs'] propType = propAttr.get('Type') propRealType = PropertyDict.get('realtype') rsvLogger.verboseout("\thas Type: {} {}".format(propType, propRealType)) # why not actually check oem # rs-assertion: 7.4.7.2 if 'Oem' in PropertyName and not rst.config.get('oemcheck', False): rsvLogger.verboseout('\tOem is skipped') counts['skipOem'] += 1 return {item: ('-', '-', 'Yes' if propExists else 'No', 'OEM')}, counts propMandatory = False propMandatoryPass = True if 'Redfish.Required' in PropertyDict: propMandatory = True propMandatoryPass = True if propExists else False rsvLogger.verboseout("\tMandatory Test: {}".format( 'OK' if propMandatoryPass else 'FAIL')) else: rsvLogger.verboseout("\tis Optional") if not propExists: rsvLogger.verboseout("\tprop Does not exist, skip...") counts['skipOptional'] += 1 return { item: ('-', displayType(propType, propRealType), 'Yes' if propExists else 'No', 'Optional') }, counts nullable_attr = propAttr.get('Nullable') propNullable = False if nullable_attr == 'false' else True # default is true # rs-assertion: Check for permission change propPermissions = PropertyDict.get('OData.Permissions') propPermissionsValue = None if propPermissions is not None: propPermissionsValue = propPermissions['EnumMember'] rsvLogger.verboseout("\tpermission {}".format(propPermissionsValue)) autoExpand = PropertyDict.get('OData.AutoExpand', None) is not None or\ PropertyDict.get('OData.AutoExpand'.lower(), None) is not None validPatternAttr = PropertyDict.get('Validation.Pattern') validMinAttr = PropertyDict.get('Validation.Minimum') validMaxAttr = PropertyDict.get('Validation.Maximum') paramPass = propNullablePass = deprecatedPass = nullValid = True # <Annotation Term="Redfish.Deprecated" String="This property has been Deprecated in favor of Thermal.v1_1_0.Thermal.Fan.Name"/> validDeprecated = PropertyDict.get('Redfish.Deprecated') if validDeprecated is not None: deprecatedPass = False counts['warnDeprecated'] += 1 rsvLogger.warning('{}: The given property is deprecated: {}'.format( item, validDeprecated.get('String', ''))) validDeprecated = PropertyDict.get('Redfish.Revisions') if validDeprecated is not None: for tag_item in validDeprecated: revision_tag = tag_item.find('PropertyValue', attrs={ 'EnumMember': 'Redfish.RevisionKind/Deprecated', 'Property': 'Kind' }) if (revision_tag): desc_tag = tag_item.find('PropertyValue', attrs={'Property': 'Description'}) deprecatedPass = False counts['warnDeprecated'] += 1 if (desc_tag): rsvLogger.warning( '{}: The given property is deprecated by revision: {}'. format(item, desc_tag.attrs.get('String', ''))) else: rsvLogger.warning( '{}: The given property is deprecated by revision'. format(item)) validMin, validMax = int(validMinAttr['Int']) if validMinAttr is not None else None, \ int(validMaxAttr['Int']) if validMaxAttr is not None else None validPattern = validPatternAttr.get( 'String', '') if validPatternAttr is not None else None # Note: consider http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/csprd01/odata-csdl-xml-v4.01-csprd01.html#_Toc472333112 # Note: make sure it checks each one propCollectionType = PropertyDict.get('isCollection') isCollection = propCollectionType is not None if isCollection and propValue is None: # illegal for a collection to be null if prop.propChild == 'HttpHeaders' and rst.getType( prop.propOwner) == 'EventDestination': rsvLogger.info('Value HttpHeaders can be Null') propNullable = True propValueList = [] resultList[item] = ('Array (size: null)', displayType(propType, propRealType, is_collection=True), 'Yes' if propExists else 'No', '...') else: rsvLogger.error( '{}: Value of Collection property is null but Collections cannot be null, only their entries' .format(item)) counts['failNullCollection'] += 1 return { item: ('-', displayType(propType, propRealType, is_collection=True), 'Yes' if propExists else 'No', 'FAIL') }, counts elif isCollection and propValue is not None: # note: handle collections correctly, this needs a nicer printout # rs-assumption: do not assume URIs for collections # rs-assumption: check @odata.count property # rs-assumption: check @odata.link property rsvLogger.verboseout("\tis Collection") if propValue == 'n/a': propValueList = [] resultList[item] = ('Array (absent)'.format(len(propValue)), displayType(propType, propRealType, is_collection=True), 'Yes' if propExists else 'No', 'PASS' if propMandatoryPass else 'FAIL') rsvLogger.error("{}: Mandatory prop does not exist".format(item)) counts['failMandatoryExist'] += 1 else: propValueList = propValue resultList[item] = ('Array (size: {})'.format(len(propValue)), displayType(propType, propRealType, is_collection=True), 'Yes' if propExists else 'No', '...') else: # not a collection propValueList = [propValue] # note: make sure we don't enter this on null values, some of which are # OK! for cnt, val in enumerate(propValueList): appendStr = (('[' + str(cnt) + ']') if isCollection else '') sub_item = item + appendStr if isinstance(val, str): if val == '' and propPermissionsValue == 'OData.Permission/Read': rsvLogger.warning( '{}: Empty string found - Services should omit properties if not supported' .format(sub_item)) nullValid = False if val.lower() == 'null': rsvLogger.warning( '{}: "null" string found - Did you mean to use an actual null value?' .format(sub_item)) nullValid = False if propRealType is not None and propExists: paramPass = propNullablePass = True if val is None: if propNullable: rsvLogger.debug( 'Property {} is nullable and is null, so Nullable checking passes' .format(sub_item)) else: propNullablePass = False elif propRealType == 'Edm.Boolean': paramPass = isinstance(val, bool) if not paramPass: rsvLogger.error("{}: Not a boolean".format(sub_item)) elif propRealType == 'Edm.DateTimeOffset': paramPass = simpletypes.validateDatetime(sub_item, val) elif propRealType == 'Edm.Duration': paramPass = simpletypes.validateDayTimeDuration(sub_item, val) elif propRealType == 'Edm.Int16' or propRealType == 'Edm.Int32' or\ propRealType == 'Edm.Int64' or propRealType == 'Edm.Int': paramPass = simpletypes.validateInt(sub_item, val, validMin, validMax) elif propRealType == 'Edm.Decimal' or propRealType == 'Edm.Double': paramPass = simpletypes.validateNumber(sub_item, val, validMin, validMax) elif propRealType == 'Edm.Guid': paramPass = simpletypes.validateGuid(sub_item, val) elif propRealType == 'Edm.String': paramPass = simpletypes.validateString(sub_item, val, validPattern) elif propRealType == 'Edm.Primitive' or propRealType == 'Edm.PrimitiveType': paramPass = simpletypes.validatePrimitive(sub_item, val) else: if propRealType == 'complex': if PropertyDict['typeprops'] is not None: if isCollection: innerComplex = PropertyDict['typeprops'][cnt] innerPropType = PropertyDict['typeprops'][ cnt].typeobj else: innerComplex = PropertyDict['typeprops'] innerPropType = PropertyDict['typeprops'].typeobj success, complexCounts, complexMessages = validateComplex( sub_item, val, innerComplex, decoded.get('@odata.type'), decoded.get('AttributeRegistry')) else: success = False if not success: counts['failComplex'] += 1 resultList[sub_item] = ('[JSON Object]', displayType( propType, propRealType), 'Yes' if propExists else 'No', 'FAIL') continue resultList[sub_item] = ('[JSON Object]', displayType( propType, propRealType), 'Yes' if propExists else 'No', 'complex') counts.update(complexCounts) resultList.update(complexMessages) allowAdditional = innerPropType.additional for key in innerComplex.unknownProperties: if sub_item + '.' + key not in complexMessages and not allowAdditional: rsvLogger.error( '{} not defined in schema {} (check version, spelling and casing)' .format(sub_item + '.' + key, innerPropType.snamespace)) counts['failComplexAdditional'] += 1 resultList[sub_item + '.' + key] = (displayValue( val[key]), '-', '-', 'FAIL') elif sub_item + '.' + key not in complexMessages: rsvLogger.warn( '{} not defined in schema {} (check version, spelling and casing)' .format(sub_item + '.' + key, innerPropType.snamespace)) counts['unverifiedComplexAdditional'] += 1 resultList[sub_item + '.' + key] = (displayValue( val[key]), '-', '-', 'Additional') continue elif propRealType == 'enum': paramPass = simpletypes.validateEnum( sub_item, val, PropertyDict['typeprops']) elif propRealType == 'deprecatedEnum': paramPass = simpletypes.validateDeprecatedEnum( sub_item, val, PropertyDict['typeprops']) elif propRealType == 'entity': paramPass = validateEntity(sub_item, val, propType, propCollectionType, schemaObj, autoExpand, parentURI) else: rsvLogger.error("%s: This type is invalid %s" % (sub_item, propRealType)) paramPass = False if not paramPass or not propMandatoryPass or not propNullablePass: result_str = 'FAIL' elif not deprecatedPass: result_str = 'Deprecated' elif not nullValid: counts['invalidPropertyValue'] += 1 result_str = 'WARN' else: result_str = 'PASS' resultList[sub_item] = (displayValue(val, sub_item if autoExpand else None), displayType(propType, propRealType), 'Yes' if propExists else 'No', result_str) if paramPass and propNullablePass and propMandatoryPass: counts['pass'] += 1 rsvLogger.verboseout("\tSuccess") else: counts['err.' + str(propType)] += 1 if not paramPass: if propMandatory: counts['failMandatoryProp'] += 1 else: counts['failProp'] += 1 elif not propMandatoryPass: rsvLogger.error( "{}: Mandatory prop does not exist".format(sub_item)) counts['failMandatoryExist'] += 1 elif not propNullablePass: rsvLogger.error( '{}: Property is null but is not Nullable'.format( sub_item)) counts['failNullable'] += 1 rsvLogger.verboseout("\tFAIL") return resultList, counts