Merge "port test_images and test_server_actions into v3 part2"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 87bf758..4d02dc5 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -138,6 +138,11 @@
 # this value as "compute"
 catalog_type = compute
 
+# The type of endpoint for a Compute v3 API service. Unless you have a
+# custom Keystone service catalog implementation, you probably want to leave
+# this value as "computev3"
+catalog_v3_type = computev3
+
 # The name of a region for compute. If empty or commented-out, the value of
 # identity.region is used instead. If no such region is found in the service
 # catalog, the first found one is used.
@@ -147,6 +152,9 @@
 volume_device_name = vdb
 
 [compute-feature-enabled]
+# Do we run the Nova V3 API tests?
+api_v3 = true
+
 # Does the Compute API support creation of images?
 create_image = true
 
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 5679a45..d185a8b 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -72,19 +72,6 @@
                 pass
 
     @classmethod
-    def rebuild_server(cls, **kwargs):
-        # Destroy an existing server and creates a new one
-        try:
-            cls.servers_client.delete_server(cls.server_id)
-            cls.servers_client.wait_for_server_termination(cls.server_id)
-        except Exception as exc:
-            LOG.exception(exc)
-            pass
-        resp, server = cls.create_server(wait_until='ACTIVE', **kwargs)
-        cls.server_id = server['id']
-        cls.password = server['adminPass']
-
-    @classmethod
     def clear_images(cls):
         for image_id in cls.images:
             try:
@@ -132,25 +119,6 @@
 
         return resp, body
 
-    @classmethod
-    def create_image_from_server(cls, server_id, **kwargs):
-        """Wrapper utility that returns an image created from the server."""
-        name = rand_name(cls.__name__ + "-image")
-        if 'name' in kwargs:
-            name = kwargs.pop('name')
-
-        resp, image = cls.images_client.create_image(
-            server_id, name)
-        image_id = parse_image_id(resp['location'])
-        cls.images.append(image_id)
-
-        if 'wait_until' in kwargs:
-            cls.images_client.wait_for_image_status(image_id,
-                                                    kwargs['wait_until'])
-            resp, image = cls.images_client.get_image(image_id)
-
-        return resp, image
-
     def wait_for(self, condition):
         """Repeatedly calls condition() until a timeout."""
         start_time = int(time.time())
@@ -191,6 +159,38 @@
         cls.hypervisor_client = cls.os.hypervisor_client
         cls.servers_client_v3_auth = cls.os.servers_client_v3_auth
 
+    @classmethod
+    def create_image_from_server(cls, server_id, **kwargs):
+        """Wrapper utility that returns an image created from the server."""
+        name = rand_name(cls.__name__ + "-image")
+        if 'name' in kwargs:
+            name = kwargs.pop('name')
+
+        resp, image = cls.images_client.create_image(
+            server_id, name)
+        image_id = parse_image_id(resp['location'])
+        cls.images.append(image_id)
+
+        if 'wait_until' in kwargs:
+            cls.images_client.wait_for_image_status(image_id,
+                                                    kwargs['wait_until'])
+            resp, image = cls.images_client.get_image(image_id)
+
+        return resp, image
+
+    @classmethod
+    def rebuild_server(cls, **kwargs):
+        # Destroy an existing server and creates a new one
+        try:
+            cls.servers_client.delete_server(cls.server_id)
+            cls.servers_client.wait_for_server_termination(cls.server_id)
+        except Exception as exc:
+            LOG.exception(exc)
+            pass
+        resp, server = cls.create_server(wait_until='ACTIVE', **kwargs)
+        cls.server_id = server['id']
+        cls.password = server['adminPass']
+
 
 class BaseV2ComputeAdminTest(BaseV2ComputeTest):
     """Base test case class for Compute Admin V2 API tests."""
@@ -215,3 +215,76 @@
                                          interface=cls._interface)
         else:
             cls.os_adm = clients.ComputeAdminManager(interface=cls._interface)
