Repository: fedora-infra/bodhi

Issue:
Do not allow users to set stable request on side-tag rawhide updates
Example: https://pagure.io/releng/issue/10832

Usually, side-tag rawhide updates are pushed to stable instantly after creation, but the above example pointed out a special case.

1. as critpath update, it wasn't pushed to stable due to failing gating tests
2. a negative comment disabled autopush
3. the update was edited so gating tests were passing, but still it wasn't pushed to stable because of 2. At this state, it should have been edited to re-enable autopush and wait for the approve_testing task, but no one did.
4. a user manually set request to push it to stable (I assume by CLI, as webUI shouldn't show the option - @AdamWill do you remember how you set the request? it was by CLI or webUI?)
5. the update was edited so that autopush was re-enabled. But the approve_testing task doesn't process updates with pending requests. And as rawhide update, the existing request wasn't process by composer neither. So the update was stuck.



Discussion:
I used the CLI, yeah. There are a few cases where you can do things with the CLI that you can't do with the web UI.

Resolving patch:
diff --git a/bodhi-server/bodhi/server/models.py b/bodhi-server/bodhi/server/models.py
--- a/bodhi-server/bodhi/server/models.py
+++ b/bodhi-server/bodhi/server/models.py
@@ -2996,6 +2996,11 @@ def set_request(self, db, action, username):
             raise LockedUpdateException("Can't change the request on a "
                                         "locked update")
 
+        if not self.release.composed_by_bodhi \
+                and action == (UpdateRequest.stable or UpdateRequest.testing):
+            raise BodhiException('Setting a request on an Update for a Release '
+                                 'not composed by Bodhi is not allowed')
+
         if action is UpdateRequest.unpush:
             self.unpush(db)
             self.comment(db, u'This update has been unpushed.', author=username)
@@ -3801,14 +3806,17 @@ def check_karma_thresholds(self, db, agent):
         if self.status not in (UpdateStatus.testing, UpdateStatus.pending):
             return
         # If an update receives negative karma disable autopush
+        # exclude rawhide updates see #4566
         if (self.autokarma or self.autotime) and self._composite_karma[1] != 0 and self.status is \
-                UpdateStatus.testing and self.request is not UpdateRequest.stable:
+                UpdateStatus.testing and self.request is not UpdateRequest.stable and \
+                self.release.composed_by_bodhi:
             log.info("Disabling Auto Push since the update has received negative karma")
             self.autokarma = False
             self.autotime = False
             text = config.get('disable_automatic_push_to_stable')
             self.comment(db, text, author='bodhi')
-        elif self.stable_karma and self.karma >= self.stable_karma:
+        elif self.stable_karma and self.karma >= self.stable_karma \
+                and self.release.composed_by_bodhi:
             if config.get('test_gating.required') and not self.test_gating_passed:
                 log.info("%s reached stable karma threshold, but does not meet gating "
                          "requirements", self.alias)


Test patch:
diff --git a/bodhi-server/tests/services/test_updates.py b/bodhi-server/tests/services/test_updates.py
--- a/bodhi-server/tests/services/test_updates.py
+++ b/bodhi-server/tests/services/test_updates.py
@@ -1068,6 +1068,27 @@ def test_set_request_locked_update(self, *args):
             "Can't change request on a locked update"
         )
 
+    @mock.patch(**mock_valid_requirements)
+    def test_set_request_rawhide(self, *args):
+        """Ensure that we get an error if trying to set request on a rawhide update"""
+        nvr = 'bodhi-2.0-1.fc17'
+
+        up = self.db.query(Build).filter_by(nvr=nvr).one().update
+        up.locked = False
+        up.requirements = ''
+        up.test_gating_status = TestGatingStatus.passed
+        up.date_testing = datetime.utcnow() - timedelta(days=8)
+        up.release.composed_by_bodhi = False
+
+        post_data = dict(update=nvr, request='stable',
+                         csrf_token=self.app.get('/csrf').json_body['csrf_token'])
+        res = self.app.post_json(f'/updates/{up.alias}/request', post_data, status=400)
+
+        assert res.json_body['status'] == 'error'
+        assert res.json_body['errors'][0]['description'] == (
+            "Setting a request on an Update for a Release not composed by Bodhi is not allowed"
+        )
+
     @mock.patch(**mock_valid_requirements)
     def test_set_request_archived_release(self, *args):
         """Ensure that we get an error if trying to setrequest of a update in an archived release"""
@@ -5257,8 +5278,9 @@ def test_manually_push_critical_update_with_autopush_turned_off(self, *args):
         assert 'text/html' in resp.headers['Content-Type']
         assert 'kernel-3.11.5-300.fc17' in resp
 
-    @mock.patch(**mock_valid_requirements)
-    def test_disable_autopush_non_critical_update_with_negative_karma(self, *args):
+    @unused_mock_patch(**mock_valid_requirements)
+    @pytest.mark.parametrize('rawhide_workflow', (True, False))
+    def test_disable_autopush_non_critical_update_with_negative_karma(self, rawhide_workflow):
         """Disable autokarma on non-critical updates upon negative comment."""
         user = User(name='bob')
         self.db.add(user)
@@ -5276,6 +5298,8 @@ def test_disable_autopush_non_critical_update_with_negative_karma(self, *args):
         assert resp.json['request'] == 'testing'
         up = self.db.query(Update).filter_by(alias=resp.json['alias']).one()
         up.status = UpdateStatus.testing
+        if rawhide_workflow:
+            up.release.composed_by_bodhi = False
         # Clear pending messages
         self.db.info['messages'] = []
         self.db.commit()
@@ -5283,8 +5307,12 @@ def test_disable_autopush_non_critical_update_with_negative_karma(self, *args):
         up.comment(self.db, 'Failed to work', author='ralph', karma=-1)
         up = self.db.query(Update).filter_by(alias=resp.json['alias']).one()
 
-        expected_comment = config.get('disable_automatic_push_to_stable')
-        assert up.comments[3].text == expected_comment
+        if not rawhide_workflow:
+            expected_comment = config.get('disable_automatic_push_to_stable')
+            assert len(up.comments) == 4
+            assert up.comments[-1].text == expected_comment
+        else:
+            assert len(up.comments) == 3
 
         up.comment(self.db, 'LGTM Now', author='ralph', karma=1)
         up = self.db.query(Update).filter_by(alias=resp.json['alias']).one()
@@ -5296,7 +5324,10 @@ def test_disable_autopush_non_critical_update_with_negative_karma(self, *args):
         up = self.db.query(Update).filter_by(alias=resp.json['alias']).one()
 
         assert up.karma == 3
-        assert up.autokarma is False
+        if not rawhide_workflow:
+            assert up.autokarma is False
+        else:
+            assert up.autokarma is True
 
         # Request and Status remains testing since the autopush is disabled
         up = self.db.query(Update).filter_by(alias=resp.json['alias']).one()