blob: 80e61c7ee28c1ee6ca128fa62ef7a8f8ff3d2be2 [file] [log] [blame]
ZhiQiang Fan39f97222013-09-20 04:49:44 +08001# Copyright 2012 OpenStack Foundation
Jay Pipes051075a2012-04-28 17:39:37 -04002# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# 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, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
Attila Fazekasf86fa312013-07-30 19:56:39 +020016import atexit
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +090017import functools
Marc Koderer24eb89c2014-01-31 11:23:33 +010018import json
Ian Wienand98c35f32013-07-23 20:34:23 +100019import os
Marc Kodererb2978da2014-03-26 13:45:43 +010020import re
Attila Fazekas53943322014-02-10 16:07:34 +010021import sys
Jay Pipes051075a2012-04-28 17:39:37 -040022import time
Marc Koderer24eb89c2014-01-31 11:23:33 +010023import urllib
24import uuid
Jay Pipes051075a2012-04-28 17:39:37 -040025
Matthew Treinish78561ad2013-07-26 11:41:56 -040026import fixtures
Doug Hellmann583ce2c2015-03-11 14:55:46 +000027from oslo_log import log as logging
28from oslo_utils import importutils
Chris Hoge296558c2015-02-19 00:29:49 -060029import six
Marc Koderer674c8fc2014-03-17 09:45:04 +010030import testscenarios
ivan-zhu1feeb382013-01-24 10:14:39 +080031import testtools
Jay Pipes051075a2012-04-28 17:39:37 -040032
Matthew Treinish3e046852013-07-23 16:00:24 -040033from tempest import clients
Andrea Frittoli8283b4e2014-07-17 13:28:58 +010034from tempest.common import credentials
Rohan Kanade9ce97df2013-12-10 18:59:35 +053035from tempest.common import fixed_network
Marc Koderer6ee82dc2014-02-17 10:26:29 +010036import tempest.common.generator.valid_generator as valid
nithya-ganesan222efd72015-01-22 12:20:27 +000037import tempest.common.validation_resources as vresources
Attila Fazekasdc216422013-01-29 15:12:14 +010038from tempest import config
Matthew Treinish16c43792013-09-09 19:55:23 +000039from tempest import exceptions
Jay Pipes051075a2012-04-28 17:39:37 -040040
41LOG = logging.getLogger(__name__)
42
Sean Dague86bd8422013-12-20 09:56:44 -050043CONF = config.CONF
44
Jay Pipes051075a2012-04-28 17:39:37 -040045
Yaroslav Lobankovda999f72015-06-30 20:32:55 +030046def attr(**kwargs):
Matthew Treinisha74f5d42014-02-07 20:25:44 -050047 """A decorator which applies the testtools attr decorator
Chris Yeoh55530bb2013-02-08 16:04:27 +103048
Matthew Treinisha74f5d42014-02-07 20:25:44 -050049 This decorator applies the testtools.testcase.attr if it is in the list of
50 attributes to testtools we want to apply.
Attila Fazekasb2902af2013-02-16 16:22:44 +010051 """
Chris Yeoh55530bb2013-02-08 16:04:27 +103052
53 def decorator(f):
Giulio Fidente4946a052013-05-14 12:23:51 +020054 if 'type' in kwargs and isinstance(kwargs['type'], str):
55 f = testtools.testcase.attr(kwargs['type'])(f)
56 elif 'type' in kwargs and isinstance(kwargs['type'], list):
57 for attr in kwargs['type']:
58 f = testtools.testcase.attr(attr)(f)
Matthew Treinisha74f5d42014-02-07 20:25:44 -050059 return f
Chris Yeoh55530bb2013-02-08 16:04:27 +103060
61 return decorator
62
63
Chris Hoge296558c2015-02-19 00:29:49 -060064def idempotent_id(id):
65 """Stub for metadata decorator"""
66 if not isinstance(id, six.string_types):
67 raise TypeError('Test idempotent_id must be string not %s'
68 '' % type(id).__name__)
69 uuid.UUID(id)
70
71 def decorator(f):
72 f = testtools.testcase.attr('id-%s' % id)(f)
73 if f.__doc__:
74 f.__doc__ = 'Test idempotent id: %s\n%s' % (id, f.__doc__)
75 else:
76 f.__doc__ = 'Test idempotent id: %s' % id
77 return f
78 return decorator
79
80
Matthew Treinish3d8c7322014-08-03 23:53:28 -040081def get_service_list():
Matthew Treinish8afbffd2014-01-21 23:56:13 +000082 service_list = {
83 'compute': CONF.service_available.nova,
84 'image': CONF.service_available.glance,
Adam Gandelman4a48a602014-03-20 18:23:18 -070085 'baremetal': CONF.service_available.ironic,
Matthew Treinish8afbffd2014-01-21 23:56:13 +000086 'volume': CONF.service_available.cinder,
87 'orchestration': CONF.service_available.heat,
88 # NOTE(mtreinish) nova-network will provide networking functionality
89 # if neutron isn't available, so always set to True.
90 'network': True,
91 'identity': True,
92 'object_storage': CONF.service_available.swift,
93 'dashboard': CONF.service_available.horizon,
ekhugen7aff0992014-08-04 19:01:57 +000094 'telemetry': CONF.service_available.ceilometer,
Matthew Treinishb66c94e2015-03-11 13:00:48 -040095 'data_processing': CONF.service_available.sahara,
96 'database': CONF.service_available.trove
Matthew Treinish8afbffd2014-01-21 23:56:13 +000097 }
Matthew Treinish3d8c7322014-08-03 23:53:28 -040098 return service_list
Matthew Treinish16c43792013-09-09 19:55:23 +000099
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400100
Yaroslav Lobankovda999f72015-06-30 20:32:55 +0300101def services(*args):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400102 """A decorator used to set an attr for each service used in a test case
103
104 This decorator applies a testtools attr for each service that gets
105 exercised by a test case.
106 """
Matthew Treinish16c43792013-09-09 19:55:23 +0000107 def decorator(f):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400108 services = ['compute', 'image', 'baremetal', 'volume', 'orchestration',
109 'network', 'identity', 'object_storage', 'dashboard',
Matthew Treinishb66c94e2015-03-11 13:00:48 -0400110 'telemetry', 'data_processing', 'database']
Matthew Treinish16c43792013-09-09 19:55:23 +0000111 for service in args:
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400112 if service not in services:
113 raise exceptions.InvalidServiceTag('%s is not a valid '
114 'service' % service)
Matthew Treinish16c43792013-09-09 19:55:23 +0000115 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000116
117 @functools.wraps(f)
118 def wrapper(self, *func_args, **func_kwargs):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400119 service_list = get_service_list()
120
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000121 for service in args:
122 if not service_list[service]:
123 msg = 'Skipped because the %s service is not available' % (
124 service)
125 raise testtools.TestCase.skipException(msg)
126 return f(self, *func_args, **func_kwargs)
127 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000128 return decorator
129
130
Yaroslav Lobankovda999f72015-06-30 20:32:55 +0300131def stresstest(**kwargs):
Marc Koderer32221b8e2013-08-23 13:57:50 +0200132 """Add stress test decorator
133
134 For all functions with this decorator a attr stress will be
135 set automatically.
136
137 @param class_setup_per: allowed values are application, process, action
138 ``application``: once in the stress job lifetime
139 ``process``: once in the worker process lifetime
140 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200141 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200142 """
143 def decorator(f):
144 if 'class_setup_per' in kwargs:
145 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
146 else:
147 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200148 if 'allow_inheritance' in kwargs:
149 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
150 else:
151 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200152 attr(type='stress')(f)
153 return f
154 return decorator
155
156
Yaroslav Lobankovda999f72015-06-30 20:32:55 +0300157def requires_ext(**kwargs):
Matthew Treinishe3d26142013-11-26 19:14:58 +0000158 """A decorator to skip tests if an extension is not enabled
159
160 @param extension
161 @param service
162 """
163 def decorator(func):
164 @functools.wraps(func)
165 def wrapper(*func_args, **func_kwargs):
166 if not is_extension_enabled(kwargs['extension'],
167 kwargs['service']):
168 msg = "Skipped because %s extension: %s is not enabled" % (
169 kwargs['service'], kwargs['extension'])
170 raise testtools.TestCase.skipException(msg)
171 return func(*func_args, **func_kwargs)
172 return wrapper
173 return decorator
174
175
176def is_extension_enabled(extension_name, service):
177 """A function that will check the list of enabled extensions from config
178
179 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000180 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000181 'compute': CONF.compute_feature_enabled.api_extensions,
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000182 'volume': CONF.volume_feature_enabled.api_extensions,
183 'network': CONF.network_feature_enabled.api_extensions,
184 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000185 }
Simeon Monov5d7effe2014-07-16 07:32:38 +0300186 if len(config_dict[service]) == 0:
187 return False
Matthew Treinishe3d26142013-11-26 19:14:58 +0000188 if config_dict[service][0] == 'all':
189 return True
190 if extension_name in config_dict[service]:
191 return True
192 return False
193
Ian Wienand98c35f32013-07-23 20:34:23 +1000194
Attila Fazekasf86fa312013-07-30 19:56:39 +0200195at_exit_set = set()
196
197
198def validate_tearDownClass():
199 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400200 LOG.error(
201 "tearDownClass does not call the super's "
202 "tearDownClass in these classes: \n"
203 + str(at_exit_set))
204
Attila Fazekasf86fa312013-07-30 19:56:39 +0200205
206atexit.register(validate_tearDownClass)
207
Attila Fazekas53943322014-02-10 16:07:34 +0100208
Matthew Treinish2474f412014-11-17 18:11:56 -0500209class BaseTestCase(testtools.testcase.WithAttributes,
210 testtools.TestCase):
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100211 """The test base class defines Tempest framework for class level fixtures.
212 `setUpClass` and `tearDownClass` are defined here and cannot be overwritten
213 by subclasses (enforced via hacking rule T105).
214
215 Set-up is split in a series of steps (setup stages), which can be
216 overwritten by test classes. Set-up stages are:
217 - skip_checks
218 - setup_credentials
219 - setup_clients
220 - resource_setup
221
222 Tear-down is also split in a series of steps (teardown stages), which are
223 stacked for execution only if the corresponding setup stage had been
224 reached during the setup phase. Tear-down stages are:
225 - clear_isolated_creds (defined in the base test class)
226 - resource_cleanup
227 """
Attila Fazekasc43fec82013-04-09 23:17:52 +0200228
Attila Fazekasf86fa312013-07-30 19:56:39 +0200229 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100230 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200231
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000232 # NOTE(andreaf) credentials holds a list of the credentials to be allocated
Andrea Frittoli (andreaf)825b2d32015-04-08 20:58:01 +0100233 # at class setup time. Credential types can be 'primary', 'alt', 'admin' or
234 # a list of roles - the first element of the list being a label, and the
235 # rest the actual roles
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000236 credentials = []
nithya-ganesan222efd72015-01-22 12:20:27 +0000237 # Resources required to validate a server using ssh
238 validation_resources = {}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500239 network_resources = {}
240
Sean Dague2ef32ac2014-06-09 11:32:23 -0400241 # NOTE(sdague): log_format is defined inline here instead of using the oslo
242 # default because going through the config path recouples config to the
243 # stress tests too early, and depending on testr order will fail unit tests
244 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
245 '[%(name)s] %(message)s')
246
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200247 @classmethod
248 def setUpClass(cls):
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100249 # It should never be overridden by descendants
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200250 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
251 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200252 cls.setUpClassCalled = True
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100253 # Stack of (name, callable) to be invoked in reverse order at teardown
254 cls.teardowns = []
255 # All the configuration checks that may generate a skip
256 cls.skip_checks()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100257 try:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100258 # Allocation of all required credentials and client managers
259 cls.teardowns.append(('credentials', cls.clear_isolated_creds))
260 cls.setup_credentials()
261 # Shortcuts to clients
262 cls.setup_clients()
263 # Additional class-wide test resources
264 cls.teardowns.append(('resources', cls.resource_cleanup))
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100265 cls.resource_setup()
266 except Exception:
267 etype, value, trace = sys.exc_info()
Matthew Treinished2ad4f2014-12-23 15:18:32 -0500268 LOG.info("%s raised in %s.setUpClass. Invoking tearDownClass." % (
269 etype, cls.__name__))
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100270 cls.tearDownClass()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100271 try:
Matthew Treinish843227d2015-04-23 10:17:17 -0400272 six.reraise(etype, value, trace)
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100273 finally:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100274 del trace # to avoid circular refs
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200275
Attila Fazekasf86fa312013-07-30 19:56:39 +0200276 @classmethod
277 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200278 at_exit_set.discard(cls)
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100279 # It should never be overridden by descendants
Attila Fazekasf86fa312013-07-30 19:56:39 +0200280 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
281 super(BaseTestCase, cls).tearDownClass()
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100282 # Save any existing exception, we always want to re-raise the original
283 # exception only
284 etype, value, trace = sys.exc_info()
285 # If there was no exception during setup we shall re-raise the first
286 # exception in teardown
287 re_raise = (etype is None)
288 while cls.teardowns:
289 name, teardown = cls.teardowns.pop()
290 # Catch any exception in tearDown so we can re-raise the original
291 # exception at the end
292 try:
293 teardown()
294 except Exception as te:
295 sys_exec_info = sys.exc_info()
296 tetype = sys_exec_info[0]
297 # TODO(andreaf): Till we have the ability to cleanup only
298 # resources that were successfully setup in resource_cleanup,
299 # log AttributeError as info instead of exception.
300 if tetype is AttributeError and name == 'resources':
301 LOG.info("tearDownClass of %s failed: %s" % (name, te))
302 else:
303 LOG.exception("teardown of %s failed: %s" % (name, te))
304 if not etype:
305 etype, value, trace = sys_exec_info
306 # If exceptions were raised during teardown, an not before, re-raise
307 # the first one
308 if re_raise and etype is not None:
309 try:
Matthew Treinish843227d2015-04-23 10:17:17 -0400310 six.reraise(etype, value, trace)
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100311 finally:
312 del trace # to avoid circular refs
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100313
314 @classmethod
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100315 def skip_checks(cls):
316 """Class level skip checks. Subclasses verify in here all
317 conditions that might prevent the execution of the entire test class.
318 Checks implemented here may not make use API calls, and should rely on
319 configuration alone.
320 In general skip checks that require an API call are discouraged.
321 If one is really needed it may be implemented either in the
322 resource_setup or at test level.
323 """
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000324 if 'admin' in cls.credentials and not credentials.is_admin_available():
325 msg = "Missing Identity Admin API credentials in configuration."
326 raise cls.skipException(msg)
LingxianKongd1994002015-05-22 14:59:22 +0800327 if 'alt' in cls.credentials and not credentials.is_alt_available():
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000328 msg = "Missing a 2nd set of API credentials in configuration."
329 raise cls.skipException(msg)
Andrea Frittoli (andreaf)41601412015-05-12 16:39:03 +0100330 if hasattr(cls, 'identity_version'):
331 if cls.identity_version == 'v2':
332 if not CONF.identity_feature_enabled.api_v2:
333 raise cls.skipException("Identity api v2 is not enabled")
334 elif cls.identity_version == 'v3':
335 if not CONF.identity_feature_enabled.api_v3:
336 raise cls.skipException("Identity api v3 is not enabled")
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100337
338 @classmethod
339 def setup_credentials(cls):
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000340 """Allocate credentials and the client managers from them.
341 A test class that requires network resources must override
342 setup_credentials and defined the required resources before super
343 is invoked.
344 """
345 for credentials_type in cls.credentials:
346 # This may raise an exception in case credentials are not available
347 # In that case we want to let the exception through and the test
348 # fail accordingly
Andrea Frittoli (andreaf)825b2d32015-04-08 20:58:01 +0100349 if isinstance(credentials_type, six.string_types):
350 manager = cls.get_client_manager(
351 credential_type=credentials_type)
352 setattr(cls, 'os_%s' % credentials_type, manager)
353 # Setup some common aliases
354 # TODO(andreaf) The aliases below are a temporary hack
355 # to avoid changing too much code in one patch. They should
356 # be removed eventually
357 if credentials_type == 'primary':
358 cls.os = cls.manager = cls.os_primary
359 if credentials_type == 'admin':
360 cls.os_adm = cls.admin_manager = cls.os_admin
361 if credentials_type == 'alt':
362 cls.alt_manager = cls.os_alt
363 elif isinstance(credentials_type, list):
364 manager = cls.get_client_manager(roles=credentials_type[1:],
365 force_new=True)
366 setattr(cls, 'os_roles_%s' % credentials_type[0], manager)
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100367
368 @classmethod
369 def setup_clients(cls):
370 """Create links to the clients into the test object."""
371 # TODO(andreaf) There is a fair amount of code that could me moved from
372 # base / test classes in here. Ideally tests should be able to only
373 # specify which client is `client` and nothing else.
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100374 pass
Attila Fazekasf86fa312013-07-30 19:56:39 +0200375
Emily Hugenbruch5bd4cbf2014-12-17 21:38:38 +0000376 @classmethod
377 def resource_setup(cls):
378 """Class level resource setup for test cases.
379 """
nithya-ganesan222efd72015-01-22 12:20:27 +0000380 if hasattr(cls, "os"):
381 cls.validation_resources = vresources.create_validation_resources(
382 cls.os, cls.validation_resources)
383 else:
384 LOG.warn("Client manager not found, validation resources not"
385 " created")
Emily Hugenbruch5bd4cbf2014-12-17 21:38:38 +0000386
387 @classmethod
388 def resource_cleanup(cls):
389 """Class level resource cleanup for test cases.
390 Resource cleanup must be able to handle the case of partially setup
391 resources, in case a failure during `resource_setup` should happen.
392 """
nithya-ganesan222efd72015-01-22 12:20:27 +0000393 if cls.validation_resources:
394 if hasattr(cls, "os"):
395 vresources.clear_validation_resources(cls.os,
396 cls.validation_resources)
397 cls.validation_resources = {}
398 else:
399 LOG.warn("Client manager not found, validation resources not"
400 " deleted")
Emily Hugenbruch5bd4cbf2014-12-17 21:38:38 +0000401
Attila Fazekasf86fa312013-07-30 19:56:39 +0200402 def setUp(self):
403 super(BaseTestCase, self).setUp()
404 if not self.setUpClassCalled:
405 raise RuntimeError("setUpClass does not calls the super's"
406 "setUpClass in the "
407 + self.__class__.__name__)
408 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400409 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
410 try:
411 test_timeout = int(test_timeout)
412 except ValueError:
413 test_timeout = 0
414 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200415 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400416
417 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
418 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200419 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
420 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400421 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
422 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200423 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
424 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200425 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
426 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200427 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400428 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200429 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400430
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100431 @property
432 def credentials_provider(self):
433 return self._get_credentials_provider()
434
435 @classmethod
436 def _get_credentials_provider(cls):
437 """Returns a credentials provider
438
439 If no credential provider exists yet creates one.
440 It uses self.identity_version if defined, or the configuration value
441 """
442 if (not hasattr(cls, '_creds_provider') or not cls._creds_provider or
443 not cls._creds_provider.name == cls.__name__):
444 force_tenant_isolation = getattr(cls, 'force_tenant_isolation',
445 False)
446 identity_version = getattr(cls, 'identity_version', None)
447 identity_version = identity_version or CONF.identity.auth_version
448
449 cls._creds_provider = credentials.get_isolated_credentials(
450 name=cls.__name__, network_resources=cls.network_resources,
451 force_tenant_isolation=force_tenant_isolation,
452 identity_version=identity_version)
453 return cls._creds_provider
454
Matthew Treinish3e046852013-07-23 16:00:24 -0400455 @classmethod
Andrea Frittoli (andreaf)41601412015-05-12 16:39:03 +0100456 def get_client_manager(cls, credential_type=None, roles=None,
457 force_new=None):
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100458 """Returns an OpenStack client manager
459
460 Returns an OpenStack client manager based on either credential_type
461 or a list of roles. If neither is specified, it defaults to
462 credential_type 'primary'
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100463 :param credential_type: string - primary, alt or admin
464 :param roles: list of roles
465
466 :returns the created client manager
467 :raises skipException: if the requested credentials are not available
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700468 """
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100469 if all([roles, credential_type]):
470 msg = "Cannot get credentials by type and roles at the same time"
471 raise ValueError(msg)
472 if not any([roles, credential_type]):
473 credential_type = 'primary'
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100474 cred_provider = cls._get_credentials_provider()
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100475 if roles:
476 for role in roles:
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100477 if not cred_provider.is_role_available(role):
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100478 skip_msg = (
479 "%s skipped because the configured credential provider"
480 " is not able to provide credentials with the %s role "
481 "assigned." % (cls.__name__, role))
482 raise cls.skipException(skip_msg)
483 params = dict(roles=roles)
484 if force_new is not None:
485 params.update(force_new=force_new)
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100486 creds = cred_provider.get_creds_by_roles(**params)
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000487 else:
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100488 credentials_method = 'get_%s_creds' % credential_type
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100489 if hasattr(cred_provider, credentials_method):
490 creds = getattr(cred_provider, credentials_method)()
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100491 else:
492 raise exceptions.InvalidCredentials(
493 "Invalid credentials type %s" % credential_type)
494 return clients.Manager(credentials=creds, service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700495
496 @classmethod
497 def clear_isolated_creds(cls):
498 """
499 Clears isolated creds if set
500 """
Attila Fazekas5b0d9262015-05-20 10:17:39 +0200501 if hasattr(cls, '_creds_provider'):
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100502 cls._creds_provider.clear_isolated_creds()
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700503
504 @classmethod
nithya-ganesan222efd72015-01-22 12:20:27 +0000505 def set_validation_resources(cls, keypair=None, floating_ip=None,
506 security_group=None,
507 security_group_rules=None):
508 """Specify which ssh server validation resources should be created.
509 Each of the argument must be set to either None, True or False, with
510 None - use default from config (security groups and security group
511 rules get created when set to None)
512 False - Do not create the validation resource
513 True - create the validation resource
514
515 @param keypair
516 @param security_group
517 @param security_group_rules
518 @param floating_ip
519 """
Matthew Treinishe5cca002015-05-11 15:36:50 -0400520 if not CONF.validation.run_validation:
521 return
nithya-ganesan222efd72015-01-22 12:20:27 +0000522 if keypair is None:
523 if CONF.validation.auth_method.lower() == "keypair":
524 keypair = True
525 else:
526 keypair = False
527 if floating_ip is None:
528 if CONF.validation.connect_method.lower() == "floating":
529 floating_ip = True
530 else:
531 floating_ip = False
532 if security_group is None:
533 security_group = True
534 if security_group_rules is None:
535 security_group_rules = True
536 if not cls.validation_resources:
537 cls.validation_resources = {
538 'keypair': keypair,
539 'security_group': security_group,
540 'security_group_rules': security_group_rules,
541 'floating_ip': floating_ip}
542
543 @classmethod
Andrea Frittoli7d5ed592015-02-10 01:10:23 +0000544 def set_network_resources(cls, network=False, router=False, subnet=False,
Matthew Treinish9f756a02014-01-15 10:26:07 -0500545 dhcp=False):
546 """Specify which network resources should be created
547
548 @param network
549 @param router
550 @param subnet
551 @param dhcp
552 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000553 # network resources should be set only once from callers
554 # in order to ensure that even if it's called multiple times in
555 # a chain of overloaded methods, the attribute is set only
556 # in the leaf class
Andrea Frittoli7d5ed592015-02-10 01:10:23 +0000557 if not cls.network_resources:
558 cls.network_resources = {
Salvatore Orlando5a337242014-01-15 22:49:22 +0000559 'network': network,
560 'router': router,
561 'subnet': subnet,
562 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500563
Rohan Kanade9ce97df2013-12-10 18:59:35 +0530564 @classmethod
565 def get_tenant_network(cls):
566 """Get the network to be used in testing
567
568 :return: network dict including 'id' and 'name'
569 """
570 # Make sure isolated_creds exists and get a network client
571 networks_client = cls.get_client_manager().networks_client
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100572 cred_provider = cls._get_credentials_provider()
Andrea Frittoli700711e2015-04-02 11:39:38 +0100573 # In case of nova network, isolated tenants are not able to list the
574 # network configured in fixed_network_name, even if the can use it
575 # for their servers, so using an admin network client to validate
576 # the network name
577 if (not CONF.service_available.neutron and
578 credentials.is_admin_available()):
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100579 admin_creds = cred_provider.get_admin_creds()
Rohan Kanade9ce97df2013-12-10 18:59:35 +0530580 networks_client = clients.Manager(admin_creds).networks_client
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100581 return fixed_network.get_tenant_network(cred_provider,
Rohan Kanade9ce97df2013-12-10 18:59:35 +0530582 networks_client)
583
Mark Maglana5885eb32014-02-28 10:57:34 -0800584 def assertEmpty(self, list, msg=None):
585 self.assertTrue(len(list) == 0, msg)
586
587 def assertNotEmpty(self, list, msg=None):
588 self.assertTrue(len(list) > 0, msg)
589
Attila Fazekasdc216422013-01-29 15:12:14 +0100590
Marc Koderer24eb89c2014-01-31 11:23:33 +0100591class NegativeAutoTest(BaseTestCase):
592
593 _resources = {}
594
595 @classmethod
596 def setUpClass(cls):
597 super(NegativeAutoTest, cls).setUpClass()
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000598 os = cls.get_client_manager(credential_type='primary')
Marc Koderer24eb89c2014-01-31 11:23:33 +0100599 cls.client = os.negative_client
600
601 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100602 def load_tests(*args):
603 """
604 Wrapper for testscenarios to set the mandatory scenarios variable
605 only in case a real test loader is in place. Will be automatically
606 called in case the variable "load_tests" is set.
607 """
608 if getattr(args[0], 'suiteClass', None) is not None:
609 loader, standard_tests, pattern = args
610 else:
611 standard_tests, module, loader = args
612 for test in testtools.iterate_tests(standard_tests):
Marc Koderer4f44d722014-08-07 14:04:58 +0200613 schema = getattr(test, '_schema', None)
Marc Koderer3dd31052014-11-27 09:31:00 +0100614 if schema is not None:
Marc Koderer4f44d722014-08-07 14:04:58 +0200615 setattr(test, 'scenarios',
616 NegativeAutoTest.generate_scenario(schema))
Marc Koderer674c8fc2014-03-17 09:45:04 +0100617 return testscenarios.load_tests_apply_scenarios(*args)
618
619 @staticmethod
Marc Koderer4f44d722014-08-07 14:04:58 +0200620 def generate_scenario(description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100621 """
622 Generates the test scenario list for a given description.
623
Marc Koderer4f44d722014-08-07 14:04:58 +0200624 :param description: A file or dictionary with the following entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100625 name (required) name for the api
626 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
627 url (required) the url to be appended to the catalog url with '%s'
628 for each resource mentioned
629 resources: (optional) A list of resource names such as "server",
630 "flavor", etc. with an element for each '%s' in the url. This
631 method will call self.get_resource for each element when
632 constructing the positive test case template so negative
633 subclasses are expected to return valid resource ids when
634 appropriate.
635 json-schema (optional) A valid json schema that will be used to
636 create invalid data for the api calls. For "GET" and "HEAD",
637 the data is used to generate query strings appended to the url,
638 otherwise for the body of the http call.
639 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100640 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100641 generator = importutils.import_class(
642 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100643 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100644 schema = description.get("json-schema", None)
645 resources = description.get("resources", [])
646 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100647 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100648 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100649 if isinstance(resource, dict):
650 expected_result = resource['expected_result']
651 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100652 LOG.debug("Add resource to test %s" % resource)
653 scn_name = "inv_res_%s" % (resource)
654 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100655 str(uuid.uuid4())),
656 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100657 }))
658 if schema is not None:
Marc Kodererf07f5d12014-09-01 09:47:23 +0200659 for scenario in generator.generate_scenarios(schema):
660 scenario_list.append((scenario['_negtest_name'],
661 scenario))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100662 LOG.debug(scenario_list)
663 return scenario_list
664
Marc Koderer4f44d722014-08-07 14:04:58 +0200665 def execute(self, description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100666 """
667 Execute a http call on an api that are expected to
668 result in client errors. First it uses invalid resources that are part
669 of the url, and then invalid data for queries and http request bodies.
670
Marc Koderer4f44d722014-08-07 14:04:58 +0200671 :param description: A json file or dictionary with the following
672 entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100673 name (required) name for the api
674 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
675 url (required) the url to be appended to the catalog url with '%s'
676 for each resource mentioned
677 resources: (optional) A list of resource names such as "server",
678 "flavor", etc. with an element for each '%s' in the url. This
679 method will call self.get_resource for each element when
680 constructing the positive test case template so negative
681 subclasses are expected to return valid resource ids when
682 appropriate.
683 json-schema (optional) A valid json schema that will be used to
684 create invalid data for the api calls. For "GET" and "HEAD",
685 the data is used to generate query strings appended to the url,
686 otherwise for the body of the http call.
687
688 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100689 LOG.info("Executing %s" % description["name"])
690 LOG.debug(description)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200691 generator = importutils.import_class(
692 CONF.negative.test_generator)()
693 schema = description.get("json-schema", None)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100694 method = description["http-method"]
695 url = description["url"]
Marc Kodererf07f5d12014-09-01 09:47:23 +0200696 expected_result = None
697 if "default_result_code" in description:
698 expected_result = description["default_result_code"]
Marc Koderer24eb89c2014-01-31 11:23:33 +0100699
700 resources = [self.get_resource(r) for
701 r in description.get("resources", [])]
702
703 if hasattr(self, "resource"):
704 # Note(mkoderer): The resources list already contains an invalid
705 # entry (see get_resource).
706 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100707 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100708 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100709 valid_schema = \
710 valid.ValidTestGenerator().generate_valid(schema)
711 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200712 elif hasattr(self, "_negtest_name"):
713 schema_under_test = \
714 valid.ValidTestGenerator().generate_valid(schema)
715 local_expected_result = \
716 generator.generate_payload(self, schema_under_test)
717 if local_expected_result is not None:
718 expected_result = local_expected_result
719 new_url, body = \
720 self._http_arguments(schema_under_test, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100721 else:
722 raise Exception("testscenarios are not active. Please make sure "
723 "that your test runner supports the load_tests "
724 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100725
Marc Kodererf857fda2014-03-05 15:58:00 +0100726 if "admin_client" in description and description["admin_client"]:
David Kranzafecec02015-03-23 14:27:15 -0400727 if not credentials.is_admin_available():
728 msg = ("Missing Identity Admin API credentials in"
729 "configuration.")
730 raise self.skipException(msg)
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100731 creds = self.credentials_provider.get_admin_creds()
David Kranzafecec02015-03-23 14:27:15 -0400732 os_adm = clients.Manager(credentials=creds)
733 client = os_adm.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100734 else:
735 client = self.client
736 resp, resp_body = client.send_request(method, new_url,
737 resources, body=body)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200738 self._check_negative_response(expected_result, resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100739
740 def _http_arguments(self, json_dict, url, method):
741 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
742 if not json_dict:
743 return url, None
744 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
745 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
746 else:
747 return url, json.dumps(json_dict)
748
Marc Kodererf07f5d12014-09-01 09:47:23 +0200749 def _check_negative_response(self, expected_result, result, body):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100750 self.assertTrue(result >= 400 and result < 500 and result != 413,
751 "Expected client error, got %s:%s" %
752 (result, body))
753 self.assertTrue(expected_result is None or expected_result == result,
754 "Expected %s, got %s:%s" %
755 (expected_result, result, body))
756
757 @classmethod
758 def set_resource(cls, name, resource):
759 """
760 This function can be used in setUpClass context to register a resoruce
761 for a test.
762
763 :param name: The name of the kind of resource such as "flavor", "role",
764 etc.
765 :resource: The id of the resource
766 """
767 cls._resources[name] = resource
768
769 def get_resource(self, name):
770 """
771 Return a valid uuid for a type of resource. If a real resource is
772 needed as part of a url then this method should return one. Otherwise
773 it can return None.
774
775 :param name: The name of the kind of resource such as "flavor", "role",
776 etc.
777 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100778 if isinstance(name, dict):
779 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100780 if hasattr(self, "resource") and self.resource[0] == name:
781 LOG.debug("Return invalid resource (%s) value: %s" %
782 (self.resource[0], self.resource[1]))
783 return self.resource[1]
784 if name in self._resources:
785 return self._resources[name]
786 return None
787
788
Marc Kodererb2978da2014-03-26 13:45:43 +0100789def SimpleNegativeAutoTest(klass):
790 """
791 This decorator registers a test function on basis of the class name.
792 """
Sean Dague5e1bcd92015-04-27 09:08:36 -0400793 @attr(type=['negative'])
Marc Kodererb2978da2014-03-26 13:45:43 +0100794 def generic_test(self):
Marc Koderer4f44d722014-08-07 14:04:58 +0200795 if hasattr(self, '_schema'):
796 self.execute(self._schema)
Marc Kodererb2978da2014-03-26 13:45:43 +0100797
798 cn = klass.__name__
799 cn = cn.replace('JSON', '')
800 cn = cn.replace('Test', '')
801 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
802 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
803 func_name = 'test_%s' % lower_cn
804 setattr(klass, func_name, generic_test)
805 return klass
806
807
Sean Dague35a7caf2013-05-10 10:38:22 -0400808def call_until_true(func, duration, sleep_for):
809 """
810 Call the given function until it returns True (and return True) or
811 until the specified duration (in seconds) elapses (and return
812 False).
813
814 :param func: A zero argument callable that returns True on success.
815 :param duration: The number of seconds for which to attempt a
816 successful call of the function.
817 :param sleep_for: The number of seconds to sleep after an unsuccessful
818 invocation of the function.
819 """
820 now = time.time()
821 timeout = now + duration
822 while now < timeout:
823 if func():
824 return True
Sean Dague35a7caf2013-05-10 10:38:22 -0400825 time.sleep(sleep_for)
826 now = time.time()
827 return False