a45f4969f3
This is similar to image-build in terms of what it does, and somewhat similar to OSBS phase in how it's implemented. The phase reads configuration, submits the build via XMLRPC call and waits for the task to finish. Then it downloads the built image and includes it in the compose metadata. JIRA: RHELCMP-315 Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com>
290 lines
9.4 KiB
Python
290 lines
9.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import mock
|
|
|
|
import os
|
|
|
|
import koji as orig_koji
|
|
|
|
from tests import helpers
|
|
from pungi.phases import osbuild
|
|
|
|
|
|
class OSBuildPhaseTest(helpers.PungiTestCase):
|
|
@mock.patch("pungi.phases.osbuild.ThreadPool")
|
|
def test_run(self, ThreadPool):
|
|
cfg = {
|
|
"name": "test-image",
|
|
"version": "1",
|
|
"target": "image-target",
|
|
"arches": ["x86_64"],
|
|
"failable": ["x86_64"],
|
|
}
|
|
compose = helpers.DummyCompose(
|
|
self.topdir, {"osbuild": {"^Everything$": [cfg]}}
|
|
)
|
|
|
|
pool = ThreadPool.return_value
|
|
|
|
phase = osbuild.OSBuildPhase(compose)
|
|
phase.run()
|
|
|
|
self.assertEqual(len(pool.add.call_args_list), 1)
|
|
self.assertEqual(
|
|
pool.queue_put.call_args_list,
|
|
[
|
|
mock.call(
|
|
(
|
|
compose,
|
|
compose.variants["Everything"],
|
|
cfg,
|
|
["x86_64"],
|
|
"1",
|
|
None,
|
|
"image-target",
|
|
[self.topdir + "/compose/Everything/$arch/os"],
|
|
["x86_64"],
|
|
),
|
|
),
|
|
],
|
|
)
|
|
|
|
@mock.patch("pungi.phases.osbuild.ThreadPool")
|
|
def test_run_with_global_options(self, ThreadPool):
|
|
cfg = {"name": "test-image"}
|
|
compose = helpers.DummyCompose(
|
|
self.topdir,
|
|
{
|
|
"osbuild": {"^Everything$": [cfg]},
|
|
"osbuild_target": "image-target",
|
|
"osbuild_version": "1",
|
|
"osbuild_release": "2",
|
|
},
|
|
)
|
|
|
|
pool = ThreadPool.return_value
|
|
|
|
phase = osbuild.OSBuildPhase(compose)
|
|
phase.run()
|
|
|
|
self.assertEqual(len(pool.add.call_args_list), 1)
|
|
self.assertEqual(
|
|
pool.queue_put.call_args_list,
|
|
[
|
|
mock.call(
|
|
(
|
|
compose,
|
|
compose.variants["Everything"],
|
|
cfg,
|
|
sorted(compose.variants["Everything"].arches),
|
|
"1",
|
|
"2",
|
|
"image-target",
|
|
[self.topdir + "/compose/Everything/$arch/os"],
|
|
[],
|
|
),
|
|
),
|
|
],
|
|
)
|
|
|
|
@mock.patch("pungi.phases.osbuild.ThreadPool")
|
|
def test_skip_without_config(self, ThreadPool):
|
|
compose = helpers.DummyCompose(self.topdir, {})
|
|
compose.just_phases = None
|
|
compose.skip_phases = []
|
|
phase = osbuild.OSBuildPhase(compose)
|
|
self.assertTrue(phase.skip())
|
|
|
|
|
|
class RunOSBuildThreadTest(helpers.PungiTestCase):
|
|
def setUp(self):
|
|
super(RunOSBuildThreadTest, self).setUp()
|
|
self.pool = mock.Mock()
|
|
self.t = osbuild.RunOSBuildThread(self.pool)
|
|
self.compose = helpers.DummyCompose(
|
|
self.topdir,
|
|
{
|
|
"koji_profile": "koji",
|
|
"translate_paths": [(self.topdir, "http://root")],
|
|
},
|
|
)
|
|
|
|
def make_fake_watch(self, retval):
|
|
def inner(task_id, log_file):
|
|
with open(log_file, "w") as f:
|
|
f.write("Creating compose: test-image-1-1 1234\n")
|
|
return retval
|
|
|
|
return inner
|
|
|
|
@mock.patch("pungi.util.get_file_size", new=lambda fp: 65536)
|
|
@mock.patch("pungi.util.get_mtime", new=lambda fp: 1024)
|
|
@mock.patch("pungi.phases.osbuild.Linker")
|
|
@mock.patch("pungi.phases.osbuild.kojiwrapper.KojiWrapper")
|
|
def test_process(self, KojiWrapper, Linker):
|
|
cfg = {"name": "test-image", "distro": "rhel8", "image_types": ["qcow2"]}
|
|
koji = KojiWrapper.return_value
|
|
koji.watch_task.side_effect = self.make_fake_watch(0)
|
|
koji.koji_proxy.osbuildImage.return_value = 1234
|
|
koji.koji_proxy.getBuild.return_value = {
|
|
"build_id": 5678,
|
|
"name": "test-image",
|
|
"version": "1",
|
|
"release": "1",
|
|
}
|
|
koji.koji_proxy.listArchives.return_value = [
|
|
{
|
|
"extra": {"image": {"arch": "aarch64"}},
|
|
"filename": "disk.aarch64.qcow2",
|
|
"type_name": "qcow2",
|
|
},
|
|
{
|
|
"extra": {"image": {"arch": "x86_64"}},
|
|
"filename": "disk.x86_64.qcow2",
|
|
"type_name": "qcow2",
|
|
},
|
|
]
|
|
koji.koji_module.pathinfo = orig_koji.pathinfo
|
|
|
|
self.t.process(
|
|
(
|
|
self.compose,
|
|
self.compose.variants["Everything"],
|
|
cfg,
|
|
["aarch64", "x86_64"],
|
|
"1",
|
|
None,
|
|
"image-target",
|
|
[self.topdir + "/compose/Everything/$arch/os"],
|
|
["x86_64"],
|
|
),
|
|
1,
|
|
)
|
|
|
|
# Verify two Koji instances were created.
|
|
self.assertEqual(len(KojiWrapper.call_args), 2)
|
|
# Verify correct calls to Koji
|
|
self.assertEqual(
|
|
koji.mock_calls,
|
|
[
|
|
mock.call.login(),
|
|
mock.call.koji_proxy.osbuildImage(
|
|
"test-image",
|
|
"1",
|
|
"rhel8",
|
|
["qcow2"],
|
|
"image-target",
|
|
["aarch64", "x86_64"],
|
|
opts={
|
|
"release": None,
|
|
"repo": [self.topdir + "/compose/Everything/$arch/os"],
|
|
},
|
|
),
|
|
mock.call.watch_task(1234, mock.ANY),
|
|
mock.call.koji_proxy.getBuild("test-image-1-1"),
|
|
mock.call.koji_proxy.listArchives(buildID=5678),
|
|
],
|
|
)
|
|
|
|
# Assert there are 2 images added to manifest and the arguments are sane
|
|
self.assertEqual(
|
|
self.compose.im.add.call_args_list,
|
|
[
|
|
mock.call(arch="aarch64", variant="Everything", image=mock.ANY),
|
|
mock.call(arch="x86_64", variant="Everything", image=mock.ANY),
|
|
],
|
|
)
|
|
for call in self.compose.im.add.call_args_list:
|
|
_, kwargs = call
|
|
image = kwargs["image"]
|
|
self.assertEqual(kwargs["variant"], "Everything")
|
|
self.assertIn(kwargs["arch"], ("aarch64", "x86_64"))
|
|
self.assertEqual(kwargs["arch"], image.arch)
|
|
self.assertEqual(
|
|
"Everything/%(arch)s/images/disk.%(arch)s.qcow2" % {"arch": image.arch},
|
|
image.path,
|
|
)
|
|
self.assertEqual("qcow2", image.format)
|
|
self.assertEqual("qcow2", image.type)
|
|
self.assertEqual("Everything", image.subvariant)
|
|
|
|
self.assertTrue(
|
|
os.path.isdir(self.topdir + "/compose/Everything/aarch64/images")
|
|
)
|
|
self.assertTrue(
|
|
os.path.isdir(self.topdir + "/compose/Everything/x86_64/images")
|
|
)
|
|
|
|
self.assertEqual(
|
|
Linker.return_value.mock_calls,
|
|
[
|
|
mock.call.link(
|
|
"/mnt/koji/packages/test-image/1/1/images/disk.%(arch)s.qcow2"
|
|
% {"arch": arch},
|
|
self.topdir
|
|
+ "/compose/Everything/%(arch)s/images/disk.%(arch)s.qcow2"
|
|
% {"arch": arch},
|
|
link_type="hardlink-or-copy",
|
|
)
|
|
for arch in ["aarch64", "x86_64"]
|
|
],
|
|
)
|
|
|
|
@mock.patch("pungi.phases.osbuild.kojiwrapper.KojiWrapper")
|
|
def test_task_fails(self, KojiWrapper):
|
|
cfg = {"name": "test-image", "distro": "rhel8", "image_types": ["qcow2"]}
|
|
koji = KojiWrapper.return_value
|
|
koji.watch_task.side_effect = self.make_fake_watch(1)
|
|
koji.koji_proxy.osbuildImage.return_value = 1234
|
|
|
|
with self.assertRaises(RuntimeError):
|
|
self.t.process(
|
|
(
|
|
self.compose,
|
|
self.compose.variants["Everything"],
|
|
cfg,
|
|
["aarch64", "x86_64"],
|
|
"1",
|
|
None,
|
|
"image-target",
|
|
[self.topdir + "/compose/Everything/$arch/os"],
|
|
["x86_64"],
|
|
),
|
|
1,
|
|
)
|
|
|
|
@mock.patch("pungi.phases.osbuild.kojiwrapper.KojiWrapper")
|
|
def test_task_fails_but_is_failable(self, KojiWrapper):
|
|
cfg = {
|
|
"name": "test-image",
|
|
"distro": "rhel8",
|
|
"image_types": ["qcow2"],
|
|
"failable": ["x86_65"],
|
|
}
|
|
koji = KojiWrapper.return_value
|
|
koji.watch_task.side_effect = self.make_fake_watch(1)
|
|
koji.koji_proxy.osbuildImage.return_value = 1234
|
|
|
|
self.t.process(
|
|
(
|
|
self.compose,
|
|
self.compose.variants["Everything"],
|
|
cfg,
|
|
["aarch64", "x86_64"],
|
|
"1",
|
|
None,
|
|
"image-target",
|
|
[self.topdir + "/compose/Everything/$arch/os"],
|
|
["x86_64"],
|
|
),
|
|
1,
|
|
)
|
|
|
|
self.assertFalse(
|
|
os.path.isdir(self.topdir + "/compose/Everything/aarch64/images")
|
|
)
|
|
self.assertFalse(
|
|
os.path.isdir(self.topdir + "/compose/Everything/x86_64/images")
|
|
)
|
|
self.assertEqual(len(self.compose.im.add.call_args_list), 0)
|