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