Add console_urls attribute to server resource

This is a composite attribute that returns a dict-like structure
lazily resolving the actual console URL based on key supplied or dict of
all available consoles if no key is provided.
Example of usage to get only 'novnc' console:

  outputs:
    console:
      value: { get_attr: [server, console_urls, novnc ] }

Change-Id: I27c0426f1a9100bb483f04c059fec87bb69b6340
Implements: blueprint vnc-console-attr
Co-Authored-By: Jun Jie Nan <nanjj@cn.ibm.com>
This commit is contained in:
Pavlo Shchelokovskyy 2014-09-26 09:45:33 +03:00
parent 951841adbf
commit 832eddbe27
4 changed files with 130 additions and 3 deletions

View File

@ -425,6 +425,41 @@ echo -e '%s\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers
return dict([(limit.name, limit.value)
for limit in list(limits.absolute)])
def get_console_urls(self, server):
"""Return dict-like structure of server's console urls.
The actual console url is lazily resolved on access.
"""
class ConsoleUrls(collections.Mapping):
def __init__(self, server):
self.console_methods = {
'novnc': server.get_vnc_console,
'xvpvnc': server.get_vnc_console,
'spice-html5': server.get_spice_console,
'rdp-html5': server.get_rdp_console,
}
def __getitem__(self, key):
try:
url = self.console_methods[key](key)['console']['url']
except exceptions.BadRequest as e:
unavailable = 'Unavailable console type'
if unavailable in e.message:
url = e.message
else:
raise
return url
def __len__(self):
return len(self.console_methods)
def __iter__(self):
return (key for key in self.console_methods)
return ConsoleUrls(server)
class ServerConstraint(constraints.BaseCustomConstraint):

View File

@ -87,10 +87,10 @@ class Server(stack_user.StackUser):
ATTRIBUTES = (
NAME_ATTR, SHOW, ADDRESSES, NETWORKS_ATTR, FIRST_ADDRESS,
INSTANCE_NAME, ACCESSIPV4, ACCESSIPV6,
INSTANCE_NAME, ACCESSIPV4, ACCESSIPV6, CONSOLE_URLS,
) = (
'name', 'show', 'addresses', 'networks', 'first_address',
'instance_name', 'accessIPv4', 'accessIPv6',
'instance_name', 'accessIPv4', 'accessIPv6', 'console_urls',
)
properties_schema = {
@ -344,6 +344,14 @@ class Server(stack_user.StackUser):
_('The manually assigned alternative public IPv6 address '
'of the server.')
),
CONSOLE_URLS: attributes.Schema(
_("URLs of server's consoles. "
"To get a specific console type, the requested type "
"can be specified as parameter to the get_attr function, "
"e.g. get_attr: [ <server>, console_urls, novnc ]. "
"Currently supported types are "
"novnc, xvpvnc, spice-html5, rdp-html5.")
),
}
# Server host name limit to 53 characters by due to typical default
@ -685,6 +693,8 @@ class Server(stack_user.StackUser):
return server.accessIPv6
if name == self.SHOW:
return server._info
if name == self.CONSOLE_URLS:
return self.client_plugin('nova').get_console_urls(server)
def add_dependencies(self, deps):
super(Server, self).add_dependencies(deps)

View File

@ -338,3 +338,68 @@ class KeypairConstraintTest(common.HeatTestCase):
self.assertTrue(constraint.validate("", ctx))
self.m.VerifyAll()
class ConsoleUrlsTest(common.HeatTestCase):
scenarios = [
('novnc', dict(console_type='novnc', srv_method='vnc')),
('xvpvnc', dict(console_type='xvpvnc', srv_method='vnc')),
('spice', dict(console_type='spice-html5', srv_method='spice')),
('rdp', dict(console_type='rdp-html5', srv_method='rdp')),
]
def setUp(self):
super(ConsoleUrlsTest, self).setUp()
self.nova_client = mock.Mock()
con = utils.dummy_context()
c = con.clients
self.nova_plugin = c.client_plugin('nova')
self.nova_plugin._client = self.nova_client
self.server = mock.Mock()
self.console_method = getattr(self.server,
'get_%s_console' % self.srv_method)
def test_get_console_url(self):
console = {
'console': {
'type': self.console_type,
'url': '%s_console_url' % self.console_type
}
}
self.console_method.return_value = console
console_url = self.nova_plugin.get_console_urls(self.server)[
self.console_type]
self.assertEqual(console['console']['url'], console_url)
self.console_method.assert_called_once_with(self.console_type)
def test_get_console_url_tolerate_unavailable(self):
msg = 'Unavailable console type %s.' % self.console_type
self.console_method.side_effect = nova_exceptions.BadRequest(
400, message=msg)
console_url = self.nova_plugin.get_console_urls(self.server)[
self.console_type]
self.console_method.assert_called_once_with(self.console_type)
self.assertEqual(msg, console_url)
def test_get_console_urls_reraises_other_400(self):
exc = nova_exceptions.BadRequest
self.console_method.side_effect = exc(400, message="spam")
urls = self.nova_plugin.get_console_urls(self.server)
e = self.assertRaises(exc, urls.__getitem__, self.console_type)
self.assertIn('spam', e.message)
self.console_method.assert_called_once_with(self.console_type)
def test_get_console_urls_reraises_other(self):
exc = Exception
self.console_method.side_effect = exc("spam")
urls = self.nova_plugin.get_console_urls(self.server)
e = self.assertRaises(exc, urls.__getitem__, self.console_type)
self.assertIn('spam', e.args)
self.console_method.assert_called_once_with(self.console_type)

View File

@ -1919,7 +1919,6 @@ class ServersTest(common.HeatTestCase):
resource_defns = tmpl.resource_definitions(stack)
server = servers.Server('server_create_image_err',
resource_defns['WebServer'], stack)
exc = self.assertRaises(exception.StackValidationFailed,
server.validate)
self.assertIn("Value '10a' is not an integer", six.text_type(exc))
@ -2152,6 +2151,24 @@ class ServersTest(common.HeatTestCase):
self.assertEqual(server._resolve_attribute("accessIPv4"), '')
self.m.VerifyAll()
def test_resolve_attribute_console_url(self):
server = self.fc.servers.list()[0]
tmpl, stack = self._setup_test_stack('console_url_stack')
ws = servers.Server(
'WebServer', tmpl.resource_definitions(stack)['WebServer'], stack)
ws.resource_id = server.id
self.m.StubOutWithMock(nova.NovaClientPlugin, '_create')
nova.NovaClientPlugin._create().AndReturn(self.fc)
self.m.StubOutWithMock(self.fc.servers, 'get')
self.fc.servers.get(server.id).AndReturn(server)
self.m.ReplayAll()
console_urls = ws._resolve_attribute('console_urls')
self.assertIsInstance(console_urls, collections.Mapping)
supported_consoles = ('novnc', 'xvpvnc', 'spice-html5', 'rdp-html5')
self.assertEqual(set(supported_consoles), set(console_urls.keys()))
self.m.VerifyAll()
def test_default_instance_user(self):
"""The default value for instance_user in heat.conf is ec2-user."""
return_server = self.fc.servers.list()[1]