blob: 6354c576b1d3fc67546022c900d385863be4bc33 [file] [log] [blame]
Matthew Treinish90e2a6d2017-02-06 19:56:43 -05001# Copyright 2016-2017 OpenStack Foundation
Michelle Mandel1f87a562016-07-15 17:11:33 -04002# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
Michelle Mandel1f87a562016-07-15 17:11:33 -040016import struct
17
18import six
Michelle Mandel1f87a562016-07-15 17:11:33 -040019import urllib3
20
21from tempest.api.compute import base
Markus Zoellerae36ce82017-03-20 16:27:26 +010022from tempest.common import compute
Michelle Mandel1f87a562016-07-15 17:11:33 -040023from tempest import config
Ken'ichi Ohmichi44f01272017-01-27 18:44:14 -080024from tempest.lib import decorators
Michelle Mandel1f87a562016-07-15 17:11:33 -040025
26CONF = config.CONF
27
Matthew Treinish90e2a6d2017-02-06 19:56:43 -050028if six.PY2:
29 ord_func = ord
30else:
31 ord_func = int
32
Michelle Mandel1f87a562016-07-15 17:11:33 -040033
34class NoVNCConsoleTestJSON(base.BaseV2ComputeTest):
35
36 @classmethod
37 def skip_checks(cls):
38 super(NoVNCConsoleTestJSON, cls).skip_checks()
39 if not CONF.compute_feature_enabled.vnc_console:
40 raise cls.skipException('VNC Console feature is disabled.')
41
42 def setUp(self):
43 super(NoVNCConsoleTestJSON, self).setUp()
44 self._websocket = None
45
46 def tearDown(self):
47 self.server_check_teardown()
48 super(NoVNCConsoleTestJSON, self).tearDown()
49 if self._websocket is not None:
50 self._websocket.close()
51
52 @classmethod
53 def setup_clients(cls):
54 super(NoVNCConsoleTestJSON, cls).setup_clients()
55 cls.client = cls.servers_client
56
57 @classmethod
58 def resource_setup(cls):
59 super(NoVNCConsoleTestJSON, cls).resource_setup()
60 cls.server = cls.create_test_server(wait_until="ACTIVE")
61
62 def _validate_novnc_html(self, vnc_url):
63 """Verify we can connect to novnc and get back the javascript."""
64 resp = urllib3.PoolManager().request('GET', vnc_url)
65 # Make sure that the GET request was accepted by the novncproxy
66 self.assertEqual(resp.status, 200, 'Got a Bad HTTP Response on the '
Matthew Treinish90e2a6d2017-02-06 19:56:43 -050067 'initial call: ' + six.text_type(resp.status))
Michelle Mandel1f87a562016-07-15 17:11:33 -040068 # Do some basic validation to make sure it is an expected HTML document
Matthew Treinish90e2a6d2017-02-06 19:56:43 -050069 resp_data = resp.data.decode()
70 self.assertIn('<html>', resp_data,
71 'Not a valid html document in the response.')
72 self.assertIn('</html>', resp_data,
73 'Not a valid html document in the response.')
Michelle Mandel1f87a562016-07-15 17:11:33 -040074 # Just try to make sure we got JavaScript back for noVNC, since we
75 # won't actually use it since not inside of a browser
Matthew Treinish90e2a6d2017-02-06 19:56:43 -050076 self.assertIn('noVNC', resp_data,
77 'Not a valid noVNC javascript html document.')
78 self.assertIn('<script', resp_data,
79 'Not a valid noVNC javascript html document.')
Michelle Mandel1f87a562016-07-15 17:11:33 -040080
81 def _validate_rfb_negotiation(self):
82 """Verify we can connect to novnc and do the websocket connection."""
83 # Turn the Socket into a WebSocket to do the communication
84 data = self._websocket.receive_frame()
85 self.assertFalse(data is None or len(data) == 0,
86 'Token must be invalid because the connection '
87 'closed.')
88 # Parse the RFB version from the data to make sure it is valid
jianghua44a0f392017-03-13 04:14:26 +000089 # and belong to the known supported RFB versions.
Michelle Mandel1f87a562016-07-15 17:11:33 -040090 version = float("%d.%d" % (int(data[4:7], base=10),
91 int(data[8:11], base=10)))
jianghua44a0f392017-03-13 04:14:26 +000092 # Add the max RFB versions supported
93 supported_versions = [3.3, 3.8]
94 self.assertIn(version, supported_versions,
95 'Bad RFB Version: ' + str(version))
96 # Send our RFB version to the server
Matthew Treinish90e2a6d2017-02-06 19:56:43 -050097 self._websocket.send_frame(data)
Michelle Mandel1f87a562016-07-15 17:11:33 -040098 # Get the sever authentication type and make sure None is supported
99 data = self._websocket.receive_frame()
100 self.assertIsNotNone(data, 'Expected authentication type None.')
jianghua44a0f392017-03-13 04:14:26 +0000101 data_length = len(data)
102 if version == 3.3:
103 # For RFB 3.3: in the security handshake, rather than a two-way
104 # negotiation, the server decides the security type and sends a
105 # single word(4 bytes).
106 self.assertEqual(
107 data_length, 4, 'Expected authentication type None.')
108 self.assertIn(1, [ord_func(data[i]) for i in (0, 3)],
109 'Expected authentication type None.')
110 else:
111 self.assertGreaterEqual(
112 len(data), 2, 'Expected authentication type None.')
113 self.assertIn(
114 1,
115 [ord_func(data[i + 1]) for i in range(ord_func(data[0]))],
116 'Expected authentication type None.')
117 # Send to the server that we only support authentication
118 # type None
119 self._websocket.send_frame(six.int2byte(1))
120
121 # The server should send 4 bytes of 0's if security
122 # handshake succeeded
123 data = self._websocket.receive_frame()
124 self.assertEqual(
125 len(data), 4,
126 'Server did not think security was successful.')
127 self.assertEqual(
128 [ord_func(i) for i in data], [0, 0, 0, 0],
129 'Server did not think security was successful.')
130
Michelle Mandel1f87a562016-07-15 17:11:33 -0400131 # Say to leave the desktop as shared as part of client initialization
132 self._websocket.send_frame(six.int2byte(1))
133 # Get the server initialization packet back and make sure it is the
134 # right structure where bytes 20-24 is the name length and
135 # 24-N is the name
136 data = self._websocket.receive_frame()
137 data_length = len(data) if data is not None else 0
138 self.assertFalse(data_length <= 24 or
139 data_length != (struct.unpack(">L",
140 data[20:24])[0] + 24),
141 'Server initialization was not the right format.')
142 # Since the rest of the data on the screen is arbitrary, we will
143 # close the socket and end our validation of the data at this point
144 # Assert that the latest check was false, meaning that the server
145 # initialization was the right format
146 self.assertFalse(data_length <= 24 or
147 data_length != (struct.unpack(">L",
148 data[20:24])[0] + 24))
149
150 def _validate_websocket_upgrade(self):
151 self.assertTrue(
Matthew Treinish90e2a6d2017-02-06 19:56:43 -0500152 self._websocket.response.startswith(b'HTTP/1.1 101 Switching '
153 b'Protocols\r\n'),
Michelle Mandel1f87a562016-07-15 17:11:33 -0400154 'Did not get the expected 101 on the websockify call: '
Matthew Treinish90e2a6d2017-02-06 19:56:43 -0500155 + six.text_type(self._websocket.response))
Michelle Mandel1f87a562016-07-15 17:11:33 -0400156 self.assertTrue(
Matthew Treinish90e2a6d2017-02-06 19:56:43 -0500157 self._websocket.response.find(b'Server: WebSockify') > 0,
Michelle Mandel1f87a562016-07-15 17:11:33 -0400158 'Did not get the expected WebSocket HTTP Response.')
159
Ken'ichi Ohmichi44f01272017-01-27 18:44:14 -0800160 @decorators.idempotent_id('c640fdff-8ab4-45a4-a5d8-7e6146cbd0dc')
Michelle Mandel1f87a562016-07-15 17:11:33 -0400161 def test_novnc(self):
162 body = self.client.get_vnc_console(self.server['id'],
163 type='novnc')['console']
164 self.assertEqual('novnc', body['type'])
165 # Do the initial HTTP Request to novncproxy to get the NoVNC JavaScript
166 self._validate_novnc_html(body['url'])
167 # Do the WebSockify HTTP Request to novncproxy to do the RFB connection
Markus Zoellerae36ce82017-03-20 16:27:26 +0100168 self._websocket = compute.create_websocket(body['url'])
Michelle Mandel1f87a562016-07-15 17:11:33 -0400169 # Validate that we succesfully connected and upgraded to Web Sockets
170 self._validate_websocket_upgrade()
171 # Validate the RFB Negotiation to determine if a valid VNC session
172 self._validate_rfb_negotiation()
173
Ken'ichi Ohmichi44f01272017-01-27 18:44:14 -0800174 @decorators.idempotent_id('f9c79937-addc-4aaa-9e0e-841eef02aeb7')
Michelle Mandel1f87a562016-07-15 17:11:33 -0400175 def test_novnc_bad_token(self):
176 body = self.client.get_vnc_console(self.server['id'],
177 type='novnc')['console']
178 self.assertEqual('novnc', body['type'])
179 # Do the WebSockify HTTP Request to novncproxy with a bad token
180 url = body['url'].replace('token=', 'token=bad')
Markus Zoellerae36ce82017-03-20 16:27:26 +0100181 self._websocket = compute.create_websocket(url)
Michelle Mandel1f87a562016-07-15 17:11:33 -0400182 # Make sure the novncproxy rejected the connection and closed it
183 data = self._websocket.receive_frame()
184 self.assertTrue(data is None or len(data) == 0,
185 "The novnc proxy actually sent us some data, but we "
186 "expected it to close the connection.")