From 8af934d5a3a459e2f0914c2f77923c7f50bfac16 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Jan 12 2017 09:52:31 +0000 Subject: [PATCH 1/15] canfail option for BaseTaskHandler.wait() --- diff --git a/koji/tasks.py b/koji/tasks.py index b8454eb..cc2c5fd 100644 --- a/koji/tasks.py +++ b/koji/tasks.py @@ -187,15 +187,20 @@ class BaseTaskHandler(object): safe_rmtree(self.workdir, unmount=False, strict=True) #os.spawnvp(os.P_WAIT, 'rm', ['rm', '-rf', self.workdir]) - def wait(self, subtasks=None, all=False, failany=False): + def wait(self, subtasks=None, all=False, failany=False, canfail=None): """Wait on subtasks subtasks is a list of integers (or an integer). If more than one subtask is specified, then the default behavior is to return when any of those tasks complete. However, if all is set to True, then it waits for all of - them to complete. If all and failany are both set to True, then each - finished task will be checked for failure, and a failure will cause all - of the unfinished tasks to be cancelled. + them to complete. + + If all and failany are both set to True, then each finished task will + be checked for failure, and a failure will cause all of the unfinished + tasks to be cancelled. + + If canfail is given a list of task ids, then those tasks can fail + without affecting the other tasks. special values: subtasks = None specify all subtasks @@ -206,6 +211,9 @@ class BaseTaskHandler(object): the database and will send the subprocess corresponding to the subtask a SIGUSR2 to wake it up when subtasks complete. """ + + if canfail is None: + canfail = [] if isinstance(subtasks, int): # allow single integer w/o enclosing list subtasks = [subtasks] @@ -221,6 +229,9 @@ class BaseTaskHandler(object): if failany: failed = False for task in finished: + if task['id'] in canfail: + # no point in checking + continue try: self.session.getTaskResult(task) except (koji.GenericError, xmlrpclib.Fault), task_error: From a236c18b26eb401df01179fda0a2099abcac9b6e Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Jan 12 2017 09:52:31 +0000 Subject: [PATCH 2/15] support optional_arches in livemedia builds --- diff --git a/builder/kojid b/builder/kojid index 446ab2e..5f46e65 100755 --- a/builder/kojid +++ b/builder/kojid @@ -2406,16 +2406,19 @@ 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("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) self.logger.debug('subtask results: %r', results) From 8649ec2f3cb73f85dea42283752b7ed66feaa087 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Jan 12 2017 09:52:31 +0000 Subject: [PATCH 3/15] adjustments to WaitTestTask --- diff --git a/koji/tasks.py b/koji/tasks.py index cc2c5fd..b540456 100644 --- a/koji/tasks.py +++ b/koji/tasks.py @@ -404,14 +404,13 @@ class WaitTestTask(BaseTaskHandler): Methods = ['waittest'] _taskWeight = 0.1 def handler(self, count, seconds=10): - tasks = [] for i in xrange(count): task_id = self.session.host.subtask(method='sleep', arglist=[seconds], label=str(i), parent=self.id) - tasks.append(task_id) - results = self.wait(all=True) + bad_task = self.subtask('sleep', ['BAD_ARG'], label='bad') + results = self.wait(all=True, failany=True, canfail=[bad_task]) self.logger.info(pprint.pformat(results)) From 4510417729f96491afcce4f79c57387867c0e450 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Jan 12 2017 09:52:31 +0000 Subject: [PATCH 4/15] fix ref to task id --- diff --git a/koji/tasks.py b/koji/tasks.py index b540456..00bcd8e 100644 --- a/koji/tasks.py +++ b/koji/tasks.py @@ -229,7 +229,7 @@ class BaseTaskHandler(object): if failany: failed = False for task in finished: - if task['id'] in canfail: + if task in canfail: # no point in checking continue try: From 930ec4ca34cdbf539790729c216182bb14137f5f Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Jan 12 2017 09:52:31 +0000 Subject: [PATCH 5/15] don't error when waiting on 'canfail' tasks --- diff --git a/hub/kojihub.py b/hub/kojihub.py index 6ef5521..37f76a1 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(' Date: Jan 12 2017 09:52:31 +0000 Subject: [PATCH 6/15] ... partial.... --- diff --git a/builder/kojid b/builder/kojid index 5f46e65..44aba22 100755 --- a/builder/kojid +++ b/builder/kojid @@ -2422,6 +2422,15 @@ class BuildLiveMediaTask(BuildImageTask): self.logger.debug('subtask results: %r', results) + # 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') if spec_url: @@ -2430,6 +2439,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, @@ -2440,6 +2451,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 From 034fd4dca9a922b4a273db841e9431e9e93756e2 Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jan 12 2017 09:52:31 +0000 Subject: [PATCH 7/15] update tests --- diff --git a/tests/test_tasks.py b/tests/test_tasks.py index 6fc50a6..c85a689 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -1,14 +1,15 @@ import random +from io import StringIO +from os import path, makedirs +from shutil import rmtree +from tempfile import gettempdir from unittest import TestCase from mock import patch, Mock, call -from tempfile import gettempdir -from shutil import rmtree -from os import path, makedirs -from io import StringIO import koji -from koji.tasks import scan_mounts, umount_all, safe_rmtree, BaseTaskHandler, FakeTask, SleepTask, ForkTask -from koji import BuildError, GenericError +from koji.tasks import BaseTaskHandler, FakeTask, ForkTask, SleepTask,\ + scan_mounts, umount_all, safe_rmtree + def get_fake_mounts_file(): """ Returns contents of /prc/mounts in a file-like object @@ -271,7 +272,7 @@ class TasksTestCase(TestCase): obj.session.host.taskWaitResults.return_value = taskWaitResults self.assertEquals(obj.wait([1551234, 1591234]), dict(taskWaitResults)) obj.session.host.taskSetWait.assert_called_once_with(12345678, [1551234, 1591234]) - obj.session.host.taskWaitResults.assert_called_once_with(12345678, [1551234, 1591234]) + obj.session.host.taskWaitResults.assert_called_once_with(12345678, [1551234, 1591234], canfail=[]) def test_BaseTaskHandler_wait_some_not_done(self): """ Tests that the wait function returns the one finished subtask results of @@ -296,7 +297,7 @@ class TasksTestCase(TestCase): obj.session.host.taskWaitResults.return_value = taskWaitResults self.assertEquals(obj.wait([1551234, 1591234]), dict(taskWaitResults)) obj.session.host.taskSetWait.assert_called_once_with(12345678, [1551234, 1591234]) - obj.session.host.taskWaitResults.assert_called_once_with(12345678, [1551234]) + obj.session.host.taskWaitResults.assert_called_once_with(12345678, [1551234], canfail=[]) @patch('signal.pause', return_value=None) def test_BaseTaskHandler_wait_some_not_done_all_set(self, mock_signal_pause): @@ -336,7 +337,7 @@ class TasksTestCase(TestCase): obj.session.host.taskSetWait.assert_called_once_with(12345678, [1551234, 1591234]) obj.session.host.taskWait.assert_has_calls([call(12345678), call(12345678)]) mock_signal_pause.assert_called_once_with() - obj.session.host.taskWaitResults.assert_called_once_with(12345678, [1551234, 1591234]) + obj.session.host.taskWaitResults.assert_called_once_with(12345678, [1551234, 1591234], canfail=[]) def test_BaseTaskHandler_wait_some_not_done_all_set_failany_set_failed_task(self): """ Tests that the wait function raises an exception when one of the subtask fails when the failany flag is set @@ -348,11 +349,11 @@ class TasksTestCase(TestCase): obj.session = Mock() obj.session.host.taskSetWait.return_value = None obj.session.host.taskWait.side_effect = [[[1551234], [1591234]], [[1551234, 1591234], []]] - obj.session.getTaskResult.side_effect = GenericError('Uh oh, we\'ve got a problem here!') + obj.session.getTaskResult.side_effect = koji.GenericError('Uh oh, we\'ve got a problem here!') try: obj.wait([1551234, 1591234], all=True, failany=True) raise Exception('A GeneralError was not raised.') - except GenericError as e: + except koji.GenericError as e: self.assertEquals(e.message, 'Uh oh, we\'ve got a problem here!') obj.session.host.taskSetWait.assert_called_once_with(12345678, [1551234, 1591234]) @@ -509,7 +510,7 @@ class TasksTestCase(TestCase): try: obj.find_arch('noarch', host, None) raise Exception('The BuildError Exception was not raised') - except BuildError as e: + except koji.BuildError as e: self.assertEquals(e.message, 'No arch list for this host: test.domain.local') def test_BaseTaskHandler_find_arch_noarch_bad_tag(self): @@ -524,7 +525,7 @@ class TasksTestCase(TestCase): try: obj.find_arch('noarch', host, tag) raise Exception('The BuildError Exception was not raised') - except BuildError as e: + except koji.BuildError as e: self.assertEquals(e.message, 'No arch list for tag: some_package-1.2-build') def test_BaseTaskHandler_find_arch_noarch(self): @@ -550,7 +551,7 @@ class TasksTestCase(TestCase): try: obj.find_arch('noarch', host, tag) raise Exception('The BuildError Exception was not raised') - except BuildError as e: + except koji.BuildError as e: self.assertEquals(e.message, ('host test.domain.local (i386) does not support ' 'any arches of tag some_package-1.2-build (aarch64, x86_64)')) @@ -646,7 +647,7 @@ class TasksTestCase(TestCase): try: obj.getRepo(8472) raise Exception('The BuildError Exception was not raised') - except BuildError as e: + except koji.BuildError as e: obj.session.getRepo.assert_called_once_with(8472) self.assertEquals(e.message, 'no repo (and no target) for tag rhel-7.3-build') From fab68b5c1972bf7194d7e2c8039d466e267bda6f Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jan 12 2017 09:52:31 +0000 Subject: [PATCH 8/15] Basic test for task canfail --- diff --git a/koji/tasks.py b/koji/tasks.py index 82f29a7..531591c 100644 --- a/koji/tasks.py +++ b/koji/tasks.py @@ -402,16 +402,23 @@ class ForkTask(BaseTaskHandler): os.spawnvp(os.P_NOWAIT, 'sleep', ['sleep', str(m)]) class WaitTestTask(BaseTaskHandler): + """ + Tests self.wait() + + Starts few tasks which just sleeps. One of them will fail due to bad + arguments. As it is listed as 'canfail' it shouldn't affect overall + CLOSED status. + """ Methods = ['waittest'] _taskWeight = 0.1 def handler(self, count, seconds=10): + tasks = [] for i in xrange(count): - task_id = self.session.host.subtask(method='sleep', - arglist=[seconds], - label=str(i), - parent=self.id) + task_id = self.subtask(method='sleep', arglist=[seconds], label=str(i), parent=self.id) + tasks.append(task_id) bad_task = self.subtask('sleep', ['BAD_ARG'], label='bad') - results = self.wait(all=True, failany=True, canfail=[bad_task]) + tasks.append(bad_task) + results = self.wait(subtasks=tasks, all=True, failany=True, canfail=[bad_task]) self.logger.info(pprint.pformat(results)) diff --git a/tests/test_tasks.py b/tests/test_tasks.py index c85a689..05661be 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -7,8 +7,9 @@ from unittest import TestCase from mock import patch, Mock, call import koji -from koji.tasks import BaseTaskHandler, FakeTask, ForkTask, SleepTask,\ - scan_mounts, umount_all, safe_rmtree +from koji.tasks import BaseTaskHandler, FakeTask, ForkTask, SleepTask, \ + WaitTestTask, scan_mounts, umount_all, \ + safe_rmtree def get_fake_mounts_file(): @@ -672,3 +673,42 @@ class TasksTestCase(TestCase): obj = ForkTask(123, 'fork', [1, 20], None, None, (get_tmp_dir_path('ForkTask'))) obj.run() mock_spawnvp.assert_called_once_with(1, 'sleep', ['sleep', '20']) + + @patch('signal.pause', return_value=None) + @patch('time.sleep') + def test_WaitTestTask_handler(self, mock_sleep, mock_signal_pause): + """ Tests that the WaitTestTask handler can be instantiated and runs appropriately based on the input + Specifically, that forking works and canfail behaves correctly. + """ + self.mock_subtask_id = 1 + def mock_subtask(method, arglist, id, **opts): + self.assertEqual(method, 'sleep') + task_id = self.mock_subtask_id + self.mock_subtask_id += 1 + obj = SleepTask(task_id, 'sleep', arglist, None, None, (get_tmp_dir_path('SleepTask'))) + obj.run() + return task_id + + mock_taskWait = [ + [[], [1, 2, 3, 4]], + [[3, 4], [1, 2]], + [[1, 2, 3, 4], []], + ] + def mock_getTaskResult(task_id): + if task_id == 4: + raise koji.GenericError() + + + obj = WaitTestTask(123, 'waittest', [3], None, None, (get_tmp_dir_path('WaitTestTask'))) + obj.session = Mock() + obj.session.host.subtask.side_effect = mock_subtask + obj.session.getTaskResult.side_effect = mock_getTaskResult + obj.session.host.taskWait.side_effect = mock_taskWait + obj.session.host.taskWaitResults.return_value = [ ['1', {}], ['2', {}], ['3', {}], ['4', {}], ] + obj.run() + #self.assertEqual(mock_sleep.call_count, 4) + obj.session.host.taskSetWait.assert_called_once() + obj.session.host.taskWait.assert_has_calls([call(123), call(123), call(123)]) + # getTaskResult should be called in 2nd round only for task 3, as 4 + # will be skipped as 'canfail' + obj.session.getTaskResult.assert_has_calls([call(3)]) From 681f78f5a4406b8ca18d948c4b8e4011559acde3 Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jan 12 2017 09:52:31 +0000 Subject: [PATCH 9/15] typo fixes --- diff --git a/hub/kojihub.py b/hub/kojihub.py index 37f76a1..885f52d 100644 --- a/hub/kojihub.py +++ b/hub/kojihub.py @@ -417,7 +417,7 @@ class Task(object): # If the result is a Fault, then loads will raise it # This is normally what we want to happen result, method = xmlrpclib.loads(xml_result) - except Fault, fault: + except xmlrpclib.Fault, fault: if raise_fault: raise # Note that you can't really return a fault over xmlrpc, except by @@ -11121,7 +11121,7 @@ class Host(object): results = [] for task_id in tasks: task = Task(task_id) - raise_fault = (result in canfail) + raise_fault = (task in canfail) results.append([task_id, task.getResult(raise_fault=raise_fault)]) return results @@ -11306,10 +11306,10 @@ class HostExports(object): host.verify() return host.taskWait(parent) - def taskWaitResults(self, parent, tasks): + def taskWaitResults(self, parent, tasks, canfail=None): host = Host() host.verify() - return host.taskWaitResults(parent, tasks) + return host.taskWaitResults(parent, tasks, canfail) def subtask(self, method, arglist, parent, **opts): host = Host() From 2bdb6cea8eccea303cbac0976e3565c5570b275a Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jan 12 2017 09:52:31 +0000 Subject: [PATCH 10/15] remove unused import --- diff --git a/builder/kojid b/builder/kojid index 44aba22..b491554 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 From 11e5d0a106fbde338821ac8ae2ea20a72fb3d3dd Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jan 12 2017 09:52:31 +0000 Subject: [PATCH 11/15] CLI options for failing arches --- diff --git a/cli/koji b/cli/koji index bfa42ca..4cc28df 100755 --- a/cli/koji +++ b/cli/koji @@ -5466,12 +5466,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,...", + 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') @@ -5732,6 +5735,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,...", + 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", @@ -5842,12 +5848,13 @@ def _build_image(options, task_opts, session, args, img_type): ksfile = os.path.join(serverdir, os.path.basename(ksfile)) print + hub_opts = {} + hub_opts['optional_arches'] = task_opts.optional_arches.split(',') passthru_opts = [ - 'isoname', 'ksurl', 'ksversion', 'scratch', 'repo', - 'release', 'skip_tag', 'vmem', 'vcpu', 'format', 'specfile', - 'title', 'install_tree_url', + 'format', 'install_tree_url', 'isoname', 'ksurl', + 'ksversion', 'release', 'repo', 'scratch', 'skip_tag', + 'specfile', 'title', 'vcpu', 'vmem', ] - hub_opts = {} for opt in passthru_opts: val = getattr(task_opts, opt, None) if val is not None: From 1e64621748b3d095119fb41f96f61847ec4a9756 Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jan 12 2017 09:52:31 +0000 Subject: [PATCH 12/15] fix default value for --can-fail --- diff --git a/cli/koji b/cli/koji index 4cc28df..7aa4611 100755 --- a/cli/koji +++ b/cli/koji @@ -5467,7 +5467,7 @@ def handle_spin_livemedia(options, session, args): 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,...", + metavar="ARCH1,ARCH2,...", default="", help=_("List of archs which are not blocking for build (separated by commas.")) (task_options, args) = parser.parse_args(args) @@ -5736,7 +5736,7 @@ def handle_image_build(options, session, args): 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,...", + 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")) From bdd3011004e68313b93a99b564819073ded40f1d Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jan 12 2017 09:52:31 +0000 Subject: [PATCH 13/15] don't try to upload image if task failed --- diff --git a/hub/kojihub.py b/hub/kojihub.py index 885f52d..929d4f1 100644 --- a/hub/kojihub.py +++ b/hub/kojihub.py @@ -11450,6 +11450,9 @@ class HostExports(object): task.assertHost(host.id) logger.debug('scratch image results: %s' % results) for sub_results in results.values(): + if 'task_id' not in sub_results: + logger.warning('Task %s failed, no image available' % task_id) + continue workdir = koji.pathinfo.task(sub_results['task_id']) scratchdir = koji.pathinfo.scratch() username = get_user(task.getOwner())['name'] @@ -11830,6 +11833,9 @@ class HostExports(object): moving the image to its final location. """ for sub_results in results.values(): + if 'task_id' not in sub_results: + logger.warning('Task %s failed, no image available' % task_id) + continue importImageInternal(task_id, build_id, sub_results) if sub_results.has_key('rpmresults'): rpm_results = sub_results['rpmresults'] From 7ff218ac1924970878bd57c75f89dd269906d3cd Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jan 12 2017 09:52:31 +0000 Subject: [PATCH 14/15] fail if all sub-tasks fails --- diff --git a/builder/kojid b/builder/kojid index b491554..9eee5a0 100755 --- a/builder/kojid +++ b/builder/kojid @@ -2421,6 +2421,16 @@ class BuildLiveMediaTask(BuildImageTask): self.logger.debug('subtask results: %r', results) + # 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: From f455bb4d9e4ce6e0afce802b86caa9872a5467b9 Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jan 12 2017 09:52:31 +0000 Subject: [PATCH 15/15] typo --- diff --git a/builder/kojid b/builder/kojid index 9eee5a0..5ab105c 100755 --- a/builder/kojid +++ b/builder/kojid @@ -2419,8 +2419,6 @@ class BuildLiveMediaTask(BuildImageTask): self.logger.debug("Waiting on livemedia subtasks...") results = self.wait(subtasks.values(), all=True, failany=True, canfail=canfail) - self.logger.debug('subtask results: %r', results) - # if everything failed, fail even if all subtasks are in canfail self.logger.debug('subtask results: %r', results) all_failed = True