From 8658b9d5d98f0ed5df4457fe859cfed492380ef1 Mon Sep 17 00:00:00 2001 From: Lukas Holecek Date: Oct 23 2018 10:37:50 +0000 Subject: Remove Bodhi dependecy Removes dependency on Bodhi - i.e. asking for all build from a Bodhi update. This removes cyclic dependency (Bodhi depends on Greenwave) and simplifies the code. Decision for bodhi_update no longer expands to include related koji_build items from the Bodhi update. All builds have to be stated explicitly in the "subject" field. Decision change message for bodhi_update is no longer published if a test results changes for a koji_build in the Bodhi update. As an side effect, the formerly deprecated "subject" field (replaced with "subject_identifier" and "subject_type") has to be used to query for a decision on multiple koji_builds. Fixes #298 Signed-off-by: Lukas Holecek --- diff --git a/docs/policies.rst b/docs/policies.rst index d148e54..bb5943e 100644 --- a/docs/policies.rst +++ b/docs/policies.rst @@ -130,9 +130,8 @@ Greenwave can make decisions about the following types of software artefacts: A distribution update in `Bodhi`_. Updates are identified by their Bodhi update id, as in ``FEDORA-2018-ec7cb4d5eb``. - A Bodhi update contains one or more Koji builds. When Greenwave makes a - decision about a Bodhi update, it *also* considers any policies which apply - to Koji builds in that update. + To make decision about Koji builds in the update, they need to be explicitly + listed in decision query. ``compose`` A distribution compose. The compose tool (typically Pungi) takes a snapshot diff --git a/functional-tests/conftest.py b/functional-tests/conftest.py index bcfc63b..7baffb0 100644 --- a/functional-tests/conftest.py +++ b/functional-tests/conftest.py @@ -169,23 +169,6 @@ def waiverdb_server(tmpdir_factory): @pytest.yield_fixture(scope='session') -def bodhi(): - start_server_arguments = [ - 'gunicorn-3', - '--bind=127.0.0.1:5677', - '--access-logfile=-', - 'fake_bodhi:application' - ] - - with server_subprocess( - name='bodhi', - port=5677, - source_path=os.path.dirname(__file__), - start_server_arguments=start_server_arguments) as url: - yield url - - -@pytest.yield_fixture(scope='session') def distgit_server(tmpdir_factory): """ Creating a fake dist-git process. It is just a serving some files in a tmp dir """ tmp_dir = tmpdir_factory.mktemp('distgit') @@ -203,7 +186,7 @@ def distgit_server(tmpdir_factory): @pytest.yield_fixture(scope='session') -def greenwave_server(tmpdir_factory, resultsdb_server, waiverdb_server, bodhi): +def greenwave_server(tmpdir_factory, resultsdb_server, waiverdb_server): cache_file = tmpdir_factory.mktemp('greenwave').join('cache.dbm') settings_content = """ CACHE = { @@ -213,8 +196,7 @@ def greenwave_server(tmpdir_factory, resultsdb_server, waiverdb_server, bodhi): } RESULTSDB_API_URL = '%sapi/v2.0' WAIVERDB_API_URL = '%sapi/v1.0' - BODHI_URL = %r - """ % (cache_file.strpath, resultsdb_server, waiverdb_server, bodhi) + """ % (cache_file.strpath, resultsdb_server, waiverdb_server) start_server_arguments = [ 'gunicorn-3', @@ -245,11 +227,10 @@ class TestDataBuilder(object): ResultsDB and WaiverDB. """ - def __init__(self, requests_session, resultsdb_url, waiverdb_url, bodhi_url, distgit_url): + def __init__(self, requests_session, resultsdb_url, waiverdb_url, distgit_url): self.requests_session = requests_session self.resultsdb_url = resultsdb_url self.waiverdb_url = waiverdb_url - self.bodhi_url = bodhi_url self.distgit_url = distgit_url self._counter = itertools.count(1) @@ -331,18 +312,8 @@ class TestDataBuilder(object): response.raise_for_status() return response.json() - def create_bodhi_update(self, build_nvrs): - data = {'builds': [{'nvr': nvr} for nvr in build_nvrs]} - response = self.requests_session.post( - self.bodhi_url + 'updates/', - headers={'Content-Type': 'application/json'}, - timeout=TEST_HTTP_TIMEOUT, - data=json.dumps(data)) - response.raise_for_status() - return response.json() - @pytest.fixture(scope='session') -def testdatabuilder(requests_session, resultsdb_server, waiverdb_server, bodhi, distgit_server): - return TestDataBuilder(requests_session, resultsdb_server, waiverdb_server, bodhi, +def testdatabuilder(requests_session, resultsdb_server, waiverdb_server, distgit_server): + return TestDataBuilder(requests_session, resultsdb_server, waiverdb_server, distgit_server) diff --git a/functional-tests/consumers/test_resultsdb.py b/functional-tests/consumers/test_resultsdb.py index 1010a03..7c562ed 100644 --- a/functional-tests/consumers/test_resultsdb.py +++ b/functional-tests/consumers/test_resultsdb.py @@ -14,8 +14,6 @@ def test_consume_new_result( testdatabuilder): load_config.return_value = {'greenwave_api_url': greenwave_server + 'api/v1.0'} nvr = testdatabuilder.unique_nvr(product_version='fc26') - update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr]) - updateid = update['updateid'] result = testdatabuilder.create_result(item=nvr, testcase_name='dist.rpmdeplint', outcome='PASSED') @@ -44,8 +42,7 @@ def test_consume_new_result( assert handler.topic == ['topic_prefix.environment.taskotron.result.new'] handler.consume(message) - # We expect 4 messages altogether. - assert len(mock_fedmsg.mock_calls) == 4 + assert len(mock_fedmsg.mock_calls) == 2 assert all(call[2]['topic'] == 'decision.update' for call in mock_fedmsg.mock_calls) actual_msgs_sent = [call[2]['msg'] for call in mock_fedmsg.mock_calls] assert actual_msgs_sent[0] == { @@ -155,117 +152,6 @@ def test_consume_new_result( ], }, } - assert actual_msgs_sent[2] == { - 'policies_satisfied': False, - 'decision_context': 'bodhi_update_push_stable', - 'product_version': 'fedora-26', - 'satisfied_requirements': [ - { - 'result_id': result['id'], - 'testcase': 'dist.rpmdeplint', - 'type': 'test-result-passed', - }, - ], - 'unsatisfied_requirements': [ - { - 'testcase': 'dist.abicheck', - 'item': {'item': nvr, 'type': 'koji_build'}, - 'subject_type': 'koji_build', - 'subject_identifier': nvr, - 'type': 'test-result-missing', - 'scenario': None, - }, - { - 'testcase': 'dist.upgradepath', - 'item': {'item': nvr, 'type': 'koji_build'}, - 'subject_type': 'koji_build', - 'subject_identifier': nvr, - 'type': 'test-result-missing', - 'scenario': None, - } - ], - 'summary': '2 of 3 required test results missing', - 'subject': [ - {'item': updateid, 'type': 'bodhi_update'}, - {'item': nvr, 'type': 'koji_build'}, - {'original_spec_nvr': nvr}, - ], - 'subject_type': 'bodhi_update', - 'subject_identifier': updateid, - 'applicable_policies': ['taskotron_release_critical_tasks_with_blacklist', - 'taskotron_release_critical_tasks'], - 'previous': { - 'applicable_policies': ['taskotron_release_critical_tasks_with_blacklist', - 'taskotron_release_critical_tasks'], - 'policies_satisfied': False, - 'summary': '3 of 3 required test results missing', - 'satisfied_requirements': [], - 'unsatisfied_requirements': [ - { - 'testcase': 'dist.abicheck', - 'item': {'item': nvr, 'type': 'koji_build'}, - 'subject_type': 'koji_build', - 'subject_identifier': nvr, - 'type': 'test-result-missing', - 'scenario': None, - }, - { - 'testcase': 'dist.rpmdeplint', - 'item': {'item': nvr, 'type': 'koji_build'}, - 'subject_type': 'koji_build', - 'subject_identifier': nvr, - 'type': 'test-result-missing', - 'scenario': None, - }, - { - 'testcase': 'dist.upgradepath', - 'item': {'item': nvr, 'type': 'koji_build'}, - 'subject_type': 'koji_build', - 'subject_identifier': nvr, - 'type': 'test-result-missing', - 'scenario': None, - }, - ], - }, - } - assert actual_msgs_sent[3] == { - 'policies_satisfied': True, - 'decision_context': 'bodhi_update_push_testing', - 'product_version': 'fedora-*', - 'satisfied_requirements': [ - { - 'result_id': result['id'], - 'testcase': 'dist.rpmdeplint', - 'type': 'test-result-passed', - }, - ], - 'unsatisfied_requirements': [], - 'summary': 'all required tests passed', - 'subject': [ - {'item': updateid, 'type': 'bodhi_update'}, - {'item': nvr, 'type': 'koji_build'}, - {'original_spec_nvr': nvr}, - ], - 'subject_type': 'bodhi_update', - 'subject_identifier': updateid, - 'applicable_policies': ['taskotron_release_critical_tasks_for_testing'], - 'previous': { - 'applicable_policies': ['taskotron_release_critical_tasks_for_testing'], - 'policies_satisfied': False, - 'summary': '1 of 1 required test results missing', - 'satisfied_requirements': [], - 'unsatisfied_requirements': [ - { - 'testcase': 'dist.rpmdeplint', - 'item': {'item': nvr, 'type': 'koji_build'}, - 'subject_type': 'koji_build', - 'subject_identifier': nvr, - 'type': 'test-result-missing', - 'scenario': None, - }, - ], - }, - } @mock.patch('greenwave.consumers.resultsdb.fedmsg.config.load_config') diff --git a/functional-tests/consumers/test_waiverdb.py b/functional-tests/consumers/test_waiverdb.py index 21373c4..ffcf61b 100644 --- a/functional-tests/consumers/test_waiverdb.py +++ b/functional-tests/consumers/test_waiverdb.py @@ -18,8 +18,6 @@ def test_consume_new_waiver( mock_fedmsg, load_config, requests_session, greenwave_server, testdatabuilder): load_config.return_value = {'greenwave_api_url': greenwave_server + 'api/v1.0'} nvr = testdatabuilder.unique_nvr() - update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr]) - updateid = update['updateid'] failing_test = TASKTRON_RELEASE_CRITICAL_TASKS[0] result = testdatabuilder.create_result( @@ -54,8 +52,7 @@ def test_consume_new_waiver( assert handler.topic == ['topic_prefix.environment.waiver.new'] handler.consume(message) - # We expect 2 messages altogether. - assert len(mock_fedmsg.mock_calls) == 2 + assert len(mock_fedmsg.mock_calls) == 1 assert all(call[2]['topic'] == 'decision.update' for call in mock_fedmsg.mock_calls) actual_msgs_sent = [call[2]['msg'] for call in mock_fedmsg.mock_calls] assert actual_msgs_sent[0] == { @@ -117,64 +114,3 @@ def test_consume_new_waiver( 'summary': 'all required tests passed', 'testcase': testcase, } - assert actual_msgs_sent[1] == { - 'applicable_policies': ['taskotron_release_critical_tasks_with_blacklist', - 'taskotron_release_critical_tasks'], - 'policies_satisfied': True, - 'decision_context': 'bodhi_update_push_stable', - 'previous': { - 'applicable_policies': ['taskotron_release_critical_tasks_with_blacklist', - 'taskotron_release_critical_tasks'], - 'policies_satisfied': False, - 'summary': '1 of 3 required tests failed', - 'satisfied_requirements': [ - { - 'result_id': results[0]['id'], - 'testcase': passing_tests[0], - 'type': 'test-result-passed' - }, - { - 'result_id': results[1]['id'], - 'testcase': passing_tests[1], - 'type': 'test-result-passed' - } - ], - 'unsatisfied_requirements': [ - { - 'result_id': result['id'], - 'item': {'item': nvr, 'type': 'koji_build'}, - 'testcase': TASKTRON_RELEASE_CRITICAL_TASKS[0], - 'type': 'test-result-failed', - 'scenario': None, - }, - ], - }, - 'product_version': 'fedora-26', - 'subject': [ - {'item': updateid, 'type': 'bodhi_update'}, - {'item': nvr, 'type': 'koji_build'}, - {'original_spec_nvr': nvr}, - ], - 'subject_type': 'bodhi_update', - 'subject_identifier': updateid, - 'satisfied_requirements': [ - { - 'result_id': result['id'], - 'testcase': failing_test, - 'type': 'test-result-passed' - }, - { - 'result_id': results[0]['id'], - 'testcase': passing_tests[0], - 'type': 'test-result-passed' - }, - { - 'result_id': results[1]['id'], - 'testcase': passing_tests[1], - 'type': 'test-result-passed' - } - ], - 'unsatisfied_requirements': [], - 'summary': 'all required tests passed', - 'testcase': testcase, - } diff --git a/functional-tests/fake_bodhi.py b/functional-tests/fake_bodhi.py deleted file mode 100644 index b5dfabc..0000000 --- a/functional-tests/fake_bodhi.py +++ /dev/null @@ -1,62 +0,0 @@ - -import re -import json -import hashlib -from urllib.parse import parse_qs - - -updates = {} #: {id -> update info} - - -def application(environ, start_response): - path_info = environ['PATH_INFO'] - - m = re.match(r'/updates/(.+)$', path_info) - if m: - updateid = m.group(1) - if environ['REQUEST_METHOD'] == 'GET': - if updateid in updates: - start_response('200 OK', [('Content-Type', 'application/json')]) - return [json.dumps({'update': updates[updateid]}).encode('utf8')] - else: - start_response('404 Not Found', []) - return [] - else: - start_response('405 Method Not Allowed', []) - return [] - - m = re.match(r'/updates/$', path_info) - if m: - if environ['REQUEST_METHOD'] == 'GET': - params = parse_qs(environ['QUERY_STRING']) - if 'builds' in params: - response_updates = [u for u in updates.values() - if set(params['builds']).issubset(build['nvr'] - for build in u['builds'])] - else: - response_updates = list(updates.values()) - response_data = { - 'page': 1, - 'pages': 1, - 'rows_per_page': len(updates), - 'total': len(updates), - 'updates': response_updates, - } - start_response('200 OK', [('Content-Type', 'application/json')]) - return [json.dumps(response_data).encode('utf8')] - if environ['REQUEST_METHOD'] == 'POST': - body = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])) - updateid = 'FEDORA-2000-{}'.format(hashlib.sha1(body).hexdigest()[-8:]) - assert updateid not in updates - update = json.loads(body) - update['updateid'] = updateid - updates[updateid] = update - print('Fake Bodhi created new update %r' % update) - start_response('201 Created', [('Content-Type', 'application/json')]) - return [json.dumps(update).encode('utf8')] # XXX check what Bodhi really returns - else: - start_response('405 Method Not Allowed', []) - return [] - - start_response('404 Not Found', []) - return [] diff --git a/functional-tests/test_api_v1.py b/functional-tests/test_api_v1.py index f69ffcb..1235036 100644 --- a/functional-tests/test_api_v1.py +++ b/functional-tests/test_api_v1.py @@ -162,36 +162,36 @@ def test_cannot_make_decision_with_invalid_subject(requests_session, greenwave_s @pytest.mark.smoke def test_404_for_invalid_product_version(requests_session, greenwave_server, testdatabuilder): - update = testdatabuilder.create_bodhi_update(build_nvrs=[testdatabuilder.unique_nvr()]) + nvr = testdatabuilder.unique_nvr() data = { 'decision_context': 'bodhi_push_update_stable', 'product_version': 'f26', # not a real product version - 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject_type': 'koji_build', + 'subject_identifier': nvr, } r = requests_session.post(greenwave_server + 'api/v1.0/decision', headers={'Content-Type': 'application/json'}, data=json.dumps(data)) assert r.status_code == 404 - expected = ('Cannot find any applicable policies for bodhi_update subjects ' + expected = ('Cannot find any applicable policies for koji_build subjects ' 'at gating point bodhi_push_update_stable in f26') assert expected == r.json()['message'] @pytest.mark.smoke def test_404_for_invalid_decision_context(requests_session, greenwave_server, testdatabuilder): - update = testdatabuilder.create_bodhi_update(build_nvrs=[testdatabuilder.unique_nvr()]) + nvr = testdatabuilder.unique_nvr() data = { 'decision_context': 'bodhi_push_update', # missing the _stable part! 'product_version': 'fedora-26', - 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject_type': 'koji_build', + 'subject_identifier': nvr, } r = requests_session.post(greenwave_server + 'api/v1.0/decision', headers={'Content-Type': 'application/json'}, data=json.dumps(data)) assert r.status_code == 404 - expected = ('Cannot find any applicable policies for bodhi_update subjects ' + expected = ('Cannot find any applicable policies for koji_build subjects ' 'at gating point bodhi_push_update in fedora-26') assert expected == r.json()['message'] @@ -222,12 +222,11 @@ def test_make_a_decision_on_passed_result(requests_session, greenwave_server, te testdatabuilder.create_result(item=nvr, testcase_name=testcase_name, outcome='PASSED') - update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr]) data = { 'decision_context': 'bodhi_update_push_stable', 'product_version': 'fedora-26', - 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject_type': 'koji_build', + 'subject_identifier': nvr, } r = requests_session.post(greenwave_server + 'api/v1.0/decision', @@ -251,12 +250,11 @@ def test_make_a_decision_with_verbose_flag(requests_session, greenwave_server, t results.append(testdatabuilder.create_result(item=nvr, testcase_name=testcase_name, outcome='PASSED')) - update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr]) data = { 'decision_context': 'bodhi_update_push_stable', 'product_version': 'fedora-26', - 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject_type': 'koji_build', + 'subject_identifier': nvr, 'verbose': True, } @@ -291,12 +289,13 @@ def test_make_a_decision_with_verbose_flag_and_multiple_nvrs_with_results( results.append(testdatabuilder.create_result( item=nvr, testcase_name=testcase_name, outcome='PASSED')) - update = testdatabuilder.create_bodhi_update(build_nvrs=build_nvrs) data = { 'decision_context': 'bodhi_update_push_stable', 'product_version': 'fedora-26', - 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject': [ + {'type': 'koji_build', 'item': nvr} + for nvr in build_nvrs + ], 'verbose': True, } @@ -324,12 +323,13 @@ def test_make_a_decision_with_verbose_flag_and_multiple_nvrs_with_waivers( comment='This is fine') waivers.append(waiver) - update = testdatabuilder.create_bodhi_update(build_nvrs=build_nvrs) data = { 'decision_context': 'bodhi_update_push_stable', 'product_version': 'fedora-26', - 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject': [ + {'type': 'koji_build', 'item': nvr} + for nvr in build_nvrs + ], 'verbose': True, } @@ -359,12 +359,11 @@ def test_make_a_decision_on_failed_result_with_waiver( testdatabuilder.create_result(item=nvr, testcase_name=testcase_name, outcome='PASSED') - update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr]) data = { 'decision_context': 'bodhi_update_push_stable', 'product_version': 'fedora-26', - 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject_type': 'koji_build', + 'subject_identifier': nvr, } r = requests_session.post(greenwave_server + 'api/v1.0/decision', headers={'Content-Type': 'application/json'}, @@ -383,12 +382,11 @@ def test_make_a_decision_on_failed_result(requests_session, greenwave_server, te result = testdatabuilder.create_result(item=nvr, testcase_name=TASKTRON_RELEASE_CRITICAL_TASKS[0], outcome='FAILED') - update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr]) data = { 'decision_context': 'bodhi_update_push_stable', 'product_version': 'fedora-26', - 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject_type': 'koji_build', + 'subject_identifier': nvr, } r = requests_session.post(greenwave_server + 'api/v1.0/decision', headers={'Content-Type': 'application/json'}, @@ -426,12 +424,11 @@ def test_make_a_decision_on_queued_result(requests_session, greenwave_server, te result = testdatabuilder.create_result(item=nvr, testcase_name=TASKTRON_RELEASE_CRITICAL_TASKS[0], outcome='QUEUED') - update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr]) data = { 'decision_context': 'bodhi_update_push_stable', 'product_version': 'fedora-26', - 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject_type': 'koji_build', + 'subject_identifier': nvr, } r = requests_session.post(greenwave_server + 'api/v1.0/decision', headers={'Content-Type': 'application/json'}, @@ -470,12 +467,11 @@ def test_make_a_decision_on_running_result(requests_session, greenwave_server, t result = testdatabuilder.create_result(item=nvr, testcase_name=TASKTRON_RELEASE_CRITICAL_TASKS[0], outcome='RUNNING') - update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr]) data = { 'decision_context': 'bodhi_update_push_stable', 'product_version': 'fedora-26', - 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject_type': 'koji_build', + 'subject_identifier': nvr, } r = requests_session.post(greenwave_server + 'api/v1.0/decision', headers={'Content-Type': 'application/json'}, @@ -511,12 +507,11 @@ def test_make_a_decision_on_running_result(requests_session, greenwave_server, t def test_make_a_decision_on_no_results(requests_session, greenwave_server, testdatabuilder): nvr = testdatabuilder.unique_nvr() - update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr]) data = { 'decision_context': 'bodhi_update_push_stable', 'product_version': 'fedora-26', - 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject_type': 'koji_build', + 'subject_identifier': nvr, } r = requests_session.post(greenwave_server + 'api/v1.0/decision', headers={'Content-Type': 'application/json'}, @@ -543,13 +538,11 @@ def test_make_a_decision_on_no_results(requests_session, greenwave_server, testd def test_empty_policy_is_always_satisfied( requests_session, greenwave_server, testdatabuilder): - nvr = testdatabuilder.unique_nvr() - update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr]) data = { 'decision_context': 'bodhi_update_push_stable', 'product_version': 'fedora-24', 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject_identifier': 'FEDORA-2000-abcdef01', } r = requests_session.post(greenwave_server + 'api/v1.0/decision', headers={'Content-Type': 'application/json'}, @@ -570,12 +563,11 @@ def test_bodhi_push_update_stable_policy( testdatabuilder.create_result(item=nvr, testcase_name=testcase_name, outcome='PASSED') - update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr]) data = { 'decision_context': 'bodhi_update_push_stable', 'product_version': 'fedora-26', - 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject_type': 'koji_build', + 'subject_identifier': nvr, } r = requests_session.post(greenwave_server + 'api/v1.0/decision', headers={'Content-Type': 'application/json'}, @@ -590,7 +582,7 @@ def test_bodhi_push_update_stable_policy( assert res_data['unsatisfied_requirements'] == [] -def test_bodhi_nonexistent_bodhi_update( +def test_bodhi_nonexistent_bodhi_update_policy( requests_session, greenwave_server, testdatabuilder): nvr = testdatabuilder.unique_nvr() for testcase_name in TASKTRON_RELEASE_CRITICAL_TASKS: @@ -610,8 +602,7 @@ def test_bodhi_nonexistent_bodhi_update( assert r.status_code == 200 res_data = r.json() assert res_data['policies_satisfied'] is True - assert 'taskotron_release_critical_tasks' in res_data['applicable_policies'] - assert 'taskotron_release_critical_tasks_with_blacklist' in res_data['applicable_policies'] + assert res_data['applicable_policies'] == [] assert res_data['summary'] == 'no tests are required' assert res_data['unsatisfied_requirements'] == [] @@ -635,12 +626,11 @@ def test_multiple_results_in_a_subject( testdatabuilder.create_result(item=nvr, testcase_name=testcase_name, outcome='PASSED') - update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr]) data = { 'decision_context': 'bodhi_update_push_stable', 'product_version': 'fedora-26', - 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject_type': 'koji_build', + 'subject_identifier': nvr, } r = requests_session.post(greenwave_server + 'api/v1.0/decision', headers={'Content-Type': 'application/json'}, @@ -677,12 +667,11 @@ def test_ignore_result(requests_session, greenwave_server, testdatabuilder): testdatabuilder.create_result(item=nvr, testcase_name=testcase_name, outcome='PASSED') - update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr]) data = { 'decision_context': 'bodhi_update_push_stable', 'product_version': 'fedora-26', - 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject_type': 'koji_build', + 'subject_identifier': nvr, } r = requests_session.post(greenwave_server + 'api/v1.0/decision', headers={'Content-Type': 'application/json'}, @@ -805,12 +794,11 @@ def test_ignore_waiver(requests_session, greenwave_server, testdatabuilder): testdatabuilder.create_result(item=nvr, testcase_name=testcase_name, outcome='PASSED') - update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr]) data = { 'decision_context': 'bodhi_update_push_stable', 'product_version': 'fedora-26', - 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject_type': 'koji_build', + 'subject_identifier': nvr, } r_ = requests_session.post(greenwave_server + 'api/v1.0/decision', headers={'Content-Type': 'application/json'}, @@ -864,12 +852,11 @@ def test_cached_false_positive(requests_session, greenwave_server, testdatabuild testdatabuilder.create_result(item=nvr, testcase_name=testcase_name, outcome='PASSED') - update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr]) data = { 'decision_context': 'bodhi_update_push_stable', 'product_version': 'fedora-26', 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject_identifier': 'FEDORA-2000-abcdef01', } r = requests_session.post(greenwave_server + 'api/v1.0/decision', headers={'Content-Type': 'application/json'}, @@ -903,12 +890,11 @@ def test_blacklist(requests_session, greenwave_server, testdatabuilder): testdatabuilder.create_result(item=nvr, testcase_name=testcase_name, outcome='PASSED') - update = testdatabuilder.create_bodhi_update(build_nvrs=[nvr]) data = { 'decision_context': 'bodhi_update_push_stable', 'product_version': 'fedora-26', 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject_identifier': 'FEDORA-2000-abcdef01', } r = requests_session.post(greenwave_server + 'api/v1.0/decision', headers={'Content-Type': 'application/json'}, @@ -1059,12 +1045,15 @@ def test_make_a_decision_for_bodhi_with_verbose_flag( results.append(testdatabuilder.create_result( item=nvr, testcase_name=testcase_name, outcome='PASSED')) - update = testdatabuilder.create_bodhi_update(build_nvrs=nvrs) data = { 'decision_context': 'bodhi_update_push_stable_with_no_rules', 'product_version': 'fedora-28', - 'subject_type': 'bodhi_update', - 'subject_identifier': update['updateid'], + 'subject': [ + {'type': 'koji_build', 'item': nvr} + for nvr in nvrs + ] + [ + {'type': 'bodhi_update', 'item': 'FEDORA-2000-abcdef01'} + ], 'verbose': True, } diff --git a/greenwave/api_v1.py b/greenwave/api_v1.py index f37e628..4237206 100644 --- a/greenwave/api_v1.py +++ b/greenwave/api_v1.py @@ -5,7 +5,7 @@ from werkzeug.exceptions import BadRequest, NotFound, UnsupportedMediaType, Inte from prometheus_client import generate_latest from greenwave import __version__ from greenwave.policies import summarize_answers, RemotePolicy, RemoteRule -from greenwave.resources import ResultsRetriever, retrieve_waivers, retrieve_builds_in_update +from greenwave.resources import ResultsRetriever, retrieve_waivers from greenwave.safe_yaml import SafeYAMLError from greenwave.utils import insert_headers, jsonp from greenwave.monitoring import ( @@ -18,7 +18,27 @@ from greenwave.monitoring import ( api = (Blueprint('api_v1', __name__)) -def subject_list_to_type_identifier(subject): +def _decision_subject(subject): + subject_type = subject.get('type') + subject_identifier = subject.get('item') + + if subject_identifier: + if subject_type in ('bodhi_update', 'component-version', 'koji_build'): + return (subject_type, subject_identifier) + + if subject_type == 'brew-build': + return ('koji_build', subject_identifier) + + if 'productmd.compose.id' in subject: + return ('compose', subject['productmd.compose.id']) + + if 'original_spec_nvr' in subject: + return ('koji_build', subject['original_spec_nvr']) + + raise BadRequest('Unrecognised subject type: %r' % subject) + + +def _decision_subjects_for_request(data): """ Greenwave < 0.8 accepted a list of arbitrary dicts for the 'subject'. Now we expect a specific type and identifier. @@ -28,29 +48,21 @@ def subject_list_to_type_identifier(subject): with WaiverDB < 0.11, but it accepts a single subject dict. Here we accept a list. """ - if (not isinstance(subject, list) or not subject or - not all(isinstance(entry, dict) for entry in subject)): - raise BadRequest('Invalid subject, must be a list of dicts') - if any(entry.get('type') == 'bodhi_update' and 'item' in entry for entry in subject): - # Assume that all the other entries in the list are just for the - # builds which are in the Bodhi update. So really, the caller wants a - # decision about the Bodhi update. Ignore everything else. (Is this - # wise? Maybe not...) - identifier = [entry for entry in subject if entry.get('type') == 'bodhi_update'][0]['item'] - return ('bodhi_update', identifier) - if len(subject) == 1 and 'productmd.compose.id' in subject[0]: - return ('compose', subject[0]['productmd.compose.id']) - # We don't know of any callers who would ask about subjects like this, - # but it's easy enough to handle here anyway: - if len(subject) == 1 and subject[0].get('type') == 'brew-build' and 'item' in subject[0]: - return ('koji_build', subject[0]['item']) - if len(subject) == 1 and subject[0].get('type') == 'koji_build' and 'item' in subject[0]: - return ('koji_build', subject[0]['item']) - if len(subject) == 1 and 'original_spec_nvr' in subject[0]: - return ('koji_build', subject[0]['original_spec_nvr']) - if len(subject) == 1 and subject[0].get('type') == 'component-version' and 'item' in subject[0]: - return ('component-version', subject[0]['item']) - raise BadRequest('Unrecognised subject type: %r' % subject) + if 'subject' in data: + subjects = data['subject'] + if (not isinstance(subjects, list) or not subjects or + not all(isinstance(entry, dict) for entry in subjects)): + raise BadRequest('Invalid subject, must be a list of dicts') + + for subject in subjects: + yield _decision_subject(subject) + else: + if 'subject_type' not in data: + raise BadRequest('Missing required "subject_type" parameter') + if 'subject_identifier' not in data: + raise BadRequest('Missing required "subject_identifier" parameter') + + yield data['subject_type'], data['subject_identifier'] def subject_type_identifier_to_list(subject_type, subject_identifier): @@ -59,19 +71,15 @@ def subject_type_identifier_to_list(subject_type, subject_identifier): This is for backwards compatibility in emitted messages. """ if subject_type == 'bodhi_update': - old_subject = [{'type': 'bodhi_update', 'item': subject_identifier}] - for nvr in retrieve_builds_in_update(subject_identifier): - old_subject.append({'type': 'koji_build', 'item': nvr}) - old_subject.append({'original_spec_nvr': nvr}) - return old_subject - elif subject_type == 'koji_build': + return [{'type': 'bodhi_update', 'item': subject_identifier}] + if subject_type == 'koji_build': return [{'type': 'koji_build', 'item': subject_identifier}] - elif subject_type == 'compose': + if subject_type == 'compose': return [{'productmd.compose.id': subject_identifier}] - elif subject_type == 'component-version': + if subject_type == 'component-version': return [{'type': 'component-version', 'item': subject_identifier}] - else: - raise BadRequest('Unrecognised subject type: %s' % subject_type) + + raise BadRequest('Unrecognised subject type: %s' % subject_type) @api.route('/version', methods=['GET']) @@ -291,20 +299,8 @@ def make_decision(): raise BadRequest('Missing required decision context') else: raise UnsupportedMediaType('No JSON payload in request') - data = request.get_json() - - # Greenwave < 0.8 - if 'subject' in data: - data['subject_type'], data['subject_identifier'] = \ - subject_list_to_type_identifier(data['subject']) - if 'subject_type' not in data: - raise BadRequest('Missing required "subject_type" parameter') - if 'subject_identifier' not in data: - raise BadRequest('Missing required "subject_identifier" parameter') - - subject_type = data['subject_type'] - subject_identifier = data['subject_identifier'] + data = request.get_json() product_version = data['product_version'] decision_context = data['decision_context'] verbose = data.get('verbose', False) @@ -324,54 +320,45 @@ def make_decision(): "'DIST_GIT_URL_TEMPLATE' and KOJI_BASE_URL in " "your configuration.") - subject_policies = [policy for policy in current_app.config['policies'] - if policy.applies_to(decision_context, product_version, subject_type)] - if subject_type == 'bodhi_update': - # Also need to apply policies for each build in the update. - build_policies = [policy for policy in current_app.config['policies'] - if policy.applies_to(decision_context, product_version, 'koji_build')] - else: - build_policies = [] - applicable_policies = subject_policies + build_policies - if not applicable_policies: - raise NotFound( - 'Cannot find any applicable policies for %s subjects at gating point %s in %s' % ( - subject_type, decision_context, product_version)) - answers = [] + verbose_results = [] + verbose_waivers = [] + applicable_policies = [] results_retriever = ResultsRetriever( cache=current_app.cache, ignore_results=ignore_results, timeout=current_app.config['REQUESTS_TIMEOUT'], verify=current_app.config['REQUESTS_VERIFY'], url=current_app.config['RESULTSDB_API_URL']) - waivers = retrieve_waivers(product_version, subject_type, [subject_identifier]) - waivers = [w for w in waivers if w['id'] not in ignore_waivers] - for policy in subject_policies: - answers.extend( - policy.check(product_version, subject_identifier, results_retriever, waivers)) + for subject_type, subject_identifier in _decision_subjects_for_request(data): + subject_policies = [ + policy for policy in current_app.config['policies'] + if policy.applies_to(decision_context, product_version, subject_type)] + if not subject_policies: + # Ignore non-existent policy for Bodhi updates. + if subject_type == 'bodhi_update': + continue - if build_policies: - build_nvrs = retrieve_builds_in_update(subject_identifier) + raise NotFound( + 'Cannot find any applicable policies for %s subjects at gating point %s in %s' % ( + subject_type, decision_context, product_version)) - nvrs_waivers = retrieve_waivers(product_version, 'koji_build', build_nvrs) - nvrs_waivers = [w for w in nvrs_waivers if w['id'] not in ignore_waivers] - waivers.extend(nvrs_waivers) + waivers = retrieve_waivers(product_version, subject_type, [subject_identifier]) + waivers = [w for w in waivers if w['id'] not in ignore_waivers] - for nvr in build_nvrs: - nvr_waivers = [ - item for item in nvrs_waivers - if nvr == item.get('subject_identifier') - ] + for policy in subject_policies: + answers.extend( + policy.check(product_version, subject_identifier, results_retriever, waivers)) - for policy in build_policies: - answers.extend( - policy.check(product_version, nvr, results_retriever, nvr_waivers)) - else: - build_nvrs = [] + applicable_policies.extend(subject_policies) - res = { + if verbose: + # Retrieve test results for all items when verbose output is requested. + verbose_results.extend(results_retriever.retrieve(subject_type, subject_identifier)) + verbose_waivers.extend(waivers) + + response = { 'policies_satisfied': all(answer.is_satisfied for answer in answers), 'summary': summarize_answers(answers), 'applicable_policies': [policy.id for policy in applicable_policies], @@ -380,17 +367,14 @@ def make_decision(): 'unsatisfied_requirements': [answer.to_json() for answer in answers if not answer.is_satisfied], } + if verbose: - # Retrieve test results for all items when verbose output is requested. - results = list(results_retriever.retrieve(subject_type, subject_identifier)) - for nvr in build_nvrs: - results += results_retriever.retrieve('koji_build', nvr) - - res.update({ - 'results': results, - 'waivers': waivers, + response.update({ + 'results': verbose_results, + 'waivers': verbose_waivers, }) - resp = jsonify(res) + + resp = jsonify(response) resp = insert_headers(resp) resp.status_code = 200 return resp diff --git a/greenwave/config.py b/greenwave/config.py index bb693e3..d3f0563 100644 --- a/greenwave/config.py +++ b/greenwave/config.py @@ -16,11 +16,6 @@ class Config(object): RESULTSDB_API_URL = 'https://taskotron.fedoraproject.org/resultsdb_api/api/v2.0' WAIVERDB_API_URL = 'https://waiverdb.fedoraproject.org/api/v1.0' - # Greenwave queries Bodhi to map builds <-> updates, - # so that it can make decisions about updates based on results for builds. - # If you don't have Bodhi, set this to None - # (this effectively disables the 'bodhi_update' subject type). - BODHI_URL = 'https://bodhi.fedoraproject.org/' # Options for outbound HTTP requests made by python-requests DIST_GIT_BASE_URL = 'https://src.fedoraproject.org/' diff --git a/greenwave/consumers/resultsdb.py b/greenwave/consumers/resultsdb.py index cfd0d32..a70f78a 100644 --- a/greenwave/consumers/resultsdb.py +++ b/greenwave/consumers/resultsdb.py @@ -172,12 +172,6 @@ class ResultsDBHandler(fedmsg.consumers.FedmsgConsumer): else: nvr = _decode(data['original_spec_nvr']) yield ('koji_build', nvr) - # If the result is for a build, it may also influence the decision - # about any update which the build is part of. - if current_app.config['BODHI_URL']: - updateid = greenwave.resources.retrieve_update_for_build(nvr) - if updateid is not None: - yield ('bodhi_update', updateid) def consume(self, message): """ diff --git a/greenwave/consumers/waiverdb.py b/greenwave/consumers/waiverdb.py index 9b2b2a3..a7180d5 100644 --- a/greenwave/consumers/waiverdb.py +++ b/greenwave/consumers/waiverdb.py @@ -12,7 +12,6 @@ to the message bus about the newly satisfied/unsatisfied policy. import logging import json -from flask import current_app import fedmsg.consumers import requests @@ -75,13 +74,6 @@ class WaiverDBHandler(fedmsg.consumers.FedmsgConsumer): with self.flask_app.app_context(): self._publish_decision_changes(subject_type, subject_identifier, msg['id'], product_version, testcase) - if subject_type == 'koji_build' and current_app.config['BODHI_URL']: - # If the waiver is for a build, it may also influence the decision - # about any update which the build is part of. - updateid = greenwave.resources.retrieve_update_for_build(subject_identifier) - if updateid is not None: - self._publish_decision_changes('bodhi_update', updateid, msg['id'], - product_version, testcase) @publish_decision_exceptions_waiver_counter.count_exceptions() def _publish_decision_changes(self, subject_type, subject_identifier, waiver_id, diff --git a/greenwave/resources.py b/greenwave/resources.py index bd818f5..3f74b7a 100644 --- a/greenwave/resources.py +++ b/greenwave/resources.py @@ -12,7 +12,7 @@ import json import requests import urllib3.exceptions -from urllib.parse import urlparse, urljoin +from urllib.parse import urlparse import xmlrpc.client from flask import current_app from werkzeug.exceptions import BadGateway @@ -207,55 +207,6 @@ def retrieve_yaml_remote_rule(rev, pkg_name, pkg_namespace): return response.content -def retrieve_builds_in_update(update_id): - """ - Queries Bodhi to find the list of builds in the given update. - Returns a list of build NVRs. - """ - if not current_app.config['BODHI_URL']: - log.warning('Making a decision about Bodhi update %s ' - 'but Bodhi integration is disabled! ' - 'Assuming no builds in update', - update_id) - return [] - update_info_url = urljoin(current_app.config['BODHI_URL'], - '/updates/{}'.format(update_id)) - timeout = current_app.config['REQUESTS_TIMEOUT'] - verify = current_app.config['REQUESTS_VERIFY'] - response = requests_session.get(update_info_url, - headers={'Accept': 'application/json'}, - timeout=timeout, verify=verify) - - # Ignore failures to retrieve Bodhi update. - if not response.ok: - log.warning( - 'Making a decision about Bodhi update %s failed: %r', - update_id, response) - return [] - - return [build['nvr'] for build in response.json()['update']['builds']] - - -def retrieve_update_for_build(nvr): - """ - Queries Bodhi to find the update which the given build is in (if any). - Returns a Bodhi updateid, or None if the build is not in any update. - """ - updates_list_url = urljoin(current_app.config['BODHI_URL'], '/updates/') - params = {'builds': nvr} - timeout = current_app.config['REQUESTS_TIMEOUT'] - verify = current_app.config['REQUESTS_VERIFY'] - response = requests_session.get(updates_list_url, - params=params, - headers={'Accept': 'application/json'}, - timeout=timeout, verify=verify) - response.raise_for_status() - matching_updates = response.json()['updates'] - if matching_updates: - return matching_updates[0]['updateid'] - return None - - # NOTE - not cached, for now. @greenwave.utils.retry(wait_on=urllib3.exceptions.NewConnectionError) def retrieve_waivers(product_version, subject_type, subject_identifiers): diff --git a/greenwave/tests/test_resultsdb_consumer.py b/greenwave/tests/test_resultsdb_consumer.py index 3d5638b..4e74225 100644 --- a/greenwave/tests/test_resultsdb_consumer.py +++ b/greenwave/tests/test_resultsdb_consumer.py @@ -11,60 +11,29 @@ from greenwave.policies import Policy def test_announcement_keys_decode_with_list(): cls = greenwave.consumers.resultsdb.ResultsDBHandler - app = greenwave.app_factory.create_app() message = {'msg': {'data': { 'original_spec_nvr': ['glibc-1.0-1.fc27'], }}} - - with app.app_context(): - with mock.patch('greenwave.resources.retrieve_update_for_build') as f: - f.return_value = None - subjects = list(cls.announcement_subjects(message)) + subjects = list(cls.announcement_subjects(message)) assert subjects == [('koji_build', 'glibc-1.0-1.fc27')] -def test_announcement_subjects_include_bodhi_update(): - cls = greenwave.consumers.resultsdb.ResultsDBHandler - app = greenwave.app_factory.create_app() - message = {'msg': {'data': { - 'original_spec_nvr': ['glibc-1.0-2.fc27'], - }}} - - with app.app_context(): - with mock.patch('greenwave.resources.retrieve_update_for_build') as f: - f.return_value = 'FEDORA-27-12345678' - subjects = list(cls.announcement_subjects(message)) - - # Result was about a Koji build, but the build is in a Bodhi update. - # So we should announce decisions about both subjects. - assert subjects == [ - ('koji_build', 'glibc-1.0-2.fc27'), - ('bodhi_update', 'FEDORA-27-12345678'), - ] - - def test_announcement_subjects_for_brew_build(): # The 'brew-build' type appears internally within Red Hat. We treat it as an # alias of 'koji_build'. cls = greenwave.consumers.resultsdb.ResultsDBHandler - app = greenwave.app_factory.create_app() message = {'msg': {'data': { 'type': 'brew-build', 'item': ['glibc-1.0-3.fc27'], }}} - - with app.app_context(): - with mock.patch('greenwave.resources.retrieve_update_for_build') as f: - f.return_value = None - subjects = list(cls.announcement_subjects(message)) + subjects = list(cls.announcement_subjects(message)) assert subjects == [('koji_build', 'glibc-1.0-3.fc27')] def test_announcement_subjects_for_autocloud_compose(): cls = greenwave.consumers.resultsdb.ResultsDBHandler - app = greenwave.app_factory.create_app() message = { 'msg': { 'task': { @@ -81,16 +50,11 @@ def test_announcement_subjects_for_autocloud_compose(): } } } - - with app.app_context(): - with mock.patch('greenwave.resources.retrieve_update_for_build') as f: - f.return_value = None - subjects = list(cls.announcement_subjects(message)) + subjects = list(cls.announcement_subjects(message)) assert subjects == [('compose', 'Fedora-AtomicHost-28_Update-20180723.1839.x86_64.qcow2')] -@mock.patch('greenwave.resources.retrieve_update_for_build') @mock.patch('greenwave.resources.ResultsRetriever.retrieve') @mock.patch('greenwave.resources.retrieve_decision') @mock.patch('greenwave.resources.retrieve_scm_from_koji') @@ -101,8 +65,7 @@ def test_remote_rule_decision_change( mock_retrieve_yaml_remote_rule, mock_retrieve_scm_from_koji, mock_retrieve_decision, - mock_retrieve_results, - mock_retrieve_bodhi_update): + mock_retrieve_results): """ Test publishing decision change message for test cases mentioned in gating.yaml. @@ -135,7 +98,6 @@ def test_remote_rule_decision_change( 'data': {'item': nvr, 'type': 'koji_build'}, } mock_retrieve_results.return_value = [result] - mock_retrieve_bodhi_update.return_value = None def retrieve_decision(url, data): #pylint: disable=unused-argument @@ -191,7 +153,6 @@ def test_remote_rule_decision_change( } -@mock.patch('greenwave.resources.retrieve_update_for_build') @mock.patch('greenwave.resources.ResultsRetriever.retrieve') @mock.patch('greenwave.resources.retrieve_decision') @mock.patch('greenwave.resources.retrieve_scm_from_koji') @@ -202,8 +163,7 @@ def test_remote_rule_decision_change_not_matching( mock_retrieve_yaml_remote_rule, mock_retrieve_scm_from_koji, mock_retrieve_decision, - mock_retrieve_results, - mock_retrieve_bodhi_update): + mock_retrieve_results): """ Test publishing decision change message for test cases mentioned in gating.yaml. @@ -236,7 +196,6 @@ def test_remote_rule_decision_change_not_matching( 'data': {'item': nvr, 'type': 'koji_build'}, } mock_retrieve_results.return_value = [result] - mock_retrieve_bodhi_update.return_value = None def retrieve_decision(url, data): #pylint: disable=unused-argument