Add support for serial console proxy access
Support access to instances via optionally enabled serial console feature provided in Nova. Seria console access is enabled using a new config flag; this flag plus the required base_url for the nova-serialproxy are also passed over the cloud-compute relation for use in nova-compute units. This is only supported in OpenStack Juno or later, and replaces the standard output to the nova console-log. Change-Id: I3bfcca88bd6147be337e6d770db7348170b914e6
This commit is contained in:
parent
71e74cd0b0
commit
469ef8dced
@ -326,6 +326,13 @@ options:
|
||||
type: string
|
||||
default:
|
||||
description: SSL key to use with certificate specified as console-ssl-cert.
|
||||
enable-serial-console:
|
||||
type: boolean
|
||||
default: false
|
||||
description: |
|
||||
Enable serial console access to instances using websockets (insecure).
|
||||
This is only supported on OpenStack Juno or later, and will disable the
|
||||
normal console-log output for an instance.
|
||||
worker-multiplier:
|
||||
type: float
|
||||
default: 2.0
|
||||
|
@ -357,6 +357,21 @@ class ConsoleSSLContext(context.OSContextGenerator):
|
||||
return ctxt
|
||||
|
||||
|
||||
class SerialConsoleContext(context.OSContextGenerator):
|
||||
interfaces = []
|
||||
|
||||
def __call__(self):
|
||||
ip_addr = resolve_address(endpoint_type=PUBLIC)
|
||||
ip_addr = format_ipv6_addr(ip_addr) or ip_addr
|
||||
|
||||
ctxt = {
|
||||
'enable_serial_console':
|
||||
str(config('enable-serial-console')).lower(),
|
||||
'serial_console_base_url': 'ws://{}:6083/'.format(ip_addr)
|
||||
}
|
||||
return ctxt
|
||||
|
||||
|
||||
class APIRateLimitingContext(context.OSContextGenerator):
|
||||
def __call__(self):
|
||||
ctxt = {}
|
||||
|
@ -112,6 +112,7 @@ from nova_cc_utils import (
|
||||
is_db_initialised,
|
||||
assess_status,
|
||||
update_aws_compat_services,
|
||||
serial_console_settings,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.hahelpers.cluster import (
|
||||
@ -263,23 +264,21 @@ def config_changed():
|
||||
save_script_rc()
|
||||
configure_https()
|
||||
CONFIGS.write_all()
|
||||
if console_attributes('protocol'):
|
||||
if not git_install_requested():
|
||||
status_set('maintenance', 'Configuring guest console access')
|
||||
apt_update()
|
||||
packages = console_attributes('packages') or []
|
||||
filtered = filter_installed_packages(packages)
|
||||
if filtered:
|
||||
apt_install(filtered, fatal=True)
|
||||
|
||||
[compute_joined(rid=rid)
|
||||
for rid in relation_ids('cloud-compute')]
|
||||
# NOTE(jamespage): deal with any changes to the console and serial
|
||||
# console configuration options
|
||||
if not git_install_requested():
|
||||
filtered = filter_installed_packages(determine_packages())
|
||||
if filtered:
|
||||
apt_install(filtered, fatal=True)
|
||||
|
||||
for r_id in relation_ids('identity-service'):
|
||||
identity_joined(rid=r_id)
|
||||
for rid in relation_ids('zeromq-configuration'):
|
||||
zeromq_configuration_relation_joined(rid)
|
||||
[cluster_joined(rid) for rid in relation_ids('cluster')]
|
||||
[compute_joined(rid=rid) for rid in relation_ids('cloud-compute')]
|
||||
|
||||
update_nrpe_config()
|
||||
|
||||
# If the region value has changed, notify the cloud-compute relations
|
||||
@ -590,8 +589,9 @@ def compute_joined(rid=None, remote_restart=False):
|
||||
# (comment from bash vers) XXX Should point to VIP if clustered, or
|
||||
# this may not even be needed.
|
||||
'ec2_host': unit_get('private-address'),
|
||||
'region': config('region')
|
||||
'region': config('region'),
|
||||
}
|
||||
rel_settings.update(serial_console_settings())
|
||||
# update relation setting if we're attempting to restart remote
|
||||
# services
|
||||
if remote_restart:
|
||||
|
@ -250,7 +250,8 @@ BASE_RESOURCE_MAP = OrderedDict([
|
||||
nova_cc_context.ConsoleSSLContext(),
|
||||
nova_cc_context.CloudComputeContext(),
|
||||
context.InternalEndpointContext(),
|
||||
nova_cc_context.NeutronAPIContext()],
|
||||
nova_cc_context.NeutronAPIContext(),
|
||||
nova_cc_context.SerialConsoleContext()],
|
||||
}),
|
||||
(NOVA_API_PASTE, {
|
||||
'services': [s for s in resolve_services() if 'api' in s],
|
||||
@ -297,6 +298,12 @@ CONSOLE_CONFIG = {
|
||||
},
|
||||
}
|
||||
|
||||
SERIAL_CONSOLE = {
|
||||
'packages': ['nova-serialproxy', 'nova-consoleauth',
|
||||
'websockify'],
|
||||
'services': ['nova-serialproxy', 'nova-consoleauth'],
|
||||
}
|
||||
|
||||
|
||||
def resource_map():
|
||||
'''
|
||||
@ -324,6 +331,11 @@ def resource_map():
|
||||
resource_map[NOVA_CONF]['services'] += \
|
||||
console_attributes('services')
|
||||
|
||||
if (config('enable-serial-console') and
|
||||
os_release('nova-common') >= 'juno'):
|
||||
resource_map[NOVA_CONF]['services'] += \
|
||||
SERIAL_CONSOLE['services']
|
||||
|
||||
# also manage any configs that are being updated by subordinates.
|
||||
vmware_ctxt = context.SubordinateConfigContext(interface='nova-vmware',
|
||||
service='nova',
|
||||
@ -399,11 +411,14 @@ def console_attributes(attr, proto=None):
|
||||
|
||||
def determine_packages():
|
||||
# currently all packages match service names
|
||||
packages = [] + BASE_PACKAGES
|
||||
packages = deepcopy(BASE_PACKAGES)
|
||||
for v in resource_map().values():
|
||||
packages.extend(v['services'])
|
||||
if console_attributes('packages'):
|
||||
packages.extend(console_attributes('packages'))
|
||||
if (config('enable-serial-console') and
|
||||
os_release('nova-common') >= 'juno'):
|
||||
packages.extend(SERIAL_CONSOLE['packages'])
|
||||
|
||||
if git_install_requested():
|
||||
packages = list(set(packages))
|
||||
@ -1370,3 +1385,10 @@ def update_aws_compat_services():
|
||||
else:
|
||||
for service_ in AWS_COMPAT_SERVICES:
|
||||
service_resume(service_)
|
||||
|
||||
|
||||
def serial_console_settings():
|
||||
'''Utility wrapper to retrieve serial console settings
|
||||
for use in cloud-compute relation
|
||||
'''
|
||||
return nova_cc_context.SerialConsoleContext()()
|
||||
|
@ -169,3 +169,5 @@ lock_path=/var/lock/nova
|
||||
|
||||
[spice]
|
||||
{% include "parts/spice" %}
|
||||
|
||||
{% include "parts/section-serial-console" %}
|
@ -167,3 +167,5 @@ lock_path=/var/lock/nova
|
||||
|
||||
[spice]
|
||||
{% include "parts/spice" %}
|
||||
|
||||
{% include "parts/section-serial-console" %}
|
@ -161,3 +161,5 @@ lock_path=/var/lock/nova
|
||||
|
||||
[spice]
|
||||
{% include "parts/spice" %}
|
||||
|
||||
{% include "parts/section-serial-console" %}
|
||||
|
3
templates/parts/section-serial-console
Normal file
3
templates/parts/section-serial-console
Normal file
@ -0,0 +1,3 @@
|
||||
[serial_console]
|
||||
enabled = {{ enable_serial_console }}
|
||||
base_url = {{ serial_console_base_url }}
|
@ -311,3 +311,38 @@ class NovaComputeContextTests(CharmTestCase):
|
||||
self.config('cpu-allocation-ratio'))
|
||||
self.assertEqual(ctxt['ram_allocation_ratio'],
|
||||
self.config('ram-allocation-ratio'))
|
||||
|
||||
@mock.patch.object(context, 'format_ipv6_addr')
|
||||
@mock.patch.object(context, 'resolve_address')
|
||||
@mock.patch.object(context, 'config')
|
||||
def test_serial_console_context(self, mock_config,
|
||||
mock_resolve_address,
|
||||
mock_format_ipv6_address):
|
||||
mock_config.side_effect = self.test_config.get
|
||||
mock_format_ipv6_address.return_value = None
|
||||
mock_resolve_address.return_value = '10.10.10.1'
|
||||
ctxt = context.SerialConsoleContext()()
|
||||
self.assertEqual(
|
||||
ctxt,
|
||||
{'serial_console_base_url': 'ws://10.10.10.1:6083/',
|
||||
'enable_serial_console': 'false'}
|
||||
)
|
||||
mock_resolve_address.assert_called_with(endpoint_type=context.PUBLIC)
|
||||
|
||||
@mock.patch.object(context, 'format_ipv6_addr')
|
||||
@mock.patch.object(context, 'resolve_address')
|
||||
@mock.patch.object(context, 'config')
|
||||
def test_serial_console_context_enabled(self, mock_config,
|
||||
mock_resolve_address,
|
||||
mock_format_ipv6_address):
|
||||
mock_config.side_effect = self.test_config.get
|
||||
self.test_config.set('enable-serial-console', True)
|
||||
mock_format_ipv6_address.return_value = None
|
||||
mock_resolve_address.return_value = '10.10.10.1'
|
||||
ctxt = context.SerialConsoleContext()()
|
||||
self.assertEqual(
|
||||
ctxt,
|
||||
{'serial_console_base_url': 'ws://10.10.10.1:6083/',
|
||||
'enable_serial_console': 'true'}
|
||||
)
|
||||
mock_resolve_address.assert_called_with(endpoint_type=context.PUBLIC)
|
||||
|
@ -91,6 +91,7 @@ TO_PATCH = [
|
||||
'status_set',
|
||||
'network_get_primary_address',
|
||||
'update_dns_ha_resource_params',
|
||||
'serial_console_settings',
|
||||
]
|
||||
|
||||
|
||||
@ -164,12 +165,15 @@ class NovaCCHooksTests(CharmTestCase):
|
||||
self.assertTrue(self.disable_services.called)
|
||||
self.cmd_all_services.assert_called_with('stop')
|
||||
|
||||
@patch.object(hooks, 'determine_packages')
|
||||
@patch.object(utils, 'service_resume')
|
||||
@patch.object(utils, 'config')
|
||||
@patch.object(hooks, 'filter_installed_packages')
|
||||
@patch.object(hooks, 'configure_https')
|
||||
def test_config_changed_no_upgrade(self, conf_https, mock_filter_packages,
|
||||
utils_config, mock_service_resume):
|
||||
utils_config, mock_service_resume,
|
||||
mock_determine_packages):
|
||||
mock_determine_packages.return_value = []
|
||||
utils_config.side_effect = self.test_config.get
|
||||
self.test_config.set('console-access-protocol', 'dummy')
|
||||
self.git_install_requested.return_value = False
|
||||
@ -202,6 +206,7 @@ class NovaCCHooksTests(CharmTestCase):
|
||||
self.git_install.assert_called_with(projects_yaml)
|
||||
self.assertFalse(self.do_openstack_upgrade.called)
|
||||
|
||||
@patch.object(hooks, 'determine_packages')
|
||||
@patch.object(utils, 'service_resume')
|
||||
@patch('charmhelpers.contrib.openstack.ip.unit_get')
|
||||
@patch('charmhelpers.contrib.hahelpers.cluster.relation_ids')
|
||||
@ -219,7 +224,9 @@ class NovaCCHooksTests(CharmTestCase):
|
||||
mock_filter_packages, db_joined,
|
||||
utils_config, mock_relids,
|
||||
mock_unit_get,
|
||||
mock_service_resume):
|
||||
mock_service_resume,
|
||||
mock_determine_packages):
|
||||
mock_determine_packages.return_value = []
|
||||
self.git_install_requested.return_value = False
|
||||
self.openstack_upgrade_available.return_value = True
|
||||
self.relation_ids.return_value = ['generic_rid']
|
||||
@ -331,6 +338,10 @@ class NovaCCHooksTests(CharmTestCase):
|
||||
self.is_elected_leader = True
|
||||
self.keystone_ca_cert_b64.return_value = 'foocert64'
|
||||
self.unit_get.return_value = 'nova-cc-host1'
|
||||
self.serial_console_settings.return_value = {
|
||||
'enable_serial_console': 'false',
|
||||
'serial_console_base_url': 'ws://controller:6803',
|
||||
}
|
||||
_canonical_url.return_value = 'http://nova-cc-host1'
|
||||
auth_config.return_value = FAKE_KS_AUTH_CFG
|
||||
hooks.compute_joined()
|
||||
@ -341,7 +352,10 @@ class NovaCCHooksTests(CharmTestCase):
|
||||
region='RegionOne',
|
||||
volume_service='cinder',
|
||||
ec2_host='nova-cc-host1',
|
||||
network_manager='neutron', **FAKE_KS_AUTH_CFG)
|
||||
network_manager='neutron',
|
||||
enable_serial_console='false',
|
||||
serial_console_base_url='ws://controller:6803',
|
||||
**FAKE_KS_AUTH_CFG)
|
||||
|
||||
@patch.object(hooks, 'canonical_url')
|
||||
@patch.object(utils, 'config')
|
||||
@ -362,6 +376,10 @@ class NovaCCHooksTests(CharmTestCase):
|
||||
self.is_elected_leader = True
|
||||
self.keystone_ca_cert_b64.return_value = 'foocert64'
|
||||
self.unit_get.return_value = 'nova-cc-host1'
|
||||
self.serial_console_settings.return_value = {
|
||||
'enable_serial_console': 'false',
|
||||
'serial_console_base_url': 'ws://controller:6803',
|
||||
}
|
||||
_canonical_url.return_value = 'http://nova-cc-host1'
|
||||
auth_config.return_value = FAKE_KS_AUTH_CFG
|
||||
hooks.compute_joined()
|
||||
@ -376,7 +394,10 @@ class NovaCCHooksTests(CharmTestCase):
|
||||
volume_service='cinder',
|
||||
ec2_host='nova-cc-host1',
|
||||
quantum_plugin='bob',
|
||||
network_manager='neutron', **FAKE_KS_AUTH_CFG)
|
||||
network_manager='neutron',
|
||||
enable_serial_console='false',
|
||||
serial_console_base_url='ws://controller:6803',
|
||||
**FAKE_KS_AUTH_CFG)
|
||||
|
||||
@patch.object(hooks, 'canonical_url')
|
||||
@patch.object(hooks, '_auth_config')
|
||||
@ -961,6 +982,7 @@ class NovaCCHooksTests(CharmTestCase):
|
||||
call(**args),
|
||||
])
|
||||
|
||||
@patch.object(hooks, 'determine_packages')
|
||||
@patch.object(utils, 'service_pause')
|
||||
@patch.object(hooks, 'filter_installed_packages')
|
||||
@patch('nova_cc_hooks.configure_https')
|
||||
@ -968,7 +990,9 @@ class NovaCCHooksTests(CharmTestCase):
|
||||
def test_config_changed_single_consoleauth(self, mock_config,
|
||||
mock_configure_https,
|
||||
mock_filter_packages,
|
||||
mock_service_pause):
|
||||
mock_service_pause,
|
||||
mock_determine_packages):
|
||||
mock_determine_packages.return_value = []
|
||||
self.config_value_changed.return_value = False
|
||||
self.git_install_requested.return_value = False
|
||||
config.return_value = 'novnc'
|
||||
|
@ -292,6 +292,34 @@ class NovaCCUtilsTests(CharmTestCase):
|
||||
ex = list(set(utils.BASE_PACKAGES + utils.BASE_SERVICES))
|
||||
self.assertEquals(ex, pkgs)
|
||||
|
||||
@patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
|
||||
@patch.object(utils, 'git_install_requested')
|
||||
def test_determine_packages_serial_console(self,
|
||||
git_requested,
|
||||
subcontext):
|
||||
git_requested.return_value = False
|
||||
self.test_config.set('enable-serial-console', True)
|
||||
self.relation_ids.return_value = []
|
||||
self.os_release.return_value = 'juno'
|
||||
pkgs = utils.determine_packages()
|
||||
console_pkgs = ['nova-serialproxy', 'nova-consoleauth']
|
||||
for console_pkg in console_pkgs:
|
||||
self.assertIn(console_pkg, pkgs)
|
||||
|
||||
@patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
|
||||
@patch.object(utils, 'git_install_requested')
|
||||
def test_determine_packages_serial_console_icehouse(self,
|
||||
git_requested,
|
||||
subcontext):
|
||||
git_requested.return_value = False
|
||||
self.test_config.set('enable-serial-console', True)
|
||||
self.relation_ids.return_value = []
|
||||
self.os_release.return_value = 'icehouse'
|
||||
pkgs = utils.determine_packages()
|
||||
console_pkgs = ['nova-serialproxy', 'nova-consoleauth']
|
||||
for console_pkg in console_pkgs:
|
||||
self.assertNotIn(console_pkg, pkgs)
|
||||
|
||||
@patch.object(utils, 'restart_map')
|
||||
def test_determine_ports(self, restart_map):
|
||||
restart_map.return_value = {
|
||||
|
Loading…
Reference in New Issue
Block a user