diff --git a/nova/compute/api.py b/nova/compute/api.py index 1c077e77826d..a963181adcc2 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -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): diff --git a/nova/compute/manager.py b/nova/compute/manager.py index f8e8ca4d145a..9ea5fd880f2c 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -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.""" diff --git a/nova/compute/rpcapi.py b/nova/compute/rpcapi.py index b98409aca114..61dced72ecc6 100644 --- a/nova/compute/rpcapi.py +++ b/nova/compute/rpcapi.py @@ -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), diff --git a/nova/console/type.py b/nova/console/type.py index 4af52f3c0b19..b4a34899b26d 100644 --- a/nova/console/type.py +++ b/nova/console/type.py @@ -44,3 +44,7 @@ class ConsoleSpice(Console): class ConsoleSerial(Console): pass + + +class ConsoleMKS(Console): + pass diff --git a/nova/mks/__init__.py b/nova/mks/__init__.py new file mode 100644 index 000000000000..6bfc5235dbcd --- /dev/null +++ b/nova/mks/__init__.py @@ -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') diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py index dc353c85b9be..9f6f1860b734 100644 --- a/nova/tests/unit/compute/test_compute.py +++ b/nova/tests/unit/compute/test_compute.py @@ -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'}) diff --git a/nova/tests/unit/compute/test_rpcapi.py b/nova/tests/unit/compute/test_rpcapi.py index 6adcdf24f958..5a6891ac8810 100644 --- a/nova/tests/unit/compute/test_rpcapi.py +++ b/nova/tests/unit/compute/test_rpcapi.py @@ -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", diff --git a/nova/tests/unit/fake_policy.py b/nova/tests/unit/fake_policy.py index e83e323491bf..01acfaccfcda 100644 --- a/nova/tests/unit/fake_policy.py +++ b/nova/tests/unit/fake_policy.py @@ -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": "", diff --git a/nova/tests/unit/virt/test_virt_drivers.py b/nova/tests/unit/virt/test_virt_drivers.py index 64d7ced729bf..96be16dfbb97 100644 --- a/nova/tests/unit/virt/test_virt_drivers.py +++ b/nova/tests/unit/virt/test_virt_drivers.py @@ -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() diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 73be103880ac..3b86c0864cb2 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -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. diff --git a/nova/virt/fake.py b/nova/virt/fake.py index dd3843d19e7f..ce196da1b182 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -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',