Merge "Ignore .tox"
[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 logging
13 import os
14 import re
15 import shutil
16 import subprocess
17 import sys
18 import time
19 from datetime import datetime as dt
20
21 import dns.resolver
22 import requests
23 from six.moves import urllib
24 import yaml
25 from git import Repo
26
27 from functest.utils import constants
28 from functest.utils import decorators
29
30 logger = logging.getLogger(__name__)
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         urllib.request.urlopen(url, timeout=5)
44         return True
45     except urllib.error.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 = urllib.request.urlopen(url)
57     except (urllib.error.HTTPError, urllib.error.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     # jenkins-functest-fuel-baremetal-weekly-master-8
116     # use regex to match branch info
117     rule = "(dai|week)ly-(.+?)-[0-9]*"
118     build_tag = get_build_tag()
119     m = re.search(rule, build_tag)
120     if m:
121         return m.group(2)
122     else:
123         return "unknown"
124
125
126 def get_pod_name():
127     """
128     Get PoD Name from env variable NODE_NAME
129     """
130     try:
131         return os.environ['NODE_NAME']
132     except KeyError:
133         logger.info(
134             "Unable to retrieve the POD name from environment. " +
135             "Using pod name 'unknown-pod'")
136         return "unknown-pod"
137
138
139 def get_build_tag():
140     """
141     Get build tag of jenkins jobs
142     """
143     try:
144         build_tag = os.environ['BUILD_TAG']
145     except KeyError:
146         logger.info("Impossible to retrieve the build tag")
147         build_tag = "none"
148
149     return build_tag
150
151
152 def get_db_url():
153     """
154     Returns DB URL
155     """
156     # TODO use CONST mechanism
157     try:
158         # if TEST_DB_URL declared in env variable, use it!
159         db_url = os.environ['TEST_DB_URL']
160     except KeyError:
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, result, details):
197     """
198     POST results to the Result target DB
199     """
200     # Retrieve params from CI and conf
201     url = get_db_url()
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     version = get_version()
212     test_start = dt.fromtimestamp(start_date).strftime('%Y-%m-%d %H:%M:%S')
213     test_stop = dt.fromtimestamp(stop_date).strftime('%Y-%m-%d %H:%M:%S')
214
215     params = {"project_name": project, "case_name": case_name,
216               "pod_name": pod_name, "installer": installer,
217               "version": version, "scenario": scenario, "criteria": result,
218               "build_tag": build_tag, "start_date": test_start,
219               "stop_date": test_stop, "details": details}
220
221     error = None
222     headers = {'Content-Type': 'application/json'}
223     try:
224         r = requests.post(url, data=json.dumps(params), headers=headers)
225         logger.debug(r)
226         r.raise_for_status()
227     except requests.RequestException as exc:
228         if 'r' in locals():
229             error = ("Pushing Result to DB(%s) failed: %s" %
230                      (r.url, r.content))
231         else:
232             error = ("Pushing Result to DB(%s) failed: %s" % (url, exc))
233     except Exception as e:
234         error = ("Error [push_results_to_db("
235                  "DB: '%(db)s', "
236                  "project: '%(project)s', "
237                  "case: '%(case)s', "
238                  "pod: '%(pod)s', "
239                  "version: '%(v)s', "
240                  "scenario: '%(s)s', "
241                  "criteria: '%(c)s', "
242                  "build_tag: '%(t)s', "
243                  "details: '%(d)s')]: "
244                  "%(error)s" %
245                  {
246                      'db': url,
247                      'project': project,
248                      'case': case_name,
249                      'pod': pod_name,
250                      'v': version,
251                      's': scenario,
252                      'c': result,
253                      't': build_tag,
254                      'd': details,
255                      'error': e
256                  })
257     finally:
258         if error:
259             logger.error(error)
260             return False
261         return True
262
263
264 def get_resolvconf_ns():
265     """
266     Get nameservers from current resolv.conf
267     """
268     nameservers = []
269     rconf = open("/etc/resolv.conf", "r")
270     line = rconf.readline()
271     resolver = dns.resolver.Resolver()
272     while line:
273         ip = re.search(r"\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b", line)
274         if ip:
275             resolver.nameservers = [ip.group(0)]
276             try:
277                 result = resolver.query('opnfv.org')[0]
278                 if result != "":
279                     nameservers.append(ip.group())
280             except dns.exception.Timeout:
281                 pass
282         line = rconf.readline()
283     return nameservers
284
285
286 def get_ci_envvars():
287     """
288     Get the CI env variables
289     """
290     ci_env_var = {
291         "installer": os.environ.get('INSTALLER_TYPE'),
292         "scenario": os.environ.get('DEPLOY_SCENARIO')}
293     return ci_env_var
294
295
296 def execute_command_raise(cmd, info=False, error_msg="",
297                           verbose=True, output_file=None):
298     ret = execute_command(cmd, info, error_msg, verbose, output_file)
299     if ret != 0:
300         raise Exception(error_msg)
301
302
303 def execute_command(cmd, info=False, error_msg="",
304                     verbose=True, output_file=None):
305     if not error_msg:
306         error_msg = ("The command '%s' failed." % cmd)
307     msg_exec = ("Executing command: '%s'" % cmd)
308     if verbose:
309         if info:
310             logger.info(msg_exec)
311         else:
312             logger.debug(msg_exec)
313     p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
314                          stderr=subprocess.STDOUT)
315     if output_file:
316         f = open(output_file, "w")
317     for line in iter(p.stdout.readline, b''):
318         if output_file:
319             f.write(line)
320         else:
321             line = line.replace('\n', '')
322             print(line)
323             sys.stdout.flush()
324     if output_file:
325         f.close()
326     p.stdout.close()
327     returncode = p.wait()
328     if returncode != 0:
329         if verbose:
330             logger.error(error_msg)
331
332     return returncode
333
334
335 def get_dict_by_test(testname):
336     with open(get_testcases_file_dir()) as f:
337         testcases_yaml = yaml.safe_load(f)
338
339     for dic_tier in testcases_yaml.get("tiers"):
340         for dic_testcase in dic_tier['testcases']:
341             if dic_testcase['case_name'] == testname:
342                 return dic_testcase
343
344     logger.error('Project %s is not defined in testcases.yaml' % testname)
345     return None
346
347
348 def get_criteria_by_test(testname):
349     dict = get_dict_by_test(testname)
350     if dict:
351         return dict['criteria']
352     return None
353
354
355 # ----------------------------------------------------------
356 #
357 #               YAML UTILS
358 #
359 # -----------------------------------------------------------
360 def get_parameter_from_yaml(parameter, file):
361     """
362     Returns the value of a given parameter in file.yaml
363     parameter must be given in string format with dots
364     Example: general.openstack.image_name
365     """
366     with open(file) as f:
367         file_yaml = yaml.safe_load(f)
368     f.close()
369     value = file_yaml
370     for element in parameter.split("."):
371         value = value.get(element)
372         if value is None:
373             raise ValueError("The parameter %s is not defined in"
374                              " %s" % (parameter, file))
375     return value
376
377
378 def get_functest_config(parameter):
379     yaml_ = constants.CONST.__getattribute__('CONFIG_FUNCTEST_YAML')
380     return get_parameter_from_yaml(parameter, yaml_)
381
382
383 def merge_dicts(dict1, dict2):
384     for k in set(dict1.keys()).union(dict2.keys()):
385         if k in dict1 and k in dict2:
386             if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
387                 yield (k, dict(merge_dicts(dict1[k], dict2[k])))
388             else:
389                 yield (k, dict2[k])
390         elif k in dict1:
391             yield (k, dict1[k])
392         else:
393             yield (k, dict2[k])
394
395
396 def get_testcases_file_dir():
397     return get_functest_config('general.functest.testcases_yaml')
398
399
400 def get_functest_yaml():
401     with open(constants.CONST.__getattribute__('CONFIG_FUNCTEST_YAML')) as f:
402         functest_yaml = yaml.safe_load(f)
403     f.close()
404     return functest_yaml
405
406
407 def print_separator():
408     logger.info("==============================================")
409
410
411 def timethis(func):
412     """Measure the time it takes for a function to complete"""
413     @functools.wraps(func)
414     def timed(*args, **kwargs):
415         ts = time.time()
416         result = func(*args, **kwargs)
417         te = time.time()
418         elapsed = '{0}'.format(te - ts)
419         logger.info('{f}(*{a}, **{kw}) took: {t} sec'.format(
420             f=func.__name__, a=args, kw=kwargs, t=elapsed))
421         return result, elapsed
422     return timed