blob: 650fad72179b0346eecc09506bf45599d5201b88 [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,
Artur Svechnikovc3bf9252014-05-05 16:37:37 +0400110 'ceilometer': CONF.service_available.ceilometer,
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000111 }
Matthew Treinish16c43792013-09-09 19:55:23 +0000112
113 def decorator(f):
114 for service in args:
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000115 if service not in service_list:
Matthew Treinish16c43792013-09-09 19:55:23 +0000116 raise exceptions.InvalidServiceTag('%s is not a valid service'
117 % service)
118 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000119
120 @functools.wraps(f)
121 def wrapper(self, *func_args, **func_kwargs):
122 for service in args:
123 if not service_list[service]:
124 msg = 'Skipped because the %s service is not available' % (
125 service)
126 raise testtools.TestCase.skipException(msg)
127 return f(self, *func_args, **func_kwargs)
128 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000129 return decorator
130
131
Marc Koderer32221b8e2013-08-23 13:57:50 +0200132def stresstest(*args, **kwargs):
133 """Add stress test decorator
134
135 For all functions with this decorator a attr stress will be
136 set automatically.
137
138 @param class_setup_per: allowed values are application, process, action
139 ``application``: once in the stress job lifetime
140 ``process``: once in the worker process lifetime
141 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200142 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200143 """
144 def decorator(f):
145 if 'class_setup_per' in kwargs:
146 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
147 else:
148 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200149 if 'allow_inheritance' in kwargs:
150 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
151 else:
152 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200153 attr(type='stress')(f)
154 return f
155 return decorator
156
157
Giulio Fidente83181a92013-10-01 06:02:24 +0200158def skip_because(*args, **kwargs):
159 """A decorator useful to skip tests hitting known bugs
160
161 @param bug: bug number causing the test to skip
162 @param condition: optional condition to be True for the skip to have place
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900163 @param interface: skip the test if it is the same as self._interface
Giulio Fidente83181a92013-10-01 06:02:24 +0200164 """
165 def decorator(f):
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900166 @functools.wraps(f)
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900167 def wrapper(self, *func_args, **func_kwargs):
168 skip = False
169 if "condition" in kwargs:
170 if kwargs["condition"] is True:
171 skip = True
172 elif "interface" in kwargs:
173 if kwargs["interface"] == self._interface:
174 skip = True
175 else:
176 skip = True
177 if "bug" in kwargs and skip is True:
Matthew Treinishede50272014-02-11 19:56:11 +0000178 if not kwargs['bug'].isdigit():
179 raise ValueError('bug must be a valid bug number')
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900180 msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
181 raise testtools.TestCase.skipException(msg)
182 return f(self, *func_args, **func_kwargs)
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900183 return wrapper
Giulio Fidente83181a92013-10-01 06:02:24 +0200184 return decorator
185
186
Matthew Treinishe3d26142013-11-26 19:14:58 +0000187def requires_ext(*args, **kwargs):
188 """A decorator to skip tests if an extension is not enabled
189
190 @param extension
191 @param service
192 """
193 def decorator(func):
194 @functools.wraps(func)
195 def wrapper(*func_args, **func_kwargs):
196 if not is_extension_enabled(kwargs['extension'],
197 kwargs['service']):
198 msg = "Skipped because %s extension: %s is not enabled" % (
199 kwargs['service'], kwargs['extension'])
200 raise testtools.TestCase.skipException(msg)
201 return func(*func_args, **func_kwargs)
202 return wrapper
203 return decorator
204
205
206def is_extension_enabled(extension_name, service):
207 """A function that will check the list of enabled extensions from config
208
209 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000210 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000211 'compute': CONF.compute_feature_enabled.api_extensions,
212 'compute_v3': CONF.compute_feature_enabled.api_v3_extensions,
213 'volume': CONF.volume_feature_enabled.api_extensions,
214 'network': CONF.network_feature_enabled.api_extensions,
215 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000216 }
217 if config_dict[service][0] == 'all':
218 return True
219 if extension_name in config_dict[service]:
220 return True
221 return False
222
Ian Wienand98c35f32013-07-23 20:34:23 +1000223
Attila Fazekasf86fa312013-07-30 19:56:39 +0200224at_exit_set = set()
225
226
227def validate_tearDownClass():
228 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400229 LOG.error(
230 "tearDownClass does not call the super's "
231 "tearDownClass in these classes: \n"
232 + str(at_exit_set))
233
Attila Fazekasf86fa312013-07-30 19:56:39 +0200234
235atexit.register(validate_tearDownClass)
236
Attila Fazekas53943322014-02-10 16:07:34 +0100237if sys.version_info >= (2, 7):
238 class BaseDeps(testtools.TestCase,
Attila Fazekasdc216422013-01-29 15:12:14 +0100239 testtools.testcase.WithAttributes,
240 testresources.ResourcedTestCase):
Attila Fazekas53943322014-02-10 16:07:34 +0100241 pass
242else:
243 # Define asserts for py26
244 import unittest2
245
246 class BaseDeps(testtools.TestCase,
247 testtools.testcase.WithAttributes,
248 testresources.ResourcedTestCase,
249 unittest2.TestCase):
250 pass
251
252
253class BaseTestCase(BaseDeps):
Attila Fazekasc43fec82013-04-09 23:17:52 +0200254
Attila Fazekasf86fa312013-07-30 19:56:39 +0200255 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100256 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200257
Matthew Treinish9f756a02014-01-15 10:26:07 -0500258 network_resources = {}
259
Sean Dague2ef32ac2014-06-09 11:32:23 -0400260 # NOTE(sdague): log_format is defined inline here instead of using the oslo
261 # default because going through the config path recouples config to the
262 # stress tests too early, and depending on testr order will fail unit tests
263 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
264 '[%(name)s] %(message)s')
265
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200266 @classmethod
267 def setUpClass(cls):
268 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
269 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200270 cls.setUpClassCalled = True
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200271
Attila Fazekasf86fa312013-07-30 19:56:39 +0200272 @classmethod
273 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200274 at_exit_set.discard(cls)
Attila Fazekasf86fa312013-07-30 19:56:39 +0200275 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
276 super(BaseTestCase, cls).tearDownClass()
277
278 def setUp(self):
279 super(BaseTestCase, self).setUp()
280 if not self.setUpClassCalled:
281 raise RuntimeError("setUpClass does not calls the super's"
282 "setUpClass in the "
283 + self.__class__.__name__)
284 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400285 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
286 try:
287 test_timeout = int(test_timeout)
288 except ValueError:
289 test_timeout = 0
290 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200291 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400292
293 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
294 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200295 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
296 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400297 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
298 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200299 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
300 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200301 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
302 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200303 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400304 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200305 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400306
Matthew Treinish3e046852013-07-23 16:00:24 -0400307 @classmethod
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000308 def get_client_manager(cls, interface=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700309 """
tanlin4956a642014-02-13 16:52:11 +0800310 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700311 """
Matthew Treinish9f756a02014-01-15 10:26:07 -0500312 cls.isolated_creds = isolated_creds.IsolatedCreds(
313 cls.__name__, network_resources=cls.network_resources)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700314
315 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000316 if CONF.compute.allow_tenant_isolation or force_tenant_isolation:
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700317 creds = cls.isolated_creds.get_primary_creds()
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000318 if getattr(cls, '_interface', None):
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000319 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100320 interface=cls._interface,
321 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000322 elif interface:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000323 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100324 interface=interface,
325 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000326 else:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000327 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100328 service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700329 else:
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000330 if getattr(cls, '_interface', None):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100331 os = clients.Manager(interface=cls._interface,
332 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000333 elif interface:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100334 os = clients.Manager(interface=interface, service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000335 else:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100336 os = clients.Manager(service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700337 return os
338
339 @classmethod
340 def clear_isolated_creds(cls):
341 """
342 Clears isolated creds if set
343 """
344 if getattr(cls, 'isolated_creds'):
345 cls.isolated_creds.clear_isolated_creds()
346
347 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400348 def _get_identity_admin_client(cls):
349 """
350 Returns an instance of the Identity Admin API client
351 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100352 os = clients.AdminManager(interface=cls._interface,
353 service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400354 admin_client = os.identity_client
355 return admin_client
356
357 @classmethod
Matthew Treinish9f756a02014-01-15 10:26:07 -0500358 def set_network_resources(self, network=False, router=False, subnet=False,
359 dhcp=False):
360 """Specify which network resources should be created
361
362 @param network
363 @param router
364 @param subnet
365 @param dhcp
366 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000367 # network resources should be set only once from callers
368 # in order to ensure that even if it's called multiple times in
369 # a chain of overloaded methods, the attribute is set only
370 # in the leaf class
371 if not self.network_resources:
372 self.network_resources = {
373 'network': network,
374 'router': router,
375 'subnet': subnet,
376 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500377
Mark Maglana5885eb32014-02-28 10:57:34 -0800378 def assertEmpty(self, list, msg=None):
379 self.assertTrue(len(list) == 0, msg)
380
381 def assertNotEmpty(self, list, msg=None):
382 self.assertTrue(len(list) > 0, msg)
383
Attila Fazekasdc216422013-01-29 15:12:14 +0100384
Marc Koderer24eb89c2014-01-31 11:23:33 +0100385class NegativeAutoTest(BaseTestCase):
386
387 _resources = {}
388
389 @classmethod
390 def setUpClass(cls):
391 super(NegativeAutoTest, cls).setUpClass()
392 os = cls.get_client_manager()
393 cls.client = os.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100394 os_admin = clients.AdminManager(interface=cls._interface,
395 service=cls._service)
396 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100397
398 @staticmethod
399 def load_schema(file):
400 """
401 Loads a schema from a file on a specified location.
402
403 :param file: the file name
404 """
Matthew Treinish57160582014-06-09 17:13:48 -0400405 # NOTE(mkoderer): must be extended for xml support
Marc Koderer24eb89c2014-01-31 11:23:33 +0100406 fn = os.path.join(
407 os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
408 "etc", "schemas", file)
409 LOG.debug("Open schema file: %s" % (fn))
410 return json.load(open(fn))
411
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)
425 if schema_file is not None:
426 setattr(test, 'scenarios',
427 NegativeAutoTest.generate_scenario(schema_file))
428 return testscenarios.load_tests_apply_scenarios(*args)
429
430 @staticmethod
Marc Koderer24eb89c2014-01-31 11:23:33 +0100431 def generate_scenario(description_file):
432 """
433 Generates the test scenario list for a given description.
434
435 :param description: A dictionary with the following entries:
436 name (required) name for the api
437 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
438 url (required) the url to be appended to the catalog url with '%s'
439 for each resource mentioned
440 resources: (optional) A list of resource names such as "server",
441 "flavor", etc. with an element for each '%s' in the url. This
442 method will call self.get_resource for each element when
443 constructing the positive test case template so negative
444 subclasses are expected to return valid resource ids when
445 appropriate.
446 json-schema (optional) A valid json schema that will be used to
447 create invalid data for the api calls. For "GET" and "HEAD",
448 the data is used to generate query strings appended to the url,
449 otherwise for the body of the http call.
450 """
451 description = NegativeAutoTest.load_schema(description_file)
452 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100453 generator = importutils.import_class(
454 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100455 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100456 schema = description.get("json-schema", None)
457 resources = description.get("resources", [])
458 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100459 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100460 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100461 if isinstance(resource, dict):
462 expected_result = resource['expected_result']
463 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100464 LOG.debug("Add resource to test %s" % resource)
465 scn_name = "inv_res_%s" % (resource)
466 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100467 str(uuid.uuid4())),
468 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100469 }))
470 if schema is not None:
Marc Kodererf857fda2014-03-05 15:58:00 +0100471 for name, schema, expected_result in generator.generate(schema):
472 if (expected_result is None and
473 "default_result_code" in description):
474 expected_result = description["default_result_code"]
475 scenario_list.append((name,
476 {"schema": schema,
477 "expected_result": expected_result}))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100478 LOG.debug(scenario_list)
479 return scenario_list
480
481 def execute(self, description_file):
482 """
483 Execute a http call on an api that are expected to
484 result in client errors. First it uses invalid resources that are part
485 of the url, and then invalid data for queries and http request bodies.
486
487 :param description: A dictionary with the following entries:
488 name (required) name for the api
489 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
490 url (required) the url to be appended to the catalog url with '%s'
491 for each resource mentioned
492 resources: (optional) A list of resource names such as "server",
493 "flavor", etc. with an element for each '%s' in the url. This
494 method will call self.get_resource for each element when
495 constructing the positive test case template so negative
496 subclasses are expected to return valid resource ids when
497 appropriate.
498 json-schema (optional) A valid json schema that will be used to
499 create invalid data for the api calls. For "GET" and "HEAD",
500 the data is used to generate query strings appended to the url,
501 otherwise for the body of the http call.
502
503 """
504 description = NegativeAutoTest.load_schema(description_file)
505 LOG.info("Executing %s" % description["name"])
506 LOG.debug(description)
507 method = description["http-method"]
508 url = description["url"]
509
510 resources = [self.get_resource(r) for
511 r in description.get("resources", [])]
512
513 if hasattr(self, "resource"):
514 # Note(mkoderer): The resources list already contains an invalid
515 # entry (see get_resource).
516 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100517 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100518 schema = description.get("json-schema", None)
519 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100520 valid_schema = \
521 valid.ValidTestGenerator().generate_valid(schema)
522 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Koderer424c84f2014-02-06 17:02:19 +0100523 elif hasattr(self, "schema"):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100524 new_url, body = self._http_arguments(self.schema, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100525 else:
526 raise Exception("testscenarios are not active. Please make sure "
527 "that your test runner supports the load_tests "
528 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100529
Marc Kodererf857fda2014-03-05 15:58:00 +0100530 if "admin_client" in description and description["admin_client"]:
531 client = self.admin_client
532 else:
533 client = self.client
534 resp, resp_body = client.send_request(method, new_url,
535 resources, body=body)
Marc Koderer424c84f2014-02-06 17:02:19 +0100536 self._check_negative_response(resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100537
538 def _http_arguments(self, json_dict, url, method):
539 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
540 if not json_dict:
541 return url, None
542 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
543 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
544 else:
545 return url, json.dumps(json_dict)
546
547 def _check_negative_response(self, result, body):
548 expected_result = getattr(self, "expected_result", None)
549 self.assertTrue(result >= 400 and result < 500 and result != 413,
550 "Expected client error, got %s:%s" %
551 (result, body))
552 self.assertTrue(expected_result is None or expected_result == result,
553 "Expected %s, got %s:%s" %
554 (expected_result, result, body))
555
556 @classmethod
557 def set_resource(cls, name, resource):
558 """
559 This function can be used in setUpClass context to register a resoruce
560 for a test.
561
562 :param name: The name of the kind of resource such as "flavor", "role",
563 etc.
564 :resource: The id of the resource
565 """
566 cls._resources[name] = resource
567
568 def get_resource(self, name):
569 """
570 Return a valid uuid for a type of resource. If a real resource is
571 needed as part of a url then this method should return one. Otherwise
572 it can return None.
573
574 :param name: The name of the kind of resource such as "flavor", "role",
575 etc.
576 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100577 if isinstance(name, dict):
578 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100579 if hasattr(self, "resource") and self.resource[0] == name:
580 LOG.debug("Return invalid resource (%s) value: %s" %
581 (self.resource[0], self.resource[1]))
582 return self.resource[1]
583 if name in self._resources:
584 return self._resources[name]
585 return None
586
587
Marc Kodererb2978da2014-03-26 13:45:43 +0100588def SimpleNegativeAutoTest(klass):
589 """
590 This decorator registers a test function on basis of the class name.
591 """
592 @attr(type=['negative', 'gate'])
593 def generic_test(self):
594 self.execute(self._schema_file)
595
596 cn = klass.__name__
597 cn = cn.replace('JSON', '')
598 cn = cn.replace('Test', '')
599 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
600 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
601 func_name = 'test_%s' % lower_cn
602 setattr(klass, func_name, generic_test)
603 return klass
604
605
Sean Dague35a7caf2013-05-10 10:38:22 -0400606def call_until_true(func, duration, sleep_for):
607 """
608 Call the given function until it returns True (and return True) or
609 until the specified duration (in seconds) elapses (and return
610 False).
611
612 :param func: A zero argument callable that returns True on success.
613 :param duration: The number of seconds for which to attempt a
614 successful call of the function.
615 :param sleep_for: The number of seconds to sleep after an unsuccessful
616 invocation of the function.
617 """
618 now = time.time()
619 timeout = now + duration
620 while now < timeout:
621 if func():
622 return True
623 LOG.debug("Sleeping for %d seconds", sleep_for)
624 time.sleep(sleep_for)
625 now = time.time()
626 return False