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