Merge "Enable test_absolute_limits.test_absLimits_get"
diff --git a/HACKING.rst b/HACKING.rst
index fed4130..a546f8c 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -153,10 +153,25 @@
                              kwarg2=dict_of_numbers)
 
 
+openstack-common
+----------------
+
+A number of modules from openstack-common are imported into the project.
+
+These modules are "incubating" in openstack-common and are kept in sync
+with the help of openstack-common's update.py script. See:
+
+  http://wiki.openstack.org/CommonLibrary#Incubation
+
+The copy of the code should never be directly modified here. Please
+always update openstack-common first and then run the script to copy
+the changes across.
+
+
 OpenStack Trademark
 -------------------
 
-OpenStack is a registered trademark of OpenStack, LLC, and uses the
+OpenStack is a registered trademark of the OpenStack Foundation, and uses the
 following capitalization:
 
    OpenStack
diff --git a/cli/__init__.py b/cli/__init__.py
new file mode 100644
index 0000000..cea0b62
--- /dev/null
+++ b/cli/__init__.py
@@ -0,0 +1,36 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import logging
+
+from tempest.openstack.common import cfg
+
+LOG = logging.getLogger(__name__)
+
+cli_opts = [
+    cfg.BoolOpt('enabled',
+                default=True,
+                help="enable cli tests"),
+    cfg.StrOpt('cli_dir',
+               default='/usr/local/bin/',
+               help="directory where python client binaries are located"),
+]
+
+CONF = cfg.CONF
+cli_group = cfg.OptGroup(name='cli', title="cli Configuration Options")
+CONF.register_group(cli_group)
+CONF.register_opts(cli_opts, group=cli_group)
diff --git a/cli/simple_read_only/README.txt b/cli/simple_read_only/README.txt
new file mode 100644
index 0000000..ca5fa2f
--- /dev/null
+++ b/cli/simple_read_only/README.txt
@@ -0,0 +1 @@
+This directory consists of simple read only python client tests.
diff --git a/tempest/services/compute/admin/__init__.py b/cli/simple_read_only/__init__.py
similarity index 100%
copy from tempest/services/compute/admin/__init__.py
copy to cli/simple_read_only/__init__.py
diff --git a/cli/simple_read_only/test_compute.py b/cli/simple_read_only/test_compute.py
new file mode 100644
index 0000000..742b5a4
--- /dev/null
+++ b/cli/simple_read_only/test_compute.py
@@ -0,0 +1,104 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import logging
+import shlex
+import subprocess
+
+import testtools
+
+import cli
+
+from tempest import config
+from tempest.openstack.common import cfg
+
+
+CONF = cfg.CONF
+
+
+LOG = logging.getLogger(__name__)
+
+
+class SimpleReadOnlyNovaCLientTest(testtools.TestCase):
+
+    """
+    This is a first pass at a simple read only python-novaclient test. This
+    only exercises client commands that are read only.
+
+    This should test commands:
+    * as a regular user
+    * as a admin user
+    * with and without optional parameters
+    * initially just check return codes, and later test command outputs
+
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        if not CONF.cli.enabled:
+            msg = "cli testing disabled"
+            raise cls.skipException(msg)
+        cls.identity = config.TempestConfig().identity
+        super(SimpleReadOnlyNovaCLientTest, cls).setUpClass()
+
+    def nova(self, action, flags='', params='', admin=True, fail_ok=False):
+        """Executes nova command for the given action."""
+        #TODO(jogo) make admin=False work
+        creds = ('--os-username %s --os-tenant-name %s --os-password %s '
+                 '--os-auth-url %s ' % (self.identity.admin_username,
+                 self.identity.admin_tenant_name, self.identity.admin_password,
+                 self.identity.uri))
+        flags = creds + ' ' + flags
+        cmd = ' '.join([CONF.cli.cli_dir + 'nova', flags, action, params])
+        LOG.info("running: '%s'" % cmd)
+        cmd = shlex.split(cmd)
+        result = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+        return result
+
+    def test_admin_version(self):
+        self.nova('', flags='--version')
+
+    def test_admin_timing(self):
+        self.nova('list', flags='--timing')
+
+    def test_admin_timeout(self):
+        self.nova('list', flags='--timeout 2')
+
+    def test_admin_debug_list(self):
+        self.nova('list', flags='--debug')
+
+    def test_admin_fake_action(self):
+        self.assertRaises(subprocess.CalledProcessError,
+                          self.nova,
+                          'this-does-nova-exist')
+
+    def test_admin_aggregate_list(self):
+        self.nova('aggregate-list')
+
+    def test_admin_cloudpipe_list(self):
+        self.nova('cloudpipe-list')
+
+    def test_admin_image_list(self):
+        self.nova('image-list')
+
+    def test_admin_dns_domains(self):
+        self.nova('dns-domains')
+
+    def test_admin_flavor_list(self):
+        self.nova('flavor-list')
+
+    #TODO(jogo) add more tests
diff --git a/run_tests.sh b/run_tests.sh
index e26c51c..3a2bd94 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -11,28 +11,12 @@
   echo "  -s, --smoke              Only run smoke tests"
   echo "  -w, --whitebox           Only run whitebox tests"
   echo "  -c, --nova-coverage      Enable Nova coverage collection"
+  echo "  -C, --config             Config file location"
   echo "  -p, --pep8               Just run pep8"
   echo "  -h, --help               Print this usage message"
   echo "  -d, --debug              Debug this script -- set -o xtrace"
   echo "  -S, --stdout             Don't capture stdout"
-  exit
-}
-
-function process_option {
-  case "$1" in
-    -h|--help) usage;;
-    -V|--virtual-env) always_venv=1; never_venv=0;;
-    -N|--no-virtual-env) always_venv=0; never_venv=1;;
-    -n|--no-site-packages) no_site_packages=1;;
-    -f|--force) force=1;;
-    -d|--debug) set -o xtrace;;
-    -c|--nova-coverage) let nova_coverage=1;;
-    -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
+  echo "  -- [NOSEOPTIONS]         After the first '--' you can pass arbitrary arguments to nosetests "
 }
 
 noseargs=""
@@ -45,6 +29,44 @@
 force=0
 wrapper=""
 nova_coverage=0
+config_file=""
+
+if ! options=$(getopt -o VNnfswcphdsC: -l virtual-env,no-virtual-env,no-site-packages,force,smoke,whitebox,nova-coverage,pep8,help,debug,stdout,config: -- "$@")
+then
+    # parse error
+    usage
+    exit 1
+fi
+
+eval set -- $options
+first_uu=yes
+while [ $# -gt 0 ]; do
+  case "$1" in
+    -h|--help) usage; exit;;
+    -V|--virtual-env) always_venv=1; never_venv=0;;
+    -N|--no-virtual-env) always_venv=0; never_venv=1;;
+    -n|--no-site-packages) no_site_packages=1;;
+    -f|--force) force=1;;
+    -d|--debug) set -o xtrace;;
+    -c|--nova-coverage) let nova_coverage=1;;
+    -C|--config) config_file=$2; shift;;
+    -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";;
+    --) [ "yes" == "$first_uu" ] || noseargs="$noseargs $1"; first_uu=no  ;;
+    *) noseargs="$noseargs $1"
+  esac
+  shift
+done
+
+if [ -n "$config_file" ]; then
+    config_file=`readlink -f "$config_file"`
+    export TEMPEST_CONFIG_DIR=`dirname "$config_file"`
+    export TEMPEST_CONFIG=`basename "$config_file"`
+fi
+
+cd `dirname "$0"`
 
 export NOSE_WITH_OPENSTACK=1
 export NOSE_OPENSTACK_COLOR=1
@@ -53,10 +75,6 @@
 export NOSE_OPENSTACK_SHOW_ELAPSED=1
 export NOSE_OPENSTACK_STDOUT=1
 
-for arg in "$@"; do
-  process_option $arg
-done
-
 if [ $no_site_packages -eq 1 ]; then
   installvenvopts="--no-site-packages"
 fi
@@ -68,7 +86,6 @@
   noseargs="$noseargs tempest"
 fi
 
-
 function run_tests {
   ${wrapper} $NOSETESTS
 }
diff --git a/tempest/clients.py b/tempest/clients.py
index 28abb79..7d314f3 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -19,20 +19,19 @@
 
 from tempest import config
 from tempest import exceptions
-from tempest.services.boto.clients import APIClientEC2
-from tempest.services.boto.clients import ObjectClientS3
+from tempest.services import botoclients
 from tempest.services.compute.json.extensions_client import \
     ExtensionsClientJSON
 from tempest.services.compute.json.flavors_client import FlavorsClientJSON
 from tempest.services.compute.json.floating_ips_client import \
     FloatingIPsClientJSON
 from tempest.services.compute.json.images_client import ImagesClientJSON
+from tempest.services.compute.json.keypairs_client import KeyPairsClientJSON
 from tempest.services.compute.json.limits_client import LimitsClientJSON
-from tempest.services.compute.json.servers_client import ServersClientJSON
+from tempest.services.compute.json.quotas_client import QuotasClientJSON
 from tempest.services.compute.json.security_groups_client import \
     SecurityGroupsClientJSON
-from tempest.services.compute.json.keypairs_client import KeyPairsClientJSON
-from tempest.services.compute.json.quotas_client import QuotasClientJSON
+from tempest.services.compute.json.servers_client import ServersClientJSON
 from tempest.services.compute.json.volumes_extensions_client import \
     VolumesExtensionsClientJSON
 from tempest.services.compute.xml.extensions_client import ExtensionsClientXML
@@ -52,19 +51,23 @@
 from tempest.services.identity.json.identity_client import TokenClientJSON
 from tempest.services.identity.xml.identity_client import IdentityClientXML
 from tempest.services.identity.xml.identity_client import TokenClientXML
-from tempest.services.image import service as image_service
+from tempest.services.image.json.image_client import ImageClientJSON
 from tempest.services.network.json.network_client import NetworkClient
 from tempest.services.object_storage.account_client import AccountClient
-from tempest.services.object_storage.container_client import ContainerClient
-from tempest.services.object_storage.object_client import ObjectClient
-from tempest.services.volume.json.snapshots_client import SnapshotsClientJSON
-from tempest.services.volume.json.volumes_client import VolumesClientJSON
-from tempest.services.volume.xml.snapshots_client import SnapshotsClientXML
-from tempest.services.volume.xml.volumes_client import VolumesClientXML
-from tempest.services.object_storage.object_client import \
-    ObjectClientCustomizedHeader
 from tempest.services.object_storage.account_client import \
     AccountClientCustomizedHeader
+from tempest.services.object_storage.container_client import ContainerClient
+from tempest.services.object_storage.object_client import ObjectClient
+from tempest.services.object_storage.object_client import \
+    ObjectClientCustomizedHeader
+from tempest.services.volume.json.admin.volume_types_client import \
+    VolumeTypesClientJSON
+from tempest.services.volume.json.snapshots_client import SnapshotsClientJSON
+from tempest.services.volume.json.volumes_client import VolumesClientJSON
+from tempest.services.volume.xml.admin.volume_types_client import \
+    VolumeTypesClientXML
+from tempest.services.volume.xml.snapshots_client import SnapshotsClientXML
+from tempest.services.volume.xml.volumes_client import VolumesClientXML
 
 LOG = logging.getLogger(__name__)
 
@@ -123,6 +126,11 @@
     "xml": VolumesClientXML,
 }
 
+VOLUME_TYPES_CLIENTS = {
+    "json": VolumeTypesClientJSON,
+    "xml": VolumeTypesClientXML,
+}
+
 IDENTITY_CLIENT = {
     "json": IdentityClientJSON,
     "xml": IdentityClientXML,
@@ -193,6 +201,8 @@
             self.floating_ips_client = FLOAT_CLIENTS[interface](*client_args)
             self.snapshots_client = SNAPSHOTS_CLIENTS[interface](*client_args)
             self.volumes_client = VOLUMES_CLIENTS[interface](*client_args)
+            self.volume_types_client = \
+                VOLUME_TYPES_CLIENTS[interface](*client_args)
             self.identity_client = IDENTITY_CLIENT[interface](*client_args)
             self.token_client = TOKEN_CLIENT[interface](self.config)
             self.security_groups_client = \
@@ -202,10 +212,11 @@
             raise exceptions.InvalidConfiguration(msg)
         self.network_client = NetworkClient(*client_args)
         self.account_client = AccountClient(*client_args)
+        self.image_client = ImageClientJSON(*client_args)
         self.container_client = ContainerClient(*client_args)
         self.object_client = ObjectClient(*client_args)
-        self.ec2api_client = APIClientEC2(*client_args)
-        self.s3_client = ObjectClientS3(*client_args)
+        self.ec2api_client = botoclients.APIClientEC2(*client_args)
+        self.s3_client = botoclients.ObjectClientS3(*client_args)
         self.custom_object_client = ObjectClientCustomizedHeader(*client_args)
         self.custom_account_client = \
             AccountClientCustomizedHeader(*client_args)
@@ -254,16 +265,3 @@
                       conf.compute_admin.password,
                       conf.compute_admin.tenant_name,
                       interface=interface)
-
-
-class ServiceManager(object):
-
-    """
-    Top-level object housing clients for OpenStack APIs
-    """
-
-    def __init__(self):
-        self.config = config.TempestConfig()
-        self.services = {}
-        self.services['image'] = image_service.Service(self.config)
-        self.images = self.services['image']
diff --git a/tempest/common/glance_http.py b/tempest/common/glance_http.py
new file mode 100644
index 0000000..faac1a0
--- /dev/null
+++ b/tempest/common/glance_http.py
@@ -0,0 +1,375 @@
+# Copyright 2012 OpenStack LLC.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+# Originally copied from python-glanceclient
+
+import copy
+import httplib
+import logging
+import posixpath
+import socket
+import StringIO
+import struct
+import urlparse
+
+try:
+    import json
+except ImportError:
+    import simplejson as json
+
+# Python 2.5 compat fix
+if not hasattr(urlparse, 'parse_qsl'):
+    import cgi
+    urlparse.parse_qsl = cgi.parse_qsl
+
+import OpenSSL
+
+from tempest import exceptions as exc
+
+
+LOG = logging.getLogger(__name__)
+USER_AGENT = 'tempest'
+CHUNKSIZE = 1024 * 64  # 64kB
+
+
+class HTTPClient(object):
+
+    def __init__(self, endpoint, **kwargs):
+        self.endpoint = endpoint
+        endpoint_parts = self.parse_endpoint(self.endpoint)
+        self.endpoint_scheme = endpoint_parts.scheme
+        self.endpoint_hostname = endpoint_parts.hostname
+        self.endpoint_port = endpoint_parts.port
+        self.endpoint_path = endpoint_parts.path
+
+        self.connection_class = self.get_connection_class(self.endpoint_scheme)
+        self.connection_kwargs = self.get_connection_kwargs(
+            self.endpoint_scheme, **kwargs)
+
+        self.auth_token = kwargs.get('token')
+
+    @staticmethod
+    def parse_endpoint(endpoint):
+        return urlparse.urlparse(endpoint)
+
+    @staticmethod
+    def get_connection_class(scheme):
+        if scheme == 'https':
+            return VerifiedHTTPSConnection
+        else:
+            return httplib.HTTPConnection
+
+    @staticmethod
+    def get_connection_kwargs(scheme, **kwargs):
+        _kwargs = {'timeout': float(kwargs.get('timeout', 600))}
+
+        if scheme == 'https':
+            _kwargs['cacert'] = kwargs.get('cacert', None)
+            _kwargs['cert_file'] = kwargs.get('cert_file', None)
+            _kwargs['key_file'] = kwargs.get('key_file', None)
+            _kwargs['insecure'] = kwargs.get('insecure', False)
+            _kwargs['ssl_compression'] = kwargs.get('ssl_compression', True)
+
+        return _kwargs
+
+    def get_connection(self):
+        _class = self.connection_class
+        try:
+            return _class(self.endpoint_hostname, self.endpoint_port,
+                          **self.connection_kwargs)
+        except httplib.InvalidURL:
+            raise exc.EndpointNotFound
+
+    def log_curl_request(self, method, url, kwargs):
+        curl = ['curl -i -X %s' % method]
+
+        for (key, value) in kwargs['headers'].items():
+            header = '-H \'%s: %s\'' % (key, value)
+            curl.append(header)
+
+        conn_params_fmt = [
+            ('key_file', '--key %s'),
+            ('cert_file', '--cert %s'),
+            ('cacert', '--cacert %s'),
+        ]
+        for (key, fmt) in conn_params_fmt:
+            value = self.connection_kwargs.get(key)
+            if value:
+                curl.append(fmt % value)
+
+        if self.connection_kwargs.get('insecure'):
+            curl.append('-k')
+
+        if 'body' in kwargs:
+            curl.append('-d \'%s\'' % kwargs['body'])
+
+        curl.append('%s%s' % (self.endpoint, url))
+        LOG.debug(' '.join(curl))
+
+    @staticmethod
+    def log_http_response(resp, body=None):
+        status = (resp.version / 10.0, resp.status, resp.reason)
+        dump = ['\nHTTP/%.1f %s %s' % status]
+        dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()])
+        dump.append('')
+        if body:
+            dump.extend([body, ''])
+        LOG.debug('\n'.join(dump))
+
+    def _http_request(self, url, method, **kwargs):
+        """ Send an http request with the specified characteristics.
+
+        Wrapper around httplib.HTTP(S)Connection.request to handle tasks such
+        as setting headers and error handling.
+        """
+        # Copy the kwargs so we can reuse the original in case of redirects
+        kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
+        kwargs['headers'].setdefault('User-Agent', USER_AGENT)
+        if self.auth_token:
+            kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
+
+        self.log_curl_request(method, url, kwargs)
+        conn = self.get_connection()
+
+        try:
+            conn_url = posixpath.normpath('%s/%s' % (self.endpoint_path, url))
+            if kwargs['headers'].get('Transfer-Encoding') == 'chunked':
+                conn.putrequest(method, conn_url)
+                for header, value in kwargs['headers'].items():
+                    conn.putheader(header, value)
+                conn.endheaders()
+                chunk = kwargs['body'].read(CHUNKSIZE)
+                # Chunk it, baby...
+                while chunk:
+                    conn.send('%x\r\n%s\r\n' % (len(chunk), chunk))
+                    chunk = kwargs['body'].read(CHUNKSIZE)
+                conn.send('0\r\n\r\n')
+            else:
+                conn.request(method, conn_url, **kwargs)
+            resp = conn.getresponse()
+        except socket.gaierror as e:
+            message = "Error finding address for %(url)s: %(e)s" % locals()
+            raise exc.EndpointNotFound
+        except (socket.error, socket.timeout) as e:
+            endpoint = self.endpoint
+            message = "Error communicating with %(endpoint)s %(e)s" % locals()
+            raise exc.TimeoutException
+
+        body_iter = ResponseBodyIterator(resp)
+
+        # Read body into string if it isn't obviously image data
+        if resp.getheader('content-type', None) != 'application/octet-stream':
+            body_str = ''.join([chunk for chunk in body_iter])
+            self.log_http_response(resp, body_str)
+            body_iter = StringIO.StringIO(body_str)
+        else:
+            self.log_http_response(resp)
+
+        return resp, body_iter
+
+    def json_request(self, method, url, **kwargs):
+        kwargs.setdefault('headers', {})
+        kwargs['headers'].setdefault('Content-Type', 'application/json')
+
+        if 'body' in kwargs:
+            kwargs['body'] = json.dumps(kwargs['body'])
+
+        resp, body_iter = self._http_request(url, method, **kwargs)
+
+        if 'application/json' in resp.getheader('content-type', None):
+            body = ''.join([chunk for chunk in body_iter])
+            try:
+                body = json.loads(body)
+            except ValueError:
+                LOG.error('Could not decode response body as JSON')
+        else:
+            body = None
+
+        return resp, body
+
+    def raw_request(self, method, url, **kwargs):
+        kwargs.setdefault('headers', {})
+        kwargs['headers'].setdefault('Content-Type',
+                                     'application/octet-stream')
+        if 'body' in kwargs:
+            if (hasattr(kwargs['body'], 'read')
+                    and method.lower() in ('post', 'put')):
+                # We use 'Transfer-Encoding: chunked' because
+                # body size may not always be known in advance.
+                kwargs['headers']['Transfer-Encoding'] = 'chunked'
+        return self._http_request(url, method, **kwargs)
+
+
+class OpenSSLConnectionDelegator(object):
+    """
+    An OpenSSL.SSL.Connection delegator.
+
+    Supplies an additional 'makefile' method which httplib requires
+    and is not present in OpenSSL.SSL.Connection.
+
+    Note: Since it is not possible to inherit from OpenSSL.SSL.Connection
+    a delegator must be used.
+    """
+    def __init__(self, *args, **kwargs):
+        self.connection = OpenSSL.SSL.Connection(*args, **kwargs)
+
+    def __getattr__(self, name):
+        return getattr(self.connection, name)
+
+    def makefile(self, *args, **kwargs):
+        return socket._fileobject(self.connection, *args, **kwargs)
+
+
+class VerifiedHTTPSConnection(httplib.HTTPSConnection):
+    """
+    Extended HTTPSConnection which uses the OpenSSL library
+    for enhanced SSL support.
+    Note: Much of this functionality can eventually be replaced
+          with native Python 3.3 code.
+    """
+    def __init__(self, host, port=None, key_file=None, cert_file=None,
+                 cacert=None, timeout=None, insecure=False,
+                 ssl_compression=True):
+        httplib.HTTPSConnection.__init__(self, host, port,
+                                         key_file=key_file,
+                                         cert_file=cert_file)
+        self.key_file = key_file
+        self.cert_file = cert_file
+        self.timeout = timeout
+        self.insecure = insecure
+        self.ssl_compression = ssl_compression
+        self.cacert = cacert
+        self.setcontext()
+
+    @staticmethod
+    def host_matches_cert(host, x509):
+        """
+        Verify that the the x509 certificate we have received
+        from 'host' correctly identifies the server we are
+        connecting to, ie that the certificate's Common Name
+        or a Subject Alternative Name matches 'host'.
+        """
+        # First see if we can match the CN
+        if x509.get_subject().commonName == host:
+            return True
+
+        # Also try Subject Alternative Names for a match
+        san_list = None
+        for i in xrange(x509.get_extension_count()):
+            ext = x509.get_extension(i)
+            if ext.get_short_name() == 'subjectAltName':
+                san_list = str(ext)
+                for san in ''.join(san_list.split()).split(','):
+                    if san == "DNS:%s" % host:
+                        return True
+
+        # Server certificate does not match host
+        msg = ('Host "%s" does not match x509 certificate contents: '
+               'CommonName "%s"' % (host, x509.get_subject().commonName))
+        if san_list is not None:
+            msg = msg + ', subjectAltName "%s"' % san_list
+        raise exc.SSLCertificateError(msg)
+
+    def verify_callback(self, connection, x509, errnum,
+                        depth, preverify_ok):
+        if x509.has_expired():
+            msg = "SSL Certificate expired on '%s'" % x509.get_notAfter()
+            raise exc.SSLCertificateError(msg)
+
+        if depth == 0 and preverify_ok is True:
+            # We verify that the host matches against the last
+            # certificate in the chain
+            return self.host_matches_cert(self.host, x509)
+        else:
+            # Pass through OpenSSL's default result
+            return preverify_ok
+
+    def setcontext(self):
+        """
+        Set up the OpenSSL context.
+        """
+        self.context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
+
+        if self.ssl_compression is False:
+            self.context.set_options(0x20000)  # SSL_OP_NO_COMPRESSION
+
+        if self.insecure is not True:
+            self.context.set_verify(OpenSSL.SSL.VERIFY_PEER,
+                                    self.verify_callback)
+        else:
+            self.context.set_verify(OpenSSL.SSL.VERIFY_NONE,
+                                    self.verify_callback)
+
+        if self.cert_file:
+            try:
+                self.context.use_certificate_file(self.cert_file)
+            except Exception, e:
+                msg = 'Unable to load cert from "%s" %s' % (self.cert_file, e)
+                raise exc.SSLConfigurationError(msg)
+            if self.key_file is None:
+                # We support having key and cert in same file
+                try:
+                    self.context.use_privatekey_file(self.cert_file)
+                except Exception, e:
+                    msg = ('No key file specified and unable to load key '
+                           'from "%s" %s' % (self.cert_file, e))
+                    raise exc.SSLConfigurationError(msg)
+
+        if self.key_file:
+            try:
+                self.context.use_privatekey_file(self.key_file)
+            except Exception, e:
+                msg = 'Unable to load key from "%s" %s' % (self.key_file, e)
+                raise exc.SSLConfigurationError(msg)
+
+        if self.cacert:
+            try:
+                self.context.load_verify_locations(self.cacert)
+            except Exception, e:
+                msg = 'Unable to load CA from "%s"' % (self.cacert, e)
+                raise exc.SSLConfigurationError(msg)
+        else:
+            self.context.set_default_verify_paths()
+
+    def connect(self):
+        """
+        Connect to an SSL port using the OpenSSL library and apply
+        per-connection parameters.
+        """
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        if self.timeout is not None:
+            # '0' microseconds
+            sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO,
+                            struct.pack('LL', self.timeout, 0))
+        self.sock = OpenSSLConnectionDelegator(self.context, sock)
+        self.sock.connect((self.host, self.port))
+
+
+class ResponseBodyIterator(object):
+    """A class that acts as an iterator over an HTTP response."""
+
+    def __init__(self, resp):
+        self.resp = resp
+
+    def __iter__(self):
+        while True:
+            yield self.next()
+
+    def next(self):
+        chunk = self.resp.read(CHUNKSIZE)
+        if chunk:
+            return chunk
+        else:
+            raise StopIteration()
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 5af4a40..e163126 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -275,6 +275,45 @@
         self._log_response(resp, resp_body)
         self.response_checker(method, url, headers, body, resp, resp_body)
 
+        self._error_checker(method, url, headers, body, resp, resp_body, depth,
+                            wait)
+
+        return resp, resp_body
+
+    def _error_checker(self, method, url,
+                       headers, body, resp, resp_body, depth=0, wait=None):
+
+        # NOTE(mtreinish): Check for httplib response from glance_http. The
+        # object can't be used here because importing httplib breaks httplib2.
+        # If another object from a class not imported were passed here as
+        # resp this could possibly fail
+        if str(type(resp)) == "<type 'instance'>":
+            ctype = resp.getheader('content-type')
+        else:
+            try:
+                ctype = resp['content-type']
+            # NOTE(mtreinish): Keystone delete user responses doesn't have a
+            # content-type header. (They don't have a body) So just pretend it
+            # is set.
+            except KeyError:
+                ctype = 'application/json'
+
+        JSON_ENC = ['application/json; charset=UTF-8', 'application/json',
+                    'application/json; charset=utf-8']
+        # NOTE(mtreinish): This is for compatibility with Glance and swift
+        # APIs. These are the return content types that Glance api v1
+        # (and occasionally swift) are using.
+        TXT_ENC = ['text/plain; charset=UTF-8', 'text/html; charset=UTF-8',
+                   'text/plain; charset=utf-8']
+        XML_ENC = ['application/xml', 'application/xml; charset=UTF-8']
+
+        if ctype in JSON_ENC or ctype in XML_ENC:
+            parse_resp = True
+        elif ctype in TXT_ENC:
+            parse_resp = False
+        else:
+            raise exceptions.RestClientException(str(resp.status))
+
         if resp.status == 401 or resp.status == 403:
             raise exceptions.Unauthorized()
 
@@ -282,44 +321,45 @@
             raise exceptions.NotFound(resp_body)
 
         if resp.status == 400:
-            resp_body = self._parse_resp(resp_body)
+            if parse_resp:
+                resp_body = self._parse_resp(resp_body)
             raise exceptions.BadRequest(resp_body)
 
         if resp.status == 409:
-            resp_body = self._parse_resp(resp_body)
+            if parse_resp:
+                resp_body = self._parse_resp(resp_body)
             raise exceptions.Duplicate(resp_body)
 
         if resp.status == 413:
-            resp_body = self._parse_resp(resp_body)
+            if parse_resp:
+                resp_body = self._parse_resp(resp_body)
             #Checking whether Absolute/Rate limit
             return self.check_over_limit(resp_body, method, url, headers, body,
                                          depth, wait)
 
         if resp.status in (500, 501):
-            resp_body = self._parse_resp(resp_body)
-            #I'm seeing both computeFault and cloudServersFault come back.
-            #Will file a bug to fix, but leave as is for now.
-
-            if 'cloudServersFault' in resp_body:
-                message = resp_body['cloudServersFault']['message']
-            elif 'computeFault' in resp_body:
-                message = resp_body['computeFault']['message']
-            elif 'error' in resp_body:  # Keystone errors
-                message = resp_body['error']['message']
-                raise exceptions.IdentityError(message)
-            elif 'message' in resp_body:
-                message = resp_body['message']
-            else:
-                message = resp_body
+            message = resp_body
+            if parse_resp:
+                resp_body = self._parse_resp(resp_body)
+                #I'm seeing both computeFault and cloudServersFault come back.
+                #Will file a bug to fix, but leave as is for now.
+                if 'cloudServersFault' in resp_body:
+                    message = resp_body['cloudServersFault']['message']
+                elif 'computeFault' in resp_body:
+                    message = resp_body['computeFault']['message']
+                elif 'error' in resp_body:  # Keystone errors
+                    message = resp_body['error']['message']
+                    raise exceptions.IdentityError(message)
+                elif 'message' in resp_body:
+                    message = resp_body['message']
 
             raise exceptions.ComputeFault(message)
 
         if resp.status >= 400:
-            resp_body = self._parse_resp(resp_body)
+            if parse_resp:
+                resp_body = self._parse_resp(resp_body)
             raise exceptions.RestClientException(str(resp.status))
 
-        return resp, resp_body
-
     def check_over_limit(self, resp_body, method, url,
                          headers, body, depth, wait):
         self.is_absolute_limit(resp_body['overLimit'])
diff --git a/tempest/config.py b/tempest/config.py
index ec48f67..c982dee 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -19,7 +19,6 @@
 import os
 import sys
 
-from tempest.common.utils import data_utils
 from tempest.common.utils.misc import singleton
 from tempest.openstack.common import cfg
 
@@ -237,6 +236,9 @@
     cfg.StrOpt('api_version',
                default='1',
                help="Version of the API"),
+    cfg.StrOpt('catalog_type',
+               default='image',
+               help='Catalog type of the Image service.')
 ]
 
 
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index fca2d2d..577aa13 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -116,6 +116,10 @@
     message = "Got compute fault"
 
 
+class ImageFault(TempestException):
+    message = "Got image fault"
+
+
 class IdentityError(TempestException):
     message = "Got identity error"
 
diff --git a/tempest/openstack/common/cfg.py b/tempest/openstack/common/cfg.py
index 1bbfe6a..08d9b47 100644
--- a/tempest/openstack/common/cfg.py
+++ b/tempest/openstack/common/cfg.py
@@ -217,7 +217,7 @@
         ...
      ]
 
-This module also contains a global instance of the CommonConfigOpts class
+This module also contains a global instance of the ConfigOpts class
 in order to support a common usage pattern in OpenStack::
 
     from tempest.openstack.common import cfg
@@ -236,10 +236,11 @@
 Positional command line arguments are supported via a 'positional' Opt
 constructor argument::
 
-    >>> CONF.register_cli_opt(MultiStrOpt('bar', positional=True))
+    >>> conf = ConfigOpts()
+    >>> conf.register_cli_opt(MultiStrOpt('bar', positional=True))
     True
-    >>> CONF(['a', 'b'])
-    >>> CONF.bar
+    >>> conf(['a', 'b'])
+    >>> conf.bar
     ['a', 'b']
 
 It is also possible to use argparse "sub-parsers" to parse additional
@@ -249,10 +250,11 @@
     ...     list_action = subparsers.add_parser('list')
     ...     list_action.add_argument('id')
     ...
-    >>> CONF.register_cli_opt(SubCommandOpt('action', handler=add_parsers))
+    >>> conf = ConfigOpts()
+    >>> conf.register_cli_opt(SubCommandOpt('action', handler=add_parsers))
     True
-    >>> CONF(['list', '10'])
-    >>> CONF.action.name, CONF.action.id
+    >>> conf(args=['list', '10'])
+    >>> conf.action.name, conf.action.id
     ('list', '10')
 
 """
