Repository: fedora-infra/bodhi

Issue:
updates created from side tags are not automatically submitted for testing like single-package updates
After creating updates for stable Fedora releases, e.g.:
https://bodhi.fedoraproject.org/updates/FEDORA-2020-c5e96618e7
https://bodhi.fedoraproject.org/updates/FEDORA-2020-4163779303
I expected they would be submitted to testing automatically, like single-package updates. After two days I noticed they were still in "pending" state and found I had to submit them for testing manually.
This is a surprising difference and not easily discoverable if it's the first time you're doing it. Please make the experience consistent with single-package updates.


Discussion:

Resolving patch:
diff --git a/bodhi/server/consumers/signed.py b/bodhi/server/consumers/signed.py
--- a/bodhi/server/consumers/signed.py
+++ b/bodhi/server/consumers/signed.py
@@ -28,7 +28,7 @@
 from sqlalchemy import func
 
 from bodhi.server.config import config
-from bodhi.server.models import Build, UpdateStatus, TestGatingStatus
+from bodhi.server.models import Build, UpdateRequest, UpdateStatus, TestGatingStatus
 from bodhi.server.util import transactional_session_maker
 
 log = logging.getLogger('bodhi')
@@ -90,16 +90,15 @@ def __call__(self, message: fedora_messaging.api.Message):
                 return
 
             if build.update \
-               and build.update.from_tag \
-               and not build.update.release.composed_by_bodhi:
+                    and build.update.from_tag \
+                    and not build.update.release.composed_by_bodhi:
                 koji_testing_tag = build.release.get_pending_testing_side_tag(build.update.from_tag)
                 if tag != koji_testing_tag:
                     log.info("Tag is not testing side tag, skipping")
                     return
-            else:
-                if build.release.pending_testing_tag != tag:
-                    log.info("Tag is not pending_testing tag, skipping")
-                    return
+            elif build.release.pending_testing_tag != tag:
+                log.info("Tag is not pending_testing tag, skipping")
+                return
 
             if build.signed:
                 log.info("Build was already marked as signed (maybe a duplicate message)")
@@ -113,7 +112,17 @@ def __call__(self, message: fedora_messaging.api.Message):
             dbsession.flush()
             log.info("Build %s has been marked as signed" % build_nvr)
 
-            # If every build in update is signed change status to testing
+            # Finally, set request to testing for non-rawhide side-tag updates
+            if build.update \
+                    and build.update.release.composed_by_bodhi \
+                    and build.update.from_tag \
+                    and build.update.signed:
+                log.info(f"Setting request for new side-tag update {build.update.alias}.")
+                req = UpdateRequest.testing
+                build.update.set_request(dbsession, req, 'bodhi')
+                return
+
+            # For rawhide updates, if every build in update is signed change status to testing
             if build.update \
                     and not build.update.release.composed_by_bodhi \
                     and build.update.signed:
@@ -130,4 +139,4 @@ def __call__(self, message: fedora_messaging.api.Message):
                     build.update.test_gating_status = TestGatingStatus.waiting
                     build.update.update_test_gating_status()
 
-                log.info(f"Update {build.update.display_name} status has been set to testing")
+                log.info(f"Update {build.update.alias} status has been set to testing")
diff --git a/bodhi/server/models.py b/bodhi/server/models.py
--- a/bodhi/server/models.py
+++ b/bodhi/server/models.py
@@ -2337,6 +2337,7 @@ def new(cls, request, data):
         log.debug(f"Triggering db commit for new update {up.alias}.")
         db.commit()
 
+        # The request to testing for side-tag updates is set within the signed consumer
         if not data.get("from_tag"):
             log.debug(f"Setting request for new update {up.alias}.")
             up.set_request(db, req, request.user.name)
@@ -2920,7 +2921,8 @@ def set_request(self, db, action, username):
 
         # Add the appropriate 'pending' koji tag to this update, so tools like
         # AutoQA can compose repositories of them for testing.
-        if action is UpdateRequest.testing:
+        # If it's a new side-tag update, koji tags are managed by the celery task
+        if action is UpdateRequest.testing and not self.from_tag:
             self.add_tag(self.release.pending_signing_tag)
         elif action is UpdateRequest.stable:
             self.add_tag(self.release.pending_stable_tag)


