Add new function to get indivial test score for aarch64
[releng.git] / utils / test / reporting / reporting / utils / reporting_utils.py
1 #!/usr/bin/python
2 #
3 # This program and the accompanying materials
4 # are made available under the terms of the Apache License, Version 2.0
5 # which accompanies this distribution, and is available at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 import logging
10 import json
11 import os
12 import requests
13 import pdfkit
14 import yaml
15
16 from urllib2 import Request, urlopen, URLError
17
18
19 # ----------------------------------------------------------
20 #
21 #               YAML UTILS
22 #
23 # -----------------------------------------------------------
24 def get_parameter_from_yaml(parameter, config_file):
25     """
26     Returns the value of a given parameter in file.yaml
27     parameter must be given in string format with dots
28     Example: general.openstack.image_name
29     """
30     with open(config_file) as my_file:
31         file_yaml = yaml.safe_load(my_file)
32     my_file.close()
33     value = file_yaml
34     for element in parameter.split("."):
35         value = value.get(element)
36         if value is None:
37             raise ValueError("The parameter %s is not defined in"
38                              " reporting.yaml" % parameter)
39     return value
40
41
42 def get_config(parameter):
43     """
44     Get configuration parameter from yaml configuration file
45     """
46     yaml_ = os.environ["CONFIG_REPORTING_YAML"]
47     return get_parameter_from_yaml(parameter, yaml_)
48
49
50 # ----------------------------------------------------------
51 #
52 #               LOGGER UTILS
53 #
54 # -----------------------------------------------------------
55 def getLogger(module):
56     """
57     Get Logger
58     """
59     log_formatter = logging.Formatter("%(asctime)s [" +
60                                       module +
61                                       "] [%(levelname)-5.5s]  %(message)s")
62     logger = logging.getLogger()
63     log_file = get_config('general.log.log_file')
64     log_level = get_config('general.log.log_level')
65
66     file_handler = logging.FileHandler("{0}/{1}".format('.', log_file))
67     file_handler.setFormatter(log_formatter)
68     logger.addHandler(file_handler)
69
70     console_handler = logging.StreamHandler()
71     console_handler.setFormatter(log_formatter)
72     logger.addHandler(console_handler)
73     logger.setLevel(log_level)
74     return logger
75
76
77 # ----------------------------------------------------------
78 #
79 #               REPORTING UTILS
80 #
81 # -----------------------------------------------------------
82 def getApiResults(case, installer, scenario, version):
83     """
84     Get Results by calling the API
85     """
86     results = json.dumps([])
87     # to remove proxy (to be removed at the end for local test only)
88     # proxy_handler = urllib2.ProxyHandler({})
89     # opener = urllib2.build_opener(proxy_handler)
90     # urllib2.install_opener(opener)
91     # url = "http://127.0.0.1:8000/results?case=" + case + \
92     #       "&period=30&installer=" + installer
93     period = get_config('general.period')
94     url_base = get_config('testapi.url')
95     nb_tests = get_config('general.nb_iteration_tests_success_criteria')
96
97     url = ("http://" + url_base + "?case=" + case +
98            "&period=" + str(period) + "&installer=" + installer +
99            "&scenario=" + scenario + "&version=" + version +
100            "&last=" + str(nb_tests))
101     request = Request(url)
102
103     try:
104         response = urlopen(request)
105         k = response.read()
106         results = json.loads(k)
107     except URLError:
108         print "Error when retrieving results form API"
109
110     return results
111
112
113 def getScenarios(project, case, installer, version):
114     """
115     Get the list of Scenarios
116     """
117
118     period = get_config('general.period')
119     url_base = get_config('testapi.url')
120
121     url = ("http://" + url_base +
122            "?installer=" + installer +
123            "&period=" + str(period))
124
125     if version is not None:
126         url += "&version=" + version
127
128     if project is not None:
129         url += "&project=" + project
130
131     if case is not None:
132         url += "&case=" + case
133
134     try:
135         request = Request(url)
136         response = urlopen(request)
137         k = response.read()
138         results = json.loads(k)
139         test_results = results['results']
140         try:
141             page = results['pagination']['total_pages']
142             if page > 1:
143                 test_results = []
144                 for i in range(1, page + 1):
145                     url_page = url + "&page=" + str(i)
146                     request = Request(url_page)
147                     response = urlopen(request)
148                     k = response.read()
149                     results = json.loads(k)
150                     test_results += results['results']
151         except KeyError:
152             print "No pagination detected"
153     except URLError as err:
154         print 'Got an error code: {}'.format(err)
155
156     if test_results is not None:
157         test_results.reverse()
158         scenario_results = {}
159
160         for my_result in test_results:
161             # Retrieve all the scenarios per installer
162             if not my_result['scenario'] in scenario_results.keys():
163                 scenario_results[my_result['scenario']] = []
164             # Do we consider results from virtual pods ...
165             # Do we consider results for non HA scenarios...
166             exclude_virtual_pod = get_config('functest.exclude_virtual')
167             exclude_noha = get_config('functest.exclude_noha')
168             if ((exclude_virtual_pod and "virtual" in my_result['pod_name']) or
169                     (exclude_noha and "noha" in my_result['scenario'])):
170                 print "exclude virtual pod results..."
171             else:
172                 scenario_results[my_result['scenario']].append(my_result)
173
174     return scenario_results
175
176
177 def getScenarioStats(scenario_results):
178     """
179     Get the number of occurence of scenarios over the defined PERIOD
180     """
181     scenario_stats = {}
182     for res_k, res_v in scenario_results.iteritems():
183         scenario_stats[res_k] = len(res_v)
184     return scenario_stats
185
186
187 def getScenarioStatus(installer, version):
188     """
189     Get the status of a scenariofor Yardstick
190     """
191     period = get_config('general.period')
192     url_base = get_config('testapi.url')
193
194     url = ("http://" + url_base + "?case=scenario_status" +
195            "&installer=" + installer +
196            "&version=" + version + "&period=" + str(period))
197     request = Request(url)
198
199     try:
200         response = urlopen(request)
201         k = response.read()
202         response.close()
203         results = json.loads(k)
204         test_results = results['results']
205     except URLError:
206         print "GetScenarioStatus: error when calling the API"
207
208     x86 = 'x86'
209     aarch64 = 'aarch64'
210     scenario_results = {x86: {}, aarch64: {}}
211     result_dict = {x86: {}, aarch64: {}}
212     if test_results is not None:
213         for test_r in test_results:
214             if (test_r['stop_date'] != 'None' and
215                     test_r['criteria'] is not None):
216                 scenario_name = test_r['scenario']
217                 if 'arm' in test_r['pod_name']:
218                     if not test_r['scenario'] in scenario_results[aarch64]:
219                         scenario_results[aarch64][scenario_name] = []
220                     scenario_results[aarch64][scenario_name].append(test_r)
221                 else:
222                     if not test_r['scenario'] in scenario_results[x86]:
223                         scenario_results[x86][scenario_name] = []
224                     scenario_results[x86][scenario_name].append(test_r)
225
226         for key in scenario_results:
227             for scen_k, scen_v in scenario_results[key].items():
228                 # scenario_results[k] = v[:LASTEST_TESTS]
229                 s_list = []
230                 for element in scen_v:
231                     if element['criteria'] == 'PASS':
232                         s_list.append(1)
233                     else:
234                         s_list.append(0)
235                 result_dict[key][scen_k] = s_list
236
237     # return scenario_results
238     return result_dict
239
240
241 def getQtipResults(version, installer):
242     """
243     Get QTIP results
244     """
245     period = get_config('qtip.period')
246     url_base = get_config('testapi.url')
247
248     url = ("http://" + url_base + "?project=qtip" +
249            "&installer=" + installer +
250            "&version=" + version + "&period=" + str(period))
251     request = Request(url)
252
253     try:
254         response = urlopen(request)
255         k = response.read()
256         response.close()
257         results = json.loads(k)['results']
258     except URLError as err:
259         print 'Got an error code: {}'.format(err)
260
261     result_dict = {}
262     if results:
263         for r in results:
264             key = '{}/{}'.format(r['pod_name'], r['scenario'])
265             if key not in result_dict.keys():
266                 result_dict[key] = []
267             result_dict[key].append(r['details']['score'])
268
269     # return scenario_results
270     return result_dict
271
272
273 def getNbtestOk(results):
274     """
275     based on default value (PASS) count the number of test OK
276     """
277     nb_test_ok = 0
278     for my_result in results:
279         for res_k, res_v in my_result.iteritems():
280             try:
281                 if "PASS" in res_v:
282                     nb_test_ok += 1
283             except Exception:
284                 print "Cannot retrieve test status"
285     return nb_test_ok
286
287
288 def getCaseScore(testCase, installer, scenario, version):
289     """
290     Get Result  for a given Functest Testcase
291     """
292     # retrieve raw results
293     results = getApiResults(testCase, installer, scenario, version)
294     # let's concentrate on test results only
295     test_results = results['results']
296
297     # if results found, analyze them
298     if test_results is not None:
299         test_results.reverse()
300
301         scenario_results = []
302
303         # print " ---------------- "
304         # print test_results
305         # print " ---------------- "
306         # print "nb of results:" + str(len(test_results))
307
308         for res_r in test_results:
309             # print r["start_date"]
310             # print r["criteria"]
311             scenario_results.append({res_r["start_date"]: res_r["criteria"]})
312         # sort results
313         scenario_results.sort()
314         # 4 levels for the results
315         # 3: 4+ consecutive runs passing the success criteria
316         # 2: <4 successful consecutive runs but passing the criteria
317         # 1: close to pass the success criteria
318         # 0: 0% success, not passing
319         # -1: no run available
320         test_result_indicator = 0
321         nbTestOk = getNbtestOk(scenario_results)
322
323         # print "Nb test OK (last 10 days):"+ str(nbTestOk)
324         # check that we have at least 4 runs
325         if len(scenario_results) < 1:
326             # No results available
327             test_result_indicator = -1
328         elif nbTestOk < 1:
329             test_result_indicator = 0
330         elif nbTestOk < 2:
331             test_result_indicator = 1
332         else:
333             # Test the last 4 run
334             if len(scenario_results) > 3:
335                 last4runResults = scenario_results[-4:]
336                 nbTestOkLast4 = getNbtestOk(last4runResults)
337                 # print "Nb test OK (last 4 run):"+ str(nbTestOkLast4)
338                 if nbTestOkLast4 > 3:
339                     test_result_indicator = 3
340                 else:
341                     test_result_indicator = 2
342             else:
343                 test_result_indicator = 2
344     return test_result_indicator
345
346
347 def getCaseScoreFromBuildTag(testCase, s_results):
348     """
349     Get Results for a given Functest Testcase with arch filtering
350     """
351     url_base = get_config('testapi.url')
352     nb_tests = get_config('general.nb_iteration_tests_success_criteria')
353     test_result_indicator = 0
354     # architecture is not a result field...so we cannot use getResult as it is
355     res_matrix = []
356     try:
357         for s_result in s_results:
358             build_tag = s_result['build_tag']
359             d = s_result['start_date']
360             res_matrix.append({'date': d,
361                                'build_tag': build_tag})
362         # sort res_matrix
363         filter_res_matrix = sorted(res_matrix, key=lambda k: k['date'],
364                                    reverse=True)[:nb_tests]
365         for my_res in filter_res_matrix:
366             url = ("http://" + url_base + "?case=" + testCase +
367                    "&build_tag=" + my_res['build_tag'])
368             request = Request(url)
369             response = urlopen(request)
370             k = response.read()
371             results = json.loads(k)
372             if "PASS" in results['results'][0]['criteria']:
373                 test_result_indicator += 1
374     except:
375         print "No results found for this case"
376     if test_result_indicator > 3:
377         test_result_indicator = 3
378
379     return test_result_indicator
380
381
382 def getJenkinsUrl(build_tag):
383     """
384     Get Jenkins url_base corespoding to the last test CI run
385     e.g. jenkins-functest-apex-apex-daily-colorado-daily-colorado-246
386     id = 246
387     jenkins-functest-compass-huawei-pod5-daily-master-136
388     id = 136
389     note it is linked to jenkins format
390     if this format changes...function to be adapted....
391     """
392     url_base = get_config('functest.jenkins_url')
393     try:
394         build_id = [int(s) for s in build_tag.split("-") if s.isdigit()]
395         url_id = (build_tag[8:-(len(str(build_id[0])) + 1)] +
396                   "/" + str(build_id[0]))
397         jenkins_url = url_base + url_id + "/console"
398     except Exception:
399         print 'Impossible to get jenkins url:'
400
401     if "jenkins-" not in build_tag:
402         jenkins_url = None
403
404     return jenkins_url
405
406
407 def getScenarioPercent(scenario_score, scenario_criteria):
408     """
409     Get success rate of the scenario (in %)
410     """
411     score = 0.0
412     try:
413         score = float(scenario_score) / float(scenario_criteria) * 100
414     except Exception:
415         print 'Impossible to calculate the percentage score'
416     return score
417
418
419 # *********
420 # Functest
421 # *********
422 def getFunctestConfig(version=""):
423     """
424     Get Functest configuration
425     """
426     config_file = get_config('functest.test_conf') + version
427     response = requests.get(config_file)
428     return yaml.safe_load(response.text)
429
430
431 def getArchitectures(scenario_results):
432     """
433     Get software architecture (x86 or Aarch64)
434     """
435     supported_arch = ['x86']
436     if len(scenario_results) > 0:
437         for scenario_result in scenario_results.values():
438             for value in scenario_result:
439                 if "armband" in value['build_tag']:
440                     supported_arch.append('aarch64')
441                     return supported_arch
442     return supported_arch
443
444
445 def filterArchitecture(results, architecture):
446     """
447     Restrict the list of results based on given architecture
448     """
449     filtered_results = {}
450     for name, res in results.items():
451         filtered_values = []
452         for value in res:
453             if architecture is "x86":
454                 # drop aarch64 results
455                 if ("armband" not in value['build_tag']):
456                     filtered_values.append(value)
457             elif architecture is "aarch64":
458                 # drop x86 results
459                 if ("armband" in value['build_tag']):
460                     filtered_values.append(value)
461         if (len(filtered_values) > 0):
462             filtered_results[name] = filtered_values
463     return filtered_results
464
465
466 # *********
467 # Yardstick
468 # *********
469 def subfind(given_list, pattern_list):
470     """
471     Yardstick util function
472     """
473     LASTEST_TESTS = get_config('general.nb_iteration_tests_success_criteria')
474     for i in range(len(given_list)):
475         if given_list[i] == pattern_list[0] and \
476                 given_list[i:i + LASTEST_TESTS] == pattern_list:
477             return True
478     return False
479
480
481 def _get_percent(status):
482     """
483     Yardstick util function to calculate success rate
484     """
485     if status * 100 % 6:
486         return round(float(status) * 100 / 6, 1)
487     else:
488         return status * 100 / 6
489
490
491 def get_percent(four_list, ten_list):
492     """
493     Yardstick util function to calculate success rate
494     """
495     four_score = 0
496     ten_score = 0
497
498     for res_v in four_list:
499         four_score += res_v
500     for res_v in ten_list:
501         ten_score += res_v
502
503     LASTEST_TESTS = get_config('general.nb_iteration_tests_success_criteria')
504     if four_score == LASTEST_TESTS:
505         status = 6
506     elif subfind(ten_list, [1, 1, 1, 1]):
507         status = 5
508     elif ten_score == 0:
509         status = 0
510     else:
511         status = four_score + 1
512
513     return _get_percent(status)
514
515
516 def _test():
517     """
518     Yardstick util function (test)
519     """
520     status = getScenarioStatus("compass", "master")
521     print "status:++++++++++++++++++++++++"
522     print json.dumps(status, indent=4)
523
524
525 # ----------------------------------------------------------
526 #
527 #               Export
528 #
529 # -----------------------------------------------------------
530
531 def export_csv(scenario_file_name, installer, version):
532     """
533     Generate sub files based on scenario_history.txt
534     """
535     scenario_installer_file_name = ("./display/" + version +
536                                     "/functest/scenario_history_" +
537                                     installer + ".csv")
538     scenario_installer_file = open(scenario_installer_file_name, "a")
539     with open(scenario_file_name, "r") as scenario_file:
540         scenario_installer_file.write("date,scenario,installer,detail,score\n")
541         for line in scenario_file:
542             if installer in line:
543                 scenario_installer_file.write(line)
544     scenario_installer_file.close
545
546
547 def generate_csv(scenario_file):
548     """
549     Generate sub files based on scenario_history.txt
550     """
551     import shutil
552     csv_file = scenario_file.replace('txt', 'csv')
553     shutil.copy2(scenario_file, csv_file)
554
555
556 def export_pdf(pdf_path, pdf_doc_name):
557     """
558     Export results to pdf
559     """
560     try:
561         pdfkit.from_file(pdf_path, pdf_doc_name)
562     except IOError:
563         print "Error but pdf generated anyway..."
564     except Exception:
565         print "impossible to generate PDF"