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