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