blob: f34933ecd2b85fa4083027f7a8e32d4ded5ef14f [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 """
Marc Koderer53b01232014-08-13 15:57:30 +020073 @functools.wraps(f)
Zhi Kun Liu22fb4702014-02-27 16:15:24 +080074 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,
ekhugen7aff0992014-08-04 19:01:57 +0000110 'telemetry': 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 }
Simeon Monov5d7effe2014-07-16 07:32:38 +0300218 if len(config_dict[service]) == 0:
219 return False
Matthew Treinishe3d26142013-11-26 19:14:58 +0000220 if config_dict[service][0] == 'all':
221 return True
222 if extension_name in config_dict[service]:
223 return True
224 return False
225
Ian Wienand98c35f32013-07-23 20:34:23 +1000226
Attila Fazekasf86fa312013-07-30 19:56:39 +0200227at_exit_set = set()
228
229
230def validate_tearDownClass():
231 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400232 LOG.error(
233 "tearDownClass does not call the super's "
234 "tearDownClass in these classes: \n"
235 + str(at_exit_set))
236
Attila Fazekasf86fa312013-07-30 19:56:39 +0200237
238atexit.register(validate_tearDownClass)
239
Attila Fazekas53943322014-02-10 16:07:34 +0100240if sys.version_info >= (2, 7):
241 class BaseDeps(testtools.TestCase,
Attila Fazekasdc216422013-01-29 15:12:14 +0100242 testtools.testcase.WithAttributes,
243 testresources.ResourcedTestCase):
Attila Fazekas53943322014-02-10 16:07:34 +0100244 pass
245else:
246 # Define asserts for py26
247 import unittest2
248
249 class BaseDeps(testtools.TestCase,
250 testtools.testcase.WithAttributes,
251 testresources.ResourcedTestCase,
252 unittest2.TestCase):
253 pass
254
255
256class BaseTestCase(BaseDeps):
Attila Fazekasc43fec82013-04-09 23:17:52 +0200257
Attila Fazekasf86fa312013-07-30 19:56:39 +0200258 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100259 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200260
Matthew Treinish9f756a02014-01-15 10:26:07 -0500261 network_resources = {}
262
Sean Dague2ef32ac2014-06-09 11:32:23 -0400263 # NOTE(sdague): log_format is defined inline here instead of using the oslo
264 # default because going through the config path recouples config to the
265 # stress tests too early, and depending on testr order will fail unit tests
266 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
267 '[%(name)s] %(message)s')
268
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200269 @classmethod
270 def setUpClass(cls):
271 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
272 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200273 cls.setUpClassCalled = True
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200274
Attila Fazekasf86fa312013-07-30 19:56:39 +0200275 @classmethod
276 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200277 at_exit_set.discard(cls)
Attila Fazekasf86fa312013-07-30 19:56:39 +0200278 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
279 super(BaseTestCase, cls).tearDownClass()
280
281 def setUp(self):
282 super(BaseTestCase, self).setUp()
283 if not self.setUpClassCalled:
284 raise RuntimeError("setUpClass does not calls the super's"
285 "setUpClass in the "
286 + self.__class__.__name__)
287 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400288 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
289 try:
290 test_timeout = int(test_timeout)
291 except ValueError:
292 test_timeout = 0
293 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200294 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400295
296 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
297 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200298 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
299 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400300 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
301 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200302 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
303 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200304 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
305 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200306 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400307 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200308 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400309
Matthew Treinish3e046852013-07-23 16:00:24 -0400310 @classmethod
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000311 def get_client_manager(cls, interface=None):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700312 """
tanlin4956a642014-02-13 16:52:11 +0800313 Returns an OpenStack client manager
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700314 """
Matthew Treinish9f756a02014-01-15 10:26:07 -0500315 cls.isolated_creds = isolated_creds.IsolatedCreds(
316 cls.__name__, network_resources=cls.network_resources)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700317
318 force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000319 if CONF.compute.allow_tenant_isolation or force_tenant_isolation:
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700320 creds = cls.isolated_creds.get_primary_creds()
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000321 if getattr(cls, '_interface', None):
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000322 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100323 interface=cls._interface,
324 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000325 elif interface:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000326 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100327 interface=interface,
328 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000329 else:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000330 os = clients.Manager(credentials=creds,
Marc Koderer24eb89c2014-01-31 11:23:33 +0100331 service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700332 else:
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000333 if getattr(cls, '_interface', None):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100334 os = clients.Manager(interface=cls._interface,
335 service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000336 elif interface:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100337 os = clients.Manager(interface=interface, service=cls._service)
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000338 else:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100339 os = clients.Manager(service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700340 return os
341
342 @classmethod
343 def clear_isolated_creds(cls):
344 """
345 Clears isolated creds if set
346 """
347 if getattr(cls, 'isolated_creds'):
348 cls.isolated_creds.clear_isolated_creds()
349
350 @classmethod
Matthew Treinish3e046852013-07-23 16:00:24 -0400351 def _get_identity_admin_client(cls):
352 """
353 Returns an instance of the Identity Admin API client
354 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100355 os = clients.AdminManager(interface=cls._interface,
356 service=cls._service)
Matthew Treinish3e046852013-07-23 16:00:24 -0400357 admin_client = os.identity_client
358 return admin_client
359
360 @classmethod
Matthew Treinish9f756a02014-01-15 10:26:07 -0500361 def set_network_resources(self, network=False, router=False, subnet=False,
362 dhcp=False):
363 """Specify which network resources should be created
364
365 @param network
366 @param router
367 @param subnet
368 @param dhcp
369 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000370 # network resources should be set only once from callers
371 # in order to ensure that even if it's called multiple times in
372 # a chain of overloaded methods, the attribute is set only
373 # in the leaf class
374 if not self.network_resources:
375 self.network_resources = {
376 'network': network,
377 'router': router,
378 'subnet': subnet,
379 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500380
Mark Maglana5885eb32014-02-28 10:57:34 -0800381 def assertEmpty(self, list, msg=None):
382 self.assertTrue(len(list) == 0, msg)
383
384 def assertNotEmpty(self, list, msg=None):
385 self.assertTrue(len(list) > 0, msg)
386
Attila Fazekasdc216422013-01-29 15:12:14 +0100387
Marc Koderer24eb89c2014-01-31 11:23:33 +0100388class NegativeAutoTest(BaseTestCase):
389
390 _resources = {}
391
392 @classmethod
393 def setUpClass(cls):
394 super(NegativeAutoTest, cls).setUpClass()
395 os = cls.get_client_manager()
396 cls.client = os.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100397 os_admin = clients.AdminManager(interface=cls._interface,
398 service=cls._service)
399 cls.admin_client = os_admin.negative_client
Marc Koderer24eb89c2014-01-31 11:23:33 +0100400
401 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100402 def load_tests(*args):
403 """
404 Wrapper for testscenarios to set the mandatory scenarios variable
405 only in case a real test loader is in place. Will be automatically
406 called in case the variable "load_tests" is set.
407 """
408 if getattr(args[0], 'suiteClass', None) is not None:
409 loader, standard_tests, pattern = args
410 else:
411 standard_tests, module, loader = args
412 for test in testtools.iterate_tests(standard_tests):
413 schema_file = getattr(test, '_schema_file', None)
Marc Koderer4f44d722014-08-07 14:04:58 +0200414 schema = getattr(test, '_schema', None)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100415 if schema_file is not None:
416 setattr(test, 'scenarios',
417 NegativeAutoTest.generate_scenario(schema_file))
Marc Koderer4f44d722014-08-07 14:04:58 +0200418 elif schema is not None:
419 setattr(test, 'scenarios',
420 NegativeAutoTest.generate_scenario(schema))
Marc Koderer674c8fc2014-03-17 09:45:04 +0100421 return testscenarios.load_tests_apply_scenarios(*args)
422
423 @staticmethod
Marc Koderer4f44d722014-08-07 14:04:58 +0200424 def generate_scenario(description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100425 """
426 Generates the test scenario list for a given description.
427
Marc Koderer4f44d722014-08-07 14:04:58 +0200428 :param description: A file or dictionary with the following entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100429 name (required) name for the api
430 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
431 url (required) the url to be appended to the catalog url with '%s'
432 for each resource mentioned
433 resources: (optional) A list of resource names such as "server",
434 "flavor", etc. with an element for each '%s' in the url. This
435 method will call self.get_resource for each element when
436 constructing the positive test case template so negative
437 subclasses are expected to return valid resource ids when
438 appropriate.
439 json-schema (optional) A valid json schema that will be used to
440 create invalid data for the api calls. For "GET" and "HEAD",
441 the data is used to generate query strings appended to the url,
442 otherwise for the body of the http call.
443 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100444 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100445 generator = importutils.import_class(
446 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100447 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100448 schema = description.get("json-schema", None)
449 resources = description.get("resources", [])
450 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100451 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100452 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100453 if isinstance(resource, dict):
454 expected_result = resource['expected_result']
455 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100456 LOG.debug("Add resource to test %s" % resource)
457 scn_name = "inv_res_%s" % (resource)
458 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100459 str(uuid.uuid4())),
460 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100461 }))
462 if schema is not None:
Marc Kodererf857fda2014-03-05 15:58:00 +0100463 for name, schema, expected_result in generator.generate(schema):
464 if (expected_result is None and
465 "default_result_code" in description):
466 expected_result = description["default_result_code"]
467 scenario_list.append((name,
468 {"schema": schema,
469 "expected_result": expected_result}))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100470 LOG.debug(scenario_list)
471 return scenario_list
472
Marc Koderer4f44d722014-08-07 14:04:58 +0200473 def execute(self, description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100474 """
475 Execute a http call on an api that are expected to
476 result in client errors. First it uses invalid resources that are part
477 of the url, and then invalid data for queries and http request bodies.
478
Marc Koderer4f44d722014-08-07 14:04:58 +0200479 :param description: A json file or dictionary with the following
480 entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100481 name (required) name for the api
482 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
483 url (required) the url to be appended to the catalog url with '%s'
484 for each resource mentioned
485 resources: (optional) A list of resource names such as "server",
486 "flavor", etc. with an element for each '%s' in the url. This
487 method will call self.get_resource for each element when
488 constructing the positive test case template so negative
489 subclasses are expected to return valid resource ids when
490 appropriate.
491 json-schema (optional) A valid json schema that will be used to
492 create invalid data for the api calls. For "GET" and "HEAD",
493 the data is used to generate query strings appended to the url,
494 otherwise for the body of the http call.
495
496 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100497 LOG.info("Executing %s" % description["name"])
498 LOG.debug(description)
499 method = description["http-method"]
500 url = description["url"]
501
502 resources = [self.get_resource(r) for
503 r in description.get("resources", [])]
504
505 if hasattr(self, "resource"):
506 # Note(mkoderer): The resources list already contains an invalid
507 # entry (see get_resource).
508 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100509 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100510 schema = description.get("json-schema", None)
511 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100512 valid_schema = \
513 valid.ValidTestGenerator().generate_valid(schema)
514 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Koderer424c84f2014-02-06 17:02:19 +0100515 elif hasattr(self, "schema"):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100516 new_url, body = self._http_arguments(self.schema, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100517 else:
518 raise Exception("testscenarios are not active. Please make sure "
519 "that your test runner supports the load_tests "
520 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100521
Marc Kodererf857fda2014-03-05 15:58:00 +0100522 if "admin_client" in description and description["admin_client"]:
523 client = self.admin_client
524 else:
525 client = self.client
526 resp, resp_body = client.send_request(method, new_url,
527 resources, body=body)
Marc Koderer424c84f2014-02-06 17:02:19 +0100528 self._check_negative_response(resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100529
530 def _http_arguments(self, json_dict, url, method):
531 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
532 if not json_dict:
533 return url, None
534 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
535 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
536 else:
537 return url, json.dumps(json_dict)
538
539 def _check_negative_response(self, result, body):
540 expected_result = getattr(self, "expected_result", None)
541 self.assertTrue(result >= 400 and result < 500 and result != 413,
542 "Expected client error, got %s:%s" %
543 (result, body))
544 self.assertTrue(expected_result is None or expected_result == result,
545 "Expected %s, got %s:%s" %
546 (expected_result, result, body))
547
548 @classmethod
549 def set_resource(cls, name, resource):
550 """
551 This function can be used in setUpClass context to register a resoruce
552 for a test.
553
554 :param name: The name of the kind of resource such as "flavor", "role",
555 etc.
556 :resource: The id of the resource
557 """
558 cls._resources[name] = resource
559
560 def get_resource(self, name):
561 """
562 Return a valid uuid for a type of resource. If a real resource is
563 needed as part of a url then this method should return one. Otherwise
564 it can return None.
565
566 :param name: The name of the kind of resource such as "flavor", "role",
567 etc.
568 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100569 if isinstance(name, dict):
570 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100571 if hasattr(self, "resource") and self.resource[0] == name:
572 LOG.debug("Return invalid resource (%s) value: %s" %
573 (self.resource[0], self.resource[1]))
574 return self.resource[1]
575 if name in self._resources:
576 return self._resources[name]
577 return None
578
579
Marc Kodererb2978da2014-03-26 13:45:43 +0100580def SimpleNegativeAutoTest(klass):
581 """
582 This decorator registers a test function on basis of the class name.
583 """
584 @attr(type=['negative', 'gate'])
585 def generic_test(self):
Marc Koderer4f44d722014-08-07 14:04:58 +0200586 if hasattr(self, '_schema'):
587 self.execute(self._schema)
Marc Kodererb2978da2014-03-26 13:45:43 +0100588
589 cn = klass.__name__
590 cn = cn.replace('JSON', '')
591 cn = cn.replace('Test', '')
592 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
593 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
594 func_name = 'test_%s' % lower_cn
595 setattr(klass, func_name, generic_test)
596 return klass
597
598
Sean Dague35a7caf2013-05-10 10:38:22 -0400599def call_until_true(func, duration, sleep_for):
600 """
601 Call the given function until it returns True (and return True) or
602 until the specified duration (in seconds) elapses (and return
603 False).
604
605 :param func: A zero argument callable that returns True on success.
606 :param duration: The number of seconds for which to attempt a
607 successful call of the function.
608 :param sleep_for: The number of seconds to sleep after an unsuccessful
609 invocation of the function.
610 """
611 now = time.time()
612 timeout = now + duration
613 while now < timeout:
614 if func():
615 return True
Sean Dague35a7caf2013-05-10 10:38:22 -0400616 time.sleep(sleep_for)
617 now = time.time()
618 return False