Test patch:
diff --git a/bodhi/tests/server/consumers/test_signed.py b/bodhi/tests/server/consumers/test_signed.py
--- a/bodhi/tests/server/consumers/test_signed.py
+++ b/bodhi/tests/server/consumers/test_signed.py
@@ -24,7 +24,7 @@
 from bodhi.messages.schemas import update as update_schemas
 from bodhi.server.config import config
 from bodhi.server.consumers import signed
-from bodhi.server.models import Build, Update, UpdateStatus, TestGatingStatus
+from bodhi.server.models import Build, Update, UpdateRequest, UpdateStatus, TestGatingStatus
 from bodhi.tests.server import base
 
 
@@ -102,8 +102,9 @@ def test_consume_build_with_no_update(self, mock_build_model):
         self.handler(self.sample_message)
         assert build.signed is True
 
+    @mock.patch('bodhi.server.consumers.signed.log')
     @mock.patch('bodhi.server.consumers.signed.Build')
-    def test_consume_not_pending_testing_tag(self, mock_build_model):
+    def test_consume_not_pending_testing_tag(self, mock_build_model, mock_log):
         """
         Assert that messages whose tag don't match the pending testing tag don't update the DB
         """
@@ -115,6 +116,9 @@ def test_consume_not_pending_testing_tag(self, mock_build_model):
         build.release.pending_testing_tag = "some tag that isn't pending testing"
 
         self.handler(self.sample_message)
+        mock_log.info.assert_called_with(
+            "Tag is not pending_testing tag, skipping")
+        assert mock_log.info.call_count == 2
         assert build.signed is False
 
     @mock.patch('bodhi.server.consumers.signed.Build')
@@ -162,18 +166,19 @@ def test_consume_duplicate(self, mock_build_model, mock_log):
     def test_consume_from_tag_wrong_tag(self, mock_build_model, mock_log):
         """
         Assert that messages about builds from side tag updates are skipped
-        when tag is not correct.
+        when tag is not correct (rawhide).
         """
         build = mock_build_model.get.return_value
         build.signed = False
         build.release = mock.MagicMock()
         update = mock.MagicMock()
         update.from_tag = "f30-side-tag-unknown"
+        update.release.composed_by_bodhi = False
         build.update = update
 
         self.handler(self.sample_side_tag_message)
         mock_log.info.assert_called_with(
-            "Tag is not pending_testing tag, skipping")
+            "Tag is not testing side tag, skipping")
         assert mock_log.info.call_count == 2
 
     @mock.patch('bodhi.server.consumers.signed.Build')
@@ -245,3 +250,41 @@ def test_consume_from_tag(self):
         assert update.status == UpdateStatus.testing
         assert update.pushed is True
         assert update.test_gating_status == TestGatingStatus.passed
+
+    @mock.patch.dict(config, [('test_gating.required', True)])
+    @mock.patch('bodhi.server.models.work_on_bugs_task', mock.Mock())
+    @mock.patch('bodhi.server.models.fetch_test_cases_task', mock.Mock())
+    @mock.patch('bodhi.server.models.Update.add_tag')
+    def test_consume_from_tag_composed_by_bodhi(self, add_tag):
+        """
+        Assert that update created from tag for a release composed by bodhi
+        is handled correctly when message is received.
+        The update request should be set to 'testing' so that the next composer run
+        will change the update status.
+        """
+        self.handler.db_factory = base.TransactionalSessionMaker(self.Session)
+        update = self.db.query(Update).filter(Build.nvr == 'bodhi-2.0-1.fc17').one()
+        update.from_tag = "f30-side-tag"
+        update.status = UpdateStatus.pending
+        update.request = None
+        update.release.composed_by_bodhi = True
+        update.release.pending_testing_tag = "f30-testing-pending"
+        update.builds[0].signed = False
+        update.pushed = False
+
+        self.db.commit()
+        with mock.patch('bodhi.server.models.util.greenwave_api_post') as mock_greenwave:
+            greenwave_response = {
+                'policies_satisfied': True,
+                'summary': "all tests have passed"
+            }
+            mock_greenwave.return_value = greenwave_response
+            with fml_testing.mock_sends(update_schemas.UpdateRequestTestingV1):
+                self.handler(self.sample_side_tag_message_2)
+
+        assert update.builds[0].signed is True
+        assert update.builds[0].update.request == UpdateRequest.testing
+        assert update.status == UpdateStatus.pending
+        assert update.pushed is False
+        assert update.test_gating_status == TestGatingStatus.passed
+        assert add_tag.not_called()