diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index 29302cfc741..c983df7dc69 100644 --- a/neutron/db/db_base_plugin_v2.py +++ b/neutron/db/db_base_plugin_v2.py @@ -500,8 +500,10 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, # cleanup if a network-owned port snuck in without failing for subnet in subnets: self._delete_subnet(context, subnet) + # TODO(ralonsoh): use payloads registry.notify(resources.SUBNET, events.AFTER_DELETE, - self, context=context, subnet=subnet.to_dict()) + self, context=context, subnet=subnet.to_dict(), + for_net_delete=True) with db_api.CONTEXT_WRITER.using(context): network_db = self._get_network(context, id) network = self._make_network_dict(network_db, context=context) diff --git a/neutron/plugins/ml2/db.py b/neutron/plugins/ml2/db.py index 1c2deb96868..5eae1daffa1 100644 --- a/neutron/plugins/ml2/db.py +++ b/neutron/plugins/ml2/db.py @@ -33,6 +33,7 @@ from neutron.db import models_v2 from neutron.objects import base as objects_base from neutron.objects import ports as port_obj from neutron.plugins.ml2 import models +from neutron.services.segments import db as seg_db from neutron.services.segments import exceptions as seg_exc LOG = log.getLogger(__name__) @@ -316,7 +317,7 @@ def is_dhcp_active_on_any_subnet(context, subnet_ids): def _prevent_segment_delete_with_port_bound(resource, event, trigger, payload=None): """Raise exception if there are any ports bound with segment_id.""" - if payload.metadata.get('for_net_delete'): + if payload.metadata.get(seg_db.FOR_NET_DELETE): # don't check for network deletes return diff --git a/neutron/services/segments/db.py b/neutron/services/segments/db.py index a8f1436b461..3f4517d9282 100644 --- a/neutron/services/segments/db.py +++ b/neutron/services/segments/db.py @@ -38,6 +38,7 @@ from neutron.services.segments import exceptions _USER_CONFIGURED_SEGMENT_PLUGIN = None +FOR_NET_DELETE = 'for_net_delete' def check_user_configured_segment_plugin(): @@ -189,7 +190,7 @@ class SegmentDbMixin(object): self.delete_segment, payload=events.DBEventPayload( context, metadata={ - 'for_net_delete': for_net_delete}, + FOR_NET_DELETE: for_net_delete}, states=(segment_dict,), resource_id=uuid)) diff --git a/neutron/services/segments/plugin.py b/neutron/services/segments/plugin.py index ab6bc05dc32..7071230de66 100644 --- a/neutron/services/segments/plugin.py +++ b/neutron/services/segments/plugin.py @@ -121,7 +121,7 @@ class Plugin(db.SegmentDbMixin, segment.SegmentPluginBase): def _prevent_segment_delete_with_subnet_associated( self, resource, event, trigger, payload=None): """Raise exception if there are any subnets associated with segment.""" - if payload.metadata.get('for_net_delete'): + if payload.metadata.get(db.FOR_NET_DELETE): # don't check if this is a part of a network delete operation return segment_id = payload.resource_id @@ -343,6 +343,9 @@ class NovaSegmentNotifier(object): @registry.receives(resources.SUBNET, [events.AFTER_DELETE]) def _notify_subnet_deleted(self, resource, event, trigger, context, subnet, **kwargs): + if kwargs.get(db.FOR_NET_DELETE): + return # skip segment RP update if it is going to be deleted + segment_id = subnet.get('segment_id') if not segment_id or subnet['ip_version'] != constants.IP_VERSION_4: return @@ -359,23 +362,32 @@ class NovaSegmentNotifier(object): self._delete_nova_inventory, segment_id)) def _get_aggregate_id(self, segment_id): - aggregate_uuid = self.p_client.list_aggregates( - segment_id)['aggregates'][0] - aggregates = self.n_client.aggregates.list() - for aggregate in aggregates: + try: + aggregate_uuid = self.p_client.list_aggregates( + segment_id)['aggregates'][0] + except placement_exc.PlacementAggregateNotFound: + LOG.info('Segment %s resource provider aggregate not found', + segment_id) + return + + for aggregate in self.n_client.aggregates.list(): nc_aggregate_uuid = self._get_nova_aggregate_uuid(aggregate) if nc_aggregate_uuid == aggregate_uuid: return aggregate.id def _delete_nova_inventory(self, event): aggregate_id = self._get_aggregate_id(event.segment_id) - aggregate = self.n_client.aggregates.get_details( - aggregate_id) - for host in aggregate.hosts: - self.n_client.aggregates.remove_host(aggregate_id, - host) - self.n_client.aggregates.delete(aggregate_id) - self.p_client.delete_resource_provider(event.segment_id) + if aggregate_id: + aggregate = self.n_client.aggregates.get_details(aggregate_id) + for host in aggregate.hosts: + self.n_client.aggregates.remove_host(aggregate_id, host) + self.n_client.aggregates.delete(aggregate_id) + + try: + self.p_client.delete_resource_provider(event.segment_id) + except placement_exc.PlacementClientError as exc: + LOG.info('Segment %s resource provider not found; error: %s', + event.segment_id, str(exc)) @registry.receives(resources.SEGMENT_HOST_MAPPING, [events.AFTER_CREATE]) def _notify_host_addition_to_aggregate(self, resource, event, trigger, @@ -390,9 +402,8 @@ class NovaSegmentNotifier(object): def _add_host_to_aggregate(self, event): for segment_id in event.segment_ids: - try: - aggregate_id = self._get_aggregate_id(segment_id) - except placement_exc.PlacementAggregateNotFound: + aggregate_id = self._get_aggregate_id(segment_id) + if not aggregate_id: LOG.info('When adding host %(host)s, aggregate not found ' 'for routed network segment %(segment_id)s', {'host': event.host, 'segment_id': segment_id}) @@ -472,6 +483,13 @@ class NovaSegmentNotifier(object): ipv4_subnet_ids.append(ip['subnet_id']) return ipv4_subnet_ids + @registry.receives(resources.SEGMENT, [events.AFTER_DELETE]) + def _notify_segment_deleted( + self, resource, event, trigger, payload=None): + if payload: + self.batch_notifier.queue_event(Event( + self._delete_nova_inventory, payload.resource_id)) + @registry.has_registry_receivers class SegmentHostRoutes(object): @@ -650,6 +668,9 @@ class SegmentHostRoutes(object): subnet, **kwargs): # If this is a routed network, remove any routes to this subnet on # this networks remaining subnets. + if kwargs.get(db.FOR_NET_DELETE): + return # skip subnet update if the network is going to be deleted + if subnet.get('segment_id'): self._update_routed_network_host_routes( context, subnet['network_id'], deleted_cidr=subnet['cidr']) diff --git a/neutron/tests/unit/extensions/test_segment.py b/neutron/tests/unit/extensions/test_segment.py index 57092f62fa5..27dd053ec12 100644 --- a/neutron/tests/unit/extensions/test_segment.py +++ b/neutron/tests/unit/extensions/test_segment.py @@ -2478,6 +2478,33 @@ class TestNovaSegmentNotifier(SegmentAwareIpamTestCase): self.segments_plugin.nova_updater._send_notifications([event]) self.assertTrue(log.called) + def _test_create_network_and_segment(self, phys_net): + with self.network() as net: + network = net['network'] + segment = self._test_create_segment( + network_id=network['id'], physical_network=phys_net, + segmentation_id=200, network_type='vlan') + return network, segment['segment'] + + def test_delete_network_and_owned_segments(self): + db.subscribe() + aggregate = mock.MagicMock() + aggregate.uuid = uuidutils.generate_uuid() + aggregate.id = 1 + aggregate.hosts = ['fakehost1'] + self.mock_p_client.list_aggregates.return_value = { + 'aggregates': [aggregate.uuid]} + self.mock_n_client.aggregates.list.return_value = [aggregate] + self.mock_n_client.aggregates.get_details.return_value = aggregate + network, segment = self._test_create_network_and_segment('physnet') + self._delete('networks', network['id']) + self.mock_n_client.aggregates.remove_host.assert_has_calls( + [mock.call(aggregate.id, 'fakehost1')]) + self.mock_n_client.aggregates.delete.assert_has_calls( + [mock.call(aggregate.id)]) + self.mock_p_client.delete_resource_provider.assert_has_calls( + [mock.call(segment['id'])]) + class TestDhcpAgentSegmentScheduling(HostSegmentMappingTestCase):