Repository: fedora-infra/bodhi

Issue:
should push to stable immediately if will be automatically set to request stable at time of push to testing
Example:
https://bodhi.fedoraproject.org/updates/FEDORA-2015-16569

This update was pushed to testing at 2015-09-27 00:42:54
It was then automatically requested for stable based upon karma 42 seconds later.
Then the package had to wait 15 hours to be pushed to stable, even though it already met the karma requirements for automatic request for stable before the push to testing.

Instead of (or in addition to?) pushing it to testing, it should have been automatically pushed to stable at the time it pushed to testing, rather than having to wait until the next time updates get pushed, which in this case was 15 hours later (in some cases the wait seems to be longer still).

Probably the best way to approach this is for Bodhi to automatically change the request from testing to stable if the Karma requirements for automatic request for stable have been reached before the package makes it to testing.

Timeliness of updates is important for security, for example. In this case, the sqlite package was holding up for several days a security update for firefox which depended upon it (https://bodhi.fedoraproject.org/updates/FEDORA-2015-16455). (Actually, it looks like this firefox package is another example where the push to stable was delayed for the same reason.)



Discussion:
This is a much desired feature of mine as well. I talked with @nirik about this tonight and we do not believe it violates any of the [written policy](https://fedoraproject.org/wiki/Updates_Policy#All_other_updates) to have an update go straight from `pending` `->` `stable`  if the karma threshold has been reached.

Often times if people have gone out of their way to grab the package and test it before it hits updates-testing it is because the update is desired to be made available faster and thus having this >= 24hour wait can be counter-productive. 
+1 to the feature. It definitely makes sense to push the updates directly to stable when it has enough karma before hitting the testing repo. I am working on it. 

Resolving patch:
diff --git a/bodhi/server/models.py b/bodhi/server/models.py
--- a/bodhi/server/models.py
+++ b/bodhi/server/models.py
@@ -2299,11 +2299,11 @@ def check_karma_thresholds(self, db, agent):
         if self.locked:
             log.debug('%s locked. Ignoring karma thresholds.' % self.title)
             raise LockedUpdateException
-        # Return if the status of the update is not in testing
-        if self.status != UpdateStatus.testing:
+        # Return if the status of the update is not in testing or pending
+        if self.status not in (UpdateStatus.testing, UpdateStatus.pending):
             return
         # If an update receives negative karma disable autopush
-        if self.autokarma and self._composite_karma[1] != 0:
+        if self.autokarma and self._composite_karma[1] != 0 and self.status is UpdateStatus.testing:
             log.info("Disabling Auto Push since the update has received negative karma")
             self.autokarma = False
             text = config.get('disable_automatic_push_to_stable')
@@ -2323,11 +2323,14 @@ def check_karma_thresholds(self, db, agent):
                     "%s update has reached the stable karma threshold and can be pushed to "
                     "stable now if the maintainer wishes") % self.title)
         elif self.unstable_karma and self.karma <= self.unstable_karma:
-            log.info("Automatically unpushing %s" % self.title)
-            self.obsolete(db)
-            notifications.publish(
-                topic='update.karma.threshold.reach',
-                msg=dict(update=self, status='unstable'))
+            if self.status is UpdateStatus.pending and not self.autokarma:
+                pass
+            else:
+                log.info("Automatically unpushing %s" % self.title)
+                self.obsolete(db)
+                notifications.publish(
+                    topic='update.karma.threshold.reach',
+                    msg=dict(update=self, status='unstable'))
 
     @property
     def builds_json(self):


Test patch:
diff --git a/bodhi/tests/server/services/test_updates.py b/bodhi/tests/server/services/test_updates.py
--- a/bodhi/tests/server/services/test_updates.py
+++ b/bodhi/tests/server/services/test_updates.py
@@ -2189,6 +2189,83 @@ def test_edit_locked_update(self, publish, *args):
         self.assertEquals(len(publish.call_args_list), 2)
         publish.assert_called_with(topic='update.edit', msg=ANY)
 
