blob: c6e3d6e77bc293ebe94b693c732b39718dc3b498 [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 Koderer24eb89c2014-01-31 11:23:33 +010030from tempest.common import generate_json
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
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040034from tempest.openstack.common import log as logging
Jay Pipes051075a2012-04-28 17:39:37 -040035
36LOG = logging.getLogger(__name__)
37
Sean Dague86bd8422013-12-20 09:56:44 -050038CONF = config.CONF
39
Samuel Merritt0d499bc2013-06-19 12:08:23 -070040# All the successful HTTP status codes from RFC 2616
41HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206)
42
Jay Pipes051075a2012-04-28 17:39:37 -040043
Chris Yeoh55530bb2013-02-08 16:04:27 +103044def attr(*args, **kwargs):
Matthew Treinisha74f5d42014-02-07 20:25:44 -050045 """A decorator which applies the testtools attr decorator
Chris Yeoh55530bb2013-02-08 16:04:27 +103046
Matthew Treinisha74f5d42014-02-07 20:25:44 -050047 This decorator applies the testtools.testcase.attr if it is in the list of
48 attributes to testtools we want to apply.
Attila Fazekasb2902af2013-02-16 16:22:44 +010049 """
Chris Yeoh55530bb2013-02-08 16:04:27 +103050
51 def decorator(f):
Giulio Fidente4946a052013-05-14 12:23:51 +020052 if 'type' in kwargs and isinstance(kwargs['type'], str):
53 f = testtools.testcase.attr(kwargs['type'])(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093054 if kwargs['type'] == 'smoke':
55 f = testtools.testcase.attr('gate')(f)
Giulio Fidente4946a052013-05-14 12:23:51 +020056 elif 'type' in kwargs and isinstance(kwargs['type'], list):
57 for attr in kwargs['type']:
58 f = testtools.testcase.attr(attr)(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093059 if attr == 'smoke':
60 f = testtools.testcase.attr('gate')(f)
Matthew Treinisha74f5d42014-02-07 20:25:44 -050061 return f
Chris Yeoh55530bb2013-02-08 16:04:27 +103062
63 return decorator
64
65
Matthew Treinish16c43792013-09-09 19:55:23 +000066def services(*args, **kwargs):
67 """A decorator used to set an attr for each service used in a test case
68
69 This decorator applies a testtools attr for each service that gets
70 exercised by a test case.
71 """
Matthew Treinish8afbffd2014-01-21 23:56:13 +000072 service_list = {
73 'compute': CONF.service_available.nova,
74 'image': CONF.service_available.glance,
75 'volume': CONF.service_available.cinder,
76 'orchestration': CONF.service_available.heat,
77 # NOTE(mtreinish) nova-network will provide networking functionality
78 # if neutron isn't available, so always set to True.
79 'network': True,
80 'identity': True,
81 'object_storage': CONF.service_available.swift,
82 'dashboard': CONF.service_available.horizon,
83 }
Matthew Treinish16c43792013-09-09 19:55:23 +000084
85 def decorator(f):
86 for service in args:
Matthew Treinish8afbffd2014-01-21 23:56:13 +000087 if service not in service_list:
Matthew Treinish16c43792013-09-09 19:55:23 +000088 raise exceptions.InvalidServiceTag('%s is not a valid service'
89 % service)
90 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +000091
92 @functools.wraps(f)
93 def wrapper(self, *func_args, **func_kwargs):
94 for service in args:
95 if not service_list[service]:
96 msg = 'Skipped because the %s service is not available' % (
97 service)
98 raise testtools.TestCase.skipException(msg)
99 return f(self, *func_args, **func_kwargs)
100 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000101 return decorator
102
103
Marc Koderer32221b8e2013-08-23 13:57:50 +0200104def stresstest(*args, **kwargs):
105 """Add stress test decorator
106
107 For all functions with this decorator a attr stress will be
108 set automatically.
109
110 @param class_setup_per: allowed values are application, process, action
111 ``application``: once in the stress job lifetime
112 ``process``: once in the worker process lifetime
113 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200114 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200115 """
116 def decorator(f):
117 if 'class_setup_per' in kwargs:
118 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
119 else:
120 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200121 if 'allow_inheritance' in kwargs:
122 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
123 else:
124 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200125 attr(type='stress')(f)
126 return f
127 return decorator
128
129
Giulio Fidente83181a92013-10-01 06:02:24 +0200130def skip_because(*args, **kwargs):
131 """A decorator useful to skip tests hitting known bugs
132
133 @param bug: bug number causing the test to skip
134 @param condition: optional condition to be True for the skip to have place
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900135 @param interface: skip the test if it is the same as self._interface
Giulio Fidente83181a92013-10-01 06:02:24 +0200136 """
137 def decorator(f):
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900138 @functools.wraps(f)
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900139 def wrapper(self, *func_args, **func_kwargs):
140 skip = False
141 if "condition" in kwargs:
142 if kwargs["condition"] is True:
143 skip = True
144 elif "interface" in kwargs:
145 if kwargs["interface"] == self._interface:
146 skip = True
147 else:
148 skip = True
149 if "bug" in kwargs and skip is True:
Matthew Treinishede50272014-02-11 19:56:11 +0000150 if not kwargs['bug'].isdigit():
151 raise ValueError('bug must be a valid bug number')
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900152 msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
153 raise testtools.TestCase.skipException(msg)
154 return f(self, *func_args, **func_kwargs)
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900155 return wrapper
Giulio Fidente83181a92013-10-01 06:02:24 +0200156 return decorator
157
158
Matthew Treinishe3d26142013-11-26 19:14:58 +0000159def requires_ext(*args, **kwargs):
160 """A decorator to skip tests if an extension is not enabled
161
162 @param extension
163 @param service
164 """
165 def decorator(func):
166 @functools.wraps(func)
167 def wrapper(*func_args, **func_kwargs):
168 if not is_extension_enabled(kwargs['extension'],
169 kwargs['service']):
170 msg = "Skipped because %s extension: %s is not enabled" % (
171 kwargs['service'], kwargs['extension'])
172 raise testtools.TestCase.skipException(msg)
173 return func(*func_args, **func_kwargs)
174 return wrapper
175 return decorator
176
177
178def is_extension_enabled(extension_name, service):
179 """A function that will check the list of enabled extensions from config
180
181 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000182 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000183 'compute': CONF.compute_feature_enabled.api_extensions,
184 'compute_v3': CONF.compute_feature_enabled.api_v3_extensions,
185 'volume': CONF.volume_feature_enabled.api_extensions,
186 'network': CONF.network_feature_enabled.api_extensions,
187 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000188 }
189 if config_dict[service][0] == 'all':
190 return True
191 if extension_name in config_dict[service]:
192 return True
193 return False
194
Ian Wienand98c35f32013-07-23 20:34:23 +1000195
Attila Fazekasf86fa312013-07-30 19:56:39 +0200196at_exit_set = set()
197
198
199def validate_tearDownClass():
200 if at_exit_set:
Vladislav Kuzmin0df88bb2014-01-28 14:58:48 +0400201 raise RuntimeError("tearDownClass does not call the super's "
Attila Fazekasf86fa312013-07-30 19:56:39 +0200202 "tearDownClass in these classes: "
Attila Fazekasd5d43b82013-10-09 16:02:19 +0200203 + str(at_exit_set) + "\n"
204 "If you see the exception, with another "
Vladislav Kuzmin0df88bb2014-01-28 14:58:48 +0400205 "exception please do not report this one! "
206 "If you are changing tempest code, make sure you "
Attila Fazekasd5d43b82013-10-09 16:02:19 +0200207 "are calling the super class's tearDownClass!")
Attila Fazekasf86fa312013-07-30 19:56:39 +0200208
209atexit.register(validate_tearDownClass)
210
Attila Fazekas53943322014-02-10 16:07:34 +0100211if sys.version_info >= (2, 7):
212 class BaseDeps(testtools.TestCase,
Attila Fazekasdc216422013-01-29 15:12:14 +0100213 testtools.testcase.WithAttributes,
214 testresources.ResourcedTestCase):
Attila Fazekas53943322014-02-10 16:07:34 +0100215 pass
216else:
217 # Define asserts for py26
218 import unittest2
219
220 class BaseDeps(testtools.TestCase,
221 testtools.testcase.WithAttributes,
222 testresources.ResourcedTestCase,
223 unittest2.TestCase):
224 pass
225
226
227class BaseTestCase(BaseDeps):
Attila Fazekasc43fec82013-04-09 23:17:52 +0200228
Attila Fazekasf86fa312013-07-30 19:56:39 +0200229 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100230 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200231
Matthew Treinish9f756a02014-01-15 10:26:07 -0500232 network_resources = {}
233
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200234 @classmethod
235 def setUpClass(cls):
236 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
237 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200238 cls.setUpClassCalled = True
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200239
Attila Fazekasf86fa312013-07-30 19:56:39 +0200240 @classmethod
241 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200242 at_exit_set.discard(cls)
Attila Fazekasf86fa312013-07-30 19:56:39 +0200243 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
244 super(BaseTestCase, cls).tearDownClass()
245
246 def setUp(self):
247 super(BaseTestCase, self).setUp()
248 if not self.setUpClassCalled:
249 raise RuntimeError("setUpClass does not calls the super's"
250 "setUpClass in the "
251 + self.__class__.__name__)
252 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400253 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
254 try:
255 test_timeout = int(test_timeout)
256 except ValueError:
257 test_timeout = 0
258 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200259 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400260
261 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
262 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200263 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
264 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400265 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
266 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200267 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
268 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200269 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
270 os.environ.get('OS_LOG_CAPTURE') != '0'):
271 log_format = '%(asctime)-15s %(message)s'
272 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Attila Fazekas90445be2013-10-24 16:46:03 +0200273 format=log_format,
274 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400275
Matthew Treinish3e046852013-07-23 16:00:24 -0400276 @classmethod
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000277 def get_client_manager(cls, interface=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700278 """
tanlin4956a642014-02-13 16:52:11 +0800279 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700280 """
Matthew Treinish9f756a02014-01-15 10:26:07 -0500281 cls.isolated_creds = isolated_creds.IsolatedCreds(
282 cls.__name__, network_resources=cls.network_resources)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700283
284 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000285 if (CONF.compute.allow_tenant_isolation or
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700286 force_tenant_isolation):
287 creds = cls.isolated_creds.get_primary_creds()
288 username, tenant_name, password = creds
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000289 if getattr(cls, '_interface', None):
290 os = clients.Manager(username=username,
291 password=password,
292 tenant_name=tenant_name,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100293 interface=cls._interface,
294 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000295 elif interface:
296 os = clients.Manager(username=username,
297 password=password,
298 tenant_name=tenant_name,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100299 interface=interface,
300 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000301 else:
302 os = clients.Manager(username=username,
303 password=password,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100304 tenant_name=tenant_name,
305 service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700306 else:
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000307 if getattr(cls, '_interface', None):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100308 os = clients.Manager(interface=cls._interface,
309 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000310 elif interface:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100311 os = clients.Manager(interface=interface, service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000312 else:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100313 os = clients.Manager(service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700314 return os
315
316 @classmethod
317 def clear_isolated_creds(cls):
318 """
319 Clears isolated creds if set
320 """
321 if getattr(cls, 'isolated_creds'):
322 cls.isolated_creds.clear_isolated_creds()
323
324 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400325 def _get_identity_admin_client(cls):
326 """
327 Returns an instance of the Identity Admin API client
328 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100329 os = clients.AdminManager(interface=cls._interface,
330 service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400331 admin_client = os.identity_client
332 return admin_client
333
334 @classmethod
Matthew Treinish9f756a02014-01-15 10:26:07 -0500335 def set_network_resources(self, network=False, router=False, subnet=False,
336 dhcp=False):
337 """Specify which network resources should be created
338
339 @param network
340 @param router
341 @param subnet
342 @param dhcp
343 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000344 # network resources should be set only once from callers
345 # in order to ensure that even if it's called multiple times in
346 # a chain of overloaded methods, the attribute is set only
347 # in the leaf class
348 if not self.network_resources:
349 self.network_resources = {
350 'network': network,
351 'router': router,
352 'subnet': subnet,
353 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500354
Attila Fazekasdc216422013-01-29 15:12:14 +0100355
Marc Koderer24eb89c2014-01-31 11:23:33 +0100356class NegativeAutoTest(BaseTestCase):
357
358 _resources = {}
359
360 @classmethod
361 def setUpClass(cls):
362 super(NegativeAutoTest, cls).setUpClass()
363 os = cls.get_client_manager()
364 cls.client = os.negative_client
365
366 @staticmethod
367 def load_schema(file):
368 """
369 Loads a schema from a file on a specified location.
370
371 :param file: the file name
372 """
373 #NOTE(mkoderer): must be extended for xml support
374 fn = os.path.join(
375 os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
376 "etc", "schemas", file)
377 LOG.debug("Open schema file: %s" % (fn))
378 return json.load(open(fn))
379
380 @staticmethod
381 def generate_scenario(description_file):
382 """
383 Generates the test scenario list for a given description.
384
385 :param description: A dictionary with the following entries:
386 name (required) name for the api
387 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
388 url (required) the url to be appended to the catalog url with '%s'
389 for each resource mentioned
390 resources: (optional) A list of resource names such as "server",
391 "flavor", etc. with an element for each '%s' in the url. This
392 method will call self.get_resource for each element when
393 constructing the positive test case template so negative
394 subclasses are expected to return valid resource ids when
395 appropriate.
396 json-schema (optional) A valid json schema that will be used to
397 create invalid data for the api calls. For "GET" and "HEAD",
398 the data is used to generate query strings appended to the url,
399 otherwise for the body of the http call.
400 """
401 description = NegativeAutoTest.load_schema(description_file)
402 LOG.debug(description)
403 generate_json.validate_negative_test_schema(description)
404 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:
419 for invalid in generate_json.generate_invalid(schema):
420 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
462 valid = None
463 schema = description.get("json-schema", None)
464 if schema:
465 valid = generate_json.generate_valid(schema)
466 new_url, body = self._http_arguments(valid, url, method)
Marc Koderer424c84f2014-02-06 17:02:19 +0100467 elif hasattr(self, "schema"):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100468 new_url, body = self._http_arguments(self.schema, url, method)
Marc Koderer424c84f2014-02-06 17:02:19 +0100469
470 resp, resp_body = self.client.send_request(method, new_url,
471 resources, body=body)
472 self._check_negative_response(resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100473
474 def _http_arguments(self, json_dict, url, method):
475 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
476 if not json_dict:
477 return url, None
478 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
479 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
480 else:
481 return url, json.dumps(json_dict)
482
483 def _check_negative_response(self, result, body):
484 expected_result = getattr(self, "expected_result", None)
485 self.assertTrue(result >= 400 and result < 500 and result != 413,
486 "Expected client error, got %s:%s" %
487 (result, body))
488 self.assertTrue(expected_result is None or expected_result == result,
489 "Expected %s, got %s:%s" %
490 (expected_result, result, body))
491
492 @classmethod
493 def set_resource(cls, name, resource):
494 """
495 This function can be used in setUpClass context to register a resoruce
496 for a test.
497
498 :param name: The name of the kind of resource such as "flavor", "role",
499 etc.
500 :resource: The id of the resource
501 """
502 cls._resources[name] = resource
503
504 def get_resource(self, name):
505 """
506 Return a valid uuid for a type of resource. If a real resource is
507 needed as part of a url then this method should return one. Otherwise
508 it can return None.
509
510 :param name: The name of the kind of resource such as "flavor", "role",
511 etc.
512 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100513 if isinstance(name, dict):
514 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100515 if hasattr(self, "resource") and self.resource[0] == name:
516 LOG.debug("Return invalid resource (%s) value: %s" %
517 (self.resource[0], self.resource[1]))
518 return self.resource[1]
519 if name in self._resources:
520 return self._resources[name]
521 return None
522
523
Sean Dague35a7caf2013-05-10 10:38:22 -0400524def call_until_true(func, duration, sleep_for):
525 """
526 Call the given function until it returns True (and return True) or
527 until the specified duration (in seconds) elapses (and return
528 False).
529
530 :param func: A zero argument callable that returns True on success.
531 :param duration: The number of seconds for which to attempt a
532 successful call of the function.
533 :param sleep_for: The number of seconds to sleep after an unsuccessful
534 invocation of the function.
535 """
536 now = time.time()
537 timeout = now + duration
538 while now < timeout:
539 if func():
540 return True
541 LOG.debug("Sleeping for %d seconds", sleep_for)
542 time.sleep(sleep_for)
543 now = time.time()
544 return False