Merge "Update rally version"
[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 IMAGE_ID = None
74 IMAGE_ID_ALT = None
75
76 FLAVOR_NAME = \
77     ft_utils.get_functest_config('general.openstack.flavor_name')
78 FLAVOR_RAM = ft_utils.get_functest_config('general.openstack.flavor_ram')
79 FLAVOR_DISK = ft_utils.get_functest_config('general.openstack.flavor_disk')
80 FLAVOR_VCPUS = ft_utils.get_functest_config('general.openstack.flavor_vcpus')
81 FLAVOR_ID = None
82 FLAVOR_ID_ALT = None
83
84 PRIVATE_NET_NAME = \
85     ft_utils.get_functest_config('tempest.private_net_name')
86 PRIVATE_SUBNET_NAME = \
87     ft_utils.get_functest_config('tempest.private_subnet_name')
88 PRIVATE_SUBNET_CIDR = \
89     ft_utils.get_functest_config('tempest.private_subnet_cidr')
90 ROUTER_NAME = \
91     ft_utils.get_functest_config('tempest.router_name')
92 TENANT_NAME = \
93     ft_utils.get_functest_config('tempest.identity.tenant_name')
94 TENANT_DESCRIPTION = \
95     ft_utils.get_functest_config('tempest.identity.tenant_description')
96 USER_NAME = \
97     ft_utils.get_functest_config('tempest.identity.user_name')
98 USER_PASSWORD = \
99     ft_utils.get_functest_config('tempest.identity.user_password')
100 SSH_TIMEOUT = \
101     ft_utils.get_functest_config('tempest.validation.ssh_timeout')
102 USE_CUSTOM_IMAGES = \
103     ft_utils.get_functest_config('tempest.use_custom_images')
104 USE_CUSTOM_FLAVORS = \
105     ft_utils.get_functest_config('tempest.use_custom_flavors')
106
107 DEPLOYMENT_MAME = \
108     ft_utils.get_functest_config('rally.deployment_name')
109 RALLY_INSTALLATION_DIR = \
110     ft_utils.get_functest_config('general.directories.dir_rally_inst')
111
112 RESULTS_DIR = \
113     ft_utils.get_functest_config('general.directories.dir_results')
114 TEMPEST_RESULTS_DIR = RESULTS_DIR + '/tempest'
115
116 REPO_PATH = ft_utils.FUNCTEST_REPO + '/'
117 TEST_LIST_DIR = \
118     ft_utils.get_functest_config('general.directories.dir_tempest_cases')
119 TEMPEST_CUSTOM = REPO_PATH + TEST_LIST_DIR + 'test_list.txt'
120 TEMPEST_BLACKLIST = REPO_PATH + TEST_LIST_DIR + 'blacklist.txt'
121 TEMPEST_DEFCORE = REPO_PATH + TEST_LIST_DIR + 'defcore_req.txt'
122 TEMPEST_RAW_LIST = TEMPEST_RESULTS_DIR + '/test_raw_list.txt'
123 TEMPEST_LIST = TEMPEST_RESULTS_DIR + '/test_list.txt'
124
125
126 def get_info(file_result):
127     test_run = ""
128     duration = ""
129     test_failed = ""
130
131     p = subprocess.Popen('cat tempest.log',
132                          shell=True, stdout=subprocess.PIPE,
133                          stderr=subprocess.STDOUT)
134     for line in p.stdout.readlines():
135         # print line,
136         if (len(test_run) < 1):
137             test_run = re.findall("[0-9]*\.[0-9]*s", line)
138         if (len(duration) < 1):
139             duration = re.findall("[0-9]*\ tests", line)
140         regexp = r"(failures=[0-9]+)"
141         if (len(test_failed) < 1):
142             test_failed = re.findall(regexp, line)
143
144     logger.debug("test_run:" + test_run)
145     logger.debug("duration:" + duration)
146
147
148 def create_tempest_resources():
149     keystone_client = os_utils.get_keystone_client()
150
151     logger.debug("Creating tenant and user for Tempest suite")
152     tenant_id = os_utils.create_tenant(keystone_client,
153                                        TENANT_NAME,
154                                        TENANT_DESCRIPTION)
155     if not tenant_id:
156         logger.error("Error : Failed to create %s tenant" % TENANT_NAME)
157
158     user_id = os_utils.create_user(keystone_client, USER_NAME, USER_PASSWORD,
159                                    None, tenant_id)
160     if not user_id:
161         logger.error("Error : Failed to create %s user" % USER_NAME)
162
163     logger.debug("Creating private network for Tempest suite")
164     network_dic = os_utils.create_shared_network_full(PRIVATE_NET_NAME,
165                                                       PRIVATE_SUBNET_NAME,
166                                                       ROUTER_NAME,
167                                                       PRIVATE_SUBNET_CIDR)
168     if not network_dic:
169         exit(1)
170
171     if USE_CUSTOM_IMAGES:
172         # adding alternative image should be trivial should we need it
173         logger.debug("Creating image for Tempest suite")
174         global IMAGE_ID
175         _, IMAGE_ID = os_utils.get_or_create_image(GLANCE_IMAGE_NAME,
176                                                    GLANCE_IMAGE_PATH,
177                                                    GLANCE_IMAGE_FORMAT)
178         if not IMAGE_ID:
179             exit(-1)
180
181     if USE_CUSTOM_FLAVORS:
182         # adding alternative flavor should be trivial should we need it
183         logger.debug("Creating flavor for Tempest suite")
184         global FLAVOR_ID
185         _, FLAVOR_ID = os_utils.get_or_create_flavor(FLAVOR_NAME,
186                                                      FLAVOR_RAM,
187                                                      FLAVOR_DISK,
188                                                      FLAVOR_VCPUS)
189         if not FLAVOR_ID:
190             exit(-1)
191
192
193 def configure_tempest(deployment_dir):
194     """
195     Add/update needed parameters into tempest.conf file generated by Rally
196     """
197
198     tempest_conf_file = deployment_dir + "/tempest.conf"
199     if os.path.isfile(tempest_conf_file):
200         logger.debug("Deleting old tempest.conf file...")
201         os.remove(tempest_conf_file)
202
203     logger.debug("Generating new tempest.conf file...")
204     cmd = "rally verify genconfig"
205     ft_utils.execute_command(cmd)
206
207     logger.debug("Finding tempest.conf file...")
208     if not os.path.isfile(tempest_conf_file):
209         logger.error("Tempest configuration file %s NOT found."
210                      % tempest_conf_file)
211         exit(-1)
212
213     logger.debug("Updating selected tempest.conf parameters...")
214     config = ConfigParser.RawConfigParser()
215     config.read(tempest_conf_file)
216     config.set('compute', 'fixed_network_name', PRIVATE_NET_NAME)
217     if USE_CUSTOM_IMAGES:
218         if IMAGE_ID is not None:
219             config.set('compute', 'image_ref', IMAGE_ID)
220         if IMAGE_ID_ALT is not None:
221             config.set('compute', 'image_ref_alt', IMAGE_ID_ALT)
222     if USE_CUSTOM_FLAVORS:
223         if FLAVOR_ID is not None:
224             config.set('compute', 'flavor_ref', FLAVOR_ID)
225         if FLAVOR_ID_ALT is not None:
226             config.set('compute', 'flavor_ref_alt', FLAVOR_ID_ALT)
227     config.set('identity', 'tenant_name', TENANT_NAME)
228     config.set('identity', 'username', USER_NAME)
229     config.set('identity', 'password', USER_PASSWORD)
230     config.set('validation', 'ssh_timeout', SSH_TIMEOUT)
231
232     if os.getenv('OS_ENDPOINT_TYPE') is not None:
233         services_list = ['compute', 'volume', 'image', 'network',
234                          'data-processing', 'object-storage', 'orchestration']
235         sections = config.sections()
236         for service in services_list:
237             if service not in sections:
238                 config.add_section(service)
239             config.set(service, 'endpoint_type',
240                        os.environ.get("OS_ENDPOINT_TYPE"))
241
242     with open(tempest_conf_file, 'wb') as config_file:
243         config.write(config_file)
244
245     # Copy tempest.conf to /home/opnfv/functest/results/tempest/
246     shutil.copyfile(tempest_conf_file, TEMPEST_RESULTS_DIR + '/tempest.conf')
247     return True
248
249
250 def read_file(filename):
251     with open(filename) as src:
252         return [line.strip() for line in src.readlines()]
253
254
255 def generate_test_list(deployment_dir, mode):
256     logger.debug("Generating test case list...")
257     if mode == 'defcore':
258         shutil.copyfile(TEMPEST_DEFCORE, TEMPEST_RAW_LIST)
259     elif mode == 'custom':
260         if os.path.isfile(TEMPEST_CUSTOM):
261             shutil.copyfile(TEMPEST_CUSTOM, TEMPEST_RAW_LIST)
262         else:
263             logger.error("Tempest test list file %s NOT found."
264                          % TEMPEST_CUSTOM)
265             exit(-1)
266     else:
267         if mode == 'smoke':
268             testr_mode = "smoke"
269         elif mode == 'feature_multisite':
270             testr_mode = " | grep -i kingbird "
271         elif mode == 'full':
272             testr_mode = ""
273         else:
274             testr_mode = 'tempest.api.' + mode
275         cmd = ("cd " + deployment_dir + ";" + "testr list-tests " +
276                testr_mode + ">" + TEMPEST_RAW_LIST + ";cd")
277         ft_utils.execute_command(cmd)
278
279
280 def apply_tempest_blacklist():
281     logger.debug("Applying tempest blacklist...")
282     cases_file = read_file(TEMPEST_RAW_LIST)
283     result_file = open(TEMPEST_LIST, 'w')
284     black_tests = []
285     try:
286         installer_type = os.getenv('INSTALLER_TYPE')
287         deploy_scenario = os.getenv('DEPLOY_SCENARIO')
288         if (bool(installer_type) * bool(deploy_scenario)):
289             # if INSTALLER_TYPE and DEPLOY_SCENARIO are set we read the file
290             black_list_file = open(TEMPEST_BLACKLIST)
291             black_list_yaml = yaml.safe_load(black_list_file)
292             black_list_file.close()
293             for item in black_list_yaml:
294                 scenarios = item['scenarios']
295                 installers = item['installers']
296                 if (deploy_scenario in scenarios and
297                         installer_type in installers):
298                     tests = item['tests']
299                     for test in tests:
300                         black_tests.append(test)
301                     break
302     except:
303         black_tests = []
304         logger.debug("Tempest blacklist file does not exist.")
305
306     for cases_line in cases_file:
307         for black_tests_line in black_tests:
308             if black_tests_line in cases_line:
309                 break
310         else:
311             result_file.write(str(cases_line) + '\n')
312     result_file.close()
313
314
315 def run_tempest(OPTION):
316     #
317     # the "main" function of the script which launches Rally to run Tempest
318     # :param option: tempest option (smoke, ..)
319     # :return: void
320     #
321     logger.info("Starting Tempest test suite: '%s'." % OPTION)
322     start_time = time.time()
323     stop_time = start_time
324     cmd_line = "rally verify start " + OPTION + " --system-wide"
325
326     header = ("Tempest environment:\n"
327               "  Installer: %s\n  Scenario: %s\n  Node: %s\n  Date: %s\n" %
328               (os.getenv('INSTALLER_TYPE', 'Unknown'),
329                os.getenv('DEPLOY_SCENARIO', 'Unknown'),
330                os.getenv('NODE_NAME', 'Unknown'),
331                time.strftime("%a %b %d %H:%M:%S %Z %Y")))
332
333     f_stdout = open(TEMPEST_RESULTS_DIR + "/tempest.log", 'w+')
334     f_stderr = open(TEMPEST_RESULTS_DIR + "/tempest-error.log", 'w+')
335     f_env = open(TEMPEST_RESULTS_DIR + "/environment.log", 'w+')
336     f_env.write(header)
337
338     # subprocess.call(cmd_line, shell=True, stdout=f_stdout, stderr=f_stderr)
339     p = subprocess.Popen(
340         cmd_line, shell=True,
341         stdout=subprocess.PIPE,
342         stderr=f_stderr,
343         bufsize=1)
344
345     with p.stdout:
346         for line in iter(p.stdout.readline, b''):
347             if re.search("\} tempest\.", line):
348                 logger.info(line.replace('\n', ''))
349             f_stdout.write(line)
350     p.wait()
351
352     f_stdout.close()
353     f_stderr.close()
354     f_env.close()
355
356     cmd_line = "rally verify show"
357     output = ""
358     p = subprocess.Popen(
359         cmd_line, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
360     for line in p.stdout:
361         if re.search("Tests\:", line):
362             break
363         output += line
364     logger.info(output)
365
366     cmd_line = "rally verify list"
367     cmd = os.popen(cmd_line)
368     output = (((cmd.read()).splitlines()[-2]).replace(" ", "")).split("|")
369     # Format:
370     # | UUID | Deployment UUID | smoke | tests | failures | Created at |
371     # Duration | Status  |
372     num_tests = output[4]
373     num_failures = output[5]
374     time_start = output[6]
375     duration = output[7]
376     # Compute duration (lets assume it does not take more than 60 min)
377     dur_min = int(duration.split(':')[1])
378     dur_sec_float = float(duration.split(':')[2])
379     dur_sec_int = int(round(dur_sec_float, 0))
380     dur_sec_int = dur_sec_int + 60 * dur_min
381     stop_time = time.time()
382
383     try:
384         diff = (int(num_tests) - int(num_failures))
385         success_rate = 100 * diff / int(num_tests)
386     except:
387         success_rate = 0
388
389     if 'smoke' in args.mode:
390         case_name = 'tempest_smoke_serial'
391     elif 'feature' in args.mode:
392         case_name = args.mode.replace("feature_", "")
393     else:
394         case_name = 'tempest_full_parallel'
395
396     status = ft_utils.check_success_rate(case_name, success_rate)
397     logger.info("Tempest %s success_rate is %s%%, is marked as %s"
398                 % (case_name, success_rate, status))
399
400     # Push results in payload of testcase
401     if args.report:
402         # add the test in error in the details sections
403         # should be possible to do it during the test
404         logger.debug("Pushing tempest results into DB...")
405         with open(TEMPEST_RESULTS_DIR + "/tempest.log", 'r') as myfile:
406             output = myfile.read()
407         error_logs = ""
408
409         for match in re.findall('(.*?)[. ]*FAILED', output):
410             error_logs += match
411
412         # Generate json results for DB
413         json_results = {"timestart": time_start, "duration": dur_sec_int,
414                         "tests": int(num_tests), "failures": int(num_failures),
415                         "errors": error_logs}
416         logger.info("Results: " + str(json_results))
417         # split Tempest smoke and full
418
419         try:
420             ft_utils.push_results_to_db("functest",
421                                         case_name,
422                                         start_time,
423                                         stop_time,
424                                         status,
425                                         json_results)
426         except:
427             logger.error("Error pushing results into Database '%s'"
428                          % sys.exc_info()[0])
429
430     if status == "PASS":
431         return 0
432     else:
433         return -1
434
435
436 def main():
437     global MODE
438
439     if not (args.mode in modes):
440         logger.error("Tempest mode not valid. "
441                      "Possible values are:\n" + str(modes))
442         exit(-1)
443
444     if not os.path.exists(TEMPEST_RESULTS_DIR):
445         os.makedirs(TEMPEST_RESULTS_DIR)
446
447     deployment_dir = ft_utils.get_deployment_dir()
448     create_tempest_resources()
449
450     if "" == args.conf:
451         MODE = ""
452         configure_tempest(deployment_dir)
453     else:
454         MODE = " --tempest-config " + args.conf
455
456     generate_test_list(deployment_dir, args.mode)
457     apply_tempest_blacklist()
458
459     MODE += " --tests-file " + TEMPEST_LIST
460     if args.serial:
461         MODE += " --concur 1"
462
463     ret_val = run_tempest(MODE)
464     if ret_val != 0:
465         sys.exit(-1)
466
467     sys.exit(0)
468
469
470 if __name__ == '__main__':
471     main()