Add MKS console support

MKS is the native protocol for VMware consoles.

DocImpact: two config properties are added in group 'mks':
'mksproxy_base_url' - specifies that base URL for the MKS proxy
'enabled' - enables MKS console

Implements: blueprint vmware-webmks-console

Change-Id: Ia494ce050bd4dc58e5947e7f07cc3c815a257004
This commit is contained in:
Radoslav Gerganov 2015-07-17 15:23:17 +03:00
parent 3ee98a619f
commit 50c8f93e51
11 changed files with 168 additions and 2 deletions

View File

@ -2943,6 +2943,19 @@ class API(base.Base):
instance=instance, console_type=console_type)
return connect_info
@wrap_check_policy
@check_instance_host
def get_mks_console(self, context, instance, console_type):
"""Get a url to a MKS console."""
connect_info = self.compute_rpcapi.get_mks_console(context,
instance=instance, console_type=console_type)
self.consoleauth_rpcapi.authorize_console(context,
connect_info['token'], console_type,
connect_info['host'], connect_info['port'],
connect_info['internal_access_path'], instance.uuid,
access_url=connect_info['access_url'])
return {'url': connect_info['access_url']}
@wrap_check_policy
@check_instance_host
def get_console_output(self, context, instance, tail_length=None):

View File

@ -254,6 +254,8 @@ CONF.import_opt('enable', 'nova.cells.opts', group='cells')
CONF.import_opt('image_cache_manager_interval', 'nova.virt.imagecache')
CONF.import_opt('enabled', 'nova.rdp', group='rdp')
CONF.import_opt('html5_proxy_base_url', 'nova.rdp', group='rdp')
CONF.import_opt('enabled', 'nova.mks', group='mks')
CONF.import_opt('mksproxy_base_url', 'nova.mks', group='mks')
CONF.import_opt('enabled', 'nova.console.serial', group='serial_console')
CONF.import_opt('base_url', 'nova.console.serial', group='serial_console')
CONF.import_opt('destroy_after_evacuate', 'nova.utils', group='workarounds')
@ -648,7 +650,7 @@ class ComputeVirtAPI(virtapi.VirtAPI):
class ComputeManager(manager.Manager):
"""Manages the running instances from creation to destruction."""
target = messaging.Target(version='4.2')
target = messaging.Target(version='4.3')
# How long to wait in seconds before re-issuing a shutdown
# signal to a instance during power off. The overall
@ -4321,6 +4323,40 @@ class ComputeManager(manager.Manager):
return connect_info
@messaging.expected_exceptions(exception.ConsoleTypeInvalid,
exception.InstanceNotReady,
exception.InstanceNotFound,
exception.ConsoleTypeUnavailable,
NotImplementedError)
@wrap_exception()
@wrap_instance_fault
def get_mks_console(self, context, console_type, instance):
"""Return connection information for a MKS console."""
context = context.elevated()
LOG.debug("Getting MKS console", instance=instance)
token = str(uuid.uuid4())
if not CONF.mks.enabled:
raise exception.ConsoleTypeUnavailable(console_type=console_type)
if console_type == 'webmks':
access_url = '%s?token=%s' % (CONF.mks.mksproxy_base_url,
token)
else:
raise exception.ConsoleTypeInvalid(console_type=console_type)
try:
# Retrieve connect info from driver, and then decorate with our
# access info token
console = self.driver.get_mks_console(context, instance)
connect_info = console.get_connection_info(token, access_url)
except exception.InstanceNotFound:
if instance.vm_state != vm_states.BUILDING:
raise
raise exception.InstanceNotReady(instance_id=instance.uuid)
return connect_info
@messaging.expected_exceptions(
exception.ConsoleTypeInvalid,
exception.InstanceNotReady,
@ -4369,6 +4405,8 @@ class ComputeManager(manager.Manager):
console_info = self.driver.get_rdp_console(ctxt, instance)
elif console_type == "serial":
console_info = self.driver.get_serial_console(ctxt, instance)
elif console_type == "webmks":
console_info = self.driver.get_mks_console(ctxt, instance)
else:
console_info = self.driver.get_vnc_console(ctxt, instance)
@ -5080,7 +5118,8 @@ class ComputeManager(manager.Manager):
def _consoles_enabled(self):
"""Returns whether a console is enable."""
return (CONF.vnc.enabled or CONF.spice.enabled or
CONF.rdp.enabled or CONF.serial_console.enabled)
CONF.rdp.enabled or CONF.serial_console.enabled or
CONF.mks.enabled)
def _clean_instance_console_tokens(self, ctxt, instance):
"""Clean console tokens stored for an instance."""

View File

