Clean up db residual record from dvr port

Delete the DVR port record on the node when the router is deleted from
the node. This patch fixes an issue where the DB actions have no context
writer which will actually update nothing duing the following
`delete_distributed_port_binding_if_stale`.
As well as fixing that when the dvr router was deleted from a node,
only the ml2_distributed_port_bindings were cleaned up and not the
ml2_port_binding_levels records for the dvr port.

Remove the last VM under one router in one host, the
ml2_distributed_port_bindings and ml2_port_binding_levels will remain
the record.  So if VMs under one router had spread on many hosts,
and router (router ports) still exists, even there is only one VM under it,
the binding entries will still be equal to amount of hosts where this
router resided before. The result is a large amount of redundant data
in the database, even if some nodes are no longer in the cluster,
there are still records for that node.

Closes-Bug: #1976439
Change-Id: I320ac2306e0f25ff933d8271203e192486062d61
(cherry picked from commit ad3f7a8b7d)
This commit is contained in:
liujinxin 2022-06-08 16:10:55 +08:00 committed by liuyulong
parent ada7616490
commit 8c98d7bd20
3 changed files with 73 additions and 12 deletions

View File

@ -283,13 +283,9 @@ class L3_DVRsch_db_mixin(l3agent_sch_db.L3AgentSchedulerDbMixin):
int_ports = self._core_plugin.get_ports(
context.elevated(), filters=filter_rtr)
for port in int_ports:
dvr_binding = (ml2_db.
get_distributed_port_binding_by_host(
context, port['id'], port_host))
if dvr_binding:
# unbind this port from router
dvr_binding['router_id'] = None
dvr_binding.update(dvr_binding)
# unbind this port from router
ml2_db.update_distributed_port_binding_by_host(
context, port['id'], port_host, None)
def _get_active_l3_agent_routers_sync_data(self, context, host, agent,
router_ids):

View File

@ -120,6 +120,13 @@ def delete_distributed_port_binding_if_stale(context, binding):
with db_api.CONTEXT_WRITER.using(context):
LOG.debug("Distributed port: Deleting binding %s", binding)
context.session.delete(binding)
for bindlv in (context.session.query(models.PortBindingLevel).
filter_by(port_id=binding.port_id, host=binding.host)):
context.session.delete(bindlv)
LOG.debug("For port %(port_id)s, host %(host)s, "
"cleared binding levels",
{'port_id': binding.port_id,
'host': binding.host})
def get_port(context, port_id):
@ -249,6 +256,17 @@ def get_distributed_port_binding_by_host(context, port_id, host):
return binding
def update_distributed_port_binding_by_host(context, port_id, host, router_id):
with db_api.CONTEXT_WRITER.using(context):
bindings = (
context.session.query(models.DistributedPortBinding).
filter(models.DistributedPortBinding.port_id.startswith(port_id),
models.DistributedPortBinding.host == host).all())
for binding in bindings or []:
binding['router_id'] = router_id or None
binding.update(binding)
def get_distributed_port_bindings(context, port_id):
with db_api.CONTEXT_READER.using(context):
bindings = (context.session.query(models.DistributedPortBinding).

View File

@ -375,6 +375,26 @@ class Ml2DvrDBTestCase(testlib_api.SqlTestCase):
self.ctx.session.add(record)
return record
def _setup_port_binding_level(self, network_id, port_id, host_id,
level=0, driver='openvswitch'):
with db_api.CONTEXT_WRITER.using(self.ctx):
record = models.PortBindingLevel(
port_id=port_id,
host=host_id,
level=level,
driver=driver,
segment_id=network_id)
self.ctx.session.add(record)
return record
def _setup_neutron_network_segment(self, segment_id, network_id):
with db_api.CONTEXT_WRITER.using(self.ctx):
segment = network_obj.NetworkSegment(
self.ctx,
id=segment_id, network_id=network_id,
network_type=constants.TYPE_FLAT)
segment.create()
def test_ensure_distributed_port_binding_deals_with_db_duplicate(self):
network_id = uuidutils.generate_uuid()
port_id = uuidutils.generate_uuid()
@ -418,17 +438,44 @@ class Ml2DvrDBTestCase(testlib_api.SqlTestCase):
def test_delete_distributed_port_binding_if_stale(self):
network_id = uuidutils.generate_uuid()
segment_id = uuidutils.generate_uuid()
port_id = uuidutils.generate_uuid()
host_id = 'foo_host_id'
self._setup_neutron_network(network_id, [port_id])
binding = self._setup_distributed_binding(
network_id, port_id, None, 'foo_host_id')
self._setup_neutron_network_segment(segment_id, network_id)
ml2_db.delete_distributed_port_binding_if_stale(self.ctx,
binding)
binding = self._setup_distributed_binding(
network_id, port_id, None, host_id)
binding_levels = self._setup_port_binding_level(
segment_id, port_id, host_id)
ml2_db.delete_distributed_port_binding_if_stale(
self.ctx, binding)
count = (self.ctx.session.query(models.DistributedPortBinding).
filter_by(port_id=binding.port_id).count())
filter_by(port_id=binding.port_id, host=host_id).count())
self.assertFalse(count)
count = (self.ctx.session.query(models.PortBindingLevel).
filter_by(port_id=binding_levels.port_id,
host=host_id).count())
self.assertFalse(count)
def test_update_distributed_port_binding_by_host(self):
network_id = uuidutils.generate_uuid()
router_id = uuidutils.generate_uuid()
port_id = uuidutils.generate_uuid()
host_id = 'foo_host_id'
self._setup_neutron_network(network_id, [port_id])
binding = self._setup_distributed_binding(
network_id, port_id, router_id, host_id)
ml2_db.update_distributed_port_binding_by_host(
self.ctx, binding.port_id, host_id, None)
binding = (self.ctx.session.query(models.DistributedPortBinding).
filter_by(port_id=port_id, host=host_id).one())
self.assertFalse(binding.router_id)
def test_get_distributed_port_binding_by_host_not_found(self):
port = ml2_db.get_distributed_port_binding_by_host(
self.ctx, 'foo_port_id', 'foo_host_id')