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