Merge "Switch to decorators.idempotent_id on identity"
diff --git a/releasenotes/notes/jsonschema-validator-2377ba131e12d3c7.yaml b/releasenotes/notes/jsonschema-validator-2377ba131e12d3c7.yaml
new file mode 100644
index 0000000..8817ed4
--- /dev/null
+++ b/releasenotes/notes/jsonschema-validator-2377ba131e12d3c7.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - Added customized JSON schema format checker for 'date-time' format.
+    Compute response schema will be validated against customized format
+    checker.
diff --git a/tempest/api/compute/servers/test_novnc.py b/tempest/api/compute/servers/test_novnc.py
new file mode 100644
index 0000000..d10f370
--- /dev/null
+++ b/tempest/api/compute/servers/test_novnc.py
@@ -0,0 +1,241 @@
+# Copyright 2016 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import socket
+import struct
+
+import six
+from six.moves.urllib import parse as urlparse
+import urllib3
+
+from tempest.api.compute import base
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class NoVNCConsoleTestJSON(base.BaseV2ComputeTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(NoVNCConsoleTestJSON, cls).skip_checks()
+        if not CONF.compute_feature_enabled.vnc_console:
+            raise cls.skipException('VNC Console feature is disabled.')
+
+    def setUp(self):
+        super(NoVNCConsoleTestJSON, self).setUp()
+        self._websocket = None
+
+    def tearDown(self):
+        self.server_check_teardown()
+        super(NoVNCConsoleTestJSON, self).tearDown()
+        if self._websocket is not None:
+            self._websocket.close()
+
+    @classmethod
+    def setup_clients(cls):
+        super(NoVNCConsoleTestJSON, cls).setup_clients()
+        cls.client = cls.servers_client
+
+    @classmethod
+    def resource_setup(cls):
+        super(NoVNCConsoleTestJSON, cls).resource_setup()
+        cls.server = cls.create_test_server(wait_until="ACTIVE")
+
+    def _validate_novnc_html(self, vnc_url):
+        """Verify we can connect to novnc and get back the javascript."""
+        resp = urllib3.PoolManager().request('GET', vnc_url)
+        # Make sure that the GET request was accepted by the novncproxy
+        self.assertEqual(resp.status, 200, 'Got a Bad HTTP Response on the '
+                         'initial call: ' + str(resp.status))
+        # Do some basic validation to make sure it is an expected HTML document
+        self.assertTrue('<html>' in resp.data and '</html>' in resp.data,
+                        'Not a valid html document in the response.')
+        # Just try to make sure we got JavaScript back for noVNC, since we
+        # won't actually use it since not inside of a browser
+        self.assertTrue('noVNC' in resp.data and '<script' in resp.data,
+                        'Not a valid noVNC javascript html document.')
+
+    def _validate_rfb_negotiation(self):
+        """Verify we can connect to novnc and do the websocket connection."""
+        # Turn the Socket into a WebSocket to do the communication
+        data = self._websocket.receive_frame()
+        self.assertFalse(data is None or len(data) == 0,
+                         'Token must be invalid because the connection '
+                         'closed.')
+        # Parse the RFB version from the data to make sure it is valid
+        # and greater than or equal to 3.3
+        version = float("%d.%d" % (int(data[4:7], base=10),
+                                   int(data[8:11], base=10)))
+        self.assertTrue(version >= 3.3, 'Bad RFB Version: ' + str(version))
+        # Send our RFB version to the server, which we will just go with 3.3
+        self._websocket.send_frame(str(data))
+        # Get the sever authentication type and make sure None is supported
+        data = self._websocket.receive_frame()
+        self.assertIsNotNone(data, 'Expected authentication type None.')
+        self.assertGreaterEqual(
+            len(data), 2, 'Expected authentication type None.')
+        self.assertIn(
+            1, [ord(data[i + 1]) for i in range(ord(data[0]))],
+            'Expected authentication type None.')
+        # Send to the server that we only support authentication type None
+        self._websocket.send_frame(six.int2byte(1))
+        # The server should send 4 bytes of 0's if security handshake succeeded
+        data = self._websocket.receive_frame()
+        self.assertEqual(
+            len(data), 4, 'Server did not think security was successful.')
+        self.assertEqual(
+            [ord(i) for i in data], [0, 0, 0, 0],
+            'Server did not think security was successful.')
+        # Say to leave the desktop as shared as part of client initialization
+        self._websocket.send_frame(six.int2byte(1))
+        # Get the server initialization packet back and make sure it is the
+        # right structure where bytes 20-24 is the name length and
+        # 24-N is the name
+        data = self._websocket.receive_frame()
+        data_length = len(data) if data is not None else 0
+        self.assertFalse(data_length <= 24 or
+                         data_length != (struct.unpack(">L",
+                                         data[20:24])[0] + 24),
+                         'Server initialization was not the right format.')
+        # Since the rest of the data on the screen is arbitrary, we will
+        # close the socket and end our validation of the data at this point
+        # Assert that the latest check was false, meaning that the server
+        # initialization was the right format
+        self.assertFalse(data_length <= 24 or
+                         data_length != (struct.unpack(">L",
+                                         data[20:24])[0] + 24))
+
+    def _validate_websocket_upgrade(self):
+        self.assertTrue(
+            self._websocket.response.startswith('HTTP/1.1 101 Switching '
+                                                'Protocols\r\n'),
+            'Did not get the expected 101 on the websockify call: '
+            + str(len(self._websocket.response)))
+        self.assertTrue(
+            self._websocket.response.find('Server: WebSockify') > 0,
+            'Did not get the expected WebSocket HTTP Response.')
+
+    def _create_websocket(self, url):
+        url = urlparse.urlparse(url)
+        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        client_socket.connect((url.hostname, url.port))
+        # Turn the Socket into a WebSocket to do the communication
+        return _WebSocket(client_socket, url)
+
+    @test.idempotent_id('c640fdff-8ab4-45a4-a5d8-7e6146cbd0dc')
+    def test_novnc(self):
+        body = self.client.get_vnc_console(self.server['id'],
+                                           type='novnc')['console']
+        self.assertEqual('novnc', body['type'])
+        # Do the initial HTTP Request to novncproxy to get the NoVNC JavaScript
+        self._validate_novnc_html(body['url'])
+        # Do the WebSockify HTTP Request to novncproxy to do the RFB connection
+        self._websocket = self._create_websocket(body['url'])
+        # Validate that we succesfully connected and upgraded to Web Sockets
+        self._validate_websocket_upgrade()
+        # Validate the RFB Negotiation to determine if a valid VNC session
+        self._validate_rfb_negotiation()
+
+    @test.idempotent_id('f9c79937-addc-4aaa-9e0e-841eef02aeb7')
+    def test_novnc_bad_token(self):
+        body = self.client.get_vnc_console(self.server['id'],
+                                           type='novnc')['console']
+        self.assertEqual('novnc', body['type'])
+        # Do the WebSockify HTTP Request to novncproxy with a bad token
+        url = body['url'].replace('token=', 'token=bad')
+        self._websocket = self._create_websocket(url)
+        # Make sure the novncproxy rejected the connection and closed it
+        data = self._websocket.receive_frame()
+        self.assertTrue(data is None or len(data) == 0,
+                        "The novnc proxy actually sent us some data, but we "
+                        "expected it to close the connection.")
+
+
+class _WebSocket(object):
+    def __init__(self, client_socket, url):
+        """Contructor for the WebSocket wrapper to the socket."""
+        self._socket = client_socket
+        # Upgrade the HTTP connection to a WebSocket
+        self._upgrade(url)
+
+    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)
+            # If we didn't receive any data, just return None
+            if len(header) == 0:
+                return None
+            # We will make the assumption that we are only dealing with
+            # frames less than 125 bytes here (for the negotiation) and
+            # 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(header[1]) & 127 > 0:
+                return self._socket.recv(ord(header[1]) & 127)
+
+    def send_frame(self, data):
+        """Wrapper for sending data to add in the WebSocket frame format."""
+        frame_bytes = list()
+        # For the first byte, want to say we are sending binary data (130)
+        frame_bytes.append(130)
+        # Only sending negotiation data so don't need to worry about > 125
+        # We do need to add the bit that says we are masking the data
+        frame_bytes.append(len(data) | 128)
+        # We don't really care about providing a random mask for security
+        # So we will just hard-code a value since a test program
+        mask = [7, 2, 1, 9]
+        for i in range(len(mask)):
+            frame_bytes.append(mask[i])
+        # Mask each of the actual data bytes that we are going to send
+        for i in range(len(data)):
+            frame_bytes.append(ord(data[i]) ^ mask[i % 4])
+        # Convert our integer list to a binary array of bytes
+        frame_bytes = struct.pack('!%iB' % len(frame_bytes), * frame_bytes)
+        self._socket.sendall(frame_bytes)
+
+    def close(self):
+        """Helper method to close the connection."""
+        # Close down the real socket connection and exit the test program
+        if self._socket is not None:
+            self._socket.shutdown(1)
+            self._socket.close()
+            self._socket = None
+
+    def _upgrade(self, url):
+        """Upgrade the HTTP connection to a WebSocket and verify."""
+        # The real request goes to the /websockify URI always
+        reqdata = 'GET /websockify HTTP/1.1\r\n'
+        reqdata += 'Host: %s:%s\r\n' % (url.hostname, url.port)
+        # Tell the HTTP Server to Upgrade the connection to a WebSocket
+        reqdata += 'Upgrade: websocket\r\nConnection: Upgrade\r\n'
+        # The token=xxx is sent as a Cookie not in the URI
+        reqdata += 'Cookie: %s\r\n' % url.query
+        # Use a hard-coded WebSocket key since a test program
+        reqdata += 'Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n'
+        reqdata += 'Sec-WebSocket-Version: 13\r\n'
+        # We are choosing to use binary even though browser may do Base64
+        reqdata += 'Sec-WebSocket-Protocol: binary\r\n\r\n'
+        # Send the HTTP GET request and get the response back
+        self._socket.sendall(reqdata)
+        self.response = data = self._socket.recv(4096)
+        # Loop through & concatenate all of the data in the response body
+        while len(data) > 0 and self.response.find('\r\n\r\n') < 0:
+            data = self._socket.recv(4096)
+            self.response += data
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 853d2ff..b22a434 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -178,6 +178,7 @@
                           self.client.rebuild_server,
                           server['id'], self.image_ref_alt)
 