@@ -861,7 +863,7 @@
                                            description=self.description,
                                            help=self.help)
 
-        if not self.handler is None:
+        if self.handler is not None:
             self.handler(subparsers)
 
 
@@ -1295,6 +1297,24 @@
         __import__(module_str)
         self._get_opt_info(name, group)
 
+    def import_group(self, group, module_str):
+        """Import an option group from a module.
+
+        Import a module and check that a given option group 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 group
+        defined by another module it can use this method to explicitly
+        declare the dependency.
+
+        :param group: an option OptGroup object or group name
+        :param module_str: the name of a module to import
+        :raises: ImportError, NoSuchGroupError
+        """
+        __import__(module_str)
+        self._get_group(group)
+
     @__clear_cache
     def set_override(self, name, override, group=None):
         """Override an opt value.
@@ -1417,7 +1437,7 @@
         logger.log(lvl, "=" * 80)
 
         def _sanitize(opt, value):
-            """Obfuscate values of options declared secret."""
+            """Obfuscate values of options declared secret"""
             return value if not opt.secret else '*' * len(str(value))
 
         for opt_name in sorted(self._opts):
@@ -1545,8 +1565,8 @@
         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:
+        if group_name not in self._groups:
+            if group is not None or not autocreate:
                 raise NoSuchGroupError(group_name)
 
             self.register_group(OptGroup(name=group_name))
@@ -1566,7 +1586,7 @@
             group = self._get_group(group)
             opts = group._opts
 
-        if not opt_name in opts:
+        if opt_name not in opts:
             raise NoSuchOptError(opt_name, group)
 
         return opts[opt_name]
@@ -1604,7 +1624,7 @@
             opt = info['opt']
 
             if opt.required:
-                if ('default' in info or 'override' in info):
+                if 'default' in info or 'override' in info:
                     continue
 
                 if self._get(opt.dest, group) is None:
@@ -1623,7 +1643,7 @@
         """
         self._args = args
 
-        for opt, group in self._all_cli_opts():
+        for opt, group in sorted(self._all_cli_opts()):
             opt._add_to_cli(self._oparser, group)
 
         return vars(self._oparser.parse_args(args))
@@ -1726,62 +1746,4 @@
             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()
+CONF = ConfigOpts()
diff --git a/tempest/openstack/common/iniparser.py b/tempest/openstack/common/iniparser.py
index b5cb604..9bf399f 100644
--- a/tempest/openstack/common/iniparser.py
+++ b/tempest/openstack/common/iniparser.py
@@ -54,7 +54,7 @@
 
         value = value.strip()
         if ((value and value[0] == value[-1]) and
-            (value[0] == "\"" or value[0] == "'")):
+                (value[0] == "\"" or value[0] == "'")):
             value = value[1:-1]
         return key.strip(), [value]
 
@@ -100,15 +100,15 @@
             self._assignment(key, value)
 
     def assignment(self, key, value):
-        """Called when a full assignment is parsed."""
+        """Called when a full assignment is parsed"""
         raise NotImplementedError()
 
     def new_section(self, section):
-        """Called when a new section is started."""
+        """Called when a new section is started"""
         raise NotImplementedError()
 
     def comment(self, comment):
-        """Called when a comment is parsed."""
+        """Called when a comment is parsed"""
         pass
 
     def error_invalid_assignment(self, line):
diff --git a/tempest/openstack/common/setup.py b/tempest/openstack/common/setup.py
index e6f72f0..35680b3 100644
--- a/tempest/openstack/common/setup.py
+++ b/tempest/openstack/common/setup.py
@@ -1,6 +1,7 @@
 # vim: tabstop=4 shiftwidth=4 softtabstop=4
 
 # Copyright 2011 OpenStack LLC.
+# Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -19,7 +20,7 @@
 Utilities with minimum-depends for use in setup.py
 """
 
-import datetime
+import email
 import os
 import re
 import subprocess
@@ -33,11 +34,12 @@
     if os.path.exists(mailmap):
         with open(mailmap, 'r') as fp:
             for l in fp:
-                l = l.strip()
-                if not l.startswith('#') and ' ' in l:
-                    canonical_email, alias = [x for x in l.split(' ')
-                                              if x.startswith('<')]
-                    mapping[alias] = canonical_email
+                try:
+                    canonical_email, alias = re.match(
+                        r'[^#]*?(<.+>).*(<.+>).*', l).groups()
+                except AttributeError:
+                    continue
+                mapping[alias] = canonical_email
     return mapping
 
 
@@ -45,8 +47,8 @@
     """Takes in a string and an email alias mapping and replaces all
        instances of the aliases in the string with their real email.
     """
-    for alias, email in mapping.iteritems():
-        changelog = changelog.replace(alias, email)
+    for alias, email_address in mapping.iteritems():
+        changelog = changelog.replace(alias, email_address)
     return changelog
 
 
@@ -106,23 +108,17 @@
     return dependency_links
 
 
-def write_requirements():
-    venv = os.environ.get('VIRTUAL_ENV', None)
-    if venv is not None:
-        with open("requirements.txt", "w") as req_file:
-            output = subprocess.Popen(["pip", "-E", venv, "freeze", "-l"],
-                                      stdout=subprocess.PIPE)
-            requirements = output.communicate()[0].strip()
-            req_file.write(requirements)
-
-
-def _run_shell_command(cmd):
+def _run_shell_command(cmd, throw_on_error=False):
     if os.name == 'nt':
         output = subprocess.Popen(["cmd.exe", "/C", cmd],
-                                  stdout=subprocess.PIPE)
+                                  stdout=subprocess.PIPE,
+                                  stderr=subprocess.PIPE)
     else:
         output = subprocess.Popen(["/bin/sh", "-c", cmd],
-                                  stdout=subprocess.PIPE)
+                                  stdout=subprocess.PIPE,
+                                  stderr=subprocess.PIPE)
+    if output.returncode and throw_on_error:
+        raise Exception("%s returned %d" % cmd, output.returncode)
     out = output.communicate()
     if len(out) == 0:
         return None
@@ -131,57 +127,6 @@
     return out[0].strip()
 
 
-def _get_git_next_version_suffix(branch_name):
-    datestamp = datetime.datetime.now().strftime('%Y%m%d')
-    if branch_name == 'milestone-proposed':
-        revno_prefix = "r"
-    else:
-        revno_prefix = ""
-    _run_shell_command("git fetch origin +refs/meta/*:refs/remotes/meta/*")
-    milestone_cmd = "git show meta/openstack/release:%s" % branch_name
-    milestonever = _run_shell_command(milestone_cmd)
-    if milestonever:
-        first_half = "%s~%s" % (milestonever, datestamp)
-    else:
-        first_half = datestamp
-
-    post_version = _get_git_post_version()
-    # post version should look like:
-    # 0.1.1.4.gcc9e28a
-    # where the bit after the last . is the short sha, and the bit between
-    # the last and second to last is the revno count
-    (revno, sha) = post_version.split(".")[-2:]
-    second_half = "%s%s.%s" % (revno_prefix, revno, sha)
-    return ".".join((first_half, second_half))
-
-
-def _get_git_current_tag():
-    return _run_shell_command("git tag --contains HEAD")
-
-
-def _get_git_tag_info():
-    return _run_shell_command("git describe --tags")
-
-
-def _get_git_post_version():
-    current_tag = _get_git_current_tag()
-    if current_tag is not None:
-        return current_tag
-    else:
-        tag_info = _get_git_tag_info()
-        if tag_info is None:
-            base_version = "0.0"
-            cmd = "git --no-pager log --oneline"
-            out = _run_shell_command(cmd)
-            revno = len(out.split("\n"))
-            sha = _run_shell_command("git describe --always")
-        else:
-            tag_infos = tag_info.split("-")
-            base_version = "-".join(tag_infos[:-2])
-            (revno, sha) = tag_infos[-2:]
-        return "%s.%s.%s" % (base_version, revno, sha)
-
-
 def write_git_changelog():
     """Write a changelog based on the git changelog."""
     new_changelog = 'ChangeLog'
@@ -227,26 +172,6 @@
 """
 
 
-def read_versioninfo(project):
-    """Read the versioninfo file. If it doesn't exist, we're in a github
-       zipball, and there's really no way to know what version we really
-       are, but that should be ok, because the utility of that should be
-       just about nil if this code path is in use in the first place."""
-    versioninfo_path = os.path.join(project, 'versioninfo')
-    if os.path.exists(versioninfo_path):
-        with open(versioninfo_path, 'r') as vinfo:
-            version = vinfo.read().strip()
-    else:
-        version = "0.0.0"
-    return version
-
-
-def write_versioninfo(project, version):
-    """Write a simple file containing the version of the package."""
-    with open(os.path.join(project, 'versioninfo'), 'w') as fil:
-        fil.write("%s\n" % version)
-
-
 def get_cmdclass():
     """Return dict of commands to run from setup.py."""
 
