blob: b80ce27ab52f3a26eeb9d03ae9bc2513466b6690 [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
Samuel Merritt0d499bc2013-06-19 12:08:23 -070042# All the successful HTTP status codes from RFC 2616
43HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206)
44
Jay Pipes051075a2012-04-28 17:39:37 -040045
Chris Yeoh55530bb2013-02-08 16:04:27 +103046def attr(*args, **kwargs):
Matthew Treinisha74f5d42014-02-07 20:25:44 -050047 """A decorator which applies the testtools attr decorator
Chris Yeoh55530bb2013-02-08 16:04:27 +103048
Matthew Treinisha74f5d42014-02-07 20:25:44 -050049 This decorator applies the testtools.testcase.attr if it is in the list of
50 attributes to testtools we want to apply.
Attila Fazekasb2902af2013-02-16 16:22:44 +010051 """
Chris Yeoh55530bb2013-02-08 16:04:27 +103052
53 def decorator(f):
Giulio Fidente4946a052013-05-14 12:23:51 +020054 if 'type' in kwargs and isinstance(kwargs['type'], str):
55 f = testtools.testcase.attr(kwargs['type'])(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093056 if kwargs['type'] == 'smoke':
57 f = testtools.testcase.attr('gate')(f)
Giulio Fidente4946a052013-05-14 12:23:51 +020058 elif 'type' in kwargs and isinstance(kwargs['type'], list):
59 for attr in kwargs['type']:
60 f = testtools.testcase.attr(attr)(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093061 if attr == 'smoke':
62 f = testtools.testcase.attr('gate')(f)
Matthew Treinisha74f5d42014-02-07 20:25:44 -050063 return f
Chris Yeoh55530bb2013-02-08 16:04:27 +103064
65 return decorator
66
67
Matthew Treinish3d8c7322014-08-03 23:53:28 -040068def get_service_list():
Matthew Treinish8afbffd2014-01-21 23:56:13 +000069 service_list = {
70 'compute': CONF.service_available.nova,
71 'image': CONF.service_available.glance,
Adam Gandelman4a48a602014-03-20 18:23:18 -070072 'baremetal': CONF.service_available.ironic,
Matthew Treinish8afbffd2014-01-21 23:56:13 +000073 'volume': CONF.service_available.cinder,
74 'orchestration': CONF.service_available.heat,
75 # NOTE(mtreinish) nova-network will provide networking functionality
76 # if neutron isn't available, so always set to True.
77 'network': True,
78 'identity': True,
79 'object_storage': CONF.service_available.swift,
80 'dashboard': CONF.service_available.horizon,
ekhugen7aff0992014-08-04 19:01:57 +000081 'telemetry': CONF.service_available.ceilometer,
Yaroslav Lobankovff42c872014-06-17 15:39:43 +040082 'data_processing': CONF.service_available.sahara
Matthew Treinish8afbffd2014-01-21 23:56:13 +000083 }
Matthew Treinish3d8c7322014-08-03 23:53:28 -040084 return service_list
Matthew Treinish16c43792013-09-09 19:55:23 +000085
Matthew Treinish3d8c7322014-08-03 23:53:28 -040086
87def services(*args, **kwargs):
88 """A decorator used to set an attr for each service used in a test case
89
90 This decorator applies a testtools attr for each service that gets
91 exercised by a test case.
92 """
Matthew Treinish16c43792013-09-09 19:55:23 +000093 def decorator(f):
Matthew Treinish3d8c7322014-08-03 23:53:28 -040094 services = ['compute', 'image', 'baremetal', 'volume', 'orchestration',
95 'network', 'identity', 'object_storage', 'dashboard',
Matthew Treinish60359052014-09-18 17:39:26 -040096 'telemetry', 'data_processing']
Matthew Treinish16c43792013-09-09 19:55:23 +000097 for service in args:
Matthew Treinish3d8c7322014-08-03 23:53:28 -040098 if service not in services:
99 raise exceptions.InvalidServiceTag('%s is not a valid '
100 'service' % service)
Matthew Treinish16c43792013-09-09 19:55:23 +0000101 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000102
103 @functools.wraps(f)
104 def wrapper(self, *func_args, **func_kwargs):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400105 service_list = get_service_list()
106
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000107 for service in args:
108 if not service_list[service]:
109 msg = 'Skipped because the %s service is not available' % (
110 service)
111 raise testtools.TestCase.skipException(msg)
112 return f(self, *func_args, **func_kwargs)
113 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000114 return decorator
115
116
Marc Koderer32221b8e2013-08-23 13:57:50 +0200117def stresstest(*args, **kwargs):
118 """Add stress test decorator
119
120 For all functions with this decorator a attr stress will be
121 set automatically.
122
123 @param class_setup_per: allowed values are application, process, action
124 ``application``: once in the stress job lifetime
125 ``process``: once in the worker process lifetime
126 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200127 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200128 """
129 def decorator(f):
130 if 'class_setup_per' in kwargs:
131 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
132 else:
133 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200134 if 'allow_inheritance' in kwargs:
135 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
136 else:
137 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200138 attr(type='stress')(f)
139 return f
140 return decorator
141
142
Giulio Fidente83181a92013-10-01 06:02:24 +0200143def skip_because(*args, **kwargs):
144 """A decorator useful to skip tests hitting known bugs
145
146 @param bug: bug number causing the test to skip
147 @param condition: optional condition to be True for the skip to have place
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900148 @param interface: skip the test if it is the same as self._interface
Giulio Fidente83181a92013-10-01 06:02:24 +0200149 """
150 def decorator(f):
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900151 @functools.wraps(f)
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900152 def wrapper(self, *func_args, **func_kwargs):
153 skip = False
154 if "condition" in kwargs:
155 if kwargs["condition"] is True:
156 skip = True
157 elif "interface" in kwargs:
158 if kwargs["interface"] == self._interface:
159 skip = True
160 else:
161 skip = True
162 if "bug" in kwargs and skip is True:
Matthew Treinishede50272014-02-11 19:56:11 +0000163 if not kwargs['bug'].isdigit():
164 raise ValueError('bug must be a valid bug number')
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900165 msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
166 raise testtools.TestCase.skipException(msg)
167 return f(self, *func_args, **func_kwargs)
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900168 return wrapper
Giulio Fidente83181a92013-10-01 06:02:24 +0200169 return decorator
170
171
Matthew Treinishe3d26142013-11-26 19:14:58 +0000172def requires_ext(*args, **kwargs):
173 """A decorator to skip tests if an extension is not enabled
174
175 @param extension
176 @param service
177 """
178 def decorator(func):
179 @functools.wraps(func)
180 def wrapper(*func_args, **func_kwargs):
181 if not is_extension_enabled(kwargs['extension'],
182 kwargs['service']):
183 msg = "Skipped because %s extension: %s is not enabled" % (
184 kwargs['service'], kwargs['extension'])
185 raise testtools.TestCase.skipException(msg)
186 return func(*func_args, **func_kwargs)
187 return wrapper
188 return decorator
189
190
191def is_extension_enabled(extension_name, service):
192 """A function that will check the list of enabled extensions from config
193
194 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000195 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000196 'compute': CONF.compute_feature_enabled.api_extensions,
197 'compute_v3': CONF.compute_feature_enabled.api_v3_extensions,
198 'volume': CONF.volume_feature_enabled.api_extensions,
199 'network': CONF.network_feature_enabled.api_extensions,
200 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000201 }
Simeon Monov5d7effe2014-07-16 07:32:38 +0300202 if len(config_dict[service]) == 0:
203 return False
Matthew Treinishe3d26142013-11-26 19:14:58 +0000204 if config_dict[service][0] == 'all':
205 return True
206 if extension_name in config_dict[service]:
207 return True
208 return False
209
Ian Wienand98c35f32013-07-23 20:34:23 +1000210
Attila Fazekasf86fa312013-07-30 19:56:39 +0200211at_exit_set = set()
212
213
214def validate_tearDownClass():
215 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400216 LOG.error(
217 "tearDownClass does not call the super's "
218 "tearDownClass in these classes: \n"
219 + str(at_exit_set))
220
Attila Fazekasf86fa312013-07-30 19:56:39 +0200221
222atexit.register(validate_tearDownClass)
223
Attila Fazekas53943322014-02-10 16:07:34 +0100224
Matthew Treinish2474f412014-11-17 18:11:56 -0500225class BaseTestCase(testtools.testcase.WithAttributes,
226 testtools.TestCase):
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100227 """The test base class defines Tempest framework for class level fixtures.
228 `setUpClass` and `tearDownClass` are defined here and cannot be overwritten
229 by subclasses (enforced via hacking rule T105).
230
231 Set-up is split in a series of steps (setup stages), which can be
232 overwritten by test classes. Set-up stages are:
233 - skip_checks
234 - setup_credentials
235 - setup_clients
236 - resource_setup
237
238 Tear-down is also split in a series of steps (teardown stages), which are
239 stacked for execution only if the corresponding setup stage had been
240 reached during the setup phase. Tear-down stages are:
241 - clear_isolated_creds (defined in the base test class)
242 - resource_cleanup
243 """
Attila Fazekasc43fec82013-04-09 23:17:52 +0200244
Attila Fazekasf86fa312013-07-30 19:56:39 +0200245 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100246 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200247
Matthew Treinish9f756a02014-01-15 10:26:07 -0500248 network_resources = {}
249
Sean Dague2ef32ac2014-06-09 11:32:23 -0400250 # NOTE(sdague): log_format is defined inline here instead of using the oslo
251 # default because going through the config path recouples config to the
252 # stress tests too early, and depending on testr order will fail unit tests
253 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
254 '[%(name)s] %(message)s')
255
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200256 @classmethod
257 def setUpClass(cls):
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100258 # It should never be overridden by descendants
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200259 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
260 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200261 cls.setUpClassCalled = True
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100262 # Stack of (name, callable) to be invoked in reverse order at teardown
263 cls.teardowns = []
264 # All the configuration checks that may generate a skip
265 cls.skip_checks()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100266 try:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100267 # Allocation of all required credentials and client managers
268 cls.teardowns.append(('credentials', cls.clear_isolated_creds))
269 cls.setup_credentials()
270 # Shortcuts to clients
271 cls.setup_clients()
272 # Additional class-wide test resources
273 cls.teardowns.append(('resources', cls.resource_cleanup))
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100274 cls.resource_setup()
275 except Exception:
276 etype, value, trace = sys.exc_info()
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100277 LOG.info("%s in %s.setUpClass. Invoking tearDownClass." % (
278 cls.__name__, etype))
279 cls.tearDownClass()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100280 try:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100281 raise etype, value, trace
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100282 finally:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100283 del trace # to avoid circular refs
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200284
Attila Fazekasf86fa312013-07-30 19:56:39 +0200285 @classmethod
286 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200287 at_exit_set.discard(cls)
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100288 # It should never be overridden by descendants
Attila Fazekasf86fa312013-07-30 19:56:39 +0200289 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
290 super(BaseTestCase, cls).tearDownClass()
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100291 # Save any existing exception, we always want to re-raise the original
292 # exception only
293 etype, value, trace = sys.exc_info()
294 # If there was no exception during setup we shall re-raise the first
295 # exception in teardown
296 re_raise = (etype is None)
297 while cls.teardowns:
298 name, teardown = cls.teardowns.pop()
299 # Catch any exception in tearDown so we can re-raise the original
300 # exception at the end
301 try:
302 teardown()
303 except Exception as te:
304 sys_exec_info = sys.exc_info()
305 tetype = sys_exec_info[0]
306 # TODO(andreaf): Till we have the ability to cleanup only
307 # resources that were successfully setup in resource_cleanup,
308 # log AttributeError as info instead of exception.
309 if tetype is AttributeError and name == 'resources':
310 LOG.info("tearDownClass of %s failed: %s" % (name, te))
311 else:
312 LOG.exception("teardown of %s failed: %s" % (name, te))
313 if not etype:
314 etype, value, trace = sys_exec_info
315 # If exceptions were raised during teardown, an not before, re-raise
316 # the first one
317 if re_raise and etype is not None:
318 try:
319 raise etype, value, trace
320 finally:
321 del trace # to avoid circular refs
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100322
323 @classmethod
324 def resource_setup(cls):
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100325 """Class level resource setup for test cases.
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100326 """
327 pass
328
329 @classmethod
330 def resource_cleanup(cls):
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100331 """Class level resource cleanup for test cases.
332 Resource cleanup must be able to handle the case of partially setup
333 resources, in case a failure during `resource_setup` should happen.
334 """
335 pass
336
337 @classmethod
338 def skip_checks(cls):
339 """Class level skip checks. Subclasses verify in here all
340 conditions that might prevent the execution of the entire test class.
341 Checks implemented here may not make use API calls, and should rely on
342 configuration alone.
343 In general skip checks that require an API call are discouraged.
344 If one is really needed it may be implemented either in the
345 resource_setup or at test level.
346 """
347 pass
348
349 @classmethod
350 def setup_credentials(cls):
351 """Allocate credentials and the client managers from them."""
352 # TODO(andreaf) There is a fair amount of code that could me moved from
353 # base / test classes in here. Ideally tests should be able to only
354 # specify a list of (additional) credentials the need to use.
355 pass
356
357 @classmethod
358 def setup_clients(cls):
359 """Create links to the clients into the test object."""
360 # TODO(andreaf) There is a fair amount of code that could me moved from
361 # base / test classes in here. Ideally tests should be able to only
362 # specify which client is `client` and nothing else.
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100363 pass
Attila Fazekasf86fa312013-07-30 19:56:39 +0200364
365 def setUp(self):
366 super(BaseTestCase, self).setUp()
367 if not self.setUpClassCalled:
368 raise RuntimeError("setUpClass does not calls the super's"
369 "setUpClass in the "
370 + self.__class__.__name__)
371 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400372 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
373 try:
374 test_timeout = int(test_timeout)
375 except ValueError:
376 test_timeout = 0
377 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200378 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400379
380 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
381 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200382 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
383 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400384 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
385 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200386 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
387 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200388 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
389 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200390 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400391 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200392 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400393
Matthew Treinish3e046852013-07-23 16:00:24 -0400394 @classmethod
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000395 def get_client_manager(cls, interface=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700396 """
tanlin4956a642014-02-13 16:52:11 +0800397 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700398 """
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700399 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100400
Marc Koderer44dce622014-11-14 10:08:12 +0100401 if (not hasattr(cls, 'isolated_creds') or
402 not cls.isolated_creds.name == cls.__name__):
403 cls.isolated_creds = credentials.get_isolated_credentials(
404 name=cls.__name__, network_resources=cls.network_resources,
405 force_tenant_isolation=force_tenant_isolation,
406 )
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100407
408 creds = cls.isolated_creds.get_primary_creds()
409 params = dict(credentials=creds, service=cls._service)
410 if getattr(cls, '_interface', None):
411 interface = cls._interface
412 if interface:
413 params['interface'] = interface
414 os = clients.Manager(**params)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700415 return os
416
417 @classmethod
418 def clear_isolated_creds(cls):
419 """
420 Clears isolated creds if set
421 """
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100422 if hasattr(cls, 'isolated_creds'):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700423 cls.isolated_creds.clear_isolated_creds()
424
425 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400426 def _get_identity_admin_client(cls):
427 """
428 Returns an instance of the Identity Admin API client
429 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100430 os = clients.AdminManager(interface=cls._interface,
431 service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400432 admin_client = os.identity_client
433 return admin_client
434
435 @classmethod
Matthew Treinish9f756a02014-01-15 10:26:07 -0500436 def set_network_resources(self, network=False, router=False, subnet=False,
437 dhcp=False):
438 """Specify which network resources should be created
439
440 @param network
441 @param router
442 @param subnet
443 @param dhcp
444 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000445 # network resources should be set only once from callers
446 # in order to ensure that even if it's called multiple times in
447 # a chain of overloaded methods, the attribute is set only
448 # in the leaf class
449 if not self.network_resources:
450 self.network_resources = {
451 'network': network,
452 'router': router,
453 'subnet': subnet,
454 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500455
Mark Maglana5885eb32014-02-28 10:57:34 -0800456 def assertEmpty(self, list, msg=None):
457 self.assertTrue(len(list) == 0, msg)
458
459 def assertNotEmpty(self, list, msg=None):
460 self.assertTrue(len(list) > 0, msg)
461
Attila Fazekasdc216422013-01-29 15:12:14 +0100462
Marc Koderer24eb89c2014-01-31 11:23:33 +0100463class NegativeAutoTest(BaseTestCase):
464
465 _resources = {}
466
467 @classmethod
468 def setUpClass(cls):
469 super(NegativeAutoTest, cls).setUpClass()
470 os = cls.get_client_manager()
471 cls.client = os.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100472 os_admin = clients.AdminManager(interface=cls._interface,
473 service=cls._service)
474 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100475
476 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100477 def load_tests(*args):
478 """
479 Wrapper for testscenarios to set the mandatory scenarios variable
480 only in case a real test loader is in place. Will be automatically
481 called in case the variable "load_tests" is set.
482 """
483 if getattr(args[0], 'suiteClass', None) is not None:
484 loader, standard_tests, pattern = args
485 else:
486 standard_tests, module, loader = args
487 for test in testtools.iterate_tests(standard_tests):
488 schema_file = getattr(test, '_schema_file', None)
Marc Koderer4f44d722014-08-07 14:04:58 +0200489 schema = getattr(test, '_schema', None)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100490 if schema_file is not None:
491 setattr(test, 'scenarios',
492 NegativeAutoTest.generate_scenario(schema_file))
Marc Koderer4f44d722014-08-07 14:04:58 +0200493 elif schema is not None:
494 setattr(test, 'scenarios',
495 NegativeAutoTest.generate_scenario(schema))
Marc Koderer674c8fc2014-03-17 09:45:04 +0100496 return testscenarios.load_tests_apply_scenarios(*args)
497
498 @staticmethod
Marc Koderer4f44d722014-08-07 14:04:58 +0200499 def generate_scenario(description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100500 """
501 Generates the test scenario list for a given description.
502
Marc Koderer4f44d722014-08-07 14:04:58 +0200503 :param description: A file or dictionary with the following entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100504 name (required) name for the api
505 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
506 url (required) the url to be appended to the catalog url with '%s'
507 for each resource mentioned
508 resources: (optional) A list of resource names such as "server",
509 "flavor", etc. with an element for each '%s' in the url. This
510 method will call self.get_resource for each element when
511 constructing the positive test case template so negative
512 subclasses are expected to return valid resource ids when
513 appropriate.
514 json-schema (optional) A valid json schema that will be used to
515 create invalid data for the api calls. For "GET" and "HEAD",
516 the data is used to generate query strings appended to the url,
517 otherwise for the body of the http call.
518 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100519 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100520 generator = importutils.import_class(
521 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100522 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100523 schema = description.get("json-schema", None)
524 resources = description.get("resources", [])
525 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100526 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100527 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100528 if isinstance(resource, dict):
529 expected_result = resource['expected_result']
530 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100531 LOG.debug("Add resource to test %s" % resource)
532 scn_name = "inv_res_%s" % (resource)
533 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100534 str(uuid.uuid4())),
535 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100536 }))
537 if schema is not None:
Marc Kodererf07f5d12014-09-01 09:47:23 +0200538 for scenario in generator.generate_scenarios(schema):
539 scenario_list.append((scenario['_negtest_name'],
540 scenario))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100541 LOG.debug(scenario_list)
542 return scenario_list
543
Marc Koderer4f44d722014-08-07 14:04:58 +0200544 def execute(self, description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100545 """
546 Execute a http call on an api that are expected to
547 result in client errors. First it uses invalid resources that are part
548 of the url, and then invalid data for queries and http request bodies.
549
Marc Koderer4f44d722014-08-07 14:04:58 +0200550 :param description: A json file or dictionary with the following
551 entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100552 name (required) name for the api
553 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
554 url (required) the url to be appended to the catalog url with '%s'
555 for each resource mentioned
556 resources: (optional) A list of resource names such as "server",
557 "flavor", etc. with an element for each '%s' in the url. This
558 method will call self.get_resource for each element when
559 constructing the positive test case template so negative
560 subclasses are expected to return valid resource ids when
561 appropriate.
562 json-schema (optional) A valid json schema that will be used to
563 create invalid data for the api calls. For "GET" and "HEAD",
564 the data is used to generate query strings appended to the url,
565 otherwise for the body of the http call.
566
567 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100568 LOG.info("Executing %s" % description["name"])
569 LOG.debug(description)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200570 generator = importutils.import_class(
571 CONF.negative.test_generator)()
572 schema = description.get("json-schema", None)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100573 method = description["http-method"]
574 url = description["url"]
Marc Kodererf07f5d12014-09-01 09:47:23 +0200575 expected_result = None
576 if "default_result_code" in description:
577 expected_result = description["default_result_code"]
Marc Koderer24eb89c2014-01-31 11:23:33 +0100578
579 resources = [self.get_resource(r) for
580 r in description.get("resources", [])]
581
582 if hasattr(self, "resource"):
583 # Note(mkoderer): The resources list already contains an invalid
584 # entry (see get_resource).
585 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100586 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100587 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100588 valid_schema = \
589 valid.ValidTestGenerator().generate_valid(schema)
590 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200591 elif hasattr(self, "_negtest_name"):
592 schema_under_test = \
593 valid.ValidTestGenerator().generate_valid(schema)
594 local_expected_result = \
595 generator.generate_payload(self, schema_under_test)
596 if local_expected_result is not None:
597 expected_result = local_expected_result
598 new_url, body = \
599 self._http_arguments(schema_under_test, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100600 else:
601 raise Exception("testscenarios are not active. Please make sure "
602 "that your test runner supports the load_tests "
603 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100604
Marc Kodererf857fda2014-03-05 15:58:00 +0100605 if "admin_client" in description and description["admin_client"]:
606 client = self.admin_client
607 else:
608 client = self.client
609 resp, resp_body = client.send_request(method, new_url,
610 resources, body=body)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200611 self._check_negative_response(expected_result, resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100612
613 def _http_arguments(self, json_dict, url, method):
614 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
615 if not json_dict:
616 return url, None
617 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
618 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
619 else:
620 return url, json.dumps(json_dict)
621
Marc Kodererf07f5d12014-09-01 09:47:23 +0200622 def _check_negative_response(self, expected_result, result, body):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100623 self.assertTrue(result >= 400 and result < 500 and result != 413,
624 "Expected client error, got %s:%s" %
625 (result, body))
626 self.assertTrue(expected_result is None or expected_result == result,
627 "Expected %s, got %s:%s" %
628 (expected_result, result, body))
629
630 @classmethod
631 def set_resource(cls, name, resource):
632 """
633 This function can be used in setUpClass context to register a resoruce
634 for a test.
635
636 :param name: The name of the kind of resource such as "flavor", "role",
637 etc.
638 :resource: The id of the resource
639 """
640 cls._resources[name] = resource
641
642 def get_resource(self, name):
643 """
644 Return a valid uuid for a type of resource. If a real resource is
645 needed as part of a url then this method should return one. Otherwise
646 it can return None.
647
648 :param name: The name of the kind of resource such as "flavor", "role",
649 etc.
650 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100651 if isinstance(name, dict):
652 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100653 if hasattr(self, "resource") and self.resource[0] == name:
654 LOG.debug("Return invalid resource (%s) value: %s" %
655 (self.resource[0], self.resource[1]))
656 return self.resource[1]
657 if name in self._resources:
658 return self._resources[name]
659 return None
660
661
Marc Kodererb2978da2014-03-26 13:45:43 +0100662def SimpleNegativeAutoTest(klass):
663 """
664 This decorator registers a test function on basis of the class name.
665 """
666 @attr(type=['negative', 'gate'])
667 def generic_test(self):
Marc Koderer4f44d722014-08-07 14:04:58 +0200668 if hasattr(self, '_schema'):
669 self.execute(self._schema)
Marc Kodererb2978da2014-03-26 13:45:43 +0100670
671 cn = klass.__name__
672 cn = cn.replace('JSON', '')
673 cn = cn.replace('Test', '')
674 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
675 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
676 func_name = 'test_%s' % lower_cn
677 setattr(klass, func_name, generic_test)
678 return klass
679
680
Sean Dague35a7caf2013-05-10 10:38:22 -0400681def call_until_true(func, duration, sleep_for):
682 """
683 Call the given function until it returns True (and return True) or
684 until the specified duration (in seconds) elapses (and return
685 False).
686
687 :param func: A zero argument callable that returns True on success.
688 :param duration: The number of seconds for which to attempt a
689 successful call of the function.
690 :param sleep_for: The number of seconds to sleep after an unsuccessful
691 invocation of the function.
692 """
693 now = time.time()
694 timeout = now + duration
695 while now < timeout:
696 if func():
697 return True
Sean Dague35a7caf2013-05-10 10:38:22 -0400698 time.sleep(sleep_for)
699 now = time.time()
700 return False