Merge "Add a response schema for "log" attribute"
diff --git a/releasenotes/notes/add-network-versions-client-d90e8334e1443f5c.yaml b/releasenotes/notes/add-network-versions-client-d90e8334e1443f5c.yaml
new file mode 100644
index 0000000..07e3151
--- /dev/null
+++ b/releasenotes/notes/add-network-versions-client-d90e8334e1443f5c.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - Adds a network version client for querying
+    Neutron's API version discovery URL ("GET /").
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index b18789e..357c907 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -109,9 +109,12 @@
     @test.attr(type=['negative'])
     @test.idempotent_id('d47c17fb-eebd-4287-8e95-f20a7e627b18')
     def test_list_servers_by_limits_greater_than_actual_count(self):
+        # Gather the complete list of servers in the project for reference
+        full_list = self.client.list_servers()['servers']
         # List servers by specifying a greater value for limit
-        body = self.client.list_servers(limit=100)
-        self.assertEqual(len(self.existing_fixtures), len(body['servers']))
+        limit = len(full_list) + 100
+        body = self.client.list_servers(limit=limit)
+        self.assertEqual(len(full_list), len(body['servers']))
 
     @test.attr(type=['negative'])
     @test.idempotent_id('679bc053-5e70-4514-9800-3dfab1a380a6')
diff --git a/tempest/api/identity/admin/v2/test_endpoints.py b/tempest/api/identity/admin/v2/test_endpoints.py
index 4493c8e..651a316 100644
--- a/tempest/api/identity/admin/v2/test_endpoints.py
+++ b/tempest/api/identity/admin/v2/test_endpoints.py
@@ -28,7 +28,8 @@
         s_type = data_utils.rand_name('type')
         s_description = data_utils.rand_name('description')
         cls.service_data = cls.services_client.create_service(
-            s_name, s_type, description=s_description)['OS-KSADM:service']
+            name=s_name, type=s_type,
+            description=s_description)['OS-KSADM:service']
         cls.service_id = cls.service_data['id']
         cls.service_ids.append(cls.service_id)
         # Create endpoints so as to use for LIST and GET test cases
diff --git a/tempest/api/identity/admin/v2/test_services.py b/tempest/api/identity/admin/v2/test_services.py
index fe83759..6c9b564 100644
--- a/tempest/api/identity/admin/v2/test_services.py
+++ b/tempest/api/identity/admin/v2/test_services.py
@@ -35,10 +35,11 @@
         # GET Service
         # Creating a Service
         name = data_utils.rand_name('service')
-        type = data_utils.rand_name('type')
+        s_type = data_utils.rand_name('type')
         description = data_utils.rand_name('description')
         service_data = self.services_client.create_service(
-            name, type, description=description)['OS-KSADM:service']
+            name=name, type=s_type,
+            description=description)['OS-KSADM:service']
         self.assertFalse(service_data['id'] is None)
         self.addCleanup(self._del_service, service_data['id'])
         # Verifying response body of create service
@@ -46,7 +47,7 @@
         self.assertIn('name', service_data)
         self.assertEqual(name, service_data['name'])
         self.assertIn('type', service_data)
-        self.assertEqual(type, service_data['type'])
+        self.assertEqual(s_type, service_data['type'])
         self.assertIn('description', service_data)
         self.assertEqual(description, service_data['description'])
         # Get service
@@ -68,15 +69,15 @@
     def test_create_service_without_description(self):
         # Create a service only with name and type
         name = data_utils.rand_name('service')
-        type = data_utils.rand_name('type')
-        service = self.services_client.create_service(name,
-                                                      type)['OS-KSADM:service']
+        s_type = data_utils.rand_name('type')
+        service = self.services_client.create_service(
+            name=name, type=s_type)['OS-KSADM:service']
         self.assertIn('id', service)
         self.addCleanup(self._del_service, service['id'])
         self.assertIn('name', service)
         self.assertEqual(name, service['name'])
         self.assertIn('type', service)
-        self.assertEqual(type, service['type'])
+        self.assertEqual(s_type, service['type'])
 
     @test.attr(type='smoke')
     @test.idempotent_id('34ea6489-012d-4a86-9038-1287cadd5eca')
@@ -85,10 +86,12 @@
         services = []
         for _ in moves.xrange(3):
             name = data_utils.rand_name('service')
-            type = data_utils.rand_name('type')
+            s_type = data_utils.rand_name('type')
             description = data_utils.rand_name('description')
+
             service = self.services_client.create_service(
-                name, type, description=description)['OS-KSADM:service']
+                name=name, type=s_type,
+                description=description)['OS-KSADM:service']
             services.append(service)
         service_ids = map(lambda x: x['id'], services)
 
diff --git a/tempest/api/identity/admin/v2/test_tokens.py b/tempest/api/identity/admin/v2/test_tokens.py
index ee04420..2297a9d 100644
--- a/tempest/api/identity/admin/v2/test_tokens.py
+++ b/tempest/api/identity/admin/v2/test_tokens.py
@@ -30,8 +30,10 @@
         tenant = self.tenants_client.create_tenant(tenant_name)['tenant']
         self.data.tenants.append(tenant)
         # second:create a user
