blob: 00f133eb5117ef1a26002ff6ad9116d06c80f19e [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
Martin Kopecdc9a93b2022-03-25 17:34:42 +010018from ssl import SSLContext as sslc
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
Ghanshyam Mann8c6d0cf2022-03-17 13:14:49 -050087def _setup_validation_fip(
88 server, clients, tenant_network, validation_resources):
89 if CONF.service_available.neutron:
90 ifaces = clients.interfaces_client.list_interfaces(server['id'])
91 validation_port = None
92 for iface in ifaces['interfaceAttachments']:
93 if iface['net_id'] == tenant_network['id']:
94 validation_port = iface['port_id']
95 break
96 if not validation_port:
97 # NOTE(artom) This will get caught by the catch-all clause in
98 # the wait_until loop below
99 raise ValueError('Unable to setup floating IP for validation: '
100 'port not found on tenant network')
101 clients.floating_ips_client.update_floatingip(
102 validation_resources['floating_ip']['id'],
103 port_id=validation_port)
104 else:
105 fip_client = clients.compute_floating_ips_client
106 fip_client.associate_floating_ip_to_server(
107 floating_ip=validation_resources['floating_ip']['ip'],
108 server_id=server['id'])
109
110
111def wait_for_ssh_or_ping(server, clients, tenant_network,
112 validatable, validation_resources, wait_until,
113 set_floatingip):
114 """Wait for the server for SSH or Ping as requested.
115
116 :param server: The server dict as returned by the API
117 :param clients: Client manager which provides OpenStack Tempest clients.
118 :param tenant_network: Tenant network to be used for creating a server.
119 :param validatable: Whether the server will be pingable or sshable.
120 :param validation_resources: Resources created for the connection to the
121 server. Include a keypair, a security group and an IP.
122 :param wait_until: Server status to wait for the server to reach.
123 It can be PINGABLE and SSHABLE states when the server is both
124 validatable and has the required validation_resources provided.
125 :param set_floatingip: If FIP needs to be associated to server
126 """
127 if set_floatingip and CONF.validation.connect_method == 'floating':
128 _setup_validation_fip(
129 server, clients, tenant_network, validation_resources)
130
131 server_ip = get_server_ip(
132 server, validation_resources=validation_resources)
133 if wait_until == 'PINGABLE':
134 waiters.wait_for_ping(
135 server_ip,
136 clients.servers_client.build_timeout,
137 clients.servers_client.build_interval
138 )
139 if wait_until == 'SSHABLE':
140 pkey = validation_resources['keypair']['private_key']
141 ssh_client = remote_client.RemoteClient(
142 server_ip,
143 CONF.validation.image_ssh_user,
144 pkey=pkey,
145 server=server,
146 servers_client=clients.servers_client
147 )
148 waiters.wait_for_ssh(
149 ssh_client,
150 clients.servers_client.build_timeout
151 )
152
153
Andrea Frittoli (andreaf)476e9192015-08-14 23:59:58 +0100154def create_test_server(clients, validatable=False, validation_resources=None,
Joe Gordon8843f0f2015-03-17 15:07:34 -0700155 tenant_network=None, wait_until=None,
Anusha Ramineni9aaef8b2016-01-19 10:56:40 +0530156 volume_backed=False, name=None, flavor=None,
Lee Yarwood1a76c2c2021-11-11 15:28:53 +0000157 image_id=None, **kwargs):
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000158 """Common wrapper utility returning a test server.
159
160 This method is a common wrapper returning a test server that can be
161 pingable or sshable.
162
Takashi NATSUME6d5a2b42015-09-08 11:27:49 +0900163 :param clients: Client manager which provides OpenStack Tempest clients.
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000164 :param validatable: Whether the server will be pingable or sshable.
165 :param validation_resources: Resources created for the connection to the
Andrea Frittoli (andreaf)9df3a522016-07-06 14:09:48 +0100166 server. Include a keypair, a security group and an IP.
Ken'ichi Ohmichid5bc31a2015-09-02 01:45:28 +0000167 :param tenant_network: Tenant network to be used for creating a server.
Ken'ichi Ohmichifc25e692015-09-02 01:48:06 +0000168 :param wait_until: Server status to wait for the server to reach after
Lee Yarwood0b4bc3d2021-11-11 17:45:25 +0000169 its creation. Additionally PINGABLE and SSHABLE states are also
170 accepted when the server is both validatable and has the required
171 validation_resources provided.
ghanshyam61db96e2016-12-16 12:49:25 +0900172 :param volume_backed: Whether the server is volume backed or not.
Sergey Vilgelmeac094a2018-11-21 18:27:51 -0600173 If this is true, a volume will be created and create server will be
174 requested with 'block_device_mapping_v2' populated with below values:
175
176 .. code-block:: python
177
178 bd_map_v2 = [{
179 'uuid': volume['volume']['id'],
180 'source_type': 'volume',
181 'destination_type': 'volume',
182 'boot_index': 0,
183 'delete_on_termination': True}]
184 kwargs['block_device_mapping_v2'] = bd_map_v2
185
186 If server needs to be booted from volume with other combination of bdm
187 inputs than mentioned above, then pass the bdm inputs explicitly as
188 kwargs and image_id as empty string ('').
Andrea Frittoli (andreaf)9df3a522016-07-06 14:09:48 +0100189 :param name: Name of the server to be provisioned. If not defined a random
190 string ending with '-instance' will be generated.
191 :param flavor: Flavor of the server to be provisioned. If not defined,
192 CONF.compute.flavor_ref will be used instead.
193 :param image_id: ID of the image to be used to provision the server. If not
194 defined, CONF.compute.image_ref will be used instead.
lei zhangdd552b22015-11-25 20:41:48 +0800195 :returns: a tuple
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000196 """
197
Anusha Ramineni9aaef8b2016-01-19 10:56:40 +0530198 if name is None:
199 name = data_utils.rand_name(__name__ + "-instance")
200 if flavor is None:
201 flavor = CONF.compute.flavor_ref
202 if image_id is None:
203 image_id = CONF.compute.image_ref
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000204
205 kwargs = fixed_network.set_networks_kwarg(
206 tenant_network, kwargs) or {}
207
Ghanshyam4de44ae2015-12-25 10:34:00 +0900208 multiple_create_request = (max(kwargs.get('min_count', 0),
209 kwargs.get('max_count', 0)) > 1)
210
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000211 if CONF.validation.run_validation and validatable:
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100212 LOG.debug("Provisioning test server with validation resources %s",
213 validation_resources)
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000214 if 'security_groups' in kwargs:
215 kwargs['security_groups'].append(
216 {'name': validation_resources['security_group']['name']})
217 else:
218 try:
219 kwargs['security_groups'] = [
220 {'name': validation_resources['security_group']['name']}]
221 except KeyError:
222 LOG.debug("No security group provided.")
223
224 if 'key_name' not in kwargs:
225 try:
226 kwargs['key_name'] = validation_resources['keypair']['name']
227 except KeyError:
228 LOG.debug("No key provided.")
229
230 if CONF.validation.connect_method == 'floating':
Ken'ichi Ohmichifc25e692015-09-02 01:48:06 +0000231 if wait_until is None:
232 wait_until = 'ACTIVE'
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000233
Clark Boylan844180e2017-03-15 15:24:58 -0700234 if 'user_data' not in kwargs:
235 # If nothing overrides the default user data script then run
236 # a simple script on the host to print networking info. This is
237 # to aid in debugging ssh failures.
238 script = '''
239 #!/bin/sh
240 echo "Printing {user} user authorized keys"
241 cat ~{user}/.ssh/authorized_keys || true
242 '''.format(user=CONF.validation.image_ssh_user)
243 script_clean = textwrap.dedent(script).lstrip().encode('utf8')
244 script_b64 = base64.b64encode(script_clean)
245 kwargs['user_data'] = script_b64
246
Joe Gordon8843f0f2015-03-17 15:07:34 -0700247 if volume_backed:
zhuflc6ce5392016-08-17 14:34:37 +0800248 volume_name = data_utils.rand_name(__name__ + '-volume')
ghanshyam6c682ff2018-08-06 09:54:45 +0000249 volumes_client = clients.volumes_client_latest
ghanshyam3bd0d2b2017-03-23 01:57:28 +0000250 params = {'name': volume_name,
ghanshyam61db96e2016-12-16 12:49:25 +0900251 'imageRef': image_id,
252 'size': CONF.volume.volume_size}
Martin Kopec00e6d6c2019-06-05 14:30:06 +0000253 if CONF.compute.compute_volume_common_az:
254 params.setdefault('availability_zone',
255 CONF.compute.compute_volume_common_az)
ghanshyam61db96e2016-12-16 12:49:25 +0900256 volume = volumes_client.create_volume(**params)
mccasland, trevor (tm2086)0fedb412019-01-21 13:37:58 -0600257 try:
258 waiters.wait_for_volume_resource_status(volumes_client,
259 volume['volume']['id'],
260 'available')
261 except Exception:
262 with excutils.save_and_reraise_exception():
263 try:
264 volumes_client.delete_volume(volume['volume']['id'])
265 volumes_client.wait_for_resource_deletion(
266 volume['volume']['id'])
267 except Exception as exc:
268 LOG.exception("Deleting volume %s failed, exception %s",
269 volume['volume']['id'], exc)
Joe Gordon8843f0f2015-03-17 15:07:34 -0700270 bd_map_v2 = [{
271 'uuid': volume['volume']['id'],
272 'source_type': 'volume',
273 'destination_type': 'volume',
274 'boot_index': 0,
ghanshyam61db96e2016-12-16 12:49:25 +0900275 'delete_on_termination': True}]
Joe Gordon8843f0f2015-03-17 15:07:34 -0700276 kwargs['block_device_mapping_v2'] = bd_map_v2
277
278 # Since this is boot from volume an image does not need
279 # to be specified.
280 image_id = ''
281
Martin Kopec00e6d6c2019-06-05 14:30:06 +0000282 if CONF.compute.compute_volume_common_az:
283 kwargs.setdefault('availability_zone',
284 CONF.compute.compute_volume_common_az)
Ken'ichi Ohmichif2d436e2015-09-03 01:13:16 +0000285 body = clients.servers_client.create_server(name=name, imageRef=image_id,
286 flavorRef=flavor,
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000287 **kwargs)
Artom Lifshitzda48e4e2021-11-22 15:59:15 -0500288 request_id = body.response['x-openstack-request-id']
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000289
290 # handle the case of multiple servers
Ghanshyam4de44ae2015-12-25 10:34:00 +0900291 if multiple_create_request:
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000292 # Get servers created which name match with name param.
293 body_servers = clients.servers_client.list_servers()
Marian Krcmarikca5ddb42022-08-29 17:01:01 +0200294 created_servers = \
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000295 [s for s in body_servers['servers'] if s['name'].startswith(name)]
ghanshyam0f825252015-08-25 16:02:50 +0900296 else:
Ken'ichi Ohmichi54030522016-03-02 11:01:34 -0800297 body = rest_client.ResponseBody(body.response, body['server'])
Marian Krcmarikca5ddb42022-08-29 17:01:01 +0200298 created_servers = [body]
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000299
Ken'ichi Ohmichifc25e692015-09-02 01:48:06 +0000300 if wait_until:
Lee Yarwood0b4bc3d2021-11-11 17:45:25 +0000301
302 # NOTE(lyarwood): PINGABLE and SSHABLE both require the instance to
303 # go ACTIVE initially before we can setup the fip(s) etc so stash
304 # this additional wait state for later use.
305 wait_until_extra = None
306 if wait_until in ['PINGABLE', 'SSHABLE']:
307 wait_until_extra = wait_until
308 wait_until = 'ACTIVE'
309
Marian Krcmarikca5ddb42022-08-29 17:01:01 +0200310 servers = []
311 try:
312 # Wait for server to be in active state and populate servers list
313 # with those full server response so that we will have addresses
314 # field present in server which is needed to be used for wait for
315 # ssh
316 for server in created_servers:
317 server = waiters.wait_for_server_status(
Artom Lifshitzda48e4e2021-11-22 15:59:15 -0500318 clients.servers_client, server['id'], wait_until,
319 request_id=request_id)
Marian Krcmarikca5ddb42022-08-29 17:01:01 +0200320 servers.append(server)
321
322 for server in servers:
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000323 if CONF.validation.run_validation and validatable:
324 if CONF.validation.connect_method == 'floating':
Ghanshyam Mann8c6d0cf2022-03-17 13:14:49 -0500325 _setup_validation_fip(
326 server, clients, tenant_network,
327 validation_resources)
328 if wait_until_extra:
329 wait_for_ssh_or_ping(
330 server, clients, tenant_network,
331 validatable, validation_resources,
332 wait_until_extra, False)
Marian Krcmarikca5ddb42022-08-29 17:01:01 +0200333 except Exception:
334 with excutils.save_and_reraise_exception():
335 for server in created_servers:
336 try:
337 clients.servers_client.delete_server(
338 server['id'])
339 except Exception:
340 LOG.exception('Deleting server %s failed',
341 server['id'])
342 for server in created_servers:
343 # NOTE(artom) If the servers were booted with volumes
344 # and with delete_on_termination=False we need to wait
345 # for the servers to go away before proceeding with
346 # cleanup, otherwise we'll attempt to delete the
347 # volumes while they're still attached to servers that
348 # are in the process of being deleted.
349 try:
350 waiters.wait_for_server_termination(
351 clients.servers_client, server['id'])
352 except Exception:
353 LOG.exception('Server %s failed to delete in time',
354 server['id'])
355 return body, servers
Lee Yarwood0b4bc3d2021-11-11 17:45:25 +0000356
Marian Krcmarikca5ddb42022-08-29 17:01:01 +0200357 return body, created_servers
ghanshyam017b5fe2016-04-15 18:49:26 +0900358
359
ghanshyam4c1391c2016-12-01 13:13:06 +0900360def shelve_server(servers_client, server_id, force_shelve_offload=False):
ghanshyam017b5fe2016-04-15 18:49:26 +0900361 """Common wrapper utility to shelve server.
362
363 This method is a common wrapper to make server in 'SHELVED'
364 or 'SHELVED_OFFLOADED' state.
365
ghanshyam4c1391c2016-12-01 13:13:06 +0900366 :param servers_clients: Compute servers client instance.
ghanshyam017b5fe2016-04-15 18:49:26 +0900367 :param server_id: Server to make in shelve state
368 :param force_shelve_offload: Forcefully offload shelve server if it
369 is configured not to offload server
370 automatically after offload time.
371 """
ghanshyam4c1391c2016-12-01 13:13:06 +0900372 servers_client.shelve_server(server_id)
ghanshyam017b5fe2016-04-15 18:49:26 +0900373
374 offload_time = CONF.compute.shelved_offload_time
375 if offload_time >= 0:
ghanshyam4c1391c2016-12-01 13:13:06 +0900376 waiters.wait_for_server_status(servers_client, server_id,
ghanshyam017b5fe2016-04-15 18:49:26 +0900377 'SHELVED_OFFLOADED',
378 extra_timeout=offload_time)
379 else:
ghanshyam4c1391c2016-12-01 13:13:06 +0900380 waiters.wait_for_server_status(servers_client, server_id, 'SHELVED')
ghanshyam017b5fe2016-04-15 18:49:26 +0900381 if force_shelve_offload:
ghanshyam4c1391c2016-12-01 13:13:06 +0900382 servers_client.shelve_offload_server(server_id)
383 waiters.wait_for_server_status(servers_client, server_id,
ghanshyam017b5fe2016-04-15 18:49:26 +0900384 'SHELVED_OFFLOADED')
Markus Zoellerae36ce82017-03-20 16:27:26 +0100385
386
387def create_websocket(url):
388 url = urlparse.urlparse(url)
Mohammed Naseraa5dd9a2017-12-29 18:52:01 -0500389
390 # NOTE(mnaser): It is possible that there is no port specified, so fall
391 # back to the default port based on the scheme.
392 port = url.port or (443 if url.scheme == 'https' else 80)
393
394 for res in socket.getaddrinfo(url.hostname, port,
Jens Harbott6bc422d2017-09-27 10:29:34 +0000395 socket.AF_UNSPEC, socket.SOCK_STREAM):
396 af, socktype, proto, _, sa = res
397 client_socket = socket.socket(af, socktype, proto)
398 if url.scheme == 'https':
Phil Sphicas8e08a772022-03-24 10:09:49 -0700399 client_socket = sslc().wrap_socket(client_socket,
400 server_hostname=url.hostname)
Jens Harbott6bc422d2017-09-27 10:29:34 +0000401 client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
402 try:
403 client_socket.connect(sa)
404 except socket.error:
405 client_socket.close()
406 continue
407 break
xxj8eb90982017-04-10 21:18:39 +0800408 else:
Jens Harbott6bc422d2017-09-27 10:29:34 +0000409 raise socket.error('WebSocket creation failed')
Markus Zoellerae36ce82017-03-20 16:27:26 +0100410 # Turn the Socket into a WebSocket to do the communication
411 return _WebSocket(client_socket, url)
412
413
414class _WebSocket(object):
415 def __init__(self, client_socket, url):
416 """Contructor for the WebSocket wrapper to the socket."""
417 self._socket = client_socket
jianghua wangd22514a2017-05-08 08:05:04 +0100418 # cached stream for early frames.
419 self.cached_stream = b''
Markus Zoellerae36ce82017-03-20 16:27:26 +0100420 # Upgrade the HTTP connection to a WebSocket
421 self._upgrade(url)
422
jianghua wangd22514a2017-05-08 08:05:04 +0100423 def _recv(self, recv_size):
424 """Wrapper to receive data from the cached stream or socket."""
425 if recv_size <= 0:
426 return None
427
428 data_from_cached = b''
429 data_from_socket = b''
430 if len(self.cached_stream) > 0:
431 read_from_cached = min(len(self.cached_stream), recv_size)
432 data_from_cached += self.cached_stream[:read_from_cached]
433 self.cached_stream = self.cached_stream[read_from_cached:]
434 recv_size -= read_from_cached
435 if recv_size > 0:
436 data_from_socket = self._socket.recv(recv_size)
437 return data_from_cached + data_from_socket
438
Markus Zoellerae36ce82017-03-20 16:27:26 +0100439 def receive_frame(self):
440 """Wrapper for receiving data to parse the WebSocket frame format"""
441 # We need to loop until we either get some bytes back in the frame
442 # or no data was received (meaning the socket was closed). This is
443 # done to handle the case where we get back some empty frames
444 while True:
jianghua wangd22514a2017-05-08 08:05:04 +0100445 header = self._recv(2)
Markus Zoellerae36ce82017-03-20 16:27:26 +0100446 # If we didn't receive any data, just return None
Masayuki Igawa0c0f0142017-04-10 17:22:02 +0900447 if not header:
Markus Zoellerae36ce82017-03-20 16:27:26 +0100448 return None
449 # We will make the assumption that we are only dealing with
450 # frames less than 125 bytes here (for the negotiation) and
451 # that only the 2nd byte contains the length, and since the
452 # server doesn't do masking, we can just read the data length
likui7d91c872020-09-22 12:29:16 +0800453 if int(header[1]) & 127 > 0:
454 return self._recv(int(header[1]) & 127)
Markus Zoellerae36ce82017-03-20 16:27:26 +0100455
456 def send_frame(self, data):
457 """Wrapper for sending data to add in the WebSocket frame format."""
458 frame_bytes = list()
459 # For the first byte, want to say we are sending binary data (130)
460 frame_bytes.append(130)
461 # Only sending negotiation data so don't need to worry about > 125
462 # We do need to add the bit that says we are masking the data
463 frame_bytes.append(len(data) | 128)
464 # We don't really care about providing a random mask for security
465 # So we will just hard-code a value since a test program
466 mask = [7, 2, 1, 9]
467 for i in range(len(mask)):
468 frame_bytes.append(mask[i])
469 # Mask each of the actual data bytes that we are going to send
470 for i in range(len(data)):
likui7d91c872020-09-22 12:29:16 +0800471 frame_bytes.append(int(data[i]) ^ mask[i % 4])
Markus Zoellerae36ce82017-03-20 16:27:26 +0100472 # Convert our integer list to a binary array of bytes
473 frame_bytes = struct.pack('!%iB' % len(frame_bytes), * frame_bytes)
474 self._socket.sendall(frame_bytes)
475
476 def close(self):
477 """Helper method to close the connection."""
478 # Close down the real socket connection and exit the test program
479 if self._socket is not None:
480 self._socket.shutdown(1)
481 self._socket.close()
482 self._socket = None
483
484 def _upgrade(self, url):
485 """Upgrade the HTTP connection to a WebSocket and verify."""
melanie witt27ba9332019-04-26 02:33:20 +0000486 # It is possible to pass the path as a query parameter in the request,
487 # so use it if present
Jason Lica0fad02020-04-06 10:56:43 -0500488 # Given noVNC format
489 # https://x.com/vnc_auto.html?path=%3Ftoken%3Dxxx,
490 # url format is
491 # ParseResult(scheme='https', netloc='x.com',
492 # path='/vnc_auto.html', params='',
493 # query='path=%3Ftoken%3Dxxx', fragment='').
494 # qparams format is {'path': ['?token=xxx']}
melanie witt27ba9332019-04-26 02:33:20 +0000495 qparams = urlparse.parse_qs(url.query)
Jason Lica0fad02020-04-06 10:56:43 -0500496 # according to references
497 # https://docs.python.org/3/library/urllib.parse.html
498 # https://tools.ietf.org/html/rfc3986#section-3.4
499 # qparams['path'][0] format is '?token=xxx' without / prefix
500 # remove / in /websockify to comply to references.
501 path = qparams['path'][0] if 'path' in qparams else 'websockify'
502 # Fix websocket request format by adding / prefix.
503 # Updated request format: GET /?token=xxx HTTP/1.1
504 # or GET /websockify HTTP/1.1
505 reqdata = 'GET /%s HTTP/1.1\r\n' % path
Mohammed Naseraa5dd9a2017-12-29 18:52:01 -0500506 reqdata += 'Host: %s' % url.hostname
507 # Add port only if we have one specified
508 if url.port:
509 reqdata += ':%s' % url.port
510 # Line-ending for Host header
511 reqdata += '\r\n'
Markus Zoellerae36ce82017-03-20 16:27:26 +0100512 # Tell the HTTP Server to Upgrade the connection to a WebSocket
513 reqdata += 'Upgrade: websocket\r\nConnection: Upgrade\r\n'
melanie witt27ba9332019-04-26 02:33:20 +0000514 # The token=xxx is sent as a Cookie not in the URI for noVNC < v1.1.0
Markus Zoellerae36ce82017-03-20 16:27:26 +0100515 reqdata += 'Cookie: %s\r\n' % url.query
516 # Use a hard-coded WebSocket key since a test program
517 reqdata += 'Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n'
518 reqdata += 'Sec-WebSocket-Version: 13\r\n'
519 # We are choosing to use binary even though browser may do Base64
520 reqdata += 'Sec-WebSocket-Protocol: binary\r\n\r\n'
521 # Send the HTTP GET request and get the response back
522 self._socket.sendall(reqdata.encode('utf8'))
523 self.response = data = self._socket.recv(4096)
524 # Loop through & concatenate all of the data in the response body
jianghua wangd22514a2017-05-08 08:05:04 +0100525 end_loc = self.response.find(b'\r\n\r\n')
526 while data and end_loc < 0:
Markus Zoellerae36ce82017-03-20 16:27:26 +0100527 data = self._socket.recv(4096)
528 self.response += data
jianghua wangd22514a2017-05-08 08:05:04 +0100529 end_loc = self.response.find(b'\r\n\r\n')
530
531 if len(self.response) > end_loc + 4:
532 # In case some frames (e.g. the first RFP negotiation) have
533 # arrived, cache it for next reading.
534 self.cached_stream = self.response[end_loc + 4:]
535 # ensure response ends with '\r\n\r\n'.
536 self.response = self.response[:end_loc + 4]