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