From 7052ea0a112ff661740055f58a7c4fc572f087bf Mon Sep 17 00:00:00 2001 From: Jan Kaluza Date: Feb 14 2019 13:18:54 +0000 Subject: Make init handler idempotent. This fixes the issue when module build is cancelled in init state. --- diff --git a/docker/Dockerfile-tests b/docker/Dockerfile-tests index c014e8f..e3bbeaa 100644 --- a/docker/Dockerfile-tests +++ b/docker/Dockerfile-tests @@ -43,6 +43,7 @@ RUN yum -y install \ python-mock \ python-tox \ rpm-build \ + python2-pyyaml \ && yum clean all # We currently require newer versions of these Python packages for the tests. # more-itertools is required by pytest, but versions 6.0.0 and up aren't Python 2 compatible diff --git a/docker/Dockerfile-tests-py3 b/docker/Dockerfile-tests-py3 index 566c9e5..f185ec2 100644 --- a/docker/Dockerfile-tests-py3 +++ b/docker/Dockerfile-tests-py3 @@ -33,6 +33,7 @@ RUN dnf -y install \ python3-mock \ python3-tox \ rpm-build \ + python3-PyYAML \ && dnf clean all VOLUME /src WORKDIR /src diff --git a/module_build_service/utils/submit.py b/module_build_service/utils/submit.py index e3de509..e9fac58 100644 --- a/module_build_service/utils/submit.py +++ b/module_build_service/utils/submit.py @@ -159,6 +159,11 @@ def format_mmd(mmd, scmurl): xmd['mbs']['rpms'] = {} # Add missing data in RPM components for pkgname, pkg in mmd.get_rpm_components().items(): + # In case of resubmit of existing module which have been + # cancelled/failed during the init state, the package + # was maybe already handled by MBS, so skip it in this case. + if pkgname in xmd['mbs']['rpms']: + continue if pkg.get_repository() and not conf.rpms_allow_repository: raise Forbidden( "Custom component repositories aren't allowed. " @@ -193,7 +198,11 @@ def format_mmd(mmd, scmurl): # by real SCM hash and store the result to our private xmd place in modulemd. pool = ThreadPool(20) try: - pkg_dicts = pool.map(_scm_get_latest, mmd.get_rpm_components().values()) + # Filter out the packages which we have already resolved in possible + # previous runs of this method (can be caused by module build resubmition). + pkgs_to_resolve = [pkg for pkg in mmd.get_rpm_components().values() + if pkg.get_name() not in xmd['mbs']['rpms']] + pkg_dicts = pool.map(_scm_get_latest, pkgs_to_resolve) finally: pool.close() @@ -355,6 +364,22 @@ def record_component_builds(mmd, module, initial_batch=1, component_ref = mmd.get_xmd()['mbs']['rpms'][component.get_name()]['ref'] full_url = component.get_repository() + "?#" + component_ref + + # Skip the ComponentBuild if it already exists in database. This can happen + # in case of module build resubmition. + existing_build = models.ComponentBuild.from_component_name( + db.session, component.get_name(), module.id) + if existing_build: + # Check that the existing build has the same most important attributes. + # This should never be a problem, but it's good to be defensive here so + # we do not mess things during resubmition. + if (existing_build.batch != batch or existing_build.scmurl != full_url or + existing_build.ref != component_ref): + raise ValidationError( + "Module build %s already exists in database, but its attributes " + " are different from resubmitted one." % component.get_name()) + continue + build = models.ComponentBuild( module_id=module.id, package=component.get_name(), diff --git a/test-requirements.txt b/test-requirements.txt index 799775d..8d8a101 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -4,3 +4,4 @@ mock pytest flake8 tox +pyyaml diff --git a/tests/test_scheduler/test_module_init.py b/tests/test_scheduler/test_module_init.py index e80a5be..9726443 100644 --- a/tests/test_scheduler/test_module_init.py +++ b/tests/test_scheduler/test_module_init.py @@ -20,6 +20,7 @@ # import os +import yaml from mock import patch, PropertyMock from gi.repository import GLib @@ -93,6 +94,23 @@ class TestModuleInit: assert type(xmd_mbs) is GLib.Variant assert xmd_mbs["buildrequires"]["platform"]["filtered_rpms"] == [ 'foo-0:2.4.48-3.el8+1308+551bfa71', 'bar-0:2.5.48-3.el8+1308+551bfa71'] + return build + + def test_init_called_twice(self): + build = self.test_init_basic() + old_component_builds = len(build.component_builds) + old_mmd = yaml.load(build.modulemd) + + build.state = 4 + db.session.commit() + build = self.test_init_basic() + db.session.refresh(build) + + assert build.state == 1 + assert old_component_builds == len(build.component_builds) + + new_mmd = yaml.load(build.modulemd) + assert old_mmd == new_mmd @patch('module_build_service.scm.SCM') def test_init_scm_not_available(self, mocked_scm):