From 9a90931859a7cfab108b9782b3450409728ed531 Mon Sep 17 00:00:00 2001 From: Jan Kaluza Date: Nov 13 2018 07:55:48 +0000 Subject: Move ODCS code from ErrataAdvisoryRPPMsSignedhandler to FreshmakerODCSClient. Currently, half of the ErrataAdvisoryRPMsSignedHandler is code to ask for various ODCS composes. This is wrong from following reasons: - The ODCS related code is not special for this particular handler and might be used in the future by other handlers. - It makes ErrataAdvisoryRPMsSignedHandler longer than needed and therefore harder to read/understand. - It makes the tests for ErrataAdvisoryRPMsSignedHandler longer and harder to understand. This commit also removes the "boot.iso" compose type from Freshmaker, because we have workarounded the boot.iso generation and we have no plans to support boot.iso generation in ODCS ever because of its complexity. There should be follow-up of this commit to further change the new FreshmakerODCSClient API to name the methods better and make them even more general, but for simplicity, it is not done in this commit - this commit only moves the code around without changing it. --- diff --git a/freshmaker/handlers/__init__.py b/freshmaker/handlers/__init__.py index 875671f..6672983 100644 --- a/freshmaker/handlers/__init__.py +++ b/freshmaker/handlers/__init__.py @@ -35,7 +35,7 @@ from freshmaker.types import EventState from freshmaker.models import ArtifactBuild, Event from freshmaker.utils import krb_context, get_rebuilt_nvr from freshmaker.errors import UnprocessableEntity, ProgrammingError -from freshmaker.odcsclient import create_odcs_client +from freshmaker.odcsclient import create_odcs_client, FreshmakerODCSClient from freshmaker.odcsclient import COMPOSE_STATES @@ -124,6 +124,7 @@ class BaseHandler(object): self._db_artifact_build_id = None self._log_prefix = "" self._force_dry_run = False + self.odcs = FreshmakerODCSClient(self) def _log(self, log_fnc, msg, *args, **kwargs): """ diff --git a/freshmaker/handlers/errata/errata_advisory_rpms_signed.py b/freshmaker/handlers/errata/errata_advisory_rpms_signed.py index aab67cd..c0815fb 100644 --- a/freshmaker/handlers/errata/errata_advisory_rpms_signed.py +++ b/freshmaker/handlers/errata/errata_advisory_rpms_signed.py @@ -25,14 +25,12 @@ import json import koji import requests -import kobo.rpmlib from six.moves import cStringIO from six.moves import configparser from freshmaker import conf, db, log from freshmaker.events import ErrataAdvisoryRPMsSignedEvent -from freshmaker.events import ODCSComposeStateChangeEvent from freshmaker.events import ManualRebuildWithAdvisoryEvent from freshmaker.handlers import ContainerBuildHandler, fail_event_on_handler_exception from freshmaker.kojiservice import koji_service @@ -41,12 +39,7 @@ 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.consumer import work_queue_put -from freshmaker.utils import ( - krb_context, retry, get_rebuilt_nvr) -from freshmaker.odcsclient import create_odcs_client - -from odcs.common.types import COMPOSE_STATES +from freshmaker.utils import get_rebuilt_nvr class ErrataAdvisoryRPMsSignedHandler(ContainerBuildHandler): @@ -57,9 +50,6 @@ class ErrataAdvisoryRPMsSignedHandler(ContainerBuildHandler): name = 'ErrataAdvisoryRPMsSignedHandler' - # Used to generate incremental compose id in dry run mode. - _FAKE_COMPOSE_ID = 0 - def can_handle(self, event): return isinstance(event, ErrataAdvisoryRPMsSignedEvent) @@ -129,7 +119,7 @@ class ErrataAdvisoryRPMsSignedHandler(ContainerBuildHandler): # available from official YUM repositories. # # Generate the ODCS compose with RPMs from the current advisory. - repo_urls = self._prepare_yum_repos_for_rebuilds(db_event) + 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: @@ -146,217 +136,6 @@ class ErrataAdvisoryRPMsSignedHandler(ContainerBuildHandler): return [] - def _fake_odcs_new_compose( - self, compose_source, tag, packages=None, results=[]): - """ - Fake KojiSession.buildContainer method used dry run mode. - - Logs the arguments and emits ErrataAdvisoryRPMsSignedHandler of - "done" state. - - :rtype: dict - :return: Fake odcs.new_compose dict. - """ - self.log_info("DRY RUN: Calling fake odcs.new_compose with args: %r", - (compose_source, tag, packages, results)) - - # Generate the new_compose dict. - ErrataAdvisoryRPMsSignedHandler._FAKE_COMPOSE_ID -= 1 - new_compose = {} - new_compose['id'] = ErrataAdvisoryRPMsSignedHandler._FAKE_COMPOSE_ID - new_compose['result_repofile'] = "http://localhost/%d.repo" % ( - new_compose['id']) - new_compose['state'] = COMPOSE_STATES['done'] - if results: - new_compose['results'] = ['boot.iso'] - - # Generate and inject the ODCSComposeStateChangeEvent event. - event = ODCSComposeStateChangeEvent( - "fake_compose_msg", new_compose) - event.dry_run = True - self.log_info("Injecting fake event: %r", event) - work_queue_put(event) - - return new_compose - - def _prepare_yum_repos_for_rebuilds(self, db_event): - repo_urls = [] - db_composes = [] - - compose = self._prepare_yum_repo(db_event) - db_composes.append(Compose(odcs_compose_id=compose['id'])) - db.session.add(db_composes[-1]) - repo_urls.append(compose['result_repofile']) - - for dep_event in db_event.find_dependent_events(): - compose = self._prepare_yum_repo(dep_event) - db_composes.append(Compose(odcs_compose_id=compose['id'])) - db.session.add(db_composes[-1]) - repo_urls.append(compose['result_repofile']) - - # commit all new composes - db.session.commit() - - for build in db_event.builds: - build.add_composes(db.session, db_composes) - db.session.commit() - - # Remove duplicates from repo_urls. - return list(set(repo_urls)) - - def _prepare_yum_repo(self, db_event): - """ - Request a compose from ODCS for builds included in Errata advisory - - Run a compose in ODCS to contain required RPMs for rebuilding images - later. - - :param Event db_event: current event being handled that contains errata - advisory to get builds containing updated RPMs. - :return: a mapping returned from ODCS that represents the request - compose. - :rtype: dict - """ - errata_id = int(db_event.search_key) - - packages = [] - errata = Errata() - builds = errata.get_builds(errata_id) - compose_source = None - for nvr in builds: - packages += self._get_packages_for_compose(nvr) - source = self._get_compose_source(nvr) - if compose_source and compose_source != source: - # TODO: Handle this by generating two ODCS composes - db_event.builds_transition( - ArtifactBuildState.FAILED.value, "Packages for errata " - "advisory %d found in multiple different tags." - % (errata_id)) - return - else: - compose_source = source - - if compose_source is None: - db_event.builds_transition( - ArtifactBuildState.FAILED.value, 'None of builds %s of ' - 'advisory %d is the latest build in its candidate tag.' - % (builds, errata_id)) - return - - self.log_info('Generating new compose for rebuild: ' - 'source: %s, source type: %s, packages: %s', - compose_source, 'tag', packages) - - if not self.dry_run: - with krb_context(): - new_compose = create_odcs_client().new_compose( - compose_source, 'tag', packages=packages, - sigkeys=conf.odcs_sigkeys, flags=["no_deps"]) - else: - new_compose = self._fake_odcs_new_compose( - compose_source, 'tag', packages=packages) - - return new_compose - - def _prepare_pulp_repo(self, build, content_sets): - """ - Prepares .repo file containing the repositories matching - the content_sets by creating new ODCS compose of PULP type. - - This currently blocks until the compose is done or failed. - - :param build: models.ModuleBuild instance associated with this compose. - :param list content_sets: List of content sets. - :rtype: dict - :return: ODCS compose dictionary. - """ - self.log_info('Generating new PULP type compose for content_sets: %r', - content_sets) - - odcs = create_odcs_client() - if not self.dry_run: - with krb_context(): - new_compose = odcs.new_compose( - ' '.join(content_sets), 'pulp') - - # Pulp composes in ODCS takes just few seconds, because ODCS - # only generates the .repo file after single query to Pulp. - # TODO: Freshmaker is currently not designed to handle - # multiple ODCS composes per rebuild Event and since these - # composes are done in no-time normally, it is OK here to - # block. It would still be nice to redesign that part of - # Freshmaker to do things "right". - # This is tracked here: https://pagure.io/freshmaker/issue/114 - @retry(timeout=60, interval=2) - def wait_for_compose(compose_id): - ret = odcs.get_compose(compose_id) - if ret["state_name"] == "done": - return True - elif ret["state_name"] == "failed": - return False - self.log_info("Waiting for Pulp compose to finish: %r", - ret) - raise Exception("ODCS compose not finished.") - - done = wait_for_compose(new_compose["id"]) - if not done: - build.transition( - ArtifactBuildState.FAILED.value, "Cannot generate " - "ODCS PULP compose %s for content_sets %r" - % (str(new_compose["id"]), content_sets)) - else: - new_compose = self._fake_odcs_new_compose( - content_sets, 'pulp') - - return new_compose - - def _prepare_odcs_compose_with_image_rpms(self, image): - """ - Request a compose from ODCS for builds included in Errata advisory - - Run a compose in ODCS to contain required RPMs for rebuilding images - later. - - :param dict image: Container image representation as returned by - LightBlue class. - :return: a mapping returned from ODCS that represents the request - compose. - :rtype: dict - """ - - if not image.get('rpm_manifest'): - self.log_warn('"rpm_manifest" not set in image.') - return - - rpm_manifest = image["rpm_manifest"][0] - if not rpm_manifest.get('rpms'): - return - - builds = set() - packages = set() - for rpm in rpm_manifest["rpms"]: - parsed_nvr = kobo.rpmlib.parse_nvra(rpm["srpm_nevra"]) - srpm_nvr = "%s-%s-%s" % (parsed_nvr["name"], parsed_nvr["version"], - parsed_nvr["release"]) - builds.add(srpm_nvr) - parsed_nvr = kobo.rpmlib.parse_nvra(rpm["nvra"]) - packages.add(parsed_nvr["name"]) - - if not self.dry_run: - with krb_context(): - new_compose = create_odcs_client().new_compose( - "", 'build', packages=packages, builds=builds, - sigkeys=conf.odcs_sigkeys, flags=["no_deps"]) - else: - new_compose = self._fake_odcs_new_compose( - "", 'build', packages=packages, - builds=builds) - - self.log_info("Started generating ODCS 'build' type compose %d." % ( - new_compose["id"])) - - return new_compose - def _get_base_image_build_target(self, image): dockerfile = image.dockerfile image_build_conf_url = dockerfile['content_url'].replace( @@ -383,88 +162,6 @@ class ErrataAdvisoryRPMsSignedHandler(ContainerBuildHandler): log.exception('image-build.conf does not have option target.') return None - def _get_base_image_build_tag(self, build_target): - with koji_service( - conf.koji_profile, log, dry_run=self.dry_run) as session: - target_info = session.get_build_target(build_target) - if target_info is None: - return target_info - else: - return target_info['build_tag_name'] - - def _request_boot_iso_compose(self, image): - """Request boot.iso compose for base image""" - target = self._get_base_image_build_target(image) - if not target: - return None - build_tag = self._get_base_image_build_tag(target) - if not build_tag: - return None - - if self.dry_run: - new_compose = self._fake_odcs_new_compose( - build_tag, 'tag', results=['boot.iso']) - else: - with krb_context(): - new_compose = create_odcs_client().new_compose( - build_tag, 'tag', results=['boot.iso']) - return new_compose - - def _get_packages_for_compose(self, nvr): - """Get RPMs of current build NVR - - :param str nvr: build NVR. - :return: list of RPM names built from given build. - :rtype: list - """ - with koji_service( - conf.koji_profile, log, dry_run=self.dry_run) as session: - rpms = session.get_build_rpms(nvr) - return list(set([rpm['name'] for rpm in rpms])) - - def _get_compose_source(self, nvr): - """Get tag from which to collect packages to compose - :param str nvr: build NVR used to find correct tag. - :return: found tag. None is returned if build is not the latest build - of found tag. - :rtype: str - """ - with koji_service( - conf.koji_profile, log, dry_run=self.dry_run) as service: - # Get the list of *-candidate tags, because packages added into - # Errata should be tagged into -candidate tag. - tags = service.session.listTags(nvr) - candidate_tags = [tag['name'] for tag in tags - if tag['name'].endswith('-candidate')] - - # Candidate tags may include unsigned packages and ODCS won't - # allow generating compose from them, so try to find out final - # version of candidate tag (without the "-candidate" suffix). - final_tags = [] - for candidate_tag in candidate_tags: - final = candidate_tag[:-len("-candidate")] - final_tags += [tag['name'] for tag in tags - if tag['name'] == final] - - # Prefer final tags over candidate tags. - tags_to_try = final_tags + candidate_tags - for tag in tags_to_try: - latest_build = service.session.listTagged( - tag, - latest=True, - package=koji.parse_NVR(nvr)['name']) - if latest_build and latest_build[0]['nvr'] == nvr: - self.log_info("Package %r is latest version in tag %r, " - "will use this tag", nvr, tag) - return tag - elif not latest_build: - self.log_info("Could not find package %r in tag %r, " - "skipping this tag", nvr, tag) - else: - self.log_info("Package %r is not he latest in the tag %r (" - "latest is %r), skipping this tag", - nvr, tag, latest_build[0]['nvr']) - def _check_images_to_rebuild(self, db_event, builds): """ Checks the images to rebuild and logs them using self.log_info(...). @@ -616,7 +313,7 @@ class ErrataAdvisoryRPMsSignedHandler(ContainerBuildHandler): if cache_key in odcs_cache: db_compose = odcs_cache[cache_key] else: - compose = self._prepare_pulp_repo( + compose = self.odcs.prepare_pulp_repo( build, image["content_sets"]) if build.state != ArtifactBuildState.FAILED.value: @@ -635,7 +332,7 @@ class ErrataAdvisoryRPMsSignedHandler(ContainerBuildHandler): # the ODCS compose with all the RPMs in the image to allow # installation of possibly unreleased RPMs. if not image["published"]: - compose = self._prepare_odcs_compose_with_image_rpms(image) + 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) @@ -643,25 +340,6 @@ class ErrataAdvisoryRPMsSignedHandler(ContainerBuildHandler): build.add_composes(db.session, [db_compose]) db.session.commit() - # TODO: uncomment following code after boot.iso compose is - # deployed in ODCS server. -# if image.is_base_image: -# compose = self._request_boot_iso_compose(image) -# if compose is None: -# log.error( -# 'Failed to request boot.iso compose for base ' -# 'image %s.', nvr) -# build.transition( -# ArtifactBuildState.FAILED.value, -# 'Cannot rebuild this base image because failed to ' -# 'requeset boot.iso compose.') -# # FIXME: mark all builds associated with build.event FAILED? -# else: -# db_compose = Compose(odcs_compose_id=compose['id']) -# db.session.add(db_compose) -# db.session.commit() -# build.add_composes(db.session, [db_compose]) - builds[nvr] = build # Reset context to db_event. diff --git a/freshmaker/models.py b/freshmaker/models.py index eeefe3e..68c29d6 100644 --- a/freshmaker/models.py +++ b/freshmaker/models.py @@ -35,7 +35,6 @@ from flask_login import UserMixin from freshmaker import db, log from freshmaker import messaging -from freshmaker.odcsclient import create_odcs_client from freshmaker.utils import get_url_for, krb_context from freshmaker.types import ArtifactType, ArtifactBuildState, EventState from freshmaker.events import ( @@ -589,6 +588,7 @@ class Compose(FreshmakerBase): @property def finished(self): + from freshmaker.odcsclient import create_odcs_client with krb_context(): return 'done' == create_odcs_client().get_compose( self.odcs_compose_id)['state_name'] diff --git a/freshmaker/odcsclient.py b/freshmaker/odcsclient.py index 0143bb4..7d7c144 100644 --- a/freshmaker/odcsclient.py +++ b/freshmaker/odcsclient.py @@ -30,12 +30,22 @@ # it would import freshmaker.handlers.odcs, so instead, we import it here # and in freshmaker.handler do "from freshmaker.odcsclient import ODCS". +import koji import os +import kobo.rpmlib + from odcs.client.odcs import AuthMech, ODCS -from odcs.common.types import COMPOSE_STATES # noqa +from odcs.common.types import COMPOSE_STATES from requests.exceptions import HTTPError -from freshmaker import conf, log +from freshmaker import conf, log, db +from freshmaker.models import Compose +from freshmaker.errata import Errata +from freshmaker.kojiservice import koji_service +from freshmaker.consumer import work_queue_put +from freshmaker.types import ArtifactBuildState +from freshmaker.utils import krb_context, retry +from freshmaker.events import ODCSComposeStateChangeEvent class RetryingODCS(ODCS): @@ -71,3 +81,294 @@ def create_odcs_client(): raise ValueError( 'Authentication mechanism {0} is not supported yet.'.format( conf.odcs_auth_mech)) + + +class FreshmakerODCSClient(object): + """ + Class wrapping ODCS providing high-level methods to generate ODCS composes. + This class is intended to be used in the BaseHandler scope. + """ + + # Used to generate incremental compose id in dry run mode. + _FAKE_COMPOSE_ID = 0 + + def __init__(self, handler): + """ + Creates new FreshmakerODCSClient. + + :param BaseHandler handler: Handler with which is the newly created + instance associated. + """ + self.handler = handler + + def _fake_odcs_new_compose( + self, compose_source, tag, packages=None, results=[]): + """ + Fake odcs.new_compose(...) method used in the dry run mode. + + Logs the arguments and emits fake ODCSComposeStateChangeEvent + + :rtype: dict + :return: Fake odcs.new_compose dict. + """ + self.handler.log_info( + "DRY RUN: Calling fake odcs.new_compose with args: %r", + (compose_source, tag, packages, results)) + + # Generate the new_compose dict. + FreshmakerODCSClient._FAKE_COMPOSE_ID -= 1 + new_compose = {} + new_compose['id'] = FreshmakerODCSClient._FAKE_COMPOSE_ID + new_compose['result_repofile'] = "http://localhost/%d.repo" % ( + new_compose['id']) + new_compose['state'] = COMPOSE_STATES['done'] + if results: + new_compose['results'] = ['boot.iso'] + + # Generate and inject the ODCSComposeStateChangeEvent event. + event = ODCSComposeStateChangeEvent( + "fake_compose_msg", new_compose) + event.dry_run = True + self.handler.log_info("Injecting fake event: %r", event) + work_queue_put(event) + + return new_compose + + def _get_packages_for_compose(self, nvr): + """Get RPMs of current build NVR + + :param str nvr: build NVR. + :return: list of RPM names built from given build. + :rtype: list + """ + with koji_service( + conf.koji_profile, log, dry_run=self.handler.dry_run) as session: + rpms = session.get_build_rpms(nvr) + return list(set([rpm['name'] for rpm in rpms])) + + def _get_compose_source(self, nvr): + """Get tag from which to collect packages to compose + :param str nvr: build NVR used to find correct tag. + :return: found tag. None is returned if build is not the latest build + of found tag. + :rtype: str + """ + with koji_service( + conf.koji_profile, log, dry_run=self.handler.dry_run) as service: + # Get the list of *-candidate tags, because packages added into + # Errata should be tagged into -candidate tag. + tags = service.session.listTags(nvr) + candidate_tags = [tag['name'] for tag in tags + if tag['name'].endswith('-candidate')] + + # Candidate tags may include unsigned packages and ODCS won't + # allow generating compose from them, so try to find out final + # version of candidate tag (without the "-candidate" suffix). + final_tags = [] + for candidate_tag in candidate_tags: + final = candidate_tag[:-len("-candidate")] + final_tags += [tag['name'] for tag in tags + if tag['name'] == final] + + # Prefer final tags over candidate tags. + tags_to_try = final_tags + candidate_tags + for tag in tags_to_try: + latest_build = service.session.listTagged( + tag, + latest=True, + package=koji.parse_NVR(nvr)['name']) + if latest_build and latest_build[0]['nvr'] == nvr: + self.handler.log_info( + "Package %r is latest version in tag %r, " + "will use this tag", nvr, tag) + return tag + elif not latest_build: + self.handler.log_info( + "Could not find package %r in tag %r, " + "skipping this tag", nvr, tag) + else: + self.handler.log_info( + "Package %r is not he latest in the tag %r (" + "latest is %r), skipping this tag", + nvr, tag, latest_build[0]['nvr']) + + def prepare_yum_repos_for_rebuilds(self, db_event): + repo_urls = [] + db_composes = [] + + compose = self.prepare_yum_repo(db_event) + db_composes.append(Compose(odcs_compose_id=compose['id'])) + db.session.add(db_composes[-1]) + repo_urls.append(compose['result_repofile']) + + for dep_event in db_event.find_dependent_events(): + compose = self.prepare_yum_repo(dep_event) + db_composes.append(Compose(odcs_compose_id=compose['id'])) + db.session.add(db_composes[-1]) + repo_urls.append(compose['result_repofile']) + + # commit all new composes + db.session.commit() + + for build in db_event.builds: + build.add_composes(db.session, db_composes) + db.session.commit() + + # Remove duplicates from repo_urls. + return list(set(repo_urls)) + + def prepare_yum_repo(self, db_event): + """ + Request a compose from ODCS for builds included in Errata advisory + + Run a compose in ODCS to contain required RPMs for rebuilding images + later. + + :param Event db_event: current event being handled that contains errata + advisory to get builds containing updated RPMs. + :return: a mapping returned from ODCS that represents the request + compose. + :rtype: dict + """ + errata_id = int(db_event.search_key) + + packages = [] + errata = Errata() + builds = errata.get_builds(errata_id) + compose_source = None + for nvr in builds: + packages += self._get_packages_for_compose(nvr) + source = self._get_compose_source(nvr) + if compose_source and compose_source != source: + # TODO: Handle this by generating two ODCS composes + db_event.builds_transition( + ArtifactBuildState.FAILED.value, "Packages for errata " + "advisory %d found in multiple different tags." + % (errata_id)) + return + else: + compose_source = source + + if compose_source is None: + db_event.builds_transition( + ArtifactBuildState.FAILED.value, 'None of builds %s of ' + 'advisory %d is the latest build in its candidate tag.' + % (builds, errata_id)) + return + + self.handler.log_info( + 'Generating new compose for rebuild: ' + 'source: %s, source type: %s, packages: %s', + compose_source, 'tag', packages) + + if not self.handler.dry_run: + with krb_context(): + new_compose = create_odcs_client().new_compose( + compose_source, 'tag', packages=packages, + sigkeys=conf.odcs_sigkeys, flags=["no_deps"]) + else: + new_compose = self._fake_odcs_new_compose( + compose_source, 'tag', packages=packages) + + return new_compose + + def prepare_pulp_repo(self, build, content_sets): + """ + Prepares .repo file containing the repositories matching + the content_sets by creating new ODCS compose of PULP type. + + This currently blocks until the compose is done or failed. + + :param build: models.ModuleBuild instance associated with this compose. + :param list content_sets: List of content sets. + :rtype: dict + :return: ODCS compose dictionary. + """ + self.handler.log_info( + 'Generating new PULP type compose for content_sets: %r', + content_sets) + + odcs = create_odcs_client() + if not self.handler.dry_run: + with krb_context(): + new_compose = odcs.new_compose( + ' '.join(content_sets), 'pulp') + + # Pulp composes in ODCS takes just few seconds, because ODCS + # only generates the .repo file after single query to Pulp. + # TODO: Freshmaker is currently not designed to handle + # multiple ODCS composes per rebuild Event and since these + # composes are done in no-time normally, it is OK here to + # block. It would still be nice to redesign that part of + # Freshmaker to do things "right". + # This is tracked here: https://pagure.io/freshmaker/issue/114 + @retry(timeout=60, interval=2) + def wait_for_compose(compose_id): + ret = odcs.get_compose(compose_id) + if ret["state_name"] == "done": + return True + elif ret["state_name"] == "failed": + return False + self.handler.log_info( + "Waiting for Pulp compose to finish: %r", ret) + raise Exception("ODCS compose not finished.") + + done = wait_for_compose(new_compose["id"]) + if not done: + build.transition( + ArtifactBuildState.FAILED.value, "Cannot generate " + "ODCS PULP compose %s for content_sets %r" + % (str(new_compose["id"]), content_sets)) + else: + new_compose = self._fake_odcs_new_compose( + content_sets, 'pulp') + + return new_compose + + def prepare_odcs_compose_with_image_rpms(self, image): + """ + Request a compose from ODCS for builds included in Errata advisory + + Run a compose in ODCS to contain required RPMs for rebuilding images + later. + + :param dict image: Container image representation as returned by + LightBlue class. + :return: a mapping returned from ODCS that represents the request + compose. + :rtype: dict + """ + + if not image.get('rpm_manifest'): + self.handler.log_warn('"rpm_manifest" not set in image.') + return + + rpm_manifest = image["rpm_manifest"][0] + if not rpm_manifest.get('rpms'): + return + + builds = set() + packages = set() + for rpm in rpm_manifest["rpms"]: + parsed_nvr = kobo.rpmlib.parse_nvra(rpm["srpm_nevra"]) + srpm_nvr = "%s-%s-%s" % (parsed_nvr["name"], parsed_nvr["version"], + parsed_nvr["release"]) + builds.add(srpm_nvr) + parsed_nvr = kobo.rpmlib.parse_nvra(rpm["nvra"]) + packages.add(parsed_nvr["name"]) + + if not self.handler.dry_run: + with krb_context(): + new_compose = create_odcs_client().new_compose( + "", 'build', packages=packages, builds=builds, + sigkeys=conf.odcs_sigkeys, flags=["no_deps"]) + else: + new_compose = self._fake_odcs_new_compose( + "", 'build', packages=packages, + builds=builds) + + self.handler.log_info( + "Started generating ODCS 'build' type compose %d." % ( + new_compose["id"])) + + return new_compose diff --git a/tests/test_errata_advisory_rpms_signed_handler.py b/tests/test_errata_advisory_rpms_signed_handler.py index 1561899..715d21e 100644 --- a/tests/test_errata_advisory_rpms_signed_handler.py +++ b/tests/test_errata_advisory_rpms_signed_handler.py @@ -19,8 +19,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import requests - from mock import patch import freshmaker @@ -56,19 +54,12 @@ class TestErrataAdvisoryRPMsSignedHandler(helpers.ModelsTestCase): 'freshmaker.messaging.publish') self.mock_prepare_pulp_repo = self.patcher.patch( - '_prepare_pulp_repo', + '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') - # boot.iso composes IDs should be different from pulp composes IDs as - # when each time to request a compose from ODCS, new compose ID will - # be returned along with new comopse. - self.mock_request_boot_iso_compose = self.patcher.patch( - '_request_boot_iso_compose', - side_effect=[{'id': 100}, {'id': 101}]) - # Fake images found to rebuild has these relationships # # Batch 1 | Batch 2 | Batch 3 @@ -370,8 +361,7 @@ class TestErrataAdvisoryRPMsSignedHandler(helpers.ModelsTestCase): @patch('freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.' 'allow_build', return_value=True) - @patch('freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.' - '_prepare_yum_repos_for_rebuilds') + @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( @@ -388,8 +378,7 @@ class TestErrataAdvisoryRPMsSignedHandler(helpers.ModelsTestCase): @patch('freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.' 'allow_build', return_value=True) - @patch('freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.' - '_prepare_yum_repos_for_rebuilds') + @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') @@ -412,201 +401,6 @@ class TestErrataAdvisoryRPMsSignedHandler(helpers.ModelsTestCase): self.assertEqual(EventState.BUILDING.value, db_event.state) -class TestGetBaseImageBuildTarget(helpers.FreshmakerTestCase): - """Test ErrataAdvisoryRPMsSignedHandler._get_base_image_build_target""" - - def setUp(self): - super(TestGetBaseImageBuildTarget, self).setUp() - - self.image = 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'], - 'brew': { - 'build': 'image-a-1.0-2', - }, - 'parent': None, - 'parsed_data': { - 'layers': [ - 'sha512:7890', - 'sha512:5678', - ], - 'files': [ - { - 'filename': 'Dockerfile', - 'content_url': 'http://pkgs.localhost/cgit/rpms/' - 'image-a/plain/Dockerfile?id=fa521323', - 'key': 'buildfile' - } - ] - }, - }) - self.handler = ErrataAdvisoryRPMsSignedHandler() - - @patch('requests.get') - def test_get_target_from_image_build_conf(self, get): - get.return_value.content = '''\ -[image-build] -name = image-a -arches = x86_64 -format = docker -disk_size = 10 -ksurl = git://git.localhost/spin-kickstarts.git?rhel7#HEAD -kickstart = rhel-7.4-server-docker.ks -version = 7.4 -target = guest-rhel-7.4-docker -distro = RHEL-7.4 -ksversion = RHEL7''' - - result = self.handler._get_base_image_build_target(self.image) - self.assertEqual('guest-rhel-7.4-docker', result) - - @patch('requests.get') - def test_image_build_conf_is_unavailable_in_distgit(self, get): - get.return_value.raise_for_status.side_effect = \ - requests.exceptions.HTTPError('error') - - result = self.handler._get_base_image_build_target(self.image) - self.assertIsNone(result) - - @patch('requests.get') - def test_image_build_conf_is_empty(self, get): - get.return_value.content = '' - - result = self.handler._get_base_image_build_target(self.image) - self.assertIsNone(result) - - @patch('requests.get') - def test_image_build_conf_is_not_INI(self, get): - get.return_value.content = 'abc' - - result = self.handler._get_base_image_build_target(self.image) - self.assertIsNone(result) - - -class TestGetBaseImageBuildTag(helpers.FreshmakerTestCase): - """Test ErrataAdvisoryRPMsSignedHandler._get_base_image_build_tag""" - - def setUp(self): - super(TestGetBaseImageBuildTag, self).setUp() - - self.image = 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'], - 'brew': { - 'build': 'image-a-1.0-2', - }, - 'parent': None, - 'parsed_data': { - 'layers': [ - 'sha512:7890', - 'sha512:5678', - ], - 'files': [ - { - 'filename': 'Dockerfile', - 'content_url': 'http://pkgs.localhost/cgit/rpms/' - 'image-a/plain/Dockerfile?id=fa521323', - 'key': 'buildfile' - } - ] - }, - }) - self.handler = ErrataAdvisoryRPMsSignedHandler() - - @helpers.mock_koji - def test_get_build_tag_name(self, mocked_koji): - result = self.handler._get_base_image_build_tag( - 'guest-rhel-7.4-docker') - self.assertEqual('guest-rhel-7.4-docker-build', result) - - @helpers.mock_koji - def test_no_target_is_returned(self, mocked_koji): - result = self.handler._get_base_image_build_tag( - 'guest-rhel-7.4-docker-unknown') - self.assertIsNone(result) - - -class TestRequestBootISOCompose(helpers.FreshmakerTestCase): - """Test ErrataAdvisoryRPMsSignedHandler._request_boot_iso_compose""" - - def setUp(self): - super(TestRequestBootISOCompose, self).setUp() - - self.image = 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'], - 'brew': { - 'build': 'image-a-1.0-2', - }, - 'parent': None, - 'parsed_data': { - 'layers': [ - 'sha512:7890', - 'sha512:5678', - ], - 'files': [ - { - 'filename': 'Dockerfile', - 'content_url': 'http://pkgs.localhost/cgit/rpms/' - 'image-a/plain/Dockerfile?id=fa521323', - 'key': 'buildfile' - } - ] - }, - }) - self.handler = ErrataAdvisoryRPMsSignedHandler() - - @patch('freshmaker.handlers.errata.errata_advisory_rpms_signed.' - 'create_odcs_client') - @patch('freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.' - '_get_base_image_build_target') - @patch('freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.' - '_get_base_image_build_tag') - def test_get_boot_iso_compose( - self, get_base_image_build_tag, get_base_image_build_target, - create_odcs_client): - odcs = create_odcs_client.return_value - odcs.new_compose.return_value = {'id': 1} - - get_base_image_build_target.return_value = 'build-target' - get_base_image_build_tag.return_value = 'build-tag' - - result = self.handler._request_boot_iso_compose(self.image) - - self.assertEqual(odcs.new_compose.return_value, result) - odcs.new_compose.assert_called_once_with( - 'build-tag', 'tag', results=['boot.iso']) - - @patch('freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.' - '_get_base_image_build_target') - def test_cannot_get_image_build_target(self, get_base_image_build_target): - get_base_image_build_target.return_value = None - - result = self.handler._request_boot_iso_compose(self.image) - self.assertIsNone(result) - - @patch('freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.' - '_get_base_image_build_target') - @patch('freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.' - '_get_base_image_build_tag') - def test_cannot_get_build_tag_from_target( - self, get_base_image_build_tag, get_base_image_build_target): - get_base_image_build_target.return_value = 'build-target' - get_base_image_build_tag.return_value = None - - result = self.handler._request_boot_iso_compose(self.image) - self.assertIsNone(result) - - class TestFindImagesToRebuild(helpers.FreshmakerTestCase): def setUp(self): diff --git a/tests/test_errata_advisory_state_changed.py b/tests/test_errata_advisory_state_changed.py index 3686f0f..423fb1e 100644 --- a/tests/test_errata_advisory_state_changed.py +++ b/tests/test_errata_advisory_state_changed.py @@ -21,7 +21,6 @@ # # Written by Chenxiong Qi -import unittest import json from mock import patch, PropertyMock, Mock, call @@ -326,7 +325,7 @@ class TestBatches(helpers.ModelsTestCase): "published": False, }) - @patch('freshmaker.handlers.errata.errata_advisory_rpms_signed.create_odcs_client') + @patch('freshmaker.odcsclient.create_odcs_client') def test_batches_records(self, create_odcs_client): """ Tests that batches are properly recorded in DB. @@ -478,243 +477,6 @@ class TestCheckImagesToRebuild(helpers.ModelsTestCase): self.assertEqual(build.state, ArtifactBuildState.FAILED.value) -class TestGetPackagesForCompose(helpers.FreshmakerTestCase): - """Test ErrataAdvisoryRPMsSignedHandler._get_packages_for_compose""" - - @helpers.mock_koji - def test_get_packages(self, mocked_koji): - build_nvr = 'chkconfig-1.7.2-1.el7_3.1' - mocked_koji.add_build(build_nvr) - mocked_koji.add_build_rpms( - build_nvr, - [build_nvr, "chkconfig-debuginfo-1.7.2-1.el7_3.1"]) - - handler = ErrataAdvisoryRPMsSignedHandler() - packages = handler._get_packages_for_compose(build_nvr) - - self.assertEqual(set(['chkconfig', 'chkconfig-debuginfo']), - set(packages)) - - -class TestGetComposeSource(helpers.FreshmakerTestCase): - """Test ErrataAdvisoryRPMsSignedHandler._get_compose_source""" - - @helpers.mock_koji - def test_get_tag(self, mocked_koji): - mocked_koji.add_build("rh-postgresql96-3.0-9.el6") - handler = ErrataAdvisoryRPMsSignedHandler() - tag = handler._get_compose_source('rh-postgresql96-3.0-9.el6') - self.assertEqual('tag-candidate', tag) - - @helpers.mock_koji - def test_get_None_if_tag_has_new_build(self, mocked_koji): - mocked_koji.add_build("rh-postgresql96-3.0-9.el6") - mocked_koji.add_build("rh-postgresql96-3.0-10.el6") - handler = ErrataAdvisoryRPMsSignedHandler() - tag = handler._get_compose_source('rh-postgresql96-3.0-9.el6') - self.assertEqual(None, tag) - - @helpers.mock_koji - def test_get_tag_prefer_final_over_candidate(self, mocked_koji): - mocked_koji.add_build("rh-postgresql96-3.0-9.el6", - ["tag-candidate", "tag"]) - handler = ErrataAdvisoryRPMsSignedHandler() - tag = handler._get_compose_source('rh-postgresql96-3.0-9.el6') - self.assertEqual('tag', tag) - - @helpers.mock_koji - def test_get_tag_fallback_to_second_tag(self, mocked_koji): - mocked_koji.add_build("rh-postgresql96-3.0-10.el6", - ["tag"]) - mocked_koji.add_build("rh-postgresql96-3.0-9.el6", - ["tag", "tag-candidate"]) - handler = ErrataAdvisoryRPMsSignedHandler() - tag = handler._get_compose_source('rh-postgresql96-3.0-9.el6') - self.assertEqual('tag-candidate', tag) - - -class TestPrepareYumRepo(helpers.ModelsTestCase): - """Test ErrataAdvisoryRPMsSignedHandler._prepare_yum_repo""" - - def setUp(self): - super(TestPrepareYumRepo, self).setUp() - - self.ev = Event.create(db.session, 'msg-id', '123', 100) - ArtifactBuild.create( - db.session, self.ev, "parent", "image", - state=ArtifactBuildState.PLANNED) - db.session.commit() - - @patch('freshmaker.handlers.errata.errata_advisory_rpms_signed.' - 'create_odcs_client') - @patch('freshmaker.handlers.errata.errata_advisory_rpms_signed.' - 'ErrataAdvisoryRPMsSignedHandler._get_packages_for_compose') - @patch('freshmaker.handlers.errata.errata_advisory_rpms_signed.' - 'ErrataAdvisoryRPMsSignedHandler._get_compose_source') - @patch('time.sleep') - @patch('freshmaker.handlers.errata.errata_advisory_rpms_signed.Errata') - def test_get_repo_url_when_succeed_to_generate_compose( - self, errata, sleep, _get_compose_source, - _get_packages_for_compose, create_odcs_client): - odcs = create_odcs_client.return_value - _get_packages_for_compose.return_value = ['httpd', 'httpd-debuginfo'] - _get_compose_source.return_value = 'rhel-7.2-candidate' - odcs.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", - } - - errata.return_value.get_builds.return_value = set(["httpd-2.4.15-1.f27"]) - - handler = ErrataAdvisoryRPMsSignedHandler() - compose = handler._prepare_yum_repo(self.ev) - - db.session.refresh(self.ev) - self.assertEqual(3, compose['id']) - - _get_compose_source.assert_called_once_with("httpd-2.4.15-1.f27") - _get_packages_for_compose.assert_called_once_with("httpd-2.4.15-1.f27") - - # Ensure new_compose is called to request a new compose - odcs.new_compose.assert_called_once_with( - 'rhel-7.2-candidate', 'tag', packages=['httpd', 'httpd-debuginfo'], - sigkeys=[], flags=["no_deps"]) - - # We should get the right repo URL eventually - self.assertEqual( - "http://localhost/composes/latest-odcs-3-1/compose/Temporary/odcs-3.repo", - compose['result_repofile']) - - @patch('freshmaker.handlers.errata.errata_advisory_rpms_signed.' - 'create_odcs_client') - @patch('freshmaker.handlers.errata.errata_advisory_rpms_signed.' - 'ErrataAdvisoryRPMsSignedHandler._get_packages_for_compose') - @patch('freshmaker.handlers.errata.errata_advisory_rpms_signed.' - 'ErrataAdvisoryRPMsSignedHandler._get_compose_source') - @patch('time.sleep') - @patch('freshmaker.handlers.errata.errata_advisory_rpms_signed.Errata') - def test_get_repo_url_packages_in_multiple_tags( - self, errata, sleep, _get_compose_source, - _get_packages_for_compose, create_odcs_client): - _get_packages_for_compose.return_value = ['httpd', 'httpd-debuginfo'] - _get_compose_source.side_effect = [ - 'rhel-7.2-candidate', 'rhel-7.7-candidate'] - - errata.return_value.get_builds.return_value = [ - set(["httpd-2.4.15-1.f27"]), set(["foo-2.4.15-1.f27"])] - - handler = ErrataAdvisoryRPMsSignedHandler() - repo_url = handler._prepare_yum_repo(self.ev) - - create_odcs_client.return_value.new_compose.assert_not_called() - self.assertEqual(repo_url, None) - - db.session.refresh(self.ev) - for build in self.ev.builds: - self.assertEqual(build.state, ArtifactBuildState.FAILED.value) - self.assertEqual(build.state_reason, "Packages for errata " - "advisory 123 found in multiple different tags.") - - @patch('freshmaker.handlers.errata.errata_advisory_rpms_signed.' - 'create_odcs_client') - @patch('freshmaker.handlers.errata.errata_advisory_rpms_signed.' - 'ErrataAdvisoryRPMsSignedHandler._get_packages_for_compose') - @patch('freshmaker.handlers.errata.errata_advisory_rpms_signed.' - 'ErrataAdvisoryRPMsSignedHandler._get_compose_source') - @patch('time.sleep') - @patch('freshmaker.handlers.errata.errata_advisory_rpms_signed.Errata') - def test_get_repo_url_packages_not_found_in_tag( - self, errata, sleep, _get_compose_source, - _get_packages_for_compose, create_odcs_client): - _get_packages_for_compose.return_value = ['httpd', 'httpd-debuginfo'] - _get_compose_source.return_value = None - - errata.return_value.get_builds.return_value = [ - set(["httpd-2.4.15-1.f27"]), set(["foo-2.4.15-1.f27"])] - - handler = ErrataAdvisoryRPMsSignedHandler() - repo_url = handler._prepare_yum_repo(self.ev) - - create_odcs_client.return_value.new_compose.assert_not_called() - self.assertEqual(repo_url, None) - - db.session.refresh(self.ev) - for build in self.ev.builds: - self.assertEqual(build.state, ArtifactBuildState.FAILED.value) - self.assertTrue(build.state_reason.endswith( - "of advisory 123 is the latest build in its candidate tag.")) - - def _get_fake_container_image(self): - return { - u'rpm_manifest': [ - {u'rpms': [{u'architecture': u'noarch', - u'gpg': u'199e2f91fd431d51', - u'name': u'apache-commons-lang', - u'nvra': u'apache-commons-lang-2.6-15.el7.noarch', - u'release': u'15.el7', - u'srpm_name': u'apache-commons-lang', - u'srpm_nevra': u'apache-commons-lang-0:2.6-15.el7.src', - u'summary': u'Provides a host of helper utilities for the java.lang API', - u'version': u'2.6'}, - {u'architecture': u'noarch', - u'gpg': u'199e2f91fd431d51', - u'name': u'avalon-logkit', - u'nvra': u'avalon-logkit-2.1-14.el7.noarch', - u'release': u'14.el7', - u'srpm_name': u'avalon-logkit', - u'srpm_nevra': u'avalon-logkit-0:2.1-14.el7.src', - u'summary': u'Java logging toolkit', - u'version': u'2.1'}]}]} - - @patch('freshmaker.handlers.errata.errata_advisory_rpms_signed.' - 'create_odcs_client') - @patch('time.sleep') - def test_prepare_odcs_compose_with_image_rpms( - self, sleep, create_odcs_client): - odcs = create_odcs_client.return_value - odcs.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", - } - - image = self._get_fake_container_image() - - handler = ErrataAdvisoryRPMsSignedHandler() - compose = handler._prepare_odcs_compose_with_image_rpms(image) - - db.session.refresh(self.ev) - self.assertEqual(3, compose['id']) - - # Ensure new_compose is called to request a new compose - odcs.new_compose.assert_called_once_with( - '', 'build', builds=set(['avalon-logkit-2.1-14.el7', 'apache-commons-lang-2.6-15.el7']), - flags=['no_deps'], packages=set([u'avalon-logkit', u'apache-commons-lang']), sigkeys=[]) - - def test_prepare_odcs_compose_with_image_rpms_no_rpm_manifest(self): - handler = ErrataAdvisoryRPMsSignedHandler() - - compose = handler._prepare_odcs_compose_with_image_rpms({}) - self.assertEqual(compose, None) - - compose = handler._prepare_odcs_compose_with_image_rpms( - {"rpm_manifest": []}) - self.assertEqual(compose, None) - - compose = handler._prepare_odcs_compose_with_image_rpms( - {"rpm_manifest": [{"rpms": []}]}) - self.assertEqual(compose, None) - - class TestErrataAdvisoryStateChangedHandler(helpers.ModelsTestCase): @patch('freshmaker.errata.Errata.advisories_from_event') @@ -923,11 +685,8 @@ class TestRecordBatchesImages(helpers.ModelsTestCase): 'freshmaker.handlers.errata.ErrataAdvisoryRPMsSignedHandler.') self.mock_prepare_pulp_repo = self.patcher.patch( - '_prepare_pulp_repo', side_effect=[{'id': 1}, {'id': 2}]) - - self.mock_request_boot_iso_compose = self.patcher.patch( - '_request_boot_iso_compose', - side_effect=[{'id': 100}, {'id': 200}]) + '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}) @@ -1067,100 +826,6 @@ class TestRecordBatchesImages(helpers.ModelsTestCase): self.assertEqual(ArtifactBuildState.PLANNED.value, parent_image.state) self.mock_prepare_pulp_repo.assert_not_called() - @unittest.skip('Enable again when enable to request boot.iso compose') - def test_pulp_compose_is_stored_for_each_build(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, - "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, - "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(2, len(parent_build.composes)) - compose_ids = sorted([rel.compose.odcs_compose_id - for rel in parent_build.composes]) - # Ensure both pulp compose id and boot.iso compose id are stored - self.assertEqual([1, 100], compose_ids) - - child_build = query.filter( - ArtifactBuild.original_nvr == 'rh-dotnetcore10-docker-1.0-16' - ).first() - self.assertEqual(1, len(child_build.composes)) - self.assertEqual(2, child_build.composes[0].compose.odcs_compose_id) - - self.mock_prepare_pulp_repo.assert_has_calls([ - call(child_build.event, ["content-set-1"]), - call(child_build.event, ["content-set-1"]) - ]) - - self.mock_request_boot_iso_compose.assert_called_once_with( - batches[0][0]) - def test_pulp_compose_generated_just_once(self): batches = [ [ContainerImage({ @@ -1449,76 +1114,6 @@ class TestRecordBatchesImages(helpers.ModelsTestCase): self.mock_prepare_pulp_repo.assert_not_called() -class TestPrepareYumReposForRebuilds(helpers.ModelsTestCase): - """Test ErrataAdvisoryRPMsSignedHandler._prepare_yum_repos_for_rebuilds""" - - def setUp(self): - super(TestPrepareYumReposForRebuilds, self).setUp() - - self.patcher = helpers.Patcher( - 'freshmaker.handlers.errata.errata_advisory_rpms_signed.' - 'ErrataAdvisoryRPMsSignedHandler.') - - self.mock_prepare_yum_repo = self.patcher.patch( - '_prepare_yum_repo', - side_effect=[ - {'id': 1, 'result_repofile': 'http://localhost/repo/1'}, - {'id': 2, 'result_repofile': 'http://localhost/repo/2'}, - {'id': 3, 'result_repofile': 'http://localhost/repo/3'}, - {'id': 4, 'result_repofile': 'http://localhost/repo/4'}, - ]) - - self.mock_find_dependent_event = self.patcher.patch( - 'freshmaker.models.Event.find_dependent_events') - - 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) - self.build_2 = ArtifactBuild.create( - db.session, self.db_event, 'build-2', ArtifactType.IMAGE) - - db.session.commit() - - def tearDown(self): - super(TestPrepareYumReposForRebuilds, self).tearDown() - self.patcher.unpatch_all() - - def test_prepare_without_dependent_events(self): - self.mock_find_dependent_event.return_value = [] - - handler = ErrataAdvisoryRPMsSignedHandler() - urls = handler._prepare_yum_repos_for_rebuilds(self.db_event) - - self.assertEqual(1, self.build_1.composes[0].compose.id) - self.assertEqual(1, self.build_2.composes[0].compose.id) - self.assertEqual(['http://localhost/repo/1'], urls) - - def test_prepare_with_dependent_events(self): - self.mock_find_dependent_event.return_value = [ - Mock(), Mock(), Mock() - ] - - handler = ErrataAdvisoryRPMsSignedHandler() - urls = handler._prepare_yum_repos_for_rebuilds(self.db_event) - - odcs_compose_ids = [rel.compose.id for rel in self.build_1.composes] - self.assertEqual([1, 2, 3, 4], sorted(odcs_compose_ids)) - - odcs_compose_ids = [rel.compose.id for rel in self.build_2.composes] - self.assertEqual([1, 2, 3, 4], sorted(odcs_compose_ids)) - - self.assertEqual([ - 'http://localhost/repo/1', - 'http://localhost/repo/2', - 'http://localhost/repo/3', - 'http://localhost/repo/4', - ], sorted(urls)) - - class TestSkipNonRPMAdvisory(helpers.FreshmakerTestCase): def test_ensure_to_handle_rpm_adivsory(self): diff --git a/tests/test_odcsclient.py b/tests/test_odcsclient.py index b883e98..983c485 100644 --- a/tests/test_odcsclient.py +++ b/tests/test_odcsclient.py @@ -23,14 +23,29 @@ import six -from mock import patch +from mock import patch, Mock from odcs.client.odcs import AuthMech -from freshmaker import conf +from freshmaker import conf, db +from freshmaker.models import Event, ArtifactBuild from freshmaker.odcsclient import create_odcs_client +from freshmaker.types import ArtifactBuildState, EventState, ArtifactType +from freshmaker.handlers import ContainerBuildHandler from tests import helpers +class MyHandler(ContainerBuildHandler): + """Handler for running tests to test things defined in parents""" + + name = "MyHandler" + + def can_handle(self, event): + """Implement BaseHandler method""" + + def handle(self, event): + """Implement BaseHandler method""" + + class TestCreateODCSClient(helpers.FreshmakerTestCase): """Test odcsclient.create_odcs_client""" @@ -69,3 +84,297 @@ class TestCreateODCSClient(helpers.FreshmakerTestCase): six.assertRaisesRegex( self, ValueError, r'Missing OpenIDC token.*', create_odcs_client) + + +class TestGetPackagesForCompose(helpers.FreshmakerTestCase): + """Test MyHandler._get_packages_for_compose""" + + @helpers.mock_koji + def test_get_packages(self, mocked_koji): + build_nvr = 'chkconfig-1.7.2-1.el7_3.1' + mocked_koji.add_build(build_nvr) + mocked_koji.add_build_rpms( + build_nvr, + [build_nvr, "chkconfig-debuginfo-1.7.2-1.el7_3.1"]) + + handler = MyHandler() + packages = handler.odcs._get_packages_for_compose(build_nvr) + + self.assertEqual(set(['chkconfig', 'chkconfig-debuginfo']), + set(packages)) + + +class TestGetComposeSource(helpers.FreshmakerTestCase): + """Test MyHandler._get_compose_source""" + + @helpers.mock_koji + def test_get_tag(self, mocked_koji): + mocked_koji.add_build("rh-postgresql96-3.0-9.el6") + handler = MyHandler() + tag = handler.odcs._get_compose_source('rh-postgresql96-3.0-9.el6') + self.assertEqual('tag-candidate', tag) + + @helpers.mock_koji + def test_get_None_if_tag_has_new_build(self, mocked_koji): + mocked_koji.add_build("rh-postgresql96-3.0-9.el6") + mocked_koji.add_build("rh-postgresql96-3.0-10.el6") + handler = MyHandler() + tag = handler.odcs._get_compose_source('rh-postgresql96-3.0-9.el6') + self.assertEqual(None, tag) + + @helpers.mock_koji + def test_get_tag_prefer_final_over_candidate(self, mocked_koji): + mocked_koji.add_build("rh-postgresql96-3.0-9.el6", + ["tag-candidate", "tag"]) + handler = MyHandler() + tag = handler.odcs._get_compose_source('rh-postgresql96-3.0-9.el6') + self.assertEqual('tag', tag) + + @helpers.mock_koji + def test_get_tag_fallback_to_second_tag(self, mocked_koji): + mocked_koji.add_build("rh-postgresql96-3.0-10.el6", + ["tag"]) + mocked_koji.add_build("rh-postgresql96-3.0-9.el6", + ["tag", "tag-candidate"]) + handler = MyHandler() + tag = handler.odcs._get_compose_source('rh-postgresql96-3.0-9.el6') + self.assertEqual('tag-candidate', tag) + + +class TestPrepareYumRepo(helpers.ModelsTestCase): + """Test MyHandler._prepare_yum_repo""" + + def setUp(self): + super(TestPrepareYumRepo, self).setUp() + + self.ev = Event.create(db.session, 'msg-id', '123', 100) + ArtifactBuild.create( + db.session, self.ev, "parent", "image", + state=ArtifactBuildState.PLANNED) + db.session.commit() + + @patch('freshmaker.odcsclient.create_odcs_client') + @patch('freshmaker.odcsclient.FreshmakerODCSClient._get_packages_for_compose') + @patch('freshmaker.odcsclient.FreshmakerODCSClient._get_compose_source') + @patch('time.sleep') + @patch('freshmaker.odcsclient.Errata') + def test_get_repo_url_when_succeed_to_generate_compose( + self, errata, sleep, _get_compose_source, + _get_packages_for_compose, create_odcs_client): + odcs = create_odcs_client.return_value + _get_packages_for_compose.return_value = ['httpd', 'httpd-debuginfo'] + _get_compose_source.return_value = 'rhel-7.2-candidate' + odcs.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", + } + + errata.return_value.get_builds.return_value = set(["httpd-2.4.15-1.f27"]) + + handler = MyHandler() + compose = handler.odcs.prepare_yum_repo(self.ev) + + db.session.refresh(self.ev) + self.assertEqual(3, compose['id']) + + _get_compose_source.assert_called_once_with("httpd-2.4.15-1.f27") + _get_packages_for_compose.assert_called_once_with("httpd-2.4.15-1.f27") + + # Ensure new_compose is called to request a new compose + odcs.new_compose.assert_called_once_with( + 'rhel-7.2-candidate', 'tag', packages=['httpd', 'httpd-debuginfo'], + sigkeys=[], flags=["no_deps"]) + + # We should get the right repo URL eventually + self.assertEqual( + "http://localhost/composes/latest-odcs-3-1/compose/Temporary/odcs-3.repo", + compose['result_repofile']) + + @patch('freshmaker.odcsclient.create_odcs_client') + @patch('freshmaker.odcsclient.FreshmakerODCSClient._get_packages_for_compose') + @patch('freshmaker.odcsclient.FreshmakerODCSClient._get_compose_source') + @patch('time.sleep') + @patch('freshmaker.odcsclient.Errata') + def test_get_repo_url_packages_in_multiple_tags( + self, errata, sleep, _get_compose_source, + _get_packages_for_compose, create_odcs_client): + _get_packages_for_compose.return_value = ['httpd', 'httpd-debuginfo'] + _get_compose_source.side_effect = [ + 'rhel-7.2-candidate', 'rhel-7.7-candidate'] + + errata.return_value.get_builds.return_value = [ + set(["httpd-2.4.15-1.f27"]), set(["foo-2.4.15-1.f27"])] + + handler = MyHandler() + repo_url = handler.odcs.prepare_yum_repo(self.ev) + + create_odcs_client.return_value.new_compose.assert_not_called() + self.assertEqual(repo_url, None) + + db.session.refresh(self.ev) + for build in self.ev.builds: + self.assertEqual(build.state, ArtifactBuildState.FAILED.value) + self.assertEqual(build.state_reason, "Packages for errata " + "advisory 123 found in multiple different tags.") + + @patch('freshmaker.odcsclient.create_odcs_client') + @patch('freshmaker.odcsclient.FreshmakerODCSClient._get_packages_for_compose') + @patch('freshmaker.odcsclient.FreshmakerODCSClient._get_compose_source') + @patch('time.sleep') + @patch('freshmaker.odcsclient.Errata') + def test_get_repo_url_packages_not_found_in_tag( + self, errata, sleep, _get_compose_source, + _get_packages_for_compose, create_odcs_client): + _get_packages_for_compose.return_value = ['httpd', 'httpd-debuginfo'] + _get_compose_source.return_value = None + + errata.return_value.get_builds.return_value = [ + set(["httpd-2.4.15-1.f27"]), set(["foo-2.4.15-1.f27"])] + + handler = MyHandler() + repo_url = handler.odcs.prepare_yum_repo(self.ev) + + create_odcs_client.return_value.new_compose.assert_not_called() + self.assertEqual(repo_url, None) + + db.session.refresh(self.ev) + for build in self.ev.builds: + self.assertEqual(build.state, ArtifactBuildState.FAILED.value) + self.assertTrue(build.state_reason.endswith( + "of advisory 123 is the latest build in its candidate tag.")) + + def _get_fake_container_image(self): + return { + u'rpm_manifest': [ + {u'rpms': [{u'architecture': u'noarch', + u'gpg': u'199e2f91fd431d51', + u'name': u'apache-commons-lang', + u'nvra': u'apache-commons-lang-2.6-15.el7.noarch', + u'release': u'15.el7', + u'srpm_name': u'apache-commons-lang', + u'srpm_nevra': u'apache-commons-lang-0:2.6-15.el7.src', + u'summary': u'Provides a host of helper utilities for the java.lang API', + u'version': u'2.6'}, + {u'architecture': u'noarch', + u'gpg': u'199e2f91fd431d51', + u'name': u'avalon-logkit', + u'nvra': u'avalon-logkit-2.1-14.el7.noarch', + u'release': u'14.el7', + u'srpm_name': u'avalon-logkit', + u'srpm_nevra': u'avalon-logkit-0:2.1-14.el7.src', + u'summary': u'Java logging toolkit', + u'version': u'2.1'}]}]} + + @patch('freshmaker.odcsclient.create_odcs_client') + @patch('time.sleep') + def test_prepare_odcs_compose_with_image_rpms( + self, sleep, create_odcs_client): + odcs = create_odcs_client.return_value + odcs.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", + } + + image = self._get_fake_container_image() + + handler = MyHandler() + compose = handler.odcs.prepare_odcs_compose_with_image_rpms(image) + + db.session.refresh(self.ev) + self.assertEqual(3, compose['id']) + + # Ensure new_compose is called to request a new compose + odcs.new_compose.assert_called_once_with( + '', 'build', builds=set(['avalon-logkit-2.1-14.el7', 'apache-commons-lang-2.6-15.el7']), + flags=['no_deps'], packages=set([u'avalon-logkit', u'apache-commons-lang']), sigkeys=[]) + + def test_prepare_odcs_compose_with_image_rpms_no_rpm_manifest(self): + handler = MyHandler() + + compose = handler.odcs.prepare_odcs_compose_with_image_rpms({}) + self.assertEqual(compose, None) + + compose = handler.odcs.prepare_odcs_compose_with_image_rpms( + {"rpm_manifest": []}) + self.assertEqual(compose, None) + + compose = handler.odcs.prepare_odcs_compose_with_image_rpms( + {"rpm_manifest": [{"rpms": []}]}) + self.assertEqual(compose, None) + + +class TestPrepareYumReposForRebuilds(helpers.ModelsTestCase): + """Test MyHandler._prepare_yum_repos_for_rebuilds""" + + def setUp(self): + super(TestPrepareYumReposForRebuilds, self).setUp() + + self.patcher = helpers.Patcher() + + self.mock_prepare_yum_repo = self.patcher.patch( + 'freshmaker.odcsclient.FreshmakerODCSClient.prepare_yum_repo', + side_effect=[ + {'id': 1, 'result_repofile': 'http://localhost/repo/1'}, + {'id': 2, 'result_repofile': 'http://localhost/repo/2'}, + {'id': 3, 'result_repofile': 'http://localhost/repo/3'}, + {'id': 4, 'result_repofile': 'http://localhost/repo/4'}, + ]) + + self.mock_find_dependent_event = self.patcher.patch( + 'freshmaker.models.Event.find_dependent_events') + + self.db_event = Event.create( + db.session, 'msg-1', 'search-key-1', 1, + state=EventState.INITIALIZED, + released=False) + self.build_1 = ArtifactBuild.create( + db.session, self.db_event, 'build-1', ArtifactType.IMAGE) + self.build_2 = ArtifactBuild.create( + db.session, self.db_event, 'build-2', ArtifactType.IMAGE) + + db.session.commit() + + def tearDown(self): + super(TestPrepareYumReposForRebuilds, self).tearDown() + self.patcher.unpatch_all() + + def test_prepare_without_dependent_events(self): + self.mock_find_dependent_event.return_value = [] + + handler = MyHandler() + urls = handler.odcs.prepare_yum_repos_for_rebuilds(self.db_event) + + self.assertEqual(1, self.build_1.composes[0].compose.id) + self.assertEqual(1, self.build_2.composes[0].compose.id) + self.assertEqual(['http://localhost/repo/1'], urls) + + def test_prepare_with_dependent_events(self): + self.mock_find_dependent_event.return_value = [ + Mock(), Mock(), Mock() + ] + + handler = MyHandler() + urls = handler.odcs.prepare_yum_repos_for_rebuilds(self.db_event) + + odcs_compose_ids = [rel.compose.id for rel in self.build_1.composes] + self.assertEqual([1, 2, 3, 4], sorted(odcs_compose_ids)) + + odcs_compose_ids = [rel.compose.id for rel in self.build_2.composes] + self.assertEqual([1, 2, 3, 4], sorted(odcs_compose_ids)) + + self.assertEqual([ + 'http://localhost/repo/1', + 'http://localhost/repo/2', + 'http://localhost/repo/3', + 'http://localhost/repo/4', + ], sorted(urls))