Repository: fedora-infra/bodhi

Issue:
bodhi didn't obsolete an update properly
These two updates were submitted about a week apart, yet the later for some reason didn't obsolete the former. This then causes the push for f22-updates testing to randomly fail. It worked on11/01 -> 13/01 this week, but then failed on 14/01 because libvisio-0.1.4-1.fc22 was "unsigned" with the error " ERROR: can't download libvisio-None:0.1.4-1.fc22.src from signed path /mnt/koji/packages/libvisio/0.1.4/1.fc22/data/signed/8e1431d5/src/libvisio-0.1.4-1.fc22.src.rpm". No idea why this randomly comes up. I've seen it a few times before. The systemd logs from today (15/01) have details but nothing really of note other than the signing errors.

https://bodhi.fedoraproject.org/updates/libvisio-0.1.4-1.fc22
https://bodhi.fedoraproject.org/updates/libvisio-0.1.5-1.fc22



Discussion:
Another example:

https://bodhi.fedoraproject.org/updates/tlp-0.8-1.el7
https://bodhi.fedoraproject.org/updates/tlp-0.8-2.el7

Another day another repeat. These ones only a month so not pre bodhi2
https://bodhi.fedoraproject.org/updates/nodejs-through2-0.6.3-4.fc23
https://bodhi.fedoraproject.org/updates/nodejs-through2-0.6.3-5.fc23

https://bodhi.fedoraproject.org/updates/nodejs-stream-spigot-3.0.5-4.fc23
https://bodhi.fedoraproject.org/updates/nodejs-stream-spigot-3.0.5-5.fc23


Resolving patch:
diff --git a/bodhi/consumers/masher.py b/bodhi/consumers/masher.py
--- a/bodhi/consumers/masher.py
+++ b/bodhi/consumers/masher.py
@@ -377,7 +377,10 @@ def work(self):
             self.success = True
             self.remove_state()
             self.unlock_updates()
+
             self.check_all_karma_thresholds()
+            self.obsolete_older_updates()
+
         except:
             self.log.exception('Exception in MasherThread(%s)' % self.id)
             self.save_state()
@@ -409,14 +412,22 @@ def check_all_karma_thresholds(self):
         the karma thresholds
         """
         if self.request is UpdateRequest.testing:
-            log.info('Determing if any testing updates reached the karma '
-                     'thresholds during the push')
+            self.log.info('Determing if any testing updates reached the karma '
+                          'thresholds during the push')
             for update in self.updates:
                 try:
                     update.check_karma_thresholds(self.db, agent=u'bodhi')
                 except BodhiException:
                     self.log.exception('Problem checking karma thresholds')
 
+    def obsolete_older_updates(self):
+        """
+        Obsolete any older updates that may still be lying around.
+        """
+        self.log.info('Checking for obsolete updates')
+        for update in self.updates:
+            update.obsolete_older_updates(self.db)
+
     def verify_updates(self):
         for update in list(self.updates):
             if update.request is not self.request:
diff --git a/bodhi/models/models.py b/bodhi/models/models.py
--- a/bodhi/models/models.py
+++ b/bodhi/models/models.py
@@ -867,14 +867,12 @@ def edit(cls, request, data):
 
         return up, caveats
 
-    def obsolete_older_updates(self, request):
+    def obsolete_older_updates(self, db):
         """Obsolete any older pending/testing updates.
 
         If a build is associated with multiple updates, make sure that
         all updates are safe to obsolete, or else just skip it.
         """
-        db = request.db
-        buildinfo = request.buildinfo
         caveats = []
         for build in self.builds:
             for oldBuild in db.query(Build).join(Update).filter(
@@ -888,7 +886,7 @@ def obsolete_older_updates(self, request):
                          Update.status == UpdateStatus.pending))
             ).all():
                 obsoletable = False
-                nvr = buildinfo[build.nvr]['nvr']
+                nvr = get_nvr(build.nvr)
                 if rpm.labelCompare(get_nvr(oldBuild.nvr), nvr) < 0:
                     log.debug("%s is newer than %s" % (nvr, oldBuild.nvr))
                     obsoletable = True
diff --git a/bodhi/services/updates.py b/bodhi/services/updates.py
--- a/bodhi/services/updates.py
+++ b/bodhi/services/updates.py
@@ -419,7 +419,7 @@ def new_update(request):
 
     for update in updates:
         try:
-            caveats.extend(update.obsolete_older_updates(request))
+            caveats.extend(update.obsolete_older_updates(request.db))
         except Exception as e:
             caveats.append({
                 'name': 'update',


Test patch:
diff --git a/bodhi/tests/test_masher.py b/bodhi/tests/test_masher.py
--- a/bodhi/tests/test_masher.py
+++ b/bodhi/tests/test_masher.py
@@ -1020,3 +1020,47 @@ def test_push_timestamps(self, publish, *args):
             up = session.query(Update).one()
             self.assertIsNone(up.request)
             self.assertIsNotNone(up.date_stable)
+
+    @mock.patch(**mock_taskotron_results)
+    @mock.patch('bodhi.consumers.masher.MasherThread.update_comps')
+    @mock.patch('bodhi.consumers.masher.MashThread.run')
+    @mock.patch('bodhi.consumers.masher.MasherThread.wait_for_mash')
+    @mock.patch('bodhi.consumers.masher.MasherThread.sanity_check_repo')
+    @mock.patch('bodhi.consumers.masher.MasherThread.stage_repo')
+    @mock.patch('bodhi.consumers.masher.MasherThread.generate_updateinfo')
+    @mock.patch('bodhi.consumers.masher.MasherThread.wait_for_sync')
+    @mock.patch('bodhi.notifications.publish')
+    def test_obsolete_older_updates(self, publish, *args):
+        otherbuild = 'bodhi-2.0-2.fc17'
+        oldbuild = None
+        self.msg['body']['msg']['updates'].insert(0, otherbuild)
+
+        with self.db_factory() as session:
+            # Put the older update into testing
+            oldupdate = session.query(Update).one()
+            oldbuild = oldupdate.builds[0].nvr
+            oldupdate.status = UpdateStatus.testing
+            oldupdate.request = None
+            oldupdate.locked = False
+
+            # Create a newer build
+            build = Build(nvr=otherbuild, package=oldupdate.builds[0].package)
+            session.add(build)
+            update = Update(title=otherbuild, builds=[build], type=UpdateType.bugfix,
+                    request=UpdateRequest.testing, notes=u'second update',
+                    user=oldupdate.user, release=oldupdate.release)
+            session.add(update)
+            session.flush()
+
+        self.masher.consume(self.msg)
+
+        with self.db_factory() as session:
+            # Ensure that the older update got obsoleted
+            up = session.query(Update).filter_by(title=oldbuild).one()
+            self.assertEquals(up.status, UpdateStatus.obsolete)
+            self.assertEquals(up.request, None)
+
+            # The latest update should be in testing
+            up = session.query(Update).filter_by(title=otherbuild).one()
+            self.assertEquals(up.status, UpdateStatus.testing)
+            self.assertEquals(up.request, None)