blob: f04aff7383ecb839d592d5f71a085474b8fd6cf0 [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
Chris Hoge296558c2015-02-19 00:29:49 -060027import six
Marc Koderer674c8fc2014-03-17 09:45:04 +010028import testscenarios
ivan-zhu1feeb382013-01-24 10:14:39 +080029import testtools
Jay Pipes051075a2012-04-28 17:39:37 -040030
Matthew Treinish3e046852013-07-23 16:00:24 -040031from tempest import clients
Andrea Frittoli8283b4e2014-07-17 13:28:58 +010032from tempest.common import credentials
Marc Koderer6ee82dc2014-02-17 10:26:29 +010033import tempest.common.generator.valid_generator as valid
Attila Fazekasdc216422013-01-29 15:12:14 +010034from tempest import config
Matthew Treinish16c43792013-09-09 19:55:23 +000035from tempest import exceptions
Marc Koderer6ee82dc2014-02-17 10:26:29 +010036from tempest.openstack.common import importutils
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040037from tempest.openstack.common import log as logging
Jay Pipes051075a2012-04-28 17:39:37 -040038
39LOG = logging.getLogger(__name__)
40
Sean Dague86bd8422013-12-20 09:56:44 -050041CONF = config.CONF
42
Jay Pipes051075a2012-04-28 17:39:37 -040043
Chris Yeoh55530bb2013-02-08 16:04:27 +103044def attr(*args, **kwargs):
Matthew Treinisha74f5d42014-02-07 20:25:44 -050045 """A decorator which applies the testtools attr decorator
Chris Yeoh55530bb2013-02-08 16:04:27 +103046
Matthew Treinisha74f5d42014-02-07 20:25:44 -050047 This decorator applies the testtools.testcase.attr if it is in the list of
48 attributes to testtools we want to apply.
Attila Fazekasb2902af2013-02-16 16:22:44 +010049 """
Chris Yeoh55530bb2013-02-08 16:04:27 +103050
51 def decorator(f):
Giulio Fidente4946a052013-05-14 12:23:51 +020052 if 'type' in kwargs and isinstance(kwargs['type'], str):
53 f = testtools.testcase.attr(kwargs['type'])(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093054 if kwargs['type'] == 'smoke':
55 f = testtools.testcase.attr('gate')(f)
Giulio Fidente4946a052013-05-14 12:23:51 +020056 elif 'type' in kwargs and isinstance(kwargs['type'], list):
57 for attr in kwargs['type']:
58 f = testtools.testcase.attr(attr)(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093059 if attr == 'smoke':
60 f = testtools.testcase.attr('gate')(f)
Matthew Treinisha74f5d42014-02-07 20:25:44 -050061 return f
Chris Yeoh55530bb2013-02-08 16:04:27 +103062
63 return decorator
64
65
Chris Hoge296558c2015-02-19 00:29:49 -060066def idempotent_id(id):
67 """Stub for metadata decorator"""
68 if not isinstance(id, six.string_types):
69 raise TypeError('Test idempotent_id must be string not %s'
70 '' % type(id).__name__)
71 uuid.UUID(id)
72
73 def decorator(f):
74 f = testtools.testcase.attr('id-%s' % id)(f)
75 if f.__doc__:
76 f.__doc__ = 'Test idempotent id: %s\n%s' % (id, f.__doc__)
77 else:
78 f.__doc__ = 'Test idempotent id: %s' % id
79 return f
80 return decorator
81
82
Matthew Treinish3d8c7322014-08-03 23:53:28 -040083def get_service_list():
Matthew Treinish8afbffd2014-01-21 23:56:13 +000084 service_list = {
85 'compute': CONF.service_available.nova,
86 'image': CONF.service_available.glance,
Adam Gandelman4a48a602014-03-20 18:23:18 -070087 'baremetal': CONF.service_available.ironic,
Matthew Treinish8afbffd2014-01-21 23:56:13 +000088 'volume': CONF.service_available.cinder,
89 'orchestration': CONF.service_available.heat,
90 # NOTE(mtreinish) nova-network will provide networking functionality
91 # if neutron isn't available, so always set to True.
92 'network': True,
93 'identity': True,
94 'object_storage': CONF.service_available.swift,
95 'dashboard': CONF.service_available.horizon,
ekhugen7aff0992014-08-04 19:01:57 +000096 'telemetry': CONF.service_available.ceilometer,
Yaroslav Lobankovff42c872014-06-17 15:39:43 +040097 'data_processing': CONF.service_available.sahara
Matthew Treinish8afbffd2014-01-21 23:56:13 +000098 }
Matthew Treinish3d8c7322014-08-03 23:53:28 -040099 return service_list
Matthew Treinish16c43792013-09-09 19:55:23 +0000100
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400101
102def services(*args, **kwargs):
103 """A decorator used to set an attr for each service used in a test case
104
105 This decorator applies a testtools attr for each service that gets
106 exercised by a test case.
107 """
Matthew Treinish16c43792013-09-09 19:55:23 +0000108 def decorator(f):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400109 services = ['compute', 'image', 'baremetal', 'volume', 'orchestration',
110 'network', 'identity', 'object_storage', 'dashboard',
Matthew Treinish60359052014-09-18 17:39:26 -0400111 'telemetry', 'data_processing']
Matthew Treinish16c43792013-09-09 19:55:23 +0000112 for service in args:
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400113 if service not in services:
114 raise exceptions.InvalidServiceTag('%s is not a valid '
115 'service' % service)
Matthew Treinish16c43792013-09-09 19:55:23 +0000116 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000117
118 @functools.wraps(f)
119 def wrapper(self, *func_args, **func_kwargs):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400120 service_list = get_service_list()
121
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000122 for service in args:
123 if not service_list[service]:
124 msg = 'Skipped because the %s service is not available' % (
125 service)
126 raise testtools.TestCase.skipException(msg)
127 return f(self, *func_args, **func_kwargs)
128 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000129 return decorator
130
131
Marc Koderer32221b8e2013-08-23 13:57:50 +0200132def stresstest(*args, **kwargs):
133 """Add stress test decorator
134
135 For all functions with this decorator a attr stress will be
136 set automatically.
137
138 @param class_setup_per: allowed values are application, process, action
139 ``application``: once in the stress job lifetime
140 ``process``: once in the worker process lifetime
141 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200142 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200143 """
144 def decorator(f):
145 if 'class_setup_per' in kwargs:
146 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
147 else:
148 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200149 if 'allow_inheritance' in kwargs:
150 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
151 else:
152 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200153 attr(type='stress')(f)
154 return f
155 return decorator
156
157
Matthew Treinishe3d26142013-11-26 19:14:58 +0000158def requires_ext(*args, **kwargs):
159 """A decorator to skip tests if an extension is not enabled
160
161 @param extension
162 @param service
163 """
164 def decorator(func):
165 @functools.wraps(func)
166 def wrapper(*func_args, **func_kwargs):
167 if not is_extension_enabled(kwargs['extension'],
168 kwargs['service']):
169 msg = "Skipped because %s extension: %s is not enabled" % (
170 kwargs['service'], kwargs['extension'])
171 raise testtools.TestCase.skipException(msg)
172 return func(*func_args, **func_kwargs)
173 return wrapper
174 return decorator
175
176
177def is_extension_enabled(extension_name, service):
178 """A function that will check the list of enabled extensions from config
179
180 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000181 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000182 'compute': CONF.compute_feature_enabled.api_extensions,
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000183 'volume': CONF.volume_feature_enabled.api_extensions,
184 'network': CONF.network_feature_enabled.api_extensions,
185 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000186 }
Simeon Monov5d7effe2014-07-16 07:32:38 +0300187 if len(config_dict[service]) == 0:
188 return False
Matthew Treinishe3d26142013-11-26 19:14:58 +0000189 if config_dict[service][0] == 'all':
190 return True
191 if extension_name in config_dict[service]:
192 return True
193 return False
194
Ian Wienand98c35f32013-07-23 20:34:23 +1000195
Attila Fazekasf86fa312013-07-30 19:56:39 +0200196at_exit_set = set()
197
198
199def validate_tearDownClass():
200 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400201 LOG.error(
202 "tearDownClass does not call the super's "
203 "tearDownClass in these classes: \n"
204 + str(at_exit_set))
205
Attila Fazekasf86fa312013-07-30 19:56:39 +0200206
207atexit.register(validate_tearDownClass)
208
Attila Fazekas53943322014-02-10 16:07:34 +0100209
Matthew Treinish2474f412014-11-17 18:11:56 -0500210class BaseTestCase(testtools.testcase.WithAttributes,
211 testtools.TestCase):
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100212 """The test base class defines Tempest framework for class level fixtures.
213 `setUpClass` and `tearDownClass` are defined here and cannot be overwritten
214 by subclasses (enforced via hacking rule T105).
215
216 Set-up is split in a series of steps (setup stages), which can be
217 overwritten by test classes. Set-up stages are:
218 - skip_checks
219 - setup_credentials
220 - setup_clients
221 - resource_setup
222
223 Tear-down is also split in a series of steps (teardown stages), which are
224 stacked for execution only if the corresponding setup stage had been
225 reached during the setup phase. Tear-down stages are:
226 - clear_isolated_creds (defined in the base test class)
227 - resource_cleanup
228 """
Attila Fazekasc43fec82013-04-09 23:17:52 +0200229
Attila Fazekasf86fa312013-07-30 19:56:39 +0200230 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100231 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200232
Matthew Treinish9f756a02014-01-15 10:26:07 -0500233 network_resources = {}
234
Sean Dague2ef32ac2014-06-09 11:32:23 -0400235 # NOTE(sdague): log_format is defined inline here instead of using the oslo
236 # default because going through the config path recouples config to the
237 # stress tests too early, and depending on testr order will fail unit tests
238 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
239 '[%(name)s] %(message)s')
240
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200241 @classmethod
242 def setUpClass(cls):
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100243 # It should never be overridden by descendants
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200244 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
245 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200246 cls.setUpClassCalled = True
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100247 # Stack of (name, callable) to be invoked in reverse order at teardown
248 cls.teardowns = []
249 # All the configuration checks that may generate a skip
250 cls.skip_checks()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100251 try:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100252 # Allocation of all required credentials and client managers
253 cls.teardowns.append(('credentials', cls.clear_isolated_creds))
254 cls.setup_credentials()
255 # Shortcuts to clients
256 cls.setup_clients()
257 # Additional class-wide test resources
258 cls.teardowns.append(('resources', cls.resource_cleanup))
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100259 cls.resource_setup()
260 except Exception:
261 etype, value, trace = sys.exc_info()
Matthew Treinished2ad4f2014-12-23 15:18:32 -0500262 LOG.info("%s raised in %s.setUpClass. Invoking tearDownClass." % (
263 etype, cls.__name__))
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100264 cls.tearDownClass()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100265 try:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100266 raise etype, value, trace
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100267 finally:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100268 del trace # to avoid circular refs
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200269
Attila Fazekasf86fa312013-07-30 19:56:39 +0200270 @classmethod
271 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200272 at_exit_set.discard(cls)
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100273 # It should never be overridden by descendants
Attila Fazekasf86fa312013-07-30 19:56:39 +0200274 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
275 super(BaseTestCase, cls).tearDownClass()
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100276 # Save any existing exception, we always want to re-raise the original
277 # exception only
278 etype, value, trace = sys.exc_info()
279 # If there was no exception during setup we shall re-raise the first
280 # exception in teardown
281 re_raise = (etype is None)
282 while cls.teardowns:
283 name, teardown = cls.teardowns.pop()
284 # Catch any exception in tearDown so we can re-raise the original
285 # exception at the end
286 try:
287 teardown()
288 except Exception as te:
289 sys_exec_info = sys.exc_info()
290 tetype = sys_exec_info[0]
291 # TODO(andreaf): Till we have the ability to cleanup only
292 # resources that were successfully setup in resource_cleanup,
293 # log AttributeError as info instead of exception.
294 if tetype is AttributeError and name == 'resources':
295 LOG.info("tearDownClass of %s failed: %s" % (name, te))
296 else:
297 LOG.exception("teardown of %s failed: %s" % (name, te))
298 if not etype:
299 etype, value, trace = sys_exec_info
300 # If exceptions were raised during teardown, an not before, re-raise
301 # the first one
302 if re_raise and etype is not None:
303 try:
304 raise etype, value, trace
305 finally:
306 del trace # to avoid circular refs
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100307
308 @classmethod
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100309 def skip_checks(cls):
310 """Class level skip checks. Subclasses verify in here all
311 conditions that might prevent the execution of the entire test class.
312 Checks implemented here may not make use API calls, and should rely on
313 configuration alone.
314 In general skip checks that require an API call are discouraged.
315 If one is really needed it may be implemented either in the
316 resource_setup or at test level.
317 """
318 pass
319
320 @classmethod
321 def setup_credentials(cls):
322 """Allocate credentials and the client managers from them."""
323 # TODO(andreaf) There is a fair amount of code that could me moved from
324 # base / test classes in here. Ideally tests should be able to only
325 # specify a list of (additional) credentials the need to use.
326 pass
327
328 @classmethod
329 def setup_clients(cls):
330 """Create links to the clients into the test object."""
331 # TODO(andreaf) There is a fair amount of code that could me moved from
332 # base / test classes in here. Ideally tests should be able to only
333 # specify which client is `client` and nothing else.
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100334 pass
Attila Fazekasf86fa312013-07-30 19:56:39 +0200335
Emily Hugenbruch5bd4cbf2014-12-17 21:38:38 +0000336 @classmethod
337 def resource_setup(cls):
338 """Class level resource setup for test cases.
339 """
340 pass
341
342 @classmethod
343 def resource_cleanup(cls):
344 """Class level resource cleanup for test cases.
345 Resource cleanup must be able to handle the case of partially setup
346 resources, in case a failure during `resource_setup` should happen.
347 """
348 pass
349
Attila Fazekasf86fa312013-07-30 19:56:39 +0200350 def setUp(self):
351 super(BaseTestCase, self).setUp()
352 if not self.setUpClassCalled:
353 raise RuntimeError("setUpClass does not calls the super's"
354 "setUpClass in the "
355 + self.__class__.__name__)
356 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400357 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
358 try:
359 test_timeout = int(test_timeout)
360 except ValueError:
361 test_timeout = 0
362 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200363 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400364
365 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
366 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200367 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
368 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400369 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
370 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200371 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
372 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200373 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
374 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200375 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400376 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200377 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400378
Matthew Treinish3e046852013-07-23 16:00:24 -0400379 @classmethod
Andrea Frittolic0978352015-02-06 15:57:40 +0000380 def get_client_manager(cls):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700381 """
tanlin4956a642014-02-13 16:52:11 +0800382 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700383 """
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700384 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100385
Marc Koderer44dce622014-11-14 10:08:12 +0100386 if (not hasattr(cls, 'isolated_creds') or
387 not cls.isolated_creds.name == cls.__name__):
388 cls.isolated_creds = credentials.get_isolated_credentials(
389 name=cls.__name__, network_resources=cls.network_resources,
390 force_tenant_isolation=force_tenant_isolation,
391 )
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100392
393 creds = cls.isolated_creds.get_primary_creds()
Andrea Frittolic0978352015-02-06 15:57:40 +0000394 os = clients.Manager(credentials=creds, service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700395 return os
396
397 @classmethod
398 def clear_isolated_creds(cls):
399 """
400 Clears isolated creds if set
401 """
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100402 if hasattr(cls, 'isolated_creds'):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700403 cls.isolated_creds.clear_isolated_creds()
404
405 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400406 def _get_identity_admin_client(cls):
407 """
408 Returns an instance of the Identity Admin API client
409 """
Andrea Frittolic0978352015-02-06 15:57:40 +0000410 os = clients.AdminManager(service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400411 admin_client = os.identity_client
412 return admin_client
413
414 @classmethod
Andrea Frittoli7d5ed592015-02-10 01:10:23 +0000415 def set_network_resources(cls, network=False, router=False, subnet=False,
Matthew Treinish9f756a02014-01-15 10:26:07 -0500416 dhcp=False):
417 """Specify which network resources should be created
418
419 @param network
420 @param router
421 @param subnet
422 @param dhcp
423 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000424 # network resources should be set only once from callers
425 # in order to ensure that even if it's called multiple times in
426 # a chain of overloaded methods, the attribute is set only
427 # in the leaf class
Andrea Frittoli7d5ed592015-02-10 01:10:23 +0000428 if not cls.network_resources:
429 cls.network_resources = {
Salvatore Orlando5a337242014-01-15 22:49:22 +0000430 'network': network,
431 'router': router,
432 'subnet': subnet,
433 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500434
Mark Maglana5885eb32014-02-28 10:57:34 -0800435 def assertEmpty(self, list, msg=None):
436 self.assertTrue(len(list) == 0, msg)
437
438 def assertNotEmpty(self, list, msg=None):
439 self.assertTrue(len(list) > 0, msg)
440
Attila Fazekasdc216422013-01-29 15:12:14 +0100441
Marc Koderer24eb89c2014-01-31 11:23:33 +0100442class NegativeAutoTest(BaseTestCase):
443
444 _resources = {}
445
446 @classmethod
447 def setUpClass(cls):
448 super(NegativeAutoTest, cls).setUpClass()
449 os = cls.get_client_manager()
450 cls.client = os.negative_client
Andrea Frittolic0978352015-02-06 15:57:40 +0000451 os_admin = clients.AdminManager(service=cls._service)
Marc Kodererf857fda2014-03-05 15:58:00 +0100452 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100453
454 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100455 def load_tests(*args):
456 """
457 Wrapper for testscenarios to set the mandatory scenarios variable
458 only in case a real test loader is in place. Will be automatically
459 called in case the variable "load_tests" is set.
460 """
461 if getattr(args[0], 'suiteClass', None) is not None:
462 loader, standard_tests, pattern = args
463 else:
464 standard_tests, module, loader = args
465 for test in testtools.iterate_tests(standard_tests):
Marc Koderer4f44d722014-08-07 14:04:58 +0200466 schema = getattr(test, '_schema', None)
Marc Koderer3dd31052014-11-27 09:31:00 +0100467 if schema is not None:
Marc Koderer4f44d722014-08-07 14:04:58 +0200468 setattr(test, 'scenarios',
469 NegativeAutoTest.generate_scenario(schema))
Marc Koderer674c8fc2014-03-17 09:45:04 +0100470 return testscenarios.load_tests_apply_scenarios(*args)
471
472 @staticmethod
Marc Koderer4f44d722014-08-07 14:04:58 +0200473 def generate_scenario(description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100474 """
475 Generates the test scenario list for a given description.
476
Marc Koderer4f44d722014-08-07 14:04:58 +0200477 :param description: A file or dictionary with the following entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100478 name (required) name for the api
479 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
480 url (required) the url to be appended to the catalog url with '%s'
481 for each resource mentioned
482 resources: (optional) A list of resource names such as "server",
483 "flavor", etc. with an element for each '%s' in the url. This
484 method will call self.get_resource for each element when
485 constructing the positive test case template so negative
486 subclasses are expected to return valid resource ids when
487 appropriate.
488 json-schema (optional) A valid json schema that will be used to
489 create invalid data for the api calls. For "GET" and "HEAD",
490 the data is used to generate query strings appended to the url,
491 otherwise for the body of the http call.
492 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100493 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100494 generator = importutils.import_class(
495 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100496 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100497 schema = description.get("json-schema", None)
498 resources = description.get("resources", [])
499 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100500 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100501 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100502 if isinstance(resource, dict):
503 expected_result = resource['expected_result']
504 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100505 LOG.debug("Add resource to test %s" % resource)
506 scn_name = "inv_res_%s" % (resource)
507 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100508 str(uuid.uuid4())),
509 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100510 }))
511 if schema is not None:
Marc Kodererf07f5d12014-09-01 09:47:23 +0200512 for scenario in generator.generate_scenarios(schema):
513 scenario_list.append((scenario['_negtest_name'],
514 scenario))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100515 LOG.debug(scenario_list)
516 return scenario_list
517
Marc Koderer4f44d722014-08-07 14:04:58 +0200518 def execute(self, description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100519 """
520 Execute a http call on an api that are expected to
521 result in client errors. First it uses invalid resources that are part
522 of the url, and then invalid data for queries and http request bodies.
523
Marc Koderer4f44d722014-08-07 14:04:58 +0200524 :param description: A json file or dictionary with the following
525 entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100526 name (required) name for the api
527 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
528 url (required) the url to be appended to the catalog url with '%s'
529 for each resource mentioned
530 resources: (optional) A list of resource names such as "server",
531 "flavor", etc. with an element for each '%s' in the url. This
532 method will call self.get_resource for each element when
533 constructing the positive test case template so negative
534 subclasses are expected to return valid resource ids when
535 appropriate.
536 json-schema (optional) A valid json schema that will be used to
537 create invalid data for the api calls. For "GET" and "HEAD",
538 the data is used to generate query strings appended to the url,
539 otherwise for the body of the http call.
540
541 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100542 LOG.info("Executing %s" % description["name"])
543 LOG.debug(description)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200544 generator = importutils.import_class(
545 CONF.negative.test_generator)()
546 schema = description.get("json-schema", None)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100547 method = description["http-method"]
548 url = description["url"]
Marc Kodererf07f5d12014-09-01 09:47:23 +0200549 expected_result = None
550 if "default_result_code" in description:
551 expected_result = description["default_result_code"]
Marc Koderer24eb89c2014-01-31 11:23:33 +0100552
553 resources = [self.get_resource(r) for
554 r in description.get("resources", [])]
555
556 if hasattr(self, "resource"):
557 # Note(mkoderer): The resources list already contains an invalid
558 # entry (see get_resource).
559 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100560 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100561 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100562 valid_schema = \
563 valid.ValidTestGenerator().generate_valid(schema)
564 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200565 elif hasattr(self, "_negtest_name"):
566 schema_under_test = \
567 valid.ValidTestGenerator().generate_valid(schema)
568 local_expected_result = \
569 generator.generate_payload(self, schema_under_test)
570 if local_expected_result is not None:
571 expected_result = local_expected_result
572 new_url, body = \
573 self._http_arguments(schema_under_test, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100574 else:
575 raise Exception("testscenarios are not active. Please make sure "
576 "that your test runner supports the load_tests "
577 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100578
Marc Kodererf857fda2014-03-05 15:58:00 +0100579 if "admin_client" in description and description["admin_client"]:
580 client = self.admin_client
581 else:
582 client = self.client
583 resp, resp_body = client.send_request(method, new_url,
584 resources, body=body)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200585 self._check_negative_response(expected_result, resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100586
587 def _http_arguments(self, json_dict, url, method):
588 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
589 if not json_dict:
590 return url, None
591 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
592 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
593 else:
594 return url, json.dumps(json_dict)
595
Marc Kodererf07f5d12014-09-01 09:47:23 +0200596 def _check_negative_response(self, expected_result, result, body):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100597 self.assertTrue(result >= 400 and result < 500 and result != 413,
598 "Expected client error, got %s:%s" %
599 (result, body))
600 self.assertTrue(expected_result is None or expected_result == result,
601 "Expected %s, got %s:%s" %
602 (expected_result, result, body))
603
604 @classmethod
605 def set_resource(cls, name, resource):
606 """
607 This function can be used in setUpClass context to register a resoruce
608 for a test.
609
610 :param name: The name of the kind of resource such as "flavor", "role",
611 etc.
612 :resource: The id of the resource
613 """
614 cls._resources[name] = resource
615
616 def get_resource(self, name):
617 """
618 Return a valid uuid for a type of resource. If a real resource is
619 needed as part of a url then this method should return one. Otherwise
620 it can return None.
621
622 :param name: The name of the kind of resource such as "flavor", "role",
623 etc.
624 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100625 if isinstance(name, dict):
626 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100627 if hasattr(self, "resource") and self.resource[0] == name:
628 LOG.debug("Return invalid resource (%s) value: %s" %
629 (self.resource[0], self.resource[1]))
630 return self.resource[1]
631 if name in self._resources:
632 return self._resources[name]
633 return None
634
635
Marc Kodererb2978da2014-03-26 13:45:43 +0100636def SimpleNegativeAutoTest(klass):
637 """
638 This decorator registers a test function on basis of the class name.
639 """
640 @attr(type=['negative', 'gate'])
641 def generic_test(self):
Marc Koderer4f44d722014-08-07 14:04:58 +0200642 if hasattr(self, '_schema'):
643 self.execute(self._schema)
Marc Kodererb2978da2014-03-26 13:45:43 +0100644
645 cn = klass.__name__
646 cn = cn.replace('JSON', '')
647 cn = cn.replace('Test', '')
648 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
649 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
650 func_name = 'test_%s' % lower_cn
651 setattr(klass, func_name, generic_test)
652 return klass
653
654
Sean Dague35a7caf2013-05-10 10:38:22 -0400655def call_until_true(func, duration, sleep_for):
656 """
657 Call the given function until it returns True (and return True) or
658 until the specified duration (in seconds) elapses (and return
659 False).
660
661 :param func: A zero argument callable that returns True on success.
662 :param duration: The number of seconds for which to attempt a
663 successful call of the function.
664 :param sleep_for: The number of seconds to sleep after an unsuccessful
665 invocation of the function.
666 """
667 now = time.time()
668 timeout = now + duration
669 while now < timeout:
670 if func():
671 return True
Sean Dague35a7caf2013-05-10 10:38:22 -0400672 time.sleep(sleep_for)
673 now = time.time()
674 return False