Merge "Put the logic from devstack gate into tox."
diff --git a/.gitignore b/.gitignore
index f5f51ab..0f4880f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@
 .venv
 dist
 build
+.testrepository
diff --git a/README.rst b/README.rst
index 04a4b3e..1f44832 100644
--- a/README.rst
+++ b/README.rst
@@ -33,11 +33,6 @@
     devstack uploaded and set the image_ref value in the [environment]
     section in the tempest.conf to that image UUID.
 
-    In addition, the ``<devstack-repo>/tools/configure_tempest.sh`` script can
-    also be used to generate a tempest.conf based on your devstack's rc files.
-    TEMPEST_DIR variable points to location /opt/stack/temptest. Update this
-    variable if the location is different..
-
 Tempest is not tied to any single test runner, but Nose been the most commonly
 used tool. After setting up your configuration file, you can execute
 the set of Tempest tests by using ``nosetests`` ::
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index ac18490..b64b047 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -164,6 +164,9 @@
 # The version of the OpenStack Images API to use
 api_version = 1
 
+# HTTP image to use for glance http image testing
+http_image = http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-uec.tar.gz
+
 [network]
 # This section contains configuration options used when executing tests
 # against the OpenStack Network API.
@@ -210,6 +213,12 @@
 # Number of seconds to time out on waiting for a volume
 # to be available or reach an expected status
 build_timeout = 300
+# Runs Cinder multi-backend tests (requires 2 backend declared in cinder.conf)
+# They must have different volume_backend_name (backend1_name and backend2_name
+# have to be different)
+multi_backend_enabled = false
+backend1_name = LVM_iSCSI
+backend2_name = LVM_iSCSI_1
 
 [object-storage]
 # This section contains configuration options used when executing tests
diff --git a/tempest/clients.py b/tempest/clients.py
index 732a982..7b1e5cc 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -64,7 +64,15 @@
 from tempest.services.identity.json.identity_client import TokenClientJSON
 from tempest.services.identity.v3.json.endpoints_client import \
     EndPointClientJSON
+from tempest.services.identity.v3.json.identity_client import \
+    IdentityV3ClientJSON
+from tempest.services.identity.v3.json.service_client import \
+    ServiceClientJSON
 from tempest.services.identity.v3.xml.endpoints_client import EndPointClientXML
+from tempest.services.identity.v3.xml.identity_client import \
+    IdentityV3ClientXML
+from tempest.services.identity.v3.xml.service_client import \
+    ServiceClientXML
 from tempest.services.identity.xml.identity_client import IdentityClientXML
 from tempest.services.identity.xml.identity_client import TokenClientXML
 from tempest.services.image.v1.json.image_client import ImageClientJSON
@@ -153,6 +161,11 @@
     "xml": IdentityClientXML,
 }
 
+IDENTITY_V3_CLIENT = {
+    "json": IdentityV3ClientJSON,
+    "xml": IdentityV3ClientXML,
+}
+
 TOKEN_CLIENT = {
     "json": TokenClientJSON,
     "xml": TokenClientXML,
@@ -183,6 +196,11 @@
     "xml": AvailabilityZoneClientXML,
 }
 
+SERVICE_CLIENT = {
+    "json": ServiceClientJSON,
+    "xml": ServiceClientXML,
+}
+
 
 class Manager(object):
 
@@ -241,6 +259,8 @@
             self.volume_types_client = \
                 VOLUME_TYPES_CLIENTS[interface](*client_args)
             self.identity_client = IDENTITY_CLIENT[interface](*client_args)
+            self.identity_v3_client = \
+                IDENTITY_V3_CLIENT[interface](*client_args)
             self.token_client = TOKEN_CLIENT[interface](self.config)
             self.security_groups_client = \
                 SECURITY_GROUPS_CLIENT[interface](*client_args)
@@ -249,6 +269,7 @@
             self.fixed_ips_client = FIXED_IPS_CLIENT[interface](*client_args)
             self.availability_zone_client = \
                 AVAILABILITY_ZONE_CLIENT[interface](*client_args)
+            self.service_client = SERVICE_CLIENT[interface](*client_args)
         except KeyError:
             msg = "Unsupported interface type `%s'" % interface
             raise exceptions.InvalidConfiguration(msg)
diff --git a/tempest/config.py b/tempest/config.py
index 556e2a7..a90767e 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -243,7 +243,11 @@
                help="Version of the API"),
     cfg.StrOpt('catalog_type',
                default='image',
-               help='Catalog type of the Image service.')
+               help='Catalog type of the Image service.'),
+    cfg.StrOpt('http_image',
+               default='http://download.cirros-cloud.net/0.3.1/'
+               'cirros-0.3.1-x86_64-uec.tar.gz',
+               help='http accessable image')
 ]
 
 
@@ -303,6 +307,15 @@
     cfg.StrOpt('catalog_type',
                default='Volume',
                help="Catalog type of the Volume Service"),
