Execute successive scenarios after task failure
[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 functest_utils
32 import functest.utils.openstack_utils as openstack_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("/home/opnfv/functest/conf/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 TENANT_NAME = functest_yaml.get("tempest").get("identity").get("tenant_name")
71 TENANT_DESCRIPTION = functest_yaml.get("tempest").get("identity").get(
72     "tenant_description")
73 USER_NAME = functest_yaml.get("tempest").get("identity").get("user_name")
74 USER_PASSWORD = functest_yaml.get("tempest").get("identity").get(
75     "user_password")
76 SSH_USER_REGEX = functest_yaml.get("tempest").get("input-scenario").get(
77     "ssh_user_regex")
78 DEPLOYMENT_MAME = functest_yaml.get("rally").get("deployment_name")
79 RALLY_INSTALLATION_DIR = functest_yaml.get("general").get("directories").get(
80     "dir_rally_inst")
81 RESULTS_DIR = functest_yaml.get("general").get("directories").get(
82     "dir_results")
83 TEMPEST_RESULTS_DIR = RESULTS_DIR + '/tempest'
84 TEST_LIST_DIR = functest_yaml.get("general").get("directories").get(
85     "dir_tempest_cases")
86 TEMPEST_LIST_FILE = REPO_PATH + TEST_LIST_DIR + 'test_list.txt'
87 TEMPEST_DEFCORE = REPO_PATH + TEST_LIST_DIR + 'defcore_req.txt'
88
89
90 def get_info(file_result):
91     test_run = ""
92     duration = ""
93     test_failed = ""
94
95     p = subprocess.Popen('cat tempest.log',
96                          shell=True, stdout=subprocess.PIPE,
97                          stderr=subprocess.STDOUT)
98     for line in p.stdout.readlines():
99         # print line,
100         if (len(test_run) < 1):
101             test_run = re.findall("[0-9]*\.[0-9]*s", line)
102         if (len(duration) < 1):
103             duration = re.findall("[0-9]*\ tests", line)
104         regexp = r"(failures=[0-9]+)"
105         if (len(test_failed) < 1):
106             test_failed = re.findall(regexp, line)
107
108     logger.debug("test_run:" + test_run)
109     logger.debug("duration:" + duration)
110
111
112 def push_results_to_db(case, payload, criteria):
113
114     # TODO move DB creds into config file
115     url = TEST_DB + "/results"
116     installer = functest_utils.get_installer_type(logger)
117     scenario = functest_utils.get_scenario(logger)
118     version = functest_utils.get_version(logger)
119     pod_name = functest_utils.get_pod_name(logger)
120
121     logger.info("Pushing results to DB: '%s'." % url)
122
123     params = {"project_name": "functest", "case_name": case,
124               "pod_name": str(pod_name), 'installer': installer,
125               "version": version, "scenario": scenario, "criteria": criteria,
126               'details': payload}
127     headers = {'Content-Type': 'application/json'}
128
129     r = requests.post(url, data=json.dumps(params), headers=headers)
130     logger.debug(r)
131
132
133 def create_tempest_resources():
134     ks_creds = openstack_utils.get_credentials("keystone")
135     logger.info("Creating tenant and user for Tempest suite")
136     keystone = ksclient.Client(**ks_creds)
137     tenant_id = openstack_utils.create_tenant(keystone,
138                                               TENANT_NAME,
139                                               TENANT_DESCRIPTION)
140     if tenant_id == '':
141         logger.error("Error : Failed to create %s tenant" % TENANT_NAME)
142
143     user_id = openstack_utils.create_user(keystone, USER_NAME, USER_PASSWORD,
144                                           None, tenant_id)
145     if user_id == '':
146         logger.error("Error : Failed to create %s user" % USER_NAME)
147
148
149 def free_tempest_resources():
150     ks_creds = openstack_utils.get_credentials("keystone")
151     logger.info("Deleting tenant and user for Tempest suite)")
152     keystone = ksclient.Client(**ks_creds)
153
154     user_id = openstack_utils.get_user_id(keystone, USER_NAME)
155     if user_id == '':
156         logger.error("Error : Failed to get id of %s user" % USER_NAME)
157     else:
158         if not openstack_utils.delete_user(keystone, user_id):
159             logger.error("Error : Failed to delete %s user" % USER_NAME)
160
161     tenant_id = openstack_utils.get_tenant_id(keystone, TENANT_NAME)
162     if tenant_id == '':
163         logger.error("Error : Failed to get id of %s tenant" % TENANT_NAME)
164     else:
165         if not openstack_utils.delete_tenant(keystone, tenant_id):
166             logger.error("Error : Failed to delete %s tenant" % TENANT_NAME)
167
168
169 def configure_tempest(mode):
170     """
171     Add/update needed parameters into tempest.conf file generated by Rally
172     """
173
174     logger.debug("Generating tempest.conf file...")
175     cmd = "rally verify genconfig"
176     functest_utils.execute_command(cmd, logger)
177
178     logger.debug("Resolving deployment UUID and directory...")
179     cmd = "rally deployment list | awk '/" + DEPLOYMENT_MAME + "/ {print $2}'"
180     p = subprocess.Popen(cmd, shell=True,
181                          stdout=subprocess.PIPE,
182                          stderr=subprocess.STDOUT)
183     deployment_uuid = p.stdout.readline().rstrip()
184     if deployment_uuid == "":
185         logger.debug("   Rally deployment NOT found")
186         return False
187     deployment_dir = (RALLY_INSTALLATION_DIR + "/tempest/for-deployment-" +
188                       deployment_uuid)
189
190     logger.debug("Finding tempest.conf file...")
191     tempest_conf_file = deployment_dir + "/tempest.conf"
192     if not os.path.isfile(tempest_conf_file):
193         logger.error("   Tempest configuration file %s NOT found."
194                      % tempest_conf_file)
195         return False
196
197     logger.debug("Generating test case list...")
198     cmd = "cd " + deployment_dir + ";"
199     if mode == 'smoke':
200         cmd += "testr list-tests smoke >" + TEMPEST_LIST_FILE + ";cd"
201         functest_utils.execute_command(cmd, logger)
202     elif mode == 'full':
203         cmd += "testr list-tests >" + TEMPEST_LIST_FILE + ";cd"
204         functest_utils.execute_command(cmd, logger)
205
206     logger.debug("Updating selected tempest.conf parameters...")
207     config = ConfigParser.RawConfigParser()
208     config.read(tempest_conf_file)
209     private_net_name = ""
210     creds_neutron = openstack_utils.get_credentials("neutron")
211     neutron_client = neutronclient.Client(**creds_neutron)
212     private_net = openstack_utils.get_private_net(neutron_client)
213     if private_net is None:
214         logger.error("No shared private networks found.")
215     else:
216         private_net_name = private_net['name']
217     config.set('compute', 'fixed_network_name', private_net_name)
218     config.set('identity', 'tenant_name', TENANT_NAME)
219     config.set('identity', 'username', USER_NAME)
220     config.set('identity', 'password', USER_PASSWORD)
221     with open(tempest_conf_file, 'wb') as config_file:
222         config.write(config_file)
223
224     # Copy tempest.conf to /home/opnfv/functest/results/tempest/
225     shutil.copyfile(tempest_conf_file, TEMPEST_RESULTS_DIR + '/tempest.conf')
226     return True
227
228
229 def run_tempest(OPTION):
230     #
231     # the "main" function of the script which launches Rally to run Tempest
232     # :param option: tempest option (smoke, ..)
233     # :return: void
234     #
235     logger.info("Starting Tempest test suite: '%s'." % OPTION)
236     cmd_line = "rally verify start " + OPTION + " --system-wide"
237     logger.debug('Executing command : {}'.format(cmd_line))
238
239     CI_DEBUG = os.environ.get("CI_DEBUG")
240     if CI_DEBUG == "true" or CI_DEBUG == "True":
241         subprocess.call(cmd_line, shell=True, stderr=subprocess.STDOUT)
242     else:
243         header = ("Tempest environment:\n"
244                   "  Installer: %s\n  Scenario: %s\n  Node: %s\n  Date: %s\n" %
245                   (os.getenv('INSTALLER_TYPE', 'Unknown'),
246                    os.getenv('DEPLOY_SCENARIO', 'Unknown'),
247                    os.getenv('NODE_NAME', 'Unknown'),
248                    time.strftime("%a %b %d %H:%M:%S %Z %Y")))
249
250         f_stdout = open(TEMPEST_RESULTS_DIR + "/tempest.log", 'w+')
251         f_stderr = open(TEMPEST_RESULTS_DIR + "/tempest-error.log", 'w+')
252         f_env = open(TEMPEST_RESULTS_DIR + "/environment.log", 'w+')
253         f_env.write(header)
254
255         subprocess.call(cmd_line, shell=True, stdout=f_stdout, stderr=f_stderr)
256
257         f_stdout.close()
258         f_stderr.close()
259         f_env.close()
260
261         cmd_line = "rally verify show"
262         subprocess.call(cmd_line, shell=True)
263
264     cmd_line = "rally verify list"
265     logger.debug('Executing command : {}'.format(cmd_line))
266     cmd = os.popen(cmd_line)
267     output = (((cmd.read()).splitlines()[3]).replace(" ", "")).split("|")
268     # Format:
269     # | UUID | Deployment UUID | smoke | tests | failures | Created at |
270     # Duration | Status  |
271     num_tests = output[4]
272     num_failures = output[5]
273     time_start = output[6]
274     duration = output[7]
275     # Compute duration (lets assume it does not take more than 60 min)
276     dur_min = int(duration.split(':')[1])
277     dur_sec_float = float(duration.split(':')[2])
278     dur_sec_int = int(round(dur_sec_float, 0))
279     dur_sec_int = dur_sec_int + 60 * dur_min
280
281     # Generate json results for DB
282     json_results = {"timestart": time_start, "duration": dur_sec_int,
283                     "tests": int(num_tests), "failures": int(num_failures)}
284     logger.info("Results: " + str(json_results))
285
286     status = "failed"
287     try:
288         diff = (int(num_tests) - int(num_failures))
289         success_rate = 100 * diff / int(num_tests)
290     except:
291         success_rate = 0
292
293     # For Tempest we assume that teh success rate is above 90%
294     if success_rate >= 90:
295         status = "passed"
296
297     # Push results in payload of testcase
298     if args.report:
299         logger.debug("Push result into DB")
300         push_results_to_db("Tempest", json_results, status)
301
302
303 def main():
304     global MODE
305
306     if not (args.mode in modes):
307         logger.error("Tempest mode not valid. "
308                      "Possible values are:\n" + str(modes))
309         exit(-1)
310
311     if args.mode == 'custom' or args.mode == 'smoke' or args.mode == 'full':
312         MODE = "--tests-file " + TEMPEST_LIST_FILE
313     elif args.mode == 'defcore':
314         MODE = "--tests-file " + TEMPEST_DEFCORE
315     else:
316         MODE = "--set " + args.mode
317
318     if args.serial:
319         MODE += " --concur 1"
320
321     if not os.path.exists(TEMPEST_RESULTS_DIR):
322         os.makedirs(TEMPEST_RESULTS_DIR)
323
324     create_tempest_resources()
325     configure_tempest(args.mode)
326     run_tempest(MODE)
327
328     if args.noclean:
329         exit(0)
330
331     free_tempest_resources()
332
333
334 if __name__ == '__main__':
335     main()