From 7c8cdae65045632541641190595a84296fedf406 Mon Sep 17 00:00:00 2001 From: Jan Kaluza Date: Jan 10 2019 05:52:26 +0000 Subject: Refactor "handlers" layout. This commit changes the directory structure for Freshmaker handlers as well as their names. The reason is that the current structure presumes there is just single handler for each Freshmaker Event, but this will not be true for new features we are working on. It is also not clear from the handler's name what is the goal of the handler which makes the code harder to understand. In this commit, we change the layout and names like this: - Handlers are now grouped according to the main service which they use when handling the Event - for example handlers resulting in Koji builds use 'koji' directory. Handlers updating just the Freshmaker's internal DB uses 'internal' directory, ... - Handlers are now named like DoSomethingOnSomeEvent. This makes it clear what is the goal of a handler. For more information, you can read: https://docs.google.com/document/d/1dx6cuFblaZlf0BZtPU6kv-ZzNJgf2JHWQ-VfSDuHVyM/edit# --- diff --git a/README.md b/README.md index 731e46d..3a51acd 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,8 @@ Most of the common tasks for handlers are already implemented in Freshmaker incl The handler which rebuilds all the modules after the `.spec` file of RPM including in a module is updated can look like this: - class GitRPMSpecChangeHandler(BaseHandler): - name = "GitRPMSpecChangeHandler" + class RebuildImagesOnGitRPMSpecChange(BaseHandler): + name = "RebuildImagesOnGitRPMSpecChange" def can_handle(self, event): return isinstance(event, GitRPMSpecChangeEvent) diff --git a/conf/config.py b/conf/config.py index 8c7e7cc..d3ccac1 100644 --- a/conf/config.py +++ b/conf/config.py @@ -51,18 +51,16 @@ class BaseConfiguration(object): 'freshmaker.parsers.internal:FreshmakerManualRebuildParser', 'freshmaker.parsers.bodhi:BodhiUpdateCompleteStableParser', 'freshmaker.parsers.git:GitReceiveParser', - 'freshmaker.parsers.koji:KojiTaskStateChangeParser', 'freshmaker.parsers.mbs:MBSModuleStateChangeParser', ] # List of enabled composing handlers. HANDLERS = [ - "freshmaker.handlers.bodhi:BodhiUpdateCompleteStableHandler", - "freshmaker.handlers.git:GitDockerfileChangeHandler", - "freshmaker.handlers.git:GitModuleMetadataChangeHandler", - "freshmaker.handlers.git:GitRPMSpecChangeHandler", - "freshmaker.handlers.koji:KojiTaskStateChangeHandler", - "freshmaker.handlers.mbs:MBSModuleStateChangeHandler", + "freshmaker.handlers.koji:RebuildImagesOnRPMBodhiUpdate", + "freshmaker.handlers.koji:RebuildImagesOnGitDockerfileChange", + "freshmaker.handlers.mbs:RebuildModulesOnGitMMDChange", + "freshmaker.handlers.mbs:RebuildModulesOnGitRPMSpecChange", + "freshmaker.handlers.internal:UpdateDBOnModuleBuild", ] # Base URL of git repository with source artifacts. @@ -309,12 +307,12 @@ class TestConfiguration(BaseConfiguration): MAX_THREAD_WORKERS = 1 HANDLER_BUILD_WHITELIST = { - 'BrewSignRPMHandler': { + 'GenerateAdvisorySignedEventOnRPMSign': { 'image': { 'advisory_state': 'REL_PREP|PUSH_READY|IN_PUSH|SHIPPED_LIVE', }, }, - 'ErrataAdvisoryStateChangedHandler': { + 'UpdateDBOnAdvisoryChange': { 'image': { 'advisory_state': 'REL_PREP|PUSH_READY|IN_PUSH|SHIPPED_LIVE', }, diff --git a/freshmaker/handlers/bodhi/__init__.py b/freshmaker/handlers/bodhi/__init__.py deleted file mode 100644 index 9b396d6..0000000 --- a/freshmaker/handlers/bodhi/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from .update_complete_stable import BodhiUpdateCompleteStableHandler # noqa diff --git a/freshmaker/handlers/bodhi/update_complete_stable.py b/freshmaker/handlers/bodhi/update_complete_stable.py deleted file mode 100644 index c7c97ae..0000000 --- a/freshmaker/handlers/bodhi/update_complete_stable.py +++ /dev/null @@ -1,95 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# Written by Chenxiong Qi - -from itertools import chain - -from freshmaker import conf -from freshmaker import log -from freshmaker import utils -from freshmaker.types import ArtifactType -from freshmaker.handlers import ContainerBuildHandler -from freshmaker.events import BodhiUpdateCompleteStableEvent -from freshmaker.pdc import PDC -from freshmaker.kojiservice import koji_service - - -class BodhiUpdateCompleteStableHandler(ContainerBuildHandler): - """Rebuild docker images when RPMs are synced by Bodhi""" - name = 'BodhiUpdateCompleteStableHandler' - - def can_handle(self, event): - return isinstance(event, BodhiUpdateCompleteStableEvent) - - def handle(self, event): - log.info('Rebuild docker images for event %s, msgid: %s', - BodhiUpdateCompleteStableEvent.__name__, event.msg_id) - - rpms = self.get_rpms_included_in_bodhi_update(event.builds) - containers = self.get_containers_including_rpms(rpms) - - log.info('Found docker images to rebuild: %s', containers) - - for container in containers: - if not self.allow_build(ArtifactType.IMAGE, name=container['name'], branch=container['branch']): - log.info("Skip rebuild of image %s:%s as it's not allowed by configured whitelist", - container['name'], container['branch']) - continue - try: - name = container['name'] - branch = container['branch'] - repo_url = '{}/{}/{}'.format(conf.git_base_url, 'container', name) - rev = utils.get_commit_hash(repo_url, branch=branch, logger=log) - - scm_url = "{}/{}/{}.git?#{}".format( - conf.git_base_url, 'container', name, rev) - - build_target = '{}-container-candidate'.format( - 'rawhide' if branch == 'master' else branch) - - task_id = self.build_container(scm_url, branch, build_target) - if task_id is not None: - self.record_build(event, container['name'], ArtifactType.IMAGE, task_id) - except Exception: - log.exception('Error when rebuild %s', container) - - return [] - - def get_rpms_included_in_bodhi_update(self, builds): - build_nvrs = (build['nvr'] for build in builds) - with koji_service(profile=conf.koji_profile, logger=log, - dry_run=conf.dry_run) as service: - return chain(*[service.get_build_rpms(nvr) for nvr in build_nvrs]) - - def get_containers_including_rpms(self, rpms): - containers = {} - pdc = PDC(conf) - for rpm in rpms: - found = pdc.find_containers_by_rpm_name(rpm['name']) - for container in found: - id = container['id'] - if id not in containers: - container_detail = pdc.get_release_component_by_id(id) - container['branch'] = container_detail['dist_git_branch'] - containers[id] = container - - return containers.values() diff --git a/freshmaker/handlers/brew/__init__.py b/freshmaker/handlers/brew/__init__.py deleted file mode 100644 index c77742c..0000000 --- a/freshmaker/handlers/brew/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from .sign_rpm import BrewSignRPMHandler # noqa -from .container_task_state_change import BrewContainerTaskStateChangeHandler # noqa diff --git a/freshmaker/handlers/brew/container_task_state_change.py b/freshmaker/handlers/brew/container_task_state_change.py deleted file mode 100644 index 26ae6a6..0000000 --- a/freshmaker/handlers/brew/container_task_state_change.py +++ /dev/null @@ -1,206 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import json -from kobo import rpmlib - -from freshmaker import conf -from freshmaker import log -from freshmaker import db -from freshmaker.errata import Errata -from freshmaker.events import ( - BrewContainerTaskStateChangeEvent, ErrataAdvisoryRPMsSignedEvent) -from freshmaker.models import ArtifactBuild, EVENT_TYPES -from freshmaker.handlers import ( - ContainerBuildHandler, fail_event_on_handler_exception) -from freshmaker.kojiservice import koji_service -from freshmaker.types import ArtifactType, ArtifactBuildState, EventState - - -class BrewContainerTaskStateChangeHandler(ContainerBuildHandler): - """Rebuild container when a dependecy container is built in Brew""" - - name = 'BrewContainerTaskStateChangeHandler' - - def can_handle(self, event): - return isinstance(event, BrewContainerTaskStateChangeEvent) - - @fail_event_on_handler_exception - def handle(self, event): - """ - When build container task state changed in brew, update build state in - db and rebuild containers depend on the success build as necessary. - """ - if event.dry_run: - self.force_dry_run() - - build_id = event.task_id - - # check db to see whether this build exists in db - found_build = db.session.query(ArtifactBuild).filter_by( - type=ArtifactType.IMAGE.value, - build_id=build_id - ).first() - - if found_build is not None: - self.set_context(found_build) - if found_build.event.state not in [EventState.INITIALIZED.value, - EventState.BUILDING.value]: - return - # update build state in db - if event.new_state == 'CLOSED': - # if build is triggered by an advisory, verify the container - # contains latest RPMs from the advisory - if found_build.event.event_type_id == EVENT_TYPES[ErrataAdvisoryRPMsSignedEvent]: - errata_id = found_build.event.search_key - # build_id is actually task id in build system, find out the actual build first - with koji_service( - conf.koji_profile, log, login=False, - dry_run=self.dry_run) as session: - container_build_id = session.get_container_build_id_from_task(build_id) - - ret, msg = self._verify_advisory_rpms_in_container_build(errata_id, container_build_id) - if ret: - found_build.transition(ArtifactBuildState.DONE.value, "Built successfully.") - else: - found_build.transition(ArtifactBuildState.FAILED.value, msg) - - # for other builds, mark them as DONE - else: - found_build.transition(ArtifactBuildState.DONE.value, "Built successfully.") - if event.new_state == 'FAILED': - args = json.loads(found_build.build_args) - if "retry_count" not in args: - args["retry_count"] = 0 - args["retry_count"] += 1 - found_build.build_args = json.dumps(args) - if args["retry_count"] < 3: - found_build.transition( - ArtifactBuildState.PLANNED.value, - "Retrying failed build %s" % (str(found_build.build_id))) - self.start_to_build_images([found_build]) - else: - found_build.transition( - ArtifactBuildState.FAILED.value, - "Failed to build in Koji.") - db.session.commit() - - if found_build.state == ArtifactBuildState.DONE.value: - # check db to see whether there is any planned image build - # depends on this build - planned_builds = db.session.query(ArtifactBuild).filter_by( - type=ArtifactType.IMAGE.value, - state=ArtifactBuildState.PLANNED.value, - dep_on=found_build - ).all() - - log.info("Found following PLANNED builds to rebuild that " - "depends on %r", found_build) - for build in planned_builds: - log.info(" %r", build) - - self.start_to_build_images(planned_builds) - - # Finally, we check if all builds scheduled by event - # found_build.event (ErrataAdvisoryRPMsSignedEvent) have been - # switched to FAILED or COMPLETE. If yes, mark the event COMPLETE. - self._mark_event_complete_when_all_builds_done(found_build.event) - - def _mark_event_complete_when_all_builds_done(self, db_event): - """Mark ErrataAdvisoryRPMsSignedEvent COMPLETE - - As we know that docker images are scheduled to be rebuilt by hanlding - event ErrataAdvisoryRPMsSignedEvent. When all those builds are done, - the event should be marked as COMPLETE accordingly. If not all finish, - nothing change to the state. - - :param Event db_event: instance of Event that represents an event - ErrataAdvisoryRPMsSignedEvent. - """ - num_failed = 0 - for build in db_event.builds: - if build.state == ArtifactBuildState.FAILED.value: - num_failed += 1 - elif build.state != ArtifactBuildState.DONE.value: - # Return when build is not DONE and also not FAILED, it means - # it's still building. - return - - if num_failed: - db_event.transition( - EventState.COMPLETE, - '%d of %d container image(s) failed to rebuild.' % ( - num_failed, len(db_event.builds),)) - else: - db_event.transition( - EventState.COMPLETE, - 'All %s container images have been rebuilt.' % ( - len(db_event.builds),)) - - def _verify_advisory_rpms_in_container_build(self, errata_id, container_build_id): - """ - verify container built on brew has the latest rpms from an advisory - """ - if self.dry_run: - return (True, '') - - # Get rpms in advisory. There can be multiple versions of RPMs with - # the same name, so we group them by a name in `advisory_rpms_by_name` - # and use set of the nvrs as a value. - advisory_rpms_by_name = {} - e = Errata() - build_nvrs = e.get_builds(errata_id) - if build_nvrs: - with koji_service( - conf.koji_profile, log, login=False, - dry_run=self.dry_run) as session: - for build_nvr in build_nvrs: - build_rpms = session.get_build_rpms(build_nvr) - for rpm in build_rpms: - if rpm['name'] not in advisory_rpms_by_name: - advisory_rpms_by_name[rpm['name']] = set() - advisory_rpms_by_name[rpm['name']].add(rpm['nvr']) - - # get rpms in container - with koji_service( - conf.koji_profile, log, login=False, - dry_run=self.dry_run) as session: - container_rpms = session.get_rpms_in_container(container_build_id) - container_rpms_by_name = { - rpmlib.parse_nvr(x)['name']: x for x in container_rpms} - - # For each RPM name in advisory, check that the RPM exists in the - # built container and its version is the same as one RPM in the - # advisory. - unmatched_rpms = [] - for rpm_name, nvrs in advisory_rpms_by_name.items(): - if rpm_name not in container_rpms_by_name: - continue - container_rpm_nvr = container_rpms_by_name[rpm_name] - if container_rpm_nvr not in nvrs: - unmatched_rpms.append(rpm_name) - - if unmatched_rpms: - msg = ("The following RPMs in container build (%s) do not match " - "with the latest RPMs in advisory (%s):\n%s" % - (container_build_id, errata_id, unmatched_rpms)) - return (False, msg) - return (True, "") diff --git a/freshmaker/handlers/brew/sign_rpm.py b/freshmaker/handlers/brew/sign_rpm.py deleted file mode 100644 index ecc9995..0000000 --- a/freshmaker/handlers/brew/sign_rpm.py +++ /dev/null @@ -1,109 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2016 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# Written by Chenxiong Qi - -from freshmaker import db, log -from freshmaker.events import BrewSignRPMEvent, ErrataAdvisoryRPMsSignedEvent -from freshmaker.handlers import BaseHandler -from freshmaker.errata import Errata -from freshmaker.types import ArtifactType -from freshmaker.models import Event - - -class BrewSignRPMHandler(BaseHandler): - """ - Checks whether all RPMs in Errata advisories for signed package are signed - and in case they are, generates ErrataAdvisoryRPMsSignedEvent events for - each advisory. - """ - - name = 'BrewSignRPMHandler' - - def can_handle(self, event): - return isinstance(event, BrewSignRPMEvent) - - def _filter_out_existing_advisories(self, advisories): - """ - Filter out all advisories which have been already handled by - Freshmaker. - - :param advisories: List of ErrataAdvisory instances. - :rtype: List of ErrataAdvisory - :return: List of ErrataAdvisory instances without already handled - advisories. - """ - ret = [] - for advisory in advisories: - if (db.session.query(Event).filter_by( - search_key=str(advisory.errata_id)).count() != 0): - log.info("Skipping advisory %s (%d), already handled by " - "Freshmaker", advisory.name, advisory.errata_id) - continue - ret.append(advisory) - return ret - - def handle(self, event): - log.info("Finding out all advisories including %s", event.nvr) - - # When get a signed RPM, first step is to find out advisories - # containing that RPM and ensure all builds are signed. - errata = Errata() - advisories = errata.advisories_from_event(event) - - # Filter out advisories which are not allowed by configuration. - advisories = [ - advisory for advisory in advisories - if self.allow_build( - ArtifactType.IMAGE, - advisory_name=advisory.name, - advisory_security_impact=advisory.security_impact, - advisory_highest_cve_severity=advisory.highest_cve_severity, - advisory_state=advisory.state)] - - # Filter out advisories which are already in Freshmaker DB. - advisories = self._filter_out_existing_advisories(advisories) - - if not advisories: - log.info("No advisories found suitable for rebuilding Docker " - "images") - return [] - - if not all((errata.builds_signed(advisory.errata_id) - for advisory in advisories)): - log.info('Not all builds in %s are signed. Do not rebuild any ' - 'docker image until signed.', advisories) - return [] - - # Now we know that all advisories with this signed RPM have also other - # RPMs signed. We can then proceed and generate - # ErrataAdvisoryRPMsSignedEvent. - new_events = [] - for advisory in advisories: - new_event = ErrataAdvisoryRPMsSignedEvent( - event.msg_id + "." + str(advisory.name), advisory) - db_event = Event.create( - db.session, new_event.msg_id, new_event.search_key, - new_event.__class__, released=False) - db.session.add(db_event) - new_events.append(new_event) - db.session.commit() - return new_events diff --git a/freshmaker/handlers/errata/__init__.py b/freshmaker/handlers/errata/__init__.py deleted file mode 100644 index 3b24429..0000000 --- a/freshmaker/handlers/errata/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from .errata_advisory_rpms_signed import ErrataAdvisoryRPMsSignedHandler # noqa -from .errata_advisory_state_changed import ErrataAdvisoryStateChangedHandler # noqa diff --git a/freshmaker/handlers/errata/errata_advisory_rpms_signed.py b/freshmaker/handlers/errata/errata_advisory_rpms_signed.py deleted file mode 100644 index 91abf47..0000000 --- a/freshmaker/handlers/errata/errata_advisory_rpms_signed.py +++ /dev/null @@ -1,388 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# Written by Chenxiong Qi -# Written by Jan Kaluza - -import json -import koji - -from freshmaker import conf, db -from freshmaker.events import ErrataAdvisoryRPMsSignedEvent -from freshmaker.events import ManualRebuildWithAdvisoryEvent -from freshmaker.handlers import ContainerBuildHandler, fail_event_on_handler_exception -from freshmaker.lightblue import LightBlue -from freshmaker.pulp import Pulp -from freshmaker.errata import Errata -from freshmaker.types import ArtifactType, ArtifactBuildState, EventState -from freshmaker.models import Event, Compose -from freshmaker.utils import get_rebuilt_nvr - - -class ErrataAdvisoryRPMsSignedHandler(ContainerBuildHandler): - """ - Rebuilds all Docker images which contain packages from the Errata - advisory. - """ - - name = 'ErrataAdvisoryRPMsSignedHandler' - - def can_handle(self, event): - return isinstance(event, ErrataAdvisoryRPMsSignedEvent) - - @fail_event_on_handler_exception - def handle(self, event): - """ - Rebuilds all Docker images which contain packages from the Errata - advisory. - """ - - if event.dry_run: - self.force_dry_run() - - self.event = event - - # Generate the Database representation of `event`, it can be - # triggered by user, we want to track what happened - - db_event = Event.get_or_create( - db.session, event.msg_id, event.search_key, event.__class__, - released=False, manual=event.manual) - db.session.commit() - self.set_context(db_event) - - # Check if we are allowed to build this advisory. - if not self.event.is_allowed(self): - msg = ("Errata advisory {0} is not allowed by internal policy " - "to trigger rebuilds.".format(event.advisory.errata_id)) - db_event.transition(EventState.SKIPPED, msg) - db.session.commit() - self.log_info(msg) - return [] - - # Get and record all images to rebuild based on the current - # ErrataAdvisoryRPMsSignedEvent event. - batches = self._find_images_to_rebuild(db_event.search_key) - builds = self._record_batches(batches, event) - - if not builds: - msg = 'No container images to rebuild for advisory %r' % event.advisory.name - self.log_info(msg) - db_event.transition(EventState.SKIPPED, msg) - db.session.commit() - return [] - - if all([build.state == ArtifactBuildState.FAILED.value - for build in builds.values()]): - db_event.transition( - EventState.COMPLETE, - "No container images to rebuild, all are in failed state.") - db.session.commit() - return [] - - if event.advisory.state != 'SHIPPED_LIVE': - # If freshmaker is configured to rebuild images only when advisory - # moves to SHIPPED_LIVE state, there is no need to generate new - # composes for rebuild as all signed RPMs should already be - # available from official YUM repositories. - # - # Generate the ODCS compose with RPMs from the current advisory. - repo_urls = self.odcs.prepare_yum_repos_for_rebuilds(db_event) - self.log_info( - "Following repositories will be used for the rebuild:") - for url in repo_urls: - self.log_info(" - %s", url) - - # Log what we are going to rebuild - self._check_images_to_rebuild(db_event, builds) - self.start_to_build_images( - db_event.get_image_builds_in_first_batch(db.session)) - - msg = ('Waiting for composes to finish in order to start to ' - 'schedule images for rebuild.') - db_event.transition(EventState.BUILDING, msg) - - return [] - - def _check_images_to_rebuild(self, db_event, builds): - """ - Checks the images to rebuild and logs them using self.log_info(...). - :param Event db_event: Database Event associated with images. - :param builds dict: list of docker images to build as returned by - _find_images_to_rebuild(...). - """ - self.log_info('Found container images to rebuild in following order:') - batch = 0 - printed = [] - while (len(printed) != len(builds.values()) or - len(printed) != len(db_event.builds)): - self.log_info(' Batch %d:', batch) - old_printed_count = len(printed) - for build in builds.values(): - # Print build only if: - # a) It depends on other build, but this dependency has not - # been printed yet or ... - # b) ... it does not depend on other build and we are printing - # batch 0 - this handles the base images - # In call cases, print only builds which have not been printed - # so far. - if (build.original_nvr not in printed and - ((build.dep_on and build.dep_on.original_nvr in printed) or - (not build.dep_on and batch == 0))): - args = json.loads(build.build_args) - based_on = "based on %s" % args["parent"] \ - if args["parent"] else "base image" - self.log_info( - ' - %s#%s (%s)' % - (args["repository"], args["commit"], based_on)) - printed.append(build.original_nvr) - - # Nothing has been printed, that means the dependencies between - # images are not OK and we would loop forever. Instead of that, - # print error and stop the rebuild. - if old_printed_count == len(printed): - db_event.builds_transition( - ArtifactBuildState.FAILED.value, - "No image to be built in batch %d." % (batch)) - self.log_error("Dumping the builds:") - for build in builds.values(): - self.log_error(" %r", build.original_nvr) - self.log_error("Printed ones:") - for p in printed: - self.log_error(" %r", p) - break - - batch += 1 - - def _record_batches(self, batches, event, builds=None): - """ - Records the images from batches to database. - - :param batches list: Output of LightBlue._find_images_to_rebuild(...). - :param event ErrataAdvisoryRPMsSignedEvent: The event this handler - is currently handling. - :param builds dict: mappings from docker image build NVR to - corresponding ArtifactBuild object, e.g. - ``{brew_build_nvr: ArtifactBuild, ...}``. Previous builds returned - from this method can be passed to this call to be extended by - adding a new mappings after docker image is stored into database. - For the first time to call this method, builds could be None. - :return: a mapping between docker image build NVR and - corresponding ArtifactBuild object representing a future rebuild of - that docker image. It is extended by including those docker images - stored into database. - :rtype: dict - """ - db_event = Event.get_or_create( - db.session, event.msg_id, event.search_key, event.__class__) - - # Used as tmp dict with {brew_build_nvr: ArtifactBuild, ...} mapping. - builds = builds or {} - - # Cache for ODCS pulp composes. Key is white-spaced, sorted, list - # of content_sets. Value is Compose database object. - odcs_cache = {} - - for batch in batches: - for image in batch: - # Reset context to db_event for each iteration before - # the ArtifactBuild is created. - self.set_context(db_event) - - nvr = image["brew"]["build"] - if nvr in builds: - self.log_debug("Skipping recording build %s, " - "it is already in db", nvr) - continue - self.log_debug("Recording %s", nvr) - parent_nvr = image["parent"]["brew"]["build"] \ - if "parent" in image and image["parent"] else None - dep_on = builds[parent_nvr] if parent_nvr in builds else None - - # If this container image depends on another container image - # we are going to rebuild, use the new NVR of that image - # as a dependency instead of the original one. - if dep_on: - parent_nvr = dep_on.rebuilt_nvr - - if "error" in image and image["error"]: - state_reason = image["error"] - state = ArtifactBuildState.FAILED.value - elif dep_on and dep_on.state == ArtifactBuildState.FAILED.value: - # If this artifact build depends on a build which cannot - # be built by Freshmaker, mark this one as failed too. - state_reason = "Cannot build artifact, because its " \ - "dependency cannot be built." - state = ArtifactBuildState.FAILED.value - else: - state_reason = "" - state = ArtifactBuildState.PLANNED.value - - rebuilt_nvr = get_rebuilt_nvr(ArtifactType.IMAGE.value, nvr) - image_name = koji.parse_NVR(image["brew"]["build"])["name"] - - build = self.record_build( - event, image_name, ArtifactType.IMAGE, - dep_on=dep_on, - state=ArtifactBuildState.PLANNED.value, - original_nvr=nvr, - rebuilt_nvr=rebuilt_nvr) - - # Set context to particular build so logging shows this build - # in case of error. - self.set_context(build) - - build.transition(state, state_reason) - - build_args = {} - build_args["repository"] = image["repository"] - build_args["commit"] = image["commit"] - build_args["parent"] = parent_nvr - build_args["target"] = image["target"] - build_args["branch"] = image["git_branch"] - build_args["arches"] = image["arches"] - build_args["renewed_odcs_compose_ids"] = image["odcs_compose_ids"] - build.build_args = json.dumps(build_args) - - db.session.commit() - - if state != ArtifactBuildState.FAILED.value: - # Store odcs pulp compose to build - if image["generate_pulp_repos"]: - # Check if the compose for these content_sets is - # already cached and use it in this case. - cache_key = " ".join(sorted(image["content_sets"])) - if cache_key in odcs_cache: - db_compose = odcs_cache[cache_key] - else: - compose = self.odcs.prepare_pulp_repo( - build, image["content_sets"]) - - if build.state != ArtifactBuildState.FAILED.value: - db_compose = Compose(odcs_compose_id=compose['id']) - db.session.add(db_compose) - db.session.commit() - odcs_cache[cache_key] = db_compose - else: - db_compose = None - db.session.commit() - if db_compose: - build.add_composes(db.session, [db_compose]) - db.session.commit() - - # Unpublished images can contain unreleased RPMs, so generate - # the ODCS compose with all the RPMs in the image to allow - # installation of possibly unreleased RPMs. - if not image["published"]: - compose = self.odcs.prepare_odcs_compose_with_image_rpms(image) - if compose: - db_compose = Compose(odcs_compose_id=compose['id']) - db.session.add(db_compose) - db.session.commit() - build.add_composes(db.session, [db_compose]) - db.session.commit() - - builds[nvr] = build - - # Reset context to db_event. - self.set_context(db_event) - - return builds - - def _filter_out_not_allowed_builds(self, image): - """ - Helper method for _find_images_to_rebuild(...) to filter - out all images which are not allowed to build by configuration. - - :param ContainerImage image: Image to be checked. - :rtype: bool - :return: True when image should be filtered out. - """ - - parsed_nvr = koji.parse_NVR(image["brew"]["build"]) - - if not self.event.is_allowed( - self, image_name=parsed_nvr["name"], - image_version=parsed_nvr["version"], - image_release=parsed_nvr["release"]): - self.log_info("Skipping rebuild of image %s, not allowed by " - "configuration", image["brew"]["build"]) - return True - return False - - def _find_images_to_rebuild(self, errata_id): - """ - Finds docker rebuild images from each build added to specific Errata - advisory. - - Found images are yielded in proper rebuild order from base images to - leaf images through the docker build dependnecy chain. - - :param int errata_id: Errata ID. - """ - errata = Errata() - errata_id = int(errata_id) - - # Use the errata_id to find out Pulp repository IDs from Errata Tool - # and furthermore get content_sets from Pulp where signed RPM will end - # up eventually when advisories are shipped. - pulp_repo_ids = list(set(errata.get_pulp_repository_ids(errata_id))) - - pulp = Pulp(server_url=conf.pulp_server_url, - username=conf.pulp_username, - password=conf.pulp_password) - content_sets = pulp.get_content_set_by_repo_ids(pulp_repo_ids) - - self.log_info('RPMs from advisory ends up in following content sets: ' - '%s', content_sets) - - # Query images from LightBlue by signed RPM's srpm name and found - # content sets - lb = LightBlue(server_url=conf.lightblue_server_url, - cert=conf.lightblue_certificate, - private_key=conf.lightblue_private_key) - - # Check if we are allowed to rebuild unpublished images and clear - # published and release_category if so. - if self.event.is_allowed(self, published=True): - published = True - release_category = "Generally Available" - else: - published = None - release_category = None - - # Limit the Lightblue query to particular leaf images if set in Event. - leaf_container_images = None - if isinstance(self.event, ManualRebuildWithAdvisoryEvent): - leaf_container_images = self.event.container_images - - # For each SRPM NVR, find out all the containers which include - # this SRPM NVR. - srpm_nvrs = set(errata.get_builds(errata_id)) - self.log_info( - "Going to find all the container images to rebuild as " - "result of %r update.", srpm_nvrs) - batches = lb.find_images_to_rebuild( - srpm_nvrs, content_sets, - filter_fnc=self._filter_out_not_allowed_builds, - published=published, release_category=release_category, - leaf_container_images=leaf_container_images) - return batches diff --git a/freshmaker/handlers/errata/errata_advisory_state_changed.py b/freshmaker/handlers/errata/errata_advisory_state_changed.py deleted file mode 100644 index 7abf90e..0000000 --- a/freshmaker/handlers/errata/errata_advisory_state_changed.py +++ /dev/null @@ -1,122 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from freshmaker import db, log -from freshmaker.events import ( - ErrataAdvisoryStateChangedEvent, ErrataAdvisoryRPMsSignedEvent) -from freshmaker.models import Event, EVENT_TYPES -from freshmaker.handlers import BaseHandler, fail_event_on_handler_exception -from freshmaker.errata import Errata -from freshmaker.types import EventState - - -class ErrataAdvisoryStateChangedHandler(BaseHandler): - """Mark Errata advisory as released - - When an advisory state is changed to SHIPPED_LIVE, mark it as released in - associated event object of ``ErrataAdvisoryStateChangedHandler``. - - This is used to avoiding generating YUM repository to include RPMs - inlcuded in a SHIPPED_LIVE advisory, because at that state, RPMs will be - available in official YUM repositories. - """ - - name = 'ErrataAdvisoryStateChangedHandler' - - def can_handle(self, event): - if not isinstance(event, ErrataAdvisoryStateChangedEvent): - return False - - if 'rpm' not in event.advisory.content_types: - log.info('Skip non-RPM advisory %s.', event.advisory.errata_id) - return False - - return True - - @fail_event_on_handler_exception - def mark_as_released(self, errata_id): - """ - Marks the Errata advisory with `errata_id` ID as "released", so it - is not included in further container images rebuilds. - """ - # check db to see whether this advisory exists in db - db_event = db.session.query(Event).filter_by( - event_type_id=EVENT_TYPES[ErrataAdvisoryRPMsSignedEvent], - search_key=str(errata_id)).first() - if not db_event: - log.debug("Ignoring Errata advisory %d - it does not exist in " - "Freshmaker db.", errata_id) - return [] - - self.set_context(db_event) - - db_event.released = True - db.session.commit() - log.info("Errata advisory %d is now marked as released", errata_id) - - def rebuild_if_not_exists(self, event, errata_id): - """ - Initiates rebuild of artifacts based on Errata advisory with - `errata_id` id. - - :rtype: List of ErrataAdvisoryRPMsSignedEvent instances. - :return: List of extra events generated to initiate the rebuild. - """ - - db_event = db.session.query(Event).filter_by( - event_type_id=EVENT_TYPES[ErrataAdvisoryRPMsSignedEvent], - search_key=str(errata_id)).first() - if (db_event and db_event.state != EventState.FAILED.value and - not event.manual): - log.debug("Ignoring Errata advisory %d - it already exists in " - "Freshmaker db.", errata_id) - return [] - - # Get additional info from Errata to fill in the needed data. - errata = Errata() - advisories = errata.advisories_from_event(event) - if not advisories: - log.error("Unknown Errata advisory %d" % errata_id) - return [] - - log.info("Generating ErrataAdvisoryRPMsSignedEvent for Errata " - "advisory %d, because its state changed to %s.", errata_id, - event.advisory.state) - advisory = advisories[0] - new_event = ErrataAdvisoryRPMsSignedEvent( - event.msg_id + "." + str(advisory.name), advisory) - new_event.dry_run = event.dry_run - new_event.manual = event.manual - return [new_event] - - def handle(self, event): - errata_id = event.advisory.errata_id - state = event.advisory.state - - extra_events = [] - - if event.is_allowed(self): - extra_events += self.rebuild_if_not_exists(event, errata_id) - - if state == "SHIPPED_LIVE": - self.mark_as_released(errata_id) - - return extra_events diff --git a/freshmaker/handlers/git/__init__.py b/freshmaker/handlers/git/__init__.py deleted file mode 100644 index 247e34f..0000000 --- a/freshmaker/handlers/git/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2016 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from .dockerfile_change import GitDockerfileChangeHandler # noqa -from .module_metadata_change import GitModuleMetadataChangeHandler # noqa -from .rpm_spec_change import GitRPMSpecChangeHandler # noqa diff --git a/freshmaker/handlers/git/dockerfile_change.py b/freshmaker/handlers/git/dockerfile_change.py deleted file mode 100644 index 6ce3766..0000000 --- a/freshmaker/handlers/git/dockerfile_change.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# Written by Chenxiong Qi - -import koji - -from freshmaker import log, conf -from freshmaker.types import ArtifactType -from freshmaker.handlers import ContainerBuildHandler -from freshmaker.events import GitDockerfileChangeEvent - - -class GitDockerfileChangeHandler(ContainerBuildHandler): - name = 'GitDockerfileChangeHandler' - - def can_handle(self, event): - return isinstance(event, GitDockerfileChangeEvent) - - def handle(self, event): - """Rebuild docker image""" - log.info('Start to rebuild docker image %s.', event.container) - - if not self.allow_build(ArtifactType.IMAGE, name=event.container, branch=event.branch): - log.info("Skip rebuild of %s:%s as it's not allowed by configured whitelist", - event.container, event.branch) - return [] - - try: - name = event.container - branch = event.branch - rev = event.rev - scm_url = "{}/{}/{}.git?#{}".format( - conf.git_base_url, 'container', name, rev) - - build_target = '{}-container-candidate'.format( - 'rawhide' if branch == 'master' else branch) - - task_id = self.build_container(scm_url, branch, build_target) - if task_id is not None: - self.record_build(event, event.container, ArtifactType.IMAGE, task_id) - - except koji.krbV.Krb5Error as e: - log.exception('Failed to login Koji via Kerberos using GSSAPI. %s', e.args[1]) - except Exception: - log.exception('Could not create task to build docker image %s', event.container) - - return [] diff --git a/freshmaker/handlers/git/module_metadata_change.py b/freshmaker/handlers/git/module_metadata_change.py deleted file mode 100644 index 5575df0..0000000 --- a/freshmaker/handlers/git/module_metadata_change.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2016 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# Written by Jan Kaluza - - -from freshmaker import log -from freshmaker.types import ArtifactType -from freshmaker.handlers import BaseHandler -from freshmaker.events import GitModuleMetadataChangeEvent - - -class GitModuleMetadataChangeHandler(BaseHandler): - name = "GitModuleMetadataChangeHandler" - - def can_handle(self, event): - if isinstance(event, GitModuleMetadataChangeEvent): - return True - - return False - - def handle(self, event): - log.info("Triggering rebuild of module %s:%s, metadata updated (%s).", - event.module, event.branch, event.rev) - if not self.allow_build(ArtifactType.MODULE, name=event.module, branch=event.branch): - log.info("Skip rebuild of %s:%s as it's not allowed by configured whitelist", - event.module, event.branch) - return [] - - build_id = self.build_module(event.module, event.branch, event.rev) - if build_id is not None: - self.record_build(event, event.module, ArtifactType.MODULE, build_id) - - return [] diff --git a/freshmaker/handlers/git/rpm_spec_change.py b/freshmaker/handlers/git/rpm_spec_change.py deleted file mode 100644 index 1ce5c5c..0000000 --- a/freshmaker/handlers/git/rpm_spec_change.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2016 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from freshmaker import log, conf, utils -from freshmaker.types import ArtifactType -from freshmaker.pdc import PDC -from freshmaker.handlers import BaseHandler -from freshmaker.events import GitRPMSpecChangeEvent - - -class GitRPMSpecChangeHandler(BaseHandler): - name = "GitRPMSpecChangeHandler" - - def can_handle(self, event): - if isinstance(event, GitRPMSpecChangeEvent): - return True - - return False - - def handle(self, event): - """ - Rebuild module when spec file of rpm in module is updated. - """ - rpm = event.rpm - branch = event.branch - rev = event.rev - - log.info("Triggering rebuild of modules on event of rpm (%s:%s) spec updated, rev: %s.", - rpm, branch, rev) - - pdc = PDC(conf) - modules = pdc.get_latest_modules(component_name=rpm, - component_branch=branch, - active='true') - - for module in modules: - name = module['name'] - version = module['stream'] - if not self.allow_build(ArtifactType.MODULE, name=name, branch=version): - log.info("Skip rebuild of %s:%s as it's not allowed by configured whitelist", - name, version) - continue - log.info("Going to rebuild module '%s:%s'.", name, version) - commit_msg = "Bump to rebuild because of %s rpm spec update (%s)." % (rpm, rev) - rev = utils.bump_distgit_repo('modules', name, branch=version, commit_msg=commit_msg, logger=log) - build_id = self.build_module(name, version, rev) - if build_id is not None: - self.record_build(event, name, ArtifactType.MODULE, build_id) - - return [] diff --git a/freshmaker/handlers/internal/__init__.py b/freshmaker/handlers/internal/__init__.py new file mode 100644 index 0000000..e814291 --- /dev/null +++ b/freshmaker/handlers/internal/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from .update_db_on_advisory_change import UpdateDBOnAdvisoryChange # noqa +from .update_db_on_module_build import UpdateDBOnModuleBuild # noqa +from .generate_advisory_signed_event_on_rpm_sign import GenerateAdvisorySignedEventOnRPMSign # noqa diff --git a/freshmaker/handlers/internal/generate_advisory_signed_event_on_rpm_sign.py b/freshmaker/handlers/internal/generate_advisory_signed_event_on_rpm_sign.py new file mode 100644 index 0000000..bfa024d --- /dev/null +++ b/freshmaker/handlers/internal/generate_advisory_signed_event_on_rpm_sign.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2016 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Chenxiong Qi + +from freshmaker import db, log +from freshmaker.events import BrewSignRPMEvent, ErrataAdvisoryRPMsSignedEvent +from freshmaker.handlers import BaseHandler +from freshmaker.errata import Errata +from freshmaker.types import ArtifactType +from freshmaker.models import Event + + +class GenerateAdvisorySignedEventOnRPMSign(BaseHandler): + """ + Checks whether all RPMs in Errata advisories for signed package are signed + and in case they are, generates ErrataAdvisoryRPMsSignedEvent events for + each advisory. + """ + + name = 'GenerateAdvisorySignedEventOnRPMSign' + + def can_handle(self, event): + return isinstance(event, BrewSignRPMEvent) + + def _filter_out_existing_advisories(self, advisories): + """ + Filter out all advisories which have been already handled by + Freshmaker. + + :param advisories: List of ErrataAdvisory instances. + :rtype: List of ErrataAdvisory + :return: List of ErrataAdvisory instances without already handled + advisories. + """ + ret = [] + for advisory in advisories: + if (db.session.query(Event).filter_by( + search_key=str(advisory.errata_id)).count() != 0): + log.info("Skipping advisory %s (%d), already handled by " + "Freshmaker", advisory.name, advisory.errata_id) + continue + ret.append(advisory) + return ret + + def handle(self, event): + log.info("Finding out all advisories including %s", event.nvr) + + # When get a signed RPM, first step is to find out advisories + # containing that RPM and ensure all builds are signed. + errata = Errata() + advisories = errata.advisories_from_event(event) + + # Filter out advisories which are not allowed by configuration. + advisories = [ + advisory for advisory in advisories + if self.allow_build( + ArtifactType.IMAGE, + advisory_name=advisory.name, + advisory_security_impact=advisory.security_impact, + advisory_highest_cve_severity=advisory.highest_cve_severity, + advisory_state=advisory.state)] + + # Filter out advisories which are already in Freshmaker DB. + advisories = self._filter_out_existing_advisories(advisories) + + if not advisories: + log.info("No advisories found suitable for rebuilding Docker " + "images") + return [] + + if not all((errata.builds_signed(advisory.errata_id) + for advisory in advisories)): + log.info('Not all builds in %s are signed. Do not rebuild any ' + 'docker image until signed.', advisories) + return [] + + # Now we know that all advisories with this signed RPM have also other + # RPMs signed. We can then proceed and generate + # ErrataAdvisoryRPMsSignedEvent. + new_events = [] + for advisory in advisories: + new_event = ErrataAdvisoryRPMsSignedEvent( + event.msg_id + "." + str(advisory.name), advisory) + db_event = Event.create( + db.session, new_event.msg_id, new_event.search_key, + new_event.__class__, released=False) + db.session.add(db_event) + new_events.append(new_event) + db.session.commit() + return new_events diff --git a/freshmaker/handlers/internal/update_db_on_advisory_change.py b/freshmaker/handlers/internal/update_db_on_advisory_change.py new file mode 100644 index 0000000..26000c5 --- /dev/null +++ b/freshmaker/handlers/internal/update_db_on_advisory_change.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from freshmaker import db, log +from freshmaker.events import ( + ErrataAdvisoryStateChangedEvent, ErrataAdvisoryRPMsSignedEvent) +from freshmaker.models import Event, EVENT_TYPES +from freshmaker.handlers import BaseHandler, fail_event_on_handler_exception +from freshmaker.errata import Errata +from freshmaker.types import EventState + + +class UpdateDBOnAdvisoryChange(BaseHandler): + """Mark Errata advisory as released + + When an advisory state is changed to SHIPPED_LIVE, mark it as released in + associated event object of ``UpdateDBOnAdvisoryChange``. + + This is used to avoiding generating YUM repository to include RPMs + inlcuded in a SHIPPED_LIVE advisory, because at that state, RPMs will be + available in official YUM repositories. + """ + + name = 'UpdateDBOnAdvisoryChange' + + def can_handle(self, event): + if not isinstance(event, ErrataAdvisoryStateChangedEvent): + return False + + if 'rpm' not in event.advisory.content_types: + log.info('Skip non-RPM advisory %s.', event.advisory.errata_id) + return False + + return True + + @fail_event_on_handler_exception + def mark_as_released(self, errata_id): + """ + Marks the Errata advisory with `errata_id` ID as "released", so it + is not included in further container images rebuilds. + """ + # check db to see whether this advisory exists in db + db_event = db.session.query(Event).filter_by( + event_type_id=EVENT_TYPES[ErrataAdvisoryRPMsSignedEvent], + search_key=str(errata_id)).first() + if not db_event: + log.debug("Ignoring Errata advisory %d - it does not exist in " + "Freshmaker db.", errata_id) + return [] + + self.set_context(db_event) + + db_event.released = True + db.session.commit() + log.info("Errata advisory %d is now marked as released", errata_id) + + def rebuild_if_not_exists(self, event, errata_id): + """ + Initiates rebuild of artifacts based on Errata advisory with + `errata_id` id. + + :rtype: List of ErrataAdvisoryRPMsSignedEvent instances. + :return: List of extra events generated to initiate the rebuild. + """ + + db_event = db.session.query(Event).filter_by( + event_type_id=EVENT_TYPES[ErrataAdvisoryRPMsSignedEvent], + search_key=str(errata_id)).first() + if (db_event and db_event.state != EventState.FAILED.value and + not event.manual): + log.debug("Ignoring Errata advisory %d - it already exists in " + "Freshmaker db.", errata_id) + return [] + + # Get additional info from Errata to fill in the needed data. + errata = Errata() + advisories = errata.advisories_from_event(event) + if not advisories: + log.error("Unknown Errata advisory %d" % errata_id) + return [] + + log.info("Generating ErrataAdvisoryRPMsSignedEvent for Errata " + "advisory %d, because its state changed to %s.", errata_id, + event.advisory.state) + advisory = advisories[0] + new_event = ErrataAdvisoryRPMsSignedEvent( + event.msg_id + "." + str(advisory.name), advisory) + new_event.dry_run = event.dry_run + new_event.manual = event.manual + return [new_event] + + def handle(self, event): + errata_id = event.advisory.errata_id + state = event.advisory.state + + extra_events = [] + + if event.is_allowed(self): + extra_events += self.rebuild_if_not_exists(event, errata_id) + + if state == "SHIPPED_LIVE": + self.mark_as_released(errata_id) + + return extra_events diff --git a/freshmaker/handlers/internal/update_db_on_module_build.py b/freshmaker/handlers/internal/update_db_on_module_build.py new file mode 100644 index 0000000..50fa0d0 --- /dev/null +++ b/freshmaker/handlers/internal/update_db_on_module_build.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2016 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Jan Kaluza + +from freshmaker import log, conf, utils, db, models +from freshmaker.types import ArtifactType, ArtifactBuildState +from freshmaker.mbs import MBS +from freshmaker.pdc import PDC +from freshmaker.handlers import BaseHandler, fail_event_on_handler_exception +from freshmaker.events import MBSModuleStateChangeEvent + + +class UpdateDBOnModuleBuild(BaseHandler): + name = "UpdateDBOnModuleBuild" + + def can_handle(self, event): + if isinstance(event, MBSModuleStateChangeEvent): + return True + + return False + + @fail_event_on_handler_exception + def handle(self, event): + """ + Update build state in db when module state changed in MBS and the + build is submitted by Freshmaker (can find that build in db). If + build state is 'ready', query PDC to get all modules that depends + on this module, rebuild all these depending modules. + """ + module_name = event.module + module_stream = event.stream + build_id = event.build_id + build_state = event.build_state + + module_build = None + # update build state if the build is submitted by Freshmaker + builds = db.session.query(models.ArtifactBuild).filter_by(build_id=build_id, + type=ArtifactType.MODULE.value).all() + if len(builds) > 1: + raise RuntimeError("Found duplicate module build '%s' in db" % build_id) + if len(builds) == 1: + # we can find this build in DB + module_build = builds.pop() + self.set_context(module_build) + if build_state in [MBS.BUILD_STATES['ready'], MBS.BUILD_STATES['failed']]: + log.info("Module build '%s' state changed in MBS, updating it in db.", build_id) + if build_state == MBS.BUILD_STATES['ready']: + module_build.state = ArtifactBuildState.DONE.value + if build_state == MBS.BUILD_STATES['failed']: + module_build.state = ArtifactBuildState.FAILED.value + db.session.commit() + + # Rebuild depending modules when state of MBSModuleStateChangeEvent is 'ready' + if build_state == MBS.BUILD_STATES['ready']: + log.info("Triggering rebuild of modules depending on %s:%s " + "in MBS", module_name, module_stream) + + if module_build: + # we have this build recorded in DB, check to prevent + # cyclic build loop + root_dep = module_build.get_root_dep_on() + if root_dep and root_dep.name == module_name: + log.info("Skipping the rebuild triggered by %s:%s as it will" + "result in cyclic build loop.", module_name, module_stream) + return [] + + pdc = PDC(conf) + modules = pdc.get_latest_modules(build_dep_name=module_name, + build_dep_stream=module_stream, + active='true') + + for mod in modules: + name = mod['name'] + version = mod['stream'] + if not self.allow_build(ArtifactType.MODULE, name=name, branch=version): + log.info("Skip rebuild of %s:%s as it's not allowed by configured whitelist", + name, version) + continue + # bump module repo first + commit_msg = "Bump to rebuild because of %s update" % module_name + rev = utils.bump_distgit_repo('modules', name, branch=version, commit_msg=commit_msg, logger=log) + new_build_id = self.build_module(name, version, rev) + if new_build_id is not None: + self.record_build(event, name, ArtifactType.MODULE, new_build_id, dep_on=module_build) + + return [] diff --git a/freshmaker/handlers/koji/__init__.py b/freshmaker/handlers/koji/__init__.py index dbb4f12..14a69fa 100644 --- a/freshmaker/handlers/koji/__init__.py +++ b/freshmaker/handlers/koji/__init__.py @@ -19,4 +19,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from .task_state_change import KojiTaskStateChangeHandler # noqa +from .rebuild_images_on_git_dockerfile_change import RebuildImagesOnGitDockerfileChange # noqa +from .rebuild_images_on_odcs_compose_done import RebuildImagesOnODCSComposeDone # noqa +from .rebuild_images_on_parent_image_build import RebuildImagesOnParentImageBuild # noqa +from .rebuild_images_on_rpm_advisory_change import RebuildImagesOnRPMAdvisoryChange # noqa +from .rebuild_images_on_rpm_bodhi_update import RebuildImagesOnRPMBodhiUpdate # noqa diff --git a/freshmaker/handlers/koji/rebuild_images_on_git_dockerfile_change.py b/freshmaker/handlers/koji/rebuild_images_on_git_dockerfile_change.py new file mode 100644 index 0000000..16832fc --- /dev/null +++ b/freshmaker/handlers/koji/rebuild_images_on_git_dockerfile_change.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Chenxiong Qi + +import koji + +from freshmaker import log, conf +from freshmaker.types import ArtifactType +from freshmaker.handlers import ContainerBuildHandler +from freshmaker.events import GitDockerfileChangeEvent + + +class RebuildImagesOnGitDockerfileChange(ContainerBuildHandler): + name = 'RebuildImagesOnGitDockerfileChange' + + def can_handle(self, event): + return isinstance(event, GitDockerfileChangeEvent) + + def handle(self, event): + """Rebuild docker image""" + log.info('Start to rebuild docker image %s.', event.container) + + if not self.allow_build(ArtifactType.IMAGE, name=event.container, branch=event.branch): + log.info("Skip rebuild of %s:%s as it's not allowed by configured whitelist", + event.container, event.branch) + return [] + + try: + name = event.container + branch = event.branch + rev = event.rev + scm_url = "{}/{}/{}.git?#{}".format( + conf.git_base_url, 'container', name, rev) + + build_target = '{}-container-candidate'.format( + 'rawhide' if branch == 'master' else branch) + + task_id = self.build_container(scm_url, branch, build_target) + if task_id is not None: + self.record_build(event, event.container, ArtifactType.IMAGE, task_id) + + except koji.krbV.Krb5Error as e: + log.exception('Failed to login Koji via Kerberos using GSSAPI. %s', e.args[1]) + except Exception: + log.exception('Could not create task to build docker image %s', event.container) + + return [] diff --git a/freshmaker/handlers/koji/rebuild_images_on_odcs_compose_done.py b/freshmaker/handlers/koji/rebuild_images_on_odcs_compose_done.py new file mode 100644 index 0000000..af8fffe --- /dev/null +++ b/freshmaker/handlers/koji/rebuild_images_on_odcs_compose_done.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Chenxiong Qi + +import six + +from freshmaker import db +from freshmaker.models import ArtifactBuild, ArtifactBuildState, Compose +from freshmaker.handlers import ( + ContainerBuildHandler, fail_event_on_handler_exception) +from freshmaker.events import ODCSComposeStateChangeEvent + +from odcs.common.types import COMPOSE_STATES + +__all__ = ('RebuildImagesOnODCSComposeDone',) + + +class RebuildImagesOnODCSComposeDone(ContainerBuildHandler): + """Start image rebuild with this compose containing included packages""" + + def can_handle(self, event): + if not isinstance(event, ODCSComposeStateChangeEvent): + return False + return event.compose['state'] == COMPOSE_STATES['done'] + + @fail_event_on_handler_exception + def handle(self, event): + if event.dry_run: + self.force_dry_run() + + query = db.session.query(ArtifactBuild).join('composes') + first_batch_builds = query.filter( + ArtifactBuild.dep_on == None, # noqa + ArtifactBuild.state == ArtifactBuildState.PLANNED.value, + Compose.odcs_compose_id == event.compose['id']) + if self.dry_run: + builds_ready_to_rebuild = first_batch_builds + else: + builds_ready_to_rebuild = six.moves.filter( + lambda build: build.composes_ready, first_batch_builds) + self.start_to_build_images(builds_ready_to_rebuild) diff --git a/freshmaker/handlers/koji/rebuild_images_on_parent_image_build.py b/freshmaker/handlers/koji/rebuild_images_on_parent_image_build.py new file mode 100644 index 0000000..f560dc3 --- /dev/null +++ b/freshmaker/handlers/koji/rebuild_images_on_parent_image_build.py @@ -0,0 +1,206 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import json +from kobo import rpmlib + +from freshmaker import conf +from freshmaker import log +from freshmaker import db +from freshmaker.errata import Errata +from freshmaker.events import ( + BrewContainerTaskStateChangeEvent, ErrataAdvisoryRPMsSignedEvent) +from freshmaker.models import ArtifactBuild, EVENT_TYPES +from freshmaker.handlers import ( + ContainerBuildHandler, fail_event_on_handler_exception) +from freshmaker.kojiservice import koji_service +from freshmaker.types import ArtifactType, ArtifactBuildState, EventState + + +class RebuildImagesOnParentImageBuild(ContainerBuildHandler): + """Rebuild container when a dependecy container is built in Brew""" + + name = 'RebuildImagesOnParentImageBuild' + + def can_handle(self, event): + return isinstance(event, BrewContainerTaskStateChangeEvent) + + @fail_event_on_handler_exception + def handle(self, event): + """ + When build container task state changed in brew, update build state in + db and rebuild containers depend on the success build as necessary. + """ + if event.dry_run: + self.force_dry_run() + + build_id = event.task_id + + # check db to see whether this build exists in db + found_build = db.session.query(ArtifactBuild).filter_by( + type=ArtifactType.IMAGE.value, + build_id=build_id + ).first() + + if found_build is not None: + self.set_context(found_build) + if found_build.event.state not in [EventState.INITIALIZED.value, + EventState.BUILDING.value]: + return + # update build state in db + if event.new_state == 'CLOSED': + # if build is triggered by an advisory, verify the container + # contains latest RPMs from the advisory + if found_build.event.event_type_id == EVENT_TYPES[ErrataAdvisoryRPMsSignedEvent]: + errata_id = found_build.event.search_key + # build_id is actually task id in build system, find out the actual build first + with koji_service( + conf.koji_profile, log, login=False, + dry_run=self.dry_run) as session: + container_build_id = session.get_container_build_id_from_task(build_id) + + ret, msg = self._verify_advisory_rpms_in_container_build(errata_id, container_build_id) + if ret: + found_build.transition(ArtifactBuildState.DONE.value, "Built successfully.") + else: + found_build.transition(ArtifactBuildState.FAILED.value, msg) + + # for other builds, mark them as DONE + else: + found_build.transition(ArtifactBuildState.DONE.value, "Built successfully.") + if event.new_state == 'FAILED': + args = json.loads(found_build.build_args) + if "retry_count" not in args: + args["retry_count"] = 0 + args["retry_count"] += 1 + found_build.build_args = json.dumps(args) + if args["retry_count"] < 3: + found_build.transition( + ArtifactBuildState.PLANNED.value, + "Retrying failed build %s" % (str(found_build.build_id))) + self.start_to_build_images([found_build]) + else: + found_build.transition( + ArtifactBuildState.FAILED.value, + "Failed to build in Koji.") + db.session.commit() + + if found_build.state == ArtifactBuildState.DONE.value: + # check db to see whether there is any planned image build + # depends on this build + planned_builds = db.session.query(ArtifactBuild).filter_by( + type=ArtifactType.IMAGE.value, + state=ArtifactBuildState.PLANNED.value, + dep_on=found_build + ).all() + + log.info("Found following PLANNED builds to rebuild that " + "depends on %r", found_build) + for build in planned_builds: + log.info(" %r", build) + + self.start_to_build_images(planned_builds) + + # Finally, we check if all builds scheduled by event + # found_build.event (ErrataAdvisoryRPMsSignedEvent) have been + # switched to FAILED or COMPLETE. If yes, mark the event COMPLETE. + self._mark_event_complete_when_all_builds_done(found_build.event) + + def _mark_event_complete_when_all_builds_done(self, db_event): + """Mark ErrataAdvisoryRPMsSignedEvent COMPLETE + + As we know that docker images are scheduled to be rebuilt by hanlding + event ErrataAdvisoryRPMsSignedEvent. When all those builds are done, + the event should be marked as COMPLETE accordingly. If not all finish, + nothing change to the state. + + :param Event db_event: instance of Event that represents an event + ErrataAdvisoryRPMsSignedEvent. + """ + num_failed = 0 + for build in db_event.builds: + if build.state == ArtifactBuildState.FAILED.value: + num_failed += 1 + elif build.state != ArtifactBuildState.DONE.value: + # Return when build is not DONE and also not FAILED, it means + # it's still building. + return + + if num_failed: + db_event.transition( + EventState.COMPLETE, + '%d of %d container image(s) failed to rebuild.' % ( + num_failed, len(db_event.builds),)) + else: + db_event.transition( + EventState.COMPLETE, + 'All %s container images have been rebuilt.' % ( + len(db_event.builds),)) + + def _verify_advisory_rpms_in_container_build(self, errata_id, container_build_id): + """ + verify container built on brew has the latest rpms from an advisory + """ + if self.dry_run: + return (True, '') + + # Get rpms in advisory. There can be multiple versions of RPMs with + # the same name, so we group them by a name in `advisory_rpms_by_name` + # and use set of the nvrs as a value. + advisory_rpms_by_name = {} + e = Errata() + build_nvrs = e.get_builds(errata_id) + if build_nvrs: + with koji_service( + conf.koji_profile, log, login=False, + dry_run=self.dry_run) as session: + for build_nvr in build_nvrs: + build_rpms = session.get_build_rpms(build_nvr) + for rpm in build_rpms: + if rpm['name'] not in advisory_rpms_by_name: + advisory_rpms_by_name[rpm['name']] = set() + advisory_rpms_by_name[rpm['name']].add(rpm['nvr']) + + # get rpms in container + with koji_service( + conf.koji_profile, log, login=False, + dry_run=self.dry_run) as session: + container_rpms = session.get_rpms_in_container(container_build_id) + container_rpms_by_name = { + rpmlib.parse_nvr(x)['name']: x for x in container_rpms} + + # For each RPM name in advisory, check that the RPM exists in the + # built container and its version is the same as one RPM in the + # advisory. + unmatched_rpms = [] + for rpm_name, nvrs in advisory_rpms_by_name.items(): + if rpm_name not in container_rpms_by_name: + continue + container_rpm_nvr = container_rpms_by_name[rpm_name] + if container_rpm_nvr not in nvrs: + unmatched_rpms.append(rpm_name) + + if unmatched_rpms: + msg = ("The following RPMs in container build (%s) do not match " + "with the latest RPMs in advisory (%s):\n%s" % + (container_build_id, errata_id, unmatched_rpms)) + return (False, msg) + return (True, "") diff --git a/freshmaker/handlers/koji/rebuild_images_on_rpm_advisory_change.py b/freshmaker/handlers/koji/rebuild_images_on_rpm_advisory_change.py new file mode 100644 index 0000000..ee98bf0 --- /dev/null +++ b/freshmaker/handlers/koji/rebuild_images_on_rpm_advisory_change.py @@ -0,0 +1,388 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Chenxiong Qi +# Written by Jan Kaluza + +import json +import koji + +from freshmaker import conf, db +from freshmaker.events import ErrataAdvisoryRPMsSignedEvent +from freshmaker.events import ManualRebuildWithAdvisoryEvent +from freshmaker.handlers import ContainerBuildHandler, fail_event_on_handler_exception +from freshmaker.lightblue import LightBlue +from freshmaker.pulp import Pulp +from freshmaker.errata import Errata +from freshmaker.types import ArtifactType, ArtifactBuildState, EventState +from freshmaker.models import Event, Compose +from freshmaker.utils import get_rebuilt_nvr + + +class RebuildImagesOnRPMAdvisoryChange(ContainerBuildHandler): + """ + Rebuilds all Docker images which contain packages from the Errata + advisory. + """ + + name = 'RebuildImagesOnRPMAdvisoryChange' + + def can_handle(self, event): + return isinstance(event, ErrataAdvisoryRPMsSignedEvent) + + @fail_event_on_handler_exception + def handle(self, event): + """ + Rebuilds all Docker images which contain packages from the Errata + advisory. + """ + + if event.dry_run: + self.force_dry_run() + + self.event = event + + # Generate the Database representation of `event`, it can be + # triggered by user, we want to track what happened + + db_event = Event.get_or_create( + db.session, event.msg_id, event.search_key, event.__class__, + released=False, manual=event.manual) + db.session.commit() + self.set_context(db_event) + + # Check if we are allowed to build this advisory. + if not self.event.is_allowed(self): + msg = ("Errata advisory {0} is not allowed by internal policy " + "to trigger rebuilds.".format(event.advisory.errata_id)) + db_event.transition(EventState.SKIPPED, msg) + db.session.commit() + self.log_info(msg) + return [] + + # Get and record all images to rebuild based on the current + # ErrataAdvisoryRPMsSignedEvent event. + batches = self._find_images_to_rebuild(db_event.search_key) + builds = self._record_batches(batches, event) + + if not builds: + msg = 'No container images to rebuild for advisory %r' % event.advisory.name + self.log_info(msg) + db_event.transition(EventState.SKIPPED, msg) + db.session.commit() + return [] + + if all([build.state == ArtifactBuildState.FAILED.value + for build in builds.values()]): + db_event.transition( + EventState.COMPLETE, + "No container images to rebuild, all are in failed state.") + db.session.commit() + return [] + + if event.advisory.state != 'SHIPPED_LIVE': + # If freshmaker is configured to rebuild images only when advisory + # moves to SHIPPED_LIVE state, there is no need to generate new + # composes for rebuild as all signed RPMs should already be + # available from official YUM repositories. + # + # Generate the ODCS compose with RPMs from the current advisory. + repo_urls = self.odcs.prepare_yum_repos_for_rebuilds(db_event) + self.log_info( + "Following repositories will be used for the rebuild:") + for url in repo_urls: + self.log_info(" - %s", url) + + # Log what we are going to rebuild + self._check_images_to_rebuild(db_event, builds) + self.start_to_build_images( + db_event.get_image_builds_in_first_batch(db.session)) + + msg = ('Waiting for composes to finish in order to start to ' + 'schedule images for rebuild.') + db_event.transition(EventState.BUILDING, msg) + + return [] + + def _check_images_to_rebuild(self, db_event, builds): + """ + Checks the images to rebuild and logs them using self.log_info(...). + :param Event db_event: Database Event associated with images. + :param builds dict: list of docker images to build as returned by + _find_images_to_rebuild(...). + """ + self.log_info('Found container images to rebuild in following order:') + batch = 0 + printed = [] + while (len(printed) != len(builds.values()) or + len(printed) != len(db_event.builds)): + self.log_info(' Batch %d:', batch) + old_printed_count = len(printed) + for build in builds.values(): + # Print build only if: + # a) It depends on other build, but this dependency has not + # been printed yet or ... + # b) ... it does not depend on other build and we are printing + # batch 0 - this handles the base images + # In call cases, print only builds which have not been printed + # so far. + if (build.original_nvr not in printed and + ((build.dep_on and build.dep_on.original_nvr in printed) or + (not build.dep_on and batch == 0))): + args = json.loads(build.build_args) + based_on = "based on %s" % args["parent"] \ + if args["parent"] else "base image" + self.log_info( + ' - %s#%s (%s)' % + (args["repository"], args["commit"], based_on)) + printed.append(build.original_nvr) + + # Nothing has been printed, that means the dependencies between + # images are not OK and we would loop forever. Instead of that, + # print error and stop the rebuild. + if old_printed_count == len(printed): + db_event.builds_transition( + ArtifactBuildState.FAILED.value, + "No image to be built in batch %d." % (batch)) + self.log_error("Dumping the builds:") + for build in builds.values(): + self.log_error(" %r", build.original_nvr) + self.log_error("Printed ones:") + for p in printed: + self.log_error(" %r", p) + break + + batch += 1 + + def _record_batches(self, batches, event, builds=None): + """ + Records the images from batches to database. + + :param batches list: Output of LightBlue._find_images_to_rebuild(...). + :param event ErrataAdvisoryRPMsSignedEvent: The event this handler + is currently handling. + :param builds dict: mappings from docker image build NVR to + corresponding ArtifactBuild object, e.g. + ``{brew_build_nvr: ArtifactBuild, ...}``. Previous builds returned + from this method can be passed to this call to be extended by + adding a new mappings after docker image is stored into database. + For the first time to call this method, builds could be None. + :return: a mapping between docker image build NVR and + corresponding ArtifactBuild object representing a future rebuild of + that docker image. It is extended by including those docker images + stored into database. + :rtype: dict + """ + db_event = Event.get_or_create( + db.session, event.msg_id, event.search_key, event.__class__) + + # Used as tmp dict with {brew_build_nvr: ArtifactBuild, ...} mapping. + builds = builds or {} + + # Cache for ODCS pulp composes. Key is white-spaced, sorted, list + # of content_sets. Value is Compose database object. + odcs_cache = {} + + for batch in batches: + for image in batch: + # Reset context to db_event for each iteration before + # the ArtifactBuild is created. + self.set_context(db_event) + + nvr = image["brew"]["build"] + if nvr in builds: + self.log_debug("Skipping recording build %s, " + "it is already in db", nvr) + continue + self.log_debug("Recording %s", nvr) + parent_nvr = image["parent"]["brew"]["build"] \ + if "parent" in image and image["parent"] else None + dep_on = builds[parent_nvr] if parent_nvr in builds else None + + # If this container image depends on another container image + # we are going to rebuild, use the new NVR of that image + # as a dependency instead of the original one. + if dep_on: + parent_nvr = dep_on.rebuilt_nvr + + if "error" in image and image["error"]: + state_reason = image["error"] + state = ArtifactBuildState.FAILED.value + elif dep_on and dep_on.state == ArtifactBuildState.FAILED.value: + # If this artifact build depends on a build which cannot + # be built by Freshmaker, mark this one as failed too. + state_reason = "Cannot build artifact, because its " \ + "dependency cannot be built." + state = ArtifactBuildState.FAILED.value + else: + state_reason = "" + state = ArtifactBuildState.PLANNED.value + + rebuilt_nvr = get_rebuilt_nvr(ArtifactType.IMAGE.value, nvr) + image_name = koji.parse_NVR(image["brew"]["build"])["name"] + + build = self.record_build( + event, image_name, ArtifactType.IMAGE, + dep_on=dep_on, + state=ArtifactBuildState.PLANNED.value, + original_nvr=nvr, + rebuilt_nvr=rebuilt_nvr) + + # Set context to particular build so logging shows this build + # in case of error. + self.set_context(build) + + build.transition(state, state_reason) + + build_args = {} + build_args["repository"] = image["repository"] + build_args["commit"] = image["commit"] + build_args["parent"] = parent_nvr + build_args["target"] = image["target"] + build_args["branch"] = image["git_branch"] + build_args["arches"] = image["arches"] + build_args["renewed_odcs_compose_ids"] = image["odcs_compose_ids"] + build.build_args = json.dumps(build_args) + + db.session.commit() + + if state != ArtifactBuildState.FAILED.value: + # Store odcs pulp compose to build + if image["generate_pulp_repos"]: + # Check if the compose for these content_sets is + # already cached and use it in this case. + cache_key = " ".join(sorted(image["content_sets"])) + if cache_key in odcs_cache: + db_compose = odcs_cache[cache_key] + else: + compose = self.odcs.prepare_pulp_repo( + build, image["content_sets"]) + + if build.state != ArtifactBuildState.FAILED.value: + db_compose = Compose(odcs_compose_id=compose['id']) + db.session.add(db_compose) + db.session.commit() + odcs_cache[cache_key] = db_compose + else: + db_compose = None + db.session.commit() + if db_compose: + build.add_composes(db.session, [db_compose]) + db.session.commit() + + # Unpublished images can contain unreleased RPMs, so generate + # the ODCS compose with all the RPMs in the image to allow + # installation of possibly unreleased RPMs. + if not image["published"]: + compose = self.odcs.prepare_odcs_compose_with_image_rpms(image) + if compose: + db_compose = Compose(odcs_compose_id=compose['id']) + db.session.add(db_compose) + db.session.commit() + build.add_composes(db.session, [db_compose]) + db.session.commit() + + builds[nvr] = build + + # Reset context to db_event. + self.set_context(db_event) + + return builds + + def _filter_out_not_allowed_builds(self, image): + """ + Helper method for _find_images_to_rebuild(...) to filter + out all images which are not allowed to build by configuration. + + :param ContainerImage image: Image to be checked. + :rtype: bool + :return: True when image should be filtered out. + """ + + parsed_nvr = koji.parse_NVR(image["brew"]["build"]) + + if not self.event.is_allowed( + self, image_name=parsed_nvr["name"], + image_version=parsed_nvr["version"], + image_release=parsed_nvr["release"]): + self.log_info("Skipping rebuild of image %s, not allowed by " + "configuration", image["brew"]["build"]) + return True + return False + + def _find_images_to_rebuild(self, errata_id): + """ + Finds docker rebuild images from each build added to specific Errata + advisory. + + Found images are yielded in proper rebuild order from base images to + leaf images through the docker build dependnecy chain. + + :param int errata_id: Errata ID. + """ + errata = Errata() + errata_id = int(errata_id) + + # Use the errata_id to find out Pulp repository IDs from Errata Tool + # and furthermore get content_sets from Pulp where signed RPM will end + # up eventually when advisories are shipped. + pulp_repo_ids = list(set(errata.get_pulp_repository_ids(errata_id))) + + pulp = Pulp(server_url=conf.pulp_server_url, + username=conf.pulp_username, + password=conf.pulp_password) + content_sets = pulp.get_content_set_by_repo_ids(pulp_repo_ids) + + self.log_info('RPMs from advisory ends up in following content sets: ' + '%s', content_sets) + + # Query images from LightBlue by signed RPM's srpm name and found + # content sets + lb = LightBlue(server_url=conf.lightblue_server_url, + cert=conf.lightblue_certificate, + private_key=conf.lightblue_private_key) + + # Check if we are allowed to rebuild unpublished images and clear + # published and release_category if so. + if self.event.is_allowed(self, published=True): + published = True + release_category = "Generally Available" + else: + published = None + release_category = None + + # Limit the Lightblue query to particular leaf images if set in Event. + leaf_container_images = None + if isinstance(self.event, ManualRebuildWithAdvisoryEvent): + leaf_container_images = self.event.container_images + + # For each SRPM NVR, find out all the containers which include + # this SRPM NVR. + srpm_nvrs = set(errata.get_builds(errata_id)) + self.log_info( + "Going to find all the container images to rebuild as " + "result of %r update.", srpm_nvrs) + batches = lb.find_images_to_rebuild( + srpm_nvrs, content_sets, + filter_fnc=self._filter_out_not_allowed_builds, + published=published, release_category=release_category, + leaf_container_images=leaf_container_images) + return batches diff --git a/freshmaker/handlers/koji/rebuild_images_on_rpm_bodhi_update.py b/freshmaker/handlers/koji/rebuild_images_on_rpm_bodhi_update.py new file mode 100644 index 0000000..4fa0453 --- /dev/null +++ b/freshmaker/handlers/koji/rebuild_images_on_rpm_bodhi_update.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Chenxiong Qi + +from itertools import chain + +from freshmaker import conf +from freshmaker import log +from freshmaker import utils +from freshmaker.types import ArtifactType +from freshmaker.handlers import ContainerBuildHandler +from freshmaker.events import BodhiUpdateCompleteStableEvent +from freshmaker.pdc import PDC +from freshmaker.kojiservice import koji_service + + +class RebuildImagesOnRPMBodhiUpdate(ContainerBuildHandler): + """Rebuild docker images when RPMs are synced by Bodhi""" + name = 'RebuildImagesOnRPMBodhiUpdate' + + def can_handle(self, event): + return isinstance(event, BodhiUpdateCompleteStableEvent) + + def handle(self, event): + log.info('Rebuild docker images for event %s, msgid: %s', + BodhiUpdateCompleteStableEvent.__name__, event.msg_id) + + rpms = self.get_rpms_included_in_bodhi_update(event.builds) + containers = self.get_containers_including_rpms(rpms) + + log.info('Found docker images to rebuild: %s', containers) + + for container in containers: + if not self.allow_build(ArtifactType.IMAGE, name=container['name'], branch=container['branch']): + log.info("Skip rebuild of image %s:%s as it's not allowed by configured whitelist", + container['name'], container['branch']) + continue + try: + name = container['name'] + branch = container['branch'] + repo_url = '{}/{}/{}'.format(conf.git_base_url, 'container', name) + rev = utils.get_commit_hash(repo_url, branch=branch, logger=log) + + scm_url = "{}/{}/{}.git?#{}".format( + conf.git_base_url, 'container', name, rev) + + build_target = '{}-container-candidate'.format( + 'rawhide' if branch == 'master' else branch) + + task_id = self.build_container(scm_url, branch, build_target) + if task_id is not None: + self.record_build(event, container['name'], ArtifactType.IMAGE, task_id) + except Exception: + log.exception('Error when rebuild %s', container) + + return [] + + def get_rpms_included_in_bodhi_update(self, builds): + build_nvrs = (build['nvr'] for build in builds) + with koji_service(profile=conf.koji_profile, logger=log, + dry_run=conf.dry_run) as service: + return chain(*[service.get_build_rpms(nvr) for nvr in build_nvrs]) + + def get_containers_including_rpms(self, rpms): + containers = {} + pdc = PDC(conf) + for rpm in rpms: + found = pdc.find_containers_by_rpm_name(rpm['name']) + for container in found: + id = container['id'] + if id not in containers: + container_detail = pdc.get_release_component_by_id(id) + container['branch'] = container_detail['dist_git_branch'] + containers[id] = container + + return containers.values() diff --git a/freshmaker/handlers/koji/task_state_change.py b/freshmaker/handlers/koji/task_state_change.py deleted file mode 100644 index ea2c1af..0000000 --- a/freshmaker/handlers/koji/task_state_change.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from freshmaker import log, db, models -from freshmaker.types import ArtifactType, ArtifactBuildState -from freshmaker.handlers import BaseHandler, fail_event_on_handler_exception -from freshmaker.events import KojiTaskStateChangeEvent - - -class KojiTaskStateChangeHandler(BaseHandler): - name = "KojiTaskStateChangeHandler" - - def can_handle(self, event): - if isinstance(event, KojiTaskStateChangeEvent): - return True - - return False - - @fail_event_on_handler_exception - def handle(self, event): - task_id = event.task_id - task_state = event.task_state - - # check whether the task exists in db as image build - builds = db.session.query(models.ArtifactBuild).filter_by(build_id=task_id, - type=ArtifactType.IMAGE.value).all() - if len(builds) > 1: - raise RuntimeError("Found duplicate image build '%s' in db" % task_id) - if len(builds) == 1: - build = builds.pop() - self.set_context(build) - if task_state in ['CLOSED', 'FAILED']: - log.info("Image build '%s' state changed in koji, updating it in db.", task_id) - if task_state == 'CLOSED': - build.state = ArtifactBuildState.DONE.value - db.session.commit() - if task_state == 'FAILED': - build.state = ArtifactBuildState.FAILED.value - db.session.commit() - - return [] diff --git a/freshmaker/handlers/mbs/__init__.py b/freshmaker/handlers/mbs/__init__.py index eb95d91..4997bfe 100644 --- a/freshmaker/handlers/mbs/__init__.py +++ b/freshmaker/handlers/mbs/__init__.py @@ -19,4 +19,5 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from .module_state_change import MBSModuleStateChangeHandler # noqa +from .rebuild_modules_on_git_mmd_change import RebuildModulesOnGitMMDChange # noqa +from .rebuild_modules_on_git_rpm_spec_change import RebuildModulesOnGitRPMSpecChange # noqa diff --git a/freshmaker/handlers/mbs/module_state_change.py b/freshmaker/handlers/mbs/module_state_change.py deleted file mode 100644 index 243bb97..0000000 --- a/freshmaker/handlers/mbs/module_state_change.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2016 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# Written by Jan Kaluza - -from freshmaker import log, conf, utils, db, models -from freshmaker.types import ArtifactType, ArtifactBuildState -from freshmaker.mbs import MBS -from freshmaker.pdc import PDC -from freshmaker.handlers import BaseHandler, fail_event_on_handler_exception -from freshmaker.events import MBSModuleStateChangeEvent - - -class MBSModuleStateChangeHandler(BaseHandler): - name = "MBSModuleStateChangeHandler" - - def can_handle(self, event): - if isinstance(event, MBSModuleStateChangeEvent): - return True - - return False - - @fail_event_on_handler_exception - def handle(self, event): - """ - Update build state in db when module state changed in MBS and the - build is submitted by Freshmaker (can find that build in db). If - build state is 'ready', query PDC to get all modules that depends - on this module, rebuild all these depending modules. - """ - module_name = event.module - module_stream = event.stream - build_id = event.build_id - build_state = event.build_state - - module_build = None - # update build state if the build is submitted by Freshmaker - builds = db.session.query(models.ArtifactBuild).filter_by(build_id=build_id, - type=ArtifactType.MODULE.value).all() - if len(builds) > 1: - raise RuntimeError("Found duplicate module build '%s' in db" % build_id) - if len(builds) == 1: - # we can find this build in DB - module_build = builds.pop() - self.set_context(module_build) - if build_state in [MBS.BUILD_STATES['ready'], MBS.BUILD_STATES['failed']]: - log.info("Module build '%s' state changed in MBS, updating it in db.", build_id) - if build_state == MBS.BUILD_STATES['ready']: - module_build.state = ArtifactBuildState.DONE.value - if build_state == MBS.BUILD_STATES['failed']: - module_build.state = ArtifactBuildState.FAILED.value - db.session.commit() - - # Rebuild depending modules when state of MBSModuleStateChangeEvent is 'ready' - if build_state == MBS.BUILD_STATES['ready']: - log.info("Triggering rebuild of modules depending on %s:%s " - "in MBS", module_name, module_stream) - - if module_build: - # we have this build recorded in DB, check to prevent - # cyclic build loop - root_dep = module_build.get_root_dep_on() - if root_dep and root_dep.name == module_name: - log.info("Skipping the rebuild triggered by %s:%s as it will" - "result in cyclic build loop.", module_name, module_stream) - return [] - - pdc = PDC(conf) - modules = pdc.get_latest_modules(build_dep_name=module_name, - build_dep_stream=module_stream, - active='true') - - for mod in modules: - name = mod['name'] - version = mod['stream'] - if not self.allow_build(ArtifactType.MODULE, name=name, branch=version): - log.info("Skip rebuild of %s:%s as it's not allowed by configured whitelist", - name, version) - continue - # bump module repo first - commit_msg = "Bump to rebuild because of %s update" % module_name - rev = utils.bump_distgit_repo('modules', name, branch=version, commit_msg=commit_msg, logger=log) - new_build_id = self.build_module(name, version, rev) - if new_build_id is not None: - self.record_build(event, name, ArtifactType.MODULE, new_build_id, dep_on=module_build) - - return [] diff --git a/freshmaker/handlers/mbs/rebuild_modules_on_git_mmd_change.py b/freshmaker/handlers/mbs/rebuild_modules_on_git_mmd_change.py new file mode 100644 index 0000000..4f62b90 --- /dev/null +++ b/freshmaker/handlers/mbs/rebuild_modules_on_git_mmd_change.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2016 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Jan Kaluza + + +from freshmaker import log +from freshmaker.types import ArtifactType +from freshmaker.handlers import BaseHandler +from freshmaker.events import GitModuleMetadataChangeEvent + + +class RebuildModulesOnGitMMDChange(BaseHandler): + name = "RebuildModulesOnGitMMDChange" + + def can_handle(self, event): + if isinstance(event, GitModuleMetadataChangeEvent): + return True + + return False + + def handle(self, event): + log.info("Triggering rebuild of module %s:%s, metadata updated (%s).", + event.module, event.branch, event.rev) + if not self.allow_build(ArtifactType.MODULE, name=event.module, branch=event.branch): + log.info("Skip rebuild of %s:%s as it's not allowed by configured whitelist", + event.module, event.branch) + return [] + + build_id = self.build_module(event.module, event.branch, event.rev) + if build_id is not None: + self.record_build(event, event.module, ArtifactType.MODULE, build_id) + + return [] diff --git a/freshmaker/handlers/mbs/rebuild_modules_on_git_rpm_spec_change.py b/freshmaker/handlers/mbs/rebuild_modules_on_git_rpm_spec_change.py new file mode 100644 index 0000000..fe4a0cb --- /dev/null +++ b/freshmaker/handlers/mbs/rebuild_modules_on_git_rpm_spec_change.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2016 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from freshmaker import log, conf, utils +from freshmaker.types import ArtifactType +from freshmaker.pdc import PDC +from freshmaker.handlers import BaseHandler +from freshmaker.events import GitRPMSpecChangeEvent + + +class RebuildModulesOnGitRPMSpecChange(BaseHandler): + name = "RebuildModulesOnGitRPMSpecChange" + + def can_handle(self, event): + if isinstance(event, GitRPMSpecChangeEvent): + return True + + return False + + def handle(self, event): + """ + Rebuild module when spec file of rpm in module is updated. + """ + rpm = event.rpm + branch = event.branch + rev = event.rev + + log.info("Triggering rebuild of modules on event of rpm (%s:%s) spec updated, rev: %s.", + rpm, branch, rev) + + pdc = PDC(conf) + modules = pdc.get_latest_modules(component_name=rpm, + component_branch=branch, + active='true') + + for module in modules: + name = module['name'] + version = module['stream'] + if not self.allow_build(ArtifactType.MODULE, name=name, branch=version): + log.info("Skip rebuild of %s:%s as it's not allowed by configured whitelist", + name, version) + continue + log.info("Going to rebuild module '%s:%s'.", name, version) + commit_msg = "Bump to rebuild because of %s rpm spec update (%s)." % (rpm, rev) + rev = utils.bump_distgit_repo('modules', name, branch=version, commit_msg=commit_msg, logger=log) + build_id = self.build_module(name, version, rev) + if build_id is not None: + self.record_build(event, name, ArtifactType.MODULE, build_id) + + return [] diff --git a/freshmaker/handlers/odcs/__init__.py b/freshmaker/handlers/odcs/__init__.py deleted file mode 100644 index b29af86..0000000 --- a/freshmaker/handlers/odcs/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2016 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from .compose_state_change import ComposeStateChangeHandler # noqa diff --git a/freshmaker/handlers/odcs/compose_state_change.py b/freshmaker/handlers/odcs/compose_state_change.py deleted file mode 100644 index 254b371..0000000 --- a/freshmaker/handlers/odcs/compose_state_change.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# Written by Chenxiong Qi - -import six - -from freshmaker import db -from freshmaker.models import ArtifactBuild, ArtifactBuildState, Compose -from freshmaker.handlers import ( - ContainerBuildHandler, fail_event_on_handler_exception) -from freshmaker.events import ODCSComposeStateChangeEvent - -from odcs.common.types import COMPOSE_STATES - -__all__ = ('ComposeStateChangeHandler',) - - -class ComposeStateChangeHandler(ContainerBuildHandler): - """Start image rebuild with this compose containing included packages""" - - def can_handle(self, event): - if not isinstance(event, ODCSComposeStateChangeEvent): - return False - return event.compose['state'] == COMPOSE_STATES['done'] - - @fail_event_on_handler_exception - def handle(self, event): - if event.dry_run: - self.force_dry_run() - - query = db.session.query(ArtifactBuild).join('composes') - first_batch_builds = query.filter( - ArtifactBuild.dep_on == None, # noqa - ArtifactBuild.state == ArtifactBuildState.PLANNED.value, - Compose.odcs_compose_id == event.compose['id']) - if self.dry_run: - builds_ready_to_rebuild = first_batch_builds - else: - builds_ready_to_rebuild = six.moves.filter( - lambda build: build.composes_ready, first_batch_builds) - self.start_to_build_images(builds_ready_to_rebuild) diff --git a/tests/handlers/__init__.py b/tests/handlers/__init__.py new file mode 100644 index 0000000..043e3c6 --- /dev/null +++ b/tests/handlers/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2016 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Jan Kaluza diff --git a/tests/handlers/internal/__init__.py b/tests/handlers/internal/__init__.py new file mode 100644 index 0000000..043e3c6 --- /dev/null +++ b/tests/handlers/internal/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2016 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Jan Kaluza diff --git a/tests/handlers/internal/test_generate_advisory_signed_event_on_rpm_sign.py b/tests/handlers/internal/test_generate_advisory_signed_event_on_rpm_sign.py new file mode 100644 index 0000000..c3a7a17 --- /dev/null +++ b/tests/handlers/internal/test_generate_advisory_signed_event_on_rpm_sign.py @@ -0,0 +1,228 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Chenxiong Qi + +from mock import patch, MagicMock, PropertyMock + +from freshmaker.handlers.internal import GenerateAdvisorySignedEventOnRPMSign +from freshmaker.errata import ErrataAdvisory +from tests import helpers + + +class TestBrewSignHandler(helpers.ModelsTestCase): + """Test GenerateAdvisorySignedEventOnRPMSign.handle""" + + @patch('freshmaker.errata.Errata.advisories_from_event') + @patch('freshmaker.errata.Errata.builds_signed') + @patch("freshmaker.config.Config.handler_build_whitelist", + new_callable=PropertyMock, return_value={ + "GenerateAdvisorySignedEventOnRPMSign": {"image": {"advisory_name": "RHSA-.*"}}}) + def test_return_value(self, handler_build_whitelist, builds_signed, + advisories_from_event): + """ + Tests that handle method returns ErrataAdvisoryRPMsSignedEvent. + """ + advisories_from_event.return_value = [ + ErrataAdvisory(123, "RHSA-2017", "REL_PREP", ["rpm"])] + builds_signed.return_value = True + + event = MagicMock() + event.msg_id = "msg_123" + handler = GenerateAdvisorySignedEventOnRPMSign() + ret = handler.handle(event) + + self.assertTrue(len(ret), 1) + self.assertEqual(ret[0].advisory.name, "RHSA-2017") + self.assertEqual(ret[0].advisory.errata_id, 123) + + @patch('freshmaker.errata.Errata.advisories_from_event') + @patch('freshmaker.errata.Errata.builds_signed') + @patch("freshmaker.config.Config.handler_build_whitelist", + new_callable=PropertyMock, return_value={ + "global": {"image": {"advisory_name": "RHSA-.*"}}}) + def test_allow_build_false_global(self, handler_build_whitelist, + builds_signed, advisories_from_event): + """ + Tests that allow_build filters out advisories based on advisory_name. + """ + advisories_from_event.return_value = [ + ErrataAdvisory(123, "RHBA-2017", "REL_PREP", ["rpm"])] + builds_signed.return_value = False + + event = MagicMock() + handler = GenerateAdvisorySignedEventOnRPMSign() + ret = handler.handle(event) + + self.assertTrue(not ret) + builds_signed.assert_not_called() + + @patch('freshmaker.errata.Errata.advisories_from_event') + @patch('freshmaker.errata.Errata.builds_signed') + @patch("freshmaker.config.Config.handler_build_whitelist", + new_callable=PropertyMock, return_value={ + "global": {"image": {"advisory_name": "RHSA-.*"}}}) + def test_allow_build_true_global(self, handler_build_whitelist, + builds_signed, advisories_from_event): + """ + Tests that allow_build does not filter out advisories based on + advisory_name. + """ + advisories_from_event.return_value = [ + ErrataAdvisory(123, "RHSA-2017", "REL_PREP", ["rpm"])] + builds_signed.return_value = False + + event = MagicMock() + handler = GenerateAdvisorySignedEventOnRPMSign() + handler.handle(event) + + builds_signed.assert_called_once() + + @patch('freshmaker.errata.Errata.advisories_from_event') + @patch('freshmaker.errata.Errata.builds_signed') + @patch("freshmaker.config.Config.handler_build_whitelist", + new_callable=PropertyMock, return_value={ + "GenerateAdvisorySignedEventOnRPMSign": {"image": {"advisory_name": "RHSA-.*"}}}) + def test_allow_build_false(self, handler_build_whitelist, builds_signed, + advisories_from_event): + """ + Tests that allow_build filters out advisories based on advisory_name. + """ + advisories_from_event.return_value = [ + ErrataAdvisory(123, "RHBA-2017", "REL_PREP", ["rpm"])] + builds_signed.return_value = False + + event = MagicMock() + handler = GenerateAdvisorySignedEventOnRPMSign() + ret = handler.handle(event) + + self.assertTrue(not ret) + builds_signed.assert_not_called() + + @patch('freshmaker.errata.Errata.advisories_from_event') + @patch('freshmaker.errata.Errata.builds_signed') + @patch("freshmaker.config.Config.handler_build_whitelist", + new_callable=PropertyMock, return_value={ + "GenerateAdvisorySignedEventOnRPMSign": {"image": {"advisory_name": "RHSA-.*"}}}) + def test_allow_build_true(self, handler_build_whitelist, builds_signed, + advisories_from_event): + """ + Tests that allow_build does not filter out advisories based on + advisory_name. + """ + advisories_from_event.return_value = [ + ErrataAdvisory(123, "RHSA-2017", "REL_PREP", ["rpm"])] + builds_signed.return_value = False + + event = MagicMock() + handler = GenerateAdvisorySignedEventOnRPMSign() + handler.handle(event) + + builds_signed.assert_called_once() + + @patch('freshmaker.errata.Errata.advisories_from_event') + @patch('freshmaker.errata.Errata.builds_signed') + @patch( + "freshmaker.config.Config.handler_build_whitelist", + new_callable=PropertyMock, + return_value={ + "GenerateAdvisorySignedEventOnRPMSign": { + "image": { + "advisory_security_impact": [ + "Normal", "Important" + ] + } + } + }) + def test_allow_security_impact_important_true( + self, handler_build_whitelist, builds_signed, + advisories_from_event): + """ + Tests that allow_build does not filter out advisories based on + advisory_security_impact. + """ + advisories_from_event.return_value = [ + ErrataAdvisory(123, "RHSA-2017", "REL_PREP", ["rpm"], "Important")] + builds_signed.return_value = False + + event = MagicMock() + handler = GenerateAdvisorySignedEventOnRPMSign() + handler.handle(event) + + builds_signed.assert_called_once() + + @patch('freshmaker.errata.Errata.advisories_from_event') + @patch('freshmaker.errata.Errata.builds_signed') + @patch( + "freshmaker.config.Config.handler_build_whitelist", + new_callable=PropertyMock, + return_value={ + "GenerateAdvisorySignedEventOnRPMSign": { + "image": { + "advisory_security_impact": [ + "Normal", "Important" + ] + } + } + }) + def test_allow_security_impact_important_false( + self, handler_build_whitelist, builds_signed, + advisories_from_event): + """ + Tests that allow_build dost filter out advisories based on + advisory_security_impact. + """ + advisories_from_event.return_value = [ + ErrataAdvisory(123, "RHSA-2017", "REL_PREP", ["rpm"], "None")] + builds_signed.return_value = False + + event = MagicMock() + handler = GenerateAdvisorySignedEventOnRPMSign() + handler.handle(event) + + builds_signed.assert_not_called() + + @patch('freshmaker.errata.Errata.advisories_from_event') + @patch('freshmaker.errata.Errata.builds_signed') + @patch("freshmaker.config.Config.handler_build_whitelist", + new_callable=PropertyMock, return_value={ + "GenerateAdvisorySignedEventOnRPMSign": {"image": {"advisory_name": "RHSA-.*"}}}) + def test_do_not_create_already_handled_event( + self, handler_build_whitelist, builds_signed, + advisories_from_event): + """ + Tests that GenerateAdvisorySignedEventOnRPMSign don't return Event which already exists + in Freshmaker DB. + """ + builds_signed.return_value = True + advisories_from_event.return_value = [ + ErrataAdvisory(123, "RHSA-2017", "REL_PREP", ["rpm"])] + + event = MagicMock() + event.msg_id = "msg_123" + handler = GenerateAdvisorySignedEventOnRPMSign() + handler.handle(event) + + builds_signed.assert_called_once() + builds_signed.reset_mock() + + handler.handle(event) + builds_signed.assert_not_called() diff --git a/tests/handlers/internal/test_update_db_on_advisory_change.py b/tests/handlers/internal/test_update_db_on_advisory_change.py new file mode 100644 index 0000000..d9e6046 --- /dev/null +++ b/tests/handlers/internal/test_update_db_on_advisory_change.py @@ -0,0 +1,1104 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Chenxiong Qi + +import json + +from mock import patch, PropertyMock, Mock, call + +from freshmaker import conf, db, events +from freshmaker.config import all_ +from freshmaker.errata import ErrataAdvisory +from freshmaker.events import ErrataAdvisoryRPMsSignedEvent +from freshmaker.events import ErrataAdvisoryStateChangedEvent +from freshmaker.handlers.koji import RebuildImagesOnRPMAdvisoryChange +from freshmaker.handlers.internal import UpdateDBOnAdvisoryChange +from freshmaker.lightblue import ContainerImage +from freshmaker.models import Event, ArtifactBuild, EVENT_TYPES +from freshmaker.types import ArtifactBuildState, ArtifactType, EventState +from tests import helpers + + +class TestAllowBuild(helpers.ModelsTestCase): + """Test RebuildImagesOnRPMAdvisoryChange.allow_build""" + + @patch("freshmaker.handlers.koji.RebuildImagesOnRPMAdvisoryChange." + "_find_images_to_rebuild", return_value=[]) + @patch("freshmaker.config.Config.handler_build_whitelist", + new_callable=PropertyMock, return_value={ + "RebuildImagesOnRPMAdvisoryChange": {"image": {"advisory_name": "RHSA-.*"}}}) + def test_allow_build_false(self, handler_build_whitelist, record_images): + """ + Tests that allow_build filters out advisories based on advisory_name. + """ + event = ErrataAdvisoryRPMsSignedEvent( + "123", + ErrataAdvisory(123, "RHBA-2017", "REL_PREP", [], + security_impact="", + product_short_name="product")) + handler = RebuildImagesOnRPMAdvisoryChange() + handler.handle(event) + + record_images.assert_not_called() + + @patch("freshmaker.handlers.koji.RebuildImagesOnRPMAdvisoryChange." + "_find_images_to_rebuild", return_value=[]) + @patch("freshmaker.config.Config.handler_build_whitelist", + new_callable=PropertyMock, return_value={ + "RebuildImagesOnRPMAdvisoryChange": {"image": {"advisory_name": "RHSA-.*"}}}) + def test_allow_build_true(self, handler_build_whitelist, record_images): + """ + Tests that allow_build does not filter out advisories based on + advisory_name. + """ + event = ErrataAdvisoryRPMsSignedEvent( + "123", + ErrataAdvisory(123, "RHSA-2017", "REL_PREP", [], + security_impact="", + product_short_name="product")) + handler = RebuildImagesOnRPMAdvisoryChange() + handler.handle(event) + + record_images.assert_called_once() + self.assertEqual(handler.current_db_event_id, 1) + + @patch("freshmaker.handlers.koji.RebuildImagesOnRPMAdvisoryChange." + "_find_images_to_rebuild", return_value=[]) + @patch( + "freshmaker.config.Config.handler_build_whitelist", + new_callable=PropertyMock, + return_value={ + "RebuildImagesOnRPMAdvisoryChange": { + "image": { + "advisory_security_impact": [ + "Normal", "Important" + ], + "image_name": "foo", + } + } + }) + def test_allow_security_impact_important_true( + self, handler_build_whitelist, record_images): + """ + Tests that allow_build does not filter out advisories based on + advisory_security_impact. + """ + event = ErrataAdvisoryRPMsSignedEvent( + "123", + ErrataAdvisory(123, "RHSA-2017", "REL_PREP", [], + security_impact="Important", + product_short_name="product")) + handler = RebuildImagesOnRPMAdvisoryChange() + handler.handle(event) + + record_images.assert_called_once() + + @patch("freshmaker.handlers.koji.RebuildImagesOnRPMAdvisoryChange." + "_find_images_to_rebuild", return_value=[]) + @patch( + "freshmaker.config.Config.handler_build_whitelist", + new_callable=PropertyMock, + return_value={ + "RebuildImagesOnRPMAdvisoryChange": { + "image": { + "advisory_security_impact": [ + "Normal", "Important" + ] + } + } + }) + def test_allow_security_impact_important_false( + self, handler_build_whitelist, record_images): + """ + Tests that allow_build dost filter out advisories based on + advisory_security_impact. + """ + event = ErrataAdvisoryRPMsSignedEvent( + "123", + ErrataAdvisory(123, "RHSA-2017", "REL_PREP", [], + security_impact="None", + product_short_name="product")) + handler = RebuildImagesOnRPMAdvisoryChange() + handler.handle(event) + + record_images.assert_not_called() + + @patch( + "freshmaker.config.Config.handler_build_whitelist", + new_callable=PropertyMock, + return_value={ + "RebuildImagesOnRPMAdvisoryChange": { + "image": { + "image_name": ["foo", "bar"] + } + } + }) + def test_filter_out_not_allowed_builds( + self, handler_build_whitelist): + """ + Tests that allow_build does filter images based on image_name. + """ + + handler = RebuildImagesOnRPMAdvisoryChange() + handler.event = ErrataAdvisoryRPMsSignedEvent( + "123", + ErrataAdvisory(123, "RHSA-2017", "REL_PREP", [], + security_impact="None", + product_short_name="product")) + + image = {"brew": {"build": "foo-1-2.3"}} + ret = handler._filter_out_not_allowed_builds(image) + self.assertEqual(ret, False) + + image = {"brew": {"build": "foo2-1-2.3"}} + ret = handler._filter_out_not_allowed_builds(image) + self.assertEqual(ret, False) + + image = {"brew": {"build": "bar-1-2.3"}} + ret = handler._filter_out_not_allowed_builds(image) + self.assertEqual(ret, False) + + image = {"brew": {"build": "unknown-1-2.3"}} + ret = handler._filter_out_not_allowed_builds(image) + self.assertEqual(ret, True) + + @patch( + "freshmaker.config.Config.handler_build_whitelist", + new_callable=PropertyMock, + return_value={ + "RebuildImagesOnRPMAdvisoryChange": { + "image": { + "image_name": ["foo", "bar"], + "advisory_name": "RHSA-.*", + } + } + }) + def test_filter_out_image_name_and_advisory_name( + self, handler_build_whitelist): + """ + Tests that allow_build does filter images based on image_name. + """ + + handler = RebuildImagesOnRPMAdvisoryChange() + handler.event = ErrataAdvisoryRPMsSignedEvent( + "123", + ErrataAdvisory(123, "RHSA-2017", "REL_PREP", [], + security_impact="None", + product_short_name="product")) + + image = {"brew": {"build": "foo-1-2.3"}} + ret = handler._filter_out_not_allowed_builds(image) + self.assertEqual(ret, False) + + image = {"brew": {"build": "unknown-1-2.3"}} + ret = handler._filter_out_not_allowed_builds(image) + self.assertEqual(ret, True) + + @patch( + "freshmaker.config.Config.handler_build_whitelist", + new_callable=PropertyMock, + return_value={ + "RebuildImagesOnRPMAdvisoryChange": { + "image": { + "image_name": ["foo", "bar"] + } + } + }) + @patch( + "freshmaker.config.Config.handler_build_blacklist", + new_callable=PropertyMock, + return_value={ + "RebuildImagesOnRPMAdvisoryChange": { + "image": all_( + { + "image_name": "foo", + "image_version": "7.3", + } + ) + } + }) + def test_filter_out_not_allowed_builds_image_version( + self, handler_build_blacklist, handler_build_whitelist): + handler = RebuildImagesOnRPMAdvisoryChange() + handler.event = ErrataAdvisoryRPMsSignedEvent( + "123", + ErrataAdvisory(123, "RHSA-2017", "REL_PREP", [], + security_impact="None", + product_short_name="product")) + + image = {"brew": {"build": "foo-1-2.3"}} + ret = handler._filter_out_not_allowed_builds(image) + self.assertEqual(ret, False) + + image = {"brew": {"build": "foo-1-7.3"}} + ret = handler._filter_out_not_allowed_builds(image) + self.assertEqual(ret, False) + + image = {"brew": {"build": "foo-7.3-2.3"}} + ret = handler._filter_out_not_allowed_builds(image) + self.assertEqual(ret, True) + + image = {"brew": {"build": "unknown-1-2.3"}} + ret = handler._filter_out_not_allowed_builds(image) + self.assertEqual(ret, True) + + +class TestBatches(helpers.ModelsTestCase): + """Test handling of batches""" + + def setUp(self): + super(TestBatches, self).setUp() + self.patcher = helpers.Patcher( + 'freshmaker.handlers.koji.RebuildImagesOnRPMAdvisoryChange.') + + def tearDown(self): + super(TestBatches, self).tearDown() + self.patcher.unpatch_all() + + def _mock_build(self, build, parent=None, error=None): + if parent: + parent = {"brew": {"build": parent + "-1-1.25"}} + return ContainerImage({ + 'brew': {'build': build + "-1-1.25"}, + 'repository': build + '_repo', + 'parsed_data': { + 'layers': [ + 'sha512:1234', + 'sha512:4567', + 'sha512:7890', + ], + }, + 'commit': build + '_123', + 'parent': parent, + "target": "t1", + 'git_branch': 'mybranch', + "error": error, + "content_sets": ["first-content-set"], + "generate_pulp_repos": True, + "arches": "x86_64", + "odcs_compose_ids": [10, 11], + "published": False, + }) + + @patch('freshmaker.odcsclient.create_odcs_client') + def test_batches_records(self, create_odcs_client): + """ + Tests that batches are properly recorded in DB. + """ + odcs = create_odcs_client.return_value + # There are 8 mock builds below and each of them requires one pulp + # compose. + composes = [{ + 'id': compose_id, + 'result_repofile': 'http://localhost/{}.repo'.format(compose_id), + 'state_name': 'done' + } for compose_id in range(1, 9)] + odcs.new_compose.side_effect = composes + odcs.get_compose.side_effect = composes + + # Creates following tree: + # shared_parent + # |- child1_parent3 + # |- child1_parent2 + # |- child1_parent1 + # |- child1 + # |- child2_parent2 + # |- child2_parent1 + # |- child2 + batches = [[self._mock_build("shared_parent")], + [self._mock_build("child1_parent3", "shared_parent"), + self._mock_build("child2_parent2", "shared_parent")], + [self._mock_build("child1_parent2", "child1_parent3"), + self._mock_build("child2_parent1", "child2_parent2")], + [self._mock_build("child1_parent1", "child1_parent2", error="Fail"), + self._mock_build("child2", "child2_parent1")], + [self._mock_build("child1", "child1_parent1")]] + + # Flat list of images from batches with brew build id as a key. + images = {} + for batch in batches: + for image in batch: + images[image['brew']['build']] = image + + # Record the batches. + event = events.BrewSignRPMEvent("123", "openssl-1.1.0-1") + handler = RebuildImagesOnRPMAdvisoryChange() + handler._record_batches(batches, event) + + # Check that the images have proper data in proper db columns. + e = db.session.query(Event).filter(Event.id == 1).one() + for build in e.builds: + # child1_parent1 and child1 are in FAILED states, because LB failed + # to resolve child1_parent1 and therefore also child1 cannot be + # build. + if build.name in ["child1_parent1", "child1"]: + self.assertEqual(build.state, ArtifactBuildState.FAILED.value) + else: + self.assertEqual(build.state, ArtifactBuildState.PLANNED.value) + self.assertEqual(build.type, ArtifactType.IMAGE.value) + + image = images[build.original_nvr] + if image['parent']: + self.assertEqual(build.dep_on.original_nvr, image['parent']['brew']['build']) + else: + self.assertEqual(build.dep_on, None) + + args = json.loads(build.build_args) + self.assertEqual(args["repository"], build.name + "_repo") + self.assertEqual(args["commit"], build.name + "_123") + self.assertEqual(args["parent"], + build.dep_on.rebuilt_nvr if build.dep_on else None) + self.assertEqual(args["renewed_odcs_compose_ids"], + [10, 11]) + + +class TestCheckImagesToRebuild(helpers.ModelsTestCase): + """Test handling of batches""" + + def setUp(self): + super(TestCheckImagesToRebuild, self).setUp() + + build_args = json.dumps({ + "parent": "nvr", + "repository": "repo", + "target": "target", + "commit": "hash", + "branch": "mybranch", + "yum_repourl": "http://localhost/composes/latest-odcs-3-1/compose/" + "Temporary/odcs-3.repo", + "odcs_pulp_compose_id": 15, + }) + + self.ev = Event.create(db.session, 'msg-id', '123', + EVENT_TYPES[ErrataAdvisoryRPMsSignedEvent]) + self.b1 = ArtifactBuild.create( + db.session, self.ev, "parent", "image", + state=ArtifactBuildState.PLANNED, + original_nvr="parent-1-25") + self.b1.build_args = build_args + self.b2 = ArtifactBuild.create( + db.session, self.ev, "child", "image", + state=ArtifactBuildState.PLANNED, + dep_on=self.b1, + original_nvr="child-1-25") + self.b2.build_args = build_args + db.session.commit() + + def test_check_images_to_rebuild(self): + builds = { + "parent-1-25": self.b1, + "child-1-25": self.b2 + } + + handler = RebuildImagesOnRPMAdvisoryChange() + handler.set_context(self.ev) + handler._check_images_to_rebuild(self.ev, builds) + + # Check that the images have proper data in proper db columns. + e = db.session.query(Event).filter(Event.id == 1).one() + for build in e.builds: + self.assertEqual(build.state, ArtifactBuildState.PLANNED.value) + + def test_check_images_to_rebuild_missing_dep(self): + # Do not include child nvr here to test that _check_images_to_rebuild + # sets the state of event to failed. + builds = { + "parent-1-25": self.b1 + } + + handler = RebuildImagesOnRPMAdvisoryChange() + handler.set_context(self.ev) + handler._check_images_to_rebuild(self.ev, builds) + + # Check that the images have proper data in proper db columns. + e = db.session.query(Event).filter(Event.id == 1).one() + for build in e.builds: + self.assertEqual(build.state, ArtifactBuildState.FAILED.value) + + def test_check_images_to_rebuild_extra_build(self): + builds = { + "parent-1-25": self.b1, + "child-1-25": self.b2, + "something-1-25": self.b1, + } + + handler = RebuildImagesOnRPMAdvisoryChange() + handler.set_context(self.ev) + handler._check_images_to_rebuild(self.ev, builds) + + # Check that the images have proper data in proper db columns. + e = db.session.query(Event).filter(Event.id == 1).one() + for build in e.builds: + self.assertEqual(build.state, ArtifactBuildState.FAILED.value) + + +class TestUpdateDBOnAdvisoryChange(helpers.ModelsTestCase): + + @patch('freshmaker.errata.Errata.advisories_from_event') + def test_rebuild_if_not_exists(self, advisories_from_event): + handler = UpdateDBOnAdvisoryChange() + + for state in ["REL_PREP", "PUSH_READY", "IN_PUSH", "SHIPPED_LIVE"]: + advisories_from_event.return_value = [ + ErrataAdvisory(123, "RHSA-2017", state, ["rpm"], "Critical")] + ev = ErrataAdvisoryStateChangedEvent( + "msg123", ErrataAdvisory(123, "RHSA-2017", state, ['rpm'])) + ret = handler.handle(ev) + + self.assertEqual(len(ret), 1) + self.assertEqual(ret[0].advisory.errata_id, 123) + self.assertEqual(ret[0].advisory.security_impact, "Critical") + self.assertEqual(ret[0].advisory.name, "RHSA-2017") + + @patch('freshmaker.errata.Errata.advisories_from_event') + @patch.object(conf, 'handler_build_whitelist', new={ + 'UpdateDBOnAdvisoryChange': { + 'image': { + 'advisory_state': r'REL_PREP|SHIPPED_LIVE', + } + } + }) + def test_rebuild_if_not_exists_unknown_states( + self, advisories_from_event): + handler = UpdateDBOnAdvisoryChange() + + for state in ["NEW_FILES", "QE", "UNKNOWN"]: + advisories_from_event.return_value = [ + ErrataAdvisory(123, "RHSA-2017", state, ["rpm"], "Critical")] + ev = ErrataAdvisoryStateChangedEvent( + "msg123", ErrataAdvisory(123, 'RHSA-2017', state, ['rpm'])) + ret = handler.handle(ev) + + self.assertEqual(len(ret), 0) + + @patch('freshmaker.errata.Errata.advisories_from_event') + @patch.object(conf, 'handler_build_whitelist', new={ + 'UpdateDBOnAdvisoryChange': { + 'image': { + 'advisory_state': '.*', + } + } + }) + def test_rebuild_if_not_exists_already_exists( + self, advisories_from_event): + handler = UpdateDBOnAdvisoryChange() + + db_event = Event.create( + db.session, "msg124", "123", ErrataAdvisoryRPMsSignedEvent) + db.session.commit() + + for manual in [True, False]: + for db_event_state in [ + EventState.INITIALIZED, EventState.BUILDING, + EventState.COMPLETE, EventState.FAILED, + EventState.SKIPPED]: + db_event.state = db_event_state + db.session.commit() + for state in ["REL_PREP", "PUSH_READY", "IN_PUSH", "SHIPPED_LIVE"]: + advisories_from_event.return_value = [ + ErrataAdvisory(123, "RHSA-2017", state, ["rpm"], "Critical")] + ev = ErrataAdvisoryStateChangedEvent( + "msg123", ErrataAdvisory(123, 'RHSA-2017', state, ['rpm'])) + ev.manual = manual + ev.dry_run = manual # use also manual just for the sake of test. + ret = handler.handle(ev) + + if db_event_state == EventState.FAILED or ev.manual: + self.assertEqual(len(ret), 1) + self.assertEqual(ret[0].manual, manual) + self.assertEqual(ret[0].dry_run, manual) + else: + self.assertEqual(len(ret), 0) + + @patch('freshmaker.errata.Errata.advisories_from_event') + def test_rebuild_if_not_exists_unknown_errata_id( + self, advisories_from_event): + advisories_from_event.return_value = [] + handler = UpdateDBOnAdvisoryChange() + + for state in ["REL_PREP", "PUSH_READY", "IN_PUSH", "SHIPPED_LIVE"]: + ev = ErrataAdvisoryStateChangedEvent( + "msg123", ErrataAdvisory(123, 'RHSA-2017', state, ['rpm'])) + ret = handler.handle(ev) + + self.assertEqual(len(ret), 0) + + def test_passing_dry_run(self): + ev = ErrataAdvisoryStateChangedEvent( + "msg123", ErrataAdvisory(123, "name", "SHIPPED_LIVE", ["rpm"]), + dry_run=True) + self.assertEqual(ev.dry_run, True) + + ev = ErrataAdvisoryRPMsSignedEvent( + "123", + ErrataAdvisory(123, "RHBA-2017", "REL_PREP", [], + security_impact="", + product_short_name="product"), + dry_run=True) + self.assertEqual(ev.dry_run, True) + + def test_mark_as_released(self): + db_event = Event.create( + db.session, "msg124", "123", ErrataAdvisoryRPMsSignedEvent, False) + db.session.commit() + + self.assertEqual(db_event.released, False) + + ev = ErrataAdvisoryStateChangedEvent( + "msg123", ErrataAdvisory(123, "name", "SHIPPED_LIVE", ["rpm"])) + + handler = UpdateDBOnAdvisoryChange() + handler.handle(ev) + + db.session.refresh(db_event) + self.assertEqual(db_event.released, True) + + def test_mark_as_released_wrong_advisory_status(self): + db_event = Event.create( + db.session, "msg124", "123", ErrataAdvisoryRPMsSignedEvent, False) + db.session.commit() + + for state in ["NEW_FILES", "QE", "REL_PREP", "PUSH_READY", "IN_PUSH"]: + ev = ErrataAdvisoryStateChangedEvent( + "msg123", ErrataAdvisory(123, "name", state, ['rpm'])) + + handler = UpdateDBOnAdvisoryChange() + handler.handle(ev) + + db.session.refresh(db_event) + self.assertEqual(db_event.released, False) + + @patch('freshmaker.errata.Errata.advisories_from_event') + def test_mark_as_released_unknown_event(self, advisories_from_event): + ev = ErrataAdvisoryStateChangedEvent( + "msg123", ErrataAdvisory(123, "name", "SHIPPED_LIVE", ["rpm"])) + + handler = UpdateDBOnAdvisoryChange() + handler.handle(ev) + + @patch('freshmaker.handlers.internal.UpdateDBOnAdvisoryChange' + '.rebuild_if_not_exists') + @patch.object(conf, 'handler_build_whitelist', new={ + 'UpdateDBOnAdvisoryChange': { + 'image': { + 'advisory_state': r'REL_PREP', + } + } + }) + def test_not_rebuild_if_errata_state_is_not_allowed( + self, rebuild_if_not_exists): + rebuild_if_not_exists.return_value = [Mock(), Mock()] + + Event.create(db.session, "msg-id-123", "123456", + ErrataAdvisoryRPMsSignedEvent, False) + db.session.commit() + + event = ErrataAdvisoryStateChangedEvent( + 'msg-id-123', + ErrataAdvisory(123456, 'name', 'SHIPPED_LIVE', ['rpm'])) + handler = UpdateDBOnAdvisoryChange() + msgs = handler.handle(event) + + self.assertEqual([], msgs) + + @patch('freshmaker.handlers.internal.UpdateDBOnAdvisoryChange' + '.rebuild_if_not_exists') + @patch.object(conf, 'handler_build_whitelist', new={ + 'UpdateDBOnAdvisoryChange': { + 'image': { + 'advisory_state': r'SHIPPED_LIVE', + } + } + }) + def test_rebuild_if_errata_state_is_not_allowed_but_manual_is_true( + self, rebuild_if_not_exists): + rebuild_if_not_exists.return_value = [Mock()] + + Event.create(db.session, "msg-id-123", "123456", + ErrataAdvisoryRPMsSignedEvent, False) + db.session.commit() + + event = ErrataAdvisoryStateChangedEvent( + 'msg-id-123', + ErrataAdvisory(123456, "name", 'SHIPPED_LIVE', ['rpm'])) + event.manual = True + handler = UpdateDBOnAdvisoryChange() + msgs = handler.handle(event) + + self.assertEqual(len(msgs), 1) + + +class TestRecordBatchesImages(helpers.ModelsTestCase): + """Test RebuildImagesOnRPMAdvisoryChange._record_batches""" + + def setUp(self): + super(TestRecordBatchesImages, self).setUp() + + self.mock_event = Mock(msg_id='msg-id', search_key=12345) + + self.patcher = helpers.Patcher( + 'freshmaker.handlers.koji.RebuildImagesOnRPMAdvisoryChange.') + + self.mock_prepare_pulp_repo = self.patcher.patch( + 'freshmaker.odcsclient.FreshmakerODCSClient.prepare_pulp_repo', + side_effect=[{'id': 1}, {'id': 2}]) + + self.patcher.patch_dict( + 'freshmaker.models.EVENT_TYPES', {self.mock_event.__class__: 0}) + + def tearDown(self): + super(TestRecordBatchesImages, self).tearDown() + self.patcher.unpatch_all() + + def test_record_batches(self): + batches = [ + [ContainerImage({ + "brew": { + "completion_date": "20170420T17:05:37.000-0400", + "build": "rhel-server-docker-7.3-82", + "package": "rhel-server-docker" + }, + 'parsed_data': { + 'layers': [ + 'sha512:12345678980', + 'sha512:10987654321' + ] + }, + "parent": None, + "content_sets": ["content-set-1"], + "repository": "repo-1", + "commit": "123456789", + "target": "target-candidate", + "git_branch": "rhel-7", + "error": None, + "generate_pulp_repos": True, + "arches": "x86_64", + "odcs_compose_ids": None, + "published": False, + })], + [ContainerImage({ + "brew": { + "build": "rh-dotnetcore10-docker-1.0-16", + "package": "rh-dotnetcore10-docker", + "completion_date": "20170511T10:06:09.000-0400" + }, + 'parsed_data': { + 'layers': [ + 'sha512:2345af2e293', + 'sha512:12345678980', + 'sha512:10987654321' + ] + }, + "parent": ContainerImage({ + "brew": { + "completion_date": "20170420T17:05:37.000-0400", + "build": "rhel-server-docker-7.3-82", + "package": "rhel-server-docker" + }, + 'parsed_data': { + 'layers': [ + 'sha512:12345678980', + 'sha512:10987654321' + ] + }, + "parent": None, + "content_sets": ["content-set-1"], + "repository": "repo-1", + "commit": "123456789", + "target": "target-candidate", + "git_branch": "rhel-7", + "error": None + }), + "content_sets": ["content-set-1"], + "repository": "repo-1", + "commit": "987654321", + "target": "target-candidate", + "git_branch": "rhel-7", + "error": None, + "generate_pulp_repos": True, + "arches": "x86_64", + "odcs_compose_ids": None, + "published": False, + })] + ] + + handler = RebuildImagesOnRPMAdvisoryChange() + handler._record_batches(batches, self.mock_event) + + # Check parent image + query = db.session.query(ArtifactBuild) + parent_image = query.filter( + ArtifactBuild.original_nvr == 'rhel-server-docker-7.3-82' + ).first() + self.assertNotEqual(None, parent_image) + self.assertEqual(ArtifactBuildState.PLANNED.value, parent_image.state) + + # Check child image + child_image = query.filter( + ArtifactBuild.original_nvr == 'rh-dotnetcore10-docker-1.0-16' + ).first() + self.assertNotEqual(None, child_image) + self.assertEqual(parent_image, child_image.dep_on) + self.assertEqual(ArtifactBuildState.PLANNED.value, child_image.state) + + def test_record_batches_should_not_generate_pulp_repos(self): + batches = [ + [ContainerImage({ + "brew": { + "completion_date": "20170420T17:05:37.000-0400", + "build": "rhel-server-docker-7.3-82", + "package": "rhel-server-docker" + }, + 'parsed_data': { + 'layers': [ + 'sha512:12345678980', + 'sha512:10987654321' + ] + }, + "parent": None, + "content_sets": ["content-set-1"], + "repository": "repo-1", + "commit": "123456789", + "target": "target-candidate", + "git_branch": "rhel-7", + "error": None, + "generate_pulp_repos": False, + "arches": "x86_64", + "odcs_compose_ids": None, + "published": False, + })] + ] + + handler = RebuildImagesOnRPMAdvisoryChange() + handler._record_batches(batches, self.mock_event) + + # Check parent image + query = db.session.query(ArtifactBuild) + parent_image = query.filter( + ArtifactBuild.original_nvr == 'rhel-server-docker-7.3-82' + ).first() + self.assertNotEqual(None, parent_image) + self.assertEqual(ArtifactBuildState.PLANNED.value, parent_image.state) + self.mock_prepare_pulp_repo.assert_not_called() + + def test_pulp_compose_generated_just_once(self): + batches = [ + [ContainerImage({ + "brew": { + "completion_date": "20170420T17:05:37.000-0400", + "build": "rhel-server-docker-7.3-82", + "package": "rhel-server-docker" + }, + 'parsed_data': { + 'layers': [ + 'sha512:12345678980', + 'sha512:10987654321' + ] + }, + "parent": None, + "content_sets": ["content-set-1"], + "repository": "repo-1", + "commit": "123456789", + "target": "target-candidate", + "git_branch": "rhel-7", + "error": None, + "arches": "x86_64", + "generate_pulp_repos": True, + "odcs_compose_ids": None, + "published": False, + })], + [ContainerImage({ + "brew": { + "build": "rh-dotnetcore10-docker-1.0-16", + "package": "rh-dotnetcore10-docker", + "completion_date": "20170511T10:06:09.000-0400" + }, + 'parsed_data': { + 'layers': [ + 'sha512:2345af2e293', + 'sha512:12345678980', + 'sha512:10987654321' + ] + }, + "parent": ContainerImage({ + "brew": { + "completion_date": "20170420T17:05:37.000-0400", + "build": "rhel-server-docker-7.3-82", + "package": "rhel-server-docker" + }, + 'parsed_data': { + 'layers': [ + 'sha512:12345678980', + 'sha512:10987654321' + ] + }, + "parent": None, + "content_sets": ["content-set-1"], + "repository": "repo-1", + "commit": "123456789", + "target": "target-candidate", + "git_branch": "rhel-7", + "error": None + }), + "content_sets": ["content-set-1"], + "repository": "repo-1", + "commit": "987654321", + "target": "target-candidate", + "git_branch": "rhel-7", + "error": None, + "arches": "x86_64", + "generate_pulp_repos": True, + "odcs_compose_ids": None, + "published": False, + })] + ] + + handler = RebuildImagesOnRPMAdvisoryChange() + handler._record_batches(batches, self.mock_event) + + query = db.session.query(ArtifactBuild) + parent_build = query.filter( + ArtifactBuild.original_nvr == 'rhel-server-docker-7.3-82' + ).first() + self.assertEqual(1, len(parent_build.composes)) + compose_ids = sorted([rel.compose.odcs_compose_id + for rel in parent_build.composes]) + self.assertEqual([1], compose_ids) + + child_build = query.filter( + ArtifactBuild.original_nvr == 'rh-dotnetcore10-docker-1.0-16' + ).first() + self.assertEqual(1, len(child_build.composes)) + + self.mock_prepare_pulp_repo.assert_has_calls([ + call(parent_build, ["content-set-1"]) + ]) + + def test_no_parent(self): + batches = [ + [ContainerImage({ + "brew": { + "completion_date": "20170420T17:05:37.000-0400", + "build": "rhel-server-docker-7.3-82", + "package": "rhel-server-docker" + }, + 'parsed_data': { + 'layers': [ + 'sha512:12345678980', + 'sha512:10987654321' + ] + }, + "content_sets": ["content-set-1"], + "repository": "repo-1", + "commit": "123456789", + "target": "target-candidate", + "git_branch": "rhel-7", + "error": "Some error occurs while getting this image.", + "arches": "x86_64", + "odcs_compose_ids": None, + "published": False, + })] + ] + + handler = RebuildImagesOnRPMAdvisoryChange() + handler._record_batches(batches, self.mock_event) + + query = db.session.query(ArtifactBuild) + build = query.filter( + ArtifactBuild.original_nvr == 'rhel-server-docker-7.3-82' + ).first() + + self.assertEqual(ArtifactBuildState.FAILED.value, build.state) + + def test_mark_failed_state_if_image_has_error(self): + batches = [ + [ContainerImage({ + "brew": { + "completion_date": "20170420T17:05:37.000-0400", + "build": "rhel-server-docker-7.3-82", + "package": "rhel-server-docker" + }, + 'parsed_data': { + 'layers': [ + 'sha512:12345678980', + 'sha512:10987654321' + ] + }, + "parent": None, + "content_sets": ["content-set-1"], + "repository": "repo-1", + "commit": "123456789", + "target": "target-candidate", + "git_branch": "rhel-7", + "error": "Some error occurs while getting this image.", + "arches": "x86_64", + "odcs_compose_ids": None, + "published": False, + })] + ] + + handler = RebuildImagesOnRPMAdvisoryChange() + handler._record_batches(batches, self.mock_event) + + query = db.session.query(ArtifactBuild) + build = query.filter( + ArtifactBuild.original_nvr == 'rhel-server-docker-7.3-82' + ).first() + + self.assertEqual(ArtifactBuildState.FAILED.value, build.state) + + def test_mark_state_failed_if_depended_image_is_failed(self): + batches = [ + [ContainerImage({ + "brew": { + "completion_date": "20170420T17:05:37.000-0400", + "build": "rhel-server-docker-7.3-82", + "package": "rhel-server-docker" + }, + 'parsed_data': { + 'layers': [ + 'sha512:12345678980', + 'sha512:10987654321' + ] + }, + "parent": None, + "content_sets": ["content-set-1"], + "repository": "repo-1", + "commit": "123456789", + "target": "target-candidate", + "git_branch": "rhel-7", + "error": "Some error occured.", + "arches": "x86_64", + "odcs_compose_ids": None, + "published": False, + })], + [ContainerImage({ + "brew": { + "build": "rh-dotnetcore10-docker-1.0-16", + "package": "rh-dotnetcore10-docker", + "completion_date": "20170511T10:06:09.000-0400" + }, + 'parsed_data': { + 'layers': [ + 'sha512:378a8ef2730', + 'sha512:12345678980', + 'sha512:10987654321' + ] + }, + "parent": ContainerImage({ + "brew": { + "completion_date": "20170420T17:05:37.000-0400", + "build": "rhel-server-docker-7.3-82", + "package": "rhel-server-docker" + }, + 'parsed_data': { + 'layers': [ + 'sha512:12345678980', + 'sha512:10987654321' + ] + }, + "parent": None, + "content_sets": ["content-set-1"], + "repository": "repo-1", + "commit": "123456789", + "target": "target-candidate", + "git_branch": "rhel-7", + "error": None + }), + "content_sets": ["content-set-1"], + "repository": "repo-1", + "commit": "987654321", + "target": "target-candidate", + "git_branch": "rhel-7", + "error": "Some error occured too.", + "arches": "x86_64", + "odcs_compose_ids": None, + "published": False, + })] + ] + + handler = RebuildImagesOnRPMAdvisoryChange() + handler._record_batches(batches, self.mock_event) + + query = db.session.query(ArtifactBuild) + build = query.filter( + ArtifactBuild.original_nvr == 'rhel-server-docker-7.3-82' + ).first() + self.assertEqual(ArtifactBuildState.FAILED.value, build.state) + + build = query.filter( + ArtifactBuild.original_nvr == 'rh-dotnetcore10-docker-1.0-16' + ).first() + self.assertEqual(ArtifactBuildState.FAILED.value, build.state) + + def test_mark_base_image_failed_if_fail_to_request_boot_iso_compose(self): + batches = [ + [ContainerImage({ + "brew": { + "completion_date": "20170420T17:05:37.000-0400", + "build": "rhel-server-docker-7.3-82", + "package": "rhel-server-docker" + }, + 'parsed_data': { + 'layers': [ + 'sha512:12345678980', + 'sha512:10987654321' + ] + }, + "parent": None, + "content_sets": ["content-set-1"], + "repository": "repo-1", + "commit": "123456789", + "target": "target-candidate", + "git_branch": "rhel-7", + "error": "Some error occured.", + "arches": "x86_64", + "odcs_compose_ids": None, + "published": False, + })], + ] + + handler = RebuildImagesOnRPMAdvisoryChange() + handler._record_batches(batches, self.mock_event) + + build = db.session.query(ArtifactBuild).filter_by( + original_nvr='rhel-server-docker-7.3-82').first() + self.assertEqual(ArtifactBuildState.FAILED.value, build.state) + + # Pulp repo should not be prepared for FAILED build. + self.mock_prepare_pulp_repo.assert_not_called() + + +class TestSkipNonRPMAdvisory(helpers.FreshmakerTestCase): + + def test_ensure_to_handle_rpm_adivsory(self): + event = ErrataAdvisoryStateChangedEvent( + 'msg-id-1', + ErrataAdvisory(123, 'name', 'REL_PREP', ['rpm', 'jar', 'pom'])) + handler = UpdateDBOnAdvisoryChange() + self.assertTrue(handler.can_handle(event)) + + def test_not_handle_non_rpm_advisory(self): + event = ErrataAdvisoryStateChangedEvent( + 'msg-id-1', ErrataAdvisory(123, 'name', 'REL_PREP', ['docker'])) + handler = UpdateDBOnAdvisoryChange() + self.assertFalse(handler.can_handle(event)) diff --git a/tests/handlers/internal/test_update_db_on_module_build.py b/tests/handlers/internal/test_update_db_on_module_build.py new file mode 100644 index 0000000..64630b3 --- /dev/null +++ b/tests/handlers/internal/test_update_db_on_module_build.py @@ -0,0 +1,237 @@ +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os +import sys +import unittest +import mock + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) # noqa +from tests import helpers + +import freshmaker + +from freshmaker import events, db, models +from freshmaker.types import ArtifactType +from freshmaker.handlers.internal import UpdateDBOnModuleBuild +from freshmaker.parsers.mbs import MBSModuleStateChangeParser +from freshmaker.config import any_ + + +class UpdateDBOnModuleBuildTest(helpers.ModelsTestCase): + def setUp(self): + super(UpdateDBOnModuleBuildTest, self).setUp() + events.BaseEvent.register_parser(MBSModuleStateChangeParser) + + def test_can_handle_module_state_change_event(self): + """ + Tests MBS handler can handle module built message + """ + for state in ['init', 'wait', 'build', 'done', 'failed', 'ready']: + msg = helpers.ModuleStateChangeMessage('testmodule', 'master', state=state).produce() + event = self.get_event_from_msg(msg) + + handler = UpdateDBOnModuleBuild() + self.assertTrue(handler.can_handle(event)) + + @mock.patch('freshmaker.handlers.internal.update_db_on_module_build.PDC') + @mock.patch('freshmaker.handlers.internal.update_db_on_module_build.utils') + @mock.patch('freshmaker.handlers.internal.update_db_on_module_build.conf') + @mock.patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'UpdateDBOnModuleBuild': { + 'module': any_({'name': r'testmodule\d*'}, {'branch': 'master'}), + } + }) + def test_can_rebuild_depending_modules(self, conf, utils, PDC): + """ + Tests handler can rebuild all modules which depend on the module + in module state change event. + """ + msg = helpers.ModuleStateChangeMessage('testmodule', 'master', state='ready').produce() + event = self.get_event_from_msg(msg) + + mod2_r1_info = helpers.PDCModuleInfo('testmodule2', 'master', '20170412010101') + mod2_r1_info.add_build_dep('testmodule', 'master') + mod2_r1 = mod2_r1_info.produce() + + mod3_r1_info = helpers.PDCModuleInfo('testmodule3', 'master', '20170412010201') + mod3_r1_info.add_build_dep('testmodule', 'master') + mod3_r1 = mod3_r1_info.produce() + + pdc = PDC.return_value + pdc.get_latest_modules.return_value = [mod2_r1, mod3_r1] + + conf.git_base_url = "git://pkgs.fedoraproject.org" + utils.bump_distgit_repo.side_effect = [ + "fae7848fa47a854f25b782aa64441040a6d86544", + "43ec03000d249231bc7135b11b810afc96e90efb", + ] + + handler = UpdateDBOnModuleBuild() + handler.build_module = mock.Mock() + handler.build_module.side_effect = [123, 456] + + self.assertTrue(handler.can_handle(event)) + handler.handle(event) + + self.assertEqual(handler.build_module.call_args_list, + [mock.call('testmodule2', 'master', 'fae7848fa47a854f25b782aa64441040a6d86544'), + mock.call('testmodule3', 'master', '43ec03000d249231bc7135b11b810afc96e90efb')]) + + event_list = models.Event.query.all() + self.assertEqual(len(event_list), 1) + self.assertEqual(event_list[0].message_id, event.msg_id) + builds = models.ArtifactBuild.query.all() + self.assertEqual(len(builds), 2) + self.assertEqual(builds[0].name, mod2_r1['name']) + self.assertEqual(builds[0].type, ArtifactType.MODULE.value) + self.assertEqual(builds[0].build_id, 123) + self.assertEqual(builds[1].name, mod3_r1['name']) + self.assertEqual(builds[1].build_id, 456) + self.assertEqual(builds[1].type, ArtifactType.MODULE.value) + + @mock.patch('freshmaker.handlers.internal.update_db_on_module_build.PDC') + @mock.patch('freshmaker.handlers.internal.update_db_on_module_build.utils') + @mock.patch('freshmaker.handlers.conf') + def test_module_is_not_allowed_in_whitelist(self, conf, utils, PDC): + conf.handler_build_whitelist = { + "UpdateDBOnModuleBuild": { + "module": any_( + { + 'name': 'base.*', + }, + ), + }, + } + + msg = helpers.ModuleStateChangeMessage('testmodule', 'master', state='ready').produce() + event = self.get_event_from_msg(msg) + + mod2_info = helpers.PDCModuleInfo('testmodule2', 'master', '20170412010101') + mod2_info.add_build_dep('testmodule', 'master') + mod2 = mod2_info.produce() + + pdc = PDC.return_value + pdc.get_latest_modules.return_value = [mod2] + + handler = UpdateDBOnModuleBuild() + handler.build_module = mock.Mock() + handler.record_build = mock.Mock() + + self.assertTrue(handler.can_handle(event)) + handler.handle(event) + + handler.build_module.assert_not_called() + + @mock.patch('freshmaker.handlers.internal.update_db_on_module_build.PDC') + @mock.patch('freshmaker.handlers.internal.update_db_on_module_build.utils') + @mock.patch('freshmaker.handlers.internal.update_db_on_module_build.log') + @mock.patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'UpdateDBOnModuleBuild': { + 'module': any_({'name': r'module\d+'}, {'branch': 'master'}) + } + }) + def test_handler_not_fall_into_cyclic_rebuild_loop(self, log, utils, PDC): + """ + Tests handler will not fall into cyclic rebuild loop when there is + build dep loop of modules. + """ + # in this case, we have: + # 1. module2 depends on module1 + # 2. module3 depends on module2 + # 3. module1 depends on module3 + # + # when we receives a modult built event of module1, the expect result is: + # 1. module2 get rebuild because module1 is built + # 2. module3 get rebuild because module2 is built + # 3. module1 get rebuild because module3 is built + # 4. stop here + + utils.bump_distgit_repo.return_value = 'abcd' + + mod1_info = helpers.PDCModuleInfo('module1', 'master', '20170412010101') + mod1_info.add_build_dep('module3', 'master') + mod1 = mod1_info.produce() + + mod2_info = helpers.PDCModuleInfo('module2', 'master', '20170412010102') + mod2_info.add_build_dep('module1', 'master') + mod2 = mod2_info.produce() + + mod3_info = helpers.PDCModuleInfo('module3', 'master', '20170412010103') + mod3_info.add_build_dep('module2', 'master') + mod3 = mod3_info.produce() + + pdc = PDC.return_value + handler = UpdateDBOnModuleBuild() + + # Assume we have build of module1 recorded in DB already, it doesn't has + # any dep_on as it was initial triggered by an event which is not + # associated with any build in our DB. + event = models.Event.create(db.session, "initial_msg_id", "test", events.TestingEvent) + models.ArtifactBuild.create(db.session, event, "module1", "module", '123') + db.session.commit() + + # we received module built event of module1 + msg = helpers.ModuleStateChangeMessage('module1', 'master', state='ready', build_id=123).produce() + event = self.get_event_from_msg(msg) + pdc.get_latest_modules.return_value = [mod2] + handler.build_module = mock.Mock() + handler.build_module.return_value = 124 + + # this will trigger module rebuild of module2 + handler.handle(event) + handler.build_module.assert_called_once_with('module2', 'master', 'abcd') + + # we received module built event of module2 + msg = helpers.ModuleStateChangeMessage('module2', 'master', state='ready', build_id=124).produce() + event = self.get_event_from_msg(msg) + pdc.get_latest_modules.return_value = [mod3] + handler.build_module = mock.Mock() + handler.build_module.return_value = 125 + + # this will trigger module rebuild of module3 + handler.handle(event) + handler.build_module.assert_called_once_with('module3', 'master', 'abcd') + + # we received module built event of module3 + msg = helpers.ModuleStateChangeMessage('module3', 'master', state='ready', build_id=125).produce() + event = self.get_event_from_msg(msg) + pdc.get_latest_modules.return_value = [mod1] + handler.build_module = mock.Mock() + handler.build_module.return_value = 126 + + # this will trigger module rebuild of module1 + handler.handle(event) + handler.build_module.assert_called_once_with('module1', 'master', 'abcd') + + # we received module built event of module1 + msg = helpers.ModuleStateChangeMessage('module1', 'master', state='ready', build_id=126).produce() + event = self.get_event_from_msg(msg) + pdc.get_latest_modules.return_value = [mod2] + handler.build_module = mock.Mock() + + # but this time we should not rebuild module2 + handler.handle(event) + handler.build_module.assert_not_called() + log.info.assert_has_calls([mock.call('Skipping the rebuild triggered by %s:%s as it willresult in cyclic build loop.', 'module1', u'master')]) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/handlers/koji/__init__.py b/tests/handlers/koji/__init__.py new file mode 100644 index 0000000..043e3c6 --- /dev/null +++ b/tests/handlers/koji/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2016 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Jan Kaluza diff --git a/tests/handlers/koji/test_rebuild_images_on_git_dockerfile_change.py b/tests/handlers/koji/test_rebuild_images_on_git_dockerfile_change.py new file mode 100644 index 0000000..ccb8a74 --- /dev/null +++ b/tests/handlers/koji/test_rebuild_images_on_git_dockerfile_change.py @@ -0,0 +1,122 @@ +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Chenxiong Qi + +from mock import patch +from mock import PropertyMock + +import freshmaker + +from freshmaker import models +from freshmaker.types import ArtifactType +from freshmaker.config import any_ +from tests import get_fedmsg, helpers + + +class BaseTestCase(helpers.ModelsTestCase): + + @patch("requests.request") + @patch('freshmaker.consumer.get_global_consumer') + def consume_fedmsg(self, msg, global_consumer, request): + consumer = self.create_consumer() + global_consumer.return_value = consumer + consumer.consume(msg) + + +class RebuildImagesOnGitDockerfileChangeTest(BaseTestCase): + + @patch('koji.read_config') + @patch('koji.ClientSession') + @patch("freshmaker.config.Config.krb_auth_principal", + new_callable=PropertyMock, return_value="user@example.com") + @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'RebuildImagesOnGitDockerfileChange': { + 'image': any_({'name': 'testimage'}, {'branch': 'master'}) + } + }) + def test_rebuild_if_dockerfile_changed( + self, auth_principal, ClientSession, read_config): + read_config.return_value = { + 'server': 'https://localhost/kojihub', + 'krb_rdns': False, + 'weburl': 'https://localhost/koji', + } + + mock_session = ClientSession.return_value + mock_session.buildContainer.return_value = 123 + msg = get_fedmsg('git_receive_dockerfile_changed') + self.consume_fedmsg(msg) + + mock_session.krb_login.assert_called() + mock_session.buildContainer.assert_called_once_with( + 'git://pkgs.fedoraproject.org/container/testimage.git?#e1f39d43471fc37ec82616f76a119da4eddec787', + 'rawhide-container-candidate', + {'scratch': True, 'git_branch': 'master'}) + mock_session.logout.assert_called_once() + + events = models.Event.query.all() + self.assertEqual(len(events), 1) + self.assertEqual(events[0].message_id, msg['body']['msg_id']) + builds = models.ArtifactBuild.query.all() + self.assertEqual(len(builds), 1) + self.assertEqual(builds[0].name, 'testimage') + self.assertEqual(builds[0].type, ArtifactType.IMAGE.value) + self.assertEqual(builds[0].build_id, 123) + + @patch('freshmaker.handlers.koji.RebuildImagesOnGitDockerfileChange.build_container') + def test_not_rebuild_if_dockerfile_not_changed(self, build_container): + self.consume_fedmsg(get_fedmsg('git_receive_dockerfile_not_changed')) + build_container.assert_not_called() + + @patch('koji.read_config') + @patch('koji.ClientSession') + @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'RebuildImagesOnGitDockerfileChange': { + 'image': any_({'name': 'testimage'}, {'branch': 'master'}) + } + }) + def test_ensure_logout_in_whatever_case(self, ClientSession, read_config): + ClientSession.return_value.buildContainer.side_effect = RuntimeError + read_config.return_value = { + 'server': 'https://localhost/kojihub', + 'krb_rdns': False, + 'weburl': 'https://localhost/koji', + } + + self.consume_fedmsg(get_fedmsg('git_receive_dockerfile_changed')) + + ClientSession.return_value.logout.assert_called_once() + + @patch('koji.read_config') + @patch('koji.ClientSession') + @patch("freshmaker.config.Config.krb_auth_principal", + new_callable=PropertyMock, return_value="user@example.com") + def test_ensure_do_nothing_if_fail_to_login_koji(self, auth_principal, ClientSession, read_config): + ClientSession.return_value.krb_login.side_effect = RuntimeError + read_config.return_value = { + 'server': 'https://localhost/kojihub', + 'krb_rdns': False, + 'weburl': 'https://localhost/koji', + } + + self.consume_fedmsg(get_fedmsg('git_receive_dockerfile_changed')) + + ClientSession.return_value.buildContainer.assert_not_called() diff --git a/tests/handlers/koji/test_rebuild_images_on_odcs_compose_done.py b/tests/handlers/koji/test_rebuild_images_on_odcs_compose_done.py new file mode 100644 index 0000000..8904efc --- /dev/null +++ b/tests/handlers/koji/test_rebuild_images_on_odcs_compose_done.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Chenxiong Qi + +import six + +from mock import patch, PropertyMock + +from freshmaker import db +from freshmaker.models import ( + Event, EventState, EVENT_TYPES, + ArtifactBuild, ArtifactType, ArtifactBuildState, ArtifactBuildCompose, + Compose +) +from freshmaker.events import ErrataAdvisoryRPMsSignedEvent +from freshmaker.handlers.koji import RebuildImagesOnODCSComposeDone +from freshmaker.events import ODCSComposeStateChangeEvent +from tests import helpers + + +class TestRebuildImagesOnODCSComposeDone(helpers.ModelsTestCase): + """Test ODCSRebuildImagesOnODCSComposeDone""" + + def setUp(self): + super(TestRebuildImagesOnODCSComposeDone, self).setUp() + + # Test data + # (Inner build depends on outer build) + # Event (ErrataAdvisoryRPMsSignedEvent): + # build 1: [compose 1, pulp compose 1] + # build 2: [compose 1, pulp compose 2] + # build 3: [compose 1, pulp compose 3] + # build 4: [compose 1, pulp compose 4] + # build 5: [compose 1, pulp compose 5] + # build 6 (not planned): [compose 1, pulp compose 6] + + self.db_event = Event.create( + db.session, 'msg-1', 'search-key-1', + EVENT_TYPES[ErrataAdvisoryRPMsSignedEvent], + state=EventState.INITIALIZED, + released=False) + + self.build_1 = ArtifactBuild.create( + db.session, self.db_event, 'build-1', ArtifactType.IMAGE, + state=ArtifactBuildState.PLANNED) + self.build_2 = ArtifactBuild.create( + db.session, self.db_event, 'build-2', ArtifactType.IMAGE, + dep_on=self.build_1, + state=ArtifactBuildState.PLANNED) + + self.build_3 = ArtifactBuild.create( + db.session, self.db_event, 'build-3', ArtifactType.IMAGE, + state=ArtifactBuildState.PLANNED) + self.build_4 = ArtifactBuild.create( + db.session, self.db_event, 'build-4', ArtifactType.IMAGE, + dep_on=self.build_3, + state=ArtifactBuildState.PLANNED) + self.build_5 = ArtifactBuild.create( + db.session, self.db_event, 'build-5', ArtifactType.IMAGE, + dep_on=self.build_3, + state=ArtifactBuildState.PLANNED) + + self.build_6 = ArtifactBuild.create( + db.session, self.db_event, 'build-6', ArtifactType.IMAGE, + state=ArtifactBuildState.BUILD) + + self.compose_1 = Compose(odcs_compose_id=1) + db.session.add(self.compose_1) + db.session.commit() + + builds = [self.build_1, self.build_2, self.build_3, + self.build_4, self.build_5, self.build_6] + composes = [self.compose_1] * 6 + for build, compose in six.moves.zip(builds, composes): + db.session.add(ArtifactBuildCompose( + build_id=build.id, compose_id=compose.id)) + db.session.commit() + + def test_cannot_handle_if_compose_is_not_done(self): + event = ODCSComposeStateChangeEvent( + 'msg-id', {'id': 1, 'state': 'generating'} + ) + handler = RebuildImagesOnODCSComposeDone() + can_handle = handler.can_handle(event) + self.assertFalse(can_handle) + + @patch('freshmaker.models.ArtifactBuild.composes_ready', + new_callable=PropertyMock) + @patch('freshmaker.handlers.ContainerBuildHandler.start_to_build_images') + def test_start_to_build(self, start_to_build_images, composes_ready): + composes_ready.return_value = True + + event = ODCSComposeStateChangeEvent( + 'msg-id', {'id': self.compose_1.id, 'state': 'done'} + ) + + handler = RebuildImagesOnODCSComposeDone() + handler.handle(event) + + args, kwargs = start_to_build_images.call_args + passed_builds = sorted(args[0], key=lambda build: build.id) + self.assertEqual([self.build_1, self.build_3], passed_builds) diff --git a/tests/handlers/koji/test_rebuild_images_on_parent_image_build.py b/tests/handlers/koji/test_rebuild_images_on_parent_image_build.py new file mode 100644 index 0000000..fb488a6 --- /dev/null +++ b/tests/handlers/koji/test_rebuild_images_on_parent_image_build.py @@ -0,0 +1,267 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import json +import mock +import os +import six +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) # noqa +from tests import get_fedmsg, helpers + +from freshmaker import db, events, models +from freshmaker.parsers.brew import BrewTaskStateChangeParser +from freshmaker.handlers.koji import RebuildImagesOnParentImageBuild +from freshmaker.types import ArtifactType, ArtifactBuildState, EventState + + +class TestRebuildImagesOnParentImageBuild(helpers.ModelsTestCase): + def setUp(self): + super(TestRebuildImagesOnParentImageBuild, self).setUp() + events.BaseEvent.register_parser(BrewTaskStateChangeParser) + self.handler = RebuildImagesOnParentImageBuild() + + def test_can_handle_brew_container_task_closed_event(self): + """ + Tests handler can handle brew build container task closed event. + """ + event = self.get_event_from_msg(get_fedmsg('brew_container_task_closed')) + self.assertTrue(self.handler.can_handle(event)) + + def test_can_handle_brew_container_task_failed_event(self): + """ + Tests handler can handle brew build container task failed event. + """ + event = self.get_event_from_msg(get_fedmsg('brew_container_task_failed')) + self.assertTrue(self.handler.can_handle(event)) + + @mock.patch('freshmaker.handlers.ContainerBuildHandler.build_image_artifact_build') + @mock.patch('freshmaker.handlers.ContainerBuildHandler.get_repo_urls') + @mock.patch('freshmaker.handlers.ContainerBuildHandler.set_context') + def test_build_containers_when_dependency_container_is_built(self, set_context, repo_urls, build_image): + """ + Tests when dependency container is built, rebuild containers depend on it. + """ + build_image.side_effect = [1, 2, 3] + repo_urls.return_value = ["url"] + e1 = models.Event.create(db.session, "test_msg_id", "RHSA-2018-001", events.TestingEvent) + event = self.get_event_from_msg(get_fedmsg('brew_container_task_closed')) + + base_build = models.ArtifactBuild.create(db.session, e1, 'test-product-docker', ArtifactType.IMAGE, event.task_id) + + build_0 = models.ArtifactBuild.create(db.session, e1, 'docker-up-0', ArtifactType.IMAGE, 0, + dep_on=base_build, state=ArtifactBuildState.PLANNED) + build_1 = models.ArtifactBuild.create(db.session, e1, 'docker-up-1', ArtifactType.IMAGE, 0, + dep_on=base_build, state=ArtifactBuildState.PLANNED) + build_2 = models.ArtifactBuild.create(db.session, e1, 'docker-up-2', ArtifactType.IMAGE, 0, + dep_on=base_build, state=ArtifactBuildState.PLANNED) + + self.handler.handle(event) + self.assertEqual(base_build.state, ArtifactBuildState.DONE.value) + build_image.assert_has_calls([ + mock.call(build_0, ['url']), mock.call(build_1, ['url']), + mock.call(build_2, ['url']), + ]) + + set_context.assert_has_calls([ + mock.call(build_0), mock.call(build_1), mock.call(build_2)]) + + self.assertEqual(build_0.build_id, 1) + self.assertEqual(build_1.build_id, 2) + self.assertEqual(build_2.build_id, 3) + + @mock.patch('freshmaker.handlers.ContainerBuildHandler.build_image_artifact_build') + @mock.patch('freshmaker.handlers.ContainerBuildHandler.get_repo_urls') + def test_not_build_containers_when_dependency_container_build_task_failed(self, repo_urls, build_image): + """ + Tests when dependency container build task failed in brew, only update build state in db. + """ + build_image.side_effect = [1, 2, 3, 4] + repo_urls.return_value = ["url"] + e1 = models.Event.create(db.session, "test_msg_id", "RHSA-2018-001", events.TestingEvent) + event = self.get_event_from_msg(get_fedmsg('brew_container_task_failed')) + + base_build = models.ArtifactBuild.create(db.session, e1, 'test-product-docker', ArtifactType.IMAGE, event.task_id) + base_build.build_args = json.dumps({}) + + models.ArtifactBuild.create(db.session, e1, 'docker-up', ArtifactType.IMAGE, 0, + dep_on=base_build, state=ArtifactBuildState.PLANNED) + self.handler.handle(event) + self.assertEqual(base_build.state, ArtifactBuildState.BUILD.value) + self.assertEqual(base_build.build_id, 1) + event.task_id = 1 + self.handler.handle(event) + self.assertEqual(base_build.state, ArtifactBuildState.BUILD.value) + self.assertEqual(base_build.build_id, 2) + event.task_id = 2 + self.handler.handle(event) + self.assertEqual(base_build.state, ArtifactBuildState.FAILED.value) + self.assertEqual(base_build.build_id, 2) + build_image.assert_called() + + @mock.patch('freshmaker.models.messaging.publish') + def test_mark_event_COMPLETE_if_all_builds_done(self, publish): + self.db_advisory_rpm_signed_event = models.Event.create( + db.session, 'msg-id-123', '12345', + events.ErrataAdvisoryStateChangedEvent, + state=EventState.BUILDING.value) + + self.image_a_build = models.ArtifactBuild.create( + db.session, self.db_advisory_rpm_signed_event, + 'image-a-0.1-1', ArtifactType.IMAGE, + state=ArtifactBuildState.DONE.value) + + self.image_b_build = models.ArtifactBuild.create( + db.session, self.db_advisory_rpm_signed_event, + 'image-b-0.1-1', ArtifactType.IMAGE, + dep_on=self.image_a_build, + state=ArtifactBuildState.DONE.value) + + self.image_c_build = models.ArtifactBuild.create( + db.session, self.db_advisory_rpm_signed_event, + 'image-c-0.1-1', ArtifactType.IMAGE, + dep_on=self.image_b_build, + state=ArtifactBuildState.FAILED.value) + + self.image_d_build = models.ArtifactBuild.create( + db.session, self.db_advisory_rpm_signed_event, + 'image-d-0.1-1', ArtifactType.IMAGE, + dep_on=self.image_a_build, + build_id=12345, + state=ArtifactBuildState.BUILD.value) + + db.session.commit() + + state_changed_event = events.BrewContainerTaskStateChangeEvent( + 'msg-id-890', 'image-d', 'branch', 'target', 12345, + 'BUILD', 'CLOSED') + + handler = RebuildImagesOnParentImageBuild() + handler.handle(state_changed_event) + + self.assertEqual(EventState.COMPLETE.value, + self.db_advisory_rpm_signed_event.state) + self.assertEqual("1 of 4 container image(s) failed to rebuild.", + self.db_advisory_rpm_signed_event.state_reason) + + @mock.patch('freshmaker.handlers.ContainerBuildHandler.build_image_artifact_build') + @mock.patch('freshmaker.handlers.ContainerBuildHandler.get_repo_urls') + def test_not_change_state_if_not_all_builds_done( + self, get_repo_urls, build_image_artifact_build): + build_image_artifact_build.return_value = 67890 + + self.db_advisory_rpm_signed_event = models.Event.create( + db.session, 'msg-id-123', '12345', + events.ErrataAdvisoryStateChangedEvent, + state=EventState.BUILDING.value) + + self.image_a_build = models.ArtifactBuild.create( + db.session, self.db_advisory_rpm_signed_event, + 'image-a-0.1-1', ArtifactType.IMAGE, + build_id=12345, + state=ArtifactBuildState.BUILD.value) + + self.image_b_build = models.ArtifactBuild.create( + db.session, self.db_advisory_rpm_signed_event, + 'image-b-0.1-1', ArtifactType.IMAGE, + dep_on=self.image_a_build, + state=ArtifactBuildState.PLANNED.value) + + self.image_c_build = models.ArtifactBuild.create( + db.session, self.db_advisory_rpm_signed_event, + 'image-c-0.1-1', ArtifactType.IMAGE, + dep_on=self.image_b_build, + state=ArtifactBuildState.FAILED.value) + + db.session.commit() + + state_changed_event = events.BrewContainerTaskStateChangeEvent( + 'msg-id-890', 'image-a', 'branch', 'target', 12345, + 'BUILD', 'CLOSED') + + handler = RebuildImagesOnParentImageBuild() + handler.handle(state_changed_event) + + # As self.image_b_build starts to be rebuilt, not all images are + # rebuilt yet. + self.assertEqual(EventState.BUILDING.value, + self.db_advisory_rpm_signed_event.state) + + @mock.patch('freshmaker.kojiservice.KojiService') + @mock.patch('freshmaker.errata.Errata.get_builds') + def test_mark_build_done_when_container_has_latest_rpms_from_advisory(self, errata_get_builds, KojiService): + """ + Tests when dependency container build task failed in brew, only update build state in db. + """ + errata_get_builds.return_value = set(['foo-1.2.1-22.el7']) + + koji_service = KojiService.return_value + koji_service.get_build_rpms.return_value = [ + {'build_id': 634904, 'nvr': 'foo-debuginfo-1.2.1-22.el7', 'name': 'foo-debuginfo'}, + {'build_id': 634904, 'nvr': 'foo-1.2.1-22.el7', 'name': 'foo'}, + {'build_id': 634904, 'nvr': 'foo-debuginfo-1.1.1-22.el7', 'name': 'foo-debuginfo'}, + {'build_id': 634904, 'nvr': 'foo-1.1.1-22.el7', 'name': 'foo'}, + ] + koji_service.get_rpms_in_container.return_value = set( + ['foo-1.2.1-22.el7', 'bar-1.2.3-1.el7'] + ) + + e1 = models.Event.create(db.session, "test_msg_id", "2018001", events.ErrataAdvisoryRPMsSignedEvent) + event = self.get_event_from_msg(get_fedmsg('brew_container_task_closed')) + build = models.ArtifactBuild.create(db.session, e1, 'test-product-docker', ArtifactType.IMAGE, event.task_id) + + self.handler.handle(event) + + self.assertEqual(build.state, ArtifactBuildState.DONE.value) + self.assertEqual(build.state_reason, 'Built successfully.') + + @mock.patch('freshmaker.kojiservice.KojiService') + @mock.patch('freshmaker.errata.Errata.get_builds') + def test_mark_build_fail_when_container_not_has_latest_rpms_from_advisory(self, errata_get_builds, KojiService): + """ + Tests when dependency container build task failed in brew, only update build state in db. + """ + errata_get_builds.return_value = set(['foo-1.2.1-23.el7']) + + koji_service = KojiService.return_value + koji_service.get_build_rpms.return_value = [ + {'build_id': 634904, 'nvr': 'foo-debuginfo-1.2.1-23.el7', 'name': 'foo-debuginfo'}, + {'build_id': 634904, 'nvr': 'foo-1.2.1-23.el7', 'name': 'foo'}, + {'build_id': 634904, 'nvr': 'foo-debuginfo-1.1.1-22.el7', 'name': 'foo-debuginfo'}, + {'build_id': 634904, 'nvr': 'foo-1.1.1-22.el7', 'name': 'foo'}, + ] + koji_service.get_rpms_in_container.return_value = set( + ['foo-1.2.1-22.el7', 'bar-1.2.3-1.el7'] + ) + + e1 = models.Event.create(db.session, "test_msg_id", "2018001", events.ErrataAdvisoryRPMsSignedEvent) + event = self.get_event_from_msg(get_fedmsg('brew_container_task_closed')) + build = models.ArtifactBuild.create(db.session, e1, 'test-product-docker', ArtifactType.IMAGE, event.task_id) + + self.handler.handle(event) + self.assertEqual(build.state, ArtifactBuildState.FAILED.value) + six.assertRegex(self, build.state_reason, r"The following RPMs in container build.*") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/handlers/koji/test_rebuild_images_on_rpm_advisory_change.py b/tests/handlers/koji/test_rebuild_images_on_rpm_advisory_change.py new file mode 100644 index 0000000..0c75f7f --- /dev/null +++ b/tests/handlers/koji/test_rebuild_images_on_rpm_advisory_change.py @@ -0,0 +1,514 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from mock import patch + +import freshmaker + +from freshmaker import db +from freshmaker.events import ( + ErrataAdvisoryRPMsSignedEvent, + ManualRebuildWithAdvisoryEvent) +from freshmaker.handlers.koji import RebuildImagesOnRPMAdvisoryChange +from freshmaker.lightblue import ContainerImage +from freshmaker.models import Event, Compose +from freshmaker.types import EventState +from freshmaker.errata import ErrataAdvisory +from freshmaker.config import any_ +from tests import helpers + + +class TestRebuildImagesOnRPMAdvisoryChange(helpers.ModelsTestCase): + + def setUp(self): + super(TestRebuildImagesOnRPMAdvisoryChange, self).setUp() + + # Each time when recording a build into database, freshmaker has to + # request a pulp repo from ODCS. This is not necessary for running + # tests. + # There are 6 images used to run tests which will be created below, so + # there should be 6 composes created as Pulp repos. + self.patcher = helpers.Patcher( + 'freshmaker.handlers.koji.RebuildImagesOnRPMAdvisoryChange.') + + # We do not want to send messages to message bus while running tests + self.mock_messaging_publish = self.patcher.patch( + 'freshmaker.messaging.publish') + + self.mock_prepare_pulp_repo = self.patcher.patch( + 'freshmaker.odcsclient.FreshmakerODCSClient.prepare_pulp_repo', + side_effect=[{'id': compose_id} for compose_id in range(1, 7)]) + + self.mock_find_images_to_rebuild = self.patcher.patch( + '_find_images_to_rebuild') + + # Fake images found to rebuild has these relationships + # + # Batch 1 | Batch 2 | Batch 3 + # image_a | image_c (child of image_a) | image_f (child of image_e) + # image_b | image_d (child of image_a) | + # | image_e (child of image_b) | + # + self.image_a = ContainerImage({ + 'repository': 'repo_1', + 'commit': '1234567', + 'target': 'docker-container-candidate', + 'git_branch': 'rhel-7.4', + 'content_sets': ['image_a_content_set_1', 'image_a_content_set_2'], + "arches": "x86_64", + 'brew': { + 'build': 'image-a-1.0-2', + }, + 'parent': None, + 'parsed_data': { + 'layers': [ + 'sha512:7890', + 'sha512:5678', + ] + }, + "generate_pulp_repos": True, + "odcs_compose_ids": None, + "published": False, + }) + self.image_b = ContainerImage({ + 'repository': 'repo_2', + 'commit': '23e9f22', + 'target': 'docker-container-candidate', + 'git_branch': 'rhel-7.4', + 'content_sets': ['image_b_content_set_1', 'image_b_content_set_2'], + "arches": "x86_64", + 'brew': { + 'build': 'image-b-1.0-1' + }, + 'parent': None, + 'parsed_data': { + 'layers': [ + 'sha512:1234', + 'sha512:4567', + ] + }, + "generate_pulp_repos": True, + "odcs_compose_ids": None, + "published": False, + }) + self.image_c = ContainerImage({ + 'repository': 'repo_2', + 'commit': '2345678', + 'target': 'docker-container-candidate', + 'git_branch': 'rhel-7.4', + 'content_sets': ['image_c_content_set_1', 'image_d_content_set_2'], + "arches": "x86_64", + 'brew': { + 'build': 'image-c-0.2-9', + }, + 'parent': self.image_a, + 'parsed_data': { + 'layers': [ + 'sha512:4ef3', + 'sha512:7890', + 'sha512:5678', + ] + }, + "generate_pulp_repos": True, + "odcs_compose_ids": None, + "published": False, + }) + self.image_d = ContainerImage({ + 'repository': 'repo_2', + 'commit': '5678901', + 'target': 'docker-container-candidate', + 'git_branch': 'rhel-7.4', + 'content_sets': ['image_d_content_set_1', 'image_d_content_set_2'], + "arches": "x86_64", + 'brew': { + 'build': 'image-d-2.14-1', + }, + 'parent': self.image_a, + 'parsed_data': { + 'layers': [ + 'sha512:f109', + 'sha512:7890', + 'sha512:5678', + ] + }, + "generate_pulp_repos": True, + "odcs_compose_ids": None, + "published": False, + }) + self.image_e = ContainerImage({ + 'repository': 'repo_2', + 'commit': '7890123', + 'target': 'docker-container-candidate', + 'git_branch': 'rhel-7.4', + 'content_sets': ['image_e_content_set_1', 'image_e_content_set_2'], + "arches": "x86_64", + 'brew': { + 'build': 'image-e-1.0-1', + }, + 'parent': self.image_b, + 'parsed_data': { + 'layers': [ + 'sha512:5aae', + 'sha512:1234', + 'sha512:4567', + ] + }, + "generate_pulp_repos": True, + "odcs_compose_ids": None, + "published": False, + }) + self.image_f = ContainerImage({ + 'repository': 'repo_2', + 'commit': '3829384', + 'target': 'docker-container-candidate', + 'git_branch': 'rhel-7.4', + 'content_sets': ['image_f_content_set_1', 'image_f_content_set_2'], + "arches": "x86_64", + 'brew': { + 'build': 'image-f-0.2-1', + }, + 'parent': self.image_b, + 'parsed_data': { + 'layers': [ + 'sha512:8b9e', + 'sha512:1234', + 'sha512:4567', + ] + }, + "generate_pulp_repos": True, + "odcs_compose_ids": None, + "published": False, + }) + # For simplicify, mocking _find_images_to_rebuild to just return one + # batch, which contains images found for rebuild from parent to + # childrens. + self.mock_find_images_to_rebuild.return_value = [ + [self.image_a, self.image_b], + [self.image_c, self.image_d, self.image_e], + [self.image_f] + ] + + self.rhba_event = ErrataAdvisoryRPMsSignedEvent( + "123", + ErrataAdvisory(123, "RHBA-2017", "REL_PREP", [], + security_impact="", + product_short_name="product")) + self.rhsa_event = ErrataAdvisoryRPMsSignedEvent( + "123", + ErrataAdvisory(123, "RHSA-2017", "REL_PREP", [], + security_impact="", + product_short_name="product")) + + def tearDown(self): + super(TestRebuildImagesOnRPMAdvisoryChange, self).tearDown() + self.patcher.unpatch_all() + + def test_can_handle_manual_rebuild_with_advisory_event(self): + event = ManualRebuildWithAdvisoryEvent( + "123", + ErrataAdvisory(123, "RHBA-2017", "REL_PREP", [], + security_impact="", + product_short_name="product"), + ["foo-container", "bar-container"]) + handler = RebuildImagesOnRPMAdvisoryChange() + ret = handler.can_handle(event) + self.assertTrue(ret) + + @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'RebuildImagesOnRPMAdvisoryChange': { + 'image': {'product_short_name': 'foo'} + } + }) + @patch.object(freshmaker.conf, 'dry_run', new=True) + def test_allow_build_by_product_short_name(self): + compose_4 = Compose(odcs_compose_id=4) + db.session.add(compose_4) + db.session.commit() + + self.mock_find_images_to_rebuild.return_value = [[]] + handler = RebuildImagesOnRPMAdvisoryChange() + handler.handle(self.rhba_event) + + db_event = Event.get(db.session, message_id='123') + self.assertEqual(db_event.state, EventState.SKIPPED.value) + + @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'RebuildImagesOnRPMAdvisoryChange': { + 'image': { + 'advisory_highest_cve_severity': ['critical', 'important'] + } + } + }) + @patch.object(freshmaker.conf, 'dry_run', new=True) + def test_allow_build_by_highest_cve_severity(self): + compose_4 = Compose(odcs_compose_id=4) + db.session.add(compose_4) + db.session.commit() + + for severity in ["moderate", "critical", "important"]: + self.rhba_event.advisory.highest_cve_severity = severity + self.mock_find_images_to_rebuild.return_value = [[]] + handler = RebuildImagesOnRPMAdvisoryChange() + handler.handle(self.rhba_event) + + db_event = Event.get(db.session, message_id='123') + self.assertEqual(db_event.state, EventState.SKIPPED.value) + if severity == "moderate": + self.assertTrue(db_event.state_reason.endswith( + "is not allowed by internal policy to trigger rebuilds.")) + else: + self.assertEqual( + db_event.state_reason, + "No container images to rebuild for advisory 'RHBA-2017'") + + @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'RebuildImagesOnRPMAdvisoryChange': { + 'image': { + 'advisory_has_hightouch_bug': True, + } + } + }) + @patch.object(freshmaker.conf, 'dry_run', new=True) + def test_allow_build_has_hightouch_bug(self): + compose_4 = Compose(odcs_compose_id=4) + db.session.add(compose_4) + db.session.commit() + + for has_hightouch_bug in [False, True]: + self.rhba_event.advisory.has_hightouch_bug = has_hightouch_bug + self.mock_find_images_to_rebuild.return_value = [[]] + handler = RebuildImagesOnRPMAdvisoryChange() + handler.handle(self.rhba_event) + + db_event = Event.get(db.session, message_id='123') + self.assertEqual(db_event.state, EventState.SKIPPED.value) + if not has_hightouch_bug: + self.assertTrue(db_event.state_reason.endswith( + "is not allowed by internal policy to trigger rebuilds.")) + else: + self.assertEqual( + db_event.state_reason, + "No container images to rebuild for advisory 'RHBA-2017'") + + @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'RebuildImagesOnRPMAdvisoryChange': { + 'image': {'advisory_name': 'RHBA-2017'} + } + }) + def test_event_state_updated_when_no_images_to_rebuild(self): + self.mock_find_images_to_rebuild.return_value = [[]] + handler = RebuildImagesOnRPMAdvisoryChange() + handler.handle(self.rhba_event) + + db_event = Event.get(db.session, message_id='123') + self.assertEqual(db_event.state, EventState.SKIPPED.value) + self.assertEqual( + db_event.state_reason, + "No container images to rebuild for advisory 'RHBA-2017'") + + @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'RebuildImagesOnRPMAdvisoryChange': { + 'image': {'advisory_name': 'RHBA-2017'} + } + }) + def test_event_state_updated_when_all_images_failed(self): + self.image_a['error'] = "foo" + self.mock_find_images_to_rebuild.return_value = [ + [self.image_a]] + handler = RebuildImagesOnRPMAdvisoryChange() + handler.handle(self.rhba_event) + + db_event = Event.get(db.session, message_id='123') + self.assertEqual(db_event.state, EventState.COMPLETE.value) + self.assertEqual( + db_event.state_reason, + "No container images to rebuild, all are in failed state.") + + @patch('freshmaker.handlers.koji.RebuildImagesOnRPMAdvisoryChange.' + 'allow_build', return_value=True) + @patch('freshmaker.odcsclient.FreshmakerODCSClient.prepare_yum_repos_for_rebuilds') + @patch('freshmaker.handlers.koji.RebuildImagesOnRPMAdvisoryChange.' + 'start_to_build_images') + def test_rebuild_if_errata_state_is_prior_to_SHIPPED_LIVE( + self, start_to_build_images, prepare_yum_repos_for_rebuilds, + allow_build): + handler = RebuildImagesOnRPMAdvisoryChange() + handler.handle(self.rhsa_event) + + prepare_yum_repos_for_rebuilds.assert_called_once() + start_to_build_images.assert_called_once() + + db_event = Event.get(db.session, self.rhsa_event.msg_id) + self.assertEqual(EventState.BUILDING.value, db_event.state) + + @patch('freshmaker.handlers.koji.RebuildImagesOnRPMAdvisoryChange.' + 'allow_build', return_value=True) + @patch('freshmaker.odcsclient.FreshmakerODCSClient.prepare_yum_repos_for_rebuilds') + @patch('freshmaker.handlers.koji.RebuildImagesOnRPMAdvisoryChange.' + 'start_to_build_images') + @patch('freshmaker.models.Event.get_image_builds_in_first_batch') + def test_rebuild_if_errata_state_is_SHIPPED_LIVE( + self, get_image_builds_in_first_batch, start_to_build_images, + prepare_yum_repos_for_rebuilds, allow_build): + event = ErrataAdvisoryRPMsSignedEvent( + 'msg-id-123', + ErrataAdvisory(123, "RHSA-2017", "SHIPPED_LIVE", [], + security_impact="", + product_short_name="product")) + handler = RebuildImagesOnRPMAdvisoryChange() + handler.handle(event) + + prepare_yum_repos_for_rebuilds.assert_not_called() + get_image_builds_in_first_batch.assert_called_once_with(db.session) + start_to_build_images.assert_called_once() + + db_event = Event.get(db.session, event.msg_id) + self.assertEqual(EventState.BUILDING.value, db_event.state) + + +class TestFindImagesToRebuild(helpers.FreshmakerTestCase): + + def setUp(self): + super(TestFindImagesToRebuild, self).setUp() + + self.patcher = helpers.Patcher( + "freshmaker.handlers.koji.RebuildImagesOnRPMAdvisoryChange.") + + self.get_content_set_by_repo_ids = self.patcher.patch( + 'freshmaker.pulp.Pulp.get_content_set_by_repo_ids', + return_value=["content-set-1"]) + + self.get_pulp_repository_ids = self.patcher.patch( + 'freshmaker.errata.Errata.get_pulp_repository_ids', + return_value=["pulp_repo_x86_64"]) + + self.get_builds = self.patcher.patch( + 'freshmaker.errata.Errata.get_builds', + return_value=["httpd-2.4-11.el7"]) + + self.find_images_to_rebuild = self.patcher.patch( + 'freshmaker.lightblue.LightBlue.find_images_to_rebuild', + return_value=[[]]) + + self.event = ErrataAdvisoryRPMsSignedEvent( + "123", + ErrataAdvisory(123, "RHBA-2017", "REL_PREP", [], + security_impact="", + product_short_name="product")) + self.manual_event = ManualRebuildWithAdvisoryEvent( + "123", + ErrataAdvisory(123, "RHBA-2017", "REL_PREP", [], + security_impact="", + product_short_name="product"), + ["foo", "bar"]) + self.handler = RebuildImagesOnRPMAdvisoryChange() + self.handler.event = self.event + + def tearDown(self): + super(TestFindImagesToRebuild, self).tearDown() + self.patcher.unpatch_all() + + @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'RebuildImagesOnRPMAdvisoryChange': { + 'image': {'advisory_name': 'RHBA-*'} + } + }) + @patch('os.path.exists', return_value=True) + def test_published_unset(self, exists): + for x in self.handler._find_images_to_rebuild(123456): + pass + + self.find_images_to_rebuild.assert_called_once_with( + set(['httpd-2.4-11.el7']), ['content-set-1'], + filter_fnc=self.handler._filter_out_not_allowed_builds, + published=True, release_category='Generally Available', + leaf_container_images=None) + + @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'RebuildImagesOnRPMAdvisoryChange': { + 'image': {'advisory_name': 'RHBA-*'} + } + }) + @patch('os.path.exists', return_value=True) + def test_multiple_srpms(self, exists): + self.get_builds.return_value = ["httpd-2.4-11.el7", "httpd-2.2-11.el6"] + for x in self.handler._find_images_to_rebuild(123456): + pass + + self.find_images_to_rebuild.assert_called_once_with( + set(['httpd-2.4-11.el7', 'httpd-2.2-11.el6']), ['content-set-1'], + filter_fnc=self.handler._filter_out_not_allowed_builds, + published=True, release_category='Generally Available', + leaf_container_images=None) + + @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'RebuildImagesOnRPMAdvisoryChange': { + 'image': any_({'advisory_name': 'RHBA-*', 'published': True, + 'advisory_product_short_name': 'foo'}, + {'advisory_name': 'RHBA-*', 'published': False, + 'advisory_product_short_name': 'product'}) + } + }) + @patch('os.path.exists', return_value=True) + def test_published_false(self, exists): + for x in self.handler._find_images_to_rebuild(123456): + pass + + self.find_images_to_rebuild.assert_called_once_with( + set(['httpd-2.4-11.el7']), ['content-set-1'], + filter_fnc=self.handler._filter_out_not_allowed_builds, + published=None, release_category=None, + leaf_container_images=None) + + @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'RebuildImagesOnRPMAdvisoryChange': { + 'image': {'advisory_name': 'RHBA-*', + 'published': True} + } + }) + @patch('os.path.exists', return_value=True) + def test_published_true(self, exists): + for x in self.handler._find_images_to_rebuild(123456): + pass + + self.find_images_to_rebuild.assert_called_once_with( + set(['httpd-2.4-11.el7']), ['content-set-1'], + filter_fnc=self.handler._filter_out_not_allowed_builds, + published=True, release_category='Generally Available', + leaf_container_images=None) + + @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'RebuildImagesOnRPMAdvisoryChange': { + 'image': {'advisory_name': 'RHBA-*', + 'published': True} + } + }) + @patch('os.path.exists', return_value=True) + def test_manual_event_leaf_container_images(self, exists): + self.handler.event = self.manual_event + for x in self.handler._find_images_to_rebuild(123456): + pass + + self.find_images_to_rebuild.assert_called_once_with( + set(['httpd-2.4-11.el7']), ['content-set-1'], + filter_fnc=self.handler._filter_out_not_allowed_builds, + published=True, release_category='Generally Available', + leaf_container_images=["foo", "bar"]) diff --git a/tests/handlers/koji/test_rebuild_images_on_rpm_bodhi_update.py b/tests/handlers/koji/test_rebuild_images_on_rpm_bodhi_update.py new file mode 100644 index 0000000..18c5313 --- /dev/null +++ b/tests/handlers/koji/test_rebuild_images_on_rpm_bodhi_update.py @@ -0,0 +1,245 @@ +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Chenxiong Qi + +import mock +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) # noqa +from tests import helpers +from tests import get_fedmsg + +import freshmaker + +from freshmaker import events, models +from freshmaker.types import ArtifactType +from freshmaker.handlers.koji import RebuildImagesOnRPMBodhiUpdate +from freshmaker.parsers.bodhi import BodhiUpdateCompleteStableParser + +mock_found_containers = [ + { + 'release': 'fedora-25-updates', + 'id': 5430, + 'name': 'testimage1', + 'branch': 'f25', + }, + { + 'release': 'fedora-25-updates', + 'id': 5431, + 'name': 'testimage2', + 'branch': 'f25', + }, +] + +mock_release_components = { + 5430: { + 'id': 5430, + 'release': { + 'active': True, + 'release_id': 'fedora-25-updates' + }, + 'bugzilla_component': None, + 'brew_package': None, + 'global_component': 'testimage1', + 'name': 'testimage1', + 'dist_git_branch': 'f25', + 'dist_git_web_url': 'http://pkgs.example.com/cgit/container/testimage1', + 'active': True, + 'type': 'container', + 'srpm': None, + }, + 5431: { + 'id': 5431, + 'release': { + 'active': True, + 'release_id': 'fedora-25-updates' + }, + 'bugzilla_component': None, + 'brew_package': None, + 'global_component': 'testimage2', + 'name': 'testimage2', + 'dist_git_branch': 'f25', + 'dist_git_web_url': 'http://pkgs.example.com/cgit/container/testimage2', + 'active': True, + 'type': 'container', + 'srpm': None, + } +} + + +def mock_get_release_component_by_id(id): + return mock_release_components[id] + + +class RebuildImagesOnRPMBodhiUpdateTest(helpers.ModelsTestCase): + def setUp(self): + super(RebuildImagesOnRPMBodhiUpdateTest, self).setUp() + events.BaseEvent.register_parser(BodhiUpdateCompleteStableParser) + + @mock.patch('freshmaker.handlers.koji.rebuild_images_on_rpm_bodhi_update.PDC') + @mock.patch('freshmaker.handlers.koji.rebuild_images_on_rpm_bodhi_update.utils') + @mock.patch('freshmaker.handlers.koji.rebuild_images_on_rpm_bodhi_update.conf') + @mock.patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'RebuildImagesOnRPMBodhiUpdate': { + 'image': {'name': r'testimage\d', 'branch': 'f25'} + } + }) + def test_trigger_rebuild_container_when_receives_bodhi_rebuild_images_on_rpm_bodhi_update_message(self, conf, utils, PDC): + + conf.git_base_url = 'git://pkgs.fedoraproject.org' + + handler = RebuildImagesOnRPMBodhiUpdate() + handler.get_rpms_included_in_bodhi_update = mock.Mock() + + handler.get_containers_including_rpms = mock.Mock() + + containers = [ + { + 'release': 'fedora-25-updates', + 'id': 5430, + 'name': 'testimage1', + 'branch': 'f25', + }, + { + 'release': 'fedora-25-updates', + 'id': 5431, + 'name': 'testimage2', + 'branch': 'f25', + }, + ] + handler.get_containers_including_rpms.return_value = containers + + utils.get_commit_hash.side_effect = ['c123', 'c456'] + + handler.build_container = mock.Mock() + handler.build_container.side_effect = [123, 456] + + msg = get_fedmsg('bodhi_update_stable') + event = self.get_event_from_msg(msg) + self.assertTrue(handler.can_handle(event)) + handler.handle(event) + + self.assertEqual( + handler.build_container.call_args_list, + [mock.call( + 'git://pkgs.fedoraproject.org/container/testimage1.git?#c123', + 'f25', 'f25-container-candidate'), + mock.call( + 'git://pkgs.fedoraproject.org/container/testimage2.git?#c456', + 'f25', 'f25-container-candidate')]) + + events = models.Event.query.all() + self.assertEqual(len(events), 1) + self.assertEqual(events[0].message_id, msg['body']['msg_id']) + builds = models.ArtifactBuild.query.all() + self.assertEqual(len(builds), 2) + self.assertEqual(builds[0].name, 'testimage1') + self.assertEqual(builds[0].type, ArtifactType.IMAGE.value) + self.assertEqual(builds[0].build_id, 123) + self.assertEqual(builds[1].name, 'testimage2') + self.assertEqual(builds[1].type, ArtifactType.IMAGE.value) + self.assertEqual(builds[1].build_id, 456) + + @mock.patch('freshmaker.handlers.koji.rebuild_images_on_rpm_bodhi_update.PDC') + @mock.patch('freshmaker.handlers.koji.rebuild_images_on_rpm_bodhi_update.utils') + @mock.patch('freshmaker.handlers.koji.rebuild_images_on_rpm_bodhi_update.conf') + def test_get_containers_including_rpms(self, conf, utils, PDC): + expected_found_containers = [ + { + 'release': 'fedora-24-updates', + 'id': 5430, + 'name': 'testimage1', + 'branch': 'f25', + }, + { + 'release': 'fedora-24-updates', + 'id': 5431, + 'name': 'testimage2', + 'branch': 'f25', + }, + ] + pdc = PDC(conf) + pdc.find_containers_by_rpm_name.return_value = expected_found_containers + + handler = RebuildImagesOnRPMBodhiUpdate() + rpms = [ + {'id': 9515683, + 'name': 'community-mysql-devel', + 'nvr': 'community-mysql-devel-5.7.18-2.fc25', + 'release': '2.fc25', + 'version': '5.7.18'}, + {'id': 9515682, + 'name': 'community-mysql-libs', + 'nvr': 'community-mysql-libs-5.7.18-2.fc25', + 'release': '2.fc25', + 'version': '5.7.18'}, + {'id': 9515681, + 'name': 'community-mysql-server', + 'nvr': 'community-mysql-server-5.7.18-2.fc25', + 'release': '2.fc25', + 'version': '5.7.18'}, + ] + + containers = handler.get_containers_including_rpms(rpms) + + self.assertEqual(3, pdc.find_containers_by_rpm_name.call_count) + found_containers = sorted(containers, key=lambda item: item['id']) + self.assertEqual(expected_found_containers, found_containers) + + @helpers.mock_koji + def test_get_rpms_included_in_bohdhi_update(self, mocked_koji): + mocked_koji.add_build("community-mysql-5.7.18-2.fc25") + mocked_koji.add_build_rpms( + "community-mysql-5.7.18-2.fc25", + ['community-mysql-devel-5.7.18-2.fc25', + 'community-mysql-libs-5.7.18-2.fc25', + 'community-mysql-server-5.7.18-2.fc25'], ["i686"]) + + mocked_koji.add_build("qt5-qtwebengine-5.8.0-11.fc25") + mocked_koji.add_build_rpms( + "qt5-qtwebengine-5.8.0-11.fc25", + ['qt5-qtwebengine-devel-5.8.0-11.fc25', + 'qt5-qtwebengine-examples-5.8.0-11.fc25'], ["i686"]) + + builds = [ + { + 'build_id': 884455, + 'name': 'qt5-qtwebengine', + 'nvr': 'qt5-qtwebengine-5.8.0-11.fc25', + 'release': '11.fc25', + 'version': '5.8.0', + }, + { + 'build_id': 881597, + 'name': 'community-mysql', + 'nvr': 'community-mysql-5.7.18-2.fc25', + 'release': '2.fc25', + 'version': '5.7.18', + } + ] + handler = RebuildImagesOnRPMBodhiUpdate() + rpms = list(handler.get_rpms_included_in_bodhi_update(builds)) + + self.assertEqual(5, len(rpms)) + + rpm = list(filter(lambda item: item['nvr'] == "community-mysql-server-5.7.18-2.fc25", rpms)) + self.assertEqual(1, len(rpm)) diff --git a/tests/handlers/mbs/__init__.py b/tests/handlers/mbs/__init__.py new file mode 100644 index 0000000..043e3c6 --- /dev/null +++ b/tests/handlers/mbs/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2016 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Jan Kaluza diff --git a/tests/handlers/mbs/test_rebuild_modules_on_git_mmd_change.py b/tests/handlers/mbs/test_rebuild_modules_on_git_mmd_change.py new file mode 100644 index 0000000..67111e8 --- /dev/null +++ b/tests/handlers/mbs/test_rebuild_modules_on_git_mmd_change.py @@ -0,0 +1,92 @@ +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os +import sys +import unittest +import mock + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) # noqa +from tests import helpers + +import freshmaker + +from freshmaker import events, models +from freshmaker.types import ArtifactType +from freshmaker.handlers.mbs import RebuildModulesOnGitMMDChange +from freshmaker.parsers.git import GitReceiveParser +from freshmaker.config import any_ + + +class RebuildModulesOnGitMMDChangeTest(helpers.ModelsTestCase): + def setUp(self): + super(RebuildModulesOnGitMMDChangeTest, self).setUp() + events.BaseEvent.register_parser(GitReceiveParser) + + def test_can_handle_module_metadata_change_event(self): + """ + Tests handler can handle module metadata change message + """ + m = helpers.DistGitMessage('modules', 'testmodule', 'master', '123') + m.add_changed_file('testmodule.yaml', 1, 1) + msg = m.produce() + + event = self.get_event_from_msg(msg) + + handler = RebuildModulesOnGitMMDChange() + self.assertTrue(handler.can_handle(event)) + + @mock.patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'RebuildModulesOnGitMMDChange': { + 'module': any_({'name': 'testmodule'}, {'branch': 'master'}) + } + }) + def test_can_rebuild_module_when_module_metadata_changed(self): + """ + Tests handler can rebuild module when module metadata is changed in dist-git + """ + m = helpers.DistGitMessage('modules', 'testmodule', 'master', '12345') + m.add_changed_file('testmodule.yaml', 1, 1) + msg = m.produce() + + event = self.get_event_from_msg(msg) + + handler = RebuildModulesOnGitMMDChange() + handler.build_module = mock.Mock() + handler.build_module.return_value = 123 + + self.assertTrue(handler.can_handle(event)) + handler.handle(event) + + self.assertEqual(handler.build_module.call_args_list, + [mock.call('testmodule', 'master', '12345')]) + + event_list = models.Event.query.all() + self.assertEqual(len(event_list), 1) + self.assertEqual(event_list[0].message_id, event.msg_id) + builds = models.ArtifactBuild.query.all() + self.assertEqual(len(builds), 1) + self.assertEqual(builds[0].name, 'testmodule') + self.assertEqual(builds[0].type, ArtifactType.MODULE.value) + self.assertEqual(builds[0].build_id, 123) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/handlers/mbs/test_rebuild_modules_on_git_rpm_spec_change.py b/tests/handlers/mbs/test_rebuild_modules_on_git_rpm_spec_change.py new file mode 100644 index 0000000..9b6e7ea --- /dev/null +++ b/tests/handlers/mbs/test_rebuild_modules_on_git_rpm_spec_change.py @@ -0,0 +1,119 @@ +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os +import sys +import unittest +import mock + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) # noqa +from tests import helpers + +import freshmaker + +from freshmaker import events, models +from freshmaker.types import ArtifactType +from freshmaker.handlers.mbs import RebuildModulesOnGitRPMSpecChange +from freshmaker.parsers.git import GitReceiveParser +from freshmaker.config import any_ + + +class RebuildModulesOnGitRPMSpecChangeTest(helpers.ModelsTestCase): + def setUp(self): + super(RebuildModulesOnGitRPMSpecChangeTest, self).setUp() + events.BaseEvent.register_parser(GitReceiveParser) + + def test_can_handle_dist_git_message_with_rpm_spec_changed(self): + """ + Tests handler can handle rpm spec change event + """ + m = helpers.DistGitMessage('rpms', 'bash', 'master', '123') + m.add_changed_file('bash.spec', 1, 1) + msg = m.produce() + + event = self.get_event_from_msg(msg) + + handler = RebuildModulesOnGitRPMSpecChange() + self.assertTrue(handler.can_handle(event)) + + def test_can_not_handle_dist_git_message_without_rpm_spec_changed(self): + """ + Tests can not handle dist git message that spec file is not changed. + """ + + m = helpers.DistGitMessage('rpms', 'bash', 'master', '123') + m.add_changed_file('test.c', 1, 1) + msg = m.produce() + + event = self.get_event_from_msg(msg) + + handler = RebuildModulesOnGitRPMSpecChange() + self.assertFalse(handler.can_handle(event)) + + @mock.patch('freshmaker.handlers.mbs.rebuild_modules_on_git_rpm_spec_change.PDC') + @mock.patch('freshmaker.handlers.mbs.rebuild_modules_on_git_rpm_spec_change.utils') + @mock.patch('freshmaker.handlers.mbs.rebuild_modules_on_git_rpm_spec_change.conf') + @mock.patch.object(freshmaker.conf, 'handler_build_whitelist', new={ + 'RebuildModulesOnGitRPMSpecChange': { + 'module': any_({'name': 'testmodule'}, {'branch': 'master'}) + } + }) + def test_can_rebuild_modules_has_rpm_included(self, conf, utils, PDC): + """ + Test handler can rebuild modules which include the rpm. + """ + conf.git_base_url = "git://pkgs.fedoraproject.org" + + m = helpers.DistGitMessage('rpms', 'bash', 'master', '123') + m.add_changed_file('bash.spec', 1, 1) + msg = m.produce() + + event = self.get_event_from_msg(msg) + + mod_info = helpers.PDCModuleInfo('testmodule', 'master', '20170412010101') + mod_info.add_rpm("bash-1.2.3-4.f26.rpm") + mod = mod_info.produce() + pdc = PDC.return_value + pdc.get_latest_modules.return_value = [mod] + + commitid = '9287eb8eb4c4c60f73b4a59f228a673846d940c6' + utils.bump_distgit_repo.return_value = commitid + + handler = RebuildModulesOnGitRPMSpecChange() + handler.build_module = mock.Mock() + handler.build_module.return_value = 123 + + self.assertTrue(handler.can_handle(event)) + handler.handle(event) + + handler.build_module.assert_called_with('testmodule', 'master', commitid) + + event_list = models.Event.query.all() + self.assertEqual(len(event_list), 1) + self.assertEqual(event_list[0].message_id, event.msg_id) + builds = models.ArtifactBuild.query.all() + self.assertEqual(len(builds), 1) + self.assertEqual(builds[0].name, 'testmodule') + self.assertEqual(builds[0].type, ArtifactType.MODULE.value) + self.assertEqual(builds[0].build_id, 123) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_bodhi_update_complete_stable_handler.py b/tests/test_bodhi_update_complete_stable_handler.py deleted file mode 100644 index 56cd866..0000000 --- a/tests/test_bodhi_update_complete_stable_handler.py +++ /dev/null @@ -1,245 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# Written by Chenxiong Qi - -import mock -import os -import sys - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) # noqa -from tests import helpers -from tests import get_fedmsg - -import freshmaker - -from freshmaker import events, models -from freshmaker.types import ArtifactType -from freshmaker.handlers.bodhi import BodhiUpdateCompleteStableHandler -from freshmaker.parsers.bodhi import BodhiUpdateCompleteStableParser - -mock_found_containers = [ - { - 'release': 'fedora-25-updates', - 'id': 5430, - 'name': 'testimage1', - 'branch': 'f25', - }, - { - 'release': 'fedora-25-updates', - 'id': 5431, - 'name': 'testimage2', - 'branch': 'f25', - }, -] - -mock_release_components = { - 5430: { - 'id': 5430, - 'release': { - 'active': True, - 'release_id': 'fedora-25-updates' - }, - 'bugzilla_component': None, - 'brew_package': None, - 'global_component': 'testimage1', - 'name': 'testimage1', - 'dist_git_branch': 'f25', - 'dist_git_web_url': 'http://pkgs.example.com/cgit/container/testimage1', - 'active': True, - 'type': 'container', - 'srpm': None, - }, - 5431: { - 'id': 5431, - 'release': { - 'active': True, - 'release_id': 'fedora-25-updates' - }, - 'bugzilla_component': None, - 'brew_package': None, - 'global_component': 'testimage2', - 'name': 'testimage2', - 'dist_git_branch': 'f25', - 'dist_git_web_url': 'http://pkgs.example.com/cgit/container/testimage2', - 'active': True, - 'type': 'container', - 'srpm': None, - } -} - - -def mock_get_release_component_by_id(id): - return mock_release_components[id] - - -class BodhiUpdateCompleteStableHandlerTest(helpers.ModelsTestCase): - def setUp(self): - super(BodhiUpdateCompleteStableHandlerTest, self).setUp() - events.BaseEvent.register_parser(BodhiUpdateCompleteStableParser) - - @mock.patch('freshmaker.handlers.bodhi.update_complete_stable.PDC') - @mock.patch('freshmaker.handlers.bodhi.update_complete_stable.utils') - @mock.patch('freshmaker.handlers.bodhi.update_complete_stable.conf') - @mock.patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'BodhiUpdateCompleteStableHandler': { - 'image': {'name': r'testimage\d', 'branch': 'f25'} - } - }) - def test_trigger_rebuild_container_when_receives_bodhi_update_complete_stable_message(self, conf, utils, PDC): - - conf.git_base_url = 'git://pkgs.fedoraproject.org' - - handler = BodhiUpdateCompleteStableHandler() - handler.get_rpms_included_in_bodhi_update = mock.Mock() - - handler.get_containers_including_rpms = mock.Mock() - - containers = [ - { - 'release': 'fedora-25-updates', - 'id': 5430, - 'name': 'testimage1', - 'branch': 'f25', - }, - { - 'release': 'fedora-25-updates', - 'id': 5431, - 'name': 'testimage2', - 'branch': 'f25', - }, - ] - handler.get_containers_including_rpms.return_value = containers - - utils.get_commit_hash.side_effect = ['c123', 'c456'] - - handler.build_container = mock.Mock() - handler.build_container.side_effect = [123, 456] - - msg = get_fedmsg('bodhi_update_stable') - event = self.get_event_from_msg(msg) - self.assertTrue(handler.can_handle(event)) - handler.handle(event) - - self.assertEqual( - handler.build_container.call_args_list, - [mock.call( - 'git://pkgs.fedoraproject.org/container/testimage1.git?#c123', - 'f25', 'f25-container-candidate'), - mock.call( - 'git://pkgs.fedoraproject.org/container/testimage2.git?#c456', - 'f25', 'f25-container-candidate')]) - - events = models.Event.query.all() - self.assertEqual(len(events), 1) - self.assertEqual(events[0].message_id, msg['body']['msg_id']) - builds = models.ArtifactBuild.query.all() - self.assertEqual(len(builds), 2) - self.assertEqual(builds[0].name, 'testimage1') - self.assertEqual(builds[0].type, ArtifactType.IMAGE.value) - self.assertEqual(builds[0].build_id, 123) - self.assertEqual(builds[1].name, 'testimage2') - self.assertEqual(builds[1].type, ArtifactType.IMAGE.value) - self.assertEqual(builds[1].build_id, 456) - - @mock.patch('freshmaker.handlers.bodhi.update_complete_stable.PDC') - @mock.patch('freshmaker.handlers.bodhi.update_complete_stable.utils') - @mock.patch('freshmaker.handlers.bodhi.update_complete_stable.conf') - def test_get_containers_including_rpms(self, conf, utils, PDC): - expected_found_containers = [ - { - 'release': 'fedora-24-updates', - 'id': 5430, - 'name': 'testimage1', - 'branch': 'f25', - }, - { - 'release': 'fedora-24-updates', - 'id': 5431, - 'name': 'testimage2', - 'branch': 'f25', - }, - ] - pdc = PDC(conf) - pdc.find_containers_by_rpm_name.return_value = expected_found_containers - - handler = BodhiUpdateCompleteStableHandler() - rpms = [ - {'id': 9515683, - 'name': 'community-mysql-devel', - 'nvr': 'community-mysql-devel-5.7.18-2.fc25', - 'release': '2.fc25', - 'version': '5.7.18'}, - {'id': 9515682, - 'name': 'community-mysql-libs', - 'nvr': 'community-mysql-libs-5.7.18-2.fc25', - 'release': '2.fc25', - 'version': '5.7.18'}, - {'id': 9515681, - 'name': 'community-mysql-server', - 'nvr': 'community-mysql-server-5.7.18-2.fc25', - 'release': '2.fc25', - 'version': '5.7.18'}, - ] - - containers = handler.get_containers_including_rpms(rpms) - - self.assertEqual(3, pdc.find_containers_by_rpm_name.call_count) - found_containers = sorted(containers, key=lambda item: item['id']) - self.assertEqual(expected_found_containers, found_containers) - - @helpers.mock_koji - def test_get_rpms_included_in_bohdhi_update(self, mocked_koji): - mocked_koji.add_build("community-mysql-5.7.18-2.fc25") - mocked_koji.add_build_rpms( - "community-mysql-5.7.18-2.fc25", - ['community-mysql-devel-5.7.18-2.fc25', - 'community-mysql-libs-5.7.18-2.fc25', - 'community-mysql-server-5.7.18-2.fc25'], ["i686"]) - - mocked_koji.add_build("qt5-qtwebengine-5.8.0-11.fc25") - mocked_koji.add_build_rpms( - "qt5-qtwebengine-5.8.0-11.fc25", - ['qt5-qtwebengine-devel-5.8.0-11.fc25', - 'qt5-qtwebengine-examples-5.8.0-11.fc25'], ["i686"]) - - builds = [ - { - 'build_id': 884455, - 'name': 'qt5-qtwebengine', - 'nvr': 'qt5-qtwebengine-5.8.0-11.fc25', - 'release': '11.fc25', - 'version': '5.8.0', - }, - { - 'build_id': 881597, - 'name': 'community-mysql', - 'nvr': 'community-mysql-5.7.18-2.fc25', - 'release': '2.fc25', - 'version': '5.7.18', - } - ] - handler = BodhiUpdateCompleteStableHandler() - rpms = list(handler.get_rpms_included_in_bodhi_update(builds)) - - self.assertEqual(5, len(rpms)) - - rpm = list(filter(lambda item: item['nvr'] == "community-mysql-server-5.7.18-2.fc25", rpms)) - self.assertEqual(1, len(rpm)) diff --git a/tests/test_brew_container_task_state_change_handler.py b/tests/test_brew_container_task_state_change_handler.py deleted file mode 100644 index b378f9a..0000000 --- a/tests/test_brew_container_task_state_change_handler.py +++ /dev/null @@ -1,267 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import json -import mock -import os -import six -import sys -import unittest - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) # noqa -from tests import get_fedmsg, helpers - -from freshmaker import db, events, models -from freshmaker.parsers.brew import BrewTaskStateChangeParser -from freshmaker.handlers.brew import BrewContainerTaskStateChangeHandler -from freshmaker.types import ArtifactType, ArtifactBuildState, EventState - - -class TestBrewContainerTaskStateChangeHandler(helpers.ModelsTestCase): - def setUp(self): - super(TestBrewContainerTaskStateChangeHandler, self).setUp() - events.BaseEvent.register_parser(BrewTaskStateChangeParser) - self.handler = BrewContainerTaskStateChangeHandler() - - def test_can_handle_brew_container_task_closed_event(self): - """ - Tests handler can handle brew build container task closed event. - """ - event = self.get_event_from_msg(get_fedmsg('brew_container_task_closed')) - self.assertTrue(self.handler.can_handle(event)) - - def test_can_handle_brew_container_task_failed_event(self): - """ - Tests handler can handle brew build container task failed event. - """ - event = self.get_event_from_msg(get_fedmsg('brew_container_task_failed')) - self.assertTrue(self.handler.can_handle(event)) - - @mock.patch('freshmaker.handlers.ContainerBuildHandler.build_image_artifact_build') - @mock.patch('freshmaker.handlers.ContainerBuildHandler.get_repo_urls') - @mock.patch('freshmaker.handlers.ContainerBuildHandler.set_context') - def test_build_containers_when_dependency_container_is_built(self, set_context, repo_urls, build_image): - """ - Tests when dependency container is built, rebuild containers depend on it. - """ - build_image.side_effect = [1, 2, 3] - repo_urls.return_value = ["url"] - e1 = models.Event.create(db.session, "test_msg_id", "RHSA-2018-001", events.TestingEvent) - event = self.get_event_from_msg(get_fedmsg('brew_container_task_closed')) - - base_build = models.ArtifactBuild.create(db.session, e1, 'test-product-docker', ArtifactType.IMAGE, event.task_id) - - build_0 = models.ArtifactBuild.create(db.session, e1, 'docker-up-0', ArtifactType.IMAGE, 0, - dep_on=base_build, state=ArtifactBuildState.PLANNED) - build_1 = models.ArtifactBuild.create(db.session, e1, 'docker-up-1', ArtifactType.IMAGE, 0, - dep_on=base_build, state=ArtifactBuildState.PLANNED) - build_2 = models.ArtifactBuild.create(db.session, e1, 'docker-up-2', ArtifactType.IMAGE, 0, - dep_on=base_build, state=ArtifactBuildState.PLANNED) - - self.handler.handle(event) - self.assertEqual(base_build.state, ArtifactBuildState.DONE.value) - build_image.assert_has_calls([ - mock.call(build_0, ['url']), mock.call(build_1, ['url']), - mock.call(build_2, ['url']), - ]) - - set_context.assert_has_calls([ - mock.call(build_0), mock.call(build_1), mock.call(build_2)]) - - self.assertEqual(build_0.build_id, 1) - self.assertEqual(build_1.build_id, 2) - self.assertEqual(build_2.build_id, 3) - - @mock.patch('freshmaker.handlers.ContainerBuildHandler.build_image_artifact_build') - @mock.patch('freshmaker.handlers.ContainerBuildHandler.get_repo_urls') - def test_not_build_containers_when_dependency_container_build_task_failed(self, repo_urls, build_image): - """ - Tests when dependency container build task failed in brew, only update build state in db. - """ - build_image.side_effect = [1, 2, 3, 4] - repo_urls.return_value = ["url"] - e1 = models.Event.create(db.session, "test_msg_id", "RHSA-2018-001", events.TestingEvent) - event = self.get_event_from_msg(get_fedmsg('brew_container_task_failed')) - - base_build = models.ArtifactBuild.create(db.session, e1, 'test-product-docker', ArtifactType.IMAGE, event.task_id) - base_build.build_args = json.dumps({}) - - models.ArtifactBuild.create(db.session, e1, 'docker-up', ArtifactType.IMAGE, 0, - dep_on=base_build, state=ArtifactBuildState.PLANNED) - self.handler.handle(event) - self.assertEqual(base_build.state, ArtifactBuildState.BUILD.value) - self.assertEqual(base_build.build_id, 1) - event.task_id = 1 - self.handler.handle(event) - self.assertEqual(base_build.state, ArtifactBuildState.BUILD.value) - self.assertEqual(base_build.build_id, 2) - event.task_id = 2 - self.handler.handle(event) - self.assertEqual(base_build.state, ArtifactBuildState.FAILED.value) - self.assertEqual(base_build.build_id, 2) - build_image.assert_called() - - @mock.patch('freshmaker.models.messaging.publish') - def test_mark_event_COMPLETE_if_all_builds_done(self, publish): - self.db_advisory_rpm_signed_event = models.Event.create( - db.session, 'msg-id-123', '12345', - events.ErrataAdvisoryStateChangedEvent, - state=EventState.BUILDING.value) - - self.image_a_build = models.ArtifactBuild.create( - db.session, self.db_advisory_rpm_signed_event, - 'image-a-0.1-1', ArtifactType.IMAGE, - state=ArtifactBuildState.DONE.value) - - self.image_b_build = models.ArtifactBuild.create( - db.session, self.db_advisory_rpm_signed_event, - 'image-b-0.1-1', ArtifactType.IMAGE, - dep_on=self.image_a_build, - state=ArtifactBuildState.DONE.value) - - self.image_c_build = models.ArtifactBuild.create( - db.session, self.db_advisory_rpm_signed_event, - 'image-c-0.1-1', ArtifactType.IMAGE, - dep_on=self.image_b_build, - state=ArtifactBuildState.FAILED.value) - - self.image_d_build = models.ArtifactBuild.create( - db.session, self.db_advisory_rpm_signed_event, - 'image-d-0.1-1', ArtifactType.IMAGE, - dep_on=self.image_a_build, - build_id=12345, - state=ArtifactBuildState.BUILD.value) - - db.session.commit() - - state_changed_event = events.BrewContainerTaskStateChangeEvent( - 'msg-id-890', 'image-d', 'branch', 'target', 12345, - 'BUILD', 'CLOSED') - - handler = BrewContainerTaskStateChangeHandler() - handler.handle(state_changed_event) - - self.assertEqual(EventState.COMPLETE.value, - self.db_advisory_rpm_signed_event.state) - self.assertEqual("1 of 4 container image(s) failed to rebuild.", - self.db_advisory_rpm_signed_event.state_reason) - - @mock.patch('freshmaker.handlers.ContainerBuildHandler.build_image_artifact_build') - @mock.patch('freshmaker.handlers.ContainerBuildHandler.get_repo_urls') - def test_not_change_state_if_not_all_builds_done( - self, get_repo_urls, build_image_artifact_build): - build_image_artifact_build.return_value = 67890 - - self.db_advisory_rpm_signed_event = models.Event.create( - db.session, 'msg-id-123', '12345', - events.ErrataAdvisoryStateChangedEvent, - state=EventState.BUILDING.value) - - self.image_a_build = models.ArtifactBuild.create( - db.session, self.db_advisory_rpm_signed_event, - 'image-a-0.1-1', ArtifactType.IMAGE, - build_id=12345, - state=ArtifactBuildState.BUILD.value) - - self.image_b_build = models.ArtifactBuild.create( - db.session, self.db_advisory_rpm_signed_event, - 'image-b-0.1-1', ArtifactType.IMAGE, - dep_on=self.image_a_build, - state=ArtifactBuildState.PLANNED.value) - - self.image_c_build = models.ArtifactBuild.create( - db.session, self.db_advisory_rpm_signed_event, - 'image-c-0.1-1', ArtifactType.IMAGE, - dep_on=self.image_b_build, - state=ArtifactBuildState.FAILED.value) - - db.session.commit() - - state_changed_event = events.BrewContainerTaskStateChangeEvent( - 'msg-id-890', 'image-a', 'branch', 'target', 12345, - 'BUILD', 'CLOSED') - - handler = BrewContainerTaskStateChangeHandler() - handler.handle(state_changed_event) - - # As self.image_b_build starts to be rebuilt, not all images are - # rebuilt yet. - self.assertEqual(EventState.BUILDING.value, - self.db_advisory_rpm_signed_event.state) - - @mock.patch('freshmaker.kojiservice.KojiService') - @mock.patch('freshmaker.errata.Errata.get_builds') - def test_mark_build_done_when_container_has_latest_rpms_from_advisory(self, errata_get_builds, KojiService): - """ - Tests when dependency container build task failed in brew, only update build state in db. - """ - errata_get_builds.return_value = set(['foo-1.2.1-22.el7']) - - koji_service = KojiService.return_value - koji_service.get_build_rpms.return_value = [ - {'build_id': 634904, 'nvr': 'foo-debuginfo-1.2.1-22.el7', 'name': 'foo-debuginfo'}, - {'build_id': 634904, 'nvr': 'foo-1.2.1-22.el7', 'name': 'foo'}, - {'build_id': 634904, 'nvr': 'foo-debuginfo-1.1.1-22.el7', 'name': 'foo-debuginfo'}, - {'build_id': 634904, 'nvr': 'foo-1.1.1-22.el7', 'name': 'foo'}, - ] - koji_service.get_rpms_in_container.return_value = set( - ['foo-1.2.1-22.el7', 'bar-1.2.3-1.el7'] - ) - - e1 = models.Event.create(db.session, "test_msg_id", "2018001", events.ErrataAdvisoryRPMsSignedEvent) - event = self.get_event_from_msg(get_fedmsg('brew_container_task_closed')) - build = models.ArtifactBuild.create(db.session, e1, 'test-product-docker', ArtifactType.IMAGE, event.task_id) - - self.handler.handle(event) - - self.assertEqual(build.state, ArtifactBuildState.DONE.value) - self.assertEqual(build.state_reason, 'Built successfully.') - - @mock.patch('freshmaker.kojiservice.KojiService') - @mock.patch('freshmaker.errata.Errata.get_builds') - def test_mark_build_fail_when_container_not_has_latest_rpms_from_advisory(self, errata_get_builds, KojiService): - """ - Tests when dependency container build task failed in brew, only update build state in db. - """ - errata_get_builds.return_value = set(['foo-1.2.1-23.el7']) - - koji_service = KojiService.return_value - koji_service.get_build_rpms.return_value = [ - {'build_id': 634904, 'nvr': 'foo-debuginfo-1.2.1-23.el7', 'name': 'foo-debuginfo'}, - {'build_id': 634904, 'nvr': 'foo-1.2.1-23.el7', 'name': 'foo'}, - {'build_id': 634904, 'nvr': 'foo-debuginfo-1.1.1-22.el7', 'name': 'foo-debuginfo'}, - {'build_id': 634904, 'nvr': 'foo-1.1.1-22.el7', 'name': 'foo'}, - ] - koji_service.get_rpms_in_container.return_value = set( - ['foo-1.2.1-22.el7', 'bar-1.2.3-1.el7'] - ) - - e1 = models.Event.create(db.session, "test_msg_id", "2018001", events.ErrataAdvisoryRPMsSignedEvent) - event = self.get_event_from_msg(get_fedmsg('brew_container_task_closed')) - build = models.ArtifactBuild.create(db.session, e1, 'test-product-docker', ArtifactType.IMAGE, event.task_id) - - self.handler.handle(event) - self.assertEqual(build.state, ArtifactBuildState.FAILED.value) - six.assertRegex(self, build.state_reason, r"The following RPMs in container build.*") - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_brew_sign_rpm_handler.py b/tests/test_brew_sign_rpm_handler.py deleted file mode 100644 index 595a2ac..0000000 --- a/tests/test_brew_sign_rpm_handler.py +++ /dev/null @@ -1,228 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# Written by Chenxiong Qi - -from mock import patch, MagicMock, PropertyMock - -from freshmaker.handlers.brew.sign_rpm import BrewSignRPMHandler -from freshmaker.errata import ErrataAdvisory -from tests import helpers - - -class TestBrewSignHandler(helpers.ModelsTestCase): - """Test BrewSignRPMHandler.handle""" - - @patch('freshmaker.errata.Errata.advisories_from_event') - @patch('freshmaker.errata.Errata.builds_signed') - @patch("freshmaker.config.Config.handler_build_whitelist", - new_callable=PropertyMock, return_value={ - "BrewSignRPMHandler": {"image": {"advisory_name": "RHSA-.*"}}}) - def test_return_value(self, handler_build_whitelist, builds_signed, - advisories_from_event): - """ - Tests that handle method returns ErrataAdvisoryRPMsSignedEvent. - """ - advisories_from_event.return_value = [ - ErrataAdvisory(123, "RHSA-2017", "REL_PREP", ["rpm"])] - builds_signed.return_value = True - - event = MagicMock() - event.msg_id = "msg_123" - handler = BrewSignRPMHandler() - ret = handler.handle(event) - - self.assertTrue(len(ret), 1) - self.assertEqual(ret[0].advisory.name, "RHSA-2017") - self.assertEqual(ret[0].advisory.errata_id, 123) - - @patch('freshmaker.errata.Errata.advisories_from_event') - @patch('freshmaker.errata.Errata.builds_signed') - @patch("freshmaker.config.Config.handler_build_whitelist", - new_callable=PropertyMock, return_value={ - "global": {"image": {"advisory_name": "RHSA-.*"}}}) - def test_allow_build_false_global(self, handler_build_whitelist, - builds_signed, advisories_from_event): - """ - Tests that allow_build filters out advisories based on advisory_name. - """ - advisories_from_event.return_value = [ - ErrataAdvisory(123, "RHBA-2017", "REL_PREP", ["rpm"])] - builds_signed.return_value = False - - event = MagicMock() - handler = BrewSignRPMHandler() - ret = handler.handle(event) - - self.assertTrue(not ret) - builds_signed.assert_not_called() - - @patch('freshmaker.errata.Errata.advisories_from_event') - @patch('freshmaker.errata.Errata.builds_signed') - @patch("freshmaker.config.Config.handler_build_whitelist", - new_callable=PropertyMock, return_value={ - "global": {"image": {"advisory_name": "RHSA-.*"}}}) - def test_allow_build_true_global(self, handler_build_whitelist, - builds_signed, advisories_from_event): - """ - Tests that allow_build does not filter out advisories based on - advisory_name. - """ - advisories_from_event.return_value = [ - ErrataAdvisory(123, "RHSA-2017", "REL_PREP", ["rpm"])] - builds_signed.return_value = False - - event = MagicMock() - handler = BrewSignRPMHandler() - handler.handle(event) - - builds_signed.assert_called_once() - - @patch('freshmaker.errata.Errata.advisories_from_event') - @patch('freshmaker.errata.Errata.builds_signed') - @patch("freshmaker.config.Config.handler_build_whitelist", - new_callable=PropertyMock, return_value={ - "BrewSignRPMHandler": {"image": {"advisory_name": "RHSA-.*"}}}) - def test_allow_build_false(self, handler_build_whitelist, builds_signed, - advisories_from_event): - """ - Tests that allow_build filters out advisories based on advisory_name. - """ - advisories_from_event.return_value = [ - ErrataAdvisory(123, "RHBA-2017", "REL_PREP", ["rpm"])] - builds_signed.return_value = False - - event = MagicMock() - handler = BrewSignRPMHandler() - ret = handler.handle(event) - - self.assertTrue(not ret) - builds_signed.assert_not_called() - - @patch('freshmaker.errata.Errata.advisories_from_event') - @patch('freshmaker.errata.Errata.builds_signed') - @patch("freshmaker.config.Config.handler_build_whitelist", - new_callable=PropertyMock, return_value={ - "BrewSignRPMHandler": {"image": {"advisory_name": "RHSA-.*"}}}) - def test_allow_build_true(self, handler_build_whitelist, builds_signed, - advisories_from_event): - """ - Tests that allow_build does not filter out advisories based on - advisory_name. - """ - advisories_from_event.return_value = [ - ErrataAdvisory(123, "RHSA-2017", "REL_PREP", ["rpm"])] - builds_signed.return_value = False - - event = MagicMock() - handler = BrewSignRPMHandler() - handler.handle(event) - - builds_signed.assert_called_once() - - @patch('freshmaker.errata.Errata.advisories_from_event') - @patch('freshmaker.errata.Errata.builds_signed') - @patch( - "freshmaker.config.Config.handler_build_whitelist", - new_callable=PropertyMock, - return_value={ - "BrewSignRPMHandler": { - "image": { - "advisory_security_impact": [ - "Normal", "Important" - ] - } - } - }) - def test_allow_security_impact_important_true( - self, handler_build_whitelist, builds_signed, - advisories_from_event): - """ - Tests that allow_build does not filter out advisories based on - advisory_security_impact. - """ - advisories_from_event.return_value = [ - ErrataAdvisory(123, "RHSA-2017", "REL_PREP", ["rpm"], "Important")] - builds_signed.return_value = False - - event = MagicMock() - handler = BrewSignRPMHandler() - handler.handle(event) - - builds_signed.assert_called_once() - - @patch('freshmaker.errata.Errata.advisories_from_event') - @patch('freshmaker.errata.Errata.builds_signed') - @patch( - "freshmaker.config.Config.handler_build_whitelist", - new_callable=PropertyMock, - return_value={ - "BrewSignRPMHandler": { - "image": { - "advisory_security_impact": [ - "Normal", "Important" - ] - } - } - }) - def test_allow_security_impact_important_false( - self, handler_build_whitelist, builds_signed, - advisories_from_event): - """ - Tests that allow_build dost filter out advisories based on - advisory_security_impact. - """ - advisories_from_event.return_value = [ - ErrataAdvisory(123, "RHSA-2017", "REL_PREP", ["rpm"], "None")] - builds_signed.return_value = False - - event = MagicMock() - handler = BrewSignRPMHandler() - handler.handle(event) - - builds_signed.assert_not_called() - - @patch('freshmaker.errata.Errata.advisories_from_event') - @patch('freshmaker.errata.Errata.builds_signed') - @patch("freshmaker.config.Config.handler_build_whitelist", - new_callable=PropertyMock, return_value={ - "BrewSignRPMHandler": {"image": {"advisory_name": "RHSA-.*"}}}) - def test_do_not_create_already_handled_event( - self, handler_build_whitelist, builds_signed, - advisories_from_event): - """ - Tests that BrewSignRPMHandler don't return Event which already exists - in Freshmaker DB. - """ - builds_signed.return_value = True - advisories_from_event.return_value = [ - ErrataAdvisory(123, "RHSA-2017", "REL_PREP", ["rpm"])] - - event = MagicMock() - event.msg_id = "msg_123" - handler = BrewSignRPMHandler() - handler.handle(event) - - builds_signed.assert_called_once() - builds_signed.reset_mock() - - handler.handle(event) - builds_signed.assert_not_called() diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..c1dd92d --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,36 @@ +# Copyright (c) 2018 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Jan Kaluza + +import os +import threading + +from freshmaker import conf +from tests import helpers + + +class TestConfig(helpers.FreshmakerTestCase): + + def test_krb_auth_ccache_file(self): + self.assertEqual( + conf.krb_auth_ccache_file, + "freshmaker_cc_%s_%s" % (os.getpid(), + threading.current_thread().ident)) diff --git a/tests/test_consumer.py b/tests/test_consumer.py index d66d76b..8f937de 100644 --- a/tests/test_consumer.py +++ b/tests/test_consumer.py @@ -50,7 +50,7 @@ class ConsumerBaseTest(helpers.ModelsTestCase): class ConsumerTest(ConsumerBaseTest): - @mock.patch("freshmaker.handlers.mbs.module_state_change.MBSModuleStateChangeHandler.handle") + @mock.patch("freshmaker.handlers.internal.UpdateDBOnModuleBuild.handle") @mock.patch("freshmaker.consumer.get_global_consumer") def test_consumer_processing_message(self, global_consumer, handle): """ @@ -80,7 +80,7 @@ class ConsumerTest(ConsumerBaseTest): for topic in topics: self.assertIn(mock.call(topic, callback), consumer.hub.subscribe.call_args_list) - @mock.patch("freshmaker.handlers.mbs.module_state_change.MBSModuleStateChangeHandler.handle", + @mock.patch("freshmaker.handlers.internal.UpdateDBOnModuleBuild.handle", autospec=True) @mock.patch("freshmaker.consumer.get_global_consumer") def test_consumer_mark_event_as_failed_on_exception( diff --git a/tests/test_errata_advisory_rpms_signed_handler.py b/tests/test_errata_advisory_rpms_signed_handler.py deleted file mode 100644 index b176c84..0000000 --- a/tests/test_errata_advisory_rpms_signed_handler.py +++ /dev/null @@ -1,514 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from mock import patch - -import freshmaker - -from freshmaker import db -from freshmaker.events import ( - ErrataAdvisoryRPMsSignedEvent, - ManualRebuildWithAdvisoryEvent) -from freshmaker.handlers.errata import ErrataAdvisoryRPMsSignedHandler -from freshmaker.lightblue import ContainerImage -from freshmaker.models import Event, Compose -from freshmaker.types import EventState -from freshmaker.errata import ErrataAdvisory -from freshmaker.config import any_ -from tests import helpers - - -class TestErrataAdvisoryRPMsSignedHandler(helpers.ModelsTestCase): - - def setUp(self): - super(TestErrataAdvisoryRPMsSignedHandler, self).setUp() - - # Each time when recording a build into database, freshmaker has to - # request a pulp repo from ODCS. This is not necessary for running - # tests. - # There are 6 images used to run tests which will be created below, so - # there should be 6 composes created as Pulp repos. - self.patcher = helpers.Patcher( - 'freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.') - - # We do not want to send messages to message bus while running tests - self.mock_messaging_publish = self.patcher.patch( - 'freshmaker.messaging.publish') - - self.mock_prepare_pulp_repo = self.patcher.patch( - 'freshmaker.odcsclient.FreshmakerODCSClient.prepare_pulp_repo', - side_effect=[{'id': compose_id} for compose_id in range(1, 7)]) - - self.mock_find_images_to_rebuild = self.patcher.patch( - '_find_images_to_rebuild') - - # Fake images found to rebuild has these relationships - # - # Batch 1 | Batch 2 | Batch 3 - # image_a | image_c (child of image_a) | image_f (child of image_e) - # image_b | image_d (child of image_a) | - # | image_e (child of image_b) | - # - self.image_a = ContainerImage({ - 'repository': 'repo_1', - 'commit': '1234567', - 'target': 'docker-container-candidate', - 'git_branch': 'rhel-7.4', - 'content_sets': ['image_a_content_set_1', 'image_a_content_set_2'], - "arches": "x86_64", - 'brew': { - 'build': 'image-a-1.0-2', - }, - 'parent': None, - 'parsed_data': { - 'layers': [ - 'sha512:7890', - 'sha512:5678', - ] - }, - "generate_pulp_repos": True, - "odcs_compose_ids": None, - "published": False, - }) - self.image_b = ContainerImage({ - 'repository': 'repo_2', - 'commit': '23e9f22', - 'target': 'docker-container-candidate', - 'git_branch': 'rhel-7.4', - 'content_sets': ['image_b_content_set_1', 'image_b_content_set_2'], - "arches": "x86_64", - 'brew': { - 'build': 'image-b-1.0-1' - }, - 'parent': None, - 'parsed_data': { - 'layers': [ - 'sha512:1234', - 'sha512:4567', - ] - }, - "generate_pulp_repos": True, - "odcs_compose_ids": None, - "published": False, - }) - self.image_c = ContainerImage({ - 'repository': 'repo_2', - 'commit': '2345678', - 'target': 'docker-container-candidate', - 'git_branch': 'rhel-7.4', - 'content_sets': ['image_c_content_set_1', 'image_d_content_set_2'], - "arches": "x86_64", - 'brew': { - 'build': 'image-c-0.2-9', - }, - 'parent': self.image_a, - 'parsed_data': { - 'layers': [ - 'sha512:4ef3', - 'sha512:7890', - 'sha512:5678', - ] - }, - "generate_pulp_repos": True, - "odcs_compose_ids": None, - "published": False, - }) - self.image_d = ContainerImage({ - 'repository': 'repo_2', - 'commit': '5678901', - 'target': 'docker-container-candidate', - 'git_branch': 'rhel-7.4', - 'content_sets': ['image_d_content_set_1', 'image_d_content_set_2'], - "arches": "x86_64", - 'brew': { - 'build': 'image-d-2.14-1', - }, - 'parent': self.image_a, - 'parsed_data': { - 'layers': [ - 'sha512:f109', - 'sha512:7890', - 'sha512:5678', - ] - }, - "generate_pulp_repos": True, - "odcs_compose_ids": None, - "published": False, - }) - self.image_e = ContainerImage({ - 'repository': 'repo_2', - 'commit': '7890123', - 'target': 'docker-container-candidate', - 'git_branch': 'rhel-7.4', - 'content_sets': ['image_e_content_set_1', 'image_e_content_set_2'], - "arches": "x86_64", - 'brew': { - 'build': 'image-e-1.0-1', - }, - 'parent': self.image_b, - 'parsed_data': { - 'layers': [ - 'sha512:5aae', - 'sha512:1234', - 'sha512:4567', - ] - }, - "generate_pulp_repos": True, - "odcs_compose_ids": None, - "published": False, - }) - self.image_f = ContainerImage({ - 'repository': 'repo_2', - 'commit': '3829384', - 'target': 'docker-container-candidate', - 'git_branch': 'rhel-7.4', - 'content_sets': ['image_f_content_set_1', 'image_f_content_set_2'], - "arches": "x86_64", - 'brew': { - 'build': 'image-f-0.2-1', - }, - 'parent': self.image_b, - 'parsed_data': { - 'layers': [ - 'sha512:8b9e', - 'sha512:1234', - 'sha512:4567', - ] - }, - "generate_pulp_repos": True, - "odcs_compose_ids": None, - "published": False, - }) - # For simplicify, mocking _find_images_to_rebuild to just return one - # batch, which contains images found for rebuild from parent to - # childrens. - self.mock_find_images_to_rebuild.return_value = [ - [self.image_a, self.image_b], - [self.image_c, self.image_d, self.image_e], - [self.image_f] - ] - - self.rhba_event = ErrataAdvisoryRPMsSignedEvent( - "123", - ErrataAdvisory(123, "RHBA-2017", "REL_PREP", [], - security_impact="", - product_short_name="product")) - self.rhsa_event = ErrataAdvisoryRPMsSignedEvent( - "123", - ErrataAdvisory(123, "RHSA-2017", "REL_PREP", [], - security_impact="", - product_short_name="product")) - - def tearDown(self): - super(TestErrataAdvisoryRPMsSignedHandler, self).tearDown() - self.patcher.unpatch_all() - - def test_can_handle_manual_rebuild_with_advisory_event(self): - event = ManualRebuildWithAdvisoryEvent( - "123", - ErrataAdvisory(123, "RHBA-2017", "REL_PREP", [], - security_impact="", - product_short_name="product"), - ["foo-container", "bar-container"]) - handler = ErrataAdvisoryRPMsSignedHandler() - ret = handler.can_handle(event) - self.assertTrue(ret) - - @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'ErrataAdvisoryRPMsSignedHandler': { - 'image': {'product_short_name': 'foo'} - } - }) - @patch.object(freshmaker.conf, 'dry_run', new=True) - def test_allow_build_by_product_short_name(self): - compose_4 = Compose(odcs_compose_id=4) - db.session.add(compose_4) - db.session.commit() - - self.mock_find_images_to_rebuild.return_value = [[]] - handler = ErrataAdvisoryRPMsSignedHandler() - handler.handle(self.rhba_event) - - db_event = Event.get(db.session, message_id='123') - self.assertEqual(db_event.state, EventState.SKIPPED.value) - - @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'ErrataAdvisoryRPMsSignedHandler': { - 'image': { - 'advisory_highest_cve_severity': ['critical', 'important'] - } - } - }) - @patch.object(freshmaker.conf, 'dry_run', new=True) - def test_allow_build_by_highest_cve_severity(self): - compose_4 = Compose(odcs_compose_id=4) - db.session.add(compose_4) - db.session.commit() - - for severity in ["moderate", "critical", "important"]: - self.rhba_event.advisory.highest_cve_severity = severity - self.mock_find_images_to_rebuild.return_value = [[]] - handler = ErrataAdvisoryRPMsSignedHandler() - handler.handle(self.rhba_event) - - db_event = Event.get(db.session, message_id='123') - self.assertEqual(db_event.state, EventState.SKIPPED.value) - if severity == "moderate": - self.assertTrue(db_event.state_reason.endswith( - "is not allowed by internal policy to trigger rebuilds.")) - else: - self.assertEqual( - db_event.state_reason, - "No container images to rebuild for advisory 'RHBA-2017'") - - @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'ErrataAdvisoryRPMsSignedHandler': { - 'image': { - 'advisory_has_hightouch_bug': True, - } - } - }) - @patch.object(freshmaker.conf, 'dry_run', new=True) - def test_allow_build_has_hightouch_bug(self): - compose_4 = Compose(odcs_compose_id=4) - db.session.add(compose_4) - db.session.commit() - - for has_hightouch_bug in [False, True]: - self.rhba_event.advisory.has_hightouch_bug = has_hightouch_bug - self.mock_find_images_to_rebuild.return_value = [[]] - handler = ErrataAdvisoryRPMsSignedHandler() - handler.handle(self.rhba_event) - - db_event = Event.get(db.session, message_id='123') - self.assertEqual(db_event.state, EventState.SKIPPED.value) - if not has_hightouch_bug: - self.assertTrue(db_event.state_reason.endswith( - "is not allowed by internal policy to trigger rebuilds.")) - else: - self.assertEqual( - db_event.state_reason, - "No container images to rebuild for advisory 'RHBA-2017'") - - @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'ErrataAdvisoryRPMsSignedHandler': { - 'image': {'advisory_name': 'RHBA-2017'} - } - }) - def test_event_state_updated_when_no_images_to_rebuild(self): - self.mock_find_images_to_rebuild.return_value = [[]] - handler = ErrataAdvisoryRPMsSignedHandler() - handler.handle(self.rhba_event) - - db_event = Event.get(db.session, message_id='123') - self.assertEqual(db_event.state, EventState.SKIPPED.value) - self.assertEqual( - db_event.state_reason, - "No container images to rebuild for advisory 'RHBA-2017'") - - @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'ErrataAdvisoryRPMsSignedHandler': { - 'image': {'advisory_name': 'RHBA-2017'} - } - }) - def test_event_state_updated_when_all_images_failed(self): - self.image_a['error'] = "foo" - self.mock_find_images_to_rebuild.return_value = [ - [self.image_a]] - handler = ErrataAdvisoryRPMsSignedHandler() - handler.handle(self.rhba_event) - - db_event = Event.get(db.session, message_id='123') - self.assertEqual(db_event.state, EventState.COMPLETE.value) - self.assertEqual( - db_event.state_reason, - "No container images to rebuild, all are in failed state.") - - @patch('freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.' - 'allow_build', return_value=True) - @patch('freshmaker.odcsclient.FreshmakerODCSClient.prepare_yum_repos_for_rebuilds') - @patch('freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.' - 'start_to_build_images') - def test_rebuild_if_errata_state_is_prior_to_SHIPPED_LIVE( - self, start_to_build_images, prepare_yum_repos_for_rebuilds, - allow_build): - handler = ErrataAdvisoryRPMsSignedHandler() - handler.handle(self.rhsa_event) - - prepare_yum_repos_for_rebuilds.assert_called_once() - start_to_build_images.assert_called_once() - - db_event = Event.get(db.session, self.rhsa_event.msg_id) - self.assertEqual(EventState.BUILDING.value, db_event.state) - - @patch('freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.' - 'allow_build', return_value=True) - @patch('freshmaker.odcsclient.FreshmakerODCSClient.prepare_yum_repos_for_rebuilds') - @patch('freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.' - 'start_to_build_images') - @patch('freshmaker.models.Event.get_image_builds_in_first_batch') - def test_rebuild_if_errata_state_is_SHIPPED_LIVE( - self, get_image_builds_in_first_batch, start_to_build_images, - prepare_yum_repos_for_rebuilds, allow_build): - event = ErrataAdvisoryRPMsSignedEvent( - 'msg-id-123', - ErrataAdvisory(123, "RHSA-2017", "SHIPPED_LIVE", [], - security_impact="", - product_short_name="product")) - handler = ErrataAdvisoryRPMsSignedHandler() - handler.handle(event) - - prepare_yum_repos_for_rebuilds.assert_not_called() - get_image_builds_in_first_batch.assert_called_once_with(db.session) - start_to_build_images.assert_called_once() - - db_event = Event.get(db.session, event.msg_id) - self.assertEqual(EventState.BUILDING.value, db_event.state) - - -class TestFindImagesToRebuild(helpers.FreshmakerTestCase): - - def setUp(self): - super(TestFindImagesToRebuild, self).setUp() - - self.patcher = helpers.Patcher( - "freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.") - - self.get_content_set_by_repo_ids = self.patcher.patch( - 'freshmaker.pulp.Pulp.get_content_set_by_repo_ids', - return_value=["content-set-1"]) - - self.get_pulp_repository_ids = self.patcher.patch( - 'freshmaker.errata.Errata.get_pulp_repository_ids', - return_value=["pulp_repo_x86_64"]) - - self.get_builds = self.patcher.patch( - 'freshmaker.errata.Errata.get_builds', - return_value=["httpd-2.4-11.el7"]) - - self.find_images_to_rebuild = self.patcher.patch( - 'freshmaker.lightblue.LightBlue.find_images_to_rebuild', - return_value=[[]]) - - self.event = ErrataAdvisoryRPMsSignedEvent( - "123", - ErrataAdvisory(123, "RHBA-2017", "REL_PREP", [], - security_impact="", - product_short_name="product")) - self.manual_event = ManualRebuildWithAdvisoryEvent( - "123", - ErrataAdvisory(123, "RHBA-2017", "REL_PREP", [], - security_impact="", - product_short_name="product"), - ["foo", "bar"]) - self.handler = ErrataAdvisoryRPMsSignedHandler() - self.handler.event = self.event - - def tearDown(self): - super(TestFindImagesToRebuild, self).tearDown() - self.patcher.unpatch_all() - - @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'ErrataAdvisoryRPMsSignedHandler': { - 'image': {'advisory_name': 'RHBA-*'} - } - }) - @patch('os.path.exists', return_value=True) - def test_published_unset(self, exists): - for x in self.handler._find_images_to_rebuild(123456): - pass - - self.find_images_to_rebuild.assert_called_once_with( - set(['httpd-2.4-11.el7']), ['content-set-1'], - filter_fnc=self.handler._filter_out_not_allowed_builds, - published=True, release_category='Generally Available', - leaf_container_images=None) - - @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'ErrataAdvisoryRPMsSignedHandler': { - 'image': {'advisory_name': 'RHBA-*'} - } - }) - @patch('os.path.exists', return_value=True) - def test_multiple_srpms(self, exists): - self.get_builds.return_value = ["httpd-2.4-11.el7", "httpd-2.2-11.el6"] - for x in self.handler._find_images_to_rebuild(123456): - pass - - self.find_images_to_rebuild.assert_called_once_with( - set(['httpd-2.4-11.el7', 'httpd-2.2-11.el6']), ['content-set-1'], - filter_fnc=self.handler._filter_out_not_allowed_builds, - published=True, release_category='Generally Available', - leaf_container_images=None) - - @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'ErrataAdvisoryRPMsSignedHandler': { - 'image': any_({'advisory_name': 'RHBA-*', 'published': True, - 'advisory_product_short_name': 'foo'}, - {'advisory_name': 'RHBA-*', 'published': False, - 'advisory_product_short_name': 'product'}) - } - }) - @patch('os.path.exists', return_value=True) - def test_published_false(self, exists): - for x in self.handler._find_images_to_rebuild(123456): - pass - - self.find_images_to_rebuild.assert_called_once_with( - set(['httpd-2.4-11.el7']), ['content-set-1'], - filter_fnc=self.handler._filter_out_not_allowed_builds, - published=None, release_category=None, - leaf_container_images=None) - - @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'ErrataAdvisoryRPMsSignedHandler': { - 'image': {'advisory_name': 'RHBA-*', - 'published': True} - } - }) - @patch('os.path.exists', return_value=True) - def test_published_true(self, exists): - for x in self.handler._find_images_to_rebuild(123456): - pass - - self.find_images_to_rebuild.assert_called_once_with( - set(['httpd-2.4-11.el7']), ['content-set-1'], - filter_fnc=self.handler._filter_out_not_allowed_builds, - published=True, release_category='Generally Available', - leaf_container_images=None) - - @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'ErrataAdvisoryRPMsSignedHandler': { - 'image': {'advisory_name': 'RHBA-*', - 'published': True} - } - }) - @patch('os.path.exists', return_value=True) - def test_manual_event_leaf_container_images(self, exists): - self.handler.event = self.manual_event - for x in self.handler._find_images_to_rebuild(123456): - pass - - self.find_images_to_rebuild.assert_called_once_with( - set(['httpd-2.4-11.el7']), ['content-set-1'], - filter_fnc=self.handler._filter_out_not_allowed_builds, - published=True, release_category='Generally Available', - leaf_container_images=["foo", "bar"]) diff --git a/tests/test_errata_advisory_state_changed.py b/tests/test_errata_advisory_state_changed.py deleted file mode 100644 index 4d0b44b..0000000 --- a/tests/test_errata_advisory_state_changed.py +++ /dev/null @@ -1,1104 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# Written by Chenxiong Qi - -import json - -from mock import patch, PropertyMock, Mock, call - -from freshmaker import conf, db, events -from freshmaker.config import all_ -from freshmaker.errata import ErrataAdvisory -from freshmaker.events import ErrataAdvisoryRPMsSignedEvent -from freshmaker.events import ErrataAdvisoryStateChangedEvent -from freshmaker.handlers.errata import ErrataAdvisoryRPMsSignedHandler -from freshmaker.handlers.errata import ErrataAdvisoryStateChangedHandler -from freshmaker.lightblue import ContainerImage -from freshmaker.models import Event, ArtifactBuild, EVENT_TYPES -from freshmaker.types import ArtifactBuildState, ArtifactType, EventState -from tests import helpers - - -class TestAllowBuild(helpers.ModelsTestCase): - """Test ErrataAdvisoryRPMsSignedHandler.allow_build""" - - @patch("freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler." - "_find_images_to_rebuild", return_value=[]) - @patch("freshmaker.config.Config.handler_build_whitelist", - new_callable=PropertyMock, return_value={ - "ErrataAdvisoryRPMsSignedHandler": {"image": {"advisory_name": "RHSA-.*"}}}) - def test_allow_build_false(self, handler_build_whitelist, record_images): - """ - Tests that allow_build filters out advisories based on advisory_name. - """ - event = ErrataAdvisoryRPMsSignedEvent( - "123", - ErrataAdvisory(123, "RHBA-2017", "REL_PREP", [], - security_impact="", - product_short_name="product")) - handler = ErrataAdvisoryRPMsSignedHandler() - handler.handle(event) - - record_images.assert_not_called() - - @patch("freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler." - "_find_images_to_rebuild", return_value=[]) - @patch("freshmaker.config.Config.handler_build_whitelist", - new_callable=PropertyMock, return_value={ - "ErrataAdvisoryRPMsSignedHandler": {"image": {"advisory_name": "RHSA-.*"}}}) - def test_allow_build_true(self, handler_build_whitelist, record_images): - """ - Tests that allow_build does not filter out advisories based on - advisory_name. - """ - event = ErrataAdvisoryRPMsSignedEvent( - "123", - ErrataAdvisory(123, "RHSA-2017", "REL_PREP", [], - security_impact="", - product_short_name="product")) - handler = ErrataAdvisoryRPMsSignedHandler() - handler.handle(event) - - record_images.assert_called_once() - self.assertEqual(handler.current_db_event_id, 1) - - @patch("freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler." - "_find_images_to_rebuild", return_value=[]) - @patch( - "freshmaker.config.Config.handler_build_whitelist", - new_callable=PropertyMock, - return_value={ - "ErrataAdvisoryRPMsSignedHandler": { - "image": { - "advisory_security_impact": [ - "Normal", "Important" - ], - "image_name": "foo", - } - } - }) - def test_allow_security_impact_important_true( - self, handler_build_whitelist, record_images): - """ - Tests that allow_build does not filter out advisories based on - advisory_security_impact. - """ - event = ErrataAdvisoryRPMsSignedEvent( - "123", - ErrataAdvisory(123, "RHSA-2017", "REL_PREP", [], - security_impact="Important", - product_short_name="product")) - handler = ErrataAdvisoryRPMsSignedHandler() - handler.handle(event) - - record_images.assert_called_once() - - @patch("freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler." - "_find_images_to_rebuild", return_value=[]) - @patch( - "freshmaker.config.Config.handler_build_whitelist", - new_callable=PropertyMock, - return_value={ - "ErrataAdvisoryRPMsSignedHandler": { - "image": { - "advisory_security_impact": [ - "Normal", "Important" - ] - } - } - }) - def test_allow_security_impact_important_false( - self, handler_build_whitelist, record_images): - """ - Tests that allow_build dost filter out advisories based on - advisory_security_impact. - """ - event = ErrataAdvisoryRPMsSignedEvent( - "123", - ErrataAdvisory(123, "RHSA-2017", "REL_PREP", [], - security_impact="None", - product_short_name="product")) - handler = ErrataAdvisoryRPMsSignedHandler() - handler.handle(event) - - record_images.assert_not_called() - - @patch( - "freshmaker.config.Config.handler_build_whitelist", - new_callable=PropertyMock, - return_value={ - "ErrataAdvisoryRPMsSignedHandler": { - "image": { - "image_name": ["foo", "bar"] - } - } - }) - def test_filter_out_not_allowed_builds( - self, handler_build_whitelist): - """ - Tests that allow_build does filter images based on image_name. - """ - - handler = ErrataAdvisoryRPMsSignedHandler() - handler.event = ErrataAdvisoryRPMsSignedEvent( - "123", - ErrataAdvisory(123, "RHSA-2017", "REL_PREP", [], - security_impact="None", - product_short_name="product")) - - image = {"brew": {"build": "foo-1-2.3"}} - ret = handler._filter_out_not_allowed_builds(image) - self.assertEqual(ret, False) - - image = {"brew": {"build": "foo2-1-2.3"}} - ret = handler._filter_out_not_allowed_builds(image) - self.assertEqual(ret, False) - - image = {"brew": {"build": "bar-1-2.3"}} - ret = handler._filter_out_not_allowed_builds(image) - self.assertEqual(ret, False) - - image = {"brew": {"build": "unknown-1-2.3"}} - ret = handler._filter_out_not_allowed_builds(image) - self.assertEqual(ret, True) - - @patch( - "freshmaker.config.Config.handler_build_whitelist", - new_callable=PropertyMock, - return_value={ - "ErrataAdvisoryRPMsSignedHandler": { - "image": { - "image_name": ["foo", "bar"], - "advisory_name": "RHSA-.*", - } - } - }) - def test_filter_out_image_name_and_advisory_name( - self, handler_build_whitelist): - """ - Tests that allow_build does filter images based on image_name. - """ - - handler = ErrataAdvisoryRPMsSignedHandler() - handler.event = ErrataAdvisoryRPMsSignedEvent( - "123", - ErrataAdvisory(123, "RHSA-2017", "REL_PREP", [], - security_impact="None", - product_short_name="product")) - - image = {"brew": {"build": "foo-1-2.3"}} - ret = handler._filter_out_not_allowed_builds(image) - self.assertEqual(ret, False) - - image = {"brew": {"build": "unknown-1-2.3"}} - ret = handler._filter_out_not_allowed_builds(image) - self.assertEqual(ret, True) - - @patch( - "freshmaker.config.Config.handler_build_whitelist", - new_callable=PropertyMock, - return_value={ - "ErrataAdvisoryRPMsSignedHandler": { - "image": { - "image_name": ["foo", "bar"] - } - } - }) - @patch( - "freshmaker.config.Config.handler_build_blacklist", - new_callable=PropertyMock, - return_value={ - "ErrataAdvisoryRPMsSignedHandler": { - "image": all_( - { - "image_name": "foo", - "image_version": "7.3", - } - ) - } - }) - def test_filter_out_not_allowed_builds_image_version( - self, handler_build_blacklist, handler_build_whitelist): - handler = ErrataAdvisoryRPMsSignedHandler() - handler.event = ErrataAdvisoryRPMsSignedEvent( - "123", - ErrataAdvisory(123, "RHSA-2017", "REL_PREP", [], - security_impact="None", - product_short_name="product")) - - image = {"brew": {"build": "foo-1-2.3"}} - ret = handler._filter_out_not_allowed_builds(image) - self.assertEqual(ret, False) - - image = {"brew": {"build": "foo-1-7.3"}} - ret = handler._filter_out_not_allowed_builds(image) - self.assertEqual(ret, False) - - image = {"brew": {"build": "foo-7.3-2.3"}} - ret = handler._filter_out_not_allowed_builds(image) - self.assertEqual(ret, True) - - image = {"brew": {"build": "unknown-1-2.3"}} - ret = handler._filter_out_not_allowed_builds(image) - self.assertEqual(ret, True) - - -class TestBatches(helpers.ModelsTestCase): - """Test handling of batches""" - - def setUp(self): - super(TestBatches, self).setUp() - self.patcher = helpers.Patcher( - 'freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.') - - def tearDown(self): - super(TestBatches, self).tearDown() - self.patcher.unpatch_all() - - def _mock_build(self, build, parent=None, error=None): - if parent: - parent = {"brew": {"build": parent + "-1-1.25"}} - return ContainerImage({ - 'brew': {'build': build + "-1-1.25"}, - 'repository': build + '_repo', - 'parsed_data': { - 'layers': [ - 'sha512:1234', - 'sha512:4567', - 'sha512:7890', - ], - }, - 'commit': build + '_123', - 'parent': parent, - "target": "t1", - 'git_branch': 'mybranch', - "error": error, - "content_sets": ["first-content-set"], - "generate_pulp_repos": True, - "arches": "x86_64", - "odcs_compose_ids": [10, 11], - "published": False, - }) - - @patch('freshmaker.odcsclient.create_odcs_client') - def test_batches_records(self, create_odcs_client): - """ - Tests that batches are properly recorded in DB. - """ - odcs = create_odcs_client.return_value - # There are 8 mock builds below and each of them requires one pulp - # compose. - composes = [{ - 'id': compose_id, - 'result_repofile': 'http://localhost/{}.repo'.format(compose_id), - 'state_name': 'done' - } for compose_id in range(1, 9)] - odcs.new_compose.side_effect = composes - odcs.get_compose.side_effect = composes - - # Creates following tree: - # shared_parent - # |- child1_parent3 - # |- child1_parent2 - # |- child1_parent1 - # |- child1 - # |- child2_parent2 - # |- child2_parent1 - # |- child2 - batches = [[self._mock_build("shared_parent")], - [self._mock_build("child1_parent3", "shared_parent"), - self._mock_build("child2_parent2", "shared_parent")], - [self._mock_build("child1_parent2", "child1_parent3"), - self._mock_build("child2_parent1", "child2_parent2")], - [self._mock_build("child1_parent1", "child1_parent2", error="Fail"), - self._mock_build("child2", "child2_parent1")], - [self._mock_build("child1", "child1_parent1")]] - - # Flat list of images from batches with brew build id as a key. - images = {} - for batch in batches: - for image in batch: - images[image['brew']['build']] = image - - # Record the batches. - event = events.BrewSignRPMEvent("123", "openssl-1.1.0-1") - handler = ErrataAdvisoryRPMsSignedHandler() - handler._record_batches(batches, event) - - # Check that the images have proper data in proper db columns. - e = db.session.query(Event).filter(Event.id == 1).one() - for build in e.builds: - # child1_parent1 and child1 are in FAILED states, because LB failed - # to resolve child1_parent1 and therefore also child1 cannot be - # build. - if build.name in ["child1_parent1", "child1"]: - self.assertEqual(build.state, ArtifactBuildState.FAILED.value) - else: - self.assertEqual(build.state, ArtifactBuildState.PLANNED.value) - self.assertEqual(build.type, ArtifactType.IMAGE.value) - - image = images[build.original_nvr] - if image['parent']: - self.assertEqual(build.dep_on.original_nvr, image['parent']['brew']['build']) - else: - self.assertEqual(build.dep_on, None) - - args = json.loads(build.build_args) - self.assertEqual(args["repository"], build.name + "_repo") - self.assertEqual(args["commit"], build.name + "_123") - self.assertEqual(args["parent"], - build.dep_on.rebuilt_nvr if build.dep_on else None) - self.assertEqual(args["renewed_odcs_compose_ids"], - [10, 11]) - - -class TestCheckImagesToRebuild(helpers.ModelsTestCase): - """Test handling of batches""" - - def setUp(self): - super(TestCheckImagesToRebuild, self).setUp() - - build_args = json.dumps({ - "parent": "nvr", - "repository": "repo", - "target": "target", - "commit": "hash", - "branch": "mybranch", - "yum_repourl": "http://localhost/composes/latest-odcs-3-1/compose/" - "Temporary/odcs-3.repo", - "odcs_pulp_compose_id": 15, - }) - - self.ev = Event.create(db.session, 'msg-id', '123', - EVENT_TYPES[ErrataAdvisoryRPMsSignedEvent]) - self.b1 = ArtifactBuild.create( - db.session, self.ev, "parent", "image", - state=ArtifactBuildState.PLANNED, - original_nvr="parent-1-25") - self.b1.build_args = build_args - self.b2 = ArtifactBuild.create( - db.session, self.ev, "child", "image", - state=ArtifactBuildState.PLANNED, - dep_on=self.b1, - original_nvr="child-1-25") - self.b2.build_args = build_args - db.session.commit() - - def test_check_images_to_rebuild(self): - builds = { - "parent-1-25": self.b1, - "child-1-25": self.b2 - } - - handler = ErrataAdvisoryRPMsSignedHandler() - handler.set_context(self.ev) - handler._check_images_to_rebuild(self.ev, builds) - - # Check that the images have proper data in proper db columns. - e = db.session.query(Event).filter(Event.id == 1).one() - for build in e.builds: - self.assertEqual(build.state, ArtifactBuildState.PLANNED.value) - - def test_check_images_to_rebuild_missing_dep(self): - # Do not include child nvr here to test that _check_images_to_rebuild - # sets the state of event to failed. - builds = { - "parent-1-25": self.b1 - } - - handler = ErrataAdvisoryRPMsSignedHandler() - handler.set_context(self.ev) - handler._check_images_to_rebuild(self.ev, builds) - - # Check that the images have proper data in proper db columns. - e = db.session.query(Event).filter(Event.id == 1).one() - for build in e.builds: - self.assertEqual(build.state, ArtifactBuildState.FAILED.value) - - def test_check_images_to_rebuild_extra_build(self): - builds = { - "parent-1-25": self.b1, - "child-1-25": self.b2, - "something-1-25": self.b1, - } - - handler = ErrataAdvisoryRPMsSignedHandler() - handler.set_context(self.ev) - handler._check_images_to_rebuild(self.ev, builds) - - # Check that the images have proper data in proper db columns. - e = db.session.query(Event).filter(Event.id == 1).one() - for build in e.builds: - self.assertEqual(build.state, ArtifactBuildState.FAILED.value) - - -class TestErrataAdvisoryStateChangedHandler(helpers.ModelsTestCase): - - @patch('freshmaker.errata.Errata.advisories_from_event') - def test_rebuild_if_not_exists(self, advisories_from_event): - handler = ErrataAdvisoryStateChangedHandler() - - for state in ["REL_PREP", "PUSH_READY", "IN_PUSH", "SHIPPED_LIVE"]: - advisories_from_event.return_value = [ - ErrataAdvisory(123, "RHSA-2017", state, ["rpm"], "Critical")] - ev = ErrataAdvisoryStateChangedEvent( - "msg123", ErrataAdvisory(123, "RHSA-2017", state, ['rpm'])) - ret = handler.handle(ev) - - self.assertEqual(len(ret), 1) - self.assertEqual(ret[0].advisory.errata_id, 123) - self.assertEqual(ret[0].advisory.security_impact, "Critical") - self.assertEqual(ret[0].advisory.name, "RHSA-2017") - - @patch('freshmaker.errata.Errata.advisories_from_event') - @patch.object(conf, 'handler_build_whitelist', new={ - 'ErrataAdvisoryStateChangedHandler': { - 'image': { - 'advisory_state': r'REL_PREP|SHIPPED_LIVE', - } - } - }) - def test_rebuild_if_not_exists_unknown_states( - self, advisories_from_event): - handler = ErrataAdvisoryStateChangedHandler() - - for state in ["NEW_FILES", "QE", "UNKNOWN"]: - advisories_from_event.return_value = [ - ErrataAdvisory(123, "RHSA-2017", state, ["rpm"], "Critical")] - ev = ErrataAdvisoryStateChangedEvent( - "msg123", ErrataAdvisory(123, 'RHSA-2017', state, ['rpm'])) - ret = handler.handle(ev) - - self.assertEqual(len(ret), 0) - - @patch('freshmaker.errata.Errata.advisories_from_event') - @patch.object(conf, 'handler_build_whitelist', new={ - 'ErrataAdvisoryStateChangedHandler': { - 'image': { - 'advisory_state': '.*', - } - } - }) - def test_rebuild_if_not_exists_already_exists( - self, advisories_from_event): - handler = ErrataAdvisoryStateChangedHandler() - - db_event = Event.create( - db.session, "msg124", "123", ErrataAdvisoryRPMsSignedEvent) - db.session.commit() - - for manual in [True, False]: - for db_event_state in [ - EventState.INITIALIZED, EventState.BUILDING, - EventState.COMPLETE, EventState.FAILED, - EventState.SKIPPED]: - db_event.state = db_event_state - db.session.commit() - for state in ["REL_PREP", "PUSH_READY", "IN_PUSH", "SHIPPED_LIVE"]: - advisories_from_event.return_value = [ - ErrataAdvisory(123, "RHSA-2017", state, ["rpm"], "Critical")] - ev = ErrataAdvisoryStateChangedEvent( - "msg123", ErrataAdvisory(123, 'RHSA-2017', state, ['rpm'])) - ev.manual = manual - ev.dry_run = manual # use also manual just for the sake of test. - ret = handler.handle(ev) - - if db_event_state == EventState.FAILED or ev.manual: - self.assertEqual(len(ret), 1) - self.assertEqual(ret[0].manual, manual) - self.assertEqual(ret[0].dry_run, manual) - else: - self.assertEqual(len(ret), 0) - - @patch('freshmaker.errata.Errata.advisories_from_event') - def test_rebuild_if_not_exists_unknown_errata_id( - self, advisories_from_event): - advisories_from_event.return_value = [] - handler = ErrataAdvisoryStateChangedHandler() - - for state in ["REL_PREP", "PUSH_READY", "IN_PUSH", "SHIPPED_LIVE"]: - ev = ErrataAdvisoryStateChangedEvent( - "msg123", ErrataAdvisory(123, 'RHSA-2017', state, ['rpm'])) - ret = handler.handle(ev) - - self.assertEqual(len(ret), 0) - - def test_passing_dry_run(self): - ev = ErrataAdvisoryStateChangedEvent( - "msg123", ErrataAdvisory(123, "name", "SHIPPED_LIVE", ["rpm"]), - dry_run=True) - self.assertEqual(ev.dry_run, True) - - ev = ErrataAdvisoryRPMsSignedEvent( - "123", - ErrataAdvisory(123, "RHBA-2017", "REL_PREP", [], - security_impact="", - product_short_name="product"), - dry_run=True) - self.assertEqual(ev.dry_run, True) - - def test_mark_as_released(self): - db_event = Event.create( - db.session, "msg124", "123", ErrataAdvisoryRPMsSignedEvent, False) - db.session.commit() - - self.assertEqual(db_event.released, False) - - ev = ErrataAdvisoryStateChangedEvent( - "msg123", ErrataAdvisory(123, "name", "SHIPPED_LIVE", ["rpm"])) - - handler = ErrataAdvisoryStateChangedHandler() - handler.handle(ev) - - db.session.refresh(db_event) - self.assertEqual(db_event.released, True) - - def test_mark_as_released_wrong_advisory_status(self): - db_event = Event.create( - db.session, "msg124", "123", ErrataAdvisoryRPMsSignedEvent, False) - db.session.commit() - - for state in ["NEW_FILES", "QE", "REL_PREP", "PUSH_READY", "IN_PUSH"]: - ev = ErrataAdvisoryStateChangedEvent( - "msg123", ErrataAdvisory(123, "name", state, ['rpm'])) - - handler = ErrataAdvisoryStateChangedHandler() - handler.handle(ev) - - db.session.refresh(db_event) - self.assertEqual(db_event.released, False) - - @patch('freshmaker.errata.Errata.advisories_from_event') - def test_mark_as_released_unknown_event(self, advisories_from_event): - ev = ErrataAdvisoryStateChangedEvent( - "msg123", ErrataAdvisory(123, "name", "SHIPPED_LIVE", ["rpm"])) - - handler = ErrataAdvisoryStateChangedHandler() - handler.handle(ev) - - @patch('freshmaker.handlers.errata.ErrataAdvisoryStateChangedHandler' - '.rebuild_if_not_exists') - @patch.object(conf, 'handler_build_whitelist', new={ - 'ErrataAdvisoryStateChangedHandler': { - 'image': { - 'advisory_state': r'REL_PREP', - } - } - }) - def test_not_rebuild_if_errata_state_is_not_allowed( - self, rebuild_if_not_exists): - rebuild_if_not_exists.return_value = [Mock(), Mock()] - - Event.create(db.session, "msg-id-123", "123456", - ErrataAdvisoryRPMsSignedEvent, False) - db.session.commit() - - event = ErrataAdvisoryStateChangedEvent( - 'msg-id-123', - ErrataAdvisory(123456, 'name', 'SHIPPED_LIVE', ['rpm'])) - handler = ErrataAdvisoryStateChangedHandler() - msgs = handler.handle(event) - - self.assertEqual([], msgs) - - @patch('freshmaker.handlers.errata.ErrataAdvisoryStateChangedHandler' - '.rebuild_if_not_exists') - @patch.object(conf, 'handler_build_whitelist', new={ - 'ErrataAdvisoryStateChangedHandler': { - 'image': { - 'advisory_state': r'SHIPPED_LIVE', - } - } - }) - def test_rebuild_if_errata_state_is_not_allowed_but_manual_is_true( - self, rebuild_if_not_exists): - rebuild_if_not_exists.return_value = [Mock()] - - Event.create(db.session, "msg-id-123", "123456", - ErrataAdvisoryRPMsSignedEvent, False) - db.session.commit() - - event = ErrataAdvisoryStateChangedEvent( - 'msg-id-123', - ErrataAdvisory(123456, "name", 'SHIPPED_LIVE', ['rpm'])) - event.manual = True - handler = ErrataAdvisoryStateChangedHandler() - msgs = handler.handle(event) - - self.assertEqual(len(msgs), 1) - - -class TestRecordBatchesImages(helpers.ModelsTestCase): - """Test ErrataAdvisoryRPMsSignedHandler._record_batches""" - - def setUp(self): - super(TestRecordBatchesImages, self).setUp() - - self.mock_event = Mock(msg_id='msg-id', search_key=12345) - - self.patcher = helpers.Patcher( - 'freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.') - - self.mock_prepare_pulp_repo = self.patcher.patch( - 'freshmaker.odcsclient.FreshmakerODCSClient.prepare_pulp_repo', - side_effect=[{'id': 1}, {'id': 2}]) - - self.patcher.patch_dict( - 'freshmaker.models.EVENT_TYPES', {self.mock_event.__class__: 0}) - - def tearDown(self): - super(TestRecordBatchesImages, self).tearDown() - self.patcher.unpatch_all() - - def test_record_batches(self): - batches = [ - [ContainerImage({ - "brew": { - "completion_date": "20170420T17:05:37.000-0400", - "build": "rhel-server-docker-7.3-82", - "package": "rhel-server-docker" - }, - 'parsed_data': { - 'layers': [ - 'sha512:12345678980', - 'sha512:10987654321' - ] - }, - "parent": None, - "content_sets": ["content-set-1"], - "repository": "repo-1", - "commit": "123456789", - "target": "target-candidate", - "git_branch": "rhel-7", - "error": None, - "generate_pulp_repos": True, - "arches": "x86_64", - "odcs_compose_ids": None, - "published": False, - })], - [ContainerImage({ - "brew": { - "build": "rh-dotnetcore10-docker-1.0-16", - "package": "rh-dotnetcore10-docker", - "completion_date": "20170511T10:06:09.000-0400" - }, - 'parsed_data': { - 'layers': [ - 'sha512:2345af2e293', - 'sha512:12345678980', - 'sha512:10987654321' - ] - }, - "parent": ContainerImage({ - "brew": { - "completion_date": "20170420T17:05:37.000-0400", - "build": "rhel-server-docker-7.3-82", - "package": "rhel-server-docker" - }, - 'parsed_data': { - 'layers': [ - 'sha512:12345678980', - 'sha512:10987654321' - ] - }, - "parent": None, - "content_sets": ["content-set-1"], - "repository": "repo-1", - "commit": "123456789", - "target": "target-candidate", - "git_branch": "rhel-7", - "error": None - }), - "content_sets": ["content-set-1"], - "repository": "repo-1", - "commit": "987654321", - "target": "target-candidate", - "git_branch": "rhel-7", - "error": None, - "generate_pulp_repos": True, - "arches": "x86_64", - "odcs_compose_ids": None, - "published": False, - })] - ] - - handler = ErrataAdvisoryRPMsSignedHandler() - handler._record_batches(batches, self.mock_event) - - # Check parent image - query = db.session.query(ArtifactBuild) - parent_image = query.filter( - ArtifactBuild.original_nvr == 'rhel-server-docker-7.3-82' - ).first() - self.assertNotEqual(None, parent_image) - self.assertEqual(ArtifactBuildState.PLANNED.value, parent_image.state) - - # Check child image - child_image = query.filter( - ArtifactBuild.original_nvr == 'rh-dotnetcore10-docker-1.0-16' - ).first() - self.assertNotEqual(None, child_image) - self.assertEqual(parent_image, child_image.dep_on) - self.assertEqual(ArtifactBuildState.PLANNED.value, child_image.state) - - def test_record_batches_should_not_generate_pulp_repos(self): - batches = [ - [ContainerImage({ - "brew": { - "completion_date": "20170420T17:05:37.000-0400", - "build": "rhel-server-docker-7.3-82", - "package": "rhel-server-docker" - }, - 'parsed_data': { - 'layers': [ - 'sha512:12345678980', - 'sha512:10987654321' - ] - }, - "parent": None, - "content_sets": ["content-set-1"], - "repository": "repo-1", - "commit": "123456789", - "target": "target-candidate", - "git_branch": "rhel-7", - "error": None, - "generate_pulp_repos": False, - "arches": "x86_64", - "odcs_compose_ids": None, - "published": False, - })] - ] - - handler = ErrataAdvisoryRPMsSignedHandler() - handler._record_batches(batches, self.mock_event) - - # Check parent image - query = db.session.query(ArtifactBuild) - parent_image = query.filter( - ArtifactBuild.original_nvr == 'rhel-server-docker-7.3-82' - ).first() - self.assertNotEqual(None, parent_image) - self.assertEqual(ArtifactBuildState.PLANNED.value, parent_image.state) - self.mock_prepare_pulp_repo.assert_not_called() - - def test_pulp_compose_generated_just_once(self): - batches = [ - [ContainerImage({ - "brew": { - "completion_date": "20170420T17:05:37.000-0400", - "build": "rhel-server-docker-7.3-82", - "package": "rhel-server-docker" - }, - 'parsed_data': { - 'layers': [ - 'sha512:12345678980', - 'sha512:10987654321' - ] - }, - "parent": None, - "content_sets": ["content-set-1"], - "repository": "repo-1", - "commit": "123456789", - "target": "target-candidate", - "git_branch": "rhel-7", - "error": None, - "arches": "x86_64", - "generate_pulp_repos": True, - "odcs_compose_ids": None, - "published": False, - })], - [ContainerImage({ - "brew": { - "build": "rh-dotnetcore10-docker-1.0-16", - "package": "rh-dotnetcore10-docker", - "completion_date": "20170511T10:06:09.000-0400" - }, - 'parsed_data': { - 'layers': [ - 'sha512:2345af2e293', - 'sha512:12345678980', - 'sha512:10987654321' - ] - }, - "parent": ContainerImage({ - "brew": { - "completion_date": "20170420T17:05:37.000-0400", - "build": "rhel-server-docker-7.3-82", - "package": "rhel-server-docker" - }, - 'parsed_data': { - 'layers': [ - 'sha512:12345678980', - 'sha512:10987654321' - ] - }, - "parent": None, - "content_sets": ["content-set-1"], - "repository": "repo-1", - "commit": "123456789", - "target": "target-candidate", - "git_branch": "rhel-7", - "error": None - }), - "content_sets": ["content-set-1"], - "repository": "repo-1", - "commit": "987654321", - "target": "target-candidate", - "git_branch": "rhel-7", - "error": None, - "arches": "x86_64", - "generate_pulp_repos": True, - "odcs_compose_ids": None, - "published": False, - })] - ] - - handler = ErrataAdvisoryRPMsSignedHandler() - handler._record_batches(batches, self.mock_event) - - query = db.session.query(ArtifactBuild) - parent_build = query.filter( - ArtifactBuild.original_nvr == 'rhel-server-docker-7.3-82' - ).first() - self.assertEqual(1, len(parent_build.composes)) - compose_ids = sorted([rel.compose.odcs_compose_id - for rel in parent_build.composes]) - self.assertEqual([1], compose_ids) - - child_build = query.filter( - ArtifactBuild.original_nvr == 'rh-dotnetcore10-docker-1.0-16' - ).first() - self.assertEqual(1, len(child_build.composes)) - - self.mock_prepare_pulp_repo.assert_has_calls([ - call(parent_build, ["content-set-1"]) - ]) - - def test_no_parent(self): - batches = [ - [ContainerImage({ - "brew": { - "completion_date": "20170420T17:05:37.000-0400", - "build": "rhel-server-docker-7.3-82", - "package": "rhel-server-docker" - }, - 'parsed_data': { - 'layers': [ - 'sha512:12345678980', - 'sha512:10987654321' - ] - }, - "content_sets": ["content-set-1"], - "repository": "repo-1", - "commit": "123456789", - "target": "target-candidate", - "git_branch": "rhel-7", - "error": "Some error occurs while getting this image.", - "arches": "x86_64", - "odcs_compose_ids": None, - "published": False, - })] - ] - - handler = ErrataAdvisoryRPMsSignedHandler() - handler._record_batches(batches, self.mock_event) - - query = db.session.query(ArtifactBuild) - build = query.filter( - ArtifactBuild.original_nvr == 'rhel-server-docker-7.3-82' - ).first() - - self.assertEqual(ArtifactBuildState.FAILED.value, build.state) - - def test_mark_failed_state_if_image_has_error(self): - batches = [ - [ContainerImage({ - "brew": { - "completion_date": "20170420T17:05:37.000-0400", - "build": "rhel-server-docker-7.3-82", - "package": "rhel-server-docker" - }, - 'parsed_data': { - 'layers': [ - 'sha512:12345678980', - 'sha512:10987654321' - ] - }, - "parent": None, - "content_sets": ["content-set-1"], - "repository": "repo-1", - "commit": "123456789", - "target": "target-candidate", - "git_branch": "rhel-7", - "error": "Some error occurs while getting this image.", - "arches": "x86_64", - "odcs_compose_ids": None, - "published": False, - })] - ] - - handler = ErrataAdvisoryRPMsSignedHandler() - handler._record_batches(batches, self.mock_event) - - query = db.session.query(ArtifactBuild) - build = query.filter( - ArtifactBuild.original_nvr == 'rhel-server-docker-7.3-82' - ).first() - - self.assertEqual(ArtifactBuildState.FAILED.value, build.state) - - def test_mark_state_failed_if_depended_image_is_failed(self): - batches = [ - [ContainerImage({ - "brew": { - "completion_date": "20170420T17:05:37.000-0400", - "build": "rhel-server-docker-7.3-82", - "package": "rhel-server-docker" - }, - 'parsed_data': { - 'layers': [ - 'sha512:12345678980', - 'sha512:10987654321' - ] - }, - "parent": None, - "content_sets": ["content-set-1"], - "repository": "repo-1", - "commit": "123456789", - "target": "target-candidate", - "git_branch": "rhel-7", - "error": "Some error occured.", - "arches": "x86_64", - "odcs_compose_ids": None, - "published": False, - })], - [ContainerImage({ - "brew": { - "build": "rh-dotnetcore10-docker-1.0-16", - "package": "rh-dotnetcore10-docker", - "completion_date": "20170511T10:06:09.000-0400" - }, - 'parsed_data': { - 'layers': [ - 'sha512:378a8ef2730', - 'sha512:12345678980', - 'sha512:10987654321' - ] - }, - "parent": ContainerImage({ - "brew": { - "completion_date": "20170420T17:05:37.000-0400", - "build": "rhel-server-docker-7.3-82", - "package": "rhel-server-docker" - }, - 'parsed_data': { - 'layers': [ - 'sha512:12345678980', - 'sha512:10987654321' - ] - }, - "parent": None, - "content_sets": ["content-set-1"], - "repository": "repo-1", - "commit": "123456789", - "target": "target-candidate", - "git_branch": "rhel-7", - "error": None - }), - "content_sets": ["content-set-1"], - "repository": "repo-1", - "commit": "987654321", - "target": "target-candidate", - "git_branch": "rhel-7", - "error": "Some error occured too.", - "arches": "x86_64", - "odcs_compose_ids": None, - "published": False, - })] - ] - - handler = ErrataAdvisoryRPMsSignedHandler() - handler._record_batches(batches, self.mock_event) - - query = db.session.query(ArtifactBuild) - build = query.filter( - ArtifactBuild.original_nvr == 'rhel-server-docker-7.3-82' - ).first() - self.assertEqual(ArtifactBuildState.FAILED.value, build.state) - - build = query.filter( - ArtifactBuild.original_nvr == 'rh-dotnetcore10-docker-1.0-16' - ).first() - self.assertEqual(ArtifactBuildState.FAILED.value, build.state) - - def test_mark_base_image_failed_if_fail_to_request_boot_iso_compose(self): - batches = [ - [ContainerImage({ - "brew": { - "completion_date": "20170420T17:05:37.000-0400", - "build": "rhel-server-docker-7.3-82", - "package": "rhel-server-docker" - }, - 'parsed_data': { - 'layers': [ - 'sha512:12345678980', - 'sha512:10987654321' - ] - }, - "parent": None, - "content_sets": ["content-set-1"], - "repository": "repo-1", - "commit": "123456789", - "target": "target-candidate", - "git_branch": "rhel-7", - "error": "Some error occured.", - "arches": "x86_64", - "odcs_compose_ids": None, - "published": False, - })], - ] - - handler = ErrataAdvisoryRPMsSignedHandler() - handler._record_batches(batches, self.mock_event) - - build = db.session.query(ArtifactBuild).filter_by( - original_nvr='rhel-server-docker-7.3-82').first() - self.assertEqual(ArtifactBuildState.FAILED.value, build.state) - - # Pulp repo should not be prepared for FAILED build. - self.mock_prepare_pulp_repo.assert_not_called() - - -class TestSkipNonRPMAdvisory(helpers.FreshmakerTestCase): - - def test_ensure_to_handle_rpm_adivsory(self): - event = ErrataAdvisoryStateChangedEvent( - 'msg-id-1', - ErrataAdvisory(123, 'name', 'REL_PREP', ['rpm', 'jar', 'pom'])) - handler = ErrataAdvisoryStateChangedHandler() - self.assertTrue(handler.can_handle(event)) - - def test_not_handle_non_rpm_advisory(self): - event = ErrataAdvisoryStateChangedEvent( - 'msg-id-1', ErrataAdvisory(123, 'name', 'REL_PREP', ['docker'])) - handler = ErrataAdvisoryStateChangedHandler() - self.assertFalse(handler.can_handle(event)) diff --git a/tests/test_git_dockerfile_change_handler.py b/tests/test_git_dockerfile_change_handler.py deleted file mode 100644 index a277a5a..0000000 --- a/tests/test_git_dockerfile_change_handler.py +++ /dev/null @@ -1,122 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# Written by Chenxiong Qi - -from mock import patch -from mock import PropertyMock - -import freshmaker - -from freshmaker import models -from freshmaker.types import ArtifactType -from freshmaker.config import any_ -from tests import get_fedmsg, helpers - - -class BaseTestCase(helpers.ModelsTestCase): - - @patch("requests.request") - @patch('freshmaker.consumer.get_global_consumer') - def consume_fedmsg(self, msg, global_consumer, request): - consumer = self.create_consumer() - global_consumer.return_value = consumer - consumer.consume(msg) - - -class GitDockerfileChangeHandlerTest(BaseTestCase): - - @patch('koji.read_config') - @patch('koji.ClientSession') - @patch("freshmaker.config.Config.krb_auth_principal", - new_callable=PropertyMock, return_value="user@example.com") - @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'GitDockerfileChangeHandler': { - 'image': any_({'name': 'testimage'}, {'branch': 'master'}) - } - }) - def test_rebuild_if_dockerfile_changed( - self, auth_principal, ClientSession, read_config): - read_config.return_value = { - 'server': 'https://localhost/kojihub', - 'krb_rdns': False, - 'weburl': 'https://localhost/koji', - } - - mock_session = ClientSession.return_value - mock_session.buildContainer.return_value = 123 - msg = get_fedmsg('git_receive_dockerfile_changed') - self.consume_fedmsg(msg) - - mock_session.krb_login.assert_called() - mock_session.buildContainer.assert_called_once_with( - 'git://pkgs.fedoraproject.org/container/testimage.git?#e1f39d43471fc37ec82616f76a119da4eddec787', - 'rawhide-container-candidate', - {'scratch': True, 'git_branch': 'master'}) - mock_session.logout.assert_called_once() - - events = models.Event.query.all() - self.assertEqual(len(events), 1) - self.assertEqual(events[0].message_id, msg['body']['msg_id']) - builds = models.ArtifactBuild.query.all() - self.assertEqual(len(builds), 1) - self.assertEqual(builds[0].name, 'testimage') - self.assertEqual(builds[0].type, ArtifactType.IMAGE.value) - self.assertEqual(builds[0].build_id, 123) - - @patch('freshmaker.handlers.git.dockerfile_change.GitDockerfileChangeHandler.build_container') - def test_not_rebuild_if_dockerfile_not_changed(self, build_container): - self.consume_fedmsg(get_fedmsg('git_receive_dockerfile_not_changed')) - build_container.assert_not_called() - - @patch('koji.read_config') - @patch('koji.ClientSession') - @patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'GitDockerfileChangeHandler': { - 'image': any_({'name': 'testimage'}, {'branch': 'master'}) - } - }) - def test_ensure_logout_in_whatever_case(self, ClientSession, read_config): - ClientSession.return_value.buildContainer.side_effect = RuntimeError - read_config.return_value = { - 'server': 'https://localhost/kojihub', - 'krb_rdns': False, - 'weburl': 'https://localhost/koji', - } - - self.consume_fedmsg(get_fedmsg('git_receive_dockerfile_changed')) - - ClientSession.return_value.logout.assert_called_once() - - @patch('koji.read_config') - @patch('koji.ClientSession') - @patch("freshmaker.config.Config.krb_auth_principal", - new_callable=PropertyMock, return_value="user@example.com") - def test_ensure_do_nothing_if_fail_to_login_koji(self, auth_principal, ClientSession, read_config): - ClientSession.return_value.krb_login.side_effect = RuntimeError - read_config.return_value = { - 'server': 'https://localhost/kojihub', - 'krb_rdns': False, - 'weburl': 'https://localhost/koji', - } - - self.consume_fedmsg(get_fedmsg('git_receive_dockerfile_changed')) - - ClientSession.return_value.buildContainer.assert_not_called() diff --git a/tests/test_git_module_metadata_change_handler.py b/tests/test_git_module_metadata_change_handler.py deleted file mode 100644 index ea1c7a9..0000000 --- a/tests/test_git_module_metadata_change_handler.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import os -import sys -import unittest -import mock - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) # noqa -from tests import helpers - -import freshmaker - -from freshmaker import events, models -from freshmaker.types import ArtifactType -from freshmaker.handlers.git import GitModuleMetadataChangeHandler -from freshmaker.parsers.git import GitReceiveParser -from freshmaker.config import any_ - - -class GitModuleMetadataChangeHandlerTest(helpers.ModelsTestCase): - def setUp(self): - super(GitModuleMetadataChangeHandlerTest, self).setUp() - events.BaseEvent.register_parser(GitReceiveParser) - - def test_can_handle_module_metadata_change_event(self): - """ - Tests handler can handle module metadata change message - """ - m = helpers.DistGitMessage('modules', 'testmodule', 'master', '123') - m.add_changed_file('testmodule.yaml', 1, 1) - msg = m.produce() - - event = self.get_event_from_msg(msg) - - handler = GitModuleMetadataChangeHandler() - self.assertTrue(handler.can_handle(event)) - - @mock.patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'GitModuleMetadataChangeHandler': { - 'module': any_({'name': 'testmodule'}, {'branch': 'master'}) - } - }) - def test_can_rebuild_module_when_module_metadata_changed(self): - """ - Tests handler can rebuild module when module metadata is changed in dist-git - """ - m = helpers.DistGitMessage('modules', 'testmodule', 'master', '12345') - m.add_changed_file('testmodule.yaml', 1, 1) - msg = m.produce() - - event = self.get_event_from_msg(msg) - - handler = GitModuleMetadataChangeHandler() - handler.build_module = mock.Mock() - handler.build_module.return_value = 123 - - self.assertTrue(handler.can_handle(event)) - handler.handle(event) - - self.assertEqual(handler.build_module.call_args_list, - [mock.call('testmodule', 'master', '12345')]) - - event_list = models.Event.query.all() - self.assertEqual(len(event_list), 1) - self.assertEqual(event_list[0].message_id, event.msg_id) - builds = models.ArtifactBuild.query.all() - self.assertEqual(len(builds), 1) - self.assertEqual(builds[0].name, 'testmodule') - self.assertEqual(builds[0].type, ArtifactType.MODULE.value) - self.assertEqual(builds[0].build_id, 123) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_git_rpm_spec_change_handler.py b/tests/test_git_rpm_spec_change_handler.py deleted file mode 100644 index 2d423d8..0000000 --- a/tests/test_git_rpm_spec_change_handler.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import os -import sys -import unittest -import mock - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) # noqa -from tests import helpers - -import freshmaker - -from freshmaker import events, models -from freshmaker.types import ArtifactType -from freshmaker.handlers.git import GitRPMSpecChangeHandler -from freshmaker.parsers.git import GitReceiveParser -from freshmaker.config import any_ - - -class GitRPMSpecChangeHandlerTest(helpers.ModelsTestCase): - def setUp(self): - super(GitRPMSpecChangeHandlerTest, self).setUp() - events.BaseEvent.register_parser(GitReceiveParser) - - def test_can_handle_dist_git_message_with_rpm_spec_changed(self): - """ - Tests handler can handle rpm spec change event - """ - m = helpers.DistGitMessage('rpms', 'bash', 'master', '123') - m.add_changed_file('bash.spec', 1, 1) - msg = m.produce() - - event = self.get_event_from_msg(msg) - - handler = GitRPMSpecChangeHandler() - self.assertTrue(handler.can_handle(event)) - - def test_can_not_handle_dist_git_message_without_rpm_spec_changed(self): - """ - Tests can not handle dist git message that spec file is not changed. - """ - - m = helpers.DistGitMessage('rpms', 'bash', 'master', '123') - m.add_changed_file('test.c', 1, 1) - msg = m.produce() - - event = self.get_event_from_msg(msg) - - handler = GitRPMSpecChangeHandler() - self.assertFalse(handler.can_handle(event)) - - @mock.patch('freshmaker.handlers.git.rpm_spec_change.PDC') - @mock.patch('freshmaker.handlers.git.rpm_spec_change.utils') - @mock.patch('freshmaker.handlers.git.rpm_spec_change.conf') - @mock.patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'GitRPMSpecChangeHandler': { - 'module': any_({'name': 'testmodule'}, {'branch': 'master'}) - } - }) - def test_can_rebuild_modules_has_rpm_included(self, conf, utils, PDC): - """ - Test handler can rebuild modules which include the rpm. - """ - conf.git_base_url = "git://pkgs.fedoraproject.org" - - m = helpers.DistGitMessage('rpms', 'bash', 'master', '123') - m.add_changed_file('bash.spec', 1, 1) - msg = m.produce() - - event = self.get_event_from_msg(msg) - - mod_info = helpers.PDCModuleInfo('testmodule', 'master', '20170412010101') - mod_info.add_rpm("bash-1.2.3-4.f26.rpm") - mod = mod_info.produce() - pdc = PDC.return_value - pdc.get_latest_modules.return_value = [mod] - - commitid = '9287eb8eb4c4c60f73b4a59f228a673846d940c6' - utils.bump_distgit_repo.return_value = commitid - - handler = GitRPMSpecChangeHandler() - handler.build_module = mock.Mock() - handler.build_module.return_value = 123 - - self.assertTrue(handler.can_handle(event)) - handler.handle(event) - - handler.build_module.assert_called_with('testmodule', 'master', commitid) - - event_list = models.Event.query.all() - self.assertEqual(len(event_list), 1) - self.assertEqual(event_list[0].message_id, event.msg_id) - builds = models.ArtifactBuild.query.all() - self.assertEqual(len(builds), 1) - self.assertEqual(builds[0].name, 'testmodule') - self.assertEqual(builds[0].type, ArtifactType.MODULE.value) - self.assertEqual(builds[0].build_id, 123) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_koji_task_state_change_handler.py b/tests/test_koji_task_state_change_handler.py deleted file mode 100644 index 5ea8cd4..0000000 --- a/tests/test_koji_task_state_change_handler.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import os -import sys -import unittest - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) # noqa -from tests import helpers - -from freshmaker import events, db, models -from freshmaker.types import ArtifactType, ArtifactBuildState -from freshmaker.handlers.koji import KojiTaskStateChangeHandler -from freshmaker.parsers.koji import KojiTaskStateChangeParser - - -class KojiTaskStateChangeHandlerTest(helpers.ModelsTestCase): - def setUp(self): - super(KojiTaskStateChangeHandlerTest, self).setUp() - events.BaseEvent.register_parser(KojiTaskStateChangeParser) - - def test_can_handle_koji_task_state_change_message(self): - """ - Tests buildsys handler can handle koji task state changed message - """ - m = helpers.KojiTaskStateChangeMessage(123, 'OPEN', 'FAILED') - msg = m.produce() - event = self.get_event_from_msg(msg) - handler = KojiTaskStateChangeHandler() - self.assertTrue(handler.can_handle(event)) - - def test_update_build_state_on_koji_task_state_change_event(self): - """ - Tests build state will be updated when receives koji task state changed message - """ - task_id = 123 - ev = models.Event.create(db.session, 'test_msg_id', "event-name", - events.KojiTaskStateChangeEvent) - build = models.ArtifactBuild.create(db.session, - ev, - 'testimage', - ArtifactType.IMAGE.value, - task_id) - db.session.add(ev) - db.session.add(build) - db.session.commit() - - m = helpers.KojiTaskStateChangeMessage(task_id, 'OPEN', 'FAILED') - msg = m.produce() - event = self.get_event_from_msg(msg) - - handler = KojiTaskStateChangeHandler() - - self.assertTrue(handler.can_handle(event)) - handler.handle(event) - - build = models.ArtifactBuild.query.all()[0] - self.assertEqual(build.state, ArtifactBuildState.FAILED.value) - - m = helpers.KojiTaskStateChangeMessage(task_id, 'OPEN', 'CLOSED') - msg = m.produce() - event = self.get_event_from_msg(msg) - - self.assertTrue(handler.can_handle(event)) - handler.handle(event) - - build = models.ArtifactBuild.query.all()[0] - self.assertEqual(build.state, ArtifactBuildState.DONE.value) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_mbs_module_state_change_handler.py b/tests/test_mbs_module_state_change_handler.py deleted file mode 100644 index ccdde12..0000000 --- a/tests/test_mbs_module_state_change_handler.py +++ /dev/null @@ -1,237 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import os -import sys -import unittest -import mock - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) # noqa -from tests import helpers - -import freshmaker - -from freshmaker import events, db, models -from freshmaker.types import ArtifactType -from freshmaker.handlers.mbs import MBSModuleStateChangeHandler -from freshmaker.parsers.mbs import MBSModuleStateChangeParser -from freshmaker.config import any_ - - -class MBSModuleStateChangeHandlerTest(helpers.ModelsTestCase): - def setUp(self): - super(MBSModuleStateChangeHandlerTest, self).setUp() - events.BaseEvent.register_parser(MBSModuleStateChangeParser) - - def test_can_handle_module_state_change_event(self): - """ - Tests MBS handler can handle module built message - """ - for state in ['init', 'wait', 'build', 'done', 'failed', 'ready']: - msg = helpers.ModuleStateChangeMessage('testmodule', 'master', state=state).produce() - event = self.get_event_from_msg(msg) - - handler = MBSModuleStateChangeHandler() - self.assertTrue(handler.can_handle(event)) - - @mock.patch('freshmaker.handlers.mbs.module_state_change.PDC') - @mock.patch('freshmaker.handlers.mbs.module_state_change.utils') - @mock.patch('freshmaker.handlers.mbs.module_state_change.conf') - @mock.patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'MBSModuleStateChangeHandler': { - 'module': any_({'name': r'testmodule\d*'}, {'branch': 'master'}), - } - }) - def test_can_rebuild_depending_modules(self, conf, utils, PDC): - """ - Tests handler can rebuild all modules which depend on the module - in module state change event. - """ - msg = helpers.ModuleStateChangeMessage('testmodule', 'master', state='ready').produce() - event = self.get_event_from_msg(msg) - - mod2_r1_info = helpers.PDCModuleInfo('testmodule2', 'master', '20170412010101') - mod2_r1_info.add_build_dep('testmodule', 'master') - mod2_r1 = mod2_r1_info.produce() - - mod3_r1_info = helpers.PDCModuleInfo('testmodule3', 'master', '20170412010201') - mod3_r1_info.add_build_dep('testmodule', 'master') - mod3_r1 = mod3_r1_info.produce() - - pdc = PDC.return_value - pdc.get_latest_modules.return_value = [mod2_r1, mod3_r1] - - conf.git_base_url = "git://pkgs.fedoraproject.org" - utils.bump_distgit_repo.side_effect = [ - "fae7848fa47a854f25b782aa64441040a6d86544", - "43ec03000d249231bc7135b11b810afc96e90efb", - ] - - handler = MBSModuleStateChangeHandler() - handler.build_module = mock.Mock() - handler.build_module.side_effect = [123, 456] - - self.assertTrue(handler.can_handle(event)) - handler.handle(event) - - self.assertEqual(handler.build_module.call_args_list, - [mock.call('testmodule2', 'master', 'fae7848fa47a854f25b782aa64441040a6d86544'), - mock.call('testmodule3', 'master', '43ec03000d249231bc7135b11b810afc96e90efb')]) - - event_list = models.Event.query.all() - self.assertEqual(len(event_list), 1) - self.assertEqual(event_list[0].message_id, event.msg_id) - builds = models.ArtifactBuild.query.all() - self.assertEqual(len(builds), 2) - self.assertEqual(builds[0].name, mod2_r1['name']) - self.assertEqual(builds[0].type, ArtifactType.MODULE.value) - self.assertEqual(builds[0].build_id, 123) - self.assertEqual(builds[1].name, mod3_r1['name']) - self.assertEqual(builds[1].build_id, 456) - self.assertEqual(builds[1].type, ArtifactType.MODULE.value) - - @mock.patch('freshmaker.handlers.mbs.module_state_change.PDC') - @mock.patch('freshmaker.handlers.mbs.module_state_change.utils') - @mock.patch('freshmaker.handlers.conf') - def test_module_is_not_allowed_in_whitelist(self, conf, utils, PDC): - conf.handler_build_whitelist = { - "MBSModuleStateChangeHandler": { - "module": any_( - { - 'name': 'base.*', - }, - ), - }, - } - - msg = helpers.ModuleStateChangeMessage('testmodule', 'master', state='ready').produce() - event = self.get_event_from_msg(msg) - - mod2_info = helpers.PDCModuleInfo('testmodule2', 'master', '20170412010101') - mod2_info.add_build_dep('testmodule', 'master') - mod2 = mod2_info.produce() - - pdc = PDC.return_value - pdc.get_latest_modules.return_value = [mod2] - - handler = MBSModuleStateChangeHandler() - handler.build_module = mock.Mock() - handler.record_build = mock.Mock() - - self.assertTrue(handler.can_handle(event)) - handler.handle(event) - - handler.build_module.assert_not_called() - - @mock.patch('freshmaker.handlers.mbs.module_state_change.PDC') - @mock.patch('freshmaker.handlers.mbs.module_state_change.utils') - @mock.patch('freshmaker.handlers.mbs.module_state_change.log') - @mock.patch.object(freshmaker.conf, 'handler_build_whitelist', new={ - 'MBSModuleStateChangeHandler': { - 'module': any_({'name': r'module\d+'}, {'branch': 'master'}) - } - }) - def test_handler_not_fall_into_cyclic_rebuild_loop(self, log, utils, PDC): - """ - Tests handler will not fall into cyclic rebuild loop when there is - build dep loop of modules. - """ - # in this case, we have: - # 1. module2 depends on module1 - # 2. module3 depends on module2 - # 3. module1 depends on module3 - # - # when we receives a modult built event of module1, the expect result is: - # 1. module2 get rebuild because module1 is built - # 2. module3 get rebuild because module2 is built - # 3. module1 get rebuild because module3 is built - # 4. stop here - - utils.bump_distgit_repo.return_value = 'abcd' - - mod1_info = helpers.PDCModuleInfo('module1', 'master', '20170412010101') - mod1_info.add_build_dep('module3', 'master') - mod1 = mod1_info.produce() - - mod2_info = helpers.PDCModuleInfo('module2', 'master', '20170412010102') - mod2_info.add_build_dep('module1', 'master') - mod2 = mod2_info.produce() - - mod3_info = helpers.PDCModuleInfo('module3', 'master', '20170412010103') - mod3_info.add_build_dep('module2', 'master') - mod3 = mod3_info.produce() - - pdc = PDC.return_value - handler = MBSModuleStateChangeHandler() - - # Assume we have build of module1 recorded in DB already, it doesn't has - # any dep_on as it was initial triggered by an event which is not - # associated with any build in our DB. - event = models.Event.create(db.session, "initial_msg_id", "test", events.TestingEvent) - models.ArtifactBuild.create(db.session, event, "module1", "module", '123') - db.session.commit() - - # we received module built event of module1 - msg = helpers.ModuleStateChangeMessage('module1', 'master', state='ready', build_id=123).produce() - event = self.get_event_from_msg(msg) - pdc.get_latest_modules.return_value = [mod2] - handler.build_module = mock.Mock() - handler.build_module.return_value = 124 - - # this will trigger module rebuild of module2 - handler.handle(event) - handler.build_module.assert_called_once_with('module2', 'master', 'abcd') - - # we received module built event of module2 - msg = helpers.ModuleStateChangeMessage('module2', 'master', state='ready', build_id=124).produce() - event = self.get_event_from_msg(msg) - pdc.get_latest_modules.return_value = [mod3] - handler.build_module = mock.Mock() - handler.build_module.return_value = 125 - - # this will trigger module rebuild of module3 - handler.handle(event) - handler.build_module.assert_called_once_with('module3', 'master', 'abcd') - - # we received module built event of module3 - msg = helpers.ModuleStateChangeMessage('module3', 'master', state='ready', build_id=125).produce() - event = self.get_event_from_msg(msg) - pdc.get_latest_modules.return_value = [mod1] - handler.build_module = mock.Mock() - handler.build_module.return_value = 126 - - # this will trigger module rebuild of module1 - handler.handle(event) - handler.build_module.assert_called_once_with('module1', 'master', 'abcd') - - # we received module built event of module1 - msg = helpers.ModuleStateChangeMessage('module1', 'master', state='ready', build_id=126).produce() - event = self.get_event_from_msg(msg) - pdc.get_latest_modules.return_value = [mod2] - handler.build_module = mock.Mock() - - # but this time we should not rebuild module2 - handler.handle(event) - handler.build_module.assert_not_called() - log.info.assert_has_calls([mock.call('Skipping the rebuild triggered by %s:%s as it willresult in cyclic build loop.', 'module1', u'master')]) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_monitor.py b/tests/test_monitor.py index 1ef7b35..21c0e39 100644 --- a/tests/test_monitor.py +++ b/tests/test_monitor.py @@ -87,7 +87,7 @@ class ConsumerTest(helpers.ModelsTestCase): return int(float(v)) return None - @mock.patch("freshmaker.handlers.mbs.module_state_change.MBSModuleStateChangeHandler.handle") + @mock.patch("freshmaker.handlers.internal.UpdateDBOnModuleBuild.handle") @mock.patch("freshmaker.consumer.get_global_consumer") def test_consumer_processing_message(self, global_consumer, handle): """ diff --git a/tests/test_odcs_compose_state_change.py b/tests/test_odcs_compose_state_change.py deleted file mode 100644 index b5527e7..0000000 --- a/tests/test_odcs_compose_state_change.py +++ /dev/null @@ -1,121 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# Written by Chenxiong Qi - -import six - -from mock import patch, PropertyMock - -from freshmaker import db -from freshmaker.models import ( - Event, EventState, EVENT_TYPES, - ArtifactBuild, ArtifactType, ArtifactBuildState, ArtifactBuildCompose, - Compose -) -from freshmaker.events import ErrataAdvisoryRPMsSignedEvent -from freshmaker.handlers.odcs import ComposeStateChangeHandler -from freshmaker.events import ODCSComposeStateChangeEvent -from tests import helpers - - -class TestComposeStateChangeHandler(helpers.ModelsTestCase): - """Test ODCSComposeStateChangeHandler""" - - def setUp(self): - super(TestComposeStateChangeHandler, self).setUp() - - # Test data - # (Inner build depends on outer build) - # Event (ErrataAdvisoryRPMsSignedEvent): - # build 1: [compose 1, pulp compose 1] - # build 2: [compose 1, pulp compose 2] - # build 3: [compose 1, pulp compose 3] - # build 4: [compose 1, pulp compose 4] - # build 5: [compose 1, pulp compose 5] - # build 6 (not planned): [compose 1, pulp compose 6] - - self.db_event = Event.create( - db.session, 'msg-1', 'search-key-1', - EVENT_TYPES[ErrataAdvisoryRPMsSignedEvent], - state=EventState.INITIALIZED, - released=False) - - self.build_1 = ArtifactBuild.create( - db.session, self.db_event, 'build-1', ArtifactType.IMAGE, - state=ArtifactBuildState.PLANNED) - self.build_2 = ArtifactBuild.create( - db.session, self.db_event, 'build-2', ArtifactType.IMAGE, - dep_on=self.build_1, - state=ArtifactBuildState.PLANNED) - - self.build_3 = ArtifactBuild.create( - db.session, self.db_event, 'build-3', ArtifactType.IMAGE, - state=ArtifactBuildState.PLANNED) - self.build_4 = ArtifactBuild.create( - db.session, self.db_event, 'build-4', ArtifactType.IMAGE, - dep_on=self.build_3, - state=ArtifactBuildState.PLANNED) - self.build_5 = ArtifactBuild.create( - db.session, self.db_event, 'build-5', ArtifactType.IMAGE, - dep_on=self.build_3, - state=ArtifactBuildState.PLANNED) - - self.build_6 = ArtifactBuild.create( - db.session, self.db_event, 'build-6', ArtifactType.IMAGE, - state=ArtifactBuildState.BUILD) - - self.compose_1 = Compose(odcs_compose_id=1) - db.session.add(self.compose_1) - db.session.commit() - - builds = [self.build_1, self.build_2, self.build_3, - self.build_4, self.build_5, self.build_6] - composes = [self.compose_1] * 6 - for build, compose in six.moves.zip(builds, composes): - db.session.add(ArtifactBuildCompose( - build_id=build.id, compose_id=compose.id)) - db.session.commit() - - def test_cannot_handle_if_compose_is_not_done(self): - event = ODCSComposeStateChangeEvent( - 'msg-id', {'id': 1, 'state': 'generating'} - ) - handler = ComposeStateChangeHandler() - can_handle = handler.can_handle(event) - self.assertFalse(can_handle) - - @patch('freshmaker.models.ArtifactBuild.composes_ready', - new_callable=PropertyMock) - @patch('freshmaker.handlers.ContainerBuildHandler.start_to_build_images') - def test_start_to_build(self, start_to_build_images, composes_ready): - composes_ready.return_value = True - - event = ODCSComposeStateChangeEvent( - 'msg-id', {'id': self.compose_1.id, 'state': 'done'} - ) - - handler = ComposeStateChangeHandler() - handler.handle(event) - - args, kwargs = start_to_build_images.call_args - passed_builds = sorted(args[0], key=lambda build: build.id) - self.assertEqual([self.build_1, self.build_3], passed_builds) diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..eaa0fb2 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,57 @@ +# Copyright (c) 2016 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Jan Kaluza + +from freshmaker.utils import sorted_by_nvr +from tests import helpers + + +class TestSortedByNVR(helpers.FreshmakerTestCase): + + def test_simple_list(self): + lst = ["foo-1-10", "foo-1-2", "foo-1-1"] + expected = ["foo-1-1", "foo-1-2", "foo-1-10"] + ret = sorted_by_nvr(lst) + self.assertEqual(ret, expected) + + def test_simple_list_reverse(self): + lst = ["foo-1-1", "foo-1-2", "foo-1-10"] + expected = ["foo-1-10", "foo-1-2", "foo-1-1"] + ret = sorted_by_nvr(lst, reverse=True) + self.assertEqual(ret, expected) + + def test_get_nvr(self): + lst = [{"nvr": "foo-1-10"}, {"nvr": "foo-1-2"}, {"nvr": "foo-1-1"}] + expected = [{"nvr": "foo-1-1"}, {"nvr": "foo-1-2"}, {"nvr": "foo-1-10"}] + ret = sorted_by_nvr(lst, lambda x: x["nvr"]) + self.assertEqual(ret, expected) + + def test_names_not_equal(self): + lst = ["foo-1-10", "bar-1-2", "foo-1-1"] + expected = ["bar-1-2", "foo-1-1", "foo-1-10"] + ret = sorted_by_nvr(lst) + self.assertEqual(ret, expected) + + def test_names_not_equal_reverse(self): + lst = ["foo-1-10", "bar-1-2", "foo-1-1"] + expected = ["bar-1-2", "foo-1-1", "foo-1-10"] + ret = sorted_by_nvr(lst, reverse=True) + self.assertEqual(ret, list(reversed(expected)))