write test results to a local file
[functest.git] / functest / utils / functest_utils.py
1 #!/usr/bin/env python
2 #
3 # jose.lausuch@ericsson.com
4 # valentin.boucher@orange.com
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 import functools
11 import json
12 import os
13 import re
14 import shutil
15 import subprocess
16 import sys
17 import time
18 import urllib2
19 from datetime import datetime as dt
20
21 import dns.resolver
22 import requests
23 import yaml
24 from git import Repo
25
26 from functest.utils.constants import CONST
27 import functest.utils.functest_logger as ft_logger
28
29
30 logger = ft_logger.Logger("functest_utils").getLogger()
31
32
33 # ----------------------------------------------------------
34 #
35 #               INTERNET UTILS
36 #
37 # -----------------------------------------------------------
38 def check_internet_connectivity(url='http://www.opnfv.org/'):
39     """
40     Check if there is access to the internet
41     """
42     try:
43         urllib2.urlopen(url, timeout=5)
44         return True
45     except urllib2.URLError:
46         return False
47
48
49 def download_url(url, dest_path):
50     """
51     Download a file to a destination path given a URL
52     """
53     name = url.rsplit('/')[-1]
54     dest = dest_path + "/" + name
55     try:
56         response = urllib2.urlopen(url)
57     except (urllib2.HTTPError, urllib2.URLError):
58         return False
59
60     with open(dest, 'wb') as f:
61         shutil.copyfileobj(response, f)
62     return True
63
64
65 # ----------------------------------------------------------
66 #
67 #               CI UTILS
68 #
69 # -----------------------------------------------------------
70 def get_git_branch(repo_path):
71     """
72     Get git branch name
73     """
74     repo = Repo(repo_path)
75     branch = repo.active_branch
76     return branch.name
77
78
79 def get_installer_type():
80     """
81     Get installer type (fuel, apex, joid, compass)
82     """
83     try:
84         installer = os.environ['INSTALLER_TYPE']
85     except KeyError:
86         logger.error("Impossible to retrieve the installer type")
87         installer = "Unknown_installer"
88
89     return installer
90
91
92 def get_scenario():
93     """
94     Get scenario
95     """
96     try:
97         scenario = os.environ['DEPLOY_SCENARIO']
98     except KeyError:
99         logger.error("Impossible to retrieve the scenario")
100         scenario = "Unknown_scenario"
101
102     return scenario
103
104
105 def get_version():
106     """
107     Get version
108     """
109     # Use the build tag to retrieve the version
110     # By default version is unknown
111     # if launched through CI the build tag has the following format
112     # jenkins-<project>-<installer>-<pod>-<job>-<branch>-<id>
113     # e.g. jenkins-functest-fuel-opnfv-jump-2-daily-master-190
114     # use regex to match branch info
115     rule = "daily-(.+?)-[0-9]*"
116     build_tag = get_build_tag()
117     m = re.search(rule, build_tag)
118     if m:
119         return m.group(1)
120     else:
121         return "unknown"
122
123
124 def get_pod_name():
125     """
126     Get PoD Name from env variable NODE_NAME
127     """
128     try:
129         return os.environ['NODE_NAME']
130     except KeyError:
131         logger.error(
132             "Unable to retrieve the POD name from environment. " +
133             "Using pod name 'unknown-pod'")
134         return "unknown-pod"
135
136
137 def get_build_tag():
138     """
139     Get build tag of jenkins jobs
140     """
141     try:
142         build_tag = os.environ['BUILD_TAG']
143     except KeyError:
144         logger.error("Impossible to retrieve the build tag")
145         build_tag = "unknown_build_tag"
146
147     return build_tag
148
149
150 def get_db_url():
151     """
152     Returns DB URL
153     """
154     return get_functest_config('results.test_db_url')
155
156
157 def logger_test_results(project, case_name, status, details):
158     pod_name = get_pod_name()
159     scenario = get_scenario()
160     version = get_version()
161     build_tag = get_build_tag()
162
163     logger.info(
164         "\n"
165         "****************************************\n"
166         "\t %(p)s/%(n)s results \n\n"
167         "****************************************\n"
168         "DB:\t%(db)s\n"
169         "pod:\t%(pod)s\n"
170         "version:\t%(v)s\n"
171         "scenario:\t%(s)s\n"
172         "status:\t%(c)s\n"
173         "build tag:\t%(b)s\n"
174         "details:\t%(d)s\n"
175         % {'p': project,
176             'n': case_name,
177             'db': get_db_url(),
178             'pod': pod_name,
179             'v': version,
180             's': scenario,
181             'c': status,
182             'b': build_tag,
183             'd': details})
184
185
186 def write_results_to_file(project, case_name, start_date,
187                           stop_date, criteria, details):
188     file_path = re.split(r'://', CONST.results_test_db_url)[1]
189
190     try:
191         installer = os.environ['INSTALLER_TYPE']
192         scenario = os.environ['DEPLOY_SCENARIO']
193         pod_name = os.environ['NODE_NAME']
194     except KeyError as e:
195         logger.error("Please set env var: " + str(e))
196         return False
197
198     test_start = dt.fromtimestamp(start_date).strftime('%Y-%m-%d %H:%M:%S')
199     test_stop = dt.fromtimestamp(stop_date).strftime('%Y-%m-%d %H:%M:%S')
200
201     params = {"project_name": project, "case_name": case_name,
202               "pod_name": pod_name, "installer": installer,
203               "scenario": scenario, "criteria": criteria,
204               "start_date": test_start, "stop_date": test_stop,
205               "details": details}
206     try:
207         with open(file_path, "a+w") as outfile:
208             json.dump(params, outfile)
209             outfile.write("\n")
210         return True
211     except Exception as e:
212         logger.error("write result data into a file failed: %s" % e)
213         return False
214
215
216 def push_results_to_db(project, case_name,
217                        start_date, stop_date, criteria, details):
218     """
219     POST results to the Result target DB
220     """
221     # Retrieve params from CI and conf
222     url = CONST.results_test_db_url + "/results"
223
224     try:
225         installer = os.environ['INSTALLER_TYPE']
226         scenario = os.environ['DEPLOY_SCENARIO']
227         pod_name = os.environ['NODE_NAME']
228         build_tag = os.environ['BUILD_TAG']
229     except KeyError as e:
230         logger.error("Please set env var: " + str(e))
231         return False
232     rule = "daily-(.+?)-[0-9]*"
233     m = re.search(rule, build_tag)
234     if m:
235         version = m.group(1)
236     else:
237         logger.error("Please fix BUILD_TAG env var: " + build_tag)
238         return False
239     test_start = dt.fromtimestamp(start_date).strftime('%Y-%m-%d %H:%M:%S')
240     test_stop = dt.fromtimestamp(stop_date).strftime('%Y-%m-%d %H:%M:%S')
241
242     params = {"project_name": project, "case_name": case_name,
243               "pod_name": pod_name, "installer": installer,
244               "version": version, "scenario": scenario, "criteria": criteria,
245               "build_tag": build_tag, "start_date": test_start,
246               "stop_date": test_stop, "details": details}
247
248     error = None
249     headers = {'Content-Type': 'application/json'}
250     try:
251         r = requests.post(url, data=json.dumps(params), headers=headers)
252         logger.debug(r)
253         r.raise_for_status()
254     except requests.RequestException as exc:
255         if 'r' in locals():
256             error = ("Pushing Result to DB(%s) failed: %s" %
257                      (r.url, r.content))
258         else:
259             error = ("Pushing Result to DB(%s) failed: %s" % (url, exc))
260     except Exception as e:
261         error = ("Error [push_results_to_db("
262                  "DB: '%(db)s', "
263                  "project: '%(project)s', "
264                  "case: '%(case)s', "
265                  "pod: '%(pod)s', "
266                  "version: '%(v)s', "
267                  "scenario: '%(s)s', "
268                  "criteria: '%(c)s', "
269                  "build_tag: '%(t)s', "
270                  "details: '%(d)s')]: "
271                  "%(error)s" %
272                  {
273                      'db': url,
274                      'project': project,
275                      'case': case_name,
276                      'pod': pod_name,
277                      'v': version,
278                      's': scenario,
279                      'c': criteria,
280                      't': build_tag,
281                      'd': details,
282                      'error': e
283                  })
284     finally:
285         if error:
286             logger.error(error)
287             return False
288         return True
289
290
291 def get_resolvconf_ns():
292     """
293     Get nameservers from current resolv.conf
294     """
295     nameservers = []
296     rconf = open("/etc/resolv.conf", "r")
297     line = rconf.readline()
298     resolver = dns.resolver.Resolver()
299     while line:
300         ip = re.search(r"\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b", line)
301         if ip:
302             resolver.nameservers = [str(ip)]
303             try:
304                 result = resolver.query('opnfv.org')[0]
305                 if result != "":
306                     nameservers.append(ip.group())
307             except dns.exception.Timeout:
308                 pass
309         line = rconf.readline()
310     return nameservers
311
312
313 def get_ci_envvars():
314     """
315     Get the CI env variables
316     """
317     ci_env_var = {
318         "installer": os.environ.get('INSTALLER_TYPE'),
319         "scenario": os.environ.get('DEPLOY_SCENARIO')}
320     return ci_env_var
321
322
323 def execute_command(cmd, info=False, error_msg="",
324                     verbose=True, output_file=None):
325     if not error_msg:
326         error_msg = ("The command '%s' failed." % cmd)
327     msg_exec = ("Executing command: '%s'" % cmd)
328     if verbose:
329         if info:
330             logger.info(msg_exec)
331         else:
332             logger.debug(msg_exec)
333     p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
334                          stderr=subprocess.STDOUT)
335     if output_file:
336         f = open(output_file, "w")
337     for line in iter(p.stdout.readline, b''):
338         if output_file:
339             f.write(line)
340         else:
341             line = line.replace('\n', '')
342             print line
343             sys.stdout.flush()
344     if output_file:
345         f.close()
346     p.stdout.close()
347     returncode = p.wait()
348     if returncode != 0:
349         if verbose:
350             logger.error(error_msg)
351
352     return returncode
353
354
355 def get_dict_by_test(testname):
356     with open(get_testcases_file_dir()) as f:
357         testcases_yaml = yaml.safe_load(f)
358
359     for dic_tier in testcases_yaml.get("tiers"):
360         for dic_testcase in dic_tier['testcases']:
361             if dic_testcase['name'] == testname:
362                 return dic_testcase
363
364     logger.error('Project %s is not defined in testcases.yaml' % testname)
365     return None
366
367
368 def get_criteria_by_test(testname):
369     dict = get_dict_by_test(testname)
370     if dict:
371         return dict['criteria']
372     return None
373
374
375 # ----------------------------------------------------------
376 #
377 #               YAML UTILS
378 #
379 # -----------------------------------------------------------
380 def get_parameter_from_yaml(parameter, file):
381     """
382     Returns the value of a given parameter in file.yaml
383     parameter must be given in string format with dots
384     Example: general.openstack.image_name
385     """
386     with open(file) as f:
387         file_yaml = yaml.safe_load(f)
388     f.close()
389     value = file_yaml
390     for element in parameter.split("."):
391         value = value.get(element)
392         if value is None:
393             raise ValueError("The parameter %s is not defined in"
394                              " config_functest.yaml" % parameter)
395     return value
396
397
398 def get_functest_config(parameter):
399     yaml_ = os.environ["CONFIG_FUNCTEST_YAML"]
400     return get_parameter_from_yaml(parameter, yaml_)
401
402
403 def check_success_rate(case_name, success_rate):
404     success_rate = float(success_rate)
405     criteria = get_criteria_by_test(case_name)
406
407     def get_criteria_value(op):
408         return float(criteria.split(op)[1].rstrip('%'))
409
410     status = 'FAIL'
411     ops = ['==', '>=']
412     for op in ops:
413         if op in criteria:
414             c_value = get_criteria_value(op)
415             if eval("%s %s %s" % (success_rate, op, c_value)):
416                 status = 'PASS'
417             break
418
419     return status
420
421
422 def merge_dicts(dict1, dict2):
423     for k in set(dict1.keys()).union(dict2.keys()):
424         if k in dict1 and k in dict2:
425             if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
426                 yield (k, dict(merge_dicts(dict1[k], dict2[k])))
427             else:
428                 yield (k, dict2[k])
429         elif k in dict1:
430             yield (k, dict1[k])
431         else:
432             yield (k, dict2[k])
433
434
435 def get_testcases_file_dir():
436     return get_functest_config('general.functest.testcases_yaml')
437
438
439 def get_functest_yaml():
440     with open(os.environ["CONFIG_FUNCTEST_YAML"]) as f:
441         functest_yaml = yaml.safe_load(f)
442     f.close()
443     return functest_yaml
444
445
446 def print_separator():
447     logger.info("==============================================")
448
449
450 def timethis(func):
451     """Measure the time it takes for a function to complete"""
452     @functools.wraps(func)
453     def timed(*args, **kwargs):
454         ts = time.time()
455         result = func(*args, **kwargs)
456         te = time.time()
457         elapsed = '{0}'.format(te - ts)
458         logger.info('{f}(*{a}, **{kw}) took: {t} sec'.format(
459             f=func.__name__, a=args, kw=kwargs, t=elapsed))
460         return result, elapsed
461     return timed