diff --git a/builder/kojid b/builder/kojid index 446ab2ea..13491bab 100755 --- a/builder/kojid +++ b/builder/kojid @@ -57,7 +57,6 @@ from ConfigParser import ConfigParser from fnmatch import fnmatch from gzip import GzipFile from optparse import OptionParser, SUPPRESS_HELP -from StringIO import StringIO from yum import repoMDObject #imports for LiveCD, LiveMedia, and Appliance handler @@ -2406,18 +2405,39 @@ class BuildLiveMediaTask(BuildImageTask): bld_info = self.initImageBuild(name, version, release, target_info, opts) subtasks = {} + canfail = [] for arch in arches: subtasks[arch] = self.subtask('createLiveMedia', [name, version, release, arch, target_info, build_tag, repo_info, ksfile, opts], label='livemedia %s' % arch, arch=arch) + if arch in opts.get('optional_arches', []): + canfail.append(subtasks[arch]) + self.logger.debug("Tasks that can fail: %r", canfail) self.logger.debug("Got image subtasks: %r", subtasks) self.logger.debug("Waiting on livemedia subtasks...") - results = self.wait(subtasks.values(), all=True, failany=True) + results = self.wait(subtasks.values(), all=True, failany=True, canfail=canfail) + # if everything failed, fail even if all subtasks are in canfail self.logger.debug('subtask results: %r', results) + all_failed = True + for result in results.values(): + if not isinstance(result, dict) or 'faultCode' not in result: + all_failed = False + break + if all_failed: + raise koji.GenericError("all subtasks failed") + + # determine ignored arch failures + ignored_arches = set() + for arch in arches: + if arch in opts.get('optional_arches', []): + task_id = subtasks[arch] + result = results[task_id] + if isinstance(result, dict) and 'faultCode' in result: + ignored_arches.add(arch) # wrap each image an RPM if needed spec_url = opts.get('specfile') @@ -2427,6 +2447,8 @@ class BuildLiveMediaTask(BuildImageTask): subtask_id = subtasks[arch] result = results[subtask_id] tinfo = self.session.getTaskInfo(subtask_id) + if arch in ignored_arches: + continue arglist = [spec_url, target_info, bld_info, tinfo, {'repo_id': repo_info['id']}] wrapper_tasks[arch] = self.subtask('wrapperRPM', arglist, @@ -2437,6 +2459,8 @@ class BuildLiveMediaTask(BuildImageTask): # add wrapper rpm results into main results for arch in arches: + if arch in ignored_arches: + continue result = results[subtasks[arch]] result2 = results2[wrapper_tasks[arch]] result['rpmresults'] = result2 diff --git a/cli/koji b/cli/koji index 17430780..d1d70c5f 100755 --- a/cli/koji +++ b/cli/koji @@ -5477,12 +5477,15 @@ def handle_spin_livemedia(options, session, args): help=_("SCM URL to spec file fragment to use to generate wrapper RPMs")) parser.add_option("--skip-tag", action="store_true", help=_("Do not attempt to tag package")) + parser.add_option("--can-fail", action="store", dest="optional_arches", + metavar="ARCH1,ARCH2,...", default="", + help=_("List of archs which are not blocking for build (separated by commas.")) (task_options, args) = parser.parse_args(args) # Make sure the target and kickstart is specified. if len(args) != 5: - parser.error(_("Five arguments are required: a name, a version, an" + - " architecture, a build target, and a relative path to" + + parser.error(_("Five arguments are required: a name, a version, a" + + " build target, an architecture, and a relative path to" + " a kickstart file.")) assert False # pragma: no cover _build_image(options, task_options, session, args, 'livemedia') @@ -5743,6 +5746,9 @@ def handle_image_build(options, session, args): help=_("Create a scratch image")) parser.add_option("--skip-tag", action="store_true", help=_("Do not attempt to tag package")) + parser.add_option("--can-fail", action="store", dest="optional_arches", + metavar="ARCH1,ARCH2,...", default="", + help=_("List of archs which are not blocking for build (separated by commas.")) parser.add_option("--specfile", metavar="URL", help=_("SCM URL to spec file fragment to use to generate wrapper RPMs")) parser.add_option("--wait", action="store_true", @@ -5853,12 +5859,13 @@ def _build_image(options, task_opts, session, args, img_type): ksfile = os.path.join(serverdir, os.path.basename(ksfile)) print - passthru_opts = [ - 'isoname', 'ksurl', 'ksversion', 'scratch', 'repo', - 'release', 'skip_tag', 'vmem', 'vcpu', 'format', 'specfile', - 'title', 'install_tree_url', - ] hub_opts = {} + hub_opts['optional_arches'] = task_opts.optional_arches.split(',') + passthru_opts = [ + 'format', 'install_tree_url', 'isoname', 'ksurl', + 'ksversion', 'release', 'repo', 'scratch', 'skip_tag', + 'specfile', 'title', 'vcpu', 'vmem', + ] for opt in passthru_opts: val = getattr(task_opts, opt, None) if val is not None: diff --git a/hub/kojihub.py b/hub/kojihub.py index 6ef55214..521442d6 100644 --- a/hub/kojihub.py +++ b/hub/kojihub.py @@ -400,7 +400,7 @@ class Task(object): params, method = xmlrpclib.loads(xml_request) return params - def getResult(self): + def getResult(self, raise_fault=True): query = """SELECT state,result FROM task WHERE id = %(id)i""" r = _fetchSingle(query, vars(self)) if not r: @@ -410,17 +410,20 @@ class Task(object): raise koji.GenericError, "Task %i is canceled" % self.id elif koji.TASK_STATES[state] not in ['CLOSED', 'FAILED']: raise koji.GenericError, "Task %i is not finished" % self.id - # If the result is a Fault, then loads will raise it - # This is probably what we want to happen. - # Note that you can't really 'return' a fault over xmlrpc, you - # can only 'raise' them. - # If you try to return a fault as a value, it gets reduced to - # a mere struct. - # f = Fault(1,"hello"); print dumps((f,)) if xml_result.find('