+
+
+class BaseV3ComputeTest(BaseComputeTest):
+
+    @classmethod
+    def setUpClass(cls):
+        super(BaseV3ComputeTest, cls).setUpClass()
+        if not cls.config.compute_feature_enabled.api_v3:
+            skip_msg = ("%s skipped as nova v3 api is not available" %
+                        cls.__name__)
+            raise cls.skipException(skip_msg)
+
+        cls.servers_client = cls.os.servers_v3_client
+        cls.images_client = cls.os.image_client
+
+    @classmethod
+    def create_image_from_server(cls, server_id, **kwargs):
+        """Wrapper utility that returns an image created from the server."""
+        name = rand_name(cls.__name__ + "-image")
+        if 'name' in kwargs:
+            name = kwargs.pop('name')
+
+        resp, image = cls.servers_client.create_image(
+            server_id, name)
+        image_id = parse_image_id(resp['location'])
+        cls.images.append(image_id)
+
+        if 'wait_until' in kwargs:
+            cls.images_client.wait_for_image_status(image_id,
+                                                    kwargs['wait_until'])
+            resp, image = cls.images_client.get_image_meta(image_id)
+
+        return resp, image
+
+    @classmethod
+    def rebuild_server(cls, **kwargs):
+        # Destroy an existing server and creates a new one
+        try:
+            cls.servers_client.delete_server(cls.server_id)
+            cls.servers_client.wait_for_server_termination(cls.server_id)
+        except Exception as exc:
+            LOG.exception(exc)
+            pass
+        resp, server = cls.create_server(wait_until='ACTIVE', **kwargs)
+        cls.server_id = server['id']
+        cls.password = server['admin_pass']
+
+
+class BaseV3ComputeAdminTest(BaseV3ComputeTest):
+    """Base test case class for all Compute Admin API V3 tests."""
+
+    @classmethod
+    def setUpClass(cls):
+        super(BaseV3ComputeAdminTest, 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 (admin_username and admin_password and admin_tenant):
+            msg = ("Missing Compute Admin API credentials "
+                   "in configuration.")
+            raise cls.skipException(msg)
+        if cls.config.compute.allow_tenant_isolation:
+            creds = cls.isolated_creds.get_admin_creds()
+            admin_username, admin_tenant_name, admin_password = creds
+            os_adm = clients.Manager(username=admin_username,
+                                     password=admin_password,
+                                     tenant_name=admin_tenant_name,
+                                     interface=cls._interface)
+        else:
+            os_adm = clients.ComputeAdminManager(interface=cls._interface)
+
+        cls.os_adm = os_adm
+        cls.severs_admin_client = cls.os_adm.servers_v3_client
diff --git a/tempest/api/compute/v3/__init__.py b/tempest/api/compute/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/compute/v3/__init__.py
diff --git a/tempest/api/compute/v3/images/__init__.py b/tempest/api/compute/v3/images/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/compute/v3/images/__init__.py
diff --git a/tempest/api/compute/v3/images/test_images.py b/tempest/api/compute/v3/images/test_images.py
index 383ea1d..f3dfeec 100644
--- a/tempest/api/compute/v3/images/test_images.py
+++ b/tempest/api/compute/v3/images/test_images.py
@@ -23,20 +23,18 @@
 from tempest.test import attr
 
 
-class ImagesTestJSON(base.BaseV2ComputeTest):
+class ImagesV3TestJSON(base.BaseV3ComputeTest):
     _interface = 'json'
 
     @classmethod
     def setUpClass(cls):
-        super(ImagesTestJSON, cls).setUpClass()
+        super(ImagesV3TestJSON, cls).setUpClass()
         if not cls.config.service_available.glance:
             skip_msg = ("%s skipped as glance is not available" % cls.__name__)
             raise cls.skipException(skip_msg)
         cls.client = cls.images_client
         cls.servers_client = cls.servers_client
 
-        cls.image_ids = []
-
         if compute.MULTI_USER:
             if cls.config.compute.allow_tenant_isolation:
                 creds = cls.isolated_creds.get_alt_creds()
@@ -49,18 +47,11 @@
                 cls.alt_manager = clients.AltManager()
             cls.alt_client = cls.alt_manager.images_client
 
-    def tearDown(self):
-        """Terminate test instances created after a test is executed."""
-        for image_id in self.image_ids:
-            self.client.delete_image(image_id)
-            self.image_ids.remove(image_id)
-        super(ImagesTestJSON, self).tearDown()
-
     def __create_image__(self, server_id, name, meta=None):
-        resp, body = self.client.create_image(server_id, name, meta)
+        resp, body = self.servers_client.create_image(server_id, name, meta)
         image_id = parse_image_id(resp['location'])
+        self.addCleanup(self.client.delete_image, image_id)
         self.client.wait_for_image_status(image_id, 'ACTIVE')
-        self.image_ids.append(image_id)
         return resp, body
 
     @attr(type=['negative', 'gate'])
@@ -99,27 +90,28 @@
         snapshot_name = rand_name('test-snap-')
         resp, image = self.create_image_from_server(server['id'],
                                                     name=snapshot_name,
-                                                    wait_until='ACTIVE')
+                                                    wait_until='active')
         self.addCleanup(self.client.delete_image, image['id'])
         self.assertEqual(snapshot_name, image['name'])
 
     @attr(type='gate')
-    def test_delete_saving_image(self):
+    def test_delete_queued_image(self):
         snapshot_name = rand_name('test-snap-')
         resp, server = self.create_server(wait_until='ACTIVE')
         self.addCleanup(self.servers_client.delete_server, server['id'])
         resp, image = self.create_image_from_server(server['id'],
                                                     name=snapshot_name,
-                                                    wait_until='SAVING')
+                                                    wait_until='queued')
         resp, body = self.client.delete_image(image['id'])
-        self.assertEqual('204', resp['status'])
+        self.assertEqual('200', resp['status'])
 
     @attr(type=['negative', 'gate'])
     def test_create_image_specify_uuid_35_characters_or_less(self):
         # Return an error if Image ID passed is 35 characters or less
         snapshot_name = rand_name('test-snap-')
         test_uuid = ('a' * 35)
-        self.assertRaises(exceptions.NotFound, self.client.create_image,
+        self.assertRaises(exceptions.NotFound,
+                          self.servers_client.create_image,
                           test_uuid, snapshot_name)
 
     @attr(type=['negative', 'gate'])
@@ -127,7 +119,8 @@
         # Return an error if Image ID passed is 37 characters or more
         snapshot_name = rand_name('test-snap-')
         test_uuid = ('a' * 37)
-        self.assertRaises(exceptions.NotFound, self.client.create_image,
+        self.assertRaises(exceptions.NotFound,
+                          self.servers_client.create_image,
                           test_uuid, snapshot_name)
 
     @attr(type=['negative', 'gate'])
@@ -168,5 +161,5 @@
                           '11a22b9-12a9-5555-cc11-00ab112223fa-3fac')
 
 
-class ImagesTestXML(ImagesTestJSON):
+class ImagesV3TestXML(ImagesV3TestJSON):
     _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/__init__.py b/tempest/api/compute/v3/servers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/compute/v3/servers/__init__.py
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index 961737a..fb4214a 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -30,7 +30,7 @@
 from tempest.test import skip_because
 
 
-class ServerActionsTestJSON(base.BaseV2ComputeTest):
+class ServerActionsV3TestJSON(base.BaseV3ComputeTest):
     _interface = 'json'
     resize_available = tempest.config.TempestConfig().\
         compute_feature_enabled.resize
@@ -39,7 +39,7 @@
     def setUp(self):
         # NOTE(afazekas): Normally we use the same server with all test cases,
         # but if it has an issue, we build a new one
-        super(ServerActionsTestJSON, self).setUp()
+        super(ServerActionsV3TestJSON, self).setUp()
         # Check if the server is in a clean state after test
         try:
             self.client.wait_for_server_status(self.server_id, 'ACTIVE')
@@ -49,7 +49,7 @@
 
     @classmethod
     def setUpClass(cls):
-        super(ServerActionsTestJSON, cls).setUpClass()
+        super(ServerActionsV3TestJSON, cls).setUpClass()
         cls.client = cls.servers_client
         cls.rebuild_server()
 
@@ -122,7 +122,7 @@
                                                    name=new_name,
                                                    metadata=meta,
                                                    personality=personality,
-                                                   adminPass=password)
+                                                   admin_pass=password)
 
         # Verify the properties in the initial response are correct
         self.assertEqual(self.server_id, rebuilt_server['id'])
