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