OpenStack Testing (Tempest) of an existing cloud
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

230 wiersze
10KB

  1. # Copyright 2016-2017 OpenStack Foundation
  2. # 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. import struct
  16. import six
  17. import six.moves.urllib.parse as urlparse
  18. import urllib3
  19. from tempest.api.compute import base
  20. from tempest.common import compute
  21. from tempest import config
  22. from tempest.lib import decorators
  23. CONF = config.CONF
  24. if six.PY2:
  25. ord_func = ord
  26. else:
  27. ord_func = int
  28. class NoVNCConsoleTestJSON(base.BaseV2ComputeTest):
  29. create_default_network = True
  30. @classmethod
  31. def skip_checks(cls):
  32. super(NoVNCConsoleTestJSON, cls).skip_checks()
  33. if not CONF.compute_feature_enabled.vnc_console:
  34. raise cls.skipException('VNC Console feature is disabled.')
  35. def setUp(self):
  36. super(NoVNCConsoleTestJSON, self).setUp()
  37. self._websocket = None
  38. def tearDown(self):
  39. super(NoVNCConsoleTestJSON, self).tearDown()
  40. if self._websocket is not None:
  41. self._websocket.close()
  42. # NOTE(zhufl): Because server_check_teardown will raise Exception
  43. # which will prevent other cleanup steps from being executed, so
  44. # server_check_teardown should be called after super's tearDown.
  45. self.server_check_teardown()
  46. @classmethod
  47. def setup_clients(cls):
  48. super(NoVNCConsoleTestJSON, cls).setup_clients()
  49. cls.client = cls.servers_client
  50. @classmethod
  51. def resource_setup(cls):
  52. super(NoVNCConsoleTestJSON, cls).resource_setup()
  53. cls.server = cls.create_test_server(wait_until="ACTIVE")
  54. cls.use_get_remote_console = False
  55. if not cls.is_requested_microversion_compatible('2.5'):
  56. cls.use_get_remote_console = True
  57. def _validate_novnc_html(self, vnc_url):
  58. """Verify we can connect to novnc and get back the javascript."""
  59. resp = urllib3.PoolManager().request('GET', vnc_url)
  60. # Make sure that the GET request was accepted by the novncproxy
  61. self.assertEqual(resp.status, 200, 'Got a Bad HTTP Response on the '
  62. 'initial call: ' + six.text_type(resp.status))
  63. # Do some basic validation to make sure it is an expected HTML document
  64. resp_data = resp.data.decode()
  65. # This is needed in the case of example: <html lang="en">
  66. self.assertRegex(resp_data, '<html.*>',
  67. 'Not a valid html document in the response.')
  68. self.assertIn('</html>', resp_data,
  69. 'Not a valid html document in the response.')
  70. # Just try to make sure we got JavaScript back for noVNC, since we
  71. # won't actually use it since not inside of a browser
  72. self.assertIn('noVNC', resp_data,
  73. 'Not a valid noVNC javascript html document.')
  74. self.assertIn('<script', resp_data,
  75. 'Not a valid noVNC javascript html document.')
  76. def _validate_rfb_negotiation(self):
  77. """Verify we can connect to novnc and do the websocket connection."""
  78. # Turn the Socket into a WebSocket to do the communication
  79. data = self._websocket.receive_frame()
  80. self.assertFalse(data is None or not data,
  81. 'Token must be invalid because the connection '
  82. 'closed.')
  83. # Parse the RFB version from the data to make sure it is valid
  84. # and belong to the known supported RFB versions.
  85. version = float("%d.%d" % (int(data[4:7], base=10),
  86. int(data[8:11], base=10)))
  87. # Add the max RFB versions supported
  88. supported_versions = [3.3, 3.8]
  89. self.assertIn(version, supported_versions,
  90. 'Bad RFB Version: ' + str(version))
  91. # Send our RFB version to the server
  92. self._websocket.send_frame(data)
  93. # Get the sever authentication type and make sure None is supported
  94. data = self._websocket.receive_frame()
  95. self.assertIsNotNone(data, 'Expected authentication type None.')
  96. data_length = len(data)
  97. if version == 3.3:
  98. # For RFB 3.3: in the security handshake, rather than a two-way
  99. # negotiation, the server decides the security type and sends a
  100. # single word(4 bytes).
  101. self.assertEqual(
  102. data_length, 4, 'Expected authentication type None.')
  103. self.assertIn(1, [ord_func(data[i]) for i in (0, 3)],
  104. 'Expected authentication type None.')
  105. else:
  106. self.assertGreaterEqual(
  107. len(data), 2, 'Expected authentication type None.')
  108. self.assertIn(
  109. 1,
  110. [ord_func(data[i + 1]) for i in range(ord_func(data[0]))],
  111. 'Expected authentication type None.')
  112. # Send to the server that we only support authentication
  113. # type None
  114. self._websocket.send_frame(six.int2byte(1))
  115. # The server should send 4 bytes of 0's if security
  116. # handshake succeeded
  117. data = self._websocket.receive_frame()
  118. self.assertEqual(
  119. len(data), 4,
  120. 'Server did not think security was successful.')
  121. self.assertEqual(
  122. [ord_func(i) for i in data], [0, 0, 0, 0],
  123. 'Server did not think security was successful.')
  124. # Say to leave the desktop as shared as part of client initialization
  125. self._websocket.send_frame(six.int2byte(1))
  126. # Get the server initialization packet back and make sure it is the
  127. # right structure where bytes 20-24 is the name length and
  128. # 24-N is the name
  129. data = self._websocket.receive_frame()
  130. data_length = len(data) if data is not None else 0
  131. self.assertFalse(data_length <= 24 or
  132. data_length != (struct.unpack(">L",
  133. data[20:24])[0] + 24),
  134. 'Server initialization was not the right format.')
  135. # Since the rest of the data on the screen is arbitrary, we will
  136. # close the socket and end our validation of the data at this point
  137. # Assert that the latest check was false, meaning that the server
  138. # initialization was the right format
  139. self.assertFalse(data_length <= 24 or
  140. data_length != (struct.unpack(">L",
  141. data[20:24])[0] + 24))
  142. def _validate_websocket_upgrade(self):
  143. """Verify that the websocket upgrade was successful.
  144. Parses response and ensures that required response
  145. fields are present and accurate.
  146. (https://tools.ietf.org/html/rfc7231#section-6.2.2)
  147. """
  148. self.assertTrue(
  149. self._websocket.response.startswith(b'HTTP/1.1 101 Switching '
  150. b'Protocols'),
  151. 'Incorrect HTTP return status code: {}'.format(
  152. six.text_type(self._websocket.response)
  153. )
  154. )
  155. _required_header = 'upgrade: websocket'
  156. _response = six.text_type(self._websocket.response).lower()
  157. self.assertIn(
  158. _required_header,
  159. _response,
  160. 'Did not get the expected WebSocket HTTP Response.'
  161. )
  162. @decorators.idempotent_id('c640fdff-8ab4-45a4-a5d8-7e6146cbd0dc')
  163. def test_novnc(self):
  164. if self.use_get_remote_console:
  165. body = self.client.get_remote_console(
  166. self.server['id'], console_type='novnc',
  167. protocol='vnc')['remote_console']
  168. else:
  169. body = self.client.get_vnc_console(self.server['id'],
  170. type='novnc')['console']
  171. self.assertEqual('novnc', body['type'])
  172. # Do the initial HTTP Request to novncproxy to get the NoVNC JavaScript
  173. self._validate_novnc_html(body['url'])
  174. # Do the WebSockify HTTP Request to novncproxy to do the RFB connection
  175. self._websocket = compute.create_websocket(body['url'])
  176. # Validate that we successfully connected and upgraded to Web Sockets
  177. self._validate_websocket_upgrade()
  178. # Validate the RFB Negotiation to determine if a valid VNC session
  179. self._validate_rfb_negotiation()
  180. @decorators.idempotent_id('f9c79937-addc-4aaa-9e0e-841eef02aeb7')
  181. def test_novnc_bad_token(self):
  182. if self.use_get_remote_console:
  183. body = self.client.get_remote_console(
  184. self.server['id'], console_type='novnc',
  185. protocol='vnc')['remote_console']
  186. else:
  187. body = self.client.get_vnc_console(self.server['id'],
  188. type='novnc')['console']
  189. self.assertEqual('novnc', body['type'])
  190. # Do the WebSockify HTTP Request to novncproxy with a bad token
  191. parts = urlparse.urlparse(body['url'])
  192. qparams = urlparse.parse_qs(parts.query)
  193. if 'path' in qparams:
  194. qparams['path'] = urlparse.unquote(qparams['path'][0]).replace(
  195. 'token=', 'token=bad')
  196. elif 'token' in qparams:
  197. qparams['token'] = 'bad' + qparams['token'][0]
  198. new_query = urlparse.urlencode(qparams)
  199. new_parts = urlparse.ParseResult(parts.scheme, parts.netloc,
  200. parts.path, parts.params, new_query,
  201. parts.fragment)
  202. url = urlparse.urlunparse(new_parts)
  203. self._websocket = compute.create_websocket(url)
  204. # Make sure the novncproxy rejected the connection and closed it
  205. data = self._websocket.receive_frame()
  206. self.assertTrue(data is None or not data,
  207. "The novnc proxy actually sent us some data, but we "
  208. "expected it to close the connection.")