blob: b8ba5f45548c9bc8ecb7629b2f33cc72baefcd85 [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
Ian Wienand98c35f32013-07-23 20:34:23 +100018import os
Marc Kodererb2978da2014-03-26 13:45:43 +010019import re
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
Doug Hellmann583ce2c2015-03-11 14:55:46 +000026from oslo_log import log as logging
Matthew Treinish21905512015-07-13 10:33:35 -040027from oslo_serialization import jsonutils as json
Doug Hellmann583ce2c2015-03-11 14:55:46 +000028from oslo_utils import importutils
Chris Hoge296558c2015-02-19 00:29:49 -060029import six
Marc Koderer674c8fc2014-03-17 09:45:04 +010030import testscenarios
ivan-zhu1feeb382013-01-24 10:14:39 +080031import testtools
Jay Pipes051075a2012-04-28 17:39:37 -040032
Matthew Treinish3e046852013-07-23 16:00:24 -040033from tempest import clients
Jamie Lennox15350172015-08-17 10:54:25 +100034from tempest.common import cred_client
Andrea Frittoli8283b4e2014-07-17 13:28:58 +010035from tempest.common import credentials
Rohan Kanade9ce97df2013-12-10 18:59:35 +053036from tempest.common import fixed_network
Marc Koderer6ee82dc2014-02-17 10:26:29 +010037import tempest.common.generator.valid_generator as valid
nithya-ganesan222efd72015-01-22 12:20:27 +000038import tempest.common.validation_resources as vresources
Attila Fazekasdc216422013-01-29 15:12:14 +010039from tempest import config
Matthew Treinish16c43792013-09-09 19:55:23 +000040from tempest import exceptions
Jay Pipes051075a2012-04-28 17:39:37 -040041
42LOG = logging.getLogger(__name__)
43
Sean Dague86bd8422013-12-20 09:56:44 -050044CONF = config.CONF
45
Jay Pipes051075a2012-04-28 17:39:37 -040046
Yaroslav Lobankovda999f72015-06-30 20:32:55 +030047def attr(**kwargs):
liuchenhong00caec52015-07-19 22:40:28 +080048 """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)
57 elif 'type' in kwargs and isinstance(kwargs['type'], list):
58 for attr in kwargs['type']:
59 f = testtools.testcase.attr(attr)(f)
Matthew Treinisha74f5d42014-02-07 20:25:44 -050060 return f
Chris Yeoh55530bb2013-02-08 16:04:27 +103061
62 return decorator
63
64
Chris Hoge296558c2015-02-19 00:29:49 -060065def idempotent_id(id):
66 """Stub for metadata decorator"""
67 if not isinstance(id, six.string_types):
68 raise TypeError('Test idempotent_id must be string not %s'
69 '' % type(id).__name__)
70 uuid.UUID(id)
71
72 def decorator(f):
73 f = testtools.testcase.attr('id-%s' % id)(f)
74 if f.__doc__:
75 f.__doc__ = 'Test idempotent id: %s\n%s' % (id, f.__doc__)
76 else:
77 f.__doc__ = 'Test idempotent id: %s' % id
78 return f
79 return decorator
80
81
Matthew Treinish3d8c7322014-08-03 23:53:28 -040082def get_service_list():
Matthew Treinish8afbffd2014-01-21 23:56:13 +000083 service_list = {
84 'compute': CONF.service_available.nova,
85 'image': CONF.service_available.glance,
Adam Gandelman4a48a602014-03-20 18:23:18 -070086 'baremetal': CONF.service_available.ironic,
Matthew Treinish8afbffd2014-01-21 23:56:13 +000087 'volume': CONF.service_available.cinder,
88 'orchestration': CONF.service_available.heat,
89 # NOTE(mtreinish) nova-network will provide networking functionality
90 # if neutron isn't available, so always set to True.
91 'network': True,
92 'identity': True,
93 'object_storage': CONF.service_available.swift,
94 'dashboard': CONF.service_available.horizon,
ekhugen7aff0992014-08-04 19:01:57 +000095 'telemetry': CONF.service_available.ceilometer,
Matthew Treinishb66c94e2015-03-11 13:00:48 -040096 'data_processing': CONF.service_available.sahara,
97 'database': CONF.service_available.trove
Matthew Treinish8afbffd2014-01-21 23:56:13 +000098 }
Matthew Treinish3d8c7322014-08-03 23:53:28 -040099 return service_list
Matthew Treinish16c43792013-09-09 19:55:23 +0000100
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400101
Yaroslav Lobankovda999f72015-06-30 20:32:55 +0300102def services(*args):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400103 """A decorator used to set an attr for each service used in a test case
104
105 This decorator applies a testtools attr for each service that gets
106 exercised by a test case.
107 """
Matthew Treinish16c43792013-09-09 19:55:23 +0000108 def decorator(f):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400109 services = ['compute', 'image', 'baremetal', 'volume', 'orchestration',
110 'network', 'identity', 'object_storage', 'dashboard',
Matthew Treinishb66c94e2015-03-11 13:00:48 -0400111 'telemetry', 'data_processing', 'database']
Matthew Treinish16c43792013-09-09 19:55:23 +0000112 for service in args:
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400113 if service not in services:
114 raise exceptions.InvalidServiceTag('%s is not a valid '
115 'service' % service)
Matthew Treinish16c43792013-09-09 19:55:23 +0000116 attr(type=list(args))(f)
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000117
118 @functools.wraps(f)
119 def wrapper(self, *func_args, **func_kwargs):
Matthew Treinish3d8c7322014-08-03 23:53:28 -0400120 service_list = get_service_list()
121
Matthew Treinish8afbffd2014-01-21 23:56:13 +0000122 for service in args:
123 if not service_list[service]:
124 msg = 'Skipped because the %s service is not available' % (
125 service)
126 raise testtools.TestCase.skipException(msg)
127 return f(self, *func_args, **func_kwargs)
128 return wrapper
Matthew Treinish16c43792013-09-09 19:55:23 +0000129 return decorator
130
131
Yaroslav Lobankovda999f72015-06-30 20:32:55 +0300132def stresstest(**kwargs):
Marc Koderer32221b8e2013-08-23 13:57:50 +0200133 """Add stress test decorator
134
135 For all functions with this decorator a attr stress will be
136 set automatically.
137
138 @param class_setup_per: allowed values are application, process, action
139 ``application``: once in the stress job lifetime
140 ``process``: once in the worker process lifetime
141 ``action``: on each action
Marc Kodererb0604412013-09-02 09:43:40 +0200142 @param allow_inheritance: allows inheritance of this attribute
Marc Koderer32221b8e2013-08-23 13:57:50 +0200143 """
144 def decorator(f):
145 if 'class_setup_per' in kwargs:
146 setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
147 else:
148 setattr(f, "st_class_setup_per", 'process')
Marc Kodererb0604412013-09-02 09:43:40 +0200149 if 'allow_inheritance' in kwargs:
150 setattr(f, "st_allow_inheritance", kwargs['allow_inheritance'])
151 else:
152 setattr(f, "st_allow_inheritance", False)
Marc Koderer32221b8e2013-08-23 13:57:50 +0200153 attr(type='stress')(f)
154 return f
155 return decorator
156
157
Yaroslav Lobankovda999f72015-06-30 20:32:55 +0300158def requires_ext(**kwargs):
Matthew Treinishe3d26142013-11-26 19:14:58 +0000159 """A decorator to skip tests if an extension is not enabled
160
161 @param extension
162 @param service
163 """
164 def decorator(func):
165 @functools.wraps(func)
166 def wrapper(*func_args, **func_kwargs):
167 if not is_extension_enabled(kwargs['extension'],
168 kwargs['service']):
169 msg = "Skipped because %s extension: %s is not enabled" % (
170 kwargs['service'], kwargs['extension'])
171 raise testtools.TestCase.skipException(msg)
172 return func(*func_args, **func_kwargs)
173 return wrapper
174 return decorator
175
176
177def is_extension_enabled(extension_name, service):
178 """A function that will check the list of enabled extensions from config
179
180 """
Matthew Treinishe3d26142013-11-26 19:14:58 +0000181 config_dict = {
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000182 'compute': CONF.compute_feature_enabled.api_extensions,
Matthew Treinishbc0e03e2014-01-30 16:51:06 +0000183 'volume': CONF.volume_feature_enabled.api_extensions,
184 'network': CONF.network_feature_enabled.api_extensions,
185 'object': CONF.object_storage_feature_enabled.discoverable_apis,
Jane Zadorozhna121576d2015-06-23 12:57:13 +0300186 'identity': CONF.identity_feature_enabled.api_extensions
Matthew Treinishe3d26142013-11-26 19:14:58 +0000187 }
Simeon Monov5d7effe2014-07-16 07:32:38 +0300188 if len(config_dict[service]) == 0:
189 return False
Matthew Treinishe3d26142013-11-26 19:14:58 +0000190 if config_dict[service][0] == 'all':
191 return True
192 if extension_name in config_dict[service]:
193 return True
194 return False
195
Ian Wienand98c35f32013-07-23 20:34:23 +1000196
Attila Fazekasf86fa312013-07-30 19:56:39 +0200197at_exit_set = set()
198
199
200def validate_tearDownClass():
201 if at_exit_set:
Sean Dagueeb1523b2014-03-10 10:17:44 -0400202 LOG.error(
203 "tearDownClass does not call the super's "
204 "tearDownClass in these classes: \n"
205 + str(at_exit_set))
206
Attila Fazekasf86fa312013-07-30 19:56:39 +0200207
208atexit.register(validate_tearDownClass)
209
Attila Fazekas53943322014-02-10 16:07:34 +0100210
Matthew Treinish2474f412014-11-17 18:11:56 -0500211class BaseTestCase(testtools.testcase.WithAttributes,
212 testtools.TestCase):
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100213 """The test base class defines Tempest framework for class level fixtures.
214 `setUpClass` and `tearDownClass` are defined here and cannot be overwritten
215 by subclasses (enforced via hacking rule T105).
216
217 Set-up is split in a series of steps (setup stages), which can be
218 overwritten by test classes. Set-up stages are:
219 - skip_checks
220 - setup_credentials
221 - setup_clients
222 - resource_setup
223
224 Tear-down is also split in a series of steps (teardown stages), which are
225 stacked for execution only if the corresponding setup stage had been
226 reached during the setup phase. Tear-down stages are:
Andrea Frittoli (andreaf)17209bb2015-05-22 10:16:57 -0700227 - clear_credentials (defined in the base test class)
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100228 - resource_cleanup
229 """
Attila Fazekasc43fec82013-04-09 23:17:52 +0200230
Attila Fazekasf86fa312013-07-30 19:56:39 +0200231 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100232 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200233
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000234 # NOTE(andreaf) credentials holds a list of the credentials to be allocated
Andrea Frittoli (andreaf)825b2d32015-04-08 20:58:01 +0100235 # at class setup time. Credential types can be 'primary', 'alt', 'admin' or
236 # a list of roles - the first element of the list being a label, and the
237 # rest the actual roles
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000238 credentials = []
nithya-ganesan222efd72015-01-22 12:20:27 +0000239 # Resources required to validate a server using ssh
240 validation_resources = {}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500241 network_resources = {}
242
Sean Dague2ef32ac2014-06-09 11:32:23 -0400243 # NOTE(sdague): log_format is defined inline here instead of using the oslo
244 # default because going through the config path recouples config to the
245 # stress tests too early, and depending on testr order will fail unit tests
246 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
247 '[%(name)s] %(message)s')
248
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200249 @classmethod
250 def setUpClass(cls):
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100251 # It should never be overridden by descendants
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200252 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
253 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200254 cls.setUpClassCalled = True
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100255 # Stack of (name, callable) to be invoked in reverse order at teardown
256 cls.teardowns = []
257 # All the configuration checks that may generate a skip
258 cls.skip_checks()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100259 try:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100260 # Allocation of all required credentials and client managers
Andrea Frittoli (andreaf)17209bb2015-05-22 10:16:57 -0700261 cls.teardowns.append(('credentials', cls.clear_credentials))
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100262 cls.setup_credentials()
263 # Shortcuts to clients
264 cls.setup_clients()
265 # Additional class-wide test resources
266 cls.teardowns.append(('resources', cls.resource_cleanup))
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100267 cls.resource_setup()
268 except Exception:
269 etype, value, trace = sys.exc_info()
Matthew Treinished2ad4f2014-12-23 15:18:32 -0500270 LOG.info("%s raised in %s.setUpClass. Invoking tearDownClass." % (
271 etype, cls.__name__))
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100272 cls.tearDownClass()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100273 try:
Matthew Treinish843227d2015-04-23 10:17:17 -0400274 six.reraise(etype, value, trace)
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100275 finally:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100276 del trace # to avoid circular refs
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200277
Attila Fazekasf86fa312013-07-30 19:56:39 +0200278 @classmethod
279 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200280 at_exit_set.discard(cls)
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100281 # It should never be overridden by descendants
Attila Fazekasf86fa312013-07-30 19:56:39 +0200282 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
283 super(BaseTestCase, cls).tearDownClass()
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100284 # Save any existing exception, we always want to re-raise the original
285 # exception only
286 etype, value, trace = sys.exc_info()
287 # If there was no exception during setup we shall re-raise the first
288 # exception in teardown
289 re_raise = (etype is None)
290 while cls.teardowns:
291 name, teardown = cls.teardowns.pop()
292 # Catch any exception in tearDown so we can re-raise the original
293 # exception at the end
294 try:
295 teardown()
296 except Exception as te:
297 sys_exec_info = sys.exc_info()
298 tetype = sys_exec_info[0]
299 # TODO(andreaf): Till we have the ability to cleanup only
300 # resources that were successfully setup in resource_cleanup,
301 # log AttributeError as info instead of exception.
302 if tetype is AttributeError and name == 'resources':
303 LOG.info("tearDownClass of %s failed: %s" % (name, te))
304 else:
305 LOG.exception("teardown of %s failed: %s" % (name, te))
306 if not etype:
307 etype, value, trace = sys_exec_info
308 # If exceptions were raised during teardown, an not before, re-raise
309 # the first one
310 if re_raise and etype is not None:
311 try:
Matthew Treinish843227d2015-04-23 10:17:17 -0400312 six.reraise(etype, value, trace)
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100313 finally:
314 del trace # to avoid circular refs
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100315
316 @classmethod
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100317 def skip_checks(cls):
318 """Class level skip checks. Subclasses verify in here all
319 conditions that might prevent the execution of the entire test class.
320 Checks implemented here may not make use API calls, and should rely on
321 configuration alone.
322 In general skip checks that require an API call are discouraged.
323 If one is really needed it may be implemented either in the
324 resource_setup or at test level.
325 """
Andrea Frittoli (andreaf)32d0de12015-10-09 14:43:53 +0100326 identity_version = cls.get_identity_version()
327 if 'admin' in cls.credentials and not credentials.is_admin_available(
328 identity_version=identity_version):
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000329 msg = "Missing Identity Admin API credentials in configuration."
330 raise cls.skipException(msg)
Andrea Frittoli (andreaf)32d0de12015-10-09 14:43:53 +0100331 if 'alt' in cls.credentials and not credentials.is_alt_available(
332 identity_version=identity_version):
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000333 msg = "Missing a 2nd set of API credentials in configuration."
334 raise cls.skipException(msg)
Andrea Frittoli (andreaf)41601412015-05-12 16:39:03 +0100335 if hasattr(cls, 'identity_version'):
336 if cls.identity_version == 'v2':
337 if not CONF.identity_feature_enabled.api_v2:
338 raise cls.skipException("Identity api v2 is not enabled")
339 elif cls.identity_version == 'v3':
340 if not CONF.identity_feature_enabled.api_v3:
341 raise cls.skipException("Identity api v3 is not enabled")
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100342
343 @classmethod
344 def setup_credentials(cls):
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000345 """Allocate credentials and the client managers from them.
346 A test class that requires network resources must override
347 setup_credentials and defined the required resources before super
348 is invoked.
349 """
350 for credentials_type in cls.credentials:
351 # This may raise an exception in case credentials are not available
352 # In that case we want to let the exception through and the test
353 # fail accordingly
Andrea Frittoli (andreaf)825b2d32015-04-08 20:58:01 +0100354 if isinstance(credentials_type, six.string_types):
355 manager = cls.get_client_manager(
356 credential_type=credentials_type)
357 setattr(cls, 'os_%s' % credentials_type, manager)
358 # Setup some common aliases
359 # TODO(andreaf) The aliases below are a temporary hack
360 # to avoid changing too much code in one patch. They should
361 # be removed eventually
362 if credentials_type == 'primary':
363 cls.os = cls.manager = cls.os_primary
364 if credentials_type == 'admin':
365 cls.os_adm = cls.admin_manager = cls.os_admin
366 if credentials_type == 'alt':
367 cls.alt_manager = cls.os_alt
368 elif isinstance(credentials_type, list):
369 manager = cls.get_client_manager(roles=credentials_type[1:],
370 force_new=True)
371 setattr(cls, 'os_roles_%s' % credentials_type[0], manager)
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100372
373 @classmethod
374 def setup_clients(cls):
375 """Create links to the clients into the test object."""
376 # TODO(andreaf) There is a fair amount of code that could me moved from
377 # base / test classes in here. Ideally tests should be able to only
378 # specify which client is `client` and nothing else.
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100379 pass
Attila Fazekasf86fa312013-07-30 19:56:39 +0200380
Emily Hugenbruch5bd4cbf2014-12-17 21:38:38 +0000381 @classmethod
382 def resource_setup(cls):
383 """Class level resource setup for test cases.
384 """
nithya-ganesan222efd72015-01-22 12:20:27 +0000385 if hasattr(cls, "os"):
386 cls.validation_resources = vresources.create_validation_resources(
387 cls.os, cls.validation_resources)
388 else:
389 LOG.warn("Client manager not found, validation resources not"
390 " created")
Emily Hugenbruch5bd4cbf2014-12-17 21:38:38 +0000391
392 @classmethod
393 def resource_cleanup(cls):
394 """Class level resource cleanup for test cases.
395 Resource cleanup must be able to handle the case of partially setup
396 resources, in case a failure during `resource_setup` should happen.
397 """
nithya-ganesan222efd72015-01-22 12:20:27 +0000398 if cls.validation_resources:
399 if hasattr(cls, "os"):
400 vresources.clear_validation_resources(cls.os,
401 cls.validation_resources)
402 cls.validation_resources = {}
403 else:
404 LOG.warn("Client manager not found, validation resources not"
405 " deleted")
Emily Hugenbruch5bd4cbf2014-12-17 21:38:38 +0000406
Attila Fazekasf86fa312013-07-30 19:56:39 +0200407 def setUp(self):
408 super(BaseTestCase, self).setUp()
409 if not self.setUpClassCalled:
410 raise RuntimeError("setUpClass does not calls the super's"
411 "setUpClass in the "
412 + self.__class__.__name__)
413 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400414 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
415 try:
416 test_timeout = int(test_timeout)
417 except ValueError:
418 test_timeout = 0
419 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200420 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400421
422 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
423 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200424 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
425 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400426 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
427 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200428 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
429 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200430 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
431 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200432 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400433 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200434 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400435
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100436 @property
437 def credentials_provider(self):
438 return self._get_credentials_provider()
439
Jamie Lennox15350172015-08-17 10:54:25 +1000440 @property
441 def identity_utils(self):
442 """A client that abstracts v2 and v3 identity operations.
443
444 This can be used for creating and tearing down projects in tests. It
445 should not be used for testing identity features.
446 """
447 if CONF.identity.auth_version == 'v2':
448 client = self.os_admin.identity_client
449 else:
450 client = self.os_admin.identity_v3_client
451
452 try:
453 domain = client.auth_provider.credentials.project_domain_name
454 except AttributeError:
455 domain = 'Default'
456
457 return cred_client.get_creds_client(client, domain)
458
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100459 @classmethod
Andrea Frittoli (andreaf)32d0de12015-10-09 14:43:53 +0100460 def get_identity_version(cls):
461 """Returns the identity version used by the test class"""
462 identity_version = getattr(cls, 'identity_version', None)
463 return identity_version or CONF.identity.auth_version
464
465 @classmethod
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100466 def _get_credentials_provider(cls):
467 """Returns a credentials provider
468
469 If no credential provider exists yet creates one.
470 It uses self.identity_version if defined, or the configuration value
471 """
472 if (not hasattr(cls, '_creds_provider') or not cls._creds_provider or
473 not cls._creds_provider.name == cls.__name__):
474 force_tenant_isolation = getattr(cls, 'force_tenant_isolation',
475 False)
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100476
Andrea Frittoli (andreaf)17209bb2015-05-22 10:16:57 -0700477 cls._creds_provider = credentials.get_credentials_provider(
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100478 name=cls.__name__, network_resources=cls.network_resources,
479 force_tenant_isolation=force_tenant_isolation,
Andrea Frittoli (andreaf)32d0de12015-10-09 14:43:53 +0100480 identity_version=cls.get_identity_version())
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100481 return cls._creds_provider
482
Matthew Treinish3e046852013-07-23 16:00:24 -0400483 @classmethod
Andrea Frittoli (andreaf)41601412015-05-12 16:39:03 +0100484 def get_client_manager(cls, credential_type=None, roles=None,
485 force_new=None):
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100486 """Returns an OpenStack client manager
487
488 Returns an OpenStack client manager based on either credential_type
489 or a list of roles. If neither is specified, it defaults to
490 credential_type 'primary'
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100491 :param credential_type: string - primary, alt or admin
492 :param roles: list of roles
493
494 :returns the created client manager
495 :raises skipException: if the requested credentials are not available
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700496 """
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100497 if all([roles, credential_type]):
498 msg = "Cannot get credentials by type and roles at the same time"
499 raise ValueError(msg)
500 if not any([roles, credential_type]):
501 credential_type = 'primary'
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100502 cred_provider = cls._get_credentials_provider()
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100503 if roles:
504 for role in roles:
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100505 if not cred_provider.is_role_available(role):
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100506 skip_msg = (
507 "%s skipped because the configured credential provider"
508 " is not able to provide credentials with the %s role "
509 "assigned." % (cls.__name__, role))
510 raise cls.skipException(skip_msg)
511 params = dict(roles=roles)
512 if force_new is not None:
513 params.update(force_new=force_new)
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100514 creds = cred_provider.get_creds_by_roles(**params)
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000515 else:
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100516 credentials_method = 'get_%s_creds' % credential_type
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100517 if hasattr(cred_provider, credentials_method):
518 creds = getattr(cred_provider, credentials_method)()
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100519 else:
520 raise exceptions.InvalidCredentials(
521 "Invalid credentials type %s" % credential_type)
522 return clients.Manager(credentials=creds, service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700523
524 @classmethod
Andrea Frittoli (andreaf)17209bb2015-05-22 10:16:57 -0700525 def clear_credentials(cls):
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700526 """
Andrea Frittoli (andreaf)17209bb2015-05-22 10:16:57 -0700527 Clears creds if set
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700528 """
Attila Fazekas5b0d9262015-05-20 10:17:39 +0200529 if hasattr(cls, '_creds_provider'):
Andrea Frittoli (andreaf)17209bb2015-05-22 10:16:57 -0700530 cls._creds_provider.clear_creds()
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700531
532 @classmethod
nithya-ganesan222efd72015-01-22 12:20:27 +0000533 def set_validation_resources(cls, keypair=None, floating_ip=None,
534 security_group=None,
535 security_group_rules=None):
536 """Specify which ssh server validation resources should be created.
537 Each of the argument must be set to either None, True or False, with
538 None - use default from config (security groups and security group
539 rules get created when set to None)
540 False - Do not create the validation resource
541 True - create the validation resource
542
543 @param keypair
544 @param security_group
545 @param security_group_rules
546 @param floating_ip
547 """
Matthew Treinishe5cca002015-05-11 15:36:50 -0400548 if not CONF.validation.run_validation:
549 return
nithya-ganesan222efd72015-01-22 12:20:27 +0000550 if keypair is None:
551 if CONF.validation.auth_method.lower() == "keypair":
552 keypair = True
553 else:
554 keypair = False
555 if floating_ip is None:
556 if CONF.validation.connect_method.lower() == "floating":
557 floating_ip = True
558 else:
559 floating_ip = False
560 if security_group is None:
Brandon Palmc6cc91d2015-08-19 13:20:21 -0500561 security_group = CONF.validation.security_group
nithya-ganesan222efd72015-01-22 12:20:27 +0000562 if security_group_rules is None:
Brandon Palmc6cc91d2015-08-19 13:20:21 -0500563 security_group_rules = CONF.validation.security_group_rules
564
nithya-ganesan222efd72015-01-22 12:20:27 +0000565 if not cls.validation_resources:
566 cls.validation_resources = {
567 'keypair': keypair,
568 'security_group': security_group,
569 'security_group_rules': security_group_rules,
570 'floating_ip': floating_ip}
571
572 @classmethod
Andrea Frittoli7d5ed592015-02-10 01:10:23 +0000573 def set_network_resources(cls, network=False, router=False, subnet=False,
Matthew Treinish9f756a02014-01-15 10:26:07 -0500574 dhcp=False):
575 """Specify which network resources should be created
576
577 @param network
578 @param router
579 @param subnet
580 @param dhcp
581 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000582 # network resources should be set only once from callers
583 # in order to ensure that even if it's called multiple times in
584 # a chain of overloaded methods, the attribute is set only
585 # in the leaf class
Andrea Frittoli7d5ed592015-02-10 01:10:23 +0000586 if not cls.network_resources:
587 cls.network_resources = {
Salvatore Orlando5a337242014-01-15 22:49:22 +0000588 'network': network,
589 'router': router,
590 'subnet': subnet,
591 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500592
Rohan Kanade9ce97df2013-12-10 18:59:35 +0530593 @classmethod
594 def get_tenant_network(cls):
595 """Get the network to be used in testing
596
597 :return: network dict including 'id' and 'name'
598 """
Andrea Frittoli (andreaf)17209bb2015-05-22 10:16:57 -0700599 # Make sure cred_provider exists and get a network client
John Warren9487a182015-09-14 18:12:56 -0400600 networks_client = cls.get_client_manager().compute_networks_client
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100601 cred_provider = cls._get_credentials_provider()
Andrea Frittoli700711e2015-04-02 11:39:38 +0100602 # In case of nova network, isolated tenants are not able to list the
603 # network configured in fixed_network_name, even if the can use it
604 # for their servers, so using an admin network client to validate
605 # the network name
606 if (not CONF.service_available.neutron and
Andrea Frittoli (andreaf)32d0de12015-10-09 14:43:53 +0100607 credentials.is_admin_available(
608 identity_version=cls.get_identity_version())):
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100609 admin_creds = cred_provider.get_admin_creds()
John Warren9487a182015-09-14 18:12:56 -0400610 admin_manager = clients.Manager(admin_creds)
611 networks_client = admin_manager.compute_networks_client
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100612 return fixed_network.get_tenant_network(cred_provider,
Rohan Kanade9ce97df2013-12-10 18:59:35 +0530613 networks_client)
614
Mark Maglana5885eb32014-02-28 10:57:34 -0800615 def assertEmpty(self, list, msg=None):
616 self.assertTrue(len(list) == 0, msg)
617
618 def assertNotEmpty(self, list, msg=None):
619 self.assertTrue(len(list) > 0, msg)
620
Attila Fazekasdc216422013-01-29 15:12:14 +0100621
Marc Koderer24eb89c2014-01-31 11:23:33 +0100622class NegativeAutoTest(BaseTestCase):
623
624 _resources = {}
625
626 @classmethod
627 def setUpClass(cls):
628 super(NegativeAutoTest, cls).setUpClass()
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000629 os = cls.get_client_manager(credential_type='primary')
Marc Koderer24eb89c2014-01-31 11:23:33 +0100630 cls.client = os.negative_client
631
632 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100633 def load_tests(*args):
634 """
635 Wrapper for testscenarios to set the mandatory scenarios variable
636 only in case a real test loader is in place. Will be automatically
637 called in case the variable "load_tests" is set.
638 """
639 if getattr(args[0], 'suiteClass', None) is not None:
640 loader, standard_tests, pattern = args
641 else:
642 standard_tests, module, loader = args
643 for test in testtools.iterate_tests(standard_tests):
Marc Koderer4f44d722014-08-07 14:04:58 +0200644 schema = getattr(test, '_schema', None)
Marc Koderer3dd31052014-11-27 09:31:00 +0100645 if schema is not None:
Marc Koderer4f44d722014-08-07 14:04:58 +0200646 setattr(test, 'scenarios',
647 NegativeAutoTest.generate_scenario(schema))
Marc Koderer674c8fc2014-03-17 09:45:04 +0100648 return testscenarios.load_tests_apply_scenarios(*args)
649
650 @staticmethod
Marc Koderer4f44d722014-08-07 14:04:58 +0200651 def generate_scenario(description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100652 """
653 Generates the test scenario list for a given description.
654
Marc Koderer4f44d722014-08-07 14:04:58 +0200655 :param description: A file or dictionary with the following entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100656 name (required) name for the api
657 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
658 url (required) the url to be appended to the catalog url with '%s'
659 for each resource mentioned
660 resources: (optional) A list of resource names such as "server",
661 "flavor", etc. with an element for each '%s' in the url. This
662 method will call self.get_resource for each element when
663 constructing the positive test case template so negative
664 subclasses are expected to return valid resource ids when
665 appropriate.
666 json-schema (optional) A valid json schema that will be used to
667 create invalid data for the api calls. For "GET" and "HEAD",
668 the data is used to generate query strings appended to the url,
669 otherwise for the body of the http call.
670 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100671 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100672 generator = importutils.import_class(
673 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100674 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100675 schema = description.get("json-schema", None)
676 resources = description.get("resources", [])
677 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100678 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100679 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100680 if isinstance(resource, dict):
681 expected_result = resource['expected_result']
682 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100683 LOG.debug("Add resource to test %s" % resource)
684 scn_name = "inv_res_%s" % (resource)
685 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100686 str(uuid.uuid4())),
687 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100688 }))
689 if schema is not None:
Marc Kodererf07f5d12014-09-01 09:47:23 +0200690 for scenario in generator.generate_scenarios(schema):
691 scenario_list.append((scenario['_negtest_name'],
692 scenario))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100693 LOG.debug(scenario_list)
694 return scenario_list
695
Marc Koderer4f44d722014-08-07 14:04:58 +0200696 def execute(self, description):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100697 """
698 Execute a http call on an api that are expected to
699 result in client errors. First it uses invalid resources that are part
700 of the url, and then invalid data for queries and http request bodies.
701
Marc Koderer4f44d722014-08-07 14:04:58 +0200702 :param description: A json file or dictionary with the following
703 entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100704 name (required) name for the api
705 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
706 url (required) the url to be appended to the catalog url with '%s'
707 for each resource mentioned
708 resources: (optional) A list of resource names such as "server",
709 "flavor", etc. with an element for each '%s' in the url. This
710 method will call self.get_resource for each element when
711 constructing the positive test case template so negative
712 subclasses are expected to return valid resource ids when
713 appropriate.
714 json-schema (optional) A valid json schema that will be used to
715 create invalid data for the api calls. For "GET" and "HEAD",
716 the data is used to generate query strings appended to the url,
717 otherwise for the body of the http call.
718
719 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100720 LOG.info("Executing %s" % description["name"])
721 LOG.debug(description)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200722 generator = importutils.import_class(
723 CONF.negative.test_generator)()
724 schema = description.get("json-schema", None)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100725 method = description["http-method"]
726 url = description["url"]
Marc Kodererf07f5d12014-09-01 09:47:23 +0200727 expected_result = None
728 if "default_result_code" in description:
729 expected_result = description["default_result_code"]
Marc Koderer24eb89c2014-01-31 11:23:33 +0100730
731 resources = [self.get_resource(r) for
732 r in description.get("resources", [])]
733
734 if hasattr(self, "resource"):
735 # Note(mkoderer): The resources list already contains an invalid
736 # entry (see get_resource).
737 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100738 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100739 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100740 valid_schema = \
741 valid.ValidTestGenerator().generate_valid(schema)
742 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200743 elif hasattr(self, "_negtest_name"):
744 schema_under_test = \
745 valid.ValidTestGenerator().generate_valid(schema)
746 local_expected_result = \
747 generator.generate_payload(self, schema_under_test)
748 if local_expected_result is not None:
749 expected_result = local_expected_result
750 new_url, body = \
751 self._http_arguments(schema_under_test, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100752 else:
753 raise Exception("testscenarios are not active. Please make sure "
754 "that your test runner supports the load_tests "
755 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100756
Marc Kodererf857fda2014-03-05 15:58:00 +0100757 if "admin_client" in description and description["admin_client"]:
Andrea Frittoli (andreaf)32d0de12015-10-09 14:43:53 +0100758 if not credentials.is_admin_available(
759 identity_version=self.get_identity_version()):
David Kranzafecec02015-03-23 14:27:15 -0400760 msg = ("Missing Identity Admin API credentials in"
761 "configuration.")
762 raise self.skipException(msg)
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100763 creds = self.credentials_provider.get_admin_creds()
David Kranzafecec02015-03-23 14:27:15 -0400764 os_adm = clients.Manager(credentials=creds)
765 client = os_adm.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100766 else:
767 client = self.client
768 resp, resp_body = client.send_request(method, new_url,
769 resources, body=body)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200770 self._check_negative_response(expected_result, resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100771
772 def _http_arguments(self, json_dict, url, method):
773 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
774 if not json_dict:
775 return url, None
776 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
777 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
778 else:
779 return url, json.dumps(json_dict)
780
Marc Kodererf07f5d12014-09-01 09:47:23 +0200781 def _check_negative_response(self, expected_result, result, body):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100782 self.assertTrue(result >= 400 and result < 500 and result != 413,
783 "Expected client error, got %s:%s" %
784 (result, body))
785 self.assertTrue(expected_result is None or expected_result == result,
786 "Expected %s, got %s:%s" %
787 (expected_result, result, body))
788
789 @classmethod
790 def set_resource(cls, name, resource):
791 """
792 This function can be used in setUpClass context to register a resoruce
793 for a test.
794
795 :param name: The name of the kind of resource such as "flavor", "role",
796 etc.
797 :resource: The id of the resource
798 """
799 cls._resources[name] = resource
800
801 def get_resource(self, name):
802 """
803 Return a valid uuid for a type of resource. If a real resource is
804 needed as part of a url then this method should return one. Otherwise
805 it can return None.
806
807 :param name: The name of the kind of resource such as "flavor", "role",
808 etc.
809 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100810 if isinstance(name, dict):
811 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100812 if hasattr(self, "resource") and self.resource[0] == name:
813 LOG.debug("Return invalid resource (%s) value: %s" %
814 (self.resource[0], self.resource[1]))
815 return self.resource[1]
816 if name in self._resources:
817 return self._resources[name]
818 return None
819
820
Marc Kodererb2978da2014-03-26 13:45:43 +0100821def SimpleNegativeAutoTest(klass):
822 """
823 This decorator registers a test function on basis of the class name.
824 """
Sean Dague5e1bcd92015-04-27 09:08:36 -0400825 @attr(type=['negative'])
Marc Kodererb2978da2014-03-26 13:45:43 +0100826 def generic_test(self):
Marc Koderer4f44d722014-08-07 14:04:58 +0200827 if hasattr(self, '_schema'):
828 self.execute(self._schema)
Marc Kodererb2978da2014-03-26 13:45:43 +0100829
830 cn = klass.__name__
831 cn = cn.replace('JSON', '')
832 cn = cn.replace('Test', '')
833 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
834 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
835 func_name = 'test_%s' % lower_cn
836 setattr(klass, func_name, generic_test)
837 return klass
838
839
Sean Dague35a7caf2013-05-10 10:38:22 -0400840def call_until_true(func, duration, sleep_for):
841 """
842 Call the given function until it returns True (and return True) or
843 until the specified duration (in seconds) elapses (and return
844 False).
845
846 :param func: A zero argument callable that returns True on success.
847 :param duration: The number of seconds for which to attempt a
848 successful call of the function.
849 :param sleep_for: The number of seconds to sleep after an unsuccessful
850 invocation of the function.
851 """
852 now = time.time()
853 timeout = now + duration
854 while now < timeout:
855 if func():
856 return True
Sean Dague35a7caf2013-05-10 10:38:22 -0400857 time.sleep(sleep_for)
858 now = time.time()
859 return False