Avoid duplicating logs in run_tests.py
[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
11 """ global variables """
12
13 from datetime import datetime as dt
14 import json
15 import os
16 import os.path
17 import re
18 import shutil
19 import subprocess
20 import sys
21 import urllib2
22 import dns.resolver
23
24 import functest.ci.tier_builder as tb
25 from git import Repo
26 import requests
27 import yaml
28
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         if logger:
88             logger.error("Impossible to retrieve the installer type")
89         installer = "Unknown_installer"
90
91     return installer
92
93
94 def get_scenario(logger=None):
95     """
96     Get scenario
97     """
98     try:
99         scenario = os.environ['DEPLOY_SCENARIO']
100     except KeyError:
101         if logger:
102             logger.error("Impossible to retrieve the scenario")
103         scenario = "Unknown_scenario"
104
105     return scenario
106
107
108 def get_version(logger=None):
109     """
110     Get version
111     """
112     # Use the build tag to retrieve the version
113     # By default version is unknown
114     # if launched through CI the build tag has the following format
115     # jenkins-<project>-<installer>-<pod>-<job>-<branch>-<id>
116     # e.g. jenkins-functest-fuel-opnfv-jump-2-daily-master-190
117     # use regex to match branch info
118     rule = "daily-(.+?)-[0-9]*"
119     build_tag = get_build_tag(logger)
120     m = re.search(rule, build_tag)
121     if m:
122         return m.group(1)
123     else:
124         return "unknown"
125
126
127 def get_pod_name(logger=None):
128     """
129     Get PoD Name from env variable NODE_NAME
130     """
131     try:
132         return os.environ['NODE_NAME']
133     except KeyError:
134         if logger:
135             logger.error(
136                 "Unable to retrieve the POD name from environment. " +
137                 "Using pod name 'unknown-pod'")
138         return "unknown-pod"
139
140
141 def get_build_tag(logger=None):
142     """
143     Get build tag of jenkins jobs
144     """
145     try:
146         build_tag = os.environ['BUILD_TAG']
147     except KeyError:
148         if logger:
149             logger.error("Impossible to retrieve the build tag")
150         build_tag = "unknown_build_tag"
151
152     return build_tag
153
154
155 def get_db_url(logger=None):
156     """
157     Returns DB URL
158     """
159     with open(os.environ["CONFIG_FUNCTEST_YAML"]) as f:
160         functest_yaml = yaml.safe_load(f)
161     f.close()
162     db_url = functest_yaml.get("results").get("test_db_url")
163     return db_url
164
165
166 def logger_test_results(logger, project, case_name, status, details):
167     pod_name = get_pod_name(logger)
168     scenario = get_scenario(logger)
169     version = get_version(logger)
170     build_tag = get_build_tag(logger)
171
172     logger.info("\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                 % {
184                     'p': project,
185                     'n': case_name,
186                     'db': get_db_url(),
187                     'pod': pod_name,
188                     'v': version,
189                     's': scenario,
190                     'c': status,
191                     'b': build_tag,
192                     'd': details,
193                 })
194
195
196 def push_results_to_db(project, case_name, logger,
197                        start_date, stop_date, criteria, details):
198     """
199     POST results to the Result target DB
200     """
201     # Retrieve params from CI and conf
202     url = get_db_url(logger) + "/results"
203     installer = get_installer_type(logger)
204     scenario = get_scenario(logger)
205     version = get_version(logger)
206     pod_name = get_pod_name(logger)
207     build_tag = get_build_tag(logger)
208     test_start = dt.fromtimestamp(start_date).strftime('%Y-%m-%d %H:%M:%S')
209     test_stop = dt.fromtimestamp(stop_date).strftime('%Y-%m-%d %H:%M:%S')
210
211     params = {"project_name": project, "case_name": case_name,
212               "pod_name": pod_name, "installer": installer,
213               "version": version, "scenario": scenario, "criteria": criteria,
214               "build_tag": build_tag, "start_date": test_start,
215               "stop_date": test_stop, "details": details}
216
217     error = None
218     headers = {'Content-Type': 'application/json'}
219     try:
220         r = requests.post(url, data=json.dumps(params), headers=headers)
221         if logger:
222             logger.debug(r)
223         r.raise_for_status()
224     except requests.RequestException as exc:
225         if 'r' in locals():
226             error = ("Pushing Result to DB(%s) failed: %s" %
227                      (r.url, r.content))
228         else:
229             error = ("Pushing Result to DB(%s) failed: %s" % (url, exc))
230     except Exception as e:
231         error = ("Error [push_results_to_db("
232                  "DB: '%(db)s', "
233                  "project: '%(project)s', "
234                  "case: '%(case)s', "
235                  "pod: '%(pod)s', "
236                  "version: '%(v)s', "
237                  "scenario: '%(s)s', "
238                  "criteria: '%(c)s', "
239                  "build_tag: '%(t)s', "
240                  "details: '%(d)s')]: "
241                  "%(error)s" %
242                  {
243                      'db': url,
244                      'project': project,
245                      'case': case_name,
246                      'pod': pod_name,
247                      'v': version,
248                      's': scenario,
249                      'c': criteria,
250                      't': build_tag,
251                      'd': details,
252                      'error': e
253                  })
254     finally:
255         if error:
256             if logger:
257                 logger.error(error)
258             else:
259                 print 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 = [str(ip)]
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(cmd, logger=None,
297                     exit_on_error=True,
298                     info=False,
299                     error_msg="",
300                     verbose=True):
301     if not error_msg:
302         error_msg = ("The command '%s' failed." % cmd)
303     msg_exec = ("Executing command: '%s'" % cmd)
304     if verbose:
305         if logger:
306             if info:
307                 logger.info(msg_exec)
308             else:
309                 logger.debug(msg_exec)
310         else:
311             print(msg_exec)
312     p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
313                          stderr=subprocess.STDOUT)
314     for line in iter(p.stdout.readline, b''):
315         line = line.replace('\n', '')
316         if logger:
317             if info:
318                 logger.info(line)
319             else:
320                 logger.debug(line)
321         else:
322             print line
323     p.stdout.close()
324     returncode = p.wait()
325     if returncode != 0:
326         if verbose:
327             if logger:
328                 logger.error(error_msg)
329             else:
330                 print(error_msg)
331         if exit_on_error:
332             sys.exit(1)
333
334     return returncode
335
336
337 def get_deployment_dir(logger=None):
338     """
339     Returns current Rally deployment directory
340     """
341     with open(os.environ["CONFIG_FUNCTEST_YAML"]) as f:
342         functest_yaml = yaml.safe_load(f)
343     f.close()
344     deployment_name = functest_yaml.get("rally").get("deployment_name")
345     rally_dir = functest_yaml.get("general").get("directories").get(
346         "dir_rally_inst")
347     cmd = ("rally deployment list | awk '/" + deployment_name +
348            "/ {print $2}'")
349     p = subprocess.Popen(cmd, shell=True,
350                          stdout=subprocess.PIPE,
351                          stderr=subprocess.STDOUT)
352     deployment_uuid = p.stdout.readline().rstrip()
353     if deployment_uuid == "":
354         if logger:
355             logger.error("Rally deployment not found.")
356         exit(-1)
357     deployment_dir = (rally_dir + "/tempest/for-deployment-" +
358                       deployment_uuid)
359     return deployment_dir
360
361
362 def get_criteria_by_test(testname):
363     criteria = ""
364     file = FUNCTEST_REPO + "/ci/testcases.yaml"
365     tiers = tb.TierBuilder("", "", file)
366     for tier in tiers.get_tiers():
367         for test in tier.get_tests():
368             if test.get_name() == testname:
369                 criteria = test.get_criteria()
370
371     return criteria
372
373
374 # ----------------------------------------------------------
375 #
376 #               YAML UTILS
377 #
378 # -----------------------------------------------------------
379 def get_parameter_from_yaml(parameter, file=None):
380     """
381     Returns the value of a given parameter in config_functest.yaml
382     parameter must be given in string format with dots
383     Example: general.openstack.image_name
384     """
385     if file is None:
386         file = os.environ["CONFIG_FUNCTEST_YAML"]
387     with open(file) as f:
388         functest_yaml = yaml.safe_load(f)
389     f.close()
390     value = functest_yaml
391     for element in parameter.split("."):
392         value = value.get(element)
393         if value is None:
394             raise ValueError("The parameter %s is not defined in"
395                              " config_functest.yaml" % parameter)
396     return value
397
398
399 def check_success_rate(case_name, success_rate):
400     success_rate = float(success_rate)
401     criteria = get_criteria_by_test(case_name)
402
403     def get_criteria_value(op):
404         return float(criteria.split(op)[1].rstrip('%'))
405
406     status = 'FAIL'
407     ops = ['==', '>=']
408     for op in ops:
409         if op in criteria:
410             c_value = get_criteria_value(op)
411             if eval("%s %s %s" % (success_rate, op, c_value)):
412                 status = 'PASS'
413             break
414
415     return status
416
417
418 def merge_dicts(dict1, dict2):
419     for k in set(dict1.keys()).union(dict2.keys()):
420         if k in dict1 and k in dict2:
421             if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
422                 yield (k, dict(merge_dicts(dict1[k], dict2[k])))
423             else:
424                 yield (k, dict2[k])
425         elif k in dict1:
426             yield (k, dict1[k])
427         else:
428             yield (k, dict2[k])
429
430
431 def check_test_result(test_name, ret, start_time, stop_time):
432     def get_criteria_value():
433         return get_criteria_by_test(test_name).split('==')[1].strip()
434
435     status = 'FAIL'
436     if str(ret) == get_criteria_value():
437         status = 'PASS'
438
439     details = {
440         'timestart': start_time,
441         'duration': round(stop_time - start_time, 1),
442         'status': status,
443     }
444
445     return status, details