+    @mock.patch(**mock_valid_requirements)
+    @mock.patch('bodhi.server.notifications.publish')
+    def test_pending_update_on_stable_karma_reached_autopush_enabled(self, publish, *args):
+        """ Ensure that pending update directly requests for stable if
+        it hits stable karma before reaching testing state """
+        nvr = u'bodhi-2.0.0-2.fc17'
+        args = self.get_update(nvr)
+        args['autokarma'] = True
+        args['stable_karma'] = 2
+        args['unstable_karma'] = -2
+        self.app.post_json('/updates/', args)
+
+        up = self.db.query(Update).filter_by(title=nvr).one()
+        up.status = UpdateStatus.pending
+        self.db.flush()
+
+        up.comment(self.db, u'WFM', author=u'dustymabe', karma=1)
+        up = self.db.query(Update).filter_by(title=nvr).one()
+
+        up.comment(self.db, u'LGTM', author=u'bowlofeggs', karma=1)
+        up = self.db.query(Update).filter_by(title=nvr).one()
+
+        self.assertEquals(up.karma, 2)
+        self.assertEquals(up.request, UpdateRequest.stable)
+        self.assertEquals(up.status, UpdateStatus.pending)
+
+    @mock.patch(**mock_valid_requirements)
+    def test_pending_update_on_stable_karma_not_reached(self, publish, *args):
+        """ Ensure that pending update does not directly request for stable
+        if it does not hit stable karma before reaching testing state """
+        nvr = u'bodhi-2.0.0-2.fc17'
+        args = self.get_update(nvr)
+        args['autokarma'] = True
+        args['stable_karma'] = 2
+        args['unstable_karma'] = -2
+        self.app.post_json('/updates/', args)
+
+        up = self.db.query(Update).filter_by(title=nvr).one()
+        up.status = UpdateStatus.pending
+        self.db.flush()
+
+        up.comment(self.db, u'WFM', author=u'dustymabe', karma=1)
+        up = self.db.query(Update).filter_by(title=nvr).one()
+
+        self.assertEquals(up.karma, 1)
+        self.assertEquals(up.request, UpdateRequest.testing)
+        self.assertEquals(up.status, UpdateStatus.pending)
+
+    @mock.patch(**mock_valid_requirements)
+    @mock.patch('bodhi.server.notifications.publish')
+    def test_pending_update_on_stable_karma_reached_autopush_disabled(self, publish, *args):
+        """ Ensure that pending update has option to request for stable directly
+        if it hits stable karma before reaching testing state """
+        nvr = u'bodhi-2.0.0-2.fc17'
+        args = self.get_update(nvr)
+        args['autokarma'] = False
+        args['stable_karma'] = 2
+        args['unstable_karma'] = -2
+        self.app.post_json('/updates/', args)
+
+        up = self.db.query(Update).filter_by(title=nvr).one()
+        up.status = UpdateStatus.pending
+        self.db.flush()
+
+        up.comment(self.db, u'WFM', author=u'dustymabe', karma=1)
+        up = self.db.query(Update).filter_by(title=nvr).one()
+
+        up.comment(self.db, u'LGTM', author=u'bowlofeggs', karma=1)
+        up = self.db.query(Update).filter_by(title=nvr).one()
+
+        self.assertEquals(up.karma, 2)
+        self.assertEquals(up.status, UpdateStatus.pending)
+
+        text = unicode(config.get('testing_approval_msg_based_on_karma'))
+        up.comment(self.db, text, author=u'bodhi')
+        self.assertIn('pushed to stable now if the maintainer wishes', up.comments[-1]['text'])
+
     @mock.patch(**mock_valid_requirements)
     @mock.patch('bodhi.server.notifications.publish')
     def test_obsoletion_locked_with_open_request(self, publish, *args):