@@ -274,5 +274,5 @@
         self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
 
 
-class ServerActionsTestXML(ServerActionsTestJSON):
+class ServerActionsV3TestXML(ServerActionsV3TestJSON):
     _interface = 'xml'
diff --git a/tempest/clients.py b/tempest/clients.py
index dd104a7..156df30 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -46,6 +46,9 @@
     TenantUsagesClientJSON
 from tempest.services.compute.json.volumes_extensions_client import \
     VolumesExtensionsClientJSON
+from tempest.services.compute.v3.json.servers_client import \
+    ServersV3ClientJSON
+from tempest.services.compute.v3.xml.servers_client import ServersV3ClientXML
 from tempest.services.compute.xml.aggregates_client import AggregatesClientXML
 from tempest.services.compute.xml.availability_zone_client import \
     AvailabilityZoneClientXML
@@ -167,6 +170,7 @@
 
         if interface == 'xml':
             self.servers_client = ServersClientXML(*client_args)
+            self.servers_v3_client = ServersV3ClientXML(*client_args)
             self.limits_client = LimitsClientXML(*client_args)
             self.images_client = ImagesClientXML(*client_args)
             self.keypairs_client = KeyPairsClientXML(*client_args)
@@ -205,6 +209,7 @@
 
         elif interface == 'json':
             self.servers_client = ServersClientJSON(*client_args)
+            self.servers_v3_client = ServersV3ClientJSON(*client_args)
             self.limits_client = LimitsClientJSON(*client_args)
             self.images_client = ImagesClientJSON(*client_args)
             self.keypairs_client = KeyPairsClientJSON(*client_args)
diff --git a/tempest/config.py b/tempest/config.py
index 925c4bd..a629486 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -186,6 +186,9 @@
                     "of identity.region is used instead. If no such region "
                     "is found in the service catalog, the first found one is "
                     "used."),
