Nova: Move _Websocket class to a common place

To avoid to introduce a dependency to "websocket-client", we re-use
the internal "_Websocket" class which encapsulates the sockets.
This class got introduced with commit 1f87a5611 for the VNC console
test cases.
This change moves this class to a common place, so that it can be
used by VNC and (in a later change) "serial console" test cases
equally.

Change-Id: I79065865c876549269b2bb962a6826ac2fd0a403
diff --git a/tempest/api/compute/servers/test_novnc.py b/tempest/api/compute/servers/test_novnc.py
index a72df5e..6354c57 100644
--- a/tempest/api/compute/servers/test_novnc.py
+++ b/tempest/api/compute/servers/test_novnc.py
@@ -13,14 +13,13 @@
 #    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.common import compute
 from tempest import config
 from tempest.lib import decorators
 
@@ -158,14 +157,6 @@
             self._websocket.response.find(b'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)
-
     @decorators.idempotent_id('c640fdff-8ab4-45a4-a5d8-7e6146cbd0dc')
     def test_novnc(self):
         body = self.client.get_vnc_console(self.server['id'],
@@ -174,7 +165,7 @@
         # 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'])
+        self._websocket = compute.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
@@ -187,84 +178,9 @@
         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)
+        self._websocket = compute.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_func(header[1]) & 127 > 0:
-                return self._socket.recv(ord_func(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_func(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.encode('utf8'))
-        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(b'\r\n\r\n') < 0:
-            data = self._socket.recv(4096)
-            self.response += data
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index b2667e5..db45f71 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -14,8 +14,13 @@
 #    limitations under the License.
 
 import base64
+import socket
+import struct
 import textwrap
 
+import six
+from six.moves.urllib import parse as urlparse
+
 from oslo_log import log as logging
 from oslo_utils import excutils
 
@@ -25,6 +30,11 @@
 from tempest.lib.common import rest_client
 from tempest.lib.common.utils import data_utils
 
+if six.PY2:
+    ord_func = ord
+else:
+    ord_func = int
+
 CONF = config.CONF
 
 LOG = logging.getLogger(__name__)
@@ -226,3 +236,87 @@
             servers_client.shelve_offload_server(server_id)
             waiters.wait_for_server_status(servers_client, server_id,
                                            'SHELVED_OFFLOADED')
+
+
+def create_websocket(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)
+
+
+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_func(header[1]) & 127 > 0:
+                return self._socket.recv(ord_func(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_func(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.encode('utf8'))
+        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(b'\r\n\r\n') < 0:
+            data = self._socket.recv(4096)
+            self.response += data