From daaae195e46c21d3e2553546a864219e78ef2107 Mon Sep 17 00:00:00 2001 From: Yashvardhan Nanavati Date: Mar 30 2019 08:15:17 +0000 Subject: Support for on-demand policy in Greenwave --- diff --git a/functional-tests/test_api_v1.py b/functional-tests/test_api_v1.py index b625494..eccbb51 100644 --- a/functional-tests/test_api_v1.py +++ b/functional-tests/test_api_v1.py @@ -94,7 +94,8 @@ def test_cannot_make_decision_without_product_version(requests_session, greenwav @pytest.mark.smoke -def test_cannot_make_decision_without_decision_context(requests_session, greenwave_server): +def test_cannot_make_decision_without_decision_context_and_user_policies( + requests_session, greenwave_server): data = { 'product_version': 'fedora-26', 'subject_type': 'bodhi_update', @@ -104,7 +105,7 @@ def test_cannot_make_decision_without_decision_context(requests_session, greenwa headers={'Content-Type': 'application/json'}, data=json.dumps(data)) assert r.status_code == 400 - assert 'Missing required decision context' == r.json()['message'] + assert 'Either one of decision context or user policies is required.' == r.json()['message'] @pytest.mark.smoke @@ -1290,3 +1291,79 @@ def test_make_a_decision_with_verbose_flag_all_results_returned( assert res_data['results'] == list(reversed(results)) assert len(res_data['waivers']) == len(expected_waivers) assert res_data['waivers'] == expected_waivers + + +@pytest.mark.smoke +def test_cannot_make_decision_with_both_decision_context_and_user_policies( + requests_session, greenwave_server): + data = { + 'product_version': 'fedora-26', + 'subject_type': 'bodhi_update', + 'subject_identifier': 'FEDORA-2018-ec7cb4d5eb', + 'decision_context': 'koji_build_push_missing_results', + 'user_policies': dedent(""" + --- !Policy + id: "koji-test-policy-missing-results" + product_versions: + - fedora-30 + decision_context: koji_build_push_missing_results + subject_type: koji_build + rules: + - !PassingTestCaseRule {test_case_name: dist.rpmdeplint} + """) + + } + r = requests_session.post(greenwave_server + 'api/v1.0/decision', + headers={'Content-Type': 'application/json'}, + data=json.dumps(data)) + assert r.status_code == 400 + assert ('Invalid request. Cannot have both' + ' decision context and user policies') == r.json()['message'] + + +def test_make_a_decision_with_verbose_flag_on_demand_policy( + requests_session, greenwave_server, testdatabuilder): + nvr = testdatabuilder.unique_nvr() + results = [] + expected_waivers = [] + # First one failed but was waived + results.append(testdatabuilder.create_result(item=nvr, + testcase_name=TASKTRON_RELEASE_CRITICAL_TASKS[0], + outcome='FAILED')) + expected_waivers.append( + testdatabuilder.create_waiver(nvr=nvr, + product_version='fedora-31', + testcase_name=TASKTRON_RELEASE_CRITICAL_TASKS[0], + comment='This is fine')) + for testcase_name in TASKTRON_RELEASE_CRITICAL_TASKS[1:]: + results.append(testdatabuilder.create_result(item=nvr, + testcase_name=testcase_name, + outcome='PASSED')) + + data = { + 'product_version': 'fedora-31', + 'subject_type': 'koji_build', + 'subject_identifier': nvr, + 'verbose': True, + 'user_policies': dedent(""" + --- !Policy + id: "user-policy-test" + product_versions: + - fedora-31 + subject_type: koji_build + rules: + - !PassingTestCaseRule {test_case_name: dist.abicheck} + - !PassingTestCaseRule {test_case_name: dist.rpmdeplint} + - !PassingTestCaseRule {test_case_name: dist.upgradepath} + """) + } + r = requests_session.post(greenwave_server + 'api/v1.0/decision', + headers={'Content-Type': 'application/json'}, + data=json.dumps(data)) + assert r.status_code == 200 + res_data = r.json() + + assert len(res_data['results']) == len(results) + assert res_data['results'] == list(reversed(results)) + assert len(res_data['waivers']) == len(expected_waivers) + assert res_data['waivers'] == expected_waivers diff --git a/greenwave/api_v1.py b/greenwave/api_v1.py index 7c472e5..eb6dd14 100644 --- a/greenwave/api_v1.py +++ b/greenwave/api_v1.py @@ -7,6 +7,7 @@ from prometheus_client import generate_latest from greenwave import __version__ from greenwave.policies import (summarize_answers, RemotePolicy, + OnDemandPolicy, _missing_decision_contexts_in_parent_policies) from greenwave.resources import ResultsRetriever, retrieve_waivers from greenwave.safe_yaml import SafeYAMLError @@ -301,9 +302,11 @@ def make_decision(): log.error('Missing required product version') raise BadRequest('Missing required product version') if ('decision_context' not in request.get_json() or - not request.get_json()['decision_context']): - log.error('Missing required decision context') - raise BadRequest('Missing required decision context') + not request.get_json()['decision_context']) and \ + ('user_policies' not in request.get_json() or + not request.get_json()['user_policies']): + log.error('Either one of decision context or user policies is required.') + raise BadRequest('Either one of decision context or user policies is required.') else: log.error('No JSON payload in request') raise UnsupportedMediaType('No JSON payload in request') @@ -311,7 +314,21 @@ def make_decision(): data = request.get_json() log.debug('New decision request for data: %s', data) product_version = data['product_version'] - decision_context = data['decision_context'] + + decision_context = data.get('decision_context', None) + on_demand_policies = data.get('user_policies', None) + if decision_context and on_demand_policies: + log.error('Invalid request. Cannot have both decision context and user policies') + raise BadRequest('Invalid request. Cannot have both decision context and user policies') + + # Validate YAML + if on_demand_policies: + try: + on_demand_policies = OnDemandPolicy.safe_load_all(on_demand_policies) + except SafeYAMLError as e: + log.error(str(e)) + raise BadRequest(str(e)) + verbose = data.get('verbose', False) if not isinstance(verbose, bool): log.error('Invalid verbose flag, must be a bool') @@ -330,9 +347,10 @@ def make_decision(): verify=current_app.config['REQUESTS_VERIFY'], url=current_app.config['RESULTSDB_API_URL']) + policies = on_demand_policies or current_app.config['policies'] for subject_type, subject_identifier in _decision_subjects_for_request(data): subject_policies = [ - policy for policy in current_app.config['policies'] + policy for policy in policies if policy.matches( decision_context=decision_context, product_version=product_version, diff --git a/greenwave/policies.py b/greenwave/policies.py index b3b6f4a..810e4ac 100644 --- a/greenwave/policies.py +++ b/greenwave/policies.py @@ -592,6 +592,24 @@ class Policy(SafeYAMLObject): return 'Policy {!r}'.format(self.id or 'untitled') +class OnDemandPolicy(Policy): + root_yaml_tag = '!Policy' + + safe_yaml_attributes = { + 'id': SafeYAMLString(), + 'product_versions': SafeYAMLList(str), + 'user_policies': SafeYAMLString(str), + 'subject_type': SafeYAMLString(), + 'rules': SafeYAMLList(Rule), + 'blacklist': SafeYAMLList(str, optional=True), + 'excluded_packages': SafeYAMLList(str, optional=True), + 'packages': SafeYAMLList(str, optional=True), + 'relevance_key': SafeYAMLString(optional=True), + 'relevance_value': SafeYAMLString(optional=True), + + } + + class RemotePolicy(Policy): root_yaml_tag = '!Policy'