blob: e4019f9b6997f14c43aacc213e80403a8243781f [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:
78 LOG.exception("setUpClass failed: %s" % se)
79 try:
80 cls.tearDownClass()
81 except Exception as te:
82 LOG.exception("tearDownClass failed: %s" % te)
83 raise se
84
85 return decorator
86
87
Matthew Treinish16c43792013-09-09 19:55:23 +000088def 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 Treinish8afbffd2014-01-21 23:56:13 +000094 service_list = {
95 'compute': CONF.service_available.nova,
96 'image': CONF.service_available.glance,
97 'volume': CONF.service_available.cinder,
98 'orchestration': CONF.service_available.heat,
99 # NOTE(mtreinish) nova-network will provide networking functionality
100 # if neutron isn't available, so always set to True.
101 'network': True,
102 'identity': True,
103 'object_storage': CONF.service_available.swift,
104 'dashboard': CONF.service_available.horizon,
105 }
Matthew Treinish16c43792013-09-09 19:55:23 +0000106
107 def decorator(f):
108 for service in args:
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000109 if service not in service_list:
Matthew Treinish16c43792013-09-09 19:55:23 +0000110 raise exceptions.InvalidServiceTag('%s is not a valid service'
111 % service)
112 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000113
114 @functools.wraps(f)
115 def wrapper(self, *func_args, **func_kwargs):
116 for service in args:
117 if not service_list[service]:
118 msg = 'Skipped because the %s service is not available' % (
119 service)
120 raise testtools.TestCase.skipException(msg)
121 return f(self, *func_args, **func_kwargs)
122 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000123 return decorator
124
125
Marc Koderer32221b8e2013-08-23 13:57:50 +0200126def stresstest(*args, **kwargs):
127 """Add stress test decorator
128
129 For all functions with this decorator a attr stress will be
130 set automatically.
131
132 @param class_setup_per: allowed values are application, process, action
133 ``application``: once in the stress job lifetime
134 ``process``: once in the worker process lifetime
135 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200136 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200137 """
138 def decorator(f):
139 if 'class_setup_per' in kwargs:
140 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
141 else:
142 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200143 if 'allow_inheritance' in kwargs:
144 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
145 else:
146 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200147 attr(type='stress')(f)
148 return f
149 return decorator
150
151
Giulio Fidente83181a92013-10-01 06:02:24 +0200152def skip_because(*args, **kwargs):
153 """A decorator useful to skip tests hitting known bugs
154
155 @param bug: bug number causing the test to skip
156 @param condition: optional condition to be True for the skip to have place
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900157 @param interface: skip the test if it is the same as self._interface
Giulio Fidente83181a92013-10-01 06:02:24 +0200158 """
159 def decorator(f):
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900160 @functools.wraps(f)
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900161 def wrapper(self, *func_args, **func_kwargs):
162 skip = False
163 if "condition" in kwargs:
164 if kwargs["condition"] is True:
165 skip = True
166 elif "interface" in kwargs:
167 if kwargs["interface"] == self._interface:
168 skip = True
169 else:
170 skip = True
171 if "bug" in kwargs and skip is True:
Matthew Treinishede50272014-02-11 19:56:11 +0000172 if not kwargs['bug'].isdigit():
173 raise ValueError('bug must be a valid bug number')
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900174 msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
175 raise testtools.TestCase.skipException(msg)
176 return f(self, *func_args, **func_kwargs)
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900177 return wrapper
Giulio Fidente83181a92013-10-01 06:02:24 +0200178 return decorator
179
180
Matthew Treinishe3d26142013-11-26 19:14:58 +0000181def requires_ext(*args, **kwargs):
182 """A decorator to skip tests if an extension is not enabled
183
184 @param extension
185 @param service
186 """
187 def decorator(func):
188 @functools.wraps(func)
189 def wrapper(*func_args, **func_kwargs):
190 if not is_extension_enabled(kwargs['extension'],
191 kwargs['service']):
192 msg = "Skipped because %s extension: %s is not enabled" % (
193 kwargs['service'], kwargs['extension'])
194 raise testtools.TestCase.skipException(msg)
195 return func(*func_args, **func_kwargs)
196 return wrapper
197 return decorator
198
199
200def is_extension_enabled(extension_name, service):
201 """A function that will check the list of enabled extensions from config
202
203 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000204 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000205 'compute': CONF.compute_feature_enabled.api_extensions,
206 'compute_v3': CONF.compute_feature_enabled.api_v3_extensions,
207 'volume': CONF.volume_feature_enabled.api_extensions,
208 'network': CONF.network_feature_enabled.api_extensions,
209 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000210 }
211 if config_dict[service][0] == 'all':
212 return True
213 if extension_name in config_dict[service]:
214 return True
215 return False
216
Ian Wienand98c35f32013-07-23 20:34:23 +1000217
Attila Fazekasf86fa312013-07-30 19:56:39 +0200218at_exit_set = set()
219
220
221def validate_tearDownClass():
222 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400223 LOG.error(
224 "tearDownClass does not call the super's "
225 "tearDownClass in these classes: \n"
226 + str(at_exit_set))
227
Attila Fazekasf86fa312013-07-30 19:56:39 +0200228
229atexit.register(validate_tearDownClass)
230
Attila Fazekas53943322014-02-10 16:07:34 +0100231if sys.version_info >= (2, 7):
232 class BaseDeps(testtools.TestCase,
Attila Fazekasdc216422013-01-29 15:12:14 +0100233 testtools.testcase.WithAttributes,
234 testresources.ResourcedTestCase):
Attila Fazekas53943322014-02-10 16:07:34 +0100235 pass
236else:
237 # Define asserts for py26
238 import unittest2
239
240 class BaseDeps(testtools.TestCase,
241 testtools.testcase.WithAttributes,
242 testresources.ResourcedTestCase,
243 unittest2.TestCase):
244 pass
245
246
247class BaseTestCase(BaseDeps):
Attila Fazekasc43fec82013-04-09 23:17:52 +0200248
Attila Fazekasf86fa312013-07-30 19:56:39 +0200249 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100250 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200251
Matthew Treinish9f756a02014-01-15 10:26:07 -0500252 network_resources = {}
253
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200254 @classmethod
255 def setUpClass(cls):
256 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
257 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200258 cls.setUpClassCalled = True
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200259
Attila Fazekasf86fa312013-07-30 19:56:39 +0200260 @classmethod
261 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200262 at_exit_set.discard(cls)
Attila Fazekasf86fa312013-07-30 19:56:39 +0200263 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
264 super(BaseTestCase, cls).tearDownClass()
265
266 def setUp(self):
267 super(BaseTestCase, self).setUp()
268 if not self.setUpClassCalled:
269 raise RuntimeError("setUpClass does not calls the super's"
270 "setUpClass in the "
271 + self.__class__.__name__)
272 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400273 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
274 try:
275 test_timeout = int(test_timeout)
276 except ValueError:
277 test_timeout = 0
278 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200279 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400280
281 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
282 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200283 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
284 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400285 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
286 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200287 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
288 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200289 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
290 os.environ.get('OS_LOG_CAPTURE') != '0'):
291 log_format = '%(asctime)-15s %(message)s'
292 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Attila Fazekas90445be2013-10-24 16:46:03 +0200293 format=log_format,
294 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400295
Matthew Treinish3e046852013-07-23 16:00:24 -0400296 @classmethod
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000297 def get_client_manager(cls, interface=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700298 """
tanlin4956a642014-02-13 16:52:11 +0800299 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700300 """
Matthew Treinish9f756a02014-01-15 10:26:07 -0500301 cls.isolated_creds = isolated_creds.IsolatedCreds(
302 cls.__name__, network_resources=cls.network_resources)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700303
304 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000305 if (CONF.compute.allow_tenant_isolation or
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700306 force_tenant_isolation):
307 creds = cls.isolated_creds.get_primary_creds()
308 username, tenant_name, password = creds
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000309 if getattr(cls, '_interface', None):
310 os = clients.Manager(username=username,
311 password=password,
312 tenant_name=tenant_name,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100313 interface=cls._interface,
314 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000315 elif interface:
316 os = clients.Manager(username=username,
317 password=password,
318 tenant_name=tenant_name,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100319 interface=interface,
320 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000321 else:
322 os = clients.Manager(username=username,
323 password=password,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100324 tenant_name=tenant_name,
325 service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700326 else:
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000327 if getattr(cls, '_interface', None):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100328 os = clients.Manager(interface=cls._interface,
329 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000330 elif interface:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100331 os = clients.Manager(interface=interface, service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000332 else:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100333 os = clients.Manager(service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700334 return os
335
336 @classmethod
337 def clear_isolated_creds(cls):
338 """
339 Clears isolated creds if set
340 """
341 if getattr(cls, 'isolated_creds'):
342 cls.isolated_creds.clear_isolated_creds()
343
344 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400345 def _get_identity_admin_client(cls):
346 """
347 Returns an instance of the Identity Admin API client
348 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100349 os = clients.AdminManager(interface=cls._interface,
350 service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400351 admin_client = os.identity_client
352 return admin_client
353
354 @classmethod
Matthew Treinish9f756a02014-01-15 10:26:07 -0500355 def set_network_resources(self, network=False, router=False, subnet=False,
356 dhcp=False):
357 """Specify which network resources should be created
358
359 @param network
360 @param router
361 @param subnet
362 @param dhcp
363 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000364 # network resources should be set only once from callers
365 # in order to ensure that even if it's called multiple times in
366 # a chain of overloaded methods, the attribute is set only
367 # in the leaf class
368 if not self.network_resources:
369 self.network_resources = {
370 'network': network,
371 'router': router,
372 'subnet': subnet,
373 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500374
Mark Maglana5885eb32014-02-28 10:57:34 -0800375 def assertEmpty(self, list, msg=None):
376 self.assertTrue(len(list) == 0, msg)
377
378 def assertNotEmpty(self, list, msg=None):
379 self.assertTrue(len(list) > 0, msg)
380
Attila Fazekasdc216422013-01-29 15:12:14 +0100381
Marc Koderer24eb89c2014-01-31 11:23:33 +0100382class NegativeAutoTest(BaseTestCase):
383
384 _resources = {}
385
386 @classmethod
387 def setUpClass(cls):
388 super(NegativeAutoTest, cls).setUpClass()
389 os = cls.get_client_manager()
390 cls.client = os.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100391 os_admin = clients.AdminManager(interface=cls._interface,
392 service=cls._service)
393 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100394
395 @staticmethod
396 def load_schema(file):
397 """
398 Loads a schema from a file on a specified location.
399
400 :param file: the file name
401 """
402 #NOTE(mkoderer): must be extended for xml support
403 fn = os.path.join(
404 os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
405 "etc", "schemas", file)
406 LOG.debug("Open schema file: %s" % (fn))
407 return json.load(open(fn))
408
409 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100410 def load_tests(*args):
411 """
412 Wrapper for testscenarios to set the mandatory scenarios variable
413 only in case a real test loader is in place. Will be automatically
414 called in case the variable "load_tests" is set.
415 """
416 if getattr(args[0], 'suiteClass', None) is not None:
417 loader, standard_tests, pattern = args
418 else:
419 standard_tests, module, loader = args
420 for test in testtools.iterate_tests(standard_tests):
421 schema_file = getattr(test, '_schema_file', None)
422 if schema_file is not None:
423 setattr(test, 'scenarios',
424 NegativeAutoTest.generate_scenario(schema_file))
425 return testscenarios.load_tests_apply_scenarios(*args)
426
427 @staticmethod
Marc Koderer24eb89c2014-01-31 11:23:33 +0100428 def generate_scenario(description_file):
429 """
430 Generates the test scenario list for a given description.
431
432 :param description: A dictionary with the following entries:
433 name (required) name for the api
434 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
435 url (required) the url to be appended to the catalog url with '%s'
436 for each resource mentioned
437 resources: (optional) A list of resource names such as "server",
438 "flavor", etc. with an element for each '%s' in the url. This
439 method will call self.get_resource for each element when
440 constructing the positive test case template so negative
441 subclasses are expected to return valid resource ids when
442 appropriate.
443 json-schema (optional) A valid json schema that will be used to
444 create invalid data for the api calls. For "GET" and "HEAD",
445 the data is used to generate query strings appended to the url,
446 otherwise for the body of the http call.
447 """
448 description = NegativeAutoTest.load_schema(description_file)
449 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100450 generator = importutils.import_class(
451 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100452 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100453 schema = description.get("json-schema", None)
454 resources = description.get("resources", [])
455 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100456 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100457 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100458 if isinstance(resource, dict):
459 expected_result = resource['expected_result']
460 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100461 LOG.debug("Add resource to test %s" % resource)
462 scn_name = "inv_res_%s" % (resource)
463 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100464 str(uuid.uuid4())),
465 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100466 }))
467 if schema is not None:
Marc Kodererf857fda2014-03-05 15:58:00 +0100468 for name, schema, expected_result in generator.generate(schema):
469 if (expected_result is None and
470 "default_result_code" in description):
471 expected_result = description["default_result_code"]
472 scenario_list.append((name,
473 {"schema": schema,
474 "expected_result": expected_result}))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100475 LOG.debug(scenario_list)
476 return scenario_list
477
478 def execute(self, description_file):
479 """
480 Execute a http call on an api that are expected to
481 result in client errors. First it uses invalid resources that are part
482 of the url, and then invalid data for queries and http request bodies.
483
484 :param description: A dictionary with the following entries:
485 name (required) name for the api
486 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
487 url (required) the url to be appended to the catalog url with '%s'
488 for each resource mentioned
489 resources: (optional) A list of resource names such as "server",
490 "flavor", etc. with an element for each '%s' in the url. This
491 method will call self.get_resource for each element when
492 constructing the positive test case template so negative
493 subclasses are expected to return valid resource ids when
494 appropriate.
495 json-schema (optional) A valid json schema that will be used to
496 create invalid data for the api calls. For "GET" and "HEAD",
497 the data is used to generate query strings appended to the url,
498 otherwise for the body of the http call.
499
500 """
501 description = NegativeAutoTest.load_schema(description_file)
502 LOG.info("Executing %s" % description["name"])
503 LOG.debug(description)
504 method = description["http-method"]
505 url = description["url"]
506
507 resources = [self.get_resource(r) for
508 r in description.get("resources", [])]
509
510 if hasattr(self, "resource"):
511 # Note(mkoderer): The resources list already contains an invalid
512 # entry (see get_resource).
513 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100514 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100515 schema = description.get("json-schema", None)
516 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100517 valid_schema = \
518 valid.ValidTestGenerator().generate_valid(schema)
519 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Koderer424c84f2014-02-06 17:02:19 +0100520 elif hasattr(self, "schema"):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100521 new_url, body = self._http_arguments(self.schema, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100522 else:
523 raise Exception("testscenarios are not active. Please make sure "
524 "that your test runner supports the load_tests "
525 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100526
Marc Kodererf857fda2014-03-05 15:58:00 +0100527 if "admin_client" in description and description["admin_client"]:
528 client = self.admin_client
529 else:
530 client = self.client
531 resp, resp_body = client.send_request(method, new_url,
532 resources, body=body)
Marc Koderer424c84f2014-02-06 17:02:19 +0100533 self._check_negative_response(resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100534
535 def _http_arguments(self, json_dict, url, method):
536 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
537 if not json_dict:
538 return url, None
539 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
540 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
541 else:
542 return url, json.dumps(json_dict)
543
544 def _check_negative_response(self, result, body):
545 expected_result = getattr(self, "expected_result", None)
546 self.assertTrue(result >= 400 and result < 500 and result != 413,
547 "Expected client error, got %s:%s" %
548 (result, body))
549 self.assertTrue(expected_result is None or expected_result == result,
550 "Expected %s, got %s:%s" %
551 (expected_result, result, body))
552
553 @classmethod
554 def set_resource(cls, name, resource):
555 """
556 This function can be used in setUpClass context to register a resoruce
557 for a test.
558
559 :param name: The name of the kind of resource such as "flavor", "role",
560 etc.
561 :resource: The id of the resource
562 """
563 cls._resources[name] = resource
564
565 def get_resource(self, name):
566 """
567 Return a valid uuid for a type of resource. If a real resource is
568 needed as part of a url then this method should return one. Otherwise
569 it can return None.
570
571 :param name: The name of the kind of resource such as "flavor", "role",
572 etc.
573 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100574 if isinstance(name, dict):
575 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100576 if hasattr(self, "resource") and self.resource[0] == name:
577 LOG.debug("Return invalid resource (%s) value: %s" %
578 (self.resource[0], self.resource[1]))
579 return self.resource[1]
580 if name in self._resources:
581 return self._resources[name]
582 return None
583
584
Marc Kodererb2978da2014-03-26 13:45:43 +0100585def SimpleNegativeAutoTest(klass):
586 """
587 This decorator registers a test function on basis of the class name.
588 """
589 @attr(type=['negative', 'gate'])
590 def generic_test(self):
591 self.execute(self._schema_file)
592
593 cn = klass.__name__
594 cn = cn.replace('JSON', '')
595 cn = cn.replace('Test', '')
596 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
597 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
598 func_name = 'test_%s' % lower_cn
599 setattr(klass, func_name, generic_test)
600 return klass
601
602
Sean Dague35a7caf2013-05-10 10:38:22 -0400603def call_until_true(func, duration, sleep_for):
604 """
605 Call the given function until it returns True (and return True) or
606 until the specified duration (in seconds) elapses (and return
607 False).
608
609 :param func: A zero argument callable that returns True on success.
610 :param duration: The number of seconds for which to attempt a
611 successful call of the function.
612 :param sleep_for: The number of seconds to sleep after an unsuccessful
613 invocation of the function.
614 """
615 now = time.time()
616 timeout = now + duration
617 while now < timeout:
618 if func():
619 return True
620 LOG.debug("Sleeping for %d seconds", sleep_for)
621 time.sleep(sleep_for)
622 now = time.time()
623 return False