@ -299,6 +299,7 @@ class ComputeAPI(object):
* 4.0 - Remove 3.x compatibility
* 4.1 - Make prep_resize() and resize_instance() send Flavor object
* 4.2 - Add migration argument to live_migration()
* 4.3 - Added get_mks_console method
'''
VERSION_ALIASES = {
@ -491,6 +492,13 @@ class ComputeAPI(object):
return cctxt.call(ctxt, 'get_rdp_console',
instance=instance, console_type=console_type)
def get_mks_console(self, ctxt, instance, console_type):
version = '4.3'
cctxt = self.client.prepare(server=_compute_host(None, instance),
version=version)
return cctxt.call(ctxt, 'get_mks_console',
instance=instance, console_type=console_type)
def get_serial_console(self, ctxt, instance, console_type):
version = '4.0'
cctxt = self.client.prepare(server=_compute_host(None, instance),

View File

@ -44,3 +44,7 @@ class ConsoleSpice(Console):
class ConsoleSerial(Console):
pass
class ConsoleMKS(Console):
pass

31
nova/mks/__init__.py Normal file
View File

@ -0,0 +1,31 @@
# Copyright 2015 VMware Inc.
#
# 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.
"""Module for MKS consoles."""
from oslo_config import cfg
mks_opts = [
cfg.StrOpt('mksproxy_base_url',
default='http://127.0.0.1:6090/',
help='Location of MKS web console proxy, in the form '
'"http://127.0.0.1:6090/"'),
cfg.BoolOpt('enabled',
default=False,
help='Enable MKS related features'),
]
CONF = cfg.CONF
CONF.register_opts(mks_opts, group='mks')

View File

@ -3332,6 +3332,17 @@ class ComputeTestCase(BaseTestCase):
context=self.context, instance=instance, port=5900,
console_type="rdp-html5"))
def test_validate_console_port_mks(self):
self.flags(enabled=True, group='mks')
instance = self._create_fake_instance_obj()
with mock.patch.object(
self.compute.driver, 'get_mks_console') as mock_getmks:
mock_getmks.return_value = ctype.ConsoleMKS(host="fake_host",
port=5900)
result = self.compute.validate_console_port(context=self.context,
instance=instance, port=5900, console_type="webmks")
self.assertTrue(result)
def test_validate_console_port_wrong_port(self):
self.flags(enabled=True, group='vnc')
self.flags(enabled=True, group='spice')
@ -9207,6 +9218,38 @@ class ComputeAPITestCase(BaseTestCase):
self.compute_api.get_serial_console,
self.context, instance, 'serial')
def test_mks_console(self):
fake_instance = self._fake_instance({'uuid': 'fake_uuid',
'host': 'fake_compute_host'})
fake_console_type = 'webmks'
fake_connect_info = {'token': 'fake_token',
'console_type': fake_console_type,
'host': 'fake_mks_host',
'port': 'fake_tcp_port',
'internal_access_path': 'fake_access_path',
'instance_uuid': fake_instance.uuid,
'access_url': 'fake_access_url'}
with contextlib.nested(
mock.patch.object(self.compute_api.compute_rpcapi,
'get_mks_console',
return_value=fake_connect_info),
mock.patch.object(self.compute_api.consoleauth_rpcapi,
'authorize_console')
) as (mock_get_mks_console, mock_authorize_console):
console = self.compute_api.get_mks_console(self.context,
fake_instance,
fake_console_type)
self.assertEqual(console, {'url': 'fake_access_url'})
def test_get_mks_console_no_host(self):
# Make sure an exception is raised when instance is not Active.
instance = self._create_fake_instance_obj(params={'host': ''})
self.assertRaises(exception.InstanceNotReady,
self.compute_api.get_mks_console,
self.context, instance, 'mks')
def test_console_output(self):
fake_instance = self._fake_instance({'uuid': 'fake_uuid',
'host': 'fake_compute_host'})

View File

@ -215,6 +215,11 @@ class ComputeRpcAPITestCase(test.NoDBTestCase):
instance=self.fake_instance_obj, console_type='serial',
version='4.0')
def test_get_mks_console(self):
self._test_compute_api('get_mks_console', 'call',
instance=self.fake_instance_obj, console_type='webmks',
version='4.3')
def test_validate_console_port(self):
self._test_compute_api('validate_console_port', 'call',
instance=self.fake_instance_obj, port="5900",

View File

@ -49,6 +49,7 @@ policy_data = """
"compute:get_spice_console": "",
"compute:get_rdp_console": "",
"compute:get_serial_console": "",
"compute:get_mks_console": "",
"compute:get_console_output": "",
"compute:reset_network": "",

View File

@ -581,6 +581,13 @@ class _VirtDriverTestCase(_FakeDriverBackendTestCase):
instance_ref)
self.assertIsInstance(serial_console, ctype.ConsoleSerial)
@catch_notimplementederror
def test_get_mks_console(self):
instance_ref, network_info = self._get_running_instance()
mks_console = self.connection.get_mks_console(self.ctxt,
instance_ref)
self.assertIsInstance(mks_console, ctype.ConsoleMKS)
@catch_notimplementederror
def test_get_console_pool_info(self):
instance_ref, network_info = self._get_running_instance()

View File

@ -433,6 +433,16 @@ class ComputeDriver(object):
"""
raise NotImplementedError()
def get_mks_console(self, context, instance):
"""Get connection info for a MKS console.
:param context: security context
:param instance: nova.objects.instance.Instance
:returns an instance of console.type.ConsoleMKS
"""
raise NotImplementedError()
def get_diagnostics(self, instance):
"""Return data about VM diagnostics.

View File

@ -411,6 +411,11 @@ class FakeDriver(driver.ComputeDriver):
host='fakerdpconsole.com',
port=6969)
def get_mks_console(self, context, instance):
return ctype.ConsoleMKS(internal_access_path='FAKE',
host='fakemksconsole.com',
port=6969)
def get_console_pool_info(self, console_type):
return {'address': '127.0.0.1',
'username': 'fakeuser',