blob: a9c8ba78bb465a344491fd5af472ee4d3ea139cb [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 Frittoli (andreaf)290b3e12015-10-08 10:25:02 +010035from tempest.common import credentials_factory as 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.
Ken'ichi Ohmichi2e2ee192015-11-19 09:48:27 +0000214
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100215 `setUpClass` and `tearDownClass` are defined here and cannot be overwritten
216 by subclasses (enforced via hacking rule T105).
217
218 Set-up is split in a series of steps (setup stages), which can be
219 overwritten by test classes. Set-up stages are:
220 - skip_checks
221 - setup_credentials
222 - setup_clients
223 - resource_setup
224
225 Tear-down is also split in a series of steps (teardown stages), which are
226 stacked for execution only if the corresponding setup stage had been
227 reached during the setup phase. Tear-down stages are:
Andrea Frittoli (andreaf)17209bb2015-05-22 10:16:57 -0700228 - clear_credentials (defined in the base test class)
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100229 - resource_cleanup
230 """
Attila Fazekasc43fec82013-04-09 23:17:52 +0200231
Attila Fazekasf86fa312013-07-30 19:56:39 +0200232 setUpClassCalled = False
Marc Koderer24eb89c2014-01-31 11:23:33 +0100233 _service = None
Attila Fazekasf86fa312013-07-30 19:56:39 +0200234
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000235 # NOTE(andreaf) credentials holds a list of the credentials to be allocated
Andrea Frittoli (andreaf)825b2d32015-04-08 20:58:01 +0100236 # at class setup time. Credential types can be 'primary', 'alt', 'admin' or
237 # a list of roles - the first element of the list being a label, and the
238 # rest the actual roles
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000239 credentials = []
nithya-ganesan222efd72015-01-22 12:20:27 +0000240 # Resources required to validate a server using ssh
241 validation_resources = {}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500242 network_resources = {}
243
Sean Dague2ef32ac2014-06-09 11:32:23 -0400244 # NOTE(sdague): log_format is defined inline here instead of using the oslo
245 # default because going through the config path recouples config to the
246 # stress tests too early, and depending on testr order will fail unit tests
247 log_format = ('%(asctime)s %(process)d %(levelname)-8s '
248 '[%(name)s] %(message)s')
249
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200250 @classmethod
251 def setUpClass(cls):
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100252 # It should never be overridden by descendants
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200253 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
254 super(BaseTestCase, cls).setUpClass()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200255 cls.setUpClassCalled = True
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100256 # Stack of (name, callable) to be invoked in reverse order at teardown
257 cls.teardowns = []
258 # All the configuration checks that may generate a skip
259 cls.skip_checks()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100260 try:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100261 # Allocation of all required credentials and client managers
Andrea Frittoli (andreaf)17209bb2015-05-22 10:16:57 -0700262 cls.teardowns.append(('credentials', cls.clear_credentials))
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100263 cls.setup_credentials()
264 # Shortcuts to clients
265 cls.setup_clients()
266 # Additional class-wide test resources
267 cls.teardowns.append(('resources', cls.resource_cleanup))
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100268 cls.resource_setup()
269 except Exception:
270 etype, value, trace = sys.exc_info()
Matthew Treinished2ad4f2014-12-23 15:18:32 -0500271 LOG.info("%s raised in %s.setUpClass. Invoking tearDownClass." % (
272 etype, cls.__name__))
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100273 cls.tearDownClass()
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100274 try:
Matthew Treinish843227d2015-04-23 10:17:17 -0400275 six.reraise(etype, value, trace)
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100276 finally:
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100277 del trace # to avoid circular refs
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200278
Attila Fazekasf86fa312013-07-30 19:56:39 +0200279 @classmethod
280 def tearDownClass(cls):
Attila Fazekas5d275302013-08-29 12:35:12 +0200281 at_exit_set.discard(cls)
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100282 # It should never be overridden by descendants
Attila Fazekasf86fa312013-07-30 19:56:39 +0200283 if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
284 super(BaseTestCase, cls).tearDownClass()
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100285 # Save any existing exception, we always want to re-raise the original
286 # exception only
287 etype, value, trace = sys.exc_info()
288 # If there was no exception during setup we shall re-raise the first
289 # exception in teardown
290 re_raise = (etype is None)
291 while cls.teardowns:
292 name, teardown = cls.teardowns.pop()
293 # Catch any exception in tearDown so we can re-raise the original
294 # exception at the end
295 try:
296 teardown()
297 except Exception as te:
298 sys_exec_info = sys.exc_info()
299 tetype = sys_exec_info[0]
300 # TODO(andreaf): Till we have the ability to cleanup only
301 # resources that were successfully setup in resource_cleanup,
302 # log AttributeError as info instead of exception.
303 if tetype is AttributeError and name == 'resources':
304 LOG.info("tearDownClass of %s failed: %s" % (name, te))
305 else:
306 LOG.exception("teardown of %s failed: %s" % (name, te))
307 if not etype:
308 etype, value, trace = sys_exec_info
309 # If exceptions were raised during teardown, an not before, re-raise
310 # the first one
311 if re_raise and etype is not None:
312 try:
Matthew Treinish843227d2015-04-23 10:17:17 -0400313 six.reraise(etype, value, trace)
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100314 finally:
315 del trace # to avoid circular refs
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100316
317 @classmethod
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100318 def skip_checks(cls):
Ken'ichi Ohmichi2e2ee192015-11-19 09:48:27 +0000319 """Class level skip checks.
320
321 Subclasses verify in here all conditions that might prevent the
322 execution of the entire test class.
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100323 Checks implemented here may not make use API calls, and should rely on
324 configuration alone.
325 In general skip checks that require an API call are discouraged.
326 If one is really needed it may be implemented either in the
327 resource_setup or at test level.
328 """
Andrea Frittoli (andreaf)32d0de12015-10-09 14:43:53 +0100329 identity_version = cls.get_identity_version()
330 if 'admin' in cls.credentials and not credentials.is_admin_available(
331 identity_version=identity_version):
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000332 msg = "Missing Identity Admin API credentials in configuration."
333 raise cls.skipException(msg)
Andrea Frittoli (andreaf)32d0de12015-10-09 14:43:53 +0100334 if 'alt' in cls.credentials and not credentials.is_alt_available(
335 identity_version=identity_version):
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000336 msg = "Missing a 2nd set of API credentials in configuration."
337 raise cls.skipException(msg)
Andrea Frittoli (andreaf)41601412015-05-12 16:39:03 +0100338 if hasattr(cls, 'identity_version'):
339 if cls.identity_version == 'v2':
340 if not CONF.identity_feature_enabled.api_v2:
341 raise cls.skipException("Identity api v2 is not enabled")
342 elif cls.identity_version == 'v3':
343 if not CONF.identity_feature_enabled.api_v3:
344 raise cls.skipException("Identity api v3 is not enabled")
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100345
346 @classmethod
347 def setup_credentials(cls):
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000348 """Allocate credentials and the client managers from them.
Ken'ichi Ohmichi2e2ee192015-11-19 09:48:27 +0000349
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000350 A test class that requires network resources must override
351 setup_credentials and defined the required resources before super
352 is invoked.
353 """
354 for credentials_type in cls.credentials:
355 # This may raise an exception in case credentials are not available
356 # In that case we want to let the exception through and the test
357 # fail accordingly
Andrea Frittoli (andreaf)825b2d32015-04-08 20:58:01 +0100358 if isinstance(credentials_type, six.string_types):
359 manager = cls.get_client_manager(
360 credential_type=credentials_type)
361 setattr(cls, 'os_%s' % credentials_type, manager)
362 # Setup some common aliases
363 # TODO(andreaf) The aliases below are a temporary hack
364 # to avoid changing too much code in one patch. They should
365 # be removed eventually
366 if credentials_type == 'primary':
367 cls.os = cls.manager = cls.os_primary
368 if credentials_type == 'admin':
369 cls.os_adm = cls.admin_manager = cls.os_admin
370 if credentials_type == 'alt':
371 cls.alt_manager = cls.os_alt
372 elif isinstance(credentials_type, list):
373 manager = cls.get_client_manager(roles=credentials_type[1:],
374 force_new=True)
375 setattr(cls, 'os_roles_%s' % credentials_type[0], manager)
Andrea Frittolia5ddd552014-08-19 18:30:00 +0100376
377 @classmethod
378 def setup_clients(cls):
379 """Create links to the clients into the test object."""
380 # TODO(andreaf) There is a fair amount of code that could me moved from
381 # base / test classes in here. Ideally tests should be able to only
382 # specify which client is `client` and nothing else.
Andrea Frittoli73ee2472014-09-15 12:31:53 +0100383 pass
Attila Fazekasf86fa312013-07-30 19:56:39 +0200384
Emily Hugenbruch5bd4cbf2014-12-17 21:38:38 +0000385 @classmethod
386 def resource_setup(cls):
Ken'ichi Ohmichi2e2ee192015-11-19 09:48:27 +0000387 """Class level resource setup for test cases."""
nithya-ganesan222efd72015-01-22 12:20:27 +0000388 if hasattr(cls, "os"):
389 cls.validation_resources = vresources.create_validation_resources(
390 cls.os, cls.validation_resources)
391 else:
392 LOG.warn("Client manager not found, validation resources not"
393 " created")
Emily Hugenbruch5bd4cbf2014-12-17 21:38:38 +0000394
395 @classmethod
396 def resource_cleanup(cls):
397 """Class level resource cleanup for test cases.
Ken'ichi Ohmichi2e2ee192015-11-19 09:48:27 +0000398
Emily Hugenbruch5bd4cbf2014-12-17 21:38:38 +0000399 Resource cleanup must be able to handle the case of partially setup
400 resources, in case a failure during `resource_setup` should happen.
401 """
nithya-ganesan222efd72015-01-22 12:20:27 +0000402 if cls.validation_resources:
403 if hasattr(cls, "os"):
404 vresources.clear_validation_resources(cls.os,
405 cls.validation_resources)
406 cls.validation_resources = {}
407 else:
408 LOG.warn("Client manager not found, validation resources not"
409 " deleted")
Emily Hugenbruch5bd4cbf2014-12-17 21:38:38 +0000410
Attila Fazekasf86fa312013-07-30 19:56:39 +0200411 def setUp(self):
412 super(BaseTestCase, self).setUp()
413 if not self.setUpClassCalled:
414 raise RuntimeError("setUpClass does not calls the super's"
415 "setUpClass in the "
416 + self.__class__.__name__)
417 at_exit_set.add(self.__class__)
Matthew Treinish78561ad2013-07-26 11:41:56 -0400418 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
419 try:
420 test_timeout = int(test_timeout)
421 except ValueError:
422 test_timeout = 0
423 if test_timeout > 0:
Attila Fazekasf86fa312013-07-30 19:56:39 +0200424 self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400425
426 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
427 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200428 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
429 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400430 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
431 os.environ.get('OS_STDERR_CAPTURE') == '1'):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200432 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
433 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
Attila Fazekas31388072013-08-15 08:58:07 +0200434 if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
435 os.environ.get('OS_LOG_CAPTURE') != '0'):
Attila Fazekas31388072013-08-15 08:58:07 +0200436 self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
Sean Dague2ef32ac2014-06-09 11:32:23 -0400437 format=self.log_format,
Attila Fazekas90445be2013-10-24 16:46:03 +0200438 level=None))
Matthew Treinish78561ad2013-07-26 11:41:56 -0400439
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100440 @property
441 def credentials_provider(self):
442 return self._get_credentials_provider()
443
Jamie Lennox15350172015-08-17 10:54:25 +1000444 @property
445 def identity_utils(self):
446 """A client that abstracts v2 and v3 identity operations.
447
448 This can be used for creating and tearing down projects in tests. It
449 should not be used for testing identity features.
450 """
451 if CONF.identity.auth_version == 'v2':
452 client = self.os_admin.identity_client
453 else:
454 client = self.os_admin.identity_v3_client
455
456 try:
457 domain = client.auth_provider.credentials.project_domain_name
458 except AttributeError:
459 domain = 'Default'
460
461 return cred_client.get_creds_client(client, domain)
462
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100463 @classmethod
Andrea Frittoli (andreaf)32d0de12015-10-09 14:43:53 +0100464 def get_identity_version(cls):
465 """Returns the identity version used by the test class"""
466 identity_version = getattr(cls, 'identity_version', None)
467 return identity_version or CONF.identity.auth_version
468
469 @classmethod
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100470 def _get_credentials_provider(cls):
471 """Returns a credentials provider
472
473 If no credential provider exists yet creates one.
474 It uses self.identity_version if defined, or the configuration value
475 """
476 if (not hasattr(cls, '_creds_provider') or not cls._creds_provider or
477 not cls._creds_provider.name == cls.__name__):
478 force_tenant_isolation = getattr(cls, 'force_tenant_isolation',
479 False)
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100480
Andrea Frittoli (andreaf)17209bb2015-05-22 10:16:57 -0700481 cls._creds_provider = credentials.get_credentials_provider(
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100482 name=cls.__name__, network_resources=cls.network_resources,
483 force_tenant_isolation=force_tenant_isolation,
Andrea Frittoli (andreaf)32d0de12015-10-09 14:43:53 +0100484 identity_version=cls.get_identity_version())
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100485 return cls._creds_provider
486
Matthew Treinish3e046852013-07-23 16:00:24 -0400487 @classmethod
Andrea Frittoli (andreaf)41601412015-05-12 16:39:03 +0100488 def get_client_manager(cls, credential_type=None, roles=None,
489 force_new=None):
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100490 """Returns an OpenStack client manager
491
492 Returns an OpenStack client manager based on either credential_type
493 or a list of roles. If neither is specified, it defaults to
494 credential_type 'primary'
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100495 :param credential_type: string - primary, alt or admin
496 :param roles: list of roles
497
498 :returns the created client manager
499 :raises skipException: if the requested credentials are not available
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700500 """
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100501 if all([roles, credential_type]):
502 msg = "Cannot get credentials by type and roles at the same time"
503 raise ValueError(msg)
504 if not any([roles, credential_type]):
505 credential_type = 'primary'
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100506 cred_provider = cls._get_credentials_provider()
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100507 if roles:
508 for role in roles:
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100509 if not cred_provider.is_role_available(role):
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100510 skip_msg = (
511 "%s skipped because the configured credential provider"
512 " is not able to provide credentials with the %s role "
513 "assigned." % (cls.__name__, role))
514 raise cls.skipException(skip_msg)
515 params = dict(roles=roles)
516 if force_new is not None:
517 params.update(force_new=force_new)
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100518 creds = cred_provider.get_creds_by_roles(**params)
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000519 else:
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100520 credentials_method = 'get_%s_creds' % credential_type
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100521 if hasattr(cred_provider, credentials_method):
522 creds = getattr(cred_provider, credentials_method)()
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +0100523 else:
524 raise exceptions.InvalidCredentials(
525 "Invalid credentials type %s" % credential_type)
526 return clients.Manager(credentials=creds, service=cls._service)
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700527
528 @classmethod
Andrea Frittoli (andreaf)17209bb2015-05-22 10:16:57 -0700529 def clear_credentials(cls):
Ken'ichi Ohmichi2e2ee192015-11-19 09:48:27 +0000530 """Clears creds if set"""
Attila Fazekas5b0d9262015-05-20 10:17:39 +0200531 if hasattr(cls, '_creds_provider'):
Andrea Frittoli (andreaf)17209bb2015-05-22 10:16:57 -0700532 cls._creds_provider.clear_creds()
Ryan Hsu6c4bb3d2013-10-21 21:22:50 -0700533
534 @classmethod
nithya-ganesan222efd72015-01-22 12:20:27 +0000535 def set_validation_resources(cls, keypair=None, floating_ip=None,
536 security_group=None,
537 security_group_rules=None):
538 """Specify which ssh server validation resources should be created.
Ken'ichi Ohmichi2e2ee192015-11-19 09:48:27 +0000539
nithya-ganesan222efd72015-01-22 12:20:27 +0000540 Each of the argument must be set to either None, True or False, with
541 None - use default from config (security groups and security group
542 rules get created when set to None)
543 False - Do not create the validation resource
544 True - create the validation resource
545
546 @param keypair
547 @param security_group
548 @param security_group_rules
549 @param floating_ip
550 """
Matthew Treinishe5cca002015-05-11 15:36:50 -0400551 if not CONF.validation.run_validation:
552 return
nithya-ganesan222efd72015-01-22 12:20:27 +0000553 if keypair is None:
554 if CONF.validation.auth_method.lower() == "keypair":
555 keypair = True
556 else:
557 keypair = False
558 if floating_ip is None:
559 if CONF.validation.connect_method.lower() == "floating":
560 floating_ip = True
561 else:
562 floating_ip = False
563 if security_group is None:
Brandon Palmc6cc91d2015-08-19 13:20:21 -0500564 security_group = CONF.validation.security_group
nithya-ganesan222efd72015-01-22 12:20:27 +0000565 if security_group_rules is None:
Brandon Palmc6cc91d2015-08-19 13:20:21 -0500566 security_group_rules = CONF.validation.security_group_rules
567
nithya-ganesan222efd72015-01-22 12:20:27 +0000568 if not cls.validation_resources:
569 cls.validation_resources = {
570 'keypair': keypair,
571 'security_group': security_group,
572 'security_group_rules': security_group_rules,
573 'floating_ip': floating_ip}
574
575 @classmethod
Andrea Frittoli7d5ed592015-02-10 01:10:23 +0000576 def set_network_resources(cls, network=False, router=False, subnet=False,
Matthew Treinish9f756a02014-01-15 10:26:07 -0500577 dhcp=False):
578 """Specify which network resources should be created
579
580 @param network
581 @param router
582 @param subnet
583 @param dhcp
584 """
Salvatore Orlando5a337242014-01-15 22:49:22 +0000585 # network resources should be set only once from callers
586 # in order to ensure that even if it's called multiple times in
587 # a chain of overloaded methods, the attribute is set only
588 # in the leaf class
Andrea Frittoli7d5ed592015-02-10 01:10:23 +0000589 if not cls.network_resources:
590 cls.network_resources = {
Salvatore Orlando5a337242014-01-15 22:49:22 +0000591 'network': network,
592 'router': router,
593 'subnet': subnet,
594 'dhcp': dhcp}
Matthew Treinish9f756a02014-01-15 10:26:07 -0500595
Rohan Kanade9ce97df2013-12-10 18:59:35 +0530596 @classmethod
597 def get_tenant_network(cls):
598 """Get the network to be used in testing
599
600 :return: network dict including 'id' and 'name'
601 """
Andrea Frittoli (andreaf)17209bb2015-05-22 10:16:57 -0700602 # Make sure cred_provider exists and get a network client
John Warren9487a182015-09-14 18:12:56 -0400603 networks_client = cls.get_client_manager().compute_networks_client
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100604 cred_provider = cls._get_credentials_provider()
Andrea Frittoli700711e2015-04-02 11:39:38 +0100605 # In case of nova network, isolated tenants are not able to list the
606 # network configured in fixed_network_name, even if the can use it
607 # for their servers, so using an admin network client to validate
608 # the network name
609 if (not CONF.service_available.neutron and
Andrea Frittoli (andreaf)32d0de12015-10-09 14:43:53 +0100610 credentials.is_admin_available(
611 identity_version=cls.get_identity_version())):
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100612 admin_creds = cred_provider.get_admin_creds()
John Warren9487a182015-09-14 18:12:56 -0400613 admin_manager = clients.Manager(admin_creds)
614 networks_client = admin_manager.compute_networks_client
Andrea Frittoli (andreaf)940f8c62015-10-30 16:39:24 +0900615 return fixed_network.get_tenant_network(
616 cred_provider, networks_client, CONF.compute.fixed_network_name)
Rohan Kanade9ce97df2013-12-10 18:59:35 +0530617
Mark Maglana5885eb32014-02-28 10:57:34 -0800618 def assertEmpty(self, list, msg=None):
619 self.assertTrue(len(list) == 0, msg)
620
621 def assertNotEmpty(self, list, msg=None):
622 self.assertTrue(len(list) > 0, msg)
623
Attila Fazekasdc216422013-01-29 15:12:14 +0100624
Marc Koderer24eb89c2014-01-31 11:23:33 +0100625class NegativeAutoTest(BaseTestCase):
626
627 _resources = {}
628
629 @classmethod
630 def setUpClass(cls):
631 super(NegativeAutoTest, cls).setUpClass()
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000632 os = cls.get_client_manager(credential_type='primary')
Marc Koderer24eb89c2014-01-31 11:23:33 +0100633 cls.client = os.negative_client
634
635 @staticmethod
Marc Koderer674c8fc2014-03-17 09:45:04 +0100636 def load_tests(*args):
Ken'ichi Ohmichi2e2ee192015-11-19 09:48:27 +0000637 """Wrapper for testscenarios
638
639 To set the mandatory scenarios variable only in case a real test
640 loader is in place. Will be automatically called in case the variable
641 "load_tests" is set.
Marc Koderer674c8fc2014-03-17 09:45:04 +0100642 """
643 if getattr(args[0], 'suiteClass', None) is not None:
644 loader, standard_tests, pattern = args
645 else:
646 standard_tests, module, loader = args
647 for test in testtools.iterate_tests(standard_tests):
Marc Koderer4f44d722014-08-07 14:04:58 +0200648 schema = getattr(test, '_schema', None)
Marc Koderer3dd31052014-11-27 09:31:00 +0100649 if schema is not None:
Marc Koderer4f44d722014-08-07 14:04:58 +0200650 setattr(test, 'scenarios',
651 NegativeAutoTest.generate_scenario(schema))
Marc Koderer674c8fc2014-03-17 09:45:04 +0100652 return testscenarios.load_tests_apply_scenarios(*args)
653
654 @staticmethod
Marc Koderer4f44d722014-08-07 14:04:58 +0200655 def generate_scenario(description):
Ken'ichi Ohmichi2e2ee192015-11-19 09:48:27 +0000656 """Generates the test scenario list for a given description.
Marc Koderer24eb89c2014-01-31 11:23:33 +0100657
Marc Koderer4f44d722014-08-07 14:04:58 +0200658 :param description: A file or dictionary with the following entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100659 name (required) name for the api
660 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
661 url (required) the url to be appended to the catalog url with '%s'
662 for each resource mentioned
663 resources: (optional) A list of resource names such as "server",
664 "flavor", etc. with an element for each '%s' in the url. This
665 method will call self.get_resource for each element when
666 constructing the positive test case template so negative
667 subclasses are expected to return valid resource ids when
668 appropriate.
669 json-schema (optional) A valid json schema that will be used to
670 create invalid data for the api calls. For "GET" and "HEAD",
671 the data is used to generate query strings appended to the url,
672 otherwise for the body of the http call.
673 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100674 LOG.debug(description)
Marc Koderer674c8fc2014-03-17 09:45:04 +0100675 generator = importutils.import_class(
676 CONF.negative.test_generator)()
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100677 generator.validate_schema(description)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100678 schema = description.get("json-schema", None)
679 resources = description.get("resources", [])
680 scenario_list = []
Marc Koderer424c84f2014-02-06 17:02:19 +0100681 expected_result = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100682 for resource in resources:
Marc Koderer424c84f2014-02-06 17:02:19 +0100683 if isinstance(resource, dict):
684 expected_result = resource['expected_result']
685 resource = resource['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100686 LOG.debug("Add resource to test %s" % resource)
687 scn_name = "inv_res_%s" % (resource)
688 scenario_list.append((scn_name, {"resource": (resource,
Marc Koderer424c84f2014-02-06 17:02:19 +0100689 str(uuid.uuid4())),
690 "expected_result": expected_result
Marc Koderer24eb89c2014-01-31 11:23:33 +0100691 }))
692 if schema is not None:
Marc Kodererf07f5d12014-09-01 09:47:23 +0200693 for scenario in generator.generate_scenarios(schema):
694 scenario_list.append((scenario['_negtest_name'],
695 scenario))
Marc Koderer24eb89c2014-01-31 11:23:33 +0100696 LOG.debug(scenario_list)
697 return scenario_list
698
Marc Koderer4f44d722014-08-07 14:04:58 +0200699 def execute(self, description):
Ken'ichi Ohmichi2e2ee192015-11-19 09:48:27 +0000700 """Execute a http call
701
Marc Koderer24eb89c2014-01-31 11:23:33 +0100702 Execute a http call on an api that are expected to
703 result in client errors. First it uses invalid resources that are part
704 of the url, and then invalid data for queries and http request bodies.
705
Marc Koderer4f44d722014-08-07 14:04:58 +0200706 :param description: A json file or dictionary with the following
707 entries:
Marc Koderer24eb89c2014-01-31 11:23:33 +0100708 name (required) name for the api
709 http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
710 url (required) the url to be appended to the catalog url with '%s'
711 for each resource mentioned
712 resources: (optional) A list of resource names such as "server",
713 "flavor", etc. with an element for each '%s' in the url. This
714 method will call self.get_resource for each element when
715 constructing the positive test case template so negative
716 subclasses are expected to return valid resource ids when
717 appropriate.
718 json-schema (optional) A valid json schema that will be used to
719 create invalid data for the api calls. For "GET" and "HEAD",
720 the data is used to generate query strings appended to the url,
721 otherwise for the body of the http call.
722
723 """
Marc Koderer24eb89c2014-01-31 11:23:33 +0100724 LOG.info("Executing %s" % description["name"])
725 LOG.debug(description)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200726 generator = importutils.import_class(
727 CONF.negative.test_generator)()
728 schema = description.get("json-schema", None)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100729 method = description["http-method"]
730 url = description["url"]
Marc Kodererf07f5d12014-09-01 09:47:23 +0200731 expected_result = None
732 if "default_result_code" in description:
733 expected_result = description["default_result_code"]
Marc Koderer24eb89c2014-01-31 11:23:33 +0100734
735 resources = [self.get_resource(r) for
736 r in description.get("resources", [])]
737
738 if hasattr(self, "resource"):
739 # Note(mkoderer): The resources list already contains an invalid
740 # entry (see get_resource).
741 # We just send a valid json-schema with it
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100742 valid_schema = None
Marc Koderer24eb89c2014-01-31 11:23:33 +0100743 if schema:
Marc Koderer6ee82dc2014-02-17 10:26:29 +0100744 valid_schema = \
745 valid.ValidTestGenerator().generate_valid(schema)
746 new_url, body = self._http_arguments(valid_schema, url, method)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200747 elif hasattr(self, "_negtest_name"):
748 schema_under_test = \
749 valid.ValidTestGenerator().generate_valid(schema)
750 local_expected_result = \
751 generator.generate_payload(self, schema_under_test)
752 if local_expected_result is not None:
753 expected_result = local_expected_result
754 new_url, body = \
755 self._http_arguments(schema_under_test, url, method)
Marc Koderer1c247c82014-03-20 08:24:38 +0100756 else:
757 raise Exception("testscenarios are not active. Please make sure "
758 "that your test runner supports the load_tests "
759 "mechanism")
Marc Koderer424c84f2014-02-06 17:02:19 +0100760
Marc Kodererf857fda2014-03-05 15:58:00 +0100761 if "admin_client" in description and description["admin_client"]:
Andrea Frittoli (andreaf)32d0de12015-10-09 14:43:53 +0100762 if not credentials.is_admin_available(
763 identity_version=self.get_identity_version()):
David Kranzafecec02015-03-23 14:27:15 -0400764 msg = ("Missing Identity Admin API credentials in"
765 "configuration.")
766 raise self.skipException(msg)
Andrea Frittoli (andreaf)1f342412015-05-12 16:37:19 +0100767 creds = self.credentials_provider.get_admin_creds()
David Kranzafecec02015-03-23 14:27:15 -0400768 os_adm = clients.Manager(credentials=creds)
769 client = os_adm.negative_client
Marc Kodererf857fda2014-03-05 15:58:00 +0100770 else:
771 client = self.client
772 resp, resp_body = client.send_request(method, new_url,
773 resources, body=body)
Marc Kodererf07f5d12014-09-01 09:47:23 +0200774 self._check_negative_response(expected_result, resp.status, resp_body)
Marc Koderer24eb89c2014-01-31 11:23:33 +0100775
776 def _http_arguments(self, json_dict, url, method):
777 LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
778 if not json_dict:
779 return url, None
780 elif method in ["GET", "HEAD", "PUT", "DELETE"]:
781 return "%s?%s" % (url, urllib.urlencode(json_dict)), None
782 else:
783 return url, json.dumps(json_dict)
784
Marc Kodererf07f5d12014-09-01 09:47:23 +0200785 def _check_negative_response(self, expected_result, result, body):
Marc Koderer24eb89c2014-01-31 11:23:33 +0100786 self.assertTrue(result >= 400 and result < 500 and result != 413,
787 "Expected client error, got %s:%s" %
788 (result, body))
789 self.assertTrue(expected_result is None or expected_result == result,
790 "Expected %s, got %s:%s" %
791 (expected_result, result, body))
792
793 @classmethod
794 def set_resource(cls, name, resource):
Ken'ichi Ohmichi2e2ee192015-11-19 09:48:27 +0000795 """Register a resoruce for a test
796
Marc Koderer24eb89c2014-01-31 11:23:33 +0100797 This function can be used in setUpClass context to register a resoruce
798 for a test.
799
800 :param name: The name of the kind of resource such as "flavor", "role",
801 etc.
802 :resource: The id of the resource
803 """
804 cls._resources[name] = resource
805
806 def get_resource(self, name):
Ken'ichi Ohmichi2e2ee192015-11-19 09:48:27 +0000807 """Return a valid uuid for a type of resource.
808
809 If a real resource is needed as part of a url then this method should
810 return one. Otherwise it can return None.
Marc Koderer24eb89c2014-01-31 11:23:33 +0100811
812 :param name: The name of the kind of resource such as "flavor", "role",
813 etc.
814 """
Marc Koderer424c84f2014-02-06 17:02:19 +0100815 if isinstance(name, dict):
816 name = name['name']
Marc Koderer24eb89c2014-01-31 11:23:33 +0100817 if hasattr(self, "resource") and self.resource[0] == name:
818 LOG.debug("Return invalid resource (%s) value: %s" %
819 (self.resource[0], self.resource[1]))
820 return self.resource[1]
821 if name in self._resources:
822 return self._resources[name]
823 return None
824
825
Marc Kodererb2978da2014-03-26 13:45:43 +0100826def SimpleNegativeAutoTest(klass):
Ken'ichi Ohmichi2e2ee192015-11-19 09:48:27 +0000827 """This decorator registers a test function on basis of the class name."""
Sean Dague5e1bcd92015-04-27 09:08:36 -0400828 @attr(type=['negative'])
Marc Kodererb2978da2014-03-26 13:45:43 +0100829 def generic_test(self):
Marc Koderer4f44d722014-08-07 14:04:58 +0200830 if hasattr(self, '_schema'):
831 self.execute(self._schema)
Marc Kodererb2978da2014-03-26 13:45:43 +0100832
833 cn = klass.__name__
834 cn = cn.replace('JSON', '')
835 cn = cn.replace('Test', '')
836 # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
837 lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
838 func_name = 'test_%s' % lower_cn
839 setattr(klass, func_name, generic_test)
840 return klass
841
842
Sean Dague35a7caf2013-05-10 10:38:22 -0400843def call_until_true(func, duration, sleep_for):
Ken'ichi Ohmichi2e2ee192015-11-19 09:48:27 +0000844 """Call the given function until it returns True (and return True)
845
846 or until the specified duration (in seconds) elapses (and return False).
Sean Dague35a7caf2013-05-10 10:38:22 -0400847
848 :param func: A zero argument callable that returns True on success.
849 :param duration: The number of seconds for which to attempt a
850 successful call of the function.
851 :param sleep_for: The number of seconds to sleep after an unsuccessful
852 invocation of the function.
853 """
854 now = time.time()
855 timeout = now + duration
856 while now < timeout:
857 if func():
858 return True
Sean Dague35a7caf2013-05-10 10:38:22 -0400859 time.sleep(sleep_for)
860 now = time.time()
861 return False