#323 Move ODCS code from ErrataAdvisoryRPPMsSignedhandler to FreshmakerODCSClient.
Merged 5 years ago by jkaluza. Opened 5 years ago by jkaluza.
jkaluza/freshmaker odcs-refactor  into  master

@@ -35,7 +35,7 @@ 

  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 @@ 

          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):

          """

@@ -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.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 @@ 

  

      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 @@ 

              # 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 @@ 

  

          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 @@ 

              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 @@ 

                          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 @@ 

                      # 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 @@ 

                              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.

file modified
+1 -1
@@ -35,7 +35,6 @@ 

  

  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 @@ 

  

      @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']

file modified
+303 -2
@@ -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 @@ 

          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

@@ -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 @@ 

              '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 @@ 

  

      @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 @@ 

  

      @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 @@ 

          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):

@@ -21,7 +21,6 @@ 

  #

  # Written by Chenxiong Qi <cqi@redhat.com>

  

- import unittest

  import json

  

  from mock import patch, PropertyMock, Mock, call
@@ -326,7 +325,7 @@ 

              "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 @@ 

              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 @@ 

              '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 @@ 

          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 @@ 

          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):

file modified
+311 -2
@@ -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 @@ 

          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))

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.

:+1: Looks reasonable to me.

Commit aa1788b fixes this pull-request

Pull-Request has been merged by jkaluza

5 years ago

Pull-Request has been merged by jkaluza

5 years ago