diff --git a/.gitignore b/.gitignore index 4ed762e412..9355e87d91 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ .kong-venv *.pyc etc/config.ini +etc/storm.conf +etc/tempest.conf include/swift_objects/swift_small include/swift_objects/swift_medium include/swift_objects/swift_large *.log +*.swp diff --git a/README.rst b/README.rst index f00ce6c70c..abe3ff661f 100644 --- a/README.rst +++ b/README.rst @@ -1,25 +1,37 @@ :: -OpenStack integration test suite -================================ +Tempest - The OpenStack Integration Test Suite +============================================== This is a set of integration tests to be run against a live cluster. Quickstart ---------- -You're going to want to make your own config.ini file in the /etc/ directory, -it needs to point at your running cluster. +To run Tempest, you first need to create a configuration file that +will tell Tempest where to find the various OpenStack services and +other testing behaviour switches. -After that try commands such as:: +The easiest way to create a configuration file is to copy the sample +one in the ``etc/`` directory :: - run_tests.sh --nova - run_tests.sh --glance - run_tests.sh --swift - run_tests.sh --auth + $> cd $TEMPEST_ROOT_DIR + $> cp etc/storm.conf.sample etc/storm.conf +After that, open up the ``etc/storm.conf`` file and edit the +variables to fit your test environment. -Additional Info ---------------- +.. note:: -There are additional README files in the various subdirectories of this project. + If you have a running devstack environment, look at the + environment variables in your ``devstack/localrc`` file. + The ADMIN_PASSWORD variable should match the api_key value + in the storm.conf [nova] configuration section. In addition, + you will need to get the UUID identifier of the image that + devstack uploaded and set the image_ref value in the [environment] + section in the storm.conf to that image UUID. + +After setting up your configuration file, you can execute the set of +Tempest tests by using ``nosetests`` :: + + $> nosetests storm diff --git a/storm/common/rest_client.py b/storm/common/rest_client.py index 88f1fb4b63..170e5230d6 100644 --- a/storm/common/rest_client.py +++ b/storm/common/rest_client.py @@ -1,14 +1,15 @@ -from storm import exceptions -import httplib2 import json + +import httplib2 + +from storm import exceptions import storm.config class RestClient(object): - def __init__(self, user, key, auth_url, tenant_name=None): - self.config = storm.config.StormConfig() - + def __init__(self, config, user, key, auth_url, tenant_name=None): + self.config = config if self.config.env.authentication == 'keystone_v2': self.token, self.base_url = self.keystone_v2_auth(user, key, @@ -55,21 +56,24 @@ class RestClient(object): resp, body = self.http_obj.request(auth_url, 'POST', headers=headers, body=body) - try: - auth_data = json.loads(body)['access'] - token = auth_data['token']['id'] - endpoints = auth_data['serviceCatalog'][0]['endpoints'] - mgmt_url = endpoints[0]['publicURL'] + if resp.status == 200: + try: + auth_data = json.loads(body)['access'] + token = auth_data['token']['id'] + endpoints = auth_data['serviceCatalog'][0]['endpoints'] + mgmt_url = endpoints[0]['publicURL'] - #TODO (dwalleck): This is a horrible stopgap. - #Need to join strings more cleanly - temp = mgmt_url.rsplit('/') - service_url = temp[0] + '//' + temp[2] + '/' + temp[3] + '/' - management_url = service_url + tenant_name - return token, management_url - except KeyError: - print "Failed to authenticate user" - raise + #TODO (dwalleck): This is a horrible stopgap. + #Need to join strings more cleanly + temp = mgmt_url.rsplit('/') + service_url = temp[0] + '//' + temp[2] + '/' + temp[3] + '/' + management_url = service_url + tenant_name + return token, management_url + except Exception, e: + print "Failed to authenticate user: %s" % e + raise + elif resp.status == 401: + raise exceptions.AuthenticationFailure(user=user, password=api_key) def post(self, url, body, headers): return self.request('POST', url, headers, body) diff --git a/storm/config.py b/storm/config.py index 454f684944..42b9894c49 100644 --- a/storm/config.py +++ b/storm/config.py @@ -1,4 +1,8 @@ import ConfigParser +import logging +import os + +LOG = logging.getLogger(__name__) class NovaConfig(object): @@ -119,15 +123,24 @@ class EnvironmentConfig(object): class StormConfig(object): """Provides OpenStack configuration information.""" - _path = "etc/storm.conf" + def __init__(self, conf_dir, conf_file): + """ + Initialize a configuration from a conf directory and conf file. - def __init__(self, path=None): - """Initialize a configuration from a path.""" - self._conf = self.load_config(self._path) + :param conf_dir: Directory to look for config files + :param conf_file: Name of config file to use + """ + path = os.path.join(conf_dir, conf_file) + + if not os.path.exists(path): + msg = "Config file %(path)s not found" % locals() + raise RuntimeError(msg) + + self._conf = self.load_config(path) self.nova = NovaConfig(self._conf) self.env = EnvironmentConfig(self._conf) - def load_config(self, path=None): + def load_config(self, path): """Read configuration from given path and return a config object.""" config = ConfigParser.SafeConfigParser() config.read(path) diff --git a/storm/exceptions.py b/storm/exceptions.py index 93ffa9124f..9290cf7b78 100644 --- a/storm/exceptions.py +++ b/storm/exceptions.py @@ -16,3 +16,10 @@ class BadRequest(Exception): def __str__(self): return repr(self.message) + + +class AuthenticationFailure(Exception): + msg = ("Authentication with user %(user)s and password " + "%(password)s failed.") + def __init__(self, **kwargs): + self.message = self.msg % kwargs diff --git a/storm/openstack.py b/storm/openstack.py index 97c5b7d8ed..78f8c03219 100644 --- a/storm/openstack.py +++ b/storm/openstack.py @@ -1,3 +1,5 @@ +import os + from storm.services.nova.json.images_client import ImagesClient from storm.services.nova.json.flavors_client import FlavorsClient from storm.services.nova.json.servers_client import ServersClient @@ -7,38 +9,57 @@ import storm.config class Manager(object): + DEFAULT_CONFIG_DIR = os.path.join( + os.path.abspath( + os.path.dirname( + os.path.dirname(__file__))), + "etc") + + DEFAULT_CONFIG_FILE = "storm.conf" + def __init__(self): """ Top level manager for all Openstack APIs """ - - self.config = storm.config.StormConfig() + # Environment variables override defaults... + config_dir = os.environ.get('TEMPEST_CONFIG_DIR', + self.DEFAULT_CONFIG_DIR) + config_file = os.environ.get('TEMPEST_CONFIG', + self.DEFAULT_CONFIG_FILE) + self.config = storm.config.StormConfig(config_dir, config_file) self.auth_url = data_utils.build_url(self.config.nova.host, self.config.nova.port, self.config.nova.apiVer, self.config.nova.path) if self.config.env.authentication == 'keystone_v2': - self.servers_client = ServersClient(self.config.nova.username, + self.servers_client = ServersClient(self.config, + self.config.nova.username, self.config.nova.api_key, self.auth_url, self.config.nova.tenant_name) - self.flavors_client = FlavorsClient(self.config.nova.username, + self.flavors_client = FlavorsClient(self.config, + self.config.nova.username, self.config.nova.api_key, self.auth_url, self.config.nova.tenant_name) - self.images_client = ImagesClient(self.config.nova.username, + self.images_client = ImagesClient(self.config, + self.config.nova.username, self.config.nova.api_key, self.auth_url, self.config.nova.tenant_name) else: #Assuming basic/native authentication - self.servers_client = ServersClient(self.config.nova.username, + self.servers_client = ServersClient(self.config, + self.config.nova.username, self.config.nova.api_key, self.auth_url) - self.flavors_client = FlavorsClient(self.config.nova.username, + self.flavors_client = FlavorsClient(self.config, + self.config.nova.username, self.config.nova.api_key, self.auth_url) - self.images_client = ImagesClient(self.config.nova.username, + self.images_client = ImagesClient(self.config, + self.config.nova.username, + self.config.nova.auth_url, self.config.nova.api_key, self.auth_url) diff --git a/storm/services/nova/json/flavors_client.py b/storm/services/nova/json/flavors_client.py index 223644c075..d6526c8837 100644 --- a/storm/services/nova/json/flavors_client.py +++ b/storm/services/nova/json/flavors_client.py @@ -5,8 +5,9 @@ import time class FlavorsClient(object): - def __init__(self, username, key, auth_url, tenant_name=None): - self.client = rest_client.RestClient(username, key, + def __init__(self, config, username, key, auth_url, tenant_name=None): + self.config = config + self.client = rest_client.RestClient(config, username, key, auth_url, tenant_name) def list_flavors(self, params=None): diff --git a/storm/services/nova/json/images_client.py b/storm/services/nova/json/images_client.py index e8a1326786..59e926988a 100644 --- a/storm/services/nova/json/images_client.py +++ b/storm/services/nova/json/images_client.py @@ -6,10 +6,10 @@ import time class ImagesClient(object): - def __init__(self, username, key, auth_url, tenant_name=None): - self.client = rest_client.RestClient(username, key, + def __init__(self, config, username, key, auth_url, tenant_name=None): + self.config = config + self.client = rest_client.RestClient(config, username, key, auth_url, tenant_name) - self.config = storm.config.StormConfig() self.build_interval = self.config.nova.build_interval self.build_timeout = self.config.nova.build_timeout self.headers = {'Content-Type': 'application/json', diff --git a/storm/services/nova/json/servers_client.py b/storm/services/nova/json/servers_client.py index e65173fad9..b587ee04e9 100644 --- a/storm/services/nova/json/servers_client.py +++ b/storm/services/nova/json/servers_client.py @@ -7,10 +7,10 @@ import time class ServersClient(object): - def __init__(self, username, key, auth_url, tenant_name=None): - self.client = rest_client.RestClient(username, key, + def __init__(self, config, username, key, auth_url, tenant_name=None): + self.config = config + self.client = rest_client.RestClient(config, username, key, auth_url, tenant_name) - self.config = storm.config.StormConfig() self.build_interval = self.config.nova.build_interval self.build_timeout = self.config.nova.build_timeout self.headers = {'Content-Type': 'application/json', diff --git a/storm/tests/test_flavors.py b/storm/tests/test_flavors.py index 590bac36b0..515369384e 100644 --- a/storm/tests/test_flavors.py +++ b/storm/tests/test_flavors.py @@ -10,7 +10,7 @@ class FlavorsTest(unittest.TestCase): def setUpClass(cls): cls.os = openstack.Manager() cls.client = cls.os.flavors_client - cls.config = storm.config.StormConfig() + cls.config = cls.os.config cls.flavor_id = cls.config.env.flavor_ref @attr(type='smoke') diff --git a/storm/tests/test_image_metadata.py b/storm/tests/test_image_metadata.py index 263a7711dd..c8dff27233 100644 --- a/storm/tests/test_image_metadata.py +++ b/storm/tests/test_image_metadata.py @@ -12,7 +12,7 @@ class ImagesMetadataTest(unittest.TestCase): cls.os = openstack.Manager() cls.servers_client = cls.os.servers_client cls.client = cls.os.images_client - cls.config = storm.config.StormConfig() + cls.config = cls.os.config cls.image_ref = cls.config.env.image_ref cls.flavor_ref = cls.config.env.flavor_ref cls.ssh_timeout = cls.config.nova.ssh_timeout diff --git a/storm/tests/test_images.py b/storm/tests/test_images.py index 635dc62b03..e1349b71d0 100644 --- a/storm/tests/test_images.py +++ b/storm/tests/test_images.py @@ -4,18 +4,21 @@ from storm.common.utils.data_utils import rand_name import unittest2 as unittest import storm.config +# Some module-level skip conditions +create_image_enabled = False + class ImagesTest(unittest.TestCase): - create_image_enabled = storm.config.StormConfig().env.create_image_enabled @classmethod def setUpClass(cls): cls.os = openstack.Manager() cls.client = cls.os.images_client cls.servers_client = cls.os.servers_client - cls.config = storm.config.StormConfig() + cls.config = cls.os.config cls.image_ref = cls.config.env.image_ref cls.flavor_ref = cls.config.env.flavor_ref + create_image_enabled = cls.config.env.create_image_enabled def _parse_image_id(self, image_ref): temp = image_ref.rsplit('/') diff --git a/storm/tests/test_server_actions.py b/storm/tests/test_server_actions.py index d6ff11c4bb..d7c9db40f4 100644 --- a/storm/tests/test_server_actions.py +++ b/storm/tests/test_server_actions.py @@ -4,19 +4,21 @@ import unittest2 as unittest import storm.config from storm.common.utils.data_utils import rand_name +# Some module-level skip conditions +resize_available = False class ServerActionsTest(unittest.TestCase): - resize_available = storm.config.StormConfig().env.resize_available @classmethod def setUpClass(cls): cls.os = openstack.Manager() cls.client = cls.os.servers_client - cls.config = storm.config.StormConfig() + cls.config = cls.os.config cls.image_ref = cls.config.env.image_ref cls.image_ref_alt = cls.config.env.image_ref_alt cls.flavor_ref = cls.config.env.flavor_ref cls.flavor_ref_alt = cls.config.env.flavor_ref_alt + resize_available = cls.config.env.resize_available def setUp(self): self.name = rand_name('server') diff --git a/storm/tests/test_server_details.py b/storm/tests/test_server_details.py index a27d838c54..b042ea2302 100644 --- a/storm/tests/test_server_details.py +++ b/storm/tests/test_server_details.py @@ -11,7 +11,7 @@ class ServerDetailsTest(unittest.TestCase): def setUpClass(cls): cls.os = openstack.Manager() cls.client = cls.os.servers_client - cls.config = storm.config.StormConfig() + cls.config = cls.os.config cls.image_ref = cls.config.env.image_ref cls.flavor_ref = cls.config.env.flavor_ref cls.image_ref_alt = cls.config.env.image_ref_alt diff --git a/storm/tests/test_server_metadata.py b/storm/tests/test_server_metadata.py index 3b569f6b8a..397599aa33 100644 --- a/storm/tests/test_server_metadata.py +++ b/storm/tests/test_server_metadata.py @@ -11,7 +11,7 @@ class ServerMetadataTest(unittest.TestCase): def setUpClass(cls): cls.os = openstack.Manager() cls.client = cls.os.servers_client - cls.config = storm.config.StormConfig() + cls.config = cls.os.config cls.image_ref = cls.config.env.image_ref cls.flavor_ref = cls.config.env.flavor_ref diff --git a/storm/tests/test_servers.py b/storm/tests/test_servers.py index 8f4bae735a..fead6aa138 100644 --- a/storm/tests/test_servers.py +++ b/storm/tests/test_servers.py @@ -13,7 +13,7 @@ class ServersTest(unittest.TestCase): def setUpClass(cls): cls.os = openstack.Manager() cls.client = cls.os.servers_client - cls.config = storm.config.StormConfig() + cls.config = cls.os.config cls.image_ref = cls.config.env.image_ref cls.flavor_ref = cls.config.env.flavor_ref cls.ssh_timeout = cls.config.nova.ssh_timeout diff --git a/storm/tests/test_servers_negative.py b/storm/tests/test_servers_negative.py index 72de6c0de5..068ca5dd3c 100644 --- a/storm/tests/test_servers_negative.py +++ b/storm/tests/test_servers_negative.py @@ -14,7 +14,7 @@ class ServersNegativeTest(unittest.TestCase): def setUpClass(cls): cls.os = openstack.Manager() cls.client = cls.os.servers_client - cls.config = storm.config.StormConfig() + cls.config = cls.os.config cls.image_ref = cls.config.env.image_ref cls.flavor_ref = cls.config.env.flavor_ref cls.ssh_timeout = cls.config.nova.ssh_timeout