unify testcases.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
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
204     try:
205         installer = os.environ['INSTALLER_TYPE']
206         scenario = os.environ['DEPLOY_SCENARIO']
207         pod_name = os.environ['NODE_NAME']
208         build_tag = os.environ['BUILD_TAG']
209     except KeyError as e:
210         msg = "Please set env var: " + str(e)
211         if logger:
212             logger.error(msg)
213         else:
214             print(msg)
215         return False
216     rule = "daily-(.+?)-[0-9]*"
217     m = re.search(rule, build_tag)
218     if m:
219         version = m.group(1)
220     else:
221         msg = "Please fix BUILD_TAG env var: " + build_tag
222         if logger:
223             logger.error(msg)
224         else:
225             print(msg)
226         return False
227     test_start = dt.fromtimestamp(start_date).strftime('%Y-%m-%d %H:%M:%S')
228     test_stop = dt.fromtimestamp(stop_date).strftime('%Y-%m-%d %H:%M:%S')
229
230     params = {"project_name": project, "case_name": case_name,
231               "pod_name": pod_name, "installer": installer,
232               "version": version, "scenario": scenario, "criteria": criteria,
233               "build_tag": build_tag, "start_date": test_start,
234               "stop_date": test_stop, "details": details}
235
236     error = None
237     headers = {'Content-Type': 'application/json'}
238     try:
239         r = requests.post(url, data=json.dumps(params), headers=headers)
240         if logger:
241             logger.debug(r)
242         r.raise_for_status()
243     except requests.RequestException as exc:
244         if 'r' in locals():
245             error = ("Pushing Result to DB(%s) failed: %s" %
246                      (r.url, r.content))
247         else:
248             error = ("Pushing Result to DB(%s) failed: %s" % (url, exc))
249     except Exception as e:
250         error = ("Error [push_results_to_db("
251                  "DB: '%(db)s', "
252                  "project: '%(project)s', "
253                  "case: '%(case)s', "
254                  "pod: '%(pod)s', "
255                  "version: '%(v)s', "
256                  "scenario: '%(s)s', "
257                  "criteria: '%(c)s', "
258                  "build_tag: '%(t)s', "
259                  "details: '%(d)s')]: "
260                  "%(error)s" %
261                  {
262                      'db': url,
263                      'project': project,
264                      'case': case_name,
265                      'pod': pod_name,
266                      'v': version,
267                      's': scenario,
268                      'c': criteria,
269                      't': build_tag,
270                      'd': details,
271                      'error': e
272                  })
273     finally:
274         if error:
275             if logger:
276                 logger.error(error)
277             else:
278                 print error
279             return False
280         return True
281
282
283 def get_resolvconf_ns():
284     """
285     Get nameservers from current resolv.conf
286     """
287     nameservers = []
288     rconf = open("/etc/resolv.conf", "r")
289     line = rconf.readline()
290     resolver = dns.resolver.Resolver()
291     while line:
292         ip = re.search(r"\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b", line)
293         if ip:
294             resolver.nameservers = [str(ip)]
295             try:
296                 result = resolver.query('opnfv.org')[0]
297                 if result != "":
298                     nameservers.append(ip.group())
299             except dns.exception.Timeout:
300                 pass
301         line = rconf.readline()
302     return nameservers
303
304
305 def get_ci_envvars():
306     """
307     Get the CI env variables
308     """
309     ci_env_var = {
310         "installer": os.environ.get('INSTALLER_TYPE'),
311         "scenario": os.environ.get('DEPLOY_SCENARIO')}
312     return ci_env_var
313
314
315 def execute_command(cmd, logger=None,
316                     exit_on_error=True,
317                     info=False,
318                     error_msg="",
319                     verbose=True):
320     if not error_msg:
321         error_msg = ("The command '%s' failed." % cmd)
322     msg_exec = ("Executing command: '%s'" % cmd)
323     if verbose:
324         if logger:
325             if info:
326                 logger.info(msg_exec)
327             else:
328                 logger.debug(msg_exec)
329         else:
330             print(msg_exec)
331     p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
332                          stderr=subprocess.STDOUT)
333     for line in iter(p.stdout.readline, b''):
334         line = line.replace('\n', '')
335         if logger:
336             if info:
337                 logger.info(line)
338             else:
339                 logger.debug(line)
340         else:
341             print line
342             sys.stdout.flush()
343     p.stdout.close()
344     returncode = p.wait()
345     if returncode != 0:
346         if verbose:
347             if logger:
348                 logger.error(error_msg)
349             else:
350                 print(error_msg)
351         if exit_on_error:
352             sys.exit(1)
353
354     return returncode
355
356
357 def get_deployment_dir(logger=None):
358     """
359     Returns current Rally deployment directory
360     """
361     with open(os.environ["CONFIG_FUNCTEST_YAML"]) as f:
362         functest_yaml = yaml.safe_load(f)
363     f.close()
364     deployment_name = functest_yaml.get("rally").get("deployment_name")
365     rally_dir = functest_yaml.get("general").get("directories").get(
366         "dir_rally_inst")
367     cmd = ("rally deployment list | awk '/" + deployment_name +
368            "/ {print $2}'")
369     p = subprocess.Popen(cmd, shell=True,
370                          stdout=subprocess.PIPE,
371                          stderr=subprocess.STDOUT)
372     deployment_uuid = p.stdout.readline().rstrip()
373     if deployment_uuid == "":
374         if logger:
375             logger.error("Rally deployment not found.")
376         exit(-1)
377     deployment_dir = (rally_dir + "/tempest/for-deployment-" +
378                       deployment_uuid)
379     return deployment_dir
380
381
382 def get_criteria_by_test(testname):
383     criteria = ""
384     file = get_testcases_file()
385     tiers = tb.TierBuilder("", "", file)
386     for tier in tiers.get_tiers():
387         for test in tier.get_tests():
388             if test.get_name() == testname:
389                 criteria = test.get_criteria()
390
391     return criteria
392
393
394 # ----------------------------------------------------------
395 #
396 #               YAML UTILS
397 #
398 # -----------------------------------------------------------
399 def get_parameter_from_yaml(parameter, file=None):
400     """
401     Returns the value of a given parameter in config_functest.yaml
402     parameter must be given in string format with dots
403     Example: general.openstack.image_name
404     """
405     if file is None:
406         file = os.environ["CONFIG_FUNCTEST_YAML"]
407     with open(file) as f:
408         functest_yaml = yaml.safe_load(f)
409     f.close()
410     value = functest_yaml
411     for element in parameter.split("."):
412         value = value.get(element)
413         if value is None:
414             raise ValueError("The parameter %s is not defined in"
415                              " config_functest.yaml" % parameter)
416     return value
417
418
419 def check_success_rate(case_name, success_rate):
420     success_rate = float(success_rate)
421     criteria = get_criteria_by_test(case_name)
422
423     def get_criteria_value(op):
424         return float(criteria.split(op)[1].rstrip('%'))
425
426     status = 'FAIL'
427     ops = ['==', '>=']
428     for op in ops:
429         if op in criteria:
430             c_value = get_criteria_value(op)
431             if eval("%s %s %s" % (success_rate, op, c_value)):
432                 status = 'PASS'
433             break
434
435     return status
436
437
438 def merge_dicts(dict1, dict2):
439     for k in set(dict1.keys()).union(dict2.keys()):
440         if k in dict1 and k in dict2:
441             if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
442                 yield (k, dict(merge_dicts(dict1[k], dict2[k])))
443             else:
444                 yield (k, dict2[k])
445         elif k in dict1:
446             yield (k, dict1[k])
447         else:
448             yield (k, dict2[k])
449
450
451 def check_test_result(test_name, ret, start_time, stop_time):
452     def get_criteria_value():
453         return get_criteria_by_test(test_name).split('==')[1].strip()
454
455     status = 'FAIL'
456     if str(ret) == get_criteria_value():
457         status = 'PASS'
458
459     details = {
460         'timestart': start_time,
461         'duration': round(stop_time - start_time, 1),
462         'status': status,
463     }
464
465     return status, details
466
467
468 def get_testcases_file():
469     return FUNCTEST_REPO + "/ci/testcases.yaml"