+    cfg.StrOpt('catalog_v3_type',
+               default='computev3',
+               help="Catalog type of the Compute v3 service."),
     cfg.StrOpt('path_to_private_key',
                default=None,
                help="Path to a private key file for SSH access to remote "
@@ -200,6 +203,9 @@
                                       title="Enabled Compute Service Features")
 
 ComputeFeaturesGroup = [
+    cfg.BoolOpt('api_v3',
+                default=True,
+                help="If false, skip all nova v3 tests."),
     cfg.BoolOpt('disk_config',
                 default=True,
                 help="If false, skip disk config tests"),
diff --git a/tempest/services/compute/v3/__init__.py b/tempest/services/compute/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/compute/v3/__init__.py
diff --git a/tempest/services/compute/v3/json/__init__.py b/tempest/services/compute/v3/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/compute/v3/json/__init__.py
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index 07bb6ce..a005edb 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -2,6 +2,7 @@
 
 # Copyright 2012 OpenStack Foundation
 # Copyright 2013 Hewlett-Packard Development Company, L.P.
+# Copyright 2013 IBM Corp
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -25,14 +26,14 @@
 from tempest import exceptions
 
 
-class ServersClientJSON(RestClient):
+class ServersV3ClientJSON(RestClient):
 
-    def __init__(self, config, username, password, auth_url, tenant_name=None,
-                 auth_version='v2'):
-        super(ServersClientJSON, self).__init__(config, username, password,
-                                                auth_url, tenant_name,
-                                                auth_version=auth_version)
-        self.service = self.config.compute.catalog_type
+    def __init__(self, config, username, password, auth_url,
+                 tenant_name=None, auth_version='v2'):
+        super(ServersV3ClientJSON, self).__init__(config, username, password,
+                                                  auth_url, tenant_name,
+                                                  auth_version=auth_version)
+        self.service = self.config.compute.catalog_v3_type
 
     def create_server(self, name, image_ref, flavor_ref, **kwargs):
         """
@@ -41,7 +42,7 @@
         image_ref (Required): Reference to the image used to build the server.
         flavor_ref (Required): The flavor used to build the server.
         Following optional keyword arguments are accepted:
-        adminPass: Sets the initial root password.
+        admin_pass: Sets the initial root password.
         key_name: Key name of keypair that was created earlier.
         meta: A dictionary of values to be used as metadata.
         personality: A list of dictionaries for files to be injected into
@@ -50,8 +51,8 @@
         networks: A list of network dicts with UUID and fixed_ip.
         user_data: User data for instance.
         availability_zone: Availability zone in which to launch instance.
-        accessIPv4: The IPv4 access address for the server.
-        accessIPv6: The IPv6 access address for the server.
+        access_ip_v4: The IPv4 access address for the server.
+        access_ip_v6: The IPv6 access address for the server.
         min_count: Count of minimum number of instances to launch.
         max_count: Count of maximum number of instances to launch.
         disk_config: Determines if user or admin controls disk configuration.
@@ -59,16 +60,22 @@
         """
         post_body = {
             'name': name,
-            'imageRef': image_ref,
-            'flavorRef': flavor_ref
+            'image_ref': image_ref,
+            'flavor_ref': flavor_ref
         }
 
-        for option in ['personality', 'adminPass', 'key_name',
-                       'security_groups', 'networks', 'user_data',
-                       'availability_zone', 'accessIPv4', 'accessIPv6',
-                       'min_count', 'max_count', ('metadata', 'meta'),
-                       ('OS-DCF:diskConfig', 'disk_config'),
-                       'return_reservation_id']:
+        for option in ['personality', 'admin_pass', 'key_name',
+                       'security_groups', 'networks',
+                       ('os-user-data:user_data', 'user_data'),
+                       ('os-availability-zone:availability_zone',
+                        'availability_zone'),
+                       'access_ip_v4', 'access_ip_v6',
+                       ('os-multiple-create:min_count', 'min_count'),
+                       ('os-multiple-create:max_count', 'max_count'),
+                       ('metadata', 'meta'),
+                       ('os-disk-config:disk_config', 'disk_config'),
+                       ('os-multiple-create:return_reservation_id',
+                       'return_reservation_id')]:
             if isinstance(option, tuple):
                 post_param = option[0]
                 key = option[1]
@@ -88,15 +95,15 @@
             return resp, body
         return resp, body['server']
 
-    def update_server(self, server_id, name=None, meta=None, accessIPv4=None,
-                      accessIPv6=None, disk_config=None):
+    def update_server(self, server_id, name=None, meta=None, access_ip_v4=None,
+                      access_ip_v6=None, disk_config=None):
         """
         Updates the properties of an existing server.
         server_id: The id of an existing server.
         name: The name of the server.
         personality: A list of files to be injected into the server.
-        accessIPv4: The IPv4 access address for the server.
-        accessIPv6: The IPv6 access address for the server.
+        access_ip_v4: The IPv4 access address for the server.
+        access_ip_v6: The IPv6 access address for the server.
         """
 
         post_body = {}
@@ -107,11 +114,11 @@
         if name is not None:
             post_body['name'] = name
 
-        if accessIPv4 is not None:
-            post_body['accessIPv4'] = accessIPv4
+        if access_ip_v4 is not None:
+            post_body['access_ip_v4'] = access_ip_v4
 
-        if accessIPv6 is not None:
-            post_body['accessIPv6'] = accessIPv6
+        if access_ip_v6 is not None:
+            post_body['access_ip_v6'] = access_ip_v6
 
         if disk_config is not None:
             post_body['OS-DCF:diskConfig'] = disk_config
@@ -197,10 +204,10 @@
             body = json.loads(body)[response_key]
         return resp, body
 
-    def change_password(self, server_id, adminPass):
+    def change_password(self, server_id, admin_password):
         """Changes the root password for the server."""
-        return self.action(server_id, 'changePassword', None,
-                           adminPass=adminPass)
+        return self.action(server_id, 'change_password', None,
+                           admin_password=admin_password)
 
     def reboot(self, server_id, reboot_type):
         """Reboots a server."""
@@ -208,31 +215,44 @@
 
     def rebuild(self, server_id, image_ref, **kwargs):
         """Rebuilds a server with a new image."""
-        kwargs['imageRef'] = image_ref
+        kwargs['image_ref'] = image_ref
         if 'disk_config' in kwargs:
-            kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
+            kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
             del kwargs['disk_config']
         return self.action(server_id, 'rebuild', 'server', **kwargs)
 
     def resize(self, server_id, flavor_ref, **kwargs):
         """Changes the flavor of a server."""
-        kwargs['flavorRef'] = flavor_ref
+        kwargs['flavor_ref'] = flavor_ref
         if 'disk_config' in kwargs:
-            kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
+            kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
             del kwargs['disk_config']
         return self.action(server_id, 'resize', None, **kwargs)
 
     def confirm_resize(self, server_id, **kwargs):
         """Confirms the flavor change for a server."""
-        return self.action(server_id, 'confirmResize', None, **kwargs)
+        return self.action(server_id, 'confirm_resize', None, **kwargs)
 
     def revert_resize(self, server_id, **kwargs):
         """Reverts a server back to its original flavor."""
-        return self.action(server_id, 'revertResize', None, **kwargs)
+        return self.action(server_id, 'revert_resize', None, **kwargs)
 
-    def create_image(self, server_id, name):
-        """Creates an image of the given server."""
-        return self.action(server_id, 'createImage', None, name=name)
+    def create_image(self, server_id, name, meta=None):
+        """Creates an image of the original server."""
+
+        post_body = {
+            'create_image': {
+                'name': name,
+            }
+        }
+
+        if meta is not None:
+            post_body['create_image']['metadata'] = meta
+
+        post_body = json.dumps(post_body)
+        resp, body = self.post('servers/%s/action' % str(server_id),
+                               post_body, self.headers)
+        return resp, body
 
     def list_server_metadata(self, server_id):
         resp, body = self.get("servers/%s/metadata" % str(server_id))
@@ -259,14 +279,14 @@
     def get_server_metadata_item(self, server_id, key):
         resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key))
         body = json.loads(body)
-        return resp, body['meta']
+        return resp, body['metadata']
 
     def set_server_metadata_item(self, server_id, key, meta):
-        post_body = json.dumps({'meta': meta})
+        post_body = json.dumps({'metadata': meta})
         resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key),
                               post_body, self.headers)
         body = json.loads(body)
