Merge "Add python-quantumclient to pip-requires"
diff --git a/openstack-common.conf b/openstack-common.conf
new file mode 100644
index 0000000..a75279f
--- /dev/null
+++ b/openstack-common.conf
@@ -0,0 +1,7 @@
+[DEFAULT]
+
+# The list of modules to copy from openstack-common
+modules=setup,cfg,iniparser
+
+# The base module to hold the copy of openstack.common
+base=tempest
diff --git a/run_tests.sh b/run_tests.sh
index af33f7c..344f3ff 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -12,7 +12,8 @@
   echo "  -w, --whitebox           Only run whitebox tests"
   echo "  -p, --pep8               Just run pep8"
   echo "  -h, --help               Print this usage message"
-  echo "  -d. --debug              Debug this script -- set -o xtrace"
+  echo "  -d, --debug              Debug this script -- set -o xtrace"
+  echo "  -S, --stdout             Don't capture stdout"
   exit
 }
 
@@ -27,6 +28,7 @@
     -p|--pep8) let just_pep8=1;;
     -s|--smoke) noseargs="$noseargs --attr=type=smoke";;
     -w|--whitebox) noseargs="$noseargs --attr=type=whitebox";;
+    -S|--stdout) noseargs="$noseargs -s";;
     *) noseargs="$noseargs $1"
   esac
 }
@@ -76,7 +78,7 @@
   srcfiles+=" setup.py"
 
   ignore='--ignore=N4,E121,E122,E125,E126'
-  
+
   ${wrapper} python tools/hacking.py ${ignore} ${srcfiles}
 }
 
diff --git a/setup.py b/setup.py
index 2e046ea..1f071bb 100755
--- a/setup.py
+++ b/setup.py
@@ -19,10 +19,10 @@
 
 import setuptools
 
-from tempest.common import setup
+from tempest.openstack.common import setup as common_setup
 
-requires = setup.parse_requirements()
-depend_links = setup.parse_dependency_links()
+requires = common_setup.parse_requirements()
+depend_links = common_setup.parse_dependency_links()
 
 setuptools.setup(name='tempest',
                  version="2012.2",
@@ -40,7 +40,7 @@
                               'Programming Language :: Python',
                               'Programming Language :: Python :: 2',
                               'Programming Language :: Python :: 2.7', ],
-                 cmdclass=setup.get_cmdclass(),
+                 cmdclass=common_setup.get_cmdclass(),
                  packages=setuptools.find_packages(exclude=['bin']),
                  install_requires=requires,
                  dependency_links=depend_links,
diff --git a/stress/test_floating_ips.py b/stress/test_floating_ips.py
index f23dca2..9d89510 100755
--- a/stress/test_floating_ips.py
+++ b/stress/test_floating_ips.py
@@ -36,14 +36,13 @@
             return None
         floating_ip.change_pending = True
         timeout = int(kwargs.get('timeout', 60))
+        cli = manager.floating_ips_client
         if floating_ip.server_id is None:
             server = random.choice(self.server_ids)
             address = floating_ip.address
             self._logger.info('Adding %s to server %s' % (address, server))
-            resp, body =\
-            manager.floating_ips_client.associate_floating_ip_to_server(
-                                                        address,
-                                                        server)
+            resp, body = cli.associate_floating_ip_to_server(address,
+                                                             server)
             if resp.status != 202:
                 raise Exception("response: %s body: %s" % (resp, body))
             floating_ip.server_id = server
@@ -53,9 +52,8 @@
             server = floating_ip.server_id
             address = floating_ip.address
             self._logger.info('Removing %s from server %s' % (address, server))
