diff --git a/bodhi/server/config.py b/bodhi/server/config.py index 90784ea160..305140c671 100644 --- a/bodhi/server/config.py +++ b/bodhi/server/config.py @@ -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. @@ -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}, diff --git a/bodhi/server/consumers/updates.py b/bodhi/server/consumers/updates.py index d72a06a98a..bc5268bd86 100644 --- a/bodhi/server/consumers/updates.py +++ b/bodhi/server/consumers/updates.py @@ -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): diff --git a/bodhi/server/models.py b/bodhi/server/models.py index d05795a16c..0f474c2a7a 100644 --- a/bodhi/server/models.py +++ b/bodhi/server/models.py @@ -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 ( @@ -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): """ diff --git a/bodhi/server/scripts/check_policies.py b/bodhi/server/scripts/check_policies.py index 085fede325..7738d1e9a1 100644 --- a/bodhi/server/scripts/check_policies.py +++ b/bodhi/server/scripts/check_policies.py @@ -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() @@ -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. diff --git a/bodhi/tests/server/consumers/test_updates.py b/bodhi/tests/server/consumers/test_updates.py index e91e0c0f0b..baf477678b 100644 --- a/bodhi/tests/server/consumers/test_updates.py +++ b/bodhi/tests/server/consumers/test_updates.py @@ -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 @@ -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. diff --git a/bodhi/tests/server/scripts/test_check_policies.py b/bodhi/tests/server/scripts/test_check_policies.py index 23f6458219..ce5f23bb86 100644 --- a/bodhi/tests/server/scripts/test_check_policies.py +++ b/bodhi/tests/server/scripts/test_check_policies.py @@ -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', @@ -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', @@ -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', @@ -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, []) @@ -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', diff --git a/bodhi/tests/server/test_config.py b/bodhi/tests/server/test_config.py index b6778a94e8..888f61a683 100644 --- a/bodhi/tests/server/test_config.py +++ b/bodhi/tests/server/test_config.py @@ -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):