-        return resp, body['meta']
+        return resp, body['metadata']
 
     def delete_server_metadata_item(self, server_id, key):
         resp, body = self.delete("servers/%s/metadata/%s" %
@@ -274,36 +294,27 @@
         return resp, body
 
     def stop(self, server_id, **kwargs):
-        return self.action(server_id, 'os-stop', None, **kwargs)
+        return self.action(server_id, 'stop', None, **kwargs)
 
     def start(self, server_id, **kwargs):
-        return self.action(server_id, 'os-start', None, **kwargs)
+        return self.action(server_id, 'start', None, **kwargs)
 
     def attach_volume(self, server_id, volume_id, device='/dev/vdz'):
         """Attaches a volume to a server instance."""
-        post_body = json.dumps({
-            'volumeAttachment': {
-                'volumeId': volume_id,
-                'device': device,
-            }
-        })
-        resp, body = self.post('servers/%s/os-volume_attachments' % server_id,
-                               post_body, self.headers)
-        return resp, body
+        return self.action(server_id, 'attach', None, volume_id=volume_id,
+                           device=device)
 
     def detach_volume(self, server_id, volume_id):
         """Detaches a volume from a server instance."""
-        resp, body = self.delete('servers/%s/os-volume_attachments/%s' %
-                                 (server_id, volume_id))
-        return resp, body
+        return self.action(server_id, 'detach', None, volume_id=volume_id)
 
     def add_security_group(self, server_id, name):
         """Adds a security group to the server."""
-        return self.action(server_id, 'addSecurityGroup', None, name=name)
+        return self.action(server_id, 'add_security_group', None, name=name)
 
     def remove_security_group(self, server_id, name):
         """Removes a security group from the server."""
-        return self.action(server_id, 'removeSecurityGroup', None, name=name)
+        return self.action(server_id, 'remove_security_group', None, name=name)
 
     def live_migrate_server(self, server_id, dest_host, use_block_migration):
         """This should be called with administrator privileges ."""
@@ -314,7 +325,7 @@
             "host": dest_host
         }
 
-        req_body = json.dumps({'os-migrateLive': migrate_params})
+        req_body = json.dumps({'migrate_live': migrate_params})
 
         resp, body = self.post("servers/%s/action" % str(server_id),
                                req_body, self.headers)
@@ -350,23 +361,15 @@
 
     def reset_state(self, server_id, state='error'):
         """Resets the state of a server to active/error."""
-        return self.action(server_id, 'os-resetState', None, state=state)
+        return self.action(server_id, 'reset_state', None, state=state)
 
     def get_console_output(self, server_id, length):
-        return self.action(server_id, 'os-getConsoleOutput', 'output',
+        return self.action(server_id, 'get_console_output', 'output',
                            length=length)
 
-    def list_virtual_interfaces(self, server_id):
-        """
-        List the virtual interfaces used in an instance.
-        """
-        resp, body = self.get('/'.join(['servers', server_id,
-                              'os-virtual-interfaces']))
-        return resp, json.loads(body)
-
     def rescue_server(self, server_id, adminPass=None):
         """Rescue the provided server."""
-        return self.action(server_id, 'rescue', None, adminPass=adminPass)
+        return self.action(server_id, 'rescue', None, admin_pass=adminPass)
 
     def unrescue_server(self, server_id):
         """Unrescue the provided server."""
@@ -374,7 +377,8 @@
 
     def get_server_diagnostics(self, server_id):
         """Get the usage data for a server."""
-        resp, body = self.get("servers/%s/diagnostics" % str(server_id))
+        resp, body = self.get("servers/%s/os-server-diagnostics" %
+                              str(server_id))
         return resp, json.loads(body)
 
     def list_instance_actions(self, server_id):
@@ -382,11 +386,11 @@
         resp, body = self.get("servers/%s/os-instance-actions" %
                               str(server_id))
         body = json.loads(body)
-        return resp, body['instanceActions']
+        return resp, body['instance_actions']
 
     def get_instance_action(self, server_id, request_id):
         """Returns the action details of the provided server."""
         resp, body = self.get("servers/%s/os-instance-actions/%s" %
                               (str(server_id), str(request_id)))
         body = json.loads(body)
-        return resp, body['instanceAction']
+        return resp, body['instance_action']
diff --git a/tempest/services/compute/v3/xml/__init__.py b/tempest/services/compute/v3/xml/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/compute/v3/xml/__init__.py
diff --git a/tempest/services/compute/v3/xml/servers_client.py b/tempest/services/compute/v3/xml/servers_client.py
index 43de4ef..6f38b6a 100644
--- a/tempest/services/compute/v3/xml/servers_client.py
+++ b/tempest/services/compute/v3/xml/servers_client.py
@@ -29,7 +29,7 @@
 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
-from tempest.services.compute.xml.common import XMLNS_11
+from tempest.services.compute.xml.common import XMLNS_V3
 
 
 LOG = logging.getLogger(__name__)
@@ -43,29 +43,21 @@
     version = ip.get('version')
     if version:
         ip['version'] = int(version)
-    # NOTE(maurosr): just a fast way to avoid the xml version with the
-    # expanded xml namespace.
-    type_ns_prefix = ('{http://docs.openstack.org/compute/ext/extended_ips/'
-                      'api/v1.1}type')
-    mac_ns_prefix = ('{http://docs.openstack.org/compute/ext/extended_ips_mac'
-                     '/api/v1.1}mac_addr')
-
-    if type_ns_prefix in ip:
-        ip['OS-EXT-IPS:type'] = ip.pop(type_ns_prefix)
-
-    if mac_ns_prefix in ip:
-        ip['OS-EXT-IPS-MAC:mac_addr'] = ip.pop(mac_ns_prefix)
+    if ip.get('type'):
+        ip['type'] = ip.get('type')
+    if ip.get('mac_addr'):
+        ip['mac_addr'] = ip.get('mac_addr')
     return ip
 
 
 def _translate_network_xml_to_json(network):
     return [_translate_ip_xml_json(ip.attrib)
-            for ip in network.findall('{%s}ip' % XMLNS_11)]
+            for ip in network.findall('{%s}ip' % XMLNS_V3)]
 
 
 def _translate_addresses_xml_to_json(xml_addresses):
     return dict((network.attrib['id'], _translate_network_xml_to_json(network))
-                for network in xml_addresses.findall('{%s}network' % XMLNS_11))
+                for network in xml_addresses.findall('{%s}network' % XMLNS_V3))
 
 
 def _translate_server_xml_to_json(xml_dom):
@@ -77,7 +69,7 @@
     Translate XML addresses subtree to JSON.
 
     Having xml_doc similar to