-            resp, body =\
-            manager.floating_ips_client.disassociate_floating_ip_from_server(
-                                                           address, server)
+            resp, body = cli.disassociate_floating_ip_from_server(address,
+                                                                  server)
             if resp.status != 202:
                 raise Exception("response: %s body: %s" % (resp, body))
             return VerifyChangeFloatingIp(manager, floating_ip,
diff --git a/stress/test_server_actions.py b/stress/test_server_actions.py
index ca66dec..3b4e71c 100644
--- a/stress/test_server_actions.py
+++ b/stress/test_server_actions.py
@@ -56,8 +56,7 @@
         reboot_target = target[0]
         # It seems that doing a reboot when in reboot is an error.
         try:
-            response, body = manager.servers_client.reboot(
-                                                           reboot_target['id'],
+            response, body = manager.servers_client.reboot(reboot_target['id'],
                                                            _reboot_arg)
         except Duplicate:
             return
diff --git a/stress/tests/create_kill.py b/stress/tests/create_kill.py
index 26600de..2565723 100644
--- a/stress/tests/create_kill.py
+++ b/stress/tests/create_kill.py
@@ -17,14 +17,14 @@
 from stress.test_servers import *
 from stress.basher import BasherAction
 from stress.driver import *
-from tempest import openstack
+from tempest import clients
 
 choice_spec = [
     BasherAction(TestCreateVM(), 50),
     BasherAction(TestKillActiveVM(), 50)
 ]
 
-nova = openstack.Manager()
+nova = clients.Manager()
 
 bash_openstack(nova,
                choice_spec,
diff --git a/stress/tests/floating_ips.py b/stress/tests/floating_ips.py
index 8db06d4..d62e892 100755
--- a/stress/tests/floating_ips.py
+++ b/stress/tests/floating_ips.py
@@ -16,14 +16,14 @@
 from stress.basher import BasherAction
 from stress.driver import *
 from stress.test_floating_ips import TestChangeFloatingIp
-from tempest import openstack
+from tempest import clients
 
 
 choice_spec = [
     BasherAction(TestChangeFloatingIp(), 100)
 ]
 
-nova = openstack.Manager()
+nova = clients.Manager()
 
 bash_openstack(nova,
                choice_spec,
diff --git a/stress/tests/hard_reboots.py b/stress/tests/hard_reboots.py
index 324b133..f35c6de 100644
--- a/stress/tests/hard_reboots.py
+++ b/stress/tests/hard_reboots.py
@@ -18,7 +18,7 @@
 from stress.test_server_actions import *
 from stress.basher import BasherAction
 from stress.driver import *
-from tempest import openstack
+from tempest import clients
 
 choice_spec = [
     BasherAction(TestCreateVM(), 50),
@@ -26,7 +26,7 @@
                  kargs={'type': 'HARD'})
 ]
 
-nova = openstack.Manager()
+nova = clients.Manager()
 
 bash_openstack(nova,
                choice_spec,
diff --git a/stress/tests/user_script_sample.py b/stress/tests/user_script_sample.py
index 51270a7..04163e3 100644
--- a/stress/tests/user_script_sample.py
+++ b/stress/tests/user_script_sample.py
@@ -18,7 +18,7 @@
 from stress.test_servers import *
 from stress.basher import BasherAction
 from stress.driver import *
-from tempest import openstack
+from tempest import clients
 
 choice_spec = [
     BasherAction(TestCreateVM(), 50,
@@ -27,7 +27,7 @@
 ]
 
 
-nova = openstack.Manager()
+nova = clients.Manager()
 
 bash_openstack(nova,
                choice_spec,
diff --git a/tempest/openstack.py b/tempest/clients.py
similarity index 100%
rename from tempest/openstack.py
rename to tempest/clients.py
diff --git a/tempest/config.py b/tempest/config.py
index fc1bd74..2b0eb70 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -15,557 +15,445 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import ConfigParser
 import logging
 import os
 import sys
 
 from tempest.common.utils import data_utils
+from tempest.openstack.common import cfg
 
 LOG = logging.getLogger(__name__)
 
+identity_group = cfg.OptGroup(name='identity',
+                              title="Keystone Configuration Options")
 
-class BaseConfig(object):
+IdentityGroup = [
+    cfg.StrOpt('catalog_type',
+               default='identity',
+               help="Catalog type of the Identity service."),
+    cfg.StrOpt('host',
+               default="127.0.0.1",
+               help="Host IP for making Identity API requests."),
+    cfg.IntOpt('port',
+               default=8773,
+               help="Port for the Identity service."),
+    cfg.StrOpt('api_version',
+               default="v1.1",
+               help="Version of the Identity API"),
+    cfg.StrOpt('path',
+               default='/',
+               help="Path of API request"),
+    cfg.BoolOpt('use_ssl',
+                default=False,
+                help="Specifies if we are using https."),
+    cfg.StrOpt('strategy',
+               default='keystone',
+               help="Which auth method does the environment use? "
+                    "(basic|keystone)"),
+    cfg.StrOpt('region',
+               default=None,
+               help="The identity region name to use."),
+]
 
-    SECTION_NAME = None
 
-    def __init__(self, conf):
-        self.conf = conf
+def register_identity_opts(conf):
+    conf.register_group(identity_group)
+    for opt in IdentityGroup:
+        conf.register_opt(opt, group='identity')
 
-    def get(self, item_name, default_value=None):
-        try:
-            return self.conf.get(self.SECTION_NAME, item_name, raw=True)
-        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
-            return default_value
+    authurl = data_utils.build_url(conf.identity.host,
+                                   str(conf.identity.port),
+                                   conf.identity.api_version,
+                                   conf.identity.path,
+                                   use_ssl=conf.identity.use_ssl)
 
-    def getboolean(self, item_name, default_value=None):
-        try:
-            return self.conf.getboolean(self.SECTION_NAME, item_name)
-        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
-            return default_value
+    auth_url = cfg.StrOpt('auth_url',
+                          default=authurl,
+                          help="The Identity URL (derived)")
+    conf.register_opt(auth_url, group="identity")
 
 
-class IdentityConfig(BaseConfig):
+identity_admin_group = cfg.OptGroup(name='identity-admin',
+                                    title="Identity Admin Options")
 
-    """Provides configuration information for authenticating with Keystone."""
+IdentityAdminGroup = [
+    cfg.StrOpt('username',
+               default='admin',
+               help="Username to use for Identity Admin API requests"),
+    cfg.StrOpt('tenant_name',
+               default='admin',
+               help="Tenant name to use for Identity Admin API requests"),
+    cfg.StrOpt('password',
+               default='pass',
+               help="API key to use for Identity Admin API requests",
+               secret=True),
+]
 
-    SECTION_NAME = "identity"
 
-    @property
-    def catalog_type(self):
-        """Catalog type of the Identity service."""
-        return self.get("catalog_type", 'identity')
+def register_identity_admin_opts(conf):
+    conf.register_group(identity_admin_group)
+    for opt in IdentityAdminGroup:
+        conf.register_opt(opt, group='identity-admin')
 
-    @property
-    def host(self):
-        """Host IP for making Identity API requests."""
-        return self.get("host", "127.0.0.1")
 
-    @property
-    def port(self):
-        """Port for the Identity service."""
-        return self.get("port", "8773")
+compute_group = cfg.OptGroup(name='compute',
+                             title='Compute Service Options')
 
-    @property
-    def api_version(self):
-        """Version of the Identity API"""
-        return self.get("api_version", "v1.1")
+ComputeGroup = [
+    cfg.BoolOpt('allow_tenant_isolation',
+                default=False,
+                help="Allows test cases to create/destroy tenants and "
+                     "users. This option enables isolated test cases and "
+                     "better parallel execution, but also requires that "
+                     "OpenStack Identity API admin credentials are known."),
+    cfg.BoolOpt('allow_tenant_reuse',
+                default=True,
+                help="If allow_tenant_isolation is True and a tenant that "
+                     "would be created for a given test already exists (such "
+                     "as from a previously-failed run), re-use that tenant "
+                     "instead of failing because of the conflict. Note that "
+                     "this would result in the tenant being deleted at the "
+                     "end of a subsequent successful run."),
+    cfg.StrOpt('username',
+               default='demo',
+               help="Username to use for Nova API requests."),
+    cfg.StrOpt('tenant_name',
+               default='demo',
+               help="Tenant name to use for Nova API requests."),
+    cfg.StrOpt('password',
+               default='pass',
+               help="API key to use when authenticating.",
+               secret=True),
+    cfg.StrOpt('alt_username',
+               default=None,
+               help="Username of alternate user to use for Nova API "
+                    "requests."),
+    cfg.StrOpt('alt_tenant_name',
+               default=None,
+               help="Alternate user's Tenant name to use for Nova API "
+                    "requests."),
+    cfg.StrOpt('alt_password',
+               default=None,
+               help="API key to use when authenticating as alternate user.",
+               secret=True),
+    cfg.StrOpt('region',
+               default=None,
+               help="The compute region name to use."),
+    cfg.StrOpt('image_ref',
+               default="{$IMAGE_ID}",
+               help="Valid secondary image reference to be used in tests."),
+    cfg.StrOpt('image_ref_alt',
+               default="{$IMAGE_ID_ALT}",
+               help="Valid secondary image reference to be used in tests."),
+    cfg.IntOpt('flavor_ref',
+               default=1,
+               help="Valid primary flavor to use in tests."),
+    cfg.IntOpt('flavor_ref_alt',
+               default=2,
+               help='Valid secondary flavor to be used in tests.'),
+    cfg.BoolOpt('resize_available',
+                default=False,
+                help="Does the test environment support resizing?"),
+    cfg.BoolOpt('live_migration_available',
+                default=False,
+                help="Does the test environment support live migration "
+                     "available?"),
+    cfg.BoolOpt('use_block_migration_for_live_migration',
+                default=False,
+                help="Does the test environment use block devices for live "
+                     "migration"),
+    cfg.BoolOpt('change_password_available',
+                default=False,
+                help="Does the test environment support changing the admin "
+                     "password?"),
+    cfg.BoolOpt('create_image_enabled',
+                default=False,
+                help="Does the test environment support snapshots?"),
+    cfg.IntOpt('build_interval',
+               default=10,
+               help="Time in seconds between build status checks."),
+    cfg.IntOpt('build_timeout',
+               default=300,
+               help="Timeout in seconds to wait for an instance to build."),
+    cfg.BoolOpt('run_ssh',
+                default=False,
+                help="Does the test environment support snapshots?"),
+    cfg.StrOpt('ssh_user',
+               default='root',
+               help="User name used to authenticate to an instance."),
+    cfg.IntOpt('ssh_timeout',
+               default=300,
+               help="Timeout in seconds to wait for authentcation to "
+                    "succeed."),
+    cfg.StrOpt('network_for_ssh',
+               default='public',
+               help="Network used for SSH connections."),
+    cfg.IntOpt('ip_version_for_ssh',
+               default=4,
+               help="IP version used for SSH connections."),
+    cfg.StrOpt('catalog_type',
+               default='compute',
+               help="Catalog type of the Compute service."),
+    cfg.StrOpt('log_level',
+               default="ERROR",
+               help="Level for logging compute API calls."),
+    cfg.BoolOpt('whitebox_enabled',
+                default=False,
+                help="Does the test environment support whitebox tests for "
+                     "Compute?"),
+    cfg.StrOpt('db_uri',
+               default=None,
+               help="Connection string to the database of Compute service"),
+    cfg.StrOpt('source_dir',
+               default="/opt/stack/nova",
+               help="Path of nova source directory"),
+    cfg.StrOpt('config_path',
+               default='/etc/nova/nova.conf',
+               help="Path of nova configuration file"),
+    cfg.StrOpt('bin_dir',
+               default="/usr/local/bin/",
+               help="Directory containing nova binaries such as nova-manage"),
+    cfg.StrOpt('path_to_private_key',
+               default=None,
+               help="Path to a private key file for SSH access to remote "
+                    "hosts"),
+    cfg.BoolOpt('disk_config_enabled_override',
+                default=True,
+                help="If false, skip config tests regardless of the "
+                     "extension status"),
+]
 
-    @property
-    def path(self):
-        """Path of API request"""
-        return self.get("path", "/")
 
-    @property
-    def auth_url(self):
-        """The Identity URL (derived)"""
-        auth_url = data_utils.build_url(self.host,
-                                        self.port,
-                                        self.api_version,
-                                        self.path,
-                                        use_ssl=self.use_ssl)
-        return auth_url
+def register_compute_opts(conf):
+    conf.register_group(compute_group)
+    for opt in ComputeGroup:
+        conf.register_opt(opt, group='compute')
 
-    @property
-    def use_ssl(self):
-        """Specifies if we are using https."""
-        return self.get("use_ssl", 'false').lower() != 'false'
+compute_admin_group = cfg.OptGroup(name='compute-admin',
+                                   title="Compute Admin Options")
 
-    @property
-    def strategy(self):
-        """Which auth method does the environment use? (basic|keystone)"""
-        return self.get("strategy", 'keystone')
+ComputeAdminGroup = [
+    cfg.StrOpt('username',
+               default='admin',
+               help="Administrative Username to use for Nova API requests."),
+    cfg.StrOpt('tenant_name',
+               default='admin',
+               help="Administrative Tenant name to use for Nova API "
+                    "requests."),
+    cfg.StrOpt('password',
+               default='pass',
+               help="API key to use when authenticating as admin.",
+               secret=True),
+]
 
-    @property
-    def region(self):
-        """The identity region name to use."""
-        return self.get("region")
 
+def register_compute_admin_opts(conf):
+    conf.register_group(compute_admin_group)
+    for opt in ComputeAdminGroup:
+        conf.register_opt(opt, group='compute-admin')
 
-class IdentityAdminConfig(BaseConfig):
 
-    SECTION_NAME = "identity-admin"
+image_group = cfg.OptGroup(name='image',
+                           title="Image Service Options")
 
-    @property
-    def username(self):
-        """Username to use for Identity Admin API requests"""
-        return self.get("username", "admin")
+ImageGroup = [
+    cfg.StrOpt('host',
+               default='127.0.0.1',
+               help="Host IP for making Images API requests. Defaults to "
+                    "'127.0.0.1'."),
+    cfg.IntOpt('port',
+               default=9292,
+               help="Listen port of the Images service."),
+    cfg.StrOpt('api_version',
+               default='1',
+               help="Version of the API"),
+    cfg.StrOpt('username',
+               default='demo',
+               help="Username to use for Images API requests. Defaults to "
+                    "'demo'."),
+    cfg.StrOpt('password',
+               default='pass',
+               help="Password for user",
+               secret=True),
+    cfg.StrOpt('tenant_name',
+               default="demo",
+               help="Tenant to use for Images API requests. Defaults to "
+                    "'demo'."),
+]
 
-    @property
-    def tenant_name(self):
-        """Tenant name to use for Identity Admin API requests"""
-        return self.get("tenant_name", "admin")
 
-    @property
-    def password(self):
-        """API key to use for Identity Admin API requests"""
-        return self.get("password", "pass")
+def register_image_opts(conf):
+    conf.register_group(image_group)
+    for opt in ImageGroup:
+        conf.register_opt(opt, group='image')
 
 
-class ComputeConfig(BaseConfig):
+network_group = cfg.OptGroup(name='network',
+                             title='Network Service Options')
 
-    SECTION_NAME = "compute"
+NetworkGroup = [
+    cfg.StrOpt('catalog_type',
+               default='network',
+               help='Catalog type of the Quantum service.'),
+    cfg.StrOpt('api_version',
+               default="v1.1",
+               help="Version of Quantum API"),
+    cfg.StrOpt('username',
+               default="demo",
+               help="Username to use for Quantum API requests."),
+    cfg.StrOpt('tenant_name',
+               default="demo",
+               help="Tenant name to use for Quantum API requests."),
+    cfg.StrOpt('password',
+               default="pass",
+               help="API key to use when authenticating as admin.",
+               secret=True),
+    cfg.StrOpt('tenant_network_cidr',
+               default="10.100.0.0/16",
+               help="The cidr block to allocate tenant networks from"),
+    cfg.IntOpt('tenant_network_mask_bits',
+               default=29,
+               help="The mask bits for tenant networks"),
+    cfg.BoolOpt('tenant_networks_reachable',
+                default=False,
+                help="Whether tenant network connectivity should be "
+                     "evaluated directly"),
+    cfg.StrOpt('public_network_id',
+               default="",
+               help="Id of the public network that provides external "
+                    "connectivity"),
+    cfg.StrOpt('public_router_id',
+               default="",
+               help="Id of the public router that provides external "
+                    "connectivity"),
+]
 
-    @property
-    def allow_tenant_isolation(self):
-        """
-        Allows test cases to create/destroy tenants and users. This option
-        enables isolated test cases and better parallel execution,
-        but also requires that OpenStack Identity API admin credentials
-        are known.
-        """
-        return self.get("allow_tenant_isolation", 'false').lower() != 'false'
 
-    @property
-    def allow_tenant_reuse(self):
-        """
-        If allow_tenant_isolation is True and a tenant that would be created
-        for a given test already exists (such as from a previously-failed run),
-        re-use that tenant instead of failing because of the conflict. Note
-        that this would result in the tenant being deleted at the end of a
-        subsequent successful run.
-        """
-        return self.get("allow_tenant_reuse", 'true').lower() != 'false'
+def register_network_opts(conf):
+    conf.register_group(network_group)
+    for opt in NetworkGroup:
+        conf.register_opt(opt, group='network')
 
-    @property
-    def username(self):
-        """Username to use for Nova API requests."""
-        return self.get("username", "demo")
+network_admin_group = cfg.OptGroup(name='network-admin',
+                                   title="Network Admin Options")
 
-    @property
-    def tenant_name(self):
-        """Tenant name to use for Nova API requests."""
-        return self.get("tenant_name", "demo")
+NetworkAdminGroup = [
+    cfg.StrOpt('username',
+               default='admin',
+               help="Administrative Username to use for Quantum API "
+                    "requests."),
+    cfg.StrOpt('tenant_name',
+               default='admin',
+               help="Administrative Tenant name to use for Quantum API "
+                    "requests."),
+    cfg.StrOpt('password',
+               default='pass',
+               help="API key to use when authenticating as admin.",
+               secret=True),
+]
 
-    @property
-    def password(self):
-        """API key to use when authenticating."""
-        return self.get("password", "pass")
 
-    @property
-    def alt_username(self):
-        """Username of alternate user to use for Nova API requests."""
-        return self.get("alt_username")
+def register_network_admin_opts(conf):
+    conf.register_group(network_admin_group)
+    for opt in NetworkAdminGroup:
+        conf.register_opt(opt, group='network-admin')
 
-    @property
-    def alt_tenant_name(self):
-        """Alternate user's Tenant name to use for Nova API requests."""
-        return self.get("alt_tenant_name")
 
-    @property
-    def alt_password(self):
-        """API key to use when authenticating as alternate user."""
-        return self.get("alt_password")
+volume_group = cfg.OptGroup(name='volume',
+                            title='Block Storage Options')
 
-    @property
-    def region(self):
-        """The compute region name to use."""
-        return self.get("region")
+VolumeGroup = [
+    cfg.IntOpt('build_interval',
+               default=10,
+               help='Time in seconds between volume availability checks.'),
+    cfg.IntOpt('build_timeout',
+               default=300,
+               help='Timeout in seconds to wait for a volume to become'
+                    'available.'),
+    cfg.StrOpt('catalog_type',
+               default='Volume',
+               help="Catalog type of the Volume Service"),
+]
 
-    @property
-    def image_ref(self):
-        """Valid primary image to use in tests."""
-        return self.get("image_ref", "{$IMAGE_ID}")
 
-    @property
-    def image_ref_alt(self):
-        """Valid secondary image reference to be used in tests."""
-        return self.get("image_ref_alt", "{$IMAGE_ID_ALT}")
-
-    @property
-    def flavor_ref(self):
-        """Valid primary flavor to use in tests."""
-        return self.get("flavor_ref", 1)
-
-    @property
-    def flavor_ref_alt(self):
-        """Valid secondary flavor to be used in tests."""
-        return self.get("flavor_ref_alt", 2)
-
-    @property
-    def resize_available(self):
-        """Does the test environment support resizing?"""
-        return self.get("resize_available", 'false').lower() != 'false'
-
-    @property
-    def live_migration_available(self):
-        return self.get(
-            "live_migration_available", 'false').lower() == 'true'
-
-    @property
-    def use_block_migration_for_live_migration(self):
-        return self.get(
-            "use_block_migration_for_live_migration", 'false'
-        ).lower() == 'true'
-
-    @property
-    def change_password_available(self):
-        """Does the test environment support changing the admin password?"""
-        return self.get("change_password_available", 'false').lower() != \
-            'false'
-
-    @property
-    def create_image_enabled(self):
-        """Does the test environment support snapshots?"""
-        return self.get("create_image_enabled", 'false').lower() != 'false'
-
-    @property
-    def build_interval(self):
-        """Time in seconds between build status checks."""
-        return float(self.get("build_interval", 10))
-
-    @property
-    def build_timeout(self):
-        """Timeout in seconds to wait for an instance to build."""
-        return float(self.get("build_timeout", 300))
-
-    @property
-    def run_ssh(self):
-        """Does the test environment support snapshots?"""
-        return self.get("run_ssh", 'false').lower() != 'false'
-
-    @property
-    def ssh_user(self):
-        """User name used to authenticate to an instance."""
-        return self.get("ssh_user", "root")
-
-    @property
-    def ssh_timeout(self):
-        """Timeout in seconds to wait for authentcation to succeed."""
-        return float(self.get("ssh_timeout", 300))
-
-    @property
-    def network_for_ssh(self):
-        """Network used for SSH connections."""
-        return self.get("network_for_ssh", "public")
-
-    @property
-    def ip_version_for_ssh(self):
-        """IP version used for SSH connections."""
-        return int(self.get("ip_version_for_ssh", 4))
-
-    @property
-    def catalog_type(self):
-        """Catalog type of the Compute service."""
-        return self.get("catalog_type", 'compute')
-
-    @property
-    def log_level(self):
-        """Level for logging compute API calls."""
-        return self.get("log_level", 'ERROR')
-
-    @property
-    def whitebox_enabled(self):
-        """Does the test environment support whitebox tests for Compute?"""
-        return self.get("whitebox_enabled", 'false').lower() != 'false'
-
-    @property
-    def db_uri(self):
-        """Connection string to the database of Compute service"""
-        return self.get("db_uri", None)
-
-    @property
-    def source_dir(self):
-        """Path of nova source directory"""
-        return self.get("source_dir", "/opt/stack/nova")
-
-    @property
-    def config_path(self):
-        """Path of nova configuration file"""
-        return self.get("config_path", "/etc/nova/nova.conf")
-
-    @property
-    def bin_dir(self):
-        """Directory containing nova binaries such as nova-manage"""
-        return self.get("bin_dir", "/usr/local/bin/")
-
-    @property
-    def path_to_private_key(self):
-        """Path to a private key file for SSH access to remote hosts"""
-        return self.get("path_to_private_key")
-
-    @property
-    def disk_config_enabled_override(self):
-        """If false, skip config tests regardless of the extension status"""
-        return self.get("disk_config_enabled_override",
-                        'true').lower() == 'true'
-
-
-class ComputeAdminConfig(BaseConfig):
-
-    SECTION_NAME = "compute-admin"
-
-    @property
-    def username(self):
-        """Administrative Username to use for Nova API requests."""
-        return self.get("username", "admin")
-
-    @property
-    def tenant_name(self):
-        """Administrative Tenant name to use for Nova API requests."""
-        return self.get("tenant_name", "admin")
-
-    @property
-    def password(self):
-        """API key to use when authenticating as admin."""
-        return self.get("password", "pass")
-
-
-class ImagesConfig(BaseConfig):
-
-    """
-    Provides configuration information for connecting to an
-    OpenStack Images service.
-    """
-
-    SECTION_NAME = "image"
-
-    @property
-    def host(self):
-        """Host IP for making Images API requests. Defaults to '127.0.0.1'."""
-        return self.get("host", "127.0.0.1")
-
-    @property
-    def port(self):
-        """Listen port of the Images service."""
-        return int(self.get("port", "9292"))
-
-    @property
-    def api_version(self):
-        """Version of the API"""
-        return self.get("api_version", "1")
-
-    @property
-    def username(self):
-        """Username to use for Images API requests. Defaults to 'demo'."""
-        return self.get("username", "demo")
-
-    @property
-    def password(self):
-        """Password for user"""
-        return self.get("password", "pass")
-
-    @property
-    def tenant_name(self):
-        """Tenant to use for Images API requests. Defaults to 'demo'."""
-        return self.get("tenant_name", "demo")
-
-
-class NetworkConfig(BaseConfig):
-    """Provides configuration information for connecting to an OpenStack
-    Network Service.
-    """
-
-    SECTION_NAME = "network"
-
-    @property
-    def catalog_type(self):
-        """Catalog type of the Quantum service."""
-        return self.get("catalog_type", 'network')
-
-    @property
-    def api_version(self):
-        """Version of Quantum API"""
-        return self.get("api_version", "v1.1")
-
-    @property
-    def username(self):
-        """Username to use for Quantum API requests."""
-        return self.get("username", "demo")
-
-    @property
-    def tenant_name(self):
-        """Tenant name to use for Quantum API requests."""
-        return self.get("tenant_name", "demo")
-
-    @property
-    def password(self):
-        """API key to use when authenticating as admin."""
-        return self.get("password", "pass")
-
-    @property
-    def tenant_network_cidr(self):
-        """The cidr block to allocate tenant networks from"""
-        return self.get("tenant_network_cidr", "10.100.0.0/16")
-
-    @property
-    def tenant_network_mask_bits(self):
-        """The mask bits for tenant networks"""
-        return int(self.get("tenant_network_mask_bits", "29"))
-
-    @property
-    def tenant_networks_reachable(self):
-        """Whether tenant network connectivity should be evaluated directly"""
-        return (
-            self.get("tenant_networks_reachable", 'false').lower() != 'false'
-        )
-
-    @property
-    def public_network_id(self):
-        """Id of the public network that provides external connectivity"""
-        return self.get("public_network_id", "")
-
-    @property
-    def public_router_id(self):
-        """Id of the public router that provides external connectivity"""
-        return self.get("public_router_id", "")
-
-
-class NetworkAdminConfig(BaseConfig):
-
-    SECTION_NAME = "network-admin"
-
-    @property
-    def username(self):
-        """Administrative Username to use for Quantum API requests."""
-        return self.get("username", "admin")
-
-    @property
-    def tenant_name(self):
-        """Administrative Tenant name to use for Quantum API requests."""
-        return self.get("tenant_name", "admin")
-
-    @property
-    def password(self):
-        """API key to use when authenticating as admin."""
-        return self.get("password", "pass")
-
-
-class VolumeConfig(BaseConfig):
-    """Provides configuration information for connecting to an OpenStack Block
-    Storage Service.
-    """
-
-    SECTION_NAME = "volume"
-
-    @property
-    def build_interval(self):
-        """Time in seconds between volume availability checks."""
-        return float(self.get("build_interval", 10))
-
-    @property
-    def build_timeout(self):
-        """Timeout in seconds to wait for a volume to become available."""
-        return float(self.get("build_timeout", 300))
-
-    @property
-    def catalog_type(self):
-        """Catalog type of the Volume Service"""
-        return self.get("catalog_type", 'volume')
-
-
-class ObjectStorageConfig(BaseConfig):
-
-    SECTION_NAME = "object-storage"
-
-    @property
-    def catalog_type(self):
-        """Catalog type of the Object-Storage service."""
-        return self.get("catalog_type", 'object-store')
-
-    @property
-    def region(self):
-        """The object-store region name to use."""
-        return self.get("region")
-
-
-class BotoConfig(BaseConfig):
-    """Provides configuration information for connecting to EC2/S3."""
-    SECTION_NAME = "boto"
-
-    @property
-    def ec2_url(self):
-        """EC2 URL"""
-        return self.get("ec2_url", "http://localhost:8773/services/Cloud")
-
-    @property
-    def s3_url(self):
-        """S3 URL"""
-        return self.get("s3_url", "http://localhost:8080")
-
-    @property
-    def aws_secret(self):
-        """AWS Secret Key"""
-        return self.get("aws_secret")
-
-    @property
-    def aws_access(self):
-        """AWS Access Key"""
-        return self.get("aws_access")
-
-    @property
-    def aws_region(self):
-        """AWS Region"""
-        return self.get("aws_region", "RegionOne")
-
-    @property
-    def s3_materials_path(self):
-        return self.get("s3_materials_path",
-                        "/opt/stack/devstack/files/images/"
-                        "s3-materials/cirros-0.3.0")
+def register_volume_opts(conf):
+    conf.register_group(volume_group)
+    for opt in VolumeGroup:
+        conf.register_opt(opt, group='volume')
 
-    @property
-    def ari_manifest(self):
-        """ARI Ramdisk Image manifest"""
-        return self.get("ari_manifest",
-                        "cirros-0.3.0-x86_64-initrd.manifest.xml")
 
-    @property
-    def ami_manifest(self):
-        """AMI Machine Image manifest"""
-        return self.get("ami_manifest",
-                        "cirros-0.3.0-x86_64-blank.img.manifest.xml")
+object_storage_group = cfg.OptGroup(name='object-storage',
+                                    title='Object Storage Service Options')
 
-    @property
-    def aki_manifest(self):
-        """AKI Kernel Image manifest"""
-        return self.get("aki_manifest",
-                        "cirros-0.3.0-x86_64-vmlinuz.manifest.xml")
+ObjectStoreConfig = [
+    cfg.StrOpt('catalog_type',
+               default='object-store',
+               help="Catalog type of the Object-Storage service."),
+    cfg.StrOpt('region',
+               default=None,
+               help='The object-store region name to use.'),
+]
 
-    @property
-    def instance_type(self):
-        """Instance type"""
-        return self.get("Instance type", "m1.tiny")
 
-    @property
-    def http_socket_timeout(self):
-        """boto Http socket timeout"""
-        return self.get("http_socket_timeout", "3")
+def register_object_storage_opts(conf):
+    conf.register_group(object_storage_group)
+    for opt in ObjectStoreConfig:
+        conf.register_opt(opt, group='object-storage')
 
-    @property
-    def num_retries(self):
-        """boto num_retries on error"""
-        return self.get("num_retries", "1")
+boto_group = cfg.OptGroup(name='boto',
+                          title='EC2/S3 options')
+BotoConfig = [
+    cfg.StrOpt('ec2_url',
+               default="http://localhost:8773/services/Cloud",
+               help="EC2 URL"),
+    cfg.StrOpt('s3_url',
+               default="http://localhost:8080",
+               help="S3 URL"),
+    cfg.StrOpt('aws_secret',
+               default=None,
+               help="AWS Secret Key",
+               secret=True),
+    cfg.StrOpt('aws_access',
+               default=None,
+               help="AWS Access Key"),
+    cfg.StrOpt('aws_region',
+               default=None,
+               help="AWS Region"),
+    cfg.StrOpt('s3_materials_path',
+               default="/opt/stack/devstack/files/images/"
+                       "s3-materials/cirros-0.3.0",
+               help="S3 Materials Path"),
+    cfg.StrOpt('ari_manifest',
+               default="cirros-0.3.0-x86_64-initrd.manifest.xml",
+               help="ARI Ramdisk Image manifest"),
+    cfg.StrOpt('ami_manifest',
+               default="cirros-0.3.0-x86_64-blank.img.manifest.xml",
+               help="AMI Machine Image manifest"),
+    cfg.StrOpt('aki_manifest',
+               default="cirros-0.3.0-x86_64-vmlinuz.manifest.xml",
+               help="AKI Kernel Image manifest"),
+    cfg.StrOpt('instance_type',
+               default="m1.tiny",
+               help="Instance type"),
+    cfg.IntOpt('http_socket_timeout',
+               default=3,
+               help="boto Http socket timeout"),
+    cfg.IntOpt('num_retries',
+               default=1,
+               help="boto num_retries on error"),
+    cfg.IntOpt('build_timeout',
+               default=60,
+               help="Status Change Timeout"),
+    cfg.IntOpt('build_interval',
+               default=1,
+               help="Status Change Test Interval"),
+]
 
-    @property
-    def build_timeout(self):
-        """status change timeout"""
-        return float(self.get("build_timeout", "60"))
 
-    @property
-    def build_interval(self):
-        """status change test interval"""
-        return float(self.get("build_interval", 1))
+def register_boto_opts(conf):
+    conf.register_group(boto_group)
+    for opt in BotoConfig:
+        conf.register_opt(opt, group='boto')
 
 
 # TODO(jaypipes): Move this to a common utils (not data_utils...)
@@ -585,9 +473,7 @@
     """Provides OpenStack configuration information."""
 
     DEFAULT_CONFIG_DIR = os.path.join(
-        os.path.abspath(
-          os.path.dirname(
-            os.path.dirname(__file__))),
+        os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
         "etc")
 
     DEFAULT_CONFIG_FILE = "tempest.conf"
@@ -614,20 +500,25 @@
             print >> sys.stderr, RuntimeError(msg)
             sys.exit(os.EX_NOINPUT)
 
-        self._conf = self.load_config(path)
-        self.compute = ComputeConfig(self._conf)
-        self.compute_admin = ComputeAdminConfig(self._conf)
-        self.identity = IdentityConfig(self._conf)
-        self.identity_admin = IdentityAdminConfig(self._conf)
-        self.images = ImagesConfig(self._conf)
-        self.network = NetworkConfig(self._conf)
-        self.network_admin = NetworkAdminConfig(self._conf)
-        self.volume = VolumeConfig(self._conf)
-        self.object_storage = ObjectStorageConfig(self._conf)
-        self.boto = BotoConfig(self._conf)
+        cfg.CONF([], project='tempest', default_config_files=[path])
 
-    def load_config(self, path):
-        """Read configuration from given path and return a config object."""
-        config = ConfigParser.SafeConfigParser()
-        config.read(path)
-        return config
+        register_compute_opts(cfg.CONF)
+        register_identity_opts(cfg.CONF)
+        register_identity_admin_opts(cfg.CONF)
+        register_compute_admin_opts(cfg.CONF)
+        register_image_opts(cfg.CONF)
+        register_network_opts(cfg.CONF)
+        register_network_admin_opts(cfg.CONF)
+        register_volume_opts(cfg.CONF)
+        register_object_storage_opts(cfg.CONF)
+        register_boto_opts(cfg.CONF)
+        self.compute = cfg.CONF.compute
+        self.compute_admin = cfg.CONF['compute-admin']
+        self.identity = cfg.CONF.identity
+        self.identity_admin = cfg.CONF['identity-admin']
+        self.images = cfg.CONF.image
+        self.network = cfg.CONF.network
+        self.network_admin = cfg.CONF['network-admin']
+        self.volume = cfg.CONF.volume
+        self.object_storage = cfg.CONF['object-storage']
+        self.boto = cfg.CONF.boto
diff --git a/tempest/openstack/__init__.py b/tempest/openstack/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/openstack/__init__.py
diff --git a/tempest/openstack/common/__init__.py b/tempest/openstack/common/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/openstack/common/__init__.py
diff --git a/tempest/openstack/common/cfg.py b/tempest/openstack/common/cfg.py
new file mode 100644
index 0000000..25667a4
--- /dev/null
+++ b/tempest/openstack/common/cfg.py
@@ -0,0 +1,1787 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Red Hat, Inc.
+#
+#    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.
+
+r"""
+Configuration options which may be set on the command line or in config files.
+
+The schema for each option is defined using the Opt sub-classes, e.g.:
+
+::
+
+    common_opts = [
+        cfg.StrOpt('bind_host',
+                   default='0.0.0.0',
+                   help='IP address to listen on'),
+        cfg.IntOpt('bind_port',
+                   default=9292,
+                   help='Port number to listen on')
+    ]
+
+Options can be strings, integers, floats, booleans, lists or 'multi strings'::
+
+    enabled_apis_opt = cfg.ListOpt('enabled_apis',
+                                   default=['ec2', 'osapi_compute'],
+                                   help='List of APIs to enable by default')
+
+    DEFAULT_EXTENSIONS = [
+        'nova.api.openstack.compute.contrib.standard_extensions'
+    ]
+    osapi_compute_extension_opt = cfg.MultiStrOpt('osapi_compute_extension',
+                                                  default=DEFAULT_EXTENSIONS)
+
+Option schemas are registered with the config manager at runtime, but before
+the option is referenced::
+
+    class ExtensionManager(object):
+
+        enabled_apis_opt = cfg.ListOpt(...)
+
+        def __init__(self, conf):
+            self.conf = conf
+            self.conf.register_opt(enabled_apis_opt)
+            ...
+
+        def _load_extensions(self):
+            for ext_factory in self.conf.osapi_compute_extension:
+                ....
+
+A common usage pattern is for each option schema to be defined in the module or
+class which uses the option::
+
+    opts = ...
+
+    def add_common_opts(conf):
+        conf.register_opts(opts)
+
+    def get_bind_host(conf):
+        return conf.bind_host
+
+    def get_bind_port(conf):
+        return conf.bind_port
+
+An option may optionally be made available via the command line. Such options
+must registered with the config manager before the command line is parsed (for
+the purposes of --help and CLI arg validation)::
+
+    cli_opts = [
+        cfg.BoolOpt('verbose',
+                    short='v',
+                    default=False,
+                    help='Print more verbose output'),
+        cfg.BoolOpt('debug',
+                    short='d',
+                    default=False,
+                    help='Print debugging output'),
+    ]
+
+    def add_common_opts(conf):
+        conf.register_cli_opts(cli_opts)
+
+The config manager has two CLI options defined by default, --config-file
+and --config-dir::
+
+    class ConfigOpts(object):
+
+        def __call__(self, ...):
+
+            opts = [
+                MultiStrOpt('config-file',
+                        ...),
+                StrOpt('config-dir',
+                       ...),
+            ]
+
+            self.register_cli_opts(opts)
+
+Option values are parsed from any supplied config files using
+openstack.common.iniparser. If none are specified, a default set is used
+e.g. glance-api.conf and glance-common.conf::
+
+    glance-api.conf:
+      [DEFAULT]
+      bind_port = 9292
+
+    glance-common.conf:
+      [DEFAULT]
+      bind_host = 0.0.0.0
+
+Option values in config files override those on the command line. Config files
+are parsed in order, with values in later files overriding those in earlier
+files.
+
+The parsing of CLI args and config files is initiated by invoking the config
+manager e.g.::
+
+    conf = ConfigOpts()
+    conf.register_opt(BoolOpt('verbose', ...))
+    conf(sys.argv[1:])
+    if conf.verbose:
+        ...
+
+Options can be registered as belonging to a group::
+
+    rabbit_group = cfg.OptGroup(name='rabbit',
+                                title='RabbitMQ options')
+
+    rabbit_host_opt = cfg.StrOpt('host',
+                                 default='localhost',
+                                 help='IP/hostname to listen on'),
+    rabbit_port_opt = cfg.IntOpt('port',
+                                 default=5672,
+                                 help='Port number to listen on')
+
+    def register_rabbit_opts(conf):
+        conf.register_group(rabbit_group)
+        # options can be registered under a group in either of these ways:
+        conf.register_opt(rabbit_host_opt, group=rabbit_group)
+        conf.register_opt(rabbit_port_opt, group='rabbit')
+
+If it no group attributes are required other than the group name, the group
+need not be explicitly registered e.g.
+
+    def register_rabbit_opts(conf):
+        # The group will automatically be created, equivalent calling::
+        #   conf.register_group(OptGroup(name='rabbit'))
+        conf.register_opt(rabbit_port_opt, group='rabbit')
+
+If no group is specified, options belong to the 'DEFAULT' section of config
+files::
+
+    glance-api.conf:
+      [DEFAULT]
+      bind_port = 9292
+      ...
+
+      [rabbit]
+      host = localhost
+      port = 5672
+      use_ssl = False
+      userid = guest
+      password = guest
+      virtual_host = /
+
+Command-line options in a group are automatically prefixed with the
+group name::
+
+    --rabbit-host localhost --rabbit-port 9999
+
+Option values in the default group are referenced as attributes/properties on
+the config manager; groups are also attributes on the config manager, with
+attributes for each of the options associated with the group::
+
+    server.start(app, conf.bind_port, conf.bind_host, conf)
+
+    self.connection = kombu.connection.BrokerConnection(
+        hostname=conf.rabbit.host,
+        port=conf.rabbit.port,
+        ...)
+
+Option values may reference other values using PEP 292 string substitution::
+
+    opts = [
+        cfg.StrOpt('state_path',
+                   default=os.path.join(os.path.dirname(__file__), '../'),
+                   help='Top-level directory for maintaining nova state'),
+        cfg.StrOpt('sqlite_db',
+                   default='nova.sqlite',
+                   help='file name for sqlite'),
+        cfg.StrOpt('sql_connection',
+                   default='sqlite:///$state_path/$sqlite_db',
+                   help='connection string for sql database'),
+    ]
+
+Note that interpolation can be avoided by using '$$'.
+
+Options may be declared as required so that an error is raised if the user
+does not supply a value for the option.
+
+Options may be declared as secret so that their values are not leaked into
+log files::
+
+     opts = [
+        cfg.StrOpt('s3_store_access_key', secret=True),
+        cfg.StrOpt('s3_store_secret_key', secret=True),
+        ...
+     ]
+
+This module also contains a global instance of the CommonConfigOpts class
+in order to support a common usage pattern in OpenStack::
+
+    from tempest.openstack.common import cfg
+
+    opts = [
+        cfg.StrOpt('bind_host', default='0.0.0.0'),
+        cfg.IntOpt('bind_port', default=9292),
+    ]
+
+    CONF = cfg.CONF
+    CONF.register_opts(opts)
+
+    def start(server, app):
+        server.start(app, CONF.bind_port, CONF.bind_host)
+
+Positional command line arguments are supported via a 'positional' Opt
+constructor argument::
+
+    >>> CONF.register_cli_opt(MultiStrOpt('bar', positional=True))
+    True
+    >>> CONF(['a', 'b'])
+    >>> CONF.bar
+    ['a', 'b']
+
+It is also possible to use argparse "sub-parsers" to parse additional
+command line arguments using the SubCommandOpt class:
+
+    >>> def add_parsers(subparsers):
+    ...     list_action = subparsers.add_parser('list')
+    ...     list_action.add_argument('id')
+    ...
+    >>> CONF.register_cli_opt(SubCommandOpt('action', handler=add_parsers))
+    True
+    >>> CONF(['list', '10'])
+    >>> CONF.action.name, CONF.action.id
+    ('list', '10')
+
+"""
+
+import argparse
+import collections
+import copy
+import functools
+import glob
+import os
+import string
+import sys
+
+from tempest.openstack.common import iniparser
+
+
+class Error(Exception):
+    """Base class for cfg exceptions."""
+
+    def __init__(self, msg=None):
+        self.msg = msg
+
+    def __str__(self):
+        return self.msg
+
+
+class ArgsAlreadyParsedError(Error):
+    """Raised if a CLI opt is registered after parsing."""
+
+    def __str__(self):
+        ret = "arguments already parsed"
+        if self.msg:
+            ret += ": " + self.msg
+        return ret
+
+
+class NoSuchOptError(Error, AttributeError):
+    """Raised if an opt which doesn't exist is referenced."""
+
+    def __init__(self, opt_name, group=None):
+        self.opt_name = opt_name
+        self.group = group
+
+    def __str__(self):
+        if self.group is None:
+            return "no such option: %s" % self.opt_name
+        else:
+            return "no such option in group %s: %s" % (self.group.name,
+                                                       self.opt_name)
+
+
+class NoSuchGroupError(Error):
+    """Raised if a group which doesn't exist is referenced."""
+
+    def __init__(self, group_name):
+        self.group_name = group_name
+
+    def __str__(self):
+        return "no such group: %s" % self.group_name
+
+
+class DuplicateOptError(Error):
+    """Raised if multiple opts with the same name are registered."""
+
+    def __init__(self, opt_name):
+        self.opt_name = opt_name
+
+    def __str__(self):
+        return "duplicate option: %s" % self.opt_name
+
+
+class RequiredOptError(Error):
+    """Raised if an option is required but no value is supplied by the user."""
+
+    def __init__(self, opt_name, group=None):
+        self.opt_name = opt_name
+        self.group = group
+
+    def __str__(self):
+        if self.group is None:
+            return "value required for option: %s" % self.opt_name
+        else:
+            return "value required for option: %s.%s" % (self.group.name,
+                                                         self.opt_name)
+
+
+class TemplateSubstitutionError(Error):
+    """Raised if an error occurs substituting a variable in an opt value."""
+
+    def __str__(self):
+        return "template substitution error: %s" % self.msg
+
+
+class ConfigFilesNotFoundError(Error):
+    """Raised if one or more config files are not found."""
+
+    def __init__(self, config_files):
+        self.config_files = config_files
+
+    def __str__(self):
+        return ('Failed to read some config files: %s' %
+                string.join(self.config_files, ','))
+
+
+class ConfigFileParseError(Error):
+    """Raised if there is an error parsing a config file."""
+
+    def __init__(self, config_file, msg):
+        self.config_file = config_file
+        self.msg = msg
+
+    def __str__(self):
+        return 'Failed to parse %s: %s' % (self.config_file, self.msg)
+
+
+class ConfigFileValueError(Error):
+    """Raised if a config file value does not match its opt type."""
+    pass
+
+
+def _fixpath(p):
+    """Apply tilde expansion and absolutization to a path."""
+    return os.path.abspath(os.path.expanduser(p))
+
+
+def _get_config_dirs(project=None):
+    """Return a list of directors where config files may be located.
+
+    :param project: an optional project name
+
+    If a project is specified, following directories are returned::
+
+      ~/.${project}/
+      ~/
+      /etc/${project}/
+      /etc/
+
+    Otherwise, these directories::
+
+      ~/
+      /etc/
+    """
+    cfg_dirs = [
+        _fixpath(os.path.join('~', '.' + project)) if project else None,
+        _fixpath('~'),
+        os.path.join('/etc', project) if project else None,
+        '/etc'
+    ]
+
+    return filter(bool, cfg_dirs)
+
+
+def _search_dirs(dirs, basename, extension=""):
+    """Search a list of directories for a given filename.
+
+    Iterator over the supplied directories, returning the first file
+    found with the supplied name and extension.
+
+    :param dirs: a list of directories
+    :param basename: the filename, e.g. 'glance-api'
+    :param extension: the file extension, e.g. '.conf'
+    :returns: the path to a matching file, or None
+    """
+    for d in dirs:
+        path = os.path.join(d, '%s%s' % (basename, extension))
+        if os.path.exists(path):
+            return path
+
+
+def find_config_files(project=None, prog=None, extension='.conf'):
+    """Return a list of default configuration files.
+
+    :param project: an optional project name
+    :param prog: the program name, defaulting to the basename of sys.argv[0]
+    :param extension: the type of the config file
+
+    We default to two config files: [${project}.conf, ${prog}.conf]
+
+    And we look for those config files in the following directories::
+
+      ~/.${project}/
+      ~/
+      /etc/${project}/
+      /etc/
+
+    We return an absolute path for (at most) one of each the default config
+    files, for the topmost directory it exists in.
+
+    For example, if project=foo, prog=bar and /etc/foo/foo.conf, /etc/bar.conf
+    and ~/.foo/bar.conf all exist, then we return ['/etc/foo/foo.conf',
+    '~/.foo/bar.conf']
+
+    If no project name is supplied, we only look for ${prog.conf}.
+    """
+    if prog is None:
+        prog = os.path.basename(sys.argv[0])
+
+    cfg_dirs = _get_config_dirs(project)
+
+    config_files = []
+    if project:
+        config_files.append(_search_dirs(cfg_dirs, project, extension))
+    config_files.append(_search_dirs(cfg_dirs, prog, extension))
+
+    return filter(bool, config_files)
+
+
+def _is_opt_registered(opts, opt):
+    """Check whether an opt with the same name is already registered.
+
+    The same opt may be registered multiple times, with only the first
+    registration having any effect. However, it is an error to attempt
+    to register a different opt with the same name.
+
+    :param opts: the set of opts already registered
+    :param opt: the opt to be registered
+    :returns: True if the opt was previously registered, False otherwise
+    :raises: DuplicateOptError if a naming conflict is detected
+    """
+    if opt.dest in opts:
+        if opts[opt.dest]['opt'] != opt:
+            raise DuplicateOptError(opt.name)
+        return True
+    else:
+        return False
+
+
+def set_defaults(opts, **kwargs):
+    for opt in opts:
+        if opt.dest in kwargs:
+            opt.default = kwargs[opt.dest]
+            break
+
+
+class Opt(object):
+
+    """Base class for all configuration options.
+
+    An Opt object has no public methods, but has a number of public string
+    properties:
+
+      name:
+        the name of the option, which may include hyphens
+      dest:
+        the (hyphen-less) ConfigOpts property which contains the option value
+      short:
+        a single character CLI option name
+      default:
+        the default value of the option
+      positional:
+        True if the option is a positional CLI argument
+      metavar:
+        the name shown as the argument to a CLI option in --help output
+      help:
+        an string explaining how the options value is used
+    """
+    multi = False
+
+    def __init__(self, name, dest=None, short=None, default=None,
+                 positional=False, metavar=None, help=None,
+                 secret=False, required=False, deprecated_name=None):
+        """Construct an Opt object.
+
+        The only required parameter is the option's name. However, it is
+        common to also supply a default and help string for all options.
+
+        :param name: the option's name
+        :param dest: the name of the corresponding ConfigOpts property
+        :param short: a single character CLI option name
+        :param default: the default value of the option
+        :param positional: True if the option is a positional CLI argument
+        :param metavar: the option argument to show in --help
+        :param help: an explanation of how the option is used
+        :param secret: true iff the value should be obfuscated in log output
+        :param required: true iff a value must be supplied for this option
+        :param deprecated_name: deprecated name option.  Acts like an alias
+        """
+        self.name = name
+        if dest is None:
+            self.dest = self.name.replace('-', '_')
+        else:
+            self.dest = dest
+        self.short = short
+        self.default = default
+        self.positional = positional
+        self.metavar = metavar
+        self.help = help
+        self.secret = secret
+        self.required = required
+        if deprecated_name is not None:
+            self.deprecated_name = deprecated_name.replace('-', '_')
+        else:
+            self.deprecated_name = None
+
+    def __ne__(self, another):
+        return vars(self) != vars(another)
+
+    def _get_from_config_parser(self, cparser, section):
+        """Retrieves the option value from a MultiConfigParser object.
+
+        This is the method ConfigOpts uses to look up the option value from
+        config files. Most opt types override this method in order to perform
+        type appropriate conversion of the returned value.
+
+        :param cparser: a ConfigParser object
+        :param section: a section name
+        """
+        return self._cparser_get_with_deprecated(cparser, section)
+
+    def _cparser_get_with_deprecated(self, cparser, section):
+        """If cannot find option as dest try deprecated_name alias."""
+        if self.deprecated_name is not None:
+            return cparser.get(section, [self.dest, self.deprecated_name])
+        return cparser.get(section, [self.dest])
+
+    def _add_to_cli(self, parser, group=None):
+        """Makes the option available in the command line interface.
+
+        This is the method ConfigOpts uses to add the opt to the CLI interface
+        as appropriate for the opt type. Some opt types may extend this method,
+        others may just extend the helper methods it uses.
+
+        :param parser: the CLI option parser
+        :param group: an optional OptGroup object
+        """
+        container = self._get_argparse_container(parser, group)
+        kwargs = self._get_argparse_kwargs(group)
+        prefix = self._get_argparse_prefix('', group)
+        self._add_to_argparse(container, self.name, self.short, kwargs, prefix,
+                              self.positional, self.deprecated_name)
+
+    def _add_to_argparse(self, container, name, short, kwargs, prefix='',
+                         positional=False, deprecated_name=None):
+        """Add an option to an argparse parser or group.
+
+        :param container: an argparse._ArgumentGroup object
+        :param name: the opt name
+        :param short: the short opt name
+        :param kwargs: the keyword arguments for add_argument()
+        :param prefix: an optional prefix to prepend to the opt name
+        :param position: whether the optional is a positional CLI argument
+        :raises: DuplicateOptError if a naming confict is detected
+        """
+        def hyphen(arg):
+            return arg if not positional else ''
+
+        args = [hyphen('--') + prefix + name]
+        if short:
+            args.append(hyphen('-') + short)
+        if deprecated_name:
+            args.append(hyphen('--') + prefix + deprecated_name)
+
+        try:
+            container.add_argument(*args, **kwargs)
+        except argparse.ArgumentError as e:
+            raise DuplicateOptError(e)
+
+    def _get_argparse_container(self, parser, group):
+        """Returns an argparse._ArgumentGroup.
+
+        :param parser: an argparse.ArgumentParser
+        :param group: an (optional) OptGroup object
+        :returns: an argparse._ArgumentGroup if group is given, else parser
+        """
+        if group is not None:
+            return group._get_argparse_group(parser)
+        else:
+            return parser
+
+    def _get_argparse_kwargs(self, group, **kwargs):
+        """Build a dict of keyword arguments for argparse's add_argument().
+
+        Most opt types extend this method to customize the behaviour of the
+        options added to argparse.
+
+        :param group: an optional group
+        :param kwargs: optional keyword arguments to add to
+        :returns: a dict of keyword arguments
+        """
+        if not self.positional:
+            dest = self.dest
+            if group is not None:
+                dest = group.name + '_' + dest
+            kwargs['dest'] = dest
+        else:
+            kwargs['nargs'] = '?'
+        kwargs.update({'default': None,
+                       'metavar': self.metavar,
+                       'help': self.help, })
+        return kwargs
+
+    def _get_argparse_prefix(self, prefix, group):
+        """Build a prefix for the CLI option name, if required.
+
+        CLI options in a group are prefixed with the group's name in order
+        to avoid conflicts between similarly named options in different
+        groups.
+
+        :param prefix: an existing prefix to append to (e.g. 'no' or '')
+        :param group: an optional OptGroup object
+        :returns: a CLI option prefix including the group name, if appropriate
+        """
+        if group is not None:
+            return group.name + '-' + prefix
+        else:
+            return prefix
+
+
+class StrOpt(Opt):
+    """
+    String opts do not have their values transformed and are returned as
+    str objects.
+    """
+    pass
+
+
+class BoolOpt(Opt):
+
+    """
+    Bool opts are set to True or False on the command line using --optname or
+    --noopttname respectively.
+
+    In config files, boolean values are case insensitive and can be set using
+    1/0, yes/no, true/false or on/off.
+    """
+
+    _boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True,
+                       '0': False, 'no': False, 'false': False, 'off': False}
+
+    def __init__(self, *args, **kwargs):
+        if 'positional' in kwargs:
+            raise ValueError('positional boolean args not supported')
+        super(BoolOpt, self).__init__(*args, **kwargs)
+
+    def _get_from_config_parser(self, cparser, section):
+        """Retrieve the opt value as a boolean from ConfigParser."""
+        def convert_bool(v):
+            value = self._boolean_states.get(v.lower())
+            if value is None:
+                raise ValueError('Unexpected boolean value %r' % v)
+
+            return value
+
+        return [convert_bool(v) for v in
+                self._cparser_get_with_deprecated(cparser, section)]
+
+    def _add_to_cli(self, parser, group=None):
+        """Extends the base class method to add the --nooptname option."""
+        super(BoolOpt, self)._add_to_cli(parser, group)
+        self._add_inverse_to_argparse(parser, group)
+
+    def _add_inverse_to_argparse(self, parser, group):
+        """Add the --nooptname option to the option parser."""
+        container = self._get_argparse_container(parser, group)
+        kwargs = self._get_argparse_kwargs(group, action='store_false')
+        prefix = self._get_argparse_prefix('no', group)
+        kwargs["help"] = "The inverse of --" + self.name
+        self._add_to_argparse(container, self.name, None, kwargs, prefix,
+                              self.positional, self.deprecated_name)
+
+    def _get_argparse_kwargs(self, group, action='store_true', **kwargs):
+        """Extends the base argparse keyword dict for boolean options."""
+
+        kwargs = super(BoolOpt, self)._get_argparse_kwargs(group, **kwargs)
+
+        # metavar has no effect for BoolOpt
+        if 'metavar' in kwargs:
+            del kwargs['metavar']
+
+        if action != 'store_true':
+            action = 'store_false'
+
+        kwargs['action'] = action
+
+        return kwargs
+
+
+class IntOpt(Opt):
+
+    """Int opt values are converted to integers using the int() builtin."""
+
+    def _get_from_config_parser(self, cparser, section):
+        """Retrieve the opt value as a integer from ConfigParser."""
+        return [int(v) for v in self._cparser_get_with_deprecated(cparser,
+                section)]
+
+    def _get_argparse_kwargs(self, group, **kwargs):
+        """Extends the base argparse keyword dict for integer options."""
+        return super(IntOpt,
+                     self)._get_argparse_kwargs(group, type=int, **kwargs)
+
+
+class FloatOpt(Opt):
+
+    """Float opt values are converted to floats using the float() builtin."""
+
+    def _get_from_config_parser(self, cparser, section):
+        """Retrieve the opt value as a float from ConfigParser."""
+        return [float(v) for v in
+                self._cparser_get_with_deprecated(cparser, section)]
+
+    def _get_argparse_kwargs(self, group, **kwargs):
+        """Extends the base argparse keyword dict for float options."""
+        return super(FloatOpt, self)._get_argparse_kwargs(group,
+                                                          type=float, **kwargs)
+
+
+class ListOpt(Opt):
+
+    """
+    List opt values are simple string values separated by commas. The opt value
+    is a list containing these strings.
+    """
+
+    class _StoreListAction(argparse.Action):
+        """
+        An argparse action for parsing an option value into a list.
+        """
+        def __call__(self, parser, namespace, values, option_string=None):
+            if values is not None:
+                values = [a.strip() for a in values.split(',')]
+            setattr(namespace, self.dest, values)
+
+    def _get_from_config_parser(self, cparser, section):
+        """Retrieve the opt value as a list from ConfigParser."""
+        return [[a.strip() for a in v.split(',')] for v in
+                self._cparser_get_with_deprecated(cparser, section)]
+
+    def _get_argparse_kwargs(self, group, **kwargs):
+        """Extends the base argparse keyword dict for list options."""
+        return Opt._get_argparse_kwargs(self,
+                                        group,
+                                        action=ListOpt._StoreListAction,
+                                        **kwargs)
+
+
+class MultiStrOpt(Opt):
+
+    """
+    Multistr opt values are string opts which may be specified multiple times.
+    The opt value is a list containing all the string values specified.
+    """
+    multi = True
+
+    def _get_argparse_kwargs(self, group, **kwargs):
+        """Extends the base argparse keyword dict for multi str options."""
+        kwargs = super(MultiStrOpt, self)._get_argparse_kwargs(group)
+        if not self.positional:
+            kwargs['action'] = 'append'
+        else:
+            kwargs['nargs'] = '*'
+        return kwargs
+
+    def _cparser_get_with_deprecated(self, cparser, section):
+        """If cannot find option as dest try deprecated_name alias."""
+        if self.deprecated_name is not None:
+            return cparser.get(section, [self.dest, self.deprecated_name],
+                               multi=True)
+        return cparser.get(section, [self.dest], multi=True)
+
+
+class SubCommandOpt(Opt):
+
+    """
+    Sub-command options allow argparse sub-parsers to be used to parse
+    additional command line arguments.
+
+    The handler argument to the SubCommandOpt contructor is a callable
+    which is supplied an argparse subparsers object. Use this handler
+    callable to add sub-parsers.
+
+    The opt value is SubCommandAttr object with the name of the chosen
+    sub-parser stored in the 'name' attribute and the values of other
+    sub-parser arguments available as additional attributes.
+    """
+
+    def __init__(self, name, dest=None, handler=None,
+                 title=None, description=None, help=None):
+        """Construct an sub-command parsing option.
+
+        This behaves similarly to other Opt sub-classes but adds a
+        'handler' argument. The handler is a callable which is supplied
+        an subparsers object when invoked. The add_parser() method on
+        this subparsers object can be used to register parsers for
+        sub-commands.
+
+        :param name: the option's name
+        :param dest: the name of the corresponding ConfigOpts property
+        :param title: title of the sub-commands group in help output
+        :param description: description of the group in help output
+        :param help: a help string giving an overview of available sub-commands
+        """
+        super(SubCommandOpt, self).__init__(name, dest=dest, help=help)
+        self.handler = handler
+        self.title = title
+        self.description = description
+
+    def _add_to_cli(self, parser, group=None):
+        """Add argparse sub-parsers and invoke the handler method."""
+        dest = self.dest
+        if group is not None:
+            dest = group.name + '_' + dest
+
+        subparsers = parser.add_subparsers(dest=dest,
+                                           title=self.title,
+                                           description=self.description,
+                                           help=self.help)
+
+        if not self.handler is None:
+            self.handler(subparsers)
+
+
+class OptGroup(object):
+
+    """
+    Represents a group of opts.
+
+    CLI opts in the group are automatically prefixed with the group name.
+
+    Each group corresponds to a section in config files.
+
+    An OptGroup object has no public methods, but has a number of public string
+    properties:
+
+      name:
+        the name of the group
+      title:
+        the group title as displayed in --help
+      help:
+        the group description as displayed in --help
+    """
+
+    def __init__(self, name, title=None, help=None):
+        """Constructs an OptGroup object.
+
+        :param name: the group name
+        :param title: the group title for --help
+        :param help: the group description for --help
+        """
+        self.name = name
+        if title is None:
+            self.title = "%s options" % title
+        else:
+            self.title = title
+        self.help = help
+
+        self._opts = {}  # dict of dicts of (opt:, override:, default:)
+        self._argparse_group = None
+
+    def _register_opt(self, opt, cli=False):
+        """Add an opt to this group.
+
+        :param opt: an Opt object
+        :param cli: whether this is a CLI option
+        :returns: False if previously registered, True otherwise
+        :raises: DuplicateOptError if a naming conflict is detected
+        """
+        if _is_opt_registered(self._opts, opt):
+            return False
+
+        self._opts[opt.dest] = {'opt': opt, 'cli': cli}
+
+        return True
+
+    def _unregister_opt(self, opt):
+        """Remove an opt from this group.
+
+        :param opt: an Opt object
+        """
+        if opt.dest in self._opts:
+            del self._opts[opt.dest]
+
+    def _get_argparse_group(self, parser):
+        if self._argparse_group is None:
+            """Build an argparse._ArgumentGroup for this group."""
+            self._argparse_group = parser.add_argument_group(self.title,
+                                                             self.help)
+        return self._argparse_group
+
+    def _clear(self):
+        """Clear this group's option parsing state."""
+        self._argparse_group = None
+
+
+class ParseError(iniparser.ParseError):
+    def __init__(self, msg, lineno, line, filename):
+        super(ParseError, self).__init__(msg, lineno, line)
+        self.filename = filename
+
+    def __str__(self):
+        return 'at %s:%d, %s: %r' % (self.filename, self.lineno,
+                                     self.msg, self.line)
+
+
+class ConfigParser(iniparser.BaseParser):
+    def __init__(self, filename, sections):
+        super(ConfigParser, self).__init__()
+        self.filename = filename
+        self.sections = sections
+        self.section = None
+
+    def parse(self):
+        with open(self.filename) as f:
+            return super(ConfigParser, self).parse(f)
+
+    def new_section(self, section):
+        self.section = section
+        self.sections.setdefault(self.section, {})
+
+    def assignment(self, key, value):
+        if not self.section:
+            raise self.error_no_section()
+
+        self.sections[self.section].setdefault(key, [])
+        self.sections[self.section][key].append('\n'.join(value))
+
+    def parse_exc(self, msg, lineno, line=None):
+        return ParseError(msg, lineno, line, self.filename)
+
+    def error_no_section(self):
+        return self.parse_exc('Section must be started before assignment',
+                              self.lineno)
+
+
+class MultiConfigParser(object):
+    def __init__(self):
+        self.parsed = []
+
+    def read(self, config_files):
+        read_ok = []
+
+        for filename in config_files:
+            sections = {}
+            parser = ConfigParser(filename, sections)
+
+            try:
+                parser.parse()
+            except IOError:
+                continue
+            self.parsed.insert(0, sections)
+            read_ok.append(filename)
+
+        return read_ok
+
+    def get(self, section, names, multi=False):
+        rvalue = []
+        for sections in self.parsed:
+            if section not in sections:
+                continue
+            for name in names:
+                if name in sections[section]:
+                    if multi:
+                        rvalue = sections[section][name] + rvalue
+                    else:
+                        return sections[section][name]
+        if multi and rvalue != []:
+            return rvalue
+        raise KeyError
+
+
+class ConfigOpts(collections.Mapping):
+
+    """
+    Config options which may be set on the command line or in config files.
+
+    ConfigOpts is a configuration option manager with APIs for registering
+    option schemas, grouping options, parsing option values and retrieving
+    the values of options.
+    """
+
+    def __init__(self):
+        """Construct a ConfigOpts object."""
+        self._opts = {}  # dict of dicts of (opt:, override:, default:)
+        self._groups = {}
+
+        self._args = None
+
+        self._oparser = None
+        self._cparser = None
+        self._cli_values = {}
+        self.__cache = {}
+        self._config_opts = []
+
+    def _pre_setup(self, project, prog, version, usage, default_config_files):
+        """Initialize a ConfigCliParser object for option parsing."""
+
+        if prog is None:
+            prog = os.path.basename(sys.argv[0])
+
+        if default_config_files is None:
+            default_config_files = find_config_files(project, prog)
+
+        self._oparser = argparse.ArgumentParser(prog=prog, usage=usage)
+        self._oparser.add_argument('--version',
+                                   action='version',
+                                   version=version)
+
+        return prog, default_config_files
+
+    def _setup(self, project, prog, version, usage, default_config_files):
+        """Initialize a ConfigOpts object for option parsing."""
+
+        self._config_opts = [
+            MultiStrOpt('config-file',
+                        default=default_config_files,
+                        metavar='PATH',
+                        help='Path to a config file to use. Multiple config '
+                             'files can be specified, with values in later '
+                             'files taking precedence. The default files '
+                             ' used are: %s' % (default_config_files, )),
+            StrOpt('config-dir',
+                   metavar='DIR',
+                   help='Path to a config directory to pull *.conf '
+                        'files from. This file set is sorted, so as to '
+                        'provide a predictable parse order if individual '
+                        'options are over-ridden. The set is parsed after '
+                        'the file(s), if any, specified via --config-file, '
+                        'hence over-ridden options in the directory take '
+                        'precedence.'),
+        ]
+        self.register_cli_opts(self._config_opts)
+
+        self.project = project
+        self.prog = prog
+        self.version = version
+        self.usage = usage
+        self.default_config_files = default_config_files
+
+    def __clear_cache(f):
+        @functools.wraps(f)
+        def __inner(self, *args, **kwargs):
+            if kwargs.pop('clear_cache', True):
+                self.__cache.clear()
+            return f(self, *args, **kwargs)
+
+        return __inner
+
+    def __call__(self,
+                 args=None,
+                 project=None,
+                 prog=None,
+                 version=None,
+                 usage=None,
+                 default_config_files=None):
+        """Parse command line arguments and config files.
+
+        Calling a ConfigOpts object causes the supplied command line arguments
+        and config files to be parsed, causing opt values to be made available
+        as attributes of the object.
+
+        The object may be called multiple times, each time causing the previous
+        set of values to be overwritten.
+
+        Automatically registers the --config-file option with either a supplied
+        list of default config files, or a list from find_config_files().
+
+        If the --config-dir option is set, any *.conf files from this
+        directory are pulled in, after all the file(s) specified by the
+        --config-file option.
+
+        :param args: command line arguments (defaults to sys.argv[1:])
+        :param project: the toplevel project name, used to locate config files
+        :param prog: the name of the program (defaults to sys.argv[0] basename)
+        :param version: the program version (for --version)
+        :param usage: a usage string (%prog will be expanded)
+        :param default_config_files: config files to use by default
+        :returns: the list of arguments left over after parsing options
+        :raises: SystemExit, ConfigFilesNotFoundError, ConfigFileParseError,
+                 RequiredOptError, DuplicateOptError
+        """
+
+        self.clear()
+
+        prog, default_config_files = self._pre_setup(project,
+                                                     prog,
+                                                     version,
+                                                     usage,
+                                                     default_config_files)
+
+        self._setup(project, prog, version, usage, default_config_files)
+
+        self._cli_values = self._parse_cli_opts(args)
+
+        self._parse_config_files()
+
+        self._check_required_opts()
+
+    def __getattr__(self, name):
+        """Look up an option value and perform string substitution.
+
+        :param name: the opt name (or 'dest', more precisely)
+        :returns: the option value (after string subsititution) or a GroupAttr
+        :raises: NoSuchOptError,ConfigFileValueError,TemplateSubstitutionError
+        """
+        return self._get(name)
+
+    def __getitem__(self, key):
+        """Look up an option value and perform string substitution."""
+        return self.__getattr__(key)
+
+    def __contains__(self, key):
+        """Return True if key is the name of a registered opt or group."""
+        return key in self._opts or key in self._groups
+
+    def __iter__(self):
+        """Iterate over all registered opt and group names."""
+        for key in self._opts.keys() + self._groups.keys():
+            yield key
+
+    def __len__(self):
+        """Return the number of options and option groups."""
+        return len(self._opts) + len(self._groups)
+
+    def reset(self):
+        """Clear the object state and unset overrides and defaults."""
+        self._unset_defaults_and_overrides()
+        self.clear()
+
+    @__clear_cache
+    def clear(self):
+        """Clear the state of the object to before it was called.
+
+        Any subparsers added using the add_cli_subparsers() will also be
+        removed as a side-effect of this method.
+        """
+        self._args = None
+        self._cli_values.clear()
+        self._oparser = argparse.ArgumentParser()
+        self._cparser = None
+        self.unregister_opts(self._config_opts)
+        for group in self._groups.values():
+            group._clear()
+
+    @__clear_cache
+    def register_opt(self, opt, group=None, cli=False):
+        """Register an option schema.
+
+        Registering an option schema makes any option value which is previously
+        or subsequently parsed from the command line or config files available
+        as an attribute of this object.
+
+        :param opt: an instance of an Opt sub-class
+        :param cli: whether this is a CLI option
+        :param group: an optional OptGroup object or group name
+        :return: False if the opt was already register, True otherwise
+        :raises: DuplicateOptError
+        """
+        if group is not None:
+            group = self._get_group(group, autocreate=True)
+            return group._register_opt(opt, cli)
+
+        if _is_opt_registered(self._opts, opt):
+            return False
+
+        self._opts[opt.dest] = {'opt': opt, 'cli': cli}
+
+        return True
+
+    @__clear_cache
+    def register_opts(self, opts, group=None):
+        """Register multiple option schemas at once."""
+        for opt in opts:
+            self.register_opt(opt, group, clear_cache=False)
+
+    @__clear_cache
+    def register_cli_opt(self, opt, group=None):
+        """Register a CLI option schema.
+
+        CLI option schemas must be registered before the command line and
+        config files are parsed. This is to ensure that all CLI options are
+        show in --help and option validation works as expected.
+
+        :param opt: an instance of an Opt sub-class
+        :param group: an optional OptGroup object or group name
+        :return: False if the opt was already register, True otherwise
+        :raises: DuplicateOptError, ArgsAlreadyParsedError
+        """
+        if self._args is not None:
+            raise ArgsAlreadyParsedError("cannot register CLI option")
+
+        return self.register_opt(opt, group, cli=True, clear_cache=False)
+
+    @__clear_cache
+    def register_cli_opts(self, opts, group=None):
+        """Register multiple CLI option schemas at once."""
+        for opt in opts:
+            self.register_cli_opt(opt, group, clear_cache=False)
+
+    def register_group(self, group):
+        """Register an option group.
+
+        An option group must be registered before options can be registered
+        with the group.
+
+        :param group: an OptGroup object
+        """
+        if group.name in self._groups:
+            return
+
+        self._groups[group.name] = copy.copy(group)
+
+    @__clear_cache
+    def unregister_opt(self, opt, group=None):
+        """Unregister an option.
+
+        :param opt: an Opt object
+        :param group: an optional OptGroup object or group name
+        :raises: ArgsAlreadyParsedError, NoSuchGroupError
+        """
+        if self._args is not None:
+            raise ArgsAlreadyParsedError("reset before unregistering options")
+
+        if group is not None:
+            self._get_group(group)._unregister_opt(opt)
+        elif opt.dest in self._opts:
+            del self._opts[opt.dest]
+
+    @__clear_cache
+    def unregister_opts(self, opts, group=None):
+        """Unregister multiple CLI option schemas at once."""
+        for opt in opts:
+            self.unregister_opt(opt, group, clear_cache=False)
+
+    def import_opt(self, name, module_str, group=None):
+        """Import an option definition from a module.
+
+        Import a module and check that a given option is registered.
+
+        This is intended for use with global configuration objects
+        like cfg.CONF where modules commonly register options with
+        CONF at module load time. If one module requires an option
+        defined by another module it can use this method to explicitly
+        declare the dependency.
+
+        :param name: the name/dest of the opt
+        :param module_str: the name of a module to import
+        :param group: an option OptGroup object or group name
+        :raises: NoSuchOptError, NoSuchGroupError
+        """
+        __import__(module_str)
+        self._get_opt_info(name, group)
+
+    @__clear_cache
+    def set_override(self, name, override, group=None):
+        """Override an opt value.
+
+        Override the command line, config file and default values of a
+        given option.
+
+        :param name: the name/dest of the opt
+        :param override: the override value
+        :param group: an option OptGroup object or group name
+        :raises: NoSuchOptError, NoSuchGroupError
+        """
+        opt_info = self._get_opt_info(name, group)
+        opt_info['override'] = override
+
+    @__clear_cache
+    def set_default(self, name, default, group=None):
+        """Override an opt's default value.
+
+        Override the default value of given option. A command line or
+        config file value will still take precedence over this default.
+
+        :param name: the name/dest of the opt
+        :param default: the default value
+        :param group: an option OptGroup object or group name
+        :raises: NoSuchOptError, NoSuchGroupError
+        """
+        opt_info = self._get_opt_info(name, group)
+        opt_info['default'] = default
+
+    @__clear_cache
+    def clear_override(self, name, group=None):
+        """Clear an override an opt value.
+
+        Clear a previously set override of the command line, config file
+        and default values of a given option.
+
+        :param name: the name/dest of the opt
+        :param group: an option OptGroup object or group name
+        :raises: NoSuchOptError, NoSuchGroupError
+        """
+        opt_info = self._get_opt_info(name, group)
+        opt_info.pop('override', None)
+
+    @__clear_cache
+    def clear_default(self, name, group=None):
+        """Clear an override an opt's default value.
+
+        Clear a previously set override of the default value of given option.
+
+        :param name: the name/dest of the opt
+        :param group: an option OptGroup object or group name
+        :raises: NoSuchOptError, NoSuchGroupError
+        """
+        opt_info = self._get_opt_info(name, group)
+        opt_info.pop('default', None)
+
+    def _all_opt_infos(self):
+        """A generator function for iteration opt infos."""
+        for info in self._opts.values():
+            yield info, None
+        for group in self._groups.values():
+            for info in group._opts.values():
+                yield info, group
+
+    def _all_cli_opts(self):
+        """A generator function for iterating CLI opts."""
+        for info, group in self._all_opt_infos():
+            if info['cli']:
+                yield info['opt'], group
+
+    def _unset_defaults_and_overrides(self):
+        """Unset any default or override on all options."""
+        for info, group in self._all_opt_infos():
+            info.pop('default', None)
+            info.pop('override', None)
+
+    def find_file(self, name):
+        """Locate a file located alongside the config files.
+
+        Search for a file with the supplied basename in the directories
+        which we have already loaded config files from and other known
+        configuration directories.
+
+        The directory, if any, supplied by the config_dir option is
+        searched first. Then the config_file option is iterated over
+        and each of the base directories of the config_files values
+        are searched. Failing both of these, the standard directories
+        searched by the module level find_config_files() function is
+        used. The first matching file is returned.
+
+        :param basename: the filename, e.g. 'policy.json'
+        :returns: the path to a matching file, or None
+        """
+        dirs = []
+        if self.config_dir:
+            dirs.append(_fixpath(self.config_dir))
+
+        for cf in reversed(self.config_file):
+            dirs.append(os.path.dirname(_fixpath(cf)))
+
+        dirs.extend(_get_config_dirs(self.project))
+
+        return _search_dirs(dirs, name)
+
+    def log_opt_values(self, logger, lvl):
+        """Log the value of all registered opts.
+
+        It's often useful for an app to log its configuration to a log file at
+        startup for debugging. This method dumps to the entire config state to
+        the supplied logger at a given log level.
+
+        :param logger: a logging.Logger object
+        :param lvl: the log level (e.g. logging.DEBUG) arg to logger.log()
+        """
+        logger.log(lvl, "*" * 80)
+        logger.log(lvl, "Configuration options gathered from:")
+        logger.log(lvl, "command line args: %s", self._args)
+        logger.log(lvl, "config files: %s", self.config_file)
+        logger.log(lvl, "=" * 80)
+
+        def _sanitize(opt, value):
+            """Obfuscate values of options declared secret"""
+            return value if not opt.secret else '*' * len(str(value))
+
+        for opt_name in sorted(self._opts):
+            opt = self._get_opt_info(opt_name)['opt']
+            logger.log(lvl, "%-30s = %s", opt_name,
+                       _sanitize(opt, getattr(self, opt_name)))
+
+        for group_name in self._groups:
+            group_attr = self.GroupAttr(self, self._get_group(group_name))
+            for opt_name in sorted(self._groups[group_name]._opts):
+                opt = self._get_opt_info(opt_name, group_name)['opt']
+                logger.log(lvl, "%-30s = %s",
+                           "%s.%s" % (group_name, opt_name),
+                           _sanitize(opt, getattr(group_attr, opt_name)))
+
+        logger.log(lvl, "*" * 80)
+
+    def print_usage(self, file=None):
+        """Print the usage message for the current program."""
+        self._oparser.print_usage(file)
+
+    def print_help(self, file=None):
+        """Print the help message for the current program."""
+        self._oparser.print_help(file)
+
+    def _get(self, name, group=None):
+        if isinstance(group, OptGroup):
+            key = (group.name, name)
+        else:
+            key = (group, name)
+        try:
+            return self.__cache[key]
+        except KeyError:
+            value = self._substitute(self._do_get(name, group))
+            self.__cache[key] = value
+            return value
+
+    def _do_get(self, name, group=None):
+        """Look up an option value.
+
+        :param name: the opt name (or 'dest', more precisely)
+        :param group: an OptGroup
+        :returns: the option value, or a GroupAttr object
+        :raises: NoSuchOptError, NoSuchGroupError, ConfigFileValueError,
+                 TemplateSubstitutionError
+        """
+        if group is None and name in self._groups:
+            return self.GroupAttr(self, self._get_group(name))
+
+        info = self._get_opt_info(name, group)
+        opt = info['opt']
+
+        if isinstance(opt, SubCommandOpt):
+            return self.SubCommandAttr(self, group, opt.dest)
+
+        if 'override' in info:
+            return info['override']
+
+        values = []
+        if self._cparser is not None:
+            section = group.name if group is not None else 'DEFAULT'
+            try:
+                value = opt._get_from_config_parser(self._cparser, section)
+            except KeyError:
+                pass
+            except ValueError as ve:
+                raise ConfigFileValueError(str(ve))
+            else:
+                if not opt.multi:
+                    # No need to continue since the last value wins
+                    return value[-1]
+                values.extend(value)
+
+        name = name if group is None else group.name + '_' + name
+        value = self._cli_values.get(name)
+        if value is not None:
+            if not opt.multi:
+                return value
+
+            # argparse ignores default=None for nargs='*'
+            if opt.positional and not value:
+                value = opt.default
+
+            return value + values
+
+        if values:
+            return values
+
+        if 'default' in info:
+            return info['default']
+
+        return opt.default
+
+    def _substitute(self, value):
+        """Perform string template substitution.
+
+        Substitute any template variables (e.g. $foo, ${bar}) in the supplied
+        string value(s) with opt values.
+
+        :param value: the string value, or list of string values
+        :returns: the substituted string(s)
+        """
+        if isinstance(value, list):
+            return [self._substitute(i) for i in value]
+        elif isinstance(value, str):
+            tmpl = string.Template(value)
+            return tmpl.safe_substitute(self.StrSubWrapper(self))
+        else:
+            return value
+
+    def _get_group(self, group_or_name, autocreate=False):
+        """Looks up a OptGroup object.
+
+        Helper function to return an OptGroup given a parameter which can
+        either be the group's name or an OptGroup object.
+
+        The OptGroup object returned is from the internal dict of OptGroup
+        objects, which will be a copy of any OptGroup object that users of
+        the API have access to.
+
+        :param group_or_name: the group's name or the OptGroup object itself
+        :param autocreate: whether to auto-create the group if it's not found
+        :raises: NoSuchGroupError
+        """
+        group = group_or_name if isinstance(group_or_name, OptGroup) else None
+        group_name = group.name if group else group_or_name
+
+        if not group_name in self._groups:
+            if not group is None or not autocreate:
+                raise NoSuchGroupError(group_name)
+
+            self.register_group(OptGroup(name=group_name))
+
+        return self._groups[group_name]
+
+    def _get_opt_info(self, opt_name, group=None):
+        """Return the (opt, override, default) dict for an opt.
+
+        :param opt_name: an opt name/dest
+        :param group: an optional group name or OptGroup object
+        :raises: NoSuchOptError, NoSuchGroupError
+        """
+        if group is None:
+            opts = self._opts
+        else:
+            group = self._get_group(group)
+            opts = group._opts
+
+        if not opt_name in opts:
+            raise NoSuchOptError(opt_name, group)
+
+        return opts[opt_name]
+
+    def _parse_config_files(self):
+        """Parse the config files from --config-file and --config-dir.
+
+        :raises: ConfigFilesNotFoundError, ConfigFileParseError
+        """
+        config_files = list(self.config_file)
+
+        if self.config_dir:
+            config_dir_glob = os.path.join(self.config_dir, '*.conf')
+            config_files += sorted(glob.glob(config_dir_glob))
+
+        config_files = [_fixpath(p) for p in config_files]
+
+        self._cparser = MultiConfigParser()
+
+        try:
+            read_ok = self._cparser.read(config_files)
+        except iniparser.ParseError as pe:
+            raise ConfigFileParseError(pe.filename, str(pe))
+
+        if read_ok != config_files:
+            not_read_ok = filter(lambda f: f not in read_ok, config_files)
+            raise ConfigFilesNotFoundError(not_read_ok)
+
+    def _check_required_opts(self):
+        """Check that all opts marked as required have values specified.
+
+        :raises: RequiredOptError
+        """
+        for info, group in self._all_opt_infos():
+            opt = info['opt']
+
+            if opt.required:
+                if ('default' in info or 'override' in info):
+                    continue
+
+                if self._get(opt.dest, group) is None:
+                    raise RequiredOptError(opt.name, group)
+
+    def _parse_cli_opts(self, args):
+        """Parse command line options.
+
+        Initializes the command line option parser and parses the supplied
+        command line arguments.
+
+        :param args: the command line arguments
+        :returns: a dict of parsed option values
+        :raises: SystemExit, DuplicateOptError
+
+        """
+        self._args = args
+
+        for opt, group in self._all_cli_opts():
+            opt._add_to_cli(self._oparser, group)
+
+        return vars(self._oparser.parse_args(args))
+
+    class GroupAttr(collections.Mapping):
+
+        """
+        A helper class representing the option values of a group as a mapping
+        and attributes.
+        """
+
+        def __init__(self, conf, group):
+            """Construct a GroupAttr object.
+
+            :param conf: a ConfigOpts object
+            :param group: an OptGroup object
+            """
+            self._conf = conf
+            self._group = group
+
+        def __getattr__(self, name):
+            """Look up an option value and perform template substitution."""
+            return self._conf._get(name, self._group)
+
+        def __getitem__(self, key):
+            """Look up an option value and perform string substitution."""
+            return self.__getattr__(key)
+
+        def __contains__(self, key):
+            """Return True if key is the name of a registered opt or group."""
+            return key in self._group._opts
+
+        def __iter__(self):
+            """Iterate over all registered opt and group names."""
+            for key in self._group._opts.keys():
+                yield key
+
+        def __len__(self):
+            """Return the number of options and option groups."""
+            return len(self._group._opts)
+
+    class SubCommandAttr(object):
+
+        """
+        A helper class representing the name and arguments of an argparse
+        sub-parser.
+        """
+
+        def __init__(self, conf, group, dest):
+            """Construct a SubCommandAttr object.
+
+            :param conf: a ConfigOpts object
+            :param group: an OptGroup object
+            :param dest: the name of the sub-parser
+            """
+            self._conf = conf
+            self._group = group
+            self._dest = dest
+
+        def __getattr__(self, name):
+            """Look up a sub-parser name or argument value."""
+            if name == 'name':
+                name = self._dest
+                if self._group is not None:
+                    name = self._group.name + '_' + name
+                return self._conf._cli_values[name]
+
+            if name in self._conf:
+                raise DuplicateOptError(name)
+
+            try:
+                return self._conf._cli_values[name]
+            except KeyError:
+                raise NoSuchOptError(name)
+
+    class StrSubWrapper(object):
+
+        """
+        A helper class exposing opt values as a dict for string substitution.
+        """
+
+        def __init__(self, conf):
+            """Construct a StrSubWrapper object.
+
+            :param conf: a ConfigOpts object
+            """
+            self.conf = conf
+
+        def __getitem__(self, key):
+            """Look up an opt value from the ConfigOpts object.
+
+            :param key: an opt name
+            :returns: an opt value
+            :raises: TemplateSubstitutionError if attribute is a group
+            """
+            value = getattr(self.conf, key)
+            if isinstance(value, self.conf.GroupAttr):
+                raise TemplateSubstitutionError(
+                    'substituting group %s not supported' % key)
+            return value
+
+
+class CommonConfigOpts(ConfigOpts):
+
+    DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s"
+    DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
+
+    common_cli_opts = [
+        BoolOpt('debug',
+                short='d',
+                default=False,
+                help='Print debugging output'),
+        BoolOpt('verbose',
+                short='v',
+                default=False,
+                help='Print more verbose output'),
+    ]
+
+    logging_cli_opts = [
+        StrOpt('log-config',
+               metavar='PATH',
+               help='If this option is specified, the logging configuration '
+                    'file specified is used and overrides any other logging '
+                    'options specified. Please see the Python logging module '
+                    'documentation for details on logging configuration '
+                    'files.'),
+        StrOpt('log-format',
+               default=DEFAULT_LOG_FORMAT,
+               metavar='FORMAT',
+               help='A logging.Formatter log message format string which may '
+                    'use any of the available logging.LogRecord attributes. '
+                    'Default: %(default)s'),
+        StrOpt('log-date-format',
+               default=DEFAULT_LOG_DATE_FORMAT,
+               metavar='DATE_FORMAT',
+               help='Format string for %%(asctime)s in log records. '
+                    'Default: %(default)s'),
+        StrOpt('log-file',
+               metavar='PATH',
+               deprecated_name='logfile',
+               help='(Optional) Name of log file to output to. '
+                    'If not set, logging will go to stdout.'),
+        StrOpt('log-dir',
+               deprecated_name='logdir',
+               help='(Optional) The directory to keep log files in '
+                    '(will be prepended to --log-file)'),
+        BoolOpt('use-syslog',
+                default=False,
+                help='Use syslog for logging.'),
+        StrOpt('syslog-log-facility',
+               default='LOG_USER',
+               help='syslog facility to receive log lines')
+    ]
+
+    def __init__(self):
+        super(CommonConfigOpts, self).__init__()
+        self.register_cli_opts(self.common_cli_opts)
+        self.register_cli_opts(self.logging_cli_opts)
+
+
+CONF = CommonConfigOpts()
diff --git a/tempest/openstack/common/iniparser.py b/tempest/openstack/common/iniparser.py
new file mode 100644
index 0000000..2412844
--- /dev/null
+++ b/tempest/openstack/common/iniparser.py
@@ -0,0 +1,130 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack LLC.
+#
+#    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.
+
+
+class ParseError(Exception):
+    def __init__(self, message, lineno, line):
+        self.msg = message
+        self.line = line
+        self.lineno = lineno
+
+    def __str__(self):
+        return 'at line %d, %s: %r' % (self.lineno, self.msg, self.line)
+
+
+class BaseParser(object):
+    lineno = 0
+    parse_exc = ParseError
+
+    def _assignment(self, key, value):
+        self.assignment(key, value)
+        return None, []
+
+    def _get_section(self, line):
+        if line[-1] != ']':
+            return self.error_no_section_end_bracket(line)
+        if len(line) <= 2:
+            return self.error_no_section_name(line)
+
+        return line[1:-1]
+
+    def _split_key_value(self, line):
+        colon = line.find(':')
+        equal = line.find('=')
+        if colon < 0 and equal < 0:
+            return self.error_invalid_assignment(line)
+
+        if colon < 0 or (equal >= 0 and equal < colon):
+            key, value = line[:equal], line[equal + 1:]
+        else:
+            key, value = line[:colon], line[colon + 1:]
+
+        value = value.strip()
+        if ((value and value[0] == value[-1]) and
+            (value[0] == "\"" or value[0] == "'")):
+            value = value[1:-1]
+        return key.strip(), [value]
+
+    def parse(self, lineiter):
+        key = None
+        value = []
+
+        for line in lineiter:
+            self.lineno += 1
+
+            line = line.rstrip()
+            if not line:
+                # Blank line, ends multi-line values
+                if key:
+                    key, value = self._assignment(key, value)
+                continue
+            elif line[0] in (' ', '\t'):
+                # Continuation of previous assignment
+                if key is None:
+                    self.error_unexpected_continuation(line)
+                else:
+                    value.append(line.lstrip())
+                continue
+
+            if key:
+                # Flush previous assignment, if any
+                key, value = self._assignment(key, value)
+
+            if line[0] == '[':
+                # Section start
+                section = self._get_section(line)
+                if section:
+                    self.new_section(section)
+            elif line[0] in '#;':
+                self.comment(line[1:].lstrip())
+            else:
+                key, value = self._split_key_value(line)
+                if not key:
+                    return self.error_empty_key(line)
+
+        if key:
+            # Flush previous assignment, if any
+            self._assignment(key, value)
+
+    def assignment(self, key, value):
+        """Called when a full assignment is parsed"""
+        raise NotImplementedError()
+
+    def new_section(self, section):
+        """Called when a new section is started"""
+        raise NotImplementedError()
+
+    def comment(self, comment):
+        """Called when a comment is parsed"""
+        pass
+
+    def error_invalid_assignment(self, line):
+        raise self.parse_exc("No ':' or '=' found in assignment",
+                             self.lineno, line)
+
+    def error_empty_key(self, line):
+        raise self.parse_exc('Key cannot be empty', self.lineno, line)
+
+    def error_unexpected_continuation(self, line):
+        raise self.parse_exc('Unexpected continuation line',
+                             self.lineno, line)
+
+    def error_no_section_end_bracket(self, line):
+        raise self.parse_exc('Invalid section (must end with ])',
+                             self.lineno, line)
+
+    def error_no_section_name(self, line):
+        raise self.parse_exc('Empty section name', self.lineno, line)
diff --git a/tempest/common/setup.py b/tempest/openstack/common/setup.py
similarity index 100%
rename from tempest/common/setup.py
rename to tempest/openstack/common/setup.py
diff --git a/tempest/services/identity/json/admin_client.py b/tempest/services/identity/json/admin_client.py
index 6c36195..7ea33b5 100644
--- a/tempest/services/identity/json/admin_client.py
+++ b/tempest/services/identity/json/admin_client.py
@@ -181,9 +181,10 @@
     def create_service(self, name, type, **kwargs):
         """Create a service"""
         post_body = {
-                      'name': name,
-                      'type': type,
-                      'description': kwargs.get('description')}
+            'name': name,
+            'type': type,
+            'description': kwargs.get('description')
+        }
         post_body = json.dumps({'OS-KSADM:service': post_body})
         resp, body = self.post('/OS-KSADM/services', post_body, self.headers)
         body = json.loads(body)
diff --git a/tempest/tests/boto/__init__.py b/tempest/tests/boto/__init__.py
index fdcd3cd..62918c2 100644
--- a/tempest/tests/boto/__init__.py
+++ b/tempest/tests/boto/__init__.py
@@ -23,9 +23,9 @@
 import boto.exception
 import keystoneclient.exceptions
 
+import tempest.clients
 from tempest.common.utils.file_utils import have_effective_read_access
 import tempest.config
-import tempest.openstack
 
 A_I_IMAGES_READY = False  # ari,ami,aki
 S3_CAN_CONNECT_ERROR = "Unknown Error"
@@ -59,7 +59,7 @@
         if not secret_matcher.match(connection_data["aws_secret_access_key"]):
             raise Exception("Invalid AWS secret Key")
         raise Exception("Unknown (Authentication?) Error")
-    openstack = tempest.openstack.Manager()
+    openstack = tempest.clients.Manager()
     try:
         if urlparse.urlparse(config.boto.ec2_url).hostname is None:
             raise Exception("Failed to get hostname from the ec2_url")
diff --git a/tempest/tests/boto/test_ec2_instance_run.py b/tempest/tests/boto/test_ec2_instance_run.py
index 023e3d0..840d6dd 100644
--- a/tempest/tests/boto/test_ec2_instance_run.py
+++ b/tempest/tests/boto/test_ec2_instance_run.py
@@ -23,10 +23,10 @@
 from nose.plugins.attrib import attr
 import unittest2 as unittest
 
+from tempest import clients
 from tempest.common.utils.data_utils import rand_name
 from tempest.common.utils.linux.remote_client import RemoteClient
 from tempest.exceptions import EC2RegisterImageException
-from tempest import openstack
 from tempest.testboto import BotoTestCase
 import tempest.tests.boto
 from tempest.tests.boto.utils.s3 import s3_upload_dir
@@ -45,7 +45,7 @@
         if not tempest.tests.boto.A_I_IMAGES_READY:
             raise nose.SkipTest("".join(("EC2 ", cls.__name__,
                                 ": requires ami/aki/ari manifest")))
-        cls.os = openstack.Manager()
+        cls.os = clients.Manager()
         cls.s3_client = cls.os.s3_client
         cls.ec2_client = cls.os.ec2api_client
         config = cls.os.config
diff --git a/tempest/tests/boto/test_ec2_keys.py b/tempest/tests/boto/test_ec2_keys.py
index b9e5508..162e2fb 100644
--- a/tempest/tests/boto/test_ec2_keys.py
+++ b/tempest/tests/boto/test_ec2_keys.py
@@ -18,8 +18,8 @@
 from nose.plugins.attrib import attr
 import unittest2 as unittest
 
+from tempest import clients
 from tempest.common.utils.data_utils import rand_name
-from tempest import openstack
 from tempest.testboto import BotoTestCase
 
 
@@ -34,7 +34,7 @@
     @classmethod
     def setUpClass(cls):
         super(EC2KeysTest, cls).setUpClass()
-        cls.os = openstack.Manager()
+        cls.os = clients.Manager()
         cls.client = cls.os.ec2api_client
 
     @attr(type='smoke')
diff --git a/tempest/tests/boto/test_ec2_network.py b/tempest/tests/boto/test_ec2_network.py
index c67b3aa..c544b06 100644
--- a/tempest/tests/boto/test_ec2_network.py
+++ b/tempest/tests/boto/test_ec2_network.py
@@ -18,7 +18,7 @@
 from nose.plugins.attrib import attr
 import unittest2 as unittest
 
-from tempest import openstack
+from tempest import clients
 from tempest.testboto import BotoTestCase
 
 
@@ -28,7 +28,7 @@
     @classmethod
     def setUpClass(cls):
         super(EC2NetworkTest, cls).setUpClass()
-        cls.os = openstack.Manager()
+        cls.os = clients.Manager()
         cls.client = cls.os.ec2api_client
 
 #Note(afazekas): these tests for things duable without an instance
diff --git a/tempest/tests/boto/test_ec2_security_groups.py b/tempest/tests/boto/test_ec2_security_groups.py
index 72e8267..c10f1df 100644
--- a/tempest/tests/boto/test_ec2_security_groups.py
+++ b/tempest/tests/boto/test_ec2_security_groups.py
@@ -18,8 +18,8 @@
 from nose.plugins.attrib import attr
 import unittest2 as unittest
 
+from tempest import clients
 from tempest.common.utils.data_utils import rand_name
-from tempest import openstack
 from tempest.testboto import BotoTestCase
 
 
@@ -29,7 +29,7 @@
     @classmethod
     def setUpClass(cls):
         super(EC2SecurityGroupTest, cls).setUpClass()
-        cls.os = openstack.Manager()
+        cls.os = clients.Manager()
         cls.client = cls.os.ec2api_client
 
     @attr(type='smoke')
@@ -41,7 +41,7 @@
                                                   group_description)
         self.addResourceCleanUp(self.client.delete_security_group, group_name)
         groups_get = self.client.get_all_security_groups(
-                                                  groupnames=(group_name,))
+            groupnames=(group_name,))
         self.assertEqual(len(groups_get), 1)
         group_get = groups_get[0]
         self.assertEqual(group.name, group_get.name)
@@ -63,7 +63,7 @@
         self.assertTrue(success)
         #TODO(afazekas): Duplicate tests
         group_get = self.client.get_all_security_groups(
-                                                 groupnames=(group_name,))[0]
+            groupnames=(group_name,))[0]
         #remove listed rules
         for ip_permission in group_get.rules:
             for cidr in ip_permission.grants:
@@ -74,6 +74,6 @@
                                 to_port=ip_permission.to_port))
 
         group_get = self.client.get_all_security_groups(
-                                                 groupnames=(group_name,))[0]
+            groupnames=(group_name,))[0]
         #all rules shuld be removed now
         self.assertEqual(0, len(group_get.rules))
