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