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