diff --git a/tempest/tests/boto/test_ec2_volumes.py b/tempest/tests/boto/test_ec2_volumes.py
index 89698de..8d6cf6f 100644
--- a/tempest/tests/boto/test_ec2_volumes.py
+++ b/tempest/tests/boto/test_ec2_volumes.py
@@ -21,7 +21,7 @@
 from nose.plugins.attrib import attr
 import unittest2 as unittest
 
-from tempest import openstack
+from tempest import clients
 from tempest.testboto import BotoTestCase
 
 LOG = logging.getLogger(__name__)
@@ -38,7 +38,7 @@
     @classmethod
     def setUpClass(cls):
         super(EC2VolumesTest, cls).setUpClass()
-        cls.os = openstack.Manager()
+        cls.os = clients.Manager()
         cls.client = cls.os.ec2api_client
         cls.zone = cls.client.get_good_zone()
 
diff --git a/tempest/tests/boto/test_s3_buckets.py b/tempest/tests/boto/test_s3_buckets.py
index ce4b210..5587673 100644
--- a/tempest/tests/boto/test_s3_buckets.py
+++ b/tempest/tests/boto/test_s3_buckets.py
@@ -18,8 +18,8 @@
 from nose.plugins.attrib import attr
 import unittest2 as unittest
 
