Merge "Add Tests for Groups Volume APIs - Part 1"
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index b516055..d6d90ba 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -326,6 +326,14 @@
 
  .. _2.42: http://docs.openstack.org/developer/nova/api_microversion_history.html#maximum-in-ocata
 
+ * `2.47`_
+
+ .. _2.47: http://docs.openstack.org/developer/nova/api_microversion_history.html#id42
+
+ * `2.48`_
+
+ .. _2.48: http://docs.openstack.org/developer/nova/api_microversion_history.html#id43
+
 * Volume
 
  * `3.3`_
diff --git a/releasenotes/notes/add-server-diagnostics-validation-schema-b5a3c55b45aa718a.yaml b/releasenotes/notes/add-server-diagnostics-validation-schema-b5a3c55b45aa718a.yaml
new file mode 100644
index 0000000..e0ac87c
--- /dev/null
+++ b/releasenotes/notes/add-server-diagnostics-validation-schema-b5a3c55b45aa718a.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    Add validation schema for Nova server diagnostics API
\ No newline at end of file
diff --git a/releasenotes/notes/set-cinder-api-v3-option-true-1b3e61e3129b7c00.yaml b/releasenotes/notes/set-cinder-api-v3-option-true-1b3e61e3129b7c00.yaml
new file mode 100644
index 0000000..6959ca7
--- /dev/null
+++ b/releasenotes/notes/set-cinder-api-v3-option-true-1b3e61e3129b7c00.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+  - |
+    The volume config option 'api_v3' default is changed to
+    ``True`` because the volume v3 API is CURRENT.
diff --git a/tempest/api/compute/admin/test_server_diagnostics.py b/tempest/api/compute/admin/test_server_diagnostics.py
new file mode 100644
index 0000000..005efdd
--- /dev/null
+++ b/tempest/api/compute/admin/test_server_diagnostics.py
@@ -0,0 +1,76 @@
+# Copyright 2017 Mirantis 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.
+
+import time
+
+from tempest.api.compute import base
+from tempest.lib import decorators
+
+
+class ServerDiagnosticsTest(base.BaseV2ComputeAdminTest):
+    min_microversion = None
+    max_microversion = '2.47'
+
+    @classmethod
+    def setup_clients(cls):
+        super(ServerDiagnosticsTest, cls).setup_clients()
+        cls.client = cls.os_admin.servers_client
+
+    @decorators.idempotent_id('31ff3486-b8a0-4f56-a6c0-aab460531db3')
+    def test_get_server_diagnostics(self):
+        server_id = self.create_test_server(wait_until='ACTIVE')['id']
+        diagnostics = self.client.show_server_diagnostics(server_id)
+
+        # NOTE(snikitin): Before microversion 2.48 response data from each
+        # hypervisor (libvirt, xen, vmware) was different. None of the fields
+        # were equal. As this test is common for libvirt, xen and vmware CI
+        # jobs we can't check any field in the response because all fields are
+        # different.
+        self.assertNotEmpty(diagnostics)
+
+
+class ServerDiagnosticsV248Test(base.BaseV2ComputeAdminTest):
+    min_microversion = '2.48'
+    max_microversion = 'latest'
+
+    @classmethod
+    def setup_clients(cls):
+        super(ServerDiagnosticsV248Test, cls).setup_clients()
+        cls.client = cls.os_admin.servers_client
+
+    @decorators.idempotent_id('64d0d48c-dff1-11e6-bf01-fe55135034f3')
+    def test_get_server_diagnostics(self):
+        server_id = self.create_test_server(wait_until='ACTIVE')['id']
+        # Response status and filed types will be checked by json schema
+        self.client.show_server_diagnostics(server_id)
+
+        # NOTE(snikitin): This is a special case for Xen hypervisor. In Xen
+        # case we're getting diagnostics stats from the RRDs which are updated
+        # every 5 seconds. It means that diagnostics information may be
+        # incomplete during first 5 seconds of VM life. In such cases methods
+        # which get diagnostics stats from Xen may raise exceptions or
+        # return `NaN` values. Such behavior must be handled correctly.
+        # Response must contain all diagnostics fields (may be with `None`
+        # values) and response status must be 200. Line above checks it by
+        # json schema.
+        time.sleep(10)
+        diagnostics = self.client.show_server_diagnostics(server_id)
+
+        # NOTE(snikitin): After 10 seconds diagnostics fields must contain
+        # not None values. But we will check only "memory_details.maximum"
+        # field because only this field meets all the following conditions:
+        # 1) This field may be unset because of Xen 5 seconds timeout.
+        # 2) This field is present in responses from all three supported
+        #    hypervisors (libvirt, xen, vmware).
+        self.assertIsNotNone(diagnostics['memory_details']['maximum'])
diff --git a/tempest/api/compute/admin/test_server_diagnostics_negative.py b/tempest/api/compute/admin/test_server_diagnostics_negative.py
new file mode 100644
index 0000000..d5b6674
--- /dev/null
+++ b/tempest/api/compute/admin/test_server_diagnostics_negative.py
@@ -0,0 +1,40 @@
+# Copyright 2017 Mirantis 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.compute import base
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+
+class ServerDiagnosticsNegativeTest(base.BaseV2ComputeAdminTest):
+    min_microversion = None
+    max_microversion = '2.47'
+
+    @classmethod
+    def setup_clients(cls):
+        super(ServerDiagnosticsNegativeTest, cls).setup_clients()
+        cls.client = cls.servers_client
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('e84e2234-60d2-42fa-8b30-e2d3049724ac')
+    def test_get_server_diagnostics_by_non_admin(self):
+        # Non-admin user cannot view server diagnostics according to policy
+        server_id = self.create_test_server(wait_until='ACTIVE')['id']
+        self.assertRaises(lib_exc.Forbidden,
+                          self.client.show_server_diagnostics, server_id)
+
+
+class ServerDiagnosticsNegativeV248Test(ServerDiagnosticsNegativeTest):
+    min_microversion = '2.48'
+    max_microversion = 'latest'
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 789049b..0521cca 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -164,17 +164,6 @@
         server = self.client.show_server(self.s1_id)['server']
         self.assertEqual(server['status'], 'ACTIVE')
 