-    <api:server  xmlns:api="http://docs.openstack.org/compute/api/v1.1">
+    <api:server  xmlns:api="http://docs.openstack.org/compute/api/v3">
         <api:addresses>
             <api:network id="foo_novanetwork">
                 <api:ip version="4" addr="192.168.0.4"/>
@@ -97,7 +89,7 @@
                                         'version': 6}],
                    'foo_novanetwork': [{'addr': '192.168.0.4', 'version': 4}]}}
     """
-    nsmap = {'api': XMLNS_11}
+    nsmap = {'api': XMLNS_V3}
     addresses = xml_dom.xpath('/api:server/api:addresses', namespaces=nsmap)
     if addresses:
         if len(addresses) > 1:
@@ -107,46 +99,47 @@
         json['addresses'] = json_addresses
     else:
         json = xml_to_json(xml_dom)
-    diskConfig = ('{http://docs.openstack.org'
-                  '/compute/ext/disk_config/api/v1.1}diskConfig')
+    disk_config = ('{http://docs.openstack.org'
+                   '/compute/ext/disk_config/api/v3}disk_config')
     terminated_at = ('{http://docs.openstack.org/'
-                     'compute/ext/server_usage/api/v1.1}terminated_at')
+                     'compute/ext/os-server-usage/api/v3}terminated_at')
     launched_at = ('{http://docs.openstack.org'
-                   '/compute/ext/server_usage/api/v1.1}launched_at')
+                   '/compute/ext/os-server-usage/api/v3}launched_at')
     power_state = ('{http://docs.openstack.org'
-                   '/compute/ext/extended_status/api/v1.1}power_state')
+                   '/compute/ext/extended_status/api/v3}power_state')
     availability_zone = ('{http://docs.openstack.org'
-                         '/compute/ext/extended_availability_zone/api/v2}'
+                         '/compute/ext/extended_availability_zone/api/v3}'
                          'availability_zone')
     vm_state = ('{http://docs.openstack.org'
-                '/compute/ext/extended_status/api/v1.1}vm_state')
+                '/compute/ext/extended_status/api/v3}vm_state')
     task_state = ('{http://docs.openstack.org'
-                  '/compute/ext/extended_status/api/v1.1}task_state')
-    if diskConfig in json:
-        json['OS-DCF:diskConfig'] = json.pop(diskConfig)
+                  '/compute/ext/extended_status/api/v3}task_state')
+    if disk_config in json:
+        json['os-disk-config:disk_config'] = json.pop(disk_config)
     if terminated_at in json:
-        json['OS-SRV-USG:terminated_at'] = json.pop(terminated_at)
+        json['os-server-usage:terminated_at'] = json.pop(terminated_at)
     if launched_at in json:
-        json['OS-SRV-USG:launched_at'] = json.pop(launched_at)
+        json['os-server-usage:launched_at'] = json.pop(launched_at)
     if power_state in json:
-        json['OS-EXT-STS:power_state'] = json.pop(power_state)
+        json['os-extended-status:power_state'] = json.pop(power_state)
     if availability_zone in json:
-        json['OS-EXT-AZ:availability_zone'] = json.pop(availability_zone)
+        json['os-extended-availability-zone:availability_zone'] = json.pop(
+            availability_zone)
     if vm_state in json:
-        json['OS-EXT-STS:vm_state'] = json.pop(vm_state)
+        json['os-extended-status:vm_state'] = json.pop(vm_state)
     if task_state in json:
-        json['OS-EXT-STS:task_state'] = json.pop(task_state)
+        json['os-extended-status:task_state'] = json.pop(task_state)
     return json
 
 
-class ServersClientXML(RestClientXML):
+class ServersV3ClientXML(RestClientXML):
 
-    def __init__(self, config, username, password, auth_url, tenant_name=None,
-                 auth_version='v2'):
-        super(ServersClientXML, self).__init__(config, username, password,
-                                               auth_url, tenant_name,
-                                               auth_version=auth_version)
-        self.service = self.config.compute.catalog_type
+    def __init__(self, config, username, password, auth_url,
+                 tenant_name=None, auth_version='v2'):
+        super(ServersV3ClientXML, self).__init__(config, username, password,
+                                                 auth_url, tenant_name,
+                                                 auth_version=auth_version)
+        self.service = self.config.compute.catalog_v3_type
 
     def _parse_key_value(self, node):
         """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
@@ -167,7 +160,7 @@
         if 'metadata' in json and json['metadata']:
             # NOTE(danms): if there was metadata, we need to re-parse
             # that as a special type
-            metadata_tag = body.find('{%s}metadata' % XMLNS_11)
+            metadata_tag = body.find('{%s}metadata' % XMLNS_V3)
             json["metadata"] = self._parse_key_value(metadata_tag)
         if 'link' in json:
             self._parse_links(body, json)
@@ -219,7 +212,7 @@
 
     def reset_state(self, server_id, state='error'):
         """Resets the state of a server to active/error."""
-        return self.action(server_id, 'os-resetState', None, state=state)
+        return self.action(server_id, 'reset_state', None, state=state)
 
     def delete_server(self, server_id):
         """Deletes the given server."""
@@ -249,22 +242,18 @@
         servers = self._parse_array(etree.fromstring(body))
         return resp, {"servers": servers}
 
-    def update_server(self, server_id, name=None, meta=None, accessIPv4=None,
-                      accessIPv6=None, disk_config=None):
+    def update_server(self, server_id, name=None, meta=None, access_ip_v4=None,
+                      access_ip_v6=None, disk_config=None):
         doc = Document()
         server = Element("server")
         doc.append(server)
 
         if name is not None:
             server.add_attr("name", name)