+from tempest import clients
 from tempest.common.utils.data_utils import rand_name
-from tempest import openstack
 from tempest.testboto import BotoTestCase
 
 
@@ -29,7 +29,7 @@
     @classmethod
     def setUpClass(cls):
         super(S3BucketsTest, cls).setUpClass()
-        cls.os = openstack.Manager()
+        cls.os = clients.Manager()
         cls.client = cls.os.s3_client
         cls.config = cls.os.config
 
diff --git a/tempest/tests/boto/test_s3_ec2_images.py b/tempest/tests/boto/test_s3_ec2_images.py
index f8f5027..7020f51 100644
--- a/tempest/tests/boto/test_s3_ec2_images.py
+++ b/tempest/tests/boto/test_s3_ec2_images.py
@@ -23,8 +23,8 @@
 from nose.plugins.attrib import attr
 import unittest2 as unittest
 
+from tempest import clients
 from tempest.common.utils.data_utils import rand_name
-from tempest import openstack
 from tempest.testboto import BotoTestCase
 import tempest.tests.boto
 from tempest.tests.boto.utils.s3 import s3_upload_dir
@@ -40,7 +40,7 @@
         if not tempest.tests.boto.A_I_IMAGES_READY:
             raise nose.SkipTest("".join(("EC2 ", cls.__name__,
                                 ": requires ami/aki/ari manifest")))