+    @test.related_bug('1660878', status_code=409)
     @test.attr(type=['negative'])
     @decorators.idempotent_id('581a397d-5eab-486f-9cf9-1014bbd4c984')
     def test_reboot_deleted_server(self):
diff --git a/tempest/api/volume/admin/test_volume_types_negative.py b/tempest/api/volume/admin/test_volume_types_negative.py
index 857e7d2..5332f1e 100644
--- a/tempest/api/volume/admin/test_volume_types_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_negative.py
@@ -51,6 +51,14 @@
                           self.admin_volume_types_client.delete_volume_type,
                           data_utils.rand_uuid())
 
+    @test.idempotent_id('8c09f849-f225-4d78-ba87-bffd9a5e0c6f')
+    def test_create_volume_with_private_volume_type(self):
+        # Should not be able to create volume with private volume type.
+        params = {'os-volume-type-access:is_public': False}
+        volume_type = self.create_volume_type(**params)
+        self.assertRaises(lib_exc.NotFound,
+                          self.create_volume, volume_type=volume_type['id'])
+
 
 class VolumeTypesNegativeV1Test(VolumeTypesNegativeV2Test):
     _api_version = 1
diff --git a/tempest/api/volume/v2/test_volumes_snapshots_list.py b/tempest/api/volume/v2/test_volumes_snapshots_list.py
new file mode 100644
index 0000000..f389b59
--- /dev/null
+++ b/tempest/api/volume/v2/test_volumes_snapshots_list.py
@@ -0,0 +1,94 @@
+# Copyright 2016 Red Hat, 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.
+
+from tempest.api.volume import base
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class VolumesV2SnapshotListTestJSON(base.BaseVolumeTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(VolumesV2SnapshotListTestJSON, cls).skip_checks()
+        if not CONF.volume_feature_enabled.snapshot:
+            raise cls.skipException("Cinder volume snapshots are disabled")
+
+    @classmethod
+    def resource_setup(cls):
+        super(VolumesV2SnapshotListTestJSON, cls).resource_setup()
+        cls.snapshot_id_list = []
+        # Create a volume
+        cls.volume_origin = cls.create_volume()
+        cls.name_field = cls.special_fields['name_field']
+        # Create 3 snapshots
+        for _ in range(3):
+            snapshot = cls.create_snapshot(cls.volume_origin['id'])
+            cls.snapshot_id_list.append(snapshot['id'])
+
+    def _list_snapshots_param_sort(self, sort_key, sort_dir):
+        """list snapshots by sort param"""
+        snap_list = self.snapshots_client.list_snapshots(
+            sort_key=sort_key, sort_dir=sort_dir)['snapshots']
+        self.assertNotEmpty(snap_list)
+        if sort_key is 'display_name':
+            sort_key = 'name'
+        # Note: On Cinder V2 API, 'display_name' works as a sort key
+        # on a request, a volume name appears as 'name' on the response.
+        # So Tempest needs to change the key name here for this inconsistent
+        # API behavior.
+        sorted_list = [snapshot[sort_key] for snapshot in snap_list]
+        msg = 'The list of snapshots was not sorted correctly.'
+        self.assertEqual(sorted(sorted_list, reverse=(sort_dir == 'desc')),
+                         sorted_list, msg)
+
+    @test.idempotent_id('c5513ada-64c1-4d28-83b9-af3307ec1388')
+    def test_snapshot_list_param_sort_id_asc(self):
+        self._list_snapshots_param_sort(sort_key='id', sort_dir='asc')
+
+    @test.idempotent_id('8a7fe058-0b41-402a-8afd-2dbc5a4a718b')
+    def test_snapshot_list_param_sort_id_desc(self):
+        self._list_snapshots_param_sort(sort_key='id', sort_dir='desc')
+
+    @test.idempotent_id('4052c3a0-2415-440a-a8cc-305a875331b0')
+    def test_snapshot_list_param_sort_created_at_asc(self):
+        self._list_snapshots_param_sort(sort_key='created_at', sort_dir='asc')
+
+    @test.idempotent_id('dcbbe24a-f3c0-4ec8-9274-55d48db8d1cf')
+    def test_snapshot_list_param_sort_created_at_desc(self):
+        self._list_snapshots_param_sort(sort_key='created_at', sort_dir='desc')
+
+    @test.idempotent_id('d58b5fed-0c37-42d3-8c5d-39014ac13c00')
+    def test_snapshot_list_param_sort_name_asc(self):
+        self._list_snapshots_param_sort(sort_key='display_name',
+                                        sort_dir='asc')
+
+    @test.idempotent_id('96ba6f4d-1f18-47e1-b4bc-76edc6c21250')
+    def test_snapshot_list_param_sort_name_desc(self):
+        self._list_snapshots_param_sort(sort_key='display_name',
+                                        sort_dir='desc')
+
+    @test.idempotent_id('05489dde-44bc-4961-a1f5-3ce7ee7824f7')
+    def test_snapshot_list_param_marker(self):
+        # The list of snapshots should end before the provided marker
+        params = {'marker': self.snapshot_id_list[1]}
+        snap_list = self.snapshots_client.list_snapshots(**params)['snapshots']
+        fetched_list_id = [snap['id'] for snap in snap_list]
+        # Verify the list of snapshots ends before the provided
+        # marker(second snapshot), therefore only the first snapshot
+        # should displayed.
+        self.assertEqual(self.snapshot_id_list[:1], fetched_list_id)
diff --git a/tempest/clients.py b/tempest/clients.py
index 18116f3..cdd6925 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -31,14 +31,6 @@
 
     default_params = config.service_client_config()
 
-    # TODO(jordanP): remove this once no Tempest plugin use that class
-    # variable.
-    default_params_with_timeout_values = {
-        'build_interval': CONF.compute.build_interval,
-        'build_timeout': CONF.compute.build_timeout
-    }
-    default_params_with_timeout_values.update(default_params)
-
     def __init__(self, credentials, scope='project'):
         """Initialization of Manager class.
 
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 4f2fe67..01de704 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -30,8 +30,7 @@
 def create_test_server(clients, validatable=False, validation_resources=None,
                        tenant_network=None, wait_until=None,
                        volume_backed=False, name=None, flavor=None,
-                       image_id=None, delete_vol_on_termination=True,
-                       **kwargs):
+                       image_id=None, **kwargs):
     """Common wrapper utility returning a test server.
 
     This method is a common wrapper returning a test server that can be
@@ -44,16 +43,30 @@
     :param tenant_network: Tenant network to be used for creating a server.
     :param wait_until: Server status to wait for the server to reach after
         its creation.
-    :param volume_backed: Whether the instance is volume backed or not.
+    :param volume_backed: Whether the server is volume backed or not.
+                          If this is true, a volume will be created and
+                          create server will be requested with
+                          'block_device_mapping_v2' populated with below
+                          values:
+                          --------------------------------------------
+                          bd_map_v2 = [{
+                              'uuid': volume['volume']['id'],
+                              'source_type': 'volume',
+                              'destination_type': 'volume',
+                              'boot_index': 0,
+                              'delete_on_termination': True}]
+                          kwargs['block_device_mapping_v2'] = bd_map_v2
+                          ---------------------------------------------
+                          If server needs to be booted from volume with other
+                          combination of bdm inputs than mentioned above, then
+                          pass the bdm inputs explicitly as kwargs and image_id
+                          as empty string ('').
     :param name: Name of the server to be provisioned. If not defined a random
         string ending with '-instance' will be generated.
     :param flavor: Flavor of the server to be provisioned. If not defined,
         CONF.compute.flavor_ref will be used instead.
     :param image_id: ID of the image to be used to provision the server. If not
         defined, CONF.compute.image_ref will be used instead.
-    :param delete_vol_on_termination: Controls whether the backing volume
-        should be deleted when the server is deleted. Only applies to volume
-        backed servers.
     :returns: a tuple
     """
 
@@ -103,12 +116,14 @@
     if volume_backed:
         volume_name = data_utils.rand_name(__name__ + '-volume')
         volumes_client = clients.volumes_v2_client
-        if CONF.volume_feature_enabled.api_v1:
+        name_field = 'name'
+        if not CONF.volume_feature_enabled.api_v2:
             volumes_client = clients.volumes_client
-        volume = volumes_client.create_volume(
-            display_name=volume_name,
-            imageRef=image_id,
-            size=CONF.volume.volume_size)
+            name_field = 'display_name'
+        params = {name_field: volume_name,
+                  'imageRef': image_id,
+                  'size': CONF.volume.volume_size}
+        volume = volumes_client.create_volume(**params)
         waiters.wait_for_volume_status(volumes_client,
                                        volume['volume']['id'], 'available')
 
@@ -117,7 +132,7 @@
             'source_type': 'volume',
             'destination_type': 'volume',
             'boot_index': 0,
-            'delete_on_termination': delete_vol_on_termination}]
+            'delete_on_termination': True}]
         kwargs['block_device_mapping_v2'] = bd_map_v2
 
         # Since this is boot from volume an image does not need
diff --git a/tempest/lib/api_schema/response/compute/v2_1/aggregates.py b/tempest/lib/api_schema/response/compute/v2_1/aggregates.py
index 1a9fe41..3289a34 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/aggregates.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/aggregates.py
@@ -14,17 +14,19 @@
 
 import copy
 
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
 # create-aggregate api doesn't have 'hosts' and 'metadata' attributes.
 aggregate_for_create = {
     'type': 'object',
     'properties': {
         'availability_zone': {'type': ['string', 'null']},
-        'created_at': {'type': 'string'},
+        'created_at': parameter_types.date_time,
         'deleted': {'type': 'boolean'},
-        'deleted_at': {'type': ['string', 'null']},
+        'deleted_at': parameter_types.date_time_or_null,
         'id': {'type': 'integer'},
         'name': {'type': 'string'},
-        'updated_at': {'type': ['string', 'null']}
+        'updated_at': parameter_types.date_time_or_null
     },
     'additionalProperties': False,
     'required': ['availability_zone', 'created_at', 'deleted',
@@ -69,9 +71,7 @@
 # The 'updated_at' attribute of 'update_aggregate' can't be null.
 update_aggregate = copy.deepcopy(get_aggregate)
 update_aggregate['response_body']['properties']['aggregate']['properties'][
-    'updated_at'] = {
-        'type': 'string'
-    }
+    'updated_at'] = parameter_types.date_time
 
 delete_aggregate = {
     'status_code': [200]
diff --git a/tempest/lib/api_schema/response/compute/v2_1/availability_zone.py b/tempest/lib/api_schema/response/compute/v2_1/availability_zone.py
index d9aebce..f7b77a1 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/availability_zone.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/availability_zone.py
@@ -14,6 +14,8 @@
 
 import copy
 
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
 
 base = {
     'status_code': [200],
@@ -61,7 +63,7 @@
                     'properties': {
                         'available': {'type': 'boolean'},
                         'active': {'type': 'boolean'},
-                        'updated_at': {'type': ['string', 'null']}
+                        'updated_at': parameter_types.date_time_or_null
                     },
                     'additionalProperties': False,
                     'required': ['available', 'active', 'updated_at']
diff --git a/tempest/lib/api_schema/response/compute/v2_1/extensions.py b/tempest/lib/api_schema/response/compute/v2_1/extensions.py
index a6a455c..b5962d7 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/extensions.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/extensions.py
@@ -12,6 +12,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
 list_extensions = {
     'status_code': [200],
     'response_body': {
@@ -22,10 +24,7 @@
                 'items': {
                     'type': 'object',
                     'properties': {
-                        'updated': {
-                            'type': 'string',
-                            'format': 'data-time'
-                        },
+                        'updated': parameter_types.date_time,
                         'name': {'type': 'string'},
                         'links': {'type': 'array'},
                         'namespace': {
diff --git a/tempest/lib/api_schema/response/compute/v2_1/images.py b/tempest/lib/api_schema/response/compute/v2_1/images.py
index f65b9d8..156ff4a 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/images.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/images.py
@@ -26,10 +26,10 @@
     'properties': {
         'id': {'type': 'string'},
         'status': {'enum': image_status_enums},
-        'updated': {'type': 'string'},
+        'updated': parameter_types.date_time,
         'links': image_links,
         'name': {'type': ['string', 'null']},
-        'created': {'type': 'string'},
+        'created': parameter_types.date_time,
         'minDisk': {'type': 'integer'},
         'minRam': {'type': 'integer'},
         'progress': {'type': 'integer'},
diff --git a/tempest/lib/api_schema/response/compute/v2_1/keypairs.py b/tempest/lib/api_schema/response/compute/v2_1/keypairs.py
index 9c04c79..2828097 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/keypairs.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/keypairs.py
@@ -12,6 +12,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
 get_keypair = {
     'status_code': [200],
     'response_body': {
@@ -25,9 +27,9 @@
                     'fingerprint': {'type': 'string'},
                     'user_id': {'type': 'string'},
                     'deleted': {'type': 'boolean'},
-                    'created_at': {'type': 'string'},
-                    'updated_at': {'type': ['string', 'null']},
-                    'deleted_at': {'type': ['string', 'null']},
+                    'created_at': parameter_types.date_time,
+                    'updated_at': parameter_types.date_time_or_null,
+                    'deleted_at': parameter_types.date_time_or_null,
                     'id': {'type': 'integer'}
 
                 },
diff --git a/tempest/lib/api_schema/response/compute/v2_1/migrations.py b/tempest/lib/api_schema/response/compute/v2_1/migrations.py
index b7d66ea..c50286d 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/migrations.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/migrations.py
@@ -12,6 +12,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
 list_migrations = {
     'status_code': [200],
     'response_body': {
@@ -32,8 +34,8 @@
                         'dest_host': {'type': ['string', 'null']},
                         'old_instance_type_id': {'type': ['integer', 'null']},
                         'new_instance_type_id': {'type': ['integer', 'null']},
-                        'created_at': {'type': 'string'},
-                        'updated_at': {'type': ['string', 'null']}
+                        'created_at': parameter_types.date_time,
+                        'updated_at': parameter_types.date_time_or_null
                     },
                     'additionalProperties': False,
                     'required': [
diff --git a/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py b/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py
index 3cc5ca4..a3c9099 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py
@@ -81,6 +81,16 @@
     }
 }
 
+date_time = {
+    'type': 'string',
+    'format': 'iso8601-date-time'
+}
+
+date_time_or_null = {
+    'type': ['string', 'null'],
+    'format': 'iso8601-date-time'
+}
+
 response_header = {
     'connection': {'type': 'string'},
     'content-length': {'type': 'string'},
@@ -89,9 +99,14 @@
     'x-compute-request-id': {'type': 'string'},
     'vary': {'type': 'string'},
     'x-openstack-nova-api-version': {'type': 'string'},
+    # NOTE(gmann): Validating this as string only as this
+    # date in header is returned in different format than
+    # ISO 8601 date time format which is not consistent with
+    # other date-time format in nova.
+    # This API is already deprecated so not worth to fix
+    # on nova side.
     'date': {
-        'type': 'string',
-        'format': 'data-time'
+        'type': 'string'
     }
 }
 
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 1264416..4ccca6f 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/servers.py
@@ -118,8 +118,8 @@
         },
         'user_id': {'type': 'string'},
         'tenant_id': {'type': 'string'},
-        'created': {'type': 'string'},
-        'updated': {'type': 'string'},
+        'created': parameter_types.date_time,
+        'updated': parameter_types.date_time,
         'progress': {'type': 'integer'},
         'metadata': {'type': 'object'},
         'links': parameter_types.links,
@@ -402,7 +402,7 @@
         'request_id': {'type': 'string'},
         'user_id': {'type': 'string'},
         'project_id': {'type': 'string'},
-        'start_time': {'type': 'string'},
+        'start_time': parameter_types.date_time,
         'message': {'type': ['string', 'null']},
         'instance_uuid': {'type': 'string'}
     },
@@ -417,8 +417,8 @@
         'type': 'object',
         'properties': {
             'event': {'type': 'string'},
-            'start_time': {'type': 'string'},
-            'finish_time': {'type': 'string'},
+            'start_time': parameter_types.date_time,
+            'finish_time': parameter_types.date_time,
             'result': {'type': 'string'},
             'traceback': {'type': ['string', 'null']}
         },
diff --git a/tempest/lib/api_schema/response/compute/v2_1/services.py b/tempest/lib/api_schema/response/compute/v2_1/services.py
index ddef7b2..6949f86 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/services.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/services.py
@@ -12,6 +12,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
 list_services = {
     'status_code': [200],
     'response_body': {
@@ -29,7 +31,7 @@
                         'state': {'type': 'string'},
                         'binary': {'type': 'string'},
                         'status': {'type': 'string'},
-                        'updated_at': {'type': ['string', 'null']},
+                        'updated_at': parameter_types.date_time_or_null,
                         'disabled_reason': {'type': ['string', 'null']}
                     },
                     'additionalProperties': False,
diff --git a/tempest/lib/api_schema/response/compute/v2_1/snapshots.py b/tempest/lib/api_schema/response/compute/v2_1/snapshots.py
index 01a524b..826f854 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/snapshots.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/snapshots.py
@@ -13,6 +13,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
 common_snapshot_info = {
     'type': 'object',
     'properties': {
@@ -20,7 +22,7 @@
         'volumeId': {'type': 'string'},
         'status': {'type': 'string'},
         'size': {'type': 'integer'},
-        'createdAt': {'type': 'string'},
+        'createdAt': parameter_types.date_time,
         'displayName': {'type': ['string', 'null']},
         'displayDescription': {'type': ['string', 'null']}
     },
diff --git a/tempest/lib/api_schema/response/compute/v2_1/tenant_usages.py b/tempest/lib/api_schema/response/compute/v2_1/tenant_usages.py
index d51ef12..b531d2e 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/tenant_usages.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/tenant_usages.py
@@ -14,24 +14,21 @@
 
 import copy
 
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
 _server_usages = {
     'type': 'array',
     'items': {
         'type': 'object',
         'properties': {
-            'ended_at': {
-                'oneOf': [
-                    {'type': 'string'},
-                    {'type': 'null'}
-                ]
-            },
+            'ended_at': parameter_types.date_time_or_null,
             'flavor': {'type': 'string'},
             'hours': {'type': 'number'},
             'instance_id': {'type': 'string'},
             'local_gb': {'type': 'integer'},
             'memory_mb': {'type': 'integer'},
             'name': {'type': 'string'},
-            'started_at': {'type': 'string'},
+            'started_at': parameter_types.date_time,
             'state': {'type': 'string'},
             'tenant_id': {'type': 'string'},
             'uptime': {'type': 'integer'},
@@ -47,8 +44,8 @@
     'type': 'object',
     'properties': {
         'server_usages': _server_usages,
-        'start': {'type': 'string'},
-        'stop': {'type': 'string'},
+        'start': parameter_types.date_time,
+        'stop': parameter_types.date_time,
         'tenant_id': {'type': 'string'},
         'total_hours': {'type': 'number'},
         'total_local_gb_usage': {'type': 'number'},
diff --git a/tempest/lib/api_schema/response/compute/v2_1/versions.py b/tempest/lib/api_schema/response/compute/v2_1/versions.py
index 08a9fab..7f56239 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/versions.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/versions.py
@@ -14,6 +14,8 @@
 
 import copy
 
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
 
 _version = {
     'type': 'object',
@@ -33,7 +35,7 @@
             }
         },
         'status': {'type': 'string'},
-        'updated': {'type': 'string', 'format': 'date-time'},
+        'updated': parameter_types.date_time,
         'version': {'type': 'string'},
         'min_version': {'type': 'string'},
         'media-types': {
diff --git a/tempest/lib/api_schema/response/compute/v2_1/volumes.py b/tempest/lib/api_schema/response/compute/v2_1/volumes.py
index bb34acb..c35dae9 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/volumes.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/volumes.py
@@ -12,6 +12,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
 create_get_volume = {
     'status_code': [200],
     'response_body': {
@@ -24,7 +26,7 @@
                     'status': {'type': 'string'},
                     'displayName': {'type': ['string', 'null']},
                     'availabilityZone': {'type': 'string'},
-                    'createdAt': {'type': 'string'},
+                    'createdAt': parameter_types.date_time,
                     'displayDescription': {'type': ['string', 'null']},
                     'volumeType': {'type': ['string', 'null']},
                     'snapshotId': {'type': ['string', 'null']},
@@ -75,7 +77,7 @@
                         'status': {'type': 'string'},
                         'displayName': {'type': ['string', 'null']},
                         'availabilityZone': {'type': 'string'},
-                        'createdAt': {'type': 'string'},
+                        'createdAt': parameter_types.date_time,
                         'displayDescription': {'type': ['string', 'null']},
                         'volumeType': {'type': ['string', 'null']},
                         'snapshotId': {'type': ['string', 'null']},
diff --git a/tempest/lib/common/jsonschema_validator.py b/tempest/lib/common/jsonschema_validator.py
new file mode 100644
index 0000000..bbdf382
--- /dev/null
+++ b/tempest/lib/common/jsonschema_validator.py
@@ -0,0 +1,39 @@
+# 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.
+
+import jsonschema
+from oslo_utils import timeutils
+
+# JSON Schema validator and format checker used for JSON Schema validation
+JSONSCHEMA_VALIDATOR = jsonschema.Draft4Validator
+FORMAT_CHECKER = jsonschema.draft4_format_checker
+
+
+# NOTE(gmann): Add customized format checker for 'date-time' format because:
+# 1. jsonschema needs strict_rfc3339 or isodate module to be installed
+#    for proper date-time checking as per rfc3339.
+# 2. Nova or other OpenStack components handle the date time format as
+#    ISO 8601 which is defined in oslo_utils.timeutils
+# so this checker will validate the date-time as defined in
+# oslo_utils.timeutils
+@FORMAT_CHECKER.checks('iso8601-date-time')
+def _validate_datetime_format(instance):
+    try:
+        if isinstance(instance, jsonschema.compat.str_types):
+            timeutils.parse_isotime(instance)
+    except ValueError:
+        return False
+    else:
+        return True
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index 2c36f55..d0e21ff 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -25,6 +25,7 @@
 import six
 
 from tempest.lib.common import http
+from tempest.lib.common import jsonschema_validator
 from tempest.lib.common.utils import test_utils
 from tempest.lib import exceptions
 
@@ -38,8 +39,8 @@
 HTTP_REDIRECTION = (300, 301, 302, 303, 304, 305, 306, 307)
 
 # JSON Schema validator and format checker used for JSON Schema validation
-JSONSCHEMA_VALIDATOR = jsonschema.Draft4Validator
-FORMAT_CHECKER = jsonschema.draft4_format_checker
+JSONSCHEMA_VALIDATOR = jsonschema_validator.JSONSCHEMA_VALIDATOR
+FORMAT_CHECKER = jsonschema_validator.FORMAT_CHECKER
 
 
 class RestClient(object):
diff --git a/tempest/lib/common/ssh.py b/tempest/lib/common/ssh.py
index 4226cd6..5e65bee 100644
--- a/tempest/lib/common/ssh.py
+++ b/tempest/lib/common/ssh.py
@@ -37,7 +37,30 @@
 
     def __init__(self, host, username, password=None, timeout=300, pkey=None,
                  channel_timeout=10, look_for_keys=False, key_filename=None,
-                 port=22):
+                 port=22, proxy_client=None):
+        """SSH client.
+
+        Many of parameters are just passed to the underlying implementation
+        as it is.  See the paramiko documentation for more details.
+        http://docs.paramiko.org/en/2.1/api/client.html#paramiko.client.SSHClient.connect
+
+        :param host: Host to login.
+        :param username: SSH username.
+        :param password: SSH password, or a password to unlock private key.
+        :param timeout: Timeout in seconds, including retries.
+            Default is 300 seconds.
+        :param pkey: Private key.
+        :param channel_timeout: Channel timeout in seconds, passed to the
+            paramiko.  Default is 10 seconds.
+        :param look_for_keys: Whether or not to search for private keys
+            in ``~/.ssh``.  Default is False.
+        :param key_filename: Filename for private key to use.
+        :param port: SSH port number.
+        :param proxy_client: Another SSH client to provide a transport
+            for ssh-over-ssh.  The default is None, which means
+            not to use ssh-over-ssh.
+        :type proxy_client: ``tempest.lib.common.ssh.Client`` object
+        """
         self.host = host
         self.username = username
         self.port = port
@@ -51,6 +74,8 @@
         self.timeout = int(timeout)
         self.channel_timeout = float(channel_timeout)
         self.buf_size = 1024
+        self.proxy_client = proxy_client
+        self._proxy_conn = None
 
     def _get_ssh_connection(self, sleep=1.5, backoff=1):
         """Returns an ssh connection to the specified host."""
@@ -59,6 +84,10 @@
         ssh.set_missing_host_key_policy(
             paramiko.AutoAddPolicy())
         _start_time = time.time()
+        if self.proxy_client is not None:
+            proxy_chan = self._get_proxy_channel()
+        else:
+            proxy_chan = None
         if self.pkey is not None:
             LOG.info("Creating ssh connection to '%s:%d' as '%s'"
                      " with public key authentication",
@@ -74,7 +103,8 @@
                             password=self.password,
                             look_for_keys=self.look_for_keys,
                             key_filename=self.key_filename,
-                            timeout=self.channel_timeout, pkey=self.pkey)
+                            timeout=self.channel_timeout, pkey=self.pkey,
+                            sock=proxy_chan)
                 LOG.info("ssh connection to %s@%s successfully created",
                          self.username, self.host)
                 return ssh
@@ -175,3 +205,14 @@
         """Raises an exception when we can not connect to server via ssh."""
         connection = self._get_ssh_connection()
         connection.close()
+
+    def _get_proxy_channel(self):
+        conn = self.proxy_client._get_ssh_connection()
+        # Keep a reference to avoid g/c
+        # https://github.com/paramiko/paramiko/issues/440
+        self._proxy_conn = conn
+        transport = conn.get_transport()
+        chan = transport.open_session()
+        cmd = 'nc %s %s' % (self.host, self.port)
+        chan.exec_command(cmd)
+        return chan
diff --git a/tempest/tests/lib/common/test_jsonschema_validator.py b/tempest/tests/lib/common/test_jsonschema_validator.py
new file mode 100644
index 0000000..8694f3d
--- /dev/null
+++ b/tempest/tests/lib/common/test_jsonschema_validator.py
@@ -0,0 +1,83 @@
+# 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.lib.api_schema.response.compute.v2_1 import parameter_types
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions
+from tempest.tests import base
+from tempest.tests.lib import fake_http
+
+
+class TestJSONSchemaDateTimeFormat(base.TestCase):
+    date_time_schema = [
+        {
+            'status_code': [200],
+            'response_body': {
+                'type': 'object',
+                'properties': {
+                    'date-time': parameter_types.date_time
+                }
+            }
+        },
+        {
+            'status_code': [200],
+            'response_body': {
+                'type': 'object',
+                'properties': {
+                    'date-time': parameter_types.date_time_or_null
+                }
+            }
+        }
+    ]
+
+    def test_valid_date_time_format(self):
+        valid_instances = ['2016-10-02T10:00:00-05:00',
+                           '2016-10-02T10:00:00+09:00',
+                           '2016-10-02T15:00:00Z',
+                           '2016-10-02T15:00:00.05Z']
+        resp = fake_http.fake_http_response('', status=200)
+        for instance in valid_instances:
+            body = {'date-time': instance}
+            for schema in self.date_time_schema:
+                rest_client.RestClient.validate_response(schema, resp, body)
+
+    def test_invalid_date_time_format(self):
+        invalid_instances = ['2016-10-02 T10:00:00-05:00',
+                             '2016-10-02T 15:00:00',
+                             '2016-10-02T15:00:00.05 Z',
+                             '2016-10-02:15:00:00.05Z',
+                             'T15:00:00.05Z',
+                             '2016:10:02T15:00:00',
+                             '2016-10-02T15-00-00',
+                             '2016-10-02T15.05Z',
+                             '09MAR2015 11:15',
+                             '13 Oct 2015 05:55:36 GMT',
+                             '']
+        resp = fake_http.fake_http_response('', status=200)
+        for instance in invalid_instances:
+            body = {'date-time': instance}
+            for schema in self.date_time_schema:
+                self.assertRaises(exceptions.InvalidHTTPResponseBody,
+                                  rest_client.RestClient.validate_response,
+                                  schema, resp, body)
+
+    def test_date_time_or_null_format(self):
+        instance = None
+        resp = fake_http.fake_http_response('', status=200)
+        body = {'date-time': instance}
+        rest_client.RestClient.validate_response(self.date_time_schema[1],
+                                                 resp, body)
+        self.assertRaises(exceptions.InvalidHTTPResponseBody,
+                          rest_client.RestClient.validate_response,
+                          self.date_time_schema[0], resp, body)
diff --git a/tempest/tests/lib/services/compute/test_servers_client.py b/tempest/tests/lib/services/compute/test_servers_client.py
index 93550fd..adfaaf2 100644
--- a/tempest/tests/lib/services/compute/test_servers_client.py
+++ b/tempest/tests/lib/services/compute/test_servers_client.py
@@ -154,7 +154,7 @@
         "request_id": "16fb98f-46ca-475e-917e-2563e5a8cd19",
         "user_id": "16fb98f-46ca-475e-917e-2563e5a8cd12",
         "project_id": "16fb98f-46ca-475e-917e-2563e5a8cd34",
-        "start_time": "09MAR2015 11:15",
+        "start_time": "2016-10-02T10:00:00-05:00",
         "message": "fake-msg",
         "instance_uuid": "16fb98f-46ca-475e-917e-2563e5a8cd12"
     }
@@ -166,8 +166,8 @@
 
     FAKE_INSTANCE_ACTION_EVENTS = {
         "event": "fake-event",
-        "start_time": "09MAR2015 11:15",
-        "finish_time": "09MAR2015 11:15",
+        "start_time": "2016-10-02T10:00:00-05:00",
+        "finish_time": "2016-10-02T10:00:00-05:00",
         "result": "fake-result",
         "traceback": "fake-trace-back"
     }
diff --git a/tempest/tests/lib/services/identity/v3/test_roles_client.py b/tempest/tests/lib/services/identity/v3/test_roles_client.py
index bad1ef9..4f70b47 100644
--- a/tempest/tests/lib/services/identity/v3/test_roles_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_roles_client.py
@@ -18,32 +18,40 @@
 
 
 class TestRolesClient(base.BaseServiceTest):
+
+    FAKE_ROLE_ID = "1"
+    FAKE_ROLE_NAME = "test"
+    FAKE_DOMAIN_ID = "1"
+
+    FAKE_ROLE_ID_2 = "2"
+    FAKE_ROLE_NAME_2 = "test2"
+
     FAKE_ROLE_INFO = {
         "role": {
-            "domain_id": "1",
-            "id": "1",
-            "name": "test",
-            "links": "example.com"
+            "domain_id": FAKE_DOMAIN_ID,
+            "id": FAKE_ROLE_ID,
+            "name": FAKE_ROLE_NAME,
+            "links": {
+                "self": "http://example.com/identity/v3/roles/%s" % (
+                    FAKE_ROLE_ID)
+            }
         }
     }
 
-    FAKE_LIST_ROLES = {
-        "roles": [
-            {
-                "domain_id": "1",
-                "id": "1",
-                "name": "test",
-                "links": "example.com"
-            },
-            {
-                "domain_id": "2",
-                "id": "2",
-                "name": "test2",
-                "links": "example.com"
+    FAKE_ROLE_INFO_2 = {
+        "role": {
+            "domain_id": FAKE_DOMAIN_ID,
+            "id": FAKE_ROLE_ID_2,
+            "name": FAKE_ROLE_NAME_2,
+            "links": {
+                "self": "http://example.com/identity/v3/roles/%s" % (
+                    FAKE_ROLE_ID_2)
             }
-        ]
+        }
     }
 
+    FAKE_LIST_ROLES = {"roles": [FAKE_ROLE_INFO, FAKE_ROLE_INFO_2]}
+
     def setUp(self):
         super(TestRolesClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -56,8 +64,8 @@
             'tempest.lib.common.rest_client.RestClient.post',
             self.FAKE_ROLE_INFO,
             bytes_body,
-            domain_id="1",
-            name="test",
+            domain_id=self.FAKE_DOMAIN_ID,
+            name=self.FAKE_ROLE_NAME,
             status=201)
 
     def _test_show_role(self, bytes_body=False):
@@ -66,7 +74,7 @@
             'tempest.lib.common.rest_client.RestClient.get',
             self.FAKE_ROLE_INFO,
             bytes_body,
-            role_id="1")
+            role_id=self.FAKE_ROLE_ID)
 
     def _test_list_roles(self, bytes_body=False):
         self.check_service_client_function(
@@ -81,8 +89,8 @@
             'tempest.lib.common.rest_client.RestClient.patch',
             self.FAKE_ROLE_INFO,
             bytes_body,
-            role_id="1",
-            name="test")
+            role_id=self.FAKE_ROLE_ID,
+            name=self.FAKE_ROLE_NAME)
 
     def _test_create_user_role_on_project(self, bytes_body=False):
         self.check_service_client_function(
@@ -193,7 +201,7 @@
             self.client.delete_role,
             'tempest.lib.common.rest_client.RestClient.delete',
             {},
-            role_id="1",
+            role_id=self.FAKE_ROLE_ID,
             status=204)
 
     def test_create_user_role_on_project_with_str_body(self):
diff --git a/tempest/tests/lib/test_ssh.py b/tempest/tests/lib/test_ssh.py
index 8a0a84c..a16da1c 100644
--- a/tempest/tests/lib/test_ssh.py
+++ b/tempest/tests/lib/test_ssh.py
@@ -75,7 +75,54 @@
             key_filename=None,
             look_for_keys=False,
             timeout=10.0,
-            password=None
+            password=None,
+            sock=None
+        )]
+        self.assertEqual(expected_connect, client_mock.connect.mock_calls)
+        self.assertEqual(0, s_mock.call_count)
+
+    def test_get_ssh_connection_over_ssh(self):
+        c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
+        proxy_client_mock = mock.MagicMock()
+        proxy_client_mock.connect.return_value = True
+        s_mock = self.patch('time.sleep')
+
+        c_mock.side_effect = [client_mock, proxy_client_mock]
+        aa_mock.return_value = mock.sentinel.aa
+
+        proxy_client = ssh.Client('proxy-host', 'proxy-user', timeout=2)
+        client = ssh.Client('localhost', 'root', timeout=2,
+                            proxy_client=proxy_client)
+        client._get_ssh_connection(sleep=1)
+
+        aa_mock.assert_has_calls([mock.call(), mock.call()])
+        proxy_client_mock.set_missing_host_key_policy.assert_called_once_with(
+            mock.sentinel.aa)
+        proxy_expected_connect = [mock.call(
+            'proxy-host',
+            port=22,
+            username='proxy-user',
+            pkey=None,
+            key_filename=None,
+            look_for_keys=False,
+            timeout=10.0,
+            password=None,
+            sock=None
+        )]
+        self.assertEqual(proxy_expected_connect,
+                         proxy_client_mock.connect.mock_calls)
+        client_mock.set_missing_host_key_policy.assert_called_once_with(
+            mock.sentinel.aa)
+        expected_connect = [mock.call(
+            'localhost',
+            port=22,
+            username='root',
+            pkey=None,
+            key_filename=None,
+            look_for_keys=False,
+            timeout=10.0,
+            password=None,
+            sock=proxy_client_mock.get_transport().open_session()
         )]
         self.assertEqual(expected_connect, client_mock.connect.mock_calls)
         self.assertEqual(0, s_mock.call_count)