@@ -276,6 +201,9 @@
         from sphinx.setup_command import BuildDoc
 
         class LocalBuildDoc(BuildDoc):
+
+            builders = ['html', 'man']
+
             def generate_autoindex(self):
                 print "**Autodocumenting from %s" % os.path.abspath(os.curdir)
                 modules = {}
@@ -311,56 +239,97 @@
                 if not os.getenv('SPHINX_DEBUG'):
                     self.generate_autoindex()
 
-                for builder in ['html', 'man']:
+                for builder in self.builders:
                     self.builder = builder
                     self.finalize_options()
                     self.project = self.distribution.get_name()
                     self.version = self.distribution.get_version()
                     self.release = self.distribution.get_version()
                     BuildDoc.run(self)
+
+        class LocalBuildLatex(LocalBuildDoc):
+            builders = ['latex']
+
         cmdclass['build_sphinx'] = LocalBuildDoc
+        cmdclass['build_sphinx_latex'] = LocalBuildLatex
     except ImportError:
         pass
 
     return cmdclass
 
 
-def get_git_branchname():
-    for branch in _run_shell_command("git branch --color=never").split("\n"):
-        if branch.startswith('*'):
-            _branch_name = branch.split()[1].strip()
-    if _branch_name == "(no":
-        _branch_name = "no-branch"
-    return _branch_name
+def _get_revno():
+    """Return the number of commits since the most recent tag.
+
+    We use git-describe to find this out, but if there are no
+    tags then we fall back to counting commits since the beginning
+    of time.
+    """
+    describe = _run_shell_command("git describe --always")
+    if "-" in describe:
+        return describe.rsplit("-", 2)[-2]
+
+    # no tags found
+    revlist = _run_shell_command("git rev-list --abbrev-commit HEAD")
+    return len(revlist.splitlines())
 
 
-def get_pre_version(projectname, base_version):
-    """Return a version which is leading up to a version that will
-       be released in the future."""
-    if os.path.isdir('.git'):
-        current_tag = _get_git_current_tag()
-        if current_tag is not None:
-            version = current_tag
-        else:
-            branch_name = os.getenv('BRANCHNAME',
-                                    os.getenv('GERRIT_REFNAME',
-                                              get_git_branchname()))
-            version_suffix = _get_git_next_version_suffix(branch_name)
-            version = "%s~%s" % (base_version, version_suffix)
-        write_versioninfo(projectname, version)
-        return version
-    else:
-        version = read_versioninfo(projectname)
-    return version
-
-
-def get_post_version(projectname):
+def _get_version_from_git(pre_version):
     """Return a version which is equal to the tag that's on the current
     revision if there is one, or tag plus number of additional revisions
     if the current revision has no tag."""
 
     if os.path.isdir('.git'):
-        version = _get_git_post_version()
-        write_versioninfo(projectname, version)
+        if pre_version:
+            try:
+                return _run_shell_command(
+                    "git describe --exact-match",
+                    throw_on_error=True).replace('-', '.')
+            except Exception:
+                sha = _run_shell_command("git log -n1 --pretty=format:%h")
+                return "%s.a%s.g%s" % (pre_version, _get_revno(), sha)
+        else:
+            return _run_shell_command(
+                "git describe --always").replace('-', '.')
+    return None
+
+
+def _get_version_from_pkg_info(package_name):
+    """Get the version from PKG-INFO file if we can."""
+    try:
+        pkg_info_file = open('PKG-INFO', 'r')
+    except (IOError, OSError):
+        return None
+    try:
+        pkg_info = email.message_from_file(pkg_info_file)
+    except email.MessageError:
+        return None
+    # Check to make sure we're in our own dir
+    if pkg_info.get('Name', None) != package_name:
+        return None
+    return pkg_info.get('Version', None)
+
+
+def get_version(package_name, pre_version=None):
+    """Get the version of the project. First, try getting it from PKG-INFO, if
+    it exists. If it does, that means we're in a distribution tarball or that
+    install has happened. Otherwise, if there is no PKG-INFO file, pull the
+    version from git.
+
+    We do not support setup.py version sanity in git archive tarballs, nor do
+    we support packagers directly sucking our git repo into theirs. We expect
+    that a source tarball be made from our git repo - or that if someone wants
+    to make a source tarball from a fork of our repo with additional tags in it
+    that they understand and desire the results of doing that.
+    """
+    version = os.environ.get("OSLO_PACKAGE_VERSION", None)
+    if version:
         return version
-    return read_versioninfo(projectname)
+    version = _get_version_from_pkg_info(package_name)
+    if version:
+        return version
+    version = _get_version_from_git(pre_version)
+    if version:
+        return version
+    raise Exception("Versioning for this project requires either an sdist"
+                    " tarball, or access to an upstream git repository.")
diff --git a/tempest/services/boto/__init__.py b/tempest/services/boto/__init__.py
deleted file mode 100644
index f744d9d..0000000
--- a/tempest/services/boto/__init__.py
+++ /dev/null
@@ -1,99 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2012 OpenStack, LLC
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-from ConfigParser import DuplicateSectionError
-from contextlib import closing
-import re
-from types import MethodType
-
-import boto
-
-from tempest.exceptions import InvalidConfiguration
-from tempest.exceptions import NotFound
-
-
-class BotoClientBase(object):
-
-    ALLOWED_METHODS = set()
-
-    def __init__(self, config,
-                 username=None, password=None,
-                 auth_url=None, tenant_name=None,
-                 *args, **kwargs):
-
-        self.connection_timeout = str(config.boto.http_socket_timeout)
-        self.num_retries = str(config.boto.num_retries)
-        self.build_timeout = config.boto.build_timeout
-        self.ks_cred = {"username": username,
-                        "password": password,
-                        "auth_url": auth_url,
-                        "tenant_name": tenant_name}
-
-    def _keystone_aws_get(self):
-        import keystoneclient.v2_0.client
-
-        keystone = keystoneclient.v2_0.client.Client(**self.ks_cred)
-        ec2_cred_list = keystone.ec2.list(keystone.auth_user_id)
-        ec2_cred = None
-        for cred in ec2_cred_list:
-            if cred.tenant_id == keystone.auth_tenant_id:
-                ec2_cred = cred
-                break
-        else:
-            ec2_cred = keystone.ec2.create(keystone.auth_user_id,
-                                           keystone.auth_tenant_id)
-        if not all((ec2_cred, ec2_cred.access, ec2_cred.secret)):
-            raise NotFound("Unable to get access and secret keys")
-        return ec2_cred
-
-    def _config_boto_timeout(self, timeout, retries):
-        try:
-            boto.config.add_section("Boto")
-        except DuplicateSectionError:
-            pass
-        boto.config.set("Boto", "http_socket_timeout", timeout)
-        boto.config.set("Boto", "num_retries", retries)
-
-    def __getattr__(self, name):
-        """Automatically creates methods for the allowed methods set."""
-        if name in self.ALLOWED_METHODS:
-            def func(self, *args, **kwargs):
-                with closing(self.get_connection()) as conn:
-                    return getattr(conn, name)(*args, **kwargs)
-
-            func.__name__ = name
-            setattr(self, name, MethodType(func, self, self.__class__))
-            setattr(self.__class__, name,
-                    MethodType(func, None, self.__class__))
-            return getattr(self, name)
-        else:
-            raise AttributeError(name)
-
-    def get_connection(self):
-        self._config_boto_timeout(self.connection_timeout, self.num_retries)
-        if not all((self.connection_data["aws_access_key_id"],
-                   self.connection_data["aws_secret_access_key"])):
-            if all(self.ks_cred.itervalues()):
-                ec2_cred = self._keystone_aws_get()
-                self.connection_data["aws_access_key_id"] = \
-                    ec2_cred.access
-                self.connection_data["aws_secret_access_key"] = \
-                    ec2_cred.secret
-            else:
-                raise InvalidConfiguration(
-                                    "Unable to get access and secret keys")
-        return self.connect_method(**self.connection_data)
diff --git a/tempest/services/boto/clients.py b/tempest/services/botoclients.py
similarity index 63%
rename from tempest/services/boto/clients.py
rename to tempest/services/botoclients.py
index 228e826..143257a 100644
--- a/tempest/services/boto/clients.py
+++ b/tempest/services/botoclients.py
@@ -15,13 +15,90 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import ConfigParser
+import contextlib
+import re
+import types
 import urlparse
 
-import boto
-from boto.ec2.regioninfo import RegionInfo
-from boto.s3.connection import OrdinaryCallingFormat
+from tempest import exceptions
 
-from tempest.services.boto import BotoClientBase
+import boto
+import boto.ec2
+import boto.s3.connection
+
+
+class BotoClientBase(object):
+
+    ALLOWED_METHODS = set()
+
+    def __init__(self, config,
+                 username=None, password=None,
+                 auth_url=None, tenant_name=None,
+                 *args, **kwargs):
+
+        self.connection_timeout = str(config.boto.http_socket_timeout)
+        self.num_retries = str(config.boto.num_retries)
+        self.build_timeout = config.boto.build_timeout
+        self.ks_cred = {"username": username,
+                        "password": password,
+                        "auth_url": auth_url,
+                        "tenant_name": tenant_name}
+
+    def _keystone_aws_get(self):
+        import keystoneclient.v2_0.client
+
+        keystone = keystoneclient.v2_0.client.Client(**self.ks_cred)
+        ec2_cred_list = keystone.ec2.list(keystone.auth_user_id)
+        ec2_cred = None
+        for cred in ec2_cred_list:
+            if cred.tenant_id == keystone.auth_tenant_id:
+                ec2_cred = cred
+                break
+        else:
+            ec2_cred = keystone.ec2.create(keystone.auth_user_id,
+                                           keystone.auth_tenant_id)
+        if not all((ec2_cred, ec2_cred.access, ec2_cred.secret)):
+            raise exceptions.NotFound("Unable to get access and secret keys")
+        return ec2_cred
+
+    def _config_boto_timeout(self, timeout, retries):
+        try:
+            boto.config.add_section("Boto")
+        except ConfigParser.DuplicateSectionError:
+            pass
+        boto.config.set("Boto", "http_socket_timeout", timeout)
+        boto.config.set("Boto", "num_retries", retries)
+
+    def __getattr__(self, name):
+        """Automatically creates methods for the allowed methods set."""
+        if name in self.ALLOWED_METHODS:
+            def func(self, *args, **kwargs):
+                with contextlib.closing(self.get_connection()) as conn:
+                    return getattr(conn, name)(*args, **kwargs)
+
+            func.__name__ = name
+            setattr(self, name, types.MethodType(func, self, self.__class__))
+            setattr(self.__class__, name,
+                    types.MethodType(func, None, self.__class__))
+            return getattr(self, name)
+        else:
+            raise AttributeError(name)
+
+    def get_connection(self):
+        self._config_boto_timeout(self.connection_timeout, self.num_retries)
+        if not all((self.connection_data["aws_access_key_id"],
+                   self.connection_data["aws_secret_access_key"])):
+            if all(self.ks_cred.itervalues()):
+                ec2_cred = self._keystone_aws_get()
+                self.connection_data["aws_access_key_id"] = \
+                    ec2_cred.access
+                self.connection_data["aws_secret_access_key"] = \
+                    ec2_cred.secret
+            else:
+                raise exceptions.InvalidConfiguration(
+                                    "Unable to get access and secret keys")
+        return self.connect_method(**self.connection_data)
 
 
 class APIClientEC2(BotoClientBase):
@@ -35,8 +112,8 @@
         aws_secret = config.boto.aws_secret
         purl = urlparse.urlparse(config.boto.ec2_url)
 
-        region = RegionInfo(name=config.identity.region,
-                            endpoint=purl.hostname)
+        region = boto.ec2.regioninfo.RegionInfo(name=config.identity.region,
+                                                endpoint=purl.hostname)
         port = purl.port
         if port is None:
             if purl.scheme is not "https":
@@ -134,7 +211,8 @@
                                 "is_secure": purl.scheme == "https",
                                 "host": purl.hostname,
                                 "port": port,
