cddfdef7de7dd2e1dc15b5eacb2bbddc9ffac3da
[snaps.git] / snaps / test_runner.py
1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 #                    and others.  All rights reserved.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 import argparse
16 import json
17 import logging
18 import unittest
19 from concurrencytest import ConcurrentTestSuite, fork_for_tests
20
21 from snaps import file_utils
22 from snaps import test_suite_builder as tsb
23 from snaps.openstack.tests import openstack_tests
24
25 __author__ = 'spisarski'
26
27 logger = logging.getLogger('test_runner')
28
29 ARG_NOT_SET = "argument not set"
30 LOG_LEVELS = {'FATAL': logging.FATAL, 'CRITICAL': logging.CRITICAL,
31               'ERROR': logging.ERROR, 'WARN': logging.WARN,
32               'INFO': logging.INFO, 'DEBUG': logging.DEBUG}
33
34
35 def __create_concurrent_test_suite(
36         source_filename, ext_net_name, proxy_settings, ssh_proxy_cmd,
37         run_unit_tests, run_connection_tests, run_api_tests,
38         run_integration_tests, run_staging_tests, flavor_metadata,
39         image_metadata, use_keystone, use_floating_ips, continuous_integration,
40         log_level):
41     """
42     Compiles the tests that can be run concurrently
43     :param source_filename: the OpenStack credentials file (required)
44     :param ext_net_name: the name of the external network to use for floating
45                          IPs (required)
46     :param run_unit_tests: when true, the tests not requiring OpenStack will be
47                            added to the test suite
48     :param run_connection_tests: when true, the tests that perform simple
49                                  connections to OpenStack are executed
50     :param run_api_tests: when true, the tests that perform simple API calls to
51                           OpenStack are executed
52     :param run_integration_tests: when true, the integration tests are executed
53     :param run_staging_tests: when true, the staging tests are executed
54     :param proxy_settings: <host>:<port> of the proxy server (optional)
55     :param ssh_proxy_cmd: the command used to connect via SSH over some proxy
56                           server (optional)
57     :param flavor_metadata: dict() object containing the metadata for flavors
58                             created for test VM instance
59     :param image_metadata: dict() object containing the metadata for overriding
60                            default images within the tests
61     :param use_keystone: when true, tests creating users and projects will be
62                          exercised and must be run on a host that
63                          has access to the cloud's administrative network
64     :param use_floating_ips: when true, tests requiring floating IPs will be
65                              executed
66     :param continuous_integration: when true, tests for CI will be run
67     :param log_level: the logging level
68     :return:
69     """
70     suite = unittest.TestSuite()
71
72     os_creds = openstack_tests.get_credentials(
73         os_env_file=source_filename, proxy_settings_str=proxy_settings,
74         ssh_proxy_cmd=ssh_proxy_cmd)
75
76     # Tests that do not require a remote connection to an OpenStack cloud
77     if run_unit_tests:
78         tsb.add_unit_tests(suite)
79
80     # Basic connection tests
81     if run_connection_tests:
82         tsb.add_openstack_client_tests(
83             suite=suite, os_creds=os_creds, ext_net_name=ext_net_name,
84             use_keystone=use_keystone, log_level=log_level)
85
86     # Tests the OpenStack API calls
87     if run_api_tests:
88         tsb.add_openstack_api_tests(
89             suite=suite, os_creds=os_creds, ext_net_name=ext_net_name,
90             use_keystone=use_keystone, flavor_metadata=flavor_metadata,
91             image_metadata=image_metadata, log_level=log_level)
92
93     # Long running integration type tests
94     if run_integration_tests:
95         tsb.add_openstack_integration_tests(
96             suite=suite, os_creds=os_creds, ext_net_name=ext_net_name,
97             use_keystone=use_keystone, flavor_metadata=flavor_metadata,
98             image_metadata=image_metadata, use_floating_ips=use_floating_ips,
99             log_level=log_level)
100
101     if run_staging_tests:
102         tsb.add_openstack_staging_tests(
103             suite=suite, os_creds=os_creds, ext_net_name=ext_net_name,
104             log_level=log_level)
105
106     if continuous_integration:
107         tsb.add_openstack_ci_tests(
108             suite=suite, os_creds=os_creds, ext_net_name=ext_net_name,
109             use_keystone=use_keystone, flavor_metadata=flavor_metadata,
110             image_metadata=image_metadata, use_floating_ips=use_floating_ips,
111             log_level=log_level)
112     return suite
113
114
115 def __create_sequential_test_suite(
116         source_filename, ext_net_name, proxy_settings, ssh_proxy_cmd,
117         run_integration_tests, flavor_metadata, image_metadata, use_keystone,
118         use_floating_ips, log_level):
119     """
120     Compiles the tests that cannot be run in parallel
121     :param source_filename: the OpenStack credentials file (required)
122     :param ext_net_name: the name of the external network to use for floating
123                          IPs (required)
124     :param run_integration_tests: when true, the integration tests are executed
125     :param proxy_settings: <host>:<port> of the proxy server (optional)
126     :param ssh_proxy_cmd: the command used to connect via SSH over some proxy
127                           server (optional)
128     :param flavor_metadata: dict() object containing the metadata for flavors
129                             created for test VM instance
130     :param image_metadata: dict() object containing the metadata for overriding
131                            default images within the tests
132     :param use_keystone: when true, tests creating users and projects will be
133                          exercised and must be run on a host that
134                          has access to the cloud's administrative network
135     :param use_floating_ips: when true, tests requiring floating IPs will be
136                              executed
137     :param log_level: the logging level
138     :return:
139     """
140     if use_floating_ips and run_integration_tests:
141         suite = unittest.TestSuite()
142
143         os_creds = openstack_tests.get_credentials(
144             os_env_file=source_filename, proxy_settings_str=proxy_settings,
145             ssh_proxy_cmd=ssh_proxy_cmd)
146
147         tsb.add_ansible_integration_tests(
148                 suite=suite, os_creds=os_creds, ext_net_name=ext_net_name,
149                 use_keystone=use_keystone, flavor_metadata=flavor_metadata,
150                 image_metadata=image_metadata, log_level=log_level)
151
152         return suite
153
154
155 def __output_results(results):
156     """
157     Sends the test results to the logger
158     :param results:
159     :return:
160     """
161
162     if results.errors:
163         logger.error('Number of errors in test suite - %s',
164                      len(results.errors))
165         for test, message in results.errors:
166             logger.error(str(test) + " ERROR with " + message)
167
168     if results.failures:
169         logger.error('Number of failures in test suite - %s',
170                      len(results.failures))
171         for test, message in results.failures:
172             logger.error(str(test) + " FAILED with " + message)
173
174
175 def main(arguments):
176     """
177     Begins running unit tests.
178     argv[1] if used must be the source filename else os_env.yaml will be
179     leveraged instead
180     argv[2] if used must be the proxy server <host>:<port>
181     """
182     logger.info('Starting test suite')
183
184     log_level = LOG_LEVELS.get(arguments.log_level, logging.DEBUG)
185
186     flavor_metadata = None
187     if arguments.flavor_metadata:
188         flavor_metadata = {
189             'metadata': {'hw:mem_page_size': arguments.flavor_metadata}}
190
191     image_metadata = None
192     if arguments.image_metadata_file:
193         image_metadata = file_utils.read_yaml(arguments.image_metadata_file)
194
195     concurrent_suite = None
196     sequential_suite = None
197
198     if arguments.env and arguments.ext_net:
199         unit = arguments.include_unit != ARG_NOT_SET
200         connection = arguments.include_connection != ARG_NOT_SET
201         api = arguments.include_api != ARG_NOT_SET
202         integration = arguments.include_integration != ARG_NOT_SET
203         ci = arguments.continuous_integration != ARG_NOT_SET
204         staging = arguments.include_staging != ARG_NOT_SET
205         if (not unit and not connection and not api and not integration
206                 and not staging and not ci):
207             unit = True
208             connection = True
209             api = True
210             integration = True
211
212         concurrent_suite = __create_concurrent_test_suite(
213             arguments.env, arguments.ext_net, arguments.proxy,
214             arguments.ssh_proxy_cmd, unit, connection, api,
215             integration, staging, flavor_metadata, image_metadata,
216             arguments.use_keystone != ARG_NOT_SET,
217             arguments.floating_ips != ARG_NOT_SET,
218             ci, log_level)
219
220         if (arguments.include_integration != ARG_NOT_SET
221                 and arguments.floating_ips != ARG_NOT_SET):
222             sequential_suite = __create_sequential_test_suite(
223                 arguments.env, arguments.ext_net, arguments.proxy,
224                 arguments.ssh_proxy_cmd, integration, flavor_metadata,
225                 image_metadata,
226                 arguments.use_keystone != ARG_NOT_SET,
227                 arguments.floating_ips != ARG_NOT_SET, log_level)
228     else:
229         logger.error('Environment file or external network not defined')
230         exit(1)
231
232     i = 0
233     while i < int(arguments.num_runs):
234         i += 1
235
236         if concurrent_suite:
237             logger.info('Running Concurrent Tests')
238             concurrent_runner = unittest.TextTestRunner(verbosity=2)
239             concurrent_suite = ConcurrentTestSuite(
240                 concurrent_suite, fork_for_tests(int(arguments.threads)))
241             concurrent_results = concurrent_runner.run(concurrent_suite)
242             __output_results(concurrent_results)
243
244             if ((concurrent_results.errors
245                     and len(concurrent_results.errors) > 0)
246                     or (concurrent_results.failures
247                         and len(concurrent_results.failures) > 0)):
248                 logger.error('See above for test failures')
249                 exit(1)
250             else:
251                 logger.info(
252                     'Concurrent tests completed successfully in run #%s', i)
253
254         if sequential_suite:
255             logger.info('Running Sequential Tests')
256             sequential_runner = unittest.TextTestRunner(verbosity=2)
257             sequential_results = sequential_runner.run(sequential_suite)
258             __output_results(sequential_results)
259
260             if ((sequential_results.errors
261                     and len(sequential_results.errors) > 0)
262                 or (sequential_results.failures
263                     and len(sequential_results.failures) > 0)):
264                 logger.error('See above for test failures')
265                 exit(1)
266             else:
267                 logger.info(
268                     'Sequential tests completed successfully in run #%s', i)
269
270     logger.info('Successful completion of %s test runs', i)
271     exit(0)
272
273
274 if __name__ == '__main__':
275     parser = argparse.ArgumentParser()
276     parser.add_argument(
277         '-e', '--env', dest='env', required=True,
278         help='OpenStack credentials file')
279     parser.add_argument(
280         '-n', '--net', dest='ext_net', required=True,
281         help='External network name')
282     parser.add_argument(
283         '-p', '--proxy', dest='proxy', nargs='?', default=None,
284         help='Optonal HTTP proxy socket (<host>:<port>)')
285     parser.add_argument(
286         '-s', '--ssh-proxy-cmd', dest='ssh_proxy_cmd', nargs='?', default=None,
287         help='Optonal SSH proxy command value')
288     parser.add_argument(
289         '-l', '--log-level', dest='log_level', default='INFO',
290         help='Logging Level (FATAL|CRITICAL|ERROR|WARN|INFO|DEBUG)')
291     parser.add_argument(
292         '-u', '--unit-tests', dest='include_unit', default=ARG_NOT_SET,
293         nargs='?', help='When argument is set, all tests not requiring '
294                         'OpenStack will be executed')
295     parser.add_argument(
296         '-c', '--connection-tests', dest='include_connection',
297         default=ARG_NOT_SET, nargs='?',
298         help='When argument is set, simple OpenStack connection tests will be '
299              'executed')
300     parser.add_argument(
301         '-a', '--api-tests', dest='include_api', default=ARG_NOT_SET,
302         nargs='?',
303         help='When argument is set, OpenStack API tests will be executed')
304     parser.add_argument(
305         '-i', '--integration-tests', dest='include_integration',
306         default=ARG_NOT_SET, nargs='?',
307         help='When argument is set, OpenStack integrations tests will be '
308              'executed')
309     parser.add_argument(
310         '-st', '--staging-tests', dest='include_staging', default=ARG_NOT_SET,
311         nargs='?',
312         help='When argument is set, OpenStack staging tests will be executed')
313     parser.add_argument(
314         '-f', '--floating-ips', dest='floating_ips', default=ARG_NOT_SET,
315         nargs='?', help='When argument is set, all integration tests requiring'
316                         ' Floating IPs will be executed')
317     parser.add_argument(
318         '-k', '--use-keystone', dest='use_keystone', default=ARG_NOT_SET,
319         nargs='?',
320         help='When argument is set, the tests will exercise the keystone APIs '
321              'and must be run on a machine that has access to the admin '
322              'network and is able to create users and groups')
323     parser.add_argument(
324         '-fm', '--flavor-meta', dest='flavor_metadata',
325         help='hw:mem_page_size flavor setting value (i.e. large). '
326              'Required for DPDK')
327     parser.add_argument(
328         '-im', '--image-meta', dest='image_metadata_file', default=None,
329         help='Location of YAML file containing the image metadata')
330     parser.add_argument(
331         '-ci', '--continuous-integration', dest='continuous_integration',
332         default=ARG_NOT_SET, nargs='?',
333         help='When argument is set, OpenStack integrations tests will be '
334              'executed')
335     parser.add_argument(
336         '-r', '--num-runs', dest='num_runs', default=1,
337         help='Number of test runs to execute (default 1)')
338     parser.add_argument(
339         '-t', '--threads', dest='threads', default=4,
340         help='Number of threads to execute the tests (default 4)')
341
342     args = parser.parse_args()
343
344     main(args)