make use of functest_utils.get_parameter_from_yaml
[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     return get_functest_config('results.test_db_url')
156
157
158 def logger_test_results(project, case_name, status, details):
159     pod_name = get_pod_name()
160     scenario = get_scenario()
161     version = get_version()
162     build_tag = get_build_tag()
163
164     logger.info(
165         "\n"
166         "****************************************\n"
167         "\t %(p)s/%(n)s results \n\n"
168         "****************************************\n"
169         "DB:\t%(db)s\n"
170         "pod:\t%(pod)s\n"
171         "version:\t%(v)s\n"
172         "scenario:\t%(s)s\n"
173         "status:\t%(c)s\n"
174         "build tag:\t%(b)s\n"
175         "details:\t%(d)s\n"
176         % {'p': project,
177             'n': case_name,
178             'db': get_db_url(),
179             'pod': pod_name,
180             'v': version,
181             's': scenario,
182             'c': status,
183             'b': build_tag,
184             'd': details})
185
186
187 def push_results_to_db(project, case_name,
188                        start_date, stop_date, criteria, details):
189     """
190     POST results to the Result target DB
191     """
192     # Retrieve params from CI and conf
193     url = get_db_url() + "/results"
194
195     try:
196         installer = os.environ['INSTALLER_TYPE']
197         scenario = os.environ['DEPLOY_SCENARIO']
198         pod_name = os.environ['NODE_NAME']
199         build_tag = os.environ['BUILD_TAG']
200     except KeyError as e:
201         logger.error("Please set env var: " + str(e))
202         return False
203     rule = "daily-(.+?)-[0-9]*"
204     m = re.search(rule, build_tag)
205     if m:
206         version = m.group(1)
207     else:
208         logger.error("Please fix BUILD_TAG env var: " + build_tag)
209         return False
210     test_start = dt.fromtimestamp(start_date).strftime('%Y-%m-%d %H:%M:%S')
211     test_stop = dt.fromtimestamp(stop_date).strftime('%Y-%m-%d %H:%M:%S')
212
213     params = {"project_name": project, "case_name": case_name,
214               "pod_name": pod_name, "installer": installer,
215               "version": version, "scenario": scenario, "criteria": criteria,
216               "build_tag": build_tag, "start_date": test_start,
217               "stop_date": test_stop, "details": details}
218
219     error = None
220     headers = {'Content-Type': 'application/json'}
221     try:
222         r = requests.post(url, data=json.dumps(params), headers=headers)
223         logger.debug(r)
224         r.raise_for_status()
225     except requests.RequestException as exc:
226         if 'r' in locals():
227             error = ("Pushing Result to DB(%s) failed: %s" %
228                      (r.url, r.content))
229         else:
230             error = ("Pushing Result to DB(%s) failed: %s" % (url, exc))
231     except Exception as e:
232         error = ("Error [push_results_to_db("
233                  "DB: '%(db)s', "
234                  "project: '%(project)s', "
235                  "case: '%(case)s', "
236                  "pod: '%(pod)s', "
237                  "version: '%(v)s', "
238                  "scenario: '%(s)s', "
239                  "criteria: '%(c)s', "
240                  "build_tag: '%(t)s', "
241                  "details: '%(d)s')]: "
242                  "%(error)s" %
243                  {
244                      'db': url,
245                      'project': project,
246                      'case': case_name,
247                      'pod': pod_name,
248                      'v': version,
249                      's': scenario,
250                      'c': criteria,
251                      't': build_tag,
252                      'd': details,
253                      'error': e
254                  })
255     finally:
256         if error:
257             logger.error(error)
258             return False
259         return True
260
261
262 def get_resolvconf_ns():
263     """
264     Get nameservers from current resolv.conf
265     """
266     nameservers = []
267     rconf = open("/etc/resolv.conf", "r")
268     line = rconf.readline()
269     resolver = dns.resolver.Resolver()
270     while line:
271         ip = re.search(r"\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b", line)
272         if ip:
273             resolver.nameservers = [str(ip)]
274             try:
275                 result = resolver.query('opnfv.org')[0]
276                 if result != "":
277                     nameservers.append(ip.group())
278             except dns.exception.Timeout:
279                 pass
280         line = rconf.readline()
281     return nameservers
282
283
284 def get_ci_envvars():
285     """
286     Get the CI env variables
287     """
288     ci_env_var = {
289         "installer": os.environ.get('INSTALLER_TYPE'),
290         "scenario": os.environ.get('DEPLOY_SCENARIO')}
291     return ci_env_var
292
293
294 def execute_command(cmd, exit_on_error=True, info=False, error_msg="",
295                     verbose=True, output_file=None):
296     if not error_msg:
297         error_msg = ("The command '%s' failed." % cmd)
298     msg_exec = ("Executing command: '%s'" % cmd)
299     if verbose:
300         if info:
301             logger.info(msg_exec)
302         else:
303             logger.debug(msg_exec)
304     p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
305                          stderr=subprocess.STDOUT)
306     if output_file:
307         f = open(output_file, "w")
308     for line in iter(p.stdout.readline, b''):
309         if output_file:
310             f.write(line)
311         else:
312             line = line.replace('\n', '')
313             print line
314             sys.stdout.flush()
315     if output_file:
316         f.close()
317     p.stdout.close()
318     returncode = p.wait()
319     if returncode != 0:
320         if verbose:
321             logger.error(error_msg)
322         if exit_on_error:
323             sys.exit(1)
324
325     return returncode
326
327
328 def get_deployment_dir():
329     """
330     Returns current Rally deployment directory
331     """
332     deployment_name = get_functest_config('rally.deployment_name')
333     rally_dir = get_functest_config('general.directories.dir_rally_inst')
334     cmd = ("rally deployment list | awk '/" + deployment_name +
335            "/ {print $2}'")
336     p = subprocess.Popen(cmd, shell=True,
337                          stdout=subprocess.PIPE,
338                          stderr=subprocess.STDOUT)
339     deployment_uuid = p.stdout.readline().rstrip()
340     if deployment_uuid == "":
341         logger.error("Rally deployment not found.")
342         exit(-1)
343     deployment_dir = (rally_dir + "/tempest/for-deployment-" +
344                       deployment_uuid)
345     return deployment_dir
346
347
348 def get_criteria_by_test(testname):
349     criteria = ""
350     file = get_testcases_file()
351     tiers = tb.TierBuilder("", "", file)
352     for tier in tiers.get_tiers():
353         for test in tier.get_tests():
354             if test.get_name() == testname:
355                 criteria = test.get_criteria()
356
357     return criteria
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                              " config_functest.yaml" % parameter)
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 check_test_result(test_name, ret, start_time, stop_time):
421     def get_criteria_value():
422         return get_criteria_by_test(test_name).split('==')[1].strip()
423
424     status = 'FAIL'
425     if str(ret) == get_criteria_value():
426         status = 'PASS'
427
428     details = {
429         'timestart': start_time,
430         'duration': round(stop_time - start_time, 1),
431         'status': status,
432     }
433
434     return status, details
435
436
437 def get_testcases_file():
438     return FUNCTEST_REPO + "/ci/testcases.yaml"
439
440
441 def get_functest_yaml():
442     with open(os.environ["CONFIG_FUNCTEST_YAML"]) as f:
443         functest_yaml = yaml.safe_load(f)
444     f.close()
445     return functest_yaml