def getSchemaDetails(SchemaType, SchemaURI): """ Find Schema file for given Namespace. param SchemaType: Schema Namespace, such as ServiceRoot param SchemaURI: uri to grab schema, given LocalOnly is False return: (success boolean, a Soup object, origin) """ rst.traverseLogger.debug('getting Schema of {} {}'.format( SchemaType, SchemaURI)) currentService = rst.currentService if SchemaType is None: return False, None, None if currentService is None: return getSchemaDetailsLocal(SchemaType, SchemaURI) elif currentService.active and getNamespace( SchemaType) in currentService.metadata.schema_store: result = rst.currentService.metadata.schema_store[getNamespace( SchemaType)] if result is not None: return True, result.soup, result.origin if not currentService.config[ 'preferonline'] and '$metadata' not in SchemaURI: success, soup, origin = getSchemaDetailsLocal(SchemaType, SchemaURI) if success: return success, soup, origin xml_suffix = currentService.config['schemasuffix'] config = rst.currentService.config LocalOnly, SchemaLocation, ServiceOnly = config['localonlymode'], config[ 'metadatafilepath'], config['servicemode'] scheme, netloc, path, params, query, fragment = urlparse(SchemaURI) inService = scheme is None and netloc is None if (SchemaURI is not None and not LocalOnly) or (SchemaURI is not None and '/redfish/v1/$metadata' in SchemaURI): # Get our expected Schema file here # if success, generate Soup, then check for frags to parse # start by parsing references, then check for the refLink if '#' in SchemaURI: base_schema_uri, frag = tuple(SchemaURI.rsplit('#', 1)) else: base_schema_uri, frag = SchemaURI, None success, data, status, elapsed = rst.callResourceURI(base_schema_uri) if success: soup = BeautifulSoup(data, "xml") # if frag, look inside xml for real target as a reference if frag is not None: # prefer type over frag, truncated down # using frag, check references frag = getNamespace(SchemaType) frag = frag.split('.', 1)[0] refType, refLink = getReferenceDetails( soup, name=base_schema_uri).get(frag, (None, None)) if refLink is not None: success, linksoup, newlink = getSchemaDetails( refType, refLink) if success: return True, linksoup, newlink else: rst.traverseLogger.error( "SchemaURI couldn't call reference link {} inside {}" .format(frag, base_schema_uri)) else: rst.traverseLogger.error( "SchemaURI missing reference link {} inside {}".format( frag, base_schema_uri)) # error reported; assume likely schema uri to allow continued validation uri = 'http://redfish.dmtf.org/schemas/v1/{}{}'.format( frag, xml_suffix) rst.traverseLogger.info( "Continue assuming schema URI for {} is {}".format( SchemaType, uri)) return getSchemaDetails(SchemaType, uri) else: storeSchemaToLocal(data, base_schema_uri) return True, soup, base_schema_uri if not inService and ServiceOnly: rst.traverseLogger.debug( "Nonservice URI skipped: {}".format(base_schema_uri)) else: rst.traverseLogger.debug( "SchemaURI called unsuccessfully: {}".format(base_schema_uri)) if LocalOnly: rst.traverseLogger.debug("This program is currently LOCAL ONLY") if ServiceOnly: rst.traverseLogger.debug("This program is currently SERVICE ONLY") if not LocalOnly and not ServiceOnly or (not inService and config['preferonline']): rst.traverseLogger.warning( "SchemaURI {} was unable to be called, defaulting to local storage in {}" .format(SchemaURI, SchemaLocation)) return getSchemaDetailsLocal(SchemaType, SchemaURI)
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 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 validateActionRequirement(propResourceObj, profile_entry, rf_payload_tuple, actionname): """ Validate Requirements for one action """ rf_payload_item, rf_payload = rf_payload_tuple rf_payload_action = None counts = Counter() msgs = [] rsvLogger.verboseout('actionRequirement \n\tval: ' + str( rf_payload_item if not isinstance(rf_payload_item, dict) else 'dict') + ' ' + str(profile_entry)) if "ReadRequirement" in profile_entry: # problem: if dne, skip msg, success = validateRequirement( profile_entry.get('ReadRequirement', "Mandatory"), rf_payload_item) msgs.append(msg) msg.name = actionname + '.' + msg.name propDoesNotExist = (rf_payload_item == 'DNE') if propDoesNotExist: return msgs, counts if "@Redfish.ActionInfo" in rf_payload_item: vallink = rf_payload_item['@Redfish.ActionInfo'] success, rf_payload_action, code, elapsed = rst.callResourceURI( vallink) if not success: rf_payload_action = None # problem: if dne, skip if "Parameters" in profile_entry: innerDict = profile_entry["Parameters"] # problem: if dne, skip # assume mandatory for k in innerDict: item = innerDict[k] values_array = None if rf_payload_action is not None: action_by_name = rf_payload_action['Parameters'] my_action = [x for x in action_by_name if x['Name'] == k] if my_action: values_array = my_action[0].get('AllowableValues') if values_array is None: values_array = rf_payload_item.get( str(k) + '@Redfish.AllowableValues', 'DNE') msg, success = validateRequirement( item.get('ReadRequirement', "Mandatory"), values_array) msgs.append(msg) msg.name = "{}.{}.{}".format(actionname, k, msg.name) if values_array == 'DNE': continue if "ParameterValues" in item: msg, success = validateSupportedValues(item["ParameterValues"], values_array) msgs.append(msg) msg.name = "{}.{}.{}".format(actionname, k, msg.name) if "RecommendedValues" in item: msg, success = validateSupportedValues( item["RecommendedValues"], values_array) msg.name = msg.name.replace('Supported', 'Recommended') if config['WarnRecommended'] and not success: rsvLogger.warn( '\tRecommended parameters do not all exist, escalating to WARN' ) msg.success = sEnum.WARN elif not success: rsvLogger.warn( '\tRecommended parameters do not all exist, but are not Mandatory' ) msg.success = sEnum.PASS msgs.append(msg) msg.name = "{}.{}.{}".format(actionname, k, msg.name) # consider requirement before anything else, what if action # if the action doesn't exist, you can't check parameters # if it doesn't exist, what should not be checked for action return msgs, counts
def validateEntity(name: str, val: dict, propType: str, propCollectionType: str, schemaObj, autoExpand, parentURI=""): """ Validates an entity based on its uri given """ rsvLogger.debug('validateEntity: name = {}'.format(name)) # check for required @odata.id if '@odata.id' not in val: if autoExpand: default = parentURI + '#/{}'.format( name.replace('[', '/').strip(']')) else: default = parentURI + '/{}'.format(name) rsvLogger.error( "{}: EntityType resource does not contain required @odata.id property, attempting default {}" .format(name, default)) if parentURI == "": return False uri = default else: uri = val['@odata.id'] # check if the entity is truly what it's supposed to be paramPass = False # if not autoexpand, we must grab the resource if not autoExpand: success, data, status, delay = rst.callResourceURI(uri) else: success, data, status, delay = True, val, 200, 0 rsvLogger.debug( '(success, uri, status, delay) = {}, (propType, propCollectionType) = {}, data = {}' .format((success, uri, status, delay), (propType, propCollectionType), data)) # if the reference is a Resource, save us some trouble as most/all basetypes are Resource generics = [ 'Resource.ItemOrCollection', 'Resource.ResourceCollection', 'Resource.Item', 'Resource.Resource' ] if (propCollectionType in generics or propType in generics) and success: return True elif success: # Attempt to grab an appropriate type to test against and its schema # Default lineup: payload type, collection type, property type currentType = data.get('@odata.type', propCollectionType) if currentType is None: currentType = propType soup, refs = schemaObj.soup, schemaObj.refs baseLink = refs.get( rst.getNamespace(propCollectionType if propCollectionType is not None else propType)) # if schema in current schema, then use it # elif namespace in References, use that # else we have no lead if soup.find('Schema', attrs={'Namespace': rst.getNamespace(currentType)}) is not None: success, baseObj = True, schemaObj elif baseLink is not None: baseObj = schemaObj.getSchemaFromReference( rst.getNamespaceUnversioned(currentType)) success = baseObj is not None else: success = False if not success: rsvLogger.error( "Schema of target {} not referenced in current resource, concluding type {} is not of expected type {}" .format(uri, currentType, propType)) rsvLogger.debug('success = {}, currentType = {}, baseLink = {}'.format( success, currentType, baseLink)) # Recurse through parent types, gather type hierarchy to check against if success and currentType is not None and baseObj.getTypeTagInSchema( currentType) is None and success: rsvLogger.error( '{}: Linked resource reports version {} not in Schema {}'. format(name.split(':')[-1], currentType, baseObj.origin)) elif success and currentType is not None: currentType = currentType.replace('#', '') allTypes = [] while currentType not in allTypes and success: allTypes.append(currentType) success, baseObj, currentType = baseObj.getParentType( currentType, 'EntityType') rsvLogger.debug('success = {}, currentType = {}'.format( success, currentType)) rsvLogger.debug( 'propType = {}, propCollectionType = {}, allTypes = {}'.format( propType, propCollectionType, allTypes)) paramPass = propType in allTypes or propCollectionType in allTypes if not paramPass: full_namespace = propCollectionType if propCollectionType is not None else propType rsvLogger.error( '{}: Linked resource reports schema version (or namespace): {} not found in typechain' .format(name.split(':')[-1], full_namespace)) else: rsvLogger.error( "{}: Could not get schema file for Entity check".format(name)) else: if "OriginOfCondition" in name: rsvLogger.verboseout( "{}: GET of resource at URI {} returned HTTP {}, but was a temporary resource." .format( name, uri, status if isinstance(status, int) and status >= 200 else "error")) return True else: rsvLogger.error( "{}: GET of resource at URI {} returned HTTP {}. Check URI.". format( name, uri, status if isinstance(status, int) and status >= 200 else "error")) return paramPass