blob: 28e1e2c52d9bafe2ee9f478300ddad2e5722d504 [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
Marc Koderer674c8fc2014-03-17 09:45:04 +010027import testscenarios
ivan-zhu1feeb382013-01-24 10:14:39 +080028import testtools
Jay Pipes051075a2012-04-28 17:39:37 -040029
Matthew Treinish3e046852013-07-23 16:00:24 -040030from tempest import clients
Andrea Frittoli8283b4e2014-07-17 13:28:58 +010031from tempest.common import credentials
Marc Koderer6ee82dc2014-02-17 10:26:29 +010032import tempest.common.generator.valid_generator as valid
Attila Fazekasdc216422013-01-29 15:12:14 +010033from tempest import config
Matthew Treinish16c43792013-09-09 19:55:23 +000034from tempest import exceptions
Marc Koderer6ee82dc2014-02-17 10:26:29 +010035from tempest.openstack.common import importutils
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040036from tempest.openstack.common import log as logging
Jay Pipes051075a2012-04-28 17:39:37 -040037
38LOG = logging.getLogger(__name__)
39
Sean Dague86bd8422013-12-20 09:56:44 -050040CONF = config.CONF
41
Jay Pipes051075a2012-04-28 17:39:37 -040042
Chris Yeoh55530bb2013-02-08 16:04:27 +103043def attr(*args, **kwargs):
Matthew Treinisha74f5d42014-02-07 20:25:44 -050044 """A decorator which applies the testtools attr decorator
Chris Yeoh55530bb2013-02-08 16:04:27 +103045
Matthew Treinisha74f5d42014-02-07 20:25:44 -050046 This decorator applies the testtools.testcase.attr if it is in the list of
47 attributes to testtools we want to apply.
Attila Fazekasb2902af2013-02-16 16:22:44 +010048 """
Chris Yeoh55530bb2013-02-08 16:04:27 +103049
50 def decorator(f):
Giulio Fidente4946a052013-05-14 12:23:51 +020051 if 'type' in kwargs and isinstance(kwargs['type'], str):
52 f = testtools.testcase.attr(kwargs['type'])(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093053 if kwargs['type'] == 'smoke':
54 f = testtools.testcase.attr('gate')(f)
Giulio Fidente4946a052013-05-14 12:23:51 +020055 elif 'type' in kwargs and isinstance(kwargs['type'], list):
56 for attr in kwargs['type']:
57 f = testtools.testcase.attr(attr)(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093058 if attr == 'smoke':
59 f = testtools.testcase.attr('gate')(f)
Matthew Treinisha74f5d42014-02-07 20:25:44 -050060 return f
Chris Yeoh55530bb2013-02-08 16:04:27 +103061
62 return decorator
63
64
Matthew Treinish3d8c7322014-08-03 23:53:28 -040065def get_service_list():
Matthew Treinish8afbffd2014-01-21 23:56:13 +000066 service_list = {
67 'compute': CONF.service_available.nova,
68 'image': CONF.service_available.glance,
Adam Gandelman4a48a602014-03-20 18:23:18 -070069 'baremetal': CONF.service_available.ironic,
Matthew Treinish8afbffd2014-01-21 23:56:13 +000070 'volume': CONF.service_available.cinder,
71 'orchestration': CONF.service_available.heat,
72 # NOTE(mtreinish) nova-network will provide networking functionality
73 # if neutron isn't available, so always set to True.
74 'network': True,
75 'identity': True,
76 'object_storage': CONF.service_available.swift,
77 'dashboard': CONF.service_available.horizon,
ekhugen7aff0992014-08-04 19:01:57 +000078 'telemetry': CONF.service_available.ceilometer,
Yaroslav Lobankovff42c872014-06-17 15:39:43 +040079 'data_processing': CONF.service_available.sahara
Matthew Treinish8afbffd2014-01-21 23:56:13 +000080 }
Matthew Treinish3d8c7322014-08-03 23:53:28 -040081 return service_list
Matthew Treinish16c43792013-09-09 19:55:23 +000082
Matthew Treinish3d8c7322014-08-03 23:53:28 -040083
84def services(*args, **kwargs):
85 """A decorator used to set an attr for each service used in a test case
86
87 This decorator applies a testtools attr for each service that gets
88 exercised by a test case.
89 """
Matthew Treinish16c43792013-09-09 19:55:23 +000090 def decorator(f):
Matthew Treinish3d8c7322014-08-03 23:53:28 -040091 services = ['compute', 'image', 'baremetal', 'volume', 'orchestration',
92 'network', 'identity', 'object_storage', 'dashboard',
Matthew Treinish60359052014-09-18 17:39:26 -040093 'telemetry', 'data_processing']
Matthew Treinish16c43792013-09-09 19:55:23 +000094 for service in args:
Matthew Treinish3d8c7322014-08-03 23:53:28 -040095 if service not in services:
96 raise exceptions.InvalidServiceTag('%s is not a valid '
97 'service' % service)
Matthew Treinish16c43792013-09-09 19:55:23 +000098 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +000099
100 @functools.wraps(f)
101 def wrapper(self, *func_args, **func_kwargs):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400102 service_list = get_service_list()
103
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000104 for service in args:
105 if not service_list[service]:
106 msg = 'Skipped because the %s service is not available' % (
107 service)
108 raise testtools.TestCase.skipException(msg)
109 return f(self, *func_args, **func_kwargs)
110 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000111 return decorator
112
113
Marc Koderer32221b8e2013-08-23 13:57:50 +0200114def stresstest(*args, **kwargs):
115 """Add stress test decorator
116
117 For all functions with this decorator a attr stress will be
118 set automatically.
119
120 @param class_setup_per: allowed values are application, process, action
121 ``application``: once in the stress job lifetime
122 ``process``: once in the worker process lifetime
123 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200124 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200125 """
126 def decorator(f):
127 if 'class_setup_per' in kwargs:
128 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
129 else:
130 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200131 if 'allow_inheritance' in kwargs:
132 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
133 else:
134 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200135 attr(type='stress')(f)
136 return f
137 return decorator
138
139
Matthew Treinishe3d26142013-11-26 19:14:58 +0000140def requires_ext(*args, **kwargs):
141 """A decorator to skip tests if an extension is not enabled
142
143 @param extension
144 @param service
145 """
146 def decorator(func):
147 @functools.wraps(func)
148 def wrapper(*func_args, **func_kwargs):
149 if not is_extension_enabled(kwargs['extension'],
150 kwargs['service']):
151 msg = "Skipped because %s extension: %s is not enabled" % (
152 kwargs['service'], kwargs['extension'])
153 raise testtools.TestCase.skipException(msg)
154 return func(*func_args, **func_kwargs)
155 return wrapper
156 return decorator
157
158
159def is_extension_enabled(extension_name, service):
160 """A function that will check the list of enabled extensions from config
161
162 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000163 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000164 'compute': CONF.compute_feature_enabled.api_extensions,
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000165 'volume': CONF.volume_feature_enabled.api_extensions,
166 'network': CONF.network_feature_enabled.api_extensions,
167 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000168 }
Simeon Monov5d7effe2014-07-16 07:32:38 +0300169 if len(config_dict[service]) == 0:
170 return False
Matthew Treinishe3d26142013-11-26 19:14:58 +0000171 if config_dict[service][0] == 'all':
172 return True
173 if extension_name in config_dict[service]:
174 return True
175 return False
176
Ian Wienand98c35f32013-07-23 20:34:23 +1000177
Attila Fazekasf86fa312013-07-30 19:56:39 +0200178at_exit_set = set()
179
180
181def validate_tearDownClass():
182 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400183 LOG.error(
184 "tearDownClass does not call the super's "
185 "tearDownClass in these classes: \n"
186 + str(at_exit_set))
187
Attila Fazekasf86fa312013-07-30 19:56:39 +0200188
189atexit.register(validate_tearDownClass)
190
Attila Fazekas53943322014-02-10 16:07:34 +0100191
Matthew Treinish2474f412014-11-17 18:11:56 -0500192class BaseTestCase(testtools.testcase.WithAttributes,
193 testtools.TestCase):
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100194 """The test base class defines Tempest framework for class level fixtures.
195 `setUpClass` and `tearDownClass` are defined here and cannot be overwritten
196 by subclasses (enforced via hacking rule T105).
197
198 Set-up is split in a series of steps (setup stages), which can be
199 overwritten by test classes. Set-up stages are:
200 - skip_checks
201 - setup_credentials
202 - setup_clients
203 - resource_setup
204
205 Tear-down is also split in a series of steps (teardown stages), which are
206 stacked for execution only if the corresponding setup stage had been
207 reached during the setup phase. Tear-down stages are:
208 - clear_isolated_creds (defined in the base test class)
209 - resource_cleanup
210 """
Attila Fazekasc43fec82013-04-09 23:17:52 +0200211
Attila Fazekasf86fa312013-07-30 19:56:39 +0200212 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100213 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200214
Matthew Treinish9f756a02014-01-15 10:26:07 -0500215 network_resources = {}
216
Sean Dague2ef32ac2014-06-09 11:32:23 -0400217 # NOTE(sdague): log_format is defined inline here instead of using the oslo
218 # default because going through the config path recouples config to the
219 # stress tests too early, and depending on testr order will fail unit tests
220 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
221 '[%(name)s] %(message)s')
222
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200223 @classmethod
224 def setUpClass(cls):
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100225 # It should never be overridden by descendants
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200226 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
227 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200228 cls.setUpClassCalled = True
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100229 # Stack of (name, callable) to be invoked in reverse order at teardown
230 cls.teardowns = []
231 # All the configuration checks that may generate a skip
232 cls.skip_checks()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100233 try:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100234 # Allocation of all required credentials and client managers
235 cls.teardowns.append(('credentials', cls.clear_isolated_creds))
236 cls.setup_credentials()
237 # Shortcuts to clients
238 cls.setup_clients()
239 # Additional class-wide test resources
240 cls.teardowns.append(('resources', cls.resource_cleanup))
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100241 cls.resource_setup()
242 except Exception:
243 etype, value, trace = sys.exc_info()
Matthew Treinished2ad4f2014-12-23 15:18:32 -0500244 LOG.info("%s raised in %s.setUpClass. Invoking tearDownClass." % (
245 etype, cls.__name__))
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100246 cls.tearDownClass()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100247 try:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100248 raise etype, value, trace
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100249 finally:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100250 del trace # to avoid circular refs
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200251
Attila Fazekasf86fa312013-07-30 19:56:39 +0200252 @classmethod
253 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200254 at_exit_set.discard(cls)
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100255 # It should never be overridden by descendants
Attila Fazekasf86fa312013-07-30 19:56:39 +0200256 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
257 super(BaseTestCase, cls).tearDownClass()
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100258 # Save any existing exception, we always want to re-raise the original
259 # exception only
260 etype, value, trace = sys.exc_info()
261 # If there was no exception during setup we shall re-raise the first
262 # exception in teardown
263 re_raise = (etype is None)
264 while cls.teardowns:
265 name, teardown = cls.teardowns.pop()
266 # Catch any exception in tearDown so we can re-raise the original
267 # exception at the end
268 try:
269 teardown()
270 except Exception as te:
271 sys_exec_info = sys.exc_info()
272 tetype = sys_exec_info[0]
273 # TODO(andreaf): Till we have the ability to cleanup only
274 # resources that were successfully setup in resource_cleanup,
275 # log AttributeError as info instead of exception.
276 if tetype is AttributeError and name == 'resources':
277 LOG.info("tearDownClass of %s failed: %s" % (name, te))
278 else:
279 LOG.exception("teardown of %s failed: %s" % (name, te))
280 if not etype:
281 etype, value, trace = sys_exec_info
282 # If exceptions were raised during teardown, an not before, re-raise
283 # the first one
284 if re_raise and etype is not None:
285 try:
286 raise etype, value, trace
287 finally:
288 del trace # to avoid circular refs
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100289
290 @classmethod
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100291 def skip_checks(cls):
292 """Class level skip checks. Subclasses verify in here all
293 conditions that might prevent the execution of the entire test class.
294 Checks implemented here may not make use API calls, and should rely on
295 configuration alone.
296 In general skip checks that require an API call are discouraged.
297 If one is really needed it may be implemented either in the
298 resource_setup or at test level.
299 """
300 pass
301
302 @classmethod
303 def setup_credentials(cls):
304 """Allocate credentials and the client managers from them."""
305 # TODO(andreaf) There is a fair amount of code that could me moved from
306 # base / test classes in here. Ideally tests should be able to only
307 # specify a list of (additional) credentials the need to use.
308 pass
309
310 @classmethod
311 def setup_clients(cls):
312 """Create links to the clients into the test object."""
313 # TODO(andreaf) There is a fair amount of code that could me moved from
314 # base / test classes in here. Ideally tests should be able to only
315 # specify which client is `client` and nothing else.
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100316 pass
Attila Fazekasf86fa312013-07-30 19:56:39 +0200317
Emily Hugenbruch5bd4cbf2014-12-17 21:38:38 +0000318 @classmethod
319 def resource_setup(cls):
320 """Class level resource setup for test cases.
321 """
322 pass
323
324 @classmethod
325 def resource_cleanup(cls):
326 """Class level resource cleanup for test cases.
327 Resource cleanup must be able to handle the case of partially setup
328 resources, in case a failure during `resource_setup` should happen.
329 """
330 pass
331
Attila Fazekasf86fa312013-07-30 19:56:39 +0200332 def setUp(self):
333 super(BaseTestCase, self).setUp()
334 if not self.setUpClassCalled:
335 raise RuntimeError("setUpClass does not calls the super's"
336 "setUpClass in the "
337 + self.__class__.__name__)
338 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400339 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
340 try:
341 test_timeout = int(test_timeout)
342 except ValueError:
343 test_timeout = 0
344 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200345 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400346
347 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
348 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200349 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
350 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400351 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
352 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200353 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
354 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200355 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
356 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200357 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400358 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200359 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400360
Matthew Treinish3e046852013-07-23 16:00:24 -0400361 @classmethod
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000362 def get_client_manager(cls, interface=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700363 """
tanlin4956a642014-02-13 16:52:11 +0800364 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700365 """
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700366 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100367
Marc Koderer44dce622014-11-14 10:08:12 +0100368 if (not hasattr(cls, 'isolated_creds') or
369 not cls.isolated_creds.name == cls.__name__):
370 cls.isolated_creds = credentials.get_isolated_credentials(
371 name=cls.__name__, network_resources=cls.network_resources,
372 force_tenant_isolation=force_tenant_isolation,
373 )
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100374
375 creds = cls.isolated_creds.get_primary_creds()
376 params = dict(credentials=creds, service=cls._service)
377 if getattr(cls, '_interface', None):
378 interface = cls._interface
379 if interface:
380 params['interface'] = interface
381 os = clients.Manager(**params)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700382 return os
383
384 @classmethod
385 def clear_isolated_creds(cls):
386 """
387 Clears isolated creds if set
388 """
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100389 if hasattr(cls, 'isolated_creds'):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700390 cls.isolated_creds.clear_isolated_creds()
391
392 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400393 def _get_identity_admin_client(cls):
394 """
395 Returns an instance of the Identity Admin API client
396 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100397 os = clients.AdminManager(interface=cls._interface,
398 service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400399 admin_client = os.identity_client
400 return admin_client
401
402 @classmethod
Matthew Treinish9f756a02014-01-15 10:26:07 -0500403 def set_network_resources(self, network=False, router=False, subnet=False,
404 dhcp=False):
405 """Specify which network resources should be created
406
407 @param network
408 @param router
409 @param subnet
410 @param dhcp
411 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000412 # network resources should be set only once from callers
413 # in order to ensure that even if it's called multiple times in
414 # a chain of overloaded methods, the attribute is set only
415 # in the leaf class
416 if not self.network_resources:
417 self.network_resources = {
418 'network': network,
419 'router': router,
420 'subnet': subnet,
421 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500422
Mark Maglana5885eb32014-02-28 10:57:34 -0800423 def assertEmpty(self, list, msg=None):
424 self.assertTrue(len(list) == 0, msg)
425
426 def assertNotEmpty(self, list, msg=None):
427 self.assertTrue(len(list) > 0, msg)
428
Attila Fazekasdc216422013-01-29 15:12:14 +0100429
Marc Koderer24eb89c2014-01-31 11:23:33 +0100430class NegativeAutoTest(BaseTestCase):
431
432 _resources = {}
433
434 @classmethod
435 def setUpClass(cls):
436 super(NegativeAutoTest, cls).setUpClass()
437 os = cls.get_client_manager()
438 cls.client = os.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100439 os_admin = clients.AdminManager(interface=cls._interface,
440 service=cls._service)
441 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100442
443 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100444 def load_tests(*args):
445 """
446 Wrapper for testscenarios to set the mandatory scenarios variable
447 only in case a real test loader is in place. Will be automatically
448 called in case the variable "load_tests" is set.
449 """
450 if getattr(args[0], 'suiteClass', None) is not None:
451 loader, standard_tests, pattern = args
452 else:
453 standard_tests, module, loader = args
454 for test in testtools.iterate_tests(standard_tests):
Marc Koderer4f44d722014-08-07 14:04:58 +0200455 schema = getattr(test, '_schema', None)
Marc Koderer3dd31052014-11-27 09:31:00 +0100456 if schema is not None:
Marc Koderer4f44d722014-08-07 14:04:58 +0200457 setattr(test, 'scenarios',
458 NegativeAutoTest.generate_scenario(schema))
Marc Koderer674c8fc2014-03-17 09:45:04 +0100459 return testscenarios.load_tests_apply_scenarios(*args)
460
461 @staticmethod
Marc Koderer4f44d722014-08-07 14:04:58 +0200462 def generate_scenario(description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100463 """
464 Generates the test scenario list for a given description.
465
Marc Koderer4f44d722014-08-07 14:04:58 +0200466 :param description: A file or dictionary with the following entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100467 name (required) name for the api
468 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
469 url (required) the url to be appended to the catalog url with '%s'
470 for each resource mentioned
471 resources: (optional) A list of resource names such as "server",
472 "flavor", etc. with an element for each '%s' in the url. This
473 method will call self.get_resource for each element when
474 constructing the positive test case template so negative
475 subclasses are expected to return valid resource ids when
476 appropriate.
477 json-schema (optional) A valid json schema that will be used to
478 create invalid data for the api calls. For "GET" and "HEAD",
479 the data is used to generate query strings appended to the url,
480 otherwise for the body of the http call.
481 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100482 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100483 generator = importutils.import_class(
484 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100485 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100486 schema = description.get("json-schema", None)
487 resources = description.get("resources", [])
488 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100489 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100490 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100491 if isinstance(resource, dict):
492 expected_result = resource['expected_result']
493 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100494 LOG.debug("Add resource to test %s" % resource)
495 scn_name = "inv_res_%s" % (resource)
496 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100497 str(uuid.uuid4())),
498 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100499 }))
500 if schema is not None:
Marc Kodererf07f5d12014-09-01 09:47:23 +0200501 for scenario in generator.generate_scenarios(schema):
502 scenario_list.append((scenario['_negtest_name'],
503 scenario))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100504 LOG.debug(scenario_list)
505 return scenario_list
506
Marc Koderer4f44d722014-08-07 14:04:58 +0200507 def execute(self, description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100508 """
509 Execute a http call on an api that are expected to
510 result in client errors. First it uses invalid resources that are part
511 of the url, and then invalid data for queries and http request bodies.
512
Marc Koderer4f44d722014-08-07 14:04:58 +0200513 :param description: A json file or dictionary with the following
514 entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100515 name (required) name for the api
516 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
517 url (required) the url to be appended to the catalog url with '%s'
518 for each resource mentioned
519 resources: (optional) A list of resource names such as "server",
520 "flavor", etc. with an element for each '%s' in the url. This
521 method will call self.get_resource for each element when
522 constructing the positive test case template so negative
523 subclasses are expected to return valid resource ids when
524 appropriate.
525 json-schema (optional) A valid json schema that will be used to
526 create invalid data for the api calls. For "GET" and "HEAD",
527 the data is used to generate query strings appended to the url,
528 otherwise for the body of the http call.
529
530 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100531 LOG.info("Executing %s" % description["name"])
532 LOG.debug(description)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200533 generator = importutils.import_class(
534 CONF.negative.test_generator)()
535 schema = description.get("json-schema", None)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100536 method = description["http-method"]
537 url = description["url"]
Marc Kodererf07f5d12014-09-01 09:47:23 +0200538 expected_result = None
539 if "default_result_code" in description:
540 expected_result = description["default_result_code"]
Marc Koderer24eb89c2014-01-31 11:23:33 +0100541
542 resources = [self.get_resource(r) for
543 r in description.get("resources", [])]
544
545 if hasattr(self, "resource"):
546 # Note(mkoderer): The resources list already contains an invalid
547 # entry (see get_resource).
548 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100549 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100550 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100551 valid_schema = \
552 valid.ValidTestGenerator().generate_valid(schema)
553 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200554 elif hasattr(self, "_negtest_name"):
555 schema_under_test = \
556 valid.ValidTestGenerator().generate_valid(schema)
557 local_expected_result = \
558 generator.generate_payload(self, schema_under_test)
559 if local_expected_result is not None:
560 expected_result = local_expected_result
561 new_url, body = \
562 self._http_arguments(schema_under_test, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100563 else:
564 raise Exception("testscenarios are not active. Please make sure "
565 "that your test runner supports the load_tests "
566 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100567
Marc Kodererf857fda2014-03-05 15:58:00 +0100568 if "admin_client" in description and description["admin_client"]:
569 client = self.admin_client
570 else:
571 client = self.client
572 resp, resp_body = client.send_request(method, new_url,
573 resources, body=body)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200574 self._check_negative_response(expected_result, resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100575
576 def _http_arguments(self, json_dict, url, method):
577 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
578 if not json_dict:
579 return url, None
580 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
581 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
582 else:
583 return url, json.dumps(json_dict)
584
Marc Kodererf07f5d12014-09-01 09:47:23 +0200585 def _check_negative_response(self, expected_result, result, body):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100586 self.assertTrue(result >= 400 and result < 500 and result != 413,
587 "Expected client error, got %s:%s" %
588 (result, body))
589 self.assertTrue(expected_result is None or expected_result == result,
590 "Expected %s, got %s:%s" %
591 (expected_result, result, body))
592
593 @classmethod
594 def set_resource(cls, name, resource):
595 """
596 This function can be used in setUpClass context to register a resoruce
597 for a test.
598
599 :param name: The name of the kind of resource such as "flavor", "role",
600 etc.
601 :resource: The id of the resource
602 """
603 cls._resources[name] = resource
604
605 def get_resource(self, name):
606 """
607 Return a valid uuid for a type of resource. If a real resource is
608 needed as part of a url then this method should return one. Otherwise
609 it can return None.
610
611 :param name: The name of the kind of resource such as "flavor", "role",
612 etc.
613 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100614 if isinstance(name, dict):
615 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100616 if hasattr(self, "resource") and self.resource[0] == name:
617 LOG.debug("Return invalid resource (%s) value: %s" %
618 (self.resource[0], self.resource[1]))
619 return self.resource[1]
620 if name in self._resources:
621 return self._resources[name]
622 return None
623
624
Marc Kodererb2978da2014-03-26 13:45:43 +0100625def SimpleNegativeAutoTest(klass):
626 """
627 This decorator registers a test function on basis of the class name.
628 """
629 @attr(type=['negative', 'gate'])
630 def generic_test(self):
Marc Koderer4f44d722014-08-07 14:04:58 +0200631 if hasattr(self, '_schema'):
632 self.execute(self._schema)
Marc Kodererb2978da2014-03-26 13:45:43 +0100633
634 cn = klass.__name__
635 cn = cn.replace('JSON', '')
636 cn = cn.replace('Test', '')
637 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
638 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
639 func_name = 'test_%s' % lower_cn
640 setattr(klass, func_name, generic_test)
641 return klass
642
643
Sean Dague35a7caf2013-05-10 10:38:22 -0400644def call_until_true(func, duration, sleep_for):
645 """
646 Call the given function until it returns True (and return True) or
647 until the specified duration (in seconds) elapses (and return
648 False).
649
650 :param func: A zero argument callable that returns True on success.
651 :param duration: The number of seconds for which to attempt a
652 successful call of the function.
653 :param sleep_for: The number of seconds to sleep after an unsuccessful
654 invocation of the function.
655 """
656 now = time.time()
657 timeout = now + duration
658 while now < timeout:
659 if func():
660 return True
Sean Dague35a7caf2013-05-10 10:38:22 -0400661 time.sleep(sleep_for)
662 now = time.time()
663 return False