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