Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check Greenwave status when updates are created or edited. #2086

Merged
merged 1 commit into from
Jan 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions bodhi/server/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,19 @@ def _validate_path(value):
return six.text_type(value)


def _validate_rstripped_str(value):
"""
Ensure that value is a str that is rstripped of the / character.

Args:
value (six.text_type): The value to be validated and rstripped.
Returns:
six.text_type: The rstripped value.
"""
value = six.text_type(value)
return value.rstrip('/')


def _validate_secret(value):
"""Ensure that the value is not CHANGEME and convert it to unicode.

Expand Down Expand Up @@ -413,10 +426,10 @@ class BodhiConfig(dict):
'validator': six.text_type},
'greenwave_api_url': {
'value': 'https://greenwave-web-greenwave.app.os.fedoraproject.org/api/v1.0',
'validator': six.text_type},
'validator': _validate_rstripped_str},
'waiverdb_api_url': {
'value': 'https://waiverdb-web-waiverdb.app.os.fedoraproject.org/api/v1.0',
'validator': six.text_type},
'validator': _validate_rstripped_str},
'waiverdb.access_token': {
'value': None,
'validator': six.text_type},
Expand Down
5 changes: 5 additions & 0 deletions bodhi/server/consumers/updates.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ def consume(self, message):
self.work_on_bugs(session, update, bugs)
self.fetch_test_cases(session, update)

if config['test_gating.required']:
with self.db_factory() as session:
update = Update.get(alias, session)
update.update_test_gating_status()

log.info("Updates Handler done with %s, %s" % (alias, topic))

def fetch_test_cases(self, session, update):
Expand Down
31 changes: 30 additions & 1 deletion bodhi/server/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
from sqlalchemy.types import SchemaType, TypeDecorator, Enum
import six

from bodhi.server import bugs, buildsys, log, mail, notifications, Session
from bodhi.server import bugs, buildsys, log, mail, notifications, Session, util
from bodhi.server.config import config
from bodhi.server.exceptions import BodhiException, LockedUpdateException
from bodhi.server.util import (
Expand Down Expand Up @@ -1743,6 +1743,35 @@ def greenwave_subject_json(self):
"""
return json.dumps(self.greenwave_subject)

def update_test_gating_status(self):
"""Query Greenwave about this update and set the test_gating_status as appropriate."""
# We retrieve updates going to testing (status=pending) and updates
# (status=testing) going to stable.
# If the update is pending, we want to know if it can go to testing
decision_context = u'bodhi_update_push_testing'
if self.status == UpdateStatus.testing:
# Update is already in testing, let's ask if it can go to stable
decision_context = u'bodhi_update_push_stable'

data = {
'product_version': self.product_version,
'decision_context': decision_context,
'subject': self.greenwave_subject
}
api_url = '{}/decision'.format(config.get('greenwave_api_url'))

decision = util.greenwave_api_post(api_url, data)
if decision['policies_satisfied']:
# If an unrestricted policy is applied and no tests are required
# on this update, let's set the test gating as ignored in Bodhi.
if decision['summary'] == 'no tests are required':
self.test_gating_status = TestGatingStatus.ignored
else:
self.test_gating_status = TestGatingStatus.passed
else:
self.test_gating_status = TestGatingStatus.failed
self.greenwave_summary_string = decision['summary']