-    @decorators.skip_because(bug="1240043")
-    @decorators.idempotent_id('31ff3486-b8a0-4f56-a6c0-aab460531db3')
-    def test_get_server_diagnostics_by_admin(self):
-        # Retrieve server diagnostics by admin user
-        diagnostic = self.client.show_server_diagnostics(self.s1_id)
-        basic_attrs = ['rx_packets', 'rx_errors', 'rx_drop',
-                       'tx_packets', 'tx_errors', 'tx_drop',
-                       'read_req', 'write_req', 'cpu', 'memory']
-        for key in basic_attrs:
-            self.assertIn(key, str(diagnostic.keys()))
-
     @decorators.idempotent_id('682cb127-e5bb-4f53-87ce-cb9003604442')
     def test_rebuild_server_in_error_state(self):
         # The server in error state should be rebuilt using the provided
diff --git a/tempest/api/compute/admin/test_servers_negative.py b/tempest/api/compute/admin/test_servers_negative.py
index ca53696..9023759 100644
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -108,14 +108,6 @@
                           self.client.reset_state, '999', state='error')
 
     @decorators.attr(type=['negative'])
-    @decorators.idempotent_id('e84e2234-60d2-42fa-8b30-e2d3049724ac')
-    def test_get_server_diagnostics_by_non_admin(self):
-        # Non-admin user can not view server diagnostics according to policy
-        self.assertRaises(lib_exc.Forbidden,
-                          self.non_adm_client.show_server_diagnostics,
-                          self.s1_id)
-
-    @decorators.attr(type=['negative'])
     @decorators.idempotent_id('46a4e1ca-87ae-4d28-987a-1b6b136a0221')
     def test_migrate_non_existent_server(self):
         # migrate a non existent server
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index d893446..429ded5 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -22,6 +22,7 @@
 from tempest.common import waiters
 from tempest import config
 from tempest import exceptions