-        cls.os = openstack.Manager()
+        cls.os = clients.Manager()
         cls.s3_client = cls.os.s3_client
         cls.images_client = cls.os.ec2api_client
         config = cls.os.config
diff --git a/tempest/tests/boto/test_s3_objects.py b/tempest/tests/boto/test_s3_objects.py
index cfb1ad5..94b04dc 100644
--- a/tempest/tests/boto/test_s3_objects.py
+++ b/tempest/tests/boto/test_s3_objects.py
@@ -21,8 +21,8 @@
 from nose.plugins.attrib import attr
 import unittest2 as unittest
 
+from tempest import clients
 from tempest.common.utils.data_utils import rand_name
-from tempest import openstack
 from tempest.testboto import BotoTestCase
 from tempest.tests import boto
 
@@ -33,7 +33,7 @@
     @classmethod
     def setUpClass(cls):
         super(S3BucketsTest, cls).setUpClass()
-        cls.os = openstack.Manager()
+        cls.os = clients.Manager()
         cls.client = cls.os.s3_client
         cls.config = cls.os.config
 
diff --git a/tempest/tests/compute/__init__.py b/tempest/tests/compute/__init__.py
index 53dc415..0258708 100644
--- a/tempest/tests/compute/__init__.py
+++ b/tempest/tests/compute/__init__.py
@@ -19,8 +19,8 @@
 
 import nose
 
