Adds get_console_connect_info API

Implements: blueprint hyper-v-rdp-console

Currently graphical console access to Nova instances is limited to
clients which are part of Nova itself (novnc, xvpvnc, spice-html5).
The mentioned clients verify the validity of a console access token
with the following private API:
nova.consoleauth.rpcapi.ConsoleAuthAPI.check_token

The usage of a private API precludes the possibility of employing
external graphical console clients, including FreeRDP-WebConnect, used
to connect to Hyper-V instances via RDP.

This change adds a public API method that wraps the aforementioned
check_token private API. This allows external clients to obtain the
necessary protocol connection information by providing a token
previously obtained with calls to get_vnc_console, get_spice_console
or get_rdp_console.

Includes V2 and V3 API implementations.

Change-Id: Idd1e4f57b16bd1488f3b72bb064cef51321a7c79
This commit is contained in:
Alessandro Pilotti 2013-08-24 21:39:25 +03:00
parent efc49f656c
commit 630a349bc3
37 changed files with 697 additions and 0 deletions

View File

@ -639,6 +639,14 @@
"name": "ExtendedServicesDelete",
"namespace": "http://docs.openstack.org/compute/ext/extended_services_delete/api/v2",
"updated": "2013-12-10T00:00:00+00:00"
},
{
"alias": "os-console-auth-tokens",
"description": "Console token authentication support.",
"links": [],
"name": "ConsoleAuthTokens",
"namespace": "http://docs.openstack.org/compute/ext/consoles-auth-tokens/api/v2",
"updated": "2013-08-13T00:00:00+00:00"
}
]
}

View File

@ -260,4 +260,7 @@
<extension alias="os-extended-services-delete" updated="2013-12-10T00:00:00" namespace="http://docs.openstack.org/compute/ext/extended_services_delete/api/v2" name="ExtendedServicesDelete">
<description>Services deletion support.</description>
</extension>
<extension alias="os-console-auth-tokens" updated="2013-08-13T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/consoles-auth-tokens/api/v2" name="ConsoleAuthTokens">
<description>Console token authentication support.</description>
</extension>
</extensions>

View File

@ -0,0 +1,8 @@
{
"console": {
"instance_uuid": "b48316c5-71e8-45e4-9884-6c78055b9b13",
"host": "localhost",
"port": 5900,
"internal_access_path": "51af38c3-555e-4884-a314-6c8cdde37444"
}
}

View File

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<console>
<instance_uuid>b48316c5-71e8-45e4-9884-6c78055b9b13</instance_uuid>
<host>localhost</host>
<port>5900</port>
<internal_access_path>51af38c3-555e-4884-a314-6c8cdde37444</internal_access_path>
</console>

View File

@ -0,0 +1,5 @@
{
"os-getRDPConsole": {
"type": "rdp-html5"
}
}

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<os-getRDPConsole type="rdp-html5" />

View File

