Refactor code around Console support
Add a console module to handle the different kinds on remote consoles supported (vnc, rdp, spice ) and refactor the code to use console.get_console. In the future, a matrix based on HyperVisor could be added in case that only one type of console is supported. Ex: HyperV <=> RDP Change-Id: If448b7c9d953765b9b56ee14b39975d951ffd92c Implements: blueprint refactor-console-support Closes-Bug: #1287881
This commit is contained in:
parent
cacd93ea49
commit
ded54e6f5d
horizon
openstack_dashboard/dashboards/project/instances
@ -169,6 +169,11 @@ class AlreadyExists(HorizonException):
|
||||
return _(self.msg) % self.attrs
|
||||
|
||||
|
||||
class NotAvailable(HorizonException):
|
||||
"""Exception to be raised when something is not available."""
|
||||
pass
|
||||
|
||||
|
||||
class WorkflowError(HorizonException):
|
||||
"""Exception to be raised when something goes wrong in a workflow."""
|
||||
pass
|
||||
@ -191,7 +196,7 @@ class HandledException(HorizonException):
|
||||
|
||||
UNAUTHORIZED = tuple(HORIZON_CONFIG['exceptions']['unauthorized'])
|
||||
NOT_FOUND = tuple(HORIZON_CONFIG['exceptions']['not_found'])
|
||||
RECOVERABLE = (AlreadyExists, Conflict,)
|
||||
RECOVERABLE = (AlreadyExists, Conflict, NotAvailable)
|
||||
RECOVERABLE += tuple(HORIZON_CONFIG['exceptions']['recoverable'])
|
||||
|
||||
|
||||
|
62
openstack_dashboard/dashboards/project/instances/console.py
Normal file
62
openstack_dashboard/dashboards/project/instances/console.py
Normal file
@ -0,0 +1,62 @@
|
||||
#
|
||||
# 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 logging
|
||||
|
||||
from django.utils.datastructures import SortedDict
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
|
||||
from novaclient import exceptions as nova_exception
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONSOLES = SortedDict([('VNC', api.nova.server_vnc_console),
|
||||
('SPICE', api.nova.server_spice_console),
|
||||
('RDP', api.nova.server_rdp_console)])
|
||||
|
||||
|
||||
def get_console(request, console_type, instance):
|
||||
"""Get a console url based on console type."""
|
||||
if console_type == 'AUTO':
|
||||
check_consoles = CONSOLES
|
||||
else:
|
||||
try:
|
||||
check_consoles = {'console_type': CONSOLES[console_type]}
|
||||
except KeyError:
|
||||
msg = _('Console type "%s" not supported.') % console_type
|
||||
LOG.error(msg)
|
||||
raise exceptions.NotAvailable(msg)
|
||||
|
||||
for api_call in check_consoles.values():
|
||||
try:
|
||||
console = api_call(request, instance.id)
|
||||
#if not supported don't log it to avoid lot of errors
|
||||
#in case of AUTO
|
||||
except nova_exception.HTTPNotImplemented:
|
||||
continue
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
continue
|
||||
|
||||
console_url = "%s&%s(%s)" % (
|
||||
console.url,
|
||||
urlencode({'title': getattr(instance, "name", "")}),
|
||||
instance.id)
|
||||
return console_url
|
||||
|
||||
raise exceptions.NotAvailable(_('No available console found.'))
|
@ -19,6 +19,7 @@ from horizon import exceptions
|
||||
from horizon import tabs
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.project.instances import console
|
||||
|
||||
|
||||
class OverviewTab(tabs.Tab):
|
||||
@ -58,63 +59,12 @@ class ConsoleTab(tabs.Tab):
|
||||
|
||||
def get_context_data(self, request):
|
||||
instance = self.tab_group.kwargs['instance']
|
||||
# Currently prefer VNC over SPICE, since noVNC has had much more
|
||||
# testing than spice-html5
|
||||
console_type = getattr(settings, 'CONSOLE_TYPE', 'AUTO')
|
||||
if console_type == 'AUTO':
|
||||
try:
|
||||
console = api.nova.server_vnc_console(request, instance.id)
|
||||
console_url = "%s&title=%s(%s)" % (
|
||||
console.url,
|
||||
getattr(instance, "name", ""),
|
||||
instance.id)
|
||||
except Exception:
|
||||
try:
|
||||
console = api.nova.server_spice_console(request,
|
||||
instance.id)
|
||||
console_url = "%s&title=%s(%s)" % (
|
||||
console.url,
|
||||
getattr(instance, "name", ""),
|
||||
instance.id)
|
||||
except Exception:
|
||||
try:
|
||||
console = api.nova.server_rdp_console(request,
|
||||
instance.id)
|
||||
console_url = "%s&title=%s(%s)" % (
|
||||
console.url,
|
||||
getattr(instance, "name", ""),
|
||||
instance.id)
|
||||
except Exception:
|
||||
console_url = None
|
||||
elif console_type == 'VNC':
|
||||
try:
|
||||
console = api.nova.server_vnc_console(request, instance.id)
|
||||
console_url = "%s&title=%s(%s)" % (
|
||||
console.url,
|
||||
getattr(instance, "name", ""),
|
||||
instance.id)
|
||||
except Exception:
|
||||
console_url = None
|
||||
elif console_type == 'SPICE':
|
||||
try:
|
||||
console = api.nova.server_spice_console(request, instance.id)
|
||||
console_url = "%s&title=%s(%s)" % (
|
||||
console.url,
|
||||
getattr(instance, "name", ""),
|
||||
instance.id)
|
||||
except Exception:
|
||||
console_url = None
|
||||
elif console_type == 'RDP':
|
||||
try:
|
||||
console = api.nova.server_rdp_console(request, instance.id)
|
||||
console_url = "%s&title=%s(%s)" % (
|
||||
console.url,
|
||||
getattr(instance, "name", ""),
|
||||
instance.id)
|
||||
except Exception:
|
||||
console_url = None
|
||||
else:
|
||||
console_url = None
|
||||
console_url = None
|
||||
try:
|
||||
console_url = console.get_console(request, console_type, instance)
|
||||
except exceptions.NotAvailable:
|
||||
pass
|
||||
|
||||
return {'console_url': console_url, 'instance_id': instance.id}
|
||||
|
||||
|
@ -29,16 +29,18 @@ from django.utils.http import urlencode
|
||||
from mox import IgnoreArg # noqa
|
||||
from mox import IsA # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon.workflows import views
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api import cinder
|
||||
from openstack_dashboard.test import helpers as test
|
||||
from openstack_dashboard.usage import quotas
|
||||
|
||||
from openstack_dashboard.dashboards.project.instances import console
|
||||
from openstack_dashboard.dashboards.project.instances import tables
|
||||
from openstack_dashboard.dashboards.project.instances import tabs
|
||||
from openstack_dashboard.dashboards.project.instances import workflows
|
||||
from openstack_dashboard.test import helpers as test
|
||||
from openstack_dashboard.usage import quotas
|
||||
|
||||
|
||||
INDEX_URL = reverse('horizon:project:instances:index')
|
||||
SEC_GROUP_ROLE_PREFIX = \
|
||||
@ -809,30 +811,35 @@ class InstanceTests(test.TestCase):
|
||||
def test_instance_vnc(self):
|
||||
server = self.servers.first()
|
||||
CONSOLE_OUTPUT = '/vncserver'
|
||||
CONSOLE_TITLE = '&title=%s(%s)' % (server.name, server.id)
|
||||
CONSOLE_URL = CONSOLE_OUTPUT + CONSOLE_TITLE
|
||||
|
||||
console_mock = self.mox.CreateMock(api.nova.VNCConsole)
|
||||
console_mock.url = CONSOLE_OUTPUT
|
||||
|
||||
self.mox.StubOutWithMock(api.nova, 'server_vnc_console')
|
||||
self.mox.StubOutWithMock(api.nova, 'server_get')
|
||||
self.mox.StubOutWithMock(console, 'get_console')
|
||||
api.nova.server_get(IsA(http.HttpRequest), server.id) \
|
||||
.AndReturn(server)
|
||||
api.nova.server_vnc_console(IgnoreArg(), server.id) \
|
||||
.AndReturn(console_mock)
|
||||
console.get_console(IgnoreArg(), 'VNC', server) \
|
||||
.AndReturn(CONSOLE_URL)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:project:instances:vnc',
|
||||
args=[server.id])
|
||||
res = self.client.get(url)
|
||||
redirect = CONSOLE_OUTPUT + '&title=%s(1)' % server.name
|
||||
redirect = CONSOLE_URL
|
||||
self.assertRedirectsNoFollow(res, redirect)
|
||||
|
||||
@test.create_stubs({api.nova: ('server_vnc_console',)})
|
||||
def test_instance_vnc_exception(self):
|
||||
def test_instance_vnc_error(self):
|
||||
server = self.servers.first()
|
||||
|
||||
api.nova.server_vnc_console(IsA(http.HttpRequest), server.id) \
|
||||
.AndRaise(self.exceptions.nova)
|
||||
self.mox.StubOutWithMock(api.nova, 'server_get')
|
||||
self.mox.StubOutWithMock(console, 'get_console')
|
||||
api.nova.server_get(IsA(http.HttpRequest), server.id) \
|
||||
.AndReturn(server)
|
||||
console.get_console(IgnoreArg(), 'VNC', server) \
|
||||
.AndRaise(exceptions.NotAvailable('console'))
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
@ -845,30 +852,35 @@ class InstanceTests(test.TestCase):
|
||||
def test_instance_spice(self):
|
||||
server = self.servers.first()
|
||||
CONSOLE_OUTPUT = '/spiceserver'
|
||||
CONSOLE_TITLE = '&title=%s(%s)' % (server.name, server.id)
|
||||
CONSOLE_URL = CONSOLE_OUTPUT + CONSOLE_TITLE
|
||||
|
||||
console_mock = self.mox.CreateMock(api.nova.SPICEConsole)
|
||||
console_mock.url = CONSOLE_OUTPUT
|
||||
|
||||
self.mox.StubOutWithMock(api.nova, 'server_spice_console')
|
||||
self.mox.StubOutWithMock(console, 'get_console')
|
||||
self.mox.StubOutWithMock(api.nova, 'server_get')
|
||||
api.nova.server_get(IsA(http.HttpRequest), server.id) \
|
||||
.AndReturn(server)
|
||||
api.nova.server_spice_console(IgnoreArg(), server.id) \
|
||||
.AndReturn(console_mock)
|
||||
console.get_console(IgnoreArg(), 'SPICE', server) \
|
||||
.AndReturn(CONSOLE_URL)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:project:instances:spice',
|
||||
args=[server.id])
|
||||
res = self.client.get(url)
|
||||
redirect = CONSOLE_OUTPUT + '&title=%s(1)' % server.name
|
||||
redirect = CONSOLE_URL
|
||||
self.assertRedirectsNoFollow(res, redirect)
|
||||
|
||||
@test.create_stubs({api.nova: ('server_spice_console',)})
|
||||
def test_instance_spice_exception(self):
|
||||
server = self.servers.first()
|
||||
|
||||
api.nova.server_spice_console(IsA(http.HttpRequest), server.id) \
|
||||
.AndRaise(self.exceptions.nova)
|
||||
self.mox.StubOutWithMock(console, 'get_console')
|
||||
self.mox.StubOutWithMock(api.nova, 'server_get')
|
||||
api.nova.server_get(IsA(http.HttpRequest), server.id) \
|
||||
.AndReturn(server)
|
||||
console.get_console(IgnoreArg(), 'SPICE', server) \
|
||||
.AndRaise(exceptions.NotAvailable('console'))
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
@ -881,30 +893,36 @@ class InstanceTests(test.TestCase):
|
||||
def test_instance_rdp(self):
|
||||
server = self.servers.first()
|
||||
CONSOLE_OUTPUT = '/rdpserver'
|
||||
CONSOLE_TITLE = '&title=%s(%s)' % (server.name, server.id)
|
||||
CONSOLE_URL = CONSOLE_OUTPUT + CONSOLE_TITLE
|
||||
|
||||
console_mock = self.mox.CreateMock(api.nova.RDPConsole)
|
||||
console_mock.url = CONSOLE_OUTPUT
|
||||
|
||||
self.mox.StubOutWithMock(api.nova, 'server_rdp_console')
|
||||
self.mox.StubOutWithMock(console, 'get_console')
|
||||
self.mox.StubOutWithMock(api.nova, 'server_get')
|
||||
api.nova.server_get(IsA(http.HttpRequest), server.id) \
|
||||
.AndReturn(server)
|
||||
api.nova.server_rdp_console(IgnoreArg(), server.id) \
|
||||
.AndReturn(console_mock)
|
||||
console.get_console(IgnoreArg(), 'RDP', server) \
|
||||
.AndReturn(CONSOLE_URL)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:project:instances:rdp',
|
||||
args=[server.id])
|
||||
res = self.client.get(url)
|
||||
redirect = CONSOLE_OUTPUT + '&title=%s(1)' % server.name
|
||||
redirect = CONSOLE_URL
|
||||
self.assertRedirectsNoFollow(res, redirect)
|
||||
|
||||
@test.create_stubs({api.nova: ('server_rdp_console',)})
|
||||
def test_instance_rdp_exception(self):
|
||||
server = self.servers.first()
|
||||
|
||||
api.nova.server_rdp_console(IsA(http.HttpRequest), server.id) \
|
||||
.AndRaise(self.exceptions.nova)
|
||||
self.mox.StubOutWithMock(console, 'get_console')
|
||||
self.mox.StubOutWithMock(api.nova, 'server_get')
|
||||
api.nova.server_get(IsA(http.HttpRequest), server.id) \
|
||||
.AndReturn(server)
|
||||
console.get_console(IgnoreArg(), 'RDP', server) \
|
||||
.AndRaise(exceptions.NotAvailable('console'))
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
@ -2913,3 +2931,92 @@ class InstanceAjaxTests(test.TestCase):
|
||||
# a different availability zone.', u'']]
|
||||
self.assertEqual(messages[0][0], 'error')
|
||||
self.assertTrue(messages[0][1].startswith('Failed'))
|
||||
|
||||
|
||||
class ConsoleManagerTests(test.TestCase):
|
||||
|
||||
def setup_consoles(self):
|
||||
#need to refresh with mocks or will fail since mox do not detect
|
||||
#the api_call() as mocked
|
||||
console.CONSOLES = SortedDict([
|
||||
('VNC', api.nova.server_vnc_console),
|
||||
('SPICE', api.nova.server_spice_console),
|
||||
('RDP', api.nova.server_rdp_console)])
|
||||
|
||||
def test_get_console_vnc(self):
|
||||
server = self.servers.first()
|
||||
console_mock = self.mox.CreateMock(api.nova.VNCConsole)
|
||||
console_mock.url = '/VNC'
|
||||
|
||||
self.mox.StubOutWithMock(api.nova, 'server_vnc_console')
|
||||
api.nova.server_vnc_console(IgnoreArg(), server.id) \
|
||||
.AndReturn(console_mock)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
self.setup_consoles()
|
||||
|
||||
url = '/VNC&title=%s(%s)' % (server.name, server.id)
|
||||
data = console.get_console(self.request, 'VNC', server)
|
||||
self.assertEqual(data, url)
|
||||
|
||||
def test_get_console_spice(self):
|
||||
server = self.servers.first()
|
||||
console_mock = self.mox.CreateMock(api.nova.SPICEConsole)
|
||||
console_mock.url = '/SPICE'
|
||||
|
||||
self.mox.StubOutWithMock(api.nova, 'server_spice_console')
|
||||
api.nova.server_spice_console(IgnoreArg(), server.id) \
|
||||
.AndReturn(console_mock)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
self.setup_consoles()
|
||||
|
||||
url = '/SPICE&title=%s(%s)' % (server.name, server.id)
|
||||
data = console.get_console(self.request, 'SPICE',
|
||||
server)
|
||||
self.assertEqual(data, url)
|
||||
|
||||
def test_get_console_rdp(self):
|
||||
server = self.servers.first()
|
||||
console_mock = self.mox.CreateMock(api.nova.RDPConsole)
|
||||
console_mock.url = '/RDP'
|
||||
|
||||
self.mox.StubOutWithMock(api.nova, 'server_rdp_console')
|
||||
api.nova.server_rdp_console(IgnoreArg(), server.id) \
|
||||
.AndReturn(console_mock)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
self.setup_consoles()
|
||||
|
||||
url = '/RDP&title=%s(%s)' % (server.name, server.id)
|
||||
data = console.get_console(self.request, 'RDP', server)
|
||||
self.assertEqual(data, url)
|
||||
|
||||
def test_get_console_auto_iterate_available(self):
|
||||
server = self.servers.first()
|
||||
|
||||
console_mock = self.mox.CreateMock(api.nova.RDPConsole)
|
||||
console_mock.url = '/RDP'
|
||||
|
||||
self.mox.StubOutWithMock(api.nova, 'server_vnc_console')
|
||||
api.nova.server_vnc_console(IgnoreArg(), server.id) \
|
||||
.AndRaise(self.exceptions.nova)
|
||||
|
||||
self.mox.StubOutWithMock(api.nova, 'server_spice_console')
|
||||
api.nova.server_spice_console(IgnoreArg(), server.id) \
|
||||
.AndRaise(self.exceptions.nova)
|
||||
|
||||
self.mox.StubOutWithMock(api.nova, 'server_rdp_console')
|
||||
api.nova.server_rdp_console(IgnoreArg(), server.id) \
|
||||
.AndReturn(console_mock)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
self.setup_consoles()
|
||||
|
||||
url = '/RDP&title=%s(%s)' % (server.name, server.id)
|
||||
data = console.get_console(self.request, 'AUTO', server)
|
||||
self.assertEqual(data, url)
|
||||
|
||||
def test_invalid_console_type_raise_value_error(self):
|
||||
self.assertRaises(exceptions.NotAvailable,
|
||||
console.get_console, None, 'FAKE', None)
|
||||
|
@ -34,6 +34,9 @@ from horizon.utils import memoized
|
||||
from horizon import workflows
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
from openstack_dashboard.dashboards.project.instances \
|
||||
import console as project_console
|
||||
from openstack_dashboard.dashboards.project.instances \
|
||||
import forms as project_forms
|
||||
from openstack_dashboard.dashboards.project.instances \
|
||||
@ -149,10 +152,9 @@ def console(request, instance_id):
|
||||
|
||||
def vnc(request, instance_id):
|
||||
try:
|
||||
console = api.nova.server_vnc_console(request, instance_id)
|
||||
instance = api.nova.server_get(request, instance_id)
|
||||
return shortcuts.redirect(console.url +
|
||||
("&title=%s(%s)" % (instance.name, instance_id)))
|
||||
console_url = project_console.get_console(request, 'VNC', instance)
|
||||
return shortcuts.redirect(console_url)
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:instances:index")
|
||||
msg = _('Unable to get VNC console for instance "%s".') % instance_id
|
||||
@ -161,10 +163,9 @@ def vnc(request, instance_id):
|
||||
|
||||
def spice(request, instance_id):
|
||||
try:
|
||||
console = api.nova.server_spice_console(request, instance_id)
|
||||
instance = api.nova.server_get(request, instance_id)
|
||||
return shortcuts.redirect(console.url +
|
||||
("&title=%s(%s)" % (instance.name, instance_id)))
|
||||
console_url = project_console.get_console(request, 'SPICE', instance)
|
||||
return shortcuts.redirect(console_url)
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:instances:index")
|
||||
msg = _('Unable to get SPICE console for instance "%s".') % instance_id
|
||||
@ -173,10 +174,9 @@ def spice(request, instance_id):
|
||||
|
||||
def rdp(request, instance_id):
|
||||
try:
|
||||
console = api.nova.server_rdp_console(request, instance_id)
|
||||
instance = api.nova.server_get(request, instance_id)
|
||||
return shortcuts.redirect(console.url +
|
||||
("&title=%s(%s)" % (instance.name, instance_id)))
|
||||
console_url = project_console.get_console(request, 'RDP', instance)
|
||||
return shortcuts.redirect(console_url)
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:instances:index")
|
||||
msg = _('Unable to get RDP console for instance "%s".') % instance_id
|
||||
|
Loading…
x
Reference in New Issue
Block a user