+from tempest import clients
 from tempest import config
-from tempest import openstack
 
 LOG = logging.getLogger(__name__)
 
@@ -40,7 +40,7 @@
     LOG.debug("Entering tempest.tests.compute.setup_package")
 
     global MULTI_USER, DISK_CONFIG_ENABLED, FLAVOR_EXTRA_DATA_ENABLED
-    os = openstack.Manager()
+    os = clients.Manager()
     images_client = os.images_client
     flavors_client = os.flavors_client
     extensions_client = os.extensions_client
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index 1195cca..a79f7f5 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -21,10 +21,10 @@
 import nose
 import unittest2 as unittest
 
+from tempest import clients
 from tempest.common.utils.data_utils import rand_name
 from tempest import config
 from tempest import exceptions
-from tempest import openstack
 
 __all__ = ['BaseComputeTest', 'BaseComputeTestJSON', 'BaseComputeTestXML',
            'BaseComputeAdminTestJSON', 'BaseComputeAdminTestXML']
@@ -44,12 +44,12 @@
         if cls.config.compute.allow_tenant_isolation:
             creds = cls._get_isolated_creds()
             username, tenant_name, password = creds
-            os = openstack.Manager(username=username,
-                                   password=password,
-                                   tenant_name=tenant_name,
-                                   interface=cls._interface)
+            os = clients.Manager(username=username,
+                                 password=password,
+                                 tenant_name=tenant_name,
+                                 interface=cls._interface)
         else:
