blob: 804f17f9dad7c6c6f42ef5f4a54c5ad59f8283f5 [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
Marc Koderer9698a4f2014-03-13 08:37:39 +010029from oslo.config import cfg
30
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
Matthew Treinish16c43792013-09-09 19:55:23 +000069def services(*args, **kwargs):
70 """A decorator used to set an attr for each service used in a test case
71
72 This decorator applies a testtools attr for each service that gets
73 exercised by a test case.
74 """
Matthew Treinish8afbffd2014-01-21 23:56:13 +000075 service_list = {
76 'compute': CONF.service_available.nova,
77 'image': CONF.service_available.glance,
78 'volume': CONF.service_available.cinder,
79 'orchestration': CONF.service_available.heat,
80 # NOTE(mtreinish) nova-network will provide networking functionality
81 # if neutron isn't available, so always set to True.
82 'network': True,
83 'identity': True,
84 'object_storage': CONF.service_available.swift,
85 'dashboard': CONF.service_available.horizon,
86 }
Matthew Treinish16c43792013-09-09 19:55:23 +000087
88 def decorator(f):
89 for service in args:
Matthew Treinish8afbffd2014-01-21 23:56:13 +000090 if service not in service_list:
Matthew Treinish16c43792013-09-09 19:55:23 +000091 raise exceptions.InvalidServiceTag('%s is not a valid service'
92 % service)
93 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +000094
95 @functools.wraps(f)
96 def wrapper(self, *func_args, **func_kwargs):
97 for service in args:
98 if not service_list[service]:
99 msg = 'Skipped because the %s service is not available' % (
100 service)
101 raise testtools.TestCase.skipException(msg)
102 return f(self, *func_args, **func_kwargs)
103 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000104 return decorator
105
106
Marc Koderer32221b8e2013-08-23 13:57:50 +0200107def stresstest(*args, **kwargs):
108 """Add stress test decorator
109
110 For all functions with this decorator a attr stress will be
111 set automatically.
112
113 @param class_setup_per: allowed values are application, process, action
114 ``application``: once in the stress job lifetime
115 ``process``: once in the worker process lifetime
116 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200117 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200118 """
119 def decorator(f):
120 if 'class_setup_per' in kwargs:
121 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
122 else:
123 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200124 if 'allow_inheritance' in kwargs:
125 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
126 else:
127 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200128 attr(type='stress')(f)
129 return f
130 return decorator
131
132
Giulio Fidente83181a92013-10-01 06:02:24 +0200133def skip_because(*args, **kwargs):
134 """A decorator useful to skip tests hitting known bugs
135
136 @param bug: bug number causing the test to skip
137 @param condition: optional condition to be True for the skip to have place
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900138 @param interface: skip the test if it is the same as self._interface
Giulio Fidente83181a92013-10-01 06:02:24 +0200139 """
140 def decorator(f):
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900141 @functools.wraps(f)
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900142 def wrapper(self, *func_args, **func_kwargs):
143 skip = False
144 if "condition" in kwargs:
145 if kwargs["condition"] is True:
146 skip = True
147 elif "interface" in kwargs:
148 if kwargs["interface"] == self._interface:
149 skip = True
150 else:
151 skip = True
152 if "bug" in kwargs and skip is True:
Matthew Treinishede50272014-02-11 19:56:11 +0000153 if not kwargs['bug'].isdigit():
154 raise ValueError('bug must be a valid bug number')
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900155 msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
156 raise testtools.TestCase.skipException(msg)
157 return f(self, *func_args, **func_kwargs)
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900158 return wrapper
Giulio Fidente83181a92013-10-01 06:02:24 +0200159 return decorator
160
161
Matthew Treinishe3d26142013-11-26 19:14:58 +0000162def requires_ext(*args, **kwargs):
163 """A decorator to skip tests if an extension is not enabled
164
165 @param extension
166 @param service
167 """
168 def decorator(func):
169 @functools.wraps(func)
170 def wrapper(*func_args, **func_kwargs):
171 if not is_extension_enabled(kwargs['extension'],
172 kwargs['service']):
173 msg = "Skipped because %s extension: %s is not enabled" % (
174 kwargs['service'], kwargs['extension'])
175 raise testtools.TestCase.skipException(msg)
176 return func(*func_args, **func_kwargs)
177 return wrapper
178 return decorator
179
180
181def is_extension_enabled(extension_name, service):
182 """A function that will check the list of enabled extensions from config
183
184 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000185 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000186 'compute': CONF.compute_feature_enabled.api_extensions,
187 'compute_v3': CONF.compute_feature_enabled.api_v3_extensions,
188 'volume': CONF.volume_feature_enabled.api_extensions,
189 'network': CONF.network_feature_enabled.api_extensions,
190 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000191 }
192 if config_dict[service][0] == 'all':
193 return True
194 if extension_name in config_dict[service]:
195 return True
196 return False
197
Ian Wienand98c35f32013-07-23 20:34:23 +1000198
Attila Fazekasf86fa312013-07-30 19:56:39 +0200199at_exit_set = set()
200
201
202def validate_tearDownClass():
203 if at_exit_set:
Vladislav Kuzmin0df88bb2014-01-28 14:58:48 +0400204 raise RuntimeError("tearDownClass does not call the super's "
Attila Fazekasf86fa312013-07-30 19:56:39 +0200205 "tearDownClass in these classes: "
Attila Fazekasd5d43b82013-10-09 16:02:19 +0200206 + str(at_exit_set) + "\n"
207 "If you see the exception, with another "
Vladislav Kuzmin0df88bb2014-01-28 14:58:48 +0400208 "exception please do not report this one! "
209 "If you are changing tempest code, make sure you "
Attila Fazekasd5d43b82013-10-09 16:02:19 +0200210 "are calling the super class's tearDownClass!")
Attila Fazekasf86fa312013-07-30 19:56:39 +0200211
212atexit.register(validate_tearDownClass)
213
Attila Fazekas53943322014-02-10 16:07:34 +0100214if sys.version_info >= (2, 7):
215 class BaseDeps(testtools.TestCase,
Attila Fazekasdc216422013-01-29 15:12:14 +0100216 testtools.testcase.WithAttributes,
217 testresources.ResourcedTestCase):
Attila Fazekas53943322014-02-10 16:07:34 +0100218 pass
219else:
220 # Define asserts for py26
221 import unittest2
222
223 class BaseDeps(testtools.TestCase,
224 testtools.testcase.WithAttributes,
225 testresources.ResourcedTestCase,
226 unittest2.TestCase):
227 pass
228
229
230class BaseTestCase(BaseDeps):
Attila Fazekasc43fec82013-04-09 23:17:52 +0200231
Attila Fazekasf86fa312013-07-30 19:56:39 +0200232 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100233 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200234
Matthew Treinish9f756a02014-01-15 10:26:07 -0500235 network_resources = {}
236
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200237 @classmethod
238 def setUpClass(cls):
239 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
240 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200241 cls.setUpClassCalled = True
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200242
Attila Fazekasf86fa312013-07-30 19:56:39 +0200243 @classmethod
244 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200245 at_exit_set.discard(cls)
Attila Fazekasf86fa312013-07-30 19:56:39 +0200246 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
247 super(BaseTestCase, cls).tearDownClass()
248
249 def setUp(self):
250 super(BaseTestCase, self).setUp()
251 if not self.setUpClassCalled:
252 raise RuntimeError("setUpClass does not calls the super's"
253 "setUpClass in the "
254 + self.__class__.__name__)
255 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400256 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
257 try:
258 test_timeout = int(test_timeout)
259 except ValueError:
260 test_timeout = 0
261 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200262 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400263
264 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
265 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200266 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
267 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400268 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
269 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200270 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
271 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200272 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
273 os.environ.get('OS_LOG_CAPTURE') != '0'):
274 log_format = '%(asctime)-15s %(message)s'
275 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Attila Fazekas90445be2013-10-24 16:46:03 +0200276 format=log_format,
277 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400278
Matthew Treinish3e046852013-07-23 16:00:24 -0400279 @classmethod
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000280 def get_client_manager(cls, interface=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700281 """
tanlin4956a642014-02-13 16:52:11 +0800282 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700283 """
Matthew Treinish9f756a02014-01-15 10:26:07 -0500284 cls.isolated_creds = isolated_creds.IsolatedCreds(
285 cls.__name__, network_resources=cls.network_resources)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700286
287 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000288 if (CONF.compute.allow_tenant_isolation or
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700289 force_tenant_isolation):
290 creds = cls.isolated_creds.get_primary_creds()
291 username, tenant_name, password = creds
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000292 if getattr(cls, '_interface', None):
293 os = clients.Manager(username=username,
294 password=password,
295 tenant_name=tenant_name,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100296 interface=cls._interface,
297 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000298 elif interface:
299 os = clients.Manager(username=username,
300 password=password,
301 tenant_name=tenant_name,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100302 interface=interface,
303 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000304 else:
305 os = clients.Manager(username=username,
306 password=password,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100307 tenant_name=tenant_name,
308 service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700309 else:
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000310 if getattr(cls, '_interface', None):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100311 os = clients.Manager(interface=cls._interface,
312 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000313 elif interface:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100314 os = clients.Manager(interface=interface, service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000315 else:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100316 os = clients.Manager(service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700317 return os
318
319 @classmethod
320 def clear_isolated_creds(cls):
321 """
322 Clears isolated creds if set
323 """
324 if getattr(cls, 'isolated_creds'):
325 cls.isolated_creds.clear_isolated_creds()
326
327 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400328 def _get_identity_admin_client(cls):
329 """
330 Returns an instance of the Identity Admin API client
331 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100332 os = clients.AdminManager(interface=cls._interface,
333 service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400334 admin_client = os.identity_client
335 return admin_client
336
337 @classmethod
Matthew Treinish9f756a02014-01-15 10:26:07 -0500338 def set_network_resources(self, network=False, router=False, subnet=False,
339 dhcp=False):
340 """Specify which network resources should be created
341
342 @param network
343 @param router
344 @param subnet
345 @param dhcp
346 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000347 # network resources should be set only once from callers
348 # in order to ensure that even if it's called multiple times in
349 # a chain of overloaded methods, the attribute is set only
350 # in the leaf class
351 if not self.network_resources:
352 self.network_resources = {
353 'network': network,
354 'router': router,
355 'subnet': subnet,
356 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500357
Mark Maglana5885eb32014-02-28 10:57:34 -0800358 def assertEmpty(self, list, msg=None):
359 self.assertTrue(len(list) == 0, msg)
360
361 def assertNotEmpty(self, list, msg=None):
362 self.assertTrue(len(list) > 0, msg)
363
Attila Fazekasdc216422013-01-29 15:12:14 +0100364
Marc Koderer24eb89c2014-01-31 11:23:33 +0100365class NegativeAutoTest(BaseTestCase):
366
367 _resources = {}
368
369 @classmethod
370 def setUpClass(cls):
371 super(NegativeAutoTest, cls).setUpClass()
372 os = cls.get_client_manager()
373 cls.client = os.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100374 os_admin = clients.AdminManager(interface=cls._interface,
375 service=cls._service)
376 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100377
378 @staticmethod
379 def load_schema(file):
380 """
381 Loads a schema from a file on a specified location.
382
383 :param file: the file name
384 """
385 #NOTE(mkoderer): must be extended for xml support
386 fn = os.path.join(
387 os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
388 "etc", "schemas", file)
389 LOG.debug("Open schema file: %s" % (fn))
390 return json.load(open(fn))
391
392 @staticmethod
393 def generate_scenario(description_file):
394 """
395 Generates the test scenario list for a given description.
396
397 :param description: A dictionary with the following entries:
398 name (required) name for the api
399 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
400 url (required) the url to be appended to the catalog url with '%s'
401 for each resource mentioned
402 resources: (optional) A list of resource names such as "server",
403 "flavor", etc. with an element for each '%s' in the url. This
404 method will call self.get_resource for each element when
405 constructing the positive test case template so negative
406 subclasses are expected to return valid resource ids when
407 appropriate.
408 json-schema (optional) A valid json schema that will be used to
409 create invalid data for the api calls. For "GET" and "HEAD",
410 the data is used to generate query strings appended to the url,
411 otherwise for the body of the http call.
412 """
413 description = NegativeAutoTest.load_schema(description_file)
414 LOG.debug(description)
Marc Koderer9698a4f2014-03-13 08:37:39 +0100415
416 # NOTE(mkoderer): since this will be executed on import level the
417 # config doesn't have to be in place (e.g. for the pep8 job).
418 # In this case simply return.
419 try:
420 generator = importutils.import_class(
421 CONF.negative.test_generator)()
422 except cfg.ConfigFilesNotFoundError:
423 LOG.critical(
424 "Tempest config not found. Test scenarios aren't created")
425 return
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100426 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100427 schema = description.get("json-schema", None)
428 resources = description.get("resources", [])
429 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100430 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100431 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100432 if isinstance(resource, dict):
433 expected_result = resource['expected_result']
434 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100435 LOG.debug("Add resource to test %s" % resource)
436 scn_name = "inv_res_%s" % (resource)
437 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100438 str(uuid.uuid4())),
439 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100440 }))
441 if schema is not None:
Marc Kodererf857fda2014-03-05 15:58:00 +0100442 for name, schema, expected_result in generator.generate(schema):
443 if (expected_result is None and
444 "default_result_code" in description):
445 expected_result = description["default_result_code"]
446 scenario_list.append((name,
447 {"schema": schema,
448 "expected_result": expected_result}))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100449 LOG.debug(scenario_list)
450 return scenario_list
451
452 def execute(self, description_file):
453 """
454 Execute a http call on an api that are expected to
455 result in client errors. First it uses invalid resources that are part
456 of the url, and then invalid data for queries and http request bodies.
457
458 :param description: A dictionary with the following entries:
459 name (required) name for the api
460 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
461 url (required) the url to be appended to the catalog url with '%s'
462 for each resource mentioned
463 resources: (optional) A list of resource names such as "server",
464 "flavor", etc. with an element for each '%s' in the url. This
465 method will call self.get_resource for each element when
466 constructing the positive test case template so negative
467 subclasses are expected to return valid resource ids when
468 appropriate.
469 json-schema (optional) A valid json schema that will be used to
470 create invalid data for the api calls. For "GET" and "HEAD",
471 the data is used to generate query strings appended to the url,
472 otherwise for the body of the http call.
473
474 """
475 description = NegativeAutoTest.load_schema(description_file)
476 LOG.info("Executing %s" % description["name"])
477 LOG.debug(description)
478 method = description["http-method"]
479 url = description["url"]
480
481 resources = [self.get_resource(r) for
482 r in description.get("resources", [])]
483
484 if hasattr(self, "resource"):
485 # Note(mkoderer): The resources list already contains an invalid
486 # entry (see get_resource).
487 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100488 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100489 schema = description.get("json-schema", None)
490 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100491 valid_schema = \
492 valid.ValidTestGenerator().generate_valid(schema)
493 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Koderer424c84f2014-02-06 17:02:19 +0100494 elif hasattr(self, "schema"):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100495 new_url, body = self._http_arguments(self.schema, url, method)
Marc Koderer424c84f2014-02-06 17:02:19 +0100496
Marc Kodererf857fda2014-03-05 15:58:00 +0100497 if "admin_client" in description and description["admin_client"]:
498 client = self.admin_client
499 else:
500 client = self.client
501 resp, resp_body = client.send_request(method, new_url,
502 resources, body=body)
Marc Koderer424c84f2014-02-06 17:02:19 +0100503 self._check_negative_response(resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100504
505 def _http_arguments(self, json_dict, url, method):
506 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
507 if not json_dict:
508 return url, None
509 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
510 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
511 else:
512 return url, json.dumps(json_dict)
513
514 def _check_negative_response(self, result, body):
515 expected_result = getattr(self, "expected_result", None)
516 self.assertTrue(result >= 400 and result < 500 and result != 413,
517 "Expected client error, got %s:%s" %
518 (result, body))
519 self.assertTrue(expected_result is None or expected_result == result,
520 "Expected %s, got %s:%s" %
521 (expected_result, result, body))
522
523 @classmethod
524 def set_resource(cls, name, resource):
525 """
526 This function can be used in setUpClass context to register a resoruce
527 for a test.
528
529 :param name: The name of the kind of resource such as "flavor", "role",
530 etc.
531 :resource: The id of the resource
532 """
533 cls._resources[name] = resource
534
535 def get_resource(self, name):
536 """
537 Return a valid uuid for a type of resource. If a real resource is
538 needed as part of a url then this method should return one. Otherwise
539 it can return None.
540
541 :param name: The name of the kind of resource such as "flavor", "role",
542 etc.
543 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100544 if isinstance(name, dict):
545 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100546 if hasattr(self, "resource") and self.resource[0] == name:
547 LOG.debug("Return invalid resource (%s) value: %s" %
548 (self.resource[0], self.resource[1]))
549 return self.resource[1]
550 if name in self._resources:
551 return self._resources[name]
552 return None
553
554
Sean Dague35a7caf2013-05-10 10:38:22 -0400555def call_until_true(func, duration, sleep_for):
556 """
557 Call the given function until it returns True (and return True) or
558 until the specified duration (in seconds) elapses (and return
559 False).
560
561 :param func: A zero argument callable that returns True on success.
562 :param duration: The number of seconds for which to attempt a
563 successful call of the function.
564 :param sleep_for: The number of seconds to sleep after an unsuccessful
565 invocation of the function.
566 """
567 now = time.time()
568 timeout = now + duration
569 while now < timeout:
570 if func():
571 return True
572 LOG.debug("Sleeping for %d seconds", sleep_for)
573 time.sleep(sleep_for)
574 now = time.time()
575 return False