From e1c5b26b744ea26c4c0db54be0a91adbacca3a38 Mon Sep 17 00:00:00 2001
From: Lukas Holecek <lholecek@redhat.com>
Date: Jul 17 2018 12:15:50 +0000
Subject: Make decision about compose for all architectures/variants


---

diff --git a/conf/policies/redhat.yaml b/conf/policies/redhat.yaml
index a5200e8..90264bb 100644
--- a/conf/policies/redhat.yaml
+++ b/conf/policies/redhat.yaml
@@ -19,3 +19,12 @@ rules:
       "policycoreutils",
     ]
   }
+--- !Policy
+id: "rtt_compose"
+product_versions:
+  - rhel-something
+decision_context: rtt_compose_gate
+subject_type: compose
+blacklist: []
+rules:
+  - !PassingTestCaseRule {test_case_name: rtt.acceptance.validation}
diff --git a/functional-tests/conftest.py b/functional-tests/conftest.py
index 47150be..e1b9ae9 100644
--- a/functional-tests/conftest.py
+++ b/functional-tests/conftest.py
@@ -274,6 +274,18 @@ class TestDataBuilder(object):
             data['data']['scenario'] = scenario
         return self._create_result(data)
 
+    def create_rtt_compose_result(self, compose_id, outcome, variant, architecture):
+        data = {
+            'testcase': {'name': 'rtt.acceptance.validation'},
+            'outcome': outcome,
+            'data': {
+                'productmd.compose.id': [compose_id],
+                'system_variant': [variant],
+                'system_architecture': [architecture],
+            }
+        }
+        return self._create_result(data)
+
     def create_koji_build_result(self, nvr, testcase_name, outcome, type_='koji_build'):
         data = {
             'testcase': {'name': testcase_name},
diff --git a/functional-tests/test_api_v1.py b/functional-tests/test_api_v1.py
index 5688ea3..cf5307b 100644
--- a/functional-tests/test_api_v1.py
+++ b/functional-tests/test_api_v1.py
@@ -36,7 +36,7 @@ def test_inspect_policies(requests_session, greenwave_server):
     assert r.status_code == 200
     body = r.json()
     policies = body['policies']
-    assert len(policies) == 7
+    assert len(policies) == 8
     assert any(p['id'] == 'taskotron_release_critical_tasks' for p in policies)
     assert any(p['decision_context'] == 'bodhi_update_push_stable' for p in policies)
     assert any(p['product_versions'] == ['fedora-26'] for p in policies)
@@ -891,3 +891,65 @@ def test_validate_gating_yaml_missing_tag(requests_session, greenwave_server):
         greenwave_server + 'api/v1.0/validate-gating-yaml', data=gating_yaml)
     assert result.json().get('message') == "Missing !Policy tag"
     assert result.status_code == 400
+
+
+def test_make_a_decision_about_compose_all_variants_architectures(
+        requests_session, greenwave_server, testdatabuilder):
+    compose_id = testdatabuilder.unique_compose_id()
+
+    failed_results = testdatabuilder.create_rtt_compose_result(
+        compose_id=compose_id,
+        variant='BaseOS',
+        architecture='ppc64',
+        outcome='FAILED')
+
+    testdatabuilder.create_rtt_compose_result(
+        compose_id=compose_id,
+        variant='BaseOS',
+        architecture='x86_64',
+        outcome='PASSED')
+
+    data = {
+        'decision_context': 'rtt_compose_gate',
+        'product_version': 'rhel-something',
+        'subject_type': 'compose',
+        'subject_identifier': compose_id,
+    }
+    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 not res_data['policies_satisfied']
+    assert res_data['unsatisfied_requirements'] == [{
+        'item': {'productmd.compose.id': compose_id},
+        'result_id': failed_results['id'],
+        'scenario': None,
+        'testcase': 'rtt.acceptance.validation',
+        'type': 'test-result-failed'
+    }]
+
+
+def test_make_a_decision_about_compose_new_variants_architectures(
+        requests_session, greenwave_server, testdatabuilder):
+    compose_id = testdatabuilder.unique_compose_id()
+
+    for arch, outcome in [('ppc64', 'FAILED'), ('ppc64', 'PASSED'), ('x86_64', 'PASSED')]:
+        testdatabuilder.create_rtt_compose_result(
+            compose_id=compose_id,
+            variant='BaseOS',
+            architecture=arch,
+            outcome=outcome)
+
+    data = {
+        'decision_context': 'rtt_compose_gate',
+        'product_version': 'rhel-something',
+        'subject_type': 'compose',
+        'subject_identifier': compose_id,
+    }
+    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 res_data['policies_satisfied']
diff --git a/greenwave/policies.py b/greenwave/policies.py
index cde6454..0d1b13b 100644
--- a/greenwave/policies.py
+++ b/greenwave/policies.py
@@ -354,21 +354,32 @@ class PassingTestCaseRule(Rule):
             return TestResultMissingWaived(
                 subject_type, subject_identifier, self.test_case_name, self.scenario)
 
+        # For compose make decisions based on all architectures and variants.
+        if subject_type == 'compose':
+            visited_arch_variants = set()
+            answers = []
+            for result in matching_results:
+                result_data = result['data']
+
+                # Items under test result "data" are lists which are unhashable
+                # types in Python. This converts anything that is stored there
+                # to a string so we don't have to care about the stored value.
+                arch_variant = (
+                    str(result_data.get('system_architecture')),
+                    str(result_data.get('system_variant')))
+
+                if arch_variant not in visited_arch_variants:
+                    visited_arch_variants.add(arch_variant)
+                    answers.append(
+                        self._answer_for_result(result, waivers, subject_type, subject_identifier))
+
+            return answers
+
         # If we find multiple matching results, we always use the first one which
         # will be the latest chronologically, because ResultsDB always returns
         # results ordered by `submit_time` descending.
         matching_result = matching_results[0]
-        if matching_result['outcome'] in ['PASSED', 'INFO']:
-            return TestResultPassed(self.test_case_name, matching_result['id'])
-
-        # XXX limit who is allowed to waive
-        if any(w['subject'] == dict([(key, value[0])
-               for key, value in matching_result['data'].items()]) and
-               w['testcase'] == matching_result['testcase']['name'] and
-               w['waived'] for w in waivers):
-            return TestResultPassed(self.test_case_name, matching_result['id'])
-        return TestResultFailed(subject_type, subject_identifier, self.test_case_name,
-                                self.scenario, matching_result['id'])
+        return self._answer_for_result(matching_result, waivers, subject_type, subject_identifier)
 
     def to_json(self):
         return {
@@ -377,6 +388,19 @@ class PassingTestCaseRule(Rule):
             'scenario': self.scenario,
         }
 
+    def _answer_for_result(self, result, waivers, subject_type, subject_identifier):
+        if result['outcome'] in ['PASSED', 'INFO']:
+            return TestResultPassed(self.test_case_name, result['id'])
+
+        # XXX limit who is allowed to waive
+        if any(w['subject'] == dict([(key, value[0])
+               for key, value in result['data'].items()]) and
+               w['testcase'] == result['testcase']['name'] and
+               w['waived'] for w in waivers):
+            return TestResultPassed(self.test_case_name, result['id'])
+        return TestResultFailed(subject_type, subject_identifier, self.test_case_name,
+                                self.scenario, result['id'])
+
 
 class PackageSpecificRule(Rule):
     """

