From 816ee80cd0e83dd3b5d9e49187caf9e6d420cf1b Mon Sep 17 00:00:00 2001 From: Felipe Reyes Date: Fri, 1 Sep 2023 17:39:23 -0400 Subject: [PATCH] Add new interface 'dashboard' This new interface consumes information exposed by openstack-dashboard to correctly configure nova-serialproxy and allow requests coming from the web browser that tries to load the serial console. Change-Id: I2d82abffb9649f16a792f180806cea36cc5e25df Closes-Bug: #2030094 --- hooks/dashboard-relation-changed | 1 + hooks/dashboard-relation-joined | 1 + hooks/nova_cc_context.py | 27 +++++++++++++++++++++- hooks/nova_cc_hooks.py | 9 ++++++++ hooks/nova_cc_utils.py | 10 +++++++- metadata.yaml | 2 ++ templates/parts/section-console | 6 +++++ templates/train/nova.conf | 2 ++ unit_tests/test_nova_cc_contexts.py | 36 +++++++++++++++++++++++++++++ unit_tests/test_nova_cc_utils.py | 28 ++++++++++++++++++++++ 10 files changed, 120 insertions(+), 2 deletions(-) create mode 120000 hooks/dashboard-relation-changed create mode 120000 hooks/dashboard-relation-joined create mode 100644 templates/parts/section-console diff --git a/hooks/dashboard-relation-changed b/hooks/dashboard-relation-changed new file mode 120000 index 00000000..f6702415 --- /dev/null +++ b/hooks/dashboard-relation-changed @@ -0,0 +1 @@ +nova_cc_hooks.py \ No newline at end of file diff --git a/hooks/dashboard-relation-joined b/hooks/dashboard-relation-joined new file mode 120000 index 00000000..f6702415 --- /dev/null +++ b/hooks/dashboard-relation-joined @@ -0,0 +1 @@ +nova_cc_hooks.py \ No newline at end of file diff --git a/hooks/nova_cc_context.py b/hooks/nova_cc_context.py index 4f072b1d..5432d9b1 100644 --- a/hooks/nova_cc_context.py +++ b/hooks/nova_cc_context.py @@ -587,8 +587,33 @@ class SerialConsoleContext(ch_context.OSContextGenerator): ctxt = { 'enable_serial_console': str(hookenv.config('enable-serial-console')).lower(), - 'serial_console_base_url': 'ws://{}:6083/'.format(ip_addr) + 'serial_console_base_url': 'ws://{}:6083/'.format(ip_addr), } + if hookenv.config('enable-serial-console'): + for rel_id in hookenv.relation_ids('dashboard'): + ctxt['console_allowed_origins'] = [] + rel_units = hookenv.related_units(rel_id) + if not rel_units: + continue + + os_public_hostname = hookenv.relation_get('os-public-hostname', + rid=rel_id, + app=rel_units[0]) + if os_public_hostname: + ctxt['console_allowed_origins'].append(os_public_hostname) + + vip = hookenv.relation_get('vip', + rid=rel_id, + app=rel_units[0]) + if vip: + ip_addresses = [ip.strip() for ip in vip.split(' ')] + ctxt['console_allowed_origins'] += ip_addresses + + for unit in rel_units: + ingress_address = hookenv.ingress_address(rel_id, unit) + if ingress_address: + ctxt['console_allowed_origins'].append(ingress_address) + return ctxt diff --git a/hooks/nova_cc_hooks.py b/hooks/nova_cc_hooks.py index 96d0a42f..87051e51 100755 --- a/hooks/nova_cc_hooks.py +++ b/hooks/nova_cc_hooks.py @@ -1403,6 +1403,15 @@ def placement_relation_changed(rid=None, unit=None): ch_host.service_restart(s) +@hooks.hook('dashboard-relation-joined', + 'dashboard-relation-changed') +def dashboard_relation_changed(): + """ + Gather information from the related openstack-dashboard units + """ + CONFIGS.write_all() + + @hooks.hook('update-status') @ch_harden.harden() def update_status(): diff --git a/hooks/nova_cc_utils.py b/hooks/nova_cc_utils.py index fffe7139..1d9ae07a 100644 --- a/hooks/nova_cc_utils.py +++ b/hooks/nova_cc_utils.py @@ -1763,7 +1763,9 @@ def check_optional_relations(configs): if hookenv.relation_ids('ha'): try: ch_cluster.get_hacluster_config() - except Exception: + except Exception as ex: + hookenv.log("get_hacluster_config exception: %s" % str(ex), + hookenv.DEBUG) return ('blocked', 'hacluster missing configuration: ' 'vip, vip_iface, vip_cidr') @@ -1780,6 +1782,12 @@ def check_optional_relations(configs): 'false, this configuration is only availabe for releases>=Train' ) + if hookenv.config('enable-serial-console'): + if not hookenv.relation_ids('dashboard'): + return ('blocked', + ("Required relation 'dashboard' needed when " + "enable-serial-console is set to True")) + # return 'unknown' as the lowest priority to not clobber an existing # status. return "unknown", None diff --git a/metadata.yaml b/metadata.yaml index 999478c7..e21df248 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -45,6 +45,8 @@ requires: interface: nova-compute cinder-volume-service: interface: cinder + dashboard: + interface: dashboard quantum-network-service: interface: quantum neutron-api: diff --git a/templates/parts/section-console b/templates/parts/section-console new file mode 100644 index 00000000..c0ca6205 --- /dev/null +++ b/templates/parts/section-console @@ -0,0 +1,6 @@ +{% if console_allowed_origins -%} +[console] +# List of allowed origins to the console websocket proxy to allow connections +# from other origin hostnames. +allowed_origins = {{ console_allowed_origins|join(',') }} +{% endif -%} diff --git a/templates/train/nova.conf b/templates/train/nova.conf index b7b344d6..21e86414 100644 --- a/templates/train/nova.conf +++ b/templates/train/nova.conf @@ -193,6 +193,8 @@ html5proxy_port = {{ console_access_port }} {% include "parts/section-serial-console" %} +{% include "parts/section-console" %} + {% if memcached_servers %} [cache] enabled = true diff --git a/unit_tests/test_nova_cc_contexts.py b/unit_tests/test_nova_cc_contexts.py index e75ba89f..5bc2c0a7 100644 --- a/unit_tests/test_nova_cc_contexts.py +++ b/unit_tests/test_nova_cc_contexts.py @@ -517,6 +517,8 @@ class NovaComputeContextTests(CharmTestCase): self.test_config.set('enable-serial-console', True) mock_format_ipv6_address.return_value = None mock_resolve_address.return_value = '10.10.10.1' + + # no relation to openstack-dashboard:dashboard ctxt = context.SerialConsoleContext()() self.assertEqual( ctxt, @@ -526,6 +528,40 @@ class NovaComputeContextTests(CharmTestCase): mock_resolve_address.assert_called_with( endpoint_type=context.ch_ip.PUBLIC) + # generated context when related to openstack-dashboard:dashboard + fake_relation_ids = { + 'dashboard': ['dashboard:1'], + } + self.relation_ids.side_effect = fake_relation_ids.get + fake_related_units = {'dashboard:1': ['openstack-dashboard/0']} + self.related_units.side_effect = fake_related_units.get + + def fake_relation_get(attribute=None, rid=None, unit=None, app=None): + data = { + 'dashboard:1': { + 'openstack-dashboard/0': { + 'os-public-hostname': 'myhostname', + 'vip': '1.2.3.4', + 'ingress-address': '10.20.30.40', + }, + } + } + if attribute: + return data[rid][unit or app][attribute] + else: + return data[rid][unit or app] + + self.relation_get.side_effect = fake_relation_get + + ctxt = context.SerialConsoleContext()() + self.assertEqual( + ctxt, + {'serial_console_base_url': 'ws://10.10.10.1:6083/', + 'enable_serial_console': 'true', + 'console_allowed_origins': ['myhostname', '1.2.3.4', + '10.20.30.40']} + ) + @mock.patch.object(context, 'ch_cluster') @mock.patch('os.path.exists') @mock.patch('charmhelpers.contrib.openstack.ip.resolve_address') diff --git a/unit_tests/test_nova_cc_utils.py b/unit_tests/test_nova_cc_utils.py index ba299d46..6e62e722 100644 --- a/unit_tests/test_nova_cc_utils.py +++ b/unit_tests/test_nova_cc_utils.py @@ -1947,3 +1947,31 @@ class NovaCCUtilsTests(CharmTestCase): call(['nova-manage', 'cell_v2', 'update_cell', '--cell_uuid', 'cell1uuid']), ]) + + @patch('charmhelpers.core.hookenv.relation_ids') + def test_check_optional_relations(self, relation_ids): + self.get_os_codename_install_source.return_value = 'ussuri' + self.os_release.return_value = 'ussuri' + + fake_relation_ids = {'placement': ['placement:0'], + 'ha': ['ha:1'], + 'dashboard': []} + relation_ids.side_effect = fake_relation_ids.get + self.test_config.set('enable-serial-console', True) + (status, workload_msg) = utils.check_optional_relations(None) + + self.assertEqual('blocked', status) + self.assertEqual(('hacluster missing configuration: vip, vip_iface, ' + 'vip_cidr'), + workload_msg) + with patch.object(utils.ch_cluster, + 'get_hacluster_config') as get_hacluster_config: # noqa + (status, workload_msg) = utils.check_optional_relations(None) + self.assertEqual('blocked', status) + self.assertEqual(("Required relation 'dashboard' needed when " + "enable-serial-console is set to True"), + workload_msg) + fake_relation_ids['dashboard'] = ['dashboard:2'] + (status, workload_msg) = utils.check_optional_relations(None) + self.assertEqual('unknown', status) + self.assertEqual(None, workload_msg)