Use a global logger instead of method args
[functest.git] / 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 from datetime import datetime as dt
11 import json
12 import os
13 import re
14 import requests
15 import shutil
16 import subprocess
17 import sys
18 import urllib2
19
20 import functest.ci.tier_builder as tb
21 import functest.utils.functest_logger as ft_logger
22
23 import dns.resolver
24 from git import Repo
25 import yaml
26
27
28 logger = ft_logger.Logger("functest_utils").getLogger()
29
30 REPOS_DIR = os.getenv('repos_dir')
31 FUNCTEST_REPO = ("%s/functest/" % REPOS_DIR)
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         urllib2.urlopen(url, timeout=5)
45         return True
46     except urllib2.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 = urllib2.urlopen(url)
58     except (urllib2.HTTPError, urllib2.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(logger=None):
81     """
82     Get installer type (fuel, apex, joid, compass)
83     """
84     try:
85         installer = os.environ['INSTALLER_TYPE']
86     except KeyError:
87         globals()['logger'].error("Impossible to retrieve the installer type")
88         installer = "Unknown_installer"
89
90     return installer
91
92
93 def get_scenario(logger=None):
94     """
95     Get scenario
96     """
97     try:
98         scenario = os.environ['DEPLOY_SCENARIO']
99     except KeyError:
100         globals()['logger'].error("Impossible to retrieve the scenario")
101         scenario = "Unknown_scenario"
102
103     return scenario
104
105
106 def get_version(logger=None):
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     # use regex to match branch info
116     rule = "daily-(.+?)-[0-9]*"
117     build_tag = get_build_tag(logger)
118     m = re.search(rule, build_tag)
119     if m:
120         return m.group(1)
121     else:
122         return "unknown"
123
124
125 def get_pod_name(logger=None):
126     """
127     Get PoD Name from env variable NODE_NAME
128     """
129     try:
130         return os.environ['NODE_NAME']
131     except KeyError:
132         globals()['logger'].error(
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(logger=None):
139     """
140     Get build tag of jenkins jobs
141     """
142     try:
143         build_tag = os.environ['BUILD_TAG']
144     except KeyError:
145         globals()['logger'].error("Impossible to retrieve the build tag")
146         build_tag = "unknown_build_tag"
147
148     return build_tag
149
150
151 def get_db_url(logger=None):
152     """
153     Returns DB URL
154     """
155     with open(os.environ["CONFIG_FUNCTEST_YAML"]) as f:
156         functest_yaml = yaml.safe_load(f)
157     f.close()
158     db_url = functest_yaml.get("results").get("test_db_url")
159     return db_url
160
161
162 def logger_test_results(logger, project, case_name, status, details):
163     pod_name = get_pod_name(logger)
164     scenario = get_scenario(logger)
165     version = get_version(logger)
166     build_tag = get_build_tag(logger)
167
168     globals()['logger'].info(
169         "\n"
170         "****************************************\n"
171         "\t %(p)s/%(n)s results \n\n"
172         "****************************************\n"
173         "DB:\t%(db)s\n"
174         "pod:\t%(pod)s\n"
175         "version:\t%(v)s\n"
176         "scenario:\t%(s)s\n"
177         "status:\t%(c)s\n"
178         "build tag:\t%(b)s\n"
179         "details:\t%(d)s\n"
180         % {'p': project,
181             'n': case_name,
182             'db': get_db_url(),
183             'pod': pod_name,
184             'v': version,
185             's': scenario,
186             'c': status,
187             'b': build_tag,
188             'd': details})
189
190
191 def push_results_to_db(project, case_name, logger,
192                        start_date, stop_date, criteria, details):
193     """
194     POST results to the Result target DB
195     """
196     # Retrieve params from CI and conf
197     url = get_db_url(logger) + "/results"
198
199     try:
200         installer = os.environ['INSTALLER_TYPE']
201         scenario = os.environ['DEPLOY_SCENARIO']
202         pod_name = os.environ['NODE_NAME']
203         build_tag = os.environ['BUILD_TAG']
204     except KeyError as e:
205         globals()['logger'].error("Please set env var: " + str(e))
206         return False
207     rule = "daily-(.+?)-[0-9]*"
208     m = re.search(rule, build_tag)
209     if m:
210         version = m.group(1)
211     else:
212         globals()['logger'].error("Please fix BUILD_TAG env var: " + build_tag)
213         return False
214     test_start = dt.fromtimestamp(start_date).strftime('%Y-%m-%d %H:%M:%S')
215     test_stop = dt.fromtimestamp(stop_date).strftime('%Y-%m-%d %H:%M:%S')
216
217     params = {"project_name": project, "case_name": case_name,
218               "pod_name": pod_name, "installer": installer,
219               "version": version, "scenario": scenario, "criteria": criteria,
220               "build_tag": build_tag, "start_date": test_start,
221               "stop_date": test_stop, "details": details}
222
223     error = None
224     headers = {'Content-Type': 'application/json'}
225     try:
226         r = requests.post(url, data=json.dumps(params), headers=headers)
227         globals()['logger'].debug(r)
228         r.raise_for_status()
229     except requests.RequestException as exc:
230         if 'r' in locals():
231             error = ("Pushing Result to DB(%s) failed: %s" %
232                      (r.url, r.content))
233         else:
234             error = ("Pushing Result to DB(%s) failed: %s" % (url, exc))
235     except Exception as e:
236         error = ("Error [push_results_to_db("
237                  "DB: '%(db)s', "
238                  "project: '%(project)s', "
239                  "case: '%(case)s', "
240                  "pod: '%(pod)s', "
241                  "version: '%(v)s', "
242                  "scenario: '%(s)s', "
243                  "criteria: '%(c)s', "
244                  "build_tag: '%(t)s', "
245                  "details: '%(d)s')]: "
246                  "%(error)s" %
247                  {
248                      'db': url,
249                      'project': project,
250                      'case': case_name,
251                      'pod': pod_name,
252                      'v': version,
253                      's': scenario,
254                      'c': criteria,
255                      't': build_tag,
256                      'd': details,
257                      'error': e
258                  })
259     finally:
260         if error:
261             globals()['logger'].error(error)
262             return False
263         return True
264
265
266 def get_resolvconf_ns():
267     """
268     Get nameservers from current resolv.conf
269     """
270     nameservers = []
271     rconf = open("/etc/resolv.conf", "r")
272     line = rconf.readline()
273     resolver = dns.resolver.Resolver()
274     while line:
275         ip = re.search(r"\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b", line)
276         if ip:
277             resolver.nameservers = [str(ip)]
278             try:
279                 result = resolver.query('opnfv.org')[0]
280                 if result != "":
281                     nameservers.append(ip.group())
282             except dns.exception.Timeout:
283                 pass
284         line = rconf.readline()
285     return nameservers
286
287
288 def get_ci_envvars():
289     """
290     Get the CI env variables
291     """
292     ci_env_var = {
293         "installer": os.environ.get('INSTALLER_TYPE'),
294         "scenario": os.environ.get('DEPLOY_SCENARIO')}
295     return ci_env_var
296
297
298 def execute_command(cmd, logger=None,
299                     exit_on_error=True,
300                     info=False,
301                     error_msg="",
302                     verbose=True):
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             globals()['logger'].info(msg_exec)
309         else:
310             globals()['logger'].debug(msg_exec)
311     p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
312                          stderr=subprocess.STDOUT)
313     for line in iter(p.stdout.readline, b''):
314         line = line.replace('\n', '')
315         print line
316         sys.stdout.flush()
317     p.stdout.close()
318     returncode = p.wait()
319     if returncode != 0:
320         if verbose:
321             globals()['logger'].error(error_msg)
322         if exit_on_error:
323             sys.exit(1)
324
325     return returncode
326
327
328 def get_deployment_dir(logger=None):
329     """
330     Returns current Rally deployment directory
331     """
332     with open(os.environ["CONFIG_FUNCTEST_YAML"]) as f:
333         functest_yaml = yaml.safe_load(f)
334     f.close()
335     deployment_name = functest_yaml.get("rally").get("deployment_name")
336     rally_dir = functest_yaml.get("general").get("directories").get(
337         "dir_rally_inst")
338     cmd = ("rally deployment list | awk '/" + deployment_name +
339            "/ {print $2}'")
340     p = subprocess.Popen(cmd, shell=True,
341                          stdout=subprocess.PIPE,
342                          stderr=subprocess.STDOUT)
343     deployment_uuid = p.stdout.readline().rstrip()
344     if deployment_uuid == "":
345         globals()['logger'].error("Rally deployment not found.")
346         exit(-1)
347     deployment_dir = (rally_dir + "/tempest/for-deployment-" +
348                       deployment_uuid)
349     return deployment_dir
350
351
352 def get_criteria_by_test(testname):
353     criteria = ""
354     file = get_testcases_file()
355     tiers = tb.TierBuilder("", "", file)
356     for tier in tiers.get_tiers():
357         for test in tier.get_tests():
358             if test.get_name() == testname:
359                 criteria = test.get_criteria()
360
361     return criteria
362
363
364 # ----------------------------------------------------------
365 #
366 #               YAML UTILS
367 #
368 # -----------------------------------------------------------
369 def get_parameter_from_yaml(parameter, file=None):
370     """
371     Returns the value of a given parameter in config_functest.yaml
372     parameter must be given in string format with dots
373     Example: general.openstack.image_name
374     """
375     if file is None:
376         file = os.environ["CONFIG_FUNCTEST_YAML"]
377     with open(file) as f:
378         functest_yaml = yaml.safe_load(f)
379     f.close()
380     value = functest_yaml
381     for element in parameter.split("."):
382         value = value.get(element)
383         if value is None:
384             raise ValueError("The parameter %s is not defined in"
385                              " config_functest.yaml" % parameter)
386     return value
387
388
389 def check_success_rate(case_name, success_rate):
390     success_rate = float(success_rate)
391     criteria = get_criteria_by_test(case_name)
392
393     def get_criteria_value(op):
394         return float(criteria.split(op)[1].rstrip('%'))
395
396     status = 'FAIL'
397     ops = ['==', '>=']
398     for op in ops:
399         if op in criteria:
400             c_value = get_criteria_value(op)
401             if eval("%s %s %s" % (success_rate, op, c_value)):
402                 status = 'PASS'
403             break
404
405     return status
406
407
408 def merge_dicts(dict1, dict2):
409     for k in set(dict1.keys()).union(dict2.keys()):
410         if k in dict1 and k in dict2:
411             if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
412                 yield (k, dict(merge_dicts(dict1[k], dict2[k])))
413             else:
414                 yield (k, dict2[k])
415         elif k in dict1:
416             yield (k, dict1[k])
417         else:
418             yield (k, dict2[k])
419
420
421 def check_test_result(test_name, ret, start_time, stop_time):
422     def get_criteria_value():
423         return get_criteria_by_test(test_name).split('==')[1].strip()
424
425     status = 'FAIL'
426     if str(ret) == get_criteria_value():
427         status = 'PASS'
428
429     details = {
430         'timestart': start_time,
431         'duration': round(stop_time - start_time, 1),
432         'status': status,
433     }
434
435     return status, details
436
437
438 def get_testcases_file():
439     return FUNCTEST_REPO + "/ci/testcases.yaml"