[ODL-SFC] Add push resulst to DB
[functest.git] / testcases / OpenStack / tempest / run_tempest.py
1 #!/usr/bin/env python
2 #
3 # Description:
4 #    Runs tempest and pushes the results to the DB
5 #
6 # Authors:
7 #    morgan.richomme@orange.com
8 #    jose.lausuch@ericsson.com
9 #    viktor.tikkanen@nokia.com
10 #
11 # All rights reserved. This program and the accompanying materials
12 # are made available under the terms of the Apache License, Version 2.0
13 # which accompanies this distribution, and is available at
14 # http://www.apache.org/licenses/LICENSE-2.0
15 #
16 import ConfigParser
17 import os
18 import re
19 import shutil
20 import subprocess
21 import sys
22 import time
23
24 import argparse
25 import yaml
26
27 import functest.utils.functest_logger as ft_logger
28 import functest.utils.functest_utils as ft_utils
29 import functest.utils.openstack_utils as os_utils
30
31 modes = ['full', 'smoke', 'baremetal', 'compute', 'data_processing',
32          'identity', 'image', 'network', 'object_storage', 'orchestration',
33          'telemetry', 'volume', 'custom', 'defcore', 'feature_multisite']
34
35 """ tests configuration """
36 parser = argparse.ArgumentParser()
37 parser.add_argument("-d", "--debug",
38                     help="Debug mode",
39                     action="store_true")
40 parser.add_argument("-s", "--serial",
41                     help="Run tests in one thread",
42                     action="store_true")
43 parser.add_argument("-m", "--mode",
44                     help="Tempest test mode [smoke, all]",
45                     default="smoke")
46 parser.add_argument("-r", "--report",
47                     help="Create json result file",
48                     action="store_true")
49 parser.add_argument("-n", "--noclean",
50                     help="Don't clean the created resources for this test.",
51                     action="store_true")
52 parser.add_argument("-c", "--conf",
53                     help="User-specified Tempest config file location",
54                     default="")
55
56 args = parser.parse_args()
57
58 """ logging configuration """
59 logger = ft_logger.Logger("run_tempest").getLogger()
60
61 TEST_DB = ft_utils.get_functest_config('results.test_db_url')
62
63 MODE = "smoke"
64 GLANCE_IMAGE_NAME = \
65     ft_utils.get_functest_config('general.openstack.image_name')
66 GLANCE_IMAGE_FILENAME = \
67     ft_utils.get_functest_config('general.openstack.image_file_name')
68 GLANCE_IMAGE_FORMAT = \
69     ft_utils.get_functest_config('general.openstack.image_disk_format')
70 GLANCE_IMAGE_PATH = \
71     ft_utils.get_functest_config('general.directories.dir_functest_data') + \
72     "/" + GLANCE_IMAGE_FILENAME
73
74 PRIVATE_NET_NAME = \
75     ft_utils.get_functest_config('tempest.private_net_name')
76 PRIVATE_SUBNET_NAME = \
77     ft_utils.get_functest_config('tempest.private_subnet_name')
78 PRIVATE_SUBNET_CIDR = \
79     ft_utils.get_functest_config('tempest.private_subnet_cidr')
80 ROUTER_NAME = \
81     ft_utils.get_functest_config('tempest.router_name')
82 TENANT_NAME = \
83     ft_utils.get_functest_config('tempest.identity.tenant_name')
84 TENANT_DESCRIPTION = \
85     ft_utils.get_functest_config('tempest.identity.tenant_description')
86 USER_NAME = \
87     ft_utils.get_functest_config('tempest.identity.user_name')
88 USER_PASSWORD = \
89     ft_utils.get_functest_config('tempest.identity.user_password')
90 SSH_TIMEOUT = \
91     ft_utils.get_functest_config('tempest.validation.ssh_timeout')
92 DEPLOYMENT_MAME = \
93     ft_utils.get_functest_config('rally.deployment_name')
94 RALLY_INSTALLATION_DIR = \
95     ft_utils.get_functest_config('general.directories.dir_rally_inst')
96
97 RESULTS_DIR = \
98     ft_utils.get_functest_config('general.directories.dir_results')
99 TEMPEST_RESULTS_DIR = RESULTS_DIR + '/tempest'
100
101 REPO_PATH = ft_utils.FUNCTEST_REPO + '/'
102 TEST_LIST_DIR = \
103     ft_utils.get_functest_config('general.directories.dir_tempest_cases')
104 TEMPEST_CUSTOM = REPO_PATH + TEST_LIST_DIR + 'test_list.txt'
105 TEMPEST_BLACKLIST = REPO_PATH + TEST_LIST_DIR + 'blacklist.txt'
106 TEMPEST_DEFCORE = REPO_PATH + TEST_LIST_DIR + 'defcore_req.txt'
107 TEMPEST_RAW_LIST = TEMPEST_RESULTS_DIR + '/test_raw_list.txt'
108 TEMPEST_LIST = TEMPEST_RESULTS_DIR + '/test_list.txt'
109
110
111 def get_info(file_result):
112     test_run = ""
113     duration = ""
114     test_failed = ""
115
116     p = subprocess.Popen('cat tempest.log',
117                          shell=True, stdout=subprocess.PIPE,
118                          stderr=subprocess.STDOUT)
119     for line in p.stdout.readlines():
120         # print line,
121         if (len(test_run) < 1):
122             test_run = re.findall("[0-9]*\.[0-9]*s", line)
123         if (len(duration) < 1):
124             duration = re.findall("[0-9]*\ tests", line)
125         regexp = r"(failures=[0-9]+)"
126         if (len(test_failed) < 1):
127             test_failed = re.findall(regexp, line)
128
129     logger.debug("test_run:" + test_run)
130     logger.debug("duration:" + duration)
131
132
133 def create_tempest_resources():
134     keystone_client = os_utils.get_keystone_client()
135
136     logger.debug("Creating tenant and user for Tempest suite")
137     tenant_id = os_utils.create_tenant(keystone_client,
138                                        TENANT_NAME,
139                                        TENANT_DESCRIPTION)
140     if not tenant_id:
141         logger.error("Error : Failed to create %s tenant" % TENANT_NAME)
142
143     user_id = os_utils.create_user(keystone_client, USER_NAME, USER_PASSWORD,
144                                    None, tenant_id)
145     if not user_id:
146         logger.error("Error : Failed to create %s user" % USER_NAME)
147
148     logger.debug("Creating private network for Tempest suite")
149     network_dic = os_utils.create_shared_network_full(PRIVATE_NET_NAME,
150                                                       PRIVATE_SUBNET_NAME,
151                                                       ROUTER_NAME,
152                                                       PRIVATE_SUBNET_CIDR)
153     if not network_dic:
154         exit(1)
155
156     logger.debug("Creating image for Tempest suite")
157     _, image_id = os_utils.get_or_create_image(GLANCE_IMAGE_NAME,
158                                                GLANCE_IMAGE_PATH,
159                                                GLANCE_IMAGE_FORMAT)
160     if not image_id:
161         exit(-1)
162
163
164 def configure_tempest(deployment_dir):
165     """
166     Add/update needed parameters into tempest.conf file generated by Rally
167     """
168
169     tempest_conf_file = deployment_dir + "/tempest.conf"
170     if os.path.isfile(tempest_conf_file):
171         logger.debug("Deleting old tempest.conf file...")
172         os.remove(tempest_conf_file)
173
174     logger.debug("Generating new tempest.conf file...")
175     cmd = "rally verify genconfig"
176     ft_utils.execute_command(cmd)
177
178     logger.debug("Finding tempest.conf file...")
179     if not os.path.isfile(tempest_conf_file):
180         logger.error("Tempest configuration file %s NOT found."
181                      % tempest_conf_file)
182         exit(-1)
183
184     logger.debug("Updating selected tempest.conf parameters...")
185     config = ConfigParser.RawConfigParser()
186     config.read(tempest_conf_file)
187     config.set('compute', 'fixed_network_name', PRIVATE_NET_NAME)
188     config.set('identity', 'tenant_name', TENANT_NAME)
189     config.set('identity', 'username', USER_NAME)
190     config.set('identity', 'password', USER_PASSWORD)
191     config.set('validation', 'ssh_timeout', SSH_TIMEOUT)
192
193     if os.getenv('OS_ENDPOINT_TYPE') is not None:
194         services_list = ['compute', 'volume', 'image', 'network',
195                          'data-processing', 'object-storage', 'orchestration']
196         sections = config.sections()
197         for service in services_list:
198             if service not in sections:
199                 config.add_section(service)
200             config.set(service, 'endpoint_type',
201                        os.environ.get("OS_ENDPOINT_TYPE"))
202
203     with open(tempest_conf_file, 'wb') as config_file:
204         config.write(config_file)
205
206     # Copy tempest.conf to /home/opnfv/functest/results/tempest/
207     shutil.copyfile(tempest_conf_file, TEMPEST_RESULTS_DIR + '/tempest.conf')
208     return True
209
210
211 def read_file(filename):
212     with open(filename) as src:
213         return [line.strip() for line in src.readlines()]
214
215
216 def generate_test_list(deployment_dir, mode):
217     logger.debug("Generating test case list...")
218     if mode == 'defcore':
219         shutil.copyfile(TEMPEST_DEFCORE, TEMPEST_RAW_LIST)
220     elif mode == 'custom':
221         if os.path.isfile(TEMPEST_CUSTOM):
222             shutil.copyfile(TEMPEST_CUSTOM, TEMPEST_RAW_LIST)
223         else:
224             logger.error("Tempest test list file %s NOT found."
225                          % TEMPEST_CUSTOM)
226             exit(-1)
227     else:
228         if mode == 'smoke':
229             testr_mode = "smoke"
230         elif mode == 'feature_multisite':
231             testr_mode = " | grep -i kingbird "
232         elif mode == 'full':
233             testr_mode = ""
234         else:
235             testr_mode = 'tempest.api.' + mode
236         cmd = ("cd " + deployment_dir + ";" + "testr list-tests " +
237                testr_mode + ">" + TEMPEST_RAW_LIST + ";cd")
238         ft_utils.execute_command(cmd)
239
240
241 def apply_tempest_blacklist():
242     logger.debug("Applying tempest blacklist...")
243     cases_file = read_file(TEMPEST_RAW_LIST)
244     result_file = open(TEMPEST_LIST, 'w')
245     black_tests = []
246     try:
247         installer_type = os.getenv('INSTALLER_TYPE')
248         deploy_scenario = os.getenv('DEPLOY_SCENARIO')
249         if (bool(installer_type) * bool(deploy_scenario)):
250             # if INSTALLER_TYPE and DEPLOY_SCENARIO are set we read the file
251             black_list_file = open(TEMPEST_BLACKLIST)
252             black_list_yaml = yaml.safe_load(black_list_file)
253             black_list_file.close()
254             for item in black_list_yaml:
255                 scenarios = item['scenarios']
256                 installers = item['installers']
257                 if (deploy_scenario in scenarios and
258                         installer_type in installers):
259                     tests = item['tests']
260                     for test in tests:
261                         black_tests.append(test)
262                     break
263     except:
264         black_tests = []
265         logger.debug("Tempest blacklist file does not exist.")
266
267     for cases_line in cases_file:
268         for black_tests_line in black_tests:
269             if black_tests_line in cases_line:
270                 break
271         else:
272             result_file.write(str(cases_line) + '\n')
273     result_file.close()
274
275
276 def run_tempest(OPTION):
277     #
278     # the "main" function of the script which launches Rally to run Tempest
279     # :param option: tempest option (smoke, ..)
280     # :return: void
281     #
282     logger.info("Starting Tempest test suite: '%s'." % OPTION)
283     start_time = time.time()
284     stop_time = start_time
285     cmd_line = "rally verify start " + OPTION + " --system-wide"
286
287     header = ("Tempest environment:\n"
288               "  Installer: %s\n  Scenario: %s\n  Node: %s\n  Date: %s\n" %
289               (os.getenv('INSTALLER_TYPE', 'Unknown'),
290                os.getenv('DEPLOY_SCENARIO', 'Unknown'),
291                os.getenv('NODE_NAME', 'Unknown'),
292                time.strftime("%a %b %d %H:%M:%S %Z %Y")))
293
294     f_stdout = open(TEMPEST_RESULTS_DIR + "/tempest.log", 'w+')
295     f_stderr = open(TEMPEST_RESULTS_DIR + "/tempest-error.log", 'w+')
296     f_env = open(TEMPEST_RESULTS_DIR + "/environment.log", 'w+')
297     f_env.write(header)
298
299     # subprocess.call(cmd_line, shell=True, stdout=f_stdout, stderr=f_stderr)
300     p = subprocess.Popen(
301         cmd_line, shell=True,
302         stdout=subprocess.PIPE,
303         stderr=f_stderr,
304         bufsize=1)
305
306     with p.stdout:
307         for line in iter(p.stdout.readline, b''):
308             if re.search("\} tempest\.", line):
309                 logger.info(line.replace('\n', ''))
310             f_stdout.write(line)
311     p.wait()
312
313     f_stdout.close()
314     f_stderr.close()
315     f_env.close()
316
317     cmd_line = "rally verify show"
318     output = ""
319     p = subprocess.Popen(
320         cmd_line, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
321     for line in p.stdout:
322         if re.search("Tests\:", line):
323             break
324         output += line
325     logger.info(output)
326
327     cmd_line = "rally verify list"
328     cmd = os.popen(cmd_line)
329     output = (((cmd.read()).splitlines()[-2]).replace(" ", "")).split("|")
330     # Format:
331     # | UUID | Deployment UUID | smoke | tests | failures | Created at |
332     # Duration | Status  |
333     num_tests = output[4]
334     num_failures = output[5]
335     time_start = output[6]
336     duration = output[7]
337     # Compute duration (lets assume it does not take more than 60 min)
338     dur_min = int(duration.split(':')[1])
339     dur_sec_float = float(duration.split(':')[2])
340     dur_sec_int = int(round(dur_sec_float, 0))
341     dur_sec_int = dur_sec_int + 60 * dur_min
342     stop_time = time.time()
343
344     try:
345         diff = (int(num_tests) - int(num_failures))
346         success_rate = 100 * diff / int(num_tests)
347     except:
348         success_rate = 0
349
350     if 'smoke' in args.mode:
351         case_name = 'tempest_smoke_serial'
352     elif 'feature' in args.mode:
353         case_name = args.mode.replace("feature_", "")
354     else:
355         case_name = 'tempest_full_parallel'
356
357     status = ft_utils.check_success_rate(case_name, success_rate)
358     logger.info("Tempest %s success_rate is %s%%, is marked as %s"
359                 % (case_name, success_rate, status))
360
361     # Push results in payload of testcase
362     if args.report:
363         # add the test in error in the details sections
364         # should be possible to do it during the test
365         logger.debug("Pushing tempest results into DB...")
366         with open(TEMPEST_RESULTS_DIR + "/tempest.log", 'r') as myfile:
367             output = myfile.read()
368         error_logs = ""
369
370         for match in re.findall('(.*?)[. ]*FAILED', output):
371             error_logs += match
372
373         # Generate json results for DB
374         json_results = {"timestart": time_start, "duration": dur_sec_int,
375                         "tests": int(num_tests), "failures": int(num_failures),
376                         "errors": error_logs}
377         logger.info("Results: " + str(json_results))
378         # split Tempest smoke and full
379
380         try:
381             ft_utils.push_results_to_db("functest",
382                                         case_name,
383                                         start_time,
384                                         stop_time,
385                                         status,
386                                         json_results)
387         except:
388             logger.error("Error pushing results into Database '%s'"
389                          % sys.exc_info()[0])
390
391     if status == "PASS":
392         return 0
393     else:
394         return -1
395
396
397 def main():
398     global MODE
399
400     if not (args.mode in modes):
401         logger.error("Tempest mode not valid. "
402                      "Possible values are:\n" + str(modes))
403         exit(-1)
404
405     if not os.path.exists(TEMPEST_RESULTS_DIR):
406         os.makedirs(TEMPEST_RESULTS_DIR)
407
408     deployment_dir = ft_utils.get_deployment_dir()
409     create_tempest_resources()
410
411     if "" == args.conf:
412         MODE = ""
413         configure_tempest(deployment_dir)
414     else:
415         MODE = " --tempest-config " + args.conf
416
417     generate_test_list(deployment_dir, args.mode)
418     apply_tempest_blacklist()
419
420     MODE += " --tests-file " + TEMPEST_LIST
421     if args.serial:
422         MODE += " --concur 1"
423
424     ret_val = run_tempest(MODE)
425     if ret_val != 0:
426         sys.exit(-1)
427
428     sys.exit(0)
429
430
431 if __name__ == '__main__':
432     main()