blob: 935bb720ad3d1fff24533a4ebc10948c1b13c90b [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
Mitsuhiko Yamazaki46818aa2013-04-18 17:49:17 +090027from tempest.common import log as logging
Matthew Treinish3e046852013-07-23 16:00:24 -040028from tempest.common.utils.data_utils import rand_name
Attila Fazekasdc216422013-01-29 15:12:14 +010029from tempest import config
Matthew Treinish3e046852013-07-23 16:00:24 -040030from tempest import exceptions
Jay Pipes051075a2012-04-28 17:39:37 -040031from tempest import manager
32
33LOG = logging.getLogger(__name__)
34
Samuel Merritt0d499bc2013-06-19 12:08:23 -070035# All the successful HTTP status codes from RFC 2616
36HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206)
37
Jay Pipes051075a2012-04-28 17:39:37 -040038
Chris Yeoh55530bb2013-02-08 16:04:27 +103039def attr(*args, **kwargs):
40 """A decorator which applies the nose and testtools attr decorator
41
42 This decorator applies the nose attr decorator as well as the
43 the testtools.testcase.attr if it is in the list of attributes
Attila Fazekasb2902af2013-02-16 16:22:44 +010044 to testtools we want to apply.
45 """
Chris Yeoh55530bb2013-02-08 16:04:27 +103046
47 def decorator(f):
Giulio Fidente4946a052013-05-14 12:23:51 +020048 if 'type' in kwargs and isinstance(kwargs['type'], str):
49 f = testtools.testcase.attr(kwargs['type'])(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093050 if kwargs['type'] == 'smoke':
51 f = testtools.testcase.attr('gate')(f)
Giulio Fidente4946a052013-05-14 12:23:51 +020052 elif 'type' in kwargs and isinstance(kwargs['type'], list):
53 for attr in kwargs['type']:
54 f = testtools.testcase.attr(attr)(f)
Chris Yeohcf3fb7c2013-05-19 15:59:00 +093055 if attr == 'smoke':
56 f = testtools.testcase.attr('gate')(f)
Giulio Fidente4946a052013-05-14 12:23:51 +020057 return nose.plugins.attrib.attr(*args, **kwargs)(f)
Chris Yeoh55530bb2013-02-08 16:04:27 +103058
59 return decorator
60
61
Ian Wienand98c35f32013-07-23 20:34:23 +100062# there is a mis-match between nose and testtools for older pythons.
63# testtools will set skipException to be either
64# unittest.case.SkipTest, unittest2.case.SkipTest or an internal skip
65# exception, depending on what it can find. Python <2.7 doesn't have
66# unittest.case.SkipTest; so if unittest2 is not installed it falls
67# back to the internal class.
68#
69# The current nose skip plugin will decide to raise either
70# unittest.case.SkipTest or its own internal exception; it does not
71# look for unittest2 or the internal unittest exception. Thus we must
72# monkey-patch testtools.TestCase.skipException to be the exception
73# the nose skip plugin expects.
74#
75# However, with the switch to testr nose may not be available, so we
76# require you to opt-in to this fix with an environment variable.
77#
78# This is temporary until upstream nose starts looking for unittest2
79# as testtools does; we can then remove this and ensure unittest2 is
80# available for older pythons; then nose and testtools will agree
81# unittest2.case.SkipTest is the one-true skip test exception.
82#
83# https://review.openstack.org/#/c/33056
84# https://github.com/nose-devs/nose/pull/699
85if 'TEMPEST_PY26_NOSE_COMPAT' in os.environ:
86 try:
87 import unittest.case.SkipTest
88 # convince pep8 we're using the import...
89 if unittest.case.SkipTest:
90 pass
91 raise RuntimeError("You have unittest.case.SkipTest; "
92 "no need to override")
93 except ImportError:
94 LOG.info("Overriding skipException to nose SkipTest")
95 testtools.TestCase.skipException = nose.plugins.skip.SkipTest
96
97
Attila Fazekasdc216422013-01-29 15:12:14 +010098class BaseTestCase(testtools.TestCase,
99 testtools.testcase.WithAttributes,
100 testresources.ResourcedTestCase):
Attila Fazekasc43fec82013-04-09 23:17:52 +0200101
102 config = config.TempestConfig()
Attila Fazekasdc216422013-01-29 15:12:14 +0100103
Pavel Sedlák1053bd32013-04-16 16:47:40 +0200104 @classmethod
105 def setUpClass(cls):
106 if hasattr(super(BaseTestCase, cls), 'setUpClass'):
107 super(BaseTestCase, cls).setUpClass()
108
Matthew Treinish78561ad2013-07-26 11:41:56 -0400109 def setUp(cls):
110 super(BaseTestCase, cls).setUp()
111 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
112 try:
113 test_timeout = int(test_timeout)
114 except ValueError:
115 test_timeout = 0
116 if test_timeout > 0:
117 cls.useFixture(fixtures.Timeout(test_timeout, gentle=True))
118
119 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
120 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
121 stdout = cls.useFixture(fixtures.StringStream('stdout')).stream
122 cls.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
123 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
124 os.environ.get('OS_STDERR_CAPTURE') == '1'):
125 stderr = cls.useFixture(fixtures.StringStream('stderr')).stream
126 cls.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
127
Matthew Treinish3e046852013-07-23 16:00:24 -0400128 @classmethod
129 def _get_identity_admin_client(cls):
130 """
131 Returns an instance of the Identity Admin API client
132 """
133 os = clients.AdminManager(interface=cls._interface)
134 admin_client = os.identity_client
135 return admin_client
136
137 @classmethod
138 def _get_client_args(cls):
139
140 return (
141 cls.config,
142 cls.config.identity.admin_username,
143 cls.config.identity.admin_password,
144 cls.config.identity.uri
145 )
146
147 @classmethod
148 def _get_isolated_creds(cls, admin=False):
149 """
150 Creates a new set of user/tenant/password credentials for a
151 **regular** user of the Compute API so that a test case can
152 operate in an isolated tenant container.
153 """
154 admin_client = cls._get_identity_admin_client()
155 password = "pass"
156
157 while True:
158 try:
159 rand_name_root = rand_name(cls.__name__)
160 if cls.isolated_creds:
161 # Main user already created. Create the alt or admin one...
162 if admin:
163 rand_name_root += '-admin'
164 else:
165 rand_name_root += '-alt'
166 tenant_name = rand_name_root + "-tenant"
167 tenant_desc = tenant_name + "-desc"
168
169 resp, tenant = admin_client.create_tenant(
170 name=tenant_name, description=tenant_desc)
171 break
172 except exceptions.Duplicate:
173 if cls.config.compute.allow_tenant_reuse:
174 tenant = admin_client.get_tenant_by_name(tenant_name)
175 LOG.info('Re-using existing tenant %s', tenant)
176 break
177
178 while True:
179 try:
180 rand_name_root = rand_name(cls.__name__)
181 if cls.isolated_creds:
182 # Main user already created. Create the alt one...
183 rand_name_root += '-alt'
184 username = rand_name_root + "-user"
185 email = rand_name_root + "@example.com"
186 resp, user = admin_client.create_user(username,
187 password,
188 tenant['id'],
189 email)
190 break
191 except exceptions.Duplicate:
192 if cls.config.compute.allow_tenant_reuse:
193 user = admin_client.get_user_by_username(tenant['id'],
194 username)
195 LOG.info('Re-using existing user %s', user)
196 break
197 # Store the complete creds (including UUID ids...) for later
198 # but return just the username, tenant_name, password tuple
199 # that the various clients will use.
200 cls.isolated_creds.append((user, tenant))
201
202 # Assign admin role if this is for admin creds
203 if admin:
204 _, roles = admin_client.list_roles()
205 role = None
206 try:
207 _, roles = admin_client.list_roles()
208 role = next(r for r in roles if r['name'] == 'admin')
209 except StopIteration:
210 msg = "No admin role found"
211 raise exceptions.NotFound(msg)
212 admin_client.assign_user_role(tenant['id'], user['id'], role['id'])
213
214 return username, tenant_name, password
215
216 @classmethod
217 def _clear_isolated_creds(cls):
218 if not cls.isolated_creds:
219 return
220 admin_client = cls._get_identity_admin_client()
221
222 for user, tenant in cls.isolated_creds:
223 admin_client.delete_user(user['id'])
224 admin_client.delete_tenant(tenant['id'])
225
Attila Fazekasdc216422013-01-29 15:12:14 +0100226
Sean Dague35a7caf2013-05-10 10:38:22 -0400227def call_until_true(func, duration, sleep_for):
228 """
229 Call the given function until it returns True (and return True) or
230 until the specified duration (in seconds) elapses (and return
231 False).
232
233 :param func: A zero argument callable that returns True on success.
234 :param duration: The number of seconds for which to attempt a
235 successful call of the function.
236 :param sleep_for: The number of seconds to sleep after an unsuccessful
237 invocation of the function.
238 """
239 now = time.time()
240 timeout = now + duration
241 while now < timeout:
242 if func():
243 return True
244 LOG.debug("Sleeping for %d seconds", sleep_for)
245 time.sleep(sleep_for)
246 now = time.time()
247 return False
248
249
Attila Fazekasdc216422013-01-29 15:12:14 +0100250class TestCase(BaseTestCase):
Pavel Sedláka2b757c2013-02-25 18:16:04 +0100251 """Base test case class for all Tempest tests
Jay Pipes051075a2012-04-28 17:39:37 -0400252
253 Contains basic setup and convenience methods
254 """
Pavel Sedláka2b757c2013-02-25 18:16:04 +0100255
Jay Pipes051075a2012-04-28 17:39:37 -0400256 manager_class = None
257
258 @classmethod
259 def setUpClass(cls):
260 cls.manager = cls.manager_class()
Maru Newbydec13ec2012-08-30 11:19:17 -0700261 for attr_name in cls.manager.client_attr_names:
262 # Ensure that pre-existing class attributes won't be
263 # accidentally overriden.
264 assert not hasattr(cls, attr_name)
265 client = getattr(cls.manager, attr_name)
266 setattr(cls, attr_name, client)
Jay Pipes051075a2012-04-28 17:39:37 -0400267 cls.resource_keys = {}
Attila Fazekasdc216422013-01-29 15:12:14 +0100268 cls.os_resources = []
Jay Pipes051075a2012-04-28 17:39:37 -0400269
270 def set_resource(self, key, thing):
271 LOG.debug("Adding %r to shared resources of %s" %
272 (thing, self.__class__.__name__))
273 self.resource_keys[key] = thing
Attila Fazekasdc216422013-01-29 15:12:14 +0100274 self.os_resources.append(thing)
Jay Pipes051075a2012-04-28 17:39:37 -0400275
276 def get_resource(self, key):
277 return self.resource_keys[key]
278
279 def remove_resource(self, key):
280 thing = self.resource_keys[key]
Attila Fazekasdc216422013-01-29 15:12:14 +0100281 self.os_resources.remove(thing)
Jay Pipes051075a2012-04-28 17:39:37 -0400282 del self.resource_keys[key]
283
Sean Dague35a7caf2013-05-10 10:38:22 -0400284 def status_timeout(self, things, thing_id, expected_status):
285 """
286 Given a thing and an expected status, do a loop, sleeping
287 for a configurable amount of time, checking for the
288 expected status to show. At any time, if the returned
289 status of the thing is ERROR, fail out.
290 """
291 def check_status():
292 # python-novaclient has resources available to its client
293 # that all implement a get() method taking an identifier
294 # for the singular resource to retrieve.
295 thing = things.get(thing_id)
296 new_status = thing.status
297 if new_status == 'ERROR':
Joe Gordonb5e10cd2013-07-10 15:51:12 +0000298 self.fail("%s failed to get to expected status. "
Pavel Sedlák51ed3562013-04-26 12:36:08 +0200299 "In ERROR state."
300 % thing)
Sean Dague35a7caf2013-05-10 10:38:22 -0400301 elif new_status == expected_status:
302 return True # All good.
303 LOG.debug("Waiting for %s to get to %s status. "
304 "Currently in %s status",
305 thing, expected_status, new_status)
306 conf = config.TempestConfig()
307 if not call_until_true(check_status,
308 conf.compute.build_timeout,
309 conf.compute.build_interval):
310 self.fail("Timed out waiting for thing %s to become %s"
Pavel Sedlák51ed3562013-04-26 12:36:08 +0200311 % (thing_id, expected_status))
Matthew Treinishfbf40fd2013-04-09 11:10:58 -0400312
313
Jay Pipes051075a2012-04-28 17:39:37 -0400314class ComputeFuzzClientTest(TestCase):
315
316 """
317 Base test case class for OpenStack Compute API (Nova)
318 that uses the Tempest REST fuzz client libs for calling the API.
319 """
320
321 manager_class = manager.ComputeFuzzClientManager