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