+    cfg.BoolOpt('multi_backend_enabled',
+                default=False,
+                help="Runs Cinder multi-backend test (requires 2 backend)"),
+    cfg.StrOpt('backend1_name',
+               default='LVM_iSCSI',
+               help="Name of the backend1 (must be declared in cinder.conf)"),
+    cfg.StrOpt('backend2_name',
+               default='LVM_iSCSI_1',
+               help="Name of the backend2 (must be declared in cinder.conf)"),
 ]
 
 
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index c806949..a216b55 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -138,6 +138,12 @@
         body = json.loads(body)
         return resp, body['user']
 
+    def get_user(self, user_id):
+        """GET a user."""
+        resp, body = self.get("users/%s" % user_id)
+        body = json.loads(body)
+        return resp, body['user']
+
     def delete_user(self, user_id):
         """Delete a user."""
         resp, body = self.delete("users/%s" % user_id)
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
new file mode 100644
index 0000000..014df1e
--- /dev/null
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -0,0 +1,162 @@
+# 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 json
+from urlparse import urlparse
+
+from tempest.common.rest_client import RestClient
+
+
+class IdentityV3ClientJSON(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(IdentityV3ClientJSON, self).__init__(config, username, password,
+                                                   auth_url, tenant_name)
+        self.service = self.config.identity.catalog_type
+        self.endpoint_url = 'adminURL'
+
+    def request(self, method, url, headers=None, body=None, wait=None):
+        """Overriding the existing HTTP request in super class rest_client."""
+        self._set_auth()
+        self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+                                              "/v3")
+        return super(IdentityV3ClientJSON, self).request(method, url,
+                                                         headers=headers,
+                                                         body=body)
+
+    def create_user(self, user_name, **kwargs):
+        """Creates a user."""
+        password = kwargs.get('password', None)
+        email = kwargs.get('email', None)
+        en = kwargs.get('enabled', True)
+        project_id = kwargs.get('project_id', None)
+        description = kwargs.get('description', None)
+        domain_id = kwargs.get('domain_id', 'default')
+        post_body = {
+            'project_id': project_id,
+            'description': description,
+            'domain_id': domain_id,
+            'email': email,
+            'enabled': en,
+            'name': user_name,
+            'password': password
+        }
+        post_body = json.dumps({'user': post_body})
+        resp, body = self.post('users', post_body,
+                               self.headers)
+        body = json.loads(body)
+        return resp, body['user']
+
+    def update_user(self, user_id, name, **kwargs):
+        """Updates a user."""
+        email = kwargs.get('email', None)
+        en = kwargs.get('enabled', True)
+        project_id = kwargs.get('project_id', None)
+        description = kwargs.get('description', None)
+        domain_id = kwargs.get('domain_id', 'default')
+        post_body = {
+            'name': name,
+            'email': email,
+            'enabled': en,
+            'project_id': project_id,
+            'id': user_id,
+            'domain_id': domain_id,
+            'description': description
+        }
+        post_body = json.dumps({'user': post_body})
+        resp, body = self.patch('users/%s' % user_id, post_body,
+                                self.headers)
+        body = json.loads(body)
+        return resp, body['user']
+
+    def list_user_projects(self, user_id):
+        """Lists the projects on which a user has roles assigned."""
+        resp, body = self.get('users/%s/projects' % user_id, self.headers)
+        body = json.loads(body)
+        return resp, body['projects']
+
+    def get_users(self):
+        """Get the list of users."""
+        resp, body = self.get("users")
+        body = json.loads(body)
+        return resp, body['users']
+
+    def get_user(self, user_id):
+        """GET a user."""
+        resp, body = self.get("users/%s" % user_id)
+        body = json.loads(body)
+        return resp, body['user']
+
+    def delete_user(self, user_id):
+        """Deletes a User."""
+        resp, body = self.delete("users/%s" % user_id)
+        return resp, body
+
+    def create_project(self, name, **kwargs):
+        """Creates a project."""
+        description = kwargs.get('description', None)
+        en = kwargs.get('enabled', True)
+        domain_id = kwargs.get('domain_id', 'default')
+        post_body = {
+            'description': description,
+            'domain_id': domain_id,
+            'enabled': en,
+            'name': name
+        }
+        post_body = json.dumps({'project': post_body})
+        resp, body = self.post('projects', post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['project']
+
+    def get_project(self, project_id):
+        """GET a Project."""
+        resp, body = self.get("projects/%s" % project_id)
+        body = json.loads(body)
+        return resp, body['project']
+
+    def delete_project(self, project_id):
+        """Delete a project."""
+        resp, body = self.delete('projects/%s' % str(project_id))
+        return resp, body
+
+    def create_role(self, name):
+        """Create a Role."""
+        post_body = {
+            'name': name
+        }
+        post_body = json.dumps({'role': post_body})
+        resp, body = self.post('roles', post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['role']
+
+    def get_role(self, role_id):
+        """GET a Role."""
+        resp, body = self.get('roles/%s' % str(role_id))
+        body = json.loads(body)
+        return resp, body['role']
+
+    def delete_role(self, role_id):
+        """Delete a role."""
+        resp, body = self.delete('roles/%s' % str(role_id))
+        return resp, body
+
+    def assign_user_role(self, project_id, user_id, role_id):
+        """Add roles to a user on a project."""
+        resp, body = self.put('projects/%s/users/%s/roles/%s' %
+                              (project_id, user_id, role_id), None,
+                              self.headers)
+        return resp, body
diff --git a/tempest/services/identity/v3/json/service_client.py b/tempest/services/identity/v3/json/service_client.py
new file mode 100644
index 0000000..dde572e
--- /dev/null
+++ b/tempest/services/identity/v3/json/service_client.py
@@ -0,0 +1,63 @@
+# 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 json
+from urlparse import urlparse
+
+from tempest.common.rest_client import RestClient
+
+
+class ServiceClientJSON(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(ServiceClientJSON, self).__init__(config, username, password,
+                                                auth_url, tenant_name)
+        self.service = self.config.identity.catalog_type
+        self.endpoint_url = 'adminURL'
+
+    def request(self, method, url, headers=None, body=None, wait=None):
+        """Overriding the existing HTTP request in super class rest_client."""
+        self._set_auth()
+        self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+                                              "/v3")
+        return super(ServiceClientJSON, self).request(method, url,
+                                                      headers=headers,
+                                                      body=body)
+
+    def update_service(self, service_id, **kwargs):
+        """Updates a service."""
+        resp, body = self.get_service(service_id)
+        name = kwargs.get('name', body['name'])
+        type = kwargs.get('type', body['type'])
+        desc = kwargs.get('description', body['description'])
+        patch_body = {
+            'description': desc,
+            'type': type,
+            'name': name
+        }
+        patch_body = json.dumps({'service': patch_body})
+        resp, body = self.patch('services/%s' % service_id,
+                                patch_body, self.headers)
+        body = json.loads(body)
+        return resp, body['service']
+
+    def get_service(self, service_id):
+        """Get Service."""
+        url = 'services/%s' % service_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        return resp, body['service']
diff --git a/tempest/services/identity/v3/xml/identity_client.py b/tempest/services/identity/v3/xml/identity_client.py
new file mode 100644
index 0000000..92151dd
--- /dev/null
+++ b/tempest/services/identity/v3/xml/identity_client.py
@@ -0,0 +1,187 @@
+# 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.
+
+from urlparse import urlparse
+
+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
+
+
+XMLNS = "http://docs.openstack.org/identity/api/v3"
+
+
+class IdentityV3ClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(IdentityV3ClientXML, self).__init__(config, username, password,
+                                                  auth_url, tenant_name)
+        self.service = self.config.identity.catalog_type
+        self.endpoint_url = 'adminURL'
+
+    def _parse_projects(self, node):
+        array = []
+        for child in node.getchildren():
+            tag_list = child.tag.split('}', 1)
+            if tag_list[1] == "project":
+                array.append(xml_to_json(child))
+        return array
+
+    def _parse_array(self, node):
+        array = []
+        for child in node.getchildren():
+            array.append(xml_to_json(child))
+        return array
+
+    def _parse_body(self, body):
+        json = xml_to_json(body)
+        return json
+
+    def request(self, method, url, headers=None, body=None, wait=None):
+        """Overriding the existing HTTP request in super class RestClient."""
+        self._set_auth()
+        self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+                                              "/v3")
+        return super(IdentityV3ClientXML, self).request(method, url,
+                                                        headers=headers,
+                                                        body=body)
+
+    def create_user(self, user_name, **kwargs):
+        """Creates a user."""
+        password = kwargs.get('password', None)
+        email = kwargs.get('email', None)
+        en = kwargs.get('enabled', 'true')
+        project_id = kwargs.get('project_id', None)
+        description = kwargs.get('description', None)
+        domain_id = kwargs.get('domain_id', 'default')
+        post_body = Element("user",
+                            xmlns=XMLNS,
+                            name=user_name,
+                            password=password,
+                            description=description,
+                            email=email,
+                            enabled=str(en).lower(),
+                            project_id=project_id,
+                            domain_id=domain_id)
+        resp, body = self.post('users', str(Document(post_body)),
+                               self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def update_user(self, user_id, name, **kwargs):
+        """Updates a user."""
+        email = kwargs.get('email', None)
+        en = kwargs.get('enabled', True)
+        project_id = kwargs.get('project_id', None)
+        domain_id = kwargs.get('domain_id', 'default')
+        description = kwargs.get('description', None)
+        update_user = Element("user",
+                              xmlns=XMLNS,
+                              name=name,
+                              email=email,
+                              project_id=project_id,
+                              domain_id=domain_id,
+                              description=description,
+                              enabled=str(en).lower())
+        resp, body = self.patch('users/%s' % user_id,
+                                str(Document(update_user)),
+                                self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def list_user_projects(self, user_id):
+        """Lists the projects on which a user has roles assigned."""
+        resp, body = self.get('users/%s/projects' % user_id, self.headers)
+        body = self._parse_projects(etree.fromstring(body))
+        return resp, body
+
+    def get_users(self):
+        """Get the list of users."""
+        resp, body = self.get("users", self.headers)
+        body = self._parse_array(etree.fromstring(body))
+        return resp, body
+
+    def get_user(self, user_id):
+        """GET a user."""
+        resp, body = self.get("users/%s" % user_id, self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def delete_user(self, user_id):
+        """Deletes a User."""
+        resp, body = self.delete("users/%s" % user_id, self.headers)
+        return resp, body
+
+    def create_project(self, name, **kwargs):
+        """Creates a project."""
+        description = kwargs.get('description', None)
+        en = kwargs.get('enabled', 'true')
+        domain_id = kwargs.get('domain_id', 'default')
+        post_body = Element("project",
+                            xmlns=XMLNS,
+                            description=description,
+                            domain_id=domain_id,
+                            enabled=str(en).lower(),
+                            name=name)
+        resp, body = self.post('projects',
+                               str(Document(post_body)),
+                               self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def get_project(self, project_id):
+        """GET a Project."""
+        resp, body = self.get("projects/%s" % project_id, self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def delete_project(self, project_id):
+        """Delete a project."""
+        resp, body = self.delete('projects/%s' % str(project_id))
+        return resp, body
+
+    def create_role(self, name):
+        """Create a Role."""
+        post_body = Element("role",
+                            xmlns=XMLNS,
+                            name=name)
+        resp, body = self.post('roles',
+                               str(Document(post_body)),
+                               self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def get_role(self, role_id):
+        """GET a Role."""
+        resp, body = self.get('roles/%s' % str(role_id), self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def delete_role(self, role_id):
+        """Delete a role."""
+        resp, body = self.delete('roles/%s' % str(role_id),
+                                 self.headers)
+        return resp, body
+
+    def assign_user_role(self, project_id, user_id, role_id):
+        """Add roles to a user on a tenant."""
+        resp, body = self.put('projects/%s/users/%s/roles/%s' %
+                              (project_id, user_id, role_id), '', self.headers)
+        return resp, body
diff --git a/tempest/services/identity/v3/xml/service_client.py b/tempest/services/identity/v3/xml/service_client.py
new file mode 100644
index 0000000..306245b
--- /dev/null
+++ b/tempest/services/identity/v3/xml/service_client.py
@@ -0,0 +1,81 @@
+# 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.
+
+from urlparse import urlparse
+
+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
+
+
+XMLNS = "http://docs.openstack.org/identity/api/v3"
+
+
+class ServiceClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(ServiceClientXML, self).__init__(config, username, password,
+                                               auth_url, tenant_name)
+        self.service = self.config.identity.catalog_type
+        self.endpoint_url = 'adminURL'
+
+    def _parse_array(self, node):
+        array = []
+        for child in node.getchildren():
+            array.append(xml_to_json(child))
+        return array
+
+    def _parse_body(self, body):
+        data = xml_to_json(body)
+        return data
+
+    def request(self, method, url, headers=None, body=None, wait=None):
+        """Overriding the existing HTTP request in super class rest_client."""
+        self._set_auth()
+        self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+                                              "/v3")
+        return super(ServiceClientXML, self).request(method, url,
+                                                     headers=headers,
+                                                     body=body)
+
+    def update_service(self, service_id, **kwargs):
+        """Updates a service_id."""
+        resp, body = self.get_service(service_id)
+        name = kwargs.get('name', body['name'])
+        description = kwargs.get('description', body['description'])
+        type = kwargs.get('type', body['type'])
+        update_service = Element("service",
+                                 xmlns=XMLNS,
+                                 id=service_id,
+                                 name=name,
+                                 description=description,
+                                 type=type)
+        resp, body = self.patch('services/%s' % service_id,
+                                str(Document(update_service)),
+                                self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def get_service(self, service_id):
+        """Get Service."""
+        url = 'services/%s' % service_id
+        resp, body = self.get(url, self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
diff --git a/tempest/services/identity/xml/identity_client.py b/tempest/services/identity/xml/identity_client.py
index 6f1b1b3..99a155a 100644
--- a/tempest/services/identity/xml/identity_client.py
+++ b/tempest/services/identity/xml/identity_client.py
@@ -172,6 +172,12 @@
         body = self._parse_body(etree.fromstring(body))
         return resp, body
 
+    def get_user(self, user_id):
+        """GET a user."""
+        resp, body = self.get("users/%s" % user_id, self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
     def delete_user(self, user_id):
         """Delete a user."""
         resp, body = self.delete("users/%s" % user_id, self.headers)
diff --git a/tempest/tests/compute/test_live_block_migration.py b/tempest/tests/compute/test_live_block_migration.py
index e438098..30ff882 100644
--- a/tempest/tests/compute/test_live_block_migration.py
+++ b/tempest/tests/compute/test_live_block_migration.py
@@ -31,11 +31,7 @@
     _host_key = 'OS-EXT-SRV-ATTR:host'
     _interface = 'json'
 
-    live_migration_available = (
-        config.TempestConfig().compute.live_migration_available)
-    use_block_migration_for_live_migration = (
-        config.TempestConfig().compute.use_block_migration_for_live_migration)
-    run_ssh = config.TempestConfig().compute.run_ssh
+    CONF = config.TempestConfig()
 
     @classmethod
     def setUpClass(cls):
@@ -63,7 +59,8 @@
 
     def _migrate_server_to(self, server_id, dest_host):
         _resp, body = self.admin_servers_client.live_migrate_server(
-            server_id, dest_host, self.use_block_migration_for_live_migration)
+            server_id, dest_host,
+            self.config.compute.use_block_migration_for_live_migration)
         return body
 
     def _get_host_other_than(self, host):
@@ -95,8 +92,8 @@
             return server_id
 
     @attr(type='positive')
-    @testtools.skipIf(not live_migration_available,
-                      'Block Live migration not available')
+    @testtools.skipIf(not CONF.compute.live_migration_available,
+                      'Live migration not available')
     def test_live_block_migration(self):
         # Live block migrate an instance to another host
         if len(self._get_compute_hostnames()) < 2:
@@ -109,11 +106,10 @@
         self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
         self.assertEquals(target_host, self._get_host_for_server(server_id))
 
-    @testtools.skipIf(not live_migration_available,
-                      'Block Live migration not available')
+    @testtools.skipIf(not CONF.compute.live_migration_available,
+                      'Live migration not available')
     def test_invalid_host_for_migration(self):
         # Migrating to an invalid host should not change the status
-
         server_id = self._get_an_active_server()
         target_host = self._get_non_existing_host_name()
 
diff --git a/tempest/tests/identity/admin/v3/test_services.py b/tempest/tests/identity/admin/v3/test_services.py
new file mode 100644
index 0000000..fef3bca
--- /dev/null
+++ b/tempest/tests/identity/admin/v3/test_services.py
@@ -0,0 +1,56 @@
+#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.
+
+
+from tempest.common.utils.data_utils import rand_name
+from tempest.tests.identity import base
+
+
+class ServicesTestJSON(base.BaseIdentityAdminTest):
+    _interface = 'json'
+
+    def test_update_service(self):
+        # Update description attribute of service
+        name = rand_name('service-')
+        type = rand_name('type--')
+        description = rand_name('description-')
+        resp, body = self.client.create_service(
+            name, type, description=description)
+        self.assertEqual('200', resp['status'])
+        #Deleting the service created in this method
+        self.addCleanup(self.client.delete_service, body['id'])
+
+        s_id = body['id']
+        resp1_desc = body['description']
+
+        s_desc2 = rand_name('desc2-')
+        resp, body = self.service_client.update_service(
+            s_id, description=s_desc2)
+        resp2_desc = body['description']
+        self.assertEqual('200', resp['status'])
+        self.assertNotEqual(resp1_desc, resp2_desc)
+
+        #Get service
+        resp, body = self.client.get_service(s_id)
+        resp3_desc = body['description']
+
+        self.assertNotEqual(resp1_desc, resp3_desc)
+        self.assertEqual(resp2_desc, resp3_desc)
+
+
+class ServicesTestXML(ServicesTestJSON):
+    _interface = 'xml'
diff --git a/tempest/tests/identity/admin/v3/test_users.py b/tempest/tests/identity/admin/v3/test_users.py
new file mode 100644
index 0000000..7118241
--- /dev/null
+++ b/tempest/tests/identity/admin/v3/test_users.py
@@ -0,0 +1,121 @@
+# 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.
+
+from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
+from tempest.tests.identity import base
+
+
+class UsersV3TestJSON(base.BaseIdentityAdminTest):
+    _interface = 'json'
+
+    @attr('smoke')
+    def test_user_update(self):
+        # Test case to check if updating of user attributes is successful.
+        #Creating first user
+        u_name = rand_name('user-')
+        u_desc = u_name + 'description'
+        u_email = u_name + '@testmail.tm'
+        u_password = rand_name('pass-')
+        resp, user = self.v3_client.create_user(
+            u_name, description=u_desc, password=u_password,
+            email=u_email, enabled=False)
+        # Delete the User at the end of this method
+        self.addCleanup(self.v3_client.delete_user, user['id'])
+        #Creating second project for updation
+        resp, project = self.v3_client.create_project(
+            rand_name('project-'), description=rand_name('project-desc-'))
+        # Delete the Project at the end of this method
+        self.addCleanup(self.v3_client.delete_project, project['id'])
+        #Updating user details with new values
+        u_name2 = rand_name('user2-')
+        u_email2 = u_name2 + '@testmail.tm'
+        u_description2 = u_name2 + ' description'
+        resp, update_user = self.v3_client.update_user(
+            user['id'], name=u_name2, description=u_description2,
+            project_id=project['id'],
+            email=u_email2, enabled=False)
+        #Assert response body of update user.
+        self.assertEqual(200, resp.status)
+        self.assertEqual(u_name2, update_user['name'])
+        self.assertEqual(u_description2, update_user['description'])
+        self.assertEqual(project['id'],
+                         update_user['project_id'])
+        self.assertEqual(u_email2, update_user['email'])
+        self.assertEqual('false', str(update_user['enabled']).lower())
+        #GET by id after updation
+        resp, new_user_get = self.v3_client.get_user(user['id'])
+        #Assert response body of GET after updation
+        self.assertEqual(u_name2, new_user_get['name'])
+        self.assertEqual(u_description2, new_user_get['description'])
+        self.assertEqual(project['id'],
+                         new_user_get['project_id'])
+        self.assertEqual(u_email2, new_user_get['email'])
+        self.assertEqual('false', str(new_user_get['enabled']).lower())
+
+    @attr('smoke')
+    def test_list_user_projects(self):
+        #List the projects that a user has access upon
+        assigned_project_ids = list()
+        fetched_project_ids = list()
+        _, u_project = self.v3_client.create_project(
+            rand_name('project-'), description=rand_name('project-desc-'))
+        #Create a user.
+        u_name = rand_name('user-')
+        u_desc = u_name + 'description'
+        u_email = u_name + '@testmail.tm'
+        u_password = rand_name('pass-')
+        _, user_body = self.v3_client.create_user(
+            u_name, description=u_desc, password=u_password,
+            email=u_email, enabled=False, project_id=u_project['id'])
+        # Delete the User at the end of this method
+        self.addCleanup(self.v3_client.delete_user, user_body['id'])
+        # Creating Role
+        _, role_body = self.v3_client.create_role(rand_name('role-'))
+        # Delete the Role at the end of this method
+        self.addCleanup(self.v3_client.delete_role, role_body['id'])
+
+        _, user = self.v3_client.get_user(user_body['id'])
+        _, role = self.v3_client.get_role(role_body['id'])
+        for i in range(2):
+            # Creating project so as to assign role
+            _, project_body = self.v3_client.create_project(
+                rand_name('project-'), description=rand_name('project-desc-'))
+            _, project = self.v3_client.get_project(project_body['id'])
+            # Delete the Project at the end of this method
+            self.addCleanup(self.v3_client.delete_project, project_body['id'])
+            #Assigning roles to user on project
+            self.v3_client.assign_user_role(project['id'],
+                                            user['id'],
+                                            role['id'])
+            assigned_project_ids.append(project['id'])
+        resp, body = self.v3_client.list_user_projects(user['id'])
+        self.assertEqual(200, resp.status)
+        for i in body:
+            fetched_project_ids.append(i['id'])
+        #verifying the project ids in list
+        missing_projects =\
+            [p for p in assigned_project_ids
+             if p not in fetched_project_ids]
+        self.assertEqual(0, len(missing_projects),
+                         "Failed to find project %s in fetched list" %
+                         ', '.join(m_project for m_project
+                         in missing_projects))
+
+
+class UsersV3TestXML(UsersV3TestJSON):
+    _interface = 'xml'
diff --git a/tempest/tests/identity/base.py b/tempest/tests/identity/base.py
index 64b8993..6980425 100644
--- a/tempest/tests/identity/base.py
+++ b/tempest/tests/identity/base.py
@@ -29,6 +29,8 @@
         cls.client = os.identity_client
         cls.token_client = os.token_client
         cls.endpoints_client = os.endpoints_client
+        cls.v3_client = os.identity_v3_client
+        cls.service_client = os.service_client
 
         if not cls.client.has_admin_extensions():
             raise cls.skipException("Admin extensions disabled")
diff --git a/tempest/tests/image/v1/test_images.py b/tempest/tests/image/v1/test_images.py
index c01aeaf..19c0aa0 100644
--- a/tempest/tests/image/v1/test_images.py
+++ b/tempest/tests/image/v1/test_images.py
@@ -81,32 +81,10 @@
         self.assertEqual(properties['key2'], 'value2')
 
     def test_register_http_image(self):
-        container_client = self.os.container_client
-        object_client = self.os.object_client
-        container_name = "image_container"
-        object_name = "test_image.img"
-        container_client.create_container(container_name)
-        self.addCleanup(container_client.delete_container, container_name)
-        cont_headers = {'X-Container-Read': '.r:*'}
-        resp, _ = container_client.update_container_metadata(
-            container_name,
-            metadata=cont_headers,
-            metadata_prefix='')
-        self.assertEqual(resp['status'], '204')
-
-        data = "TESTIMAGE"
-        resp, _ = object_client.create_object(container_name,
-                                              object_name, data)
-        self.addCleanup(object_client.delete_object, container_name,
-                        object_name)
-        self.assertEqual(resp['status'], '201')
-        object_url = '/'.join((object_client.base_url,
-                               container_name,
-                               object_name))
         resp, body = self.create_image(name='New Http Image',
                                        container_format='bare',
                                        disk_format='raw', is_public=True,
-                                       copy_from=object_url)
+                                       copy_from=self.config.images.http_image)
         self.assertTrue('id' in body)
         image_id = body.get('id')
         self.created_images.append(image_id)
@@ -115,7 +93,6 @@
         self.client.wait_for_image_status(image_id, 'active')
         resp, body = self.client.get_image(image_id)
         self.assertEqual(resp['status'], '200')
-        self.assertEqual(body, data)
 
     @attr(type='image')
     def test_register_image_with_min_ram(self):
diff --git a/tempest/tests/volume/admin/test_multi_backend.py b/tempest/tests/volume/admin/test_multi_backend.py
new file mode 100644
index 0000000..04007c9
--- /dev/null
+++ b/tempest/tests/volume/admin/test_multi_backend.py
@@ -0,0 +1,156 @@
+# 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 testtools
+
+from tempest.common.utils.data_utils import rand_name
+from tempest import config
+from tempest.services.volume.json.admin import volume_types_client
+from tempest.services.volume.json import volumes_client
+from tempest.tests.volume import base
+
+LOG = logging.getLogger(__name__)
+
+
+class VolumeMultiBackendTest(base.BaseVolumeAdminTest):
+    _interface = "json"
+
+    multi_backend_enabled = config.TempestConfig().volume.multi_backend_enabled
+    backend1_name = config.TempestConfig().volume.backend1_name
+    backend2_name = config.TempestConfig().volume.backend2_name
+    backend_names_equal = False
+    if (backend1_name == backend2_name):
+        backend_names_equal = True
+
+    @classmethod
+    @testtools.skipIf(not multi_backend_enabled,
+                      "Cinder multi-backend feature is not available")
+    def setUpClass(cls):
+        super(VolumeMultiBackendTest, 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 = volumes_client.VolumesClientJSON(cls.config,
+                                                      adm_user,
+                                                      adm_pass,
+                                                      auth_url,
+                                                      adm_tenant)
+        cls.client2 = volume_types_client.VolumeTypesClientJSON(cls.config,
+                                                                adm_user,
+                                                                adm_pass,
+                                                                auth_url,
+                                                                adm_tenant)
+
+        ## variables initialization
+        type_name1 = rand_name('type-')
+        type_name2 = rand_name('type-')
+        cls.volume_type_list = []
+
+        vol_name1 = rand_name('Volume-')
+        vol_name2 = rand_name('Volume-')
+        cls.volume_id_list = []
+
+        try:
+            ## Volume types creation
+            extra_specs1 = {"volume_backend_name": cls.backend1_name}
+            resp, cls.body1 = cls.client2.create_volume_type(
+                type_name1, extra_specs=extra_specs1)
+            cls.volume_type_list.append(cls.body1)
+
+            extra_specs2 = {"volume_backend_name": cls.backend2_name}
+            resp, cls.body2 = cls.client2.create_volume_type(
+                type_name2, extra_specs=extra_specs2)
+            cls.volume_type_list.append(cls.body2)
+
+            ## Volumes creation
+            resp, cls.volume1 = cls.client.create_volume(
+                size=1, display_name=vol_name1, volume_type=type_name1)
+            cls.client.wait_for_volume_status(cls.volume1['id'], 'available')
+            cls.volume_id_list.append(cls.volume1['id'])
+
+            resp, cls.volume2 = cls.client.create_volume(
+                size=1, display_name=vol_name2, volume_type=type_name2)
+            cls.client.wait_for_volume_status(cls.volume2['id'], 'available')
+            cls.volume_id_list.append(cls.volume2['id'])
+        except Exception:
+            LOG.exception("setup failed")
+            cls.tearDownClass()
+            raise
+
+    @classmethod
+    def tearDownClass(cls):
+        super(VolumeMultiBackendTest, cls).tearDownClass()
+
+        ## volumes deletion
+        for volume_id in cls.volume_id_list:
+            cls.client.delete_volume(volume_id)
+            cls.client.wait_for_resource_deletion(volume_id)
+
+        ## volume types deletion
+        for volume_type in cls.volume_type_list:
+            cls.client2.delete_volume_type(volume_type)
+
+    def test_multi_backend_enabled(self):
+        # this test checks that multi backend is enabled for at least the
+        # computes where the volumes created in setUp were made
+        # if multi-backend is enabled: os-vol-attr:host should be like:
+        # host@backend_name
+        # this test fails if:
+        # - multi backend is not enabled
+        resp, fetched_volume = self.client.get_volume(self.volume1['id'])
+        self.assertEqual(200, resp.status)
+
+        volume_host1 = fetched_volume['os-vol-host-attr:host']
+        msg = ("Multi-backend is not available for at least host "
+               "%(volume_host1)s") % locals()
+        self.assertTrue(len(volume_host1.split("@")) > 1, msg)
+
+        resp, fetched_volume = self.client.get_volume(self.volume2['id'])
+        self.assertEqual(200, resp.status)
+
+        volume_host2 = fetched_volume['os-vol-host-attr:host']
+        msg = ("Multi-backend is not available for at least host "
+               "%(volume_host2)s") % locals()
+        self.assertTrue(len(volume_host2.split("@")) > 1, msg)
+
+    def test_backend_name_distinction(self):
+        # this test checks that the two volumes created at setUp doesn't
+        # belong to the same backend (if they are in the same backend, that
+        # means, volume_backend_name distinction is not working properly)
+        # this test fails if:
+        # - tempest.conf is not well configured
+        # - the two volumes belongs to the same backend
+
+        # checks tempest.conf
+        msg = ("tempest.conf is not well configured, "
+               "backend1_name and backend2_name are equal")
+        self.assertEqual(self.backend_names_equal, False, msg)
+
+        # checks the two volumes belongs to different backend
+        resp, fetched_volume = self.client.get_volume(self.volume1['id'])
+        volume_host1 = fetched_volume['os-vol-host-attr:host']
+
+        resp, fetched_volume = self.client.get_volume(self.volume2['id'])
+        volume_host2 = fetched_volume['os-vol-host-attr:host']
+
+        msg = ("volume2 was created in the same backend as volume1: "
+               "%(volume_host2)s.") % locals()
+        self.assertNotEqual(volume_host2, volume_host1, msg)
diff --git a/tempest/tests/volume/test_volumes_snapshots.py b/tempest/tests/volume/test_volumes_snapshots.py
index e7fa97d..ba8ba6c 100644
--- a/tempest/tests/volume/test_volumes_snapshots.py
+++ b/tempest/tests/volume/test_volumes_snapshots.py
@@ -12,27 +12,59 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import logging
+
+from tempest.test import attr
 from tempest.tests.volume import base
 
+LOG = logging.getLogger(__name__)
+
 
 class VolumesSnapshotTest(base.BaseVolumeTest):
     _interface = "json"
 
-    def test_volume_from_snapshot(self):
-        volume_origin = self.create_volume(size=1)
-        snapshot = self.create_snapshot(volume_origin['id'])
-        volume_snap = self.create_volume(size=1,
-                                         snapshot_id=
-                                         snapshot['id'])
+    @classmethod
+    def setUpClass(cls):
+        super(VolumesSnapshotTest, cls).setUpClass()
+        try:
+            cls.volume_origin = cls.create_volume()
+        except Exception:
+            LOG.exception("setup failed")
+            cls.tearDownClass()
+            raise
+
+    @classmethod
+    def tearDownClass(cls):
+        super(VolumesSnapshotTest, cls).tearDownClass()
+
+    @attr(type='smoke')
+    def test_snapshot_create_get_delete(self):
+        # Create a snapshot, get some of the details and then deletes it
+        resp, snapshot = self.snapshots_client.create_snapshot(
+            self.volume_origin['id'])
+        self.assertEqual(200, resp.status)
+        self.snapshots_client.wait_for_snapshot_status(snapshot['id'],
+                                                       'available')
+        errmsg = "Referred volume origin ID mismatch"
+        self.assertEqual(self.volume_origin['id'],
+                         snapshot['volume_id'],
+                         errmsg)
         self.snapshots_client.delete_snapshot(snapshot['id'])
-        self.volumes_client.delete_volume(volume_snap['id'])
         self.snapshots_client.wait_for_resource_deletion(snapshot['id'])
-        self.snapshots.remove(snapshot)
-        self.volumes_client.delete_volume(volume_origin['id'])
-        self.volumes_client.wait_for_resource_deletion(volume_snap['id'])
-        self.volumes.remove(volume_snap)
-        self.volumes_client.wait_for_resource_deletion(volume_origin['id'])
-        self.volumes.remove(volume_origin)
+
+    def test_volume_from_snapshot(self):
+        # Create a temporary snap using wrapper method from base, then
+        # create a snap based volume, check resp code and deletes it
+        snapshot = self.create_snapshot(self.volume_origin['id'])
+        # NOTE: size is required also when passing snapshot_id
+        resp, volume = self.volumes_client.create_volume(
+            size=1,
+            snapshot_id=snapshot['id'])
+        self.assertEqual(200, resp.status)
+        self.volumes_client.wait_for_volume_status(volume['id'], 'available')
+        self.volumes_client.delete_volume(volume['id'])
+        self.volumes_client.wait_for_resource_deletion(volume['id'])
+        self.clear_snapshots()
 
 
 class VolumesSnapshotTestXML(VolumesSnapshotTest):