Add utils for getting bgpvpn information
[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 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, exit_on_error=True, 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         if exit_on_error:
321             sys.exit(1)
322
323     return returncode
324
325
326 def get_deployment_dir():
327     """
328     Returns current Rally deployment directory
329     """
330     deployment_name = get_functest_config('rally.deployment_name')
331     rally_dir = get_functest_config('general.directories.dir_rally_inst')
332     cmd = ("rally deployment list | awk '/" + deployment_name +
333            "/ {print $2}'")
334     p = subprocess.Popen(cmd, shell=True,
335                          stdout=subprocess.PIPE,
336                          stderr=subprocess.STDOUT)
337     deployment_uuid = p.stdout.readline().rstrip()
338     if deployment_uuid == "":
339         logger.error("Rally deployment not found.")
340         exit(-1)
341     deployment_dir = (rally_dir + "/tempest/for-deployment-" +
342                       deployment_uuid)
343     return deployment_dir
344
345
346 def get_criteria_by_test(testname):
347     with open(get_testcases_file()) as f:
348         testcases_yaml = yaml.safe_load(f)
349
350     for dic_tier in testcases_yaml.get("tiers"):
351         for dic_testcase in dic_tier['testcases']:
352             if dic_testcase['name'] == testname:
353                 return dic_testcase['criteria']
354
355     logger.error('Project %s is not defined in testcases.yaml' % testname)
356     return None
357
358
359 # ----------------------------------------------------------
360 #
361 #               YAML UTILS
362 #
363 # -----------------------------------------------------------
364 def get_parameter_from_yaml(parameter, file):
365     """
366     Returns the value of a given parameter in file.yaml
367     parameter must be given in string format with dots
368     Example: general.openstack.image_name
369     """
370     with open(file) as f:
371         file_yaml = yaml.safe_load(f)
372     f.close()
373     value = file_yaml
374     for element in parameter.split("."):
375         value = value.get(element)
376         if value is None:
377             raise ValueError("The parameter %s is not defined in"
378                              " config_functest.yaml" % parameter)
379     return value
380
381
382 def get_functest_config(parameter):
383     yaml_ = os.environ["CONFIG_FUNCTEST_YAML"]
384     return get_parameter_from_yaml(parameter, yaml_)
385
386
387 def check_success_rate(case_name, success_rate):
388     success_rate = float(success_rate)
389     criteria = get_criteria_by_test(case_name)
390
391     def get_criteria_value(op):
392         return float(criteria.split(op)[1].rstrip('%'))
393
394     status = 'FAIL'
395     ops = ['==', '>=']
396     for op in ops:
397         if op in criteria:
398             c_value = get_criteria_value(op)
399             if eval("%s %s %s" % (success_rate, op, c_value)):
400                 status = 'PASS'
401             break
402
403     return status
404
405
406 def merge_dicts(dict1, dict2):
407     for k in set(dict1.keys()).union(dict2.keys()):
408         if k in dict1 and k in dict2:
409             if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
410                 yield (k, dict(merge_dicts(dict1[k], dict2[k])))
411             else:
412                 yield (k, dict2[k])
413         elif k in dict1:
414             yield (k, dict1[k])
415         else:
416             yield (k, dict2[k])
417
418
419 def check_test_result(test_name, ret, start_time, stop_time):
420     def get_criteria_value():
421         return get_criteria_by_test(test_name).split('==')[1].strip()
422
423     status = 'FAIL'
424     if str(ret) == get_criteria_value():
425         status = 'PASS'
426
427     details = {
428         'timestart': start_time,
429         'duration': round(stop_time - start_time, 1),
430         'status': status,
431     }
432
433     return status, details
434
435
436 def get_testcases_file():
437     return FUNCTEST_REPO + "/ci/testcases.yaml"
438
439
440 def get_functest_yaml():
441     with open(os.environ["CONFIG_FUNCTEST_YAML"]) as f:
442         functest_yaml = yaml.safe_load(f)
443     f.close()
444     return functest_yaml