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