blob: 43e30ad33be94d1697f185bb8f40248003f5a8f6 [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
Lee Yarwood0b4bc3d2021-11-11 17:45:25 +000026from tempest.common.utils.linux import remote_client
Ken'ichi Ohmichi0eb153c2015-07-13 02:18:25 +000027from tempest.common import waiters
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000028from tempest import config
Lee Yarwood20556df2021-11-12 09:38:18 +000029from tempest import exceptions
Matthew Treinishb19c55d2017-07-17 12:38:35 -040030from tempest.lib.common import fixed_network
Ken'ichi Ohmichi54030522016-03-02 11:01:34 -080031from tempest.lib.common import rest_client
Ken'ichi Ohmichi757833a2017-03-10 10:30:30 -080032from tempest.lib.common.utils import data_utils
Lee Yarwood20556df2021-11-12 09:38:18 +000033from tempest.lib import exceptions as lib_exc
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000034
35CONF = config.CONF
36
37LOG = logging.getLogger(__name__)
38
39
Andrea Frittoli88eb6772017-08-07 21:06:27 +010040def is_scheduler_filter_enabled(filter_name):
41 """Check the list of enabled compute scheduler filters from config.
42
Artom Lifshitz595ae162018-05-23 10:19:18 -040043 This function checks whether the given compute scheduler filter is enabled
44 in the nova config file. If the scheduler_enabled_filters option is set to
45 'all' in tempest.conf then, this function returns True with assumption that
46 requested filter 'filter_name' is one of the enabled filters in nova
47 ("nova.scheduler.filters.all_filters").
Andrea Frittoli88eb6772017-08-07 21:06:27 +010048 """
49
Artom Lifshitz595ae162018-05-23 10:19:18 -040050 filters = CONF.compute_feature_enabled.scheduler_enabled_filters
Andrea Frittoli88eb6772017-08-07 21:06:27 +010051 if not filters:
52 return False
53 if 'all' in filters:
54 return True
55 if filter_name in filters:
56 return True
57 return False
58
59
Lee Yarwood20556df2021-11-12 09:38:18 +000060def get_server_ip(server, validation_resources=None):
61 """Get the server fixed or floating IP.
62
63 Based on the configuration we're in, return a correct ip
64 address for validating that a guest is up.
65
66 :param server: The server dict as returned by the API
67 :param validation_resources: The dict of validation resources
68 provisioned for the server.
69 """
70 if CONF.validation.connect_method == 'floating':
71 if validation_resources:
72 return validation_resources['floating_ip']['ip']
73 else:
74 msg = ('When validation.connect_method equals floating, '
75 'validation_resources cannot be None')
76 raise lib_exc.InvalidParam(invalid_param=msg)
77 elif CONF.validation.connect_method == 'fixed':
78 addresses = server['addresses'][CONF.validation.network_for_ssh]
79 for address in addresses:
80 if address['version'] == CONF.validation.ip_version_for_ssh:
81 return address['addr']
82 raise exceptions.ServerUnreachable(server_id=server['id'])
83 else:
84 raise lib_exc.InvalidConfiguration()
85
86
Andrea Frittoli (andreaf)476e9192015-08-14 23:59:58 +010087def create_test_server(clients, validatable=False, validation_resources=None,
Joe Gordon8843f0f2015-03-17 15:07:34 -070088 tenant_network=None, wait_until=None,
Anusha Ramineni9aaef8b2016-01-19 10:56:40 +053089 volume_backed=False, name=None, flavor=None,
Lee Yarwood1a76c2c2021-11-11 15:28:53 +000090 image_id=None, **kwargs):
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000091 """Common wrapper utility returning a test server.
92
93 This method is a common wrapper returning a test server that can be
94 pingable or sshable.
95
Takashi NATSUME6d5a2b42015-09-08 11:27:49 +090096 :param clients: Client manager which provides OpenStack Tempest clients.
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000097 :param validatable: Whether the server will be pingable or sshable.
98 :param validation_resources: Resources created for the connection to the
Andrea Frittoli (andreaf)9df3a522016-07-06 14:09:48 +010099 server. Include a keypair, a security group and an IP.
Ken'ichi Ohmichid5bc31a2015-09-02 01:45:28 +0000100 :param tenant_network: Tenant network to be used for creating a server.
Ken'ichi Ohmichifc25e692015-09-02 01:48:06 +0000101 :param wait_until: Server status to wait for the server to reach after
Lee Yarwood0b4bc3d2021-11-11 17:45:25 +0000102 its creation. Additionally PINGABLE and SSHABLE states are also
103 accepted when the server is both validatable and has the required
104 validation_resources provided.
ghanshyam61db96e2016-12-16 12:49:25 +0900105 :param volume_backed: Whether the server is volume backed or not.
Sergey Vilgelmeac094a2018-11-21 18:27:51 -0600106 If this is true, a volume will be created and create server will be
107 requested with 'block_device_mapping_v2' populated with below values:
108
109 .. code-block:: python
110
111 bd_map_v2 = [{
112 'uuid': volume['volume']['id'],
113 'source_type': 'volume',
114 'destination_type': 'volume',
115 'boot_index': 0,
116 'delete_on_termination': True}]
117 kwargs['block_device_mapping_v2'] = bd_map_v2
118
119 If server needs to be booted from volume with other combination of bdm
120 inputs than mentioned above, then pass the bdm inputs explicitly as
121 kwargs and image_id as empty string ('').
Andrea Frittoli (andreaf)9df3a522016-07-06 14:09:48 +0100122 :param name: Name of the server to be provisioned. If not defined a random
123 string ending with '-instance' will be generated.
124 :param flavor: Flavor of the server to be provisioned. If not defined,
125 CONF.compute.flavor_ref will be used instead.
126 :param image_id: ID of the image to be used to provision the server. If not
127 defined, CONF.compute.image_ref will be used instead.
lei zhangdd552b22015-11-25 20:41:48 +0800128 :returns: a tuple
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000129 """
130
Anusha Ramineni9aaef8b2016-01-19 10:56:40 +0530131 if name is None:
132 name = data_utils.rand_name(__name__ + "-instance")
133 if flavor is None:
134 flavor = CONF.compute.flavor_ref
135 if image_id is None:
136 image_id = CONF.compute.image_ref
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000137
138 kwargs = fixed_network.set_networks_kwarg(
139 tenant_network, kwargs) or {}
140
Ghanshyam4de44ae2015-12-25 10:34:00 +0900141 multiple_create_request = (max(kwargs.get('min_count', 0),
142 kwargs.get('max_count', 0)) > 1)
143
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000144 if CONF.validation.run_validation and validatable:
145 # As a first implementation, multiple pingable or sshable servers will
146 # not be supported
Ghanshyam4de44ae2015-12-25 10:34:00 +0900147 if multiple_create_request:
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000148 msg = ("Multiple pingable or sshable servers not supported at "
149 "this stage.")
150 raise ValueError(msg)
151
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100152 LOG.debug("Provisioning test server with validation resources %s",
153 validation_resources)
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000154 if 'security_groups' in kwargs:
155 kwargs['security_groups'].append(
156 {'name': validation_resources['security_group']['name']})
157 else:
158 try:
159 kwargs['security_groups'] = [
160 {'name': validation_resources['security_group']['name']}]
161 except KeyError:
162 LOG.debug("No security group provided.")
163
164 if 'key_name' not in kwargs:
165 try:
166 kwargs['key_name'] = validation_resources['keypair']['name']
167 except KeyError:
168 LOG.debug("No key provided.")
169
170 if CONF.validation.connect_method == 'floating':
Ken'ichi Ohmichifc25e692015-09-02 01:48:06 +0000171 if wait_until is None:
172 wait_until = 'ACTIVE'
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000173
Clark Boylan844180e2017-03-15 15:24:58 -0700174 if 'user_data' not in kwargs:
175 # If nothing overrides the default user data script then run
176 # a simple script on the host to print networking info. This is
177 # to aid in debugging ssh failures.
178 script = '''
179 #!/bin/sh
180 echo "Printing {user} user authorized keys"
181 cat ~{user}/.ssh/authorized_keys || true
182 '''.format(user=CONF.validation.image_ssh_user)
183 script_clean = textwrap.dedent(script).lstrip().encode('utf8')
184 script_b64 = base64.b64encode(script_clean)
185 kwargs['user_data'] = script_b64
186
Joe Gordon8843f0f2015-03-17 15:07:34 -0700187 if volume_backed:
zhuflc6ce5392016-08-17 14:34:37 +0800188 volume_name = data_utils.rand_name(__name__ + '-volume')
ghanshyam6c682ff2018-08-06 09:54:45 +0000189 volumes_client = clients.volumes_client_latest
ghanshyam3bd0d2b2017-03-23 01:57:28 +0000190 params = {'name': volume_name,
ghanshyam61db96e2016-12-16 12:49:25 +0900191 'imageRef': image_id,
192 'size': CONF.volume.volume_size}
Martin Kopec00e6d6c2019-06-05 14:30:06 +0000193 if CONF.compute.compute_volume_common_az:
194 params.setdefault('availability_zone',
195 CONF.compute.compute_volume_common_az)
ghanshyam61db96e2016-12-16 12:49:25 +0900196 volume = volumes_client.create_volume(**params)
mccasland, trevor (tm2086)0fedb412019-01-21 13:37:58 -0600197 try:
198 waiters.wait_for_volume_resource_status(volumes_client,
199 volume['volume']['id'],
200 'available')
201 except Exception:
202 with excutils.save_and_reraise_exception():
203 try:
204 volumes_client.delete_volume(volume['volume']['id'])
205 volumes_client.wait_for_resource_deletion(
206 volume['volume']['id'])
207 except Exception as exc:
208 LOG.exception("Deleting volume %s failed, exception %s",
209 volume['volume']['id'], exc)
Joe Gordon8843f0f2015-03-17 15:07:34 -0700210 bd_map_v2 = [{
211 'uuid': volume['volume']['id'],
212 'source_type': 'volume',
213 'destination_type': 'volume',
214 'boot_index': 0,
ghanshyam61db96e2016-12-16 12:49:25 +0900215 'delete_on_termination': True}]
Joe Gordon8843f0f2015-03-17 15:07:34 -0700216 kwargs['block_device_mapping_v2'] = bd_map_v2
217
218 # Since this is boot from volume an image does not need
219 # to be specified.
220 image_id = ''
221
Martin Kopec00e6d6c2019-06-05 14:30:06 +0000222 if CONF.compute.compute_volume_common_az:
223 kwargs.setdefault('availability_zone',
224 CONF.compute.compute_volume_common_az)
Ken'ichi Ohmichif2d436e2015-09-03 01:13:16 +0000225 body = clients.servers_client.create_server(name=name, imageRef=image_id,
226 flavorRef=flavor,
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000227 **kwargs)
Artom Lifshitzda48e4e2021-11-22 15:59:15 -0500228 request_id = body.response['x-openstack-request-id']
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000229
230 # handle the case of multiple servers
Ghanshyam4de44ae2015-12-25 10:34:00 +0900231 if multiple_create_request:
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000232 # Get servers created which name match with name param.
233 body_servers = clients.servers_client.list_servers()
234 servers = \
235 [s for s in body_servers['servers'] if s['name'].startswith(name)]
ghanshyam0f825252015-08-25 16:02:50 +0900236 else:
Ken'ichi Ohmichi54030522016-03-02 11:01:34 -0800237 body = rest_client.ResponseBody(body.response, body['server'])
ghanshyam0f825252015-08-25 16:02:50 +0900238 servers = [body]
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000239
Artom Lifshitz70d7a112017-05-10 17:25:54 +0000240 def _setup_validation_fip():
241 if CONF.service_available.neutron:
242 ifaces = clients.interfaces_client.list_interfaces(server['id'])
243 validation_port = None
244 for iface in ifaces['interfaceAttachments']:
245 if iface['net_id'] == tenant_network['id']:
246 validation_port = iface['port_id']
247 break
248 if not validation_port:
249 # NOTE(artom) This will get caught by the catch-all clause in
250 # the wait_until loop below
251 raise ValueError('Unable to setup floating IP for validation: '
252 'port not found on tenant network')
253 clients.floating_ips_client.update_floatingip(
254 validation_resources['floating_ip']['id'],
255 port_id=validation_port)
256 else:
257 fip_client = clients.compute_floating_ips_client
258 fip_client.associate_floating_ip_to_server(
259 floating_ip=validation_resources['floating_ip']['ip'],
260 server_id=servers[0]['id'])
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000261
Ken'ichi Ohmichifc25e692015-09-02 01:48:06 +0000262 if wait_until:
Lee Yarwood0b4bc3d2021-11-11 17:45:25 +0000263
264 # NOTE(lyarwood): PINGABLE and SSHABLE both require the instance to
265 # go ACTIVE initially before we can setup the fip(s) etc so stash
266 # this additional wait state for later use.
267 wait_until_extra = None
268 if wait_until in ['PINGABLE', 'SSHABLE']:
269 wait_until_extra = wait_until
270 wait_until = 'ACTIVE'
271
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000272 for server in servers:
273 try:
Ken'ichi Ohmichi0eb153c2015-07-13 02:18:25 +0000274 waiters.wait_for_server_status(
Artom Lifshitzda48e4e2021-11-22 15:59:15 -0500275 clients.servers_client, server['id'], wait_until,
276 request_id=request_id)
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000277
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000278 if CONF.validation.run_validation and validatable:
Lee Yarwood0b4bc3d2021-11-11 17:45:25 +0000279
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000280 if CONF.validation.connect_method == 'floating':
Artom Lifshitz70d7a112017-05-10 17:25:54 +0000281 _setup_validation_fip()
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000282
Lee Yarwood0b4bc3d2021-11-11 17:45:25 +0000283 server_ip = get_server_ip(
284 server, validation_resources=validation_resources)
285
286 if wait_until_extra == 'PINGABLE':
287 waiters.wait_for_ping(
288 server_ip,
289 clients.servers_client.build_timeout,
290 clients.servers_client.build_interval
291 )
292
293 if wait_until_extra == 'SSHABLE':
294 pkey = validation_resources['keypair']['private_key']
295 ssh_client = remote_client.RemoteClient(
296 server_ip,
297 CONF.validation.image_ssh_user,
298 pkey=pkey,
299 server=server,
300 servers_client=clients.servers_client
301 )
302 waiters.wait_for_ssh(
303 ssh_client,
304 clients.servers_client.build_timeout
305 )
306
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000307 except Exception:
308 with excutils.save_and_reraise_exception():
Jordan Pittier87ba2872016-03-08 11:43:11 +0100309 for server in servers:
310 try:
311 clients.servers_client.delete_server(
312 server['id'])
313 except Exception:
Jordan Pittier525ec712016-12-07 17:51:26 +0100314 LOG.exception('Deleting server %s failed',
315 server['id'])
Artom Lifshitz9b3f42b2017-06-19 05:46:32 +0000316 for server in servers:
317 # NOTE(artom) If the servers were booted with volumes
318 # and with delete_on_termination=False we need to wait
319 # for the servers to go away before proceeding with
320 # cleanup, otherwise we'll attempt to delete the
321 # volumes while they're still attached to servers that
322 # are in the process of being deleted.
323 try:
324 waiters.wait_for_server_termination(
325 clients.servers_client, server['id'])
326 except Exception:
327 LOG.exception('Server %s failed to delete in time',
328 server['id'])
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000329
330 return body, servers
ghanshyam017b5fe2016-04-15 18:49:26 +0900331
332
ghanshyam4c1391c2016-12-01 13:13:06 +0900333def shelve_server(servers_client, server_id, force_shelve_offload=False):
ghanshyam017b5fe2016-04-15 18:49:26 +0900334 """Common wrapper utility to shelve server.
335
336 This method is a common wrapper to make server in 'SHELVED'
337 or 'SHELVED_OFFLOADED' state.
338
ghanshyam4c1391c2016-12-01 13:13:06 +0900339 :param servers_clients: Compute servers client instance.
ghanshyam017b5fe2016-04-15 18:49:26 +0900340 :param server_id: Server to make in shelve state
341 :param force_shelve_offload: Forcefully offload shelve server if it
342 is configured not to offload server
343 automatically after offload time.
344 """
ghanshyam4c1391c2016-12-01 13:13:06 +0900345 servers_client.shelve_server(server_id)
ghanshyam017b5fe2016-04-15 18:49:26 +0900346
347 offload_time = CONF.compute.shelved_offload_time
348 if offload_time >= 0:
ghanshyam4c1391c2016-12-01 13:13:06 +0900349 waiters.wait_for_server_status(servers_client, server_id,
ghanshyam017b5fe2016-04-15 18:49:26 +0900350 'SHELVED_OFFLOADED',
351 extra_timeout=offload_time)
352 else:
ghanshyam4c1391c2016-12-01 13:13:06 +0900353 waiters.wait_for_server_status(servers_client, server_id, 'SHELVED')
ghanshyam017b5fe2016-04-15 18:49:26 +0900354 if force_shelve_offload:
ghanshyam4c1391c2016-12-01 13:13:06 +0900355 servers_client.shelve_offload_server(server_id)
356 waiters.wait_for_server_status(servers_client, server_id,
ghanshyam017b5fe2016-04-15 18:49:26 +0900357 'SHELVED_OFFLOADED')
Markus Zoellerae36ce82017-03-20 16:27:26 +0100358
359
360def create_websocket(url):
361 url = urlparse.urlparse(url)
Mohammed Naseraa5dd9a2017-12-29 18:52:01 -0500362
363 # NOTE(mnaser): It is possible that there is no port specified, so fall
364 # back to the default port based on the scheme.
365 port = url.port or (443 if url.scheme == 'https' else 80)
366
367 for res in socket.getaddrinfo(url.hostname, port,
Jens Harbott6bc422d2017-09-27 10:29:34 +0000368 socket.AF_UNSPEC, socket.SOCK_STREAM):
369 af, socktype, proto, _, sa = res
370 client_socket = socket.socket(af, socktype, proto)
371 if url.scheme == 'https':
372 client_socket = ssl.wrap_socket(client_socket)
373 client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
374 try:
375 client_socket.connect(sa)
376 except socket.error:
377 client_socket.close()
378 continue
379 break
xxj8eb90982017-04-10 21:18:39 +0800380 else:
Jens Harbott6bc422d2017-09-27 10:29:34 +0000381 raise socket.error('WebSocket creation failed')
Markus Zoellerae36ce82017-03-20 16:27:26 +0100382 # Turn the Socket into a WebSocket to do the communication
383 return _WebSocket(client_socket, url)
384
385
386class _WebSocket(object):
387 def __init__(self, client_socket, url):
388 """Contructor for the WebSocket wrapper to the socket."""
389 self._socket = client_socket
jianghua wangd22514a2017-05-08 08:05:04 +0100390 # cached stream for early frames.
391 self.cached_stream = b''
Markus Zoellerae36ce82017-03-20 16:27:26 +0100392 # Upgrade the HTTP connection to a WebSocket
393 self._upgrade(url)
394
jianghua wangd22514a2017-05-08 08:05:04 +0100395 def _recv(self, recv_size):
396 """Wrapper to receive data from the cached stream or socket."""
397 if recv_size <= 0:
398 return None
399
400 data_from_cached = b''
401 data_from_socket = b''
402 if len(self.cached_stream) > 0:
403 read_from_cached = min(len(self.cached_stream), recv_size)
404 data_from_cached += self.cached_stream[:read_from_cached]
405 self.cached_stream = self.cached_stream[read_from_cached:]
406 recv_size -= read_from_cached
407 if recv_size > 0:
408 data_from_socket = self._socket.recv(recv_size)
409 return data_from_cached + data_from_socket
410
Markus Zoellerae36ce82017-03-20 16:27:26 +0100411 def receive_frame(self):
412 """Wrapper for receiving data to parse the WebSocket frame format"""
413 # We need to loop until we either get some bytes back in the frame
414 # or no data was received (meaning the socket was closed). This is
415 # done to handle the case where we get back some empty frames
416 while True:
jianghua wangd22514a2017-05-08 08:05:04 +0100417 header = self._recv(2)
Markus Zoellerae36ce82017-03-20 16:27:26 +0100418 # If we didn't receive any data, just return None
Masayuki Igawa0c0f0142017-04-10 17:22:02 +0900419 if not header:
Markus Zoellerae36ce82017-03-20 16:27:26 +0100420 return None
421 # We will make the assumption that we are only dealing with
422 # frames less than 125 bytes here (for the negotiation) and
423 # that only the 2nd byte contains the length, and since the
424 # server doesn't do masking, we can just read the data length
likui7d91c872020-09-22 12:29:16 +0800425 if int(header[1]) & 127 > 0:
426 return self._recv(int(header[1]) & 127)
Markus Zoellerae36ce82017-03-20 16:27:26 +0100427
428 def send_frame(self, data):
429 """Wrapper for sending data to add in the WebSocket frame format."""
430 frame_bytes = list()
431 # For the first byte, want to say we are sending binary data (130)
432 frame_bytes.append(130)
433 # Only sending negotiation data so don't need to worry about > 125
434 # We do need to add the bit that says we are masking the data
435 frame_bytes.append(len(data) | 128)
436 # We don't really care about providing a random mask for security
437 # So we will just hard-code a value since a test program
438 mask = [7, 2, 1, 9]
439 for i in range(len(mask)):
440 frame_bytes.append(mask[i])
441 # Mask each of the actual data bytes that we are going to send
442 for i in range(len(data)):
likui7d91c872020-09-22 12:29:16 +0800443 frame_bytes.append(int(data[i]) ^ mask[i % 4])
Markus Zoellerae36ce82017-03-20 16:27:26 +0100444 # Convert our integer list to a binary array of bytes
445 frame_bytes = struct.pack('!%iB' % len(frame_bytes), * frame_bytes)
446 self._socket.sendall(frame_bytes)
447
448 def close(self):
449 """Helper method to close the connection."""
450 # Close down the real socket connection and exit the test program
451 if self._socket is not None:
452 self._socket.shutdown(1)
453 self._socket.close()
454 self._socket = None
455
456 def _upgrade(self, url):
457 """Upgrade the HTTP connection to a WebSocket and verify."""
melanie witt27ba9332019-04-26 02:33:20 +0000458 # It is possible to pass the path as a query parameter in the request,
459 # so use it if present
Jason Lica0fad02020-04-06 10:56:43 -0500460 # Given noVNC format
461 # https://x.com/vnc_auto.html?path=%3Ftoken%3Dxxx,
462 # url format is
463 # ParseResult(scheme='https', netloc='x.com',
464 # path='/vnc_auto.html', params='',
465 # query='path=%3Ftoken%3Dxxx', fragment='').
466 # qparams format is {'path': ['?token=xxx']}
melanie witt27ba9332019-04-26 02:33:20 +0000467 qparams = urlparse.parse_qs(url.query)
Jason Lica0fad02020-04-06 10:56:43 -0500468 # according to references
469 # https://docs.python.org/3/library/urllib.parse.html
470 # https://tools.ietf.org/html/rfc3986#section-3.4
471 # qparams['path'][0] format is '?token=xxx' without / prefix
472 # remove / in /websockify to comply to references.
473 path = qparams['path'][0] if 'path' in qparams else 'websockify'
474 # Fix websocket request format by adding / prefix.
475 # Updated request format: GET /?token=xxx HTTP/1.1
476 # or GET /websockify HTTP/1.1
477 reqdata = 'GET /%s HTTP/1.1\r\n' % path
Mohammed Naseraa5dd9a2017-12-29 18:52:01 -0500478 reqdata += 'Host: %s' % url.hostname
479 # Add port only if we have one specified
480 if url.port:
481 reqdata += ':%s' % url.port
482 # Line-ending for Host header
483 reqdata += '\r\n'
Markus Zoellerae36ce82017-03-20 16:27:26 +0100484 # Tell the HTTP Server to Upgrade the connection to a WebSocket
485 reqdata += 'Upgrade: websocket\r\nConnection: Upgrade\r\n'
melanie witt27ba9332019-04-26 02:33:20 +0000486 # The token=xxx is sent as a Cookie not in the URI for noVNC < v1.1.0
Markus Zoellerae36ce82017-03-20 16:27:26 +0100487 reqdata += 'Cookie: %s\r\n' % url.query
488 # Use a hard-coded WebSocket key since a test program
489 reqdata += 'Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n'
490 reqdata += 'Sec-WebSocket-Version: 13\r\n'
491 # We are choosing to use binary even though browser may do Base64
492 reqdata += 'Sec-WebSocket-Protocol: binary\r\n\r\n'
493 # Send the HTTP GET request and get the response back
494 self._socket.sendall(reqdata.encode('utf8'))
495 self.response = data = self._socket.recv(4096)
496 # Loop through & concatenate all of the data in the response body
jianghua wangd22514a2017-05-08 08:05:04 +0100497 end_loc = self.response.find(b'\r\n\r\n')
498 while data and end_loc < 0:
Markus Zoellerae36ce82017-03-20 16:27:26 +0100499 data = self._socket.recv(4096)
500 self.response += data
jianghua wangd22514a2017-05-08 08:05:04 +0100501 end_loc = self.response.find(b'\r\n\r\n')
502
503 if len(self.response) > end_loc + 4:
504 # In case some frames (e.g. the first RFP negotiation) have
505 # arrived, cache it for next reading.
506 self.cached_stream = self.response[end_loc + 4:]
507 # ensure response ends with '\r\n\r\n'.
508 self.response = self.response[:end_loc + 4]