-            os = openstack.Manager(interface=cls._interface)
+            os = clients.Manager(interface=cls._interface)
 
         cls.os = os
         cls.servers_client = os.servers_client
@@ -78,7 +78,7 @@
         """
         Returns an instance of the Identity Admin API client
         """
-        os = openstack.IdentityManager(interface=cls._interface)
+        os = clients.IdentityManager(interface=cls._interface)
         admin_client = os.admin_client
         return admin_client
 
@@ -260,7 +260,7 @@
                    "in configuration.")
             raise nose.SkipTest(msg)
 
-        cls.os = openstack.AdminManager(interface=cls._interface)
+        cls.os = clients.AdminManager(interface=cls._interface)
 
 
 class BaseComputeAdminTestJSON(BaseComputeAdminTest):
diff --git a/tempest/tests/compute/flavors/test_flavors.py b/tempest/tests/compute/flavors/test_flavors.py
index 31cf66d..6423075 100644
--- a/tempest/tests/compute/flavors/test_flavors.py
+++ b/tempest/tests/compute/flavors/test_flavors.py
@@ -42,7 +42,7 @@
     def test_get_flavor(self):
         """The expected flavor details should be returned"""
         resp, flavor = self.client.get_flavor_details(self.flavor_ref)
-        self.assertEqual(self.flavor_ref, str(flavor['id']))
+        self.assertEqual(self.flavor_ref, int(flavor['id']))
 
     @attr(type='negative')
     def test_get_non_existant_flavor(self):