@ -0,0 +1,16 @@
{
"server" : {
"name" : "new-server-test",
"imageRef" : "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b",
"flavorRef" : "http://openstack.example.com/openstack/flavors/1",
"metadata" : {
"My Server Name" : "Apache1"
},
"personality" : [
{
"path" : "/etc/banner.txt",
"contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
}
]
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<server xmlns="http://docs.openstack.org/compute/api/v1.1" imageRef="http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b" flavorRef="http://openstack.example.com/openstack/flavors/1" name="new-server-test">
<metadata>
<meta key="My Server Name">Apache1</meta>
</metadata>
<personality>
<file path="/etc/banner.txt">
ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp
dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k
IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs
c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g
QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo
ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv
dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy
c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6
b25zLiINCg0KLVJpY2hhcmQgQmFjaA==
</file>
</personality>
</server>

View File

@ -0,0 +1,16 @@
{
"server": {
"adminPass": "MVk5HPrazHcG",
"id": "5bbcc3c4-1da2-4437-a48a-66f15b1b13f9",
"links": [
{
"href": "http://openstack.example.com/v2/openstack/servers/5bbcc3c4-1da2-4437-a48a-66f15b1b13f9",
"rel": "self"
},
{
"href": "http://openstack.example.com/openstack/servers/5bbcc3c4-1da2-4437-a48a-66f15b1b13f9",
"rel": "bookmark"
}
]
}
}

View File

@ -0,0 +1,6 @@
<?xml version='1.0' encoding='UTF-8'?>
<server xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" id="5bbcc3c4-1da2-4437-a48a-66f15b1b13f9" adminPass="MVk5HPrazHcG">
<metadata/>
<atom:link href="http://openstack.example.com/v2/openstack/servers/5bbcc3c4-1da2-4437-a48a-66f15b1b13f9" rel="self"/>
<atom:link href="http://openstack.example.com/openstack/servers/5bbcc3c4-1da2-4437-a48a-66f15b1b13f9" rel="bookmark"/>
</server>

View File

@ -0,0 +1,8 @@
{
"console": {
"instance_uuid": "b48316c5-71e8-45e4-9884-6c78055b9b13",
"host": "localhost",
"port": 5900,
"internal_access_path": "51af38c3-555e-4884-a314-6c8cdde37444"
}
}

View File

@ -0,0 +1,5 @@
{
"get_rdp_console": {
"type": "rdp-html5"
}
}

View File

@ -0,0 +1,10 @@
{
"server" : {
"name" : "new-server-test",
"image_ref" : "http://glance.openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b",
"flavor_ref" : "http://openstack.example.com/flavors/1",
"metadata" : {
"My Server Name" : "Apache1"
}
}
}

View File

@ -0,0 +1,16 @@
{
"server": {
"admin_password": "Kwg5tff6KiUU",
"id": "8619225c-67c8-424f-9b46-cec5bad137a2",
"links": [
{
"href": "http://openstack.example.com/v3/servers/8619225c-67c8-424f-9b46-cec5bad137a2",
"rel": "self"
},
{
"href": "http://openstack.example.com/servers/8619225c-67c8-424f-9b46-cec5bad137a2",
"rel": "bookmark"
}
]
}
}

View File

@ -256,6 +256,8 @@
"compute_extension:v3:os-migrations:discoverable": "",
"compute_extension:os-assisted-volume-snapshots:create": "rule:admin_api",
"compute_extension:os-assisted-volume-snapshots:delete": "rule:admin_api",
"compute_extension:console_auth_tokens": "rule:admin_api",
"compute_extension:v3:os-console-auth-tokens": "rule:admin_api",
"volume:create": "",

View File

@ -0,0 +1,68 @@
# Copyright 2013 Cloudbase Solutions Srl
# 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 webob
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova.consoleauth import rpcapi as consoleauth_rpcapi
from nova.openstack.common.gettextutils import _
authorize = extensions.extension_authorizer('compute', 'console_auth_tokens')
class ConsoleAuthTokensController(wsgi.Controller):
def __init__(self, *args, **kwargs):
self._consoleauth_rpcapi = consoleauth_rpcapi.ConsoleAuthAPI()
super(ConsoleAuthTokensController, self).__init__(*args, **kwargs)
def show(self, req, id):
"""Checks a console auth token and returns the related connect info."""
context = req.environ['nova.context']
authorize(context)
token = id
connect_info = self._consoleauth_rpcapi.check_token(context, token)
if not connect_info:
raise webob.exc.HTTPNotFound(explanation=_("Token not found"))
console_type = connect_info.get('console_type')
# This is currently required only for RDP consoles
if console_type != "rdp-html5":
raise webob.exc.HTTPUnauthorized(
explanation=_("The requested console type details are not "
"accessible"))
return {'console':
dict([(i, connect_info[i])
for i in ['instance_uuid', 'host', 'port',
'internal_access_path']
if i in connect_info])}
class Console_auth_tokens(extensions.ExtensionDescriptor):
"""Console token authentication support."""
name = "ConsoleAuthTokens"
alias = "os-console-auth-tokens"
namespace = ("http://docs.openstack.org/compute/ext/"
"consoles-auth-tokens/api/v2")
updated = "2013-08-13T00:00:00+00:00"
def get_resources(self):
controller = ConsoleAuthTokensController()
ext = extensions.ResourceExtension('os-console-auth-tokens',
controller)
return [ext]

View File

@ -0,0 +1,75 @@
# Copyright 2013 Cloudbase Solutions Srl
# 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 webob
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova.consoleauth import rpcapi as consoleauth_rpcapi
from nova.openstack.common.gettextutils import _
ALIAS = "os-console-auth-tokens"
authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS)
class ConsoleAuthTokensController(wsgi.Controller):
def __init__(self, *args, **kwargs):
self._consoleauth_rpcapi = consoleauth_rpcapi.ConsoleAuthAPI()
super(ConsoleAuthTokensController, self).__init__(*args, **kwargs)
@extensions.expected_errors((400, 401, 404))
def show(self, req, id):
"""Checks a console auth token and returns the related connect info."""
context = req.environ['nova.context']
authorize(context)
token = id
if not token:
msg = _("token not provided")
raise webob.exc.HTTPBadRequest(explanation=msg)
connect_info = self._consoleauth_rpcapi.check_token(context, token)
if not connect_info:
raise webob.exc.HTTPNotFound(explanation=_("Token not found"))
console_type = connect_info.get('console_type')
# This is currently required only for RDP consoles
if console_type != "rdp-html5":
raise webob.exc.HTTPUnauthorized(
explanation=_("The requested console type details are not "
"accessible"))
return {'console':
dict([(i, connect_info[i])
for i in ['instance_uuid', 'host', 'port',
'internal_access_path']
if i in connect_info])}
class ConsoleAuthTokens(extensions.V3APIExtensionBase):
"""Console token authentication support."""
name = "ConsoleAuthTokens"
alias = ALIAS
namespace = "http://docs.openstack.org/compute/ext/%s/api/v3" % ALIAS
version = 1
def get_resources(self):
controller = ConsoleAuthTokensController()
ext = extensions.ResourceExtension('os-console-auth-tokens',
controller)
return [ext]
def get_controller_extensions(self):
return []

View File

@ -0,0 +1,104 @@
# Copyright 2013 Cloudbase Solutions Srl
# 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 webob
from oslo.config import cfg
from nova.consoleauth import rpcapi as consoleauth_rpcapi
from nova import context
from nova.openstack.common import jsonutils
from nova import test
from nova.tests.api.openstack import fakes
CONF = cfg.CONF
CONF.import_opt('osapi_compute_ext_list', 'nova.api.openstack.compute.contrib')
_FAKE_CONNECT_INFO = {'instance_uuid': 'fake_instance_uuid',
'host': 'fake_host',
'port': 'fake_port',
'internal_access_path': 'fake_access_path',
'console_type': 'rdp-html5'}
def _fake_check_token(self, context, token):
return _FAKE_CONNECT_INFO
def _fake_check_token_not_found(self, context, token):
return None
def _fake_check_token_unauthorized(self, context, token):
connect_info = _FAKE_CONNECT_INFO
connect_info['console_type'] = 'unauthorized_console_type'
return connect_info
class ConsoleAuthTokensExtensionTest(test.TestCase):
_FAKE_URL = '/v2/fake/os-console-auth-tokens/1'
_EXPECTED_OUTPUT = {'console': {'instance_uuid': 'fake_instance_uuid',
'host': 'fake_host',
'port': 'fake_port',
'internal_access_path':
'fake_access_path'}}
def setUp(self):
super(ConsoleAuthTokensExtensionTest, self).setUp()
self.stubs.Set(consoleauth_rpcapi.ConsoleAuthAPI, 'check_token',
_fake_check_token)
self.flags(
osapi_compute_extension=[
'nova.api.openstack.compute.contrib.select_extensions'],
osapi_compute_ext_list=['Console_auth_tokens'])
ctxt = self._get_admin_context()
self.app = fakes.wsgi_app(init_only=('os-console-auth-tokens',),
fake_auth_context=ctxt)
def _get_admin_context(self):
ctxt = context.get_admin_context()
ctxt.user_id = 'fake'
ctxt.project_id = 'fake'
return ctxt
def _create_request(self):
req = webob.Request.blank(self._FAKE_URL)
req.method = "GET"
req.headers["content-type"] = "application/json"
return req
def test_get_console_connect_info(self):
req = self._create_request()
res = req.get_response(self.app)
self.assertEqual(200, res.status_int)
output = jsonutils.loads(res.body)
self.assertEqual(self._EXPECTED_OUTPUT, output)
def test_get_console_connect_info_token_not_found(self):
self.stubs.Set(consoleauth_rpcapi.ConsoleAuthAPI, 'check_token',
_fake_check_token_not_found)
req = self._create_request()
res = req.get_response(self.app)
self.assertEqual(404, res.status_int)
def test_get_console_connect_info_unauthorized_console_type(self):
self.stubs.Set(consoleauth_rpcapi.ConsoleAuthAPI, 'check_token',
_fake_check_token_unauthorized)
req = self._create_request()
res = req.get_response(self.app)
self.assertEqual(401, res.status_int)

View File

@ -0,0 +1,94 @@
# Copyright 2013 Cloudbase Solutions Srl
# 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.
from nova.consoleauth import rpcapi as consoleauth_rpcapi
from nova import context
from nova.openstack.common import jsonutils
from nova import test
from nova.tests.api.openstack import fakes
_FAKE_CONNECT_INFO = {'instance_uuid': 'fake_instance_uuid',
'host': 'fake_host',
'port': 'fake_port',
'internal_access_path': 'fake_access_path',
'console_type': 'rdp-html5'}
def _fake_check_token(self, context, token):
return _FAKE_CONNECT_INFO
def _fake_check_token_not_found(self, context, token):
return None
def _fake_check_token_unauthorized(self, context, token):
connect_info = _FAKE_CONNECT_INFO
connect_info['console_type'] = 'unauthorized_console_type'
return connect_info
class ConsoleAuthTokensExtensionTest(test.TestCase):
_FAKE_URL = '/v3/os-console-auth-tokens/1'
_EXPECTED_OUTPUT = {'console': {'instance_uuid': 'fake_instance_uuid',
'host': 'fake_host',
'port': 'fake_port',
'internal_access_path':
'fake_access_path'}}
def setUp(self):
super(ConsoleAuthTokensExtensionTest, self).setUp()
self.stubs.Set(consoleauth_rpcapi.ConsoleAuthAPI, 'check_token',
_fake_check_token)
ctxt = self._get_admin_context()
self.app = fakes.wsgi_app_v3(init_only=('os-console-auth-tokens'),
fake_auth_context=ctxt)
def _get_admin_context(self):
ctxt = context.get_admin_context()
ctxt.user_id = 'fake'
ctxt.project_id = 'fake'
return ctxt
def _create_request(self):
req = fakes.HTTPRequestV3.blank(self._FAKE_URL)
req.method = "GET"
req.headers["content-type"] = "application/json"
return req
def test_get_console_connect_info(self):
req = self._create_request()
res = req.get_response(self.app)
self.assertEqual(200, res.status_int)
output = jsonutils.loads(res.body)
self.assertEqual(self._EXPECTED_OUTPUT, output)
def test_get_console_connect_info_token_not_found(self):
self.stubs.Set(consoleauth_rpcapi.ConsoleAuthAPI, 'check_token',
_fake_check_token_not_found)
req = self._create_request()
res = req.get_response(self.app)
self.assertEqual(404, res.status_int)
def test_get_console_connect_info_unauthorized_console_type(self):
self.stubs.Set(consoleauth_rpcapi.ConsoleAuthAPI, 'check_token',
_fake_check_token_unauthorized)
req = self._create_request()
res = req.get_response(self.app)
self.assertEqual(401, res.status_int)

View File

@ -294,6 +294,8 @@ policy_data = """
"compute_extension:v3:os-migrations:index": "is_admin:True",
"compute_extension:os-assisted-volume-snapshots:create": "",
"compute_extension:os-assisted-volume-snapshots:delete": "",
"compute_extension:console_auth_tokens": "is_admin:True",
"compute_extension:v3:os-console-auth-tokens": "is_admin:True",
"volume:create": "",
"volume:get": "",

View File

@ -104,6 +104,14 @@
"namespace": "http://docs.openstack.org/compute/ext/server_usage/api/v1.1",
"updated": "%(timestamp)s"
},
{
"alias": "os-console-auth-tokens",
"description": "%(text)s",
"links": [],
"name": "ConsoleAuthTokens",
"namespace": "http://docs.openstack.org/compute/ext/consoles-auth-tokens/api/v2",
"updated": "%(timestamp)s"
},
{
"alias": "OS-SCH-HNT",
"description": "%(text)s",

View File

@ -239,4 +239,7 @@
<extension alias="os-extended-services-delete" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/extended_services_delete/api/v2" name="ExtendedServicesDelete">
<description>%(text)s</description>
</extension>
<extension alias="os-console-auth-tokens" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/consoles-auth-tokens/api/v2" name="ConsoleAuthTokens">
<description>%(text)s</description>
</extension>
</extensions>

View File

@ -0,0 +1,8 @@
{
"console": {
"instance_uuid": "%(id)s",
"host": "%(host)s",
"port": %(port)s,
"internal_access_path": "%(internal_access_path)s"
}
}

View File

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<console>
<instance_uuid>%(id)s</instance_uuid>
<host>%(host)s</host>
<port>%(port)s</port>
<internal_access_path>%(internal_access_path)s</internal_access_path>
</console>

View File

@ -0,0 +1,5 @@
{
"os-getRDPConsole": {
"type": "rdp-html5"
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<os-getRDPConsole>
<type>rdp-html5</type>
</os-getRDPConsole>

View File

@ -0,0 +1,16 @@
{
"server" : {
"name" : "new-server-test",
"imageRef" : "%(host)s/openstack/images/%(image_id)s",
"flavorRef" : "%(host)s/openstack/flavors/1",
"metadata" : {
"My Server Name" : "Apache1"
},
"personality" : [
{
"path" : "/etc/banner.txt",
"contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
}
]
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<server xmlns="http://docs.openstack.org/compute/api/v1.1" imageRef="%(host)s/openstack/images/%(image_id)s" flavorRef="%(host)s/openstack/flavors/1" name="new-server-test">
<metadata>
<meta key="My Server Name">Apache1</meta>
</metadata>
<personality>
<file path="/etc/banner.txt">
ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp
dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k
IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs
c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g
QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo
ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv
dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy
c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6
b25zLiINCg0KLVJpY2hhcmQgQmFjaA==
</file>
</personality>
</server>

View File

@ -0,0 +1,16 @@
{
"server": {
"adminPass": "%(password)s",
"id": "%(id)s",
"links": [
{
"href": "%(host)s/v2/openstack/servers/%(uuid)s",
"rel": "self"
},
{
"href": "%(host)s/openstack/servers/%(uuid)s",
"rel": "bookmark"
}
]
}
}

View File

@ -0,0 +1,6 @@
<?xml version='1.0' encoding='UTF-8'?>
<server xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" id="%(id)s" adminPass="%(password)s">
<metadata/>
<atom:link href="%(host)s/v2/openstack/servers/%(uuid)s" rel="self"/>
<atom:link href="%(host)s/openstack/servers/%(uuid)s" rel="bookmark"/>
</server>

View File

@ -19,6 +19,7 @@ import datetime
import inspect
import json
import os
import re
import urllib
import uuid as uuid_lib
@ -2037,6 +2038,46 @@ class ConsolesSampleXmlTests(ConsolesSampleJsonTests):
ctype = 'xml'
class ConsoleAuthTokensSampleJsonTests(ServersSampleBase):
extends_name = ("nova.api.openstack.compute.contrib.consoles.Consoles")
extension_name = ("nova.api.openstack.compute.contrib.console_auth_tokens."
"Console_auth_tokens")
def _get_console_url(self, data):
return json.loads(data)["console"]["url"]
def _get_console_token(self, uuid):
response = self._do_post('servers/%s/action' % uuid,
'get-rdp-console-post-req',
{'action': 'os-getRDPConsole'})
url = self._get_console_url(response.read())
return re.match('.+?token=([^&]+)', url).groups()[0]
def test_get_console_connect_info(self):
self.flags(enabled=True, group='rdp')
uuid = self._post_server()
token = self._get_console_token(uuid)
response = self._do_get('os-console-auth-tokens/%s' % token)
subs = self._get_regexes()
subs["uuid"] = uuid
subs["host"] = r"[\w\.\-]+"
subs["port"] = "[0-9]+"
subs["internal_access_path"] = ".*"
self._verify_response('get-console-connect-info-get-resp', subs,
response, 200)
class ConsoleAuthTokensSampleXmlTests(ConsoleAuthTokensSampleJsonTests):
ctype = 'xml'
def _get_console_url(self, data):
return etree.fromstring(data).find('url').text
class DeferredDeleteSampleJsonTests(ServersSampleBase):
extension_name = ("nova.api.openstack.compute.contrib"
".deferred_delete.Deferred_delete")

View File

@ -0,0 +1,8 @@
{
"console": {
"instance_uuid": "%(id)s",
"host": "%(host)s",
"port": %(port)s,
"internal_access_path": "%(internal_access_path)s"
}
}

View File

@ -0,0 +1,5 @@
{
"get_rdp_console": {
"type": "rdp-html5"
}
}

View File

@ -0,0 +1,10 @@
{
"server" : {
"name" : "new-server-test",
"image_ref" : "%(glance_host)s/images/%(image_id)s",
"flavor_ref" : "%(host)s/flavors/1",
"metadata" : {
"My Server Name" : "Apache1"
}
}
}

View File

@ -0,0 +1,16 @@
{
"server": {
"admin_password": "%(password)s",
"id": "%(id)s",
"links": [
{
"href": "%(host)s/v3/servers/%(uuid)s",
"rel": "self"
},
{
"href": "%(host)s/servers/%(uuid)s",
"rel": "bookmark"
}
]
}
}

View File

@ -0,0 +1,50 @@
# Copyright 2013 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.
import json
import re
from nova.tests.integrated.v3 import test_servers
class ConsoleAuthTokensSampleJsonTests(test_servers.ServersSampleBase):
extension_name = "os-console-auth-tokens"
extra_extensions_to_load = ["os-remote-consoles"]
def _get_console_url(self, data):
return json.loads(data)["console"]["url"]
def _get_console_token(self, uuid):
response = self._do_post('servers/%s/action' % uuid,
'get-rdp-console-post-req',
{'action': 'os-getRDPConsole'})
url = self._get_console_url(response.read())
return re.match('.+?token=([^&]+)', url).groups()[0]
def test_get_console_connect_info(self):
self.flags(enabled=True, group='rdp')
uuid = self._post_server()
token = self._get_console_token(uuid)
response = self._do_get('os-console-auth-tokens/%s' % token)
subs = self._get_regexes()
subs["uuid"] = uuid
subs["host"] = r"[\w\.\-]+"
subs["port"] = "[0-9]+"
subs["internal_access_path"] = ".*"
self._verify_response('get-console-connect-info-get-resp', subs,
response, 200)

View File

@ -67,6 +67,7 @@ nova.api.v3.extensions =
cells = nova.api.openstack.compute.plugins.v3.cells:Cells
certificates = nova.api.openstack.compute.plugins.v3.certificates:Certificates
config_drive = nova.api.openstack.compute.plugins.v3.config_drive:ConfigDrive
console_auth_tokens = nova.api.openstack.compute.plugins.v3.console_auth_tokens:ConsoleAuthTokens
console_output = nova.api.openstack.compute.plugins.v3.console_output:ConsoleOutput
consoles = nova.api.openstack.compute.plugins.v3.consoles:Consoles
deferred_delete = nova.api.openstack.compute.plugins.v3.deferred_delete:DeferredDelete