-        if accessIPv4 is not None:
-            server.add_attr("accessIPv4", accessIPv4)
-        if accessIPv6 is not None:
-            server.add_attr("accessIPv6", accessIPv6)
-        if disk_config is not None:
-            server.add_attr('xmlns:OS-DCF', "http://docs.openstack.org/"
-                            "compute/ext/disk_config/api/v1.1")
-            server.add_attr("OS-DCF:diskConfig", disk_config)
+        if access_ip_v4 is not None:
+            server.add_attr("access_ip_v4", access_ip_v4)
+        if access_ip_v6 is not None:
+            server.add_attr("access_ip_v6", access_ip_v6)
         if meta is not None:
             metadata = Element("metadata")
             server.append(metadata)
@@ -284,7 +273,7 @@
         image_ref (Required): Reference to the image used to build the server.
         flavor_ref (Required): The flavor used to build the server.
         Following optional keyword arguments are accepted:
-        adminPass: Sets the initial root password.
+        admin_password: Sets the initial root password.
         key_name: Key name of keypair that was created earlier.
         meta: A dictionary of values to be used as metadata.
         personality: A list of dictionaries for files to be injected into
@@ -293,28 +282,64 @@
         networks: A list of network dicts with UUID and fixed_ip.
         user_data: User data for instance.
         availability_zone: Availability zone in which to launch instance.
-        accessIPv4: The IPv4 access address for the server.
-        accessIPv6: The IPv6 access address for the server.
+        access_ip_v4: The IPv4 access address for the server.
+        access_ip_v6: The IPv6 access address for the server.
         min_count: Count of minimum number of instances to launch.
         max_count: Count of maximum number of instances to launch.
         disk_config: Determines if user or admin controls disk configuration.
