67e51045e8eb550c40d984aef2002011b2b147bb
[nfvbench.git] / behave_tests / features / steps / testapi.py
1 #!/usr/bin/env python
2 # Copyright 2021 Orange
3 #
4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
5 #    not use this file except in compliance with the License. You may obtain
6 #    a copy of the License at
7 #
8 #         http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #    Unless required by applicable law or agreed to in writing, software
11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 #    License for the specific language governing permissions and limitations
14 #    under the License.
15 #
16
17 import json
18 import requests
19
20
21 class TestapiClient:
22     def __init__(self, testapi_url: str, logger):
23         """
24         Args:
25             testapi_url: testapi URL as a string, for instance
26                 "http://172.20.73.203:8000/api/v1/results"
27
28             logger: reference to behave_tests logger.
29
30         """
31         self._base_url = testapi_url
32         self._logger = logger
33
34     def find_last_result(self, testapi_params, scenario_tag: str, nfvbench_test_input):
35         """Search testapi database and return latest result matching filters.
36
37         Look for the most recent testapi result matching testapi params, behave
38         scenario tag and nfvbench test input params, and return that result as a
39         dictionary.
40
41         Args:
42             testapi_params: dict holding the parameters of the testapi request.  See
43                 `build_testapi_url()` for the list of supported keys.
44
45             scenario_tag: Behave scenario tag to filter results.  One of
46                 "throughput" or "latency".
47
48             nfvbench_test_input: dict holding nfvbench test parameters and used
49                 to filter the testapi results.  The following keys are currently
50                 supported:
51                 - mandatory keys: 'duration_sec', 'frame_sizes', 'flow_count', 'rate'
52                 - optional keys: 'user_label', 'flavor_type'
53
54         Returns:
55             None if no result matching the filters can be found, else a dictionary
56             built from testapi JSON test result.
57
58         """
59         self._logger.info(f"find_last_result: filter on scenario tag: {scenario_tag}")
60         nfvbench_input_str = nfvbench_input_to_str(nfvbench_test_input)
61         self._logger.info(f"find_last_result: filter on test conditions: {nfvbench_input_str}")
62
63         page = 1
64         while True:  # While there are results pages to read
65             url = self._build_testapi_url(testapi_params, page)
66             self._logger.info("find_last_result: GET " + url)
67             last_results = self._do_testapi_request(url)
68
69             for result in last_results["results"]:
70                 for tagged_result in result["details"]["results"][scenario_tag]:
71                     if tagged_result["output"]["status"] != "OK":
72                         # Drop result if nfvbench status is not OK
73                         # (such result should not have been put in database by behave_tests,
74                         # but let's be cautious)
75                         continue
76                     if equal_test_conditions(tagged_result["input"], nfvbench_test_input):
77                         return tagged_result
78
79             if page >= last_results["pagination"]["total_pages"]:
80                 break
81             page += 1
82
83         return None
84
85     def _build_testapi_url(self, testapi_params, page=1):
86         """Build URL for testapi request.
87
88         Build a URL for a testapi HTTP GET request using the provided parameters and
89         limiting the results to the tests whose criteria equals "PASS".
90
91         Args:
92             testapi_params: dictionary holding the parameters of the testapi
93                 request:
94                 - mandatory keys: "project_name", "case_name"
95                 - optional keys: "installer", "pod_name"
96                 - ignored keys: "build_tag", "scenario", "version", "criteria".
97
98             page: (Optional) number of the results page to get.
99
100         """
101         url = self._base_url
102         url += f"?project={testapi_params['project_name']}"
103         url += f"&case={testapi_params['case_name']}"
104
105         if "installer" in testapi_params.keys():
106             url += f"&installer={testapi_params['installer']}"
107         if "pod_name" in testapi_params.keys():
108             url += f"&pod={testapi_params['pod_name']}"
109
110         url += '&criteria=PASS'
111         url += f"&page={page}"
112
113         return url
114
115     def _do_testapi_request(self, testapi_url):
116         """Perform HTTP GET request on testapi.
117
118         Perform an HTTP GET request on testapi, check status code and return JSON
119         results as dictionary.
120
121         Args: testapi_url: a complete URL to request testapi results (with base
122             endpoint and parameters)
123
124         Returns:
125             The JSON document from testapi as a Python dictionary
126
127         Raises:
128
129         """
130         response = requests.get(testapi_url)
131         assert response.status_code == 200  # TODO: better error message
132         results = json.loads(response.text)
133         return results
134
135
136 def equal_test_conditions(testapi_input, nfvbench_input):
137     """Check test conditions in behave scenario results record.
138
139     Check whether a behave scenario results record from testapi matches a given
140     nfvbench input, ie whether the record comes from a test done under the same
141     conditions (frame size, flow count, ...)
142
143     Args:
144         testapi_input: dict holding the test conditions of a behave scenario
145             results record from testapi
146
147         nfvbench_input: dict of nfvbench test parameters (reference)
148
149     The following dict keys are currently supported:
150         - mandatory keys: 'duration_sec', 'frame_sizes', 'flow_count', 'rate'
151         - optional keys: 'user_label', 'flavor_type'
152
153     Optional keys are taken into account only when they can be found in
154     `nfvbench_input`, else they are ignored.
155
156     Returns:
157         True if test conditions match, else False.
158
159     """
160     # Select required keys (other keys can be not set or unconsistent between scenarios)
161     required_keys = ['duration_sec', 'frame_sizes', 'flow_count', 'rate']
162     if 'user_label' in nfvbench_input:
163         required_keys.append('user_label')
164     if 'flavor_type' in nfvbench_input:
165         required_keys.append('flavor_type')
166
167     try:
168         testapi_subset = {k: testapi_input[k] for k in required_keys}
169         nfvbench_subset = {k: nfvbench_input[k] for k in required_keys}
170         return testapi_subset == nfvbench_subset
171     except KeyError:
172         # Fail the comparison if a required key is missing from one of the dicts
173         return False
174
175
176 def nfvbench_input_to_str(nfvbench_input: dict) -> str:
177     """Build string showing nfvbench input parameters used for results search
178
179     Args:
180         nfvbench_input: dict of nfvbench test parameters
181     """
182     string = ""
183     for key in ['user_label', 'flavor_type', 'frame_sizes', 'flow_count', 'rate', 'duration_sec']:
184         if key in nfvbench_input:
185             string += f"{key}={nfvbench_input[key]} "
186     return string