blob: afe7a96ada8ee49e8d0082cee436a20527678b1a [file] [log] [blame]
ZhiQiang Fan39f97222013-09-20 04:49:44 +08001# Copyright 2012 OpenStack Foundation
Jay Pipes051075a2012-04-28 17:39:37 -04002# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
Attila Fazekasf86fa312013-07-30 19:56:39 +020016import atexit
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +090017import functools
Marc Koderer24eb89c2014-01-31 11:23:33 +010018import json
Ian Wienand98c35f32013-07-23 20:34:23 +100019import os
Marc Kodererb2978da2014-03-26 13:45:43 +010020import re
Attila Fazekas53943322014-02-10 16:07:34 +010021import sys
Jay Pipes051075a2012-04-28 17:39:37 -040022import time
Marc Koderer24eb89c2014-01-31 11:23:33 +010023import urllib
24import uuid
Jay Pipes051075a2012-04-28 17:39:37 -040025
Matthew Treinish78561ad2013-07-26 11:41:56 -040026import fixtures
Attila Fazekasdc216422013-01-29 15:12:14 +010027import testresources
Marc Koderer674c8fc2014-03-17 09:45:04 +010028import testscenarios
ivan-zhu1feeb382013-01-24 10:14:39 +080029import testtools
Jay Pipes051075a2012-04-28 17:39:37 -040030
Matthew Treinish3e046852013-07-23 16:00:24 -040031from tempest import clients
Marc Koderer6ee82dc2014-02-17 10:26:29 +010032import tempest.common.generator.valid_generator as valid
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -070033from tempest.common import isolated_creds
Attila Fazekasdc216422013-01-29 15:12:14 +010034from tempest import config
Matthew Treinish16c43792013-09-09 19:55:23 +000035from tempest import exceptions
Marc Koderer6ee82dc2014-02-17 10:26:29 +010036from tempest.openstack.common import importutils
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040037from tempest.openstack.common import log as logging
Jay Pipes051075a2012-04-28 17:39:37 -040038
39LOG = logging.getLogger(__name__)
40
Sean Dague86bd8422013-12-20 09:56:44 -050041CONF = config.CONF
42
Samuel Merritt0d499bc2013-06-19 12:08:23 -070043# All the successful HTTP status codes from RFC 2616
44HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206)
45
Jay Pipes051075a2012-04-28 17:39:37 -040046
Chris Yeoh55530bb2013-02-08 16:04:27 +103047def attr(*args, **kwargs):
Matthew Treinisha74f5d42014-02-07 20:25:44 -050048 """A decorator which applies the testtools attr decorator
Chris Yeoh55530bb2013-02-08 16:04:27 +103049
Matthew Treinisha74f5d42014-02-07 20:25:44 -050050 This decorator applies the testtools.testcase.attr if it is in the list of
51 attributes to testtools we want to apply.
Attila Fazekasb2902af2013-02-16 16:22:44 +010052 """
Chris Yeoh55530bb2013-02-08 16:04:27 +103053
54 def decorator(f):
Giulio Fidente4946a052013-05-14 12:23:51 +020055 if 'type' in kwargs and isinstance(kwargs['type'], str):
56 f = testtools.testcase.attr(kwargs['type'])(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093057 if kwargs['type'] == 'smoke':
58 f = testtools.testcase.attr('gate')(f)
Giulio Fidente4946a052013-05-14 12:23:51 +020059 elif 'type' in kwargs and isinstance(kwargs['type'], list):
60 for attr in kwargs['type']:
61 f = testtools.testcase.attr(attr)(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093062 if attr == 'smoke':
63 f = testtools.testcase.attr('gate')(f)
Matthew Treinisha74f5d42014-02-07 20:25:44 -050064 return f
Chris Yeoh55530bb2013-02-08 16:04:27 +103065
66 return decorator
67
68
Zhi Kun Liu22fb4702014-02-27 16:15:24 +080069def safe_setup(f):
70 """A decorator used to wrap the setUpClass for cleaning up resources
71 when setUpClass failed.
72 """
73
74 def decorator(cls):
75 try:
76 f(cls)
77 except Exception as se:
Attila Fazekasbfeb2822014-04-09 11:24:33 +020078 etype, value, trace = sys.exc_info()
Zhi Kun Liu22fb4702014-02-27 16:15:24 +080079 LOG.exception("setUpClass failed: %s" % se)
80 try:
81 cls.tearDownClass()
82 except Exception as te:
83 LOG.exception("tearDownClass failed: %s" % te)
Attila Fazekasbfeb2822014-04-09 11:24:33 +020084 try:
85 raise etype(value), None, trace
86 finally:
87 del trace # for avoiding circular refs
Zhi Kun Liu22fb4702014-02-27 16:15:24 +080088
89 return decorator
90
91
Matthew Treinish16c43792013-09-09 19:55:23 +000092def services(*args, **kwargs):
93 """A decorator used to set an attr for each service used in a test case
94
95 This decorator applies a testtools attr for each service that gets
96 exercised by a test case.
97 """
Matthew Treinish8afbffd2014-01-21 23:56:13 +000098 service_list = {
99 'compute': CONF.service_available.nova,
100 'image': CONF.service_available.glance,
Adam Gandelman4a48a602014-03-20 18:23:18 -0700101 'baremetal': CONF.service_available.ironic,
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000102 'volume': CONF.service_available.cinder,
103 'orchestration': CONF.service_available.heat,
104 # NOTE(mtreinish) nova-network will provide networking functionality
105 # if neutron isn't available, so always set to True.
106 'network': True,
107 'identity': True,
108 'object_storage': CONF.service_available.swift,
109 'dashboard': CONF.service_available.horizon,
Artur Svechnikovc3bf9252014-05-05 16:37:37 +0400110 'ceilometer': CONF.service_available.ceilometer,
Yaroslav Lobankovff42c872014-06-17 15:39:43 +0400111 'data_processing': CONF.service_available.sahara
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000112 }
Matthew Treinish16c43792013-09-09 19:55:23 +0000113
114 def decorator(f):
115 for service in args:
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000116 if service not in service_list:
Matthew Treinish16c43792013-09-09 19:55:23 +0000117 raise exceptions.InvalidServiceTag('%s is not a valid service'
118 % service)
119 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000120
121 @functools.wraps(f)
122 def wrapper(self, *func_args, **func_kwargs):
123 for service in args:
124 if not service_list[service]:
125 msg = 'Skipped because the %s service is not available' % (
126 service)
127 raise testtools.TestCase.skipException(msg)
128 return f(self, *func_args, **func_kwargs)
129 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000130 return decorator
131
132
Marc Koderer32221b8e2013-08-23 13:57:50 +0200133def stresstest(*args, **kwargs):
134 """Add stress test decorator
135
136 For all functions with this decorator a attr stress will be
137 set automatically.
138
139 @param class_setup_per: allowed values are application, process, action
140 ``application``: once in the stress job lifetime
141 ``process``: once in the worker process lifetime
142 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200143 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200144 """
145 def decorator(f):
146 if 'class_setup_per' in kwargs:
147 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
148 else:
149 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200150 if 'allow_inheritance' in kwargs:
151 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
152 else:
153 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200154 attr(type='stress')(f)
155 return f
156 return decorator
157
158
Giulio Fidente83181a92013-10-01 06:02:24 +0200159def skip_because(*args, **kwargs):
160 """A decorator useful to skip tests hitting known bugs
161
162 @param bug: bug number causing the test to skip
163 @param condition: optional condition to be True for the skip to have place
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900164 @param interface: skip the test if it is the same as self._interface
Giulio Fidente83181a92013-10-01 06:02:24 +0200165 """
166 def decorator(f):
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900167 @functools.wraps(f)
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900168 def wrapper(self, *func_args, **func_kwargs):
169 skip = False
170 if "condition" in kwargs:
171 if kwargs["condition"] is True:
172 skip = True
173 elif "interface" in kwargs:
174 if kwargs["interface"] == self._interface:
175 skip = True
176 else:
177 skip = True
178 if "bug" in kwargs and skip is True:
Matthew Treinishede50272014-02-11 19:56:11 +0000179 if not kwargs['bug'].isdigit():
180 raise ValueError('bug must be a valid bug number')
Ken'ichi Ohmichia1aa44c2013-12-06 20:48:24 +0900181 msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
182 raise testtools.TestCase.skipException(msg)
183 return f(self, *func_args, **func_kwargs)
Masayuki Igawa80c1b9f2013-10-07 17:19:11 +0900184 return wrapper
Giulio Fidente83181a92013-10-01 06:02:24 +0200185 return decorator
186
187
Matthew Treinishe3d26142013-11-26 19:14:58 +0000188def requires_ext(*args, **kwargs):
189 """A decorator to skip tests if an extension is not enabled
190
191 @param extension
192 @param service
193 """
194 def decorator(func):
195 @functools.wraps(func)
196 def wrapper(*func_args, **func_kwargs):
197 if not is_extension_enabled(kwargs['extension'],
198 kwargs['service']):
199 msg = "Skipped because %s extension: %s is not enabled" % (
200 kwargs['service'], kwargs['extension'])
201 raise testtools.TestCase.skipException(msg)
202 return func(*func_args, **func_kwargs)
203 return wrapper
204 return decorator
205
206
207def is_extension_enabled(extension_name, service):
208 """A function that will check the list of enabled extensions from config
209
210 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000211 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000212 'compute': CONF.compute_feature_enabled.api_extensions,
213 'compute_v3': CONF.compute_feature_enabled.api_v3_extensions,
214 'volume': CONF.volume_feature_enabled.api_extensions,
215 'network': CONF.network_feature_enabled.api_extensions,
216 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Matthew Treinishe3d26142013-11-26 19:14:58 +0000217 }
218 if config_dict[service][0] == 'all':
219 return True
220 if extension_name in config_dict[service]:
221 return True
222 return False
223
Ian Wienand98c35f32013-07-23 20:34:23 +1000224
Attila Fazekasf86fa312013-07-30 19:56:39 +0200225at_exit_set = set()
226
227
228def validate_tearDownClass():
229 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400230 LOG.error(
231 "tearDownClass does not call the super's "
232 "tearDownClass in these classes: \n"
233 + str(at_exit_set))
234
Attila Fazekasf86fa312013-07-30 19:56:39 +0200235
236atexit.register(validate_tearDownClass)
237
Attila Fazekas53943322014-02-10 16:07:34 +0100238if sys.version_info >= (2, 7):
239 class BaseDeps(testtools.TestCase,
Attila Fazekasdc216422013-01-29 15:12:14 +0100240 testtools.testcase.WithAttributes,
241 testresources.ResourcedTestCase):
Attila Fazekas53943322014-02-10 16:07:34 +0100242 pass
243else:
244 # Define asserts for py26
245 import unittest2
246
247 class BaseDeps(testtools.TestCase,
248 testtools.testcase.WithAttributes,
249 testresources.ResourcedTestCase,
250 unittest2.TestCase):
251 pass
252
253
254class BaseTestCase(BaseDeps):
Attila Fazekasc43fec82013-04-09 23:17:52 +0200255
Attila Fazekasf86fa312013-07-30 19:56:39 +0200256 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100257 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200258
Matthew Treinish9f756a02014-01-15 10:26:07 -0500259 network_resources = {}
260
Sean Dague2ef32ac2014-06-09 11:32:23 -0400261 # NOTE(sdague): log_format is defined inline here instead of using the oslo
262 # default because going through the config path recouples config to the
263 # stress tests too early, and depending on testr order will fail unit tests
264 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
265 '[%(name)s] %(message)s')
266
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200267 @classmethod
268 def setUpClass(cls):
269 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
270 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200271 cls.setUpClassCalled = True
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200272
Attila Fazekasf86fa312013-07-30 19:56:39 +0200273 @classmethod
274 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200275 at_exit_set.discard(cls)
Attila Fazekasf86fa312013-07-30 19:56:39 +0200276 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
277 super(BaseTestCase, cls).tearDownClass()
278
279 def setUp(self):
280 super(BaseTestCase, self).setUp()
281 if not self.setUpClassCalled:
282 raise RuntimeError("setUpClass does not calls the super's"
283 "setUpClass in the "
284 + self.__class__.__name__)
285 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400286 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
287 try:
288 test_timeout = int(test_timeout)
289 except ValueError:
290 test_timeout = 0
291 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200292 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400293
294 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
295 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200296 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
297 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400298 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
299 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200300 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
301 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200302 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
303 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200304 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400305 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200306 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400307
Matthew Treinish3e046852013-07-23 16:00:24 -0400308 @classmethod
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000309 def get_client_manager(cls, interface=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700310 """
tanlin4956a642014-02-13 16:52:11 +0800311 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700312 """
Matthew Treinish9f756a02014-01-15 10:26:07 -0500313 cls.isolated_creds = isolated_creds.IsolatedCreds(
314 cls.__name__, network_resources=cls.network_resources)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700315
316 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000317 if CONF.compute.allow_tenant_isolation or force_tenant_isolation:
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700318 creds = cls.isolated_creds.get_primary_creds()
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000319 if getattr(cls, '_interface', None):
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000320 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100321 interface=cls._interface,
322 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000323 elif interface:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000324 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100325 interface=interface,
326 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000327 else:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000328 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100329 service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700330 else:
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000331 if getattr(cls, '_interface', None):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100332 os = clients.Manager(interface=cls._interface,
333 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000334 elif interface:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100335 os = clients.Manager(interface=interface, service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000336 else:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100337 os = clients.Manager(service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700338 return os
339
340 @classmethod
341 def clear_isolated_creds(cls):
342 """
343 Clears isolated creds if set
344 """
345 if getattr(cls, 'isolated_creds'):
346 cls.isolated_creds.clear_isolated_creds()
347
348 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400349 def _get_identity_admin_client(cls):
350 """
351 Returns an instance of the Identity Admin API client
352 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100353 os = clients.AdminManager(interface=cls._interface,
354 service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400355 admin_client = os.identity_client
356 return admin_client
357
358 @classmethod
Matthew Treinish9f756a02014-01-15 10:26:07 -0500359 def set_network_resources(self, network=False, router=False, subnet=False,
360 dhcp=False):
361 """Specify which network resources should be created
362
363 @param network
364 @param router
365 @param subnet
366 @param dhcp
367 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000368 # network resources should be set only once from callers
369 # in order to ensure that even if it's called multiple times in
370 # a chain of overloaded methods, the attribute is set only
371 # in the leaf class
372 if not self.network_resources:
373 self.network_resources = {
374 'network': network,
375 'router': router,
376 'subnet': subnet,
377 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500378
Mark Maglana5885eb32014-02-28 10:57:34 -0800379 def assertEmpty(self, list, msg=None):
380 self.assertTrue(len(list) == 0, msg)
381
382 def assertNotEmpty(self, list, msg=None):
383 self.assertTrue(len(list) > 0, msg)
384
Attila Fazekasdc216422013-01-29 15:12:14 +0100385
Marc Koderer24eb89c2014-01-31 11:23:33 +0100386class NegativeAutoTest(BaseTestCase):
387
388 _resources = {}
389
390 @classmethod
391 def setUpClass(cls):
392 super(NegativeAutoTest, cls).setUpClass()
393 os = cls.get_client_manager()
394 cls.client = os.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100395 os_admin = clients.AdminManager(interface=cls._interface,
396 service=cls._service)
397 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100398
399 @staticmethod
400 def load_schema(file):
401 """
402 Loads a schema from a file on a specified location.
403
404 :param file: the file name
405 """
Matthew Treinish57160582014-06-09 17:13:48 -0400406 # NOTE(mkoderer): must be extended for xml support
Marc Koderer24eb89c2014-01-31 11:23:33 +0100407 fn = os.path.join(
408 os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
409 "etc", "schemas", file)
410 LOG.debug("Open schema file: %s" % (fn))
411 return json.load(open(fn))
412
413 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100414 def load_tests(*args):
415 """
416 Wrapper for testscenarios to set the mandatory scenarios variable
417 only in case a real test loader is in place. Will be automatically
418 called in case the variable "load_tests" is set.
419 """
420 if getattr(args[0], 'suiteClass', None) is not None:
421 loader, standard_tests, pattern = args
422 else:
423 standard_tests, module, loader = args
424 for test in testtools.iterate_tests(standard_tests):
425 schema_file = getattr(test, '_schema_file', None)
426 if schema_file is not None:
427 setattr(test, 'scenarios',
428 NegativeAutoTest.generate_scenario(schema_file))
429 return testscenarios.load_tests_apply_scenarios(*args)
430
431 @staticmethod
Marc Koderer24eb89c2014-01-31 11:23:33 +0100432 def generate_scenario(description_file):
433 """
434 Generates the test scenario list for a given description.
435
436 :param description: A dictionary with the following entries:
437 name (required) name for the api
438 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
439 url (required) the url to be appended to the catalog url with '%s'
440 for each resource mentioned
441 resources: (optional) A list of resource names such as "server",
442 "flavor", etc. with an element for each '%s' in the url. This
443 method will call self.get_resource for each element when
444 constructing the positive test case template so negative
445 subclasses are expected to return valid resource ids when
446 appropriate.
447 json-schema (optional) A valid json schema that will be used to
448 create invalid data for the api calls. For "GET" and "HEAD",
449 the data is used to generate query strings appended to the url,
450 otherwise for the body of the http call.
451 """
452 description = NegativeAutoTest.load_schema(description_file)
453 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100454 generator = importutils.import_class(
455 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100456 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100457 schema = description.get("json-schema", None)
458 resources = description.get("resources", [])
459 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100460 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100461 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100462 if isinstance(resource, dict):
463 expected_result = resource['expected_result']
464 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100465 LOG.debug("Add resource to test %s" % resource)
466 scn_name = "inv_res_%s" % (resource)
467 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100468 str(uuid.uuid4())),
469 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100470 }))
471 if schema is not None:
Marc Kodererf857fda2014-03-05 15:58:00 +0100472 for name, schema, expected_result in generator.generate(schema):
473 if (expected_result is None and
474 "default_result_code" in description):
475 expected_result = description["default_result_code"]
476 scenario_list.append((name,
477 {"schema": schema,
478 "expected_result": expected_result}))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100479 LOG.debug(scenario_list)
480 return scenario_list
481
482 def execute(self, description_file):
483 """
484 Execute a http call on an api that are expected to
485 result in client errors. First it uses invalid resources that are part
486 of the url, and then invalid data for queries and http request bodies.
487
488 :param description: A dictionary with the following entries:
489 name (required) name for the api
490 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
491 url (required) the url to be appended to the catalog url with '%s'
492 for each resource mentioned
493 resources: (optional) A list of resource names such as "server",
494 "flavor", etc. with an element for each '%s' in the url. This
495 method will call self.get_resource for each element when
496 constructing the positive test case template so negative
497 subclasses are expected to return valid resource ids when
498 appropriate.
499 json-schema (optional) A valid json schema that will be used to
500 create invalid data for the api calls. For "GET" and "HEAD",
501 the data is used to generate query strings appended to the url,
502 otherwise for the body of the http call.
503
504 """
505 description = NegativeAutoTest.load_schema(description_file)
506 LOG.info("Executing %s" % description["name"])
507 LOG.debug(description)
508 method = description["http-method"]
509 url = description["url"]
510
511 resources = [self.get_resource(r) for
512 r in description.get("resources", [])]
513
514 if hasattr(self, "resource"):
515 # Note(mkoderer): The resources list already contains an invalid
516 # entry (see get_resource).
517 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100518 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100519 schema = description.get("json-schema", None)
520 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100521 valid_schema = \
522 valid.ValidTestGenerator().generate_valid(schema)
523 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Koderer424c84f2014-02-06 17:02:19 +0100524 elif hasattr(self, "schema"):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100525 new_url, body = self._http_arguments(self.schema, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100526 else:
527 raise Exception("testscenarios are not active. Please make sure "
528 "that your test runner supports the load_tests "
529 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100530
Marc Kodererf857fda2014-03-05 15:58:00 +0100531 if "admin_client" in description and description["admin_client"]:
532 client = self.admin_client
533 else:
534 client = self.client
535 resp, resp_body = client.send_request(method, new_url,
536 resources, body=body)
Marc Koderer424c84f2014-02-06 17:02:19 +0100537 self._check_negative_response(resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100538
539 def _http_arguments(self, json_dict, url, method):
540 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
541 if not json_dict:
542 return url, None
543 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
544 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
545 else:
546 return url, json.dumps(json_dict)
547
548 def _check_negative_response(self, result, body):
549 expected_result = getattr(self, "expected_result", None)
550 self.assertTrue(result >= 400 and result < 500 and result != 413,
551 "Expected client error, got %s:%s" %
552 (result, body))
553 self.assertTrue(expected_result is None or expected_result == result,
554 "Expected %s, got %s:%s" %
555 (expected_result, result, body))
556
557 @classmethod
558 def set_resource(cls, name, resource):
559 """
560 This function can be used in setUpClass context to register a resoruce
561 for a test.
562
563 :param name: The name of the kind of resource such as "flavor", "role",
564 etc.
565 :resource: The id of the resource
566 """
567 cls._resources[name] = resource
568
569 def get_resource(self, name):
570 """
571 Return a valid uuid for a type of resource. If a real resource is
572 needed as part of a url then this method should return one. Otherwise
573 it can return None.
574
575 :param name: The name of the kind of resource such as "flavor", "role",
576 etc.
577 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100578 if isinstance(name, dict):
579 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100580 if hasattr(self, "resource") and self.resource[0] == name:
581 LOG.debug("Return invalid resource (%s) value: %s" %
582 (self.resource[0], self.resource[1]))
583 return self.resource[1]
584 if name in self._resources:
585 return self._resources[name]
586 return None
587
588
Marc Kodererb2978da2014-03-26 13:45:43 +0100589def SimpleNegativeAutoTest(klass):
590 """
591 This decorator registers a test function on basis of the class name.
592 """
593 @attr(type=['negative', 'gate'])
594 def generic_test(self):
595 self.execute(self._schema_file)
596
597 cn = klass.__name__
598 cn = cn.replace('JSON', '')
599 cn = cn.replace('Test', '')
600 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
601 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
602 func_name = 'test_%s' % lower_cn
603 setattr(klass, func_name, generic_test)
604 return klass
605
606
Sean Dague35a7caf2013-05-10 10:38:22 -0400607def call_until_true(func, duration, sleep_for):
608 """
609 Call the given function until it returns True (and return True) or
610 until the specified duration (in seconds) elapses (and return
611 False).
612
613 :param func: A zero argument callable that returns True on success.
614 :param duration: The number of seconds for which to attempt a
615 successful call of the function.
616 :param sleep_for: The number of seconds to sleep after an unsuccessful
617 invocation of the function.
618 """
619 now = time.time()
620 timeout = now + duration
621 while now < timeout:
622 if func():
623 return True
624 LOG.debug("Sleeping for %d seconds", sleep_for)
625 time.sleep(sleep_for)
626 now = time.time()
627 return False