Put scenario in version field on the Test DB (rather that git indication)
[functest.git] / testcases / VIM / OpenStack / CI / libraries / run_rally.py
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2015 Orange
4 # guyrodrigue.koffi@orange.com
5 # morgan.richomme@orange.com
6 # All rights reserved. This program and the accompanying materials
7 # are made available under the terms of the Apache License, Version 2.0
8 # which accompanies this distribution, and is available at
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # 0.1 (05/2015) initial commit
12 # 0.2 (28/09/2015) extract Tempest, format json result, add ceilometer suite
13 # 0.3 (19/10/2015) remove Tempest from run_rally
14 # and push result into test DB
15 #
16
17 import re
18 import json
19 import os
20 import argparse
21 import logging
22 import yaml
23 import requests
24 import subprocess
25 import sys
26 from novaclient import client as novaclient
27 from keystoneclient.v2_0 import client as keystoneclient
28 from glanceclient import client as glanceclient
29
30 """ tests configuration """
31 tests = ['authenticate', 'glance', 'cinder', 'ceilometer', 'heat', 'keystone',
32          'neutron', 'nova', 'quotas', 'requests', 'vm', 'all']
33 parser = argparse.ArgumentParser()
34 parser.add_argument("test_name",
35                     help="Module name to be tested"
36                          "Possible values are : "
37                          "[ {d[0]} | {d[1]} | {d[2]} | {d[3]} | {d[4]} | "
38                          "{d[5]} | {d[6]} | {d[7]} | {d[8]} | {d[9]} | "
39                          "{d[10]} | {d[11]}]. The 'all' value "
40                          "performs all the  possible tests scenarios"
41                          .format(d=tests))
42
43 parser.add_argument("-d", "--debug", help="Debug mode",  action="store_true")
44 parser.add_argument("-r", "--report",
45                     help="Create json result file",
46                     action="store_true")
47 parser.add_argument("-v", "--verbose",
48                     help="Print verbose info about the progress",
49                     action="store_true")
50
51 args = parser.parse_args()
52
53 if args.verbose:
54     RALLY_STDERR = subprocess.STDOUT
55 else:
56     RALLY_STDERR = open(os.devnull, 'w')
57
58 """ logging configuration """
59 logger = logging.getLogger("run_rally")
60 logger.setLevel(logging.DEBUG)
61
62 ch = logging.StreamHandler()
63 if args.debug:
64     ch.setLevel(logging.DEBUG)
65 else:
66     ch.setLevel(logging.INFO)
67
68 formatter = logging.Formatter("%(asctime)s - %(name)s - "
69                               "%(levelname)s - %(message)s")
70 ch.setFormatter(formatter)
71 logger.addHandler(ch)
72
73 REPO_PATH=os.environ['repos_dir']+'/functest/'
74 if not os.path.exists(REPO_PATH):
75     logger.error("Functest repository directory not found '%s'" % REPO_PATH)
76     exit(-1)
77 sys.path.append(REPO_PATH + "testcases/")
78 import functest_utils
79
80 with open("/home/opnfv/functest/conf/config_functest.yaml") as f:
81     functest_yaml = yaml.safe_load(f)
82 f.close()
83
84 HOME = os.environ['HOME']+"/"
85 SCENARIOS_DIR = REPO_PATH + functest_yaml.get("general"). \
86     get("directories").get("dir_rally_scn")
87 RESULTS_DIR = functest_yaml.get("general").get("directories"). \
88     get("dir_rally_res")
89 TEST_DB = functest_yaml.get("results").get("test_db_url")
90
91 GLANCE_IMAGE_NAME = "functest-img-rally"
92 GLANCE_IMAGE_FILENAME = functest_yaml.get("general"). \
93     get("openstack").get("image_file_name")
94 GLANCE_IMAGE_FORMAT = functest_yaml.get("general"). \
95     get("openstack").get("image_disk_format")
96 GLANCE_IMAGE_PATH = functest_yaml.get("general"). \
97     get("directories").get("dir_functest_data") + "/" + GLANCE_IMAGE_FILENAME
98
99
100 def push_results_to_db(payload):
101
102     url = TEST_DB + "/results"
103     installer = functest_utils.get_installer_type(logger)
104     scenario = functest_utils.get_scenario(logger)
105     pod_name = functest_utils.get_pod_name(logger)
106     # TODO pod_name hardcoded, info shall come from Jenkins
107     params = {"project_name": "functest", "case_name": "Rally",
108               "pod_name": pod_name, "installer": installer,
109               "version": scenario, "details": payload}
110
111     headers = {'Content-Type': 'application/json'}
112     r = requests.post(url, data=json.dumps(params), headers=headers)
113     logger.debug(r)
114
115
116 def get_task_id(cmd_raw):
117     """
118     get task id from command rally result
119     :param cmd_raw:
120     :return: task_id as string
121     """
122     taskid_re = re.compile('^Task +(.*): started$')
123     for line in cmd_raw.splitlines(True):
124         line = line.strip()
125         match = taskid_re.match(line)
126         if match:
127             return match.group(1)
128     return None
129
130
131 def task_succeed(json_raw):
132     """
133     Parse JSON from rally JSON results
134     :param json_raw:
135     :return: Bool
136     """
137     rally_report = json.loads(json_raw)
138     rally_report = rally_report[0]
139     if rally_report is None:
140         return False
141     if rally_report.get('result') is None:
142         return False
143
144     for result in rally_report.get('result'):
145         if len(result.get('error')) > 0:
146             return False
147
148     return True
149
150
151 def run_task(test_name):
152     #
153     # the "main" function of the script who lunch rally for a task
154     # :param test_name: name for the rally test
155     # :return: void
156     #
157
158     logger.info('starting {} test ...'.format(test_name))
159
160     # check directory for scenarios test files or retrieve from git otherwise
161     proceed_test = True
162     test_file_name = '{}opnfv-{}.json'.format(SCENARIOS_DIR, test_name)
163
164     if not os.path.exists(test_file_name):
165         logger.error("The scenario '%s' does not exist." % test_file_name)
166         exit(-1)
167
168     # we do the test only if we have a scenario test file
169     if proceed_test:
170         logger.debug('Scenario fetched from : {}'.format(test_file_name))
171         cmd_line = "rally task start --abort-on-sla-failure {}".format(test_file_name)
172         logger.debug('running command line : {}'.format(cmd_line))
173         p = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=RALLY_STDERR, shell=True)
174         result = ""
175         while p.poll() is None:
176             l = p.stdout.readline()
177             print l.replace('\n', '')
178             result += l
179
180         task_id = get_task_id(result)
181         logger.debug('task_id : {}'.format(task_id))
182
183         if task_id is None:
184             logger.error("failed to retrieve task_id")
185             exit(-1)
186
187         # check for result directory and create it otherwise
188         if not os.path.exists(RESULTS_DIR):
189             logger.debug('does not exists, we create it'.format(RESULTS_DIR))
190             os.makedirs(RESULTS_DIR)
191
192         # write html report file
193         report_file_name = '{}opnfv-{}.html'.format(RESULTS_DIR, test_name)
194         cmd_line = "rally task report {} --out {}".format(task_id,
195                                                           report_file_name)
196
197         logger.debug('running command line : {}'.format(cmd_line))
198         os.popen(cmd_line)
199
200         # get and save rally operation JSON result
201         cmd_line = "rally task results %s" % task_id
202         logger.debug('running command line : {}'.format(cmd_line))
203         cmd = os.popen(cmd_line)
204         json_results = cmd.read()
205         with open('{}opnfv-{}.json'.format(RESULTS_DIR, test_name), 'w') as f:
206             logger.debug('saving json file')
207             f.write(json_results)
208
209         with open('{}opnfv-{}.json'
210                   .format(RESULTS_DIR, test_name)) as json_file:
211             json_data = json.load(json_file)
212
213         # Push results in payload of testcase
214         if args.report:
215             logger.debug("Push result into DB")
216             push_results_to_db(json_data)
217
218         """ parse JSON operation result """
219         if task_succeed(json_results):
220             print 'Test OK'
221         else:
222             print 'Test KO'
223     else:
224         logger.error('{} test failed, unable to fetch a scenario test file'
225                      .format(test_name))
226
227
228 def main():
229     # configure script
230     if not (args.test_name in tests):
231         logger.error('argument not valid')
232         exit(-1)
233
234     creds_nova = functest_utils.get_credentials("nova")
235     nova_client = novaclient.Client('2',**creds_nova)
236     creds_keystone = functest_utils.get_credentials("keystone")
237     keystone_client = keystoneclient.Client(**creds_keystone)
238     glance_endpoint = keystone_client.service_catalog.url_for(service_type='image',
239                                                    endpoint_type='publicURL')
240     glance_client = glanceclient.Client(1, glance_endpoint,
241                                         token=keystone_client.auth_token)
242
243
244     image_id = functest_utils.get_image_id(glance_client, GLANCE_IMAGE_NAME)
245
246     if image_id == '':
247         logger.debug("Creating image '%s' from '%s'..." % (GLANCE_IMAGE_NAME, \
248                                                            GLANCE_IMAGE_PATH))
249         image_id = functest_utils.create_glance_image(glance_client,\
250                                                 GLANCE_IMAGE_NAME,GLANCE_IMAGE_PATH)
251         if not image_id:
252             logger.error("Failed to create the Glance image...")
253             exit(-1)
254         else:
255             logger.debug("Image '%s' with ID '%s' created succesfully ." \
256                          % (GLANCE_IMAGE_NAME, image_id))
257     else:
258         logger.debug("Using existing image '%s' with ID '%s'..." \
259                      % (GLANCE_IMAGE_NAME,image_id))
260
261
262     if args.test_name == "all":
263         for test_name in tests:
264             if not (test_name == 'all' or
265                     test_name == 'heat' or
266                     test_name == 'ceilometer' or
267                     test_name == 'smoke' or
268                     test_name == 'vm'):
269                 print(test_name)
270                 run_task(test_name)
271     else:
272         print(args.test_name)
273         run_task(args.test_name)
274
275     logger.debug("Deleting image '%s' with ID '%s'..." \
276                          % (GLANCE_IMAGE_NAME, image_id))
277     if not functest_utils.delete_glance_image(nova_client, image_id):
278         logger.error("Error deleting the glance image")
279
280 if __name__ == '__main__':
281     main()