blob: 6c304c362e6e2342246c4abb8835e95120b245ef [file] [log] [blame]
Jay Pipes051075a2012-04-28 17:39:37 -04001# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2012 OpenStack, LLC
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
Ian Wienand98c35f32013-07-23 20:34:23 +100018import os
Jay Pipes051075a2012-04-28 17:39:37 -040019import time
20
Matthew Treinish78561ad2013-07-26 11:41:56 -040021import fixtures
Chris Yeoh55530bb2013-02-08 16:04:27 +103022import nose.plugins.attrib
Attila Fazekasdc216422013-01-29 15:12:14 +010023import testresources
ivan-zhu1feeb382013-01-24 10:14:39 +080024import testtools
Jay Pipes051075a2012-04-28 17:39:37 -040025
Matthew Treinish3e046852013-07-23 16:00:24 -040026from tempest import clients
Matthew Treinish3e046852013-07-23 16:00:24 -040027from tempest.common.utils.data_utils import rand_name
Attila Fazekasdc216422013-01-29 15:12:14 +010028from tempest import config
Matthew Treinish3e046852013-07-23 16:00:24 -040029from tempest import exceptions
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040030from tempest.openstack.common import log as logging
Jay Pipes051075a2012-04-28 17:39:37 -040031
32LOG = logging.getLogger(__name__)
33
Samuel Merritt0d499bc2013-06-19 12:08:23 -070034# All the successful HTTP status codes from RFC 2616
35HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206)
36
Jay Pipes051075a2012-04-28 17:39:37 -040037
Chris Yeoh55530bb2013-02-08 16:04:27 +103038def attr(*args, **kwargs):
39 """A decorator which applies the nose and testtools attr decorator
40
41 This decorator applies the nose attr decorator as well as the
42 the testtools.testcase.attr if it is in the list of attributes
Attila Fazekasb2902af2013-02-16 16:22:44 +010043 to testtools we want to apply.
44 """
Chris Yeoh55530bb2013-02-08 16:04:27 +103045
46 def decorator(f):
Giulio Fidente4946a052013-05-14 12:23:51 +020047 if 'type' in kwargs and isinstance(kwargs['type'], str):
48 f = testtools.testcase.attr(kwargs['type'])(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093049 if kwargs['type'] == 'smoke':
50 f = testtools.testcase.attr('gate')(f)
Giulio Fidente4946a052013-05-14 12:23:51 +020051 elif 'type' in kwargs and isinstance(kwargs['type'], list):
52 for attr in kwargs['type']:
53 f = testtools.testcase.attr(attr)(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093054 if attr == 'smoke':
55 f = testtools.testcase.attr('gate')(f)
Giulio Fidente4946a052013-05-14 12:23:51 +020056 return nose.plugins.attrib.attr(*args, **kwargs)(f)
Chris Yeoh55530bb2013-02-08 16:04:27 +103057
58 return decorator
59
60
Ian Wienand98c35f32013-07-23 20:34:23 +100061# there is a mis-match between nose and testtools for older pythons.
62# testtools will set skipException to be either
63# unittest.case.SkipTest, unittest2.case.SkipTest or an internal skip
64# exception, depending on what it can find. Python <2.7 doesn't have
65# unittest.case.SkipTest; so if unittest2 is not installed it falls
66# back to the internal class.
67#
68# The current nose skip plugin will decide to raise either
69# unittest.case.SkipTest or its own internal exception; it does not
70# look for unittest2 or the internal unittest exception. Thus we must
71# monkey-patch testtools.TestCase.skipException to be the exception
72# the nose skip plugin expects.
73#
74# However, with the switch to testr nose may not be available, so we
75# require you to opt-in to this fix with an environment variable.
76#
77# This is temporary until upstream nose starts looking for unittest2
78# as testtools does; we can then remove this and ensure unittest2 is
79# available for older pythons; then nose and testtools will agree
80# unittest2.case.SkipTest is the one-true skip test exception.
81#
82# https://review.openstack.org/#/c/33056
83# https://github.com/nose-devs/nose/pull/699
84if 'TEMPEST_PY26_NOSE_COMPAT' in os.environ:
85 try:
86 import unittest.case.SkipTest
87 # convince pep8 we're using the import...
88 if unittest.case.SkipTest:
89 pass
90 raise RuntimeError("You have unittest.case.SkipTest; "
91 "no need to override")
92 except ImportError:
93 LOG.info("Overriding skipException to nose SkipTest")
94 testtools.TestCase.skipException = nose.plugins.skip.SkipTest
95
96
Attila Fazekasdc216422013-01-29 15:12:14 +010097class BaseTestCase(testtools.TestCase,
98 testtools.testcase.WithAttributes,
99 testresources.ResourcedTestCase):
Attila Fazekasc43fec82013-04-09 23:17:52 +0200100
101 config = config.TempestConfig()
Attila Fazekasdc216422013-01-29 15:12:14 +0100102
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200103 @classmethod
104 def setUpClass(cls):
105 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
106 super(BaseTestCase, cls).setUpClass()
107
Matthew Treinish78561ad2013-07-26 11:41:56 -0400108 def setUp(cls):
109 super(BaseTestCase, cls).setUp()
110 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
111 try:
112 test_timeout = int(test_timeout)
113 except ValueError:
114 test_timeout = 0
115 if test_timeout > 0:
116 cls.useFixture(fixtures.Timeout(test_timeout, gentle=True))
117
118 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
119 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
120 stdout = cls.useFixture(fixtures.StringStream('stdout')).stream
121 cls.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
122 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
123 os.environ.get('OS_STDERR_CAPTURE') == '1'):
124 stderr = cls.useFixture(fixtures.StringStream('stderr')).stream
125 cls.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
126
Matthew Treinish3e046852013-07-23 16:00:24 -0400127 @classmethod
128 def _get_identity_admin_client(cls):
129 """
130 Returns an instance of the Identity Admin API client
131 """
132 os = clients.AdminManager(interface=cls._interface)
133 admin_client = os.identity_client
134 return admin_client
135
136 @classmethod
137 def _get_client_args(cls):
138
139 return (
140 cls.config,
141 cls.config.identity.admin_username,
142 cls.config.identity.admin_password,
143 cls.config.identity.uri
144 )
145
146 @classmethod
147 def _get_isolated_creds(cls, admin=False):
148 """
149 Creates a new set of user/tenant/password credentials for a
150 **regular** user of the Compute API so that a test case can
151 operate in an isolated tenant container.
152 """
153 admin_client = cls._get_identity_admin_client()
154 password = "pass"
155
156 while True:
157 try:
158 rand_name_root = rand_name(cls.__name__)
159 if cls.isolated_creds:
160 # Main user already created. Create the alt or admin one...
161 if admin:
162 rand_name_root += '-admin'
163 else:
164 rand_name_root += '-alt'
165 tenant_name = rand_name_root + "-tenant"
166 tenant_desc = tenant_name + "-desc"
167
168 resp, tenant = admin_client.create_tenant(
169 name=tenant_name, description=tenant_desc)
170 break
171 except exceptions.Duplicate:
172 if cls.config.compute.allow_tenant_reuse:
173 tenant = admin_client.get_tenant_by_name(tenant_name)
174 LOG.info('Re-using existing tenant %s', tenant)
175 break
176
177 while True:
178 try:
179 rand_name_root = rand_name(cls.__name__)
180 if cls.isolated_creds:
181 # Main user already created. Create the alt one...
182 rand_name_root += '-alt'
183 username = rand_name_root + "-user"
184 email = rand_name_root + "@example.com"
185 resp, user = admin_client.create_user(username,
186 password,
187 tenant['id'],
188 email)
189 break
190 except exceptions.Duplicate:
191 if cls.config.compute.allow_tenant_reuse:
192 user = admin_client.get_user_by_username(tenant['id'],
193 username)
194 LOG.info('Re-using existing user %s', user)
195 break
196 # Store the complete creds (including UUID ids...) for later
197 # but return just the username, tenant_name, password tuple
198 # that the various clients will use.
199 cls.isolated_creds.append((user, tenant))
200
201 # Assign admin role if this is for admin creds
202 if admin:
203 _, roles = admin_client.list_roles()
204 role = None
205 try:
206 _, roles = admin_client.list_roles()
207 role = next(r for r in roles if r['name'] == 'admin')
208 except StopIteration:
209 msg = "No admin role found"
210 raise exceptions.NotFound(msg)
211 admin_client.assign_user_role(tenant['id'], user['id'], role['id'])
212
213 return username, tenant_name, password
214
215 @classmethod
216 def _clear_isolated_creds(cls):
217 if not cls.isolated_creds:
218 return
219 admin_client = cls._get_identity_admin_client()
220
221 for user, tenant in cls.isolated_creds:
222 admin_client.delete_user(user['id'])
223 admin_client.delete_tenant(tenant['id'])
224
Attila Fazekasdc216422013-01-29 15:12:14 +0100225
Sean Dague35a7caf2013-05-10 10:38:22 -0400226def call_until_true(func, duration, sleep_for):
227 """
228 Call the given function until it returns True (and return True) or
229 until the specified duration (in seconds) elapses (and return
230 False).
231
232 :param func: A zero argument callable that returns True on success.
233 :param duration: The number of seconds for which to attempt a
234 successful call of the function.
235 :param sleep_for: The number of seconds to sleep after an unsuccessful
236 invocation of the function.
237 """
238 now = time.time()
239 timeout = now + duration
240 while now < timeout:
241 if func():
242 return True
243 LOG.debug("Sleeping for %d seconds", sleep_for)
244 time.sleep(sleep_for)
245 now = time.time()
246 return False
247
248
Attila Fazekasdc216422013-01-29 15:12:14 +0100249class TestCase(BaseTestCase):
Pavel Sedláka2b757c2013-02-25 18:16:04 +0100250 """Base test case class for all Tempest tests
Jay Pipes051075a2012-04-28 17:39:37 -0400251
252 Contains basic setup and convenience methods
253 """
Pavel Sedláka2b757c2013-02-25 18:16:04 +0100254
Jay Pipes051075a2012-04-28 17:39:37 -0400255 manager_class = None
256
257 @classmethod
258 def setUpClass(cls):
259 cls.manager = cls.manager_class()
Maru Newbydec13ec2012-08-30 11:19:17 -0700260 for attr_name in cls.manager.client_attr_names:
261 # Ensure that pre-existing class attributes won't be
262 # accidentally overriden.
263 assert not hasattr(cls, attr_name)
264 client = getattr(cls.manager, attr_name)
265 setattr(cls, attr_name, client)
Jay Pipes051075a2012-04-28 17:39:37 -0400266 cls.resource_keys = {}
Attila Fazekasdc216422013-01-29 15:12:14 +0100267 cls.os_resources = []
Jay Pipes051075a2012-04-28 17:39:37 -0400268
Attila Fazekasd6d86292013-07-02 16:15:01 +0200269 @classmethod
270 def set_resource(cls, key, thing):
Jay Pipes051075a2012-04-28 17:39:37 -0400271 LOG.debug("Adding %r to shared resources of %s" %
Attila Fazekasd6d86292013-07-02 16:15:01 +0200272 (thing, cls.__name__))
273 cls.resource_keys[key] = thing
274 cls.os_resources.append(thing)
Jay Pipes051075a2012-04-28 17:39:37 -0400275
Attila Fazekasd6d86292013-07-02 16:15:01 +0200276 @classmethod
277 def get_resource(cls, key):
278 return cls.resource_keys[key]
Jay Pipes051075a2012-04-28 17:39:37 -0400279
Attila Fazekasd6d86292013-07-02 16:15:01 +0200280 @classmethod
281 def remove_resource(cls, key):
282 thing = cls.resource_keys[key]
283 cls.os_resources.remove(thing)
284 del cls.resource_keys[key]
Jay Pipes051075a2012-04-28 17:39:37 -0400285
Sean Dague35a7caf2013-05-10 10:38:22 -0400286 def status_timeout(self, things, thing_id, expected_status):
287 """
288 Given a thing and an expected status, do a loop, sleeping
289 for a configurable amount of time, checking for the
290 expected status to show. At any time, if the returned
291 status of the thing is ERROR, fail out.
292 """
293 def check_status():
294 # python-novaclient has resources available to its client
295 # that all implement a get() method taking an identifier
296 # for the singular resource to retrieve.
297 thing = things.get(thing_id)
298 new_status = thing.status
299 if new_status == 'ERROR':
Joe Gordonb5e10cd2013-07-10 15:51:12 +0000300 self.fail("%s failed to get to expected status. "
Pavel Sedlák51ed3562013-04-26 12:36:08 +0200301 "In ERROR state."
302 % thing)
Sean Dague35a7caf2013-05-10 10:38:22 -0400303 elif new_status == expected_status:
304 return True # All good.
305 LOG.debug("Waiting for %s to get to %s status. "
306 "Currently in %s status",
307 thing, expected_status, new_status)
308 conf = config.TempestConfig()
309 if not call_until_true(check_status,
310 conf.compute.build_timeout,
311 conf.compute.build_interval):
312 self.fail("Timed out waiting for thing %s to become %s"
Pavel Sedlák51ed3562013-04-26 12:36:08 +0200313 % (thing_id, expected_status))