-        user = self.users_client.create_user(user_name, user_password,
-                                             tenant['id'], '')['user']
+        user = self.users_client.create_user(name=user_name,
+                                             password=user_password,
+                                             tenantId=tenant['id'],
+                                             email='')['user']
         self.data.users.append(user)
         # then get a token for the user
         body = self.token_client.auth(user_name,
@@ -62,8 +64,10 @@
         user_password = data_utils.rand_password()
         tenant_id = None  # No default tenant so will get unscoped token.
         email = ''
-        user = self.users_client.create_user(user_name, user_password,
-                                             tenant_id, email)['user']
+        user = self.users_client.create_user(name=user_name,
+                                             password=user_password,
+                                             tenantId=tenant_id,
+                                             email=email)['user']
         self.data.users.append(user)
 
         # Create a couple tenants.
diff --git a/tempest/api/identity/admin/v2/test_users.py b/tempest/api/identity/admin/v2/test_users.py
index d860d2f..0f783b3 100644
--- a/tempest/api/identity/admin/v2/test_users.py
+++ b/tempest/api/identity/admin/v2/test_users.py
@@ -36,9 +36,10 @@
     def test_create_user(self):
         # Create a user
         self.data.setup_test_tenant()
-        user = self.users_client.create_user(self.alt_user, self.alt_password,
-                                             self.data.tenant['id'],
-                                             self.alt_email)['user']
+        user = self.users_client.create_user(name=self.alt_user,
+                                             password=self.alt_password,
+                                             tenantId=self.data.tenant['id'],
+                                             email=self.alt_email)['user']
         self.data.users.append(user)
         self.assertEqual(self.alt_user, user['name'])
 
@@ -47,9 +48,10 @@
         # Create a user with enabled : False
         self.data.setup_test_tenant()
         name = data_utils.rand_name('test_user')
-        user = self.users_client.create_user(name, self.alt_password,
-                                             self.data.tenant['id'],
-                                             self.alt_email,
+        user = self.users_client.create_user(name=name,
+                                             password=self.alt_password,
+                                             tenantId=self.data.tenant['id'],
+                                             email=self.alt_email,
                                              enabled=False)['user']
         self.data.users.append(user)
         self.assertEqual(name, user['name'])
@@ -61,9 +63,10 @@
         # Test case to check if updating of user attributes is successful.
         test_user = data_utils.rand_name('test_user')
         self.data.setup_test_tenant()
-        user = self.users_client.create_user(test_user, self.alt_password,
-                                             self.data.tenant['id'],
-                                             self.alt_email)['user']
+        user = self.users_client.create_user(name=test_user,
+                                             password=self.alt_password,
+                                             tenantId=self.data.tenant['id'],
+                                             email=self.alt_email)['user']
         # Delete the User at the end of this method
         self.addCleanup(self.users_client.delete_user, user['id'])
         # Updating user details with new values
@@ -87,9 +90,10 @@
         # Delete a user
         test_user = data_utils.rand_name('test_user')
         self.data.setup_test_tenant()
-        user = self.users_client.create_user(test_user, self.alt_password,
-                                             self.data.tenant['id'],
-                                             self.alt_email)['user']
+        user = self.users_client.create_user(name=test_user,
+                                             password=self.alt_password,
+                                             tenantId=self.data.tenant['id'],
+                                             email=self.alt_email)['user']
         self.users_client.delete_user(user['id'])
 
     @test.idempotent_id('aca696c3-d645-4f45-b728-63646045beb1')
@@ -139,16 +143,18 @@
         fetched_user_ids = list()
         password1 = data_utils.rand_password()
         alt_tenant_user1 = data_utils.rand_name('tenant_user1')
-        user1 = self.users_client.create_user(alt_tenant_user1, password1,
-                                              self.data.tenant['id'],
-                                              'user1@123')['user']
+        user1 = self.users_client.create_user(name=alt_tenant_user1,
+                                              password=password1,
+                                              tenantId=self.data.tenant['id'],
+                                              email='user1@123')['user']
         user_ids.append(user1['id'])
         self.data.users.append(user1)
         password2 = data_utils.rand_password()
         alt_tenant_user2 = data_utils.rand_name('tenant_user2')
-        user2 = self.users_client.create_user(alt_tenant_user2, password2,
-                                              self.data.tenant['id'],
-                                              'user2@123')['user']
+        user2 = self.users_client.create_user(name=alt_tenant_user2,
+                                              password=password2,
+                                              tenantId=self.data.tenant['id'],
+                                              email='user2@123')['user']
         user_ids.append(user2['id'])
         self.data.users.append(user2)
         # List of users for the respective tenant ID
@@ -180,9 +186,11 @@
 
         alt_user2 = data_utils.rand_name('second_user')
         alt_password2 = data_utils.rand_password()
-        second_user = self.users_client.create_user(alt_user2, alt_password2,
-                                                    self.data.tenant['id'],
-                                                    'user2@123')['user']
+        second_user = self.users_client.create_user(
+            name=alt_user2,
+            password=alt_password2,
+            tenantId=self.data.tenant['id'],
+            email='user2@123')['user']
         user_ids.append(second_user['id'])
         self.data.users.append(second_user)
         role = self.roles_client.assign_user_role(tenant['id'],
diff --git a/tempest/api/identity/admin/v2/test_users_negative.py b/tempest/api/identity/admin/v2/test_users_negative.py
index 5fda4c14..78b89fa 100644
--- a/tempest/api/identity/admin/v2/test_users_negative.py
+++ b/tempest/api/identity/admin/v2/test_users_negative.py
@@ -35,9 +35,9 @@
         self.data.setup_test_tenant()
         self.assertRaises(lib_exc.Forbidden,
                           self.non_admin_users_client.create_user,
-                          self.alt_user, self.alt_password,
-                          self.data.tenant['id'],
-                          self.alt_email)
+                          name=self.alt_user, password=self.alt_password,
+                          tenantId=self.data.tenant['id'],
+                          email=self.alt_email)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('d80d0c2f-4514-4d1e-806d-0930dfc5a187')
@@ -45,8 +45,9 @@
         # User with an empty name should not be created
         self.data.setup_test_tenant()
         self.assertRaises(lib_exc.BadRequest, self.users_client.create_user,
-                          '', self.alt_password, self.data.tenant['id'],
-                          self.alt_email)
+                          name='', password=self.alt_password,
+                          tenantId=self.data.tenant['id'],
+                          email=self.alt_email)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('7704b4f3-3b75-4b82-87cc-931d41c8f780')
@@ -54,8 +55,9 @@
         # Length of user name filed should be restricted to 255 characters
         self.data.setup_test_tenant()
         self.assertRaises(lib_exc.BadRequest, self.users_client.create_user,
-                          'a' * 256, self.alt_password,
-                          self.data.tenant['id'], self.alt_email)
+                          name='a' * 256, password=self.alt_password,
+                          tenantId=self.data.tenant['id'],
+                          email=self.alt_email)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('57ae8558-120c-4723-9308-3751474e7ecf')
@@ -63,16 +65,20 @@
         # Duplicate user should not be created
         self.data.setup_test_user()
         self.assertRaises(lib_exc.Conflict, self.users_client.create_user,
-                          self.data.user['name'], self.data.user_password,
-                          self.data.tenant['id'], self.data.user['email'])
+                          name=self.data.user['name'],
+                          password=self.data.user_password,
+                          tenantId=self.data.tenant['id'],
+                          email=self.data.user['email'])
 
     @test.attr(type=['negative'])
     @test.idempotent_id('0132cc22-7c4f-42e1-9e50-ac6aad31d59a')
     def test_create_user_for_non_existent_tenant(self):
         # Attempt to create a user in a non-existent tenant should fail
         self.assertRaises(lib_exc.NotFound, self.users_client.create_user,
-                          self.alt_user, self.alt_password, '49ffgg99999',
-                          self.alt_email)
+                          name=self.alt_user,
+                          password=self.alt_password,
+                          tenantId='49ffgg99999',
+                          email=self.alt_email)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('55bbb103-d1ae-437b-989b-bcdf8175c1f4')
@@ -88,8 +94,9 @@
         self.addCleanup(self.client.auth_provider.clear_auth)
 
         self.assertRaises(lib_exc.Unauthorized, self.users_client.create_user,
-                          self.alt_user, self.alt_password,
-                          self.data.tenant['id'], self.alt_email)
+                          name=self.alt_user, password=self.alt_password,
+                          tenantId=self.data.tenant['id'],
+                          email=self.alt_email)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('23a2f3da-4a1a-41da-abdd-632328a861ad')
@@ -98,9 +105,9 @@
         self.data.setup_test_tenant()
         name = data_utils.rand_name('test_user')
         self.assertRaises(lib_exc.BadRequest, self.users_client.create_user,
-                          name, self.alt_password,
-                          self.data.tenant['id'],
-                          self.alt_email, enabled=3)
+                          name=name, password=self.alt_password,
+                          tenantId=self.data.tenant['id'],
+                          email=self.alt_email, enabled=3)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('3d07e294-27a0-4144-b780-a2a1bf6fee19')
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 31420d1..bc1b158 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -215,11 +215,10 @@
         self.domains = []
 
     def _create_test_user(self, **kwargs):
