Source code for compute.admin.test_spice

# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import socket
import struct
import urllib.parse as urlparse

from tempest.api.compute import base
from tempest import config
from tempest.lib import decorators

CONF = config.CONF


[docs] class SpiceDirectConsoleTestJSON(base.BaseV2ComputeAdminTest): """Test the spice-direct console""" create_default_network = True min_microversion = '2.99' max_microversion = 'latest' # SPICE client protocol constants magic = b'REDQ' major = 2 minor = 2 main_channel = 1 common_caps = 11 # AuthSelection, AuthSpice, MiniHeader channel_caps = 9 # SemiSeamlessMigrate, SeamlessMigrate @classmethod def skip_checks(cls): super().skip_checks() if not CONF.compute_feature_enabled.spice_console: raise cls.skipException('SPICE console feature is disabled.') def tearDown(self): super().tearDown() # NOTE(zhufl): Because server_check_teardown will raise Exception # which will prevent other cleanup steps from being executed, so # server_check_teardown should be called after super's tearDown. self.server_check_teardown() @classmethod def setup_clients(cls): super().setup_clients() cls.client = cls.servers_client @classmethod def resource_setup(cls): super().resource_setup() cls.server = cls.create_test_server(wait_until="ACTIVE")
[docs] @decorators.idempotent_id('80f4460d-1a06-403c-9e93-cf434c70be05') def test_spice_direct(self): """Test accessing spice-direct console of server""" # Request a spice-direct console and validate the result. Any user can # do this. body = self.servers_client.get_remote_console( self.server['id'], console_type='spice-direct', protocol='spice') console_url = body['remote_console']['url'] parts = urlparse.urlparse(console_url) qparams = urlparse.parse_qs(parts.query) self.assertIn('token', qparams) self.assertNotEmpty(qparams['token']) self.assertEqual(1, len(qparams['token'])) self.assertEqual('spice', body['remote_console']['protocol']) self.assertEqual('spice-direct', body['remote_console']['type']) # For reasons best know to the python developers, the qparams values # are lists as documented at # https://docs.python.org/3/library/urllib.parse.html token = qparams['token'][0] # Turn that console token into hypervisor connection details. Only # admins can do this because its expected that the request is coming # from a proxy and we don't want to expose intimate hypervisor details # to all users. body = self.admin_servers_client.get_console_auth_token_details( token) console = body['console'] self.assertEqual(self.server['id'], console['instance_uuid']) self.assertIn('port', console) self.assertIn('tls_port', console) self.assertIsNone(console['internal_access_path']) # Connect to the specified non-TLS port and verify we get back # a SPICE protocol greeting sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((console['host'], console['port'])) # Send a client greeting # # ---- SpiceLinkMess ---- # 4s UINT32 magic value, must be REDQ # I UINT32 major_version, must be 2 # I UINT32 minor_version, must be 2 # I UINT32 size number of bytes following this field to the end # of this message. # I UINT32 connection_id. In case of a new session (i.e., channel # type is SPICE_CHANNEL_MAIN) this field is set to zero, # and in response the server will allocate session id # and will send it via the SpiceLinkReply message. In # case of all other channel types, this field will be # equal to the allocated session id. # B UINT8 channel_type, we use main # B UINT8 channel_id to connect to # I UINT32 num_common_caps number of common client channel # capabilities words # I UINT32 num_channel_caps number of specific client channel # capabilities words # I UINT32 caps_offset location of the start of the capabilities # vector given by the bytes offset from the “size” # member (i.e., from the address of the “connection_id” # member). # ... capabilities sock.sendall(struct.pack( '<4sIIIIBBIIIII', self.magic, self.major, self.minor, 42 - 16, 0, self.main_channel, 0, 1, 1, 18, self.common_caps, self.channel_caps)) # ---- SpiceLinkReply ---- # 4s UINT32 magic value, must be equal to SPICE_MAGIC # I UINT32 major_version, must be equal to SPICE_VERSION_MAJOR # I UINT32 minor_version, must be equal to SPICE_VERSION_MINOR # I UINT32 size number of bytes following this field to the end # of this message. # I UINT32 error code # ... buffered = sock.recv(20) self.assertIsNotNone(buffered) self.assertEqual(20, len(buffered)) magic, major, minor, _, error = struct.unpack_from('<4sIIII', buffered) self.assertEqual(b'REDQ', magic) self.assertEqual(2, major) self.assertEqual(2, minor) self.assertEqual(0, error)