-                                "calling_format": OrdinaryCallingFormat()}
+                                "calling_format": boto.s3.connection.
+                                OrdinaryCallingFormat()}
 
     ALLOWED_METHODS = set(('create_bucket', 'delete_bucket', 'generate_url',
                            'get_all_buckets', 'get_bucket', 'delete_key',
diff --git a/tempest/services/compute/admin/json/__init__.py b/tempest/services/compute/admin/json/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/compute/admin/json/__init__.py
+++ /dev/null
diff --git a/tempest/services/compute/admin/json/quotas_client.py b/tempest/services/compute/admin/json/quotas_client.py
deleted file mode 100644
index 0a4bd72..0000000
--- a/tempest/services/compute/admin/json/quotas_client.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-#
-# Copyright 2012 NTT Data
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-import json
-
-from tempest.services.compute.json.quotas_client import QuotasClientJSON
-
-
-class AdminQuotasClientJSON(QuotasClientJSON):
-
-    def __init__(self, config, username, password, auth_url, tenant_name=None):
-        super(AdminQuotasClientJSON, self).__init__(config, username, password,
-                                                    auth_url, tenant_name)
-
-    def update_quota_set(self, tenant_id, injected_file_content_bytes=None,
-                         metadata_items=None, ram=None, floating_ips=None,
-                         key_pairs=None, instances=None,
-                         security_group_rules=None, injected_files=None,
-                         cores=None, injected_file_path_bytes=None,
-                         security_groups=None):
-        """
-        Updates the tenant's quota limits for one or more resources
-        """
-        post_body = {}
-
-        if injected_file_content_bytes >= 0:
-            post_body['injected_file_content_bytes'] = \
-                injected_file_content_bytes
-
-        if metadata_items >= 0:
-            post_body['metadata_items'] = metadata_items
-
-        if ram >= 0:
-            post_body['ram'] = ram
-
-        if floating_ips >= 0:
-            post_body['floating_ips'] = floating_ips
-
-        if key_pairs >= 0:
-            post_body['key_pairs'] = key_pairs
-
-        if instances >= 0:
-            post_body['instances'] = instances
-
-        if security_group_rules >= 0:
-            post_body['security_group_rules'] = security_group_rules
-
-        if injected_files >= 0:
-            post_body['injected_files'] = injected_files
-
-        if cores >= 0:
-            post_body['cores'] = cores
-
-        if injected_file_path_bytes >= 0:
-            post_body['injected_file_path_bytes'] = injected_file_path_bytes
-
-        if security_groups >= 0:
-            post_body['security_groups'] = security_groups
-
-        post_body = json.dumps({'quota_set': post_body})
-        resp, body = self.put('os-quota-sets/%s' % str(tenant_id), post_body,
-                              self.headers)
-
-        body = json.loads(body)
-        return resp, body['quota_set']
diff --git a/tempest/services/compute/admin/xml/__init__.py b/tempest/services/compute/admin/xml/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/compute/admin/xml/__init__.py
+++ /dev/null
diff --git a/tempest/services/compute/admin/xml/quotas_client.py b/tempest/services/compute/admin/xml/quotas_client.py
deleted file mode 100644
index d567a9c..0000000
--- a/tempest/services/compute/admin/xml/quotas_client.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-#
-# Copyright 2012 NTT Data
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-import urllib
-
-from lxml import etree
-
-from tempest.common.rest_client import RestClientXML
-from tempest.services.compute.xml.common import Document
-from tempest.services.compute.xml.common import Element
-from tempest.services.compute.xml.common import xml_to_json
-from tempest.services.compute.xml.common import XMLNS_11
-from tempest.services.compute.xml.quotas_client import QuotasClientXML
-
-
-class AdminQuotasClientXML(QuotasClientXML):
-
-    def __init__(self, config, username, password, auth_url, tenant_name=None):
-        super(AdminQuotasClientXML, self).__init__(config, username, password,
-                                                   auth_url, tenant_name)
-
-    def update_quota_set(self, tenant_id, injected_file_content_bytes=None,
-                         metadata_items=None, ram=None, floating_ips=None,
-                         key_pairs=None, instances=None,
-                         security_group_rules=None, injected_files=None,
-                         cores=None, injected_file_path_bytes=None,
-                         security_groups=None):
-        """
-        Updates the tenant's quota limits for one or more resources
-        """
-        post_body = Element("quota_set",
-                            xmlns=XMLNS_11)
-
-        if injected_file_content_bytes is not None:
-            post_body.add_attr('injected_file_content_bytes',
-                               injected_file_content_bytes)
-
-        if metadata_items is not None:
-            post_body.add_attr('metadata_items', metadata_items)
-
-        if ram is not None:
-            post_body.add_attr('ram', ram)
-
-        if floating_ips is not None:
-            post_body.add_attr('floating_ips', floating_ips)
-
-        if key_pairs is not None:
-            post_body.add_attr('key_pairs', key_pairs)
-
-        if instances is not None:
-            post_body.add_attr('instances', instances)
-
-        if security_group_rules is not None:
-            post_body.add_attr('security_group_rules', security_group_rules)
-
-        if injected_files is not None:
-            post_body.add_attr('injected_files', injected_files)
-
-        if cores is not None:
-            post_body.add_attr('cores', cores)
-
-        if injected_file_path_bytes is not None:
-            post_body.add_attr('injected_file_path_bytes',
-                               injected_file_path_bytes)
-
-        if security_groups is not None:
-            post_body.add_attr('security_groups', security_groups)
-
-        resp, body = self.put('os-quota-sets/%s' % str(tenant_id),
-                              str(Document(post_body)),
-                              self.headers)
-        body = xml_to_json(etree.fromstring(body))
-        body = self._format_quota(body)
-        return resp, body
diff --git a/tempest/services/compute/json/quotas_client.py b/tempest/services/compute/json/quotas_client.py
index a95ff1c..330e80b 100644
--- a/tempest/services/compute/json/quotas_client.py
+++ b/tempest/services/compute/json/quotas_client.py
@@ -34,3 +34,55 @@
         resp, body = self.get(url)
         body = json.loads(body)
         return resp, body['quota_set']
+
+    def update_quota_set(self, tenant_id, injected_file_content_bytes=None,
+                         metadata_items=None, ram=None, floating_ips=None,
+                         key_pairs=None, instances=None,
+                         security_group_rules=None, injected_files=None,
+                         cores=None, injected_file_path_bytes=None,
+                         security_groups=None):
+        """
+        Updates the tenant's quota limits for one or more resources
+        """
+        post_body = {}
+
+        if injected_file_content_bytes is not None:
+            post_body['injected_file_content_bytes'] = \
+                injected_file_content_bytes
+
+        if metadata_items is not None:
+            post_body['metadata_items'] = metadata_items
+
+        if ram is not None:
+            post_body['ram'] = ram
+
+        if floating_ips is not None:
+            post_body['floating_ips'] = floating_ips
+
+        if key_pairs is not None:
+            post_body['key_pairs'] = key_pairs
+
+        if instances is not None:
+            post_body['instances'] = instances
+
+        if security_group_rules is not None:
+            post_body['security_group_rules'] = security_group_rules
+
+        if injected_files is not None:
+            post_body['injected_files'] = injected_files
+
+        if cores is not None:
+            post_body['cores'] = cores
+
+        if injected_file_path_bytes is not None:
+            post_body['injected_file_path_bytes'] = injected_file_path_bytes
+
+        if security_groups is not None:
+            post_body['security_groups'] = security_groups
+
+        post_body = json.dumps({'quota_set': post_body})
+        resp, body = self.put('os-quota-sets/%s' % str(tenant_id), post_body,
+                              self.headers)
+
+        body = json.loads(body)
+        return resp, body['quota_set']
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 2116e1e..02cf970 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -403,7 +403,7 @@
 
         url = self.base_url + '/servers?all_tenants=1'
         resp = self.requests.get(url)
-        resp, body = self.post('servers', post_body, self.headers)
+        resp, body = self.get('servers', self.headers)
 
         body = json.loads(body)
         return resp, body['servers']
diff --git a/tempest/services/compute/json/volumes_extensions_client.py b/tempest/services/compute/json/volumes_extensions_client.py
index a5f6ec3..e4271d9 100644
--- a/tempest/services/compute/json/volumes_extensions_client.py
+++ b/tempest/services/compute/json/volumes_extensions_client.py
@@ -105,7 +105,7 @@
 
     def is_resource_deleted(self, id):
         try:
-            self.get_volume(id, wait=True)
+            self.get_volume(id)
         except exceptions.NotFound:
             return True
         return False
diff --git a/tempest/services/compute/xml/common.py b/tempest/services/compute/xml/common.py
index 9bb1d11..6469761 100644
--- a/tempest/services/compute/xml/common.py
+++ b/tempest/services/compute/xml/common.py
@@ -15,8 +15,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from lxml import etree
-
 XMLNS_11 = "http://docs.openstack.org/compute/api/v1.1"
 
 
diff --git a/tempest/services/compute/xml/images_client.py b/tempest/services/compute/xml/images_client.py
index bde9e16..3b01efb 100644
--- a/tempest/services/compute/xml/images_client.py
+++ b/tempest/services/compute/xml/images_client.py
@@ -39,43 +39,43 @@
         self.build_timeout = self.config.compute.build_timeout
 
     def _parse_server(self, node):
-        json = xml_to_json(node)
-        return self._parse_links(node, json)
+        data = xml_to_json(node)
+        return self._parse_links(node, data)
 
     def _parse_image(self, node):
         """Parses detailed XML image information into dictionary."""
-        json = xml_to_json(node)
+        data = xml_to_json(node)
 
-        self._parse_links(node, json)
+        self._parse_links(node, data)
 
         # parse all metadata
-        if 'metadata' in json:
+        if 'metadata' in data:
             tag = node.find('{%s}metadata' % XMLNS_11)
-            json['metadata'] = dict((x.get('key'), x.text)
+            data['metadata'] = dict((x.get('key'), x.text)
                                     for x in tag.getchildren())
 
         # parse server information
-        if 'server' in json:
+        if 'server' in data:
             tag = node.find('{%s}server' % XMLNS_11)
-            json['server'] = self._parse_server(tag)
-        return json
+            data['server'] = self._parse_server(tag)
+        return data
 
-    def _parse_links(self, node, json):
+    def _parse_links(self, node, data):
         """Append multiple links under a list."""
         # look for links
-        if 'link' in json:
+        if 'link' in data:
             # remove single link element
-            del json['link']
-            json['links'] = [xml_to_json(x) for x in
+            del data['link']
+            data['links'] = [xml_to_json(x) for x in
                              node.findall('{http://www.w3.org/2005/Atom}link')]
-        return json
+        return data
 
     def _parse_images(self, xml):
-        json = {'images': []}
+        data = {'images': []}
         images = xml.getchildren()
         for image in images:
-            json['images'].append(self._parse_image(image))
-        return json
+            data['images'].append(self._parse_image(image))
+        return data
 
     def create_image(self, server_id, name, meta=None):
         """Creates an image of the original server."""
@@ -160,9 +160,17 @@
         body = xml_to_json(etree.fromstring(body))
         return resp, body['metadata']
 
+    def _metadata_body(image_id, meta):
+        post_body = Document('metadata')
+        for k, v in meta:
+            text = Text(v)
+            metadata = Element('meta', text, key=k)
+            post_body.append(metadata)
+        return post_body
+
     def set_image_metadata(self, image_id, meta):
         """Sets the metadata for an image."""
-        post_body = json.dumps({'metadata': meta})
+        post_body = self._metadata_body(image_id, meta)
         resp, body = self.put('images/%s/metadata' % str(image_id),
                               post_body, self.headers)
         body = xml_to_json(etree.fromstring(body))
@@ -170,13 +178,7 @@
 
     def update_image_metadata(self, image_id, meta):
         """Updates the metadata for an image."""
-        post_body = Element('metadata', meta)
-        for k, v in meta:
-            metadata = Element('meta', key=k)
-            text = Text(v)
-            metadata.append(text)
-            post_body.append(metadata)
-
+        post_body = self._metadata_body(image_id, meta)
         resp, body = self.post('images/%s/metadata' % str(image_id),
                                post_body, self.headers)
         body = xml_to_json(etree.fromstring(body))
@@ -191,7 +193,15 @@
 
     def set_image_metadata_item(self, image_id, key, meta):
         """Sets the value for a specific image metadata key."""
-        post_body = json.dumps({'meta': meta})
+        post_body = Document('meta', Text(meta), key=key)
+        resp, body = self.post('images/%s/metadata/%s' % (str(image_id), key),
+                               post_body, self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        return resp, body['meta']
+
+    def update_image_metadata_item(self, image_id, key, meta):
+        """Sets the value for a specific image metadata key."""
+        post_body = Document('meta', Text(meta), key=key)
         resp, body = self.put('images/%s/metadata/%s' % (str(image_id), key),
                               post_body, self.headers)
         body = xml_to_json(etree.fromstring(body))
diff --git a/tempest/services/compute/xml/limits_client.py b/tempest/services/compute/xml/limits_client.py
index 473952b..d233bba 100644
--- a/tempest/services/compute/xml/limits_client.py
+++ b/tempest/services/compute/xml/limits_client.py
@@ -15,7 +15,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from lxml import etree
 from lxml import objectify
 
 from tempest.common.rest_client import RestClientXML
diff --git a/tempest/services/compute/xml/quotas_client.py b/tempest/services/compute/xml/quotas_client.py
index 8978214..0437205 100644
--- a/tempest/services/compute/xml/quotas_client.py
+++ b/tempest/services/compute/xml/quotas_client.py
@@ -15,8 +15,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import urllib
-
 from lxml import etree
 
 from tempest.common.rest_client import RestClientXML
@@ -56,3 +54,57 @@
         body = xml_to_json(etree.fromstring(body))
         body = self._format_quota(body)
         return resp, body
+
+    def update_quota_set(self, tenant_id, injected_file_content_bytes=None,
+                         metadata_items=None, ram=None, floating_ips=None,
+                         key_pairs=None, instances=None,
+                         security_group_rules=None, injected_files=None,
+                         cores=None, injected_file_path_bytes=None,
+                         security_groups=None):
+        """
+        Updates the tenant's quota limits for one or more resources
+        """
+        post_body = Element("quota_set",
+                            xmlns=XMLNS_11)
+
+        if injected_file_content_bytes is not None:
+            post_body.add_attr('injected_file_content_bytes',
+                               injected_file_content_bytes)
+
+        if metadata_items is not None:
+            post_body.add_attr('metadata_items', metadata_items)
+
+        if ram is not None:
+            post_body.add_attr('ram', ram)
+
+        if floating_ips is not None:
+            post_body.add_attr('floating_ips', floating_ips)
+
+        if key_pairs is not None:
+            post_body.add_attr('key_pairs', key_pairs)
+
+        if instances is not None:
+            post_body.add_attr('instances', instances)
+
+        if security_group_rules is not None:
+            post_body.add_attr('security_group_rules', security_group_rules)
+
+        if injected_files is not None:
+            post_body.add_attr('injected_files', injected_files)
+
+        if cores is not None:
+            post_body.add_attr('cores', cores)
+
+        if injected_file_path_bytes is not None:
+            post_body.add_attr('injected_file_path_bytes',
+                               injected_file_path_bytes)
+
+        if security_groups is not None:
+            post_body.add_attr('security_groups', security_groups)
+
+        resp, body = self.put('os-quota-sets/%s' % str(tenant_id),
+                              str(Document(post_body)),
+                              self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        body = self._format_quota(body)
+        return resp, body
diff --git a/tempest/services/compute/xml/volumes_extensions_client.py b/tempest/services/compute/xml/volumes_extensions_client.py
index 60ef398..69b9bac 100644
--- a/tempest/services/compute/xml/volumes_extensions_client.py
+++ b/tempest/services/compute/xml/volumes_extensions_client.py
@@ -141,7 +141,7 @@
 
     def is_resource_deleted(self, id):
         try:
-            self.get_volume(id, wait=True)
+            self.get_volume(id)
         except exceptions.NotFound:
             return True
         return False
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index 403a3ac..68e7d4b 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -244,8 +244,10 @@
         if headers is None:
             headers = {}
 
+        self._log_request(method, url, headers, body)
         resp, resp_body = self.http_obj.request(url, method,
                                                 headers=headers, body=body)
+        self._log_response(resp, resp_body)
 
         if resp.status in (401, 403):
             resp_body = json.loads(resp_body)
diff --git a/tempest/services/identity/xml/identity_client.py b/tempest/services/identity/xml/identity_client.py
index f79c3d5..2431282 100644
--- a/tempest/services/identity/xml/identity_client.py
+++ b/tempest/services/identity/xml/identity_client.py
@@ -17,16 +17,13 @@
 
 import httplib2
 import json
-import logging
 
 from lxml import etree
 
-from tempest.common.rest_client import RestClient
 from tempest.common.rest_client import RestClientXML
 from tempest import exceptions
 from tempest.services.compute.xml.common import Document
 from tempest.services.compute.xml.common import Element
-from tempest.services.compute.xml.common import Text
 from tempest.services.compute.xml.common import xml_to_json
 
 
@@ -48,8 +45,8 @@
         return array
 
     def _parse_body(self, body):
-        json = xml_to_json(body)
-        return json
+        data = xml_to_json(body)
+        return data
 
     def has_admin_extensions(self):
         """
@@ -275,9 +272,10 @@
         self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv)
         if headers is None:
             headers = {}
-
+        self._log_request(method, url, headers, body)
         resp, resp_body = self.http_obj.request(url, method,
                                                 headers=headers, body=body)
+        self._log_response(resp, resp_body)
 
         if resp.status in (401, 403):
             resp_body = json.loads(resp_body)
diff --git a/tempest/services/compute/admin/__init__.py b/tempest/services/image/json/__init__.py
similarity index 100%
rename from tempest/services/compute/admin/__init__.py
rename to tempest/services/image/json/__init__.py
diff --git a/tempest/services/image/json/image_client.py b/tempest/services/image/json/image_client.py
new file mode 100644
index 0000000..e9276aa
--- /dev/null
+++ b/tempest/services/image/json/image_client.py
@@ -0,0 +1,200 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 IBM
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import copy
+import errno
+import json
+import os
+import time
+import urllib
+
+from tempest.common import glance_http
+from tempest.common.rest_client import RestClient
+from tempest import exceptions
+from tempest import manager
+
+
+class ImageClientJSON(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(ImageClientJSON, self).__init__(config, username, password,
+                                              auth_url, tenant_name)
+        self.service = self.config.images.catalog_type
+        self.http = self._get_http()
+
+    def _image_meta_from_headers(self, headers):
+        meta = {'properties': {}}
+        for key, value in headers.iteritems():
+            if key.startswith('x-image-meta-property-'):
+                _key = key[22:]
+                meta['properties'][_key] = value
+            elif key.startswith('x-image-meta-'):
+                _key = key[13:]
+                meta[_key] = value
+
+        for key in ['is_public', 'protected', 'deleted']:
+            if key in meta:
+                meta[key] = meta[key].strip().lower() in ('t', 'true', 'yes',
+                                                          '1')
+        for key in ['size', 'min_ram', 'min_disk']:
+            if key in meta:
+                try:
+                    meta[key] = int(meta[key])
+                except ValueError:
+                    pass
+        return meta
+
+    def _image_meta_to_headers(self, fields):
+        headers = {}
+        fields_copy = copy.deepcopy(fields)
+        for key, value in fields_copy.pop('properties', {}).iteritems():
+            headers['x-image-meta-property-%s' % key] = str(value)
+        for key, value in fields_copy.iteritems():
+            headers['x-image-meta-%s' % key] = str(value)
+        return headers
+
+    def _get_file_size(self, obj):
+        """Analyze file-like object and attempt to determine its size.
+
+        :param obj: file-like object, typically redirected from stdin.
+        :retval The file's size or None if it cannot be determined.
+        """
+        # For large images, we need to supply the size of the
+        # image file. See LP Bugs #827660 and #845788.
+        if hasattr(obj, 'seek') and hasattr(obj, 'tell'):
+            try:
+                obj.seek(0, os.SEEK_END)
+                obj_size = obj.tell()
+                obj.seek(0)
+                return obj_size
+            except IOError, e:
+                if e.errno == errno.ESPIPE:
+                    # Illegal seek. This means the user is trying
+                    # to pipe image data to the client, e.g.
+                    # echo testdata | bin/glance add blah..., or
+                    # that stdin is empty, or that a file-like
+                    # object which doesn't support 'seek/tell' has
+                    # been supplied.
+                    return None
+                else:
+                    raise
+        else:
+            # Cannot determine size of input image
+            return None
+
+    def _get_http(self):
+        temp_manager = manager.DefaultClientManager()
+        keystone = temp_manager._get_identity_client()
+        token = keystone.auth_token
+        endpoint = keystone.service_catalog.url_for(service_type='image',
+                                                    endpoint_type='publicURL')
+        dscv = self.config.identity.disable_ssl_certificate_validation
+        return glance_http.HTTPClient(endpoint=endpoint, token=token,
+                                      insecure=dscv)
+
+    def _create_with_data(self, headers, data):
+        resp, body_iter = self.http.raw_request('POST', '/v1/images',
+                                                headers=headers, body=data)
+        self._error_checker('POST', '/v1/images', headers, data, resp,
+                            body_iter)
+        body = json.loads(''.join([c for c in body_iter]))
+        return resp, body['image']
+
+    def _update_with_data(self, image_id, headers, data):
+        url = '/v1/images/%s' % image_id
+        resp, body_iter = self.http.raw_request('PUT', url, headers=headers,
+                                                body=data)
+        self._error_checker('PUT', url, headers, data,
+                            resp, body_iter)
+        body = json.loads(''.join([c for c in body_iter]))
+        return resp, body['image']
+
+    def create_image(self, name, container_format, disk_format, is_public=True,
+                     location=None, properties=None, data=None):
+        params = {
+            "name": name,
+            "container_format": container_format,
+            "disk_format": disk_format,
+            "is_public": is_public,
+        }
+        headers = {}
+
+        if location is not None:
+            params['location'] = location
+
+        if properties is not None:
+            params['properties'] = properties
+
+        headers.update(self._image_meta_to_headers(params))
+
+        if data is not None:
+            return self._create_with_data(headers, data)
+
+        resp, body = self.post('v1/images', data, headers)
+        body = json.loads(body)
+        return resp, body['image']
+
+    def update_image(self, image_id, name=None, container_format=None,
+                     data=None):
+        params = {}
+        headers = {}
+        if name is not None:
+            params['name'] = name
+
+        if container_format is not None:
+            params['container_format'] = container_format
+
+        headers.update(self._image_meta_to_headers(params))
+
+        if data is not None:
+            return self._update_with_data(image_id, headers, data)
+
+        url = 'v1/images/%s' % image_id
+        resp, body = self.put(url, data, headers)
+        body = json.loads(body)
+        return resp, body['image']
+
+    def delete_image(self, image_id):
+        url = 'v1/images/%s' % image_id
+        try:
+            self.delete(url)
+        except exceptions.Unauthorized:
+            url = '/' + url
+            self.http.raw_request('DELETE', url)
+
+    def image_list(self, params=None):
+        url = 'v1/images'
+
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url)
+        body = json.loads(body)
+        return resp, body['images']
+
+    def get_image(self, image_id, wait=None):
+        url = 'v1/images/%s' % image_id
+        resp, __ = self.get(url, wait=wait)
+        body = self._image_meta_from_headers(resp)
+        return resp, body
+
+    def is_resource_deleted(self, id):
+        try:
+            self.get_image(id, wait=True)
+        except exceptions.NotFound:
+            return True
+        return False
diff --git a/tempest/services/image/service.py b/tempest/services/image/service.py
deleted file mode 100644
index 66ba219..0000000
--- a/tempest/services/image/service.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2012 OpenStack, LLC
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-"""
-Image Service class, which acts as a descriptor for the OpenStack Images
-service running in the test environment.
-"""
-
-from tempest.services import Service as BaseService
-
-
-class Service(BaseService):
-
-    def __init__(self, config):
-        """
-        Initializes the service.
-
-        :param config: `tempest.config.Config` object
-        """
-        self.config = config
-
-        # Determine the Images API version
-        self.api_version = int(config.images.api_version)
-
-        # We load the client class specific to the API version...
-        if self.api_version == 1:
-            import glanceclient
-            import keystoneclient.v2_0.client
-
-            dscv = self.config.identity.disable_ssl_certificate_validation
-            auth_url = self.config.identity.uri
-            keystone = keystoneclient.v2_0.client.Client(
-                    username=config.identity.username,
-                    password=config.identity.password,
-                    tenant_name=config.identity.tenant_name,
-                    auth_url=auth_url,
-                    insecure=dscv)
-            token = keystone.auth_token
-            endpoint = keystone.service_catalog.url_for(
-                    service_type='image',
-                    endpoint_type='publicURL')
-
-            self._client = glanceclient.Client('1',
-                                               endpoint=endpoint,
-                                               token=token,
-                                               insecure=dscv)
-        else:
-            raise NotImplementedError
-
-    def get_client(self):
-        """
-        Returns a client object that may be used to query
-        the service API.
-        """
-        assert self._client
-        return self._client
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index 187e80d..ac1859a 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -18,11 +18,10 @@
 from hashlib import sha1
 import hmac
 import httplib2
-import json
-import re
+from urlparse import urlparse
+
 from tempest.common.rest_client import RestClient
 from tempest import exceptions
-from urlparse import urlparse
 
 
 class ObjectClient(RestClient):
@@ -41,7 +40,7 @@
 
     def update_object(self, container, object_name, data):
         """Upload data to replace current storage object."""
-        return create_object(container, object_name, data)
+        return self.create_object(container, object_name, data)
 
     def delete_object(self, container, object_name):
         """Delete storage object."""
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index 75e1a8b..ff1556f 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -125,7 +125,7 @@
 
     def is_resource_deleted(self, id):
         try:
-            self.get_volume(id, wait=True)
+            self.get_volume(id)
         except exceptions.NotFound:
             return True
         return False
diff --git a/tempest/services/volume/xml/snapshots_client.py b/tempest/services/volume/xml/snapshots_client.py
index c89f66e..dc70cb7 100644
--- a/tempest/services/volume/xml/snapshots_client.py
+++ b/tempest/services/volume/xml/snapshots_client.py
@@ -85,10 +85,6 @@
         body = xml_to_json(etree.fromstring(body))
         return resp, body
 
-    def _get_snapshot_status(self, snapshot_id):
-        resp, body = self.get_snapshot(snapshot_id)
-        return body['status']
-
     #NOTE(afazekas): just for the wait function
     def _get_snapshot_status(self, snapshot_id):
         resp, body = self.get_snapshot(snapshot_id)
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index 862ffae..5041869 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -151,7 +151,7 @@
 
     def is_resource_deleted(self, id):
         try:
-            self.get_volume(id, wait=True)
+            self.get_volume(id)
         except exceptions.NotFound:
             return True
         return False
diff --git a/tempest/test.py b/tempest/test.py
index 7804e12..58fadeb 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -18,6 +18,7 @@
 import logging
 import time
 
+import nose.plugins.attrib
 import testtools
 
 from tempest import manager
@@ -25,6 +26,25 @@
 LOG = logging.getLogger(__name__)
 
 
+def attr(*args, **kwargs):
+    """A decorator which applies the nose and testtools attr decorator
+
+    This decorator applies the nose attr decorator as well as the
+    the testtools.testcase.attr if it is in the list of attributes
+    to testtools we want to apply."""
+
+    def decorator(f):
+        testtool_attributes = ('smoke')
+
+        if 'type' in kwargs and kwargs['type'] in testtool_attributes:
+            return nose.plugins.attrib.attr(*args, **kwargs)(
+                testtools.testcase.attr(kwargs['type'])(f))
+        else:
+            return nose.plugins.attrib.attr(*args, **kwargs)(f)
+
+    return decorator
+
+
 class TestCase(testtools.TestCase):
 
     """
diff --git a/tempest/testboto.py b/tempest/testboto.py
index 8bcaa33..14844b3 100644
--- a/tempest/testboto.py
+++ b/tempest/testboto.py
@@ -18,13 +18,11 @@
 from contextlib import closing
 import logging
 import re
-import time
 
 import boto
 from boto.exception import BotoServerError
 from boto.exception import EC2ResponseError
 from boto.s3.bucket import Bucket
-from boto.s3.key import Key
 import testresources
 import testtools
 
@@ -33,7 +31,6 @@
 from tempest.tests.boto.utils.wait import re_search_wait
 from tempest.tests.boto.utils.wait import state_wait
 from tempest.tests.boto.utils.wait import wait_exception
-from tempest.tests.boto.utils.wait import wait_no_exception
 
 LOG = logging.getLogger(__name__)
 
@@ -122,7 +119,8 @@
     return string + ")"
 
 
-class BotoTestCase(testtools.TestCase,
+class BotoTestCase(testtools.testcase.WithAttributes,
+                   testtools.TestCase,
                    testresources.ResourcedTestCase):
     """Recommended to use as base class for boto related test."""
 
@@ -378,7 +376,7 @@
         snaps = volume.snapshots()
         if len(snaps):
             LOG.critical("%s Volume has %s snapshot(s)", volume.id,
-                         map(snps.id, snaps))
+                         map(snaps.id, snaps))
 
         #Note(afazekas): detaching/attching not valid EC2 status
         def _volume_state():
diff --git a/tempest/tests/boto/__init__.py b/tempest/tests/boto/__init__.py
index 6d5149e..dd224d6 100644
--- a/tempest/tests/boto/__init__.py
+++ b/tempest/tests/boto/__init__.py
@@ -27,9 +27,9 @@
 from tempest.common.utils.file_utils import have_effective_read_access
 import tempest.config
 
-A_I_IMAGES_READY = False  # ari,ami,aki
-S3_CAN_CONNECT_ERROR = "Unknown Error"
-EC2_CAN_CONNECT_ERROR = "Unknown Error"
+A_I_IMAGES_READY = True  # ari,ami,aki
+S3_CAN_CONNECT_ERROR = None
+EC2_CAN_CONNECT_ERROR = None
 
 
 def generic_setup_package():
@@ -76,8 +76,6 @@
                                 " faild to get them even by keystoneclient"
     except Exception as exc:
         EC2_CAN_CONNECT_ERROR = str(exc)
-    else:
-        EC2_CAN_CONNECT_ERROR = None
 
     try:
         if urlparse.urlparse(config.boto.s3_url).hostname is None:
@@ -93,6 +91,4 @@
     except keystoneclient.exceptions.Unauthorized:
         S3_CAN_CONNECT_ERROR = "AWS credentials not set," +\
                                " faild to get them even by keystoneclient"
-    else:
-        S3_CAN_CONNECT_ERROR = None
     boto_logger.setLevel(level)
diff --git a/tempest/tests/boto/test_ec2_instance_run.py b/tempest/tests/boto/test_ec2_instance_run.py
index 1adb5fb..403ec4c 100644
--- a/tempest/tests/boto/test_ec2_instance_run.py
+++ b/tempest/tests/boto/test_ec2_instance_run.py
@@ -15,18 +15,15 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from contextlib import closing
 import logging
 
 from boto.exception import EC2ResponseError
-from boto.s3.key import Key
-from nose.plugins.attrib import attr
 import testtools
 
 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.test import attr
 from tempest.testboto import BotoTestCase
 import tempest.tests.boto
 from tempest.tests.boto.utils.s3 import s3_upload_dir
@@ -88,8 +85,8 @@
             state = state_wait(_state, "available")
             if state != "available":
                 for _image in cls.images.itervalues():
-                    ec2_client.deregister_image(_image["image_id"])
-                raise RegisterImageException(image_id=image["image_id"])
+                    cls.ec2_client.deregister_image(_image["image_id"])
+                raise EC2RegisterImageException(image_id=image["image_id"])
 
     @attr(type='smoke')
     def test_run_stop_terminate_instance(self):
diff --git a/tempest/tests/boto/test_ec2_keys.py b/tempest/tests/boto/test_ec2_keys.py
index dea895f..d96ee11 100644
--- a/tempest/tests/boto/test_ec2_keys.py
+++ b/tempest/tests/boto/test_ec2_keys.py
@@ -15,11 +15,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
 import testtools
 
 from tempest import clients
 from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
 from tempest.testboto import BotoTestCase
 
 
diff --git a/tempest/tests/boto/test_ec2_network.py b/tempest/tests/boto/test_ec2_network.py
index 76103c2..ef307a1 100644
--- a/tempest/tests/boto/test_ec2_network.py
+++ b/tempest/tests/boto/test_ec2_network.py
@@ -15,10 +15,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
 import testtools
 
 from tempest import clients
+from tempest.test import attr
 from tempest.testboto import BotoTestCase
 
 
@@ -47,4 +47,4 @@
                              address.disassociate)
         self.client.release_address(public_ip)
         self.cancelResourceCleanUp(rcuk)
-        assertAddressReleasedWait(address)
+        self.assertAddressReleasedWait(address)
diff --git a/tempest/tests/boto/test_ec2_security_groups.py b/tempest/tests/boto/test_ec2_security_groups.py
index ed7bedb..dd46a91 100644
--- a/tempest/tests/boto/test_ec2_security_groups.py
+++ b/tempest/tests/boto/test_ec2_security_groups.py
@@ -15,11 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-import testtools
-
 from tempest import clients
 from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
 from tempest.testboto import BotoTestCase
 
 
diff --git a/tempest/tests/boto/test_ec2_volumes.py b/tempest/tests/boto/test_ec2_volumes.py
index 7397cdb..aa2325f 100644
--- a/tempest/tests/boto/test_ec2_volumes.py
+++ b/tempest/tests/boto/test_ec2_volumes.py
@@ -16,12 +16,9 @@
 #    under the License.
 
 import logging
-import time
-
-from nose.plugins.attrib import attr
-import testtools
 
 from tempest import clients
+from tempest.test import attr
 from tempest.testboto import BotoTestCase
 
 LOG = logging.getLogger(__name__)
diff --git a/tempest/tests/boto/test_s3_buckets.py b/tempest/tests/boto/test_s3_buckets.py
index 36aa319..a4d1927 100644
--- a/tempest/tests/boto/test_s3_buckets.py
+++ b/tempest/tests/boto/test_s3_buckets.py
@@ -15,11 +15,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
 import testtools
 
 from tempest import clients
 from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
 from tempest.testboto import BotoTestCase
 
 
diff --git a/tempest/tests/boto/test_s3_ec2_images.py b/tempest/tests/boto/test_s3_ec2_images.py
index e0dc124..8913395 100644
--- a/tempest/tests/boto/test_s3_ec2_images.py
+++ b/tempest/tests/boto/test_s3_ec2_images.py
@@ -15,15 +15,13 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from contextlib import closing
 import os
 
-from boto.s3.key import Key
-from nose.plugins.attrib import attr
 import testtools
 
 from tempest import clients
 from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
 from tempest.testboto import BotoTestCase
 import tempest.tests.boto
 from tempest.tests.boto.utils.s3 import s3_upload_dir
diff --git a/tempest/tests/boto/test_s3_objects.py b/tempest/tests/boto/test_s3_objects.py
index cacd99f..8334b07 100644
--- a/tempest/tests/boto/test_s3_objects.py
+++ b/tempest/tests/boto/test_s3_objects.py
@@ -18,13 +18,12 @@
 from contextlib import closing
 
 from boto.s3.key import Key
-from nose.plugins.attrib import attr
 import testtools
 
 from tempest import clients
 from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
 from tempest.testboto import BotoTestCase
-from tempest.tests import boto
 
 
 @attr("S3")
diff --git a/tempest/tests/compute/__init__.py b/tempest/tests/compute/__init__.py
index 5b59a70..a3c6380 100644
--- a/tempest/tests/compute/__init__.py
+++ b/tempest/tests/compute/__init__.py
@@ -30,10 +30,10 @@
 RESIZE_AVAILABLE = CONFIG.compute.resize_available
 CHANGE_PASSWORD_AVAILABLE = CONFIG.compute.change_password_available
 WHITEBOX_ENABLED = CONFIG.whitebox.whitebox_enabled
-DISK_CONFIG_ENABLED = False
+DISK_CONFIG_ENABLED = True
 DISK_CONFIG_ENABLED_OVERRIDE = CONFIG.compute.disk_config_enabled_override
-FLAVOR_EXTRA_DATA_ENABLED = False
-MULTI_USER = False
+FLAVOR_EXTRA_DATA_ENABLED = True
+MULTI_USER = True
 
 
 # All compute tests -- single setup function
@@ -66,12 +66,12 @@
     # used in testing. If the test cases are allowed to create
     # users (config.compute.allow_tenant_isolation is true,
     # then we allow multi-user.
-    if CONFIG.compute.allow_tenant_isolation:
-        MULTI_USER = True
-    else:
+    if not CONFIG.compute.allow_tenant_isolation:
         user1 = CONFIG.identity.username
         user2 = CONFIG.identity.alt_username
-        if user2 and user1 != user2:
+        if not user2 or user1 == user2:
+            MULTI_USER = False
+        else:
             user2_password = CONFIG.identity.alt_password
             user2_tenant_name = CONFIG.identity.alt_tenant_name
             if not user2_password or not user2_tenant_name:
@@ -79,7 +79,6 @@
                        "tenant or password: alt_tenant_name=%s alt_password=%s"
                        % (user2_tenant_name, user2_password))
                 raise InvalidConfiguration(msg)
-            MULTI_USER = True
 
 
 class ComputeResource(TestResourceManager):
diff --git a/tempest/tests/compute/admin/test_flavors.py b/tempest/tests/compute/admin/test_flavors.py
index 7b80011..30c3d59 100644
--- a/tempest/tests/compute/admin/test_flavors.py
+++ b/tempest/tests/compute/admin/test_flavors.py
@@ -15,12 +15,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-import testtools
-
 from tempest.common.utils.data_utils import rand_int_id
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests import compute
 from tempest.tests.compute import base
 
@@ -37,7 +35,7 @@
             msg = "FlavorExtraData extension not enabled."
             raise cls.skipException(msg)
 
-        cls.client = cls.os.flavors_client
+        cls.client = cls.os_adm.flavors_client
         cls.flavor_name_prefix = 'test_flavor_'
         cls.ram = 512
         cls.vcpus = 1
@@ -314,6 +312,8 @@
                           self.client.list_flavors_with_detail,
                           {'is_public': 'invalid'})
 
+#TODO(afazekas): Negative tests with regular user
+
 
 class FlavorsAdminTestXML(base.BaseComputeAdminTestXML,
                           base.BaseComputeTestXML,
diff --git a/tempest/tests/compute/admin/test_flavors_extra_specs.py b/tempest/tests/compute/admin/test_flavors_extra_specs.py
index ef4c20e..968603d 100644
--- a/tempest/tests/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/tests/compute/admin/test_flavors_extra_specs.py
@@ -35,7 +35,7 @@
             msg = "FlavorExtraData extension not enabled."
             raise cls.skipException(msg)
 
-        cls.client = cls.os.flavors_client
+        cls.client = cls.os_adm.flavors_client
         flavor_name = 'test_flavor2'
         ram = 512
         vcpus = 1
diff --git a/tempest/tests/compute/admin/test_quotas.py b/tempest/tests/compute/admin/test_quotas.py
index b2b515a..a845632 100644
--- a/tempest/tests/compute/admin/test_quotas.py
+++ b/tempest/tests/compute/admin/test_quotas.py
@@ -15,13 +15,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
 from tempest import exceptions
-from tempest.services.compute.admin.json \
-    import quotas_client as adm_quotas_json
-from tempest.services.compute.admin.xml import quotas_client as adm_quotas_xml
-from tempest.tests import compute
+from tempest.test import attr
 from tempest.tests.compute import base
 
 
@@ -29,23 +24,21 @@
 
     @classmethod
     def setUpClass(cls):
-        cls.c_adm_user = cls.config.compute_admin.username
-        cls.c_adm_pass = cls.config.compute_admin.password
-        cls.c_adm_tenant = cls.config.compute_admin.tenant_name
         cls.auth_url = cls.config.identity.uri
         cls.client = cls.os.quotas_client
+        cls.adm_client = cls.os_adm.quotas_client
         cls.identity_admin_client = cls._get_identity_admin_client()
+
         resp, tenants = cls.identity_admin_client.list_tenants()
 
+        #NOTE(afazekas): these test cases should always create and use a new
+        # tenant most of them should be skipped if we can't do that
         if cls.config.compute.allow_tenant_isolation:
             cls.demo_tenant_id = cls.isolated_creds[0][0]['tenantId']
         else:
             cls.demo_tenant_id = [tnt['id'] for tnt in tenants if tnt['name']
                                   == cls.config.identity.tenant_name][0]
 
-        cls.adm_tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
-                             cls.config.compute_admin.tenant_name][0]
-
         cls.default_quota_set = {'injected_file_content_bytes': 10240,
                                  'metadata_items': 128, 'injected_files': 5,
                                  'ram': 51200, 'floating_ips': 10,
@@ -70,7 +63,7 @@
         try:
             resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
             self.assertEqual(200, resp.status)
-            self.assertSequenceEqual(expected_quota_set, quota_set)
+            self.assertEqual(expected_quota_set, quota_set)
         except Exception:
             self.fail("Admin could not get the default quota set for a tenant")
 
@@ -88,7 +81,7 @@
                 self.demo_tenant_id,
                 **new_quota_set)
             self.assertEqual(200, resp.status)
-            self.assertSequenceEqual(new_quota_set, quota_set)
+            self.assertEqual(new_quota_set, quota_set)
         except Exception:
             self.fail("Admin could not update quota set for the tenant")
         finally:
@@ -99,6 +92,7 @@
             self.assertEqual(200, resp.status, "Failed to reset quota "
                              "defaults")
 
+    #TODO(afazekas): merge these test cases
     def test_get_updated_quotas(self):
         # Verify that GET shows the updated quota set
         self.adm_client.update_quota_set(self.demo_tenant_id,
@@ -153,6 +147,8 @@
             self.adm_client.update_quota_set(self.demo_tenant_id,
                                              ram=default_mem_quota)
 
+#TODO(afazekas): Add test that tried to update the quota_set as a regular user
+
 
 class QuotasAdminTestJSON(QuotasAdminTestBase, base.BaseComputeAdminTestJSON,
                           base.BaseComputeTest):
@@ -163,10 +159,6 @@
         base.BaseComputeTest.setUpClass()
         super(QuotasAdminTestJSON, cls).setUpClass()
 
-        cls.adm_client = adm_quotas_json.AdminQuotasClientJSON(
-            cls.config, cls.c_adm_user, cls.c_adm_pass,
-            cls.auth_url, cls.c_adm_tenant)
-
     @classmethod
     def tearDownClass(cls):
         super(QuotasAdminTestJSON, cls).tearDownClass()
@@ -182,12 +174,6 @@
         base.BaseComputeTest.setUpClass()
         super(QuotasAdminTestXML, cls).setUpClass()
 
-        cls.adm_client = adm_quotas_xml.AdminQuotasClientXML(cls.config,
-                                                             cls.c_adm_user,
-                                                             cls.c_adm_pass,
-                                                             cls.auth_url,
-                                                             cls.c_adm_tenant)
-
     @classmethod
     def tearDownClass(cls):
         super(QuotasAdminTestXML, cls).tearDownClass()
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index 3810046..2aae89c 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -33,7 +33,8 @@
 LOG = logging.getLogger(__name__)
 
 
-class BaseCompTest(testtools.TestCase,
+class BaseCompTest(testtools.testcase.WithAttributes,
+                   testtools.TestCase,
                    testresources.ResourcedTestCase):
     """Base test case class for all Compute API tests."""
 
@@ -153,7 +154,7 @@
     @classmethod
     def clear_isolated_creds(cls):
         if not cls.isolated_creds:
-            pass
+            return
         admin_client = cls._get_identity_admin_client()
 
         for user, tenant in cls.isolated_creds:
@@ -246,23 +247,23 @@
         super(BaseComputeTestXML, cls).setUpClass()
 
 
-class BaseComputeAdminTest(testtools.TestCase):
+class BaseComputeAdminTest(BaseCompTest):
 
     """Base test case class for all Compute Admin API tests."""
 
     @classmethod
     def setUpClass(cls):
-        cls.config = config.TempestConfig()
-        cls.admin_username = cls.config.identity.admin_username
-        cls.admin_password = cls.config.identity.admin_password
-        cls.admin_tenant = cls.config.identity.admin_tenant_name
+        super(BaseComputeAdminTest, cls).setUpClass()
+        admin_username = cls.config.compute_admin.username
+        admin_password = cls.config.compute_admin.password
+        admin_tenant = cls.config.compute_admin.tenant_name
 
-        if not cls.admin_username and cls.admin_password and cls.admin_tenant:
+        if not (admin_username and admin_password and admin_tenant):
             msg = ("Missing Compute Admin API credentials "
                    "in configuration.")
             raise cls.skipException(msg)
 
-        cls.os = clients.ComputeAdminManager(interface=cls._interface)
+        cls.os_adm = clients.ComputeAdminManager(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 298984f..8b49390 100644
--- a/tempest/tests/compute/flavors/test_flavors.py
+++ b/tempest/tests/compute/flavors/test_flavors.py
@@ -15,8 +15,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.compute import base
 
 
@@ -131,6 +131,18 @@
         self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
 
     @attr(type='negative')
+    def test_invalid_minRam_filter(self):
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.list_flavors_with_detail,
+                          {'minRam': 'invalid'})
+
+    @attr(type='negative')
+    def test_invalid_minDisk_filter(self):
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.list_flavors_with_detail,
+                          {'minDisk': 'invalid'})
+
+    @attr(type='negative')
     def test_get_flavor_details_for_invalid_flavor_id(self):
         # Ensure 404 returned for non-existant flavor ID
         self.assertRaises(exceptions.NotFound, self.client.get_flavor_details,
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 59faf66..165cf79 100644
--- a/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
@@ -15,12 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-import testtools
-
-from tempest import clients
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.compute import base
 
 
diff --git a/tempest/tests/compute/floating_ips/test_list_floating_ips.py b/tempest/tests/compute/floating_ips/test_list_floating_ips.py
index ea99e89..42befd0 100644
--- a/tempest/tests/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/tests/compute/floating_ips/test_list_floating_ips.py
@@ -15,11 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-import testtools
-
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.compute import base
 
 
diff --git a/tempest/tests/compute/images/test_image_metadata.py b/tempest/tests/compute/images/test_image_metadata.py
index 94bdca7..7119a8e 100644
--- a/tempest/tests/compute/images/test_image_metadata.py
+++ b/tempest/tests/compute/images/test_image_metadata.py
@@ -15,10 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.compute.base import BaseComputeTest
 
 
diff --git a/tempest/tests/compute/images/test_images.py b/tempest/tests/compute/images/test_images.py
index 1fc03b8..edc58e7 100644
--- a/tempest/tests/compute/images/test_images.py
+++ b/tempest/tests/compute/images/test_images.py
@@ -15,14 +15,13 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
 import testtools
 
 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.test import attr
 from tempest.tests import compute
 from tempest.tests.compute import base
 
diff --git a/tempest/tests/compute/images/test_images_oneserver.py b/tempest/tests/compute/images/test_images_oneserver.py
index 232d5d3..08966fd 100644
--- a/tempest/tests/compute/images/test_images_oneserver.py
+++ b/tempest/tests/compute/images/test_images_oneserver.py
@@ -15,14 +15,13 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
 import testtools
 
 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.test import attr
 from tempest.tests import compute
 from tempest.tests.compute import base
 
diff --git a/tempest/tests/compute/images/test_images_whitebox.py b/tempest/tests/compute/images/test_images_whitebox.py
index b2419b4..88f3b53 100644
--- a/tempest/tests/compute/images/test_images_whitebox.py
+++ b/tempest/tests/compute/images/test_images_whitebox.py
@@ -15,10 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.compute import base
 from tempest import whitebox
 
diff --git a/tempest/tests/compute/images/test_list_image_filters.py b/tempest/tests/compute/images/test_list_image_filters.py
index 26119e3..316d90a 100644
--- a/tempest/tests/compute/images/test_list_image_filters.py
+++ b/tempest/tests/compute/images/test_list_image_filters.py
@@ -15,11 +15,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
 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.test import attr
 from tempest.tests.compute.base import BaseComputeTest
 
 
diff --git a/tempest/tests/compute/images/test_list_images.py b/tempest/tests/compute/images/test_list_images.py
index da92ca8..0f661b7 100644
--- a/tempest/tests/compute/images/test_list_images.py
+++ b/tempest/tests/compute/images/test_list_images.py
@@ -15,11 +15,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
-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.test import attr
 from tempest.tests.compute.base import BaseComputeTest
 
 
diff --git a/tempest/tests/compute/keypairs/test_keypairs.py b/tempest/tests/compute/keypairs/test_keypairs.py
index 45c9079..6f4569b 100644
--- a/tempest/tests/compute/keypairs/test_keypairs.py
+++ b/tempest/tests/compute/keypairs/test_keypairs.py
@@ -15,11 +15,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
 import testtools
 
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.compute.base import BaseComputeTestJSON
 from tempest.tests.compute.base import BaseComputeTestXML
 
diff --git a/tempest/tests/compute/security_groups/test_security_group_rules.py b/tempest/tests/compute/security_groups/test_security_group_rules.py
index 805adf4..41ad2c8 100644
--- a/tempest/tests/compute/security_groups/test_security_group_rules.py
+++ b/tempest/tests/compute/security_groups/test_security_group_rules.py
@@ -15,10 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.compute import base
 
 
diff --git a/tempest/tests/compute/security_groups/test_security_groups.py b/tempest/tests/compute/security_groups/test_security_groups.py
index 5c0bd82..f6caf2b 100644
--- a/tempest/tests/compute/security_groups/test_security_groups.py
+++ b/tempest/tests/compute/security_groups/test_security_groups.py
@@ -15,10 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.compute import base
 
 
diff --git a/tempest/tests/compute/servers/test_console_output.py b/tempest/tests/compute/servers/test_console_output.py
index 1eadbb5..bdf449d 100644
--- a/tempest/tests/compute/servers/test_console_output.py
+++ b/tempest/tests/compute/servers/test_console_output.py
@@ -15,11 +15,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
 import testtools
 
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.compute import base
 
 
@@ -28,7 +28,6 @@
     @classmethod
     def setUpClass(self, cls):
         cls.name = rand_name('server')
-        cls.client = cls.servers_client
         resp, server = cls.servers_client.create_server(cls.name,
                                                         cls.image_ref,
                                                         cls.flavor_ref)
@@ -45,7 +44,8 @@
         # Positive test:Should be able to GET the console output
         # for a given server_id and number of lines
         def get_output():
-            resp, output = self.client.get_console_output(self.server_id, 10)
+            resp, output = self.servers_client.get_console_output(
+                self.server_id, 10)
             self.assertEqual(200, resp.status)
             self.assertNotEqual(output, None)
             lines = len(output.split('\n'))
@@ -57,7 +57,8 @@
         # Negative test: Should not be able to get the console output
         # for an invalid server_id
         try:
-            resp, output = self.client.get_console_output('!@#$%^&*()', 10)
+            resp, output = self.servers_client.get_console_output(
+                '!@#$%^&*()', 10)
         except exceptions.NotFound:
             pass
 
@@ -72,8 +73,8 @@
                                                        'REBOOT')
             resp, server = self.servers_client.get_server(self.server_id)
             if (server['status'] == 'REBOOT'):
-                resp, output = self.client.get_console_output(self.server_id,
-                                                              10)
+                resp, output = self.servers_client.get_console_output(
+                    self.server_id, 10)
                 self.assertEqual(200, resp.status)
                 self.assertNotEqual(output, None)
                 lines = len(output.split('\n'))
diff --git a/tempest/tests/compute/servers/test_create_server.py b/tempest/tests/compute/servers/test_create_server.py
index 0dcc79f..54f1131 100644
--- a/tempest/tests/compute/servers/test_create_server.py
+++ b/tempest/tests/compute/servers/test_create_server.py
@@ -17,13 +17,13 @@
 
 import base64
 
-from nose.plugins.attrib import attr
 import testtools
 
 
 from tempest.common.utils.data_utils import rand_name
 from tempest.common.utils.linux.remote_client import RemoteClient
 import tempest.config
+from tempest.test import attr
 from tempest.tests import compute
 from tempest.tests.compute import base
 
diff --git a/tempest/tests/compute/servers/test_disk_config.py b/tempest/tests/compute/servers/test_disk_config.py
index c3a37ff..57b95f2 100644
--- a/tempest/tests/compute/servers/test_disk_config.py
+++ b/tempest/tests/compute/servers/test_disk_config.py
@@ -15,11 +15,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
 import testtools
 
 from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
+from tempest.test import attr
 from tempest.tests import compute
 from tempest.tests.compute.base import BaseComputeTest
 
diff --git a/tempest/tests/compute/servers/test_list_server_filters.py b/tempest/tests/compute/servers/test_list_server_filters.py
index 45ea3a0..9b061b5 100644
--- a/tempest/tests/compute/servers/test_list_server_filters.py
+++ b/tempest/tests/compute/servers/test_list_server_filters.py
@@ -16,11 +16,9 @@
 #    under the License.
 
 
-from nose.plugins.attrib import attr
-import testtools
-
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.compute import base
 from tempest.tests import utils
 
diff --git a/tempest/tests/compute/servers/test_list_servers_negative.py b/tempest/tests/compute/servers/test_list_servers_negative.py
index eb4ea02..f93bebf 100644
--- a/tempest/tests/compute/servers/test_list_servers_negative.py
+++ b/tempest/tests/compute/servers/test_list_servers_negative.py
@@ -15,13 +15,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import re
-import sys
-
-import testtools
 
 from tempest import clients
-from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
 from tempest.tests import compute
 from tempest.tests.compute.base import BaseComputeTest
diff --git a/tempest/tests/compute/servers/test_server_actions.py b/tempest/tests/compute/servers/test_server_actions.py
index fd35461..06e441a 100644
--- a/tempest/tests/compute/servers/test_server_actions.py
+++ b/tempest/tests/compute/servers/test_server_actions.py
@@ -18,13 +18,13 @@
 import base64
 import time
 
-from nose.plugins.attrib import attr
 import testtools
 
 from tempest.common.utils.data_utils import rand_name
 from tempest.common.utils.linux.remote_client import RemoteClient
 import tempest.config
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests import compute
 from tempest.tests.compute import base
 
@@ -122,13 +122,24 @@
             linux_client = RemoteClient(server, self.ssh_user, password)
             self.assertTrue(linux_client.can_authenticate())
 
+    def _detect_server_image_flavor(self, server_id):
+        # Detects the current server image flavor ref.
+        resp, server = self.client.get_server(self.server_id)
+        current_flavor = server['flavor']['id']
+        new_flavor_ref = self.flavor_ref_alt \
+            if int(current_flavor) == self.flavor_ref else self.flavor_ref
+        return int(current_flavor), int(new_flavor_ref)
+
     @attr(type='smoke')
     @testtools.skipIf(not resize_available, 'Resize not available.')
     def test_resize_server_confirm(self):
         # The server's RAM and disk space should be modified to that of
         # the provided flavor
 
-        resp, server = self.client.resize(self.server_id, self.flavor_ref_alt)
+        previous_flavor_ref, new_flavor_ref = \
+            self._detect_server_image_flavor(self.server_id)
+
+        resp, server = self.client.resize(self.server_id, new_flavor_ref)
         self.assertEqual(202, resp.status)
         self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
 
@@ -136,7 +147,7 @@
         self.client.wait_for_server_status(self.server_id, 'ACTIVE')
 
         resp, server = self.client.get_server(self.server_id)
-        self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
+        self.assertEqual(new_flavor_ref, int(server['flavor']['id']))
 
     @attr(type='positive')
     @testtools.skipIf(not resize_available, 'Resize not available.')
@@ -144,7 +155,10 @@
         # The server's RAM and disk space should return to its original
         # values after a resize is reverted
 
-        resp, server = self.client.resize(self.server_id, self.flavor_ref_alt)
+        previous_flavor_ref, new_flavor_ref = \
+            self._detect_server_image_flavor(self.server_id)
+
+        resp, server = self.client.resize(self.server_id, new_flavor_ref)
         self.assertEqual(202, resp.status)
         self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
 
@@ -155,7 +169,7 @@
         resp, server = self.client.get_server(self.server_id)
         start = int(time.time())
 
-        while server['flavor']['id'] != self.flavor_ref:
+        while int(server['flavor']['id']) != previous_flavor_ref:
             time.sleep(self.build_interval)
             resp, server = self.client.get_server(self.server_id)
 
@@ -192,6 +206,18 @@
             self.fail('The server rebuild for a non existing server should not'
                       ' be allowed')
 
+    @classmethod
+    def rebuild_servers(cls):
+        # Destroy any existing server and creates a new one
+        cls.clear_servers()
+        cls.name = rand_name('server')
+        resp, server = cls.create_server_with_extras(cls.name,
+                                                     cls.image_ref,
+                                                     cls.flavor_ref)
+        cls.server_id = server['id']
+        cls.password = server['adminPass']
+        cls.client.wait_for_server_status(cls.server_id, 'ACTIVE')
+
 
 class ServerActionsTestXML(base.BaseComputeTestXML,
                            ServerActionsTestBase):
@@ -202,30 +228,13 @@
             self.client.wait_for_server_status(self.server_id, 'ACTIVE')
         except exceptions:
             # Rebuild server if something happened to it during a test
-            self.clear_servers()
-            resp, server = self.create_server_with_extras(self.name,
-                                                          self.image_ref,
-                                                          self.flavor_ref)
-            self.server_id = server['id']
-            self.password = server['adminPass']
-            self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+            self.rebuild_servers()
 
     @classmethod
     def setUpClass(cls):
         super(ServerActionsTestXML, cls).setUpClass()
         cls.client = cls.servers_client
-        cls.name = rand_name('server')
-        resp, server = cls.create_server_with_extras(cls.name,
-                                                     cls.image_ref,
-                                                     cls.flavor_ref)
-        cls.server_id = server['id']
-        cls.password = server['adminPass']
-        cls.client.wait_for_server_status(cls.server_id, 'ACTIVE')
-
-    @classmethod
-    def tearDownClass(cls):
-        cls.clear_servers()
-        super(ServerActionsTestXML, cls).tearDownClass()
+        cls.rebuild_servers()
 
 
 class ServerActionsTestJSON(base.BaseComputeTestJSON,
@@ -237,27 +246,10 @@
             self.client.wait_for_server_status(self.server_id, 'ACTIVE')
         except exceptions:
             # Rebuild server if something happened to it during a test
-            self.clear_servers()
-            resp, server = self.create_server_with_extras(self.name,
-                                                          self.image_ref,
-                                                          self.flavor_ref)
-            self.server_id = server['id']
-            self.password = server['adminPass']
-            self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+            self.rebuild_servers()
 
     @classmethod
     def setUpClass(cls):
         super(ServerActionsTestJSON, cls).setUpClass()
         cls.client = cls.servers_client
-        cls.name = rand_name('server')
-        resp, server = cls.create_server_with_extras(cls.name,
-                                                     cls.image_ref,
-                                                     cls.flavor_ref)
-        cls.server_id = server['id']
-        cls.password = server['adminPass']
-        cls.client.wait_for_server_status(cls.server_id, 'ACTIVE')
-
-    @classmethod
-    def tearDownClass(cls):
-        cls.clear_servers()
-        super(ServerActionsTestJSON, cls).tearDownClass()
+        cls.rebuild_servers()
diff --git a/tempest/tests/compute/servers/test_server_addresses.py b/tempest/tests/compute/servers/test_server_addresses.py
index 6b0f7ae..8888281 100644
--- a/tempest/tests/compute/servers/test_server_addresses.py
+++ b/tempest/tests/compute/servers/test_server_addresses.py
@@ -15,10 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.compute.base import BaseComputeTest
 
 
diff --git a/tempest/tests/compute/servers/test_server_metadata.py b/tempest/tests/compute/servers/test_server_metadata.py
index 7db963e..ef1b956 100644
--- a/tempest/tests/compute/servers/test_server_metadata.py
+++ b/tempest/tests/compute/servers/test_server_metadata.py
@@ -15,10 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.compute.base import BaseComputeTest
 
 
@@ -87,6 +86,16 @@
 
         # no teardown - all creates should fail
 
+    @attr(type='negative')
+    def test_create_metadata_key_error(self):
+        # Blank key should trigger an error.
+        meta = {'': 'data1'}
+        name = rand_name('server')
+        self.assertRaises(exceptions.BadRequest,
+                          self.create_server_with_extras,
+                          name, self.image_ref,
+                          self.flavor_ref, meta=meta)
+
     def test_update_server_metadata(self):
         # The server's metadata values should be updated to the
         # provided values
@@ -177,10 +186,17 @@
             self.fail('An update should not happen for a nonexistant image')
 
     @attr(type='negative')
+    def test_update_metadata_key_error(self):
+        # Blank key should trigger an error.
+        meta = {'': 'data1'}
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.update_server_metadata,
+                          self.server_id, meta=meta)
+
+    @attr(type='negative')
     def test_delete_nonexistant_server_metadata_item(self):
         # Negative test: Should not be able to delete metadata item from a
         # nonexistant server
-        meta = {'d': 'delvalue'}
 
         #Delete the metadata item
         try:
diff --git a/tempest/tests/compute/servers/test_server_personality.py b/tempest/tests/compute/servers/test_server_personality.py
index e98e559..862cfb2 100644
--- a/tempest/tests/compute/servers/test_server_personality.py
+++ b/tempest/tests/compute/servers/test_server_personality.py
@@ -17,10 +17,9 @@
 
 import base64
 
-from nose.plugins.attrib import attr
-
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.compute import base
 
 
diff --git a/tempest/tests/compute/servers/test_servers.py b/tempest/tests/compute/servers/test_servers.py
index e0d4d44..caf0679 100644
--- a/tempest/tests/compute/servers/test_servers.py
+++ b/tempest/tests/compute/servers/test_servers.py
@@ -15,9 +15,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
 from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
 from tempest.tests.compute import base
 
 
@@ -162,25 +161,9 @@
         super(ServersTestJSON, cls).setUpClass()
         cls.client = cls.servers_client
 
-    def tearDown(self):
-        # clean up any remaining servers and wait for them to fully
-        # delete. This is done because delete calls are async, and if
-        # deletes are running slow we could very well overrun system
-        # memory
-        self.clear_servers()
-        super(ServersTestJSON, self).tearDown()
-
 
 class ServersTestXML(base.BaseComputeTestXML, ServersTestBase):
     @classmethod
     def setUpClass(cls):
         super(ServersTestXML, cls).setUpClass()
         cls.client = cls.servers_client
-
-    def tearDown(self):
-        # clean up any remaining servers and wait for them to fully
-        # delete. This is done because delete calls are async, and if
-        # deletes are running slow we could very well overrun system
-        # memory
-        self.clear_servers()
-        super(ServersTestXML, self).tearDown()
diff --git a/tempest/tests/compute/servers/test_servers_negative.py b/tempest/tests/compute/servers/test_servers_negative.py
index ea63360..9b528f6 100644
--- a/tempest/tests/compute/servers/test_servers_negative.py
+++ b/tempest/tests/compute/servers/test_servers_negative.py
@@ -17,12 +17,10 @@
 
 import sys
 
-from nose.plugins.attrib import attr
-import testtools
-
 from tempest import clients
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.compute.base import BaseComputeTest
 
 
@@ -275,3 +273,17 @@
 
         self.assertRaises(exceptions.NotFound, self.client.delete_server,
                           sys.maxint + 1)
+
+    @attr(type='negative')
+    def test_create_with_nonexistent_security_group(self):
+        # Create a server with a nonexistent security group
+        try:
+            security_groups = [{'name': 'does_not_exist'}]
+            self.create_server_with_extras('fail',
+                                           self.image_ref,
+                                           self.flavor_ref,
+                                           security_groups=security_groups)
+        except exceptions.BadRequest:
+            pass
+        else:
+            self.fail('Server was created with nonexistent security group')
diff --git a/tempest/tests/compute/servers/test_servers_whitebox.py b/tempest/tests/compute/servers/test_servers_whitebox.py
index 502f16b..94502c3 100644
--- a/tempest/tests/compute/servers/test_servers_whitebox.py
+++ b/tempest/tests/compute/servers/test_servers_whitebox.py
@@ -15,9 +15,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.identity.base import BaseIdentityAdminTest
 from tempest import whitebox
 
diff --git a/tempest/tests/compute/test_authorization.py b/tempest/tests/compute/test_authorization.py
index 8f4ad50..d826d40 100644
--- a/tempest/tests/compute/test_authorization.py
+++ b/tempest/tests/compute/test_authorization.py
@@ -243,7 +243,6 @@
             # Reset the base_url...
             self.alt_security_client.base_url = self.saved_base_url
             if resp['status'] is not None:
-                resp, _ = \
                 self.alt_security_client.delete_security_group(body['id'])
                 self.fail("Create Security Group request should not happen if"
                           "the tenant id does not match the current user")
@@ -283,7 +282,6 @@
             # Reset the base_url...
             self.alt_security_client.base_url = self.saved_base_url
             if resp['status'] is not None:
-                resp, _ = \
                 self.alt_security_client.delete_security_group_rule(
                                         body['id'])
                 self.fail("Create security group rule request should not "
diff --git a/tempest/tests/compute/test_extensions.py b/tempest/tests/compute/test_extensions.py
index 829e295..36a04d2 100644
--- a/tempest/tests/compute/test_extensions.py
+++ b/tempest/tests/compute/test_extensions.py
@@ -15,8 +15,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
 
+from tempest.test import attr
 from tempest.tests.compute import base
 
 
diff --git a/tempest/tests/compute/test_live_block_migration.py b/tempest/tests/compute/test_live_block_migration.py
index 1b651ab..6449ddc 100644
--- a/tempest/tests/compute/test_live_block_migration.py
+++ b/tempest/tests/compute/test_live_block_migration.py
@@ -18,14 +18,13 @@
 import random
 import string
 
-from nose.plugins.attrib import attr
 import testtools
 
-from tempest.common.utils.linux.remote_client import RemoteClient
 from tempest import config
 from tempest import exceptions
 from tempest.services.compute.json.hosts_client import HostsClientJSON
 from tempest.services.compute.json.servers_client import ServersClientJSON
+from tempest.test import attr
 from tempest.tests.compute import base
 
 
@@ -124,8 +123,8 @@
         server_id = self._get_an_active_server()
         target_host = self._get_non_existing_host_name()
 
-        with self.assertRaises(exceptions.BadRequest) as cm:
-            self._migrate_server_to(server_id, target_host)
+        self.assertRaises(exceptions.BadRequest, self._migrate_server_to,
+                          server_id, target_host)
         self.assertEquals('ACTIVE', self._get_server_status(server_id))
 
     @classmethod
diff --git a/tempest/tests/compute/test_quotas.py b/tempest/tests/compute/test_quotas.py
index 370840e..c2ad358 100644
--- a/tempest/tests/compute/test_quotas.py
+++ b/tempest/tests/compute/test_quotas.py
@@ -15,8 +15,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
+from tempest.test import attr
 from tempest.tests.compute import base
 
 
diff --git a/tempest/tests/compute/volumes/test_attach_volume.py b/tempest/tests/compute/volumes/test_attach_volume.py
index 0e0e4a5..1aed833 100644
--- a/tempest/tests/compute/volumes/test_attach_volume.py
+++ b/tempest/tests/compute/volumes/test_attach_volume.py
@@ -15,13 +15,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
 import testtools
 
-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.test import attr
 from tempest.tests.compute import base
 
 
diff --git a/tempest/tests/compute/volumes/test_volumes_get.py b/tempest/tests/compute/volumes/test_volumes_get.py
index afb00cd..edbfc32 100644
--- a/tempest/tests/compute/volumes/test_volumes_get.py
+++ b/tempest/tests/compute/volumes/test_volumes_get.py
@@ -15,9 +15,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
 from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
 from tempest.tests.compute import base
 
 
diff --git a/tempest/tests/identity/admin/test_roles.py b/tempest/tests/identity/admin/test_roles.py
index 2779b51..46db4fb 100644
--- a/tempest/tests/identity/admin/test_roles.py
+++ b/tempest/tests/identity/admin/test_roles.py
@@ -15,8 +15,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import testtools
-
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
 from tempest.tests.identity import base
@@ -120,13 +118,19 @@
 
 class UserRolesTestBase(RolesTestBase):
 
+    def assert_role_in_role_list(self, role, roles):
+        found = False
+        for user_role in roles:
+            if user_role['id'] == role['id']:
+                found = True
+        self.assertTrue(found, "assigned role was not in list")
+
     def test_assign_user_role(self):
         # Assign a role to a user on a tenant
         (user, tenant, role) = self._get_role_params()
         self.client.assign_user_role(tenant['id'], user['id'], role['id'])
         resp, roles = self.client.list_user_roles(tenant['id'], user['id'])
-        self.assertEquals(1, len(roles))
-        self.assertEquals(roles[0]['id'], role['id'])
+        self.assert_role_in_role_list(role, roles)
 
     def test_assign_user_role_by_unauthorized_user(self):
         # Non admin user should not be authorized to assign a role to user
@@ -234,8 +238,7 @@
         (user, tenant, role) = self._get_role_params()
         self.client.assign_user_role(tenant['id'], user['id'], role['id'])
         resp, roles = self.client.list_user_roles(tenant['id'], user['id'])
-        self.assertEquals(1, len(roles))
-        self.assertEquals(role['id'], roles[0]['id'])
+        self.assert_role_in_role_list(role, roles)
 
     def test_list_user_roles_by_unauthorized_user(self):
         # Non admin user should not be authorized to list a user's roles
diff --git a/tempest/tests/identity/admin/test_services.py b/tempest/tests/identity/admin/test_services.py
index b74266c..9ac102a 100644
--- a/tempest/tests/identity/admin/test_services.py
+++ b/tempest/tests/identity/admin/test_services.py
@@ -16,8 +16,6 @@
 #    under the License.
 
 
-from nose.plugins.attrib import attr
-
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
 from tempest.tests.identity import base
@@ -103,4 +101,3 @@
     @classmethod
     def setUpClass(cls):
         super(ServicesTestXML, cls).setUpClass()
-        raise cls.skipException("Skipping until Bug #1061738 resolved")
diff --git a/tempest/tests/identity/admin/test_tenants.py b/tempest/tests/identity/admin/test_tenants.py
index 54383f1..594afe8 100644
--- a/tempest/tests/identity/admin/test_tenants.py
+++ b/tempest/tests/identity/admin/test_tenants.py
@@ -15,11 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import testtools
-
-from nose.plugins.attrib import attr
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.identity import base
 
 
diff --git a/tempest/tests/identity/admin/test_users.py b/tempest/tests/identity/admin/test_users.py
index ef7d934..8396b91 100644
--- a/tempest/tests/identity/admin/test_users.py
+++ b/tempest/tests/identity/admin/test_users.py
@@ -15,9 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.identity import base
 import testtools
 from testtools.matchers._basic import Contains
diff --git a/tempest/tests/identity/base.py b/tempest/tests/identity/base.py
index cbd943e..fbff88f 100644
--- a/tempest/tests/identity/base.py
+++ b/tempest/tests/identity/base.py
@@ -21,7 +21,8 @@
 from tempest.common.utils.data_utils import rand_name
 
 
-class BaseIdAdminTest(testtools.TestCase):
+class BaseIdAdminTest(testtools.testcase.WithAttributes,
+                      testtools.TestCase):
 
     @classmethod
     def setUpClass(cls):
diff --git a/tempest/tests/image/test_images.py b/tempest/tests/image/test_images.py
index 0a1a25f..511f8b0 100644
--- a/tempest/tests/image/test_images.py
+++ b/tempest/tests/image/test_images.py
@@ -20,20 +20,15 @@
 
 import testtools
 
-from nose.plugins.attrib import attr
+from tempest.test import attr
 
 
-GLANCE_INSTALLED = False
-try:
-    import glanceclient
-    GLANCE_INSTALLED = True
-except ImportError:
-    pass
-
 from tempest import clients
+from tempest import exceptions
 
 
-class CreateRegisterImagesTest(testtools.TestCase):
+class CreateRegisterImagesTest(testtools.testcase.WithAttributes,
+                               testtools.TestCase):
 
     """
     Here we test the registration and creation of images
@@ -41,94 +36,80 @@
 
     @classmethod
     def setUpClass(cls):
-        if not GLANCE_INSTALLED:
-            raise cls.skipException('Glance not installed')
-        cls.os = clients.ServiceManager()
-        cls.client = cls.os.images.get_client()
+        cls.os = clients.Manager()
+        cls.client = cls.os.image_client
         cls.created_images = []
 
     @classmethod
     def tearDownClass(cls):
         for image_id in cls.created_images:
-            cls.client.images.delete(image_id)
+            cls.client.delete(image_id)
 
-    @attr(type='image')
-    def test_register_with_invalid_data(self):
+    @attr(type='negative')
+    def test_register_with_invalid_container_format(self):
         # Negative tests for invalid data supplied to POST /images
+        try:
+            resp, body = self.client.create_image('test', 'wrong', 'vhd')
+        except exceptions.BadRequest:
+            pass
+        else:
+            self.fail('Invalid container format should not be accepted')
 
-        metas = [
-            {
-                'id': '1'
-            },  # Cannot specify ID in registration
-            {
-                'container_format': 'wrong',
-            },  # Invalid container format
-            {
-                'disk_format': 'wrong',
-            },  # Invalid disk format
-        ]
-        for meta in metas:
-            try:
-                self.client.images.create(**meta)
-            except glanceclient.exc.HTTPBadRequest:
-                continue
-            self.fail("Did not raise Invalid for meta %s." % meta)
+    @attr(type='negative')
+    def test_register_with_invalid_disk_format(self):
+        try:
+            resp, body = self.client.create_image('test', 'bare', 'wrong')
+        except exceptions.BadRequest:
+                pass
+        else:
+            self.fail("Invalid disk format should not be accepted")
 
     @attr(type='image')
     def test_register_then_upload(self):
         # Register, then upload an image
-        meta = {
-            'name': 'New Name',
-            'is_public': True,
-            'disk_format': 'vhd',
-            'container_format': 'bare',
-            'properties': {'prop1': 'val1'}
-        }
-        results = self.client.images.create(**meta)
-        self.assertTrue(hasattr(results, 'id'))
-        image_id = results.id
+        properties = {'prop1': 'val1'}
+        resp, body = self.client.create_image('New Name', 'bare', 'raw',
+                                              is_public=True,
+                                              properties=properties)
+        self.assertTrue('id' in body)
+        image_id = body.get('id')
         self.created_images.append(image_id)
-        self.assertTrue(hasattr(results, 'name'))
-        self.assertEqual(meta['name'], results.name)
-        self.assertTrue(hasattr(results, 'is_public'))
-        self.assertEqual(meta['is_public'], results.is_public)
-        self.assertTrue(hasattr(results, 'status'))
-        self.assertEqual('queued', results.status)
-        self.assertTrue(hasattr(results, 'properties'))
-        for key, val in meta['properties'].items():
-            self.assertEqual(val, results.properties[key])
+        self.assertTrue('name' in body)
+        self.assertEqual('New Name', body.get('name'))
+        self.assertTrue('is_public' in body)
+        self.assertTrue(body.get('is_public'))
+        self.assertTrue('status' in body)
+        self.assertEqual('queued', body.get('status'))
+        self.assertTrue('properties' in body)
+        for key, val in properties.items():
+            self.assertEqual(val, body.get('properties')[key])
 
         # Now try uploading an image file
-        image_file = StringIO.StringIO('*' * 1024)
-        results = self.client.images.update(image_id, data=image_file)
-        self.assertTrue(hasattr(results, 'status'))
-        self.assertEqual('active', results.status)
-        self.assertTrue(hasattr(results, 'size'))
-        self.assertEqual(1024, results.size)
+        image_file = StringIO.StringIO(('*' * 1024))
+        resp, body = self.client.update_image(image_id, data=image_file)
+        self.assertTrue('size' in body)
+        self.assertEqual(1024, body.get('size'))
 
     @attr(type='image')
     def test_register_remote_image(self):
         # Register a new remote image
-        meta = {
-            'name': 'New Remote Image',
-            'is_public': True,
-            'disk_format': 'raw',
-            'container_format': 'bare',
-            'location': 'http://example.com/someimage.iso'
-        }
-        results = self.client.images.create(**meta)
-        self.assertTrue(hasattr(results, 'id'))
-        image_id = results.id
+        resp, body = self.client.create_image('New Remote Image', 'bare',
+                                              'raw', is_public=True,
+                                              location='http://example.com'
+                                                       '/someimage.iso')
+        self.assertTrue('id' in body)
+        image_id = body.get('id')
         self.created_images.append(image_id)
-        self.assertTrue(hasattr(results, 'name'))
-        self.assertEqual(meta['name'], results.name)
-        self.assertTrue(hasattr(results, 'is_public'))
-        self.assertEqual(meta['is_public'], results.is_public)
-        self.assertTrue(hasattr(results, 'status'))
-        self.assertEqual('active', results.status)
+        self.assertTrue('name' in body)
+        self.assertEqual('New Remote Image', body.get('name'))
+        self.assertTrue('is_public' in body)
+        self.assertTrue(body.get('is_public'))
+        self.assertTrue('status' in body)
+        self.assertEqual('active', body.get('status'))
 
 
-class ListImagesTest(testtools.TestCase):
+class ListImagesTest(testtools.testcase.WithAttributes,
+                     testtools.TestCase):
 
     """
     Here we test the listing of image information
@@ -136,10 +117,8 @@
 
     @classmethod
     def setUpClass(cls):
-        if not GLANCE_INSTALLED:
-            raise cls.skipException('Glance not installed')
-        cls.os = clients.ServiceManager()
-        cls.client = cls.os.images.get_client()
+        cls.os = clients.Manager()
+        cls.client = cls.os.image_client
         cls.created_images = []
 
         # We add a few images here to test the listing functionality of
@@ -154,7 +133,8 @@
     @classmethod
     def tearDownClass(cls):
         for image_id in cls.created_images:
-            cls.client.images.delete(image_id)
+            cls.client.delete_image(image_id)
+            cls.client.wait_for_resource_deletion(image_id)
 
     @classmethod
     def _create_remote_image(cls, x):
@@ -162,15 +142,12 @@
         Create a new remote image and return the ID of the newly-registered
         image
         """
-        meta = {
-            'name': 'New Remote Image %s' % x,
-            'is_public': True,
-            'disk_format': 'raw',
-            'container_format': 'bare',
-            'location': 'http://example.com/someimage_%s.iso' % x
-        }
-        results = cls.client.images.create(**meta)
-        image_id = results.id
+        name = 'New Remote Image %s' % x
+        location = 'http://example.com/someimage_%s.iso' % x
+        resp, body = cls.client.create_image(name, 'bare', 'raw',
+                                             is_public=True,
+                                             location=location)
+        image_id = body['id']
         return image_id
 
     @classmethod
@@ -181,19 +158,17 @@
         1024 and 4096
         """
         image_file = StringIO.StringIO('*' * random.randint(1024, 4096))
-        meta = {
-            'name': 'New Standard Image %s' % x,
-            'is_public': True,
-            'disk_format': 'raw',
-            'container_format': 'bare',
-            'data': image_file,
-        }
-        results = cls.client.images.create(**meta)
-        image_id = results.id
+        name = 'New Standard Image %s' % x
+        resp, body = cls.client.create_image(name, 'bare', 'raw',
+                                             is_public=True, data=image_file)
+        image_id = body['id']
         return image_id
 
     @attr(type='image')
     def test_index_no_params(self):
         # Simple test to see all fixture images returned
-        current_images = set(i.id for i in self.client.images.list())
-        self.assertTrue(set(self.created_images) <= current_images)
+        resp, images_list = self.client.image_list()
+        self.assertEqual(resp['status'], '200')
+        image_list = map(lambda x: x['id'], images_list)
+        for image in self.created_images:
+            self.assertTrue(image in image_list)
diff --git a/tempest/tests/network/base.py b/tempest/tests/network/base.py
index 01330cc..8606079 100644
--- a/tempest/tests/network/base.py
+++ b/tempest/tests/network/base.py
@@ -22,14 +22,13 @@
 from tempest import exceptions
 
 
-class BaseNetworkTest(testtools.TestCase):
+class BaseNetworkTest(testtools.testcase.WithAttributes,
+                      testtools.TestCase):
 
     @classmethod
     def setUpClass(cls):
         os = clients.Manager()
         client = os.network_client
-        config = os.config
-        networks = []
         enabled = True
 
         # Validate that there is even an endpoint configured
diff --git a/tempest/tests/network/test_network_basic_ops.py b/tempest/tests/network/test_network_basic_ops.py
index d297c98..aed368e 100644
--- a/tempest/tests/network/test_network_basic_ops.py
+++ b/tempest/tests/network/test_network_basic_ops.py
@@ -184,7 +184,6 @@
     def setUpClass(cls):
         super(TestNetworkBasicOps, cls).setUpClass()
         cls.check_preconditions()
-        cfg = cls.config.network
         cls.tenant_id = cls.manager._get_identity_client(
             cls.config.identity.username,
             cls.config.identity.password,
diff --git a/tempest/tests/network/test_networks.py b/tempest/tests/network/test_networks.py
index d7f09c4..136279f 100644
--- a/tempest/tests/network/test_networks.py
+++ b/tempest/tests/network/test_networks.py
@@ -15,7 +15,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
+from tempest.test import attr
 
 from tempest.common.utils.data_utils import rand_name
 from tempest.tests.network import base
diff --git a/tempest/tests/object_storage/base.py b/tempest/tests/object_storage/base.py
index 8c32ffc..2492d8b 100644
--- a/tempest/tests/object_storage/base.py
+++ b/tempest/tests/object_storage/base.py
@@ -18,12 +18,12 @@
 import testtools
 
 from tempest import clients
-import tempest.config
 from tempest import exceptions
 from tempest.tests.identity.base import DataGenerator
 
 
-class BaseObjectTest(testtools.TestCase):
+class BaseObjectTest(testtools.testcase.WithAttributes,
+                     testtools.TestCase):
 
     @classmethod
     def setUpClass(cls):
@@ -47,6 +47,5 @@
         try:
             cls.account_client.list_account_containers()
         except exceptions.EndpointNotFound:
-            enabled = False
             skip_msg = "No OpenStack Object Storage API endpoint"
             raise cls.skipException(skip_msg)
diff --git a/tempest/tests/object_storage/test_account_services.py b/tempest/tests/object_storage/test_account_services.py
index e34e349..14f94f7 100644
--- a/tempest/tests/object_storage/test_account_services.py
+++ b/tempest/tests/object_storage/test_account_services.py
@@ -15,9 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.object_storage import base
 
 
diff --git a/tempest/tests/object_storage/test_container_services.py b/tempest/tests/object_storage/test_container_services.py
index fe09341..2c5b1ff 100644
--- a/tempest/tests/object_storage/test_container_services.py
+++ b/tempest/tests/object_storage/test_container_services.py
@@ -15,10 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
 from tempest.common.utils.data_utils import arbitrary_string
 from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
 from tempest.tests.object_storage import base
 
 
diff --git a/tempest/tests/object_storage/test_container_sync.py b/tempest/tests/object_storage/test_container_sync.py
index f156f45..dad6309 100644
--- a/tempest/tests/object_storage/test_container_sync.py
+++ b/tempest/tests/object_storage/test_container_sync.py
@@ -15,9 +15,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-from tempest.common.utils.data_utils import arbitrary_string
 from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
 from tempest.tests.object_storage import base
 import testtools
 import time
diff --git a/tempest/tests/object_storage/test_object_expiry.py b/tempest/tests/object_storage/test_object_expiry.py
index 8e6b23b..1738ddf 100644
--- a/tempest/tests/object_storage/test_object_expiry.py
+++ b/tempest/tests/object_storage/test_object_expiry.py
@@ -15,10 +15,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
 from tempest.common.utils.data_utils import arbitrary_string
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.object_storage import base
 import testtools
 from time import sleep
diff --git a/tempest/tests/object_storage/test_object_services.py b/tempest/tests/object_storage/test_object_services.py
index d5b6d5c..e0a2fbb 100644
--- a/tempest/tests/object_storage/test_object_services.py
+++ b/tempest/tests/object_storage/test_object_services.py
@@ -15,11 +15,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
 from tempest.common.utils.data_utils import arbitrary_string
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.object_storage import base
 import testtools
 from time import time
diff --git a/tempest/tests/object_storage/test_object_version.py b/tempest/tests/object_storage/test_object_version.py
index 28e0893..bc1c045 100644
--- a/tempest/tests/object_storage/test_object_version.py
+++ b/tempest/tests/object_storage/test_object_version.py
@@ -15,8 +15,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
 from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
 from tempest.tests.object_storage import base
 
 
diff --git a/tempest/tests/volume/admin/base.py b/tempest/tests/volume/admin/base.py
deleted file mode 100644
index 21425be..0000000
--- a/tempest/tests/volume/admin/base.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2012 OpenStack, LLC
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-
-from tempest import config
-import tempest.services.volume.json.admin.volume_types_client \
-        as volume_types_json_client
-import tempest.services.volume.xml.admin.volume_types_client \
-        as volume_types_xml_client
-from tempest.tests.volume.base import BaseVolumeTest
-
-
-class BaseVolumeAdminTest(BaseVolumeTest):
-    """Base test case class for all Volume Admin API tests."""
-    @classmethod
-    def setUpClass(cls):
-        super(BaseVolumeAdminTest, cls).setUpClass()
-        cls.config = config.TempestConfig()
-        cls.adm_user = cls.config.identity.admin_username
-        cls.adm_pass = cls.config.identity.admin_password
-        cls.adm_tenant = cls.config.identity.admin_tenant_name
-        cls.auth_url = cls.config.identity.uri
-
-        if not cls.adm_user and cls.adm_pass and cls.adm_tenant:
-            msg = ("Missing Volume Admin API credentials "
-                   "in configuration.")
-            raise cls.skipException(msg)
-
-    @classmethod
-    def tearDownClass(cls):
-        super(BaseVolumeAdminTest, cls).tearDownClass()
-
-
-class BaseVolumeAdminTestJSON(BaseVolumeAdminTest):
-    @classmethod
-    def setUpClass(cls):
-        cls._interface = "json"
-        super(BaseVolumeAdminTestJSON, cls).setUpClass()
-        cls.client = volume_types_json_client.\
-        VolumeTypesClientJSON(cls.config, cls.adm_user, cls.adm_pass,
-                              cls.auth_url, cls.adm_tenant)
-
-
-class BaseVolumeAdminTestXML(BaseVolumeAdminTest):
-    @classmethod
-    def setUpClass(cls):
-        cls._interface = "xml"
-        super(BaseVolumeAdminTestXML, cls).setUpClass()
-        cls.client = volume_types_xml_client.\
-        VolumeTypesClientXML(cls.config, cls.adm_user, cls.adm_pass,
-                             cls.auth_url, cls.adm_tenant)
diff --git a/tempest/tests/volume/admin/test_volume_types_extra_specs.py b/tempest/tests/volume/admin/test_volume_types_extra_specs.py
index c743567..31e2879 100644
--- a/tempest/tests/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/tests/volume/admin/test_volume_types_extra_specs.py
@@ -16,30 +16,16 @@
 #    under the License.
 
 from tempest.common.utils.data_utils import rand_name
-from tempest.services.volume.json.admin import volume_types_client
-from tempest.tests.volume.base import BaseVolumeTest
+from tempest.tests.volume import base
 
 
-class VolumeTypesExtraSpecsTest(BaseVolumeTest):
+class VolumeTypesExtraSpecsTest(base.BaseVolumeAdminTest):
     _interface = "json"
 
     @classmethod
     def setUpClass(cls):
         super(VolumeTypesExtraSpecsTest, cls).setUpClass()
-
-        adm_user = cls.config.identity.admin_username
-        adm_pass = cls.config.identity.admin_password
-        adm_tenant = cls.config.identity.admin_tenant_name
-        auth_url = cls.config.identity.uri
-
-        cls.client = volume_types_client.VolumeTypesClientJSON(cls.config,
-                                                               adm_user,
-                                                               adm_pass,
-                                                               auth_url,
-                                                               adm_tenant)
-
         vol_type_name = rand_name('Volume-type-')
-        cls.extra_spec = {"spec1": "val1"}
         resp, cls.volume_type = cls.client.create_volume_type(vol_type_name)
 
     @classmethod
@@ -50,6 +36,12 @@
     def test_volume_type_extra_specs_list(self):
         # List Volume types extra specs.
         try:
+            extra_specs = {"spec1": "val1"}
+            resp, body = self.client.\
+            create_volume_type_extra_specs(self.volume_type['id'], extra_specs)
+            self.assertEqual(200, resp.status)
+            self.assertEqual(extra_specs, body,
+                             "Volume type extra spec incorrectly created")
             resp, body = self.client.\
             list_volume_types_extra_specs(self.volume_type['id'])
             self.assertEqual(200, resp.status)
@@ -62,49 +54,46 @@
     def test_volume_type_extra_specs_update(self):
         # Update volume type extra specs
         try:
-            extra_spec = {"spec1": "val2"}
-            resp, body = self.client.\
-            update_volume_type_extra_specs(self.volume_type['id'],
-                                           extra_spec.keys()[0],
-                                           extra_spec)
-            self.assertEqual(200, resp.status)
-            self.assertTrue('spec1' in body,
-                            "Volume type extra spec incorrectly updated")
-            self.assertEqual(extra_spec['spec1'], body['spec1'],
-                             "Volume type extra spec incorrectly updated")
-        except Exception:
-            self.fail("Couldnt update volume type extra spec")
-
-    def test_volume_type_extra_spec_create_delete(self):
-        # Create/Delete volume type extra spec.
-        try:
             extra_specs = {"spec2": "val1"}
             resp, body = self.client.\
             create_volume_type_extra_specs(self.volume_type['id'], extra_specs)
             self.assertEqual(200, resp.status)
             self.assertEqual(extra_specs, body,
                              "Volume type extra spec incorrectly created")
-            resp, _ = self.client.\
-            delete_volume_type_extra_specs(self.volume_type['id'],
-                                           extra_specs.keys()[0])
-            self.assertEqual(202, resp.status)
-        except Exception:
-            self.fail("Could not create a volume_type extra spec")
 
-    def test_volume_type_extra_spec_create_get(self):
-        # Create/get volume type extra spec
+            extra_spec = {"spec2": "val2"}
+            resp, body = self.client.\
+            update_volume_type_extra_specs(self.volume_type['id'],
+                                           extra_spec.keys()[0],
+                                           extra_spec)
+            self.assertEqual(200, resp.status)
+            self.assertTrue('spec2' in body,
+                            "Volume type extra spec incorrectly updated")
+            self.assertEqual(extra_spec['spec2'], body['spec2'],
+                             "Volume type extra spec incorrectly updated")
+        except Exception:
+            self.fail("Couldnt update volume type extra spec")
+
+    def test_volume_type_extra_spec_create_get_delete(self):
+        # Create/Get/Delete volume type extra spec.
         try:
-            extra_specs = {"spec1": "val1"}
+            extra_specs = {"spec3": "val1"}
             resp, body = self.client.\
             create_volume_type_extra_specs(self.volume_type['id'], extra_specs)
             self.assertEqual(200, resp.status)
             self.assertEqual(extra_specs, body,
                              "Volume type extra spec incorrectly created")
+
             resp, fetched_vol_type_extra_spec = self.client.\
             get_volume_type_extra_specs(self.volume_type['id'],
                                         extra_specs.keys()[0])
             self.assertEqual(200, resp.status)
             self.assertEqual(extra_specs, body,
                              "Volume type extra spec incorrectly fetched")
+
+            resp, _ = self.client.\
+            delete_volume_type_extra_specs(self.volume_type['id'],
+                                           extra_specs.keys()[0])
+            self.assertEqual(202, resp.status)
         except Exception:
             self.fail("Could not create a volume_type extra spec")
diff --git a/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py
index 9aa8409..e201853 100644
--- a/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py
@@ -20,23 +20,24 @@
 
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
-from tempest.tests.volume.admin.base import BaseVolumeAdminTestJSON
-from tempest.tests.volume.admin.base import BaseVolumeAdminTestXML
+from tempest.tests.volume import base
 
 
-class ExtraSpecsNegativeTestBase():
+class ExtraSpecsNegativeTest(base.BaseVolumeAdminTest):
+    _interface = 'json'
 
-    @staticmethod
+    @classmethod
     def setUpClass(cls):
-        cls.client = cls.client
+        super(ExtraSpecsNegativeTest, cls).setUpClass()
         vol_type_name = rand_name('Volume-type-')
         cls.extra_specs = {"spec1": "val1"}
         resp, cls.volume_type = cls.client.create_volume_type(vol_type_name,
                                                               extra_specs=
                                                               cls.extra_specs)
 
-    @staticmethod
+    @classmethod
     def tearDownClass(cls):
+        super(ExtraSpecsNegativeTest, cls).tearDownClass()
         cls.client.delete_volume_type(cls.volume_type['id'])
 
     @testtools.skip('Until bug 1090320 is fixed')
@@ -122,29 +123,5 @@
                           self.volume_type['id'], str(uuid.uuid4()))
 
 
-class ExtraSpecsNegativeTestXML(BaseVolumeAdminTestXML,
-                                ExtraSpecsNegativeTestBase):
-
-    @classmethod
-    def setUpClass(cls):
-        super(ExtraSpecsNegativeTestXML, cls).setUpClass()
-        ExtraSpecsNegativeTestBase.setUpClass(cls)
-
-    @classmethod
-    def tearDownClass(cls):
-        super(ExtraSpecsNegativeTestXML, cls).tearDownClass()
-        ExtraSpecsNegativeTestBase.tearDownClass(cls)
-
-
-class ExtraSpecsNegativeTestJSON(BaseVolumeAdminTestJSON,
-                                 ExtraSpecsNegativeTestBase):
-
-    @classmethod
-    def setUpClass(cls):
-        super(ExtraSpecsNegativeTestJSON, cls).setUpClass()
-        ExtraSpecsNegativeTestBase.setUpClass(cls)
-
-    @classmethod
-    def tearDownClass(cls):
-        super(ExtraSpecsNegativeTestJSON, cls).tearDownClass()
-        ExtraSpecsNegativeTestBase.tearDownClass(cls)
+class ExtraSpecsNegativeTestXML(ExtraSpecsNegativeTest):
+    _interface = 'xml'
diff --git a/tempest/tests/volume/admin/test_volume_types_negative.py b/tempest/tests/volume/admin/test_volume_types_negative.py
index 8e7fa23..c706f3d 100644
--- a/tempest/tests/volume/admin/test_volume_types_negative.py
+++ b/tempest/tests/volume/admin/test_volume_types_negative.py
@@ -19,15 +19,11 @@
 import uuid
 
 from tempest import exceptions
-from tempest.tests.volume.admin.base import BaseVolumeAdminTestJSON
-from tempest.tests.volume.admin.base import BaseVolumeAdminTestXML
+from tempest.tests.volume import base
 
 
-class VolumeTypesNegativeTestBase():
-
-    @staticmethod
-    def setUpClass(cls):
-        cls.client = cls.client
+class VolumeTypesNegativeTest(base.BaseVolumeAdminTest):
+    _interface = 'json'
 
     def test_create_with_nonexistent_volume_type(self):
         # Should not be able to create volume with nonexistent volume_type.
@@ -53,17 +49,5 @@
                           str(uuid.uuid4()))
 
 
-class VolumesTypesNegativeTestXML(BaseVolumeAdminTestXML,
-                                  VolumeTypesNegativeTestBase):
-    @classmethod
-    def setUpClass(cls):
-        super(VolumesTypesNegativeTestXML, cls).setUpClass()
-        VolumeTypesNegativeTestBase.setUpClass(cls)
-
-
-class VolumesTypesNegativeTestJSON(BaseVolumeAdminTestJSON,
-                                   VolumeTypesNegativeTestBase):
-    @classmethod
-    def setUpClass(cls):
-        super(VolumesTypesNegativeTestJSON, cls).setUpClass()
-        VolumeTypesNegativeTestBase.setUpClass(cls)
+class VolumesTypesNegativeTestXML(VolumeTypesNegativeTest):
+    _interface = 'xml'
diff --git a/tempest/tests/volume/base.py b/tempest/tests/volume/base.py
index 49918e8..4ddd670 100644
--- a/tempest/tests/volume/base.py
+++ b/tempest/tests/volume/base.py
@@ -28,7 +28,8 @@
 LOG = logging.getLogger(__name__)
 
 
-class BaseVolumeTest(testtools.TestCase):
+class BaseVolumeTest(testtools.testcase.WithAttributes,
+                     testtools.TestCase):
 
     """Base test case class for all Cinder API tests."""
 
@@ -112,7 +113,7 @@
     @classmethod
     def clear_isolated_creds(cls):
         if not cls.isolated_creds:
-            pass
+            return
         admin_client = cls._get_identity_admin_client()
 
         for user, tenant in cls.isolated_creds:
@@ -192,15 +193,18 @@
             time.sleep(self.build_interval)
 
 
-class BaseVolumeTestJSON(BaseVolumeTest):
+class BaseVolumeAdminTest(BaseVolumeTest):
+    """Base test case class for all Volume Admin API tests."""
     @classmethod
     def setUpClass(cls):
-        cls._interface = "json"
-        super(BaseVolumeTestJSON, cls).setUpClass()
+        super(BaseVolumeAdminTest, cls).setUpClass()
+        cls.adm_user = cls.config.identity.admin_username
+        cls.adm_pass = cls.config.identity.admin_password
+        cls.adm_tenant = cls.config.identity.admin_tenant_name
+        if not all((cls.adm_user, cls.adm_pass, cls.adm_tenant)):
+            msg = ("Missing Volume Admin API credentials "
+                   "in configuration.")
+            raise cls.skipException(msg)
 
-
-class BaseVolumeTestXML(BaseVolumeTest):
-    @classmethod
-    def setUpClass(cls):
-        cls._interface = "xml"
-        super(BaseVolumeTestXML, cls).setUpClass()
+        cls.os_adm = clients.AdminManager(interface=cls._interface)
+        cls.client = cls.os_adm.volume_types_client
diff --git a/tempest/tests/volume/test_volumes_actions.py b/tempest/tests/volume/test_volumes_actions.py
index dd93b89..fb9b975 100644
--- a/tempest/tests/volume/test_volumes_actions.py
+++ b/tempest/tests/volume/test_volumes_actions.py
@@ -15,9 +15,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
 from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
 from tempest.tests.volume.base import BaseVolumeTest
 
 
diff --git a/tempest/tests/volume/test_volumes_get.py b/tempest/tests/volume/test_volumes_get.py
index bc64ff4..a246afe 100644
--- a/tempest/tests/volume/test_volumes_get.py
+++ b/tempest/tests/volume/test_volumes_get.py
@@ -15,13 +15,19 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
 from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
 from tempest.tests.volume import base
 
 
-class VolumesGetTestBase(object):
+class VolumesGetTest(base.BaseVolumeTest):
+
+    _interface = "json"
+
+    @classmethod
+    def setUpClass(cls):
+        super(VolumesGetTest, cls).setUpClass()
+        cls.client = cls.volumes_client
 
     @attr(type='smoke')
     def test_volume_create_get_delete(self):
@@ -95,17 +101,5 @@
                 self.client.wait_for_resource_deletion(volume['id'])
 
 
-class VolumesGetTestXML(base.BaseVolumeTestXML, VolumesGetTestBase):
-    @classmethod
-    def setUpClass(cls):
-        cls._interface = "xml"
-        super(VolumesGetTestXML, cls).setUpClass()
-        cls.client = cls.volumes_client
-
-
-class VolumesGetTestJSON(base.BaseVolumeTestJSON, VolumesGetTestBase):
-    @classmethod
-    def setUpClass(cls):
-        cls._interface = "json"
-        super(VolumesGetTestJSON, cls).setUpClass()
-        cls.client = cls.volumes_client
+class VolumesGetTestXML(VolumesGetTest):
+    _interface = "xml"
diff --git a/tempest/tests/volume/test_volumes_list.py b/tempest/tests/volume/test_volumes_list.py
index 92d3d3f..a8fedb9 100644
--- a/tempest/tests/volume/test_volumes_list.py
+++ b/tempest/tests/volume/test_volumes_list.py
@@ -15,14 +15,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import nose
-from nose.plugins.attrib import attr
-
 from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
 from tempest.tests.volume import base
 
 
-class VolumesListTestBase(object):
+class VolumesListTest(base.BaseVolumeTest):
 
     """
     This test creates a number of 1G volumes. To run successfully,
@@ -32,6 +30,52 @@
     VOLUME_BACKING_FILE_SIZE is atleast 4G in your localrc
     """
 
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(VolumesListTest, cls).setUpClass()
+        cls.client = cls.volumes_client
+
+        # Create 3 test volumes
+        cls.volume_list = []
+        cls.volume_id_list = []
+        for i in range(3):
+            v_name = rand_name('volume')
+            metadata = {'Type': 'work'}
+            try:
+                resp, volume = cls.client.create_volume(size=1,
+                                                        display_name=v_name,
+                                                        metadata=metadata)
+                cls.client.wait_for_volume_status(volume['id'], 'available')
+                resp, volume = cls.client.get_volume(volume['id'])
+                cls.volume_list.append(volume)
+                cls.volume_id_list.append(volume['id'])
+            except Exception:
+                if cls.volume_list:
+                    # We could not create all the volumes, though we were able
+                    # to create *some* of the volumes. This is typically
+                    # because the backing file size of the volume group is
+                    # too small. So, here, we clean up whatever we did manage
+                    # to create and raise a SkipTest
+                    for volid in cls.volume_id_list:
+                        cls.client.delete_volume(volid)
+                        cls.client.wait_for_resource_deletion(volid)
+                    msg = ("Failed to create ALL necessary volumes to run "
+                           "test. This typically means that the backing file "
+                           "size of the nova-volumes group is too small to "
+                           "create the 3 volumes needed by this test case")
+                    raise cls.skipException(msg)
+                raise
+
+    @classmethod
+    def tearDownClass(cls):
+        # Delete the created volumes
+        for volid in cls.volume_id_list:
+            resp, _ = cls.client.delete_volume(volid)
+            cls.client.wait_for_resource_deletion(volid)
+        super(VolumesListTest, cls).tearDownClass()
+
     @attr(type='smoke')
     def test_volume_list(self):
         # Get a list of Volumes
@@ -59,95 +103,5 @@
                                    for m_vol in missing_vols))
 
 
-class VolumeListTestXML(base.BaseVolumeTestXML, VolumesListTestBase):
-    @classmethod
-    def setUpClass(cls):
-        cls._interface = 'xml'
-        super(VolumeListTestXML, cls).setUpClass()
-        cls.client = cls.volumes_client
-
-        # Create 3 test volumes
-        cls.volume_list = []
-        cls.volume_id_list = []
-        for i in range(3):
-            v_name = rand_name('volume')
-            metadata = {'Type': 'work'}
-            try:
-                resp, volume = cls.client.create_volume(size=1,
-                                                        display_name=v_name,
-                                                        metadata=metadata)
-                cls.client.wait_for_volume_status(volume['id'], 'available')
-                resp, volume = cls.client.get_volume(volume['id'])
-                cls.volume_list.append(volume)
-                cls.volume_id_list.append(volume['id'])
-            except Exception:
-                if cls.volume_list:
-                    # We could not create all the volumes, though we were able
-                    # to create *some* of the volumes. This is typically
-                    # because the backing file size of the volume group is
-                    # too small. So, here, we clean up whatever we did manage
-                    # to create and raise a SkipTest
-                    for volid in cls.volume_id_list:
-                        cls.client.delete_volume(volid)
-                        cls.client.wait_for_resource_deletion(volid)
-                    msg = ("Failed to create ALL necessary volumes to run "
-                           "test. This typically means that the backing file "
-                           "size of the nova-volumes group is too small to "
-                           "create the 3 volumes needed by this test case")
-                    raise cls.skipException(msg)
-                raise
-
-    @classmethod
-    def tearDownClass(cls):
-        # Delete the created volumes
-        for volid in cls.volume_id_list:
-            resp, _ = cls.client.delete_volume(volid)
-            cls.client.wait_for_resource_deletion(volid)
-        super(VolumeListTestXML, cls).tearDownClass()
-
-
-class VolumeListTestJSON(base.BaseVolumeTestJSON, VolumesListTestBase):
-    @classmethod
-    def setUpClass(cls):
-        cls._interface = 'json'
-        super(VolumeListTestJSON, cls).setUpClass()
-        cls.client = cls.volumes_client
-
-        # Create 3 test volumes
-        cls.volume_list = []
-        cls.volume_id_list = []
-        for i in range(3):
-            v_name = rand_name('volume')
-            metadata = {'Type': 'work'}
-            try:
-                resp, volume = cls.client.create_volume(size=1,
-                                                        display_name=v_name,
-                                                        metadata=metadata)
-                cls.client.wait_for_volume_status(volume['id'], 'available')
-                resp, volume = cls.client.get_volume(volume['id'])
-                cls.volume_list.append(volume)
-                cls.volume_id_list.append(volume['id'])
-            except Exception:
-                if cls.volume_list:
-                    # We could not create all the volumes, though we were able
-                    # to create *some* of the volumes. This is typically
-                    # because the backing file size of the volume group is
-                    # too small. So, here, we clean up whatever we did manage
-                    # to create and raise a SkipTest
-                    for volid in cls.volume_id_list:
-                        cls.client.delete_volume(volid)
-                        cls.client.wait_for_resource_deletion(volid)
-                    msg = ("Failed to create ALL necessary volumes to run "
-                           "test. This typically means that the backing file "
-                           "size of the nova-volumes group is too small to "
-                           "create the 3 volumes needed by this test case")
-                    raise cls.skipException(msg)
-                raise
-
-    @classmethod
-    def tearDownClass(cls):
-        # Delete the created volumes
-        for volid in cls.volume_id_list:
-            resp, _ = cls.client.delete_volume(volid)
-            cls.client.wait_for_resource_deletion(volid)
-        super(VolumeListTestJSON, cls).tearDownClass()
+class VolumeListTestXML(VolumesListTest):
+    _interface = 'xml'
diff --git a/tempest/tests/volume/test_volumes_negative.py b/tempest/tests/volume/test_volumes_negative.py
index dc1fad0..c7d4374 100644
--- a/tempest/tests/volume/test_volumes_negative.py
+++ b/tempest/tests/volume/test_volumes_negative.py
@@ -20,7 +20,13 @@
 from tempest.tests.volume import base
 
 
-class VolumesNegativeTestBase(object):
+class VolumesNegativeTest(base.BaseVolumeTest):
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(VolumesNegativeTest, cls).setUpClass()
+        cls.client = cls.volumes_client
 
     def test_volume_get_nonexistant_volume_id(self):
         # Should not be able to get a nonexistant volume
@@ -94,16 +100,5 @@
         self.assertRaises(exceptions.NotFound, self.client.delete_volume, '')
 
 
-class VolumesNegativeTestXML(base.BaseVolumeTestXML, VolumesNegativeTestBase):
-    @classmethod
-    def setUpClass(cls):
-        super(VolumesNegativeTestXML, cls).setUpClass()
-        cls.client = cls.volumes_client
-
-
-class VolumesNegativeTestJSON(base.BaseVolumeTestJSON,
-                              VolumesNegativeTestBase):
-    @classmethod
-    def setUpClass(cls):
-        super(VolumesNegativeTestJSON, cls).setUpClass()
-        cls.client = cls.volumes_client
+class VolumesNegativeTestXML(VolumesNegativeTest):
+    _interface = 'xml'
diff --git a/tempest/tests/volume/test_volumes_snapshots.py b/tempest/tests/volume/test_volumes_snapshots.py
index 1e87525..e7fa97d 100644
--- a/tempest/tests/volume/test_volumes_snapshots.py
+++ b/tempest/tests/volume/test_volumes_snapshots.py
@@ -12,13 +12,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from nose.plugins.attrib import attr
-
-from tempest.common.utils.data_utils import rand_name
 from tempest.tests.volume import base
 
 
-class VolumesSnapshotTestBase(object):
+class VolumesSnapshotTest(base.BaseVolumeTest):
+    _interface = "json"
 
     def test_volume_from_snapshot(self):
         volume_origin = self.create_volume(size=1)
@@ -27,29 +25,15 @@
                                          snapshot_id=
                                          snapshot['id'])
         self.snapshots_client.delete_snapshot(snapshot['id'])
-        self.client.delete_volume(volume_snap['id'])
+        self.volumes_client.delete_volume(volume_snap['id'])
         self.snapshots_client.wait_for_resource_deletion(snapshot['id'])
         self.snapshots.remove(snapshot)
-        self.client.delete_volume(volume_origin['id'])
-        self.client.wait_for_resource_deletion(volume_snap['id'])
+        self.volumes_client.delete_volume(volume_origin['id'])
+        self.volumes_client.wait_for_resource_deletion(volume_snap['id'])
         self.volumes.remove(volume_snap)
-        self.client.wait_for_resource_deletion(volume_origin['id'])
+        self.volumes_client.wait_for_resource_deletion(volume_origin['id'])
         self.volumes.remove(volume_origin)
 
 
-class VolumesSnapshotTestXML(base.BaseVolumeTestXML,
-                             VolumesSnapshotTestBase):
-    @classmethod
-    def setUpClass(cls):
-        cls._interface = "xml"
-        super(VolumesSnapshotTestXML, cls).setUpClass()
-        cls.client = cls.volumes_client
-
-
-class VolumesSnapshotTestJSON(base.BaseVolumeTestJSON,
-                              VolumesSnapshotTestBase):
-    @classmethod
-    def setUpClass(cls):
-        cls._interface = "json"
-        super(VolumesSnapshotTestJSON, cls).setUpClass()
-        cls.client = cls.volumes_client
+class VolumesSnapshotTestXML(VolumesSnapshotTest):
+    _interface = "xml"
diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py
index 8b676bf..8f91251 100644
--- a/tools/install_venv_common.py
+++ b/tools/install_venv_common.py
@@ -58,7 +58,7 @@
                               check_exit_code=True):
         """Runs a command in an out-of-process shell.
 
-        Returns the output of that command. Working directory is ROOT.
+        Returns the output of that command. Working directory is self.root.
         """
         if redirect_output:
             stdout = subprocess.PIPE
@@ -101,7 +101,7 @@
             else:
                 self.run_command(['virtualenv', '-q', self.venv])
             print 'done.'
-            print 'Installing pip in virtualenv...',
+            print 'Installing pip in venv...',
             if not self.run_command(['tools/with_venv.sh', 'easy_install',
                                     'pip>1.0']).strip():
                 self.die("Failed to install pip.")