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