blob: d34cd6d140621e6fc290550a14fc36a2e4dcceec [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
Lee Yarwood20556df2021-11-12 09:38:18 +000028from tempest import exceptions
Matthew Treinishb19c55d2017-07-17 12:38:35 -040029from tempest.lib.common import fixed_network
Ken'ichi Ohmichi54030522016-03-02 11:01:34 -080030from tempest.lib.common import rest_client
Ken'ichi Ohmichi757833a2017-03-10 10:30:30 -080031from tempest.lib.common.utils import data_utils
Lee Yarwood20556df2021-11-12 09:38:18 +000032from tempest.lib import exceptions as lib_exc
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000033
34CONF = config.CONF
35
36LOG = logging.getLogger(__name__)
37
38
Andrea Frittoli88eb6772017-08-07 21:06:27 +010039def is_scheduler_filter_enabled(filter_name):
40 """Check the list of enabled compute scheduler filters from config.
41
Artom Lifshitz595ae162018-05-23 10:19:18 -040042 This function checks whether the given compute scheduler filter is enabled
43 in the nova config file. If the scheduler_enabled_filters option is set to
44 'all' in tempest.conf then, this function returns True with assumption that
45 requested filter 'filter_name' is one of the enabled filters in nova
46 ("nova.scheduler.filters.all_filters").
Andrea Frittoli88eb6772017-08-07 21:06:27 +010047 """
48
Artom Lifshitz595ae162018-05-23 10:19:18 -040049 filters = CONF.compute_feature_enabled.scheduler_enabled_filters
Andrea Frittoli88eb6772017-08-07 21:06:27 +010050 if not filters:
51 return False
52 if 'all' in filters:
53 return True
54 if filter_name in filters:
55 return True
56 return False
57
58
Lee Yarwood20556df2021-11-12 09:38:18 +000059def get_server_ip(server, validation_resources=None):
60 """Get the server fixed or floating IP.
61
62 Based on the configuration we're in, return a correct ip
63 address for validating that a guest is up.
64
65 :param server: The server dict as returned by the API
66 :param validation_resources: The dict of validation resources
67 provisioned for the server.
68 """
69 if CONF.validation.connect_method == 'floating':
70 if validation_resources:
71 return validation_resources['floating_ip']['ip']
72 else:
73 msg = ('When validation.connect_method equals floating, '
74 'validation_resources cannot be None')
75 raise lib_exc.InvalidParam(invalid_param=msg)
76 elif CONF.validation.connect_method == 'fixed':
77 addresses = server['addresses'][CONF.validation.network_for_ssh]
78 for address in addresses:
79 if address['version'] == CONF.validation.ip_version_for_ssh:
80 return address['addr']
81 raise exceptions.ServerUnreachable(server_id=server['id'])
82 else:
83 raise lib_exc.InvalidConfiguration()
84
85
Andrea Frittoli (andreaf)476e9192015-08-14 23:59:58 +010086def create_test_server(clients, validatable=False, validation_resources=None,
Joe Gordon8843f0f2015-03-17 15:07:34 -070087 tenant_network=None, wait_until=None,
Anusha Ramineni9aaef8b2016-01-19 10:56:40 +053088 volume_backed=False, name=None, flavor=None,
Lee Yarwood1a76c2c2021-11-11 15:28:53 +000089 image_id=None, **kwargs):
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000090 """Common wrapper utility returning a test server.
91
92 This method is a common wrapper returning a test server that can be
93 pingable or sshable.
94
Takashi NATSUME6d5a2b42015-09-08 11:27:49 +090095 :param clients: Client manager which provides OpenStack Tempest clients.
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000096 :param validatable: Whether the server will be pingable or sshable.
97 :param validation_resources: Resources created for the connection to the
Andrea Frittoli (andreaf)9df3a522016-07-06 14:09:48 +010098 server. Include a keypair, a security group and an IP.
Ken'ichi Ohmichid5bc31a2015-09-02 01:45:28 +000099 :param tenant_network: Tenant network to be used for creating a server.
Ken'ichi Ohmichifc25e692015-09-02 01:48:06 +0000100 :param wait_until: Server status to wait for the server to reach after
Andrea Frittoli (andreaf)9df3a522016-07-06 14:09:48 +0100101 its creation.
ghanshyam61db96e2016-12-16 12:49:25 +0900102 :param volume_backed: Whether the server is volume backed or not.
Sergey Vilgelmeac094a2018-11-21 18:27:51 -0600103 If this is true, a volume will be created and create server will be
104 requested with 'block_device_mapping_v2' populated with below values:
105
106 .. code-block:: python
107
108 bd_map_v2 = [{
109 'uuid': volume['volume']['id'],
110 'source_type': 'volume',
111 'destination_type': 'volume',
112 'boot_index': 0,
113 'delete_on_termination': True}]
114 kwargs['block_device_mapping_v2'] = bd_map_v2
115
116 If server needs to be booted from volume with other combination of bdm
117 inputs than mentioned above, then pass the bdm inputs explicitly as
118 kwargs and image_id as empty string ('').
Andrea Frittoli (andreaf)9df3a522016-07-06 14:09:48 +0100119 :param name: Name of the server to be provisioned. If not defined a random
120 string ending with '-instance' will be generated.
121 :param flavor: Flavor of the server to be provisioned. If not defined,
122 CONF.compute.flavor_ref will be used instead.
123 :param image_id: ID of the image to be used to provision the server. If not
124 defined, CONF.compute.image_ref will be used instead.
lei zhangdd552b22015-11-25 20:41:48 +0800125 :returns: a tuple
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000126 """
127
128 # TODO(jlanoux) add support of wait_until PINGABLE/SSHABLE
129
Anusha Ramineni9aaef8b2016-01-19 10:56:40 +0530130 if name is None:
131 name = data_utils.rand_name(__name__ + "-instance")
132 if flavor is None:
133 flavor = CONF.compute.flavor_ref
134 if image_id is None:
135 image_id = CONF.compute.image_ref
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000136
137 kwargs = fixed_network.set_networks_kwarg(
138 tenant_network, kwargs) or {}
139
Ghanshyam4de44ae2015-12-25 10:34:00 +0900140 multiple_create_request = (max(kwargs.get('min_count', 0),
141 kwargs.get('max_count', 0)) > 1)
142
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000143 if CONF.validation.run_validation and validatable:
144 # As a first implementation, multiple pingable or sshable servers will
145 # not be supported
Ghanshyam4de44ae2015-12-25 10:34:00 +0900146 if multiple_create_request:
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000147 msg = ("Multiple pingable or sshable servers not supported at "
148 "this stage.")
149 raise ValueError(msg)
150
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100151 LOG.debug("Provisioning test server with validation resources %s",
152 validation_resources)
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000153 if 'security_groups' in kwargs:
154 kwargs['security_groups'].append(
155 {'name': validation_resources['security_group']['name']})
156 else:
157 try:
158 kwargs['security_groups'] = [
159 {'name': validation_resources['security_group']['name']}]
160 except KeyError:
161 LOG.debug("No security group provided.")
162
163 if 'key_name' not in kwargs:
164 try:
165 kwargs['key_name'] = validation_resources['keypair']['name']
166 except KeyError:
167 LOG.debug("No key provided.")
168
169 if CONF.validation.connect_method == 'floating':
Ken'ichi Ohmichifc25e692015-09-02 01:48:06 +0000170 if wait_until is None:
171 wait_until = 'ACTIVE'
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000172
Clark Boylan844180e2017-03-15 15:24:58 -0700173 if 'user_data' not in kwargs:
174 # If nothing overrides the default user data script then run
175 # a simple script on the host to print networking info. This is
176 # to aid in debugging ssh failures.
177 script = '''
178 #!/bin/sh
179 echo "Printing {user} user authorized keys"
180 cat ~{user}/.ssh/authorized_keys || true
181 '''.format(user=CONF.validation.image_ssh_user)
182 script_clean = textwrap.dedent(script).lstrip().encode('utf8')
183 script_b64 = base64.b64encode(script_clean)
184 kwargs['user_data'] = script_b64
185
Joe Gordon8843f0f2015-03-17 15:07:34 -0700186 if volume_backed:
zhuflc6ce5392016-08-17 14:34:37 +0800187 volume_name = data_utils.rand_name(__name__ + '-volume')
ghanshyam6c682ff2018-08-06 09:54:45 +0000188 volumes_client = clients.volumes_client_latest
ghanshyam3bd0d2b2017-03-23 01:57:28 +0000189 params = {'name': volume_name,
ghanshyam61db96e2016-12-16 12:49:25 +0900190 'imageRef': image_id,
191 'size': CONF.volume.volume_size}
Martin Kopec00e6d6c2019-06-05 14:30:06 +0000192 if CONF.compute.compute_volume_common_az:
193 params.setdefault('availability_zone',
194 CONF.compute.compute_volume_common_az)
ghanshyam61db96e2016-12-16 12:49:25 +0900195 volume = volumes_client.create_volume(**params)
mccasland, trevor (tm2086)0fedb412019-01-21 13:37:58 -0600196 try:
197 waiters.wait_for_volume_resource_status(volumes_client,
198 volume['volume']['id'],
199 'available')
200 except Exception:
201 with excutils.save_and_reraise_exception():
202 try:
203 volumes_client.delete_volume(volume['volume']['id'])
204 volumes_client.wait_for_resource_deletion(
205 volume['volume']['id'])
206 except Exception as exc:
207 LOG.exception("Deleting volume %s failed, exception %s",
208 volume['volume']['id'], exc)
Joe Gordon8843f0f2015-03-17 15:07:34 -0700209 bd_map_v2 = [{
210 'uuid': volume['volume']['id'],
211 'source_type': 'volume',
212 'destination_type': 'volume',
213 'boot_index': 0,
ghanshyam61db96e2016-12-16 12:49:25 +0900214 'delete_on_termination': True}]
Joe Gordon8843f0f2015-03-17 15:07:34 -0700215 kwargs['block_device_mapping_v2'] = bd_map_v2
216
217 # Since this is boot from volume an image does not need
218 # to be specified.
219 image_id = ''
220
Martin Kopec00e6d6c2019-06-05 14:30:06 +0000221 if CONF.compute.compute_volume_common_az:
222 kwargs.setdefault('availability_zone',
223 CONF.compute.compute_volume_common_az)
Ken'ichi Ohmichif2d436e2015-09-03 01:13:16 +0000224 body = clients.servers_client.create_server(name=name, imageRef=image_id,
225 flavorRef=flavor,
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000226 **kwargs)
Artom Lifshitzda48e4e2021-11-22 15:59:15 -0500227 request_id = body.response['x-openstack-request-id']
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000228
229 # handle the case of multiple servers
Ghanshyam4de44ae2015-12-25 10:34:00 +0900230 if multiple_create_request:
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000231 # Get servers created which name match with name param.
232 body_servers = clients.servers_client.list_servers()
233 servers = \
234 [s for s in body_servers['servers'] if s['name'].startswith(name)]
ghanshyam0f825252015-08-25 16:02:50 +0900235 else:
Ken'ichi Ohmichi54030522016-03-02 11:01:34 -0800236 body = rest_client.ResponseBody(body.response, body['server'])
ghanshyam0f825252015-08-25 16:02:50 +0900237 servers = [body]
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000238
Artom Lifshitz70d7a112017-05-10 17:25:54 +0000239 def _setup_validation_fip():
240 if CONF.service_available.neutron:
241 ifaces = clients.interfaces_client.list_interfaces(server['id'])
242 validation_port = None
243 for iface in ifaces['interfaceAttachments']:
244 if iface['net_id'] == tenant_network['id']:
245 validation_port = iface['port_id']
246 break
247 if not validation_port:
248 # NOTE(artom) This will get caught by the catch-all clause in
249 # the wait_until loop below
250 raise ValueError('Unable to setup floating IP for validation: '
251 'port not found on tenant network')
252 clients.floating_ips_client.update_floatingip(
253 validation_resources['floating_ip']['id'],
254 port_id=validation_port)
255 else:
256 fip_client = clients.compute_floating_ips_client
257 fip_client.associate_floating_ip_to_server(
258 floating_ip=validation_resources['floating_ip']['ip'],
259 server_id=servers[0]['id'])
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000260
Ken'ichi Ohmichifc25e692015-09-02 01:48:06 +0000261 if wait_until:
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000262 for server in servers:
263 try:
Ken'ichi Ohmichi0eb153c2015-07-13 02:18:25 +0000264 waiters.wait_for_server_status(
Artom Lifshitzda48e4e2021-11-22 15:59:15 -0500265 clients.servers_client, server['id'], wait_until,
266 request_id=request_id)
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000267
268 # Multiple validatable servers are not supported for now. Their
Masayuki Igawa544b3c82017-12-08 15:39:36 +0900269 # creation will fail with the condition above.
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000270 if CONF.validation.run_validation and validatable:
271 if CONF.validation.connect_method == 'floating':
Artom Lifshitz70d7a112017-05-10 17:25:54 +0000272 _setup_validation_fip()
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000273
274 except Exception:
275 with excutils.save_and_reraise_exception():
Jordan Pittier87ba2872016-03-08 11:43:11 +0100276 for server in servers:
277 try:
278 clients.servers_client.delete_server(
279 server['id'])
280 except Exception:
Jordan Pittier525ec712016-12-07 17:51:26 +0100281 LOG.exception('Deleting server %s failed',
282 server['id'])
Artom Lifshitz9b3f42b2017-06-19 05:46:32 +0000283 for server in servers:
284 # NOTE(artom) If the servers were booted with volumes
285 # and with delete_on_termination=False we need to wait
286 # for the servers to go away before proceeding with
287 # cleanup, otherwise we'll attempt to delete the
288 # volumes while they're still attached to servers that
289 # are in the process of being deleted.
290 try:
291 waiters.wait_for_server_termination(
292 clients.servers_client, server['id'])
293 except Exception:
294 LOG.exception('Server %s failed to delete in time',
295 server['id'])
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000296
297 return body, servers
ghanshyam017b5fe2016-04-15 18:49:26 +0900298
299
ghanshyam4c1391c2016-12-01 13:13:06 +0900300def shelve_server(servers_client, server_id, force_shelve_offload=False):
ghanshyam017b5fe2016-04-15 18:49:26 +0900301 """Common wrapper utility to shelve server.
302
303 This method is a common wrapper to make server in 'SHELVED'
304 or 'SHELVED_OFFLOADED' state.
305
ghanshyam4c1391c2016-12-01 13:13:06 +0900306 :param servers_clients: Compute servers client instance.
ghanshyam017b5fe2016-04-15 18:49:26 +0900307 :param server_id: Server to make in shelve state
308 :param force_shelve_offload: Forcefully offload shelve server if it
309 is configured not to offload server
310 automatically after offload time.
311 """
ghanshyam4c1391c2016-12-01 13:13:06 +0900312 servers_client.shelve_server(server_id)
ghanshyam017b5fe2016-04-15 18:49:26 +0900313
314 offload_time = CONF.compute.shelved_offload_time
315 if offload_time >= 0:
ghanshyam4c1391c2016-12-01 13:13:06 +0900316 waiters.wait_for_server_status(servers_client, server_id,
ghanshyam017b5fe2016-04-15 18:49:26 +0900317 'SHELVED_OFFLOADED',
318 extra_timeout=offload_time)
319 else:
ghanshyam4c1391c2016-12-01 13:13:06 +0900320 waiters.wait_for_server_status(servers_client, server_id, 'SHELVED')
ghanshyam017b5fe2016-04-15 18:49:26 +0900321 if force_shelve_offload:
ghanshyam4c1391c2016-12-01 13:13:06 +0900322 servers_client.shelve_offload_server(server_id)
323 waiters.wait_for_server_status(servers_client, server_id,
ghanshyam017b5fe2016-04-15 18:49:26 +0900324 'SHELVED_OFFLOADED')
Markus Zoellerae36ce82017-03-20 16:27:26 +0100325
326
327def create_websocket(url):
328 url = urlparse.urlparse(url)
Mohammed Naseraa5dd9a2017-12-29 18:52:01 -0500329
330 # NOTE(mnaser): It is possible that there is no port specified, so fall
331 # back to the default port based on the scheme.
332 port = url.port or (443 if url.scheme == 'https' else 80)
333
334 for res in socket.getaddrinfo(url.hostname, port,
Jens Harbott6bc422d2017-09-27 10:29:34 +0000335 socket.AF_UNSPEC, socket.SOCK_STREAM):
336 af, socktype, proto, _, sa = res
337 client_socket = socket.socket(af, socktype, proto)
338 if url.scheme == 'https':
339 client_socket = ssl.wrap_socket(client_socket)
340 client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
341 try:
342 client_socket.connect(sa)
343 except socket.error:
344 client_socket.close()
345 continue
346 break
xxj8eb90982017-04-10 21:18:39 +0800347 else:
Jens Harbott6bc422d2017-09-27 10:29:34 +0000348 raise socket.error('WebSocket creation failed')
Markus Zoellerae36ce82017-03-20 16:27:26 +0100349 # Turn the Socket into a WebSocket to do the communication
350 return _WebSocket(client_socket, url)
351
352
353class _WebSocket(object):
354 def __init__(self, client_socket, url):
355 """Contructor for the WebSocket wrapper to the socket."""
356 self._socket = client_socket
jianghua wangd22514a2017-05-08 08:05:04 +0100357 # cached stream for early frames.
358 self.cached_stream = b''
Markus Zoellerae36ce82017-03-20 16:27:26 +0100359 # Upgrade the HTTP connection to a WebSocket
360 self._upgrade(url)
361
jianghua wangd22514a2017-05-08 08:05:04 +0100362 def _recv(self, recv_size):
363 """Wrapper to receive data from the cached stream or socket."""
364 if recv_size <= 0:
365 return None
366
367 data_from_cached = b''
368 data_from_socket = b''
369 if len(self.cached_stream) > 0:
370 read_from_cached = min(len(self.cached_stream), recv_size)
371 data_from_cached += self.cached_stream[:read_from_cached]
372 self.cached_stream = self.cached_stream[read_from_cached:]
373 recv_size -= read_from_cached
374 if recv_size > 0:
375 data_from_socket = self._socket.recv(recv_size)
376 return data_from_cached + data_from_socket
377
Markus Zoellerae36ce82017-03-20 16:27:26 +0100378 def receive_frame(self):
379 """Wrapper for receiving data to parse the WebSocket frame format"""
380 # We need to loop until we either get some bytes back in the frame
381 # or no data was received (meaning the socket was closed). This is
382 # done to handle the case where we get back some empty frames
383 while True:
jianghua wangd22514a2017-05-08 08:05:04 +0100384 header = self._recv(2)
Markus Zoellerae36ce82017-03-20 16:27:26 +0100385 # If we didn't receive any data, just return None
Masayuki Igawa0c0f0142017-04-10 17:22:02 +0900386 if not header:
Markus Zoellerae36ce82017-03-20 16:27:26 +0100387 return None
388 # We will make the assumption that we are only dealing with
389 # frames less than 125 bytes here (for the negotiation) and
390 # that only the 2nd byte contains the length, and since the
391 # server doesn't do masking, we can just read the data length
likui7d91c872020-09-22 12:29:16 +0800392 if int(header[1]) & 127 > 0:
393 return self._recv(int(header[1]) & 127)
Markus Zoellerae36ce82017-03-20 16:27:26 +0100394
395 def send_frame(self, data):
396 """Wrapper for sending data to add in the WebSocket frame format."""
397 frame_bytes = list()
398 # For the first byte, want to say we are sending binary data (130)
399 frame_bytes.append(130)
400 # Only sending negotiation data so don't need to worry about > 125
401 # We do need to add the bit that says we are masking the data
402 frame_bytes.append(len(data) | 128)
403 # We don't really care about providing a random mask for security
404 # So we will just hard-code a value since a test program
405 mask = [7, 2, 1, 9]
406 for i in range(len(mask)):
407 frame_bytes.append(mask[i])
408 # Mask each of the actual data bytes that we are going to send
409 for i in range(len(data)):
likui7d91c872020-09-22 12:29:16 +0800410 frame_bytes.append(int(data[i]) ^ mask[i % 4])
Markus Zoellerae36ce82017-03-20 16:27:26 +0100411 # Convert our integer list to a binary array of bytes
412 frame_bytes = struct.pack('!%iB' % len(frame_bytes), * frame_bytes)
413 self._socket.sendall(frame_bytes)
414
415 def close(self):
416 """Helper method to close the connection."""
417 # Close down the real socket connection and exit the test program
418 if self._socket is not None:
419 self._socket.shutdown(1)
420 self._socket.close()
421 self._socket = None
422
423 def _upgrade(self, url):
424 """Upgrade the HTTP connection to a WebSocket and verify."""
melanie witt27ba9332019-04-26 02:33:20 +0000425 # It is possible to pass the path as a query parameter in the request,
426 # so use it if present
Jason Lica0fad02020-04-06 10:56:43 -0500427 # Given noVNC format
428 # https://x.com/vnc_auto.html?path=%3Ftoken%3Dxxx,
429 # url format is
430 # ParseResult(scheme='https', netloc='x.com',
431 # path='/vnc_auto.html', params='',
432 # query='path=%3Ftoken%3Dxxx', fragment='').
433 # qparams format is {'path': ['?token=xxx']}
melanie witt27ba9332019-04-26 02:33:20 +0000434 qparams = urlparse.parse_qs(url.query)
Jason Lica0fad02020-04-06 10:56:43 -0500435 # according to references
436 # https://docs.python.org/3/library/urllib.parse.html
437 # https://tools.ietf.org/html/rfc3986#section-3.4
438 # qparams['path'][0] format is '?token=xxx' without / prefix
439 # remove / in /websockify to comply to references.
440 path = qparams['path'][0] if 'path' in qparams else 'websockify'
441 # Fix websocket request format by adding / prefix.
442 # Updated request format: GET /?token=xxx HTTP/1.1
443 # or GET /websockify HTTP/1.1
444 reqdata = 'GET /%s HTTP/1.1\r\n' % path
Mohammed Naseraa5dd9a2017-12-29 18:52:01 -0500445 reqdata += 'Host: %s' % url.hostname
446 # Add port only if we have one specified
447 if url.port:
448 reqdata += ':%s' % url.port
449 # Line-ending for Host header
450 reqdata += '\r\n'
Markus Zoellerae36ce82017-03-20 16:27:26 +0100451 # Tell the HTTP Server to Upgrade the connection to a WebSocket
452 reqdata += 'Upgrade: websocket\r\nConnection: Upgrade\r\n'
melanie witt27ba9332019-04-26 02:33:20 +0000453 # The token=xxx is sent as a Cookie not in the URI for noVNC < v1.1.0
Markus Zoellerae36ce82017-03-20 16:27:26 +0100454 reqdata += 'Cookie: %s\r\n' % url.query
455 # Use a hard-coded WebSocket key since a test program
456 reqdata += 'Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n'
457 reqdata += 'Sec-WebSocket-Version: 13\r\n'
458 # We are choosing to use binary even though browser may do Base64
459 reqdata += 'Sec-WebSocket-Protocol: binary\r\n\r\n'
460 # Send the HTTP GET request and get the response back
461 self._socket.sendall(reqdata.encode('utf8'))
462 self.response = data = self._socket.recv(4096)
463 # Loop through & concatenate all of the data in the response body
jianghua wangd22514a2017-05-08 08:05:04 +0100464 end_loc = self.response.find(b'\r\n\r\n')
465 while data and end_loc < 0:
Markus Zoellerae36ce82017-03-20 16:27:26 +0100466 data = self._socket.recv(4096)
467 self.response += data
jianghua wangd22514a2017-05-08 08:05:04 +0100468 end_loc = self.response.find(b'\r\n\r\n')
469
470 if len(self.response) > end_loc + 4:
471 # In case some frames (e.g. the first RFP negotiation) have
472 # arrived, cache it for next reading.
473 self.cached_stream = self.response[end_loc + 4:]
474 # ensure response ends with '\r\n\r\n'.
475 self.response = self.response[:end_loc + 4]