From 84412ccb6239f097f898d61c0472bcc072a778ab Mon Sep 17 00:00:00 2001 From: Chenxiong Qi Date: Sep 13 2017 13:07:15 +0000 Subject: Start image rebuild when compose is done Event parser and handler are created for handling ODCS state change event. Waiting for and handling DONE event, that is compose for rebuilding docker images is generated. Original looping, that is used for blocking and waiting for generating compose, is removed. In the meanwhile, repository file, that can be passed to OSBS and used to build images, is returned as part of a new compose created in ODCS. So, URL pointing to that repository is stored in artifact build's build_args for saving time to retrieve compose again when start to rebuild. Signed-off-by: Chenxiong Qi --- diff --git a/freshmaker/events.py b/freshmaker/events.py index 6b74b60..ffcd100 100644 --- a/freshmaker/events.py +++ b/freshmaker/events.py @@ -304,3 +304,11 @@ class BrewContainerTaskStateChangeEvent(BaseEvent): @property def search_key(self): return str(self.task_id) + + +class ODCSComposeStateChangeEvent(BaseEvent): + """Represent a compose' state change event from ODCS""" + + def __init__(self, msg_id, compose): + self.msg_id = msg_id + self.compose = compose diff --git a/freshmaker/handlers/__init__.py b/freshmaker/handlers/__init__.py index d7fc469..82df05d 100644 --- a/freshmaker/handlers/__init__.py +++ b/freshmaker/handlers/__init__.py @@ -22,11 +22,15 @@ # Written by Jan Kaluza import abc +import json import re +import time from freshmaker import conf, log, db, models -from freshmaker.mbs import MBS from freshmaker.kojiservice import koji_service +from freshmaker.mbs import MBS +from freshmaker.models import ArtifactBuildState +from freshmaker.models import Event from krbcontext import krbContext @@ -83,42 +87,6 @@ class BaseHandler(object): mbs = MBS(conf) return mbs.build_module(name, branch, rev) - def build_container(self, scm_url, branch, target, - repo_urls=None, isolated=False, - release=None, koji_parent_build=None): - """ - Build a container in Koji. - - :param str name: container name. - :param str branch: container branch. - :param str rev: revision. - :param str namespace: namespace of container in dist-git. By default, - it is container. - :return: task id returned from Koji buildContainer API. - :rtype: int - """ - with koji_service(profile=conf.koji_profile, logger=log) as service: - log.debug('Logging into %s with Kerberos authentication.', service.server) - proxyuser = conf.koji_build_owner if conf.koji_proxyuser else None - - with self.krb_context: - service.krb_login(proxyuser=proxyuser) - - if not service.logged_in: - log.error('Could not login server %s', service.server) - return None - - log.debug('Building container from source: %s', scm_url) - - return service.build_container(scm_url, - branch, - target, - repo_urls=repo_urls, - isolated=isolated, - release=release, - koji_parent_build=koji_parent_build, - scratch=conf.koji_container_scratch_build) - def record_build(self, event, name, artifact_type, build_id=None, dep_on=None, state=None): """ @@ -200,3 +168,92 @@ class BaseHandler(object): handler_name, artifact_type.name.lower(), str(exc)) return True return in_whitelist and not in_blacklist + + +class ContainerBuildHandler(BaseHandler): + """Handler for building containers""" + + def build_container(self, scm_url, branch, target, + repo_urls=None, isolated=False, + release=None, koji_parent_build=None): + """ + Build a container in Koji. + + :param str name: container name. + :param str branch: container branch. + :param str rev: revision. + :param str namespace: namespace of container in dist-git. By default, + it is container. + :return: task id returned from Koji buildContainer API. + :rtype: int + """ + with koji_service(profile=conf.koji_profile, logger=log) as service: + log.debug('Logging into %s with Kerberos authentication.', + service.server) + + proxyuser = conf.koji_build_owner if conf.koji_proxyuser else None + + with self.krb_context: + service.krb_login(proxyuser=proxyuser) + + if not service.logged_in: + log.error('Could not login server %s', service.server) + return None + + log.debug('Building container from source: %s', scm_url) + + return service.build_container(scm_url, + branch, + target, + repo_urls=repo_urls, + isolated=isolated, + release=release, + koji_parent_build=koji_parent_build, + scratch=conf.koji_container_scratch_build) + + def _build_first_batch(self, db_event): + """ + Rebuilds all the parents images - images in the first batch which don't + depend on other images. + """ + + rebuild_event = Event.get(db.session, db_event.message_id) + # TODO: Add other repofiles from "extra events" + + for build in rebuild_event.builds: + if build.dep_on: + continue + + if build.state != ArtifactBuildState.PLANNED.value: + log.error("Trying to build first batch of container images, " + "but build %r is not in PLANNED state", build) + continue + + if not build.build_args: + log.error("Cannot rebuild container image %r, build_args not " + "defined", build) + continue + + args = json.loads(build.build_args) + + if not args["parent"]: + log.error("Base image %r should be rebuild, but this is not " + "supported yet", build) + continue + + parent = args["parent"] + scm_url = "%s/%s#%s" % (conf.git_base_url, args["repository"], + args["commit"]) + release = build.name.split("-")[-1] + "." + str(int(time.time())) + # According to Luiz from OSBS team, it is OK to use "unknown" if + # we don't know the branch name. TODO: Get the branch name from + # Koji in lightblue.py. + branch = "unknown" + target = args["target"] + + build.build_id = self.build_container( + scm_url, branch, target, repo_urls=[args['yum_repourl']], + isolated=True, release=release, koji_parent_build=parent) + build.state = ArtifactBuildState.BUILD.value + db.session.add(build) + db.session.commit() diff --git a/freshmaker/handlers/errata/errata_advisory_rpms_signed.py b/freshmaker/handlers/errata/errata_advisory_rpms_signed.py index f763e58..e0cf0cd 100644 --- a/freshmaker/handlers/errata/errata_advisory_rpms_signed.py +++ b/freshmaker/handlers/errata/errata_advisory_rpms_signed.py @@ -24,7 +24,6 @@ import json import koji -import time from freshmaker import conf from freshmaker import log @@ -126,57 +125,6 @@ class ErrataAdvisoryRPMsSignedHandler(BaseHandler): return [] - def _build_first_batch(self, db_event): - """ - Rebuilds all the parents images - images in the first batch which don't - depend on other images. - """ - - rebuild_event = Event.get(db.session, db_event.message_id) - odcs = ODCS(conf.odcs_server_url, auth_mech=AuthMech.Kerberos, - verify_ssl=conf.odcs_verify_ssl) - compose = odcs.get_compose(rebuild_event.compose_id) - # TODO: Add other repofiles from "extra events" - repo_urls = [compose["result_repofile"]] - - for build in rebuild_event.builds: - if build.dep_on: - continue - - if build.state != ArtifactBuildState.PLANNED.value: - log.error("Trying to build first batch of container images, " - "but build %r is not in PLANNED state", build) - continue - - if not build.build_args: - log.error("Cannot rebuild container image %r, build_args not " - "defined", build) - continue - - args = json.loads(build.build_args) - - if not args["parent"]: - log.error("Base image %r should be rebuild, but this is not " - "supported yet", build) - continue - - parent = args["parent"] - scm_url = "%s/%s#%s" % (conf.git_base_url, args["repository"], - args["commit"]) - release = build.name.split("-")[-1] + "." + str(int(time.time())) - # According to Luiz from OSBS team, it is OK to use "unknown" if - # we don't know the branch name. TODO: Get the branch name from - # Koji in lightblue.py. - branch = "unknown" - target = args["target"] - - build.build_id = self.build_container( - scm_url, branch, target, repo_urls=repo_urls, isolated=True, - release=release, koji_parent_build=parent) - build.state = ArtifactBuildState.BUILD.value - db.session.add(build) - db.session.commit() - def _prepare_yum_repo(self, db_event): """ Prepare a yum repo for rebuild @@ -217,37 +165,19 @@ class ErrataAdvisoryRPMsSignedHandler(BaseHandler): 'tag', packages=packages) compose_id = new_compose['id'] + yum_repourl = new_compose['result_repofile'] rebuild_event = Event.get(db.session, db_event.msg_id) rebuild_event.compose_id = compose_id + # Save YUM repo URL for saving time to retrieve compose from ODCS again + # when start to rebuild images. + for build in rebuild_event.builds: + data = json.loads(build.build_args) + data['yum_repourl'] = yum_repourl + build.build_args = json.dumps(data) db.session.commit() - log.info('Waiting for ODCS to finish the compose: %d', compose_id) - - while True: - time.sleep(1) - - new_compose = odcs.get_compose(compose_id) - state = new_compose['state'] - if state == 0: # waiting for generating compose - log.info('Waiting for generating new compose') - elif state == 1: # generating in progress - log.info('ODCS is generating the compose') - elif state == 4: # Failed to generate compose - log.error('ODCS fails to generate compose: %d', compose_id) - log.error('Please consult ODCS to see what is wrong with it') - return - elif state == 2: # Succeed to generate compose - log.info('ODCS has finished to generate compose. Continue to rebuild') - break - else: - log.error('Got unexpected compose state {0} from ODCS.'.format(state)) - return - - log.info('Repo URL containing packages used to rebuild container: %s', - new_compose['result_repo']) - - return new_compose['result_repo'] + return yum_repourl def _get_packages_for_compose(self, nvr): """Get RPMs of current build NVR diff --git a/freshmaker/handlers/git/dockerfile_change.py b/freshmaker/handlers/git/dockerfile_change.py index 7495353..f3a4d4e 100644 --- a/freshmaker/handlers/git/dockerfile_change.py +++ b/freshmaker/handlers/git/dockerfile_change.py @@ -23,11 +23,11 @@ from freshmaker import log, conf from freshmaker.types import ArtifactType -from freshmaker.handlers import BaseHandler +from freshmaker.handlers import ContainerBuildHandler from freshmaker.events import GitDockerfileChangeEvent -class GitDockerfileChangeHandler(BaseHandler): +class GitDockerfileChangeHandler(ContainerBuildHandler): name = 'GitDockerfileChangeHandler' def can_handle(self, event): diff --git a/freshmaker/handlers/odcs/__init__.py b/freshmaker/handlers/odcs/__init__.py new file mode 100644 index 0000000..86b1b04 --- /dev/null +++ b/freshmaker/handlers/odcs/__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. + +from .compose_state_change import ComposeStateChangeHandler # noqa \ No newline at end of file diff --git a/freshmaker/handlers/odcs/compose_state_change.py b/freshmaker/handlers/odcs/compose_state_change.py new file mode 100644 index 0000000..a8e083e --- /dev/null +++ b/freshmaker/handlers/odcs/compose_state_change.py @@ -0,0 +1,46 @@ +# -*- 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 freshmaker import db +from freshmaker.models import Event +from freshmaker.handlers import ContainerBuildHandler +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'] + + def handle(self, event): + errata_signed_events = db.session.query(Event).filter( + Event.compose_id == event.compose['id']).all() + for event in errata_signed_events: + self._build_first_batch(event) diff --git a/freshmaker/parsers/odcs/__init__.py b/freshmaker/parsers/odcs/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/freshmaker/parsers/odcs/__init__.py diff --git a/freshmaker/parsers/odcs/state_change.py b/freshmaker/parsers/odcs/state_change.py new file mode 100644 index 0000000..6c9c8b4 --- /dev/null +++ b/freshmaker/parsers/odcs/state_change.py @@ -0,0 +1,40 @@ +# -*- 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.parsers import BaseParser +from freshmaker.events import ODCSComposeStateChangeEvent + + +class ComposeStateChangeParser(BaseParser): + """Parser parsing odcs.compose.state.change""" + + name = "ComposeStateChangeEvent" + topic_suffixes = ["eng.odcs.compose.state.change"] + + def can_parse(self, topic, msg): + return any([topic.endswith(s) for s in self.topic_suffixes]) + + def parse(self, topic, msg): + msg_id = msg.get('msg_id') + inner_msg = msg.get('msg') + compose = inner_msg.get('compose') + + return ODCSComposeStateChangeEvent(msg_id, compose) diff --git a/tests/test_errata_advisory_state_changed.py b/tests/test_errata_advisory_state_changed.py index adae8b5..948902c 100644 --- a/tests/test_errata_advisory_state_changed.py +++ b/tests/test_errata_advisory_state_changed.py @@ -388,19 +388,12 @@ class TestPrepareYumRepo(unittest.TestCase): ODCS.return_value.new_compose.return_value = { "id": 3, "result_repo": "http://localhost/composes/latest-odcs-3-1/compose/Temporary", + "result_repofile": "http://localhost/composes/latest-odcs-3-1/compose/Temporary/odcs-3.repo", "source": "f26", "source_type": 1, "state": 0, "state_name": "wait", } - ODCS.return_value.get_compose.return_value = { - "id": 3, - "result_repo": "http://localhost/composes/latest-odcs-3-1/compose/Temporary", - "source": "f26", - "source_type": 1, - "state": 2, - "state_name": "done", - } errata.return_value.get_builds.return_value = set(["httpd-2.4.15-1.f27"]) @@ -419,13 +412,9 @@ class TestPrepareYumRepo(unittest.TestCase): ODCS.return_value.new_compose.assert_called_once_with( 'rhel-7.2-candidate', 'tag', packages=['httpd', 'httpd-debuginfo']) - # Ensure get_compose is called once in order to get lates state and see - # if it still needs to wait for ODCS - ODCS.return_value.get_compose.assert_called_once_with(3) - # We should get the right repo URL eventually self.assertEqual( - 'http://localhost/composes/latest-odcs-3-1/compose/Temporary', + "http://localhost/composes/latest-odcs-3-1/compose/Temporary/odcs-3.repo", repo_url) @@ -473,81 +462,3 @@ class TestFindEventsToInclude(unittest.TestCase): self.assertEqual(len(events), 1) self.assertEqual(events[0].search_key, "old_event_foo") - - -class AnyStringWith(str): - def __eq__(self, other): - return self in other - - -class TestBuildFirstBatch(unittest.TestCase): - """Test ErrataAdvisoryRPMsSignedHandler._build_first_batch""" - - def setUp(self): - db.session.remove() - db.drop_all() - db.create_all() - db.session.commit() - - build_args = '{"parent": "nvr", "repository": "repo", \ - "target": "target", "commit": "hash"}' - - self.db_event = Event.get_or_create( - db.session, "msg1", "current_event", ErrataAdvisoryRPMsSignedEvent, - released=False) - self.db_event.compose_id = 3 - p1 = ArtifactBuild.create(db.session, self.db_event, "parent1-1-4", - "image", - state=ArtifactBuildState.PLANNED.value) - p1.build_args = build_args - b = ArtifactBuild.create(db.session, self.db_event, "parent1_child1", "image", - state=ArtifactBuildState.PLANNED.value, - dep_on=p1) - b.build_args = build_args - b = ArtifactBuild.create(db.session, self.db_event, "parent3", "image", - state=ArtifactBuildState.BUILD.value) - b.build_args = build_args - db.session.commit() - - def tearDown(self): - db.session.remove() - db.drop_all() - db.session.commit() - - @patch('freshmaker.handlers.errata.errata_advisory_rpms_signed.ODCS') - @patch('koji.ClientSession') - @patch('freshmaker.handlers.krbContext') - def test_build_first_batch(self, krb, ClientSession, ODCS): - """ - Tests that only PLANNED images without a parent are submitted to - build system. - """ - ODCS.return_value.get_compose.return_value = { - "id": 3, - "result_repo": "http://localhost/composes/latest-odcs-3-1/compose/Temporary", - "result_repofile": "http://localhost/composes/latest-odcs-3-1/compose/Temporary/odcs-3.repo", - "source": "f26", - "source_type": 1, - "state": 2, - "state_name": "done", - } - mock_session = ClientSession.return_value - mock_session.buildContainer.return_value = 123 - - handler = ErrataAdvisoryRPMsSignedHandler() - handler._build_first_batch(self.db_event) - - mock_session.buildContainer.assert_called_once_with( - 'git://pkgs.fedoraproject.org/repo#hash', - 'target', - {'scratch': True, 'isolated': True, 'koji_parent_build': u'nvr', - 'git_branch': 'unknown', 'release': AnyStringWith('4.'), - 'yum_repourls': [ - 'http://localhost/composes/latest-odcs-3-1/compose/Temporary/odcs-3.repo']}) - - db.session.refresh(self.db_event) - for build in self.db_event.builds: - if build.name == "parent1-1-4": - self.assertEqual(build.build_id, 123) - else: - self.assertEqual(build.build_id, None) diff --git a/tests/test_handler.py b/tests/test_handler.py index 198759a..dd61cb2 100644 --- a/tests/test_handler.py +++ b/tests/test_handler.py @@ -22,15 +22,21 @@ # # Written by Chenxiong Qi +import json from mock import patch from unittest import TestCase -from freshmaker.handlers import BaseHandler +from freshmaker import db +from freshmaker.events import ErrataAdvisoryRPMsSignedEvent +from freshmaker.handlers import ContainerBuildHandler +from freshmaker.models import ArtifactBuild +from freshmaker.models import ArtifactBuildState +from freshmaker.models import Event -class MyHandler(BaseHandler): - """Handler for running tests to test things defined in BaseHandler""" +class MyHandler(ContainerBuildHandler): + """Handler for running tests to test things defined in parents""" def can_handle(self, event): """Implement BaseHandler method""" @@ -81,3 +87,78 @@ class TestKrbContextPreparedForBuildContainer(TestCase): principal='somebody@REALM', ccache_file='/tmp/freshmaker_cc', ) + + +class AnyStringWith(str): + def __eq__(self, other): + return self in other + + +class TestBuildFirstBatch(TestCase): + """Test ErrataAdvisoryRPMsSignedHandler._build_first_batch""" + + def setUp(self): + db.session.remove() + db.drop_all() + db.create_all() + db.session.commit() + + build_args = json.dumps({ + "parent": "nvr", + "repository": "repo", + "target": "target", + "commit": "hash", + "yum_repourl": "http://localhost/composes/latest-odcs-3-1/compose/" + "Temporary/odcs-3.repo", + }) + + self.db_event = Event.get_or_create( + db.session, "msg1", "current_event", ErrataAdvisoryRPMsSignedEvent, + released=False) + self.db_event.compose_id = 3 + p1 = ArtifactBuild.create(db.session, self.db_event, "parent1-1-4", + "image", + state=ArtifactBuildState.PLANNED.value) + p1.build_args = build_args + b = ArtifactBuild.create(db.session, self.db_event, + "parent1_child1", "image", + state=ArtifactBuildState.PLANNED.value, + dep_on=p1) + b.build_args = build_args + b = ArtifactBuild.create(db.session, self.db_event, "parent3", "image", + state=ArtifactBuildState.BUILD.value) + b.build_args = build_args + db.session.commit() + + def tearDown(self): + db.session.remove() + db.drop_all() + db.session.commit() + + @patch('koji.ClientSession') + @patch('freshmaker.handlers.krbContext') + def test_build_first_batch(self, krb, ClientSession): + """ + Tests that only PLANNED images without a parent are submitted to + build system. + """ + mock_session = ClientSession.return_value + mock_session.buildContainer.return_value = 123 + + handler = MyHandler() + handler._build_first_batch(self.db_event) + + mock_session.buildContainer.assert_called_once_with( + 'git://pkgs.fedoraproject.org/repo#hash', + 'target', + {'scratch': True, 'isolated': True, 'koji_parent_build': u'nvr', + 'git_branch': 'unknown', 'release': AnyStringWith('4.'), + 'yum_repourls': [ + 'http://localhost/composes/latest-odcs-3-1/compose/Temporary/odcs-3.repo']}) + + db.session.refresh(self.db_event) + for build in self.db_event.builds: + if build.name == "parent1-1-4": + self.assertEqual(build.build_id, 123) + else: + self.assertEqual(build.build_id, None) diff --git a/tests/test_odcs_compose_state_change.py b/tests/test_odcs_compose_state_change.py new file mode 100644 index 0000000..3b8173f --- /dev/null +++ b/tests/test_odcs_compose_state_change.py @@ -0,0 +1,83 @@ +# -*- 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 unittest + +from mock import call, patch + +from freshmaker import db +from freshmaker.models import Event +from freshmaker.models import EVENT_TYPES +from freshmaker.events import ErrataAdvisoryRPMsSignedEvent +from freshmaker.events import KojiTaskStateChangeEvent +from freshmaker.handlers.odcs import ComposeStateChangeHandler +from freshmaker.events import ODCSComposeStateChangeEvent + + +class TestComposeStateChangeHandler(unittest.TestCase): + """Test ODCSComposeStateChangeHandler""" + + def setUp(self): + db.session.remove() + db.drop_all() + db.create_all() + db.session.commit() + + self.adv_signed_event1 = Event.get_or_create( + db.session, 'msg-id-1', 'msg-id-1', + EVENT_TYPES[ErrataAdvisoryRPMsSignedEvent]) + self.adv_signed_event2 = Event.get_or_create( + db.session, 'msg-id-2', 'msg-id-2', + EVENT_TYPES[ErrataAdvisoryRPMsSignedEvent]) + self.unrelated_event = Event.get_or_create( + db.session, 'msg-id-3', 'msg-id-3', + EVENT_TYPES[KojiTaskStateChangeEvent]) + + self.adv_signed_event1.compose_id = 1 + self.adv_signed_event2.compose_id = 1 + db.session.commit() + + def tearDown(self): + db.session.remove() + db.drop_all() + 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.handlers.ContainerBuildHandler._build_first_batch') + def test_start_to_build(self, build_first_batch): + event = ODCSComposeStateChangeEvent( + 'msg-id', {'id': 1, 'state': 'done'} + ) + handler = ComposeStateChangeHandler() + handler.handle(event) + build_first_batch.assert_has_calls([ + call(self.adv_signed_event1), + call(self.adv_signed_event2), + ])