Merge "Parameterize Rally scenarios"
[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 sys
25 import novaclient.v2.client as novaclient
26 from keystoneclient.v2_0 import client as keystoneclient
27 from glanceclient import client as glanceclient
28
29 """ tests configuration """
30 tests = ['authenticate', 'glance', 'cinder', 'ceilometer', 'heat', 'keystone',
31          'neutron', 'nova', 'quotas', 'requests', 'vm', 'all']
32 parser = argparse.ArgumentParser()
33 parser.add_argument("repo_path", help="Path to the repository")
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
48 args = parser.parse_args()
49
50 sys.path.append(args.repo_path + "testcases/")
51 import functest_utils
52
53 """ logging configuration """
54 logger = logging.getLogger("run_rally")
55 logger.setLevel(logging.DEBUG)
56
57 ch = logging.StreamHandler()
58 if args.debug:
59     ch.setLevel(logging.DEBUG)
60 else:
61     ch.setLevel(logging.INFO)
62
63 formatter = logging.Formatter("%(asctime)s - %(name)s - "
64                               "%(levelname)s - %(message)s")
65 ch.setFormatter(formatter)
66 logger.addHandler(ch)
67
68 with open(args.repo_path+"testcases/config_functest.yaml") as f:
69     functest_yaml = yaml.safe_load(f)
70 f.close()
71
72 HOME = os.environ['HOME']+"/"
73 REPO_PATH = args.repo_path
74 SCENARIOS_DIR = REPO_PATH + functest_yaml.get("general"). \
75     get("directories").get("dir_rally_scn")
76 RESULTS_DIR = functest_yaml.get("general").get("directories"). \
77     get("dir_rally_res")
78 TEST_DB = functest_yaml.get("results").get("test_db_url")
79
80 GLANCE_IMAGE_NAME = "functest-img-rally"
81 GLANCE_IMAGE_FILENAME = functest_yaml.get("general"). \
82     get("openstack").get("image_file_name")
83 GLANCE_IMAGE_FORMAT = functest_yaml.get("general"). \
84     get("openstack").get("image_disk_format")
85 GLANCE_IMAGE_PATH = functest_yaml.get("general"). \
86     get("directories").get("dir_functest_data") + "/" + GLANCE_IMAGE_FILENAME
87
88
89 def push_results_to_db(payload):
90
91     url = TEST_DB + "/results"
92     installer = functest_utils.get_installer_type(logger)
93     git_version = functest_utils.get_git_branch(args.repo_path)
94     pod_name = functest_utils.get_pod_name(logger)
95     # TODO pod_name hardcoded, info shall come from Jenkins
96     params = {"project_name": "functest", "case_name": "Rally",
97               "pod_name": pod_name, "installer": installer,
98               "version": git_version, "details": payload}
99
100     headers = {'Content-Type': 'application/json'}
101     r = requests.post(url, data=json.dumps(params), headers=headers)
102     logger.debug(r)
103
104
105 def get_task_id(cmd_raw):
106     """
107     get task id from command rally result
108     :param cmd_raw:
109     :return: task_id as string
110     """
111     taskid_re = re.compile('^Task +(.*): started$')
112     for line in cmd_raw.splitlines(True):
113         line = line.strip()
114         match = taskid_re.match(line)
115         if match:
116             return match.group(1)
117     return None
118
119
120 def create_glance_image(path, name, disk_format):
121     """
122     Create a glance image given the absolute path of the image, its name and the disk format
123     """
124     cmd = ("glance image-create --name " + name + "  --visibility public "
125            "--disk-format " + disk_format + " --container-format bare --file " + path)
126     functest_utils.execute_command(cmd, logger)
127     return True
128
129
130 def task_succeed(json_raw):
131     """
132     Parse JSON from rally JSON results
133     :param json_raw:
134     :return: Bool
135     """
136     rally_report = json.loads(json_raw)
137     rally_report = rally_report[0]
138     if rally_report is None:
139         return False
140     if rally_report.get('result') is None:
141         return False
142
143     for result in rally_report.get('result'):
144         if len(result.get('error')) > 0:
145             return False
146
147     return True
148
149
150 def run_task(test_name):
151     #
152     # the "main" function of the script who lunch rally for a task
153     # :param test_name: name for the rally test
154     # :return: void
155     #
156
157     logger.info('starting {} test ...'.format(test_name))
158
159     # check directory for scenarios test files or retrieve from git otherwise
160     proceed_test = True
161     test_file_name = '{}opnfv-{}.json'.format(SCENARIOS_DIR, test_name)
162
163     if not os.path.exists(test_file_name):
164         logger.error("The scenario '%s' does not exist." % test_file_name)
165         exit(-1)
166
167     # we do the test only if we have a scenario test file
168     if proceed_test:
169         logger.debug('Scenario fetched from : {}'.format(test_file_name))
170         cmd_line = "rally task start --abort-on-sla-failure {}".format(test_file_name)
171         logger.debug('running command line : {}'.format(cmd_line))
172         cmd = os.popen(cmd_line)
173         task_id = get_task_id(cmd.read())
174         logger.debug('task_id : {}'.format(task_id))
175
176         if task_id is None:
177             logger.error("failed to retrieve task_id")
178             exit(-1)
179
180         # check for result directory and create it otherwise
181         if not os.path.exists(RESULTS_DIR):
182             logger.debug('does not exists, we create it'.format(RESULTS_DIR))
183             os.makedirs(RESULTS_DIR)
184
185         # write html report file
186         report_file_name = '{}opnfv-{}.html'.format(RESULTS_DIR, test_name)
187         cmd_line = "rally task report {} --out {}".format(task_id,
188                                                           report_file_name)
189
190         logger.debug('running command line : {}'.format(cmd_line))
191         os.popen(cmd_line)
192
193         # get and save rally operation JSON result
194         cmd_line = "rally task results %s" % task_id
195         logger.debug('running command line : {}'.format(cmd_line))
196         cmd = os.popen(cmd_line)
197         json_results = cmd.read()
198         with open('{}opnfv-{}.json'.format(RESULTS_DIR, test_name), 'w') as f:
199             logger.debug('saving json file')
200             f.write(json_results)
201
202         with open('{}opnfv-{}.json'
203                   .format(RESULTS_DIR, test_name)) as json_file:
204             json_data = json.load(json_file)
205
206         # Push results in payload of testcase
207         if args.report:
208             logger.debug("Push result into DB")
209             push_results_to_db(json_data)
210
211         """ parse JSON operation result """
212         if task_succeed(json_results):
213             print 'Test OK'
214         else:
215             print 'Test KO'
216     else:
217         logger.error('{} test failed, unable to fetch a scenario test file'
218                      .format(test_name))
219
220
221 def main():
222     # configure script
223     if not (args.test_name in tests):
224         logger.error('argument not valid')
225         exit(-1)
226
227     creds_nova = functest_utils.get_credentials("nova")
228     nova_client = novaclient.Client(**creds_nova)
229     creds_keystone = functest_utils.get_credentials("keystone")
230     keystone_client = keystoneclient.Client(**creds_keystone)
231     glance_endpoint = keystone_client.service_catalog.url_for(service_type='image',
232                                                    endpoint_type='publicURL')
233     glance_client = glanceclient.Client(1, glance_endpoint,
234                                         token=keystone_client.auth_token)
235
236     logger.debug("Creating image '%s' from '%s'..." % (GLANCE_IMAGE_NAME, GLANCE_IMAGE_PATH))
237     image_id = functest_utils.create_glance_image(glance_client,
238                                             GLANCE_IMAGE_NAME,GLANCE_IMAGE_PATH)
239     if not image_id:
240         logger.error("Failed to create a Glance image...")
241         exit(-1)
242     # Check if the given image exists
243     try:
244         nova_client.images.find(name=GLANCE_IMAGE_NAME)
245         logger.info("Glance image found '%s'" % GLANCE_IMAGE_NAME)
246     except:
247         logger.error("ERROR: Glance image '%s' not found." % GLANCE_IMAGE_NAME)
248         logger.info("Available images are: ")
249         exit(-1)
250
251     if args.test_name == "all":
252         for test_name in tests:
253             if not (test_name == 'all' or
254                     test_name == 'heat' or
255                     test_name == 'ceilometer' or
256                     test_name == 'smoke' or
257                     test_name == 'vm'):
258                 print(test_name)
259                 run_task(test_name)
260     else:
261         print(args.test_name)
262         run_task(args.test_name)
263
264     if not functest_utils.delete_glance_image(nova_client, image_id):
265         logger.error("Error deleting the glance image")
266
267 if __name__ == '__main__':
268     main()