blob: 58842e41827a178478cbc6814740c99d149cccc6 [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
ivan-zhu1feeb382013-01-24 10:14:39 +080027import testtools
Jay Pipes051075a2012-04-28 17:39:37 -040028
Matthew Treinish3e046852013-07-23 16:00:24 -040029from tempest import clients
Marc Koderer6ee82dc2014-02-17 10:26:29 +010030import tempest.common.generator.valid_generator as valid
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -070031from tempest.common import isolated_creds
Attila Fazekasdc216422013-01-29 15:12:14 +010032from tempest import config
Matthew Treinish16c43792013-09-09 19:55:23 +000033from tempest import exceptions
Marc Koderer6ee82dc2014-02-17 10:26:29 +010034from tempest.openstack.common import importutils
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040035from tempest.openstack.common import log as logging
Jay Pipes051075a2012-04-28 17:39:37 -040036
37LOG = logging.getLogger(__name__)
38
Sean Dague86bd8422013-12-20 09:56:44 -050039CONF = config.CONF
40
Samuel Merritt0d499bc2013-06-19 12:08:23 -070041# All the successful HTTP status codes from RFC 2616
42HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206)
43
Jay Pipes051075a2012-04-28 17:39:37 -040044
Chris Yeoh55530bb2013-02-08 16:04:27 +103045def attr(*args, **kwargs):
Matthew Treinisha74f5d42014-02-07 20:25:44 -050046 """A decorator which applies the testtools attr decorator
Chris Yeoh55530bb2013-02-08 16:04:27 +103047
Matthew Treinisha74f5d42014-02-07 20:25:44 -050048 This decorator applies the testtools.testcase.attr if it is in the list of
49 attributes to testtools we want to apply.
Attila Fazekasb2902af2013-02-16 16:22:44 +010050 """
Chris Yeoh55530bb2013-02-08 16:04:27 +103051
52 def decorator(f):
Giulio Fidente4946a052013-05-14 12:23:51 +020053 if 'type' in kwargs and isinstance(kwargs['type'], str):
54 f = testtools.testcase.attr(kwargs['type'])(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093055 if kwargs['type'] == 'smoke':
56 f = testtools.testcase.attr('gate')(f)
Giulio Fidente4946a052013-05-14 12:23:51 +020057 elif 'type' in kwargs and isinstance(kwargs['type'], list):
58 for attr in kwargs['type']:
59 f = testtools.testcase.attr(attr)(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093060 if attr == 'smoke':
61 f = testtools.testcase.attr('gate')(f)
Matthew Treinisha74f5d42014-02-07 20:25:44 -050062 return f
Chris Yeoh55530bb2013-02-08 16:04:27 +103063
64 return decorator
65
66
Matthew Treinish16c43792013-09-09 19:55:23 +000067def services(*args, **kwargs):
68 """A decorator used to set an attr for each service used in a test case
69
70 This decorator applies a testtools attr for each service that gets
71 exercised by a test case.
72 """
Matthew Treinish8afbffd2014-01-21 23:56:13 +000073 service_list = {
74 'compute': CONF.service_available.nova,
75 'image': CONF.service_available.glance,
76 'volume': CONF.service_available.cinder,
77 'orchestration': CONF.service_available.heat,
78 # NOTE(mtreinish) nova-network will provide networking functionality
79 # if neutron isn't available, so always set to True.
80 'network': True,
81 'identity': True,
82 'object_storage': CONF.service_available.swift,
83 'dashboard': CONF.service_available.horizon,
84 }
Matthew Treinish16c43792013-09-09 19:55:23 +000085
86 def decorator(f):
87 for service in args:
Matthew Treinish8afbffd2014-01-21 23:56:13 +000088 if service not in service_list:
Matthew Treinish16c43792013-09-09 19:55:23 +000089 raise exceptions.InvalidServiceTag('%s is not a valid service'
90 % service)
91 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +000092
93 @functools.wraps(f)
94 def wrapper(self, *func_args, **func_kwargs):
95 for service in args:
96 if not service_list[service]:
97 msg = 'Skipped because the %s service is not available' % (
98 service)
99 raise testtools.TestCase.skipException(msg)
100 return f(self, *func_args, **func_kwargs)
101 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000102 return decorator
103
104
Marc Koderer32221b8e2013-08-23 13:57:50 +0200105def stresstest(*args, **kwargs):
106 """Add stress test decorator
107
108 For all functions with this decorator a attr stress will be
109 set automatically.
110
111 @param class_setup_per: allowed values are application, process, action
112 ``application``: once in the stress job lifetime
113 ``process``: once in the worker process lifetime
114 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200115 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200116 """
117 def decorator(f):
118 if 'class_setup_per' in kwargs:
119 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
120 else:
121 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200122 if 'allow_inheritance' in kwargs:
123 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
124 else:
125 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200126 attr(type='stress')(f)
127 return f
128 return decorator
129
130
Giulio Fidente83181a92013-10-01 06:02:24 +0200131def skip_because(*args, **kwargs):
132 """A decorator useful to skip tests hitting known bugs
133
134 @param bug: bug number causing the test to skip
135 @param condition: optional condition to be True for the skip to have place
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900136 @param interface: skip the test if it is the same as self._interface
Giulio Fidente83181a92013-10-01 06:02:24 +0200137 """
138 def decorator(f):
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900139 @functools.wraps(f)
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900140 def wrapper(self, *func_args, **func_kwargs):
141 skip = False
142 if "condition" in kwargs:
143 if kwargs["condition"] is True:
144 skip = True
145 elif "interface" in kwargs:
146 if kwargs["interface"] == self._interface:
147 skip = True
148 else:
149 skip = True
150 if "bug" in kwargs and skip is True:
Matthew Treinishede50272014-02-11 19:56:11 +0000151 if not kwargs['bug'].isdigit():
152 raise ValueError('bug must be a valid bug number')
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900153 msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
154 raise testtools.TestCase.skipException(msg)
155 return f(self, *func_args, **func_kwargs)
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900156 return wrapper
Giulio Fidente83181a92013-10-01 06:02:24 +0200157 return decorator
158
159
Matthew Treinishe3d26142013-11-26 19:14:58 +0000160def requires_ext(*args, **kwargs):
161 """A decorator to skip tests if an extension is not enabled
162
163 @param extension
164 @param service
165 """
166 def decorator(func):
167 @functools.wraps(func)
168 def wrapper(*func_args, **func_kwargs):
169 if not is_extension_enabled(kwargs['extension'],
170 kwargs['service']):
171 msg = "Skipped because %s extension: %s is not enabled" % (
172 kwargs['service'], kwargs['extension'])
173 raise testtools.TestCase.skipException(msg)
174 return func(*func_args, **func_kwargs)
175 return wrapper
176 return decorator
177
178
179def is_extension_enabled(extension_name, service):
180 """A function that will check the list of enabled extensions from config
181
182 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000183 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000184 'compute': CONF.compute_feature_enabled.api_extensions,
185 'compute_v3': CONF.compute_feature_enabled.api_v3_extensions,
186 'volume': CONF.volume_feature_enabled.api_extensions,
187 'network': CONF.network_feature_enabled.api_extensions,
188 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000189 }
190 if config_dict[service][0] == 'all':
191 return True
192 if extension_name in config_dict[service]:
193 return True
194 return False
195
Ian Wienand98c35f32013-07-23 20:34:23 +1000196
Attila Fazekasf86fa312013-07-30 19:56:39 +0200197at_exit_set = set()
198
199
200def validate_tearDownClass():
201 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400202 LOG.error(
203 "tearDownClass does not call the super's "
204 "tearDownClass in these classes: \n"
205 + str(at_exit_set))
206
Attila Fazekasf86fa312013-07-30 19:56:39 +0200207
208atexit.register(validate_tearDownClass)
209
Attila Fazekas53943322014-02-10 16:07:34 +0100210if sys.version_info >= (2, 7):
211 class BaseDeps(testtools.TestCase,
Attila Fazekasdc216422013-01-29 15:12:14 +0100212 testtools.testcase.WithAttributes,
213 testresources.ResourcedTestCase):
Attila Fazekas53943322014-02-10 16:07:34 +0100214 pass
215else:
216 # Define asserts for py26
217 import unittest2
218
219 class BaseDeps(testtools.TestCase,
220 testtools.testcase.WithAttributes,
221 testresources.ResourcedTestCase,
222 unittest2.TestCase):
223 pass
224
225
226class BaseTestCase(BaseDeps):
Attila Fazekasc43fec82013-04-09 23:17:52 +0200227
Attila Fazekasf86fa312013-07-30 19:56:39 +0200228 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100229 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200230
Matthew Treinish9f756a02014-01-15 10:26:07 -0500231 network_resources = {}
232
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200233 @classmethod
234 def setUpClass(cls):
235 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
236 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200237 cls.setUpClassCalled = True
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200238
Attila Fazekasf86fa312013-07-30 19:56:39 +0200239 @classmethod
240 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200241 at_exit_set.discard(cls)
Attila Fazekasf86fa312013-07-30 19:56:39 +0200242 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
243 super(BaseTestCase, cls).tearDownClass()
244
245 def setUp(self):
246 super(BaseTestCase, self).setUp()
247 if not self.setUpClassCalled:
248 raise RuntimeError("setUpClass does not calls the super's"
249 "setUpClass in the "
250 + self.__class__.__name__)
251 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400252 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
253 try:
254 test_timeout = int(test_timeout)
255 except ValueError:
256 test_timeout = 0
257 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200258 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400259
260 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
261 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200262 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
263 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400264 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
265 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200266 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
267 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200268 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
269 os.environ.get('OS_LOG_CAPTURE') != '0'):
270 log_format = '%(asctime)-15s %(message)s'
271 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Attila Fazekas90445be2013-10-24 16:46:03 +0200272 format=log_format,
273 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400274
Matthew Treinish3e046852013-07-23 16:00:24 -0400275 @classmethod
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000276 def get_client_manager(cls, interface=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700277 """
tanlin4956a642014-02-13 16:52:11 +0800278 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700279 """
Matthew Treinish9f756a02014-01-15 10:26:07 -0500280 cls.isolated_creds = isolated_creds.IsolatedCreds(
281 cls.__name__, network_resources=cls.network_resources)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700282
283 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000284 if (CONF.compute.allow_tenant_isolation or
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700285 force_tenant_isolation):
286 creds = cls.isolated_creds.get_primary_creds()
287 username, tenant_name, password = creds
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000288 if getattr(cls, '_interface', None):
289 os = clients.Manager(username=username,
290 password=password,
291 tenant_name=tenant_name,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100292 interface=cls._interface,
293 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000294 elif interface:
295 os = clients.Manager(username=username,
296 password=password,
297 tenant_name=tenant_name,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100298 interface=interface,
299 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000300 else:
301 os = clients.Manager(username=username,
302 password=password,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100303 tenant_name=tenant_name,
304 service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700305 else:
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000306 if getattr(cls, '_interface', None):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100307 os = clients.Manager(interface=cls._interface,
308 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000309 elif interface:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100310 os = clients.Manager(interface=interface, service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000311 else:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100312 os = clients.Manager(service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700313 return os
314
315 @classmethod
316 def clear_isolated_creds(cls):
317 """
318 Clears isolated creds if set
319 """
320 if getattr(cls, 'isolated_creds'):
321 cls.isolated_creds.clear_isolated_creds()
322
323 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400324 def _get_identity_admin_client(cls):
325 """
326 Returns an instance of the Identity Admin API client
327 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100328 os = clients.AdminManager(interface=cls._interface,
329 service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400330 admin_client = os.identity_client
331 return admin_client
332
333 @classmethod
Matthew Treinish9f756a02014-01-15 10:26:07 -0500334 def set_network_resources(self, network=False, router=False, subnet=False,
335 dhcp=False):
336 """Specify which network resources should be created
337
338 @param network
339 @param router
340 @param subnet
341 @param dhcp
342 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000343 # network resources should be set only once from callers
344 # in order to ensure that even if it's called multiple times in
345 # a chain of overloaded methods, the attribute is set only
346 # in the leaf class
347 if not self.network_resources:
348 self.network_resources = {
349 'network': network,
350 'router': router,
351 'subnet': subnet,
352 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500353
Attila Fazekasdc216422013-01-29 15:12:14 +0100354
Marc Koderer24eb89c2014-01-31 11:23:33 +0100355class NegativeAutoTest(BaseTestCase):
356
357 _resources = {}
358
359 @classmethod
360 def setUpClass(cls):
361 super(NegativeAutoTest, cls).setUpClass()
362 os = cls.get_client_manager()
363 cls.client = os.negative_client
364
365 @staticmethod
366 def load_schema(file):
367 """
368 Loads a schema from a file on a specified location.
369
370 :param file: the file name
371 """
372 #NOTE(mkoderer): must be extended for xml support
373 fn = os.path.join(
374 os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
375 "etc", "schemas", file)
376 LOG.debug("Open schema file: %s" % (fn))
377 return json.load(open(fn))
378
379 @staticmethod
380 def generate_scenario(description_file):
381 """
382 Generates the test scenario list for a given description.
383
384 :param description: A dictionary with the following entries:
385 name (required) name for the api
386 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
387 url (required) the url to be appended to the catalog url with '%s'
388 for each resource mentioned
389 resources: (optional) A list of resource names such as "server",
390 "flavor", etc. with an element for each '%s' in the url. This
391 method will call self.get_resource for each element when
392 constructing the positive test case template so negative
393 subclasses are expected to return valid resource ids when
394 appropriate.
395 json-schema (optional) A valid json schema that will be used to
396 create invalid data for the api calls. For "GET" and "HEAD",
397 the data is used to generate query strings appended to the url,
398 otherwise for the body of the http call.
399 """
400 description = NegativeAutoTest.load_schema(description_file)
401 LOG.debug(description)
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100402 generator = importutils.import_class(CONF.negative.test_generator)()
403 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100404 schema = description.get("json-schema", None)
405 resources = description.get("resources", [])
406 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100407 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100408 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100409 if isinstance(resource, dict):
410 expected_result = resource['expected_result']
411 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100412 LOG.debug("Add resource to test %s" % resource)
413 scn_name = "inv_res_%s" % (resource)
414 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100415 str(uuid.uuid4())),
416 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100417 }))
418 if schema is not None:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100419 for invalid in generator.generate(schema):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100420 scenario_list.append((invalid[0],
421 {"schema": invalid[1],
422 "expected_result": invalid[2]}))
423 LOG.debug(scenario_list)
424 return scenario_list
425
426 def execute(self, description_file):
427 """
428 Execute a http call on an api that are expected to
429 result in client errors. First it uses invalid resources that are part
430 of the url, and then invalid data for queries and http request bodies.
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 """
449 description = NegativeAutoTest.load_schema(description_file)
450 LOG.info("Executing %s" % description["name"])
451 LOG.debug(description)
452 method = description["http-method"]
453 url = description["url"]
454
455 resources = [self.get_resource(r) for
456 r in description.get("resources", [])]
457
458 if hasattr(self, "resource"):
459 # Note(mkoderer): The resources list already contains an invalid
460 # entry (see get_resource).
461 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100462 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100463 schema = description.get("json-schema", None)
464 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100465 valid_schema = \
466 valid.ValidTestGenerator().generate_valid(schema)
467 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Koderer424c84f2014-02-06 17:02:19 +0100468 elif hasattr(self, "schema"):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100469 new_url, body = self._http_arguments(self.schema, url, method)
Marc Koderer424c84f2014-02-06 17:02:19 +0100470
471 resp, resp_body = self.client.send_request(method, new_url,
472 resources, body=body)
473 self._check_negative_response(resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100474
475 def _http_arguments(self, json_dict, url, method):
476 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
477 if not json_dict:
478 return url, None
479 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
480 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
481 else:
482 return url, json.dumps(json_dict)
483
484 def _check_negative_response(self, result, body):
485 expected_result = getattr(self, "expected_result", None)
486 self.assertTrue(result >= 400 and result < 500 and result != 413,
487 "Expected client error, got %s:%s" %
488 (result, body))
489 self.assertTrue(expected_result is None or expected_result == result,
490 "Expected %s, got %s:%s" %
491 (expected_result, result, body))
492
493 @classmethod
494 def set_resource(cls, name, resource):
495 """
496 This function can be used in setUpClass context to register a resoruce
497 for a test.
498
499 :param name: The name of the kind of resource such as "flavor", "role",
500 etc.
501 :resource: The id of the resource
502 """
503 cls._resources[name] = resource
504
505 def get_resource(self, name):
506 """
507 Return a valid uuid for a type of resource. If a real resource is
508 needed as part of a url then this method should return one. Otherwise
509 it can return None.
510
511 :param name: The name of the kind of resource such as "flavor", "role",
512 etc.
513 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100514 if isinstance(name, dict):
515 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100516 if hasattr(self, "resource") and self.resource[0] == name:
517 LOG.debug("Return invalid resource (%s) value: %s" %
518 (self.resource[0], self.resource[1]))
519 return self.resource[1]
520 if name in self._resources:
521 return self._resources[name]
522 return None
523
524
Sean Dague35a7caf2013-05-10 10:38:22 -0400525def call_until_true(func, duration, sleep_for):
526 """
527 Call the given function until it returns True (and return True) or
528 until the specified duration (in seconds) elapses (and return
529 False).
530
531 :param func: A zero argument callable that returns True on success.
532 :param duration: The number of seconds for which to attempt a
533 successful call of the function.
534 :param sleep_for: The number of seconds to sleep after an unsuccessful
535 invocation of the function.
536 """
537 now = time.time()
538 timeout = now + duration
539 while now < timeout:
540 if func():
541 return True
542 LOG.debug("Sleeping for %d seconds", sleep_for)
543 time.sleep(sleep_for)
544 now = time.time()
545 return False