diff --git a/tempest/tests/compute/floating_ips/test_floating_ips_actions.py b/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
index de6eca3..4e0efaa 100644
--- a/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
@@ -18,9 +18,9 @@
 from nose.plugins.attrib import attr
 import unittest2 as unittest
 
+from tempest import clients
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
-from tempest import openstack
 from tempest.tests.compute import base
 
 
diff --git a/tempest/tests/compute/images/test_images.py b/tempest/tests/compute/images/test_images.py
index 1fbbf2e..5c07626 100644
--- a/tempest/tests/compute/images/test_images.py
+++ b/tempest/tests/compute/images/test_images.py
@@ -19,11 +19,11 @@
 from nose.plugins.attrib import attr
 import unittest2 as unittest
 
+from tempest import clients
 from tempest.common.utils.data_utils import parse_image_id
 from tempest.common.utils.data_utils import rand_name
 import tempest.config
 from tempest import exceptions
-from tempest import openstack
 from tempest.tests import compute
 from tempest.tests.compute import base
 
@@ -391,12 +391,12 @@
             if cls.config.compute.allow_tenant_isolation:
                 creds = cls._get_isolated_creds()
                 username, tenant_name, password = creds
-                cls.alt_manager = openstack.Manager(username=username,
-                                                    password=password,
-                                                    tenant_name=tenant_name)
+                cls.alt_manager = clients.Manager(username=username,
+                                                  password=password,
+                                                  tenant_name=tenant_name)
             else:
                 # Use the alt_XXX credentials in the config file
-                cls.alt_manager = openstack.AltManager()
+                cls.alt_manager = clients.AltManager()
             cls.alt_client = cls.alt_manager.images_client
 
 
@@ -418,10 +418,10 @@
             if cls.config.compute.allow_tenant_isolation:
                 creds = cls._get_isolated_creds()
                 username, tenant_name, password = creds
-                cls.alt_manager = openstack.Manager(username=username,
-                                                    password=password,
-                                                    tenant_name=tenant_name)
+                cls.alt_manager = clients.Manager(username=username,
+                                                  password=password,
+                                                  tenant_name=tenant_name)
             else:
                 # Use the alt_XXX credentials in the config file
-                cls.alt_manager = openstack.AltManager()
+                cls.alt_manager = clients.AltManager()
             cls.alt_client = cls.alt_manager.images_client
diff --git a/tempest/tests/compute/images/test_images_whitebox.py b/tempest/tests/compute/images/test_images_whitebox.py
index 8eb258c..c2a5b05 100644
--- a/tempest/tests/compute/images/test_images_whitebox.py
+++ b/tempest/tests/compute/images/test_images_whitebox.py
@@ -57,9 +57,9 @@
 
         instances = self.meta.tables['instances']
         stmt = instances.update().where(instances.c.uuid == server_id).values(
-                                                               deleted=deleted,
-                                                             vm_state=vm_state,
-                                                         task_state=task_state)
+            deleted=deleted,
+            vm_state=vm_state,
+            task_state=task_state)
 
         self.connection.execute(stmt, autocommit=True)
 
diff --git a/tempest/tests/compute/servers/test_list_servers_negative.py b/tempest/tests/compute/servers/test_list_servers_negative.py
index f891c49..dc6e339 100644
--- a/tempest/tests/compute/servers/test_list_servers_negative.py
+++ b/tempest/tests/compute/servers/test_list_servers_negative.py
@@ -21,9 +21,9 @@
 import nose
 import unittest2 as unittest
 
+from tempest import clients
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
-from tempest import openstack
 from tempest.tests import compute
 from tempest.tests.compute.base import BaseComputeTest
 
@@ -40,12 +40,12 @@
             if cls.config.compute.allow_tenant_isolation:
                 creds = cls._get_isolated_creds()
                 username, tenant_name, password = creds
-                cls.alt_manager = openstack.Manager(username=username,
-                                                    password=password,
-                                                    tenant_name=tenant_name)
+                cls.alt_manager = clients.Manager(username=username,
+                                                  password=password,
+                                                  tenant_name=tenant_name)
             else:
                 # Use the alt_XXX credentials in the config file
-                cls.alt_manager = openstack.AltManager()
+                cls.alt_manager = clients.AltManager()
             cls.alt_client = cls.alt_manager.servers_client
 
         # Under circumstances when there is not a tenant/user
@@ -164,8 +164,8 @@
 
     def test_list_servers_by_changes_since(self):
         """Servers are listed by specifying changes-since date"""
-        resp, body = self.client.list_servers(
-                         {'changes-since': '2011-01-01T12:34:00Z'})
+        changes_since = {'changes-since': '2011-01-01T12:34:00Z'}
+        resp, body = self.client.list_servers(changes_since)
         self.assertEqual('200', resp['status'])
         # changes-since returns all instances, including deleted.
         num_expected = (len(self.existing_fixtures) +
@@ -179,8 +179,8 @@
 
     def test_list_servers_by_changes_since_future_date(self):
         """Return an empty list when a date in the future is passed"""
-        resp, body = self.client.list_servers(
-                         {'changes-since': '2051-01-01T12:34:00Z'})
+        changes_since = {'changes-since': '2051-01-01T12:34:00Z'}
+        resp, body = self.client.list_servers(changes_since)
         self.assertEqual('200', resp['status'])
         self.assertEqual(0, len(body['servers']))
 
diff --git a/tempest/tests/compute/servers/test_server_actions.py b/tempest/tests/compute/servers/test_server_actions.py
index 63308fc..df971e9 100644
--- a/tempest/tests/compute/servers/test_server_actions.py
+++ b/tempest/tests/compute/servers/test_server_actions.py
@@ -124,7 +124,7 @@
         self.assertEqual(self.server_id, rebuilt_server['id'])
         rebuilt_image_id = rebuilt_server['image']['id']
         self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id))
-        self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id'])
+        self.assertEqual(self.flavor_ref, int(rebuilt_server['flavor']['id']))
 
         #Verify the server properties after the rebuild completes
         self.client.wait_for_server_status(rebuilt_server['id'], 'ACTIVE')
diff --git a/tempest/tests/compute/servers/test_servers_negative.py b/tempest/tests/compute/servers/test_servers_negative.py
index c9ed5ed..fd067cd 100644
--- a/tempest/tests/compute/servers/test_servers_negative.py
+++ b/tempest/tests/compute/servers/test_servers_negative.py
@@ -21,9 +21,9 @@
 from nose.plugins.attrib import attr
 import unittest2 as unittest
 
+from tempest import clients
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
-from tempest import openstack
 from tempest.tests.compute.base import BaseComputeTest
 
 
@@ -35,7 +35,7 @@
         super(ServersNegativeTest, cls).setUpClass()
         cls.client = cls.servers_client
         cls.img_client = cls.images_client
-        cls.alt_os = openstack.AltManager()
+        cls.alt_os = clients.AltManager()
         cls.alt_client = cls.alt_os.servers_client
 
     @attr(type='negative')
diff --git a/tempest/tests/compute/servers/test_servers_whitebox.py b/tempest/tests/compute/servers/test_servers_whitebox.py
index c02a1a2..16254e7 100644
--- a/tempest/tests/compute/servers/test_servers_whitebox.py
+++ b/tempest/tests/compute/servers/test_servers_whitebox.py
@@ -56,8 +56,8 @@
         """Disallow server creation when tenant's vcpu quota is full"""
         quotas = self.meta.tables['quotas']
         stmt = quotas.select().where(
-                              quotas.c.project_id == self.tenant_id).where(
-                              quotas.c.resource == 'cores')
+            quotas.c.project_id == self.tenant_id).where(
+            quotas.c.resource == 'cores')
         result = self.connection.execute(stmt).first()
 
         # Set vcpu quota for tenant if not already set
@@ -88,8 +88,8 @@
         """Disallow server creation when tenant's memory quota is full"""
         quotas = self.meta.tables['quotas']
         stmt = quotas.select().where(
-                              quotas.c.project_id == self.tenant_id).where(
-                              quotas.c.resource == 'ram')
+            quotas.c.project_id == self.tenant_id).where(
+            quotas.c.resource == 'ram')
         result = self.connection.execute(stmt).first()
 
         # Set memory quota for tenant if not already set
@@ -123,9 +123,9 @@
 
         instances = self.meta.tables['instances']
         stmt = instances.update().where(instances.c.uuid == server_id).values(
-                                                               deleted=deleted,
-                                                             vm_state=vm_state,
-                                                         task_state=task_state)
+            deleted=deleted,
+            vm_state=vm_state,
+            task_state=task_state)
         self.connection.execute(stmt, autocommit=True)
 
     def _test_delete_server_base(self, vm_state, task_state):
diff --git a/tempest/tests/compute/test_authorization.py b/tempest/tests/compute/test_authorization.py
index a31ee49..64f6464 100644
--- a/tempest/tests/compute/test_authorization.py
+++ b/tempest/tests/compute/test_authorization.py
@@ -20,10 +20,10 @@
 from nose.tools import raises
 import unittest2 as unittest
 
+from tempest import clients
 from tempest.common.utils.data_utils import parse_image_id
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
-from tempest import openstack
 from tempest.tests import compute
 from tempest.tests.compute.base import BaseComputeTest
 
@@ -47,12 +47,12 @@
         if cls.config.compute.allow_tenant_isolation:
             creds = cls._get_isolated_creds()
             username, tenant_name, password = creds
-            cls.alt_manager = openstack.Manager(username=username,
-                                                password=password,
-                                                tenant_name=tenant_name)
+            cls.alt_manager = clients.Manager(username=username,
+                                              password=password,
+                                              tenant_name=tenant_name)
         else:
             # Use the alt_XXX credentials in the config file
-            cls.alt_manager = openstack.AltManager()
+            cls.alt_manager = clients.AltManager()
 
         cls.alt_client = cls.alt_manager.servers_client
         cls.alt_images_client = cls.alt_manager.images_client
diff --git a/tempest/tests/compute/volumes/test_attach_volume.py b/tempest/tests/compute/volumes/test_attach_volume.py
index b95a9fd..09b146b 100644
--- a/tempest/tests/compute/volumes/test_attach_volume.py
+++ b/tempest/tests/compute/volumes/test_attach_volume.py
@@ -18,10 +18,10 @@
 from nose.plugins.attrib import attr
 import unittest2 as unittest
 
+from tempest import clients
 from tempest.common.utils.data_utils import rand_name
 from tempest.common.utils.linux.remote_client import RemoteClient
 import tempest.config
-from tempest import openstack
 from tempest.tests.compute import base
 
 
diff --git a/tempest/tests/identity/base.py b/tempest/tests/identity/base.py
index 618b328..3867f0a 100644
--- a/tempest/tests/identity/base.py
+++ b/tempest/tests/identity/base.py
@@ -18,15 +18,15 @@
 import nose
 import unittest2 as unittest
 
+from tempest import clients
 from tempest.common.utils.data_utils import rand_name
-from tempest import openstack
 
 
 class BaseIdAdminTest(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
-        os = openstack.IdentityManager(interface=cls._interface)
+        os = clients.IdentityManager(interface=cls._interface)
         cls.client = os.admin_client
         cls.token_client = os.token_client
 
@@ -35,7 +35,7 @@
 
         cls.data = DataGenerator(cls.client)
 
-        os = openstack.IdentityNaManager(interface=cls._interface)
+        os = clients.IdentityNaManager(interface=cls._interface)
         cls.non_admin_client = os.admin_client
 
     @classmethod
@@ -111,8 +111,8 @@
             self.test_tenant = rand_name('test_tenant_')
             self.test_description = rand_name('desc_')
             resp, self.tenant = self.client.create_tenant(
-                                                        name=self.test_tenant,
-                                             description=self.test_description)
+                name=self.test_tenant,
+                description=self.test_description)
             self.tenants.append(self.tenant)
 
         def setup_test_role(self):
diff --git a/tempest/tests/image/test_images.py b/tempest/tests/image/test_images.py
index 3feac36..a3c9390 100644
--- a/tempest/tests/image/test_images.py
+++ b/tempest/tests/image/test_images.py
@@ -30,7 +30,7 @@
 except ImportError:
     pass
 
-from tempest import openstack
+from tempest import clients
 
 
 class CreateRegisterImagesTest(unittest.TestCase):
@@ -43,7 +43,7 @@
     def setUpClass(cls):
         if not GLANCE_INSTALLED:
             raise SkipTest('Glance not installed')
-        cls.os = openstack.ServiceManager()
+        cls.os = clients.ServiceManager()
         cls.client = cls.os.images.get_client()
         cls.created_images = []
 
@@ -138,7 +138,7 @@
     def setUpClass(cls):
         if not GLANCE_INSTALLED:
             raise SkipTest('Glance not installed')
-        cls.os = openstack.ServiceManager()
+        cls.os = clients.ServiceManager()
         cls.client = cls.os.images.get_client()
         cls.created_images = []
 
diff --git a/tempest/tests/network/base.py b/tempest/tests/network/base.py
index 78a69f8..f993441 100644
--- a/tempest/tests/network/base.py
+++ b/tempest/tests/network/base.py
@@ -18,16 +18,16 @@
 import nose
 import unittest2 as unittest
 
+from tempest import clients
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
-from tempest import openstack
 
 
 class BaseNetworkTest(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
-        os = openstack.Manager()
+        os = clients.Manager()
         client = os.network_client
         config = os.config
         networks = []
diff --git a/tempest/tests/object_storage/base.py b/tempest/tests/object_storage/base.py
index f29798f..3992b13 100644
--- a/tempest/tests/object_storage/base.py
+++ b/tempest/tests/object_storage/base.py
@@ -18,16 +18,16 @@
 import nose
 import unittest2 as unittest
 
+from tempest import clients
 import tempest.config
 from tempest import exceptions
-from tempest import openstack
 
 
 class BaseObjectTest(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
-        cls.os = openstack.Manager()
+        cls.os = clients.Manager()
         cls.object_client = cls.os.object_client
         cls.container_client = cls.os.container_client
         cls.account_client = cls.os.account_client
diff --git a/tempest/tests/volume/base.py b/tempest/tests/volume/base.py
index f4401ff..549f541 100644
--- a/tempest/tests/volume/base.py
+++ b/tempest/tests/volume/base.py
@@ -21,10 +21,10 @@
 import nose
 import unittest2 as unittest
 
+from tempest import clients
 from tempest.common.utils.data_utils import rand_name
 from tempest import config
 from tempest import exceptions
-from tempest import openstack
 
 LOG = logging.getLogger(__name__)
 
@@ -41,11 +41,11 @@
         if cls.config.compute.allow_tenant_isolation:
             creds = cls._get_isolated_creds()
             username, tenant_name, password = creds
-            os = openstack.Manager(username=username,
-                                   password=password,
-                                   tenant_name=tenant_name)
+            os = clients.Manager(username=username,
+                                 password=password,
+                                 tenant_name=tenant_name)
         else:
-            os = openstack.Manager()
+            os = clients.Manager()
 
         cls.os = os
         cls.volumes_client = os.volumes_client
@@ -73,7 +73,7 @@
         """
         Returns an instance of the Identity Admin API client
         """
-        os = openstack.IdentityManager()
+        os = clients.IdentityManager()
         return os.admin_client
 
     @classmethod
diff --git a/tox.ini b/tox.ini
index e2dbdc8..2d8e627 100644
--- a/tox.ini
+++ b/tox.ini
@@ -15,4 +15,4 @@
 
 [testenv:pep8]
 deps = pep8==1.3.3
-commands = python tools/hacking.py --ignore=N4,E121,E122,E125,E126 --repeat --show-source --exclude=.venv,.tox,dist,doc,openstack .
+commands = python tools/hacking.py --ignore=N4,E122,E125,E126 --repeat --show-source --exclude=.venv,.tox,dist,doc,openstack,*egg .