@classmethod
def new(cls, request, data):
"""
Expand Down
29 changes: 1 addition & 28 deletions bodhi/server/scripts/check_policies.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
from sqlalchemy.sql.expression import false

from bodhi.server import config, initialize_db, models, Session
from bodhi.server.util import greenwave_api_post


@click.command()
Expand All @@ -41,34 +40,8 @@ def check():
.filter(models.Update.status.in_(
[models.UpdateStatus.pending, models.UpdateStatus.testing]))
for update in updates:
# We retrieve updates going to testing (status=pending) and updates
# (status=testing) going to stable.
# If the update is pending, we want to know if it can go to testing
decision_context = u'bodhi_update_push_testing'
if update.status == models.UpdateStatus.testing:
# Update is already in testing, let's ask if it can go to stable
decision_context = u'bodhi_update_push_stable'

data = {
'product_version': update.product_version,
'decision_context': decision_context,
'subject': update.greenwave_subject
}
api_url = '{}/decision'.format(
config.config.get('greenwave_api_url').rstrip('/'))

try:
decision = greenwave_api_post(api_url, data)
if decision['policies_satisfied']:
# If an unrestricted policy is applied and no tests are required
# on this update, let's set the test gating as ignored in Bodhi.
if decision['summary'] == 'no tests are required':
update.test_gating_status = models.TestGatingStatus.ignored
else:
update.test_gating_status = models.TestGatingStatus.passed
else:
update.test_gating_status = models.TestGatingStatus.failed
update.greenwave_summary_string = decision['summary']
update.update_test_gating_status()
session.commit()
except Exception as e:
# If there is a problem talking to Greenwave server, print the error.
Expand Down
67 changes: 66 additions & 1 deletion bodhi/tests/server/consumers/test_updates.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# -*- coding: utf-8 -*-

# Copyright © 2016-2017 Red Hat, Inc. and Caleigh Runge-Hotman
#
# This file is part of Bodhi.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
Expand Down Expand Up @@ -83,6 +86,68 @@ def test_edited_update_bugs_in_update(self, work_on_bugs, fetch_test_cases):
self.assertTrue(isinstance(fetch_test_cases.mock_calls[0][1][1],
sqlalchemy.orm.session.Session))

@mock.patch.dict('bodhi.server.config.config', {'test_gating.required': False})
def test_gating_required_false(self):
"""Assert that test_gating_status is not updated if test_gating is not enabled."""
update = models.Update.query.filter_by(title=u'bodhi-2.0-1.fc17').one()
update.test_gating_status = None
hub = mock.MagicMock()
hub.config = {'environment': 'environment',
'topic_prefix': 'topic_prefix'}
h = updates.UpdatesHandler(hub)
h.db_factory = base.TransactionalSessionMaker(self.Session)
# Throw a bogus bug id in there to ensure it doesn't raise AssertionError.
message = {
'topic': 'bodhi.update.request.testing',
'body': {'msg': {'update': {'alias': u'bodhi-2.0-1.fc17'},
'new_bugs': []}}}

with mock.patch('bodhi.server.models.util.greenwave_api_post') as mock_greenwave:
greenwave_response = {
'policies_satisfied': False,
'summary': u'what have you done‽',
'applicable_policies': ['taskotron_release_critical_tasks'],
'unsatisfied_requirements': ['some arbitrary test you disagree with']
}
mock_greenwave.return_value = greenwave_response

h.consume(message)

update = models.Update.query.filter_by(title=u'bodhi-2.0-1.fc17').one()
self.assertIsNone(update.test_gating_status)
self.assertIsNone(update.greenwave_summary_string)

@mock.patch.dict('bodhi.server.config.config', {'test_gating.required': True})
def test_gating_required_true(self):
"""Assert that test_gating_status is updated when test_gating is enabled."""
update = models.Update.query.filter_by(title=u'bodhi-2.0-1.fc17').one()
update.test_gating_status = None
hub = mock.MagicMock()
hub.config = {'environment': 'environment',
'topic_prefix': 'topic_prefix'}
h = updates.UpdatesHandler(hub)
h.db_factory = base.TransactionalSessionMaker(self.Session)
# Throw a bogus bug id in there to ensure it doesn't raise AssertionError.
message = {
'topic': 'bodhi.update.request.testing',
'body': {'msg': {'update': {'alias': u'bodhi-2.0-1.fc17'},
'new_bugs': []}}}

with mock.patch('bodhi.server.models.util.greenwave_api_post') as mock_greenwave:
greenwave_response = {
'policies_satisfied': False,
'summary': u'what have you done‽',
'applicable_policies': ['taskotron_release_critical_tasks'],
'unsatisfied_requirements': ['some arbitrary test you disagree with']
}
mock_greenwave.return_value = greenwave_response

h.consume(message)

update = models.Update.query.filter_by(title=u'bodhi-2.0-1.fc17').one()
self.assertEqual(update.test_gating_status, models.TestGatingStatus.failed)
self.assertEqual(update.greenwave_summary_string, u'what have you done‽')

# We're going to use side effects to mock but still call work_on_bugs and fetch_test_cases so we
# can ensure that we aren't raising Exceptions from them, while allowing us to only assert that
# we called them correctly without having to assert all of their behaviors as well.
Expand Down
10 changes: 5 additions & 5 deletions bodhi/tests/server/scripts/test_check_policies.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def test_policies_satisfied(self):
update = self.db.query(models.Update).all()[0]
update.status = models.UpdateStatus.testing
self.db.commit()
with patch('bodhi.server.scripts.check_policies.greenwave_api_post') as mock_greenwave:
with patch('bodhi.server.models.util.greenwave_api_post') as mock_greenwave:
greenwave_response = {
'policies_satisfied': True,
'summary': 'All tests passed',
Expand Down Expand Up @@ -69,7 +69,7 @@ def test_policies_pending_satisfied(self):
update = self.db.query(models.Update).all()[0]
update.status = models.UpdateStatus.pending
self.db.commit()
with patch('bodhi.server.scripts.check_policies.greenwave_api_post') as mock_greenwave:
with patch('bodhi.server.models.util.greenwave_api_post') as mock_greenwave:
greenwave_response = {
'policies_satisfied': True,
'summary': 'All tests passed',
Expand Down Expand Up @@ -100,7 +100,7 @@ def test_policies_unsatisfied(self):
update = self.db.query(models.Update).all()[0]
update.status = models.UpdateStatus.testing
self.db.commit()
with patch('bodhi.server.scripts.check_policies.greenwave_api_post') as mock_greenwave:
with patch('bodhi.server.models.util.greenwave_api_post') as mock_greenwave:
greenwave_response = {
'policies_satisfied': False,
'summary': '1 of 2 tests are failed',
Expand Down Expand Up @@ -141,7 +141,7 @@ def test_no_policies_enforced(self):
update.status = models.UpdateStatus.testing
update.test_gating_status = None
self.db.commit()
with patch('bodhi.server.scripts.check_policies.greenwave_api_post') as mock_greenwave:
with patch('bodhi.server.models.util.greenwave_api_post') as mock_greenwave:
mock_greenwave.return_value = RuntimeError('The error was blablabla')

result = runner.invoke(check_policies.check, [])
Expand All @@ -167,7 +167,7 @@ def test_unrestricted_policy(self):
update = self.db.query(models.Update).all()[0]
update.status = models.UpdateStatus.testing
self.db.commit()
with patch('bodhi.server.scripts.check_policies.greenwave_api_post') as mock_greenwave:
with patch('bodhi.server.models.util.greenwave_api_post') as mock_greenwave:
greenwave_response = {
'policies_satisfied': True,
'summary': 'no tests are required',
Expand Down
15 changes: 15 additions & 0 deletions bodhi/tests/server/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,21 @@ def test_path_exists(self):
self.assertTrue(isinstance(result, six.text_type))


class ValidateRstrippedStrTests(unittest.TestCase):
"""Test the _validate_rstripped_str() function."""
def test_with_trailing_slash(self):
"""Ensure that a trailing slash is removed."""
result = config._validate_rstripped_str('this/should/be/rstripped/')

self.assertEqual(result, 'this/should/be/rstripped')

def test_without_trailing_slash(self):
"""With no trailing slash, the string should be returned as is."""
result = config._validate_rstripped_str('this/should/stay/the/same')

self.assertEqual(result, 'this/should/stay/the/same')


class ValidateSecretTests(unittest.TestCase):
"""Test the _validate_secret() function."""
def test_with_changeme(self):
Expand Down