d8a8a1acb7a57e254ffb1ce6ebadb0ee07b121e5
[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
258     header = ("Tempest environment:\n"
259               "  Installer: %s\n  Scenario: %s\n  Node: %s\n  Date: %s\n" %
260               (os.getenv('INSTALLER_TYPE', 'Unknown'),
261                os.getenv('DEPLOY_SCENARIO', 'Unknown'),
262                os.getenv('NODE_NAME', 'Unknown'),
263                time.strftime("%a %b %d %H:%M:%S %Z %Y")))
264
265     f_stdout = open(TEMPEST_RESULTS_DIR + "/tempest.log", 'w+')
266     f_stderr = open(TEMPEST_RESULTS_DIR + "/tempest-error.log", 'w+')
267     f_env = open(TEMPEST_RESULTS_DIR + "/environment.log", 'w+')
268     f_env.write(header)
269
270     subprocess.call(cmd_line, shell=True, stdout=f_stdout, stderr=f_stderr)
271
272     f_stdout.close()
273     f_stderr.close()
274     f_env.close()
275
276     cmd_line = "rally verify show"
277     ft_utils.execute_command(cmd_line, logger,
278                              exit_on_error=True, info=True)
279
280     cmd_line = "rally verify list"
281     logger.debug('Executing command : {}'.format(cmd_line))
282     cmd = os.popen(cmd_line)
283     output = (((cmd.read()).splitlines()[-2]).replace(" ", "")).split("|")
284     # Format:
285     # | UUID | Deployment UUID | smoke | tests | failures | Created at |
286     # Duration | Status  |
287     num_tests = output[4]
288     num_failures = output[5]
289     time_start = output[6]
290     duration = output[7]
291     # Compute duration (lets assume it does not take more than 60 min)
292     dur_min = int(duration.split(':')[1])
293     dur_sec_float = float(duration.split(':')[2])
294     dur_sec_int = int(round(dur_sec_float, 0))
295     dur_sec_int = dur_sec_int + 60 * dur_min
296
297     # Push results in payload of testcase
298     if args.report:
299         # Note criteria hardcoded...TODO move to testcase.yaml
300         status = "failed"
301         try:
302             diff = (int(num_tests) - int(num_failures))
303             success_rate = 100 * diff / int(num_tests)
304         except:
305             success_rate = 0
306
307         # For Tempest we assume that the success rate is above 90%
308         if success_rate >= 90:
309             status = "passed"
310
311         # add the test in error in the details sections
312         # should be possible to do it during the test
313         with open(TEMPEST_RESULTS_DIR + "/tempest.log", 'r') as myfile:
314             output = myfile.read()
315         error_logs = ""
316
317         for match in re.findall('(.*?)[. ]*FAILED', output):
318                 error_logs += match
319
320         # Generate json results for DB
321         json_results = {"timestart": time_start, "duration": dur_sec_int,
322                         "tests": int(num_tests), "failures": int(num_failures),
323                         "errors": error_logs}
324         logger.info("Results: " + str(json_results))
325
326         logger.debug("Push result into DB")
327         push_results_to_db("Tempest", json_results, status)
328
329
330 def main():
331     global MODE
332
333     if not (args.mode in modes):
334         logger.error("Tempest mode not valid. "
335                      "Possible values are:\n" + str(modes))
336         exit(-1)
337
338     if not os.path.exists(TEMPEST_RESULTS_DIR):
339         os.makedirs(TEMPEST_RESULTS_DIR)
340
341     deployment_dir = ft_utils.get_deployment_dir(logger)
342     configure_tempest(deployment_dir)
343     create_tempest_resources()
344     generate_test_list(deployment_dir, args.mode)
345     apply_tempest_blacklist()
346
347     MODE = "--tests-file " + TEMPEST_LIST
348     if args.serial:
349         MODE += " --concur 1"
350
351     run_tempest(MODE)
352
353
354 if __name__ == '__main__':
355     main()