+        return_reservation_id: Enable/Disable the return of reservation id.
         """
         server = Element("server",
-                         xmlns=XMLNS_11,
                          imageRef=image_ref,
-                         flavorRef=flavor_ref,
+                         xmlns=XMLNS_V3,
+                         flavor_ref=flavor_ref,
+                         image_ref=image_ref,
                          name=name)
+        attrs = ["admin_pass", "access_ip_v4", "access_ip_v6", "key_name",
+                 ("os-user-data:user_data",
+                  'user_data',
+                  'xmlns:os-user-data',
+                  "http://docs.openstack.org/compute/ext/userdata/api/v3"),
+                 ("os-availability-zone:availability_zone",
+                  'availability_zone',
+                  'xmlns:os-availability-zone',
+                  "http://docs.openstack.org/compute/ext/"
+                  "availabilityzone/api/v3"),
+                 ("os-multiple-create:min_count",
+                  'min_count',
+                  'xmlns:os-multiple-create',
+                  "http://docs.openstack.org/compute/ext/"
+                  "multiplecreate/api/v3"),
+                 ("os-multiple-create:max_count",
+                  'max_count',
+                  'xmlns:os-multiple-create',
+                  "http://docs.openstack.org/compute/ext/"
+                  "multiplecreate/api/v3"),
+                 ("os-multiple-create:return_reservation_id",
+                  "return_reservation_id",
+                  'xmlns:os-multiple-create',
+                  "http://docs.openstack.org/compute/ext/"
+                  "multiplecreate/api/v3"),
+                 ("os-disk-config:disk_config",
+                  "disk_config",
+                  "xmlns:os-disk-config",
+                  "http://docs.openstack.org/"
+                  "compute/ext/disk_config/api/v3")]
 
-        for attr in ["adminPass", "accessIPv4", "accessIPv6", "key_name",
-                     "user_data", "availability_zone", "min_count",
-                     "max_count", "return_reservation_id"]:
-            if attr in kwargs:
-                server.add_attr(attr, kwargs[attr])
-
-        if 'disk_config' in kwargs:
-            server.add_attr('xmlns:OS-DCF', "http://docs.openstack.org/"
-                            "compute/ext/disk_config/api/v1.1")
-            server.add_attr('OS-DCF:diskConfig', kwargs['disk_config'])
+        for attr in attrs:
+            if isinstance(attr, tuple):
+                post_param = attr[0]
+                key = attr[1]
+                value = kwargs.get(key)
+                if value is not None:
+                    server.add_attr(attr[2], attr[3])
+                    server.add_attr(post_param, value)
+            else:
+                post_param = attr
+                key = attr
+                value = kwargs.get(key)
+                if value is not None:
+                    server.add_attr(post_param, value)
 
         if 'security_groups' in kwargs:
             secgroups = Element("security_groups")
@@ -403,7 +428,7 @@
 
     def action(self, server_id, action_name, response_key, **kwargs):
         if 'xmlns' not in kwargs:
-            kwargs['xmlns'] = XMLNS_11
+            kwargs['xmlns'] = XMLNS_V3
         doc = Document((Element(action_name, **kwargs)))
         resp, body = self.post("servers/%s/action" % server_id,
                                str(doc), self.headers)
@@ -412,22 +437,22 @@
         return resp, body
 
     def change_password(self, server_id, password):
-        return self.action(server_id, "changePassword", None,
-                           adminPass=password)
+        return self.action(server_id, "change_password", None,
+                           admin_pass=password)
 
     def reboot(self, server_id, reboot_type):
         return self.action(server_id, "reboot", None, type=reboot_type)
 
     def rebuild(self, server_id, image_ref, **kwargs):
-        kwargs['imageRef'] = image_ref
+        kwargs['image_ref'] = image_ref
         if 'disk_config' in kwargs:
-            kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
+            kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
             del kwargs['disk_config']
-            kwargs['xmlns:OS-DCF'] = "http://docs.openstack.org/"\
-                                     "compute/ext/disk_config/api/v1.1"
+            kwargs['xmlns:os-disk-config'] = "http://docs.openstack.org/"\
+                                             "compute/ext/disk_config/api/v3"
             kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom"
         if 'xmlns' not in kwargs:
-            kwargs['xmlns'] = XMLNS_11
+            kwargs['xmlns'] = XMLNS_V3
 
         attrs = kwargs.copy()
         if 'metadata' in attrs:
@@ -450,40 +475,52 @@
 
     def resize(self, server_id, flavor_ref, **kwargs):
         if 'disk_config' in kwargs:
-            kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
+            kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
             del kwargs['disk_config']
-            kwargs['xmlns:OS-DCF'] = "http://docs.openstack.org/"\
-                                     "compute/ext/disk_config/api/v1.1"
+            kwargs['xmlns:os-disk-config'] = "http://docs.openstack.org/"\
+                                             "compute/ext/disk_config/api/v3"
             kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom"
-        kwargs['flavorRef'] = flavor_ref
+        kwargs['flavor_ref'] = flavor_ref
         return self.action(server_id, 'resize', None, **kwargs)
 
     def confirm_resize(self, server_id, **kwargs):
-        return self.action(server_id, 'confirmResize', None, **kwargs)
+        return self.action(server_id, 'confirm_resize', None, **kwargs)
 
     def revert_resize(self, server_id, **kwargs):
-        return self.action(server_id, 'revertResize', None, **kwargs)
+        return self.action(server_id, 'revert_resize', None, **kwargs)
 
     def stop(self, server_id, **kwargs):
-        return self.action(server_id, 'os-stop', None, **kwargs)
+        return self.action(server_id, 'stop', None, **kwargs)
 
     def start(self, server_id, **kwargs):
-        return self.action(server_id, 'os-start', None, **kwargs)
+        return self.action(server_id, 'start', None, **kwargs)
 
-    def create_image(self, server_id, name):
-        return self.action(server_id, 'createImage', None, name=name)
+    def create_image(self, server_id, name, meta=None):
+        """Creates an image of the original server."""
+        post_body = Element('create_image', name=name)
+
+        if meta:
+            metadata = Element('metadata')
+            post_body.append(metadata)
+            for k, v in meta.items():
+                data = Element('meta', key=k)
+                data.append(Text(v))
+                metadata.append(data)
+        resp, body = self.post('servers/%s/action' % str(server_id),
+                               str(Document(post_body)), self.headers)
+        return resp, body
 
     def add_security_group(self, server_id, name):
-        return self.action(server_id, 'addSecurityGroup', None, name=name)
+        return self.action(server_id, 'add_security_group', None, name=name)
 
     def remove_security_group(self, server_id, name):
-        return self.action(server_id, 'removeSecurityGroup', None, name=name)
+        return self.action(server_id, 'remove_security_group', None, name=name)
 
     def live_migrate_server(self, server_id, dest_host, use_block_migration):
         """This should be called with administrator privileges ."""
 
-        req_body = Element("os-migrateLive",
-                           xmlns=XMLNS_11,
+        req_body = Element("migrate_live",
+                           xmlns=XMLNS_V3,
                            disk_over_commit=False,
                            block_migration=use_block_migration,
                            host=dest_host)
@@ -546,43 +583,27 @@
         return resp, body
 
     def get_console_output(self, server_id, length):
-        return self.action(server_id, 'os-getConsoleOutput', 'output',
+        return self.action(server_id, 'get_console_output', 'output',
                            length=length)
 
-    def list_virtual_interfaces(self, server_id):
-        """
-        List the virtual interfaces used in an instance.
-        """
-        resp, body = self.get('/'.join(['servers', server_id,
-                              'os-virtual-interfaces']), self.headers)
-        virt_int = self._parse_xml_virtual_interfaces(etree.fromstring(body))
-        return resp, virt_int
-
-    def rescue_server(self, server_id, adminPass=None):
+    def rescue_server(self, server_id, admin_pass=None):
         """Rescue the provided server."""
-        return self.action(server_id, 'rescue', None, adminPass=adminPass)
+        return self.action(server_id, 'rescue', None, admin_pass=admin_pass)
 
     def unrescue_server(self, server_id):
         """Unrescue the provided server."""
         return self.action(server_id, 'unrescue', None)
 
     def attach_volume(self, server_id, volume_id, device='/dev/vdz'):
-        post_body = Element("volumeAttachment", volumeId=volume_id,
-                            device=device)
-        resp, body = self.post('servers/%s/os-volume_attachments' % server_id,
-                               str(Document(post_body)), self.headers)
-        return resp, body
+        return self.action(server_id, "attach", None, volume_id=volume_id,
+                           device=device)
 
     def detach_volume(self, server_id, volume_id):
-        headers = {'Content-Type': 'application/xml',
-                   'Accept': 'application/xml'}
-        resp, body = self.delete('servers/%s/os-volume_attachments/%s' %
-                                 (server_id, volume_id), headers)
-        return resp, body
+        return self.action(server_id, "detach", None, volume_id=volume_id)
 
     def get_server_diagnostics(self, server_id):
         """Get the usage data for a server."""
-        resp, body = self.get("servers/%s/diagnostics" % server_id,
+        resp, body = self.get("servers/%s/os-server-diagnostics" % server_id,
                               self.headers)
         body = xml_to_json(etree.fromstring(body))
         return resp, body
diff --git a/tempest/services/compute/xml/common.py b/tempest/services/compute/xml/common.py
index ad79ed6..860dd5b 100644
--- a/tempest/services/compute/xml/common.py
+++ b/tempest/services/compute/xml/common.py
@@ -18,6 +18,7 @@
 import collections
 
 XMLNS_11 = "http://docs.openstack.org/compute/api/v1.1"
+XMLNS_V3 = "http://docs.openstack.org/compute/api/v1.1"
 
 
 # NOTE(danms): This is just a silly implementation to help make generating
diff --git a/tempest/services/image/v1/json/image_client.py b/tempest/services/image/v1/json/image_client.py
index 41b9e81..b19f65d 100644
--- a/tempest/services/image/v1/json/image_client.py
+++ b/tempest/services/image/v1/json/image_client.py
@@ -175,7 +175,7 @@
 
     def delete_image(self, image_id):
         url = 'v1/images/%s' % image_id
-        self.delete(url)
+        return self.delete(url)
 
     def image_list(self, **kwargs):
         url = 'v1/images'