+from tempest.lib.common import api_version_request
 from tempest.lib.common import api_version_utils
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
@@ -202,6 +203,15 @@
         """
         if 'name' not in kwargs:
             kwargs['name'] = data_utils.rand_name(cls.__name__ + "-server")
+
+        request_version = api_version_request.APIVersionRequest(
+            cls.request_microversion)
+        v2_37_version = api_version_request.APIVersionRequest('2.37')
+
+        # NOTE(snikitin): since microversion v2.37 'networks' field is required
+        if request_version >= v2_37_version and 'networks' not in kwargs:
+            kwargs['networks'] = 'none'
+
         tenant_network = cls.get_tenant_network()
         body, servers = compute.create_test_server(
             cls.os_primary,
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index ea4c141..7fd1dd1 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -134,3 +134,14 @@
         waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
         server = self.client.show_server(server['id'])['server']
         self.assertEqual('2001:2001::3', server['accessIPv6'])
+
+
+class ServerShowV247Test(base.BaseV2ComputeTest):
+    min_microversion = '2.47'
+    max_microversion = 'latest'
+
+    @decorators.idempotent_id('88b0bdb2-494c-11e7-a919-92ebcb67fe33')
+    def test_show_server(self):
+        server = self.create_test_server()
+        # All fields will be checked by API schema
+        self.servers_client.show_server(server['id'])
diff --git a/tempest/api/volume/admin/test_snapshot_manage.py b/tempest/api/volume/admin/test_snapshot_manage.py
index 6c09042..9ff7160 100644
--- a/tempest/api/volume/admin/test_snapshot_manage.py
+++ b/tempest/api/volume/admin/test_snapshot_manage.py
@@ -18,6 +18,7 @@
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
+from tempest.lib import exceptions
 
 CONF = config.CONF
 
@@ -38,8 +39,9 @@
             raise cls.skipException("Manage snapshot tests are disabled")
 
         if len(CONF.volume.manage_snapshot_ref) != 2:
-            raise cls.skipException("Manage snapshot ref is not correctly "
-                                    "configured")
+            msg = ("Manage snapshot ref is not correctly configured, "
+                   "it should be a list of two elements")
+            raise exceptions.InvalidConfiguration(msg)
 
     @decorators.idempotent_id('0132f42d-0147-4b45-8501-cc504bbf7810')
     def test_unmanage_manage_snapshot(self):
diff --git a/tempest/api/volume/admin/test_volume_manage.py b/tempest/api/volume/admin/test_volume_manage.py
index a039085..4b352e0 100644
--- a/tempest/api/volume/admin/test_volume_manage.py
+++ b/tempest/api/volume/admin/test_volume_manage.py
@@ -18,6 +18,7 @@
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
+from tempest.lib import exceptions
 
 CONF = config.CONF
 
@@ -32,8 +33,9 @@
             raise cls.skipException("Manage volume tests are disabled")
 
         if len(CONF.volume.manage_volume_ref) != 2:
-            raise cls.skipException("Manage volume ref is not correctly "
-                                    "configured")
+            msg = ("Manage volume ref is not correctly configured, "
+                   "it should be a list of two elements")
+            raise exceptions.InvalidConfiguration(msg)
 
     @decorators.idempotent_id('70076c71-0ce1-4208-a8ff-36a66e65cc1e')
     def test_unmanage_manage_volume(self):
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 9110c4a..9f467fe 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -204,6 +204,19 @@
                         except Exception:
                             LOG.exception('Deleting server %s failed',
                                           server['id'])
+                    for server in servers:
+                        # NOTE(artom) If the servers were booted with volumes
+                        # and with delete_on_termination=False we need to wait
+                        # for the servers to go away before proceeding with
+                        # cleanup, otherwise we'll attempt to delete the
+                        # volumes while they're still attached to servers that
+                        # are in the process of being deleted.
+                        try:
+                            waiters.wait_for_server_termination(
+                                clients.servers_client, server['id'])
+                        except Exception:
+                            LOG.exception('Server %s failed to delete in time',
+                                          server['id'])
 
     return body, servers
 
@@ -252,16 +265,34 @@
     def __init__(self, client_socket, url):
         """Contructor for the WebSocket wrapper to the socket."""
         self._socket = client_socket
+        # cached stream for early frames.
+        self.cached_stream = b''
         # Upgrade the HTTP connection to a WebSocket
         self._upgrade(url)
 
+    def _recv(self, recv_size):
+        """Wrapper to receive data from the cached stream or socket."""
+        if recv_size <= 0:
+            return None
+
+        data_from_cached = b''
+        data_from_socket = b''
+        if len(self.cached_stream) > 0:
+            read_from_cached = min(len(self.cached_stream), recv_size)
+            data_from_cached += self.cached_stream[:read_from_cached]
+            self.cached_stream = self.cached_stream[read_from_cached:]
+            recv_size -= read_from_cached
+        if recv_size > 0:
+            data_from_socket = self._socket.recv(recv_size)
+        return data_from_cached + data_from_socket
+
     def receive_frame(self):
         """Wrapper for receiving data to parse the WebSocket frame format"""
         # We need to loop until we either get some bytes back in the frame
         # or no data was received (meaning the socket was closed).  This is
         # done to handle the case where we get back some empty frames
         while True:
-            header = self._socket.recv(2)
+            header = self._recv(2)
             # If we didn't receive any data, just return None
             if not header:
                 return None
@@ -270,7 +301,7 @@
             # that only the 2nd byte contains the length, and since the
             # server doesn't do masking, we can just read the data length
             if ord_func(header[1]) & 127 > 0:
-                return self._socket.recv(ord_func(header[1]) & 127)
+                return self._recv(ord_func(header[1]) & 127)
 
     def send_frame(self, data):
         """Wrapper for sending data to add in the WebSocket frame format."""
@@ -318,6 +349,15 @@
         self._socket.sendall(reqdata.encode('utf8'))
         self.response = data = self._socket.recv(4096)
         # Loop through & concatenate all of the data in the response body
-        while data and self.response.find(b'\r\n\r\n') < 0:
+        end_loc = self.response.find(b'\r\n\r\n')
+        while data and end_loc < 0:
             data = self._socket.recv(4096)
             self.response += data
+            end_loc = self.response.find(b'\r\n\r\n')
+
+        if len(self.response) > end_loc + 4:
+            # In case some frames (e.g. the first RFP negotiation) have
+            # arrived, cache it for next reading.
+            self.cached_stream = self.response[end_loc + 4:]
+            # ensure response ends with '\r\n\r\n'.
+            self.response = self.response[:end_loc + 4]
diff --git a/tempest/config.py b/tempest/config.py
index fbe0a1b..7b96281 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -832,7 +832,7 @@
                 default=True,
                 help="Is the v2 volume API enabled"),
     cfg.BoolOpt('api_v3',
-                default=False,
+                default=True,
                 help="Is the v3 volume API enabled")
 ]
 
diff --git a/tempest/lib/api_schema/response/compute/v2_1/servers.py b/tempest/lib/api_schema/response/compute/v2_1/servers.py
index 33a7757..7360396 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/servers.py
@@ -582,3 +582,10 @@
         'required': ['adminPass']
     }
 }
+
+show_server_diagnostics = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object'
+    }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_47/__init__.py b/tempest/lib/api_schema/response/compute/v2_47/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_47/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_47/servers.py b/tempest/lib/api_schema/response/compute/v2_47/servers.py
new file mode 100644
index 0000000..37a084f
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_47/servers.py
@@ -0,0 +1,39 @@
+#    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.api_schema.response.compute.v2_26 import servers as servers226
+
+flavor = {
+    'type': 'object',
+    'properties': {
+        'original_name': {'type': 'string'},
+        'disk': {'type': 'integer'},
+        'ephemeral': {'type': 'integer'},
+        'ram': {'type': 'integer'},
+        'swap': {'type': 'integer'},
+        'vcpus': {'type': 'integer'},
+        'extra_specs': {
+            'type': 'object',
+            'patternProperties': {
+                '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+            }
+        }
+    },
+    'additionalProperties': False,
+    'required': ['original_name', 'disk', 'ephemeral', 'ram', 'swap', 'vcpus']
+}
+
+get_server = copy.deepcopy(servers226.get_server)
+get_server['response_body']['properties']['server'][
+    'properties'].update({'flavor': flavor})
diff --git a/tempest/lib/api_schema/response/compute/v2_48/__init__.py b/tempest/lib/api_schema/response/compute/v2_48/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_48/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_48/servers.py b/tempest/lib/api_schema/response/compute/v2_48/servers.py
new file mode 100644
index 0000000..5904758
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_48/servers.py
@@ -0,0 +1,115 @@
+# Copyright 2017 Mirantis 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+from tempest.lib.api_schema.response.compute.v2_47 import servers as servers247
+
+
+show_server_diagnostics = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'state': {
+                'type': 'string', 'enum': [
+                    'pending', 'running', 'paused', 'shutdown', 'crashed',
+                    'suspended']
+            },
+            'driver': {
+                'type': 'string', 'enum': [
+                    'libvirt', 'xenapi', 'vmwareapi', 'ironic', 'hyperv']
+            },
+            'hypervisor': {'type': ['string', 'null']},
+            'hypervisor_os': {'type': ['string', 'null']},
+            'uptime': {'type': ['integer', 'null']},
+            'config_drive': {'type': 'boolean'},
+            'num_cpus': {'type': 'integer'},
+            'num_nics': {'type': 'integer'},
+            'num_disks': {'type': 'integer'},
+            'memory_details': {
+                'type': 'object',
+                'properties': {
+                    'maximum': {'type': ['integer', 'null']},
+                    'used': {'type': ['integer', 'null']}
+                },
+                'additionalProperties': False,
+                'required': ['maximum', 'used']
+            },
+            'cpu_details': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'id': {'type': ['integer', 'null']},
+                        'time': {'type': ['integer', 'null']},
+                        'utilisation': {'type': ['integer', 'null']}
+                    },
+                    'additionalProperties': False,
+                    'required': ['id', 'time', 'utilisation']
+                }
+            },
+            'nic_details': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'mac_address': {'oneOf': [parameter_types.mac_address,
+                                                  {'type': 'null'}]},
+                        'rx_octets': {'type': ['integer', 'null']},
+                        'rx_errors': {'type': ['integer', 'null']},
+                        'rx_drop': {'type': ['integer', 'null']},
+                        'rx_packets': {'type': ['integer', 'null']},
+                        'rx_rate': {'type': ['integer', 'null']},
+                        'tx_octets': {'type': ['integer', 'null']},
+                        'tx_errors': {'type': ['integer', 'null']},
+                        'tx_drop': {'type': ['integer', 'null']},
+                        'tx_packets': {'type': ['integer', 'null']},
+                        'tx_rate': {'type': ['integer', 'null']}
+                    },
+                    'additionalProperties': False,
+                    'required': ['mac_address', 'rx_octets', 'rx_errors',
+                                 'rx_drop',
+                                 'rx_packets', 'rx_rate', 'tx_octets',
+                                 'tx_errors',
+                                 'tx_drop', 'tx_packets', 'tx_rate']
+                }
+            },
+            'disk_details': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'read_bytes': {'type': ['integer', 'null']},
+                        'read_requests': {'type': ['integer', 'null']},
+                        'write_bytes': {'type': ['integer', 'null']},
+                        'write_requests': {'type': ['integer', 'null']},
+                        'errors_count': {'type': ['integer', 'null']}
+                    },
+                    'additionalProperties': False,
+                    'required': ['read_bytes', 'read_requests', 'write_bytes',
+                                 'write_requests', 'errors_count']
+                }
+            }
+        },
+        'additionalProperties': False,
+        'required': [
+            'state', 'driver', 'hypervisor', 'hypervisor_os', 'uptime',
+            'config_drive', 'num_cpus', 'num_nics', 'num_disks',
+            'memory_details', 'cpu_details', 'nic_details', 'disk_details'],
+    }
+}
+
+get_server = copy.deepcopy(servers247.get_server)
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index ff65b25..598d5a6 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -27,6 +27,8 @@
 from tempest.lib.api_schema.response.compute.v2_19 import servers as schemav219
 from tempest.lib.api_schema.response.compute.v2_26 import servers as schemav226
 from tempest.lib.api_schema.response.compute.v2_3 import servers as schemav23
+from tempest.lib.api_schema.response.compute.v2_47 import servers as schemav247
+from tempest.lib.api_schema.response.compute.v2_48 import servers as schemav248
 from tempest.lib.api_schema.response.compute.v2_6 import servers as schemav26
 from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29
 from tempest.lib.common import rest_client
@@ -43,7 +45,9 @@
         {'min': '2.9', 'max': '2.15', 'schema': schemav29},
         {'min': '2.16', 'max': '2.18', 'schema': schemav216},
         {'min': '2.19', 'max': '2.25', 'schema': schemav219},
-        {'min': '2.26', 'max': None, 'schema': schemav226}]
+        {'min': '2.26', 'max': '2.46', 'schema': schemav226},
+        {'min': '2.47', 'max': '2.47', 'schema': schemav247},
+        {'min': '2.48', 'max': None, 'schema': schemav248}]
 
     def __init__(self, auth_provider, service, region,
                  enable_instance_password=True, **kwargs):
@@ -656,7 +660,10 @@
     def show_server_diagnostics(self, server_id):
         """Get the usage data for a server."""
         resp, body = self.get("servers/%s/diagnostics" % server_id)
-        return rest_client.ResponseBody(resp, json.loads(body))
+        body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
+        self.validate_response(schema.show_server_diagnostics, resp, body)
+        return rest_client.ResponseBody(resp, body)
 
     def list_instance_actions(self, server_id):
         """List the provided server action."""
diff --git a/tempest/lib/services/volume/v2/encryption_types_client.py b/tempest/lib/services/volume/v2/encryption_types_client.py
index eeff537..20f3356 100755
--- a/tempest/lib/services/volume/v2/encryption_types_client.py
+++ b/tempest/lib/services/volume/v2/encryption_types_client.py
@@ -50,9 +50,9 @@
     def create_encryption_type(self, volume_type_id, **kwargs):
         """Create encryption type.
 
