blob: da936b4eff4d6a2e177b56f1af174ba0ba369aaf [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)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093055 if kwargs['type'] == 'smoke':
56 f = testtools.testcase.attr('gate')(f)
Giulio Fidente4946a052013-05-14 12:23:51 +020057 elif 'type' in kwargs and isinstance(kwargs['type'], list):
58 for attr in kwargs['type']:
59 f = testtools.testcase.attr(attr)(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093060 if attr == 'smoke':
61 f = testtools.testcase.attr('gate')(f)
Matthew Treinisha74f5d42014-02-07 20:25:44 -050062 return f
Chris Yeoh55530bb2013-02-08 16:04:27 +103063
64 return decorator
65
66
Chris Hoge296558c2015-02-19 00:29:49 -060067def idempotent_id(id):
68 """Stub for metadata decorator"""
69 if not isinstance(id, six.string_types):
70 raise TypeError('Test idempotent_id must be string not %s'
71 '' % type(id).__name__)
72 uuid.UUID(id)
73
74 def decorator(f):
75 f = testtools.testcase.attr('id-%s' % id)(f)
76 if f.__doc__:
77 f.__doc__ = 'Test idempotent id: %s\n%s' % (id, f.__doc__)
78 else:
79 f.__doc__ = 'Test idempotent id: %s' % id
80 return f
81 return decorator
82
83
Matthew Treinish3d8c7322014-08-03 23:53:28 -040084def get_service_list():
Matthew Treinish8afbffd2014-01-21 23:56:13 +000085 service_list = {
86 'compute': CONF.service_available.nova,
87 'image': CONF.service_available.glance,
Adam Gandelman4a48a602014-03-20 18:23:18 -070088 'baremetal': CONF.service_available.ironic,
Matthew Treinish8afbffd2014-01-21 23:56:13 +000089 'volume': CONF.service_available.cinder,
90 'orchestration': CONF.service_available.heat,
91 # NOTE(mtreinish) nova-network will provide networking functionality
92 # if neutron isn't available, so always set to True.
93 'network': True,
94 'identity': True,
95 'object_storage': CONF.service_available.swift,
96 'dashboard': CONF.service_available.horizon,
ekhugen7aff0992014-08-04 19:01:57 +000097 'telemetry': CONF.service_available.ceilometer,
Matthew Treinishb66c94e2015-03-11 13:00:48 -040098 'data_processing': CONF.service_available.sahara,
99 'database': CONF.service_available.trove
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000100 }
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400101 return service_list
Matthew Treinish16c43792013-09-09 19:55:23 +0000102
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400103
104def services(*args, **kwargs):
105 """A decorator used to set an attr for each service used in a test case
106
107 This decorator applies a testtools attr for each service that gets
108 exercised by a test case.
109 """
Matthew Treinish16c43792013-09-09 19:55:23 +0000110 def decorator(f):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400111 services = ['compute', 'image', 'baremetal', 'volume', 'orchestration',
112 'network', 'identity', 'object_storage', 'dashboard',
Matthew Treinishb66c94e2015-03-11 13:00:48 -0400113 'telemetry', 'data_processing', 'database']
Matthew Treinish16c43792013-09-09 19:55:23 +0000114 for service in args:
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400115 if service not in services:
116 raise exceptions.InvalidServiceTag('%s is not a valid '
117 'service' % service)
Matthew Treinish16c43792013-09-09 19:55:23 +0000118 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000119
120 @functools.wraps(f)
121 def wrapper(self, *func_args, **func_kwargs):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400122 service_list = get_service_list()
123
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000124 for service in args:
125 if not service_list[service]:
126 msg = 'Skipped because the %s service is not available' % (
127 service)
128 raise testtools.TestCase.skipException(msg)
129 return f(self, *func_args, **func_kwargs)
130 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000131 return decorator
132
133
Marc Koderer32221b8e2013-08-23 13:57:50 +0200134def stresstest(*args, **kwargs):
135 """Add stress test decorator
136
137 For all functions with this decorator a attr stress will be
138 set automatically.
139
140 @param class_setup_per: allowed values are application, process, action
141 ``application``: once in the stress job lifetime
142 ``process``: once in the worker process lifetime
143 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200144 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200145 """
146 def decorator(f):
147 if 'class_setup_per' in kwargs:
148 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
149 else:
150 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200151 if 'allow_inheritance' in kwargs:
152 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
153 else:
154 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200155 attr(type='stress')(f)
156 return f
157 return decorator
158
159
Matthew Treinishe3d26142013-11-26 19:14:58 +0000160def requires_ext(*args, **kwargs):
161 """A decorator to skip tests if an extension is not enabled
162
163 @param extension
164 @param service
165 """
166 def decorator(func):
167 @functools.wraps(func)
168 def wrapper(*func_args, **func_kwargs):
169 if not is_extension_enabled(kwargs['extension'],
170 kwargs['service']):
171 msg = "Skipped because %s extension: %s is not enabled" % (
172 kwargs['service'], kwargs['extension'])
173 raise testtools.TestCase.skipException(msg)
174 return func(*func_args, **func_kwargs)
175 return wrapper
176 return decorator
177
178
179def is_extension_enabled(extension_name, service):
180 """A function that will check the list of enabled extensions from config
181
182 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000183 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000184 'compute': CONF.compute_feature_enabled.api_extensions,
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000185 'volume': CONF.volume_feature_enabled.api_extensions,
186 'network': CONF.network_feature_enabled.api_extensions,
187 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000188 }
Simeon Monov5d7effe2014-07-16 07:32:38 +0300189 if len(config_dict[service]) == 0:
190 return False
Matthew Treinishe3d26142013-11-26 19:14:58 +0000191 if config_dict[service][0] == 'all':
192 return True
193 if extension_name in config_dict[service]:
194 return True
195 return False
196
Ian Wienand98c35f32013-07-23 20:34:23 +1000197
Attila Fazekasf86fa312013-07-30 19:56:39 +0200198at_exit_set = set()
199
200
201def validate_tearDownClass():
202 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400203 LOG.error(
204 "tearDownClass does not call the super's "
205 "tearDownClass in these classes: \n"
206 + str(at_exit_set))
207
Attila Fazekasf86fa312013-07-30 19:56:39 +0200208
209atexit.register(validate_tearDownClass)
210
Attila Fazekas53943322014-02-10 16:07:34 +0100211
Matthew Treinish2474f412014-11-17 18:11:56 -0500212class BaseTestCase(testtools.testcase.WithAttributes,
213 testtools.TestCase):
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100214 """The test base class defines Tempest framework for class level fixtures.
215 `setUpClass` and `tearDownClass` are defined here and cannot be overwritten
216 by subclasses (enforced via hacking rule T105).
217
218 Set-up is split in a series of steps (setup stages), which can be
219 overwritten by test classes. Set-up stages are:
220 - skip_checks
221 - setup_credentials
222 - setup_clients
223 - resource_setup
224
225 Tear-down is also split in a series of steps (teardown stages), which are
226 stacked for execution only if the corresponding setup stage had been
227 reached during the setup phase. Tear-down stages are:
228 - clear_isolated_creds (defined in the base test class)
229 - resource_cleanup
230 """
Attila Fazekasc43fec82013-04-09 23:17:52 +0200231
Attila Fazekasf86fa312013-07-30 19:56:39 +0200232 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100233 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200234
Matthew Treinish9f756a02014-01-15 10:26:07 -0500235 network_resources = {}
236
Sean Dague2ef32ac2014-06-09 11:32:23 -0400237 # NOTE(sdague): log_format is defined inline here instead of using the oslo
238 # default because going through the config path recouples config to the
239 # stress tests too early, and depending on testr order will fail unit tests
240 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
241 '[%(name)s] %(message)s')
242
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200243 @classmethod
244 def setUpClass(cls):
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100245 # It should never be overridden by descendants
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200246 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
247 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200248 cls.setUpClassCalled = True
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100249 # Stack of (name, callable) to be invoked in reverse order at teardown
250 cls.teardowns = []
251 # All the configuration checks that may generate a skip
252 cls.skip_checks()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100253 try:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100254 # Allocation of all required credentials and client managers
255 cls.teardowns.append(('credentials', cls.clear_isolated_creds))
256 cls.setup_credentials()
257 # Shortcuts to clients
258 cls.setup_clients()
259 # Additional class-wide test resources
260 cls.teardowns.append(('resources', cls.resource_cleanup))
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100261 cls.resource_setup()
262 except Exception:
263 etype, value, trace = sys.exc_info()
Matthew Treinished2ad4f2014-12-23 15:18:32 -0500264 LOG.info("%s raised in %s.setUpClass. Invoking tearDownClass." % (
265 etype, cls.__name__))
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100266 cls.tearDownClass()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100267 try:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100268 raise etype, value, trace
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100269 finally:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100270 del trace # to avoid circular refs
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200271
Attila Fazekasf86fa312013-07-30 19:56:39 +0200272 @classmethod
273 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200274 at_exit_set.discard(cls)
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100275 # It should never be overridden by descendants
Attila Fazekasf86fa312013-07-30 19:56:39 +0200276 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
277 super(BaseTestCase, cls).tearDownClass()
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100278 # Save any existing exception, we always want to re-raise the original
279 # exception only
280 etype, value, trace = sys.exc_info()
281 # If there was no exception during setup we shall re-raise the first
282 # exception in teardown
283 re_raise = (etype is None)
284 while cls.teardowns:
285 name, teardown = cls.teardowns.pop()
286 # Catch any exception in tearDown so we can re-raise the original
287 # exception at the end
288 try:
289 teardown()
290 except Exception as te:
291 sys_exec_info = sys.exc_info()
292 tetype = sys_exec_info[0]
293 # TODO(andreaf): Till we have the ability to cleanup only
294 # resources that were successfully setup in resource_cleanup,
295 # log AttributeError as info instead of exception.
296 if tetype is AttributeError and name == 'resources':
297 LOG.info("tearDownClass of %s failed: %s" % (name, te))
298 else:
299 LOG.exception("teardown of %s failed: %s" % (name, te))
300 if not etype:
301 etype, value, trace = sys_exec_info
302 # If exceptions were raised during teardown, an not before, re-raise
303 # the first one
304 if re_raise and etype is not None:
305 try:
306 raise etype, value, trace
307 finally:
308 del trace # to avoid circular refs
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100309
310 @classmethod
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100311 def skip_checks(cls):
312 """Class level skip checks. Subclasses verify in here all
313 conditions that might prevent the execution of the entire test class.
314 Checks implemented here may not make use API calls, and should rely on
315 configuration alone.
316 In general skip checks that require an API call are discouraged.
317 If one is really needed it may be implemented either in the
318 resource_setup or at test level.
319 """
320 pass
321
322 @classmethod
323 def setup_credentials(cls):
324 """Allocate credentials and the client managers from them."""
325 # TODO(andreaf) There is a fair amount of code that could me moved from
326 # base / test classes in here. Ideally tests should be able to only
327 # specify a list of (additional) credentials the need to use.
328 pass
329
330 @classmethod
331 def setup_clients(cls):
332 """Create links to the clients into the test object."""
333 # TODO(andreaf) There is a fair amount of code that could me moved from
334 # base / test classes in here. Ideally tests should be able to only
335 # specify which client is `client` and nothing else.
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100336 pass
Attila Fazekasf86fa312013-07-30 19:56:39 +0200337
Emily Hugenbruch5bd4cbf2014-12-17 21:38:38 +0000338 @classmethod
339 def resource_setup(cls):
340 """Class level resource setup for test cases.
341 """
342 pass
343
344 @classmethod
345 def resource_cleanup(cls):
346 """Class level resource cleanup for test cases.
347 Resource cleanup must be able to handle the case of partially setup
348 resources, in case a failure during `resource_setup` should happen.
349 """
350 pass
351
Attila Fazekasf86fa312013-07-30 19:56:39 +0200352 def setUp(self):
353 super(BaseTestCase, self).setUp()
354 if not self.setUpClassCalled:
355 raise RuntimeError("setUpClass does not calls the super's"
356 "setUpClass in the "
357 + self.__class__.__name__)
358 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400359 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
360 try:
361 test_timeout = int(test_timeout)
362 except ValueError:
363 test_timeout = 0
364 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200365 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400366
367 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
368 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200369 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
370 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400371 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
372 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200373 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
374 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200375 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
376 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200377 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400378 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200379 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400380
Matthew Treinish3e046852013-07-23 16:00:24 -0400381 @classmethod
Andrea Frittoli (andreaf)345c7fa2015-03-25 10:10:42 -0400382 def get_client_manager(cls, identity_version=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700383 """
tanlin4956a642014-02-13 16:52:11 +0800384 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700385 """
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700386 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Andrea Frittoli (andreaf)345c7fa2015-03-25 10:10:42 -0400387 identity_version = identity_version or CONF.identity.auth_version
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100388
Marc Koderer44dce622014-11-14 10:08:12 +0100389 if (not hasattr(cls, 'isolated_creds') or
390 not cls.isolated_creds.name == cls.__name__):
391 cls.isolated_creds = credentials.get_isolated_credentials(
392 name=cls.__name__, network_resources=cls.network_resources,
393 force_tenant_isolation=force_tenant_isolation,
Andrea Frittoli (andreaf)345c7fa2015-03-25 10:10:42 -0400394 identity_version=identity_version
Marc Koderer44dce622014-11-14 10:08:12 +0100395 )
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100396
397 creds = cls.isolated_creds.get_primary_creds()
Andrea Frittolic0978352015-02-06 15:57:40 +0000398 os = clients.Manager(credentials=creds, service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700399 return os
400
401 @classmethod
402 def clear_isolated_creds(cls):
403 """
404 Clears isolated creds if set
405 """
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100406 if hasattr(cls, 'isolated_creds'):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700407 cls.isolated_creds.clear_isolated_creds()
408
409 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400410 def _get_identity_admin_client(cls):
411 """
412 Returns an instance of the Identity Admin API client
413 """
Andrea Frittolic0978352015-02-06 15:57:40 +0000414 os = clients.AdminManager(service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400415 admin_client = os.identity_client
416 return admin_client
417
418 @classmethod
Andrea Frittoli7d5ed592015-02-10 01:10:23 +0000419 def set_network_resources(cls, network=False, router=False, subnet=False,
Matthew Treinish9f756a02014-01-15 10:26:07 -0500420 dhcp=False):
421 """Specify which network resources should be created
422
423 @param network
424 @param router
425 @param subnet
426 @param dhcp
427 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000428 # network resources should be set only once from callers
429 # in order to ensure that even if it's called multiple times in
430 # a chain of overloaded methods, the attribute is set only
431 # in the leaf class
Andrea Frittoli7d5ed592015-02-10 01:10:23 +0000432 if not cls.network_resources:
433 cls.network_resources = {
Salvatore Orlando5a337242014-01-15 22:49:22 +0000434 'network': network,
435 'router': router,
436 'subnet': subnet,
437 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500438
Rohan Kanade9ce97df2013-12-10 18:59:35 +0530439 @classmethod
440 def get_tenant_network(cls):
441 """Get the network to be used in testing
442
443 :return: network dict including 'id' and 'name'
444 """
445 # Make sure isolated_creds exists and get a network client
446 networks_client = cls.get_client_manager().networks_client
447 isolated_creds = getattr(cls, 'isolated_creds', None)
448 if credentials.is_admin_available():
449 admin_creds = isolated_creds.get_admin_creds()
450 networks_client = clients.Manager(admin_creds).networks_client
451 return fixed_network.get_tenant_network(isolated_creds,
452 networks_client)
453
Mark Maglana5885eb32014-02-28 10:57:34 -0800454 def assertEmpty(self, list, msg=None):
455 self.assertTrue(len(list) == 0, msg)
456
457 def assertNotEmpty(self, list, msg=None):
458 self.assertTrue(len(list) > 0, msg)
459
Attila Fazekasdc216422013-01-29 15:12:14 +0100460
Marc Koderer24eb89c2014-01-31 11:23:33 +0100461class NegativeAutoTest(BaseTestCase):
462
463 _resources = {}
464
465 @classmethod
466 def setUpClass(cls):
467 super(NegativeAutoTest, cls).setUpClass()
468 os = cls.get_client_manager()
469 cls.client = os.negative_client
Andrea Frittolic0978352015-02-06 15:57:40 +0000470 os_admin = clients.AdminManager(service=cls._service)
Marc Kodererf857fda2014-03-05 15:58:00 +0100471 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100472
473 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100474 def load_tests(*args):
475 """
476 Wrapper for testscenarios to set the mandatory scenarios variable
477 only in case a real test loader is in place. Will be automatically
478 called in case the variable "load_tests" is set.
479 """
480 if getattr(args[0], 'suiteClass', None) is not None:
481 loader, standard_tests, pattern = args
482 else:
483 standard_tests, module, loader = args
484 for test in testtools.iterate_tests(standard_tests):
Marc Koderer4f44d722014-08-07 14:04:58 +0200485 schema = getattr(test, '_schema', None)
Marc Koderer3dd31052014-11-27 09:31:00 +0100486 if schema is not None:
Marc Koderer4f44d722014-08-07 14:04:58 +0200487 setattr(test, 'scenarios',
488 NegativeAutoTest.generate_scenario(schema))
Marc Koderer674c8fc2014-03-17 09:45:04 +0100489 return testscenarios.load_tests_apply_scenarios(*args)
490
491 @staticmethod
Marc Koderer4f44d722014-08-07 14:04:58 +0200492 def generate_scenario(description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100493 """
494 Generates the test scenario list for a given description.
495
Marc Koderer4f44d722014-08-07 14:04:58 +0200496 :param description: A file or dictionary with the following entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100497 name (required) name for the api
498 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
499 url (required) the url to be appended to the catalog url with '%s'
500 for each resource mentioned
501 resources: (optional) A list of resource names such as "server",
502 "flavor", etc. with an element for each '%s' in the url. This
503 method will call self.get_resource for each element when
504 constructing the positive test case template so negative
505 subclasses are expected to return valid resource ids when
506 appropriate.
507 json-schema (optional) A valid json schema that will be used to
508 create invalid data for the api calls. For "GET" and "HEAD",
509 the data is used to generate query strings appended to the url,
510 otherwise for the body of the http call.
511 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100512 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100513 generator = importutils.import_class(
514 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100515 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100516 schema = description.get("json-schema", None)
517 resources = description.get("resources", [])
518 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100519 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100520 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100521 if isinstance(resource, dict):
522 expected_result = resource['expected_result']
523 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100524 LOG.debug("Add resource to test %s" % resource)
525 scn_name = "inv_res_%s" % (resource)
526 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100527 str(uuid.uuid4())),
528 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100529 }))
530 if schema is not None:
Marc Kodererf07f5d12014-09-01 09:47:23 +0200531 for scenario in generator.generate_scenarios(schema):
532 scenario_list.append((scenario['_negtest_name'],
533 scenario))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100534 LOG.debug(scenario_list)
535 return scenario_list
536
Marc Koderer4f44d722014-08-07 14:04:58 +0200537 def execute(self, description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100538 """
539 Execute a http call on an api that are expected to
540 result in client errors. First it uses invalid resources that are part
541 of the url, and then invalid data for queries and http request bodies.
542
Marc Koderer4f44d722014-08-07 14:04:58 +0200543 :param description: A json file or dictionary with the following
544 entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100545 name (required) name for the api
546 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
547 url (required) the url to be appended to the catalog url with '%s'
548 for each resource mentioned
549 resources: (optional) A list of resource names such as "server",
550 "flavor", etc. with an element for each '%s' in the url. This
551 method will call self.get_resource for each element when
552 constructing the positive test case template so negative
553 subclasses are expected to return valid resource ids when
554 appropriate.
555 json-schema (optional) A valid json schema that will be used to
556 create invalid data for the api calls. For "GET" and "HEAD",
557 the data is used to generate query strings appended to the url,
558 otherwise for the body of the http call.
559
560 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100561 LOG.info("Executing %s" % description["name"])
562 LOG.debug(description)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200563 generator = importutils.import_class(
564 CONF.negative.test_generator)()
565 schema = description.get("json-schema", None)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100566 method = description["http-method"]
567 url = description["url"]
Marc Kodererf07f5d12014-09-01 09:47:23 +0200568 expected_result = None
569 if "default_result_code" in description:
570 expected_result = description["default_result_code"]
Marc Koderer24eb89c2014-01-31 11:23:33 +0100571
572 resources = [self.get_resource(r) for
573 r in description.get("resources", [])]
574
575 if hasattr(self, "resource"):
576 # Note(mkoderer): The resources list already contains an invalid
577 # entry (see get_resource).
578 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100579 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100580 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100581 valid_schema = \
582 valid.ValidTestGenerator().generate_valid(schema)
583 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200584 elif hasattr(self, "_negtest_name"):
585 schema_under_test = \
586 valid.ValidTestGenerator().generate_valid(schema)
587 local_expected_result = \
588 generator.generate_payload(self, schema_under_test)
589 if local_expected_result is not None:
590 expected_result = local_expected_result
591 new_url, body = \
592 self._http_arguments(schema_under_test, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100593 else:
594 raise Exception("testscenarios are not active. Please make sure "
595 "that your test runner supports the load_tests "
596 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100597
Marc Kodererf857fda2014-03-05 15:58:00 +0100598 if "admin_client" in description and description["admin_client"]:
599 client = self.admin_client
600 else:
601 client = self.client
602 resp, resp_body = client.send_request(method, new_url,
603 resources, body=body)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200604 self._check_negative_response(expected_result, resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100605
606 def _http_arguments(self, json_dict, url, method):
607 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
608 if not json_dict:
609 return url, None
610 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
611 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
612 else:
613 return url, json.dumps(json_dict)
614
Marc Kodererf07f5d12014-09-01 09:47:23 +0200615 def _check_negative_response(self, expected_result, result, body):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100616 self.assertTrue(result >= 400 and result < 500 and result != 413,
617 "Expected client error, got %s:%s" %
618 (result, body))
619 self.assertTrue(expected_result is None or expected_result == result,
620 "Expected %s, got %s:%s" %
621 (expected_result, result, body))
622
623 @classmethod
624 def set_resource(cls, name, resource):
625 """
626 This function can be used in setUpClass context to register a resoruce
627 for a test.
628
629 :param name: The name of the kind of resource such as "flavor", "role",
630 etc.
631 :resource: The id of the resource
632 """
633 cls._resources[name] = resource
634
635 def get_resource(self, name):
636 """
637 Return a valid uuid for a type of resource. If a real resource is
638 needed as part of a url then this method should return one. Otherwise
639 it can return None.
640
641 :param name: The name of the kind of resource such as "flavor", "role",
642 etc.
643 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100644 if isinstance(name, dict):
645 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100646 if hasattr(self, "resource") and self.resource[0] == name:
647 LOG.debug("Return invalid resource (%s) value: %s" %
648 (self.resource[0], self.resource[1]))
649 return self.resource[1]
650 if name in self._resources:
651 return self._resources[name]
652 return None
653
654
Marc Kodererb2978da2014-03-26 13:45:43 +0100655def SimpleNegativeAutoTest(klass):
656 """
657 This decorator registers a test function on basis of the class name.
658 """
659 @attr(type=['negative', 'gate'])
660 def generic_test(self):
Marc Koderer4f44d722014-08-07 14:04:58 +0200661 if hasattr(self, '_schema'):
662 self.execute(self._schema)
Marc Kodererb2978da2014-03-26 13:45:43 +0100663
664 cn = klass.__name__
665 cn = cn.replace('JSON', '')
666 cn = cn.replace('Test', '')
667 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
668 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
669 func_name = 'test_%s' % lower_cn
670 setattr(klass, func_name, generic_test)
671 return klass
672
673
Sean Dague35a7caf2013-05-10 10:38:22 -0400674def call_until_true(func, duration, sleep_for):
675 """
676 Call the given function until it returns True (and return True) or
677 until the specified duration (in seconds) elapses (and return
678 False).
679
680 :param func: A zero argument callable that returns True on success.
681 :param duration: The number of seconds for which to attempt a
682 successful call of the function.
683 :param sleep_for: The number of seconds to sleep after an unsuccessful
684 invocation of the function.
685 """
686 now = time.time()
687 timeout = now + duration
688 while now < timeout:
689 if func():
690 return True
Sean Dague35a7caf2013-05-10 10:38:22 -0400691 time.sleep(sleep_for)
692 now = time.time()
693 return False