Bugfix: can not find tempest.log
[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 argparse
17 import json
18 import os
19 import re
20 import requests
21 import shutil
22 import subprocess
23 import time
24 import yaml
25 import ConfigParser
26
27 import keystoneclient.v2_0.client as ksclient
28 from neutronclient.v2_0 import client as neutronclient
29
30 import functest.utils.functest_logger as ft_logger
31 import functest.utils.functest_utils as ft_utils
32 import functest.utils.openstack_utils as os_utils
33
34 modes = ['full', 'smoke', 'baremetal', 'compute', 'data_processing',
35          'identity', 'image', 'network', 'object_storage', 'orchestration',
36          'telemetry', 'volume', 'custom', 'defcore']
37
38 """ tests configuration """
39 parser = argparse.ArgumentParser()
40 parser.add_argument("-d", "--debug",
41                     help="Debug mode",
42                     action="store_true")
43 parser.add_argument("-s", "--serial",
44                     help="Run tests in one thread",
45                     action="store_true")
46 parser.add_argument("-m", "--mode",
47                     help="Tempest test mode [smoke, all]",
48                     default="smoke")
49 parser.add_argument("-r", "--report",
50                     help="Create json result file",
51                     action="store_true")
52 parser.add_argument("-n", "--noclean",
53                     help="Don't clean the created resources for this test.",
54                     action="store_true")
55
56 args = parser.parse_args()
57
58 """ logging configuration """
59 logger = ft_logger.Logger("run_tempest").getLogger()
60
61 REPO_PATH = os.environ['repos_dir'] + '/functest/'
62
63
64 with open(os.environ["CONFIG_FUNCTEST_YAML"]) as f:
65     functest_yaml = yaml.safe_load(f)
66 f.close()
67 TEST_DB = functest_yaml.get("results").get("test_db_url")
68
69 MODE = "smoke"
70 PRIVATE_NET_NAME = functest_yaml.get("tempest").get("private_net_name")
71 PRIVATE_SUBNET_NAME = functest_yaml.get("tempest").get("private_subnet_name")
72 PRIVATE_SUBNET_CIDR = functest_yaml.get("tempest").get("private_subnet_cidr")
73 ROUTER_NAME = functest_yaml.get("tempest").get("router_name")
74 TENANT_NAME = functest_yaml.get("tempest").get("identity").get("tenant_name")
75 TENANT_DESCRIPTION = functest_yaml.get("tempest").get("identity").get(
76     "tenant_description")
77 USER_NAME = functest_yaml.get("tempest").get("identity").get("user_name")
78 USER_PASSWORD = functest_yaml.get("tempest").get("identity").get(
79     "user_password")
80 DEPLOYMENT_MAME = functest_yaml.get("rally").get("deployment_name")
81 RALLY_INSTALLATION_DIR = functest_yaml.get("general").get("directories").get(
82     "dir_rally_inst")
83 RESULTS_DIR = functest_yaml.get("general").get("directories").get(
84     "dir_results")
85 TEMPEST_RESULTS_DIR = RESULTS_DIR + '/tempest'
86 TEST_LIST_DIR = functest_yaml.get("general").get("directories").get(
87     "dir_tempest_cases")
88 TEMPEST_CUSTOM = REPO_PATH + TEST_LIST_DIR + 'test_list.txt'
89 TEMPEST_BLACKLIST = REPO_PATH + TEST_LIST_DIR + 'blacklist.txt'
90 TEMPEST_DEFCORE = REPO_PATH + TEST_LIST_DIR + 'defcore_req.txt'
91 TEMPEST_RAW_LIST = TEMPEST_RESULTS_DIR + '/test_raw_list.txt'
92 TEMPEST_LIST = TEMPEST_RESULTS_DIR + '/test_list.txt'
93
94
95 def get_info(file_result):
96     test_run = ""
97     duration = ""
98     test_failed = ""
99
100     p = subprocess.Popen('cat tempest.log',
101                          shell=True, stdout=subprocess.PIPE,
102                          stderr=subprocess.STDOUT)
103     for line in p.stdout.readlines():
104         # print line,
105         if (len(test_run) < 1):
106             test_run = re.findall("[0-9]*\.[0-9]*s", line)
107         if (len(duration) < 1):
108             duration = re.findall("[0-9]*\ tests", line)
109         regexp = r"(failures=[0-9]+)"
110         if (len(test_failed) < 1):
111             test_failed = re.findall(regexp, line)
112
113     logger.debug("test_run:" + test_run)
114     logger.debug("duration:" + duration)
115
116
117 def push_results_to_db(case, payload, criteria):
118
119     # TODO move DB creds into config file
120     url = TEST_DB + "/results"
121     installer = ft_utils.get_installer_type(logger)
122     scenario = ft_utils.get_scenario(logger)
123     version = ft_utils.get_version(logger)
124     pod_name = ft_utils.get_pod_name(logger)
125
126     logger.info("Pushing results to DB: '%s'." % url)
127
128     params = {"project_name": "functest", "case_name": case,
129               "pod_name": str(pod_name), 'installer': installer,
130               "version": version, "scenario": scenario, "criteria": criteria,
131               'details': payload}
132     headers = {'Content-Type': 'application/json'}
133
134     r = requests.post(url, data=json.dumps(params), headers=headers)
135     logger.debug(r)
136
137
138 def create_tempest_resources():
139     ks_creds = os_utils.get_credentials("keystone")
140     logger.debug("Creating tenant and user for Tempest suite")
141     keystone = ksclient.Client(**ks_creds)
142     tenant_id = os_utils.create_tenant(keystone,
143                                        TENANT_NAME,
144                                        TENANT_DESCRIPTION)
145     if tenant_id == '':
146         logger.error("Error : Failed to create %s tenant" % TENANT_NAME)
147
148     user_id = os_utils.create_user(keystone, USER_NAME, USER_PASSWORD,
149                                    None, tenant_id)
150     if user_id == '':
151         logger.error("Error : Failed to create %s user" % USER_NAME)
152
153     logger.debug("Creating private network for Tempest suite")
154     creds_neutron = os_utils.get_credentials("neutron")
155     neutron_client = neutronclient.Client(**creds_neutron)
156     network_dic = os_utils.create_network_full(logger,
157                                                neutron_client,
158                                                PRIVATE_NET_NAME,
159                                                PRIVATE_SUBNET_NAME,
160                                                ROUTER_NAME,
161                                                PRIVATE_SUBNET_CIDR)
162     if network_dic:
163         if not os_utils.update_neutron_net(neutron_client,
164                                            network_dic['net_id'],
165                                            shared=True):
166             logger.error("Failed to update private network...")
167             exit(-1)
168         else:
169             logger.debug("Network '%s' is available..." % PRIVATE_NET_NAME)
170     else:
171         logger.error("Private network creation failed")
172         exit(-1)
173
174
175 def configure_tempest(deployment_dir):
176     """
177     Add/update needed parameters into tempest.conf file generated by Rally
178     """
179
180     logger.debug("Generating tempest.conf file...")
181     cmd = "rally verify genconfig"
182     ft_utils.execute_command(cmd, logger)
183
184     logger.debug("Finding tempest.conf file...")
185     tempest_conf_file = deployment_dir + "/tempest.conf"
186     if not os.path.isfile(tempest_conf_file):
187         logger.error("Tempest configuration file %s NOT found."
188                      % tempest_conf_file)
189         exit(-1)
190
191     logger.debug("Updating selected tempest.conf parameters...")
192     config = ConfigParser.RawConfigParser()
193     config.read(tempest_conf_file)
194     config.set('compute', 'fixed_network_name', PRIVATE_NET_NAME)
195     config.set('identity', 'tenant_name', TENANT_NAME)
196     config.set('identity', 'username', USER_NAME)
197     config.set('identity', 'password', USER_PASSWORD)
198     with open(tempest_conf_file, 'wb') as config_file:
199         config.write(config_file)
200
201     # Copy tempest.conf to /home/opnfv/functest/results/tempest/
202     shutil.copyfile(tempest_conf_file, TEMPEST_RESULTS_DIR + '/tempest.conf')
203     return True
204
205
206 def read_file(filename):
207     with open(filename) as src:
208         return [line.strip() for line in src.readlines()]
209
210
211 def generate_test_list(deployment_dir, mode):
212     logger.debug("Generating test case list...")
213     if mode == 'defcore':
214         shutil.copyfile(TEMPEST_DEFCORE, TEMPEST_RAW_LIST)
215     elif mode == 'custom':
216         if os.path.isfile(TEMPEST_CUSTOM):
217             shutil.copyfile(TEMPEST_CUSTOM, TEMPEST_RAW_LIST)
218         else:
219             logger.error("Tempest test list file %s NOT found."
220                          % TEMPEST_CUSTOM)
221             exit(-1)
222     else:
223         if mode == 'smoke':
224             testr_mode = "smoke"
225         elif mode == 'full':
226             testr_mode = ""
227         else:
228             testr_mode = 'tempest.api.' + mode
229         cmd = ("cd " + deployment_dir + ";" + "testr list-tests " +
230                testr_mode + ">" + TEMPEST_RAW_LIST + ";cd")
231         ft_utils.execute_command(cmd, logger)
232
233
234 def apply_tempest_blacklist():
235     logger.debug("Applying tempest blacklist...")
236     cases_file = read_file(TEMPEST_RAW_LIST)
237     result_file = open(TEMPEST_LIST, 'w')
238     try:
239         black_file = read_file(TEMPEST_BLACKLIST)
240     except:
241         black_file = ''
242         logger.debug("Tempest blacklist file does not exist.")
243     for line in cases_file:
244         if line not in black_file:
245             result_file.write(str(line) + '\n')
246     result_file.close()
247
248
249 def run_tempest(OPTION):
250     #
251     # the "main" function of the script which launches Rally to run Tempest
252     # :param option: tempest option (smoke, ..)
253     # :return: void
254     #
255     logger.info("Starting Tempest test suite: '%s'." % OPTION)
256     cmd_line = "rally verify start " + OPTION + " --system-wide"
257     CI_DEBUG = os.environ.get("CI_DEBUG")
258
259     header = ("Tempest environment:\n"
260               "  Installer: %s\n  Scenario: %s\n  Node: %s\n  Date: %s\n" %
261               (os.getenv('INSTALLER_TYPE', 'Unknown'),
262                os.getenv('DEPLOY_SCENARIO', 'Unknown'),
263                os.getenv('NODE_NAME', 'Unknown'),
264                time.strftime("%a %b %d %H:%M:%S %Z %Y")))
265
266     f_stdout = open(TEMPEST_RESULTS_DIR + "/tempest.log", 'w+')
267     f_stderr = open(TEMPEST_RESULTS_DIR + "/tempest-error.log", 'w+')
268     f_env = open(TEMPEST_RESULTS_DIR + "/environment.log", 'w+')
269     f_env.write(header)
270
271     subprocess.call(cmd_line, shell=True, stdout=f_stdout, stderr=f_stderr)
272
273     f_stdout.close()
274     f_stderr.close()
275     f_env.close()
276
277     cmd_line = "rally verify show"
278     ft_utils.execute_command(cmd_line, logger,
279                              exit_on_error=True, info=True)
280
281     cmd_line = "rally verify list"
282     logger.debug('Executing command : {}'.format(cmd_line))
283     cmd = os.popen(cmd_line)
284     output = (((cmd.read()).splitlines()[-2]).replace(" ", "")).split("|")
285     # Format:
286     # | UUID | Deployment UUID | smoke | tests | failures | Created at |
287     # Duration | Status  |
288     num_tests = output[4]
289     num_failures = output[5]
290     time_start = output[6]
291     duration = output[7]
292     # Compute duration (lets assume it does not take more than 60 min)
293     dur_min = int(duration.split(':')[1])
294     dur_sec_float = float(duration.split(':')[2])
295     dur_sec_int = int(round(dur_sec_float, 0))
296     dur_sec_int = dur_sec_int + 60 * dur_min
297
298     # Push results in payload of testcase
299     if args.report:
300         # Note criteria hardcoded...TODO move to testcase.yaml
301         status = "failed"
302         try:
303             diff = (int(num_tests) - int(num_failures))
304             success_rate = 100 * diff / int(num_tests)
305         except:
306             success_rate = 0
307
308         # For Tempest we assume that the success rate is above 90%
309         if success_rate >= 90:
310             status = "passed"
311
312         # add the test in error in the details sections
313         # should be possible to do it during the test
314         with open(TEMPEST_RESULTS_DIR + "/tempest.log", 'r') as myfile:
315             output = myfile.read()
316         error_logs = ""
317
318         for match in re.findall('(.*?)[. ]*FAILED', output):
319                 error_logs += match
320
321         # Generate json results for DB
322         json_results = {"timestart": time_start, "duration": dur_sec_int,
323                         "tests": int(num_tests), "failures": int(num_failures),
324                         "errors": error_logs}
325         logger.info("Results: " + str(json_results))
326
327         logger.debug("Push result into DB")
328         push_results_to_db("Tempest", json_results, status)
329
330
331 def main():
332     global MODE
333
334     if not (args.mode in modes):
335         logger.error("Tempest mode not valid. "
336                      "Possible values are:\n" + str(modes))
337         exit(-1)
338
339     if not os.path.exists(TEMPEST_RESULTS_DIR):
340         os.makedirs(TEMPEST_RESULTS_DIR)
341
342     deployment_dir = ft_utils.get_deployment_dir(logger)
343     configure_tempest(deployment_dir)
344     create_tempest_resources()
345     generate_test_list(deployment_dir, args.mode)
346     apply_tempest_blacklist()
347
348     MODE = "--tests-file " + TEMPEST_LIST
349     if args.serial:
350         MODE += " --concur 1"
351
352     run_tempest(MODE)
353
354
355 if __name__ == '__main__':
356     main()