-        TODO: Current api-site doesn't contain this API description.
-        After fixing the api-site, we need to fix here also for putting
-        the link to api-site.
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v2/#create-an-encryption-type-for-v2
         """
         url = "/types/%s/encryption" % volume_type_id
         post_body = json.dumps({'encryption': kwargs})
@@ -71,9 +71,9 @@
     def update_encryption_type(self, volume_type_id, **kwargs):
         """Update an encryption type for an existing volume type.
 
-        TODO: Current api-site doesn't contain this API description.
-        After fixing the api-site, we need to fix here also for putting
-        the link to api-site.
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v2/#update-an-encryption-type-for-v2
         """
         url = "/types/%s/encryption/provider" % volume_type_id
         put_body = json.dumps({'encryption': kwargs})
diff --git a/tempest/lib/services/volume/v2/hosts_client.py b/tempest/lib/services/volume/v2/hosts_client.py
index 8fcf4d0..f44bda3 100644
--- a/tempest/lib/services/volume/v2/hosts_client.py
+++ b/tempest/lib/services/volume/v2/hosts_client.py
@@ -24,8 +24,12 @@
     api_version = "v2"
 
     def list_hosts(self, **params):
-        """Lists all hosts."""
+        """Lists all hosts.
 
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v2/#list-all-hosts
+        """
         url = 'os-hosts'
         if params:
             url += '?%s' % urllib.urlencode(params)
diff --git a/tempest/lib/services/volume/v2/snapshots_client.py b/tempest/lib/services/volume/v2/snapshots_client.py
index 983ed89..5f4e7de 100644
--- a/tempest/lib/services/volume/v2/snapshots_client.py
+++ b/tempest/lib/services/volume/v2/snapshots_client.py
@@ -124,7 +124,12 @@
         return rest_client.ResponseBody(resp, body)
 
     def create_snapshot_metadata(self, snapshot_id, metadata):
-        """Create metadata for the snapshot."""
+        """Create metadata for the snapshot.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/block-storage/v2/#create-snapshot-metadata
+        """
         put_body = json.dumps({'metadata': metadata})
         url = "snapshots/%s/metadata" % snapshot_id
         resp, body = self.post(url, put_body)
diff --git a/tempest/lib/services/volume/v2/volumes_client.py b/tempest/lib/services/volume/v2/volumes_client.py
index f4e7c6a..86e3836 100644
--- a/tempest/lib/services/volume/v2/volumes_client.py
+++ b/tempest/lib/services/volume/v2/volumes_client.py
@@ -72,6 +72,10 @@
         """List all the volumes created.
 
         Params can be a string (must be urlencoded) or a dictionary.
