blob: 2443a675303e121a95e7323a4a23502acea0d349 [file] [log] [blame]
Joseph Lanouxb3e1f872015-01-30 11:13:07 +00001# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain 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,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
Clark Boylan844180e2017-03-15 15:24:58 -070016import base64
Markus Zoellerae36ce82017-03-20 16:27:26 +010017import socket
xxj8eb90982017-04-10 21:18:39 +080018import ssl
Markus Zoellerae36ce82017-03-20 16:27:26 +010019import struct
Clark Boylan844180e2017-03-15 15:24:58 -070020import textwrap
songwenping99d6e002021-01-05 03:07:46 +000021from urllib import parse as urlparse
Markus Zoellerae36ce82017-03-20 16:27:26 +010022
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000023from oslo_log import log as logging
24from oslo_utils import excutils
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000025
Ken'ichi Ohmichi0eb153c2015-07-13 02:18:25 +000026from tempest.common import waiters
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000027from tempest import config
Matthew Treinishb19c55d2017-07-17 12:38:35 -040028from tempest.lib.common import fixed_network
Ken'ichi Ohmichi54030522016-03-02 11:01:34 -080029from tempest.lib.common import rest_client
Ken'ichi Ohmichi757833a2017-03-10 10:30:30 -080030from tempest.lib.common.utils import data_utils
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000031
32CONF = config.CONF
33
34LOG = logging.getLogger(__name__)
35
36
Andrea Frittoli88eb6772017-08-07 21:06:27 +010037def is_scheduler_filter_enabled(filter_name):
38 """Check the list of enabled compute scheduler filters from config.
39
Artom Lifshitz595ae162018-05-23 10:19:18 -040040 This function checks whether the given compute scheduler filter is enabled
41 in the nova config file. If the scheduler_enabled_filters option is set to
42 'all' in tempest.conf then, this function returns True with assumption that
43 requested filter 'filter_name' is one of the enabled filters in nova
44 ("nova.scheduler.filters.all_filters").
Andrea Frittoli88eb6772017-08-07 21:06:27 +010045 """
46
Artom Lifshitz595ae162018-05-23 10:19:18 -040047 filters = CONF.compute_feature_enabled.scheduler_enabled_filters
Andrea Frittoli88eb6772017-08-07 21:06:27 +010048 if not filters:
49 return False
50 if 'all' in filters:
51 return True
52 if filter_name in filters:
53 return True
54 return False
55
56
Andrea Frittoli (andreaf)476e9192015-08-14 23:59:58 +010057def create_test_server(clients, validatable=False, validation_resources=None,
Joe Gordon8843f0f2015-03-17 15:07:34 -070058 tenant_network=None, wait_until=None,
Anusha Ramineni9aaef8b2016-01-19 10:56:40 +053059 volume_backed=False, name=None, flavor=None,
Slawek Kaplonskie3405ba2020-11-09 17:24:13 +010060 image_id=None, wait_for_sshable=True, **kwargs):
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000061 """Common wrapper utility returning a test server.
62
63 This method is a common wrapper returning a test server that can be
64 pingable or sshable.
65
Takashi NATSUME6d5a2b42015-09-08 11:27:49 +090066 :param clients: Client manager which provides OpenStack Tempest clients.
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000067 :param validatable: Whether the server will be pingable or sshable.
68 :param validation_resources: Resources created for the connection to the
Andrea Frittoli (andreaf)9df3a522016-07-06 14:09:48 +010069 server. Include a keypair, a security group and an IP.
Ken'ichi Ohmichid5bc31a2015-09-02 01:45:28 +000070 :param tenant_network: Tenant network to be used for creating a server.
Ken'ichi Ohmichifc25e692015-09-02 01:48:06 +000071 :param wait_until: Server status to wait for the server to reach after
Andrea Frittoli (andreaf)9df3a522016-07-06 14:09:48 +010072 its creation.
ghanshyam61db96e2016-12-16 12:49:25 +090073 :param volume_backed: Whether the server is volume backed or not.
Sergey Vilgelmeac094a2018-11-21 18:27:51 -060074 If this is true, a volume will be created and create server will be
75 requested with 'block_device_mapping_v2' populated with below values:
76
77 .. code-block:: python
78
79 bd_map_v2 = [{
80 'uuid': volume['volume']['id'],
81 'source_type': 'volume',
82 'destination_type': 'volume',
83 'boot_index': 0,
84 'delete_on_termination': True}]
85 kwargs['block_device_mapping_v2'] = bd_map_v2
86
87 If server needs to be booted from volume with other combination of bdm
88 inputs than mentioned above, then pass the bdm inputs explicitly as
89 kwargs and image_id as empty string ('').
Andrea Frittoli (andreaf)9df3a522016-07-06 14:09:48 +010090 :param name: Name of the server to be provisioned. If not defined a random
91 string ending with '-instance' will be generated.
92 :param flavor: Flavor of the server to be provisioned. If not defined,
93 CONF.compute.flavor_ref will be used instead.
94 :param image_id: ID of the image to be used to provision the server. If not
95 defined, CONF.compute.image_ref will be used instead.
Slawek Kaplonskie3405ba2020-11-09 17:24:13 +010096 :param wait_for_sshable: Check server's console log and wait until it will
97 be ready to login.
lei zhangdd552b22015-11-25 20:41:48 +080098 :returns: a tuple
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000099 """
100
101 # TODO(jlanoux) add support of wait_until PINGABLE/SSHABLE
102
Anusha Ramineni9aaef8b2016-01-19 10:56:40 +0530103 if name is None:
104 name = data_utils.rand_name(__name__ + "-instance")
105 if flavor is None:
106 flavor = CONF.compute.flavor_ref
107 if image_id is None:
108 image_id = CONF.compute.image_ref
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000109
110 kwargs = fixed_network.set_networks_kwarg(
111 tenant_network, kwargs) or {}
112
Ghanshyam4de44ae2015-12-25 10:34:00 +0900113 multiple_create_request = (max(kwargs.get('min_count', 0),
114 kwargs.get('max_count', 0)) > 1)
115
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000116 if CONF.validation.run_validation and validatable:
117 # As a first implementation, multiple pingable or sshable servers will
118 # not be supported
Ghanshyam4de44ae2015-12-25 10:34:00 +0900119 if multiple_create_request:
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000120 msg = ("Multiple pingable or sshable servers not supported at "
121 "this stage.")
122 raise ValueError(msg)
123
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100124 LOG.debug("Provisioning test server with validation resources %s",
125 validation_resources)
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000126 if 'security_groups' in kwargs:
127 kwargs['security_groups'].append(
128 {'name': validation_resources['security_group']['name']})
129 else:
130 try:
131 kwargs['security_groups'] = [
132 {'name': validation_resources['security_group']['name']}]
133 except KeyError:
134 LOG.debug("No security group provided.")
135
136 if 'key_name' not in kwargs:
137 try:
138 kwargs['key_name'] = validation_resources['keypair']['name']
139 except KeyError:
140 LOG.debug("No key provided.")
141
142 if CONF.validation.connect_method == 'floating':
Ken'ichi Ohmichifc25e692015-09-02 01:48:06 +0000143 if wait_until is None:
144 wait_until = 'ACTIVE'
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000145
Clark Boylan844180e2017-03-15 15:24:58 -0700146 if 'user_data' not in kwargs:
147 # If nothing overrides the default user data script then run
148 # a simple script on the host to print networking info. This is
149 # to aid in debugging ssh failures.
150 script = '''
151 #!/bin/sh
152 echo "Printing {user} user authorized keys"
153 cat ~{user}/.ssh/authorized_keys || true
154 '''.format(user=CONF.validation.image_ssh_user)
155 script_clean = textwrap.dedent(script).lstrip().encode('utf8')
156 script_b64 = base64.b64encode(script_clean)
157 kwargs['user_data'] = script_b64
158
Joe Gordon8843f0f2015-03-17 15:07:34 -0700159 if volume_backed:
zhuflc6ce5392016-08-17 14:34:37 +0800160 volume_name = data_utils.rand_name(__name__ + '-volume')
ghanshyam6c682ff2018-08-06 09:54:45 +0000161 volumes_client = clients.volumes_client_latest
ghanshyam3bd0d2b2017-03-23 01:57:28 +0000162 params = {'name': volume_name,
ghanshyam61db96e2016-12-16 12:49:25 +0900163 'imageRef': image_id,
164 'size': CONF.volume.volume_size}
Martin Kopec00e6d6c2019-06-05 14:30:06 +0000165 if CONF.compute.compute_volume_common_az:
166 params.setdefault('availability_zone',
167 CONF.compute.compute_volume_common_az)
ghanshyam61db96e2016-12-16 12:49:25 +0900168 volume = volumes_client.create_volume(**params)
mccasland, trevor (tm2086)0fedb412019-01-21 13:37:58 -0600169 try:
170 waiters.wait_for_volume_resource_status(volumes_client,
171 volume['volume']['id'],
172 'available')
173 except Exception:
174 with excutils.save_and_reraise_exception():
175 try:
176 volumes_client.delete_volume(volume['volume']['id'])
177 volumes_client.wait_for_resource_deletion(
178 volume['volume']['id'])
179 except Exception as exc:
180 LOG.exception("Deleting volume %s failed, exception %s",
181 volume['volume']['id'], exc)
Joe Gordon8843f0f2015-03-17 15:07:34 -0700182 bd_map_v2 = [{
183 'uuid': volume['volume']['id'],
184 'source_type': 'volume',
185 'destination_type': 'volume',
186 'boot_index': 0,
ghanshyam61db96e2016-12-16 12:49:25 +0900187 'delete_on_termination': True}]
Joe Gordon8843f0f2015-03-17 15:07:34 -0700188 kwargs['block_device_mapping_v2'] = bd_map_v2
189
190 # Since this is boot from volume an image does not need
191 # to be specified.
192 image_id = ''
193
Martin Kopec00e6d6c2019-06-05 14:30:06 +0000194 if CONF.compute.compute_volume_common_az:
195 kwargs.setdefault('availability_zone',
196 CONF.compute.compute_volume_common_az)
Ken'ichi Ohmichif2d436e2015-09-03 01:13:16 +0000197 body = clients.servers_client.create_server(name=name, imageRef=image_id,
198 flavorRef=flavor,
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000199 **kwargs)
Artom Lifshitzda48e4e2021-11-22 15:59:15 -0500200 request_id = body.response['x-openstack-request-id']
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000201
202 # handle the case of multiple servers
Ghanshyam4de44ae2015-12-25 10:34:00 +0900203 if multiple_create_request:
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000204 # Get servers created which name match with name param.
205 body_servers = clients.servers_client.list_servers()
206 servers = \
207 [s for s in body_servers['servers'] if s['name'].startswith(name)]
ghanshyam0f825252015-08-25 16:02:50 +0900208 else:
Ken'ichi Ohmichi54030522016-03-02 11:01:34 -0800209 body = rest_client.ResponseBody(body.response, body['server'])
ghanshyam0f825252015-08-25 16:02:50 +0900210 servers = [body]
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000211
Artom Lifshitz70d7a112017-05-10 17:25:54 +0000212 def _setup_validation_fip():
213 if CONF.service_available.neutron:
214 ifaces = clients.interfaces_client.list_interfaces(server['id'])
215 validation_port = None
216 for iface in ifaces['interfaceAttachments']:
217 if iface['net_id'] == tenant_network['id']:
218 validation_port = iface['port_id']
219 break
220 if not validation_port:
221 # NOTE(artom) This will get caught by the catch-all clause in
222 # the wait_until loop below
223 raise ValueError('Unable to setup floating IP for validation: '
224 'port not found on tenant network')
225 clients.floating_ips_client.update_floatingip(
226 validation_resources['floating_ip']['id'],
227 port_id=validation_port)
228 else:
229 fip_client = clients.compute_floating_ips_client
230 fip_client.associate_floating_ip_to_server(
231 floating_ip=validation_resources['floating_ip']['ip'],
232 server_id=servers[0]['id'])
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000233
Ken'ichi Ohmichifc25e692015-09-02 01:48:06 +0000234 if wait_until:
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000235 for server in servers:
236 try:
Ken'ichi Ohmichi0eb153c2015-07-13 02:18:25 +0000237 waiters.wait_for_server_status(
Artom Lifshitzda48e4e2021-11-22 15:59:15 -0500238 clients.servers_client, server['id'], wait_until,
239 request_id=request_id)
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000240
241 # Multiple validatable servers are not supported for now. Their
Masayuki Igawa544b3c82017-12-08 15:39:36 +0900242 # creation will fail with the condition above.
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000243 if CONF.validation.run_validation and validatable:
244 if CONF.validation.connect_method == 'floating':
Artom Lifshitz70d7a112017-05-10 17:25:54 +0000245 _setup_validation_fip()
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000246
247 except Exception:
248 with excutils.save_and_reraise_exception():
Jordan Pittier87ba2872016-03-08 11:43:11 +0100249 for server in servers:
250 try:
251 clients.servers_client.delete_server(
252 server['id'])
253 except Exception:
Jordan Pittier525ec712016-12-07 17:51:26 +0100254 LOG.exception('Deleting server %s failed',
255 server['id'])
Artom Lifshitz9b3f42b2017-06-19 05:46:32 +0000256 for server in servers:
257 # NOTE(artom) If the servers were booted with volumes
258 # and with delete_on_termination=False we need to wait
259 # for the servers to go away before proceeding with
260 # cleanup, otherwise we'll attempt to delete the
261 # volumes while they're still attached to servers that
262 # are in the process of being deleted.
263 try:
264 waiters.wait_for_server_termination(
265 clients.servers_client, server['id'])
266 except Exception:
267 LOG.exception('Server %s failed to delete in time',
268 server['id'])
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000269
Slawek Kaplonskie3405ba2020-11-09 17:24:13 +0100270 if (validatable and CONF.compute_feature_enabled.console_output and
271 wait_for_sshable):
272 waiters.wait_for_guest_os_boot(clients.servers_client, server['id'])
273
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000274 return body, servers
ghanshyam017b5fe2016-04-15 18:49:26 +0900275
276
ghanshyam4c1391c2016-12-01 13:13:06 +0900277def shelve_server(servers_client, server_id, force_shelve_offload=False):
ghanshyam017b5fe2016-04-15 18:49:26 +0900278 """Common wrapper utility to shelve server.
279
280 This method is a common wrapper to make server in 'SHELVED'
281 or 'SHELVED_OFFLOADED' state.
282
ghanshyam4c1391c2016-12-01 13:13:06 +0900283 :param servers_clients: Compute servers client instance.
ghanshyam017b5fe2016-04-15 18:49:26 +0900284 :param server_id: Server to make in shelve state
285 :param force_shelve_offload: Forcefully offload shelve server if it
286 is configured not to offload server
287 automatically after offload time.
288 """
ghanshyam4c1391c2016-12-01 13:13:06 +0900289 servers_client.shelve_server(server_id)
ghanshyam017b5fe2016-04-15 18:49:26 +0900290
291 offload_time = CONF.compute.shelved_offload_time
292 if offload_time >= 0:
ghanshyam4c1391c2016-12-01 13:13:06 +0900293 waiters.wait_for_server_status(servers_client, server_id,
ghanshyam017b5fe2016-04-15 18:49:26 +0900294 'SHELVED_OFFLOADED',
295 extra_timeout=offload_time)
296 else:
ghanshyam4c1391c2016-12-01 13:13:06 +0900297 waiters.wait_for_server_status(servers_client, server_id, 'SHELVED')
ghanshyam017b5fe2016-04-15 18:49:26 +0900298 if force_shelve_offload:
ghanshyam4c1391c2016-12-01 13:13:06 +0900299 servers_client.shelve_offload_server(server_id)
300 waiters.wait_for_server_status(servers_client, server_id,
ghanshyam017b5fe2016-04-15 18:49:26 +0900301 'SHELVED_OFFLOADED')
Markus Zoellerae36ce82017-03-20 16:27:26 +0100302
303
304def create_websocket(url):
305 url = urlparse.urlparse(url)
Mohammed Naseraa5dd9a2017-12-29 18:52:01 -0500306
307 # NOTE(mnaser): It is possible that there is no port specified, so fall
308 # back to the default port based on the scheme.
309 port = url.port or (443 if url.scheme == 'https' else 80)
310
311 for res in socket.getaddrinfo(url.hostname, port,
Jens Harbott6bc422d2017-09-27 10:29:34 +0000312 socket.AF_UNSPEC, socket.SOCK_STREAM):
313 af, socktype, proto, _, sa = res
314 client_socket = socket.socket(af, socktype, proto)
315 if url.scheme == 'https':
316 client_socket = ssl.wrap_socket(client_socket)
317 client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
318 try:
319 client_socket.connect(sa)
320 except socket.error:
321 client_socket.close()
322 continue
323 break
xxj8eb90982017-04-10 21:18:39 +0800324 else:
Jens Harbott6bc422d2017-09-27 10:29:34 +0000325 raise socket.error('WebSocket creation failed')
Markus Zoellerae36ce82017-03-20 16:27:26 +0100326 # Turn the Socket into a WebSocket to do the communication
327 return _WebSocket(client_socket, url)
328
329
330class _WebSocket(object):
331 def __init__(self, client_socket, url):
332 """Contructor for the WebSocket wrapper to the socket."""
333 self._socket = client_socket
jianghua wangd22514a2017-05-08 08:05:04 +0100334 # cached stream for early frames.
335 self.cached_stream = b''
Markus Zoellerae36ce82017-03-20 16:27:26 +0100336 # Upgrade the HTTP connection to a WebSocket
337 self._upgrade(url)
338
jianghua wangd22514a2017-05-08 08:05:04 +0100339 def _recv(self, recv_size):
340 """Wrapper to receive data from the cached stream or socket."""
341 if recv_size <= 0:
342 return None
343
344 data_from_cached = b''
345 data_from_socket = b''
346 if len(self.cached_stream) > 0:
347 read_from_cached = min(len(self.cached_stream), recv_size)
348 data_from_cached += self.cached_stream[:read_from_cached]
349 self.cached_stream = self.cached_stream[read_from_cached:]
350 recv_size -= read_from_cached
351 if recv_size > 0:
352 data_from_socket = self._socket.recv(recv_size)
353 return data_from_cached + data_from_socket
354
Markus Zoellerae36ce82017-03-20 16:27:26 +0100355 def receive_frame(self):
356 """Wrapper for receiving data to parse the WebSocket frame format"""
357 # We need to loop until we either get some bytes back in the frame
358 # or no data was received (meaning the socket was closed). This is
359 # done to handle the case where we get back some empty frames
360 while True:
jianghua wangd22514a2017-05-08 08:05:04 +0100361 header = self._recv(2)
Markus Zoellerae36ce82017-03-20 16:27:26 +0100362 # If we didn't receive any data, just return None
Masayuki Igawa0c0f0142017-04-10 17:22:02 +0900363 if not header:
Markus Zoellerae36ce82017-03-20 16:27:26 +0100364 return None
365 # We will make the assumption that we are only dealing with
366 # frames less than 125 bytes here (for the negotiation) and
367 # that only the 2nd byte contains the length, and since the
368 # server doesn't do masking, we can just read the data length
likui7d91c872020-09-22 12:29:16 +0800369 if int(header[1]) & 127 > 0:
370 return self._recv(int(header[1]) & 127)
Markus Zoellerae36ce82017-03-20 16:27:26 +0100371
372 def send_frame(self, data):
373 """Wrapper for sending data to add in the WebSocket frame format."""
374 frame_bytes = list()
375 # For the first byte, want to say we are sending binary data (130)
376 frame_bytes.append(130)
377 # Only sending negotiation data so don't need to worry about > 125
378 # We do need to add the bit that says we are masking the data
379 frame_bytes.append(len(data) | 128)
380 # We don't really care about providing a random mask for security
381 # So we will just hard-code a value since a test program
382 mask = [7, 2, 1, 9]
383 for i in range(len(mask)):
384 frame_bytes.append(mask[i])
385 # Mask each of the actual data bytes that we are going to send
386 for i in range(len(data)):
likui7d91c872020-09-22 12:29:16 +0800387 frame_bytes.append(int(data[i]) ^ mask[i % 4])
Markus Zoellerae36ce82017-03-20 16:27:26 +0100388 # Convert our integer list to a binary array of bytes
389 frame_bytes = struct.pack('!%iB' % len(frame_bytes), * frame_bytes)
390 self._socket.sendall(frame_bytes)
391
392 def close(self):
393 """Helper method to close the connection."""
394 # Close down the real socket connection and exit the test program
395 if self._socket is not None:
396 self._socket.shutdown(1)
397 self._socket.close()
398 self._socket = None
399
400 def _upgrade(self, url):
401 """Upgrade the HTTP connection to a WebSocket and verify."""
melanie witt27ba9332019-04-26 02:33:20 +0000402 # It is possible to pass the path as a query parameter in the request,
403 # so use it if present
Jason Lica0fad02020-04-06 10:56:43 -0500404 # Given noVNC format
405 # https://x.com/vnc_auto.html?path=%3Ftoken%3Dxxx,
406 # url format is
407 # ParseResult(scheme='https', netloc='x.com',
408 # path='/vnc_auto.html', params='',
409 # query='path=%3Ftoken%3Dxxx', fragment='').
410 # qparams format is {'path': ['?token=xxx']}
melanie witt27ba9332019-04-26 02:33:20 +0000411 qparams = urlparse.parse_qs(url.query)
Jason Lica0fad02020-04-06 10:56:43 -0500412 # according to references
413 # https://docs.python.org/3/library/urllib.parse.html
414 # https://tools.ietf.org/html/rfc3986#section-3.4
415 # qparams['path'][0] format is '?token=xxx' without / prefix
416 # remove / in /websockify to comply to references.
417 path = qparams['path'][0] if 'path' in qparams else 'websockify'
418 # Fix websocket request format by adding / prefix.
419 # Updated request format: GET /?token=xxx HTTP/1.1
420 # or GET /websockify HTTP/1.1
421 reqdata = 'GET /%s HTTP/1.1\r\n' % path
Mohammed Naseraa5dd9a2017-12-29 18:52:01 -0500422 reqdata += 'Host: %s' % url.hostname
423 # Add port only if we have one specified
424 if url.port:
425 reqdata += ':%s' % url.port
426 # Line-ending for Host header
427 reqdata += '\r\n'
Markus Zoellerae36ce82017-03-20 16:27:26 +0100428 # Tell the HTTP Server to Upgrade the connection to a WebSocket
429 reqdata += 'Upgrade: websocket\r\nConnection: Upgrade\r\n'
melanie witt27ba9332019-04-26 02:33:20 +0000430 # The token=xxx is sent as a Cookie not in the URI for noVNC < v1.1.0
Markus Zoellerae36ce82017-03-20 16:27:26 +0100431 reqdata += 'Cookie: %s\r\n' % url.query
432 # Use a hard-coded WebSocket key since a test program
433 reqdata += 'Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n'
434 reqdata += 'Sec-WebSocket-Version: 13\r\n'
435 # We are choosing to use binary even though browser may do Base64
436 reqdata += 'Sec-WebSocket-Protocol: binary\r\n\r\n'
437 # Send the HTTP GET request and get the response back
438 self._socket.sendall(reqdata.encode('utf8'))
439 self.response = data = self._socket.recv(4096)
440 # Loop through & concatenate all of the data in the response body
jianghua wangd22514a2017-05-08 08:05:04 +0100441 end_loc = self.response.find(b'\r\n\r\n')
442 while data and end_loc < 0:
Markus Zoellerae36ce82017-03-20 16:27:26 +0100443 data = self._socket.recv(4096)
444 self.response += data
jianghua wangd22514a2017-05-08 08:05:04 +0100445 end_loc = self.response.find(b'\r\n\r\n')
446
447 if len(self.response) > end_loc + 4:
448 # In case some frames (e.g. the first RFP negotiation) have
449 # arrived, cache it for next reading.
450 self.cached_stream = self.response[end_loc + 4:]
451 # ensure response ends with '\r\n\r\n'.
452 self.response = self.response[:end_loc + 4]