blob: 14cf3bb7932aaa7d8b88ed2be52e4674d621323c [file] [log] [blame]
ZhiQiang Fan39f97222013-09-20 04:49:44 +08001# Copyright 2012 OpenStack Foundation
Jay Pipes051075a2012-04-28 17:39:37 -04002# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
Attila Fazekasf86fa312013-07-30 19:56:39 +020016import atexit
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +090017import functools
Marc Koderer24eb89c2014-01-31 11:23:33 +010018import json
Ian Wienand98c35f32013-07-23 20:34:23 +100019import os
Marc Kodererb2978da2014-03-26 13:45:43 +010020import re
Attila Fazekas53943322014-02-10 16:07:34 +010021import sys
Jay Pipes051075a2012-04-28 17:39:37 -040022import time
Marc Koderer24eb89c2014-01-31 11:23:33 +010023import urllib
24import uuid
Jay Pipes051075a2012-04-28 17:39:37 -040025
Matthew Treinish78561ad2013-07-26 11:41:56 -040026import fixtures
Marc Koderer674c8fc2014-03-17 09:45:04 +010027import testscenarios
ivan-zhu1feeb382013-01-24 10:14:39 +080028import testtools
Jay Pipes051075a2012-04-28 17:39:37 -040029
Matthew Treinish3e046852013-07-23 16:00:24 -040030from tempest import clients
Andrea Frittoli8283b4e2014-07-17 13:28:58 +010031from tempest.common import credentials
Marc Koderer6ee82dc2014-02-17 10:26:29 +010032import tempest.common.generator.valid_generator as valid
Attila Fazekasdc216422013-01-29 15:12:14 +010033from tempest import config
Matthew Treinish16c43792013-09-09 19:55:23 +000034from tempest import exceptions
Marc Koderer6ee82dc2014-02-17 10:26:29 +010035from tempest.openstack.common import importutils
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040036from tempest.openstack.common import log as logging
Jay Pipes051075a2012-04-28 17:39:37 -040037
38LOG = logging.getLogger(__name__)
39
Sean Dague86bd8422013-12-20 09:56:44 -050040CONF = config.CONF
41
Samuel Merritt0d499bc2013-06-19 12:08:23 -070042# All the successful HTTP status codes from RFC 2616
43HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206)
44
Jay Pipes051075a2012-04-28 17:39:37 -040045
Chris Yeoh55530bb2013-02-08 16:04:27 +103046def attr(*args, **kwargs):
Matthew Treinisha74f5d42014-02-07 20:25:44 -050047 """A decorator which applies the testtools attr decorator
Chris Yeoh55530bb2013-02-08 16:04:27 +103048
Matthew Treinisha74f5d42014-02-07 20:25:44 -050049 This decorator applies the testtools.testcase.attr if it is in the list of
50 attributes to testtools we want to apply.
Attila Fazekasb2902af2013-02-16 16:22:44 +010051 """
Chris Yeoh55530bb2013-02-08 16:04:27 +103052
53 def decorator(f):
Giulio Fidente4946a052013-05-14 12:23:51 +020054 if 'type' in kwargs and isinstance(kwargs['type'], str):
55 f = testtools.testcase.attr(kwargs['type'])(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093056 if kwargs['type'] == 'smoke':
57 f = testtools.testcase.attr('gate')(f)
Giulio Fidente4946a052013-05-14 12:23:51 +020058 elif 'type' in kwargs and isinstance(kwargs['type'], list):
59 for attr in kwargs['type']:
60 f = testtools.testcase.attr(attr)(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093061 if attr == 'smoke':
62 f = testtools.testcase.attr('gate')(f)
Matthew Treinisha74f5d42014-02-07 20:25:44 -050063 return f
Chris Yeoh55530bb2013-02-08 16:04:27 +103064
65 return decorator
66
67
Matthew Treinish3d8c7322014-08-03 23:53:28 -040068def get_service_list():
Matthew Treinish8afbffd2014-01-21 23:56:13 +000069 service_list = {
70 'compute': CONF.service_available.nova,
71 'image': CONF.service_available.glance,
Adam Gandelman4a48a602014-03-20 18:23:18 -070072 'baremetal': CONF.service_available.ironic,
Matthew Treinish8afbffd2014-01-21 23:56:13 +000073 'volume': CONF.service_available.cinder,
74 'orchestration': CONF.service_available.heat,
75 # NOTE(mtreinish) nova-network will provide networking functionality
76 # if neutron isn't available, so always set to True.
77 'network': True,
78 'identity': True,
79 'object_storage': CONF.service_available.swift,
80 'dashboard': CONF.service_available.horizon,
ekhugen7aff0992014-08-04 19:01:57 +000081 'telemetry': CONF.service_available.ceilometer,
Yaroslav Lobankovff42c872014-06-17 15:39:43 +040082 'data_processing': CONF.service_available.sahara
Matthew Treinish8afbffd2014-01-21 23:56:13 +000083 }
Matthew Treinish3d8c7322014-08-03 23:53:28 -040084 return service_list
Matthew Treinish16c43792013-09-09 19:55:23 +000085
Matthew Treinish3d8c7322014-08-03 23:53:28 -040086
87def services(*args, **kwargs):
88 """A decorator used to set an attr for each service used in a test case
89
90 This decorator applies a testtools attr for each service that gets
91 exercised by a test case.
92 """
Matthew Treinish16c43792013-09-09 19:55:23 +000093 def decorator(f):
Matthew Treinish3d8c7322014-08-03 23:53:28 -040094 services = ['compute', 'image', 'baremetal', 'volume', 'orchestration',
95 'network', 'identity', 'object_storage', 'dashboard',
Matthew Treinish60359052014-09-18 17:39:26 -040096 'telemetry', 'data_processing']
Matthew Treinish16c43792013-09-09 19:55:23 +000097 for service in args:
Matthew Treinish3d8c7322014-08-03 23:53:28 -040098 if service not in services:
99 raise exceptions.InvalidServiceTag('%s is not a valid '
100 'service' % service)
Matthew Treinish16c43792013-09-09 19:55:23 +0000101 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000102
103 @functools.wraps(f)
104 def wrapper(self, *func_args, **func_kwargs):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400105 service_list = get_service_list()
106
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000107 for service in args:
108 if not service_list[service]:
109 msg = 'Skipped because the %s service is not available' % (
110 service)
111 raise testtools.TestCase.skipException(msg)
112 return f(self, *func_args, **func_kwargs)
113 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000114 return decorator
115
116
Marc Koderer32221b8e2013-08-23 13:57:50 +0200117def stresstest(*args, **kwargs):
118 """Add stress test decorator
119
120 For all functions with this decorator a attr stress will be
121 set automatically.
122
123 @param class_setup_per: allowed values are application, process, action
124 ``application``: once in the stress job lifetime
125 ``process``: once in the worker process lifetime
126 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200127 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200128 """
129 def decorator(f):
130 if 'class_setup_per' in kwargs:
131 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
132 else:
133 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200134 if 'allow_inheritance' in kwargs:
135 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
136 else:
137 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200138 attr(type='stress')(f)
139 return f
140 return decorator
141
142
Giulio Fidente83181a92013-10-01 06:02:24 +0200143def skip_because(*args, **kwargs):
144 """A decorator useful to skip tests hitting known bugs
145
146 @param bug: bug number causing the test to skip
147 @param condition: optional condition to be True for the skip to have place
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900148 @param interface: skip the test if it is the same as self._interface
Giulio Fidente83181a92013-10-01 06:02:24 +0200149 """
150 def decorator(f):
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900151 @functools.wraps(f)
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900152 def wrapper(self, *func_args, **func_kwargs):
153 skip = False
154 if "condition" in kwargs:
155 if kwargs["condition"] is True:
156 skip = True
157 elif "interface" in kwargs:
158 if kwargs["interface"] == self._interface:
159 skip = True
160 else:
161 skip = True
162 if "bug" in kwargs and skip is True:
Matthew Treinishede50272014-02-11 19:56:11 +0000163 if not kwargs['bug'].isdigit():
164 raise ValueError('bug must be a valid bug number')
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900165 msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
166 raise testtools.TestCase.skipException(msg)
167 return f(self, *func_args, **func_kwargs)
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900168 return wrapper
Giulio Fidente83181a92013-10-01 06:02:24 +0200169 return decorator
170
171
Matthew Treinishe3d26142013-11-26 19:14:58 +0000172def requires_ext(*args, **kwargs):
173 """A decorator to skip tests if an extension is not enabled
174
175 @param extension
176 @param service
177 """
178 def decorator(func):
179 @functools.wraps(func)
180 def wrapper(*func_args, **func_kwargs):
181 if not is_extension_enabled(kwargs['extension'],
182 kwargs['service']):
183 msg = "Skipped because %s extension: %s is not enabled" % (
184 kwargs['service'], kwargs['extension'])
185 raise testtools.TestCase.skipException(msg)
186 return func(*func_args, **func_kwargs)
187 return wrapper
188 return decorator
189
190
191def is_extension_enabled(extension_name, service):
192 """A function that will check the list of enabled extensions from config
193
194 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000195 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000196 'compute': CONF.compute_feature_enabled.api_extensions,
197 'compute_v3': CONF.compute_feature_enabled.api_v3_extensions,
198 'volume': CONF.volume_feature_enabled.api_extensions,
199 'network': CONF.network_feature_enabled.api_extensions,
200 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000201 }
Simeon Monov5d7effe2014-07-16 07:32:38 +0300202 if len(config_dict[service]) == 0:
203 return False
Matthew Treinishe3d26142013-11-26 19:14:58 +0000204 if config_dict[service][0] == 'all':
205 return True
206 if extension_name in config_dict[service]:
207 return True
208 return False
209
Ian Wienand98c35f32013-07-23 20:34:23 +1000210
Attila Fazekasf86fa312013-07-30 19:56:39 +0200211at_exit_set = set()
212
213
214def validate_tearDownClass():
215 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400216 LOG.error(
217 "tearDownClass does not call the super's "
218 "tearDownClass in these classes: \n"
219 + str(at_exit_set))
220
Attila Fazekasf86fa312013-07-30 19:56:39 +0200221
222atexit.register(validate_tearDownClass)
223
Attila Fazekas53943322014-02-10 16:07:34 +0100224
Matthew Treinish2474f412014-11-17 18:11:56 -0500225class BaseTestCase(testtools.testcase.WithAttributes,
226 testtools.TestCase):
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
Matthew Treinish9f756a02014-01-15 10:26:07 -0500231 network_resources = {}
232
Sean Dague2ef32ac2014-06-09 11:32:23 -0400233 # NOTE(sdague): log_format is defined inline here instead of using the oslo
234 # default because going through the config path recouples config to the
235 # stress tests too early, and depending on testr order will fail unit tests
236 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
237 '[%(name)s] %(message)s')
238
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200239 @classmethod
240 def setUpClass(cls):
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100241 # It should never be overridden by descendants
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200242 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
243 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200244 cls.setUpClassCalled = True
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100245 # No test resource is allocated until here
246 try:
247 # TODO(andreaf) Split-up resource_setup in stages:
248 # skip checks, pre-hook, credentials, clients, resources, post-hook
249 cls.resource_setup()
250 except Exception:
251 etype, value, trace = sys.exc_info()
252 LOG.info("%s in resource setup. Invoking tearDownClass." % etype)
253 # Catch any exception in tearDown so we can re-raise the original
254 # exception at the end
255 try:
256 cls.tearDownClass()
257 except Exception as te:
ghanshyam44b19b92014-10-06 15:59:04 +0900258 tetype, _, _ = sys.exc_info()
259 # TODO(gmann): Till we split-up resource_setup &
260 # resource_cleanup in more structural way, log
261 # AttributeError as info instead of exception.
262 if tetype is AttributeError:
263 LOG.info("tearDownClass failed: %s" % te)
264 else:
265 LOG.exception("tearDownClass failed: %s" % te)
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100266 try:
267 raise etype(value), None, trace
268 finally:
269 del trace # for avoiding 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 Frittoli73ee2472014-09-15 12:31:53 +0100277 try:
278 cls.resource_cleanup()
279 finally:
280 cls.clear_isolated_creds()
281
282 @classmethod
283 def resource_setup(cls):
284 """Class level setup steps for test cases.
285 Recommended order: skip checks, credentials, clients, resources.
286 """
287 pass
288
289 @classmethod
290 def resource_cleanup(cls):
291 """Class level resource cleanup for test cases. """
292 pass
Attila Fazekasf86fa312013-07-30 19:56:39 +0200293
294 def setUp(self):
295 super(BaseTestCase, self).setUp()
296 if not self.setUpClassCalled:
297 raise RuntimeError("setUpClass does not calls the super's"
298 "setUpClass in the "
299 + self.__class__.__name__)
300 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400301 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
302 try:
303 test_timeout = int(test_timeout)
304 except ValueError:
305 test_timeout = 0
306 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200307 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400308
309 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
310 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200311 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
312 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400313 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
314 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200315 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
316 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200317 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
318 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200319 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400320 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200321 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400322
Matthew Treinish3e046852013-07-23 16:00:24 -0400323 @classmethod
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000324 def get_client_manager(cls, interface=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700325 """
tanlin4956a642014-02-13 16:52:11 +0800326 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700327 """
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700328 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100329
Marc Koderer44dce622014-11-14 10:08:12 +0100330 if (not hasattr(cls, 'isolated_creds') or
331 not cls.isolated_creds.name == cls.__name__):
332 cls.isolated_creds = credentials.get_isolated_credentials(
333 name=cls.__name__, network_resources=cls.network_resources,
334 force_tenant_isolation=force_tenant_isolation,
335 )
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100336
337 creds = cls.isolated_creds.get_primary_creds()
338 params = dict(credentials=creds, service=cls._service)
339 if getattr(cls, '_interface', None):
340 interface = cls._interface
341 if interface:
342 params['interface'] = interface
343 os = clients.Manager(**params)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700344 return os
345
346 @classmethod
347 def clear_isolated_creds(cls):
348 """
349 Clears isolated creds if set
350 """
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100351 if hasattr(cls, 'isolated_creds'):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700352 cls.isolated_creds.clear_isolated_creds()
353
354 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400355 def _get_identity_admin_client(cls):
356 """
357 Returns an instance of the Identity Admin API client
358 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100359 os = clients.AdminManager(interface=cls._interface,
360 service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400361 admin_client = os.identity_client
362 return admin_client
363
364 @classmethod
Matthew Treinish9f756a02014-01-15 10:26:07 -0500365 def set_network_resources(self, network=False, router=False, subnet=False,
366 dhcp=False):
367 """Specify which network resources should be created
368
369 @param network
370 @param router
371 @param subnet
372 @param dhcp
373 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000374 # network resources should be set only once from callers
375 # in order to ensure that even if it's called multiple times in
376 # a chain of overloaded methods, the attribute is set only
377 # in the leaf class
378 if not self.network_resources:
379 self.network_resources = {
380 'network': network,
381 'router': router,
382 'subnet': subnet,
383 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500384
Mark Maglana5885eb32014-02-28 10:57:34 -0800385 def assertEmpty(self, list, msg=None):
386 self.assertTrue(len(list) == 0, msg)
387
388 def assertNotEmpty(self, list, msg=None):
389 self.assertTrue(len(list) > 0, msg)
390
Attila Fazekasdc216422013-01-29 15:12:14 +0100391
Marc Koderer24eb89c2014-01-31 11:23:33 +0100392class NegativeAutoTest(BaseTestCase):
393
394 _resources = {}
395
396 @classmethod
397 def setUpClass(cls):
398 super(NegativeAutoTest, cls).setUpClass()
399 os = cls.get_client_manager()
400 cls.client = os.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100401 os_admin = clients.AdminManager(interface=cls._interface,
402 service=cls._service)
403 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100404
405 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100406 def load_tests(*args):
407 """
408 Wrapper for testscenarios to set the mandatory scenarios variable
409 only in case a real test loader is in place. Will be automatically
410 called in case the variable "load_tests" is set.
411 """
412 if getattr(args[0], 'suiteClass', None) is not None:
413 loader, standard_tests, pattern = args
414 else:
415 standard_tests, module, loader = args
416 for test in testtools.iterate_tests(standard_tests):
417 schema_file = getattr(test, '_schema_file', None)
Marc Koderer4f44d722014-08-07 14:04:58 +0200418 schema = getattr(test, '_schema', None)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100419 if schema_file is not None:
420 setattr(test, 'scenarios',
421 NegativeAutoTest.generate_scenario(schema_file))
Marc Koderer4f44d722014-08-07 14:04:58 +0200422 elif schema is not None:
423 setattr(test, 'scenarios',
424 NegativeAutoTest.generate_scenario(schema))
Marc Koderer674c8fc2014-03-17 09:45:04 +0100425 return testscenarios.load_tests_apply_scenarios(*args)
426
427 @staticmethod
Marc Koderer4f44d722014-08-07 14:04:58 +0200428 def generate_scenario(description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100429 """
430 Generates the test scenario list for a given description.
431
Marc Koderer4f44d722014-08-07 14:04:58 +0200432 :param description: A file or dictionary with the following entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100433 name (required) name for the api
434 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
435 url (required) the url to be appended to the catalog url with '%s'
436 for each resource mentioned
437 resources: (optional) A list of resource names such as "server",
438 "flavor", etc. with an element for each '%s' in the url. This
439 method will call self.get_resource for each element when
440 constructing the positive test case template so negative
441 subclasses are expected to return valid resource ids when
442 appropriate.
443 json-schema (optional) A valid json schema that will be used to
444 create invalid data for the api calls. For "GET" and "HEAD",
445 the data is used to generate query strings appended to the url,
446 otherwise for the body of the http call.
447 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100448 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100449 generator = importutils.import_class(
450 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100451 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100452 schema = description.get("json-schema", None)
453 resources = description.get("resources", [])
454 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100455 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100456 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100457 if isinstance(resource, dict):
458 expected_result = resource['expected_result']
459 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100460 LOG.debug("Add resource to test %s" % resource)
461 scn_name = "inv_res_%s" % (resource)
462 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100463 str(uuid.uuid4())),
464 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100465 }))
466 if schema is not None:
Marc Kodererf07f5d12014-09-01 09:47:23 +0200467 for scenario in generator.generate_scenarios(schema):
468 scenario_list.append((scenario['_negtest_name'],
469 scenario))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100470 LOG.debug(scenario_list)
471 return scenario_list
472
Marc Koderer4f44d722014-08-07 14:04:58 +0200473 def execute(self, description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100474 """
475 Execute a http call on an api that are expected to
476 result in client errors. First it uses invalid resources that are part
477 of the url, and then invalid data for queries and http request bodies.
478
Marc Koderer4f44d722014-08-07 14:04:58 +0200479 :param description: A json file or dictionary with the following
480 entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100481 name (required) name for the api
482 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
483 url (required) the url to be appended to the catalog url with '%s'
484 for each resource mentioned
485 resources: (optional) A list of resource names such as "server",
486 "flavor", etc. with an element for each '%s' in the url. This
487 method will call self.get_resource for each element when
488 constructing the positive test case template so negative
489 subclasses are expected to return valid resource ids when
490 appropriate.
491 json-schema (optional) A valid json schema that will be used to
492 create invalid data for the api calls. For "GET" and "HEAD",
493 the data is used to generate query strings appended to the url,
494 otherwise for the body of the http call.
495
496 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100497 LOG.info("Executing %s" % description["name"])
498 LOG.debug(description)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200499 generator = importutils.import_class(
500 CONF.negative.test_generator)()
501 schema = description.get("json-schema", None)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100502 method = description["http-method"]
503 url = description["url"]
Marc Kodererf07f5d12014-09-01 09:47:23 +0200504 expected_result = None
505 if "default_result_code" in description:
506 expected_result = description["default_result_code"]
Marc Koderer24eb89c2014-01-31 11:23:33 +0100507
508 resources = [self.get_resource(r) for
509 r in description.get("resources", [])]
510
511 if hasattr(self, "resource"):
512 # Note(mkoderer): The resources list already contains an invalid
513 # entry (see get_resource).
514 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100515 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100516 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100517 valid_schema = \
518 valid.ValidTestGenerator().generate_valid(schema)
519 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200520 elif hasattr(self, "_negtest_name"):
521 schema_under_test = \
522 valid.ValidTestGenerator().generate_valid(schema)
523 local_expected_result = \
524 generator.generate_payload(self, schema_under_test)
525 if local_expected_result is not None:
526 expected_result = local_expected_result
527 new_url, body = \
528 self._http_arguments(schema_under_test, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100529 else:
530 raise Exception("testscenarios are not active. Please make sure "
531 "that your test runner supports the load_tests "
532 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100533
Marc Kodererf857fda2014-03-05 15:58:00 +0100534 if "admin_client" in description and description["admin_client"]:
535 client = self.admin_client
536 else:
537 client = self.client
538 resp, resp_body = client.send_request(method, new_url,
539 resources, body=body)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200540 self._check_negative_response(expected_result, resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100541
542 def _http_arguments(self, json_dict, url, method):
543 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
544 if not json_dict:
545 return url, None
546 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
547 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
548 else:
549 return url, json.dumps(json_dict)
550
Marc Kodererf07f5d12014-09-01 09:47:23 +0200551 def _check_negative_response(self, expected_result, result, body):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100552 self.assertTrue(result >= 400 and result < 500 and result != 413,
553 "Expected client error, got %s:%s" %
554 (result, body))
555 self.assertTrue(expected_result is None or expected_result == result,
556 "Expected %s, got %s:%s" %
557 (expected_result, result, body))
558
559 @classmethod
560 def set_resource(cls, name, resource):
561 """
562 This function can be used in setUpClass context to register a resoruce
563 for a test.
564
565 :param name: The name of the kind of resource such as "flavor", "role",
566 etc.
567 :resource: The id of the resource
568 """
569 cls._resources[name] = resource
570
571 def get_resource(self, name):
572 """
573 Return a valid uuid for a type of resource. If a real resource is
574 needed as part of a url then this method should return one. Otherwise
575 it can return None.
576
577 :param name: The name of the kind of resource such as "flavor", "role",
578 etc.
579 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100580 if isinstance(name, dict):
581 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100582 if hasattr(self, "resource") and self.resource[0] == name:
583 LOG.debug("Return invalid resource (%s) value: %s" %
584 (self.resource[0], self.resource[1]))
585 return self.resource[1]
586 if name in self._resources:
587 return self._resources[name]
588 return None
589
590
Marc Kodererb2978da2014-03-26 13:45:43 +0100591def SimpleNegativeAutoTest(klass):
592 """
593 This decorator registers a test function on basis of the class name.
594 """
595 @attr(type=['negative', 'gate'])
596 def generic_test(self):
Marc Koderer4f44d722014-08-07 14:04:58 +0200597 if hasattr(self, '_schema'):
598 self.execute(self._schema)
Marc Kodererb2978da2014-03-26 13:45:43 +0100599
600 cn = klass.__name__
601 cn = cn.replace('JSON', '')
602 cn = cn.replace('Test', '')
603 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
604 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
605 func_name = 'test_%s' % lower_cn
606 setattr(klass, func_name, generic_test)
607 return klass
608
609
Sean Dague35a7caf2013-05-10 10:38:22 -0400610def call_until_true(func, duration, sleep_for):
611 """
612 Call the given function until it returns True (and return True) or
613 until the specified duration (in seconds) elapses (and return
614 False).
615
616 :param func: A zero argument callable that returns True on success.
617 :param duration: The number of seconds for which to attempt a
618 successful call of the function.
619 :param sleep_for: The number of seconds to sleep after an unsuccessful
620 invocation of the function.
621 """
622 now = time.time()
623 timeout = now + duration
624 while now < timeout:
625 if func():
626 return True
Sean Dague35a7caf2013-05-10 10:38:22 -0400627 time.sleep(sleep_for)
628 now = time.time()
629 return False