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