summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlessandro Pilotti <apilotti@cloudbasesolutions.com>2013-08-23 17:55:14 +0300
committerAlessandro Pilotti <apilotti@cloudbasesolutions.com>2014-02-07 21:20:08 +0200
commite638a8f2ecdb0035b25aca6e8867c732296c0782 (patch)
tree4c803cf1d721e4d9bc79fec0b9930a9f11ab804e
parent4d55ab49fda87aebe34e5ee9dc1c7a1f3ca4286f (diff)
downloadnova-e638a8f2ecdb0035b25aca6e8867c732296c0782.tar.gz
Adds RDP console support
Implements: blueprint hyper-v-rdp-console Nova currently supports VNC and SPICE remote console protocols. This commit adds support for the RDP protocol in a similar way. Change-Id: I2c219d4a200122c6d6cfcbd8e074dca0f6fea598
-rw-r--r--doc/api_samples/os-consoles/get-rdp-console-post-req.json5
-rw-r--r--doc/api_samples/os-consoles/get-rdp-console-post-req.xml2
-rw-r--r--doc/api_samples/os-consoles/get-rdp-console-post-resp.json6
-rw-r--r--doc/api_samples/os-consoles/get-rdp-console-post-resp.xml5
-rw-r--r--doc/v3/api_samples/os-remote-consoles/get-rdp-console-post-req.json5
-rw-r--r--doc/v3/api_samples/os-remote-consoles/get-rdp-console-post-resp.json6
-rw-r--r--etc/nova/nova.conf.sample14
-rw-r--r--nova/api/openstack/compute/contrib/consoles.py28
-rw-r--r--nova/api/openstack/compute/plugins/v3/remote_consoles.py29
-rw-r--r--nova/compute/api.py20
-rw-r--r--nova/compute/cells_api.py16
-rw-r--r--nova/compute/manager.py44
-rw-r--r--nova/compute/rpcapi.py8
-rw-r--r--nova/rdp/__init__.py31
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_consoles.py92
-rw-r--r--nova/tests/api/openstack/compute/plugins/v3/test_remote_consoles.py109
-rw-r--r--nova/tests/compute/test_compute.py134
-rw-r--r--nova/tests/compute/test_rpcapi.py5
-rw-r--r--nova/tests/fake_policy.py1
-rw-r--r--nova/tests/integrated/api_samples/os-consoles/get-rdp-console-post-req.json.tpl5
-rw-r--r--nova/tests/integrated/api_samples/os-consoles/get-rdp-console-post-req.xml.tpl4
-rw-r--r--nova/tests/integrated/api_samples/os-consoles/get-rdp-console-post-resp.json.tpl6
-rw-r--r--nova/tests/integrated/api_samples/os-consoles/get-rdp-console-post-resp.xml.tpl5
-rw-r--r--nova/tests/integrated/test_api_samples.py12
-rw-r--r--nova/tests/integrated/v3/api_samples/os-remote-consoles/get-rdp-console-post-req.json.tpl5
-rw-r--r--nova/tests/integrated/v3/api_samples/os-remote-consoles/get-rdp-console-post-resp.json.tpl6
-rw-r--r--nova/tests/integrated/v3/test_remote_consoles.py12
-rw-r--r--nova/tests/virt/test_virt_drivers.py8
-rw-r--r--nova/virt/driver.py8
-rw-r--r--nova/virt/fake.py5
30 files changed, 633 insertions, 3 deletions
diff --git a/doc/api_samples/os-consoles/get-rdp-console-post-req.json b/doc/api_samples/os-consoles/get-rdp-console-post-req.json
new file mode 100644
index 0000000000..00956b90e4
--- /dev/null
+++ b/doc/api_samples/os-consoles/get-rdp-console-post-req.json
@@ -0,0 +1,5 @@
+{
+ "os-getRDPConsole": {
+ "type": "rdp-html5"
+ }
+}
diff --git a/doc/api_samples/os-consoles/get-rdp-console-post-req.xml b/doc/api_samples/os-consoles/get-rdp-console-post-req.xml
new file mode 100644
index 0000000000..16cf28832d
--- /dev/null
+++ b/doc/api_samples/os-consoles/get-rdp-console-post-req.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<os-getRDPConsole type="rdp-html5" />
diff --git a/doc/api_samples/os-consoles/get-rdp-console-post-resp.json b/doc/api_samples/os-consoles/get-rdp-console-post-resp.json
new file mode 100644
index 0000000000..583d9c1e40
--- /dev/null
+++ b/doc/api_samples/os-consoles/get-rdp-console-post-resp.json
@@ -0,0 +1,6 @@
+{
+ "console": {
+ "type": "rdp-html5",
+ "url": "http://example.com:6083/?token=f9906a48-b71e-4f18-baca-c987da3ebdb3&title=dafa(75ecef58-3b8e-4659-ab3b-5501454188e9)"
+ }
+}
diff --git a/doc/api_samples/os-consoles/get-rdp-console-post-resp.xml b/doc/api_samples/os-consoles/get-rdp-console-post-resp.xml
new file mode 100644
index 0000000000..6c45d6e269
--- /dev/null
+++ b/doc/api_samples/os-consoles/get-rdp-console-post-resp.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<console>
+ <type>rdp-html5</type>
+ <url>http://example.com:6083/?token=f9906a48-b71e-4f18-baca-c987da3ebdb3</url>
+</console>
diff --git a/doc/v3/api_samples/os-remote-consoles/get-rdp-console-post-req.json b/doc/v3/api_samples/os-remote-consoles/get-rdp-console-post-req.json
new file mode 100644
index 0000000000..075d3b28a8
--- /dev/null
+++ b/doc/v3/api_samples/os-remote-consoles/get-rdp-console-post-req.json
@@ -0,0 +1,5 @@
+{
+ "get_rdp_console": {
+ "type": "rdp-html5"
+ }
+}
diff --git a/doc/v3/api_samples/os-remote-consoles/get-rdp-console-post-resp.json b/doc/v3/api_samples/os-remote-consoles/get-rdp-console-post-resp.json
new file mode 100644
index 0000000000..09f3ca3d8c
--- /dev/null
+++ b/doc/v3/api_samples/os-remote-consoles/get-rdp-console-post-resp.json
@@ -0,0 +1,6 @@
+{
+ "console": {
+ "type": "rdp-html5",
+ "url": "http://127.0.0.1:6083/?token=191996c3-7b0f-42f3-95a7-f1839f2da6ed"
+ }
+}
diff --git a/etc/nova/nova.conf.sample b/etc/nova/nova.conf.sample
index 5aa2a5a8fd..7834040b6c 100644
--- a/etc/nova/nova.conf.sample
+++ b/etc/nova/nova.conf.sample
@@ -3033,6 +3033,20 @@
#extensions_whitelist=
+[rdp]
+
+#
+# Options defined in nova.rdp
+#
+
+# Location of RDP html5 console proxy, in the form
+# "http://127.0.0.1:6083/" (string value)
+#html5_proxy_base_url=http://127.0.0.1:6083/
+
+# Enable RDP related features (boolean value)
+#enabled=false
+
+
[remote_debug]
#
diff --git a/nova/api/openstack/compute/contrib/consoles.py b/nova/api/openstack/compute/contrib/consoles.py
index 07bab0be48..54ebc9a683 100644
--- a/nova/api/openstack/compute/contrib/consoles.py
+++ b/nova/api/openstack/compute/contrib/consoles.py
@@ -75,12 +75,38 @@ class ConsolesController(wsgi.Controller):
return {'console': {'type': console_type, 'url': output['url']}}
+ @wsgi.action('os-getRDPConsole')
+ def get_rdp_console(self, req, id, body):
+ """Get text console output."""
+ context = req.environ['nova.context']
+ authorize(context)
+
+ # If type is not supplied or unknown, get_rdp_console below will cope
+ console_type = body['os-getRDPConsole'].get('type')
+
+ try:
+ instance = self.compute_api.get(context, id, want_objects=True)
+ output = self.compute_api.get_rdp_console(context,
+ instance,
+ console_type)
+ except exception.InstanceNotFound as e:
+ raise webob.exc.HTTPNotFound(explanation=e.format_message())
+ except exception.InstanceNotReady as e:
+ raise webob.exc.HTTPConflict(explanation=e.format_message())
+ except NotImplementedError:
+ msg = _("Unable to get rdp console, functionality not implemented")
+ raise webob.exc.HTTPNotImplemented(explanation=msg)
+
+ return {'console': {'type': console_type, 'url': output['url']}}
+
def get_actions(self):
"""Return the actions the extension adds, as required by contract."""
actions = [extensions.ActionExtension("servers", "os-getVNCConsole",
self.get_vnc_console),
extensions.ActionExtension("servers", "os-getSPICEConsole",
- self.get_spice_console)]
+ self.get_spice_console),
+ extensions.ActionExtension("servers", "os-getRDPConsole",
+ self.get_rdp_console)]
return actions
diff --git a/nova/api/openstack/compute/plugins/v3/remote_consoles.py b/nova/api/openstack/compute/plugins/v3/remote_consoles.py
index e78fa0ad0c..726dc96344 100644
--- a/nova/api/openstack/compute/plugins/v3/remote_consoles.py
+++ b/nova/api/openstack/compute/plugins/v3/remote_consoles.py
@@ -85,6 +85,35 @@ class RemoteConsolesController(wsgi.Controller):
return {'console': {'type': console_type, 'url': output['url']}}
+ @extensions.expected_errors((400, 404, 409, 501))
+ @wsgi.action('get_rdp_console')
+ def get_rdp_console(self, req, id, body):
+ """Get text console output."""
+ context = req.environ['nova.context']
+ authorize(context)
+
+ # If type is not supplied or unknown, get_rdp_console below will cope
+ console_type = body['get_rdp_console'].get('type')
+
+ try:
+ instance = self.compute_api.get(context, id, want_objects=True)
+ output = self.compute_api.get_rdp_console(context,
+ instance,
+ console_type)
+ except exception.ConsoleTypeInvalid as e:
+ raise webob.exc.HTTPBadRequest(explanation=e.format_message())
+ except exception.ConsoleTypeUnavailable as e:
+ raise webob.exc.HTTPBadRequest(explanation=e.format_message())
+ except exception.InstanceNotFound as e:
+ raise webob.exc.HTTPNotFound(explanation=e.format_message())
+ except exception.InstanceNotReady as e:
+ raise webob.exc.HTTPConflict(explanation=e.format_message())
+ except NotImplementedError:
+ msg = _("Unable to get rdp console, functionality not implemented")
+ raise webob.exc.HTTPNotImplemented(explanation=msg)
+
+ return {'console': {'type': console_type, 'url': output['url']}}
+
class RemoteConsoles(extensions.V3APIExtensionBase):
"""Interactive Console support."""
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 20b00bfa1e..2e7af4ee59 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -2636,6 +2636,26 @@ class API(base.Base):
@wrap_check_policy
@check_instance_host
+ def get_rdp_console(self, context, instance, console_type):
+ """Get a url to an instance Console."""
+ connect_info = self.compute_rpcapi.get_rdp_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'])
+
+ return {'url': connect_info['access_url']}
+
+ @check_instance_host
+ def get_rdp_connect_info(self, context, instance, console_type):
+ """Used in a child cell to get console info."""
+ connect_info = self.compute_rpcapi.get_rdp_console(context,
+ instance=instance, console_type=console_type)
+ return connect_info
+
+ @wrap_check_policy
+ @check_instance_host
def get_console_output(self, context, instance, tail_length=None):
"""Get console output for an instance."""
return self.compute_rpcapi.get_console_output(context,
diff --git a/nova/compute/cells_api.py b/nova/compute/cells_api.py
index 5543b5bf9d..0f31c24286 100644
--- a/nova/compute/cells_api.py
+++ b/nova/compute/cells_api.py
@@ -384,6 +384,22 @@ class ComputeCellsAPI(compute_api.API):
instance['uuid'])
return {'url': connect_info['access_url']}
+ @wrap_check_policy
+ @check_instance_cell
+ def get_rdp_console(self, context, instance, console_type):
+ """Get a url to a RDP Console."""
+ if not instance['host']:
+ raise exception.InstanceNotReady(instance_id=instance['uuid'])
+
+ connect_info = self._call_to_cells(context, instance,
+ 'get_rdp_connect_info', 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'])
+ return {'url': connect_info['access_url']}
+
@check_instance_cell
def get_console_output(self, context, instance, *args, **kwargs):
"""Get console output for an an instance."""
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 24ba25c77e..97b92e9413 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -207,6 +207,8 @@ CONF.import_opt('enabled', 'nova.spice', group='spice')
CONF.import_opt('enable', 'nova.cells.opts', group='cells')
CONF.import_opt('image_cache_subdirectory_name', 'nova.virt.imagecache')
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')
LOG = logging.getLogger(__name__)
@@ -409,7 +411,7 @@ class ComputeVirtAPI(virtapi.VirtAPI):
class ComputeManager(manager.Manager):
"""Manages the running instances from creation to destruction."""
- target = messaging.Target(version='3.9')
+ target = messaging.Target(version='3.10')
def __init__(self, compute_driver=None, *args, **kwargs):
"""Load configuration options and connect to the hypervisor."""
@@ -3760,6 +3762,42 @@ class ComputeManager(manager.Manager):
return connect_info
+ @object_compat
+ @messaging.expected_exceptions(exception.ConsoleTypeInvalid,
+ exception.InstanceNotReady,
+ exception.InstanceNotFound,
+ exception.ConsoleTypeUnavailable,
+ NotImplementedError)
+ @wrap_exception()
+ @wrap_instance_fault
+ def get_rdp_console(self, context, console_type, instance):
+ """Return connection information for a RDP console."""
+ context = context.elevated()
+ LOG.debug(_("Getting RDP console"), instance=instance)
+ token = str(uuid.uuid4())
+
+ if not CONF.rdp.enabled:
+ raise exception.ConsoleTypeInvalid(console_type=console_type)
+
+ if console_type == 'rdp-html5':
+ access_url = '%s?token=%s' % (CONF.rdp.html5_proxy_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
+ connect_info = self.driver.get_rdp_console(context, instance)
+ connect_info['token'] = token
+ connect_info['access_url'] = 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,
exception.InstanceNotFound)
@@ -3769,6 +3807,8 @@ class ComputeManager(manager.Manager):
def validate_console_port(self, ctxt, instance, port, console_type):
if console_type == "spice-html5":
console_info = self.driver.get_spice_console(ctxt, instance)
+ elif console_type == "rdp-html5":
+ console_info = self.driver.get_rdp_console(ctxt, instance)
else:
console_info = self.driver.get_vnc_console(ctxt, instance)
@@ -4345,7 +4385,7 @@ class ComputeManager(manager.Manager):
"This error can be safely ignored."),
instance=instance_ref)
- if CONF.vnc_enabled or CONF.spice.enabled:
+ if CONF.vnc_enabled or CONF.spice.enabled or CONF.rdp.enabled:
if CONF.cells.enable:
self.cells_rpcapi.consoleauth_delete_tokens(ctxt,
instance_ref['uuid'])
diff --git a/nova/compute/rpcapi.py b/nova/compute/rpcapi.py
index 4140a72297..2cdd409a98 100644
--- a/nova/compute/rpcapi.py
+++ b/nova/compute/rpcapi.py
@@ -223,6 +223,7 @@ class ComputeAPI(object):
3.7 - Update change_instance_metadata() to take an instance object
3.8 - Update set_admin_password() to take an instance object
3.9 - Update rescue_instance() to take an instance object
+ 3.10 - Added get_rdp_console method
'''
VERSION_ALIASES = {
@@ -450,6 +451,13 @@ class ComputeAPI(object):
return cctxt.call(ctxt, 'get_spice_console',
instance=instance, console_type=console_type)
+ def get_rdp_console(self, ctxt, instance, console_type):
+ version = '3.10'
+ cctxt = self.client.prepare(server=_compute_host(None, instance),
+ version=version)
+ return cctxt.call(ctxt, 'get_rdp_console',
+ instance=instance, console_type=console_type)
+
def validate_console_port(self, ctxt, instance, port, console_type):
if self.client.can_send_version('3.3'):
version = '3.3'
diff --git a/nova/rdp/__init__.py b/nova/rdp/__init__.py
new file mode 100644
index 0000000000..ef30ba184b
--- /dev/null
+++ b/nova/rdp/__init__.py
@@ -0,0 +1,31 @@
+# Copyright 2014 Cloudbase Solutions Srl
+#
+# 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 RDP Proxying."""
+
+from oslo.config import cfg
+
+
+rdp_opts = [
+ cfg.StrOpt('html5_proxy_base_url',
+ default='http://127.0.0.1:6083/',
+ help='Location of RDP html5 console proxy, in the form '
+ '"http://127.0.0.1:6083/"'),
+ cfg.BoolOpt('enabled',
+ default=False,
+ help='Enable RDP related features'),
+ ]
+
+CONF = cfg.CONF
+CONF.register_opts(rdp_opts, group='rdp')
diff --git a/nova/tests/api/openstack/compute/contrib/test_consoles.py b/nova/tests/api/openstack/compute/contrib/test_consoles.py
index 8fd6e6abb6..cafed85f22 100644
--- a/nova/tests/api/openstack/compute/contrib/test_consoles.py
+++ b/nova/tests/api/openstack/compute/contrib/test_consoles.py
@@ -30,6 +30,10 @@ def fake_get_spice_console(self, _context, _instance, _console_type):
return {'url': 'http://fake'}
+def fake_get_rdp_console(self, _context, _instance, _console_type):
+ return {'url': 'http://fake'}
+
+
def fake_get_vnc_console_invalid_type(self, _context,
_instance, _console_type):
raise exception.ConsoleTypeInvalid(console_type=_console_type)
@@ -40,6 +44,11 @@ def fake_get_spice_console_invalid_type(self, _context,
raise exception.ConsoleTypeInvalid(console_type=_console_type)
+def fake_get_rdp_console_invalid_type(self, _context,
+ _instance, _console_type):
+ raise exception.ConsoleTypeInvalid(console_type=_console_type)
+
+
def fake_get_vnc_console_not_ready(self, _context, instance, _console_type):
raise exception.InstanceNotReady(instance_id=instance["uuid"])
@@ -48,6 +57,10 @@ def fake_get_spice_console_not_ready(self, _context, instance, _console_type):
raise exception.InstanceNotReady(instance_id=instance["uuid"])
+def fake_get_rdp_console_not_ready(self, _context, instance, _console_type):
+ raise exception.InstanceNotReady(instance_id=instance["uuid"])
+
+
def fake_get_vnc_console_not_found(self, _context, instance, _console_type):
raise exception.InstanceNotFound(instance_id=instance["uuid"])
@@ -56,6 +69,10 @@ def fake_get_spice_console_not_found(self, _context, instance, _console_type):
raise exception.InstanceNotFound(instance_id=instance["uuid"])
+def fake_get_rdp_console_not_found(self, _context, instance, _console_type):
+ raise exception.InstanceNotFound(instance_id=instance["uuid"])
+
+
def fake_get(self, context, instance_uuid, want_objects=False):
return {'uuid': instance_uuid}
@@ -72,6 +89,8 @@ class ConsolesExtensionTest(test.NoDBTestCase):
fake_get_vnc_console)
self.stubs.Set(compute_api.API, 'get_spice_console',
fake_get_spice_console)
+ self.stubs.Set(compute_api.API, 'get_rdp_console',
+ fake_get_rdp_console)
self.stubs.Set(compute_api.API, 'get', fake_get)
self.flags(
osapi_compute_extension=[
@@ -237,3 +256,76 @@ class ConsolesExtensionTest(test.NoDBTestCase):
res = req.get_response(self.app)
self.assertEqual(res.status_int, 400)
+
+ def test_get_rdp_console(self):
+ body = {'os-getRDPConsole': {'type': 'rdp-html5'}}
+ req = webob.Request.blank('/v2/fake/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ output = jsonutils.loads(res.body)
+ self.assertEqual(res.status_int, 200)
+ self.assertEqual(output,
+ {u'console': {u'url': u'http://fake', u'type': u'rdp-html5'}})
+
+ def test_get_rdp_console_not_ready(self):
+ self.stubs.Set(compute_api.API, 'get_rdp_console',
+ fake_get_rdp_console_not_ready)
+ body = {'os-getRDPConsole': {'type': 'rdp-html5'}}
+ req = webob.Request.blank('/v2/fake/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ output = jsonutils.loads(res.body)
+ self.assertEqual(res.status_int, 409)
+
+ def test_get_rdp_console_no_type(self):
+ self.stubs.Set(compute_api.API, 'get_rdp_console',
+ fake_get_rdp_console_invalid_type)
+ body = {'os-getRDPConsole': {}}
+ req = webob.Request.blank('/v2/fake/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 400)
+
+ def test_get_rdp_console_no_instance(self):
+ self.stubs.Set(compute_api.API, 'get', fake_get_not_found)
+ body = {'os-getRDPConsole': {'type': 'rdp-html5'}}
+ req = webob.Request.blank('/v2/fake/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 404)
+
+ def test_get_rdp_console_no_instance_on_console_get(self):
+ self.stubs.Set(compute_api.API, 'get_rdp_console',
+ fake_get_rdp_console_not_found)
+ body = {'os-getRDPConsole': {'type': 'rdp-html5'}}
+ req = webob.Request.blank('/v2/fake/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 404)
+
+ def test_get_rdp_console_invalid_type(self):
+ body = {'os-getRDPConsole': {'type': 'invalid'}}
+ self.stubs.Set(compute_api.API, 'get_rdp_console',
+ fake_get_rdp_console_invalid_type)
+ req = webob.Request.blank('/v2/fake/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 400)
diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_remote_consoles.py b/nova/tests/api/openstack/compute/plugins/v3/test_remote_consoles.py
index f4ee188aea..1af57e0558 100644
--- a/nova/tests/api/openstack/compute/plugins/v3/test_remote_consoles.py
+++ b/nova/tests/api/openstack/compute/plugins/v3/test_remote_consoles.py
@@ -30,6 +30,10 @@ def fake_get_spice_console(self, _context, _instance, _console_type):
return {'url': 'http://fake'}
+def fake_get_rdp_console(self, _context, _instance, _console_type):
+ return {'url': 'http://fake'}
+
+
def fake_get_vnc_console_invalid_type(self, _context,
_instance, _console_type):
raise exception.ConsoleTypeInvalid(console_type=_console_type)
@@ -40,6 +44,11 @@ def fake_get_spice_console_invalid_type(self, _context,
raise exception.ConsoleTypeInvalid(console_type=_console_type)
+def fake_get_rdp_console_invalid_type(self, _context,
+ _instance, _console_type):
+ raise exception.ConsoleTypeInvalid(console_type=_console_type)
+
+
def fake_get_vnc_console_type_unavailable(self, _context,
_instance, _console_type):
raise exception.ConsoleTypeUnavailable(console_type=_console_type)
@@ -50,6 +59,11 @@ def fake_get_spice_console_type_unavailable(self, _context,
raise exception.ConsoleTypeUnavailable(console_type=_console_type)
+def fake_get_rdp_console_type_unavailable(self, _context,
+ _instance, _console_type):
+ raise exception.ConsoleTypeUnavailable(console_type=_console_type)
+
+
def fake_get_vnc_console_not_ready(self, _context, instance, _console_type):
raise exception.InstanceNotReady(instance_id=instance["uuid"])
@@ -58,6 +72,10 @@ def fake_get_spice_console_not_ready(self, _context, instance, _console_type):
raise exception.InstanceNotReady(instance_id=instance["uuid"])
+def fake_get_rdp_console_not_ready(self, _context, instance, _console_type):
+ raise exception.InstanceNotReady(instance_id=instance["uuid"])
+
+
def fake_get_vnc_console_not_found(self, _context, instance, _console_type):
raise exception.InstanceNotFound(instance_id=instance["uuid"])
@@ -66,6 +84,10 @@ def fake_get_spice_console_not_found(self, _context, instance, _console_type):
raise exception.InstanceNotFound(instance_id=instance["uuid"])
+def fake_get_rdp_console_not_found(self, _context, instance, _console_type):
+ raise exception.InstanceNotFound(instance_id=instance["uuid"])
+
+
def fake_get(self, context, instance_uuid, want_objects=False):
return {'uuid': instance_uuid}
@@ -82,6 +104,8 @@ class ConsolesExtensionTest(test.NoDBTestCase):
fake_get_vnc_console)
self.stubs.Set(compute_api.API, 'get_spice_console',
fake_get_spice_console)
+ self.stubs.Set(compute_api.API, 'get_rdp_console',
+ fake_get_rdp_console)
self.stubs.Set(compute_api.API, 'get', fake_get)
self.app = fakes.wsgi_app_v3(init_only=('servers',
'os-remote-consoles'))
@@ -268,3 +292,88 @@ class ConsolesExtensionTest(test.NoDBTestCase):
res = req.get_response(self.app)
self.assertEqual(400, res.status_int)
+
+ def test_get_rdp_console(self):
+ body = {'get_rdp_console': {'type': 'rdp-html5'}}
+ req = webob.Request.blank('/v3/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ output = jsonutils.loads(res.body)
+ self.assertEqual(res.status_int, 200)
+ self.assertEqual(output,
+ {u'console': {u'url': u'http://fake', u'type': u'rdp-html5'}})
+
+ def test_get_rdp_console_not_ready(self):
+ self.stubs.Set(compute_api.API, 'get_rdp_console',
+ fake_get_rdp_console_not_ready)
+ body = {'get_rdp_console': {'type': 'rdp-html5'}}
+ req = webob.Request.blank('/v3/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ output = jsonutils.loads(res.body)
+ self.assertEqual(res.status_int, 409)
+
+ def test_get_rdp_console_no_type(self):
+ self.stubs.Set(compute_api.API, 'get_rdp_console',
+ fake_get_rdp_console_invalid_type)
+ body = {'get_rdp_console': {}}
+ req = webob.Request.blank('/v3/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 400)
+
+ def test_get_rdp_console_no_instance(self):
+ self.stubs.Set(compute_api.API, 'get', fake_get_not_found)
+ body = {'get_rdp_console': {'type': 'rdp-html5'}}
+ req = webob.Request.blank('/v3/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 404)
+
+ def test_get_rdp_console_no_instance_on_console_get(self):
+ self.stubs.Set(compute_api.API, 'get_rdp_console',
+ fake_get_rdp_console_not_found)
+ body = {'get_rdp_console': {'type': 'rdp-html5'}}
+ req = webob.Request.blank('/v3/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 404)
+
+ def test_get_rdp_console_invalid_type(self):
+ body = {'get_rdp_console': {'type': 'invalid'}}
+ self.stubs.Set(compute_api.API, 'get_rdp_console',
+ fake_get_rdp_console_invalid_type)
+ req = webob.Request.blank('/v3/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 400)
+
+ def test_get_rdp_console_type_unavailable(self):
+ body = {'get_rdp_console': {'type': 'unavailable'}}
+ self.stubs.Set(compute_api.API, 'get_rdp_console',
+ fake_get_rdp_console_type_unavailable)
+ req = webob.Request.blank('/v3/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ self.assertEqual(400, res.status_int)
diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py
index bf58634ef9..950b8c2c4c 100644
--- a/nova/tests/compute/test_compute.py
+++ b/nova/tests/compute/test_compute.py
@@ -2746,6 +2746,20 @@ class ComputeTestCase(BaseTestCase):
context=self.context, instance=instance, port="5900",
console_type="spice-html5"))
+ def test_validate_console_port_rdp(self):
+ self.flags(enabled=True, group='rdp')
+ instance = self._create_fake_instance_obj()
+
+ def fake_driver_get_console(*args, **kwargs):
+ return {'host': "fake_host", 'port': "5900",
+ 'internal_access_path': None}
+ self.stubs.Set(self.compute.driver, "get_rdp_console",
+ fake_driver_get_console)
+
+ self.assertTrue(self.compute.validate_console_port(
+ context=self.context, instance=instance, port="5900",
+ console_type="rdp-html5"))
+
def test_validate_console_port_wrong_port(self):
self.flags(vnc_enabled=True)
self.flags(enabled=True, group='spice')
@@ -2902,6 +2916,67 @@ class ComputeTestCase(BaseTestCase):
self.compute.terminate_instance(self.context, instance, [], [])
+ def test_rdphtml5_rdp_console(self):
+ # Make sure we can a rdp console for an instance.
+ self.flags(vnc_enabled=False)
+ self.flags(enabled=True, group='rdp')
+
+ instance = self._create_fake_instance_obj()
+ self.compute.run_instance(self.context,
+ jsonutils.to_primitive(instance), {}, {}, [], None,
+ None, True, None, False)
+
+ # Try with the full instance
+ console = self.compute.get_rdp_console(self.context, 'rdp-html5',
+ instance=instance)
+ self.assertTrue(console)
+
+ self.compute.terminate_instance(self.context, instance, [], [])
+
+ def test_invalid_rdp_console_type(self):
+ # Raise useful error if console type is an unrecognised string
+ self.flags(vnc_enabled=False)
+ self.flags(enabled=True, group='rdp')
+
+ instance = self._create_fake_instance_obj()
+ self.compute.run_instance(self.context,
+ jsonutils.to_primitive(instance), {}, {}, [], None,
+ None, True, None, False)
+
+ self.assertRaises(messaging.ExpectedException,
+ self.compute.get_rdp_console,
+ self.context, 'invalid', instance=instance)
+
+ self.compute = utils.ExceptionHelper(self.compute)
+
+ self.assertRaises(exception.ConsoleTypeInvalid,
+ self.compute.get_rdp_console,
+ self.context, 'invalid', instance=instance)
+
+ self.compute.terminate_instance(self.context, instance, [], [])
+
+ def test_missing_rdp_console_type(self):
+ # Raise useful error is console type is None
+ self.flags(vnc_enabled=False)
+ self.flags(enabled=True, group='rdp')
+
+ instance = self._create_fake_instance_obj()
+ self.compute.run_instance(self.context,
+ jsonutils.to_primitive(instance), {}, {}, [], None,
+ None, True, None, False)
+
+ self.assertRaises(messaging.ExpectedException,
+ self.compute.get_rdp_console,
+ self.context, None, instance=instance)
+
+ self.compute = utils.ExceptionHelper(self.compute)
+
+ self.assertRaises(exception.ConsoleTypeInvalid,
+ self.compute.get_rdp_console,
+ self.context, None, instance=instance)
+
+ self.compute.terminate_instance(self.context, instance, [], [])
+
def test_vnc_console_instance_not_ready(self):
self.flags(vnc_enabled=True)
self.flags(enabled=False, group='spice')
@@ -2938,6 +3013,24 @@ class ComputeTestCase(BaseTestCase):
self.compute.get_spice_console, self.context, 'spice-html5',
instance=instance)
+ def test_rdp_console_instance_not_ready(self):
+ self.flags(vnc_enabled=False)
+ self.flags(enabled=True, group='rdp')
+ instance = self._create_fake_instance_obj(
+ params={'vm_state': vm_states.BUILDING})
+
+ def fake_driver_get_console(*args, **kwargs):
+ raise exception.InstanceNotFound(instance_id=instance['uuid'])
+
+ self.stubs.Set(self.compute.driver, "get_rdp_console",
+ fake_driver_get_console)
+
+ self.compute = utils.ExceptionHelper(self.compute)
+
+ self.assertRaises(exception.InstanceNotReady,
+ self.compute.get_rdp_console, self.context, 'rdp-html5',
+ instance=instance)
+
def test_diagnostics(self):
# Make sure we can get diagnostics for an instance.
expected_diagnostic = {'cpu0_time': 17300000000,
@@ -8039,6 +8132,47 @@ class ComputeAPITestCase(BaseTestCase):
db.instance_destroy(self.context, instance['uuid'])
+ def test_rdp_console(self):
+ # Make sure we can a rdp console for an instance.
+
+ fake_instance = {'uuid': 'fake_uuid',
+ 'host': 'fake_compute_host'}
+ fake_console_type = "rdp-html5"
+ fake_connect_info = {'token': 'fake_token',
+ 'console_type': fake_console_type,
+ 'host': 'fake_console_host',
+ 'port': 'fake_console_port',
+ 'internal_access_path': 'fake_access_path',
+ 'instance_uuid': fake_instance['uuid'],
+ 'access_url': 'fake_console_url'}
+
+ rpcapi = compute_rpcapi.ComputeAPI
+ self.mox.StubOutWithMock(rpcapi, 'get_rdp_console')
+ rpcapi.get_rdp_console(
+ self.context, instance=fake_instance,
+ console_type=fake_console_type).AndReturn(fake_connect_info)
+
+ self.mox.StubOutWithMock(self.compute_api.consoleauth_rpcapi,
+ 'authorize_console')
+ self.compute_api.consoleauth_rpcapi.authorize_console(
+ self.context, 'fake_token', fake_console_type, 'fake_console_host',
+ 'fake_console_port', 'fake_access_path', 'fake_uuid')
+
+ self.mox.ReplayAll()
+
+ console = self.compute_api.get_rdp_console(self.context,
+ fake_instance, fake_console_type)
+ self.assertEqual(console, {'url': 'fake_console_url'})
+
+ def test_get_rdp_console_no_host(self):
+ instance = self._create_fake_instance(params={'host': ''})
+
+ self.assertRaises(exception.InstanceNotReady,
+ self.compute_api.get_rdp_console,
+ self.context, instance, 'rdp')
+
+ db.instance_destroy(self.context, instance['uuid'])
+
def test_console_output(self):
fake_instance = {'uuid': 'fake_uuid',
'host': 'fake_compute_host'}
diff --git a/nova/tests/compute/test_rpcapi.py b/nova/tests/compute/test_rpcapi.py
index 0b56e5d06b..e48810eaa8 100644
--- a/nova/tests/compute/test_rpcapi.py
+++ b/nova/tests/compute/test_rpcapi.py
@@ -305,6 +305,11 @@ class ComputeRpcAPITestCase(test.TestCase):
instance=self.fake_instance, console_type='type',
version='2.24')
+ def test_get_rdp_console(self):
+ self._test_compute_api('get_rdp_console', 'call',
+ instance=self.fake_instance, console_type='type',
+ version='3.10')
+
def test_validate_console_port(self):
self._test_compute_api('validate_console_port', 'call',
instance=self.fake_instance, port="5900",
diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py
index 8a18826608..efaef59235 100644
--- a/nova/tests/fake_policy.py
+++ b/nova/tests/fake_policy.py
@@ -46,6 +46,7 @@ policy_data = """
"compute:get_vnc_console": "",
"compute:get_spice_console": "",
+ "compute:get_rdp_console": "",
"compute:get_console_output": "",
"compute:associate_floating_ip": "",
diff --git a/nova/tests/integrated/api_samples/os-consoles/get-rdp-console-post-req.json.tpl b/nova/tests/integrated/api_samples/os-consoles/get-rdp-console-post-req.json.tpl
new file mode 100644
index 0000000000..00956b90e4
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-consoles/get-rdp-console-post-req.json.tpl
@@ -0,0 +1,5 @@
+{
+ "os-getRDPConsole": {
+ "type": "rdp-html5"
+ }
+}
diff --git a/nova/tests/integrated/api_samples/os-consoles/get-rdp-console-post-req.xml.tpl b/nova/tests/integrated/api_samples/os-consoles/get-rdp-console-post-req.xml.tpl
new file mode 100644
index 0000000000..b761d78b67
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-consoles/get-rdp-console-post-req.xml.tpl
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<os-getRDPConsole>
+ <type>rdp-html5</type>
+</os-getRDPConsole>
diff --git a/nova/tests/integrated/api_samples/os-consoles/get-rdp-console-post-resp.json.tpl b/nova/tests/integrated/api_samples/os-consoles/get-rdp-console-post-resp.json.tpl
new file mode 100644
index 0000000000..b8272ca5c0
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-consoles/get-rdp-console-post-resp.json.tpl
@@ -0,0 +1,6 @@
+{
+ "console": {
+ "type": "rdp-html5",
+ "url":"%(url)s"
+ }
+}
diff --git a/nova/tests/integrated/api_samples/os-consoles/get-rdp-console-post-resp.xml.tpl b/nova/tests/integrated/api_samples/os-consoles/get-rdp-console-post-resp.xml.tpl
new file mode 100644
index 0000000000..24fc3cd848
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-consoles/get-rdp-console-post-resp.xml.tpl
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<console>
+ <type>rdp-html5</type>
+ <url>%(url)s</url>
+</console>
diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py
index a81ab20e72..fab6009346 100644
--- a/nova/tests/integrated/test_api_samples.py
+++ b/nova/tests/integrated/test_api_samples.py
@@ -1952,6 +1952,7 @@ class ConsolesSampleJsonTests(ServersSampleBase):
super(ConsolesSampleJsonTests, self).setUp()
self.flags(vnc_enabled=True)
self.flags(enabled=True, group='spice')
+ self.flags(enabled=True, group='rdp')
def test_get_vnc_console(self):
uuid = self._post_server()
@@ -1974,6 +1975,17 @@ class ConsolesSampleJsonTests(ServersSampleBase):
self._verify_response('get-spice-console-post-resp', subs,
response, 200)
+ def test_get_rdp_console(self):
+ uuid = self._post_server()
+ response = self._do_post('servers/%s/action' % uuid,
+ 'get-rdp-console-post-req',
+ {'action': 'os-getRDPConsole'})
+ subs = self._get_regexes()
+ subs["url"] = \
+ "((https?):((//)|(\\\\))+([\w\d:#@%/;$()~_?\+-=\\\.&](#!)?)*)"
+ self._verify_response('get-rdp-console-post-resp', subs,
+ response, 200)
+
class ConsolesSampleXmlTests(ConsolesSampleJsonTests):
ctype = 'xml'
diff --git a/nova/tests/integrated/v3/api_samples/os-remote-consoles/get-rdp-console-post-req.json.tpl b/nova/tests/integrated/v3/api_samples/os-remote-consoles/get-rdp-console-post-req.json.tpl
new file mode 100644
index 0000000000..075d3b28a8
--- /dev/null
+++ b/nova/tests/integrated/v3/api_samples/os-remote-consoles/get-rdp-console-post-req.json.tpl
@@ -0,0 +1,5 @@
+{
+ "get_rdp_console": {
+ "type": "rdp-html5"
+ }
+}
diff --git a/nova/tests/integrated/v3/api_samples/os-remote-consoles/get-rdp-console-post-resp.json.tpl b/nova/tests/integrated/v3/api_samples/os-remote-consoles/get-rdp-console-post-resp.json.tpl
new file mode 100644
index 0000000000..c3955d6ac0
--- /dev/null
+++ b/nova/tests/integrated/v3/api_samples/os-remote-consoles/get-rdp-console-post-resp.json.tpl
@@ -0,0 +1,6 @@
+{
+ "console": {
+ "type": "rdp-html5",
+ "url": "http://127.0.0.1:6083/?token=%(uuid)s"
+ }
+}
diff --git a/nova/tests/integrated/v3/test_remote_consoles.py b/nova/tests/integrated/v3/test_remote_consoles.py
index 3d61b72df9..b7f1cef8c9 100644
--- a/nova/tests/integrated/v3/test_remote_consoles.py
+++ b/nova/tests/integrated/v3/test_remote_consoles.py
@@ -23,6 +23,7 @@ class ConsolesSampleJsonTests(test_servers.ServersSampleBase):
super(ConsolesSampleJsonTests, self).setUp()
self.flags(vnc_enabled=True)
self.flags(enabled=True, group='spice')
+ self.flags(enabled=True, group='rdp')
def test_get_vnc_console(self):
uuid = self._post_server()
@@ -44,3 +45,14 @@ class ConsolesSampleJsonTests(test_servers.ServersSampleBase):
"((https?):((//)|(\\\\))+([\w\d:#@%/;$()~_?\+-=\\\.&](#!)?)*)"
self._verify_response('get-spice-console-post-resp', subs,
response, 200)
+
+ def test_get_rdp_console(self):
+ uuid = self._post_server()
+ response = self._do_post('servers/%s/action' % uuid,
+ 'get-rdp-console-post-req',
+ {'action': 'os-getRDPConsole'})
+ subs = self._get_regexes()
+ subs["url"] = \
+ "((https?):((//)|(\\\\))+([\w\d:#@%/;$()~_?\+-=\\\.&](#!)?)*)"
+ self._verify_response('get-rdp-console-post-resp', subs,
+ response, 200)
diff --git a/nova/tests/virt/test_virt_drivers.py b/nova/tests/virt/test_virt_drivers.py
index 563759879c..8e0a2d7230 100644
--- a/nova/tests/virt/test_virt_drivers.py
+++ b/nova/tests/virt/test_virt_drivers.py
@@ -519,6 +519,14 @@ class _VirtDriverTestCase(_FakeDriverBackendTestCase):
self.assertIn('tlsPort', spice_console)
@catch_notimplementederror
+ def test_get_rdp_console(self):
+ instance_ref, network_info = self._get_running_instance()
+ rdp_console = self.connection.get_rdp_console(self.ctxt, instance_ref)
+ self.assertIn('internal_access_path', rdp_console)
+ self.assertIn('host', rdp_console)
+ self.assertIn('port', rdp_console)
+
+ @catch_notimplementederror
def test_get_console_pool_info(self):
instance_ref, network_info = self._get_running_instance()
console_pool = self.connection.get_console_pool_info(instance_ref)
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index 381c906d70..8299428697 100644
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -365,6 +365,14 @@ class ComputeDriver(object):
"""
raise NotImplementedError()
+ def get_rdp_console(self, context, instance):
+ """Get connection info for a rdp console.
+
+ :param context: security context
+ :param instance: nova.objects.instance.Instance
+ """
+ raise NotImplementedError()
+
def get_diagnostics(self, instance):
"""Return data about VM diagnostics."""
# TODO(Vek): Need to pass context in for access to auth_token
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index 33dd687437..77012c1c36 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -324,6 +324,11 @@ class FakeDriver(driver.ComputeDriver):
'port': 6969,
'tlsPort': 6970}
+ def get_rdp_console(self, context, instance):
+ return {'internal_access_path': 'FAKE',
+ 'host': 'fakerdpconsole.com',
+ 'port': 6969}
+
def get_console_pool_info(self, console_type):
return {'address': '127.0.0.1',
'username': 'fakeuser',