# -*- coding: utf-8 -*- from unittest import mock import six from pungi.phases.live_images import LiveImagesPhase, CreateLiveImageThread from tests.helpers import DummyCompose, PungiTestCase, boom class TestLiveImagesPhase(PungiTestCase): @mock.patch("pungi.phases.live_images.ThreadPool") def test_live_image_build(self, ThreadPool): compose = DummyCompose( self.topdir, { "live_images": [ ( "^Client$", { "amd64": { "kickstart": "test.ks", "repo": [ "http://example.com/repo/", "Everything", "Server-optional", ], "release": None, "target": "f27", } }, ) ], }, ) compose.setup_optional() self.assertValidConfig(compose.conf) phase = LiveImagesPhase(compose) phase.run() # assert at least one thread was started phase.pool.add.assert_called() self.maxDiff = None six.assertCountEqual( self, phase.pool.queue_put.mock_calls, [ mock.call( ( compose, { "ks_file": "test.ks", "build_arch": "amd64", "dest_dir": self.topdir + "/compose/Client/amd64/iso", "scratch": False, "repos": [ self.topdir + "/compose/Client/amd64/os", "http://example.com/repo/", self.topdir + "/compose/Everything/amd64/os", self.topdir + "/compose/Server-optional/amd64/os", ], "label": "", "name": None, "filename": "image-name", "version": "25", "specfile": None, "sign": False, "type": "live", "release": "20151203.t.0", "subvariant": "Client", "failable_arches": [], "ksurl": None, "target": "f27", }, compose.variants["Client"], "amd64", ) ) ], ) six.assertCountEqual( self, compose.get_image_name.mock_calls, [ mock.call( "amd64", compose.variants["Client"], disc_num=None, disc_type="live", format="%(compose_id)s-%(variant)s-%(arch)s-%(disc_type)s%(disc_num)s%(suffix)s", # noqa: E501 ) ], ) @mock.patch("pungi.phases.live_images.ThreadPool") def test_live_image_build_single_repo_from(self, ThreadPool): compose = DummyCompose( self.topdir, { "live_images": [ ( "^Client$", { "amd64": { "kickstart": "test.ks", "repo": ["http://example.com/repo/", "Everything"], "release": None, "target": "f27", } }, ) ], }, ) self.assertValidConfig(compose.conf) phase = LiveImagesPhase(compose) phase.run() # assert at least one thread was started phase.pool.add.assert_called() self.maxDiff = None six.assertCountEqual( self, phase.pool.queue_put.mock_calls, [ mock.call( ( compose, { "ks_file": "test.ks", "build_arch": "amd64", "dest_dir": self.topdir + "/compose/Client/amd64/iso", "scratch": False, "repos": [ self.topdir + "/compose/Client/amd64/os", "http://example.com/repo/", self.topdir + "/compose/Everything/amd64/os", ], "label": "", "name": None, "filename": "image-name", "version": "25", "specfile": None, "sign": False, "type": "live", "release": "20151203.t.0", "subvariant": "Client", "failable_arches": [], "ksurl": None, "target": "f27", }, compose.variants["Client"], "amd64", ) ) ], ) @mock.patch("pungi.phases.live_images.ThreadPool") def test_live_image_build_without_rename(self, ThreadPool): compose = DummyCompose( self.topdir, { "live_images_no_rename": True, "live_images": [ ( "^Client$", { "amd64": { "kickstart": "test.ks", "repo": ["http://example.com/repo/", "Everything"], "release": None, "target": "f27", } }, ) ], }, ) self.assertValidConfig(compose.conf) phase = LiveImagesPhase(compose) phase.run() # assert at least one thread was started phase.pool.add.assert_called() self.maxDiff = None six.assertCountEqual( self, phase.pool.queue_put.mock_calls, [ mock.call( ( compose, { "ks_file": "test.ks", "build_arch": "amd64", "dest_dir": self.topdir + "/compose/Client/amd64/iso", "scratch": False, "repos": [ self.topdir + "/compose/Client/amd64/os", "http://example.com/repo/", self.topdir + "/compose/Everything/amd64/os", ], "label": "", "name": None, "filename": None, "version": "25", "specfile": None, "sign": False, "type": "live", "release": "20151203.t.0", "subvariant": "Client", "failable_arches": [], "ksurl": None, "target": "f27", }, compose.variants["Client"], "amd64", ) ) ], ) @mock.patch("pungi.phases.live_images.ThreadPool") def test_live_image_build_two_images(self, ThreadPool): compose = DummyCompose( self.topdir, { "live_images": [ ( "^Client$", { "amd64": [ { "kickstart": "test.ks", "repo": ["http://example.com/repo/", "Everything"], "target": "f27", }, { "kickstart": "another.ks", "repo": ["http://example.com/repo/", "Everything"], "target": "f27", }, ] }, ) ], }, ) self.assertValidConfig(compose.conf) phase = LiveImagesPhase(compose) phase.run() # assert at least one thread was started phase.pool.add.assert_called() self.maxDiff = None six.assertCountEqual( self, phase.pool.queue_put.mock_calls, [ mock.call( ( compose, { "ks_file": "test.ks", "build_arch": "amd64", "dest_dir": self.topdir + "/compose/Client/amd64/iso", "scratch": False, "repos": [ self.topdir + "/compose/Client/amd64/os", "http://example.com/repo/", self.topdir + "/compose/Everything/amd64/os", ], "label": "", "name": None, "filename": "image-name", "version": "25", "specfile": None, "sign": False, "type": "live", "release": None, "subvariant": "Client", "failable_arches": [], "target": "f27", "ksurl": None, }, compose.variants["Client"], "amd64", ) ), mock.call( ( compose, { "ks_file": "another.ks", "build_arch": "amd64", "dest_dir": self.topdir + "/compose/Client/amd64/iso", "scratch": False, "repos": [ self.topdir + "/compose/Client/amd64/os", "http://example.com/repo/", self.topdir + "/compose/Everything/amd64/os", ], "label": "", "name": None, "filename": "image-name", "version": "25", "specfile": None, "sign": False, "type": "live", "release": None, "subvariant": "Client", "failable_arches": [], "target": "f27", "ksurl": None, }, compose.variants["Client"], "amd64", ) ), ], ) @mock.patch("pungi.phases.live_images.ThreadPool") def test_spin_appliance(self, ThreadPool): compose = DummyCompose( self.topdir, { "live_images": [ ( "^Client$", { "amd64": { "kickstart": "test.ks", "ksurl": "https://git.example.com/kickstarts.git?#CAFEBABE", # noqa: E501 "repo": ["http://example.com/repo/", "Everything"], "type": "appliance", "target": "f27", } }, ) ], }, ) self.assertValidConfig(compose.conf) phase = LiveImagesPhase(compose) phase.run() # assert at least one thread was started phase.pool.add.assert_called() self.maxDiff = None six.assertCountEqual( self, phase.pool.queue_put.mock_calls, [ mock.call( ( compose, { "ks_file": "test.ks", "build_arch": "amd64", "dest_dir": self.topdir + "/compose/Client/amd64/images", "scratch": False, "repos": [ self.topdir + "/compose/Client/amd64/os", "http://example.com/repo/", self.topdir + "/compose/Everything/amd64/os", ], "label": "", "name": None, "filename": "image-name", "version": "25", "specfile": None, "sign": False, "type": "appliance", "release": None, "subvariant": "Client", "failable_arches": [], "target": "f27", "ksurl": "https://git.example.com/kickstarts.git?#CAFEBABE", }, compose.variants["Client"], "amd64", ) ) ], ) @mock.patch("pungi.phases.live_images.ThreadPool") def test_spin_appliance_phase_global_settings(self, ThreadPool): compose = DummyCompose( self.topdir, { "live_images_ksurl": "https://git.example.com/kickstarts.git?#CAFEBABE", "live_images_release": None, "live_images_version": "Rawhide", "live_images_target": "f27", "live_images": [ ( "^Client$", { "amd64": { "kickstart": "test.ks", "repo": ["http://example.com/repo/", "Everything"], "type": "appliance", } }, ) ], }, ) self.assertValidConfig(compose.conf) phase = LiveImagesPhase(compose) phase.run() # assert at least one thread was started phase.pool.add.assert_called() self.maxDiff = None six.assertCountEqual( self, phase.pool.queue_put.mock_calls, [ mock.call( ( compose, { "ks_file": "test.ks", "build_arch": "amd64", "dest_dir": self.topdir + "/compose/Client/amd64/images", "scratch": False, "repos": [ self.topdir + "/compose/Client/amd64/os", "http://example.com/repo/", self.topdir + "/compose/Everything/amd64/os", ], "label": "", "name": None, "filename": "image-name", "version": "Rawhide", "specfile": None, "sign": False, "type": "appliance", "release": "20151203.t.0", "subvariant": "Client", "failable_arches": [], "target": "f27", "ksurl": "https://git.example.com/kickstarts.git?#CAFEBABE", }, compose.variants["Client"], "amd64", ) ) ], ) @mock.patch("pungi.phases.live_images.ThreadPool") def test_spin_appliance_global_settings(self, ThreadPool): compose = DummyCompose( self.topdir, { "global_ksurl": "https://git.example.com/kickstarts.git?#CAFEBABE", "global_release": None, "global_version": "Rawhide", "global_target": "f27", "live_images": [ ( "^Client$", { "amd64": { "kickstart": "test.ks", "repo": ["http://example.com/repo/", "Everything"], "type": "appliance", } }, ) ], }, ) self.assertValidConfig(compose.conf) phase = LiveImagesPhase(compose) phase.run() # assert at least one thread was started phase.pool.add.assert_called() self.maxDiff = None six.assertCountEqual( self, phase.pool.queue_put.mock_calls, [ mock.call( ( compose, { "ks_file": "test.ks", "build_arch": "amd64", "dest_dir": self.topdir + "/compose/Client/amd64/images", "scratch": False, "repos": [ self.topdir + "/compose/Client/amd64/os", "http://example.com/repo/", self.topdir + "/compose/Everything/amd64/os", ], "label": "", "name": None, "filename": "image-name", "version": "Rawhide", "specfile": None, "sign": False, "type": "appliance", "release": "20151203.t.0", "subvariant": "Client", "failable_arches": [], "target": "f27", "ksurl": "https://git.example.com/kickstarts.git?#CAFEBABE", }, compose.variants["Client"], "amd64", ) ) ], ) @mock.patch("pungi.phases.live_images.ThreadPool") def test_live_image_build_custom_type(self, ThreadPool): compose = DummyCompose( self.topdir, { "disc_types": {"live": "Live"}, "live_images": [ ( "^Client$", { "amd64": { "kickstart": "test.ks", "repo": ["http://example.com/repo/", "Everything"], "release": None, "target": "f27", } }, ) ], }, ) self.assertValidConfig(compose.conf) phase = LiveImagesPhase(compose) phase.run() # assert at least one thread was started phase.pool.add.assert_called() self.maxDiff = None six.assertCountEqual( self, phase.pool.queue_put.mock_calls, [ mock.call( ( compose, { "ks_file": "test.ks", "build_arch": "amd64", "dest_dir": self.topdir + "/compose/Client/amd64/iso", "scratch": False, "repos": [ self.topdir + "/compose/Client/amd64/os", "http://example.com/repo/", self.topdir + "/compose/Everything/amd64/os", ], "label": "", "name": None, "filename": "image-name", "version": "25", "specfile": None, "sign": False, "type": "live", "release": "20151203.t.0", "subvariant": "Client", "failable_arches": [], "target": "f27", "ksurl": None, }, compose.variants["Client"], "amd64", ) ) ], ) six.assertCountEqual( self, compose.get_image_name.mock_calls, [ mock.call( "amd64", compose.variants["Client"], disc_num=None, disc_type="Live", format="%(compose_id)s-%(variant)s-%(arch)s-%(disc_type)s%(disc_num)s%(suffix)s", # noqa: E501 ) ], ) class TestCreateLiveImageThread(PungiTestCase): @mock.patch("pungi.phases.live_images.Image") @mock.patch("shutil.copy2") @mock.patch("pungi.phases.live_images.run") @mock.patch("pungi.phases.live_images.KojiWrapper") def test_process(self, KojiWrapper, run, copy2, Image): compose = DummyCompose(self.topdir, {"koji_profile": "koji"}) pool = mock.Mock() cmd = { "ks_file": "/path/to/ks_file", "build_arch": "amd64", "dest_dir": self.topdir + "/compose/Client/amd64/iso", "scratch": False, "repos": [ "/repo/amd64/Client", "http://example.com/repo/", "/repo/amd64/Everything", ], "label": "", "name": None, "filename": "image-name", "version": None, "specfile": None, "type": "live", "ksurl": "https://git.example.com/kickstarts.git?#CAFEBABE", "release": None, "subvariant": "Something", "target": "f27", } koji_wrapper = KojiWrapper.return_value koji_wrapper.get_create_image_cmd.return_value = "koji spin-livecd ..." koji_wrapper.run_blocking_cmd.return_value = { "retcode": 0, "output": "some output", "task_id": 123, } koji_wrapper.get_image_path.return_value = ["/path/to/image.iso"] t = CreateLiveImageThread(pool) with mock.patch("pungi.phases.live_images.get_file_size") as get_file_size: get_file_size.return_value = 1024 with mock.patch("pungi.phases.live_images.get_mtime") as get_mtime: get_mtime.return_value = 13579 with mock.patch("time.sleep"): t.process((compose, cmd, compose.variants["Client"], "amd64"), 1) self.assertEqual( koji_wrapper.run_blocking_cmd.mock_calls, [ mock.call( "koji spin-livecd ...", log_file=self.topdir + "/logs/amd64/liveimage-None-None-None.amd64.log", ) ], ) self.assertEqual(koji_wrapper.get_image_path.mock_calls, [mock.call(123)]) self.assertEqual( copy2.mock_calls, [ mock.call( "/path/to/image.iso", self.topdir + "/compose/Client/amd64/iso/image-name", ) ], ) write_manifest_cmd = " && ".join( [ "cd " + self.topdir + "/compose/Client/amd64/iso", "isoinfo -R -f -i image-name | grep -v '/TRANS.TBL$' | sort >> image-name.manifest", # noqa: E501 ] ) self.assertEqual(run.mock_calls, [mock.call(write_manifest_cmd)]) self.assertEqual( koji_wrapper.get_create_image_cmd.mock_calls, [ mock.call( "test-Something-Live-amd64", "20151203.0.t", "f27", "amd64", "/path/to/ks_file", [ "/repo/amd64/Client", "http://example.com/repo/", "/repo/amd64/Everything", ], image_type="live", archive=False, specfile=None, wait=True, release=None, ksurl="https://git.example.com/kickstarts.git?#CAFEBABE", ) ], ) self.assertEqual(Image.return_value.type, "live") self.assertEqual(Image.return_value.format, "iso") self.assertEqual(Image.return_value.path, "Client/amd64/iso/image-name") self.assertEqual(Image.return_value.size, 1024) self.assertEqual(Image.return_value.mtime, 13579) self.assertEqual(Image.return_value.arch, "amd64") self.assertEqual(Image.return_value.disc_number, 1) self.assertEqual(Image.return_value.disc_count, 1) self.assertTrue(Image.return_value.bootable) self.assertEqual( compose.im.add.mock_calls, [mock.call(variant="Client", arch="amd64", image=Image.return_value)], ) @mock.patch("pungi.phases.live_images.Image") @mock.patch("shutil.copy2") @mock.patch("pungi.phases.live_images.run") @mock.patch("pungi.phases.live_images.KojiWrapper") def test_process_no_rename(self, KojiWrapper, run, copy2, Image): compose = DummyCompose(self.topdir, {"koji_profile": "koji"}) pool = mock.Mock() cmd = { "ks_file": "/path/to/ks_file", "build_arch": "amd64", "dest_dir": self.topdir + "/compose/Client/amd64/iso", "scratch": False, "repos": [ "/repo/amd64/Client", "http://example.com/repo/", "/repo/amd64/Everything", ], "label": "", "name": None, "filename": None, "version": None, "specfile": None, "type": "live", "ksurl": "https://git.example.com/kickstarts.git?#CAFEBABE", "release": None, "subvariant": "Client", "target": "f27", } koji_wrapper = KojiWrapper.return_value koji_wrapper.get_create_image_cmd.return_value = "koji spin-livecd ..." koji_wrapper.run_blocking_cmd.return_value = { "retcode": 0, "output": "some output", "task_id": 123, } koji_wrapper.get_image_path.return_value = ["/path/to/image.iso"] t = CreateLiveImageThread(pool) with mock.patch("pungi.phases.live_images.get_file_size") as get_file_size: get_file_size.return_value = 1024 with mock.patch("pungi.phases.live_images.get_mtime") as get_mtime: get_mtime.return_value = 13579 with mock.patch("time.sleep"): t.process((compose, cmd, compose.variants["Client"], "amd64"), 1) self.assertEqual( koji_wrapper.run_blocking_cmd.mock_calls, [ mock.call( "koji spin-livecd ...", log_file=self.topdir + "/logs/amd64/liveimage-None-None-None.amd64.log", ) ], ) self.assertEqual(koji_wrapper.get_image_path.mock_calls, [mock.call(123)]) self.assertEqual( copy2.mock_calls, [ mock.call( "/path/to/image.iso", self.topdir + "/compose/Client/amd64/iso/image.iso", ) ], ) write_manifest_cmd = " && ".join( [ "cd " + self.topdir + "/compose/Client/amd64/iso", "isoinfo -R -f -i image.iso | grep -v '/TRANS.TBL$' | sort >> image.iso.manifest", # noqa: E501 ] ) self.assertEqual(run.mock_calls, [mock.call(write_manifest_cmd)]) self.assertEqual( koji_wrapper.get_create_image_cmd.mock_calls, [ mock.call( "test-Client-Live-amd64", "20151203.0.t", "f27", "amd64", "/path/to/ks_file", [ "/repo/amd64/Client", "http://example.com/repo/", "/repo/amd64/Everything", ], image_type="live", archive=False, specfile=None, wait=True, release=None, ksurl="https://git.example.com/kickstarts.git?#CAFEBABE", ) ], ) self.assertEqual(Image.return_value.type, "live") self.assertEqual(Image.return_value.format, "iso") self.assertEqual(Image.return_value.path, "Client/amd64/iso/image.iso") self.assertEqual(Image.return_value.size, 1024) self.assertEqual(Image.return_value.mtime, 13579) self.assertEqual(Image.return_value.arch, "amd64") self.assertEqual(Image.return_value.disc_number, 1) self.assertEqual(Image.return_value.disc_count, 1) self.assertTrue(Image.return_value.bootable) self.assertEqual( compose.im.add.mock_calls, [mock.call(variant="Client", arch="amd64", image=Image.return_value)], ) @mock.patch("pungi.phases.live_images.Image") @mock.patch("shutil.copy2") @mock.patch("pungi.phases.live_images.run") @mock.patch("pungi.phases.live_images.KojiWrapper") def test_process_applicance(self, KojiWrapper, run, copy2, Image): compose = DummyCompose(self.topdir, {"koji_profile": "koji"}) pool = mock.Mock() cmd = { "ks_file": "/path/to/ks_file", "build_arch": "amd64", "dest_dir": self.topdir + "/compose/Client/amd64/iso", "scratch": False, "repos": [ "/repo/amd64/Client", "http://example.com/repo/", "/repo/amd64/Everything", ], "label": "", "name": None, "filename": "image-name", "version": None, "specfile": None, "type": "appliance", "ksurl": None, "release": None, "subvariant": "Client", "target": "f27", } koji_wrapper = KojiWrapper.return_value koji_wrapper.get_create_image_cmd.return_value = "koji spin-livecd ..." koji_wrapper.run_blocking_cmd.return_value = { "retcode": 0, "output": "some output", "task_id": 123, } koji_wrapper.get_image_path.return_value = ["/path/to/image-a.b-sda.raw.xz"] t = CreateLiveImageThread(pool) with mock.patch("pungi.phases.live_images.get_file_size") as get_file_size: get_file_size.return_value = 1024 with mock.patch("pungi.phases.live_images.get_mtime") as get_mtime: get_mtime.return_value = 13579 with mock.patch("time.sleep"): t.process((compose, cmd, compose.variants["Client"], "amd64"), 1) self.assertEqual( koji_wrapper.run_blocking_cmd.mock_calls, [ mock.call( "koji spin-livecd ...", log_file=self.topdir + "/logs/amd64/liveimage-None-None-None.amd64.log", ) ], ) self.assertEqual(koji_wrapper.get_image_path.mock_calls, [mock.call(123)]) self.assertEqual( copy2.mock_calls, [ mock.call( "/path/to/image-a.b-sda.raw.xz", self.topdir + "/compose/Client/amd64/iso/image-name", ) ], ) self.assertEqual(run.mock_calls, []) self.assertEqual( koji_wrapper.get_create_image_cmd.mock_calls, [ mock.call( "test-Client-Disk-amd64", "20151203.0.t", "f27", "amd64", "/path/to/ks_file", [ "/repo/amd64/Client", "http://example.com/repo/", "/repo/amd64/Everything", ], image_type="appliance", archive=False, specfile=None, wait=True, release=None, ksurl=None, ) ], ) self.assertEqual(Image.return_value.type, "raw-xz") self.assertEqual(Image.return_value.format, "raw.xz") self.assertEqual(Image.return_value.path, "Client/amd64/iso/image-name") self.assertEqual(Image.return_value.size, 1024) self.assertEqual(Image.return_value.mtime, 13579) self.assertEqual(Image.return_value.arch, "amd64") self.assertEqual(Image.return_value.disc_number, 1) self.assertEqual(Image.return_value.disc_count, 1) self.assertTrue(Image.return_value.bootable) self.assertEqual( compose.im.add.mock_calls, [mock.call(variant="Client", arch="amd64", image=Image.return_value)], ) @mock.patch("shutil.copy2") @mock.patch("pungi.phases.live_images.run") @mock.patch("pungi.phases.live_images.KojiWrapper") def test_process_handles_fail(self, KojiWrapper, run, copy2): compose = DummyCompose( self.topdir, {"koji_profile": "koji", "koji_cache": "/tmp"} ) pool = mock.Mock() cmd = { "ks_file": "/path/to/ks_file", "build_arch": "amd64", "dest_dir": "/top/iso_dir/amd64/Client", "scratch": False, "repos": [ "/repo/amd64/Client", "http://example.com/repo/", "/repo/amd64/Everything", ], "label": "", "name": None, "filename": "image-name", "version": None, "specfile": None, "ksurl": None, "subvariant": "Client", "release": "xyz", "type": "live", "failable_arches": ["*"], "target": "f27", } koji_wrapper = KojiWrapper.return_value koji_wrapper.get_create_image_cmd.return_value = "koji spin-livecd ..." koji_wrapper.run_blocking_cmd.return_value = { "retcode": 1, "output": "some output", "task_id": 123, } t = CreateLiveImageThread(pool) with mock.patch("time.sleep"): t.process((compose, cmd, compose.variants["Client"], "amd64"), 1) pool._logger.error.assert_has_calls( [ mock.call( "[FAIL] Live (variant Client, arch amd64, subvariant Client) failed, but going on anyway." # noqa: E501 ), mock.call( "LiveImage task failed: 123. See %s/logs/amd64/liveimage-None-None-xyz.amd64.log for more details." # noqa: E501 % self.topdir ), ] ) @mock.patch("shutil.copy2") @mock.patch("pungi.phases.live_images.run") @mock.patch("pungi.phases.live_images.KojiWrapper") def test_process_handles_exception(self, KojiWrapper, run, copy2): compose = DummyCompose( self.topdir, {"koji_profile": "koji", "koji_cache": "/tmp"} ) pool = mock.Mock() cmd = { "ks_file": "/path/to/ks_file", "build_arch": "amd64", "dest_dir": "/top/iso_dir/amd64/Client", "scratch": False, "repos": [ "/repo/amd64/Client", "http://example.com/repo/", "/repo/amd64/Everything", ], "label": "", "name": None, "filename": "image-name", "version": None, "specfile": None, "ksurl": None, "subvariant": "Client", "release": "xyz", "type": "live", "failable_arches": ["*"], "target": "f27", } koji_wrapper = KojiWrapper.return_value koji_wrapper.get_create_image_cmd.side_effect = boom t = CreateLiveImageThread(pool) with mock.patch("time.sleep"): t.process((compose, cmd, compose.variants["Client"], "amd64"), 1) pool._logger.error.assert_has_calls( [ mock.call( "[FAIL] Live (variant Client, arch amd64, subvariant Client) failed, but going on anyway." # noqa: E501 ), mock.call("BOOM"), ] )