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)