Merge "Add ODL netvirt connectivity suites to list of robot tests"
[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.info("Impossible to retrieve the scenario."
100                     "Use default os-nosdn-nofeature-noha")
101         scenario = "os-nosdn-nofeature-noha"
102
103     return scenario
104
105
106 def get_version():
107     """
108     Get version
109     """
110     # Use the build tag to retrieve the version
111     # By default version is unknown
112     # if launched through CI the build tag has the following format
113     # jenkins-<project>-<installer>-<pod>-<job>-<branch>-<id>
114     # e.g. jenkins-functest-fuel-opnfv-jump-2-daily-master-190
115     # use regex to match branch info
116     rule = "daily-(.+?)-[0-9]*"
117     build_tag = get_build_tag()
118     m = re.search(rule, build_tag)
119     if m:
120         return m.group(1)
121     else:
122         return "unknown"
123
124
125 def get_pod_name():
126     """
127     Get PoD Name from env variable NODE_NAME
128     """
129     try:
130         return os.environ['NODE_NAME']
131     except KeyError:
132         logger.info(
133             "Unable to retrieve the POD name from environment. " +
134             "Using pod name 'unknown-pod'")
135         return "unknown-pod"
136
137
138 def get_build_tag():
139     """
140     Get build tag of jenkins jobs
141     """
142     try:
143         build_tag = os.environ['BUILD_TAG']
144     except KeyError:
145         logger.info("Impossible to retrieve the build tag")
146         build_tag = "none"
147
148     return build_tag
149
150
151 def get_db_url():
152     """
153     Returns DB URL
154     """
155     return get_functest_config('results.test_db_url')
156
157
158 def logger_test_results(project, case_name, status, details):
159     pod_name = get_pod_name()
160     scenario = get_scenario()
161     version = get_version()
162     build_tag = get_build_tag()
163
164     logger.info(
165         "\n"
166         "****************************************\n"
167         "\t %(p)s/%(n)s results \n\n"
168         "****************************************\n"
169         "DB:\t%(db)s\n"
170         "pod:\t%(pod)s\n"
171         "version:\t%(v)s\n"
172         "scenario:\t%(s)s\n"
173         "status:\t%(c)s\n"
174         "build tag:\t%(b)s\n"
175         "details:\t%(d)s\n"
176         % {'p': project,
177             'n': case_name,
178             'db': get_db_url(),
179             'pod': pod_name,
180             'v': version,
181             's': scenario,
182             'c': status,
183             'b': build_tag,
184             'd': details})
185
186
187 def write_results_to_file(project, case_name, start_date,
188                           stop_date, criteria, details):
189     file_path = re.split(r'://', CONST.results_test_db_url)[1]
190
191     try:
192         installer = os.environ['INSTALLER_TYPE']
193         scenario = os.environ['DEPLOY_SCENARIO']
194         pod_name = os.environ['NODE_NAME']
195     except KeyError as e:
196         logger.error("Please set env var: " + str(e))
197         return False
198
199     test_start = dt.fromtimestamp(start_date).strftime('%Y-%m-%d %H:%M:%S')
200     test_stop = dt.fromtimestamp(stop_date).strftime('%Y-%m-%d %H:%M:%S')
201
202     params = {"project_name": project, "case_name": case_name,
203               "pod_name": pod_name, "installer": installer,
204               "scenario": scenario, "criteria": criteria,
205               "start_date": test_start, "stop_date": test_stop,
206               "details": details}
207     try:
208         with open(file_path, "a+w") as outfile:
209             json.dump(params, outfile)
210             outfile.write("\n")
211         return True
212     except Exception as e:
213         logger.error("write result data into a file failed: %s" % e)
214         return False
215
216
217 def push_results_to_db(project, case_name,
218                        start_date, stop_date, criteria, details):
219     """
220     POST results to the Result target DB
221     """
222     # Retrieve params from CI and conf
223     url = CONST.results_test_db_url + "/results"
224
225     try:
226         installer = os.environ['INSTALLER_TYPE']
227         scenario = os.environ['DEPLOY_SCENARIO']
228         pod_name = os.environ['NODE_NAME']
229         build_tag = os.environ['BUILD_TAG']
230     except KeyError as e:
231         logger.error("Please set env var: " + str(e))
232         return False
233     rule = "daily-(.+?)-[0-9]*"
234     m = re.search(rule, build_tag)
235     if m:
236         version = m.group(1)
237     else:
238         logger.error("Please fix BUILD_TAG env var: " + build_tag)
239         return False
240     test_start = dt.fromtimestamp(start_date).strftime('%Y-%m-%d %H:%M:%S')
241     test_stop = dt.fromtimestamp(stop_date).strftime('%Y-%m-%d %H:%M:%S')
242
243     params = {"project_name": project, "case_name": case_name,
244               "pod_name": pod_name, "installer": installer,
245               "version": version, "scenario": scenario, "criteria": criteria,
246               "build_tag": build_tag, "start_date": test_start,
247               "stop_date": test_stop, "details": details}
248
249     error = None
250     headers = {'Content-Type': 'application/json'}
251     try:
252         r = requests.post(url, data=json.dumps(params), headers=headers)
253         logger.debug(r)
254         r.raise_for_status()
255     except requests.RequestException as exc:
256         if 'r' in locals():
257             error = ("Pushing Result to DB(%s) failed: %s" %
258                      (r.url, r.content))
259         else:
260             error = ("Pushing Result to DB(%s) failed: %s" % (url, exc))
261     except Exception as e:
262         error = ("Error [push_results_to_db("
263                  "DB: '%(db)s', "
264                  "project: '%(project)s', "
265                  "case: '%(case)s', "
266                  "pod: '%(pod)s', "
267                  "version: '%(v)s', "
268                  "scenario: '%(s)s', "
269                  "criteria: '%(c)s', "
270                  "build_tag: '%(t)s', "
271                  "details: '%(d)s')]: "
272                  "%(error)s" %
273                  {
274                      'db': url,
275                      'project': project,
276                      'case': case_name,
277                      'pod': pod_name,
278                      'v': version,
279                      's': scenario,
280                      'c': criteria,
281                      't': build_tag,
282                      'd': details,
283                      'error': e
284                  })
285     finally:
286         if error:
287             logger.error(error)
288             return False
289         return True
290
291
292 def get_resolvconf_ns():
293     """
294     Get nameservers from current resolv.conf
295     """
296     nameservers = []
297     rconf = open("/etc/resolv.conf", "r")
298     line = rconf.readline()
299     resolver = dns.resolver.Resolver()
300     while line:
301         ip = re.search(r"\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b", line)
302         if ip:
303             resolver.nameservers = [str(ip)]
304             try:
305                 result = resolver.query('opnfv.org')[0]
306                 if result != "":
307                     nameservers.append(ip.group())
308             except dns.exception.Timeout:
309                 pass
310         line = rconf.readline()
311     return nameservers
312
313
314 def get_ci_envvars():
315     """
316     Get the CI env variables
317     """
318     ci_env_var = {
319         "installer": os.environ.get('INSTALLER_TYPE'),
320         "scenario": os.environ.get('DEPLOY_SCENARIO')}
321     return ci_env_var
322
323
324 def execute_command(cmd, info=False, error_msg="",
325                     verbose=True, output_file=None):
326     if not error_msg:
327         error_msg = ("The command '%s' failed." % cmd)
328     msg_exec = ("Executing command: '%s'" % cmd)
329     if verbose:
330         if info:
331             logger.info(msg_exec)
332         else:
333             logger.debug(msg_exec)
334     p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
335                          stderr=subprocess.STDOUT)
336     if output_file:
337         f = open(output_file, "w")
338     for line in iter(p.stdout.readline, b''):
339         if output_file:
340             f.write(line)
341         else:
342             line = line.replace('\n', '')
343             print line
344             sys.stdout.flush()
345     if output_file:
346         f.close()
347     p.stdout.close()
348     returncode = p.wait()
349     if returncode != 0:
350         if verbose:
351             logger.error(error_msg)
352
353     return returncode
354
355
356 def get_dict_by_test(testname):
357     with open(get_testcases_file_dir()) as f:
358         testcases_yaml = yaml.safe_load(f)
359
360     for dic_tier in testcases_yaml.get("tiers"):
361         for dic_testcase in dic_tier['testcases']:
362             if dic_testcase['name'] == testname:
363                 return dic_testcase
364
365     logger.error('Project %s is not defined in testcases.yaml' % testname)
366     return None
367
368
369 def get_criteria_by_test(testname):
370     dict = get_dict_by_test(testname)
371     if dict:
372         return dict['criteria']
373     return None
374
375
376 # ----------------------------------------------------------
377 #
378 #               YAML UTILS
379 #
380 # -----------------------------------------------------------
381 def get_parameter_from_yaml(parameter, file):
382     """
383     Returns the value of a given parameter in file.yaml
384     parameter must be given in string format with dots
385     Example: general.openstack.image_name
386     """
387     with open(file) as f:
388         file_yaml = yaml.safe_load(f)
389     f.close()
390     value = file_yaml
391     for element in parameter.split("."):
392         value = value.get(element)
393         if value is None:
394             raise ValueError("The parameter %s is not defined in"
395                              " config_functest.yaml" % parameter)
396     return value
397
398
399 def get_functest_config(parameter):
400     yaml_ = os.environ["CONFIG_FUNCTEST_YAML"]
401     return get_parameter_from_yaml(parameter, yaml_)
402
403
404 def check_success_rate(case_name, success_rate):
405     success_rate = float(success_rate)
406     criteria = get_criteria_by_test(case_name)
407
408     def get_criteria_value(op):
409         return float(criteria.split(op)[1].rstrip('%'))
410
411     status = 'FAIL'
412     ops = ['==', '>=']
413     for op in ops:
414         if op in criteria:
415             c_value = get_criteria_value(op)
416             if eval("%s %s %s" % (success_rate, op, c_value)):
417                 status = 'PASS'
418             break
419
420     return status
421
422
423 def merge_dicts(dict1, dict2):
424     for k in set(dict1.keys()).union(dict2.keys()):
425         if k in dict1 and k in dict2:
426             if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
427                 yield (k, dict(merge_dicts(dict1[k], dict2[k])))
428             else:
429                 yield (k, dict2[k])
430         elif k in dict1:
431             yield (k, dict1[k])
432         else:
433             yield (k, dict2[k])
434
435
436 def get_testcases_file_dir():
437     return get_functest_config('general.functest.testcases_yaml')
438
439
440 def get_functest_yaml():
441     with open(os.environ["CONFIG_FUNCTEST_YAML"]) as f:
442         functest_yaml = yaml.safe_load(f)
443     f.close()
444     return functest_yaml
445
446
447 def print_separator():
448     logger.info("==============================================")
449
450
451 def timethis(func):
452     """Measure the time it takes for a function to complete"""
453     @functools.wraps(func)
454     def timed(*args, **kwargs):
455         ts = time.time()
456         result = func(*args, **kwargs)
457         te = time.time()
458         elapsed = '{0}'.format(te - ts)
459         logger.info('{f}(*{a}, **{kw}) took: {t} sec'.format(
460             f=func.__name__, a=args, kw=kwargs, t=elapsed))
461         return result, elapsed
462     return timed