def test_create_original_to_rebuilt_nvrs_map(self, get_original_build): get_original_build.side_effect = ["original_1", "original_2"] self.handler.event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory) self.botas_advisory._builds = { "product_name": { "builds": [{ "some_name-2-12345": { "nvr": "some_name-2-12345" } }, { "some_name_two-2-2": { "nvr": "some_name_two-2-2" } }] } } self.get_blocking_advisories.return_value = { "some_name-1-1", "some_name-2-1" } expected_map = { "original_1": "some_name-2-12345", "original_2": "some_name_two-2-2", "some_name-2-1": "some_name-2-12345" } mapping = self.handler._create_original_to_rebuilt_nvrs_map() self.assertEqual(get_original_build.call_count, 2) self.assertEqual(mapping, expected_map)
def test_handle(self, allow_build, handle_auto_rebuild, prepare_builds, start_to_build_images): event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory) db_event = Event.get_or_create_from_event(db.session, event) allow_build.return_value = True handle_auto_rebuild.return_value = [{"bundle": 1}, {"bundle": 2}] prepare_builds.return_value = [ ArtifactBuild.create(db.session, db_event, "ed0", "image", 1234, original_nvr="some_name-2-12345", rebuilt_nvr="some_name-2-12346"), ArtifactBuild.create(db.session, db_event, "ed0", "image", 12345, original_nvr="some_name_2-2-2", rebuilt_nvr="some_name_2-2-210") ] self.handler.handle(event) self.handler._prepare_builds.assert_called_once() self.assertEqual(self.handler._prepare_builds.call_args[0][0], db_event) self.assertEqual(self.handler._prepare_builds.call_args[0][1], [{ "bundle": 1 }, { "bundle": 2 }])
def test_handle_set_dry_run(self): event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory, dry_run=True) self.handler.handle(event) self.assertTrue(self.handler._force_dry_run) self.assertTrue(self.handler.dry_run)
def test_handle_isnt_allowed_by_internal_policy(self): event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory) self.handler.handle(event) db_event = Event.get(db.session, message_id='test_msg_id') self.assertEqual(db_event.state, EventState.SKIPPED.value) self.assertTrue(db_event.state_reason.startswith( "This image rebuild is not allowed by internal policy."))
def test_handle_no_digests_error(self): event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory) self.pyxis().get_digests_by_nvrs.return_value = set() self.botas_advisory._builds = {} self.handler.handle(event) db_event = Event.get(db.session, message_id='test_msg_id') self.assertEqual(db_event.state, EventState.SKIPPED.value) self.assertTrue( db_event.state_reason.startswith("There are no digests for NVRs:"))
def test_handle_no_digests_error(self): event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory) self.pyxis().get_manifest_list_digest_by_nvr.return_value = None self.botas_advisory._builds = {} self.handler.handle(event) db_event = Event.get(db.session, message_id='test_msg_id') self.assertEqual(db_event.state, EventState.SKIPPED.value) self.assertTrue( db_event.state_reason.startswith( "None of the original images have digest"))
def test_get_original_nvrs(self, get_build): event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory) self.botas_advisory._builds = { "product_name": { "builds": [{"nvr": "some_name-2-2"}, {"nvr": "some_name_two-2-2"}] } } get_build.return_value = "some_name-1-0" self.handler.handle(event) self.pyxis().get_digests_by_nvrs.assert_called_with({"some_name-1-0"})
def parse(self, topic, msg): msg_id = msg.get('msg_id') inner_msg = msg.get('msg') errata_id = int(inner_msg.get('errata_id')) errata = Errata() advisory = ErrataAdvisory.from_advisory_id(errata, errata_id) # If advisory created by BOTAS and it's shipped, # then return BotasErrataShippedEvent event if advisory.state == "SHIPPED_LIVE" and \ advisory.reporter.startswith('botas'): event = BotasErrataShippedEvent(msg_id, advisory) else: event = ErrataAdvisoryStateChangedEvent(msg_id, advisory) return event
def test_handle_get_bundle_paths(self): event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory) self.pyxis().get_digests_by_nvrs.return_value = {'nvr1'} bundles = [ { "bundle_path": "some_path", "bundle_path_digest": "sha256:123123", "channel_name": "streams-1.5.x", "related_images": [ { "image": "registry/amq7/amq-streams-r-operator@sha256:111", "name": "strimzi-cluster-operator", "digest": "sha256:111" }, ], "version": "1.5.3" }, { "bundle_path": "some_path_2", "channel_name": "streams-1.5.x", "related_images": [ { "image": "registry/amq7/amq-streams-r-operator@sha256:555", "name": "strimzi-cluster-operator", "digest": "sha256:555" }, ], "version": "1.5.4" }, ] self.pyxis().filter_bundles_by_related_image_digests.return_value = bundles self.botas_advisory._builds = {} self.handler.handle(event) db_event = Event.get(db.session, message_id='test_msg_id') # should be called only with the first digest, because second one # doesn't have 'bundle_path_digest' self.pyxis().get_images_by_digests.assert_called_once_with({"sha256:123123"}) self.assertEqual(db_event.state, EventState.SKIPPED.value) self.assertTrue( db_event.state_reason.startswith("Skipping the rebuild of"))
def test_get_original_nvrs(self, get_build): event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory) self.botas_advisory._builds = { "product_name": { "builds": [{ "nvr": "some_name-2-2" }, { "nvr": "some_name_two-2-2" }] } } get_build.return_value = "some_name-1-0" self.handler.handle(event) self.pyxis().get_manifest_list_digest_by_nvr.assert_has_calls( [ call("some_name-1-0"), call("some_name_two-2-2"), ], any_order=True)
def parse(self, topic, msg): msg_id = msg.get('msg_id') inner_msg = msg.get('msg') errata_id = int(inner_msg.get('errata_id')) new_state = inner_msg.get('to') errata = Errata() advisory = ErrataAdvisory.from_advisory_id(errata, errata_id) # When there is message delay, state change messages can arrive after # advisory has been changed to a different state other than the one # in message, so we override advisory state with the state in message advisory.state = new_state # If advisory created by BOTAS and it's shipped, # then return BotasErrataShippedEvent event if advisory.state == "SHIPPED_LIVE" and advisory.reporter.startswith( 'botas'): event = BotasErrataShippedEvent(msg_id, advisory) else: event = ErrataAdvisoryStateChangedEvent(msg_id, advisory) return event
def test_get_pullspecs_mapping(self): event = ManualBundleRebuild( "test_msg_id", container_images=[], bundle_images=["bundle_image_1", "bundle_image_2"]) event2 = BotasErrataShippedEvent("test_msg_id", self.botas_advisory) db_event = Event.get_or_create_from_event(db.session, event2) build = ArtifactBuild.create(db.session, db_event, "ed0", "image", 1234, rebuilt_nvr="bundle_image_1") build.bundle_pullspec_overrides = { "pullspec_replacements": [{ "new": "some_pullspec", "original": "original_pullspec", "pinned": True }, { "new": "new_pullspec", "original": "original_pullspec", "pinned": True, "_old": "old_pullspec" }] } self.handler.event = event db.session.commit() with self.assertLogs("freshmaker", "WARNING") as log: pullspec_map = self.handler._get_pullspecs_mapping() expected_map = {"old_pullspec": "new_pullspec"} self.assertTrue( "Can't find build for a bundle image \"bundle_image_2\"" in log.output[0]) self.assertEqual(pullspec_map, expected_map)
def test_can_handle_botas_adisory(self): handler = HandleBotasAdvisory() event = BotasErrataShippedEvent("123", self.botas_advisory) self.assertTrue(handler.can_handle(event))
def test_multiple_bundles_to_single_related_image(self, mock_koji, get_published): event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory) self.botas_advisory._builds = { "product_name": { "builds": [{ "foo-1-2.123": { "nvr": "foo-1-2.123" } }, { "bar-2-2.134": { "nvr": "bar-2-2.134" } }] } } published_nvrs = {"foo-1-2.123": "foo-1-2", "bar-2-2.134": "bar-2-2"} get_published.side_effect = lambda x: published_nvrs[x] digests_by_nvrs = { "foo-1-2": "sha256:111", "bar-2-2": "sha256:222", "foo-1-2.123": "sha256:333", "bar-2-2.134": "sha256:444", } def gmldbn(nvr, must_be_published=True): return digests_by_nvrs[nvr] self.pyxis().get_manifest_list_digest_by_nvr.side_effect = gmldbn bundles_by_related_digest = { "sha256:111": [ { "bundle_path": "bundle-a/path", "bundle_path_digest": "sha256:123123", "channel_name": "streams-1.5.x", "csv_name": "amq-streams.1.5.3", "related_images": [ { "image": "foo@sha256:111", "name": "foo", "digest": "sha256:111" }, ], "version": "1.5.3" }, { "bundle_path": "bundle-b/path", "bundle_path_digest": "sha256:023023", "channel_name": "4.5", "csv_name": "amq-streams.2.4.2", "related_images": [ { "image": "foo@sha256:111", "name": "foo", "digest": "sha256:111" }, ], "version": "2.4.2" }, ], "sha256:222": [] } self.pyxis().get_bundles_by_related_image_digest.side_effect = \ lambda x, _: bundles_by_related_digest[x] bundle_images = { "sha256:123123": [{ "brew": { "build": "foo-a-bundle-2.1-2", "nvra": "foo-a-bundle-2.1-2.amd64", "package": "foo-a-bundle", }, "repositories": [{ "content_advisory_ids": [], "manifest_list_digest": "sha256:12322", "manifest_schema2_digest": "sha256:123123", "published": True, "registry": "registry.example.com", "repository": "foo/foo-a-operator-bundle", "tags": [{ "name": "2" }, { "name": "2.1" }], }], }], "sha256:023023": [{ "brew": { "build": "foo-b-bundle-3.1-2", "nvra": "foo-b-bundle-3.1-2.amd64", "package": "foo-b-bundle", }, "repositories": [{ "content_advisory_ids": [], "manifest_list_digest": "sha256:12345", "manifest_schema2_digest": "sha256:023023", "published": True, "registry": "registry.example.com", "repository": "foo/foo-b-operator-bundle", "tags": [{ "name": "3" }, { "name": "3.1" }], }], }] } self.pyxis( ).get_images_by_digest.side_effect = lambda x: bundle_images[x] def _fake_get_auto_rebuild_tags(registry, repository): if repository == "foo/foo-a-operator-bundle": return ["2", "latest"] if repository == "foo/foo-b-operator-bundle": return ["3", "latest"] self.pyxis( ).get_auto_rebuild_tags.side_effect = _fake_get_auto_rebuild_tags koji_builds = { "foo-a-bundle-2.1-2": { "build_id": 123, "extra": { "image": { "operator_manifests": { "related_images": { "created_by_osbs": True, "pullspecs": [{ "new": "registry.example.com/foo/foo-container@sha256:111", "original": "registry.exampl.com/foo/foo-container:0.1", "pinned": True, }], } }, } }, "name": "foo-a-bundle", "nvr": "foo-a-bundle-2.1-2", }, "foo-b-bundle-3.1-2": { "build_id": 234, "extra": { "image": { "operator_manifests": { "related_images": { "created_by_osbs": True, "pullspecs": [{ "new": "registry.example.com/foo/foo-container@sha256:111", "original": "registry.exampl.com/foo/foo-container:0.1", "pinned": True, }], } }, } }, "name": "foo-b-bundle", "nvr": "foo-b-bundle-3.1-2", } } mock_koji.return_value.get_build.side_effect = lambda x: koji_builds[x] self.handler._prepare_builds = MagicMock() self.handler.start_to_build_images = MagicMock() self.handler.handle(event) db_event = Event.get(db.session, message_id='test_msg_id') self.pyxis().get_images_by_digest.assert_has_calls( [call("sha256:123123"), call("sha256:023023")], any_order=True) self.assertEqual(db_event.state, EventState.BUILDING.value)
def test_handle_auto_rebuild(self): nvr_to_digest = { "original_1": "original_1_digest", "some_name-1-12345": "some_name-1-12345_digest", "original_2": "original_2_digest", "some_name_2-2-2": "some_name_2-2-2_digest", } bundles = [{ "bundle_path_digest": "original_1_digest" }, { "bundle_path_digest": "some_name-1-12345_digest" }, { "bundle_path_digest": "original_2_digest" }, { "bundle_path_digest": "some_name_2-2-2_digest" }] bundles_with_related_images = { "original_1_digest": [ { "bundle_path_digest": "bundle_with_related_images_1_digest", "csv_name": "image.1.2.3", "version": "1.2.3", }, ], "original_2_digest": [ { "bundle_path_digest": "bundle_with_related_images_2_digest", "csv_name": "image.1.2.4", "version": "1.2.4", }, ] } image_by_digest = { "bundle_with_related_images_1_digest": { "brew": { "build": "bundle1_nvr-1-1" } }, "bundle_with_related_images_2_digest": { "brew": { "build": "bundle2_nvr-1-1" } }, } builds = { "bundle1_nvr-1-1": { "task_id": 1, "extra": { "image": { "operator_manifests": { "related_images": { "created_by_osbs": True, "pullspecs": [{ "new": "registry/repo/operator1@original_1_digest", "original": "registry/repo/operator1:v2.2.0", "pinned": True, }] }, } } } }, "bundle2_nvr-1-1": { "task_id": 2, "extra": { "image": { "operator_manifests": { "related_images": { "created_by_osbs": True, "pullspecs": [{ "new": "registry/repo/operator2@original_2_digest", "original": "registry/repo/operator2:v2.2.0", "pinned": True, }] }, } } } } } event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory) db_event = Event.get_or_create_from_event(db.session, event) self.handler.event = event self.handler._create_original_to_rebuilt_nvrs_map = \ MagicMock(return_value={"original_1": "some_name-1-12345", "original_2": "some_name_2-2-2"}) def gmldbn(nvr, must_be_published=True): return nvr_to_digest[nvr] self.pyxis().get_manifest_list_digest_by_nvr.side_effect = gmldbn self.pyxis().get_operator_indices.return_value = [] self.pyxis().get_latest_bundles.return_value = bundles # return bundles for original images self.pyxis( ).get_bundles_by_related_image_digest.side_effect = lambda x, y: bundles_with_related_images[ x] self.pyxis( ).get_images_by_digest.side_effect = lambda x: [image_by_digest[x]] self.handler.image_has_auto_rebuild_tag = MagicMock(return_value=True) get_build = self.patcher.patch( "freshmaker.kojiservice.KojiService.get_build") get_build.side_effect = lambda x: builds[x] now = datetime(year=2020, month=12, day=25, hour=0, minute=0, second=0) with freezegun.freeze_time(now): bundles_to_rebuild = self.handler._handle_auto_rebuild(db_event) self.assertNotEqual(db_event.state, EventState.SKIPPED.value) get_build.assert_has_calls( [call("bundle1_nvr-1-1"), call("bundle2_nvr-1-1")], any_order=True) bundles_by_digest = { "bundle_with_related_images_1_digest": { "auto_rebuild": True, "images": [{ "brew": { "build": "bundle1_nvr-1-1" } }], "nvr": "bundle1_nvr-1-1", "osbs_pinning": True, "pullspec_replacements": [{ "new": "registry/repo/operator1@some_name-1-12345_digest", "original": "registry/repo/operator1:v2.2.0", "pinned": True, "_old": "registry/repo/operator1@original_1_digest" }], "update": { "metadata": { "name": "image.1.2.3-0.1608854400.p", "annotations": { "olm.substitutesFor": "1.2.3" }, }, "spec": { "version": "1.2.3+0.1608854400.p" }, }, }, "bundle_with_related_images_2_digest": { "auto_rebuild": True, "images": [{ "brew": { "build": "bundle2_nvr-1-1" } }], "nvr": "bundle2_nvr-1-1", "osbs_pinning": True, "pullspec_replacements": [{ "new": "registry/repo/operator2@some_name_2-2-2_digest", "original": "registry/repo/operator2:v2.2.0", "pinned": True, "_old": "registry/repo/operator2@original_2_digest" }], "update": { "metadata": { "name": "image.1.2.4-0.1608854400.p", "annotations": { "olm.substitutesFor": "1.2.4" }, }, "spec": { "version": "1.2.4+0.1608854400.p" }, }, }, } self.assertCountEqual(bundles_to_rebuild, list(bundles_by_digest.values()))
def test_handle(self): event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory) self.handler.allow_build = MagicMock(return_value=True) self.handler._create_original_to_rebuilt_nvrs_map = \ MagicMock(return_value={"original_1": "some_name-1-12345", "original_2": "some_name_2-2-2"}) nvr_to_digest = { "original_1": "original_1_digest", "some_name-1-12345": "some_name-1-12345_digest", "original_2": "original_2_digest", "some_name_2-2-2": "some_name_2-2-2_digest", } bundles = [{ "bundle_path_digest": "original_1_digest" }, { "bundle_path_digest": "some_name-1-12345_digest" }, { "bundle_path_digest": "original_2_digest" }, { "bundle_path_digest": "some_name_2-2-2_digest" }] bundles_with_related_images = { "original_1_digest": [ { "bundle_path_digest": "bundle_with_related_images_1_digest", "csv_name": "image.1.2.3", "version": "1.2.3", }, ], "original_2_digest": [ { "bundle_path_digest": "bundle_with_related_images_2_digest", "csv_name": "image.1.2.4", "version": "1.2.4", }, ] } image_by_digest = { "bundle_with_related_images_1_digest": { "brew": { "build": "bundle1_nvr-1-1" } }, "bundle_with_related_images_2_digest": { "brew": { "build": "bundle2_nvr-1-1" } }, } self.pyxis( ).get_manifest_list_digest_by_nvr.side_effect = lambda x: nvr_to_digest[ x] self.pyxis().get_operator_indices.return_value = [] self.pyxis().get_latest_bundles.return_value = bundles # return bundles for original images self.pyxis( ).get_bundles_by_related_image_digest.side_effect = lambda x, y: bundles_with_related_images[ x] self.pyxis( ).get_images_by_digest.side_effect = lambda x: [image_by_digest[x]] self.handler.image_has_auto_rebuild_tag = MagicMock(return_value=True) get_build = self.patcher.patch( "freshmaker.kojiservice.KojiService.get_build") builds = { "bundle1_nvr-1-1": { "task_id": 1, "extra": { "image": { "operator_manifests": { "related_images": { "created_by_osbs": True, "pullspecs": [{ "new": "registry/repo/operator1@original_1_digest", "original": "registry/repo/operator1:v2.2.0", "pinned": True, }] }, } } } }, "bundle2_nvr-1-1": { "task_id": 2, "extra": { "image": { "operator_manifests": { "related_images": { "created_by_osbs": True, "pullspecs": [{ "new": "registry/repo/operator2@original_2_digest", "original": "registry/repo/operator2:v2.2.0", "pinned": True, }] }, } } } } } get_build.side_effect = lambda x: builds[x] db_event = Event.get_or_create_from_event(db.session, event) self.handler._prepare_builds = MagicMock(return_value=[ ArtifactBuild.create(db.session, db_event, "ed0", "image", 1234, original_nvr="some_name-2-12345", rebuilt_nvr="some_name-2-12346"), ArtifactBuild.create(db.session, db_event, "ed0", "image", 12345, original_nvr="some_name_2-2-2", rebuilt_nvr="some_name_2-2-210") ]) self.handler.start_to_build_images = MagicMock() db.session.commit() now = datetime(year=2020, month=12, day=25, hour=0, minute=0, second=0) with freezegun.freeze_time(now): self.handler.handle(event) self.assertEqual(db_event.state, EventState.BUILDING.value) get_build.assert_has_calls( [call("bundle1_nvr-1-1"), call("bundle2_nvr-1-1")], any_order=True) bundles_by_digest = { "bundle_with_related_images_1_digest": { "append": { "spec": { "skips": ["1.2.3"] } }, "auto_rebuild": True, "images": [{ "brew": { "build": "bundle1_nvr-1-1" } }], "nvr": "bundle1_nvr-1-1", "osbs_pinning": True, "pullspecs": [{ "new": "registry/repo/operator1@some_name-1-12345_digest", "original": "registry/repo/operator1:v2.2.0", "pinned": True, }], "update": { "metadata": { "name": "image.1.2.3+0.1608854400.patched", "substitutes-for": "1.2.3", }, "spec": { "version": "1.2.3+0.1608854400.patched" }, }, }, "bundle_with_related_images_2_digest": { "append": { "spec": { "skips": ["1.2.4"] } }, "auto_rebuild": True, "images": [{ "brew": { "build": "bundle2_nvr-1-1" } }], "nvr": "bundle2_nvr-1-1", "osbs_pinning": True, "pullspecs": [{ "new": "registry/repo/operator2@some_name_2-2-2_digest", "original": "registry/repo/operator2:v2.2.0", "pinned": True, }], "update": { "metadata": { "name": "image.1.2.4+0.1608854400.patched", "substitutes-for": "1.2.4", }, "spec": { "version": "1.2.4+0.1608854400.patched" }, }, }, } self.handler._prepare_builds.assert_called_with( db_event, bundles_by_digest, { 'bundle_with_related_images_1_digest', 'bundle_with_related_images_2_digest' })