pungi/tests/test_osbuild_phase.py
Ondřej Budai 779793386c osbuild: add support for building ostree artifacts
In order to start building Fedora IoT images with osbuild, we need to be able
to pass ostree options from pungi to the koji's osbuildImage task.

This commit adds support for it via new configuration options: ostree_url,
ostree_url and ostree_parent.

A test was added to cover these new options and they are were also added
into the documentation.

JIRA: COMPOSER-1702
Merges: https://pagure.io/pungi/pull-request/1624
Signed-off-by: Ondřej Budai <ondrej@budai.cz>
2022-08-25 15:35:17 +02:00

557 lines
19 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",
"distro": "rhel-8",
"version": "1",
"target": "image-target",
"arches": ["x86_64"],
"failable": ["x86_64"],
"image_types": ["qcow2"],
}
compose = helpers.DummyCompose(
self.topdir, {"osbuild": {"^Everything$": [cfg]}}
)
self.assertValidConfig(compose.conf)
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",
"distro": "rhel-8",
"image_types": ["qcow2"],
}
compose = helpers.DummyCompose(
self.topdir,
{
"osbuild": {"^Everything$": [cfg]},
"osbuild_target": "image-target",
"osbuild_version": "1",
"osbuild_release": "2",
},
)
self.assertValidConfig(compose.conf)
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": "rhel-8", "image_types": ["qcow2"]}
build_id = 5678
koji = KojiWrapper.return_value
koji.watch_task.side_effect = self.make_fake_watch(0)
koji.koji_proxy.osbuildImage.return_value = 1234
koji.koji_proxy.getTaskResult.return_value = {
"composer": {"server": "https://composer.osbuild.org", "id": ""},
"koji": {"build": build_id},
}
koji.koji_proxy.getBuild.return_value = {
"build_id": build_id,
"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", # version
"15", # release
"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",
"rhel-8",
["qcow2"],
"image-target",
["aarch64", "x86_64"],
opts={
"release": "15",
"repo": [self.topdir + "/compose/Everything/$arch/os"],
},
),
mock.call.save_task_id(1234),
mock.call.watch_task(1234, mock.ANY),
mock.call.koji_proxy.getTaskResult(1234),
mock.call.koji_proxy.getBuild(build_id),
mock.call.koji_proxy.listArchives(buildID=build_id),
],
)
# 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.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_ostree(self, KojiWrapper, Linker):
cfg = {
"name": "test-image",
"distro": "rhel-8",
"image_types": ["edge-raw-disk"],
"ostree_url": "http://edge.example.com/repo",
"ostree_ref": "test/iot",
"ostree_parent": "test/iot-parent",
}
build_id = 5678
koji = KojiWrapper.return_value
koji.watch_task.side_effect = self.make_fake_watch(0)
koji.koji_proxy.osbuildImage.return_value = 1234
koji.koji_proxy.getTaskResult.return_value = {
"composer": {"server": "https://composer.osbuild.org", "id": ""},
"koji": {"build": build_id},
}
koji.koji_proxy.getBuild.return_value = {
"build_id": build_id,
"name": "test-image",
"version": "1",
"release": "1",
}
koji.koji_proxy.listArchives.return_value = [
{
"extra": {"image": {"arch": "aarch64"}},
"filename": "image.aarch64.raw.xz",
"type_name": "raw-xz",
},
{
"extra": {"image": {"arch": "x86_64"}},
"filename": "image.x86_64.raw.xz",
"type_name": "raw-xz",
},
]
koji.koji_module.pathinfo = orig_koji.pathinfo
self.t.process(
(
self.compose,
self.compose.variants["Everything"],
cfg,
["aarch64", "x86_64"],
"1", # version
"15", # release
"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",
"rhel-8",
["edge-raw-disk"],
"image-target",
["aarch64", "x86_64"],
opts={
"release": "15",
"repo": [self.topdir + "/compose/Everything/$arch/os"],
"ostree": {
"url": "http://edge.example.com/repo",
"ref": "test/iot",
"parent": "test/iot-parent",
},
},
),
mock.call.save_task_id(1234),
mock.call.watch_task(1234, mock.ANY),
mock.call.koji_proxy.getTaskResult(1234),
mock.call.koji_proxy.getBuild(build_id),
mock.call.koji_proxy.listArchives(buildID=build_id),
],
)
# 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/image.%(arch)s.raw.xz"
% {"arch": image.arch},
image.path,
)
self.assertEqual("raw.xz", image.format)
self.assertEqual("raw-xz", 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/image.%(arch)s.raw.xz"
% {"arch": arch},
self.topdir
+ "/compose/Everything/%(arch)s/images/image.%(arch)s.raw.xz"
% {"arch": arch},
link_type="hardlink-or-copy",
)
for arch in ["aarch64", "x86_64"]
],
)
@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_without_release(self, KojiWrapper, Linker):
cfg = {"name": "test-image", "distro": "rhel-8", "image_types": ["qcow2"]}
build_id = 5678
koji = KojiWrapper.return_value
koji.watch_task.side_effect = self.make_fake_watch(0)
koji.koji_proxy.osbuildImage.return_value = 1234
koji.koji_proxy.getTaskResult.return_value = {
"composer": {"server": "https://composer.osbuild.org", "id": ""},
"koji": {"build": build_id},
}
koji.koji_proxy.getBuild.return_value = {
"build_id": build_id,
"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",
"rhel-8",
["qcow2"],
"image-target",
["aarch64", "x86_64"],
opts={"repo": [self.topdir + "/compose/Everything/$arch/os"]},
),
mock.call.save_task_id(1234),
mock.call.watch_task(1234, mock.ANY),
mock.call.koji_proxy.getTaskResult(1234),
mock.call.koji_proxy.getBuild(build_id),
mock.call.koji_proxy.listArchives(buildID=build_id),
],
)
# 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": "rhel-8", "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"],
False,
),
1,
)
@mock.patch("pungi.phases.osbuild.kojiwrapper.KojiWrapper")
def test_task_fails_but_is_failable(self, KojiWrapper):
cfg = {
"name": "test-image",
"distro": "rhel-8",
"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"],
True,
),
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)