Merge "unify test result check for feature project and apply to parser"
[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("Pushing %(p)s/%(n)s results: TEST_DB_URL=%(db)s "
173                 "pod_name=%(pod)s version=%(v)s scenario=%(s)s "
174                 "criteria=%(c)s details=%(d)s" % {
175                     'p': project,
176                     'n': case_name,
177                     'db': get_db_url(),
178                     'pod': pod_name,
179                     'v': version,
180                     's': scenario,
181                     'c': status,
182                     'b': build_tag,
183                     'd': details,
184                 })
185
186
187 def push_results_to_db(project, case_name, logger,
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(logger) + "/results"
194     installer = get_installer_type(logger)
195     scenario = get_scenario(logger)
196     version = get_version(logger)
197     pod_name = get_pod_name(logger)
198     build_tag = get_build_tag(logger)
199     test_start = dt.fromtimestamp(start_date).strftime('%Y-%m-%d %H:%M:%S')
200     test_stop = dt.fromtimestamp(stop_date).strftime('%Y-%m-%d %H:%M:%S')
201
202     params = {"project_name": project, "case_name": case_name,
203               "pod_name": pod_name, "installer": installer,
204               "version": version, "scenario": scenario, "criteria": criteria,
205               "build_tag": build_tag, "start_date": test_start,
206               "stop_date": test_stop, "details": details}
207
208     headers = {'Content-Type': 'application/json'}
209     try:
210         r = requests.post(url, data=json.dumps(params), headers=headers)
211         if logger:
212             logger.debug(r)
213         return True
214     except Exception, e:
215         print ("Error [push_results_to_db('%s', '%s', '%s', " +
216                "'%s', '%s', '%s', '%s', '%s', '%s')]:" %
217                (url, project, case_name, pod_name, version,
218                 scenario, criteria, build_tag, details)), e
219         return False
220
221
222 def get_resolvconf_ns():
223     """
224     Get nameservers from current resolv.conf
225     """
226     nameservers = []
227     rconf = open("/etc/resolv.conf", "r")
228     line = rconf.readline()
229     resolver = dns.resolver.Resolver()
230     while line:
231         ip = re.search(r"\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b", line)
232         if ip:
233             resolver.nameservers = [str(ip)]
234             try:
235                 result = resolver.query('opnfv.org')[0]
236                 if result != "":
237                     nameservers.append(ip.group())
238             except dns.exception.Timeout:
239                 pass
240         line = rconf.readline()
241     return nameservers
242
243
244 def get_ci_envvars():
245     """
246     Get the CI env variables
247     """
248     ci_env_var = {
249         "installer": os.environ.get('INSTALLER_TYPE'),
250         "scenario": os.environ.get('DEPLOY_SCENARIO')}
251     return ci_env_var
252
253
254 def execute_command(cmd, logger=None,
255                     exit_on_error=True,
256                     info=False,
257                     error_msg="",
258                     verbose=True):
259     if not error_msg:
260         error_msg = ("The command '%s' failed." % cmd)
261     msg_exec = ("Executing command: '%s'" % cmd)
262     if verbose:
263         if logger:
264             if info:
265                 logger.info(msg_exec)
266             else:
267                 logger.debug(msg_exec)
268         else:
269             print(msg_exec)
270     p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
271     for line in iter(p.stdout.readline, b''):
272         line = line.replace('\n', '')
273         if logger:
274             if info:
275                 logger.info(line)
276             else:
277                 logger.debug(line)
278         else:
279             print line
280     p.stdout.close()
281     returncode = p.wait()
282     if returncode != 0:
283         if verbose:
284             if logger:
285                 logger.error(error_msg)
286             else:
287                 print(error_msg)
288         if exit_on_error:
289             sys.exit(1)
290
291     return returncode
292
293
294 def get_deployment_dir(logger=None):
295     """
296     Returns current Rally deployment directory
297     """
298     with open(os.environ["CONFIG_FUNCTEST_YAML"]) as f:
299         functest_yaml = yaml.safe_load(f)
300     f.close()
301     deployment_name = functest_yaml.get("rally").get("deployment_name")
302     rally_dir = functest_yaml.get("general").get("directories").get(
303         "dir_rally_inst")
304     cmd = ("rally deployment list | awk '/" + deployment_name +
305            "/ {print $2}'")
306     p = subprocess.Popen(cmd, shell=True,
307                          stdout=subprocess.PIPE,
308                          stderr=subprocess.STDOUT)
309     deployment_uuid = p.stdout.readline().rstrip()
310     if deployment_uuid == "":
311         if logger:
312             logger.error("Rally deployment not found.")
313         exit(-1)
314     deployment_dir = (rally_dir + "/tempest/for-deployment-" +
315                       deployment_uuid)
316     return deployment_dir
317
318
319 def get_criteria_by_test(testname):
320     criteria = ""
321     file = FUNCTEST_REPO + "/ci/testcases.yaml"
322     tiers = tb.TierBuilder("", "", file)
323     for tier in tiers.get_tiers():
324         for test in tier.get_tests():
325             if test.get_name() == testname:
326                 criteria = test.get_criteria()
327
328     return criteria
329
330
331 # ----------------------------------------------------------
332 #
333 #               YAML UTILS
334 #
335 # -----------------------------------------------------------
336 def get_parameter_from_yaml(parameter, file=None):
337     """
338     Returns the value of a given parameter in config_functest.yaml
339     parameter must be given in string format with dots
340     Example: general.openstack.image_name
341     """
342     if file is None:
343         file = os.environ["CONFIG_FUNCTEST_YAML"]
344     with open(file) as f:
345         functest_yaml = yaml.safe_load(f)
346     f.close()
347     value = functest_yaml
348     for element in parameter.split("."):
349         value = value.get(element)
350         if value is None:
351             raise ValueError("The parameter %s is not defined in"
352                              " config_functest.yaml" % parameter)
353     return value
354
355
356 def check_success_rate(case_name, success_rate):
357     success_rate = float(success_rate)
358     criteria = get_criteria_by_test(case_name)
359
360     def get_criteria_value(op):
361         return float(criteria.split(op)[1].rstrip('%'))
362
363     status = 'FAIL'
364     ops = ['==', '>=']
365     for op in ops:
366         if op in criteria:
367             c_value = get_criteria_value(op)
368             if eval("%s %s %s" % (success_rate, op, c_value)):
369                 status = 'PASS'
370             break
371
372     return status
373
374
375 def check_test_result(test_name, ret, start_time, stop_time):
376     def get_criteria_value():
377         return get_criteria_by_test(test_name).split('==')[1].strip()
378
379     status = 'FAIL'
380     if str(ret) == get_criteria_value():
381         status = 'PASS'
382
383     details = {
384         'timestart': start_time,
385         'duration': round(stop_time - start_time, 1),
386         'status': status,
387     }
388
389     return status, details