unify functest_yaml obtain process
[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     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(logger, project, case_name, status, details):
161     pod_name = get_pod_name(logger)
162     scenario = get_scenario(logger)
163     version = get_version(logger)
164     build_tag = get_build_tag(logger)
165
166     globals()['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, logger,
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(logger) + "/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         globals()['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         globals()['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         globals()['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             globals()['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, 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 info:
306             globals()['logger'].info(msg_exec)
307         else:
308             globals()['logger'].debug(msg_exec)
309     p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
310                          stderr=subprocess.STDOUT)
311     for line in iter(p.stdout.readline, b''):
312         line = line.replace('\n', '')
313         print line
314         sys.stdout.flush()
315     p.stdout.close()
316     returncode = p.wait()
317     if returncode != 0:
318         if verbose:
319             globals()['logger'].error(error_msg)
320         if exit_on_error:
321             sys.exit(1)
322
323     return returncode
324
325
326 def get_deployment_dir(logger=None):
327     """
328     Returns current Rally deployment directory
329     """
330     functest_yaml = get_functest_yaml()
331     deployment_name = functest_yaml.get("rally").get("deployment_name")
332     rally_dir = functest_yaml.get("general").get("directories").get(
333         "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         globals()['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=None):
366     """
367     Returns the value of a given parameter in config_functest.yaml
368     parameter must be given in string format with dots
369     Example: general.openstack.image_name
370     """
371     if file is None:
372         file = os.environ["CONFIG_FUNCTEST_YAML"]
373     with open(file) as f:
374         functest_yaml = yaml.safe_load(f)
375     f.close()
376     value = functest_yaml
377     for element in parameter.split("."):
378         value = value.get(element)
379         if value is None:
380             raise ValueError("The parameter %s is not defined in"
381                              " config_functest.yaml" % parameter)
382     return value
383
384
385 def check_success_rate(case_name, success_rate):
386     success_rate = float(success_rate)
387     criteria = get_criteria_by_test(case_name)
388
389     def get_criteria_value(op):
390         return float(criteria.split(op)[1].rstrip('%'))
391
392     status = 'FAIL'
393     ops = ['==', '>=']
394     for op in ops:
395         if op in criteria:
396             c_value = get_criteria_value(op)
397             if eval("%s %s %s" % (success_rate, op, c_value)):
398                 status = 'PASS'
399             break
400
401     return status
402
403
404 def merge_dicts(dict1, dict2):
405     for k in set(dict1.keys()).union(dict2.keys()):
406         if k in dict1 and k in dict2:
407             if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
408                 yield (k, dict(merge_dicts(dict1[k], dict2[k])))
409             else:
410                 yield (k, dict2[k])
411         elif k in dict1:
412             yield (k, dict1[k])
413         else:
414             yield (k, dict2[k])
415
416
417 def check_test_result(test_name, ret, start_time, stop_time):
418     def get_criteria_value():
419         return get_criteria_by_test(test_name).split('==')[1].strip()
420
421     status = 'FAIL'
422     if str(ret) == get_criteria_value():
423         status = 'PASS'
424
425     details = {
426         'timestart': start_time,
427         'duration': round(stop_time - start_time, 1),
428         'status': status,
429     }
430
431     return status, details
432
433
434 def get_testcases_file():
435     return FUNCTEST_REPO + "/ci/testcases.yaml"
436
437
438 def get_functest_yaml():
439     with open(os.environ["CONFIG_FUNCTEST_YAML"]) as f:
440         functest_yaml = yaml.safe_load(f)
441     f.close()
442     return functest_yaml