-        username = data_utils.rand_name('test_user')
         self.user_password = data_utils.rand_password()
         self.user = self.users_client.create_user(
-            username, password=self.user_password,
-            email=username + '@testmail.tm', **kwargs)['user']
+            password=self.user_password,
+            **kwargs)['user']
         self.users.append(self.user)
 
     def setup_test_role(self):
@@ -256,7 +255,10 @@
     def setup_test_user(self):
         """Set up a test user."""
         self.setup_test_tenant()
-        self._create_test_user(tenant_id=self.tenant['id'])
+        username = data_utils.rand_name('test_user')
+        email = username + '@testmail.tm'
+        self._create_test_user(name=username, email=email,
+                               tenantId=self.tenant['id'])
 
     def setup_test_tenant(self):
         """Set up a test tenant."""
@@ -271,7 +273,10 @@
     def setup_test_user(self):
         """Set up a test user."""
         self.setup_test_project()
-        self._create_test_user(project_id=self.project['id'])
+        username = data_utils.rand_name('test_user')
+        email = username + '@testmail.tm'
+        self._create_test_user(user_name=username, email=email,
+                               project_id=self.project['id'])
 
     def setup_test_project(self):
         """Set up a test project."""
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 6d5559d..59ac646 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -16,6 +16,7 @@
 from six import moves
 
 from tempest.api.image import base
+from tempest.common import image as common_image
 from tempest.common.utils import data_utils
 from tempest.common import waiters
 from tempest import config
@@ -306,7 +307,8 @@
     @test.idempotent_id('01752c1c-0275-4de3-9e5b-876e44541928')
     def test_list_image_metadata(self):
         # All metadata key/value pairs for an image should be returned
-        resp_metadata = self.client.check_image(self.image_id)
+        resp = self.client.check_image(self.image_id)
+        resp_metadata = common_image.get_image_meta_from_headers(resp)
         expected = {'key1': 'value1'}
         self.assertEqual(expected, resp_metadata['properties'])
 
@@ -314,12 +316,14 @@
     def test_update_image_metadata(self):
         # The metadata for the image should match the updated values
         req_metadata = {'key1': 'alt1', 'key2': 'value2'}
-        metadata = self.client.check_image(self.image_id)
+        resp = self.client.check_image(self.image_id)
+        metadata = common_image.get_image_meta_from_headers(resp)
         self.assertEqual(metadata['properties'], {'key1': 'value1'})
         metadata['properties'].update(req_metadata)
         metadata = self.client.update_image(
             self.image_id, properties=metadata['properties'])['image']
 
-        resp_metadata = self.client.check_image(self.image_id)
+        resp = self.client.check_image(self.image_id)
+        resp_metadata = common_image.get_image_meta_from_headers(resp)
         expected = {'key1': 'alt1', 'key2': 'value2'}
         self.assertEqual(expected, resp_metadata['properties'])
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 89fa576..9e7c795 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -80,6 +80,7 @@
         cls.security_groups_client = cls.os.security_groups_client
         cls.security_group_rules_client = (
             cls.os.security_group_rules_client)
+        cls.network_versions_client = cls.os.network_versions_client
 
     @classmethod
     def resource_setup(cls):
diff --git a/tempest/api/network/test_versions.py b/tempest/api/network/test_versions.py
new file mode 100644
index 0000000..9cf93f6
--- /dev/null
+++ b/tempest/api/network/test_versions.py
@@ -0,0 +1,40 @@
+# Copyright 2016 VMware, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.network import base
+from tempest import test
+
+
+class NetworksApiDiscovery(base.BaseNetworkTest):
+    @test.attr(type='smoke')
+    @test.idempotent_id('cac8a836-c2e0-4304-b556-cd299c7281d1')
+    def test_api_version_resources(self):
+        """Test that GET / returns expected resources.
+
+        The versions document returned by Neutron returns a few other
+        resources other than just available API versions: it also
+        states the status of each API version and provides links to
+        schema.
+        """
+
+        result = self.network_versions_client.list_versions()
+        expected_versions = ('v2.0')
+        expected_resources = ('id', 'links', 'status')
+        received_list = result.values()
+
+        for item in received_list:
+            for version in item:
+                for resource in expected_resources:
+                    self.assertIn(resource, version)
+                self.assertIn(version['id'], expected_versions)
diff --git a/tempest/api/volume/api_microversion_fixture.py b/tempest/api/volume/api_microversion_fixture.py
new file mode 100644
index 0000000..6817eaa
--- /dev/null
+++ b/tempest/api/volume/api_microversion_fixture.py
@@ -0,0 +1,30 @@
+#
+# 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 fixtures
+
+from tempest.services.volume.base import base_v3_client
+
+
+class APIMicroversionFixture(fixtures.Fixture):
+
+    def __init__(self, volume_microversion):
+        self.volume_microversion = volume_microversion
+
+    def _setUp(self):
+        super(APIMicroversionFixture, self)._setUp()
+        base_v3_client.VOLUME_MICROVERSION = self.volume_microversion
+        self.addCleanup(self._reset_volume_microversion)
+
+    def _reset_volume_microversion(self):
+        base_v3_client.VOLUME_MICROVERSION = None
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index cd21424..9010c89 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -45,6 +45,10 @@
             if not CONF.volume_feature_enabled.api_v2:
                 msg = "Volume API v2 is disabled"
                 raise cls.skipException(msg)
+        elif cls._api_version == 3:
+            if not CONF.volume_feature_enabled.api_v3:
+                msg = "Volume API v3 is disabled"
+                raise cls.skipException(msg)
         else:
             msg = ("Invalid Cinder API version (%s)" % cls._api_version)
             raise exceptions.InvalidConfiguration(message=msg)
