blob: e9e46ab9895ec6447c29a3547b032641151dc940 [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,
Matthew Treinishb66c94e2015-03-11 13:00:48 -040097 'data_processing': CONF.service_available.sahara,
98 'database': CONF.service_available.trove
Matthew Treinish8afbffd2014-01-21 23:56:13 +000099 }
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400100 return service_list
Matthew Treinish16c43792013-09-09 19:55:23 +0000101
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400102
103def services(*args, **kwargs):
104 """A decorator used to set an attr for each service used in a test case
105
106 This decorator applies a testtools attr for each service that gets
107 exercised by a test case.
108 """
Matthew Treinish16c43792013-09-09 19:55:23 +0000109 def decorator(f):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400110 services = ['compute', 'image', 'baremetal', 'volume', 'orchestration',
111 'network', 'identity', 'object_storage', 'dashboard',
Matthew Treinishb66c94e2015-03-11 13:00:48 -0400112 'telemetry', 'data_processing', 'database']
Matthew Treinish16c43792013-09-09 19:55:23 +0000113 for service in args:
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400114 if service not in services:
115 raise exceptions.InvalidServiceTag('%s is not a valid '
116 'service' % service)
Matthew Treinish16c43792013-09-09 19:55:23 +0000117 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000118
119 @functools.wraps(f)
120 def wrapper(self, *func_args, **func_kwargs):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400121 service_list = get_service_list()
122
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000123 for service in args:
124 if not service_list[service]:
125 msg = 'Skipped because the %s service is not available' % (
126 service)
127 raise testtools.TestCase.skipException(msg)
128 return f(self, *func_args, **func_kwargs)
129 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000130 return decorator
131
132
Marc Koderer32221b8e2013-08-23 13:57:50 +0200133def stresstest(*args, **kwargs):
134 """Add stress test decorator
135
136 For all functions with this decorator a attr stress will be
137 set automatically.
138
139 @param class_setup_per: allowed values are application, process, action
140 ``application``: once in the stress job lifetime
141 ``process``: once in the worker process lifetime
142 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200143 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200144 """
145 def decorator(f):
146 if 'class_setup_per' in kwargs:
147 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
148 else:
149 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200150 if 'allow_inheritance' in kwargs:
151 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
152 else:
153 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200154 attr(type='stress')(f)
155 return f
156 return decorator
157
158
Matthew Treinishe3d26142013-11-26 19:14:58 +0000159def requires_ext(*args, **kwargs):
160 """A decorator to skip tests if an extension is not enabled
161
162 @param extension
163 @param service
164 """
165 def decorator(func):
166 @functools.wraps(func)
167 def wrapper(*func_args, **func_kwargs):
168 if not is_extension_enabled(kwargs['extension'],
169 kwargs['service']):
170 msg = "Skipped because %s extension: %s is not enabled" % (
171 kwargs['service'], kwargs['extension'])
172 raise testtools.TestCase.skipException(msg)
173 return func(*func_args, **func_kwargs)
174 return wrapper
175 return decorator
176
177
178def is_extension_enabled(extension_name, service):
179 """A function that will check the list of enabled extensions from config
180
181 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000182 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000183 'compute': CONF.compute_feature_enabled.api_extensions,
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000184 'volume': CONF.volume_feature_enabled.api_extensions,
185 'network': CONF.network_feature_enabled.api_extensions,
186 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000187 }
Simeon Monov5d7effe2014-07-16 07:32:38 +0300188 if len(config_dict[service]) == 0:
189 return False
Matthew Treinishe3d26142013-11-26 19:14:58 +0000190 if config_dict[service][0] == 'all':
191 return True
192 if extension_name in config_dict[service]:
193 return True
194 return False
195
Ian Wienand98c35f32013-07-23 20:34:23 +1000196
Attila Fazekasf86fa312013-07-30 19:56:39 +0200197at_exit_set = set()
198
199
200def validate_tearDownClass():
201 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400202 LOG.error(
203 "tearDownClass does not call the super's "
204 "tearDownClass in these classes: \n"
205 + str(at_exit_set))
206
Attila Fazekasf86fa312013-07-30 19:56:39 +0200207
208atexit.register(validate_tearDownClass)
209
Attila Fazekas53943322014-02-10 16:07:34 +0100210
Matthew Treinish2474f412014-11-17 18:11:56 -0500211class BaseTestCase(testtools.testcase.WithAttributes,
212 testtools.TestCase):
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100213 """The test base class defines Tempest framework for class level fixtures.
214 `setUpClass` and `tearDownClass` are defined here and cannot be overwritten
215 by subclasses (enforced via hacking rule T105).
216
217 Set-up is split in a series of steps (setup stages), which can be
218 overwritten by test classes. Set-up stages are:
219 - skip_checks
220 - setup_credentials
221 - setup_clients
222 - resource_setup
223
224 Tear-down is also split in a series of steps (teardown stages), which are
225 stacked for execution only if the corresponding setup stage had been
226 reached during the setup phase. Tear-down stages are:
227 - clear_isolated_creds (defined in the base test class)
228 - resource_cleanup
229 """
Attila Fazekasc43fec82013-04-09 23:17:52 +0200230
Attila Fazekasf86fa312013-07-30 19:56:39 +0200231 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100232 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200233
Matthew Treinish9f756a02014-01-15 10:26:07 -0500234 network_resources = {}
235
Sean Dague2ef32ac2014-06-09 11:32:23 -0400236 # NOTE(sdague): log_format is defined inline here instead of using the oslo
237 # default because going through the config path recouples config to the
238 # stress tests too early, and depending on testr order will fail unit tests
239 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
240 '[%(name)s] %(message)s')
241
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200242 @classmethod
243 def setUpClass(cls):
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100244 # It should never be overridden by descendants
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200245 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
246 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200247 cls.setUpClassCalled = True
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100248 # Stack of (name, callable) to be invoked in reverse order at teardown
249 cls.teardowns = []
250 # All the configuration checks that may generate a skip
251 cls.skip_checks()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100252 try:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100253 # Allocation of all required credentials and client managers
254 cls.teardowns.append(('credentials', cls.clear_isolated_creds))
255 cls.setup_credentials()
256 # Shortcuts to clients
257 cls.setup_clients()
258 # Additional class-wide test resources
259 cls.teardowns.append(('resources', cls.resource_cleanup))
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100260 cls.resource_setup()
261 except Exception:
262 etype, value, trace = sys.exc_info()
Matthew Treinished2ad4f2014-12-23 15:18:32 -0500263 LOG.info("%s raised in %s.setUpClass. Invoking tearDownClass." % (
264 etype, cls.__name__))
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100265 cls.tearDownClass()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100266 try:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100267 raise etype, value, trace
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100268 finally:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100269 del trace # to avoid circular refs
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200270
Attila Fazekasf86fa312013-07-30 19:56:39 +0200271 @classmethod
272 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200273 at_exit_set.discard(cls)
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100274 # It should never be overridden by descendants
Attila Fazekasf86fa312013-07-30 19:56:39 +0200275 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
276 super(BaseTestCase, cls).tearDownClass()
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100277 # Save any existing exception, we always want to re-raise the original
278 # exception only
279 etype, value, trace = sys.exc_info()
280 # If there was no exception during setup we shall re-raise the first
281 # exception in teardown
282 re_raise = (etype is None)
283 while cls.teardowns:
284 name, teardown = cls.teardowns.pop()
285 # Catch any exception in tearDown so we can re-raise the original
286 # exception at the end
287 try:
288 teardown()
289 except Exception as te:
290 sys_exec_info = sys.exc_info()
291 tetype = sys_exec_info[0]
292 # TODO(andreaf): Till we have the ability to cleanup only
293 # resources that were successfully setup in resource_cleanup,
294 # log AttributeError as info instead of exception.
295 if tetype is AttributeError and name == 'resources':
296 LOG.info("tearDownClass of %s failed: %s" % (name, te))
297 else:
298 LOG.exception("teardown of %s failed: %s" % (name, te))
299 if not etype:
300 etype, value, trace = sys_exec_info
301 # If exceptions were raised during teardown, an not before, re-raise
302 # the first one
303 if re_raise and etype is not None:
304 try:
305 raise etype, value, trace
306 finally:
307 del trace # to avoid circular refs
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100308
309 @classmethod
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100310 def skip_checks(cls):
311 """Class level skip checks. Subclasses verify in here all
312 conditions that might prevent the execution of the entire test class.
313 Checks implemented here may not make use API calls, and should rely on
314 configuration alone.
315 In general skip checks that require an API call are discouraged.
316 If one is really needed it may be implemented either in the
317 resource_setup or at test level.
318 """
319 pass
320
321 @classmethod
322 def setup_credentials(cls):
323 """Allocate credentials and the client managers from them."""
324 # TODO(andreaf) There is a fair amount of code that could me moved from
325 # base / test classes in here. Ideally tests should be able to only
326 # specify a list of (additional) credentials the need to use.
327 pass
328
329 @classmethod
330 def setup_clients(cls):
331 """Create links to the clients into the test object."""
332 # TODO(andreaf) There is a fair amount of code that could me moved from
333 # base / test classes in here. Ideally tests should be able to only
334 # specify which client is `client` and nothing else.
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100335 pass
Attila Fazekasf86fa312013-07-30 19:56:39 +0200336
Emily Hugenbruch5bd4cbf2014-12-17 21:38:38 +0000337 @classmethod
338 def resource_setup(cls):
339 """Class level resource setup for test cases.
340 """
341 pass
342
343 @classmethod
344 def resource_cleanup(cls):
345 """Class level resource cleanup for test cases.
346 Resource cleanup must be able to handle the case of partially setup
347 resources, in case a failure during `resource_setup` should happen.
348 """
349 pass
350
Attila Fazekasf86fa312013-07-30 19:56:39 +0200351 def setUp(self):
352 super(BaseTestCase, self).setUp()
353 if not self.setUpClassCalled:
354 raise RuntimeError("setUpClass does not calls the super's"
355 "setUpClass in the "
356 + self.__class__.__name__)
357 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400358 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
359 try:
360 test_timeout = int(test_timeout)
361 except ValueError:
362 test_timeout = 0
363 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200364 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400365
366 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
367 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200368 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
369 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400370 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
371 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200372 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
373 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200374 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
375 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200376 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400377 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200378 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400379
Matthew Treinish3e046852013-07-23 16:00:24 -0400380 @classmethod
Andrea Frittolic0978352015-02-06 15:57:40 +0000381 def get_client_manager(cls):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700382 """
tanlin4956a642014-02-13 16:52:11 +0800383 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700384 """
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700385 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100386
Marc Koderer44dce622014-11-14 10:08:12 +0100387 if (not hasattr(cls, 'isolated_creds') or
388 not cls.isolated_creds.name == cls.__name__):
389 cls.isolated_creds = credentials.get_isolated_credentials(
390 name=cls.__name__, network_resources=cls.network_resources,
391 force_tenant_isolation=force_tenant_isolation,
392 )
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100393
394 creds = cls.isolated_creds.get_primary_creds()
Andrea Frittolic0978352015-02-06 15:57:40 +0000395 os = clients.Manager(credentials=creds, service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700396 return os
397
398 @classmethod
399 def clear_isolated_creds(cls):
400 """
401 Clears isolated creds if set
402 """
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100403 if hasattr(cls, 'isolated_creds'):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700404 cls.isolated_creds.clear_isolated_creds()
405
406 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400407 def _get_identity_admin_client(cls):
408 """
409 Returns an instance of the Identity Admin API client
410 """
Andrea Frittolic0978352015-02-06 15:57:40 +0000411 os = clients.AdminManager(service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400412 admin_client = os.identity_client
413 return admin_client
414
415 @classmethod
Andrea Frittoli7d5ed592015-02-10 01:10:23 +0000416 def set_network_resources(cls, network=False, router=False, subnet=False,
Matthew Treinish9f756a02014-01-15 10:26:07 -0500417 dhcp=False):
418 """Specify which network resources should be created
419
420 @param network
421 @param router
422 @param subnet
423 @param dhcp
424 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000425 # network resources should be set only once from callers
426 # in order to ensure that even if it's called multiple times in
427 # a chain of overloaded methods, the attribute is set only
428 # in the leaf class
Andrea Frittoli7d5ed592015-02-10 01:10:23 +0000429 if not cls.network_resources:
430 cls.network_resources = {
Salvatore Orlando5a337242014-01-15 22:49:22 +0000431 'network': network,
432 'router': router,
433 'subnet': subnet,
434 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500435
Mark Maglana5885eb32014-02-28 10:57:34 -0800436 def assertEmpty(self, list, msg=None):
437 self.assertTrue(len(list) == 0, msg)
438
439 def assertNotEmpty(self, list, msg=None):
440 self.assertTrue(len(list) > 0, msg)
441
Attila Fazekasdc216422013-01-29 15:12:14 +0100442
Marc Koderer24eb89c2014-01-31 11:23:33 +0100443class NegativeAutoTest(BaseTestCase):
444
445 _resources = {}
446
447 @classmethod
448 def setUpClass(cls):
449 super(NegativeAutoTest, cls).setUpClass()
450 os = cls.get_client_manager()
451 cls.client = os.negative_client
Andrea Frittolic0978352015-02-06 15:57:40 +0000452 os_admin = clients.AdminManager(service=cls._service)
Marc Kodererf857fda2014-03-05 15:58:00 +0100453 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100454
455 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100456 def load_tests(*args):
457 """
458 Wrapper for testscenarios to set the mandatory scenarios variable
459 only in case a real test loader is in place. Will be automatically
460 called in case the variable "load_tests" is set.
461 """
462 if getattr(args[0], 'suiteClass', None) is not None:
463 loader, standard_tests, pattern = args
464 else:
465 standard_tests, module, loader = args
466 for test in testtools.iterate_tests(standard_tests):
Marc Koderer4f44d722014-08-07 14:04:58 +0200467 schema = getattr(test, '_schema', None)
Marc Koderer3dd31052014-11-27 09:31:00 +0100468 if schema is not None:
Marc Koderer4f44d722014-08-07 14:04:58 +0200469 setattr(test, 'scenarios',
470 NegativeAutoTest.generate_scenario(schema))
Marc Koderer674c8fc2014-03-17 09:45:04 +0100471 return testscenarios.load_tests_apply_scenarios(*args)
472
473 @staticmethod
Marc Koderer4f44d722014-08-07 14:04:58 +0200474 def generate_scenario(description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100475 """
476 Generates the test scenario list for a given description.
477
Marc Koderer4f44d722014-08-07 14:04:58 +0200478 :param description: A file or dictionary with the following entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100479 name (required) name for the api
480 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
481 url (required) the url to be appended to the catalog url with '%s'
482 for each resource mentioned
483 resources: (optional) A list of resource names such as "server",
484 "flavor", etc. with an element for each '%s' in the url. This
485 method will call self.get_resource for each element when
486 constructing the positive test case template so negative
487 subclasses are expected to return valid resource ids when
488 appropriate.
489 json-schema (optional) A valid json schema that will be used to
490 create invalid data for the api calls. For "GET" and "HEAD",
491 the data is used to generate query strings appended to the url,
492 otherwise for the body of the http call.
493 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100494 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100495 generator = importutils.import_class(
496 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100497 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100498 schema = description.get("json-schema", None)
499 resources = description.get("resources", [])
500 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100501 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100502 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100503 if isinstance(resource, dict):
504 expected_result = resource['expected_result']
505 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100506 LOG.debug("Add resource to test %s" % resource)
507 scn_name = "inv_res_%s" % (resource)
508 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100509 str(uuid.uuid4())),
510 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100511 }))
512 if schema is not None:
Marc Kodererf07f5d12014-09-01 09:47:23 +0200513 for scenario in generator.generate_scenarios(schema):
514 scenario_list.append((scenario['_negtest_name'],
515 scenario))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100516 LOG.debug(scenario_list)
517 return scenario_list
518
Marc Koderer4f44d722014-08-07 14:04:58 +0200519 def execute(self, description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100520 """
521 Execute a http call on an api that are expected to
522 result in client errors. First it uses invalid resources that are part
523 of the url, and then invalid data for queries and http request bodies.
524
Marc Koderer4f44d722014-08-07 14:04:58 +0200525 :param description: A json file or dictionary with the following
526 entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100527 name (required) name for the api
528 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
529 url (required) the url to be appended to the catalog url with '%s'
530 for each resource mentioned
531 resources: (optional) A list of resource names such as "server",
532 "flavor", etc. with an element for each '%s' in the url. This
533 method will call self.get_resource for each element when
534 constructing the positive test case template so negative
535 subclasses are expected to return valid resource ids when
536 appropriate.
537 json-schema (optional) A valid json schema that will be used to
538 create invalid data for the api calls. For "GET" and "HEAD",
539 the data is used to generate query strings appended to the url,
540 otherwise for the body of the http call.
541
542 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100543 LOG.info("Executing %s" % description["name"])
544 LOG.debug(description)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200545 generator = importutils.import_class(
546 CONF.negative.test_generator)()
547 schema = description.get("json-schema", None)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100548 method = description["http-method"]
549 url = description["url"]
Marc Kodererf07f5d12014-09-01 09:47:23 +0200550 expected_result = None
551 if "default_result_code" in description:
552 expected_result = description["default_result_code"]
Marc Koderer24eb89c2014-01-31 11:23:33 +0100553
554 resources = [self.get_resource(r) for
555 r in description.get("resources", [])]
556
557 if hasattr(self, "resource"):
558 # Note(mkoderer): The resources list already contains an invalid
559 # entry (see get_resource).
560 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100561 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100562 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100563 valid_schema = \
564 valid.ValidTestGenerator().generate_valid(schema)
565 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200566 elif hasattr(self, "_negtest_name"):
567 schema_under_test = \
568 valid.ValidTestGenerator().generate_valid(schema)
569 local_expected_result = \
570 generator.generate_payload(self, schema_under_test)
571 if local_expected_result is not None:
572 expected_result = local_expected_result
573 new_url, body = \
574 self._http_arguments(schema_under_test, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100575 else:
576 raise Exception("testscenarios are not active. Please make sure "
577 "that your test runner supports the load_tests "
578 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100579
Marc Kodererf857fda2014-03-05 15:58:00 +0100580 if "admin_client" in description and description["admin_client"]:
581 client = self.admin_client
582 else:
583 client = self.client
584 resp, resp_body = client.send_request(method, new_url,
585 resources, body=body)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200586 self._check_negative_response(expected_result, resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100587
588 def _http_arguments(self, json_dict, url, method):
589 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
590 if not json_dict:
591 return url, None
592 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
593 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
594 else:
595 return url, json.dumps(json_dict)
596
Marc Kodererf07f5d12014-09-01 09:47:23 +0200597 def _check_negative_response(self, expected_result, result, body):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100598 self.assertTrue(result >= 400 and result < 500 and result != 413,
599 "Expected client error, got %s:%s" %
600 (result, body))
601 self.assertTrue(expected_result is None or expected_result == result,
602 "Expected %s, got %s:%s" %
603 (expected_result, result, body))
604
605 @classmethod
606 def set_resource(cls, name, resource):
607 """
608 This function can be used in setUpClass context to register a resoruce
609 for a test.
610
611 :param name: The name of the kind of resource such as "flavor", "role",
612 etc.
613 :resource: The id of the resource
614 """
615 cls._resources[name] = resource
616
617 def get_resource(self, name):
618 """
619 Return a valid uuid for a type of resource. If a real resource is
620 needed as part of a url then this method should return one. Otherwise
621 it can return None.
622
623 :param name: The name of the kind of resource such as "flavor", "role",
624 etc.
625 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100626 if isinstance(name, dict):
627 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100628 if hasattr(self, "resource") and self.resource[0] == name:
629 LOG.debug("Return invalid resource (%s) value: %s" %
630 (self.resource[0], self.resource[1]))
631 return self.resource[1]
632 if name in self._resources:
633 return self._resources[name]
634 return None
635
636
Marc Kodererb2978da2014-03-26 13:45:43 +0100637def SimpleNegativeAutoTest(klass):
638 """
639 This decorator registers a test function on basis of the class name.
640 """
641 @attr(type=['negative', 'gate'])
642 def generic_test(self):
Marc Koderer4f44d722014-08-07 14:04:58 +0200643 if hasattr(self, '_schema'):
644 self.execute(self._schema)
Marc Kodererb2978da2014-03-26 13:45:43 +0100645
646 cn = klass.__name__
647 cn = cn.replace('JSON', '')
648 cn = cn.replace('Test', '')
649 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
650 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
651 func_name = 'test_%s' % lower_cn
652 setattr(klass, func_name, generic_test)
653 return klass
654
655
Sean Dague35a7caf2013-05-10 10:38:22 -0400656def call_until_true(func, duration, sleep_for):
657 """
658 Call the given function until it returns True (and return True) or
659 until the specified duration (in seconds) elapses (and return
660 False).
661
662 :param func: A zero argument callable that returns True on success.
663 :param duration: The number of seconds for which to attempt a
664 successful call of the function.
665 :param sleep_for: The number of seconds to sleep after an unsuccessful
666 invocation of the function.
667 """
668 now = time.time()
669 timeout = now + duration
670 while now < timeout:
671 if func():
672 return True
Sean Dague35a7caf2013-05-10 10:38:22 -0400673 time.sleep(sleep_for)
674 now = time.time()
675 return False