blob: ae77284d71da9aa79c91ba968ba5f5a546494142 [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
Doug Hellmann583ce2c2015-03-11 14:55:46 +000027from oslo_log import log as logging
28from oslo_utils import importutils
Chris Hoge296558c2015-02-19 00:29:49 -060029import six
Marc Koderer674c8fc2014-03-17 09:45:04 +010030import testscenarios
ivan-zhu1feeb382013-01-24 10:14:39 +080031import testtools
Jay Pipes051075a2012-04-28 17:39:37 -040032
Matthew Treinish3e046852013-07-23 16:00:24 -040033from tempest import clients
Andrea Frittoli8283b4e2014-07-17 13:28:58 +010034from tempest.common import credentials
Rohan Kanade9ce97df2013-12-10 18:59:35 +053035from tempest.common import fixed_network
Marc Koderer6ee82dc2014-02-17 10:26:29 +010036import tempest.common.generator.valid_generator as valid
Attila Fazekasdc216422013-01-29 15:12:14 +010037from tempest import config
Matthew Treinish16c43792013-09-09 19:55:23 +000038from tempest import exceptions
Jay Pipes051075a2012-04-28 17:39:37 -040039
40LOG = logging.getLogger(__name__)
41
Sean Dague86bd8422013-12-20 09:56:44 -050042CONF = config.CONF
43
Jay Pipes051075a2012-04-28 17:39:37 -040044
Chris Yeoh55530bb2013-02-08 16:04:27 +103045def attr(*args, **kwargs):
Matthew Treinisha74f5d42014-02-07 20:25:44 -050046 """A decorator which applies the testtools attr decorator
Chris Yeoh55530bb2013-02-08 16:04:27 +103047
Matthew Treinisha74f5d42014-02-07 20:25:44 -050048 This decorator applies the testtools.testcase.attr if it is in the list of
49 attributes to testtools we want to apply.
Attila Fazekasb2902af2013-02-16 16:22:44 +010050 """
Chris Yeoh55530bb2013-02-08 16:04:27 +103051
52 def decorator(f):
Giulio Fidente4946a052013-05-14 12:23:51 +020053 if 'type' in kwargs and isinstance(kwargs['type'], str):
54 f = testtools.testcase.attr(kwargs['type'])(f)
55 elif 'type' in kwargs and isinstance(kwargs['type'], list):
56 for attr in kwargs['type']:
57 f = testtools.testcase.attr(attr)(f)
Matthew Treinisha74f5d42014-02-07 20:25:44 -050058 return f
Chris Yeoh55530bb2013-02-08 16:04:27 +103059
60 return decorator
61
62
Chris Hoge296558c2015-02-19 00:29:49 -060063def idempotent_id(id):
64 """Stub for metadata decorator"""
65 if not isinstance(id, six.string_types):
66 raise TypeError('Test idempotent_id must be string not %s'
67 '' % type(id).__name__)
68 uuid.UUID(id)
69
70 def decorator(f):
71 f = testtools.testcase.attr('id-%s' % id)(f)
72 if f.__doc__:
73 f.__doc__ = 'Test idempotent id: %s\n%s' % (id, f.__doc__)
74 else:
75 f.__doc__ = 'Test idempotent id: %s' % id
76 return f
77 return decorator
78
79
Matthew Treinish3d8c7322014-08-03 23:53:28 -040080def get_service_list():
Matthew Treinish8afbffd2014-01-21 23:56:13 +000081 service_list = {
82 'compute': CONF.service_available.nova,
83 'image': CONF.service_available.glance,
Adam Gandelman4a48a602014-03-20 18:23:18 -070084 'baremetal': CONF.service_available.ironic,
Matthew Treinish8afbffd2014-01-21 23:56:13 +000085 'volume': CONF.service_available.cinder,
86 'orchestration': CONF.service_available.heat,
87 # NOTE(mtreinish) nova-network will provide networking functionality
88 # if neutron isn't available, so always set to True.
89 'network': True,
90 'identity': True,
91 'object_storage': CONF.service_available.swift,
92 'dashboard': CONF.service_available.horizon,
ekhugen7aff0992014-08-04 19:01:57 +000093 'telemetry': CONF.service_available.ceilometer,
Matthew Treinishb66c94e2015-03-11 13:00:48 -040094 'data_processing': CONF.service_available.sahara,
95 'database': CONF.service_available.trove
Matthew Treinish8afbffd2014-01-21 23:56:13 +000096 }
Matthew Treinish3d8c7322014-08-03 23:53:28 -040097 return service_list
Matthew Treinish16c43792013-09-09 19:55:23 +000098
Matthew Treinish3d8c7322014-08-03 23:53:28 -040099
100def services(*args, **kwargs):
101 """A decorator used to set an attr for each service used in a test case
102
103 This decorator applies a testtools attr for each service that gets
104 exercised by a test case.
105 """
Matthew Treinish16c43792013-09-09 19:55:23 +0000106 def decorator(f):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400107 services = ['compute', 'image', 'baremetal', 'volume', 'orchestration',
108 'network', 'identity', 'object_storage', 'dashboard',
Matthew Treinishb66c94e2015-03-11 13:00:48 -0400109 'telemetry', 'data_processing', 'database']
Matthew Treinish16c43792013-09-09 19:55:23 +0000110 for service in args:
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400111 if service not in services:
112 raise exceptions.InvalidServiceTag('%s is not a valid '
113 'service' % service)
Matthew Treinish16c43792013-09-09 19:55:23 +0000114 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000115
116 @functools.wraps(f)
117 def wrapper(self, *func_args, **func_kwargs):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400118 service_list = get_service_list()
119
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000120 for service in args:
121 if not service_list[service]:
122 msg = 'Skipped because the %s service is not available' % (
123 service)
124 raise testtools.TestCase.skipException(msg)
125 return f(self, *func_args, **func_kwargs)
126 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000127 return decorator
128
129
Marc Koderer32221b8e2013-08-23 13:57:50 +0200130def stresstest(*args, **kwargs):
131 """Add stress test decorator
132
133 For all functions with this decorator a attr stress will be
134 set automatically.
135
136 @param class_setup_per: allowed values are application, process, action
137 ``application``: once in the stress job lifetime
138 ``process``: once in the worker process lifetime
139 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200140 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200141 """
142 def decorator(f):
143 if 'class_setup_per' in kwargs:
144 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
145 else:
146 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200147 if 'allow_inheritance' in kwargs:
148 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
149 else:
150 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200151 attr(type='stress')(f)
152 return f
153 return decorator
154
155
Matthew Treinishe3d26142013-11-26 19:14:58 +0000156def requires_ext(*args, **kwargs):
157 """A decorator to skip tests if an extension is not enabled
158
159 @param extension
160 @param service
161 """
162 def decorator(func):
163 @functools.wraps(func)
164 def wrapper(*func_args, **func_kwargs):
165 if not is_extension_enabled(kwargs['extension'],
166 kwargs['service']):
167 msg = "Skipped because %s extension: %s is not enabled" % (
168 kwargs['service'], kwargs['extension'])
169 raise testtools.TestCase.skipException(msg)
170 return func(*func_args, **func_kwargs)
171 return wrapper
172 return decorator
173
174
175def is_extension_enabled(extension_name, service):
176 """A function that will check the list of enabled extensions from config
177
178 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000179 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000180 'compute': CONF.compute_feature_enabled.api_extensions,
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000181 'volume': CONF.volume_feature_enabled.api_extensions,
182 'network': CONF.network_feature_enabled.api_extensions,
183 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000184 }
Simeon Monov5d7effe2014-07-16 07:32:38 +0300185 if len(config_dict[service]) == 0:
186 return False
Matthew Treinishe3d26142013-11-26 19:14:58 +0000187 if config_dict[service][0] == 'all':
188 return True
189 if extension_name in config_dict[service]:
190 return True
191 return False
192
Ian Wienand98c35f32013-07-23 20:34:23 +1000193
Attila Fazekasf86fa312013-07-30 19:56:39 +0200194at_exit_set = set()
195
196
197def validate_tearDownClass():
198 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400199 LOG.error(
200 "tearDownClass does not call the super's "
201 "tearDownClass in these classes: \n"
202 + str(at_exit_set))
203
Attila Fazekasf86fa312013-07-30 19:56:39 +0200204
205atexit.register(validate_tearDownClass)
206
Attila Fazekas53943322014-02-10 16:07:34 +0100207
Matthew Treinish2474f412014-11-17 18:11:56 -0500208class BaseTestCase(testtools.testcase.WithAttributes,
209 testtools.TestCase):
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100210 """The test base class defines Tempest framework for class level fixtures.
211 `setUpClass` and `tearDownClass` are defined here and cannot be overwritten
212 by subclasses (enforced via hacking rule T105).
213
214 Set-up is split in a series of steps (setup stages), which can be
215 overwritten by test classes. Set-up stages are:
216 - skip_checks
217 - setup_credentials
218 - setup_clients
219 - resource_setup
220
221 Tear-down is also split in a series of steps (teardown stages), which are
222 stacked for execution only if the corresponding setup stage had been
223 reached during the setup phase. Tear-down stages are:
224 - clear_isolated_creds (defined in the base test class)
225 - resource_cleanup
226 """
Attila Fazekasc43fec82013-04-09 23:17:52 +0200227
Attila Fazekasf86fa312013-07-30 19:56:39 +0200228 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100229 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200230
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000231 # NOTE(andreaf) credentials holds a list of the credentials to be allocated
232 # at class setup time. Credential types can be 'primary', 'alt' or 'admin'
233 credentials = []
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 """
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000319 if 'admin' in cls.credentials and not credentials.is_admin_available():
320 msg = "Missing Identity Admin API credentials in configuration."
321 raise cls.skipException(msg)
322 if 'alt' is cls.credentials and not credentials.is_alt_available():
323 msg = "Missing a 2nd set of API credentials in configuration."
324 raise cls.skipException(msg)
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100325
326 @classmethod
327 def setup_credentials(cls):
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000328 """Allocate credentials and the client managers from them.
329 A test class that requires network resources must override
330 setup_credentials and defined the required resources before super
331 is invoked.
332 """
333 for credentials_type in cls.credentials:
334 # This may raise an exception in case credentials are not available
335 # In that case we want to let the exception through and the test
336 # fail accordingly
337 manager = cls.get_client_manager(
338 credential_type=credentials_type)
339 setattr(cls, 'os_%s' % credentials_type, manager)
340 # Setup some common aliases
341 # TODO(andreaf) The aliases below are a temporary hack
342 # to avoid changing too much code in one patch. They should
343 # be removed eventually
344 if credentials_type == 'primary':
345 cls.os = cls.manager = cls.os_primary
346 if credentials_type == 'admin':
347 cls.os_adm = cls.admin_manager = cls.os_admin
348 if credentials_type == 'alt':
349 cls.alt_manager = cls.os_alt
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100350
351 @classmethod
352 def setup_clients(cls):
353 """Create links to the clients into the test object."""
354 # TODO(andreaf) There is a fair amount of code that could me moved from
355 # base / test classes in here. Ideally tests should be able to only
356 # specify which client is `client` and nothing else.
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100357 pass
Attila Fazekasf86fa312013-07-30 19:56:39 +0200358
Emily Hugenbruch5bd4cbf2014-12-17 21:38:38 +0000359 @classmethod
360 def resource_setup(cls):
361 """Class level resource setup for test cases.
362 """
363 pass
364
365 @classmethod
366 def resource_cleanup(cls):
367 """Class level resource cleanup for test cases.
368 Resource cleanup must be able to handle the case of partially setup
369 resources, in case a failure during `resource_setup` should happen.
370 """
371 pass
372
Attila Fazekasf86fa312013-07-30 19:56:39 +0200373 def setUp(self):
374 super(BaseTestCase, self).setUp()
375 if not self.setUpClassCalled:
376 raise RuntimeError("setUpClass does not calls the super's"
377 "setUpClass in the "
378 + self.__class__.__name__)
379 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400380 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
381 try:
382 test_timeout = int(test_timeout)
383 except ValueError:
384 test_timeout = 0
385 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200386 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400387
388 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
389 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200390 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
391 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400392 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
393 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200394 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
395 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200396 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
397 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200398 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400399 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200400 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400401
Matthew Treinish3e046852013-07-23 16:00:24 -0400402 @classmethod
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000403 def get_client_manager(cls, identity_version=None,
404 credential_type='primary'):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700405 """
tanlin4956a642014-02-13 16:52:11 +0800406 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700407 """
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700408 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Andrea Frittoli (andreaf)345c7fa2015-03-25 10:10:42 -0400409 identity_version = identity_version or CONF.identity.auth_version
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100410
Marc Koderer44dce622014-11-14 10:08:12 +0100411 if (not hasattr(cls, 'isolated_creds') or
412 not cls.isolated_creds.name == cls.__name__):
413 cls.isolated_creds = credentials.get_isolated_credentials(
414 name=cls.__name__, network_resources=cls.network_resources,
415 force_tenant_isolation=force_tenant_isolation,
Andrea Frittoli (andreaf)345c7fa2015-03-25 10:10:42 -0400416 identity_version=identity_version
Marc Koderer44dce622014-11-14 10:08:12 +0100417 )
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100418
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000419 credentials_method = 'get_%s_creds' % credential_type
420 if hasattr(cls.isolated_creds, credentials_method):
421 creds = getattr(cls.isolated_creds, credentials_method)()
422 else:
423 raise exceptions.InvalidCredentials(
424 "Invalid credentials type %s" % credential_type)
Andrea Frittolic0978352015-02-06 15:57:40 +0000425 os = clients.Manager(credentials=creds, service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700426 return os
427
428 @classmethod
429 def clear_isolated_creds(cls):
430 """
431 Clears isolated creds if set
432 """
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100433 if hasattr(cls, 'isolated_creds'):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700434 cls.isolated_creds.clear_isolated_creds()
435
436 @classmethod
Andrea Frittoli7d5ed592015-02-10 01:10:23 +0000437 def set_network_resources(cls, network=False, router=False, subnet=False,
Matthew Treinish9f756a02014-01-15 10:26:07 -0500438 dhcp=False):
439 """Specify which network resources should be created
440
441 @param network
442 @param router
443 @param subnet
444 @param dhcp
445 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000446 # network resources should be set only once from callers
447 # in order to ensure that even if it's called multiple times in
448 # a chain of overloaded methods, the attribute is set only
449 # in the leaf class
Andrea Frittoli7d5ed592015-02-10 01:10:23 +0000450 if not cls.network_resources:
451 cls.network_resources = {
Salvatore Orlando5a337242014-01-15 22:49:22 +0000452 'network': network,
453 'router': router,
454 'subnet': subnet,
455 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500456
Rohan Kanade9ce97df2013-12-10 18:59:35 +0530457 @classmethod
458 def get_tenant_network(cls):
459 """Get the network to be used in testing
460
461 :return: network dict including 'id' and 'name'
462 """
463 # Make sure isolated_creds exists and get a network client
464 networks_client = cls.get_client_manager().networks_client
465 isolated_creds = getattr(cls, 'isolated_creds', None)
Andrea Frittoli700711e2015-04-02 11:39:38 +0100466 # In case of nova network, isolated tenants are not able to list the
467 # network configured in fixed_network_name, even if the can use it
468 # for their servers, so using an admin network client to validate
469 # the network name
470 if (not CONF.service_available.neutron and
471 credentials.is_admin_available()):
Rohan Kanade9ce97df2013-12-10 18:59:35 +0530472 admin_creds = isolated_creds.get_admin_creds()
473 networks_client = clients.Manager(admin_creds).networks_client
474 return fixed_network.get_tenant_network(isolated_creds,
475 networks_client)
476
Mark Maglana5885eb32014-02-28 10:57:34 -0800477 def assertEmpty(self, list, msg=None):
478 self.assertTrue(len(list) == 0, msg)
479
480 def assertNotEmpty(self, list, msg=None):
481 self.assertTrue(len(list) > 0, msg)
482
Attila Fazekasdc216422013-01-29 15:12:14 +0100483
Marc Koderer24eb89c2014-01-31 11:23:33 +0100484class NegativeAutoTest(BaseTestCase):
485
486 _resources = {}
487
488 @classmethod
489 def setUpClass(cls):
490 super(NegativeAutoTest, cls).setUpClass()
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000491 os = cls.get_client_manager(credential_type='primary')
Marc Koderer24eb89c2014-01-31 11:23:33 +0100492 cls.client = os.negative_client
493
494 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100495 def load_tests(*args):
496 """
497 Wrapper for testscenarios to set the mandatory scenarios variable
498 only in case a real test loader is in place. Will be automatically
499 called in case the variable "load_tests" is set.
500 """
501 if getattr(args[0], 'suiteClass', None) is not None:
502 loader, standard_tests, pattern = args
503 else:
504 standard_tests, module, loader = args
505 for test in testtools.iterate_tests(standard_tests):
Marc Koderer4f44d722014-08-07 14:04:58 +0200506 schema = getattr(test, '_schema', None)
Marc Koderer3dd31052014-11-27 09:31:00 +0100507 if schema is not None:
Marc Koderer4f44d722014-08-07 14:04:58 +0200508 setattr(test, 'scenarios',
509 NegativeAutoTest.generate_scenario(schema))
Marc Koderer674c8fc2014-03-17 09:45:04 +0100510 return testscenarios.load_tests_apply_scenarios(*args)
511
512 @staticmethod
Marc Koderer4f44d722014-08-07 14:04:58 +0200513 def generate_scenario(description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100514 """
515 Generates the test scenario list for a given description.
516
Marc Koderer4f44d722014-08-07 14:04:58 +0200517 :param description: A file or dictionary with the following entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100518 name (required) name for the api
519 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
520 url (required) the url to be appended to the catalog url with '%s'
521 for each resource mentioned
522 resources: (optional) A list of resource names such as "server",
523 "flavor", etc. with an element for each '%s' in the url. This
524 method will call self.get_resource for each element when
525 constructing the positive test case template so negative
526 subclasses are expected to return valid resource ids when
527 appropriate.
528 json-schema (optional) A valid json schema that will be used to
529 create invalid data for the api calls. For "GET" and "HEAD",
530 the data is used to generate query strings appended to the url,
531 otherwise for the body of the http call.
532 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100533 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100534 generator = importutils.import_class(
535 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100536 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100537 schema = description.get("json-schema", None)
538 resources = description.get("resources", [])
539 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100540 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100541 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100542 if isinstance(resource, dict):
543 expected_result = resource['expected_result']
544 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100545 LOG.debug("Add resource to test %s" % resource)
546 scn_name = "inv_res_%s" % (resource)
547 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100548 str(uuid.uuid4())),
549 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100550 }))
551 if schema is not None:
Marc Kodererf07f5d12014-09-01 09:47:23 +0200552 for scenario in generator.generate_scenarios(schema):
553 scenario_list.append((scenario['_negtest_name'],
554 scenario))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100555 LOG.debug(scenario_list)
556 return scenario_list
557
Marc Koderer4f44d722014-08-07 14:04:58 +0200558 def execute(self, description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100559 """
560 Execute a http call on an api that are expected to
561 result in client errors. First it uses invalid resources that are part
562 of the url, and then invalid data for queries and http request bodies.
563
Marc Koderer4f44d722014-08-07 14:04:58 +0200564 :param description: A json file or dictionary with the following
565 entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100566 name (required) name for the api
567 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
568 url (required) the url to be appended to the catalog url with '%s'
569 for each resource mentioned
570 resources: (optional) A list of resource names such as "server",
571 "flavor", etc. with an element for each '%s' in the url. This
572 method will call self.get_resource for each element when
573 constructing the positive test case template so negative
574 subclasses are expected to return valid resource ids when
575 appropriate.
576 json-schema (optional) A valid json schema that will be used to
577 create invalid data for the api calls. For "GET" and "HEAD",
578 the data is used to generate query strings appended to the url,
579 otherwise for the body of the http call.
580
581 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100582 LOG.info("Executing %s" % description["name"])
583 LOG.debug(description)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200584 generator = importutils.import_class(
585 CONF.negative.test_generator)()
586 schema = description.get("json-schema", None)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100587 method = description["http-method"]
588 url = description["url"]
Marc Kodererf07f5d12014-09-01 09:47:23 +0200589 expected_result = None
590 if "default_result_code" in description:
591 expected_result = description["default_result_code"]
Marc Koderer24eb89c2014-01-31 11:23:33 +0100592
593 resources = [self.get_resource(r) for
594 r in description.get("resources", [])]
595
596 if hasattr(self, "resource"):
597 # Note(mkoderer): The resources list already contains an invalid
598 # entry (see get_resource).
599 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100600 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100601 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100602 valid_schema = \
603 valid.ValidTestGenerator().generate_valid(schema)
604 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200605 elif hasattr(self, "_negtest_name"):
606 schema_under_test = \
607 valid.ValidTestGenerator().generate_valid(schema)
608 local_expected_result = \
609 generator.generate_payload(self, schema_under_test)
610 if local_expected_result is not None:
611 expected_result = local_expected_result
612 new_url, body = \
613 self._http_arguments(schema_under_test, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100614 else:
615 raise Exception("testscenarios are not active. Please make sure "
616 "that your test runner supports the load_tests "
617 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100618
Marc Kodererf857fda2014-03-05 15:58:00 +0100619 if "admin_client" in description and description["admin_client"]:
David Kranzafecec02015-03-23 14:27:15 -0400620 if not credentials.is_admin_available():
621 msg = ("Missing Identity Admin API credentials in"
622 "configuration.")
623 raise self.skipException(msg)
624 creds = self.isolated_creds.get_admin_creds()
625 os_adm = clients.Manager(credentials=creds)
626 client = os_adm.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100627 else:
628 client = self.client
629 resp, resp_body = client.send_request(method, new_url,
630 resources, body=body)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200631 self._check_negative_response(expected_result, resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100632
633 def _http_arguments(self, json_dict, url, method):
634 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
635 if not json_dict:
636 return url, None
637 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
638 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
639 else:
640 return url, json.dumps(json_dict)
641
Marc Kodererf07f5d12014-09-01 09:47:23 +0200642 def _check_negative_response(self, expected_result, result, body):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100643 self.assertTrue(result >= 400 and result < 500 and result != 413,
644 "Expected client error, got %s:%s" %
645 (result, body))
646 self.assertTrue(expected_result is None or expected_result == result,
647 "Expected %s, got %s:%s" %
648 (expected_result, result, body))
649
650 @classmethod
651 def set_resource(cls, name, resource):
652 """
653 This function can be used in setUpClass context to register a resoruce
654 for a test.
655
656 :param name: The name of the kind of resource such as "flavor", "role",
657 etc.
658 :resource: The id of the resource
659 """
660 cls._resources[name] = resource
661
662 def get_resource(self, name):
663 """
664 Return a valid uuid for a type of resource. If a real resource is
665 needed as part of a url then this method should return one. Otherwise
666 it can return None.
667
668 :param name: The name of the kind of resource such as "flavor", "role",
669 etc.
670 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100671 if isinstance(name, dict):
672 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100673 if hasattr(self, "resource") and self.resource[0] == name:
674 LOG.debug("Return invalid resource (%s) value: %s" %
675 (self.resource[0], self.resource[1]))
676 return self.resource[1]
677 if name in self._resources:
678 return self._resources[name]
679 return None
680
681
Marc Kodererb2978da2014-03-26 13:45:43 +0100682def SimpleNegativeAutoTest(klass):
683 """
684 This decorator registers a test function on basis of the class name.
685 """
Sean Dague5e1bcd92015-04-27 09:08:36 -0400686 @attr(type=['negative'])
Marc Kodererb2978da2014-03-26 13:45:43 +0100687 def generic_test(self):
Marc Koderer4f44d722014-08-07 14:04:58 +0200688 if hasattr(self, '_schema'):
689 self.execute(self._schema)
Marc Kodererb2978da2014-03-26 13:45:43 +0100690
691 cn = klass.__name__
692 cn = cn.replace('JSON', '')
693 cn = cn.replace('Test', '')
694 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
695 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
696 func_name = 'test_%s' % lower_cn
697 setattr(klass, func_name, generic_test)
698 return klass
699
700
Sean Dague35a7caf2013-05-10 10:38:22 -0400701def call_until_true(func, duration, sleep_for):
702 """
703 Call the given function until it returns True (and return True) or
704 until the specified duration (in seconds) elapses (and return
705 False).
706
707 :param func: A zero argument callable that returns True on success.
708 :param duration: The number of seconds for which to attempt a
709 successful call of the function.
710 :param sleep_for: The number of seconds to sleep after an unsuccessful
711 invocation of the function.
712 """
713 now = time.time()
714 timeout = now + duration
715 while now < timeout:
716 if func():
717 return True
Sean Dague35a7caf2013-05-10 10:38:22 -0400718 time.sleep(sleep_for)
719 now = time.time()
720 return False