image-build: Allow failure only on some arches

This uses the --can-fail option in koji. Failing an optional image will
not abort whole task. If the whole task fails (or there is a problem on
the compose side), we abort unless all arches are optional.

Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com>
This commit is contained in:
Lubomír Sedlář 2016-11-28 15:28:00 +01:00
parent 7f56b978ce
commit 356b78d440
3 changed files with 59 additions and 16 deletions

View File

@ -120,6 +120,12 @@ class ImageBuildPhase(base.PhaseLoggerMixin, base.ImageConfigMixin, base.ConfigG
image_conf["image-build"]["format"] = ",".join([x[0] for x in image_conf["image-build"]["format"]]) image_conf["image-build"]["format"] = ",".join([x[0] for x in image_conf["image-build"]["format"]])
image_conf["image-build"]['repo'] = self._get_repo(image_conf['image-build'], variant) image_conf["image-build"]['repo'] = self._get_repo(image_conf['image-build'], variant)
can_fail = image_conf['image-build'].pop('failable', [])
if can_fail == ['*']:
can_fail = image_conf['image-build']['arches']
if can_fail:
image_conf['image-build']['can_fail'] = ','.join(sorted(can_fail))
cmd = { cmd = {
"format": format, "format": format,
"image_conf": image_conf, "image_conf": image_conf,
@ -134,7 +140,6 @@ class ImageBuildPhase(base.PhaseLoggerMixin, base.ImageConfigMixin, base.ConfigG
), ),
"link_type": self.compose.conf["link_type"], "link_type": self.compose.conf["link_type"],
"scratch": image_conf['image-build'].pop('scratch', False), "scratch": image_conf['image-build'].pop('scratch', False),
"failable_arches": image_conf['image-build'].pop('failable', []),
} }
self.pool.add(CreateImageBuildThread(self.pool)) self.pool.add(CreateImageBuildThread(self.pool))
self.pool.queue_put((self.compose, cmd)) self.pool.queue_put((self.compose, cmd))
@ -150,15 +155,15 @@ class CreateImageBuildThread(WorkerThread):
compose, cmd = item compose, cmd = item
variant = cmd["image_conf"]["image-build"]["variant"] variant = cmd["image_conf"]["image-build"]["variant"]
subvariant = cmd["image_conf"]["image-build"].get("subvariant", variant.uid) subvariant = cmd["image_conf"]["image-build"].get("subvariant", variant.uid)
failable_arches = cmd.get('failable_arches', []) self.failable_arches = cmd["image_conf"]['image-build'].get('can_fail', '')
self.can_fail = bool(failable_arches) self.can_fail = self.failable_arches == cmd['image_conf']['image-build']['arches']
# TODO handle failure per architecture; currently not possible in single task
with failable(compose, self.can_fail, variant, '*', 'image-build', subvariant, with failable(compose, self.can_fail, variant, '*', 'image-build', subvariant,
logger=self.pool._logger): logger=self.pool._logger):
self.worker(num, compose, variant, subvariant, cmd) self.worker(num, compose, variant, subvariant, cmd)
def worker(self, num, compose, variant, subvariant, cmd): def worker(self, num, compose, variant, subvariant, cmd):
arches = cmd["image_conf"]["image-build"]['arches'].split(',') arches = cmd["image_conf"]["image-build"]['arches'].split(',')
failable_arches = self.failable_arches.split(',')
dash_arches = '-'.join(arches) dash_arches = '-'.join(arches)
log_file = compose.paths.log.log_file( log_file = compose.paths.log.log_file(
dash_arches, dash_arches,
@ -202,7 +207,7 @@ class CreateImageBuildThread(WorkerThread):
image_infos.append({'path': path, 'suffix': suffix, 'type': format, 'arch': arch}) image_infos.append({'path': path, 'suffix': suffix, 'type': format, 'arch': arch})
break break
if len(image_infos) != len(cmd['format']) * len(arches): if len(image_infos) != len(cmd['format']) * (len(arches) - len(failable_arches)):
self.pool.log_error( self.pool.log_error(
"Error in koji task %s. Expected to find same amount of images " "Error in koji task %s. Expected to find same amount of images "
"as in suffixes attr in image-build (%s) for each arch (%s). Got '%s'." % "as in suffixes attr in image-build (%s) for each arch (%s). Got '%s'." %

View File

@ -190,7 +190,7 @@ class KojiWrapper(object):
cmd.append('--release=%s' % options['release']) cmd.append('--release=%s' % options['release'])
if 'can_fail' in options: if 'can_fail' in options:
cmd.append('--can-fail=%s' % options['can_fail']) cmd.append('--can-fail=%s' % ','.join(options['can_fail']))
if wait: if wait:
cmd.append('--wait') cmd.append('--wait')

View File

@ -70,6 +70,7 @@ class TestImageBuildPhase(PungiTestCase):
'version': 'Rawhide', 'version': 'Rawhide',
'ksurl': 'git://git.fedorahosted.org/git/spin-kickstarts.git', 'ksurl': 'git://git.fedorahosted.org/git/spin-kickstarts.git',
'distro': 'Fedora-20', 'distro': 'Fedora-20',
'can_fail': 'x86_64',
} }
}, },
"conf_file": self.topdir + '/work/image-build/Client/docker_Fedora-Docker-Base.cfg', "conf_file": self.topdir + '/work/image-build/Client/docker_Fedora-Docker-Base.cfg',
@ -77,7 +78,6 @@ class TestImageBuildPhase(PungiTestCase):
"relative_image_dir": 'Client/%(arch)s/images', "relative_image_dir": 'Client/%(arch)s/images',
"link_type": 'hardlink-or-copy', "link_type": 'hardlink-or-copy',
"scratch": False, "scratch": False,
"failable_arches": ['x86_64'],
} }
server_args = { server_args = {
"format": [('docker', 'tar.xz')], "format": [('docker', 'tar.xz')],
@ -95,6 +95,7 @@ class TestImageBuildPhase(PungiTestCase):
'version': 'Rawhide', 'version': 'Rawhide',
'ksurl': 'git://git.fedorahosted.org/git/spin-kickstarts.git', 'ksurl': 'git://git.fedorahosted.org/git/spin-kickstarts.git',
'distro': 'Fedora-20', 'distro': 'Fedora-20',
'can_fail': 'x86_64',
} }
}, },
"conf_file": self.topdir + '/work/image-build/Server/docker_Fedora-Docker-Base.cfg', "conf_file": self.topdir + '/work/image-build/Server/docker_Fedora-Docker-Base.cfg',
@ -102,7 +103,6 @@ class TestImageBuildPhase(PungiTestCase):
"relative_image_dir": 'Server/%(arch)s/images', "relative_image_dir": 'Server/%(arch)s/images',
"link_type": 'hardlink-or-copy', "link_type": 'hardlink-or-copy',
"scratch": False, "scratch": False,
"failable_arches": ['x86_64'],
} }
self.assertItemsEqual(phase.pool.queue_put.mock_calls, self.assertItemsEqual(phase.pool.queue_put.mock_calls,
[mock.call((compose, client_args)), [mock.call((compose, client_args)),
@ -163,7 +163,6 @@ class TestImageBuildPhase(PungiTestCase):
"relative_image_dir": 'Server/%(arch)s/images', "relative_image_dir": 'Server/%(arch)s/images',
"link_type": 'hardlink-or-copy', "link_type": 'hardlink-or-copy',
"scratch": False, "scratch": False,
"failable_arches": [],
} }
self.assertItemsEqual(phase.pool.queue_put.mock_calls, self.assertItemsEqual(phase.pool.queue_put.mock_calls,
[mock.call((compose, server_args))]) [mock.call((compose, server_args))])
@ -220,7 +219,6 @@ class TestImageBuildPhase(PungiTestCase):
"relative_image_dir": 'Server/%(arch)s/images', "relative_image_dir": 'Server/%(arch)s/images',
"link_type": 'hardlink-or-copy', "link_type": 'hardlink-or-copy',
"scratch": False, "scratch": False,
"failable_arches": [],
} }
self.assertItemsEqual(phase.pool.queue_put.mock_calls, self.assertItemsEqual(phase.pool.queue_put.mock_calls,
[mock.call((compose, server_args))]) [mock.call((compose, server_args))])
@ -318,7 +316,6 @@ class TestImageBuildPhase(PungiTestCase):
"relative_image_dir": 'Server/%(arch)s/images', "relative_image_dir": 'Server/%(arch)s/images',
"link_type": 'hardlink-or-copy', "link_type": 'hardlink-or-copy',
"scratch": False, "scratch": False,
"failable_arches": [],
}) })
@mock.patch('pungi.phases.image_build.ThreadPool') @mock.patch('pungi.phases.image_build.ThreadPool')
@ -383,7 +380,6 @@ class TestImageBuildPhase(PungiTestCase):
"relative_image_dir": 'Server/%(arch)s/images', "relative_image_dir": 'Server/%(arch)s/images',
"link_type": 'hardlink-or-copy', "link_type": 'hardlink-or-copy',
"scratch": False, "scratch": False,
"failable_arches": [],
}) })
@mock.patch('pungi.phases.image_build.ThreadPool') @mock.patch('pungi.phases.image_build.ThreadPool')
@ -445,7 +441,6 @@ class TestImageBuildPhase(PungiTestCase):
"relative_image_dir": 'Server/%(arch)s/images', "relative_image_dir": 'Server/%(arch)s/images',
"link_type": 'hardlink-or-copy', "link_type": 'hardlink-or-copy',
"scratch": False, "scratch": False,
"failable_arches": [],
}) })
@mock.patch('pungi.phases.image_build.ThreadPool') @mock.patch('pungi.phases.image_build.ThreadPool')
@ -612,6 +607,7 @@ class TestImageBuildPhase(PungiTestCase):
'version': 'Rawhide', 'version': 'Rawhide',
'ksurl': 'git://git.fedorahosted.org/git/spin-kickstarts.git', 'ksurl': 'git://git.fedorahosted.org/git/spin-kickstarts.git',
'distro': 'Fedora-20', 'distro': 'Fedora-20',
'can_fail': 'x86_64',
} }
}, },
"conf_file": self.topdir + '/work/image-build/Server-optional/docker_Fedora-Docker-Base.cfg', "conf_file": self.topdir + '/work/image-build/Server-optional/docker_Fedora-Docker-Base.cfg',
@ -619,7 +615,6 @@ class TestImageBuildPhase(PungiTestCase):
"relative_image_dir": 'Server-optional/%(arch)s/images', "relative_image_dir": 'Server-optional/%(arch)s/images',
"link_type": 'hardlink-or-copy', "link_type": 'hardlink-or-copy',
"scratch": False, "scratch": False,
"failable_arches": ['x86_64'],
} }
self.assertItemsEqual(phase.pool.queue_put.mock_calls, self.assertItemsEqual(phase.pool.queue_put.mock_calls,
[mock.call((compose, server_args))]) [mock.call((compose, server_args))])
@ -781,6 +776,7 @@ class TestCreateImageBuildThread(PungiTestCase):
'version': 'Rawhide', 'version': 'Rawhide',
'ksurl': 'git://git.fedorahosted.org/git/spin-kickstarts.git', 'ksurl': 'git://git.fedorahosted.org/git/spin-kickstarts.git',
'distro': 'Fedora-20', 'distro': 'Fedora-20',
"can_fail": 'amd64,x86_64',
} }
}, },
"conf_file": 'amd64,x86_64-Client-Fedora-Docker-Base-docker', "conf_file": 'amd64,x86_64-Client-Fedora-Docker-Base-docker',
@ -788,7 +784,6 @@ class TestCreateImageBuildThread(PungiTestCase):
"relative_image_dir": 'image_dir/Client/%(arch)s', "relative_image_dir": 'image_dir/Client/%(arch)s',
"link_type": 'hardlink-or-copy', "link_type": 'hardlink-or-copy',
'scratch': False, 'scratch': False,
"failable_arches": ['*'],
} }
koji_wrapper = KojiWrapper.return_value koji_wrapper = KojiWrapper.return_value
koji_wrapper.run_blocking_cmd.return_value = { koji_wrapper.run_blocking_cmd.return_value = {
@ -829,6 +824,7 @@ class TestCreateImageBuildThread(PungiTestCase):
'version': 'Rawhide', 'version': 'Rawhide',
'ksurl': 'git://git.fedorahosted.org/git/spin-kickstarts.git', 'ksurl': 'git://git.fedorahosted.org/git/spin-kickstarts.git',
'distro': 'Fedora-20', 'distro': 'Fedora-20',
'can_fail': 'amd64,x86_64',
} }
}, },
"conf_file": 'amd64,x86_64-Client-Fedora-Docker-Base-docker', "conf_file": 'amd64,x86_64-Client-Fedora-Docker-Base-docker',
@ -836,7 +832,6 @@ class TestCreateImageBuildThread(PungiTestCase):
"relative_image_dir": 'image_dir/Client/%(arch)s', "relative_image_dir": 'image_dir/Client/%(arch)s',
"link_type": 'hardlink-or-copy', "link_type": 'hardlink-or-copy',
'scratch': False, 'scratch': False,
"failable_arches": ['*'],
} }
koji_wrapper = KojiWrapper.return_value koji_wrapper = KojiWrapper.return_value
@ -851,6 +846,49 @@ class TestCreateImageBuildThread(PungiTestCase):
mock.call('BOOM'), mock.call('BOOM'),
]) ])
@mock.patch('pungi.phases.image_build.KojiWrapper')
@mock.patch('pungi.phases.image_build.Linker')
def test_process_handle_fail_only_one_optional(self, Linker, KojiWrapper):
compose = DummyCompose(self.topdir, {'koji_profile': 'koji'})
pool = mock.Mock()
cmd = {
"format": [('docker', 'tar.xz'), ('qcow2', 'qcow2')],
"image_conf": {
'image-build': {
'install_tree': '/ostree/$arch/Client',
'kickstart': 'fedora-docker-base.ks',
'format': 'docker',
'repo': '/ostree/$arch/Client',
'variant': compose.variants['Client'],
'target': 'f24',
'disk_size': 3,
'name': 'Fedora-Docker-Base',
'arches': 'amd64,x86_64',
'version': 'Rawhide',
'ksurl': 'git://git.fedorahosted.org/git/spin-kickstarts.git',
'distro': 'Fedora-20',
'can_fail': 'amd64',
}
},
"conf_file": 'amd64,x86_64-Client-Fedora-Docker-Base-docker',
"image_dir": '/image_dir/Client/%(arch)s',
"relative_image_dir": 'image_dir/Client/%(arch)s',
"link_type": 'hardlink-or-copy',
'scratch': False,
}
koji_wrapper = KojiWrapper.return_value
koji_wrapper.run_blocking_cmd.return_value = {
"retcode": 1,
"output": None,
"task_id": 1234,
}
t = CreateImageBuildThread(pool)
with self.assertRaises(RuntimeError):
with mock.patch('time.sleep'):
t.process((compose, cmd), 1)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()