def navigateJsonFragment(decoded, URILink): traverseLogger = rst.getLogger() if '#' in URILink: URIfragless, frag = tuple(URILink.rsplit('#', 1)) fragNavigate = frag.split('/') for item in fragNavigate: if item == '': continue if isinstance(decoded, dict): decoded = decoded.get(item) elif isinstance(decoded, list): if not item.isdigit(): traverseLogger.error( "This URI ({}) is accessing an array, but this is not an index: {}" .format(URILink, item)) return None if int(item) >= len(decoded): traverseLogger.error( "This URI ({}) is accessing an array, but the index is too large for an array of size {}: {}" .format(URILink, len(decoded), item)) return None decoded = decoded[int(item)] else: traverseLogger.error( "This URI ({}) has resolved to an invalid object that is neither an array or dictionary" .format(URILink)) return None return decoded
def navigateJsonFragment(decoded, URILink): traverseLogger = rst.getLogger() if '#' in URILink: URILink, frag = tuple(URILink.rsplit('#', 1)) fragNavigate = frag.split('/') for item in fragNavigate: if item == '': continue if isinstance(decoded, dict): decoded = decoded.get(item) elif isinstance(decoded, list): if not item.isdigit(): traverseLogger.error("This is an Array, but this is not an index, aborting: {} {}".format(URILink, item)) return None decoded = decoded[int(item)] if int(item) < len(decoded) else None if not isinstance(decoded, dict): traverseLogger.error( "Decoded object no longer a dictionary {}".format(URILink)) return None return decoded
def renderHtml(results, tool_version, startTick, nowTick, service, printCSV): # Render html config = service.config config_str = ', '.join(sorted(list(config.keys() - set(['systeminfo', 'targetip', 'password', 'description'])))) sysDescription, ConfigURI = (config['systeminfo'], config['targetip']) rsvLogger = rst.getLogger() logpath = config['logpath'] error_lines, finalCounts = count_errors(results) if service.metadata is not None: finalCounts.update(service.metadata.get_counter()) # wrap html htmlPage = '' htmlStrTop = '<head><title>Conformance Test Summary</title>\ <style>\ .pass {background-color:#99EE99}\ .column {\ float: left;\ width: 45%;\ }\ .fail {background-color:#EE9999}\ .warn {background-color:#EEEE99}\ .bluebg {background-color:#BDD6EE}\ .button {padding: 12px; display: inline-block}\ .center {text-align:center;}\ .log {text-align:left; white-space:pre-wrap; word-wrap:break-word; font-size:smaller; padding: 6px}\ .title {background-color:#DDDDDD; border: 1pt solid; font-height: 30px; padding: 8px}\ .titlesub {padding: 8px}\ .titlerow {border: 2pt solid}\ .results {transition: visibility 0s, opacity 0.5s linear; display: none; opacity: 0}\ .resultsShow {display: block; opacity: 1}\ body {background-color:lightgrey; border: 1pt solid; text-align:center; margin-left:auto; margin-right:auto}\ th {text-align:center; background-color:beige; border: 1pt solid}\ td {text-align:left; background-color:white; border: 1pt solid; word-wrap:break-word; overflow:hidden;}\ table {width:90%; margin: 0px auto; table-layout:fixed;}\ .titletable {width:100%}\ </style>\ </head>' htmlStrBodyHeader = '' # Logo and logname infos = [wrapTag('##### Redfish Conformance Test Report #####', 'h2')] infos.append(wrapTag('<img align="center" alt="DMTF Redfish Logo" height="203" width="288"' 'src="data:image/gif;base64,' + logo.logo + '">', 'h4')) infos.append('<h4><a href="https://github.com/DMTF/Redfish-Service-Validator">' 'https://github.com/DMTF/Redfish-Service-Validator</a></h4>') infos.append('Tool Version: {}'.format(tool_version)) infos.append(startTick.strftime('%c')) infos.append('(Run time: {})'.format( str(nowTick - startTick).rsplit('.', 1)[0])) infos.append('<h4>This tool is provided and maintained by the DMTF. ' 'For feedback, please open issues<br>in the tool\'s Github repository: ' '<a href="https://github.com/DMTF/Redfish-Service-Validator/issues">' 'https://github.com/DMTF/Redfish-Service-Validator/issues</a></h4>') htmlStrBodyHeader += tag.tr(tag.th(infoBlock(infos))) infos = {x: config[x] for x in config if x not in ['systeminfo', 'targetip', 'password', 'description']} infos_left, infos_right = dict(), dict() for key in sorted(infos.keys()): if len(infos_left) <= len(infos_right): infos_left[key] = infos[key] else: infos_right[key] = infos[key] block = tag.td(tag.div(infoBlock(infos_left), 'class=\'column log\'') \ + tag.div(infoBlock(infos_right), 'class=\'column log\''), 'id=\'resNumConfig\' class=\'results resultsShow\'') htmlButtons = '<div class="button warn" onClick="arr = document.getElementsByClassName(\'results\'); for (var i = 0; i < arr.length; i++){arr[i].classList.add(\'resultsShow\')};">Expand All</div>' htmlButtons += '<div class="button fail" onClick="arr = document.getElementsByClassName(\'results\'); for (var i = 0; i < arr.length; i++){arr[i].classList.remove(\'resultsShow\')};">Collapse All</div>' htmlButtons += tag.div('Toggle Config', attr='class="button pass" onClick="document.getElementById(\'resNumConfig\').classList.toggle(\'resultsShow\');"') htmlStrBodyHeader += tag.tr(tag.th(htmlButtons)) htmlStrBodyHeader += tag.tr(tag.th('Test Summary')) infos = {'System': ConfigURI, 'Description': sysDescription} htmlStrBodyHeader += tag.tr(tag.th(infoBlock(infos))) errors = error_lines if len(errors) == 0: errors = ['No errors located.'] errorTags = tag.td(infoBlock(errors), 'class="log"') infos_left, infos_right = dict(), dict() for key in sorted(finalCounts.keys()): if finalCounts.get(key) == 0: continue if len(infos_left) <= len(infos_right): infos_left[key] = finalCounts[key] else: infos_right[key] = finalCounts[key] htmlStrCounts = (tag.div(infoBlock(infos_left), 'class=\'column log\'') + tag.div(infoBlock(infos_right), 'class=\'column log\'')) htmlStrBodyHeader += tag.tr(tag.td(htmlStrCounts)) htmlStrBodyHeader += tag.tr(errorTags) htmlStrBodyHeader += tag.tr(block) if service.metadata is not None: htmlPage = service.metadata.to_html() for cnt, item in enumerate(results): entry = [] val = results[item] rtime = '(response time: {})'.format(val['rtime']) rcode = '({})'.format(val['rcode']) payload = val.get('payload', {}) # uri block prop_type = val['fulltype'] if prop_type is not None: namespace = getNamespace(prop_type) type_name = getType(prop_type) infos = [str(val.get(x)) for x in ['uri', 'samplemapped'] if val.get(x) not in ['',None]] infos.append(rtime) infos.append(type_name) uriTag = tag.tr(tag.th(infoBlock(infos, ' '), 'class="titlerow bluebg"')) entry.append(uriTag) # info block infos = [str(val.get(x)) for x in ['uri'] if val.get(x) not in ['',None]] infos.append(rtime) infos.append(tag.div('Show Results', attr='class="button warn"\ onClick="document.getElementById(\'payload{}\').classList.remove(\'resultsShow\');\ document.getElementById(\'resNum{}\').classList.toggle(\'resultsShow\');"'.format(cnt, cnt))) infos.append(tag.div('Show Payload', attr='class="button pass"\ onClick="document.getElementById(\'payload{}\').classList.toggle(\'resultsShow\');\ document.getElementById(\'resNum{}\').classList.add(\'resultsShow\');"'.format(cnt, cnt))) buttonTag = tag.td(infoBlock(infos), 'class="title" style="width:30%"') infos = [str(val.get(x)) for x in ['context', 'origin', 'fulltype']] infos = {y: x for x, y in zip(infos, ['Context', 'File Origin', 'Resource Type'])} infosTag = tag.td(infoBlock(infos), 'class="titlesub log" style="width:40%"') success = val['success'] if success: getTag = tag.td('GET Success HTTP Code {}'.format(rcode), 'class="pass"') else: getTag = tag.td('GET Failure HTTP Code {}'.format(rcode), 'class="fail"') countsTag = tag.td(infoBlock(val['counts'], split='', ffunc=applyInfoSuccessColor), 'class="log"') rhead = ''.join([buttonTag, infosTag, getTag, countsTag]) for x in [('tr',), ('table', 'class=titletable'), ('td', 'class=titlerow'), ('tr')]: rhead = wrapTag(''.join(rhead), *x) entry.append(rhead) # actual table rows = [[str(m)] + list([str(x) for x in val['messages'][m]]) for m in val['messages']] titles = ['Property Name', 'Value', 'Type', 'Exists', 'Result'] widths = ['15', '30', '30', '10', '15'] tableHeader = tableBlock(rows, titles, widths, ffunc=applySuccessColor) # lets wrap table and errors and warns into one single column table tableHeader = tag.tr(tag.td((tableHeader))) infos_a = [str(val.get(x)) for x in ['uri'] if val.get(x) not in ['',None]] infos_a.append(rtime) if(printCSV): rsvLogger.info(','.join(infos_a)) rsvLogger.info(','.join(infos)) rsvLogger.info(','.join(titles)) rsvLogger.info('\n'.join([','.join(x) for x in rows])) rsvLogger.info(',') # warns and errors errors = val['errors'] if len(errors) == 0: errors = 'No errors' infos = errors.split('\n') errorTags = tag.tr(tag.td(infoBlock(infos), 'class="fail log"')) warns = val['warns'] if len(warns) == 0: warns = 'No warns' infos = warns.split('\n') warnTags = tag.tr(tag.td(infoBlock(infos), 'class="warn log"')) payloadTag = tag.td(json.dumps(payload, indent=4, sort_keys=True), 'id=\'payload{}\' class=\'results log\''.format(cnt)) tableHeader += errorTags tableHeader += warnTags tableHeader += payloadTag tableHeader = tag.table(tableHeader) tableHeader = tag.td(tableHeader, 'class="results" id=\'resNum{}\''.format(cnt)) entry.append(tableHeader) # append htmlPage += ''.join([tag.tr(x) for x in entry]) return wrapTag(wrapTag(htmlStrTop + wrapTag(htmlStrBodyHeader + htmlPage, 'table'), 'body'), 'html')
# License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Service-Validator/blob/master/LICENSE.md # # Unit tests for RedfishServiceValidator.py # from unittest import TestCase from unittest import mock import datetime import sys sys.path.append('../') import simpletypes as st import traverseService as rst rsvLogger = rst.getLogger() rsvLogger.disabled = True class ValidatorTest(TestCase): """ Tests for functions setup_operation() and run_systems_operation() """ def test_no_test(self): self.assertTrue(True, 'Huh?') def test_validate_number(self): empty = (None, None) numbers = [0, 10000, 20, 20, 23e-1, 23.0, 14.0, 999.0]
def renderHtml(results, finalCounts, tool_version, startTick, nowTick, printCSV): # Render html config = rst.config config_str = ', '.join( sorted( list(config.keys() - set(['systeminfo', 'targetip', 'password', 'description'])))) rsvLogger = rst.getLogger() sysDescription, ConfigURI = (config['systeminfo'], config['targetip']) logpath = config['logpath'] # wrap html htmlPage = '' htmlStrTop = '<head><title>Conformance Test Summary</title>\ <style>\ .pass {background-color:#99EE99}\ .fail {background-color:#EE9999}\ .warn {background-color:#EEEE99}\ .bluebg {background-color:#BDD6EE}\ .button {padding: 12px; display: inline-block}\ .center {text-align:center;}\ .log {text-align:left; white-space:pre-wrap; word-wrap:break-word; font-size:smaller}\ .title {background-color:#DDDDDD; border: 1pt solid; font-height: 30px; padding: 8px}\ .titlesub {padding: 8px}\ .titlerow {border: 2pt solid}\ .results {transition: visibility 0s, opacity 0.5s linear; display: none; opacity: 0}\ .resultsShow {display: block; opacity: 1}\ body {background-color:lightgrey; border: 1pt solid; text-align:center; margin-left:auto; margin-right:auto}\ th {text-align:center; background-color:beige; border: 1pt solid}\ td {text-align:left; background-color:white; border: 1pt solid; word-wrap:break-word;}\ table {width:90%; margin: 0px auto; table-layout:fixed;}\ .titletable {width:100%}\ </style>\ </head>' htmlStrBodyHeader = '' # Logo and logname infos = [wrapTag('##### Redfish Conformance Test Report #####', 'h2')] infos.append( wrapTag( '<img align="center" alt="DMTF Redfish Logo" height="203" width="288"' 'src="data:image/gif;base64,' + logo.logo + '">', 'h4')) infos.append( '<h4><a href="https://github.com/DMTF/Redfish-Interop-Validator">' 'https://github.com/DMTF/Redfish-Interop-Validator</a></h4>') infos.append('Tool Version: {}'.format(tool_version)) infos.append(startTick.strftime('%c')) infos.append('(Run time: {})'.format( str(nowTick - startTick).rsplit('.', 1)[0])) infos.append( '<h4>This tool is provided and maintained by the DMTF. ' 'For feedback, please open issues<br>in the tool\'s Github repository: ' '<a href="https://github.com/DMTF/Redfish-Interop-Validator/issues">' 'https://github.com/DMTF/Redfish-Interop-Validator/issues</a></h4>') htmlStrBodyHeader += tr(th(infoBlock(infos))) infos = {'System': ConfigURI, 'Description': sysDescription} htmlStrBodyHeader += tr(th(infoBlock(infos))) infos = {'Profile': config['profile'], 'Schema': config['schema']} htmlStrBodyHeader += tr(th(infoBlock(infos))) infos = { x: config[x] for x in config if x not in [ 'systeminfo', 'targetip', 'password', 'description', 'profile', 'schema' ] } block = tr(th(infoBlock(infos, '|||'))) for num, block in enumerate(block.split('|||'), 1): sep = '<br/>' if num % 4 == 0 else ', ' sep = '' if num == len(infos) else sep htmlStrBodyHeader += block + sep htmlStrTotal = '<div>Final counts: ' for countType in sorted(finalCounts.keys()): if finalCounts.get(countType) == 0: continue htmlStrTotal += '{p}: {q}, '.format(p=countType, q=finalCounts.get(countType, 0)) htmlStrTotal += '</div><div class="button warn" onClick="arr = document.getElementsByClassName(\'results\'); for (var i = 0; i < arr.length; i++){arr[i].className = \'results resultsShow\'};">Expand All</div>' htmlStrTotal += '</div><div class="button fail" onClick="arr = document.getElementsByClassName(\'results\'); for (var i = 0; i < arr.length; i++){arr[i].className = \'results\'};">Collapse All</div>' htmlStrBodyHeader += tr(td(htmlStrTotal)) htmlPage = rst.currentService.metadata.to_html() for cnt, item in enumerate(results): entry = [] val = results[item] rtime = '(response time: {})'.format(val['rtime']) if len(val['messages']) == 0 and len(val['errors']) == 0: continue # uri block prop_type = val['fulltype'] if prop_type is not None: namespace = getNamespace(prop_type) type_name = getType(prop_type) infos_a = [ str(val.get(x)) for x in ['uri', 'samplemapped'] if val.get(x) not in ['', None] ] infos_a.append(rtime) infos_a.append(type_name) uriTag = tr(th(infoBlock(infos_a, ' '), 'class="titlerow bluebg"')) entry.append(uriTag) # info block infos_b = [ str(val.get(x)) for x in ['uri'] if val.get(x) not in ['', None] ] infos_b.append(rtime) infos_b.append( div('Show Results', attr= 'class="button warn" onClick="document.getElementById(\'resNum{}\').classList.toggle(\'resultsShow\');"' .format(cnt))) buttonTag = td(infoBlock(infos_b), 'class="title" style="width:30%"') infos_content = [ str(val.get(x)) for x in ['context', 'origin', 'fulltype'] ] infos_c = { y: x for x, y in zip(infos_content, ['Context', 'File Origin', 'Resource Type']) } infosTag = td(infoBlock(infos_c), 'class="titlesub log" style="width:40%"') success = val['success'] if success: getTag = td('GET Success', 'class="pass"') else: getTag = td('GET Failure', 'class="fail"') countsTag = td( infoBlock(val['counts'], split='', ffunc=applyInfoSuccessColor), 'class="log"') rhead = ''.join([buttonTag, infosTag, getTag, countsTag]) for x in [('tr', ), ('table', 'class=titletable'), ('td', 'class=titlerow'), ('tr')]: rhead = wrapTag(''.join(rhead), *x) entry.append(rhead) # actual table rows = [(str(i.name), str(i.entry), str(i.expected), str(i.actual), str(i.success.value)) for i in val['messages']] titles = ['Property Name', 'Value', 'Expected', 'Actual', 'Result'] widths = ['15', '30', '30', '10', '15'] tableHeader = tableBlock(rows, titles, widths, ffunc=applySuccessColor) # lets wrap table and errors and warns into one single column table tableHeader = tr(td((tableHeader))) if (printCSV): rsvLogger.info(','.join(infos_a)) rsvLogger.info(','.join(infos_content)) rsvLogger.info(','.join(titles)) rsvLogger.info('\n'.join([','.join(x) for x in rows])) rsvLogger.info(',') # warns and errors errors = val['errors'] if len(errors) == 0: errors = 'No errors' infos = errors.split('\n') errorTags = tr(td(infoBlock(infos), 'class="fail log"')) warns = val['warns'] if len(warns) == 0: warns = 'No warns' infos = warns.split('\n') warnTags = tr(td(infoBlock(infos), 'class="warn log"')) tableHeader += errorTags tableHeader += warnTags tableHeader = table(tableHeader) tableHeader = td(tableHeader, 'class="results" id=\'resNum{}\''.format(cnt)) entry.append(tableHeader) # append htmlPage += ''.join([tr(x) for x in entry]) return wrapTag( wrapTag(htmlStrTop + wrapTag(htmlStrBodyHeader + htmlPage, 'table'), 'body'), 'html')
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 validateURITree(URI, uriName, expectedType=None, expectedSchema=None, expectedJson=None, parent=None, allLinks=None): # from given URI, validate it, then follow its links like nodes # Other than expecting a valid URI, on success (real URI) expects valid links # valid links come from getAllLinks, includes info such as expected values, etc # as long as it is able to pass that info, should not crash # info: destinations, individual expectations of each? # error: on fail # warn: reference only? # debug: traverseLogger = rst.getLogger() # If this is our first called URI top = allLinks is None if top: allLinks = set() allLinks.add(URI) def executeLink(linkItem, parent=None): linkURI, autoExpand, linkType, linkSchema, innerJson, original_name = linkItem if linkType is not None and autoExpand: returnVal = validateURITree(linkURI, uriName + ' -> ' + linkName, linkType, linkSchema, innerJson, parent, allLinks) else: returnVal = validateURITree(linkURI, uriName + ' -> ' + linkName, parent=parent, allLinks=allLinks) traverseLogger.verboseout('%s, %s', linkName, returnVal[1]) return returnVal refLinks = OrderedDict() validateSuccess, counts, results, links, thisobj = validateSingleURI( URI, uriName, expectedType, expectedSchema, expectedJson, parent) if validateSuccess: for linkName in links: if any( x in links[linkName].origin_property for x in ['RelatedItem', 'Redundancy', 'Links', 'OriginOfCondition']): refLinks[linkName] = (links[linkName], thisobj) continue if links[linkName].uri in allLinks: counts['repeat'] += 1 continue elif links[linkName].uri is None: errmsg = 'URI for NavigationProperty is missing {} {}'.format( uriName, links[linkName].linktype) traverseLogger.error(errmsg) results[uriName]['errors'] += '\n' + errmsg counts['errorMissingOdata'] += 1 continue elif links[linkName].uri.split('#')[0].endswith('/'): # (elegantly) add warn message to resource html warnmsg = 'URI acquired ends in slash: {}'.format( links[linkName].uri) traverseLogger.warning(warnmsg) results[uriName]['warns'] += '\n' + warnmsg counts['warnTrailingSlashLink'] += 1 newLink = ''.join(links[linkName].uri.split('/')[:-1]) if newLink in allLinks: counts['repeat'] += 1 continue success, linkCounts, linkResults, xlinks, xobj = executeLink( links[linkName], thisobj) refLinks.update(xlinks) if not success: counts['unvalidated'] += 1 results.update(linkResults) if top: for linkName in refLinks: ref_link, refparent = refLinks[linkName] if ref_link.uri is None: errmsg = 'URI for ReferenceLink is missing {} {}'.format( uriName, ref_link.linktype) traverseLogger.error(errmsg) results[uriName]['errors'] += '\n' + errmsg counts['errorMissingReferenceOdata'] += 1 continue elif ref_link.uri.split('#')[0].endswith('/'): # (elegantly) add warn message to resource html warnmsg = 'Referenced URI acquired ends in slash: {}'.format( ref_link.uri) traverseLogger.warning(warnmsg) results[uriName]['warns'] += '\n' + warnmsg counts['warnTrailingSlashRefLink'] += 1 new_ref_link = ''.join(ref_link.uri.split('/')[:-1]) if new_ref_link in allLinks: counts['repeat'] += 1 continue if ref_link.uri not in allLinks: traverseLogger.verboseout('{}, {}'.format(linkName, ref_link)) counts['reflink'] += 1 else: continue success, linkCounts, linkResults, xlinks, xobj = executeLink( ref_link, refparent) if not success: counts['unvalidatedRef'] += 1 if 'OriginOfCondition' in ref_link.origin_property: traverseLogger.info( 'Link was unsuccessful, but non mandatory') pass else: results.update(linkResults) else: results.update(linkResults) return validateSuccess, counts, results, refLinks, thisobj
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