+        For a full list of available parameters, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/block-storage/v2/#list-volumes-with-details
+        http://developer.openstack.org/api-ref/block-storage/v2/#list-volumes
         """
         url = 'volumes'
         if detail:
@@ -155,7 +159,12 @@
         return rest_client.ResponseBody(resp, body)
 
     def set_bootable_volume(self, volume_id, **kwargs):
-        """set a bootable flag for a volume - true or false."""
+        """Set a bootable flag for a volume - true or false.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-bootable-status
+        """
         post_body = json.dumps({'os-set_bootable': kwargs})
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
@@ -239,7 +248,12 @@
         return rest_client.ResponseBody(resp, body)
 
     def create_volume_metadata(self, volume_id, metadata):
-        """Create metadata for the volume."""
+        """Create metadata for the volume.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/block-storage/v2/#create-volume-metadata
+        """
         put_body = json.dumps({'metadata': metadata})
         url = "volumes/%s/metadata" % volume_id
         resp, body = self.post(url, put_body)
@@ -256,7 +270,12 @@
         return rest_client.ResponseBody(resp, body)
 
     def update_volume_metadata(self, volume_id, metadata):
-        """Update metadata for the volume."""
+        """Update metadata for the volume.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-metadata
+        """
         put_body = json.dumps({'metadata': metadata})
         url = "volumes/%s/metadata" % volume_id
         resp, body = self.put(url, put_body)
@@ -281,7 +300,12 @@
         return rest_client.ResponseBody(resp, body)
 
     def retype_volume(self, volume_id, **kwargs):
-        """Updates volume with new volume type."""
+        """Updates volume with new volume type.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v2/#retype-volume
+        """
         post_body = json.dumps({'os-retype': kwargs})
         resp, body = self.post('volumes/%s/action' % volume_id, post_body)
         self.expected_success(202, resp.status)
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 640dcd4..b0e74fb 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -199,7 +199,9 @@
         with mock.patch.object(verify_tempest_config,
                                'print_and_or_update') as print_mock:
             verify_tempest_config.verify_cinder_api_versions(fake_os, True)
-        print_mock.assert_not_called()
+        print_mock.assert_any_call('api_v3', 'volume-feature-enabled',
+                                   False, True)
+        self.assertEqual(1, print_mock.call_count)
 
     @mock.patch('tempest.lib.common.http.ClosingHttp.request')
     def test_verify_cinder_api_versions_no_v2(self, mock_request):
@@ -215,9 +217,7 @@
             verify_tempest_config.verify_cinder_api_versions(fake_os, True)
         print_mock.assert_any_call('api_v2', 'volume-feature-enabled',
                                    False, True)
-        print_mock.assert_any_call('api_v3', 'volume-feature-enabled',
-                                   True, True)
-        self.assertEqual(2, print_mock.call_count)
+        self.assertEqual(1, print_mock.call_count)
 
     @mock.patch('tempest.lib.common.http.ClosingHttp.request')
     def test_verify_cinder_api_versions_no_v1(self, mock_request):
@@ -231,9 +231,7 @@
         with mock.patch.object(verify_tempest_config,
                                'print_and_or_update') as print_mock:
             verify_tempest_config.verify_cinder_api_versions(fake_os, True)
-        print_mock.assert_any_call('api_v3', 'volume-feature-enabled',
-                                   True, True)
-        self.assertEqual(1, print_mock.call_count)
+        print_mock.assert_not_called()
 
     def test_verify_glance_version_no_v2_with_v1_1(self):
         def fake_get_versions():
diff --git a/tempest/tests/common/test_compute.py b/tempest/tests/common/test_compute.py
new file mode 100644
index 0000000..c108be9
--- /dev/null
+++ b/tempest/tests/common/test_compute.py
@@ -0,0 +1,106 @@
+# Copyright 2017 Citrix Systems
+# 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 six.moves.urllib import parse as urlparse
+
+import mock
+
+from tempest.common import compute
+from tempest.tests import base
+
+
+class TestCompute(base.TestCase):
+    def setUp(self):
+        super(TestCompute, self).setUp()
+        self.client_sock = mock.Mock()
+        self.url = urlparse.urlparse("http://www.fake.com:80")
+
+    def test_rfp_frame_not_cached(self):
+        # rfp negotiation frame arrived separately after upgrade
+        # response, so it's not cached.
+        RFP_VERSION = b'RFB.003.003\x0a'
+        rfp_frame_header = b'\x82\x0c'
+
+        self.client_sock.recv.side_effect = [
+            b'fake response start\r\n',
+            b'fake response end\r\n\r\n',
+            rfp_frame_header,
+            RFP_VERSION]
+        expect_response = b'fake response start\r\nfake response end\r\n\r\n'
+
+        webSocket = compute._WebSocket(self.client_sock, self.url)
+
+        self.assertEqual(webSocket.response, expect_response)
+        # no cache
+        self.assertEqual(webSocket.cached_stream, b'')
+        self.client_sock.recv.assert_has_calls([mock.call(4096),
+                                                mock.call(4096)])
+
+        self.client_sock.recv.reset_mock()
+        recv_version = webSocket.receive_frame()
+
+        self.assertEqual(recv_version, RFP_VERSION)
+        self.client_sock.recv.assert_has_calls([mock.call(2),
+                                                mock.call(12)])
+
+    def test_rfp_frame_fully_cached(self):
+        RFP_VERSION = b'RFB.003.003\x0a'
+        rfp_version_frame = b'\x82\x0c%s' % RFP_VERSION
+
+        self.client_sock.recv.side_effect = [
+            b'fake response start\r\n',
+            b'fake response end\r\n\r\n%s' % rfp_version_frame]
+        expect_response = b'fake response start\r\nfake response end\r\n\r\n'
+        webSocket = compute._WebSocket(self.client_sock, self.url)
+
+        self.client_sock.recv.assert_has_calls([mock.call(4096),
+                                                mock.call(4096)])
+        self.assertEqual(webSocket.response, expect_response)
+        self.assertEqual(webSocket.cached_stream, rfp_version_frame)
+
+        self.client_sock.recv.reset_mock()
+        recv_version = webSocket.receive_frame()
+
+        self.client_sock.recv.assert_not_called()
+        self.assertEqual(recv_version, RFP_VERSION)
+        # cached_stream should be empty in the end.
+        self.assertEqual(webSocket.cached_stream, b'')
+
+    def test_rfp_frame_partially_cached(self):
+        RFP_VERSION = b'RFB.003.003\x0a'
+        rfp_version_frame = b'\x82\x0c%s' % RFP_VERSION
+        frame_part1 = rfp_version_frame[:6]
+        frame_part2 = rfp_version_frame[6:]
+
+        self.client_sock.recv.side_effect = [
+            b'fake response start\r\n',
+            b'fake response end\r\n\r\n%s' % frame_part1,
+            frame_part2]
+        expect_response = b'fake response start\r\nfake response end\r\n\r\n'
+        webSocket = compute._WebSocket(self.client_sock, self.url)
+
+        self.client_sock.recv.assert_has_calls([mock.call(4096),
+                                                mock.call(4096)])
+        self.assertEqual(webSocket.response, expect_response)
+        self.assertEqual(webSocket.cached_stream, frame_part1)
+
+        self.client_sock.recv.reset_mock()
+
+        recv_version = webSocket.receive_frame()
+
+        self.client_sock.recv.assert_called_once_with(len(frame_part2))
+        self.assertEqual(recv_version, RFP_VERSION)
+        # cached_stream should be empty in the end.
+        self.assertEqual(webSocket.cached_stream, b'')
diff --git a/tempest/tests/lib/services/network/test_quotas_client.py b/tempest/tests/lib/services/network/test_quotas_client.py
new file mode 100644
index 0000000..e76bc9c
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_quotas_client.py
@@ -0,0 +1,99 @@
+# Copyright 2017 AT&T 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.lib.services.network import quotas_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestQuotasClient(base.BaseServiceTest):
+
+    FAKE_QUOTAS = {
+        "quotas": [
+            {
+                "floatingip": 50,
+                "network": 15,
+                "port": 50,
+                "project_id": "bab7d5c60cd041a0a36f7c4b6e1dd978",
+                "rbac_policy": -1,
+                "router": 10,
+                "security_group": 10,
+                "security_group_rule": 100,
+                "subnet": 10,
+                "subnetpool": -1,
+                "tenant_id": "bab7d5c60cd041a0a36f7c4b6e1dd978"
+            }
+        ]
+    }
+
+    FAKE_QUOTA_TENANT_ID = "bab7d5c60cd041a0a36f7c4b6e1dd978"
+
+    def setUp(self):
+        super(TestQuotasClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.quotas_client = quotas_client.QuotasClient(
+            fake_auth, "network", "regionOne")
+
+    def _test_list_quotas(self, bytes_body=False):
+        self.check_service_client_function(
+            self.quotas_client.list_quotas,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_QUOTAS,
+            bytes_body,
+            200)
+
+    def _test_show_quotas(self, bytes_body=False):
+        self.check_service_client_function(
+            self.quotas_client.show_quotas,
+            "tempest.lib.common.rest_client.RestClient.get",
+            {"quota": self.FAKE_QUOTAS["quotas"][0]},
+            bytes_body,
+            200,
+            tenant_id=self.FAKE_QUOTA_TENANT_ID)
+
+    def _test_update_quotas(self, bytes_body=False):
+        self.check_service_client_function(
+            self.quotas_client.update_quotas,
+            "tempest.lib.common.rest_client.RestClient.put",
+            {"quota": self.FAKE_QUOTAS["quotas"][0]},
+            bytes_body,
+            200,
+            tenant_id=self.FAKE_QUOTA_TENANT_ID)
+
+    def test_reset_quotas(self):
+        self.check_service_client_function(
+            self.quotas_client.reset_quotas,
+            "tempest.lib.common.rest_client.RestClient.delete",
+            {},
+            status=204,
+            tenant_id=self.FAKE_QUOTA_TENANT_ID)
+
+    def test_list_quotas_with_str_body(self):
+        self._test_list_quotas()
+
+    def test_list_quotas_with_bytes_body(self):
+        self._test_list_quotas(bytes_body=True)
+
+    def test_show_quotas_with_str_body(self):
+        self._test_show_quotas()
+
+    def test_show_quotas_with_bytes_body(self):
+        self._test_show_quotas(bytes_body=True)
+
+    def test_update_quotas_with_str_body(self):
+        self._test_update_quotas()
+
+    def test_update_quotas_with_bytes_body(self):
+        self._test_update_quotas(bytes_body=True)
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index 238a976..a33962b 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -72,7 +72,13 @@
 # json library won't choke.
 projects = sorted(filter(is_in_openstack_namespace, json.loads(r.read()[4:])))
 
-found_plugins = list(filter(has_tempest_plugin, projects))
+# Retrieve projects having no deb, ui or spec namespace as those namespaces
+# do not contains tempest plugins.
+projects_list = [i for i in projects if not (i.startswith('openstack/deb-') or
+                                             i.endswith('-ui') or
+                                             i.endswith('-specs'))]
+
+found_plugins = list(filter(has_tempest_plugin, projects_list))
 
 # Every element of the found_plugins list begins with "openstack/".
 # We drop those initial 10 octets when printing the list.