blob: 4a22b1b0844c6395613e8cc9ebf63de01735df97 [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
Attila Fazekasdc216422013-01-29 15:12:14 +010027import testresources
Marc Koderer674c8fc2014-03-17 09:45:04 +010028import testscenarios
ivan-zhu1feeb382013-01-24 10:14:39 +080029import testtools
Jay Pipes051075a2012-04-28 17:39:37 -040030
Matthew Treinish3e046852013-07-23 16:00:24 -040031from tempest import clients
Marc Koderer6ee82dc2014-02-17 10:26:29 +010032import tempest.common.generator.valid_generator as valid
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -070033from tempest.common import isolated_creds
Attila Fazekasdc216422013-01-29 15:12:14 +010034from tempest import config
Matthew Treinish16c43792013-09-09 19:55:23 +000035from tempest import exceptions
Marc Koderer6ee82dc2014-02-17 10:26:29 +010036from tempest.openstack.common import importutils
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040037from tempest.openstack.common import log as logging
Jay Pipes051075a2012-04-28 17:39:37 -040038
39LOG = logging.getLogger(__name__)
40
Sean Dague86bd8422013-12-20 09:56:44 -050041CONF = config.CONF
42
Samuel Merritt0d499bc2013-06-19 12:08:23 -070043# All the successful HTTP status codes from RFC 2616
44HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206)
45
Jay Pipes051075a2012-04-28 17:39:37 -040046
Chris Yeoh55530bb2013-02-08 16:04:27 +103047def attr(*args, **kwargs):
Matthew Treinisha74f5d42014-02-07 20:25:44 -050048 """A decorator which applies the testtools attr decorator
Chris Yeoh55530bb2013-02-08 16:04:27 +103049
Matthew Treinisha74f5d42014-02-07 20:25:44 -050050 This decorator applies the testtools.testcase.attr if it is in the list of
51 attributes to testtools we want to apply.
Attila Fazekasb2902af2013-02-16 16:22:44 +010052 """
Chris Yeoh55530bb2013-02-08 16:04:27 +103053
54 def decorator(f):
Giulio Fidente4946a052013-05-14 12:23:51 +020055 if 'type' in kwargs and isinstance(kwargs['type'], str):
56 f = testtools.testcase.attr(kwargs['type'])(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093057 if kwargs['type'] == 'smoke':
58 f = testtools.testcase.attr('gate')(f)
Giulio Fidente4946a052013-05-14 12:23:51 +020059 elif 'type' in kwargs and isinstance(kwargs['type'], list):
60 for attr in kwargs['type']:
61 f = testtools.testcase.attr(attr)(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093062 if attr == 'smoke':
63 f = testtools.testcase.attr('gate')(f)
Matthew Treinisha74f5d42014-02-07 20:25:44 -050064 return f
Chris Yeoh55530bb2013-02-08 16:04:27 +103065
66 return decorator
67
68
Zhi Kun Liu22fb4702014-02-27 16:15:24 +080069def safe_setup(f):
70 """A decorator used to wrap the setUpClass for cleaning up resources
71 when setUpClass failed.
Andrea Frittoli73ee2472014-09-15 12:31:53 +010072
73 Deprecated, see:
74 http://specs.openstack.org/openstack/qa-specs/specs/resource-cleanup.html
Zhi Kun Liu22fb4702014-02-27 16:15:24 +080075 """
Marc Koderer53b01232014-08-13 15:57:30 +020076 @functools.wraps(f)
Zhi Kun Liu22fb4702014-02-27 16:15:24 +080077 def decorator(cls):
78 try:
79 f(cls)
80 except Exception as se:
Attila Fazekasbfeb2822014-04-09 11:24:33 +020081 etype, value, trace = sys.exc_info()
Mauro S. M. Rodrigues5942bc02014-08-08 14:16:29 +000082 if etype is cls.skipException:
83 LOG.info("setUpClass skipped: %s:" % se)
84 else:
85 LOG.exception("setUpClass failed: %s" % se)
Zhi Kun Liu22fb4702014-02-27 16:15:24 +080086 try:
87 cls.tearDownClass()
88 except Exception as te:
89 LOG.exception("tearDownClass failed: %s" % te)
Attila Fazekasbfeb2822014-04-09 11:24:33 +020090 try:
91 raise etype(value), None, trace
92 finally:
93 del trace # for avoiding circular refs
Zhi Kun Liu22fb4702014-02-27 16:15:24 +080094
95 return decorator
96
97
Matthew Treinish3d8c7322014-08-03 23:53:28 -040098def get_service_list():
Matthew Treinish8afbffd2014-01-21 23:56:13 +000099 service_list = {
100 'compute': CONF.service_available.nova,
101 'image': CONF.service_available.glance,
Adam Gandelman4a48a602014-03-20 18:23:18 -0700102 'baremetal': CONF.service_available.ironic,
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000103 'volume': CONF.service_available.cinder,
104 'orchestration': CONF.service_available.heat,
105 # NOTE(mtreinish) nova-network will provide networking functionality
106 # if neutron isn't available, so always set to True.
107 'network': True,
108 'identity': True,
109 'object_storage': CONF.service_available.swift,
110 'dashboard': CONF.service_available.horizon,
ekhugen7aff0992014-08-04 19:01:57 +0000111 'telemetry': CONF.service_available.ceilometer,
Yaroslav Lobankovff42c872014-06-17 15:39:43 +0400112 'data_processing': CONF.service_available.sahara
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000113 }
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400114 return service_list
Matthew Treinish16c43792013-09-09 19:55:23 +0000115
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400116
117def services(*args, **kwargs):
118 """A decorator used to set an attr for each service used in a test case
119
120 This decorator applies a testtools attr for each service that gets
121 exercised by a test case.
122 """
Matthew Treinish16c43792013-09-09 19:55:23 +0000123 def decorator(f):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400124 services = ['compute', 'image', 'baremetal', 'volume', 'orchestration',
125 'network', 'identity', 'object_storage', 'dashboard',
126 'ceilometer', 'data_processing']
Matthew Treinish16c43792013-09-09 19:55:23 +0000127 for service in args:
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400128 if service not in services:
129 raise exceptions.InvalidServiceTag('%s is not a valid '
130 'service' % service)
Matthew Treinish16c43792013-09-09 19:55:23 +0000131 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000132
133 @functools.wraps(f)
134 def wrapper(self, *func_args, **func_kwargs):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400135 service_list = get_service_list()
136
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000137 for service in args:
138 if not service_list[service]:
139 msg = 'Skipped because the %s service is not available' % (
140 service)
141 raise testtools.TestCase.skipException(msg)
142 return f(self, *func_args, **func_kwargs)
143 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000144 return decorator
145
146
Marc Koderer32221b8e2013-08-23 13:57:50 +0200147def stresstest(*args, **kwargs):
148 """Add stress test decorator
149
150 For all functions with this decorator a attr stress will be
151 set automatically.
152
153 @param class_setup_per: allowed values are application, process, action
154 ``application``: once in the stress job lifetime
155 ``process``: once in the worker process lifetime
156 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200157 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200158 """
159 def decorator(f):
160 if 'class_setup_per' in kwargs:
161 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
162 else:
163 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200164 if 'allow_inheritance' in kwargs:
165 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
166 else:
167 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200168 attr(type='stress')(f)
169 return f
170 return decorator
171
172
Giulio Fidente83181a92013-10-01 06:02:24 +0200173def skip_because(*args, **kwargs):
174 """A decorator useful to skip tests hitting known bugs
175
176 @param bug: bug number causing the test to skip
177 @param condition: optional condition to be True for the skip to have place
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900178 @param interface: skip the test if it is the same as self._interface
Giulio Fidente83181a92013-10-01 06:02:24 +0200179 """
180 def decorator(f):
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900181 @functools.wraps(f)
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900182 def wrapper(self, *func_args, **func_kwargs):
183 skip = False
184 if "condition" in kwargs:
185 if kwargs["condition"] is True:
186 skip = True
187 elif "interface" in kwargs:
188 if kwargs["interface"] == self._interface:
189 skip = True
190 else:
191 skip = True
192 if "bug" in kwargs and skip is True:
Matthew Treinishede50272014-02-11 19:56:11 +0000193 if not kwargs['bug'].isdigit():
194 raise ValueError('bug must be a valid bug number')
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900195 msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
196 raise testtools.TestCase.skipException(msg)
197 return f(self, *func_args, **func_kwargs)
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900198 return wrapper
Giulio Fidente83181a92013-10-01 06:02:24 +0200199 return decorator
200
201
Matthew Treinishe3d26142013-11-26 19:14:58 +0000202def requires_ext(*args, **kwargs):
203 """A decorator to skip tests if an extension is not enabled
204
205 @param extension
206 @param service
207 """
208 def decorator(func):
209 @functools.wraps(func)
210 def wrapper(*func_args, **func_kwargs):
211 if not is_extension_enabled(kwargs['extension'],
212 kwargs['service']):
213 msg = "Skipped because %s extension: %s is not enabled" % (
214 kwargs['service'], kwargs['extension'])
215 raise testtools.TestCase.skipException(msg)
216 return func(*func_args, **func_kwargs)
217 return wrapper
218 return decorator
219
220
221def is_extension_enabled(extension_name, service):
222 """A function that will check the list of enabled extensions from config
223
224 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000225 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000226 'compute': CONF.compute_feature_enabled.api_extensions,
227 'compute_v3': CONF.compute_feature_enabled.api_v3_extensions,
228 'volume': CONF.volume_feature_enabled.api_extensions,
229 'network': CONF.network_feature_enabled.api_extensions,
230 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000231 }
Simeon Monov5d7effe2014-07-16 07:32:38 +0300232 if len(config_dict[service]) == 0:
233 return False
Matthew Treinishe3d26142013-11-26 19:14:58 +0000234 if config_dict[service][0] == 'all':
235 return True
236 if extension_name in config_dict[service]:
237 return True
238 return False
239
Ian Wienand98c35f32013-07-23 20:34:23 +1000240
Attila Fazekasf86fa312013-07-30 19:56:39 +0200241at_exit_set = set()
242
243
244def validate_tearDownClass():
245 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400246 LOG.error(
247 "tearDownClass does not call the super's "
248 "tearDownClass in these classes: \n"
249 + str(at_exit_set))
250
Attila Fazekasf86fa312013-07-30 19:56:39 +0200251
252atexit.register(validate_tearDownClass)
253
Attila Fazekas53943322014-02-10 16:07:34 +0100254if sys.version_info >= (2, 7):
255 class BaseDeps(testtools.TestCase,
Attila Fazekasdc216422013-01-29 15:12:14 +0100256 testtools.testcase.WithAttributes,
257 testresources.ResourcedTestCase):
Attila Fazekas53943322014-02-10 16:07:34 +0100258 pass
259else:
260 # Define asserts for py26
261 import unittest2
262
263 class BaseDeps(testtools.TestCase,
264 testtools.testcase.WithAttributes,
265 testresources.ResourcedTestCase,
266 unittest2.TestCase):
267 pass
268
269
270class BaseTestCase(BaseDeps):
Attila Fazekasc43fec82013-04-09 23:17:52 +0200271
Attila Fazekasf86fa312013-07-30 19:56:39 +0200272 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100273 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200274
Matthew Treinish9f756a02014-01-15 10:26:07 -0500275 network_resources = {}
276
Sean Dague2ef32ac2014-06-09 11:32:23 -0400277 # NOTE(sdague): log_format is defined inline here instead of using the oslo
278 # default because going through the config path recouples config to the
279 # stress tests too early, and depending on testr order will fail unit tests
280 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
281 '[%(name)s] %(message)s')
282
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200283 @classmethod
284 def setUpClass(cls):
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100285 # It should never be overridden by descendants
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200286 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
287 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200288 cls.setUpClassCalled = True
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100289 # No test resource is allocated until here
290 try:
291 # TODO(andreaf) Split-up resource_setup in stages:
292 # skip checks, pre-hook, credentials, clients, resources, post-hook
293 cls.resource_setup()
294 except Exception:
295 etype, value, trace = sys.exc_info()
296 LOG.info("%s in resource setup. Invoking tearDownClass." % etype)
297 # Catch any exception in tearDown so we can re-raise the original
298 # exception at the end
299 try:
300 cls.tearDownClass()
301 except Exception as te:
302 LOG.exception("tearDownClass failed: %s" % te)
303 try:
304 raise etype(value), None, trace
305 finally:
306 del trace # for avoiding circular refs
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200307
Attila Fazekasf86fa312013-07-30 19:56:39 +0200308 @classmethod
309 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200310 at_exit_set.discard(cls)
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100311 # It should never be overridden by descendants
Attila Fazekasf86fa312013-07-30 19:56:39 +0200312 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
313 super(BaseTestCase, cls).tearDownClass()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100314 try:
315 cls.resource_cleanup()
316 finally:
317 cls.clear_isolated_creds()
318
319 @classmethod
320 def resource_setup(cls):
321 """Class level setup steps for test cases.
322 Recommended order: skip checks, credentials, clients, resources.
323 """
324 pass
325
326 @classmethod
327 def resource_cleanup(cls):
328 """Class level resource cleanup for test cases. """
329 pass
Attila Fazekasf86fa312013-07-30 19:56:39 +0200330
331 def setUp(self):
332 super(BaseTestCase, self).setUp()
333 if not self.setUpClassCalled:
334 raise RuntimeError("setUpClass does not calls the super's"
335 "setUpClass in the "
336 + self.__class__.__name__)
337 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400338 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
339 try:
340 test_timeout = int(test_timeout)
341 except ValueError:
342 test_timeout = 0
343 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200344 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400345
346 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
347 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200348 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
349 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400350 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
351 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200352 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
353 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200354 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
355 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200356 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400357 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200358 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400359
Matthew Treinish3e046852013-07-23 16:00:24 -0400360 @classmethod
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000361 def get_client_manager(cls, interface=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700362 """
tanlin4956a642014-02-13 16:52:11 +0800363 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700364 """
Matthew Treinish9f756a02014-01-15 10:26:07 -0500365 cls.isolated_creds = isolated_creds.IsolatedCreds(
366 cls.__name__, network_resources=cls.network_resources)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700367
368 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000369 if CONF.compute.allow_tenant_isolation or force_tenant_isolation:
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700370 creds = cls.isolated_creds.get_primary_creds()
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000371 if getattr(cls, '_interface', None):
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000372 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100373 interface=cls._interface,
374 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000375 elif interface:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000376 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100377 interface=interface,
378 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000379 else:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000380 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100381 service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700382 else:
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000383 if getattr(cls, '_interface', None):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100384 os = clients.Manager(interface=cls._interface,
385 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000386 elif interface:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100387 os = clients.Manager(interface=interface, service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000388 else:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100389 os = clients.Manager(service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700390 return os
391
392 @classmethod
393 def clear_isolated_creds(cls):
394 """
395 Clears isolated creds if set
396 """
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100397 if hasattr(cls, 'isolated_creds'):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700398 cls.isolated_creds.clear_isolated_creds()
399
400 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400401 def _get_identity_admin_client(cls):
402 """
403 Returns an instance of the Identity Admin API client
404 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100405 os = clients.AdminManager(interface=cls._interface,
406 service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400407 admin_client = os.identity_client
408 return admin_client
409
410 @classmethod
Matthew Treinish9f756a02014-01-15 10:26:07 -0500411 def set_network_resources(self, network=False, router=False, subnet=False,
412 dhcp=False):
413 """Specify which network resources should be created
414
415 @param network
416 @param router
417 @param subnet
418 @param dhcp
419 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000420 # network resources should be set only once from callers
421 # in order to ensure that even if it's called multiple times in
422 # a chain of overloaded methods, the attribute is set only
423 # in the leaf class
424 if not self.network_resources:
425 self.network_resources = {
426 'network': network,
427 'router': router,
428 'subnet': subnet,
429 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500430
Mark Maglana5885eb32014-02-28 10:57:34 -0800431 def assertEmpty(self, list, msg=None):
432 self.assertTrue(len(list) == 0, msg)
433
434 def assertNotEmpty(self, list, msg=None):
435 self.assertTrue(len(list) > 0, msg)
436
Attila Fazekasdc216422013-01-29 15:12:14 +0100437
Marc Koderer24eb89c2014-01-31 11:23:33 +0100438class NegativeAutoTest(BaseTestCase):
439
440 _resources = {}
441
442 @classmethod
443 def setUpClass(cls):
444 super(NegativeAutoTest, cls).setUpClass()
445 os = cls.get_client_manager()
446 cls.client = os.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100447 os_admin = clients.AdminManager(interface=cls._interface,
448 service=cls._service)
449 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100450
451 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100452 def load_tests(*args):
453 """
454 Wrapper for testscenarios to set the mandatory scenarios variable
455 only in case a real test loader is in place. Will be automatically
456 called in case the variable "load_tests" is set.
457 """
458 if getattr(args[0], 'suiteClass', None) is not None:
459 loader, standard_tests, pattern = args
460 else:
461 standard_tests, module, loader = args
462 for test in testtools.iterate_tests(standard_tests):
463 schema_file = getattr(test, '_schema_file', None)
Marc Koderer4f44d722014-08-07 14:04:58 +0200464 schema = getattr(test, '_schema', None)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100465 if schema_file is not None:
466 setattr(test, 'scenarios',
467 NegativeAutoTest.generate_scenario(schema_file))
Marc Koderer4f44d722014-08-07 14:04:58 +0200468 elif schema is not None:
469 setattr(test, 'scenarios',
470 NegativeAutoTest.generate_scenario(schema))
Marc Koderer674c8fc2014-03-17 09:45:04 +0100471 return testscenarios.load_tests_apply_scenarios(*args)
472
473 @staticmethod
Marc Koderer4f44d722014-08-07 14:04:58 +0200474 def generate_scenario(description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100475 """
476 Generates the test scenario list for a given description.
477
Marc Koderer4f44d722014-08-07 14:04:58 +0200478 :param description: A file or dictionary with the following entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100479 name (required) name for the api
480 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
481 url (required) the url to be appended to the catalog url with '%s'
482 for each resource mentioned
483 resources: (optional) A list of resource names such as "server",
484 "flavor", etc. with an element for each '%s' in the url. This
485 method will call self.get_resource for each element when
486 constructing the positive test case template so negative
487 subclasses are expected to return valid resource ids when
488 appropriate.
489 json-schema (optional) A valid json schema that will be used to
490 create invalid data for the api calls. For "GET" and "HEAD",
491 the data is used to generate query strings appended to the url,
492 otherwise for the body of the http call.
493 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100494 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100495 generator = importutils.import_class(
496 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100497 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100498 schema = description.get("json-schema", None)
499 resources = description.get("resources", [])
500 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100501 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100502 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100503 if isinstance(resource, dict):
504 expected_result = resource['expected_result']
505 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100506 LOG.debug("Add resource to test %s" % resource)
507 scn_name = "inv_res_%s" % (resource)
508 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100509 str(uuid.uuid4())),
510 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100511 }))
512 if schema is not None:
Marc Kodererf857fda2014-03-05 15:58:00 +0100513 for name, schema, expected_result in generator.generate(schema):
514 if (expected_result is None and
515 "default_result_code" in description):
516 expected_result = description["default_result_code"]
517 scenario_list.append((name,
518 {"schema": schema,
519 "expected_result": expected_result}))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100520 LOG.debug(scenario_list)
521 return scenario_list
522
Marc Koderer4f44d722014-08-07 14:04:58 +0200523 def execute(self, description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100524 """
525 Execute a http call on an api that are expected to
526 result in client errors. First it uses invalid resources that are part
527 of the url, and then invalid data for queries and http request bodies.
528
Marc Koderer4f44d722014-08-07 14:04:58 +0200529 :param description: A json file or dictionary with the following
530 entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100531 name (required) name for the api
532 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
533 url (required) the url to be appended to the catalog url with '%s'
534 for each resource mentioned
535 resources: (optional) A list of resource names such as "server",
536 "flavor", etc. with an element for each '%s' in the url. This
537 method will call self.get_resource for each element when
538 constructing the positive test case template so negative
539 subclasses are expected to return valid resource ids when
540 appropriate.
541 json-schema (optional) A valid json schema that will be used to
542 create invalid data for the api calls. For "GET" and "HEAD",
543 the data is used to generate query strings appended to the url,
544 otherwise for the body of the http call.
545
546 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100547 LOG.info("Executing %s" % description["name"])
548 LOG.debug(description)
549 method = description["http-method"]
550 url = description["url"]
551
552 resources = [self.get_resource(r) for
553 r in description.get("resources", [])]
554
555 if hasattr(self, "resource"):
556 # Note(mkoderer): The resources list already contains an invalid
557 # entry (see get_resource).
558 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100559 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100560 schema = description.get("json-schema", None)
561 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100562 valid_schema = \
563 valid.ValidTestGenerator().generate_valid(schema)
564 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Koderer424c84f2014-02-06 17:02:19 +0100565 elif hasattr(self, "schema"):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100566 new_url, body = self._http_arguments(self.schema, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100567 else:
568 raise Exception("testscenarios are not active. Please make sure "
569 "that your test runner supports the load_tests "
570 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100571
Marc Kodererf857fda2014-03-05 15:58:00 +0100572 if "admin_client" in description and description["admin_client"]:
573 client = self.admin_client
574 else:
575 client = self.client
576 resp, resp_body = client.send_request(method, new_url,
577 resources, body=body)
Marc Koderer424c84f2014-02-06 17:02:19 +0100578 self._check_negative_response(resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100579
580 def _http_arguments(self, json_dict, url, method):
581 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
582 if not json_dict:
583 return url, None
584 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
585 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
586 else:
587 return url, json.dumps(json_dict)
588
589 def _check_negative_response(self, result, body):
590 expected_result = getattr(self, "expected_result", None)
591 self.assertTrue(result >= 400 and result < 500 and result != 413,
592 "Expected client error, got %s:%s" %
593 (result, body))
594 self.assertTrue(expected_result is None or expected_result == result,
595 "Expected %s, got %s:%s" %
596 (expected_result, result, body))
597
598 @classmethod
599 def set_resource(cls, name, resource):
600 """
601 This function can be used in setUpClass context to register a resoruce
602 for a test.
603
604 :param name: The name of the kind of resource such as "flavor", "role",
605 etc.
606 :resource: The id of the resource
607 """
608 cls._resources[name] = resource
609
610 def get_resource(self, name):
611 """
612 Return a valid uuid for a type of resource. If a real resource is
613 needed as part of a url then this method should return one. Otherwise
614 it can return None.
615
616 :param name: The name of the kind of resource such as "flavor", "role",
617 etc.
618 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100619 if isinstance(name, dict):
620 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100621 if hasattr(self, "resource") and self.resource[0] == name:
622 LOG.debug("Return invalid resource (%s) value: %s" %
623 (self.resource[0], self.resource[1]))
624 return self.resource[1]
625 if name in self._resources:
626 return self._resources[name]
627 return None
628
629
Marc Kodererb2978da2014-03-26 13:45:43 +0100630def SimpleNegativeAutoTest(klass):
631 """
632 This decorator registers a test function on basis of the class name.
633 """
634 @attr(type=['negative', 'gate'])
635 def generic_test(self):
Marc Koderer4f44d722014-08-07 14:04:58 +0200636 if hasattr(self, '_schema'):
637 self.execute(self._schema)
Marc Kodererb2978da2014-03-26 13:45:43 +0100638
639 cn = klass.__name__
640 cn = cn.replace('JSON', '')
641 cn = cn.replace('Test', '')
642 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
643 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
644 func_name = 'test_%s' % lower_cn
645 setattr(klass, func_name, generic_test)
646 return klass
647
648
Sean Dague35a7caf2013-05-10 10:38:22 -0400649def call_until_true(func, duration, sleep_for):
650 """
651 Call the given function until it returns True (and return True) or
652 until the specified duration (in seconds) elapses (and return
653 False).
654
655 :param func: A zero argument callable that returns True on success.
656 :param duration: The number of seconds for which to attempt a
657 successful call of the function.
658 :param sleep_for: The number of seconds to sleep after an unsuccessful
659 invocation of the function.
660 """
661 now = time.time()
662 timeout = now + duration
663 while now < timeout:
664 if func():
665 return True
Sean Dague35a7caf2013-05-10 10:38:22 -0400666 time.sleep(sleep_for)
667 now = time.time()
668 return False