diff --git a/tempest/api/volume/v3/__init__.py b/tempest/api/volume/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/volume/v3/__init__.py
diff --git a/tempest/api/volume/v3/admin/__init__.py b/tempest/api/volume/v3/admin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/volume/v3/admin/__init__.py
diff --git a/tempest/api/volume/v3/admin/test_user_messages.py b/tempest/api/volume/v3/admin/test_user_messages.py
new file mode 100644
index 0000000..19c37be
--- /dev/null
+++ b/tempest/api/volume/v3/admin/test_user_messages.py
@@ -0,0 +1,98 @@
+# Copyright 2016 Andrew Kerr
+# 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.api.volume.v3 import base
+from tempest.common.utils import data_utils
+from tempest.common import waiters
+from tempest import exceptions
+from tempest import test
+
+MESSAGE_KEYS = [
+    'created_at',
+    'event_id',
+    'guaranteed_until',
+    'id',
+    'message_level',
+    'request_id',
+    'resource_type',
+    'resource_uuid',
+    'user_message',
+    'links']
+
+
+class UserMessagesTest(base.VolumesV3AdminTest):
+    min_microversion = '3.3'
+    max_microversion = 'latest'
+
+    def _delete_volume(self, volume_id):
+        self.volumes_client.delete_volume(volume_id)
+        self.volumes_client.wait_for_resource_deletion(volume_id)
+
+    def _create_user_message(self):
+        """Trigger a 'no valid host' situation to generate a message."""
+        bad_protocol = data_utils.rand_name('storage_protocol')
+        bad_vendor = data_utils.rand_name('vendor_name')
+        extra_specs = {'storage_protocol': bad_protocol,
+                       'vendor_name': bad_vendor}
+        vol_type_name = data_utils.rand_name('volume-type')
+        bogus_type = self.admin_volume_types_client.create_volume_type(
+            name=vol_type_name,
+            extra_specs=extra_specs)['volume_type']
+        self.addCleanup(self.admin_volume_types_client.delete_volume_type,
+                        bogus_type['id'])
+        params = {'volume_type': bogus_type['id']}
+        volume = self.volumes_client.create_volume(**params)['volume']
+        self.addCleanup(self._delete_volume, volume['id'])
+        try:
+            waiters.wait_for_volume_status(self.volumes_client, volume['id'],
+                                           'error')
+        except exceptions.VolumeBuildErrorException:
+            # Error state is expected and desired
+            pass
+        messages = self.messages_client.list_messages()['messages']
+        message_id = None
+        for message in messages:
+            if message['resource_uuid'] == volume['id']:
+                message_id = message['id']
+                break
+        self.assertIsNotNone(message_id, 'No user message generated for '
+                                         'volume %s' % volume['id'])
+        return message_id
+
+    @test.idempotent_id('50f29e6e-f363-42e1-8ad1-f67ae7fd4d5a')
+    def test_list_messages(self):
+        self._create_user_message()
+        messages = self.messages_client.list_messages()['messages']
+        self.assertIsInstance(messages, list)
+        for message in messages:
+            for key in MESSAGE_KEYS:
+                self.assertIn(key, message.keys(),
+                              'Missing expected key %s' % key)
+
+    @test.idempotent_id('55a4a61e-c7b2-4ba0-a05d-b914bdef3070')
+    def test_show_message(self):
+        message_id = self._create_user_message()
+        self.addCleanup(self.messages_client.delete_message, message_id)
+
+        message = self.messages_client.show_message(message_id)['message']
+
+        for key in MESSAGE_KEYS:
+            self.assertIn(key, message.keys(), 'Missing expected key %s' % key)
+
+    @test.idempotent_id('c6eb6901-cdcc-490f-b735-4fe251842aed')
+    def test_delete_message(self):
+        message_id = self._create_user_message()
+        self.messages_client.delete_message(message_id)
+        self.messages_client.wait_for_resource_deletion(message_id)
diff --git a/tempest/api/volume/v3/base.py b/tempest/api/volume/v3/base.py
new file mode 100644
index 0000000..c31c83c
--- /dev/null
+++ b/tempest/api/volume/v3/base.py
@@ -0,0 +1,64 @@
+# Copyright 2016 Andrew Kerr
+# 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.api.volume import api_microversion_fixture
+from tempest.api.volume import base
+from tempest import config
+from tempest.lib.common import api_version_utils
+
+CONF = config.CONF
+
+
+class VolumesV3Test(api_version_utils.BaseMicroversionTest,
+                    base.BaseVolumeTest):
+    """Base test case class for all v3 Cinder API tests."""
+
+    _api_version = 3
+
+    @classmethod
+    def skip_checks(cls):
+        super(VolumesV3Test, cls).skip_checks()
+        api_version_utils.check_skip_with_microversion(
+            cls.min_microversion, cls.max_microversion,
+            CONF.volume.min_microversion, CONF.volume.max_microversion)
+
+    @classmethod
+    def resource_setup(cls):
+        super(VolumesV3Test, cls).resource_setup()
+        cls.request_microversion = (
+            api_version_utils.select_request_microversion(
+                cls.min_microversion,
+                CONF.volume.min_microversion))
+
+    @classmethod
+    def setup_clients(cls):
+        super(VolumesV3Test, cls).setup_clients()
+        cls.messages_client = cls.os.volume_messages_client
+
+    def setUp(self):
+        super(VolumesV3Test, self).setUp()
+        self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+            self.request_microversion))
+
+
+class VolumesV3AdminTest(VolumesV3Test):
+    """Base test case class for all v3 Volume Admin API tests."""
+
+    credentials = ['primary', 'admin']
+
+    @classmethod
+    def setup_clients(cls):
+        super(VolumesV3AdminTest, cls).setup_clients()
+        cls.admin_messages_client = cls.os_adm.volume_messages_client
+        cls.admin_volume_types_client = cls.os_adm.volume_types_v2_client
diff --git a/tempest/clients.py b/tempest/clients.py
index ccbec4e..7a7d15d 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -104,6 +104,8 @@
     SecurityGroupsClient
 from tempest.lib.services.network.subnetpools_client import SubnetpoolsClient
 from tempest.lib.services.network.subnets_client import SubnetsClient
+from tempest.lib.services.network.versions_client import \
+    NetworkVersionsClient
 from tempest import manager
 from tempest.services.baremetal.v1.json.baremetal_client import \
     BaremetalClient
@@ -183,6 +185,7 @@
     SnapshotsClient as SnapshotsV2Client
 from tempest.services.volume.v2.json.volumes_client import \
     VolumesClient as VolumesV2Client
