From 81860afeca1b189dfcd8c06c67efcb94d67eeec5 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Tue, 8 Oct 2019 18:03:47 +0000 Subject: [PATCH] Disable nova placement API The placement project has split from nova into its own project in Train. This patch disables the nova placement API as of Stein when the placement charm relatation joins, and discontinues nova placement installation as of Train for new installs. Change-Id: If7c37ef8936e418b5afd21d83c9322563348cbcf Needed-By: https://review.opendev.org/#/c/687915/ Partial-Bug: 1811681 --- hooks/nova_cc_context.py | 5 ++++- hooks/nova_cc_hooks.py | 26 ++++++++++++++++++++++++++ hooks/nova_cc_utils.py | 21 +++++++++++++++++---- hooks/placement-relation-changed | 1 + hooks/placement-relation-joined | 1 + metadata.yaml | 2 ++ tests/basic_deployment.py | 9 ++++++--- unit_tests/test_nova_cc_contexts.py | 4 +++- unit_tests/test_nova_cc_hooks.py | 23 +++++++++++++++++++++++ unit_tests/test_nova_cc_utils.py | 29 +++++++++++++++++++++++++---- 10 files changed, 108 insertions(+), 13 deletions(-) create mode 120000 hooks/placement-relation-changed create mode 120000 hooks/placement-relation-joined diff --git a/hooks/nova_cc_context.py b/hooks/nova_cc_context.py index 08c5691a..7af1bb3c 100644 --- a/hooks/nova_cc_context.py +++ b/hooks/nova_cc_context.py @@ -218,7 +218,10 @@ class HAProxyContext(ch_context.HAProxyContext): del port_mapping['nova-api-ec2'] del port_mapping['nova-objectstore'] - if cmp_os_rel < 'ocata': + rids = hookenv.relation_ids('placement') + if (rids or + cmp_os_rel < 'ocata' or + cmp_os_rel > 'stein'): del listen_ports['placement_listen_port'] del port_mapping['nova-placement-api'] diff --git a/hooks/nova_cc_hooks.py b/hooks/nova_cc_hooks.py index d20f47a8..02d87b87 100755 --- a/hooks/nova_cc_hooks.py +++ b/hooks/nova_cc_hooks.py @@ -1292,6 +1292,32 @@ def nova_cell_api_relation_changed(rid=None, unit=None): restart_trigger=str(uuid.uuid4())) +@hooks.hook('placement-relation-joined') +def placement_relation_joined(rid=None, unit=None): + # For Stein->Train upgrades, operators must pause nova-cloud-controller + # while placement charm is added. + ncc_utils.disable_deprecated_nova_placement_apache_site() + hookenv.relation_set(nova_placement_disabled=True, relation_id=rid) + + +@hooks.hook('placement-relation-changed') +def placement_relation_changed(rid=None, unit=None): + data = hookenv.relation_get(rid=rid, unit=unit) + if not data.get('placement_enabled'): + return + CONFIGS.write_all() + # kick identity_joined() to publish endpoints without nova placement + for rid in hookenv.relation_ids('identity-service'): + identity_joined(rid) + # kick compute_joined() to restart services on nova compute + for rid in hookenv.relation_ids('cloud-compute'): + compute_joined(rid=rid, remote_restart=True) + # For Stein->Train upgrades, operators can resume nova-cloud-controller + # once the new placement endpoints are published. + for s in ncc_utils.services(): + ch_host.service_restart(s) + + @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 81d438e8..de4b1e99 100644 --- a/hooks/nova_cc_utils.py +++ b/hooks/nova_cc_utils.py @@ -91,6 +91,7 @@ AWS_COMPAT_SERVICES = ['nova-api-ec2', 'nova-objectstore'] SERVICE_BLACKLIST = { 'liberty': AWS_COMPAT_SERVICES, 'newton': ['nova-cert'], + 'train': ['nova-placement-api'], } # API_PORTS is now in nova_cc_common.py to break the circular dependency @@ -1447,7 +1448,7 @@ def determine_endpoints(public_url, internal_url, admin_url): common.api_port('nova-objectstore')) s3_admin_url = '%s:%s' % (admin_url, common.api_port('nova-objectstore')) - if cmp_os_rel >= 'ocata': + if placement_api_enabled(): placement_public_url = '%s:%s' % ( public_url, common.api_port('nova-placement-api')) placement_internal_url = '%s:%s' % ( @@ -1491,7 +1492,7 @@ def determine_endpoints(public_url, internal_url, admin_url): 's3_internal_url': None, }) - if cmp_os_rel >= 'ocata': + if placement_api_enabled(): endpoints.update({ 'placement_service': 'placement', 'placement_region': region, @@ -1755,8 +1756,12 @@ def serial_console_settings(): def placement_api_enabled(): """Return true if nova-placement-api is enabled in this release""" - return ch_utils.CompareOpenStackReleases( - ch_utils.os_release('nova-common')) >= 'ocata' + rids = hookenv.relation_ids('placement') + release = ch_utils.os_release('nova-common') + return ( + not rids and + ch_utils.CompareOpenStackReleases(release) >= 'ocata' and + ch_utils.CompareOpenStackReleases(release) <= 'stein') def enable_metadata_api(release=None): @@ -1808,6 +1813,14 @@ def stop_deprecated_services(): ch_host.service_pause('nova-api-os-compute') +def disable_deprecated_nova_placement_apache_site(): + """Disable deprecated nova placement apache2 configuration""" + release = ch_utils.os_release('nova-common') + if ch_utils.CompareOpenStackReleases(release) >= 'stein': + if os.path.exists(WSGI_NOVA_PLACEMENT_API_CONF): + os.remove(WSGI_NOVA_PLACEMENT_API_CONF) + + def get_shared_metadatasecret(): """Return the shared metadata secret.""" return hookenv.leader_get(SHARED_METADATA_SECRET_KEY) diff --git a/hooks/placement-relation-changed b/hooks/placement-relation-changed new file mode 120000 index 00000000..f6702415 --- /dev/null +++ b/hooks/placement-relation-changed @@ -0,0 +1 @@ +nova_cc_hooks.py \ No newline at end of file diff --git a/hooks/placement-relation-joined b/hooks/placement-relation-joined new file mode 120000 index 00000000..f6702415 --- /dev/null +++ b/hooks/placement-relation-joined @@ -0,0 +1 @@ +nova_cc_hooks.py \ No newline at end of file diff --git a/metadata.yaml b/metadata.yaml index 883c7dfb..7cacc6ee 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -64,6 +64,8 @@ requires: interface: mysql-shared amqp-cell: interface: rabbitmq + placement: + interface: placement peers: cluster: interface: nova-ha diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index 3d8e8cfc..0118debd 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -504,7 +504,8 @@ class NovaCCBasicDeployment(OpenStackAmuletDeployment): expected['ec2_region'] = 'RegionOne' expected['ec2_service'] = 'ec2' - if self._get_openstack_release() >= self.xenial_ocata: + if (self._get_openstack_release() >= self.xenial_ocata and + self._get_openstack_release() <= self.disco_stein): expected['placement_service'] = 'placement' expected['placement_internal_url'] = u.valid_url expected['placement_public_url'] = u.valid_url @@ -539,7 +540,8 @@ class NovaCCBasicDeployment(OpenStackAmuletDeployment): if self._get_openstack_release() >= self.trusty_kilo: expected['service_username'] = 'nova' - if self._get_openstack_release() >= self.xenial_ocata: + if (self._get_openstack_release() >= self.xenial_ocata and + self._get_openstack_release() <= self.disco_stein): expected['service_username'] = 'nova_placement' ret = u.validate_relation_data(unit, relation, expected) @@ -903,7 +905,8 @@ class NovaCCBasicDeployment(OpenStackAmuletDeployment): del services['nova-api-os-compute'] services['apache2'] = conf_file - if self._get_openstack_release() >= self.xenial_ocata: + if (self._get_openstack_release() >= self.xenial_ocata and + self._get_openstack_release() <= self.disco_stein): # nova-placement-api is run under apache2 with mod_wsgi services['apache2'] = conf_file diff --git a/unit_tests/test_nova_cc_contexts.py b/unit_tests/test_nova_cc_contexts.py index 2ab68842..080a88f3 100644 --- a/unit_tests/test_nova_cc_contexts.py +++ b/unit_tests/test_nova_cc_contexts.py @@ -132,6 +132,7 @@ class NovaComputeContextTests(CharmTestCase): self.assertEqual(ctxt['nova_url'], 'http://10.0.1.1:8774/v2') self.assertFalse('neutron_url' in ctxt) + @mock.patch('charmhelpers.core.hookenv.relation_ids') @mock.patch('charmhelpers.contrib.openstack.context.config') @mock.patch('charmhelpers.contrib.openstack.context.get_relation_ip') @mock.patch('charmhelpers.contrib.openstack.context.mkdir') @@ -150,12 +151,13 @@ class NovaComputeContextTests(CharmTestCase): mock_local_unit, mock_get_netmask_for_address, mock_get_address_in_network, mock_kv, mock_https, mock_unit_get, mock_network_manager, mock_mkdir, - mock_get_relation_ip, mock_config): + mock_get_relation_ip, mock_config, mock_rids): self.os_release.return_value = 'ocata' mock_config.side_effect = self.test_config.get mock_https.return_value = False mock_unit_get.return_value = '127.0.0.1' mock_network_manager.return_value = 'neutron' + mock_rids.return_value = [] ctxt = context.HAProxyContext()() self.assertEqual(ctxt['service_ports']['nova-api-os-compute'], [8774, 8764]) diff --git a/unit_tests/test_nova_cc_hooks.py b/unit_tests/test_nova_cc_hooks.py index bde2a311..58c7e98b 100644 --- a/unit_tests/test_nova_cc_hooks.py +++ b/unit_tests/test_nova_cc_hooks.py @@ -57,6 +57,7 @@ TO_PATCH = [ 'charmhelpers.core.hookenv.unit_get', 'charmhelpers.core.host.service_pause', 'charmhelpers.core.host.service_reload', + 'charmhelpers.core.host.service_restart', 'charmhelpers.core.host.service_resume', 'charmhelpers.fetch.apt_install', 'charmhelpers.fetch.apt_update', @@ -1060,6 +1061,28 @@ class NovaCCHooksTests(CharmTestCase): ha='settings', relation_id=None) + @patch('hooks.nova_cc_utils.disable_deprecated_nova_placement_apache_site') + def test_placement_joined(self, disable_nova_placement): + hooks.placement_relation_joined() + self.assertTrue(disable_nova_placement.called) + self.relation_set.assert_called_with(nova_placement_disabled=True, + relation_id=None) + + @patch.object(hooks, 'compute_joined') + @patch.object(hooks, 'identity_joined') + @patch.object(hooks, 'CONFIGS') + def test_placement_changed(self, configs, identity_joined, compute_joined): + self.test_relation.set({ + 'placement_enabled': True, + }) + self.services.return_value = ['dummy-service'] + self.relation_ids.return_value = ['generic_rid'] + hooks.placement_relation_changed() + self.assertTrue(self.service_restart.called) + self.assertTrue(configs.write_all.called) + self.assertTrue(identity_joined.called) + self.assertTrue(compute_joined.called) + @patch.object(hooks, 'memcached_common') def test_memcache_joined(self, _memcached_common): self.get_relation_ip.return_value = 'foo' diff --git a/unit_tests/test_nova_cc_utils.py b/unit_tests/test_nova_cc_utils.py index 66146183..6de12c7b 100644 --- a/unit_tests/test_nova_cc_utils.py +++ b/unit_tests/test_nova_cc_utils.py @@ -340,41 +340,47 @@ class NovaCCUtilsTests(CharmTestCase): self.assertIsInstance(_map, OrderedDict) self.assertEqual(_map, RESTART_MAP_ICEHOUSE) + @patch('charmhelpers.core.hookenv.relation_ids') @patch('charmhelpers.contrib.openstack.neutron.os_release') @patch('os.path.exists') @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') def test_restart_map_api_actual_ocata( - self, subcontext, _exists, _os_release): + self, subcontext, _exists, _os_release, rids): _os_release.return_value = 'ocata' self.os_release.return_value = 'ocata' _exists.return_value = False self.enable_memcache.return_value = False + rids.return_value = [] _map = utils.restart_map() self.assertIsInstance(_map, OrderedDict) self.assertEqual(_map, RESTART_MAP_OCATA_ACTUAL) + @patch('charmhelpers.core.hookenv.relation_ids') @patch('charmhelpers.contrib.openstack.neutron.os_release') @patch('os.path.exists') @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') def test_restart_map_api_actual_rocky( - self, subcontext, _exists, _os_release): + self, subcontext, _exists, _os_release, rids): _os_release.return_value = 'rocky' self.os_release.return_value = 'rocky' _exists.return_value = False self.enable_memcache.return_value = False + rids.return_value = [] _map = utils.restart_map() self.assertIsInstance(_map, OrderedDict) self.assertEqual(_map, RESTART_MAP_ROCKY_ACTUAL) + @patch('charmhelpers.core.hookenv.relation_ids') @patch('charmhelpers.contrib.openstack.neutron.os_release') @patch('os.path.exists') @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') def test_restart_map_api_ocata_base( - self, subcontext, _exists, _os_release): + self, subcontext, _exists, _os_release, rids): _os_release.return_value = 'ocata' self.os_release.return_value = 'ocata' _exists.return_value = False self.enable_memcache.return_value = False + rids.return_value = [] _map = utils.restart_map(actual_services=False) self.assertIsInstance(_map, OrderedDict) self.assertEqual(_map, RESTART_MAP_OCATA_BASE) @@ -1456,10 +1462,16 @@ class NovaCCUtilsTests(CharmTestCase): shared_db.assert_called_once() self.log.assert_not_called() - def test_placement_api_enabled(self): + @patch('charmhelpers.core.hookenv.relation_ids') + def test_placement_api_enabled(self, rids): self.os_release.return_value = 'ocata' + rids.return_value = [] self.assertTrue(utils.placement_api_enabled()) self.os_release.return_value = 'mitaka' + rids.return_value = [] + self.assertFalse(utils.placement_api_enabled()) + self.os_release.return_value = 'train' + rids.return_value = ['placement:1'] self.assertFalse(utils.placement_api_enabled()) def test_enable_metadata_api(self): @@ -1635,3 +1647,12 @@ class NovaCCUtilsTests(CharmTestCase): utils.update_child_cell('cell2', 'mysql-cell2', 'amqp-cell2') self.assertFalse(mock_check_output.called) self.assertFalse(self.service_restart.called) + + @patch('os.remove') + @patch('os.path.exists') + def test_disable_deprecated_nova_placement_apache_site(self, exists, + remove): + self.os_release.return_value = 'stein' + exists.return_value = True + utils.disable_deprecated_nova_placement_apache_site() + self.assertTrue(remove.called)