blob: cc8370c7891f89d1de091cff0d8508ff158d2f5b [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,
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000197 'volume': CONF.volume_feature_enabled.api_extensions,
198 'network': CONF.network_feature_enabled.api_extensions,
199 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000200 }
Simeon Monov5d7effe2014-07-16 07:32:38 +0300201 if len(config_dict[service]) == 0:
202 return False
Matthew Treinishe3d26142013-11-26 19:14:58 +0000203 if config_dict[service][0] == 'all':
204 return True
205 if extension_name in config_dict[service]:
206 return True
207 return False
208
Ian Wienand98c35f32013-07-23 20:34:23 +1000209
Attila Fazekasf86fa312013-07-30 19:56:39 +0200210at_exit_set = set()
211
212
213def validate_tearDownClass():
214 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400215 LOG.error(
216 "tearDownClass does not call the super's "
217 "tearDownClass in these classes: \n"
218 + str(at_exit_set))
219
Attila Fazekasf86fa312013-07-30 19:56:39 +0200220
221atexit.register(validate_tearDownClass)
222
Attila Fazekas53943322014-02-10 16:07:34 +0100223
Matthew Treinish2474f412014-11-17 18:11:56 -0500224class BaseTestCase(testtools.testcase.WithAttributes,
225 testtools.TestCase):
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100226 """The test base class defines Tempest framework for class level fixtures.
227 `setUpClass` and `tearDownClass` are defined here and cannot be overwritten
228 by subclasses (enforced via hacking rule T105).
229
230 Set-up is split in a series of steps (setup stages), which can be
231 overwritten by test classes. Set-up stages are:
232 - skip_checks
233 - setup_credentials
234 - setup_clients
235 - resource_setup
236
237 Tear-down is also split in a series of steps (teardown stages), which are
238 stacked for execution only if the corresponding setup stage had been
239 reached during the setup phase. Tear-down stages are:
240 - clear_isolated_creds (defined in the base test class)
241 - resource_cleanup
242 """
Attila Fazekasc43fec82013-04-09 23:17:52 +0200243
Attila Fazekasf86fa312013-07-30 19:56:39 +0200244 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100245 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200246
Matthew Treinish9f756a02014-01-15 10:26:07 -0500247 network_resources = {}
248
Sean Dague2ef32ac2014-06-09 11:32:23 -0400249 # NOTE(sdague): log_format is defined inline here instead of using the oslo
250 # default because going through the config path recouples config to the
251 # stress tests too early, and depending on testr order will fail unit tests
252 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
253 '[%(name)s] %(message)s')
254
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200255 @classmethod
256 def setUpClass(cls):
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100257 # It should never be overridden by descendants
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200258 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
259 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200260 cls.setUpClassCalled = True
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100261 # Stack of (name, callable) to be invoked in reverse order at teardown
262 cls.teardowns = []
263 # All the configuration checks that may generate a skip
264 cls.skip_checks()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100265 try:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100266 # Allocation of all required credentials and client managers
267 cls.teardowns.append(('credentials', cls.clear_isolated_creds))
268 cls.setup_credentials()
269 # Shortcuts to clients
270 cls.setup_clients()
271 # Additional class-wide test resources
272 cls.teardowns.append(('resources', cls.resource_cleanup))
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100273 cls.resource_setup()
274 except Exception:
275 etype, value, trace = sys.exc_info()
Matthew Treinished2ad4f2014-12-23 15:18:32 -0500276 LOG.info("%s raised in %s.setUpClass. Invoking tearDownClass." % (
277 etype, cls.__name__))
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100278 cls.tearDownClass()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100279 try:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100280 raise etype, value, trace
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100281 finally:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100282 del trace # to avoid circular refs
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200283
Attila Fazekasf86fa312013-07-30 19:56:39 +0200284 @classmethod
285 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200286 at_exit_set.discard(cls)
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100287 # It should never be overridden by descendants
Attila Fazekasf86fa312013-07-30 19:56:39 +0200288 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
289 super(BaseTestCase, cls).tearDownClass()
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100290 # Save any existing exception, we always want to re-raise the original
291 # exception only
292 etype, value, trace = sys.exc_info()
293 # If there was no exception during setup we shall re-raise the first
294 # exception in teardown
295 re_raise = (etype is None)
296 while cls.teardowns:
297 name, teardown = cls.teardowns.pop()
298 # Catch any exception in tearDown so we can re-raise the original
299 # exception at the end
300 try:
301 teardown()
302 except Exception as te:
303 sys_exec_info = sys.exc_info()
304 tetype = sys_exec_info[0]
305 # TODO(andreaf): Till we have the ability to cleanup only
306 # resources that were successfully setup in resource_cleanup,
307 # log AttributeError as info instead of exception.
308 if tetype is AttributeError and name == 'resources':
309 LOG.info("tearDownClass of %s failed: %s" % (name, te))
310 else:
311 LOG.exception("teardown of %s failed: %s" % (name, te))
312 if not etype:
313 etype, value, trace = sys_exec_info
314 # If exceptions were raised during teardown, an not before, re-raise
315 # the first one
316 if re_raise and etype is not None:
317 try:
318 raise etype, value, trace
319 finally:
320 del trace # to avoid circular refs
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100321
322 @classmethod
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100323 def skip_checks(cls):
324 """Class level skip checks. Subclasses verify in here all
325 conditions that might prevent the execution of the entire test class.
326 Checks implemented here may not make use API calls, and should rely on
327 configuration alone.
328 In general skip checks that require an API call are discouraged.
329 If one is really needed it may be implemented either in the
330 resource_setup or at test level.
331 """
332 pass
333
334 @classmethod
335 def setup_credentials(cls):
336 """Allocate credentials and the client managers from them."""
337 # TODO(andreaf) There is a fair amount of code that could me moved from
338 # base / test classes in here. Ideally tests should be able to only
339 # specify a list of (additional) credentials the need to use.
340 pass
341
342 @classmethod
343 def setup_clients(cls):
344 """Create links to the clients into the test object."""
345 # TODO(andreaf) There is a fair amount of code that could me moved from
346 # base / test classes in here. Ideally tests should be able to only
347 # specify which client is `client` and nothing else.
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100348 pass
Attila Fazekasf86fa312013-07-30 19:56:39 +0200349
Emily Hugenbruch5bd4cbf2014-12-17 21:38:38 +0000350 @classmethod
351 def resource_setup(cls):
352 """Class level resource setup for test cases.
353 """
354 pass
355
356 @classmethod
357 def resource_cleanup(cls):
358 """Class level resource cleanup for test cases.
359 Resource cleanup must be able to handle the case of partially setup
360 resources, in case a failure during `resource_setup` should happen.
361 """
362 pass
363
Attila Fazekasf86fa312013-07-30 19:56:39 +0200364 def setUp(self):
365 super(BaseTestCase, self).setUp()
366 if not self.setUpClassCalled:
367 raise RuntimeError("setUpClass does not calls the super's"
368 "setUpClass in the "
369 + self.__class__.__name__)
370 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400371 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
372 try:
373 test_timeout = int(test_timeout)
374 except ValueError:
375 test_timeout = 0
376 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200377 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400378
379 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
380 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200381 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
382 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400383 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
384 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200385 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
386 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200387 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
388 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200389 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400390 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200391 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400392
Matthew Treinish3e046852013-07-23 16:00:24 -0400393 @classmethod
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000394 def get_client_manager(cls, interface=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700395 """
tanlin4956a642014-02-13 16:52:11 +0800396 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700397 """
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700398 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100399
Marc Koderer44dce622014-11-14 10:08:12 +0100400 if (not hasattr(cls, 'isolated_creds') or
401 not cls.isolated_creds.name == cls.__name__):
402 cls.isolated_creds = credentials.get_isolated_credentials(
403 name=cls.__name__, network_resources=cls.network_resources,
404 force_tenant_isolation=force_tenant_isolation,
405 )
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100406
407 creds = cls.isolated_creds.get_primary_creds()
408 params = dict(credentials=creds, service=cls._service)
409 if getattr(cls, '_interface', None):
410 interface = cls._interface
411 if interface:
412 params['interface'] = interface
413 os = clients.Manager(**params)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700414 return os
415
416 @classmethod
417 def clear_isolated_creds(cls):
418 """
419 Clears isolated creds if set
420 """
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100421 if hasattr(cls, 'isolated_creds'):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700422 cls.isolated_creds.clear_isolated_creds()
423
424 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400425 def _get_identity_admin_client(cls):
426 """
427 Returns an instance of the Identity Admin API client
428 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100429 os = clients.AdminManager(interface=cls._interface,
430 service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400431 admin_client = os.identity_client
432 return admin_client
433
434 @classmethod
Matthew Treinish9f756a02014-01-15 10:26:07 -0500435 def set_network_resources(self, network=False, router=False, subnet=False,
436 dhcp=False):
437 """Specify which network resources should be created
438
439 @param network
440 @param router
441 @param subnet
442 @param dhcp
443 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000444 # network resources should be set only once from callers
445 # in order to ensure that even if it's called multiple times in
446 # a chain of overloaded methods, the attribute is set only
447 # in the leaf class
448 if not self.network_resources:
449 self.network_resources = {
450 'network': network,
451 'router': router,
452 'subnet': subnet,
453 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500454
Mark Maglana5885eb32014-02-28 10:57:34 -0800455 def assertEmpty(self, list, msg=None):
456 self.assertTrue(len(list) == 0, msg)
457
458 def assertNotEmpty(self, list, msg=None):
459 self.assertTrue(len(list) > 0, msg)
460
Attila Fazekasdc216422013-01-29 15:12:14 +0100461
Marc Koderer24eb89c2014-01-31 11:23:33 +0100462class NegativeAutoTest(BaseTestCase):
463
464 _resources = {}
465
466 @classmethod
467 def setUpClass(cls):
468 super(NegativeAutoTest, cls).setUpClass()
469 os = cls.get_client_manager()
470 cls.client = os.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100471 os_admin = clients.AdminManager(interface=cls._interface,
472 service=cls._service)
473 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100474
475 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100476 def load_tests(*args):
477 """
478 Wrapper for testscenarios to set the mandatory scenarios variable
479 only in case a real test loader is in place. Will be automatically
480 called in case the variable "load_tests" is set.
481 """
482 if getattr(args[0], 'suiteClass', None) is not None:
483 loader, standard_tests, pattern = args
484 else:
485 standard_tests, module, loader = args
486 for test in testtools.iterate_tests(standard_tests):
Marc Koderer4f44d722014-08-07 14:04:58 +0200487 schema = getattr(test, '_schema', None)
Marc Koderer3dd31052014-11-27 09:31:00 +0100488 if schema is not None:
Marc Koderer4f44d722014-08-07 14:04:58 +0200489 setattr(test, 'scenarios',
490 NegativeAutoTest.generate_scenario(schema))
Marc Koderer674c8fc2014-03-17 09:45:04 +0100491 return testscenarios.load_tests_apply_scenarios(*args)
492
493 @staticmethod
Marc Koderer4f44d722014-08-07 14:04:58 +0200494 def generate_scenario(description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100495 """
496 Generates the test scenario list for a given description.
497
Marc Koderer4f44d722014-08-07 14:04:58 +0200498 :param description: A file or dictionary with the following entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100499 name (required) name for the api
500 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
501 url (required) the url to be appended to the catalog url with '%s'
502 for each resource mentioned
503 resources: (optional) A list of resource names such as "server",
504 "flavor", etc. with an element for each '%s' in the url. This
505 method will call self.get_resource for each element when
506 constructing the positive test case template so negative
507 subclasses are expected to return valid resource ids when
508 appropriate.
509 json-schema (optional) A valid json schema that will be used to
510 create invalid data for the api calls. For "GET" and "HEAD",
511 the data is used to generate query strings appended to the url,
512 otherwise for the body of the http call.
513 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100514 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100515 generator = importutils.import_class(
516 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100517 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100518 schema = description.get("json-schema", None)
519 resources = description.get("resources", [])
520 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100521 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100522 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100523 if isinstance(resource, dict):
524 expected_result = resource['expected_result']
525 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100526 LOG.debug("Add resource to test %s" % resource)
527 scn_name = "inv_res_%s" % (resource)
528 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100529 str(uuid.uuid4())),
530 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100531 }))
532 if schema is not None:
Marc Kodererf07f5d12014-09-01 09:47:23 +0200533 for scenario in generator.generate_scenarios(schema):
534 scenario_list.append((scenario['_negtest_name'],
535 scenario))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100536 LOG.debug(scenario_list)
537 return scenario_list
538
Marc Koderer4f44d722014-08-07 14:04:58 +0200539 def execute(self, description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100540 """
541 Execute a http call on an api that are expected to
542 result in client errors. First it uses invalid resources that are part
543 of the url, and then invalid data for queries and http request bodies.
544
Marc Koderer4f44d722014-08-07 14:04:58 +0200545 :param description: A json file or dictionary with the following
546 entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100547 name (required) name for the api
548 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
549 url (required) the url to be appended to the catalog url with '%s'
550 for each resource mentioned
551 resources: (optional) A list of resource names such as "server",
552 "flavor", etc. with an element for each '%s' in the url. This
553 method will call self.get_resource for each element when
554 constructing the positive test case template so negative
555 subclasses are expected to return valid resource ids when
556 appropriate.
557 json-schema (optional) A valid json schema that will be used to
558 create invalid data for the api calls. For "GET" and "HEAD",
559 the data is used to generate query strings appended to the url,
560 otherwise for the body of the http call.
561
562 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100563 LOG.info("Executing %s" % description["name"])
564 LOG.debug(description)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200565 generator = importutils.import_class(
566 CONF.negative.test_generator)()
567 schema = description.get("json-schema", None)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100568 method = description["http-method"]
569 url = description["url"]
Marc Kodererf07f5d12014-09-01 09:47:23 +0200570 expected_result = None
571 if "default_result_code" in description:
572 expected_result = description["default_result_code"]
Marc Koderer24eb89c2014-01-31 11:23:33 +0100573
574 resources = [self.get_resource(r) for
575 r in description.get("resources", [])]
576
577 if hasattr(self, "resource"):
578 # Note(mkoderer): The resources list already contains an invalid
579 # entry (see get_resource).
580 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100581 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100582 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100583 valid_schema = \
584 valid.ValidTestGenerator().generate_valid(schema)
585 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200586 elif hasattr(self, "_negtest_name"):
587 schema_under_test = \
588 valid.ValidTestGenerator().generate_valid(schema)
589 local_expected_result = \
590 generator.generate_payload(self, schema_under_test)
591 if local_expected_result is not None:
592 expected_result = local_expected_result
593 new_url, body = \
594 self._http_arguments(schema_under_test, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100595 else:
596 raise Exception("testscenarios are not active. Please make sure "
597 "that your test runner supports the load_tests "
598 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100599
Marc Kodererf857fda2014-03-05 15:58:00 +0100600 if "admin_client" in description and description["admin_client"]:
601 client = self.admin_client
602 else:
603 client = self.client
604 resp, resp_body = client.send_request(method, new_url,
605 resources, body=body)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200606 self._check_negative_response(expected_result, resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100607
608 def _http_arguments(self, json_dict, url, method):
609 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
610 if not json_dict:
611 return url, None
612 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
613 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
614 else:
615 return url, json.dumps(json_dict)
616
Marc Kodererf07f5d12014-09-01 09:47:23 +0200617 def _check_negative_response(self, expected_result, result, body):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100618 self.assertTrue(result >= 400 and result < 500 and result != 413,
619 "Expected client error, got %s:%s" %
620 (result, body))
621 self.assertTrue(expected_result is None or expected_result == result,
622 "Expected %s, got %s:%s" %
623 (expected_result, result, body))
624
625 @classmethod
626 def set_resource(cls, name, resource):
627 """
628 This function can be used in setUpClass context to register a resoruce
629 for a test.
630
631 :param name: The name of the kind of resource such as "flavor", "role",
632 etc.
633 :resource: The id of the resource
634 """
635 cls._resources[name] = resource
636
637 def get_resource(self, name):
638 """
639 Return a valid uuid for a type of resource. If a real resource is
640 needed as part of a url then this method should return one. Otherwise
641 it can return None.
642
643 :param name: The name of the kind of resource such as "flavor", "role",
644 etc.
645 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100646 if isinstance(name, dict):
647 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100648 if hasattr(self, "resource") and self.resource[0] == name:
649 LOG.debug("Return invalid resource (%s) value: %s" %
650 (self.resource[0], self.resource[1]))
651 return self.resource[1]
652 if name in self._resources:
653 return self._resources[name]
654 return None
655
656
Marc Kodererb2978da2014-03-26 13:45:43 +0100657def SimpleNegativeAutoTest(klass):
658 """
659 This decorator registers a test function on basis of the class name.
660 """
661 @attr(type=['negative', 'gate'])
662 def generic_test(self):
Marc Koderer4f44d722014-08-07 14:04:58 +0200663 if hasattr(self, '_schema'):
664 self.execute(self._schema)
Marc Kodererb2978da2014-03-26 13:45:43 +0100665
666 cn = klass.__name__
667 cn = cn.replace('JSON', '')
668 cn = cn.replace('Test', '')
669 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
670 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
671 func_name = 'test_%s' % lower_cn
672 setattr(klass, func_name, generic_test)
673 return klass
674
675
Sean Dague35a7caf2013-05-10 10:38:22 -0400676def call_until_true(func, duration, sleep_for):
677 """
678 Call the given function until it returns True (and return True) or
679 until the specified duration (in seconds) elapses (and return
680 False).
681
682 :param func: A zero argument callable that returns True on success.
683 :param duration: The number of seconds for which to attempt a
684 successful call of the function.
685 :param sleep_for: The number of seconds to sleep after an unsuccessful
686 invocation of the function.
687 """
688 now = time.time()
689 timeout = now + duration
690 while now < timeout:
691 if func():
692 return True
Sean Dague35a7caf2013-05-10 10:38:22 -0400693 time.sleep(sleep_for)
694 now = time.time()
695 return False