+from tempest.services.volume.v3.json.messages_client import MessagesClient
 
 CONF = config.CONF
 LOG = logging.getLogger(__name__)
@@ -281,6 +284,8 @@
             self.auth_provider, **params)
         self.security_groups_client = SecurityGroupsClient(
             self.auth_provider, **params)
+        self.network_versions_client = NetworkVersionsClient(
+            self.auth_provider, **params)
 
     def _set_image_clients(self):
         params = {
@@ -508,6 +513,8 @@
         self.volumes_v2_client = VolumesV2Client(
             self.auth_provider, default_volume_size=CONF.volume.volume_size,
             **params)
+        self.volume_messages_client = MessagesClient(self.auth_provider,
+                                                     **params)
         self.volume_types_client = VolumeTypesClient(self.auth_provider,
                                                      **params)
         self.volume_types_v2_client = VolumeTypesV2Client(
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index db323de..f9d7a9b 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -157,7 +157,8 @@
         spec.append([CONF.object_storage.operator_role])
         spec.append([CONF.object_storage.reseller_admin_role])
     if CONF.service_available.heat:
-        spec.append([CONF.orchestration.stack_owner_role])
+        spec.append([CONF.orchestration.stack_owner_role,
+                     CONF.object_storage.operator_role])
     if admin:
         spec.append('admin')
     resources = []
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index 1f433eb..7ce6225 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -391,8 +391,9 @@
                         % u['name'])
         except lib_exc.NotFound:
             admin.users.create_user(
-                u['name'], u['pass'], tenant['id'],
-                "%s@%s" % (u['name'], tenant['id']),
+                name=u['name'], password=u['pass'],
+                tenantId=tenant['id'],
+                email="%s@%s" % (u['name'], tenant['id']),
                 enabled=True)
 
 
diff --git a/tempest/common/cred_client.py b/tempest/common/cred_client.py
index aac036b..48d81ca 100644
--- a/tempest/common/cred_client.py
+++ b/tempest/common/cred_client.py
@@ -40,8 +40,9 @@
         self.roles_client = roles_client
 
     def create_user(self, username, password, project, email):
-        user = self.users_client.create_user(
-            username, password, project['id'], email)
+        params = self._create_user_params(username, password,
+                                          project['id'], email)
+        user = self.users_client.create_user(**params)
         if 'user' in user:
             user = user['user']
         return user
@@ -101,6 +102,13 @@
                                             users_client,
                                             roles_client)
 
+    def _create_user_params(self, username, password, project_id, email):
+        params = {'name': username,
+                  'password': password,
+                  'tenantId': project_id,
+                  'email': email}
+        return params
+
     def create_project(self, name, description):
         tenant = self.projects_client.create_tenant(
             name=name, description=description)['tenant']
@@ -143,6 +151,13 @@
             msg = "Requested domain %s could not be found" % domain_name
             raise lib_exc.InvalidCredentials(msg)
 
+    def _create_user_params(self, username, password, project_id, email):
+        params = {'user_name': username,
+                  'password': password,
+                  'project_id': project_id,
+                  'email': email}
+        return params
+
     def create_project(self, name, description):
         project = self.projects_client.create_project(
             name=name, description=description,
diff --git a/tempest/common/dynamic_creds.py b/tempest/common/dynamic_creds.py
index 0b92c15..b0c01f5 100644
--- a/tempest/common/dynamic_creds.py
+++ b/tempest/common/dynamic_creds.py
@@ -109,22 +109,22 @@
                     os.subnets_client, os.ports_client,
                     os.security_groups_client)
 
-    def _create_creds(self, suffix="", admin=False, roles=None):
-        """Create random credentials under the following schema.
+    def _create_creds(self, admin=False, roles=None):
+        """Create credentials with random name.
 
-        If the name contains a '.' is the full class path of something, and
-        we don't really care. If it isn't, it's probably a meaningful name,
-        so use it.
+        Creates project and user. When admin flag is True create user
+        with admin role. Assign user with additional roles (for example
+        _member_) and roles requested by caller.
 
