Added handling of blacklist for tempest
[functest.git] / testcases / VIM / OpenStack / CI / libraries / 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 get_deployment_dir():
176     logger.debug("Resolving deployment UUID and directory...")
177     cmd = "rally deployment list | awk '/" + DEPLOYMENT_MAME + "/ {print $2}'"
178     p = subprocess.Popen(cmd, shell=True,
179                          stdout=subprocess.PIPE,
180                          stderr=subprocess.STDOUT)
181     deployment_uuid = p.stdout.readline().rstrip()
182     if deployment_uuid == "":
183         logger.error("Rally deployment NOT found.")
184         exit(-1)
185     deployment_dir = (RALLY_INSTALLATION_DIR + "/tempest/for-deployment-" +
186                       deployment_uuid)
187     return deployment_dir
188
189
190 def configure_tempest(deployment_dir):
191     """
192     Add/update needed parameters into tempest.conf file generated by Rally
193     """
194
195     logger.debug("Generating tempest.conf file...")
196     cmd = "rally verify genconfig"
197     ft_utils.execute_command(cmd, logger)
198
199     logger.debug("Finding tempest.conf file...")
200     tempest_conf_file = deployment_dir + "/tempest.conf"
201     if not os.path.isfile(tempest_conf_file):
202         logger.error("Tempest configuration file %s NOT found."
203                      % tempest_conf_file)
204         exit(-1)
205
206     logger.debug("Updating selected tempest.conf parameters...")
207     config = ConfigParser.RawConfigParser()
208     config.read(tempest_conf_file)
209     config.set('compute', 'fixed_network_name', PRIVATE_NET_NAME)
210     config.set('identity', 'tenant_name', TENANT_NAME)
211     config.set('identity', 'username', USER_NAME)
212     config.set('identity', 'password', USER_PASSWORD)
213     with open(tempest_conf_file, 'wb') as config_file:
214         config.write(config_file)
215
216     # Copy tempest.conf to /home/opnfv/functest/results/tempest/
217     shutil.copyfile(tempest_conf_file, TEMPEST_RESULTS_DIR + '/tempest.conf')
218     return True
219
220
221 def read_file(filename):
222     with open(filename) as src:
223         return [line.strip() for line in src.readlines()]
224
225
226 def generate_test_list(deployment_dir, mode):
227     logger.debug("Generating test case list...")
228     if mode == 'defcore':
229         shutil.copyfile(TEMPEST_DEFCORE, TEMPEST_RAW_LIST)
230     elif mode == 'custom':
231         if os.path.isfile(TEMPEST_CUSTOM):
232             shutil.copyfile(TEMPEST_CUSTOM, TEMPEST_RAW_LIST)
233         else:
234             logger.error("Tempest test list file %s NOT found."
235                          % TEMPEST_CUSTOM)
236             exit(-1)
237     else:
238         if mode == 'smoke':
239             testr_mode = "smoke"
240         elif mode == 'full':
241             testr_mode = ""
242         else:
243             testr_mode = 'tempest.api.' + mode
244         cmd = ("cd " + deployment_dir + ";" + "testr list-tests " +
245                testr_mode + ">" + TEMPEST_RAW_LIST + ";cd")
246         ft_utils.execute_command(cmd, logger)
247
248
249 def apply_tempest_blacklist():
250     logger.debug("Applying tempest blacklist...")
251     cases_file = read_file(TEMPEST_RAW_LIST)
252     result_file = open(TEMPEST_LIST, 'w')
253     try:
254         black_file = read_file(TEMPEST_BLACKLIST)
255     except:
256         black_file = ''
257         logger.debug("Tempest blacklist file does not exist.")
258     for line in cases_file:
259         if line not in black_file:
260             result_file.write(str(line) + '\n')
261     result_file.close()
262
263
264 def run_tempest(OPTION):
265     #
266     # the "main" function of the script which launches Rally to run Tempest
267     # :param option: tempest option (smoke, ..)
268     # :return: void
269     #
270     logger.info("Starting Tempest test suite: '%s'." % OPTION)
271     cmd_line = "rally verify start " + OPTION + " --system-wide"
272     CI_DEBUG = os.environ.get("CI_DEBUG")
273     if CI_DEBUG == "true" or CI_DEBUG == "True":
274         ft_utils.execute_command(cmd_line, logger, exit_on_error=True)
275     else:
276         header = ("Tempest environment:\n"
277                   "  Installer: %s\n  Scenario: %s\n  Node: %s\n  Date: %s\n" %
278                   (os.getenv('INSTALLER_TYPE', 'Unknown'),
279                    os.getenv('DEPLOY_SCENARIO', 'Unknown'),
280                    os.getenv('NODE_NAME', 'Unknown'),
281                    time.strftime("%a %b %d %H:%M:%S %Z %Y")))
282
283         f_stdout = open(TEMPEST_RESULTS_DIR + "/tempest.log", 'w+')
284         f_stderr = open(TEMPEST_RESULTS_DIR + "/tempest-error.log", 'w+')
285         f_env = open(TEMPEST_RESULTS_DIR + "/environment.log", 'w+')
286         f_env.write(header)
287
288         subprocess.call(cmd_line, shell=True, stdout=f_stdout, stderr=f_stderr)
289
290         f_stdout.close()
291         f_stderr.close()
292         f_env.close()
293
294         cmd_line = "rally verify show"
295         ft_utils.execute_command(cmd_line, logger,
296                                  exit_on_error=True, info=True)
297
298     cmd_line = "rally verify list"
299     logger.debug('Executing command : {}'.format(cmd_line))
300     cmd = os.popen(cmd_line)
301     output = (((cmd.read()).splitlines()[-2]).replace(" ", "")).split("|")
302     # Format:
303     # | UUID | Deployment UUID | smoke | tests | failures | Created at |
304     # Duration | Status  |
305     num_tests = output[4]
306     num_failures = output[5]
307     time_start = output[6]
308     duration = output[7]
309     # Compute duration (lets assume it does not take more than 60 min)
310     dur_min = int(duration.split(':')[1])
311     dur_sec_float = float(duration.split(':')[2])
312     dur_sec_int = int(round(dur_sec_float, 0))
313     dur_sec_int = dur_sec_int + 60 * dur_min
314
315     # Generate json results for DB
316     json_results = {"timestart": time_start, "duration": dur_sec_int,
317                     "tests": int(num_tests), "failures": int(num_failures)}
318     logger.info("Results: " + str(json_results))
319
320     status = "failed"
321     try:
322         diff = (int(num_tests) - int(num_failures))
323         success_rate = 100 * diff / int(num_tests)
324     except:
325         success_rate = 0
326
327     # For Tempest we assume that teh success rate is above 90%
328     if success_rate >= 90:
329         status = "passed"
330
331     # Push results in payload of testcase
332     if args.report:
333         logger.debug("Push result into DB")
334         push_results_to_db("Tempest", json_results, status)
335
336
337 def main():
338     global MODE
339
340     if not (args.mode in modes):
341         logger.error("Tempest mode not valid. "
342                      "Possible values are:\n" + str(modes))
343         exit(-1)
344
345     if not os.path.exists(TEMPEST_RESULTS_DIR):
346         os.makedirs(TEMPEST_RESULTS_DIR)
347
348     deployment_dir = get_deployment_dir()
349     configure_tempest(deployment_dir)
350     create_tempest_resources()
351     generate_test_list(deployment_dir, args.mode)
352     apply_tempest_blacklist()
353
354     MODE = "--tests-file " + TEMPEST_LIST
355     if args.serial:
356         MODE += " --concur 1"
357
358     run_tempest(MODE)
359
360
361 if __name__ == '__main__':
362     main()