blob: cbf3c9cab2d9eaac87b6a783c9d2d1626dc9865c [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 """
73
74 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()
Zhi Kun Liu22fb4702014-02-27 16:15:24 +080079 LOG.exception("setUpClass failed: %s" % se)
80 try:
81 cls.tearDownClass()
82 except Exception as te:
83 LOG.exception("tearDownClass failed: %s" % te)
Attila Fazekasbfeb2822014-04-09 11:24:33 +020084 try:
85 raise etype(value), None, trace
86 finally:
87 del trace # for avoiding circular refs
Zhi Kun Liu22fb4702014-02-27 16:15:24 +080088
89 return decorator
90
91
Matthew Treinish16c43792013-09-09 19:55:23 +000092def services(*args, **kwargs):
93 """A decorator used to set an attr for each service used in a test case
94
95 This decorator applies a testtools attr for each service that gets
96 exercised by a test case.
97 """
Matthew Treinish8afbffd2014-01-21 23:56:13 +000098 service_list = {
99 'compute': CONF.service_available.nova,
100 'image': CONF.service_available.glance,
Adam Gandelman4a48a602014-03-20 18:23:18 -0700101 'baremetal': CONF.service_available.ironic,
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000102 'volume': CONF.service_available.cinder,
103 'orchestration': CONF.service_available.heat,
104 # NOTE(mtreinish) nova-network will provide networking functionality
105 # if neutron isn't available, so always set to True.
106 'network': True,
107 'identity': True,
108 'object_storage': CONF.service_available.swift,
109 'dashboard': CONF.service_available.horizon,
110 }
Matthew Treinish16c43792013-09-09 19:55:23 +0000111
112 def decorator(f):
113 for service in args:
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000114 if service not in service_list:
Matthew Treinish16c43792013-09-09 19:55:23 +0000115 raise exceptions.InvalidServiceTag('%s is not a valid service'
116 % service)
117 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000118
119 @functools.wraps(f)
120 def wrapper(self, *func_args, **func_kwargs):
121 for service in args:
122 if not service_list[service]:
123 msg = 'Skipped because the %s service is not available' % (
124 service)
125 raise testtools.TestCase.skipException(msg)
126 return f(self, *func_args, **func_kwargs)
127 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000128 return decorator
129
130
Marc Koderer32221b8e2013-08-23 13:57:50 +0200131def stresstest(*args, **kwargs):
132 """Add stress test decorator
133
134 For all functions with this decorator a attr stress will be
135 set automatically.
136
137 @param class_setup_per: allowed values are application, process, action
138 ``application``: once in the stress job lifetime
139 ``process``: once in the worker process lifetime
140 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200141 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200142 """
143 def decorator(f):
144 if 'class_setup_per' in kwargs:
145 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
146 else:
147 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200148 if 'allow_inheritance' in kwargs:
149 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
150 else:
151 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200152 attr(type='stress')(f)
153 return f
154 return decorator
155
156
Giulio Fidente83181a92013-10-01 06:02:24 +0200157def skip_because(*args, **kwargs):
158 """A decorator useful to skip tests hitting known bugs
159
160 @param bug: bug number causing the test to skip
161 @param condition: optional condition to be True for the skip to have place
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900162 @param interface: skip the test if it is the same as self._interface
Giulio Fidente83181a92013-10-01 06:02:24 +0200163 """
164 def decorator(f):
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900165 @functools.wraps(f)
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900166 def wrapper(self, *func_args, **func_kwargs):
167 skip = False
168 if "condition" in kwargs:
169 if kwargs["condition"] is True:
170 skip = True
171 elif "interface" in kwargs:
172 if kwargs["interface"] == self._interface:
173 skip = True
174 else:
175 skip = True
176 if "bug" in kwargs and skip is True:
Matthew Treinishede50272014-02-11 19:56:11 +0000177 if not kwargs['bug'].isdigit():
178 raise ValueError('bug must be a valid bug number')
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900179 msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
180 raise testtools.TestCase.skipException(msg)
181 return f(self, *func_args, **func_kwargs)
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900182 return wrapper
Giulio Fidente83181a92013-10-01 06:02:24 +0200183 return decorator
184
185
Matthew Treinishe3d26142013-11-26 19:14:58 +0000186def requires_ext(*args, **kwargs):
187 """A decorator to skip tests if an extension is not enabled
188
189 @param extension
190 @param service
191 """
192 def decorator(func):
193 @functools.wraps(func)
194 def wrapper(*func_args, **func_kwargs):
195 if not is_extension_enabled(kwargs['extension'],
196 kwargs['service']):
197 msg = "Skipped because %s extension: %s is not enabled" % (
198 kwargs['service'], kwargs['extension'])
199 raise testtools.TestCase.skipException(msg)
200 return func(*func_args, **func_kwargs)
201 return wrapper
202 return decorator
203
204
205def is_extension_enabled(extension_name, service):
206 """A function that will check the list of enabled extensions from config
207
208 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000209 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000210 'compute': CONF.compute_feature_enabled.api_extensions,
211 'compute_v3': CONF.compute_feature_enabled.api_v3_extensions,
212 'volume': CONF.volume_feature_enabled.api_extensions,
213 'network': CONF.network_feature_enabled.api_extensions,
214 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000215 }
216 if config_dict[service][0] == 'all':
217 return True
218 if extension_name in config_dict[service]:
219 return True
220 return False
221
Ian Wienand98c35f32013-07-23 20:34:23 +1000222
Attila Fazekasf86fa312013-07-30 19:56:39 +0200223at_exit_set = set()
224
225
226def validate_tearDownClass():
227 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400228 LOG.error(
229 "tearDownClass does not call the super's "
230 "tearDownClass in these classes: \n"
231 + str(at_exit_set))
232
Attila Fazekasf86fa312013-07-30 19:56:39 +0200233
234atexit.register(validate_tearDownClass)
235
Attila Fazekas53943322014-02-10 16:07:34 +0100236if sys.version_info >= (2, 7):
237 class BaseDeps(testtools.TestCase,
Attila Fazekasdc216422013-01-29 15:12:14 +0100238 testtools.testcase.WithAttributes,
239 testresources.ResourcedTestCase):
Attila Fazekas53943322014-02-10 16:07:34 +0100240 pass
241else:
242 # Define asserts for py26
243 import unittest2
244
245 class BaseDeps(testtools.TestCase,
246 testtools.testcase.WithAttributes,
247 testresources.ResourcedTestCase,
248 unittest2.TestCase):
249 pass
250
251
252class BaseTestCase(BaseDeps):
Attila Fazekasc43fec82013-04-09 23:17:52 +0200253
Attila Fazekasf86fa312013-07-30 19:56:39 +0200254 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100255 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200256
Matthew Treinish9f756a02014-01-15 10:26:07 -0500257 network_resources = {}
258
Sean Dague2ef32ac2014-06-09 11:32:23 -0400259 # NOTE(sdague): log_format is defined inline here instead of using the oslo
260 # default because going through the config path recouples config to the
261 # stress tests too early, and depending on testr order will fail unit tests
262 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
263 '[%(name)s] %(message)s')
264
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200265 @classmethod
266 def setUpClass(cls):
267 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
268 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200269 cls.setUpClassCalled = True
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)
Attila Fazekasf86fa312013-07-30 19:56:39 +0200274 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
275 super(BaseTestCase, cls).tearDownClass()
276
277 def setUp(self):
278 super(BaseTestCase, self).setUp()
279 if not self.setUpClassCalled:
280 raise RuntimeError("setUpClass does not calls the super's"
281 "setUpClass in the "
282 + self.__class__.__name__)
283 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400284 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
285 try:
286 test_timeout = int(test_timeout)
287 except ValueError:
288 test_timeout = 0
289 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200290 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400291
292 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
293 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200294 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
295 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400296 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
297 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200298 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
299 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200300 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
301 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200302 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400303 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200304 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400305
Matthew Treinish3e046852013-07-23 16:00:24 -0400306 @classmethod
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000307 def get_client_manager(cls, interface=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700308 """
tanlin4956a642014-02-13 16:52:11 +0800309 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700310 """
Matthew Treinish9f756a02014-01-15 10:26:07 -0500311 cls.isolated_creds = isolated_creds.IsolatedCreds(
312 cls.__name__, network_resources=cls.network_resources)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700313
314 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000315 if CONF.compute.allow_tenant_isolation or force_tenant_isolation:
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700316 creds = cls.isolated_creds.get_primary_creds()
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000317 if getattr(cls, '_interface', None):
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000318 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100319 interface=cls._interface,
320 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000321 elif interface:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000322 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100323 interface=interface,
324 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000325 else:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000326 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100327 service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700328 else:
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000329 if getattr(cls, '_interface', None):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100330 os = clients.Manager(interface=cls._interface,
331 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000332 elif interface:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100333 os = clients.Manager(interface=interface, service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000334 else:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100335 os = clients.Manager(service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700336 return os
337
338 @classmethod
339 def clear_isolated_creds(cls):
340 """
341 Clears isolated creds if set
342 """
343 if getattr(cls, 'isolated_creds'):
344 cls.isolated_creds.clear_isolated_creds()
345
346 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400347 def _get_identity_admin_client(cls):
348 """
349 Returns an instance of the Identity Admin API client
350 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100351 os = clients.AdminManager(interface=cls._interface,
352 service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400353 admin_client = os.identity_client
354 return admin_client
355
356 @classmethod
Matthew Treinish9f756a02014-01-15 10:26:07 -0500357 def set_network_resources(self, network=False, router=False, subnet=False,
358 dhcp=False):
359 """Specify which network resources should be created
360
361 @param network
362 @param router
363 @param subnet
364 @param dhcp
365 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000366 # network resources should be set only once from callers
367 # in order to ensure that even if it's called multiple times in
368 # a chain of overloaded methods, the attribute is set only
369 # in the leaf class
370 if not self.network_resources:
371 self.network_resources = {
372 'network': network,
373 'router': router,
374 'subnet': subnet,
375 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500376
Mark Maglana5885eb32014-02-28 10:57:34 -0800377 def assertEmpty(self, list, msg=None):
378 self.assertTrue(len(list) == 0, msg)
379
380 def assertNotEmpty(self, list, msg=None):
381 self.assertTrue(len(list) > 0, msg)
382
Attila Fazekasdc216422013-01-29 15:12:14 +0100383
Marc Koderer24eb89c2014-01-31 11:23:33 +0100384class NegativeAutoTest(BaseTestCase):
385
386 _resources = {}
387
388 @classmethod
389 def setUpClass(cls):
390 super(NegativeAutoTest, cls).setUpClass()
391 os = cls.get_client_manager()
392 cls.client = os.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100393 os_admin = clients.AdminManager(interface=cls._interface,
394 service=cls._service)
395 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100396
397 @staticmethod
398 def load_schema(file):
399 """
400 Loads a schema from a file on a specified location.
401
402 :param file: the file name
403 """
404 #NOTE(mkoderer): must be extended for xml support
405 fn = os.path.join(
406 os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
407 "etc", "schemas", file)
408 LOG.debug("Open schema file: %s" % (fn))
409 return json.load(open(fn))
410
411 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100412 def load_tests(*args):
413 """
414 Wrapper for testscenarios to set the mandatory scenarios variable
415 only in case a real test loader is in place. Will be automatically
416 called in case the variable "load_tests" is set.
417 """
418 if getattr(args[0], 'suiteClass', None) is not None:
419 loader, standard_tests, pattern = args
420 else:
421 standard_tests, module, loader = args
422 for test in testtools.iterate_tests(standard_tests):
423 schema_file = getattr(test, '_schema_file', None)
424 if schema_file is not None:
425 setattr(test, 'scenarios',
426 NegativeAutoTest.generate_scenario(schema_file))
427 return testscenarios.load_tests_apply_scenarios(*args)
428
429 @staticmethod
Marc Koderer24eb89c2014-01-31 11:23:33 +0100430 def generate_scenario(description_file):
431 """
432 Generates the test scenario list for a given description.
433
434 :param description: A dictionary with the following entries:
435 name (required) name for the api
436 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
437 url (required) the url to be appended to the catalog url with '%s'
438 for each resource mentioned
439 resources: (optional) A list of resource names such as "server",
440 "flavor", etc. with an element for each '%s' in the url. This
441 method will call self.get_resource for each element when
442 constructing the positive test case template so negative
443 subclasses are expected to return valid resource ids when
444 appropriate.
445 json-schema (optional) A valid json schema that will be used to
446 create invalid data for the api calls. For "GET" and "HEAD",
447 the data is used to generate query strings appended to the url,
448 otherwise for the body of the http call.
449 """
450 description = NegativeAutoTest.load_schema(description_file)
451 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100452 generator = importutils.import_class(
453 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100454 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100455 schema = description.get("json-schema", None)
456 resources = description.get("resources", [])
457 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100458 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100459 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100460 if isinstance(resource, dict):
461 expected_result = resource['expected_result']
462 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100463 LOG.debug("Add resource to test %s" % resource)
464 scn_name = "inv_res_%s" % (resource)
465 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100466 str(uuid.uuid4())),
467 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100468 }))
469 if schema is not None:
Marc Kodererf857fda2014-03-05 15:58:00 +0100470 for name, schema, expected_result in generator.generate(schema):
471 if (expected_result is None and
472 "default_result_code" in description):
473 expected_result = description["default_result_code"]
474 scenario_list.append((name,
475 {"schema": schema,
476 "expected_result": expected_result}))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100477 LOG.debug(scenario_list)
478 return scenario_list
479
480 def execute(self, description_file):
481 """
482 Execute a http call on an api that are expected to
483 result in client errors. First it uses invalid resources that are part
484 of the url, and then invalid data for queries and http request bodies.
485
486 :param description: A dictionary with the following entries:
487 name (required) name for the api
488 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
489 url (required) the url to be appended to the catalog url with '%s'
490 for each resource mentioned
491 resources: (optional) A list of resource names such as "server",
492 "flavor", etc. with an element for each '%s' in the url. This
493 method will call self.get_resource for each element when
494 constructing the positive test case template so negative
495 subclasses are expected to return valid resource ids when
496 appropriate.
497 json-schema (optional) A valid json schema that will be used to
498 create invalid data for the api calls. For "GET" and "HEAD",
499 the data is used to generate query strings appended to the url,
500 otherwise for the body of the http call.
501
502 """
503 description = NegativeAutoTest.load_schema(description_file)
504 LOG.info("Executing %s" % description["name"])
505 LOG.debug(description)
506 method = description["http-method"]
507 url = description["url"]
508
509 resources = [self.get_resource(r) for
510 r in description.get("resources", [])]
511
512 if hasattr(self, "resource"):
513 # Note(mkoderer): The resources list already contains an invalid
514 # entry (see get_resource).
515 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100516 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100517 schema = description.get("json-schema", None)
518 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100519 valid_schema = \
520 valid.ValidTestGenerator().generate_valid(schema)
521 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Koderer424c84f2014-02-06 17:02:19 +0100522 elif hasattr(self, "schema"):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100523 new_url, body = self._http_arguments(self.schema, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100524 else:
525 raise Exception("testscenarios are not active. Please make sure "
526 "that your test runner supports the load_tests "
527 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100528
Marc Kodererf857fda2014-03-05 15:58:00 +0100529 if "admin_client" in description and description["admin_client"]:
530 client = self.admin_client
531 else:
532 client = self.client
533 resp, resp_body = client.send_request(method, new_url,
534 resources, body=body)
Marc Koderer424c84f2014-02-06 17:02:19 +0100535 self._check_negative_response(resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100536
537 def _http_arguments(self, json_dict, url, method):
538 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
539 if not json_dict:
540 return url, None
541 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
542 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
543 else:
544 return url, json.dumps(json_dict)
545
546 def _check_negative_response(self, result, body):
547 expected_result = getattr(self, "expected_result", None)
548 self.assertTrue(result >= 400 and result < 500 and result != 413,
549 "Expected client error, got %s:%s" %
550 (result, body))
551 self.assertTrue(expected_result is None or expected_result == result,
552 "Expected %s, got %s:%s" %
553 (expected_result, result, body))
554
555 @classmethod
556 def set_resource(cls, name, resource):
557 """
558 This function can be used in setUpClass context to register a resoruce
559 for a test.
560
561 :param name: The name of the kind of resource such as "flavor", "role",
562 etc.
563 :resource: The id of the resource
564 """
565 cls._resources[name] = resource
566
567 def get_resource(self, name):
568 """
569 Return a valid uuid for a type of resource. If a real resource is
570 needed as part of a url then this method should return one. Otherwise
571 it can return None.
572
573 :param name: The name of the kind of resource such as "flavor", "role",
574 etc.
575 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100576 if isinstance(name, dict):
577 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100578 if hasattr(self, "resource") and self.resource[0] == name:
579 LOG.debug("Return invalid resource (%s) value: %s" %
580 (self.resource[0], self.resource[1]))
581 return self.resource[1]
582 if name in self._resources:
583 return self._resources[name]
584 return None
585
586
Marc Kodererb2978da2014-03-26 13:45:43 +0100587def SimpleNegativeAutoTest(klass):
588 """
589 This decorator registers a test function on basis of the class name.
590 """
591 @attr(type=['negative', 'gate'])
592 def generic_test(self):
593 self.execute(self._schema_file)
594
595 cn = klass.__name__
596 cn = cn.replace('JSON', '')
597 cn = cn.replace('Test', '')
598 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
599 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
600 func_name = 'test_%s' % lower_cn
601 setattr(klass, func_name, generic_test)
602 return klass
603
604
Sean Dague35a7caf2013-05-10 10:38:22 -0400605def call_until_true(func, duration, sleep_for):
606 """
607 Call the given function until it returns True (and return True) or
608 until the specified duration (in seconds) elapses (and return
609 False).
610
611 :param func: A zero argument callable that returns True on success.
612 :param duration: The number of seconds for which to attempt a
613 successful call of the function.
614 :param sleep_for: The number of seconds to sleep after an unsuccessful
615 invocation of the function.
616 """
617 now = time.time()
618 timeout = now + duration
619 while now < timeout:
620 if func():
621 return True
622 LOG.debug("Sleeping for %d seconds", sleep_for)
623 time.sleep(sleep_for)
624 now = time.time()
625 return False