Merge "[Promise] Fix bug opening the json result file" into stable/colorado
[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():
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.error("Impossible to retrieve the scenario")
101         scenario = "Unknown_scenario"
102
103     return scenario
104
105
106 def get_version():
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()
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():
126     """
127     Get PoD Name from env variable NODE_NAME
128     """
129     try:
130         return os.environ['NODE_NAME']
131     except KeyError:
132         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():
139     """
140     Get build tag of jenkins jobs
141     """
142     try:
143         build_tag = os.environ['BUILD_TAG']
144     except KeyError:
145         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():
152     """
153     Returns DB URL
154     """
155     functest_yaml = get_functest_yaml()
156     db_url = functest_yaml.get("results").get("test_db_url")
157     return db_url
158
159
160 def logger_test_results(project, case_name, status, details):
161     pod_name = get_pod_name()
162     scenario = get_scenario()
163     version = get_version()
164     build_tag = get_build_tag()
165
166     logger.info(
167         "\n"
168         "****************************************\n"
169         "\t %(p)s/%(n)s results \n\n"
170         "****************************************\n"
171         "DB:\t%(db)s\n"
172         "pod:\t%(pod)s\n"
173         "version:\t%(v)s\n"
174         "scenario:\t%(s)s\n"
175         "status:\t%(c)s\n"
176         "build tag:\t%(b)s\n"
177         "details:\t%(d)s\n"
178         % {'p': project,
179             'n': case_name,
180             'db': get_db_url(),
181             'pod': pod_name,
182             'v': version,
183             's': scenario,
184             'c': status,
185             'b': build_tag,
186             'd': details})
187
188
189 def push_results_to_db(project, case_name,
190                        start_date, stop_date, criteria, details):
191     """
192     POST results to the Result target DB
193     """
194     # Retrieve params from CI and conf
195     url = get_db_url() + "/results"
196
197     try:
198         installer = os.environ['INSTALLER_TYPE']
199         scenario = os.environ['DEPLOY_SCENARIO']
200         pod_name = os.environ['NODE_NAME']
201         build_tag = os.environ['BUILD_TAG']
202     except KeyError as e:
203         logger.error("Please set env var: " + str(e))
204         return False
205     rule = "daily-(.+?)-[0-9]*"
206     m = re.search(rule, build_tag)
207     if m:
208         version = m.group(1)
209     else:
210         logger.error("Please fix BUILD_TAG env var: " + build_tag)
211         return False
212     test_start = dt.fromtimestamp(start_date).strftime('%Y-%m-%d %H:%M:%S')
213     test_stop = dt.fromtimestamp(stop_date).strftime('%Y-%m-%d %H:%M:%S')
214
215     params = {"project_name": project, "case_name": case_name,
216               "pod_name": pod_name, "installer": installer,
217               "version": version, "scenario": scenario, "criteria": criteria,
218               "build_tag": build_tag, "start_date": test_start,
219               "stop_date": test_stop, "details": details}
220
221     error = None
222     headers = {'Content-Type': 'application/json'}
223     try:
224         r = requests.post(url, data=json.dumps(params), headers=headers)
225         logger.debug(r)
226         r.raise_for_status()
227     except requests.RequestException as exc:
228         if 'r' in locals():
229             error = ("Pushing Result to DB(%s) failed: %s" %
230                      (r.url, r.content))
231         else:
232             error = ("Pushing Result to DB(%s) failed: %s" % (url, exc))
233     except Exception as e:
234         error = ("Error [push_results_to_db("
235                  "DB: '%(db)s', "
236                  "project: '%(project)s', "
237                  "case: '%(case)s', "
238                  "pod: '%(pod)s', "
239                  "version: '%(v)s', "
240                  "scenario: '%(s)s', "
241                  "criteria: '%(c)s', "
242                  "build_tag: '%(t)s', "
243                  "details: '%(d)s')]: "
244                  "%(error)s" %
245                  {
246                      'db': url,
247                      'project': project,
248                      'case': case_name,
249                      'pod': pod_name,
250                      'v': version,
251                      's': scenario,
252                      'c': criteria,
253                      't': build_tag,
254                      'd': details,
255                      'error': e
256                  })
257     finally:
258         if error:
259             logger.error(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, exit_on_error=True, 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         if exit_on_error:
325             sys.exit(1)
326
327     return returncode
328
329
330 def get_deployment_dir():
331     """
332     Returns current Rally deployment directory
333     """
334     functest_yaml = get_functest_yaml()
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         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"
440
441
442 def get_functest_yaml():
443     with open(os.environ["CONFIG_FUNCTEST_YAML"]) as f:
444         functest_yaml = yaml.safe_load(f)
445     f.close()
446     return functest_yaml