bodhi/config/app.cfg | 11 ++++-
bodhi/jobs.py | 31 +++++++++++++++
bodhi/model.py | 80 +++++++++++++++++++++++++++++++++++++---
bodhi/tests/test_client.py | 6 +--
bodhi/tests/test_controllers.py | 68 ++++++++++++++++++++++++++++++++++
5 files changed, 185 insertions(+), 11 deletions(-)
New commits:
commit cd090b61c980e7b209b4cdfdd8679173935b591f
Author: Luke Macken <lmacken(a)redhat.com>
Date: Tue Aug 3 15:19:15 2010 -0400
Bring bodhi up to speed with the Package Update Acceptance Criteria policy.
https://fedoraproject.org/wiki/Package_update_acceptance_criteria
- Allow for a 'mandatory_days_in_testing' configurable variable to be set for
each release. If it exists, bodhi will prevent updates from being pushed to
stable until it either reaches the 'stable karma' defined by the submitter,
or spends a certain amount of time in testing (defaults to 7 days for Fedora,
14 days for EPEL).
- Add an `approve_testing_updates` job that runs every night and approves
updates that have reached this minimum amount of testing
- Add a unit test that exercies this workflow
diff --git a/bodhi/config/app.cfg b/bodhi/config/app.cfg
index 419bcb1..5b9b69b 100644
--- a/bodhi/config/app.cfg
+++ b/bodhi/config/app.cfg
@@ -38,8 +38,8 @@ mash_conf = '%(top_level_dir)s/config/mash.conf'
createrepo_cache_dir = "/var/tmp/createrepo"
## Our periodic jobs
-#jobs = 'clean_repo nagmail fix_bug_titles cache_release_data'
-jobs = 'cache_release_data refresh_metrics'
+#jobs = 'clean_repo nagmail fix_bug_titles cache_release_data
approve_testing_updates'
+jobs = 'cache_release_data refresh_metrics approve_testing_updates'
## Comps configuration
comps_dir = '/usr/share/bodhi/comps'
@@ -171,7 +171,12 @@ critpath.num_admin_approvals = 1
# The net karma required to submit a critial path update to a pending release)
critpath.min_karma = 2
-# The commented out values below are the defaults
+# The minimum amount of time an update must spend in testing before
+# it can reach the stable repository
+fedora.mandatory_days_in_testing = 7
+fedora_epel.mandatory_days_in_testing = 14
+
+testing_approval_msg = "This update has reached %d days in testing and can be pushed
to stable now if the maintainer wishes"
# The number of days worth of updates/comments to display
feeds.num_days_to_show = 7
diff --git a/bodhi/jobs.py b/bodhi/jobs.py
index 6af78a2..125b205 100644
--- a/bodhi/jobs.py
+++ b/bodhi/jobs.py
@@ -166,6 +166,30 @@ def refresh_metrics():
log.exception(e)
+def approve_testing_updates():
+ """
+ Scan all testing updates and approve ones that have met the per-release
+ testing requirements.
+
+
https://fedoraproject.org/wiki/Package_update_acceptance_criteria
+ """
+ log.info('Running approve_testing_updates job...')
+ for update in PackageUpdate.select(
+ AND(PackageUpdate.q.status == 'testing',
+ PackageUpdate.q.request == None)):
+ # If this release does not have any testing requirements, skip it
+ if not update.release.mandatory_days_in_testing:
+ continue
+ # If this has already met testing requirements, skip it
+ if update.met_testing_requirements:
+ continue
+ if update.meets_testing_requirements:
+ log.info('%s now meets testing requirements' % update.title)
+ update.comment(
+ config.get('testing_approval_msg') % update.days_in_testing,
+ author='bodhi')
+
+
def schedule():
""" Schedule our periodic tasks """
@@ -209,3 +233,10 @@ def schedule():
taskname='Refresh our metrics',
initialdelay=7200,
interval=86400)
+
+ # Approve updates that have been in testing for a certain amount of time
+ if 'approve_testing_updates' in jobs:
+ log.debug("Scheduling approve_testing_updates job")
+ scheduler.add_weekday_task(action=approve_testing_updates,
+ weekdays=range(1,8),
+ timeonday=(0,0))
diff --git a/bodhi/model.py b/bodhi/model.py
index 1bdff4b..08a4caa 100644
--- a/bodhi/model.py
+++ b/bodhi/model.py
@@ -109,6 +109,11 @@ class Release(SQLObject):
else:
return '%s-updates-testing' % id
+ @property
+ def mandatory_days_in_testing(self):
+ return config.get('%s.mandatory_days_in_testing' %
+ self.id_prefix.lower().replace('-', '_'))
+
def __json__(self):
return dict(name=self.name, long_name=self.long_name,
id_prefix=self.id_prefix, dist_tag=self.dist_tag,
@@ -446,11 +451,35 @@ class PackageUpdate(SQLObject):
log.info('Forcing critical path update into testing')
action = 'testing'
+ # Ensure this update meets the minimum testing requirements
+ flash_notes = ''
+ if action == 'stable' and not self.critpath:
+ # Check if we've met the karma requirements
+ if self.karma >= self.stable_karma:
+ pass
+ else:
+ # If we haven't met the stable karma requirements, check if it has
met
+ # the mandatory time-in-testing requirements
+ if self.release.mandatory_days_in_testing:
+ if not self.met_testing_requirements:
+ flash_notes = 'This update has not yet met the minimum
testing requirements defined in the <a
href="https://fedoraproject.org/wiki/Package_update_acceptance_crite...
Update Acceptance Criteria</a>'
+ if self.status == 'testing':
+ self.request = None
+ flash_log(flash_notes)
+ return
+ elif self.request == 'testing':
+ flash_log(flash_notes)
+ return
+ else:
+ action = 'testing'
+
self.request = action
self.pushed = False
#self.date_pushed = None
notes = notes and '. '.join(notes) or ''
- flash_log("%s has been submitted for %s. %s"
%(self.title,action,notes))
+ flash_notes = flash_notes and '. %s' % flash_notes
+ flash_log("%s has been submitted for %s. %s%s" % (self.title,
+ action, notes, flash_notes))
self.comment('This update has been submitted for %s by %s. %s' % (
action, identity.current.user_name, notes), author='bodhi')
mail.send_admin(action, self)
@@ -985,22 +1014,63 @@ class PackageUpdate(SQLObject):
self.status)
@property
- def time_in_testing(self):
+ def days_in_testing(self):
""" Return the number of days that this update has been in testing
"""
timestamp = None
for comment in self.comments:
if comment.text == 'This update has been pushed to testing':
timestamp = comment.timestamp
if self.status == 'testing':
- return datetime.utcnow() - timestamp
+ return (datetime.utcnow() - timestamp).days
else:
break
if not timestamp:
return
for comment in self.comments:
if comment.text == 'This update has been pushed to stable':
- return comment.timestamp - timestamp
- return datetime.utcnow() - timestamp
+ return (comment.timestamp - timestamp).days
+ return (datetime.utcnow() - timestamp).days
+
+ @property
+ def meets_testing_requirements(self):
+ """
+ Return whether or not this update meets the testing requirements
+ for this specific release.
+
+ If this release does not have a mandatory testing requirement, then
+ simply return True.
+ """
+ if self.critpath:
+ return self.critpath_approved
+ num_days = self.release.mandatory_days_in_testing
+ if not num_days:
+ return True
+ return self.days_in_testing >= num_days
+
+ @property
+ def met_testing_requirements(self):
+ """
+ Return whether or not this update has already met the testing
+ requirements.
+
+ If this release does not have a mandatory testing requirement, then
+ simply return True.
+ """
+ min_num_days = self.release.mandatory_days_in_testing
+ num_days = self.days_in_testing
+ if min_num_days:
+ if num_days < min_num_days:
+ return False
+ else:
+ return True
+ for comment in self.comments:
+ if comment.author == 'bodhi' and \
+ comment.text.startswith('This update has reached') and \
+ comment.text.endswith('days in testing and can be pushed to'
+ ' stable now if the maintainer wishes'):
+ return True
+ return False
+
class Comment(SQLObject):
diff --git a/bodhi/tests/test_client.py b/bodhi/tests/test_client.py
index 2213e6b..7892fd3 100644
--- a/bodhi/tests/test_client.py
+++ b/bodhi/tests/test_client.py
@@ -146,7 +146,7 @@ class TestClient(testutil.DBTest):
assert PackageUpdate.byTitle(self.build)
bodhi.request(update=self.build, request=opts.request)
update = PackageUpdate.byTitle(self.build)
- assert update.request == 'stable'
+ assert update.request == 'testing'
opts.request = 'testing'
bodhi.request(update=self.build, request=opts.request)
update = PackageUpdate.byTitle(self.build)
@@ -209,5 +209,5 @@ close_bugs=True
testutil.print_log()
update = bodhi.query()['updates'][0]
assert update and isinstance(update, dict)
- assert
bodhi.update_str(update).startswith(u'================================================================================\n
TurboGears-1.0.3.2-1.fc7\n================================================================================\n
Release: Fedora 7\n Status: pending\n Type: bugfix\n Karma: 0\n
Request: stable\n Bugs: 12345 - None\n : 6789 - None\n Notes: foo\n
Submitter: guest\n')
- assert bodhi.update_str(update).endswith(u' (karma 0)\n This
update has been submitted for stable by guest.\n\n
http://localhost:8084/updates/TurboGears-1.0.3.2-1.fc7\n')
+ assert
bodhi.update_str(update).startswith(u'================================================================================\n
TurboGears-1.0.3.2-1.fc7\n================================================================================\n
Release: Fedora 7\n Status: pending\n Type: bugfix\n Karma: 0\n
Request: testing\n Bugs: 12345 - None\n : 6789 - None\n Notes: foo\n
Submitter: guest\n')
+ assert bodhi.update_str(update).endswith(u' (karma 0)\n This
update has been submitted for testing by guest.\n\n
http://localhost:8084/updates/TurboGears-1.0.3.2-1.fc7\n')
diff --git a/bodhi/tests/test_controllers.py b/bodhi/tests/test_controllers.py
index 4a86638..96ce0fd 100644
--- a/bodhi/tests/test_controllers.py
+++ b/bodhi/tests/test_controllers.py
@@ -2246,3 +2246,71 @@ class TestControllers(testutil.DBTest):
assert False, "Update with duplicate packages was saved!"
except SQLObjectNotFound, e:
pass
+
+ def test_week_in_testing(self):
+ from bodhi.jobs import approve_testing_updates
+ session = login()
+ create_release()
+ params = {
+ 'builds' : 'TurboGears-1.0.8-1.fc7',
+ 'release' : 'Fedora 7',
+ 'type_' : 'bugfix',
+ 'bugs' : '',
+ 'notes' : 'foobar',
+ 'request' : 'Stable',
+ 'suggest_reboot' : True,
+ 'autokarma' : True,
+ 'stable_karma' : 5,
+ 'unstable_karma' : -5
+ }
+ self.save_update(params, session)
+ up = PackageUpdate.byTitle(params['builds'])
+
+ # Ensure we can't push directly to stable
+ assert up.request == 'testing'
+
+ # Pretend it's pushed to testing
+ up.status = 'testing'
+ up.status_comment()
+ assert not up.days_in_testing
+
+ # Run the approve_testing_updates job, which shouldn't do anything
+ approve_testing_updates()
+
+ # Fake almost a week of testing
+ assert len(up.comments) == 2, up.comments
+ up.comments[1].timestamp -= timedelta(days=6)
+ assert up.days_in_testing == 6
+
+ # Run the approve_testing_updates job, which shouldn't do anything
+ approve_testing_updates()
+
+ # Ensure it can't be pushed, and that there are no comments
+ testutil.create_request('/updates/request/stable/%s' %
params['builds'],
+ method='POST', headers=session)
+ up = PackageUpdate.byTitle(params['builds'])
+ assert not up.request
+ assert len(up.comments) == 2, up.comments[-1].text
+
+ # Fake a week of testing
+ up.comments[0].timestamp -= timedelta(days=1)
+ assert up.days_in_testing == 7, up.days_in_testing
+
+ # Run the approve_testing_updates job, which should approve this update
+ approve_testing_updates()
+
+ # assert approval comment in comments
+ up = PackageUpdate.byTitle(params['builds'])
+ assert len(up.comments) == 3
+ assert up.comments[-1].text == 'This update has reached 7 days in testing and
can be pushed to stable now if the maintainer wishes'
+
+ # ensure it's not auto pushed to testing
+ assert up.status == 'testing'
+ assert not up.request
+
+ # ensure we can push it to testing
+ testutil.create_request('/updates/request/stable/%s' %
params['builds'],
+ method='POST', headers=session)
+ up = PackageUpdate.byTitle(params['builds'])
+ assert up.request == 'stable'
+ assert up.comments[-1].text == u'This update has been submitted for stable by
guest. '