bug fix: score calculation based on build_tag
[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     test_results = None
118     scenario_results = None
119     period = get_config('general.period')
120     url_base = get_config('testapi.url')
121
122     url = ("http://" + url_base +
123            "?installer=" + installer +
124            "&period=" + str(period))
125
126     if version is not None:
127         url += "&version=" + version
128
129     if project is not None:
130         url += "&project=" + project
131
132     if case is not None:
133         url += "&case=" + case
134
135     try:
136         request = Request(url)
137         response = urlopen(request)
138         k = response.read()
139         results = json.loads(k)
140         test_results = results['results']
141         try:
142             page = results['pagination']['total_pages']
143             if page > 1:
144                 test_results = []
145                 for i in range(1, page + 1):
146                     url_page = url + "&page=" + str(i)
147                     request = Request(url_page)
148                     response = urlopen(request)
149                     k = response.read()
150                     results = json.loads(k)
151                     test_results += results['results']
152         except KeyError:
153             print "No pagination detected"
154     except URLError as err:
155         print 'Got an error code: {}'.format(err)
156
157     if test_results is not None:
158         test_results.reverse()
159         scenario_results = {}
160
161         for my_result in test_results:
162             # Retrieve all the scenarios per installer
163             if not my_result['scenario'] in scenario_results.keys():
164                 scenario_results[my_result['scenario']] = []
165             # Do we consider results from virtual pods ...
166             # Do we consider results for non HA scenarios...
167             exclude_virtual_pod = get_config('functest.exclude_virtual')
168             exclude_noha = get_config('functest.exclude_noha')
169             if ((exclude_virtual_pod and "virtual" in my_result['pod_name']) or
170                     (exclude_noha and "noha" in my_result['scenario'])):
171                 print "exclude virtual pod results..."
172             else:
173                 scenario_results[my_result['scenario']].append(my_result)
174
175     return scenario_results
176
177
178 def getScenarioStats(scenario_results):
179     """
180     Get the number of occurence of scenarios over the defined PERIOD
181     """
182     scenario_stats = {}
183     for res_k, res_v in scenario_results.iteritems():
184         scenario_stats[res_k] = len(res_v)
185     return scenario_stats
186
187
188 def getScenarioStatus(installer, version):
189     """
190     Get the status of a scenariofor Yardstick
191     """
192     period = get_config('general.period')
193     url_base = get_config('testapi.url')
194
195     url = ("http://" + url_base + "?case=scenario_status" +
196            "&installer=" + installer +
197            "&version=" + version + "&period=" + str(period))
198     request = Request(url)
199
200     try:
201         response = urlopen(request)
202         k = response.read()
203         response.close()
204         results = json.loads(k)
205         test_results = results['results']
206     except URLError:
207         print "GetScenarioStatus: error when calling the API"
208
209     x86 = 'x86'
210     aarch64 = 'aarch64'
211     scenario_results = {x86: {}, aarch64: {}}
212     result_dict = {x86: {}, aarch64: {}}
213     if test_results is not None:
214         for test_r in test_results:
215             if (test_r['stop_date'] != 'None' and
216                     test_r['criteria'] is not None):
217                 scenario_name = test_r['scenario']
218                 if 'arm' in test_r['pod_name']:
219                     if not test_r['scenario'] in scenario_results[aarch64]:
220                         scenario_results[aarch64][scenario_name] = []
221                     scenario_results[aarch64][scenario_name].append(test_r)
222                 else:
223                     if not test_r['scenario'] in scenario_results[x86]:
224                         scenario_results[x86][scenario_name] = []
225                     scenario_results[x86][scenario_name].append(test_r)
226
227         for key in scenario_results:
228             for scen_k, scen_v in scenario_results[key].items():
229                 # scenario_results[k] = v[:LASTEST_TESTS]
230                 s_list = []
231                 for element in scen_v:
232                     if element['criteria'] == 'PASS':
233                         s_list.append(1)
234                     else:
235                         s_list.append(0)
236                 result_dict[key][scen_k] = s_list
237
238     # return scenario_results
239     return result_dict
240
241
242 def getQtipResults(version, installer):
243     """
244     Get QTIP results
245     """
246     period = get_config('qtip.period')
247     url_base = get_config('testapi.url')
248
249     url = ("http://" + url_base + "?project=qtip" +
250            "&installer=" + installer +
251            "&version=" + version + "&period=" + str(period))
252     request = Request(url)
253
254     try:
255         response = urlopen(request)
256         k = response.read()
257         response.close()
258         results = json.loads(k)['results']
259     except URLError as err:
260         print 'Got an error code: {}'.format(err)
261
262     result_dict = {}
263     if results:
264         for r in results:
265             key = '{}/{}'.format(r['pod_name'], r['scenario'])
266             if key not in result_dict.keys():
267                 result_dict[key] = []
268             result_dict[key].append(r['details']['score'])
269
270     # return scenario_results
271     return result_dict
272
273
274 def getNbtestOk(results):
275     """
276     based on default value (PASS) count the number of test OK
277     """
278     nb_test_ok = 0
279     for my_result in results:
280         for res_k, res_v in my_result.iteritems():
281             try:
282                 if "PASS" in res_v:
283                     nb_test_ok += 1
284             except Exception:
285                 print "Cannot retrieve test status"
286     return nb_test_ok
287
288
289 def getCaseScore(testCase, installer, scenario, version):
290     """
291     Get Result  for a given Functest Testcase
292     """
293     # retrieve raw results
294     results = getApiResults(testCase, installer, scenario, version)
295     # let's concentrate on test results only
296     test_results = results['results']
297
298     # if results found, analyze them
299     if test_results is not None:
300         test_results.reverse()
301
302         scenario_results = []
303
304         # print " ---------------- "
305         # print test_results
306         # print " ---------------- "
307         # print "nb of results:" + str(len(test_results))
308
309         for res_r in test_results:
310             # print r["start_date"]
311             # print r["criteria"]
312             scenario_results.append({res_r["start_date"]: res_r["criteria"]})
313         # sort results
314         scenario_results.sort()
315         # 4 levels for the results
316         # 3: 4+ consecutive runs passing the success criteria
317         # 2: <4 successful consecutive runs but passing the criteria
318         # 1: close to pass the success criteria
319         # 0: 0% success, not passing
320         # -1: no run available
321         test_result_indicator = 0
322         nbTestOk = getNbtestOk(scenario_results)
323
324         # print "Nb test OK (last 10 days):"+ str(nbTestOk)
325         # check that we have at least 4 runs
326         if len(scenario_results) < 1:
327             # No results available
328             test_result_indicator = -1
329         elif nbTestOk < 1:
330             test_result_indicator = 0
331         elif nbTestOk < 2:
332             test_result_indicator = 1
333         else:
334             # Test the last 4 run
335             if len(scenario_results) > 3:
336                 last4runResults = scenario_results[-4:]
337                 nbTestOkLast4 = getNbtestOk(last4runResults)
338                 # print "Nb test OK (last 4 run):"+ str(nbTestOkLast4)
339                 if nbTestOkLast4 > 3:
340                     test_result_indicator = 3
341                 else:
342                     test_result_indicator = 2
343             else:
344                 test_result_indicator = 2
345     return test_result_indicator
346
347
348 def getCaseScoreFromBuildTag(testCase, s_results):
349     """
350     Get Results for a given Functest Testcase with arch filtering
351     """
352     url_base = get_config('testapi.url')
353     nb_tests = get_config('general.nb_iteration_tests_success_criteria')
354     test_result_indicator = 0
355     # architecture is not a result field...so we cannot use getResult as it is
356     res_matrix = []
357     try:
358         for s_result in s_results:
359             build_tag = s_result['build_tag']
360             d = s_result['start_date']
361             res_matrix.append({'date': d,
362                                'build_tag': build_tag})
363         # sort res_matrix
364         filter_res_matrix = sorted(res_matrix, key=lambda k: k['date'],
365                                    reverse=True)[:nb_tests]
366         for my_res in filter_res_matrix:
367             url = ("http://" + url_base + "?case=" + testCase +
368                    "&build_tag=" + my_res['build_tag'])
369             request = Request(url)
370             response = urlopen(request)
371             k = response.read()
372             results = json.loads(k)
373             if "PASS" in results['results'][0]['criteria']:
374                 test_result_indicator += 1
375     except:
376         print "No results found for this case"
377     if test_result_indicator > 2:
378         test_result_indicator = test_result_indicator - 1
379
380     return test_result_indicator
381
382
383 def getJenkinsUrl(build_tag):
384     """
385     Get Jenkins url_base corespoding to the last test CI run
386     e.g. jenkins-functest-apex-apex-daily-colorado-daily-colorado-246
387     id = 246
388     jenkins-functest-compass-huawei-pod5-daily-master-136
389     id = 136
390     note it is linked to jenkins format
391     if this format changes...function to be adapted....
392     """
393     url_base = get_config('functest.jenkins_url')
394     try:
395         build_id = [int(s) for s in build_tag.split("-") if s.isdigit()]
396         url_id = (build_tag[8:-(len(str(build_id[0])) + 1)] +
397                   "/" + str(build_id[0]))
398         jenkins_url = url_base + url_id + "/console"
399     except Exception:
400         print 'Impossible to get jenkins url:'
401
402     if "jenkins-" not in build_tag:
403         jenkins_url = None
404
405     return jenkins_url
406
407
408 def getScenarioPercent(scenario_score, scenario_criteria):
409     """
410     Get success rate of the scenario (in %)
411     """
412     score = 0.0
413     try:
414         score = float(scenario_score) / float(scenario_criteria) * 100
415     except Exception:
416         print 'Impossible to calculate the percentage score'
417     return score
418
419
420 # *********
421 # Functest
422 # *********
423 def getFunctestConfig(version=""):
424     """
425     Get Functest configuration
426     """
427     config_file = get_config('functest.test_conf') + version
428     response = requests.get(config_file)
429     return yaml.safe_load(response.text)
430
431
432 def getArchitectures(scenario_results):
433     """
434     Get software architecture (x86 or Aarch64)
435     """
436     supported_arch = ['x86']
437     if len(scenario_results) > 0:
438         for scenario_result in scenario_results.values():
439             for value in scenario_result:
440                 if "armband" in value['build_tag']:
441                     supported_arch.append('aarch64')
442                     return supported_arch
443     return supported_arch
444
445
446 def filterArchitecture(results, architecture):
447     """
448     Restrict the list of results based on given architecture
449     """
450     filtered_results = {}
451     for name, res in results.items():
452         filtered_values = []
453         for value in res:
454             if architecture is "x86":
455                 # drop aarch64 results
456                 if ("armband" not in value['build_tag']):
457                     filtered_values.append(value)
458             elif architecture is "aarch64":
459                 # drop x86 results
460                 if ("armband" in value['build_tag']):
461                     filtered_values.append(value)
462         if (len(filtered_values) > 0):
463             filtered_results[name] = filtered_values
464     return filtered_results
465
466
467 # *********
468 # Yardstick
469 # *********
470 def subfind(given_list, pattern_list):
471     """
472     Yardstick util function
473     """
474     LASTEST_TESTS = get_config('general.nb_iteration_tests_success_criteria')
475     for i in range(len(given_list)):
476         if given_list[i] == pattern_list[0] and \
477                 given_list[i:i + LASTEST_TESTS] == pattern_list:
478             return True
479     return False
480
481
482 def _get_percent(status):
483     """
484     Yardstick util function to calculate success rate
485     """
486     if status * 100 % 6:
487         return round(float(status) * 100 / 6, 1)
488     else:
489         return status * 100 / 6
490
491
492 def get_percent(four_list, ten_list):
493     """
494     Yardstick util function to calculate success rate
495     """
496     four_score = 0
497     ten_score = 0
498
499     for res_v in four_list:
500         four_score += res_v
501     for res_v in ten_list:
502         ten_score += res_v
503
504     LASTEST_TESTS = get_config('general.nb_iteration_tests_success_criteria')
505     if four_score == LASTEST_TESTS:
506         status = 6
507     elif subfind(ten_list, [1, 1, 1, 1]):
508         status = 5
509     elif ten_score == 0:
510         status = 0
511     else:
512         status = four_score + 1
513
514     return _get_percent(status)
515
516
517 def _test():
518     """
519     Yardstick util function (test)
520     """
521     status = getScenarioStatus("compass", "master")
522     print "status:++++++++++++++++++++++++"
523     print json.dumps(status, indent=4)
524
525
526 # ----------------------------------------------------------
527 #
528 #               Export
529 #
530 # -----------------------------------------------------------
531
532 def export_csv(scenario_file_name, installer, version):
533     """
534     Generate sub files based on scenario_history.txt
535     """
536     scenario_installer_file_name = ("./display/" + version +
537                                     "/functest/scenario_history_" +
538                                     installer + ".csv")
539     scenario_installer_file = open(scenario_installer_file_name, "a")
540     with open(scenario_file_name, "r") as scenario_file:
541         scenario_installer_file.write("date,scenario,installer,detail,score\n")
542         for line in scenario_file:
543             if installer in line:
544                 scenario_installer_file.write(line)
545     scenario_installer_file.close
546
547
548 def generate_csv(scenario_file):
549     """
550     Generate sub files based on scenario_history.txt
551     """
552     import shutil
553     csv_file = scenario_file.replace('txt', 'csv')
554     shutil.copy2(scenario_file, csv_file)
555
556
557 def export_pdf(pdf_path, pdf_doc_name):
558     """
559     Export results to pdf
560     """
561     try:
562         pdfkit.from_file(pdf_path, pdf_doc_name)
563     except IOError:
564         print "Error but pdf generated anyway..."
565     except Exception:
566         print "impossible to generate PDF"