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