-        For logging purposes, -user and -tenant are long and redundant,
-        don't use them. The user# will be sufficient to figure it out.
+        :param admin: Flag if to assign to the user admin role
+        :type admin: bool
+        :param roles: Roles to assign for the user
+        :type roles: list
+        :return: Readonly Credentials with network resources
         """
-        if '.' in self.name:
-            root = ""
-        else:
-            root = self.name
+        root = self.name
 
-        project_name = data_utils.rand_name(root) + suffix
+        project_name = data_utils.rand_name(root)
         project_desc = project_name + "-desc"
         project = self.creds_client.create_project(
             name=project_name, description=project_desc)
@@ -133,15 +133,14 @@
         # having the same ID in both makes it easier to match them and debug.
         username = project_name
         user_password = data_utils.rand_password()
-        email = data_utils.rand_name(root) + suffix + "@example.com"
+        email = data_utils.rand_name(root) + "@example.com"
         user = self.creds_client.create_user(
             username, user_password, project, email)
         if 'user' in user:
             user = user['user']
         role_assigned = False
         if admin:
-            self.creds_client.assign_user_role(user, project,
-                                               self.admin_role)
+            self.creds_client.assign_user_role(user, project, self.admin_role)
             role_assigned = True
             if (self.identity_version == 'v3' and
                     CONF.identity.admin_domain_scope):
diff --git a/tempest/common/image.py b/tempest/common/image.py
new file mode 100644
index 0000000..42ce5ac
--- /dev/null
+++ b/tempest/common/image.py
@@ -0,0 +1,38 @@
+# Copyright 2016 NEC Corporation
+# 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.
+
+
+def get_image_meta_from_headers(resp):
+    meta = {'properties': {}}
+    for key in resp.response:
+        value = resp.response[key]
+        if key.startswith('x-image-meta-property-'):
+            _key = key[22:]
+            meta['properties'][_key] = value
+        elif key.startswith('x-image-meta-'):
+            _key = key[13:]
+            meta[_key] = value
+
+    for key in ['is_public', 'protected', 'deleted']:
+        if key in meta:
+            meta[key] = meta[key].strip().lower() in ('t', 'true', 'yes', '1')
+
+    for key in ['size', 'min_ram', 'min_disk']:
+        if key in meta:
+            try:
+                meta[key] = int(meta[key])
+            except ValueError:
+                pass
+    return meta
diff --git a/tempest/common/validation_resources.py b/tempest/common/validation_resources.py
index c3c9a41..a55ee32 100644
--- a/tempest/common/validation_resources.py
+++ b/tempest/common/validation_resources.py
@@ -22,6 +22,26 @@
 LOG = logging.getLogger(__name__)
 
 
+def _create_neutron_sec_group_rules(os, sec_group):
+    sec_group_rules_client = os.security_group_rules_client
+    ethertype = 'IPv4'
+    if CONF.validation.ip_version_for_ssh == 6:
+        ethertype = 'IPv6'
+
+    sec_group_rules_client.create_security_group_rule(
+        security_group_id=sec_group['id'],
+        protocol='tcp',
+        ethertype=ethertype,
+        port_range_min=22,
+        port_range_max=22,
+        direction='ingress')
+    sec_group_rules_client.create_security_group_rule(
+        security_group_id=sec_group['id'],
+        protocol='icmp',
+        ethertype=ethertype,
+        direction='ingress')
+
+
 def create_ssh_security_group(os, add_rule=False):
     security_groups_client = os.compute_security_groups_client
     security_group_rules_client = os.compute_security_group_rules_client
@@ -30,12 +50,15 @@
     security_group = security_groups_client.create_security_group(
         name=sg_name, description=sg_description)['security_group']
     if add_rule:
-        security_group_rules_client.create_security_group_rule(
-            parent_group_id=security_group['id'], ip_protocol='tcp',
-            from_port=22, to_port=22)
-        security_group_rules_client.create_security_group_rule(
-            parent_group_id=security_group['id'], ip_protocol='icmp',
-            from_port=-1, to_port=-1)
+        if CONF.service_available.neutron:
+            _create_neutron_sec_group_rules(os, security_group)
+        else:
+            security_group_rules_client.create_security_group_rule(
+                parent_group_id=security_group['id'], ip_protocol='tcp',
+                from_port=22, to_port=22)
+            security_group_rules_client.create_security_group_rule(
+                parent_group_id=security_group['id'], ip_protocol='icmp',
+                from_port=-1, to_port=-1)
     LOG.debug("SSH Validation resource security group with tcp and icmp "
               "rules %s created"
               % sg_name)
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 23d7f88..d8dad69 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -15,6 +15,7 @@
 
 from oslo_log import log as logging
 
+from tempest.common import image as common_image
 from tempest import config
 from tempest import exceptions
 from tempest.lib.common.utils import misc as misc_utils
@@ -127,7 +128,11 @@
         # The 'check_image' method is used here because the show_image method
         # returns image details plus the image itself which is very expensive.
         # The 'check_image' method returns just image details.
-        show_image = client.check_image
+        def _show_image_v1(image_id):
+            resp = client.check_image(image_id)
+            return common_image.get_image_meta_from_headers(resp)
+
+        show_image = _show_image_v1
     else:
         show_image = client.show_image
 
diff --git a/tempest/config.py b/tempest/config.py
index 1f88871..a9cf537 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -682,6 +682,24 @@
     cfg.IntOpt('volume_size',
                default=1,
                help='Default size in GB for volumes created by volumes tests'),
+    cfg.StrOpt('min_microversion',
+               default=None,
+               help="Lower version of the test target microversion range. "
+                    "The format is 'X.Y', where 'X' and 'Y' are int values. "
+                    "Tempest selects tests based on the range between "
+                    "min_microversion and max_microversion. "
+                    "If both values are not specified, Tempest avoids tests "
+                    "which require a microversion. Valid values are string "
+                    "with format 'X.Y' or string 'latest'",),
+    cfg.StrOpt('max_microversion',
+               default=None,
+               help="Upper version of the test target microversion range. "
+                    "The format is 'X.Y', where 'X' and 'Y' are int values. "
+                    "Tempest selects tests based on the range between "
+                    "min_microversion and max_microversion. "
+                    "If both values are not specified, Tempest avoids tests "
+                    "which require a microversion. Valid values are string "
+                    "with format 'X.Y' or string 'latest'",),
 ]
 
 volume_feature_group = cfg.OptGroup(name='volume-feature-enabled',
@@ -711,6 +729,9 @@
     cfg.BoolOpt('api_v2',
                 default=True,
                 help="Is the v2 volume API enabled"),
+    cfg.BoolOpt('api_v3',
+                default=False,
+                help="Is the v3 volume API enabled"),
     cfg.BoolOpt('bootable',
                 default=True,
                 help='Update bootable status of a volume '
diff --git a/tempest/lib/services/network/versions_client.py b/tempest/lib/services/network/versions_client.py
new file mode 100644
index 0000000..0202927
--- /dev/null
+++ b/tempest/lib/services/network/versions_client.py
@@ -0,0 +1,45 @@
+# Copyright 2016 VMware, Inc.  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 time
+
+from oslo_serialization import jsonutils as json
+from six.moves import urllib
+
+from tempest.lib.services.network import base
+
+
+class NetworkVersionsClient(base.BaseNetworkClient):
+
+    def list_versions(self):
+        """Do a GET / to fetch available API version information."""
+
+        endpoint = self.base_url
+        url = urllib.parse.urlparse(endpoint)
+        version_url = '%s://%s/' % (url.scheme, url.netloc)
+
+        # Note: we do a raw_request here because we want to use
+        # an unversioned URL, not "v2/$project_id/".
+        # Since raw_request doesn't log anything, we do that too.
+        start = time.time()
+        self._log_request_start('GET', version_url)
+        response, body = self.raw_request(version_url, 'GET')
+        end = time.time()
+        self._log_request('GET', version_url, response,
+                          secs=(end - start), resp_body=body)
+
+        self.response_checker('GET', response, body)
+        self.expected_success(200, response.status)
+        body = json.loads(body)
+        return body
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 4ef9a87..dd6e0e5 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -23,6 +23,7 @@
 import six
 
 from tempest.common import compute
+from tempest.common import image as common_image
 from tempest.common.utils import data_utils
 from tempest.common.utils.linux import remote_client
 from tempest.common import waiters
@@ -468,7 +469,8 @@
             cleanup_args=[_image_client.delete_image, image_id])
         if CONF.image_feature_enabled.api_v1:
             # In glance v1 the additional properties are stored in the headers.
-            snapshot_image = _image_client.check_image(image_id)
+            resp = _image_client.check_image(image_id)
+            snapshot_image = common_image.get_image_meta_from_headers(resp)
             image_props = snapshot_image.get('properties', {})
         else:
             # In glance v2 the additional properties are flattened.
diff --git a/tempest/services/identity/v2/json/services_client.py b/tempest/services/identity/v2/json/services_client.py
index d8be6c6..4a63d56 100644
--- a/tempest/services/identity/v2/json/services_client.py
+++ b/tempest/services/identity/v2/json/services_client.py
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
@@ -20,14 +21,9 @@
 class ServicesClient(rest_client.RestClient):
     api_version = "v2.0"
 
-    def create_service(self, name, type, **kwargs):
+    def create_service(self, **kwargs):
         """Create a service."""
-        post_body = {
-            'name': name,
-            'type': type,
-            'description': kwargs.get('description')
-        }
-        post_body = json.dumps({'OS-KSADM:service': post_body})
+        post_body = json.dumps({'OS-KSADM:service': kwargs})
         resp, body = self.post('/OS-KSADM/services', post_body)
         self.expected_success(200, resp.status)
         body = json.loads(body)
@@ -41,9 +37,12 @@
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
 
-    def list_services(self):
+    def list_services(self, **params):
         """List Service - Returns Services."""
-        resp, body = self.get('/OS-KSADM/services')
+        url = '/OS-KSADM/services'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v2/json/users_client.py b/tempest/services/identity/v2/json/users_client.py
index 6573b6e..1048840 100644
--- a/tempest/services/identity/v2/json/users_client.py
+++ b/tempest/services/identity/v2/json/users_client.py
@@ -11,6 +11,7 @@
 #    under the License.
 
 from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
@@ -18,18 +19,13 @@
 class UsersClient(rest_client.RestClient):
     api_version = "v2.0"
 
-    def create_user(self, name, password, tenant_id, email, **kwargs):
-        """Create a user."""
-        post_body = {
-            'name': name,
-            'password': password,
-            'email': email
-        }
-        if tenant_id is not None:
-            post_body['tenantId'] = tenant_id
-        if kwargs.get('enabled') is not None:
-            post_body['enabled'] = kwargs.get('enabled')
-        post_body = json.dumps({'user': post_body})
+    def create_user(self, **kwargs):
+        """Create a user.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-admin-v2.html#admin-createUser
+        """
+        post_body = json.dumps({'user': kwargs})
         resp, body = self.post('users', post_body)
         self.expected_success(200, resp.status)
         body = json.loads(body)
@@ -48,21 +44,36 @@
         return rest_client.ResponseBody(resp, body)
 
     def show_user(self, user_id):
-        """GET a user."""
+        """GET a user.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-admin-v2.html#admin-showUser
+        """
         resp, body = self.get("users/%s" % user_id)
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
 
     def delete_user(self, user_id):
-        """Delete a user."""
+        """Delete a user.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-admin-v2.html#admin-deleteUser
+        """
         resp, body = self.delete("users/%s" % user_id)
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
 
-    def list_users(self):
-        """Get the list of users."""
-        resp, body = self.get("users")
+    def list_users(self, **params):
+        """Get the list of users.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-admin-v2.html#admin-listUsers
+        """
+        url = "users"
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/image/v1/json/images_client.py b/tempest/services/image/v1/json/images_client.py
index 4ffaf3b..30325c0 100644
--- a/tempest/services/image/v1/json/images_client.py
+++ b/tempest/services/image/v1/json/images_client.py
@@ -31,28 +31,6 @@
 class ImagesClient(rest_client.RestClient):
     api_version = "v1"
 
-    def _image_meta_from_headers(self, headers):
-        meta = {'properties': {}}
-        for key, value in six.iteritems(headers):
-            if key.startswith('x-image-meta-property-'):
-                _key = key[22:]
-                meta['properties'][_key] = value
-            elif key.startswith('x-image-meta-'):
-                _key = key[13:]
-                meta[_key] = value
-
-        for key in ['is_public', 'protected', 'deleted']:
-            if key in meta:
-                meta[key] = meta[key].strip().lower() in ('t', 'true', 'yes',
-                                                          '1')
-        for key in ['size', 'min_ram', 'min_disk']:
-            if key in meta:
-                try:
-                    meta[key] = int(meta[key])
-                except ValueError:
-                    pass
-        return meta
-
     def _image_meta_to_headers(self, fields):
         headers = {}
         fields_copy = copy.deepcopy(fields)
@@ -98,9 +76,13 @@
             self._http = self._get_http()
         return self._http
 
-    def create_image(self, **kwargs):
+    def create_image(self, data=None, **kwargs):
+        """Create an image.
+
+        Available params: http://developer.openstack.org/
+                          api-ref-image-v1.html#createImage-v1
+        """
         headers = {}
-        data = kwargs.pop('data', None)
         headers.update(self._image_meta_to_headers(kwargs))
 
         if data is not None:
@@ -111,9 +93,13 @@
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
 
-    def update_image(self, image_id, **kwargs):
+    def update_image(self, image_id, data=None, **kwargs):
+        """Update an image.
+
+        Available params: http://developer.openstack.org/
+                          api-ref-image-v1.html#updateImage-v1
+        """
         headers = {}
-        data = kwargs.pop('data', None)
         headers.update(self._image_meta_to_headers(kwargs))
 
         if data is not None:
@@ -164,9 +150,8 @@
     def check_image(self, image_id):
         """Check image metadata."""
         url = 'images/%s' % image_id
-        resp, __ = self.head(url)
+        resp, body = self.head(url)
         self.expected_success(200, resp.status)
-        body = self._image_meta_from_headers(resp)
         return rest_client.ResponseBody(resp, body)
 
     def show_image(self, image_id):
@@ -178,7 +163,8 @@
 
     def is_resource_deleted(self, id):
         try:
-            if self.check_image(id)['status'] == 'deleted':
+            resp = self.check_image(id)
+            if resp.response["x-image-meta-status"] == 'deleted':
                 return True
         except lib_exc.NotFound:
             return True
diff --git a/tempest/services/volume/base/base_v3_client.py b/tempest/services/volume/base/base_v3_client.py
new file mode 100644
index 0000000..ad6f760
--- /dev/null
+++ b/tempest/services/volume/base/base_v3_client.py
@@ -0,0 +1,46 @@
+# Copyright 2016 Andrew Kerr
+# 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.lib.common import api_version_utils
+from tempest.lib.common import rest_client
+
+VOLUME_MICROVERSION = None
+
+
+class BaseV3Client(rest_client.RestClient):
+    """Base class to handle Cinder v3 client microversion support."""
+    api_version = 'v3'
+    api_microversion_header_name = 'Openstack-Api-Version'
+
+    def get_headers(self, accept_type=None, send_type=None):
+        headers = super(BaseV3Client, self).get_headers(
+            accept_type=accept_type, send_type=send_type)
+        if VOLUME_MICROVERSION:
+            headers[self.api_microversion_header_name] = ('volume %s' %
+                                                          VOLUME_MICROVERSION)
+        return headers
+
+    def request(self, method, url, extra_headers=False, headers=None,
+                body=None, chunked=False):
+
+        resp, resp_body = super(BaseV3Client, self).request(
+            method, url, extra_headers, headers, body, chunked)
+        if (VOLUME_MICROVERSION and
+            VOLUME_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
+            api_version_utils.assert_version_header_matches_request(
+                self.api_microversion_header_name,
+                'volume %s' % VOLUME_MICROVERSION,
+                resp)
+        return resp, resp_body
diff --git a/tempest/services/volume/v3/__init__.py b/tempest/services/volume/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/volume/v3/__init__.py
diff --git a/tempest/services/volume/v3/json/__init__.py b/tempest/services/volume/v3/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/volume/v3/json/__init__.py
diff --git a/tempest/services/volume/v3/json/messages_client.py b/tempest/services/volume/v3/json/messages_client.py
new file mode 100644
index 0000000..6be6d59
--- /dev/null
+++ b/tempest/services/volume/v3/json/messages_client.py
@@ -0,0 +1,59 @@
+# Copyright 2016 Andrew Kerr
+# 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+from tempest.services.volume.base import base_v3_client
+
+
+class MessagesClient(base_v3_client.BaseV3Client):
+    """Client class to send user messages API requests."""
+
+    def show_message(self, message_id):
+        """Show details for a single message."""
+        url = 'messages/%s' % str(message_id)
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_messages(self):
+        """List all messages."""
+        url = 'messages'
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_message(self, message_id):
+        """Delete a single message."""
+        url = 'messages/%s' % str(message_id)
+        resp, body = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def is_resource_deleted(self, id):
+        try:
+            self.show_message(id)
+        except lib_exc.NotFound:
+            return True
+        return False
+
+    @property
+    def resource_type(self):
+        """Returns the primary type of resource this client works with."""
+        return 'message'
diff --git a/tempest/tests/cmd/test_account_generator.py b/tempest/tests/cmd/test_account_generator.py
index f253802..b3931d1 100755
--- a/tempest/tests/cmd/test_account_generator.py
+++ b/tempest/tests/cmd/test_account_generator.py
@@ -234,7 +234,7 @@
         self.assertIn('admin', resource_types)
         self.assertIn(['fake_operator'], resource_types)
         self.assertIn(['fake_reseller'], resource_types)
-        self.assertIn(['fake_owner'], resource_types)
+        self.assertIn(['fake_owner', 'fake_operator'], resource_types)
         for resource in resources:
             self.assertIsNotNone(resource[1].network)
             self.assertIsNotNone(resource[1].router)
diff --git a/tempest/tests/cmd/test_javelin.py b/tempest/tests/cmd/test_javelin.py
index 2d0256a..50660ff 100644
--- a/tempest/tests/cmd/test_javelin.py
+++ b/tempest/tests/cmd/test_javelin.py
@@ -120,11 +120,12 @@
         fake_tenant_id = self.fake_object['tenant']['id']
         fake_email = "%s@%s" % (self.fake_object['user'], fake_tenant_id)
         mocked_function = self.fake_client.users.create_user
-        mocked_function.assert_called_once_with(self.fake_object['name'],
-                                                self.fake_object['password'],
-                                                fake_tenant_id,
-                                                fake_email,
-                                                enabled=True)
+        mocked_function.assert_called_once_with(
+            name=self.fake_object['name'],
+            password=self.fake_object['password'],
+            tenantId=fake_tenant_id,
+            email=fake_email,
+            enabled=True)
 
     def test_create_user_missing_tenant(self):
         self.useFixture(mockpatch.Patch(
diff --git a/tempest/tests/common/test_image.py b/tempest/tests/common/test_image.py
new file mode 100644
index 0000000..fdd0ae8
--- /dev/null
+++ b/tempest/tests/common/test_image.py
@@ -0,0 +1,40 @@
+# Copyright 2016 NEC Corporation
+# 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 import image
+from tempest.lib.common import rest_client
+from tempest.tests import base
+
+
+class TestImage(base.TestCase):
+
+    def test_get_image_meta_from_headers(self):
+        resp = {
+            'x-image-meta-id': 'ea30c926-0629-4400-bb6e-f8a8da6a4e56',
+            'x-image-meta-owner': '8f421f9470e645b1b10f5d2db7804924',
+            'x-image-meta-status': 'queued',
+            'x-image-meta-name': 'New Http Image'
+        }
+        respbody = rest_client.ResponseBody(resp)
+        observed = image.get_image_meta_from_headers(respbody)
+
+        expected = {
+            'properties': {},
+            'id': 'ea30c926-0629-4400-bb6e-f8a8da6a4e56',
+            'owner': '8f421f9470e645b1b10f5d2db7804924',
+            'status': 'queued',
+            'name': 'New Http Image'
+        }
+        self.assertEqual(expected, observed)
diff --git a/tempest/tests/lib/services/network/test_versions_client.py b/tempest/tests/lib/services/network/test_versions_client.py
new file mode 100644
index 0000000..715176b
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_versions_client.py
@@ -0,0 +1,77 @@
+# Copyright 2016 VMware, Inc.  All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import copy
+
+from tempest.lib.services.network.versions_client import NetworkVersionsClient
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestNetworkVersionsClient(base.BaseServiceTest):
+
+    FAKE_INIT_VERSION = {
+        "version": {
+            "id": "v2.0",
+            "links": [
+                {
+                    "href": "http://openstack.example.com/v2.0/",
+                    "rel": "self"
+                },
+                {
+                    "href": "http://docs.openstack.org/",
+                    "rel": "describedby",
+                    "type": "text/html"
+                }
+            ],
+            "status": "CURRENT",
+            "updated": "2013-07-23T11:33:21Z",
+            "version": "2.0",
+            "min_version": "2.0"
+            }
+        }
+
+    FAKE_VERSIONS_INFO = {
+        "versions": [FAKE_INIT_VERSION["version"]]
+        }
+
+    FAKE_VERSION_INFO = copy.deepcopy(FAKE_INIT_VERSION)
+
+    FAKE_VERSION_INFO["version"]["media-types"] = [
+        {
+            "base": "application/json",
+            "type": "application/vnd.openstack.network+json;version=2.0"
+        }
+        ]
+
+    def setUp(self):
+        super(TestNetworkVersionsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.versions_client = (
+            NetworkVersionsClient
+            (fake_auth, 'compute', 'regionOne'))
+
+    def _test_versions_client(self, bytes_body=False):
+        self.check_service_client_function(
+            self.versions_client.list_versions,
+            'tempest.lib.common.rest_client.RestClient.raw_request',
+            self.FAKE_VERSIONS_INFO,
+            bytes_body,
+            200)
+
+    def test_list_versions_client_with_str_body(self):
+        self._test_versions_client()
+
+    def test_list_versions_client_with_bytes_body(self):
+        self._test_versions_client(bytes_body=True)