54caccb7888efd08d3f8e99b45a17d58235d7a2c
[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 os
19 import unittest
20
21 from snaps import test_suite_builder, file_utils
22 from snaps.openstack.tests import openstack_tests
23
24 __author__ = 'spisarski'
25
26 logger = logging.getLogger('test_runner')
27
28 ARG_NOT_SET = "argument not set"
29 LOG_LEVELS = {'FATAL': logging.FATAL, 'CRITICAL': logging.CRITICAL, 'ERROR': logging.ERROR, 'WARN': logging.WARN,
30               'INFO': logging.INFO, 'DEBUG': logging.DEBUG}
31
32
33 def __create_test_suite(source_filename, ext_net_name, proxy_settings, ssh_proxy_cmd, run_unit_tests,
34                         run_connection_tests, run_api_tests, run_integration_tests, run_staging_tests, flavor_metadata,
35                         image_metadata, use_keystone, use_floating_ips, log_level):
36     """
37     Compiles the tests that should run
38     :param source_filename: the OpenStack credentials file (required)
39     :param ext_net_name: the name of the external network to use for floating IPs (required)
40     :param run_unit_tests: when true, the tests not requiring OpenStack will be added to the test suite
41     :param run_connection_tests: when true, the tests that perform simple connections to OpenStack are executed
42     :param run_api_tests: when true, the tests that perform simple API calls to OpenStack are executed
43     :param run_integration_tests: when true, the integration tests are executed
44     :param run_staging_tests: when true, the staging tests are executed
45     :param proxy_settings: <host>:<port> of the proxy server (optional)
46     :param ssh_proxy_cmd: the command used to connect via SSH over some proxy server (optional)
47     :param flavor_metadata: dict() object containing the metadata for flavors created for test VM instance
48     :param image_metadata: dict() object containing the metadata for overriding default images within the tests
49     :param use_keystone: when true, tests creating users and projects will be exercised and must be run on a host that
50                          has access to the cloud's administrative network
51     :param use_floating_ips: when true, tests requiring floating IPs will be executed
52     :param log_level: the logging level
53     :return:
54     """
55     suite = unittest.TestSuite()
56
57     os_creds = openstack_tests.get_credentials(os_env_file=source_filename, proxy_settings_str=proxy_settings,
58                                                ssh_proxy_cmd=ssh_proxy_cmd)
59
60     # Tests that do not require a remote connection to an OpenStack cloud
61     if run_unit_tests:
62         test_suite_builder.add_unit_tests(suite)
63
64     # Basic connection tests
65     if run_connection_tests:
66         test_suite_builder.add_openstack_client_tests(
67             suite=suite, os_creds=os_creds, ext_net_name=ext_net_name, use_keystone=use_keystone, log_level=log_level)
68
69     # Tests the OpenStack API calls
70     if run_api_tests:
71         test_suite_builder.add_openstack_api_tests(
72             suite=suite, os_creds=os_creds, ext_net_name=ext_net_name, use_keystone=use_keystone,
73             image_metadata=image_metadata, log_level=log_level)
74
75     # Long running integration type tests
76     if run_integration_tests:
77         test_suite_builder.add_openstack_integration_tests(
78             suite=suite, os_creds=os_creds, ext_net_name=ext_net_name, use_keystone=use_keystone,
79             flavor_metadata=flavor_metadata, image_metadata=image_metadata, use_floating_ips=use_floating_ips,
80             log_level=log_level)
81
82     if run_staging_tests:
83         test_suite_builder.add_openstack_staging_tests(
84             suite=suite, os_creds=os_creds, ext_net_name=ext_net_name, log_level=log_level)
85     return suite
86
87
88 def main(arguments):
89     """
90     Begins running unit tests.
91     argv[1] if used must be the source filename else os_env.yaml will be leveraged instead
92     argv[2] if used must be the proxy server <host>:<port>
93     """
94     logger.info('Starting test suite')
95
96     log_level = LOG_LEVELS.get(arguments.log_level, logging.DEBUG)
97
98     flavor_metadata = None
99     if arguments.flavor_metadata:
100         flavor_metadata = json.loads(arguments.flavor_metadata)
101
102     image_metadata = None
103     if arguments.image_metadata_file:
104         image_metadata = file_utils.read_yaml(arguments.image_metadata_file)
105
106     suite = None
107     if arguments.env and arguments.ext_net:
108         unit = arguments.include_unit != ARG_NOT_SET
109         connection = arguments.include_connection != ARG_NOT_SET
110         api = arguments.include_api != ARG_NOT_SET
111         integration = arguments.include_integration != ARG_NOT_SET
112         staging = arguments.include_staging != ARG_NOT_SET
113         if not unit and not connection and not api and not integration and not staging:
114             unit = True
115             connection = True
116             api = True
117             integration = True
118
119         suite = __create_test_suite(arguments.env, arguments.ext_net, arguments.proxy, arguments.ssh_proxy_cmd,
120                                     unit, connection, api, integration, staging, flavor_metadata, image_metadata,
121                                     arguments.use_keystone != ARG_NOT_SET,
122                                     arguments.floating_ips != ARG_NOT_SET, log_level)
123     else:
124         logger.error('Environment file or external network not defined')
125         exit(1)
126
127     # To ensure any files referenced via a relative path will begin from the diectory in which this file resides
128     os.chdir(os.path.dirname(os.path.realpath(__file__)))
129
130     i = 0
131     while i < int(arguments.num_runs):
132         result = unittest.TextTestRunner(verbosity=2).run(suite)
133         i += 1
134
135         if result.errors:
136             logger.error('Number of errors in test suite - ' + str(len(result.errors)))
137             for test, message in result.errors:
138                 logger.error(str(test) + " ERROR with " + message)
139
140         if result.failures:
141             logger.error('Number of failures in test suite - ' + str(len(result.failures)))
142             for test, message in result.failures:
143                 logger.error(str(test) + " FAILED with " + message)
144
145         if (result.errors and len(result.errors) > 0) or (result.failures and len(result.failures) > 0):
146             logger.error('See above for test failures')
147             exit(1)
148         else:
149             logger.info('All tests completed successfully in run #' + str(i))
150
151     logger.info('Successful completion of ' + str(i) + ' test runs')
152     exit(0)
153
154
155 if __name__ == '__main__':
156     parser = argparse.ArgumentParser()
157     parser.add_argument('-e', '--env', dest='env', required=True, help='OpenStack credentials file')
158     parser.add_argument('-n', '--net', dest='ext_net', required=True, help='External network name')
159     parser.add_argument('-p', '--proxy', dest='proxy', nargs='?', default=None,
160                         help='Optonal HTTP proxy socket (<host>:<port>)')
161     parser.add_argument('-s', '--ssh-proxy-cmd', dest='ssh_proxy_cmd', nargs='?', default=None,
162                         help='Optonal SSH proxy command value')
163     parser.add_argument('-l', '--log-level', dest='log_level', default='INFO',
164                         help='Logging Level (FATAL|CRITICAL|ERROR|WARN|INFO|DEBUG)')
165     parser.add_argument('-u', '--unit-tests', dest='include_unit', default=ARG_NOT_SET, nargs='?',
166                         help='When argument is set, all tests not requiring OpenStack will be executed')
167     parser.add_argument('-c', '--connection-tests', dest='include_connection', default=ARG_NOT_SET, nargs='?',
168                         help='When argument is set, simple OpenStack connection tests will be executed')
169     parser.add_argument('-a', '--api-tests', dest='include_api', default=ARG_NOT_SET, nargs='?',
170                         help='When argument is set, OpenStack API tests will be executed')
171     parser.add_argument('-i', '--integration-tests', dest='include_integration', default=ARG_NOT_SET, nargs='?',
172                         help='When argument is set, OpenStack integrations tests will be executed')
173     parser.add_argument('-st', '--staging-tests', dest='include_staging', default=ARG_NOT_SET, nargs='?',
174                         help='When argument is set, OpenStack staging tests will be executed')
175     parser.add_argument('-f', '--floating-ips', dest='floating_ips', default=ARG_NOT_SET, nargs='?',
176                         help='When argument is set, all integration tests requiring Floating IPs will be executed')
177     parser.add_argument('-k', '--use-keystone', dest='use_keystone', default=ARG_NOT_SET, nargs='?',
178                         help='When argument is set, the tests will exercise the keystone APIs and must be run on a ' +
179                              'machine that has access to the admin network' +
180                              ' and is able to create users and groups')
181     parser.add_argument('-fm', '--flavor-meta', dest='flavor_metadata',
182                         default='{\"hw:mem_page_size\": \"any\"}',
183                         help='JSON string to be used as flavor metadata for all test instances created')
184     parser.add_argument('-im', '--image-meta', dest='image_metadata_file',
185                         default=None,
186                         help='Location of YAML file containing the image metadata')
187     parser.add_argument('-r', '--num-runs', dest='num_runs', default=1,
188                         help='Number of test runs to execute (default 1)')
189
190     args = parser.parse_args()
191
192     main(args)