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