| # vim: tabstop=4 shiftwidth=4 softtabstop=4 |
| |
| # Copyright 2012 OpenStack, LLC |
| # All Rights Reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| # not use this file except in compliance with the License. You may obtain |
| # a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations |
| # under the License. |
| |
| import logging |
| import time |
| |
| import nose.plugins.attrib |
| import testresources |
| import testtools |
| |
| from tempest import config |
| from tempest import manager |
| |
| LOG = logging.getLogger(__name__) |
| |
| |
| def attr(*args, **kwargs): |
| """A decorator which applies the nose and testtools attr decorator |
| |
| This decorator applies the nose attr decorator as well as the |
| the testtools.testcase.attr if it is in the list of attributes |
| to testtools we want to apply. |
| """ |
| |
| def decorator(f): |
| testtool_attributes = ('smoke') |
| |
| if 'type' in kwargs and kwargs['type'] in testtool_attributes: |
| return nose.plugins.attrib.attr(*args, **kwargs)( |
| testtools.testcase.attr(kwargs['type'])(f)) |
| else: |
| return nose.plugins.attrib.attr(*args, **kwargs)(f) |
| |
| return decorator |
| |
| |
| class BaseTestCase(testtools.TestCase, |
| testtools.testcase.WithAttributes, |
| testresources.ResourcedTestCase): |
| def __init__(self, *args, **kwargs): |
| super(BaseTestCase, self).__init__(*args, **kwargs) |
| #NOTE(afazekas): inspection workaround |
| BaseTestCase.config = config.TempestConfig() |
| |
| @classmethod |
| def setUpClass(cls): |
| if hasattr(super(BaseTestCase, cls), 'setUpClass'): |
| super(BaseTestCase, cls).setUpClass() |
| |
| |
| class TestCase(BaseTestCase): |
| """Base test case class for all Tempest tests |
| |
| Contains basic setup and convenience methods |
| """ |
| |
| manager_class = None |
| |
| @classmethod |
| def setUpClass(cls): |
| cls.manager = cls.manager_class() |
| for attr_name in cls.manager.client_attr_names: |
| # Ensure that pre-existing class attributes won't be |
| # accidentally overriden. |
| assert not hasattr(cls, attr_name) |
| client = getattr(cls.manager, attr_name) |
| setattr(cls, attr_name, client) |
| cls.resource_keys = {} |
| cls.os_resources = [] |
| |
| def set_resource(self, key, thing): |
| LOG.debug("Adding %r to shared resources of %s" % |
| (thing, self.__class__.__name__)) |
| self.resource_keys[key] = thing |
| self.os_resources.append(thing) |
| |
| def get_resource(self, key): |
| return self.resource_keys[key] |
| |
| def remove_resource(self, key): |
| thing = self.resource_keys[key] |
| self.os_resources.remove(thing) |
| del self.resource_keys[key] |
| |
| |
| def call_until_true(func, duration, sleep_for): |
| """ |
| Call the given function until it returns True (and return True) or |
| until the specified duration (in seconds) elapses (and return |
| False). |
| |
| :param func: A zero argument callable that returns True on success. |
| :param duration: The number of seconds for which to attempt a successful |
| call of the function. |
| :param sleep_for: The number of seconds to sleep after an unsuccessful |
| invocation of the function. |
| """ |
| now = time.time() |
| timeout = now + duration |
| while now < timeout: |
| if func(): |
| return True |
| LOG.debug("Sleeping for %d seconds", sleep_for) |
| time.sleep(sleep_for) |
| now = time.time() |
| return False |
| |
| |
| def status_timeout(things, thing_id, expected_status): |
| """ |
| Given a thing and an expected status, do a loop, sleeping |
| for a configurable amount of time, checking for the |
| expected status to show. At any time, if the returned |
| status of the thing is ERROR, fail out. |
| """ |
| def check_status(): |
| # python-novaclient has resources available to its client |
| # that all implement a get() method taking an identifier |
| # for the singular resource to retrieve. |
| thing = things.get(thing_id) |
| new_status = thing.status |
| if new_status == 'ERROR': |
| self.fail("%s failed to get to expected status." |
| "In ERROR state." |
| % thing) |
| elif new_status == expected_status: |
| return True # All good. |
| LOG.debug("Waiting for %s to get to %s status. " |
| "Currently in %s status", |
| thing, expected_status, new_status) |
| conf = config.TempestConfig() |
| if not call_until_true(check_status, |
| conf.compute.build_timeout, |
| conf.compute.build_interval): |
| self.fail("Timed out waiting for thing %s to become %s" |
| % (thing_id, expected_status)) |
| |
| |
| class DefaultClientSmokeTest(TestCase): |
| |
| """ |
| Base smoke test case class that provides the default clients to |
| access the various OpenStack APIs. |
| |
| Smoke tests are tests that have the following characteristics: |
| |
| * Test basic operations of an API, typically in an order that |
| a regular user would perform those operations |
| * Test only the correct inputs and action paths -- no fuzz or |
| random input data is sent, only valid inputs. |
| * Use only the default client tool for calling an API |
| """ |
| |
| manager_class = manager.DefaultClientManager |
| |
| @classmethod |
| def tearDownClass(cls): |
| # NOTE(jaypipes): Because smoke tests are typically run in a specific |
| # order, and because test methods in smoke tests generally create |
| # resources in a particular order, we destroy resources in the reverse |
| # order in which resources are added to the smoke test class object |
| while cls.os_resources: |
| thing = cls.os_resources.pop() |
| LOG.debug("Deleting %r from shared resources of %s" % |
| (thing, cls.__name__)) |
| |
| try: |
| # OpenStack resources are assumed to have a delete() |
| # method which destroys the resource... |
| thing.delete() |
| except Exception as e: |
| # If the resource is already missing, mission accomplished. |
| if e.__class__.__name__ == 'NotFound': |
| continue |
| raise |
| |
| def is_deletion_complete(): |
| # Deletion testing is only required for objects whose |
| # existence cannot be checked via retrieval. |
| if isinstance(thing, dict): |
| return True |
| try: |
| thing.get() |
| except Exception as e: |
| # Clients are expected to return an exception |
| # called 'NotFound' if retrieval fails. |
| if e.__class__.__name__ == 'NotFound': |
| return True |
| raise |
| return False |
| |
| # Block until resource deletion has completed or timed-out |
| call_until_true(is_deletion_complete, 10, 1) |
| |
| |
| class ComputeFuzzClientTest(TestCase): |
| |
| """ |
| Base test case class for OpenStack Compute API (Nova) |
| that uses the Tempest REST fuzz client libs for calling the API. |
| """ |
| |
| manager_class = manager.ComputeFuzzClientManager |