blob: abf42c09cf85902456d3d87b59ae1ce0efcc7c52 [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
Attila Fazekas53943322014-02-10 16:07:34 +010020import sys
Jay Pipes051075a2012-04-28 17:39:37 -040021import time
Marc Koderer24eb89c2014-01-31 11:23:33 +010022import urllib
23import uuid
Jay Pipes051075a2012-04-28 17:39:37 -040024
Matthew Treinish78561ad2013-07-26 11:41:56 -040025import fixtures
Attila Fazekasdc216422013-01-29 15:12:14 +010026import testresources
Marc Koderer674c8fc2014-03-17 09:45:04 +010027import testscenarios
ivan-zhu1feeb382013-01-24 10:14:39 +080028import testtools
Jay Pipes051075a2012-04-28 17:39:37 -040029
Matthew Treinish3e046852013-07-23 16:00:24 -040030from tempest import clients
Marc Koderer6ee82dc2014-02-17 10:26:29 +010031import tempest.common.generator.valid_generator as valid
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -070032from tempest.common import isolated_creds
Attila Fazekasdc216422013-01-29 15:12:14 +010033from tempest import config
Matthew Treinish16c43792013-09-09 19:55:23 +000034from tempest import exceptions
Marc Koderer6ee82dc2014-02-17 10:26:29 +010035from tempest.openstack.common import importutils
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040036from tempest.openstack.common import log as logging
Jay Pipes051075a2012-04-28 17:39:37 -040037
38LOG = logging.getLogger(__name__)
39
Sean Dague86bd8422013-12-20 09:56:44 -050040CONF = config.CONF
41
Samuel Merritt0d499bc2013-06-19 12:08:23 -070042# All the successful HTTP status codes from RFC 2616
43HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206)
44
Jay Pipes051075a2012-04-28 17:39:37 -040045
Chris Yeoh55530bb2013-02-08 16:04:27 +103046def attr(*args, **kwargs):
Matthew Treinisha74f5d42014-02-07 20:25:44 -050047 """A decorator which applies the testtools attr decorator
Chris Yeoh55530bb2013-02-08 16:04:27 +103048
Matthew Treinisha74f5d42014-02-07 20:25:44 -050049 This decorator applies the testtools.testcase.attr if it is in the list of
50 attributes to testtools we want to apply.
Attila Fazekasb2902af2013-02-16 16:22:44 +010051 """
Chris Yeoh55530bb2013-02-08 16:04:27 +103052
53 def decorator(f):
Giulio Fidente4946a052013-05-14 12:23:51 +020054 if 'type' in kwargs and isinstance(kwargs['type'], str):
55 f = testtools.testcase.attr(kwargs['type'])(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093056 if kwargs['type'] == 'smoke':
57 f = testtools.testcase.attr('gate')(f)
Giulio Fidente4946a052013-05-14 12:23:51 +020058 elif 'type' in kwargs and isinstance(kwargs['type'], list):
59 for attr in kwargs['type']:
60 f = testtools.testcase.attr(attr)(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093061 if attr == 'smoke':
62 f = testtools.testcase.attr('gate')(f)
Matthew Treinisha74f5d42014-02-07 20:25:44 -050063 return f
Chris Yeoh55530bb2013-02-08 16:04:27 +103064
65 return decorator
66
67
Zhi Kun Liu22fb4702014-02-27 16:15:24 +080068def safe_setup(f):
69 """A decorator used to wrap the setUpClass for cleaning up resources
70 when setUpClass failed.
71 """
72
73 def decorator(cls):
74 try:
75 f(cls)
76 except Exception as se:
77 LOG.exception("setUpClass failed: %s" % se)
78 try:
79 cls.tearDownClass()
80 except Exception as te:
81 LOG.exception("tearDownClass failed: %s" % te)
82 raise se
83
84 return decorator
85
86
Matthew Treinish16c43792013-09-09 19:55:23 +000087def services(*args, **kwargs):
88 """A decorator used to set an attr for each service used in a test case
89
90 This decorator applies a testtools attr for each service that gets
91 exercised by a test case.
92 """
Matthew Treinish8afbffd2014-01-21 23:56:13 +000093 service_list = {
94 'compute': CONF.service_available.nova,
95 'image': CONF.service_available.glance,
96 'volume': CONF.service_available.cinder,
97 'orchestration': CONF.service_available.heat,
98 # NOTE(mtreinish) nova-network will provide networking functionality
99 # if neutron isn't available, so always set to True.
100 'network': True,
101 'identity': True,
102 'object_storage': CONF.service_available.swift,
103 'dashboard': CONF.service_available.horizon,
104 }
Matthew Treinish16c43792013-09-09 19:55:23 +0000105
106 def decorator(f):
107 for service in args:
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000108 if service not in service_list:
Matthew Treinish16c43792013-09-09 19:55:23 +0000109 raise exceptions.InvalidServiceTag('%s is not a valid service'
110 % service)
111 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000112
113 @functools.wraps(f)
114 def wrapper(self, *func_args, **func_kwargs):
115 for service in args:
116 if not service_list[service]:
117 msg = 'Skipped because the %s service is not available' % (
118 service)
119 raise testtools.TestCase.skipException(msg)
120 return f(self, *func_args, **func_kwargs)
121 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000122 return decorator
123
124
Marc Koderer32221b8e2013-08-23 13:57:50 +0200125def stresstest(*args, **kwargs):
126 """Add stress test decorator
127
128 For all functions with this decorator a attr stress will be
129 set automatically.
130
131 @param class_setup_per: allowed values are application, process, action
132 ``application``: once in the stress job lifetime
133 ``process``: once in the worker process lifetime
134 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200135 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200136 """
137 def decorator(f):
138 if 'class_setup_per' in kwargs:
139 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
140 else:
141 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200142 if 'allow_inheritance' in kwargs:
143 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
144 else:
145 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200146 attr(type='stress')(f)
147 return f
148 return decorator
149
150
Giulio Fidente83181a92013-10-01 06:02:24 +0200151def skip_because(*args, **kwargs):
152 """A decorator useful to skip tests hitting known bugs
153
154 @param bug: bug number causing the test to skip
155 @param condition: optional condition to be True for the skip to have place
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900156 @param interface: skip the test if it is the same as self._interface
Giulio Fidente83181a92013-10-01 06:02:24 +0200157 """
158 def decorator(f):
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900159 @functools.wraps(f)
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900160 def wrapper(self, *func_args, **func_kwargs):
161 skip = False
162 if "condition" in kwargs:
163 if kwargs["condition"] is True:
164 skip = True
165 elif "interface" in kwargs:
166 if kwargs["interface"] == self._interface:
167 skip = True
168 else:
169 skip = True
170 if "bug" in kwargs and skip is True:
Matthew Treinishede50272014-02-11 19:56:11 +0000171 if not kwargs['bug'].isdigit():
172 raise ValueError('bug must be a valid bug number')
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900173 msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
174 raise testtools.TestCase.skipException(msg)
175 return f(self, *func_args, **func_kwargs)
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900176 return wrapper
Giulio Fidente83181a92013-10-01 06:02:24 +0200177 return decorator
178
179
Matthew Treinishe3d26142013-11-26 19:14:58 +0000180def requires_ext(*args, **kwargs):
181 """A decorator to skip tests if an extension is not enabled
182
183 @param extension
184 @param service
185 """
186 def decorator(func):
187 @functools.wraps(func)
188 def wrapper(*func_args, **func_kwargs):
189 if not is_extension_enabled(kwargs['extension'],
190 kwargs['service']):
191 msg = "Skipped because %s extension: %s is not enabled" % (
192 kwargs['service'], kwargs['extension'])
193 raise testtools.TestCase.skipException(msg)
194 return func(*func_args, **func_kwargs)
195 return wrapper
196 return decorator
197
198
199def is_extension_enabled(extension_name, service):
200 """A function that will check the list of enabled extensions from config
201
202 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000203 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000204 'compute': CONF.compute_feature_enabled.api_extensions,
205 'compute_v3': CONF.compute_feature_enabled.api_v3_extensions,
206 'volume': CONF.volume_feature_enabled.api_extensions,
207 'network': CONF.network_feature_enabled.api_extensions,
208 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000209 }
210 if config_dict[service][0] == 'all':
211 return True
212 if extension_name in config_dict[service]:
213 return True
214 return False
215
Ian Wienand98c35f32013-07-23 20:34:23 +1000216
Attila Fazekasf86fa312013-07-30 19:56:39 +0200217at_exit_set = set()
218
219
220def validate_tearDownClass():
221 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400222 LOG.error(
223 "tearDownClass does not call the super's "
224 "tearDownClass in these classes: \n"
225 + str(at_exit_set))
226
Attila Fazekasf86fa312013-07-30 19:56:39 +0200227
228atexit.register(validate_tearDownClass)
229
Attila Fazekas53943322014-02-10 16:07:34 +0100230if sys.version_info >= (2, 7):
231 class BaseDeps(testtools.TestCase,
Attila Fazekasdc216422013-01-29 15:12:14 +0100232 testtools.testcase.WithAttributes,
233 testresources.ResourcedTestCase):
Attila Fazekas53943322014-02-10 16:07:34 +0100234 pass
235else:
236 # Define asserts for py26
237 import unittest2
238
239 class BaseDeps(testtools.TestCase,
240 testtools.testcase.WithAttributes,
241 testresources.ResourcedTestCase,
242 unittest2.TestCase):
243 pass
244
245
246class BaseTestCase(BaseDeps):
Attila Fazekasc43fec82013-04-09 23:17:52 +0200247
Attila Fazekasf86fa312013-07-30 19:56:39 +0200248 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100249 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200250
Matthew Treinish9f756a02014-01-15 10:26:07 -0500251 network_resources = {}
252
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200253 @classmethod
254 def setUpClass(cls):
255 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
256 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200257 cls.setUpClassCalled = True
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200258
Attila Fazekasf86fa312013-07-30 19:56:39 +0200259 @classmethod
260 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200261 at_exit_set.discard(cls)
Attila Fazekasf86fa312013-07-30 19:56:39 +0200262 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
263 super(BaseTestCase, cls).tearDownClass()
264
265 def setUp(self):
266 super(BaseTestCase, self).setUp()
267 if not self.setUpClassCalled:
268 raise RuntimeError("setUpClass does not calls the super's"
269 "setUpClass in the "
270 + self.__class__.__name__)
271 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400272 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
273 try:
274 test_timeout = int(test_timeout)
275 except ValueError:
276 test_timeout = 0
277 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200278 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400279
280 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
281 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200282 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
283 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400284 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
285 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200286 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
287 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200288 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
289 os.environ.get('OS_LOG_CAPTURE') != '0'):
290 log_format = '%(asctime)-15s %(message)s'
291 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Attila Fazekas90445be2013-10-24 16:46:03 +0200292 format=log_format,
293 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400294
Matthew Treinish3e046852013-07-23 16:00:24 -0400295 @classmethod
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000296 def get_client_manager(cls, interface=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700297 """
tanlin4956a642014-02-13 16:52:11 +0800298 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700299 """
Matthew Treinish9f756a02014-01-15 10:26:07 -0500300 cls.isolated_creds = isolated_creds.IsolatedCreds(
301 cls.__name__, network_resources=cls.network_resources)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700302
303 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000304 if (CONF.compute.allow_tenant_isolation or
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700305 force_tenant_isolation):
306 creds = cls.isolated_creds.get_primary_creds()
307 username, tenant_name, password = creds
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000308 if getattr(cls, '_interface', None):
309 os = clients.Manager(username=username,
310 password=password,
311 tenant_name=tenant_name,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100312 interface=cls._interface,
313 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000314 elif interface:
315 os = clients.Manager(username=username,
316 password=password,
317 tenant_name=tenant_name,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100318 interface=interface,
319 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000320 else:
321 os = clients.Manager(username=username,
322 password=password,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100323 tenant_name=tenant_name,
324 service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700325 else:
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000326 if getattr(cls, '_interface', None):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100327 os = clients.Manager(interface=cls._interface,
328 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000329 elif interface:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100330 os = clients.Manager(interface=interface, service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000331 else:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100332 os = clients.Manager(service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700333 return os
334
335 @classmethod
336 def clear_isolated_creds(cls):
337 """
338 Clears isolated creds if set
339 """
340 if getattr(cls, 'isolated_creds'):
341 cls.isolated_creds.clear_isolated_creds()
342
343 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400344 def _get_identity_admin_client(cls):
345 """
346 Returns an instance of the Identity Admin API client
347 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100348 os = clients.AdminManager(interface=cls._interface,
349 service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400350 admin_client = os.identity_client
351 return admin_client
352
353 @classmethod
Matthew Treinish9f756a02014-01-15 10:26:07 -0500354 def set_network_resources(self, network=False, router=False, subnet=False,
355 dhcp=False):
356 """Specify which network resources should be created
357
358 @param network
359 @param router
360 @param subnet
361 @param dhcp
362 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000363 # network resources should be set only once from callers
364 # in order to ensure that even if it's called multiple times in
365 # a chain of overloaded methods, the attribute is set only
366 # in the leaf class
367 if not self.network_resources:
368 self.network_resources = {
369 'network': network,
370 'router': router,
371 'subnet': subnet,
372 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500373
Mark Maglana5885eb32014-02-28 10:57:34 -0800374 def assertEmpty(self, list, msg=None):
375 self.assertTrue(len(list) == 0, msg)
376
377 def assertNotEmpty(self, list, msg=None):
378 self.assertTrue(len(list) > 0, msg)
379
Attila Fazekasdc216422013-01-29 15:12:14 +0100380
Marc Koderer24eb89c2014-01-31 11:23:33 +0100381class NegativeAutoTest(BaseTestCase):
382
383 _resources = {}
384
385 @classmethod
386 def setUpClass(cls):
387 super(NegativeAutoTest, cls).setUpClass()
388 os = cls.get_client_manager()
389 cls.client = os.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100390 os_admin = clients.AdminManager(interface=cls._interface,
391 service=cls._service)
392 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100393
394 @staticmethod
395 def load_schema(file):
396 """
397 Loads a schema from a file on a specified location.
398
399 :param file: the file name
400 """
401 #NOTE(mkoderer): must be extended for xml support
402 fn = os.path.join(
403 os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
404 "etc", "schemas", file)
405 LOG.debug("Open schema file: %s" % (fn))
406 return json.load(open(fn))
407
408 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100409 def load_tests(*args):
410 """
411 Wrapper for testscenarios to set the mandatory scenarios variable
412 only in case a real test loader is in place. Will be automatically
413 called in case the variable "load_tests" is set.
414 """
415 if getattr(args[0], 'suiteClass', None) is not None:
416 loader, standard_tests, pattern = args
417 else:
418 standard_tests, module, loader = args
419 for test in testtools.iterate_tests(standard_tests):
420 schema_file = getattr(test, '_schema_file', None)
421 if schema_file is not None:
422 setattr(test, 'scenarios',
423 NegativeAutoTest.generate_scenario(schema_file))
424 return testscenarios.load_tests_apply_scenarios(*args)
425
426 @staticmethod
Marc Koderer24eb89c2014-01-31 11:23:33 +0100427 def generate_scenario(description_file):
428 """
429 Generates the test scenario list for a given description.
430
431 :param description: A dictionary with the following entries:
432 name (required) name for the api
433 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
434 url (required) the url to be appended to the catalog url with '%s'
435 for each resource mentioned
436 resources: (optional) A list of resource names such as "server",
437 "flavor", etc. with an element for each '%s' in the url. This
438 method will call self.get_resource for each element when
439 constructing the positive test case template so negative
440 subclasses are expected to return valid resource ids when
441 appropriate.
442 json-schema (optional) A valid json schema that will be used to
443 create invalid data for the api calls. For "GET" and "HEAD",
444 the data is used to generate query strings appended to the url,
445 otherwise for the body of the http call.
446 """
447 description = NegativeAutoTest.load_schema(description_file)
448 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100449 generator = importutils.import_class(
450 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100451 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100452 schema = description.get("json-schema", None)
453 resources = description.get("resources", [])
454 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100455 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100456 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100457 if isinstance(resource, dict):
458 expected_result = resource['expected_result']
459 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100460 LOG.debug("Add resource to test %s" % resource)
461 scn_name = "inv_res_%s" % (resource)
462 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100463 str(uuid.uuid4())),
464 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100465 }))
466 if schema is not None:
Marc Kodererf857fda2014-03-05 15:58:00 +0100467 for name, schema, expected_result in generator.generate(schema):
468 if (expected_result is None and
469 "default_result_code" in description):
470 expected_result = description["default_result_code"]
471 scenario_list.append((name,
472 {"schema": schema,
473 "expected_result": expected_result}))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100474 LOG.debug(scenario_list)
475 return scenario_list
476
477 def execute(self, description_file):
478 """
479 Execute a http call on an api that are expected to
480 result in client errors. First it uses invalid resources that are part
481 of the url, and then invalid data for queries and http request bodies.
482
483 :param description: A dictionary with the following entries:
484 name (required) name for the api
485 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
486 url (required) the url to be appended to the catalog url with '%s'
487 for each resource mentioned
488 resources: (optional) A list of resource names such as "server",
489 "flavor", etc. with an element for each '%s' in the url. This
490 method will call self.get_resource for each element when
491 constructing the positive test case template so negative
492 subclasses are expected to return valid resource ids when
493 appropriate.
494 json-schema (optional) A valid json schema that will be used to
495 create invalid data for the api calls. For "GET" and "HEAD",
496 the data is used to generate query strings appended to the url,
497 otherwise for the body of the http call.
498
499 """
500 description = NegativeAutoTest.load_schema(description_file)
501 LOG.info("Executing %s" % description["name"])
502 LOG.debug(description)
503 method = description["http-method"]
504 url = description["url"]
505
506 resources = [self.get_resource(r) for
507 r in description.get("resources", [])]
508
509 if hasattr(self, "resource"):
510 # Note(mkoderer): The resources list already contains an invalid
511 # entry (see get_resource).
512 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100513 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100514 schema = description.get("json-schema", None)
515 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100516 valid_schema = \
517 valid.ValidTestGenerator().generate_valid(schema)
518 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Koderer424c84f2014-02-06 17:02:19 +0100519 elif hasattr(self, "schema"):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100520 new_url, body = self._http_arguments(self.schema, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100521 else:
522 raise Exception("testscenarios are not active. Please make sure "
523 "that your test runner supports the load_tests "
524 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100525
Marc Kodererf857fda2014-03-05 15:58:00 +0100526 if "admin_client" in description and description["admin_client"]:
527 client = self.admin_client
528 else:
529 client = self.client
530 resp, resp_body = client.send_request(method, new_url,
531 resources, body=body)
Marc Koderer424c84f2014-02-06 17:02:19 +0100532 self._check_negative_response(resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100533
534 def _http_arguments(self, json_dict, url, method):
535 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
536 if not json_dict:
537 return url, None
538 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
539 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
540 else:
541 return url, json.dumps(json_dict)
542
543 def _check_negative_response(self, result, body):
544 expected_result = getattr(self, "expected_result", None)
545 self.assertTrue(result >= 400 and result < 500 and result != 413,
546 "Expected client error, got %s:%s" %
547 (result, body))
548 self.assertTrue(expected_result is None or expected_result == result,
549 "Expected %s, got %s:%s" %
550 (expected_result, result, body))
551
552 @classmethod
553 def set_resource(cls, name, resource):
554 """
555 This function can be used in setUpClass context to register a resoruce
556 for a test.
557
558 :param name: The name of the kind of resource such as "flavor", "role",
559 etc.
560 :resource: The id of the resource
561 """
562 cls._resources[name] = resource
563
564 def get_resource(self, name):
565 """
566 Return a valid uuid for a type of resource. If a real resource is
567 needed as part of a url then this method should return one. Otherwise
568 it can return None.
569
570 :param name: The name of the kind of resource such as "flavor", "role",
571 etc.
572 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100573 if isinstance(name, dict):
574 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100575 if hasattr(self, "resource") and self.resource[0] == name:
576 LOG.debug("Return invalid resource (%s) value: %s" %
577 (self.resource[0], self.resource[1]))
578 return self.resource[1]
579 if name in self._resources:
580 return self._resources[name]
581 return None
582
583
Sean Dague35a7caf2013-05-10 10:38:22 -0400584def call_until_true(func, duration, sleep_for):
585 """
586 Call the given function until it returns True (and return True) or
587 until the specified duration (in seconds) elapses (and return
588 False).
589
590 :param func: A zero argument callable that returns True on success.
591 :param duration: The number of seconds for which to attempt a
592 successful call of the function.
593 :param sleep_for: The number of seconds to sleep after an unsuccessful
594 invocation of the function.
595 """
596 now = time.time()
597 timeout = now + duration
598 while now < timeout:
599 if func():
600 return True
601 LOG.debug("Sleeping for %d seconds", sleep_for)
602 time.sleep(sleep_for)
603 now = time.time()
604 return False