Repo structure modification
[functest.git] / functest / 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 import json
11 import os
12 import re
13 import shutil
14 import subprocess
15 import sys
16 import urllib2
17 from datetime import datetime as dt
18
19 import dns.resolver
20 import requests
21 import yaml
22 from git import Repo
23
24 import functest.utils.functest_logger as ft_logger
25
26 logger = ft_logger.Logger("functest_utils").getLogger()
27
28 REPOS_DIR = os.getenv('repos_dir')
29 FUNCTEST_REPO = ("%s/functest" % REPOS_DIR)
30
31
32 # ----------------------------------------------------------
33 #
34 #               INTERNET UTILS
35 #
36 # -----------------------------------------------------------
37 def check_internet_connectivity(url='http://www.opnfv.org/'):
38     """
39     Check if there is access to the internet
40     """
41     try:
42         urllib2.urlopen(url, timeout=5)
43         return True
44     except urllib2.URLError:
45         return False
46
47
48 def download_url(url, dest_path):
49     """
50     Download a file to a destination path given a URL
51     """
52     name = url.rsplit('/')[-1]
53     dest = dest_path + "/" + name
54     try:
55         response = urllib2.urlopen(url)
56     except (urllib2.HTTPError, urllib2.URLError):
57         return False
58
59     with open(dest, 'wb') as f:
60         shutil.copyfileobj(response, f)
61     return True
62
63
64 # ----------------------------------------------------------
65 #
66 #               CI UTILS
67 #
68 # -----------------------------------------------------------
69 def get_git_branch(repo_path):
70     """
71     Get git branch name
72     """
73     repo = Repo(repo_path)
74     branch = repo.active_branch
75     return branch.name
76
77
78 def get_installer_type():
79     """
80     Get installer type (fuel, apex, joid, compass)
81     """
82     try:
83         installer = os.environ['INSTALLER_TYPE']
84     except KeyError:
85         logger.error("Impossible to retrieve the installer type")
86         installer = "Unknown_installer"
87
88     return installer
89
90
91 def get_scenario():
92     """
93     Get scenario
94     """
95     try:
96         scenario = os.environ['DEPLOY_SCENARIO']
97     except KeyError:
98         logger.error("Impossible to retrieve the scenario")
99         scenario = "Unknown_scenario"
100
101     return scenario
102
103
104 def get_version():
105     """
106     Get version
107     """
108     # Use the build tag to retrieve the version
109     # By default version is unknown
110     # if launched through CI the build tag has the following format
111     # jenkins-<project>-<installer>-<pod>-<job>-<branch>-<id>
112     # e.g. jenkins-functest-fuel-opnfv-jump-2-daily-master-190
113     # use regex to match branch info
114     rule = "daily-(.+?)-[0-9]*"
115     build_tag = get_build_tag()
116     m = re.search(rule, build_tag)
117     if m:
118         return m.group(1)
119     else:
120         return "unknown"
121
122
123 def get_pod_name():
124     """
125     Get PoD Name from env variable NODE_NAME
126     """
127     try:
128         return os.environ['NODE_NAME']
129     except KeyError:
130         logger.error(
131             "Unable to retrieve the POD name from environment. " +
132             "Using pod name 'unknown-pod'")
133         return "unknown-pod"
134
135
136 def get_build_tag():
137     """
138     Get build tag of jenkins jobs
139     """
140     try:
141         build_tag = os.environ['BUILD_TAG']
142     except KeyError:
143         logger.error("Impossible to retrieve the build tag")
144         build_tag = "unknown_build_tag"
145
146     return build_tag
147
148
149 def get_db_url():
150     """
151     Returns DB URL
152     """
153     return get_functest_config('results.test_db_url')
154
155
156 def logger_test_results(project, case_name, status, details):
157     pod_name = get_pod_name()
158     scenario = get_scenario()
159     version = get_version()
160     build_tag = get_build_tag()
161
162     logger.info(
163         "\n"
164         "****************************************\n"
165         "\t %(p)s/%(n)s results \n\n"
166         "****************************************\n"
167         "DB:\t%(db)s\n"
168         "pod:\t%(pod)s\n"
169         "version:\t%(v)s\n"
170         "scenario:\t%(s)s\n"
171         "status:\t%(c)s\n"
172         "build tag:\t%(b)s\n"
173         "details:\t%(d)s\n"
174         % {'p': project,
175             'n': case_name,
176             'db': get_db_url(),
177             'pod': pod_name,
178             'v': version,
179             's': scenario,
180             'c': status,
181             'b': build_tag,
182             'd': details})
183
184
185 def push_results_to_db(project, case_name,
186                        start_date, stop_date, criteria, details):
187     """
188     POST results to the Result target DB
189     """
190     # Retrieve params from CI and conf
191     url = get_db_url() + "/results"
192
193     try:
194         installer = os.environ['INSTALLER_TYPE']
195         scenario = os.environ['DEPLOY_SCENARIO']
196         pod_name = os.environ['NODE_NAME']
197         build_tag = os.environ['BUILD_TAG']
198     except KeyError as e:
199         logger.error("Please set env var: " + str(e))
200         return False
201     rule = "daily-(.+?)-[0-9]*"
202     m = re.search(rule, build_tag)
203     if m:
204         version = m.group(1)
205     else:
206         logger.error("Please fix BUILD_TAG env var: " + build_tag)
207         return False
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     error = None
218     headers = {'Content-Type': 'application/json'}
219     try:
220         r = requests.post(url, data=json.dumps(params), headers=headers)
221         logger.debug(r)
222         r.raise_for_status()
223     except requests.RequestException as exc:
224         if 'r' in locals():
225             error = ("Pushing Result to DB(%s) failed: %s" %
226                      (r.url, r.content))
227         else:
228             error = ("Pushing Result to DB(%s) failed: %s" % (url, exc))
229     except Exception as e:
230         error = ("Error [push_results_to_db("
231                  "DB: '%(db)s', "
232                  "project: '%(project)s', "
233                  "case: '%(case)s', "
234                  "pod: '%(pod)s', "
235                  "version: '%(v)s', "
236                  "scenario: '%(s)s', "
237                  "criteria: '%(c)s', "
238                  "build_tag: '%(t)s', "
239                  "details: '%(d)s')]: "
240                  "%(error)s" %
241                  {
242                      'db': url,
243                      'project': project,
244                      'case': case_name,
245                      'pod': pod_name,
246                      'v': version,
247                      's': scenario,
248                      'c': criteria,
249                      't': build_tag,
250                      'd': details,
251                      'error': e
252                  })
253     finally:
254         if error:
255             logger.error(error)
256             return False
257         return True
258
259
260 def get_resolvconf_ns():
261     """
262     Get nameservers from current resolv.conf
263     """
264     nameservers = []
265     rconf = open("/etc/resolv.conf", "r")
266     line = rconf.readline()
267     resolver = dns.resolver.Resolver()
268     while line:
269         ip = re.search(r"\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b", line)
270         if ip:
271             resolver.nameservers = [str(ip)]
272             try:
273                 result = resolver.query('opnfv.org')[0]
274                 if result != "":
275                     nameservers.append(ip.group())
276             except dns.exception.Timeout:
277                 pass
278         line = rconf.readline()
279     return nameservers
280
281
282 def get_ci_envvars():
283     """
284     Get the CI env variables
285     """
286     ci_env_var = {
287         "installer": os.environ.get('INSTALLER_TYPE'),
288         "scenario": os.environ.get('DEPLOY_SCENARIO')}
289     return ci_env_var
290
291
292 def execute_command(cmd, info=False, error_msg="",
293                     verbose=True, output_file=None):
294     if not error_msg:
295         error_msg = ("The command '%s' failed." % cmd)
296     msg_exec = ("Executing command: '%s'" % cmd)
297     if verbose:
298         if info:
299             logger.info(msg_exec)
300         else:
301             logger.debug(msg_exec)
302     p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
303                          stderr=subprocess.STDOUT)
304     if output_file:
305         f = open(output_file, "w")
306     for line in iter(p.stdout.readline, b''):
307         if output_file:
308             f.write(line)
309         else:
310             line = line.replace('\n', '')
311             print line
312             sys.stdout.flush()
313     if output_file:
314         f.close()
315     p.stdout.close()
316     returncode = p.wait()
317     if returncode != 0:
318         if verbose:
319             logger.error(error_msg)
320
321     return returncode
322
323
324 def get_deployment_dir():
325     """
326     Returns current Rally deployment directory
327     """
328     deployment_name = get_functest_config('rally.deployment_name')
329     rally_dir = get_functest_config('general.directories.dir_rally_inst')
330     cmd = ("rally deployment list | awk '/" + deployment_name +
331            "/ {print $2}'")
332     p = subprocess.Popen(cmd, shell=True,
333                          stdout=subprocess.PIPE,
334                          stderr=subprocess.STDOUT)
335     deployment_uuid = p.stdout.readline().rstrip()
336     if deployment_uuid == "":
337         logger.error("Rally deployment not found.")
338         exit(-1)
339     deployment_dir = (rally_dir + "/tempest/for-deployment-" +
340                       deployment_uuid)
341     return deployment_dir
342
343
344 def get_dict_by_test(testname):
345     with open(get_testcases_file()) as f:
346         testcases_yaml = yaml.safe_load(f)
347
348     for dic_tier in testcases_yaml.get("tiers"):
349         for dic_testcase in dic_tier['testcases']:
350             if dic_testcase['name'] == testname:
351                 return dic_testcase
352
353     logger.error('Project %s is not defined in testcases.yaml' % testname)
354     return None
355
356
357 def get_criteria_by_test(testname):
358     dict = get_dict_by_test(testname)
359     if dict:
360         return dict['criteria']
361     return None
362
363
364 # ----------------------------------------------------------
365 #
366 #               YAML UTILS
367 #
368 # -----------------------------------------------------------
369 def get_parameter_from_yaml(parameter, file):
370     """
371     Returns the value of a given parameter in file.yaml
372     parameter must be given in string format with dots
373     Example: general.openstack.image_name
374     """
375     with open(file) as f:
376         file_yaml = yaml.safe_load(f)
377     f.close()
378     value = file_yaml
379     for element in parameter.split("."):
380         value = value.get(element)
381         if value is None:
382             raise ValueError("The parameter %s is not defined in"
383                              " config_functest.yaml" % parameter)
384     return value
385
386
387 def get_functest_config(parameter):
388     yaml_ = os.environ["CONFIG_FUNCTEST_YAML"]
389     return get_parameter_from_yaml(parameter, yaml_)
390
391
392 def check_success_rate(case_name, success_rate):
393     success_rate = float(success_rate)
394     criteria = get_criteria_by_test(case_name)
395
396     def get_criteria_value(op):
397         return float(criteria.split(op)[1].rstrip('%'))
398
399     status = 'FAIL'
400     ops = ['==', '>=']
401     for op in ops:
402         if op in criteria:
403             c_value = get_criteria_value(op)
404             if eval("%s %s %s" % (success_rate, op, c_value)):
405                 status = 'PASS'
406             break
407
408     return status
409
410
411 def merge_dicts(dict1, dict2):
412     for k in set(dict1.keys()).union(dict2.keys()):
413         if k in dict1 and k in dict2:
414             if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
415                 yield (k, dict(merge_dicts(dict1[k], dict2[k])))
416             else:
417                 yield (k, dict2[k])
418         elif k in dict1:
419             yield (k, dict1[k])
420         else:
421             yield (k, dict2[k])
422
423
424 def check_test_result(test_name, ret, start_time, stop_time):
425     def get_criteria_value():
426         return get_criteria_by_test(test_name).split('==')[1].strip()
427
428     status = 'FAIL'
429     if str(ret) == get_criteria_value():
430         status = 'PASS'
431
432     details = {
433         'timestart': start_time,
434         'duration': round(stop_time - start_time, 1),
435         'status': status,
436     }
437
438     return status, details
439
440
441 def get_testcases_file():
442     return FUNCTEST_REPO + "/ci/testcases.yaml"
443
444
445 def get_functest_yaml():
446     with open(os.environ["CONFIG_FUNCTEST_YAML"]) as f:
447         functest_yaml = yaml.safe_load(f)
448     f.close()
449     return functest_yaml