#17 Updating existing inheritance instead of removing and adding
Merged 4 years ago by qwan. Opened 4 years ago by qwan.

@@ -142,7 +142,9 @@ 

                  'priority': 10

              },

              {

+                 'child_id': 99,

                  'parent_id': 100,

+                 'name': "module-mariadb-10.4-3020190313091759-a5b0195c",

                  'priority': 50,

                  'maxdepth': None,

                  'intransitive': False,
@@ -172,6 +174,8 @@ 

                  'priority': 10

              },

              {

+                 'child_id': 99,

+                 'name': 'module-mariadb-10.4-3020190313091759-a5b0195c',

                  'parent_id': 100,

                  'priority': 50,

                  'maxdepth': None,
@@ -258,7 +262,9 @@ 

                  'delete link': True,

              },

              {

+                 'child_id': 99,

                  'parent_id': 100,

+                 'name': 'module-mariadb-10.4-3020190313091759-a5b0195c',

                  'priority': 50,

                  'maxdepth': None,

                  'intransitive': False,
@@ -331,7 +337,9 @@ 

              },

              # This is the koji_tag of the latest module mariadb:10.4 to add

              {

+                 'child_id': 99,

                  'parent_id': 100,

+                 'name': 'module-mariadb-10.4-3020190313091759-a5b0195c',

                  'priority': 50,

                  'maxdepth': None,

                  'intransitive': False,

file modified
+16 -5
@@ -140,6 +140,14 @@ 

          session = koji.ClientSession.return_value

          session.getInheritanceData.side_effect = self.fake_get_inheritance_data

  

+         def fake_update_tag_inheritance(tag, add_tags=None, remove_tags=None):

+             if tag == "rhel-8.0-build":

+                 return (['module-123456'], ['module-908765'])

+             elif tag == "other-8.0-tag":

+                 return (['module-123456'], [])

+ 

+         update_tag_inheritance.side_effect = fake_update_tag_inheritance

+ 

          self.handler._mbs.get_module_mmd.return_value = make_mmd(

              'autotools', 'rhel-8.0', '20180101', '123456',

              {'platform': ['rhel-8.0']})
@@ -171,11 +179,11 @@ 

          rhel_80_build_data = {

              'removed_tags': ['module-908765'],

              'tag': 'rhel-8.0-build',

-             'added_tags': [u'module-123456'],

+             'added_tags': ['module-123456'],

              'module': {

                  'build_id': 123,

                  'nsvc': 'autotools:rhel-8.0:20180101:123456',

-                 'koji_tag': u'module-123456',

+                 'koji_tag': 'module-123456',

                  'requires': {'platform': ['rhel-8.0']},

              },

              'build_url': build_url,
@@ -184,18 +192,18 @@ 

          other_80_tag_data = {

              'removed_tags': [],

              'tag': 'other-8.0-tag',

-             'added_tags': [u'module-123456'],

+             'added_tags': ['module-123456'],

              'module': {

                  'build_id': 123,

                  'nsvc': 'autotools:rhel-8.0:20180101:123456',

-                 'koji_tag': u'module-123456',

+                 'koji_tag': 'module-123456',

                  'requires': {'platform': ['rhel-8.0']},

              },

              'build_url': build_url,

          }

  

          self.handler.mail_api.send_mail.assert_called()

- 

+         self.assertEqual(u'test', 'test')

          self.handler.mail_api.send_mail.assert_has_calls(

              [mock.call('add-tag.txt',

                         ['foo@example.com'],
@@ -379,6 +387,9 @@ 

          self.handler._koji.get_inheritance_data.return_value = \

              inheritance_tags

  

+         self.handler._koji.update_tag_inheritance.return_value = (

+             ['module-0c8d21d5c89ec363'], ['module-358b0c179d25310c'])

+ 

          module_config = ModuleConfig({

              'name': 'testmodule', 'stream': 'rhel-8.0',

              'priority': 10

file modified
+24 -7
@@ -363,14 +363,31 @@ 

          )

  

          add_tags = [{'name': 'f27-buildrequires', 'priority': 50}]

-         with self.assertRaises(RuntimeError) as ctx:

-             self.koji.update_tag_inheritance('f27-build', add_tags)

+         added_tags, removed_tags = self.koji.update_tag_inheritance('f27-build', add_tags)

  

-         self.assertIn(

-             ("Tag f27-buildrequires already exists in tag f27-build's "

-              "inheritance"),

-             str(ctx.exception))

-         self.koji.koji_proxy.setInheritanceData.assert_not_called()

+         self.koji.koji_proxy.setInheritanceData.called_with(

+             3,

+             [

+                 {'intransitive': False,

+                  'name': 'f27-buildrequires',

+                  'pkg_filter': '',

+                  'priority': 50,

+                  'parent_id': 4,

+                  'maxdepth': None,

+                  'noconfig': False,

+                  'child_id': 3},

+                 {'intransitive': False,

+                  'parent_id': 12,

+                  'maxdepth': None,

+                  'noconfig': False,

+                  'child_id': 3,

+                  'pkg_filter': '',

+                  'priority': 30,

+                  'name': 'f27-go-toolset-candidate'}

+             ]

+         )

+         self.assertEqual(added_tags, ['f27-buildrequires'])

+         self.assertEqual(removed_tags, [])

  

      @mock.patch('ursa_major.koji_service.log')

      def test_update_tag_inheritance_with_removing_non_exist_tag(self, log):

file modified
+20 -13
@@ -149,30 +149,37 @@ 

          :rtype: tuple[list[str], list[str]]

          """

          old_tags = self.find_module_tags_from_koji_inheritance(tag, module_config)

+ 

+         # if this is a tag already exists, we will try to update it

+         if modinfo.koji_tag in old_tags:

+             old_tags.remove(modinfo.koji_tag)

+ 

          add_tag_info = {

              'name': modinfo.koji_tag,

              'priority': module_config.priority

          }

  

          self.koji.login()

-         self.koji.update_tag_inheritance(

+         added_tags, removed_tags = self.koji.update_tag_inheritance(

              tag,

              add_tags=[add_tag_info],

              remove_tags=[{'name': name} for name in old_tags]

          )

  

-         if self.koji.is_build_tag(tag):

-             build_tags = [tag]

-         else:

-             build_tags = self.koji.find_build_tags(tag)

+         # regen repo for build tags only when inheritance data is updated

+         if added_tags or removed_tags:

+             if self.koji.is_build_tag(tag):

+                 build_tags = [tag]

+             else:

+                 build_tags = self.koji.find_build_tags(tag)

  

-         if not build_tags:

-             log.warning("No build tag found for %s, not regen any repo", tag)

-         else:

-             log.info("Found build tags for '%s': %r", tag, build_tags)

-             self.koji.regen_repos(build_tags, wait=self.wait_regen_repo)

+             if not build_tags:

+                 log.warning("No build tag found for %s, not regen any repo", tag)

+             else:

+                 log.info("Found build tags for '%s': %r", tag, build_tags)

+                 self.koji.regen_repos(build_tags, wait=self.wait_regen_repo)

  

-         return [modinfo.koji_tag], old_tags

+         return added_tags, removed_tags

  

      def run(self):

          try:
@@ -235,14 +242,14 @@ 

                  if self.dry_run or not tag_owners:

                      continue

  

-                 if self.send_mail and tag_owners:

+                 if (added_tags or removed_tags) and self.send_mail and tag_owners:

                      # send tag update success mail

                      mail_subject = 'Tag {} is updated'.format(tag)

                      content = copy.deepcopy(self.mail_data)

                      content['tag'] = tag

                      content['added_tags'] = added_tags

                      content['removed_tags'] = removed_tags

-                     log.info("Sending mail to %r with content:\n%r", tag_owners, content)

+                     log.info("Sending mail to %r with content:\n%r", str(tag_owners), content)

                      self.mail_api.send_mail('add-tag.txt', tag_owners,

                                              subject=mail_subject, data=content)

  

file modified
+52 -73
@@ -255,7 +255,7 @@ 

          else:

              return self.koji_proxy.setInheritanceData(tag, data)

  

-     def update_tag_inheritance(self, tag, add_tags=None, remove_tags=None, edit_tags=None):

+     def update_tag_inheritance(self, tag, add_tags=None, remove_tags=None):

          """Update tag inheritance data with specified data of tags.

  

          :param tag:         id or name of child tag
@@ -263,100 +263,72 @@ 

                              dict containing at least tag name or id

          :param remove_tags: list of tags to be removed, each tag is a dict

                              containing at least tag name or id

-         :param edit_tags:   list of tags to be edited, each tag is a dict

-                             containing at least tag name or id, and tags in

-                             list should already exist in inheritance data

+         :return: a tuple of (added_tags, removed_tags)

+         :rtype: tuple[list[str], list[str]]

          """

+         # lists to store the added/updated and removed tags, return them

+         # after at the end

+         added_tags_list = []

+         removed_tags_list = []

+ 

          child_tag = self.get_tag(tag)

          if not child_tag:

              raise RuntimeError("Unknown koji tag: {}".format(tag))

  

-         if add_tags is None and remove_tags is None and edit_tags is None:

+         if add_tags is None and remove_tags is None:

              log.info("There is no update data specified for updating tag "

                       "inheritance of '%s', do nothing", tag)

              return

  

          inheritance_data = self.get_inheritance_data(child_tag['id'])

+         log.debug("Inheritance data of tag %s:\n%s",

+                   child_tag['name'], json.dumps(inheritance_data, indent=2))

+         parent_ids = [d['parent_id'] for d in inheritance_data]

  

-         # update inheritance data for tags need to be removed or updated

-         # do this before add new parent tags because we can add tags

-         # to replace some old tags with the same priority

- 

-         # remove tags are also tags to be edited, we add these tags to

-         # edit_tags, then handle them in the similar way

- 

-         if edit_tags is None:

-             edit_tags = []

- 

-         if remove_tags is None:

-             remove_tags = []

-         for remove_tag in remove_tags:

-             edit_tags.append(remove_tag)

- 

-         if edit_tags:

-             for tag in edit_tags:

+         if remove_tags is not None:

+             for tag in remove_tags:

                  tag_name_or_id = tag.get('name') or tag.get('id')

-                 edit_tag = self.get_tag(tag_name_or_id)

-                 if not edit_tag:

+                 remove_tag = self.get_tag(tag_name_or_id)

+                 if not remove_tag:

                      raise RuntimeError("Tag {} doesn't exist".format(tag_name_or_id))

  

-                 data = [d for d in inheritance_data

-                         if d['parent_id'] == edit_tag['id']]

- 

-                 if len(data) == 0:

-                     msg = "Tag {} not found in inheritance.".format(edit_tag['name'])

-                     log.error(msg)

-                     raise RuntimeError(msg)

-                 if len(data) > 1:

-                     msg = "Multiple tag {} found in inheritance".format(edit_tag['name'])

+                 if remove_tag['id'] not in parent_ids:

+                     msg = "Tag {} not found in inheritance.".format(tag_name_or_id)

                      log.error(msg)

                      raise RuntimeError(msg)

  

-                 data = data[0]

- 

-                 edit_data = data.copy()

-                 if tag in remove_tags:

-                     edit_data['delete link'] = True

-                 else:

-                     valid_keys = ['priority', 'maxdepth', 'intransitive',

-                                   'noconfig', 'pkg_filter']

-                     for key in valid_keys:

-                         if key in tag.keys():

-                             edit_data[key] = tag.get(key)

- 

-                     same_priority = [d for d in inheritance_data if

-                                      d['priority'] == edit_data['priority'] and

-                                      not d.get('delete link', False) and

-                                      d['name'] != edit_tag['name']]

-                     if same_priority:

-                         msg = ("Tag {} has an inheritance with the same priority "

-                                "{} as tag {}".format(child_tag['name'],

-                                                      edit_data['priority'],

-                                                      edit_tag['name']))

-                         log.error(msg)

-                         raise RuntimeError(msg)

+                 data = next(d for d in inheritance_data if d['parent_id'] == remove_tag['id'])

  

+                 remove_data = data.copy()

+                 remove_data['delete link'] = True

                  index = inheritance_data.index(data)

-                 # update the tag to be edited with updated tag data

-                 inheritance_data[index] = edit_data

+                 inheritance_data[index] = remove_data

+                 removed_tags_list.append(remove_data['name'])

  

          if add_tags is not None:

-             # update inheritance data for tags need to be added

+             # update inheritance data for tags need to be added or updated

              for tag in add_tags:

                  add_tag = self.get_tag(tag.get('name') or tag.get('id'))

                  if not add_tag:

                      raise RuntimeError("Tag {} doesn't exist".format(tag))

  

+                 # construct the inheritance data with all fields in koji so

+                 # we can compare later if this tag already exists

                  add_data = {}

+                 add_data['child_id'] = child_tag['id']

                  add_data['parent_id'] = add_tag.get('id')

+                 add_data['name'] = add_tag.get('name')

                  add_data['priority'] = tag.get('priority', 0)

                  add_data['maxdepth'] = tag.get('maxdepth', None)

                  add_data['intransitive'] = tag.get('intransitive', False)

                  add_data['noconfig'] = tag.get('noconfig', False)

                  add_data['pkg_filter'] = tag.get('pkg_filter', '')

  

+                 # check priority conflict, filter out the existing same tag

+                 # and the tags we're going to remove

                  same_priority = [d for d in inheritance_data if

                                   d['priority'] == add_data['priority'] and

+                                  d['parent_id'] != add_data['parent_id'] and

                                   not d.get('delete link', False)]

                  if same_priority:

                      # TODO: should we raise error or just warn
@@ -366,20 +338,27 @@ 

                                                   add_tag['name']))

                      log.warning(msg)

  

-                 same_parent = [d for d in inheritance_data if

-                                d['parent_id'] == add_data['parent_id'] and

-                                not d.get('delete link', False)]

-                 if same_parent:

-                     raise RuntimeError(

-                         "Tag %s already exists in tag %s's inheritance" %

-                         (add_tag['name'], child_tag['name']))

- 

-                 inheritance_data.append(add_data)

- 

-         log.info(

-             'Update inheritance of tag %s with inheritance data: %s',

-             child_tag['name'], json.dumps(inheritance_data, indent=2))

-         self.set_inheritance_data(child_tag['id'], inheritance_data)

+                 if add_data['parent_id'] in parent_ids:

+                     # this is a tag already exists in inheritance data

+                     # we're going to update its data

+                     data = next(d for d in inheritance_data

+                                 if d['parent_id'] == add_data['parent_id'])

+                     if data == add_data:

+                         log.info("Inheritance %s has same data as existing, skipping.", add_data)

+                     else:

+                         index = inheritance_data.index(data)

+                         inheritance_data[index] = add_data

+                         added_tags_list.append(add_data['name'])

+                 else:

+                     inheritance_data.append(add_data)

+                     added_tags_list.append(add_data['name'])

+ 

+         if added_tags_list or removed_tags_list:

+             log.info(

+                 'Update inheritance of tag %s with inheritance data: %s',

+                 child_tag['name'], json.dumps(inheritance_data, indent=2))

+             self.set_inheritance_data(child_tag['id'], inheritance_data)

+         return (added_tags_list, removed_tags_list)

  

      def remove_tags_from_inheritance(self, tag, tags):

          """ Remove tags from a tag's inheritance data.

Before this fix, We remove the old inheritance no matter it's same
as the new module build or not, and add new module build tag with
new attributions in the same one koji call. For example, if there is a
go-toolset module build exists in inhertiance of tag
'rhel-8-go-toolset-build', like:

rhel-8-go-toolset-buildroot (12001)
└─ module-go-toolset-rhel8-20190501-123456 (20020)

then when we re-add module-go-toolset-rhel8-20190501-123456 to tag's
inheritance data, we update the inheritance with:

[
  {
    "pkg_filter": "",
    "name": "module-go-toolset-rhel8-20190501-123456",
    "delete link": true,
    "intransitive": false,
    "parent_id": 20020,
    "maxdepth": null,
    "noconfig": false,
    "child_id": 12001,
    "priority": 10
  },
  {
    "pkg_filter": "",
    "intransitive": false,
    "priority": 10,
    "parent_id": 20020,
    "maxdepth": null,
    "noconfig": false
  }
]

the 2 tags in inheritance data are just the same tag, one is for
removing (with "delete link" is true), and another is for adding.

This worked before, but now koji only removes the tag from inheritance
data.

In this fix, when the new module build is already in inheritance,
and we're going to add it with different attributions as exsiting
(priorities and etc), we update old inheritance with new attributions.
If new attributions are same as before, we just skip and not update
inheritance data.

rebased onto 63388d8

4 years ago

First time reviewing ursa-major PR, but it looks correct to me, +1.

Pull-Request has been merged by qwan

4 years ago