Merge "Remove FIP agent's gw port when L3 agent is deleted" into stable/queens

This commit is contained in:
Zuul 2021-06-29 12:19:51 +00:00 committed by Gerrit Code Review
commit 8458e78649
4 changed files with 185 additions and 0 deletions

View File

@ -207,6 +207,9 @@ class AgentDbMixin(ext_agent.AgentPluginBase, AgentAvailabilityZoneMixin):
registry.notify(resources.AGENT, events.BEFORE_DELETE, self,
context=context, agent=agent)
agent.delete()
registry.publish(resources.AGENT, events.AFTER_DELETE, self,
payload=events.DBEventPayload(
context, states=(agent,), resource_id=id))
@db_api.retry_if_session_inactive()
def update_agent(self, context, id, agent):

View File

@ -49,6 +49,7 @@ from neutron.ipam import utils as ipam_utils
from neutron.objects import agent as ag_obj
from neutron.objects import base as base_obj
from neutron.objects import l3agent as rb_obj
from neutron.objects import ports as port_obj
from neutron.objects import router as l3_obj
from neutron.plugins.common import utils as p_utils
@ -694,6 +695,61 @@ class DVRResourceOperationHandler(object):
p['id'],
l3_port_check=False)
def _get_ext_nets_by_host(self, context, host):
ext_nets = set()
ports_ids = port_obj.Port.get_ports_by_host(context, host)
for port_id in ports_ids:
fips = self._get_floatingips_by_port_id(context, port_id)
for fip in fips:
ext_nets.add(fip.floating_network_id)
return ext_nets
@registry.receives(resources.AGENT, [events.AFTER_CREATE])
def create_fip_agent_gw_ports(self, resource, event,
trigger, payload=None):
"""Create floating agent gw ports for DVR L3 agent.
Create floating IP Agent gateway ports when an L3 agent is created.
"""
if not payload:
return
agent = payload.latest_state
if agent.get('agent_type') != const.AGENT_TYPE_L3:
return
# NOTE(slaweq) agent is passed in payload as dict so to avoid getting
# again from db, lets just get configuration from this dict directly
l3_agent_mode = agent.get('configurations', {}).get('agent_mode')
if l3_agent_mode not in [const.L3_AGENT_MODE_DVR,
const.L3_AGENT_MODE_DVR_SNAT]:
return
host = agent['host']
context = payload.context.elevated()
for ext_net in self._get_ext_nets_by_host(context, host):
self.create_fip_agent_gw_port_if_not_exists(
context, ext_net, host)
@registry.receives(resources.AGENT, [events.AFTER_DELETE])
def delete_fip_agent_gw_ports(self, resource, event,
trigger, payload=None):
"""Delete floating agent gw ports for DVR.
Delete floating IP Agent gateway ports from host when an L3 agent is
deleted.
"""
if not payload:
return
agent = payload.latest_state
if agent.get('agent_type') != const.AGENT_TYPE_L3:
return
if self._get_agent_mode(agent) not in [const.L3_AGENT_MODE_DVR,
const.L3_AGENT_MODE_DVR_SNAT]:
return
agent_gw_ports = self._get_agent_gw_ports(payload.context, agent['id'])
for gw_port in agent_gw_ports:
self._core_plugin.delete_port(payload.context, gw_port['id'])
class _DVRAgentInterfaceMixin(object):
"""Contains calls made by the DVR scheduler and RPC interface.
@ -973,6 +1029,14 @@ class _DVRAgentInterfaceMixin(object):
if ports:
return ports[0]
def _get_agent_gw_ports(self, context, agent_id):
"""Return agent gw ports."""
filters = {
'device_id': [agent_id],
'device_owner': [const.DEVICE_OWNER_AGENT_GW]
}
return self._core_plugin.get_ports(context, filters)
def check_for_fip_and_create_agent_gw_port_on_host_if_not_exists(
self, context, port, host):
"""Create fip agent_gw_port on host if not exists"""

View File

@ -526,6 +526,14 @@ class Port(base.NeutronDbObject):
security_group_ids))
return [port_binding['port_id'] for port_binding in query.all()]
@classmethod
def get_ports_by_host(cls, context, host):
query = context.session.query(models_v2.Port.id).join(
ml2_models.PortBinding)
query = query.filter(
ml2_models.PortBinding.host == host)
return [port_id[0] for port_id in query.all()]
@classmethod
def get_ports_by_binding_type_and_host(cls, context,
binding_type, host):

View File

@ -35,6 +35,7 @@ from neutron.db.models import l3 as l3_models
from neutron.db import models_v2
from neutron.objects import agent as agent_obj
from neutron.objects import l3agent as rb_obj
from neutron.objects import ports as port_obj
from neutron.objects import router as router_obj
from neutron.tests.unit.db import test_db_base_plugin_v2
@ -526,6 +527,115 @@ class L3DvrTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
fip, floatingip, router))
self.assertFalse(create_fip.called)
def test_get_ext_nets_by_host(self):
ports = [mock.Mock(id=_uuid()) for _ in range(3)]
fips = [mock.Mock(fixed_port_id=p.id, floating_network_id=_uuid())
for p in ports]
expected_ext_nets = set([fip.floating_network_id for fip in fips])
with mock.patch.object(
port_obj.Port, 'get_ports_by_host',
return_value=[p.id for p in ports]
) as get_ports_by_host, mock.patch.object(
self.mixin, '_get_floatingips_by_port_id', return_value=fips
) as get_floatingips_by_port_id:
self.assertEqual(
expected_ext_nets,
self.mixin._get_ext_nets_by_host(self.ctx, 'host'))
get_ports_by_host.assert_called_once_with(self.ctx, 'host')
get_floatingips_by_port_id.assert_has_calls(
[mock.call(self.ctx, p.id) for p in ports])
def _test_create_fip_agent_gw_ports(self, agent_type, agent_mode=None):
agent = {
'id': _uuid(),
'host': 'host',
'agent_type': agent_type,
'configurations': {'agent_mode': agent_mode}}
payload = events.DBEventPayload(
self.ctx, states=(agent,), resource_id=agent['id'])
ext_nets = ['ext-net-1', 'ext-net-2']
with mock.patch.object(
self.mixin,
'create_fip_agent_gw_port_if_not_exists'
) as create_fip_gw, mock.patch.object(
self.mixin, "_get_ext_nets_by_host",
return_value=ext_nets
) as get_ext_nets_by_host:
registry.publish(resources.AGENT, events.AFTER_CREATE, mock.Mock(),
payload=payload)
if agent_type == 'L3 agent' and agent_mode in ['dvr', 'dvr_snat']:
get_ext_nets_by_host.assert_called_once_with(
mock.ANY, 'host')
create_fip_gw.assert_has_calls(
[mock.call(mock.ANY, ext_net, 'host') for
ext_net in ext_nets])
else:
get_ext_nets_by_host.assert_not_called()
create_fip_gw.assert_not_called()
def test_create_fip_agent_gw_ports(self):
self._test_create_fip_agent_gw_ports(
agent_type='L3 agent', agent_mode='dvr')
self._test_create_fip_agent_gw_ports(
agent_type='L3 agent', agent_mode='dvr_snat')
def test_create_fip_agent_gw_ports_dvr_no_external_agent(self):
self._test_create_fip_agent_gw_ports(
agent_type='L3 agent', agent_mode='dvr_no_external')
def test_create_fip_agent_gw_ports_non_dvr_agent(self):
self._test_create_fip_agent_gw_ports(
agent_type='L3 agent', agent_mode='legacy')
def test_create_fip_agent_gw_ports_deleted_non_l3_agent(self):
self._test_create_fip_agent_gw_ports('Other agent type')
def _test_delete_fip_agent_gw_ports(self, agent_type, agent_mode=None):
agent = agent_obj.Agent(
self.ctx, id=_uuid(), agent_type=agent_type,
configurations={"agent_mode": agent_mode})
payload = events.DBEventPayload(
self.ctx, states=(agent,), resource_id=agent.id)
gw_port = {'id': _uuid(), 'network_id': _uuid()}
with mock.patch.object(
self.mixin, '_get_agent_gw_ports',
return_value=[gw_port]
) as get_agent_gw_ports, mock.patch.object(
self.core_plugin, 'delete_port'
) as delete_port:
registry.publish(resources.AGENT, events.AFTER_DELETE, mock.Mock(),
payload=payload)
if agent_type == 'L3 agent' and agent_mode in ['dvr', 'dvr_snat']:
get_agent_gw_ports.assert_called_once_with(payload.context,
agent['id'])
delete_port.assert_called_once_with(payload.context,
gw_port['id'])
else:
get_agent_gw_ports.assert_not_called()
delete_port.assert_not_called()
def test_delete_fip_agent_gw_ports(self):
self._test_delete_fip_agent_gw_ports(
agent_type='L3 agent', agent_mode='dvr')
self._test_delete_fip_agent_gw_ports(
agent_type='L3 agent', agent_mode='dvr_snat')
def test_delete_fip_agent_gw_ports_dvr_no_external_agent(self):
self._test_delete_fip_agent_gw_ports(
agent_type='L3 agent', agent_mode='dvr_no_external')
def test_delete_fip_agent_gw_ports_non_dvr_agent(self):
self._test_delete_fip_agent_gw_ports(
agent_type='L3 agent', agent_mode='legacy')
def test_delete_fip_agent_gw_ports_deleted_non_l3_agent(self):
self._test_delete_fip_agent_gw_ports('Other agent type')
def test_update_router_gw_info_external_network_change(self):
router_dict = {'name': 'test_router', 'admin_state_up': True,
'distributed': True}