blob: d2b32d49a1258abeb80b8b68e94588f5e62492ee [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.
72 """
Marc Koderer53b01232014-08-13 15:57:30 +020073 @functools.wraps(f)
Zhi Kun Liu22fb4702014-02-27 16:15:24 +080074 def decorator(cls):
75 try:
76 f(cls)
77 except Exception as se:
Attila Fazekasbfeb2822014-04-09 11:24:33 +020078 etype, value, trace = sys.exc_info()
Mauro S. M. Rodrigues5942bc02014-08-08 14:16:29 +000079 if etype is cls.skipException:
80 LOG.info("setUpClass skipped: %s:" % se)
81 else:
82 LOG.exception("setUpClass failed: %s" % se)
Zhi Kun Liu22fb4702014-02-27 16:15:24 +080083 try:
84 cls.tearDownClass()
85 except Exception as te:
86 LOG.exception("tearDownClass failed: %s" % te)
Attila Fazekasbfeb2822014-04-09 11:24:33 +020087 try:
88 raise etype(value), None, trace
89 finally:
90 del trace # for avoiding circular refs
Zhi Kun Liu22fb4702014-02-27 16:15:24 +080091
92 return decorator
93
94
Matthew Treinish3d8c7322014-08-03 23:53:28 -040095def get_service_list():
Matthew Treinish8afbffd2014-01-21 23:56:13 +000096 service_list = {
97 'compute': CONF.service_available.nova,
98 'image': CONF.service_available.glance,
Adam Gandelman4a48a602014-03-20 18:23:18 -070099 'baremetal': CONF.service_available.ironic,
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000100 'volume': CONF.service_available.cinder,
101 'orchestration': CONF.service_available.heat,
102 # NOTE(mtreinish) nova-network will provide networking functionality
103 # if neutron isn't available, so always set to True.
104 'network': True,
105 'identity': True,
106 'object_storage': CONF.service_available.swift,
107 'dashboard': CONF.service_available.horizon,
ekhugen7aff0992014-08-04 19:01:57 +0000108 'telemetry': CONF.service_available.ceilometer,
Yaroslav Lobankovff42c872014-06-17 15:39:43 +0400109 'data_processing': CONF.service_available.sahara
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000110 }
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400111 return service_list
Matthew Treinish16c43792013-09-09 19:55:23 +0000112
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400113
114def services(*args, **kwargs):
115 """A decorator used to set an attr for each service used in a test case
116
117 This decorator applies a testtools attr for each service that gets
118 exercised by a test case.
119 """
Matthew Treinish16c43792013-09-09 19:55:23 +0000120 def decorator(f):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400121 services = ['compute', 'image', 'baremetal', 'volume', 'orchestration',
122 'network', 'identity', 'object_storage', 'dashboard',
123 'ceilometer', 'data_processing']
Matthew Treinish16c43792013-09-09 19:55:23 +0000124 for service in args:
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400125 if service not in services:
126 raise exceptions.InvalidServiceTag('%s is not a valid '
127 'service' % service)
Matthew Treinish16c43792013-09-09 19:55:23 +0000128 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000129
130 @functools.wraps(f)
131 def wrapper(self, *func_args, **func_kwargs):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400132 service_list = get_service_list()
133
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000134 for service in args:
135 if not service_list[service]:
136 msg = 'Skipped because the %s service is not available' % (
137 service)
138 raise testtools.TestCase.skipException(msg)
139 return f(self, *func_args, **func_kwargs)
140 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000141 return decorator
142
143
Marc Koderer32221b8e2013-08-23 13:57:50 +0200144def stresstest(*args, **kwargs):
145 """Add stress test decorator
146
147 For all functions with this decorator a attr stress will be
148 set automatically.
149
150 @param class_setup_per: allowed values are application, process, action
151 ``application``: once in the stress job lifetime
152 ``process``: once in the worker process lifetime
153 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200154 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200155 """
156 def decorator(f):
157 if 'class_setup_per' in kwargs:
158 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
159 else:
160 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200161 if 'allow_inheritance' in kwargs:
162 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
163 else:
164 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200165 attr(type='stress')(f)
166 return f
167 return decorator
168
169
Giulio Fidente83181a92013-10-01 06:02:24 +0200170def skip_because(*args, **kwargs):
171 """A decorator useful to skip tests hitting known bugs
172
173 @param bug: bug number causing the test to skip
174 @param condition: optional condition to be True for the skip to have place
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900175 @param interface: skip the test if it is the same as self._interface
Giulio Fidente83181a92013-10-01 06:02:24 +0200176 """
177 def decorator(f):
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900178 @functools.wraps(f)
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900179 def wrapper(self, *func_args, **func_kwargs):
180 skip = False
181 if "condition" in kwargs:
182 if kwargs["condition"] is True:
183 skip = True
184 elif "interface" in kwargs:
185 if kwargs["interface"] == self._interface:
186 skip = True
187 else:
188 skip = True
189 if "bug" in kwargs and skip is True:
Matthew Treinishede50272014-02-11 19:56:11 +0000190 if not kwargs['bug'].isdigit():
191 raise ValueError('bug must be a valid bug number')
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900192 msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
193 raise testtools.TestCase.skipException(msg)
194 return f(self, *func_args, **func_kwargs)
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900195 return wrapper
Giulio Fidente83181a92013-10-01 06:02:24 +0200196 return decorator
197
198
Matthew Treinishe3d26142013-11-26 19:14:58 +0000199def requires_ext(*args, **kwargs):
200 """A decorator to skip tests if an extension is not enabled
201
202 @param extension
203 @param service
204 """
205 def decorator(func):
206 @functools.wraps(func)
207 def wrapper(*func_args, **func_kwargs):
208 if not is_extension_enabled(kwargs['extension'],
209 kwargs['service']):
210 msg = "Skipped because %s extension: %s is not enabled" % (
211 kwargs['service'], kwargs['extension'])
212 raise testtools.TestCase.skipException(msg)
213 return func(*func_args, **func_kwargs)
214 return wrapper
215 return decorator
216
217
218def is_extension_enabled(extension_name, service):
219 """A function that will check the list of enabled extensions from config
220
221 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000222 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000223 'compute': CONF.compute_feature_enabled.api_extensions,
224 'compute_v3': CONF.compute_feature_enabled.api_v3_extensions,
225 'volume': CONF.volume_feature_enabled.api_extensions,
226 'network': CONF.network_feature_enabled.api_extensions,
227 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000228 }
Simeon Monov5d7effe2014-07-16 07:32:38 +0300229 if len(config_dict[service]) == 0:
230 return False
Matthew Treinishe3d26142013-11-26 19:14:58 +0000231 if config_dict[service][0] == 'all':
232 return True
233 if extension_name in config_dict[service]:
234 return True
235 return False
236
Ian Wienand98c35f32013-07-23 20:34:23 +1000237
Attila Fazekasf86fa312013-07-30 19:56:39 +0200238at_exit_set = set()
239
240
241def validate_tearDownClass():
242 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400243 LOG.error(
244 "tearDownClass does not call the super's "
245 "tearDownClass in these classes: \n"
246 + str(at_exit_set))
247
Attila Fazekasf86fa312013-07-30 19:56:39 +0200248
249atexit.register(validate_tearDownClass)
250
Attila Fazekas53943322014-02-10 16:07:34 +0100251if sys.version_info >= (2, 7):
252 class BaseDeps(testtools.TestCase,
Attila Fazekasdc216422013-01-29 15:12:14 +0100253 testtools.testcase.WithAttributes,
254 testresources.ResourcedTestCase):
Attila Fazekas53943322014-02-10 16:07:34 +0100255 pass
256else:
257 # Define asserts for py26
258 import unittest2
259
260 class BaseDeps(testtools.TestCase,
261 testtools.testcase.WithAttributes,
262 testresources.ResourcedTestCase,
263 unittest2.TestCase):
264 pass
265
266
267class BaseTestCase(BaseDeps):
Attila Fazekasc43fec82013-04-09 23:17:52 +0200268
Attila Fazekasf86fa312013-07-30 19:56:39 +0200269 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100270 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200271
Matthew Treinish9f756a02014-01-15 10:26:07 -0500272 network_resources = {}
273
Sean Dague2ef32ac2014-06-09 11:32:23 -0400274 # NOTE(sdague): log_format is defined inline here instead of using the oslo
275 # default because going through the config path recouples config to the
276 # stress tests too early, and depending on testr order will fail unit tests
277 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
278 '[%(name)s] %(message)s')
279
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200280 @classmethod
281 def setUpClass(cls):
282 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
283 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200284 cls.setUpClassCalled = True
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)
Attila Fazekasf86fa312013-07-30 19:56:39 +0200289 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
290 super(BaseTestCase, cls).tearDownClass()
291
292 def setUp(self):
293 super(BaseTestCase, self).setUp()
294 if not self.setUpClassCalled:
295 raise RuntimeError("setUpClass does not calls the super's"
296 "setUpClass in the "
297 + self.__class__.__name__)
298 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400299 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
300 try:
301 test_timeout = int(test_timeout)
302 except ValueError:
303 test_timeout = 0
304 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200305 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400306
307 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
308 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200309 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
310 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400311 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
312 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200313 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
314 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200315 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
316 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200317 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400318 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200319 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400320
Matthew Treinish3e046852013-07-23 16:00:24 -0400321 @classmethod
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000322 def get_client_manager(cls, interface=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700323 """
tanlin4956a642014-02-13 16:52:11 +0800324 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700325 """
Matthew Treinish9f756a02014-01-15 10:26:07 -0500326 cls.isolated_creds = isolated_creds.IsolatedCreds(
327 cls.__name__, network_resources=cls.network_resources)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700328
329 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000330 if CONF.compute.allow_tenant_isolation or force_tenant_isolation:
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700331 creds = cls.isolated_creds.get_primary_creds()
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000332 if getattr(cls, '_interface', None):
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000333 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100334 interface=cls._interface,
335 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000336 elif interface:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000337 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100338 interface=interface,
339 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000340 else:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000341 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100342 service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700343 else:
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000344 if getattr(cls, '_interface', None):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100345 os = clients.Manager(interface=cls._interface,
346 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000347 elif interface:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100348 os = clients.Manager(interface=interface, service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000349 else:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100350 os = clients.Manager(service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700351 return os
352
353 @classmethod
354 def clear_isolated_creds(cls):
355 """
356 Clears isolated creds if set
357 """
358 if getattr(cls, 'isolated_creds'):
359 cls.isolated_creds.clear_isolated_creds()
360
361 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400362 def _get_identity_admin_client(cls):
363 """
364 Returns an instance of the Identity Admin API client
365 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100366 os = clients.AdminManager(interface=cls._interface,
367 service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400368 admin_client = os.identity_client
369 return admin_client
370
371 @classmethod
Matthew Treinish9f756a02014-01-15 10:26:07 -0500372 def set_network_resources(self, network=False, router=False, subnet=False,
373 dhcp=False):
374 """Specify which network resources should be created
375
376 @param network
377 @param router
378 @param subnet
379 @param dhcp
380 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000381 # network resources should be set only once from callers
382 # in order to ensure that even if it's called multiple times in
383 # a chain of overloaded methods, the attribute is set only
384 # in the leaf class
385 if not self.network_resources:
386 self.network_resources = {
387 'network': network,
388 'router': router,
389 'subnet': subnet,
390 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500391
Mark Maglana5885eb32014-02-28 10:57:34 -0800392 def assertEmpty(self, list, msg=None):
393 self.assertTrue(len(list) == 0, msg)
394
395 def assertNotEmpty(self, list, msg=None):
396 self.assertTrue(len(list) > 0, msg)
397
Attila Fazekasdc216422013-01-29 15:12:14 +0100398
Marc Koderer24eb89c2014-01-31 11:23:33 +0100399class NegativeAutoTest(BaseTestCase):
400
401 _resources = {}
402
403 @classmethod
404 def setUpClass(cls):
405 super(NegativeAutoTest, cls).setUpClass()
406 os = cls.get_client_manager()
407 cls.client = os.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100408 os_admin = clients.AdminManager(interface=cls._interface,
409 service=cls._service)
410 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100411
412 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100413 def load_tests(*args):
414 """
415 Wrapper for testscenarios to set the mandatory scenarios variable
416 only in case a real test loader is in place. Will be automatically
417 called in case the variable "load_tests" is set.
418 """
419 if getattr(args[0], 'suiteClass', None) is not None:
420 loader, standard_tests, pattern = args
421 else:
422 standard_tests, module, loader = args
423 for test in testtools.iterate_tests(standard_tests):
424 schema_file = getattr(test, '_schema_file', None)
Marc Koderer4f44d722014-08-07 14:04:58 +0200425 schema = getattr(test, '_schema', None)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100426 if schema_file is not None:
427 setattr(test, 'scenarios',
428 NegativeAutoTest.generate_scenario(schema_file))
Marc Koderer4f44d722014-08-07 14:04:58 +0200429 elif schema is not None:
430 setattr(test, 'scenarios',
431 NegativeAutoTest.generate_scenario(schema))
Marc Koderer674c8fc2014-03-17 09:45:04 +0100432 return testscenarios.load_tests_apply_scenarios(*args)
433
434 @staticmethod
Marc Koderer4f44d722014-08-07 14:04:58 +0200435 def generate_scenario(description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100436 """
437 Generates the test scenario list for a given description.
438
Marc Koderer4f44d722014-08-07 14:04:58 +0200439 :param description: A file or dictionary with the following entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100440 name (required) name for the api
441 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
442 url (required) the url to be appended to the catalog url with '%s'
443 for each resource mentioned
444 resources: (optional) A list of resource names such as "server",
445 "flavor", etc. with an element for each '%s' in the url. This
446 method will call self.get_resource for each element when
447 constructing the positive test case template so negative
448 subclasses are expected to return valid resource ids when
449 appropriate.
450 json-schema (optional) A valid json schema that will be used to
451 create invalid data for the api calls. For "GET" and "HEAD",
452 the data is used to generate query strings appended to the url,
453 otherwise for the body of the http call.
454 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100455 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100456 generator = importutils.import_class(
457 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100458 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100459 schema = description.get("json-schema", None)
460 resources = description.get("resources", [])
461 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100462 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100463 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100464 if isinstance(resource, dict):
465 expected_result = resource['expected_result']
466 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100467 LOG.debug("Add resource to test %s" % resource)
468 scn_name = "inv_res_%s" % (resource)
469 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100470 str(uuid.uuid4())),
471 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100472 }))
473 if schema is not None:
Marc Kodererf857fda2014-03-05 15:58:00 +0100474 for name, schema, expected_result in generator.generate(schema):
475 if (expected_result is None and
476 "default_result_code" in description):
477 expected_result = description["default_result_code"]
478 scenario_list.append((name,
479 {"schema": schema,
480 "expected_result": expected_result}))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100481 LOG.debug(scenario_list)
482 return scenario_list
483
Marc Koderer4f44d722014-08-07 14:04:58 +0200484 def execute(self, description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100485 """
486 Execute a http call on an api that are expected to
487 result in client errors. First it uses invalid resources that are part
488 of the url, and then invalid data for queries and http request bodies.
489
Marc Koderer4f44d722014-08-07 14:04:58 +0200490 :param description: A json file or dictionary with the following
491 entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100492 name (required) name for the api
493 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
494 url (required) the url to be appended to the catalog url with '%s'
495 for each resource mentioned
496 resources: (optional) A list of resource names such as "server",
497 "flavor", etc. with an element for each '%s' in the url. This
498 method will call self.get_resource for each element when
499 constructing the positive test case template so negative
500 subclasses are expected to return valid resource ids when
501 appropriate.
502 json-schema (optional) A valid json schema that will be used to
503 create invalid data for the api calls. For "GET" and "HEAD",
504 the data is used to generate query strings appended to the url,
505 otherwise for the body of the http call.
506
507 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100508 LOG.info("Executing %s" % description["name"])
509 LOG.debug(description)
510 method = description["http-method"]
511 url = description["url"]
512
513 resources = [self.get_resource(r) for
514 r in description.get("resources", [])]
515
516 if hasattr(self, "resource"):
517 # Note(mkoderer): The resources list already contains an invalid
518 # entry (see get_resource).
519 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100520 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100521 schema = description.get("json-schema", None)
522 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100523 valid_schema = \
524 valid.ValidTestGenerator().generate_valid(schema)
525 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Koderer424c84f2014-02-06 17:02:19 +0100526 elif hasattr(self, "schema"):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100527 new_url, body = self._http_arguments(self.schema, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100528 else:
529 raise Exception("testscenarios are not active. Please make sure "
530 "that your test runner supports the load_tests "
531 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100532
Marc Kodererf857fda2014-03-05 15:58:00 +0100533 if "admin_client" in description and description["admin_client"]:
534 client = self.admin_client
535 else:
536 client = self.client
537 resp, resp_body = client.send_request(method, new_url,
538 resources, body=body)
Marc Koderer424c84f2014-02-06 17:02:19 +0100539 self._check_negative_response(resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100540
541 def _http_arguments(self, json_dict, url, method):
542 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
543 if not json_dict:
544 return url, None
545 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
546 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
547 else:
548 return url, json.dumps(json_dict)
549
550 def _check_negative_response(self, result, body):
551 expected_result = getattr(self, "expected_result", None)
552 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