diff --git a/upgradepath.py b/upgradepath.py --- a/upgradepath.py +++ b/upgradepath.py @@ -22,6 +22,7 @@ import operator import sys import argparse +import os from libtaskotron import koji_utils from libtaskotron import bodhi_utils @@ -43,13 +44,11 @@ def __init__(self): - # mapping of package build -> CheckDetail for the corresponding Bodhi update + # mapping of package build -> CheckDetail self.build2detail = {} - # mapping of package build -> check result - # This is useful to track results of all individual builds, instead of - # results of whole updates (accessible via self.build2detail). - self.build2result = {} + # mapping of BodhiUpdate -> CheckDetail + self.update2detail = {} # dict of problematic builds (Koji/Bodhi problems): {NEVR: error} self.problematic = {} @@ -61,10 +60,46 @@ self.bodhi = bodhi_utils.BodhiUtils() + def store_logs(self): + '''Take ``self.build2detail`` and ``self.update2detail`` and save it as + inidividual log files in ``args.storedir`` directory, provided it is + defined.''' + if not args.storedir: + logger.debug('Not writing individual logs, not requested') + return + + try: + storedir = os.path.abspath(args.storedir) + logger.debug('Writing individual logs into: %s', storedir) + + if not os.path.exists(storedir): + os.makedirs(storedir) + + # save build logs + for build, detail in self.build2detail.items(): + filename = os.path.join(storedir, build) + '.log' + fout = open(filename, 'w') + fout.write('\n'.join(detail.output) + '\n') + fout.close() + + # save update logs + for update, detail in self.update2detail.items(): + filename = os.path.join(storedir, update.update['updateid'] or + update.update['title']) + '.log' + fout = open(filename, 'w') + fout.write('\n'.join(detail.output) + '\n') + fout.close() + + except OSError as e: + logger.error('Individual logs not saved into %s, an error occured. ' + 'Raising.', args.storedir) + raise e + + def compare(self, proposed_build, tags, op, tag_builds, check_detail, check_pending=False): '''Compare proposed_build with given operator to latest build in - all tags and update check_detail.result. + all tags and update check_detail.outcome. @param proposed_build Koji Build object @param tags list of kojitags or list of tuples containing kojitags which should be checked for upgradepath conformance. If @@ -327,7 +362,8 @@ nevrs = sorted([koji_utils.getNEVR(u) for u in updates]) else: # event is post-bodhi-update nevrs = kwargs['nvrs'] - logger.debug('Builds to be checked:\n %s' % '\n '.join(nevrs)) + logger.debug('%s builds to be checked:\n %s', len(nevrs), + '\n '.join(nevrs)) # For every build checked get a Bodhi update that corresponds to it. # Check that all builds for that update are scheduled for testing. @@ -366,26 +402,16 @@ build2update[build] = BodhiUpdate(update) bodhi_objects.add(build2update[build]) - logger.debug('Bodhi updates found:\n %s' % '\n '.join( - sorted([update.update['title'] for update in - set(build2update.values())]))) - - # Now it is time to populate self.build2detail: - # {build NVR: CheckDetail} - # All builds from the same Bodhi update should get the same - # CheckDetail object. - # - # As an intermediate step use update2detail variable: - # {BodhiUpdate: CheckDetail} - - update2detail = {} - for update in set(build2update.values()): - update2detail[update] = check.CheckDetail( - item=update.update['title'], - report_type=check.ReportType.BODHI_UPDATE) - - for build, update in build2update.iteritems(): - self.build2detail[build] = update2detail[update] + # print some debugging Bodhi info + def bodhi_desc(update): + if update.update['updateid']: + return '%s (%s)' % (update.update['updateid'], update.update['title']) + else: + return update.update['title'] + logger.debug('%s Bodhi updates found:\n %s', + len(set(build2update.values())), + '\n '.join(sorted([bodhi_desc(update) for update in + set(build2update.values())]))) # Get build info from Koji for every NEVR # Use multicall to speed it up @@ -398,22 +424,22 @@ # Let the testing begin! logger.debug('Running main upgradepath checks...') for (nevr, koji_build) in zip(nevrs, koji_builds): - detail = self.build2detail[nevr] - detail.summary = '%s into %s' % (nevr, kojitag) - - if detail.broken(): - # this Bodhi update was already broken by some nevr, skip it - continue + # Populate self.build2detail: + # {build NVR: CheckDetail} + detail = check.CheckDetail(item=nevr, + report_type=check.ReportType.KOJI_BUILD) + self.build2detail[nevr] = detail + detail.summary = '%s into %s' % (nevr, kojitag) detail.store('%s\n%s\n%s' % (60*'=', detail.summary, 60*'=')) # Koji returns list if the call succeeded and dict (containing error # information) if the call failed if isinstance(koji_build, dict): msg = "Can't fetch info about %s from Koji: %s" % (nevr, koji_build) detail.store(msg) self.problematic[nevr] = msg - detail.update_outcome('ABORTED') + detail.outcome = 'ABORTED' continue proposed_build = koji_build[0] @@ -451,14 +477,12 @@ ### start the main testing phase ### - nvr_result = check.CheckDetail(item=None) if push_to_main: # compare with lower tags, so version has to be greater or equal low_tags = [repo['tag'] for repo in low_repos] - result = self.compare(proposed_build, low_tags, operator.ge, - tag_builds, detail) - nvr_result.update_outcome(result) + self.compare(proposed_build, low_tags, operator.ge, + tag_builds, detail) # print infobar about currently proposed build detail.store('{0:<7}{1}'.format('[--->]', kojitag)) @@ -468,16 +492,14 @@ # compare with higher tags, so version has to be lower or equal hi_tags = [repo['tag'] for repo in hi_repos] - result = self.compare(proposed_build, hi_tags, operator.le, - tag_builds, detail) - nvr_result.update_outcome(result) + self.compare(proposed_build, hi_tags, operator.le, + tag_builds, detail) else: # pushing to update repository # compare with lower tags, so version has to be greater or equal low_tags = [repo['tag'] for repo in low_repos] - result = self.compare(proposed_build, low_tags, operator.ge, - tag_builds, detail) - nvr_result.update_outcome(result) + self.compare(proposed_build, low_tags, operator.ge, + tag_builds, detail) # print infobar about currently proposed build detail.store('{0:<7}{1}'.format('[--->]', kojitag)) @@ -514,14 +536,10 @@ # now do the comparison finally # this time we also want to check for pending Bodhi requests - result = self.compare(proposed_build, hi_tags, operator.le, - tag_builds, detail, check_pending=True) - nvr_result.update_outcome(result) - - detail.store('RESULT: %s' % nvr_result.outcome) + self.compare(proposed_build, hi_tags, operator.le, + tag_builds, detail, check_pending=True) - # save this build's result - self.build2result[nevr] = nvr_result.outcome + detail.store('RESULT: %s' % detail.outcome) # print broken updates if self.problematic: @@ -531,12 +549,36 @@ # print summary summary = check.CheckDetail.create_multi_item_summary( - self.build2result.values()) - logger.info('SUMMARY: ' + summary) + self.build2detail.values()) + logger.info('OVERALL SUMMARY: ' + summary) + + # Aggregate individual build results to Bodhi update results + for build, update in sorted(build2update.iteritems(), key=lambda t: t[0]): + if update not in self.update2detail: + self.update2detail[update] = check.CheckDetail( + item=update.update['updateid'] or update.update['title'], + report_type=check.ReportType.BODHI_UPDATE) + + update_detail = self.update2detail[update] + build_detail = self.build2detail[build] + update_detail.update_outcome(build_detail.outcome) + update_detail.output.extend(build_detail.output) + + for update, detail in self.update2detail.iteritems(): + builds = [b for b, u in build2update.iteritems() if u is update] + bdetails = [d for b, d in self.build2detail.iteritems() if b in builds] + summary = check.CheckDetail.create_multi_item_summary(bdetails) + detail.summary = summary + detail.store('\nSUMMARY: %s' % summary, printout=False) + + # save logs if requested + self.store_logs() # report results - details = sorted(update2detail.values(), key=lambda d: d.item) - tapout = check.export_TAP(details) + build_details = sorted(self.build2detail.values(), key=lambda d: d.item) + update_details = sorted(self.update2detail.values(), key=lambda d: d.item) + details = build_details + update_details + tapout = check.export_TAP(details, checkname='upgradepath') return tapout @@ -594,6 +636,9 @@ # 'builds in the -pending version of KOJI_TAG get checked.') parser.add_argument('--debug', action='store_true', help='print debugging ' 'info') + parser.add_argument('--storedir', help='An output directory where to store ' + 'individual per-build/update results. If not provided, they are not ' + 'generated at all.') args = parser.parse_args(custom_args) # if custom_args==None, then default # sys.argv is used diff --git a/upgradepath.yml b/upgradepath.yml --- a/upgradepath.yml +++ b/upgradepath.yml @@ -16,7 +16,7 @@ python: file: upgradepath.py callable: main - custom_args: [--debug, "${koji_tag}"] + custom_args: [--debug, --storedir, "${artifactsdir}" , "${koji_tag}"] export: upgradepath_output - name: report results to resultsdb