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