blob: db8736ef3f0f2e2223d25e8b3cb3e48e3e5143ea [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
Andrea Frittoli8283b4e2014-07-17 13:28:58 +010032from tempest.common import credentials
Marc Koderer6ee82dc2014-02-17 10:26:29 +010033import tempest.common.generator.valid_generator as valid
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
Matthew Treinish3d8c7322014-08-03 23:53:28 -040069def get_service_list():
Matthew Treinish8afbffd2014-01-21 23:56:13 +000070 service_list = {
71 'compute': CONF.service_available.nova,
72 'image': CONF.service_available.glance,
Adam Gandelman4a48a602014-03-20 18:23:18 -070073 'baremetal': CONF.service_available.ironic,
Matthew Treinish8afbffd2014-01-21 23:56:13 +000074 'volume': CONF.service_available.cinder,
75 'orchestration': CONF.service_available.heat,
76 # NOTE(mtreinish) nova-network will provide networking functionality
77 # if neutron isn't available, so always set to True.
78 'network': True,
79 'identity': True,
80 'object_storage': CONF.service_available.swift,
81 'dashboard': CONF.service_available.horizon,
ekhugen7aff0992014-08-04 19:01:57 +000082 'telemetry': CONF.service_available.ceilometer,
Yaroslav Lobankovff42c872014-06-17 15:39:43 +040083 'data_processing': CONF.service_available.sahara
Matthew Treinish8afbffd2014-01-21 23:56:13 +000084 }
Matthew Treinish3d8c7322014-08-03 23:53:28 -040085 return service_list
Matthew Treinish16c43792013-09-09 19:55:23 +000086
Matthew Treinish3d8c7322014-08-03 23:53:28 -040087
88def services(*args, **kwargs):
89 """A decorator used to set an attr for each service used in a test case
90
91 This decorator applies a testtools attr for each service that gets
92 exercised by a test case.
93 """
Matthew Treinish16c43792013-09-09 19:55:23 +000094 def decorator(f):
Matthew Treinish3d8c7322014-08-03 23:53:28 -040095 services = ['compute', 'image', 'baremetal', 'volume', 'orchestration',
96 'network', 'identity', 'object_storage', 'dashboard',
Matthew Treinish60359052014-09-18 17:39:26 -040097 'telemetry', 'data_processing']
Matthew Treinish16c43792013-09-09 19:55:23 +000098 for service in args:
Matthew Treinish3d8c7322014-08-03 23:53:28 -040099 if service not in services:
100 raise exceptions.InvalidServiceTag('%s is not a valid '
101 'service' % service)
Matthew Treinish16c43792013-09-09 19:55:23 +0000102 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000103
104 @functools.wraps(f)
105 def wrapper(self, *func_args, **func_kwargs):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400106 service_list = get_service_list()
107
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000108 for service in args:
109 if not service_list[service]:
110 msg = 'Skipped because the %s service is not available' % (
111 service)
112 raise testtools.TestCase.skipException(msg)
113 return f(self, *func_args, **func_kwargs)
114 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000115 return decorator
116
117
Marc Koderer32221b8e2013-08-23 13:57:50 +0200118def stresstest(*args, **kwargs):
119 """Add stress test decorator
120
121 For all functions with this decorator a attr stress will be
122 set automatically.
123
124 @param class_setup_per: allowed values are application, process, action
125 ``application``: once in the stress job lifetime
126 ``process``: once in the worker process lifetime
127 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200128 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200129 """
130 def decorator(f):
131 if 'class_setup_per' in kwargs:
132 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
133 else:
134 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200135 if 'allow_inheritance' in kwargs:
136 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
137 else:
138 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200139 attr(type='stress')(f)
140 return f
141 return decorator
142
143
Giulio Fidente83181a92013-10-01 06:02:24 +0200144def skip_because(*args, **kwargs):
145 """A decorator useful to skip tests hitting known bugs
146
147 @param bug: bug number causing the test to skip
148 @param condition: optional condition to be True for the skip to have place
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900149 @param interface: skip the test if it is the same as self._interface
Giulio Fidente83181a92013-10-01 06:02:24 +0200150 """
151 def decorator(f):
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900152 @functools.wraps(f)
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900153 def wrapper(self, *func_args, **func_kwargs):
154 skip = False
155 if "condition" in kwargs:
156 if kwargs["condition"] is True:
157 skip = True
158 elif "interface" in kwargs:
159 if kwargs["interface"] == self._interface:
160 skip = True
161 else:
162 skip = True
163 if "bug" in kwargs and skip is True:
Matthew Treinishede50272014-02-11 19:56:11 +0000164 if not kwargs['bug'].isdigit():
165 raise ValueError('bug must be a valid bug number')
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900166 msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
167 raise testtools.TestCase.skipException(msg)
168 return f(self, *func_args, **func_kwargs)
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900169 return wrapper
Giulio Fidente83181a92013-10-01 06:02:24 +0200170 return decorator
171
172
Matthew Treinishe3d26142013-11-26 19:14:58 +0000173def requires_ext(*args, **kwargs):
174 """A decorator to skip tests if an extension is not enabled
175
176 @param extension
177 @param service
178 """
179 def decorator(func):
180 @functools.wraps(func)
181 def wrapper(*func_args, **func_kwargs):
182 if not is_extension_enabled(kwargs['extension'],
183 kwargs['service']):
184 msg = "Skipped because %s extension: %s is not enabled" % (
185 kwargs['service'], kwargs['extension'])
186 raise testtools.TestCase.skipException(msg)
187 return func(*func_args, **func_kwargs)
188 return wrapper
189 return decorator
190
191
192def is_extension_enabled(extension_name, service):
193 """A function that will check the list of enabled extensions from config
194
195 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000196 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000197 'compute': CONF.compute_feature_enabled.api_extensions,
198 'compute_v3': CONF.compute_feature_enabled.api_v3_extensions,
199 'volume': CONF.volume_feature_enabled.api_extensions,
200 'network': CONF.network_feature_enabled.api_extensions,
201 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000202 }
Simeon Monov5d7effe2014-07-16 07:32:38 +0300203 if len(config_dict[service]) == 0:
204 return False
Matthew Treinishe3d26142013-11-26 19:14:58 +0000205 if config_dict[service][0] == 'all':
206 return True
207 if extension_name in config_dict[service]:
208 return True
209 return False
210
Ian Wienand98c35f32013-07-23 20:34:23 +1000211
Attila Fazekasf86fa312013-07-30 19:56:39 +0200212at_exit_set = set()
213
214
215def validate_tearDownClass():
216 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400217 LOG.error(
218 "tearDownClass does not call the super's "
219 "tearDownClass in these classes: \n"
220 + str(at_exit_set))
221
Attila Fazekasf86fa312013-07-30 19:56:39 +0200222
223atexit.register(validate_tearDownClass)
224
Attila Fazekas53943322014-02-10 16:07:34 +0100225if sys.version_info >= (2, 7):
226 class BaseDeps(testtools.TestCase,
Attila Fazekasdc216422013-01-29 15:12:14 +0100227 testtools.testcase.WithAttributes,
228 testresources.ResourcedTestCase):
Attila Fazekas53943322014-02-10 16:07:34 +0100229 pass
230else:
231 # Define asserts for py26
232 import unittest2
233
234 class BaseDeps(testtools.TestCase,
235 testtools.testcase.WithAttributes,
236 testresources.ResourcedTestCase,
237 unittest2.TestCase):
238 pass
239
240
241class BaseTestCase(BaseDeps):
Attila Fazekasc43fec82013-04-09 23:17:52 +0200242
Attila Fazekasf86fa312013-07-30 19:56:39 +0200243 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100244 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200245
Matthew Treinish9f756a02014-01-15 10:26:07 -0500246 network_resources = {}
247
Sean Dague2ef32ac2014-06-09 11:32:23 -0400248 # NOTE(sdague): log_format is defined inline here instead of using the oslo
249 # default because going through the config path recouples config to the
250 # stress tests too early, and depending on testr order will fail unit tests
251 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
252 '[%(name)s] %(message)s')
253
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200254 @classmethod
255 def setUpClass(cls):
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100256 # It should never be overridden by descendants
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200257 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
258 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200259 cls.setUpClassCalled = True
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100260 # No test resource is allocated until here
261 try:
262 # TODO(andreaf) Split-up resource_setup in stages:
263 # skip checks, pre-hook, credentials, clients, resources, post-hook
264 cls.resource_setup()
265 except Exception:
266 etype, value, trace = sys.exc_info()
267 LOG.info("%s in resource setup. Invoking tearDownClass." % etype)
268 # Catch any exception in tearDown so we can re-raise the original
269 # exception at the end
270 try:
271 cls.tearDownClass()
272 except Exception as te:
ghanshyam44b19b92014-10-06 15:59:04 +0900273 tetype, _, _ = sys.exc_info()
274 # TODO(gmann): Till we split-up resource_setup &
275 # resource_cleanup in more structural way, log
276 # AttributeError as info instead of exception.
277 if tetype is AttributeError:
278 LOG.info("tearDownClass failed: %s" % te)
279 else:
280 LOG.exception("tearDownClass failed: %s" % te)
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100281 try:
282 raise etype(value), None, trace
283 finally:
284 del trace # for avoiding circular refs
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200285
Attila Fazekasf86fa312013-07-30 19:56:39 +0200286 @classmethod
287 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200288 at_exit_set.discard(cls)
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100289 # It should never be overridden by descendants
Attila Fazekasf86fa312013-07-30 19:56:39 +0200290 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
291 super(BaseTestCase, cls).tearDownClass()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100292 try:
293 cls.resource_cleanup()
294 finally:
295 cls.clear_isolated_creds()
296
297 @classmethod
298 def resource_setup(cls):
299 """Class level setup steps for test cases.
300 Recommended order: skip checks, credentials, clients, resources.
301 """
302 pass
303
304 @classmethod
305 def resource_cleanup(cls):
306 """Class level resource cleanup for test cases. """
307 pass
Attila Fazekasf86fa312013-07-30 19:56:39 +0200308
309 def setUp(self):
310 super(BaseTestCase, self).setUp()
311 if not self.setUpClassCalled:
312 raise RuntimeError("setUpClass does not calls the super's"
313 "setUpClass in the "
314 + self.__class__.__name__)
315 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400316 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
317 try:
318 test_timeout = int(test_timeout)
319 except ValueError:
320 test_timeout = 0
321 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200322 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400323
324 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
325 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200326 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
327 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400328 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
329 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200330 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
331 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200332 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
333 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200334 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400335 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200336 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400337
Matthew Treinish3e046852013-07-23 16:00:24 -0400338 @classmethod
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000339 def get_client_manager(cls, interface=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700340 """
tanlin4956a642014-02-13 16:52:11 +0800341 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700342 """
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700343 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100344
Marc Koderer44dce622014-11-14 10:08:12 +0100345 if (not hasattr(cls, 'isolated_creds') or
346 not cls.isolated_creds.name == cls.__name__):
347 cls.isolated_creds = credentials.get_isolated_credentials(
348 name=cls.__name__, network_resources=cls.network_resources,
349 force_tenant_isolation=force_tenant_isolation,
350 )
Andrea Frittoli8283b4e2014-07-17 13:28:58 +0100351
352 creds = cls.isolated_creds.get_primary_creds()
353 params = dict(credentials=creds, service=cls._service)
354 if getattr(cls, '_interface', None):
355 interface = cls._interface
356 if interface:
357 params['interface'] = interface
358 os = clients.Manager(**params)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700359 return os
360
361 @classmethod
362 def clear_isolated_creds(cls):
363 """
364 Clears isolated creds if set
365 """
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100366 if hasattr(cls, 'isolated_creds'):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700367 cls.isolated_creds.clear_isolated_creds()
368
369 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400370 def _get_identity_admin_client(cls):
371 """
372 Returns an instance of the Identity Admin API client
373 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100374 os = clients.AdminManager(interface=cls._interface,
375 service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400376 admin_client = os.identity_client
377 return admin_client
378
379 @classmethod
Matthew Treinish9f756a02014-01-15 10:26:07 -0500380 def set_network_resources(self, network=False, router=False, subnet=False,
381 dhcp=False):
382 """Specify which network resources should be created
383
384 @param network
385 @param router
386 @param subnet
387 @param dhcp
388 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000389 # network resources should be set only once from callers
390 # in order to ensure that even if it's called multiple times in
391 # a chain of overloaded methods, the attribute is set only
392 # in the leaf class
393 if not self.network_resources:
394 self.network_resources = {
395 'network': network,
396 'router': router,
397 'subnet': subnet,
398 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500399
Mark Maglana5885eb32014-02-28 10:57:34 -0800400 def assertEmpty(self, list, msg=None):
401 self.assertTrue(len(list) == 0, msg)
402
403 def assertNotEmpty(self, list, msg=None):
404 self.assertTrue(len(list) > 0, msg)
405
Attila Fazekasdc216422013-01-29 15:12:14 +0100406
Marc Koderer24eb89c2014-01-31 11:23:33 +0100407class NegativeAutoTest(BaseTestCase):
408
409 _resources = {}
410
411 @classmethod
412 def setUpClass(cls):
413 super(NegativeAutoTest, cls).setUpClass()
414 os = cls.get_client_manager()
415 cls.client = os.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100416 os_admin = clients.AdminManager(interface=cls._interface,
417 service=cls._service)
418 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100419
420 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100421 def load_tests(*args):
422 """
423 Wrapper for testscenarios to set the mandatory scenarios variable
424 only in case a real test loader is in place. Will be automatically
425 called in case the variable "load_tests" is set.
426 """
427 if getattr(args[0], 'suiteClass', None) is not None:
428 loader, standard_tests, pattern = args
429 else:
430 standard_tests, module, loader = args
431 for test in testtools.iterate_tests(standard_tests):
432 schema_file = getattr(test, '_schema_file', None)
Marc Koderer4f44d722014-08-07 14:04:58 +0200433 schema = getattr(test, '_schema', None)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100434 if schema_file is not None:
435 setattr(test, 'scenarios',
436 NegativeAutoTest.generate_scenario(schema_file))
Marc Koderer4f44d722014-08-07 14:04:58 +0200437 elif schema is not None:
438 setattr(test, 'scenarios',
439 NegativeAutoTest.generate_scenario(schema))
Marc Koderer674c8fc2014-03-17 09:45:04 +0100440 return testscenarios.load_tests_apply_scenarios(*args)
441
442 @staticmethod
Marc Koderer4f44d722014-08-07 14:04:58 +0200443 def generate_scenario(description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100444 """
445 Generates the test scenario list for a given description.
446
Marc Koderer4f44d722014-08-07 14:04:58 +0200447 :param description: A file or dictionary with the following entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100448 name (required) name for the api
449 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
450 url (required) the url to be appended to the catalog url with '%s'
451 for each resource mentioned
452 resources: (optional) A list of resource names such as "server",
453 "flavor", etc. with an element for each '%s' in the url. This
454 method will call self.get_resource for each element when
455 constructing the positive test case template so negative
456 subclasses are expected to return valid resource ids when
457 appropriate.
458 json-schema (optional) A valid json schema that will be used to
459 create invalid data for the api calls. For "GET" and "HEAD",
460 the data is used to generate query strings appended to the url,
461 otherwise for the body of the http call.
462 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100463 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100464 generator = importutils.import_class(
465 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100466 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100467 schema = description.get("json-schema", None)
468 resources = description.get("resources", [])
469 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100470 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100471 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100472 if isinstance(resource, dict):
473 expected_result = resource['expected_result']
474 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100475 LOG.debug("Add resource to test %s" % resource)
476 scn_name = "inv_res_%s" % (resource)
477 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100478 str(uuid.uuid4())),
479 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100480 }))
481 if schema is not None:
Marc Kodererf07f5d12014-09-01 09:47:23 +0200482 for scenario in generator.generate_scenarios(schema):
483 scenario_list.append((scenario['_negtest_name'],
484 scenario))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100485 LOG.debug(scenario_list)
486 return scenario_list
487
Marc Koderer4f44d722014-08-07 14:04:58 +0200488 def execute(self, description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100489 """
490 Execute a http call on an api that are expected to
491 result in client errors. First it uses invalid resources that are part
492 of the url, and then invalid data for queries and http request bodies.
493
Marc Koderer4f44d722014-08-07 14:04:58 +0200494 :param description: A json file or dictionary with the following
495 entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100496 name (required) name for the api
497 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
498 url (required) the url to be appended to the catalog url with '%s'
499 for each resource mentioned
500 resources: (optional) A list of resource names such as "server",
501 "flavor", etc. with an element for each '%s' in the url. This
502 method will call self.get_resource for each element when
503 constructing the positive test case template so negative
504 subclasses are expected to return valid resource ids when
505 appropriate.
506 json-schema (optional) A valid json schema that will be used to
507 create invalid data for the api calls. For "GET" and "HEAD",
508 the data is used to generate query strings appended to the url,
509 otherwise for the body of the http call.
510
511 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100512 LOG.info("Executing %s" % description["name"])
513 LOG.debug(description)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200514 generator = importutils.import_class(
515 CONF.negative.test_generator)()
516 schema = description.get("json-schema", None)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100517 method = description["http-method"]
518 url = description["url"]
Marc Kodererf07f5d12014-09-01 09:47:23 +0200519 expected_result = None
520 if "default_result_code" in description:
521 expected_result = description["default_result_code"]
Marc Koderer24eb89c2014-01-31 11:23:33 +0100522
523 resources = [self.get_resource(r) for
524 r in description.get("resources", [])]
525
526 if hasattr(self, "resource"):
527 # Note(mkoderer): The resources list already contains an invalid
528 # entry (see get_resource).
529 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100530 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100531 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100532 valid_schema = \
533 valid.ValidTestGenerator().generate_valid(schema)
534 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200535 elif hasattr(self, "_negtest_name"):
536 schema_under_test = \
537 valid.ValidTestGenerator().generate_valid(schema)
538 local_expected_result = \
539 generator.generate_payload(self, schema_under_test)
540 if local_expected_result is not None:
541 expected_result = local_expected_result
542 new_url, body = \
543 self._http_arguments(schema_under_test, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100544 else:
545 raise Exception("testscenarios are not active. Please make sure "
546 "that your test runner supports the load_tests "
547 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100548
Marc Kodererf857fda2014-03-05 15:58:00 +0100549 if "admin_client" in description and description["admin_client"]:
550 client = self.admin_client
551 else:
552 client = self.client
553 resp, resp_body = client.send_request(method, new_url,
554 resources, body=body)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200555 self._check_negative_response(expected_result, resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100556
557 def _http_arguments(self, json_dict, url, method):
558 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
559 if not json_dict:
560 return url, None
561 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
562 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
563 else:
564 return url, json.dumps(json_dict)
565
Marc Kodererf07f5d12014-09-01 09:47:23 +0200566 def _check_negative_response(self, expected_result, result, body):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100567 self.assertTrue(result >= 400 and result < 500 and result != 413,
568 "Expected client error, got %s:%s" %
569 (result, body))
570 self.assertTrue(expected_result is None or expected_result == result,
571 "Expected %s, got %s:%s" %
572 (expected_result, result, body))
573
574 @classmethod
575 def set_resource(cls, name, resource):
576 """
577 This function can be used in setUpClass context to register a resoruce
578 for a test.
579
580 :param name: The name of the kind of resource such as "flavor", "role",
581 etc.
582 :resource: The id of the resource
583 """
584 cls._resources[name] = resource
585
586 def get_resource(self, name):
587 """
588 Return a valid uuid for a type of resource. If a real resource is
589 needed as part of a url then this method should return one. Otherwise
590 it can return None.
591
592 :param name: The name of the kind of resource such as "flavor", "role",
593 etc.
594 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100595 if isinstance(name, dict):
596 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100597 if hasattr(self, "resource") and self.resource[0] == name:
598 LOG.debug("Return invalid resource (%s) value: %s" %
599 (self.resource[0], self.resource[1]))
600 return self.resource[1]
601 if name in self._resources:
602 return self._resources[name]
603 return None
604
605
Marc Kodererb2978da2014-03-26 13:45:43 +0100606def SimpleNegativeAutoTest(klass):
607 """
608 This decorator registers a test function on basis of the class name.
609 """
610 @attr(type=['negative', 'gate'])
611 def generic_test(self):
Marc Koderer4f44d722014-08-07 14:04:58 +0200612 if hasattr(self, '_schema'):
613 self.execute(self._schema)
Marc Kodererb2978da2014-03-26 13:45:43 +0100614
615 cn = klass.__name__
616 cn = cn.replace('JSON', '')
617 cn = cn.replace('Test', '')
618 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
619 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
620 func_name = 'test_%s' % lower_cn
621 setattr(klass, func_name, generic_test)
622 return klass
623
624
Sean Dague35a7caf2013-05-10 10:38:22 -0400625def call_until_true(func, duration, sleep_for):
626 """
627 Call the given function until it returns True (and return True) or
628 until the specified duration (in seconds) elapses (and return
629 False).
630
631 :param func: A zero argument callable that returns True on success.
632 :param duration: The number of seconds for which to attempt a
633 successful call of the function.
634 :param sleep_for: The number of seconds to sleep after an unsuccessful
635 invocation of the function.
636 """
637 now = time.time()
638 timeout = now + duration
639 while now < timeout:
640 if func():
641 return True
Sean Dague35a7caf2013-05-10 10:38:22 -0400642 time.sleep(sleep_for)
643 now = time.time()
644 return False