manual add/remove router for dvr_snat agent

This patch is to address the failure of manual move of
dvr_snat routers from one service node to another.

The entry in the csnat_l3_agent_bindings table is now removed
during the router to agent unbind operation.
Appropriate notification is now sent to the agent to remove
snat/qrouter namespace.

There were other places in the code
that needed to examine the snat binding table to
check if updates were required -
validate_agent_router_combination() and
check_agent_router_scheduling_needed().

Additionally, schedule_routers() was made optional
within the rpc _notification path since it can
override the manual move being attempted.

Change-Id: Iac9598eb79f455c4ef3d3243a96bed524e3d2f7c
Closes-Bug: #1369721
Co-Authored-By: Ila Palanisamy <ilavajuthy.palanisamy@hp.com>
Co-Authored-By: Oleg Bondarev <obondarev@mirantis.com>
This commit is contained in:
Michael Smith 2014-09-16 17:00:05 -07:00 committed by Oleg Bondarev
parent a264b329df
commit 7cfcbac066
5 changed files with 158 additions and 40 deletions

View File

@ -100,7 +100,7 @@ class L3AgentNotifyAPI(object):
cctxt.cast(context, method, payload=dvr_arptable)
def _notification(self, context, method, router_ids, operation,
shuffle_agents):
shuffle_agents, schedule_routers=True):
"""Notify all the agents that are hosting the routers."""
plugin = manager.NeutronManager.get_service_plugins().get(
service_constants.L3_ROUTER_NAT)
@ -112,7 +112,8 @@ class L3AgentNotifyAPI(object):
plugin, constants.L3_AGENT_SCHEDULER_EXT_ALIAS):
adminContext = (context.is_admin and
context or context.elevated())
plugin.schedule_routers(adminContext, router_ids)
if schedule_routers:
plugin.schedule_routers(adminContext, router_ids)
self._agent_notification(
context, method, router_ids, operation, shuffle_agents)
else:
@ -138,10 +139,10 @@ class L3AgentNotifyAPI(object):
self._notification_fanout(context, 'router_deleted', router_id)
def routers_updated(self, context, router_ids, operation=None, data=None,
shuffle_agents=False):
shuffle_agents=False, schedule_routers=True):
if router_ids:
self._notification(context, 'routers_updated', router_ids,
operation, shuffle_agents)
operation, shuffle_agents, schedule_routers)
def add_arp_entry(self, context, router_id, arp_table, operation=None):
self._agent_notification_arp(context, 'add_arp_entry', router_id,

View File

@ -98,13 +98,16 @@ class L3RpcCallback(object):
LOG.debug("Checking router: %(id)s for host: %(host)s",
{'id': router['id'], 'host': host})
if router.get('gw_port') and router.get('distributed'):
# '' is used to effectively clear binding of a gw port if not
# bound (snat is not hosted on any l3 agent)
gw_port_host = router.get('gw_port_host') or ''
self._ensure_host_set_on_port(context,
router.get('gw_port_host'),
gw_port_host,
router.get('gw_port'),
router['id'])
for p in router.get(constants.SNAT_ROUTER_INTF_KEY, []):
self._ensure_host_set_on_port(context,
router.get('gw_port_host'),
gw_port_host,
p, router['id'])
else:
self._ensure_host_set_on_port(
@ -143,6 +146,8 @@ class L3RpcCallback(object):
context,
port['id'],
{'port': {portbindings.HOST_ID: host}})
# updating port's host to pass actual info to l3 agent
port[portbindings.HOST_ID] = host
except exceptions.PortNotFound:
LOG.debug("Port %(port)s not found while updating "
"agent binding for router %(router)s.",

View File

@ -148,6 +148,9 @@ class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
:raises: DVRL3CannotAssignToDvrAgent if attempting to assign DVR
router from one DVR Agent to another.
"""
if agent['agent_type'] != constants.AGENT_TYPE_L3:
raise l3agentscheduler.InvalidL3Agent(id=agent['id'])
is_distributed = router.get('distributed')
agent_mode = self._get_agent_mode(agent)
router_type = (
@ -167,13 +170,14 @@ class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
router_type=router_type, router_id=router['id'],
agent_id=agent['id'])
is_wrong_type_or_unsuitable_agent = (
agent['agent_type'] != constants.AGENT_TYPE_L3 or
not agentschedulers_db.services_available(agent['admin_state_up'])
or
not self.get_l3_agent_candidates(context, router, [agent],
ignore_admin_state=True))
if is_wrong_type_or_unsuitable_agent:
is_suitable_agent = (
agentschedulers_db.services_available(agent['admin_state_up']) and
(self.get_l3_agent_candidates(context, router,
[agent],
ignore_admin_state=True) or
self.get_snat_candidates(router, [agent]))
)
if not is_suitable_agent:
raise l3agentscheduler.InvalidL3Agent(id=agent['id'])
def check_agent_router_scheduling_needed(self, context, agent, router):
@ -193,8 +197,6 @@ class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
if binding.l3_agent_id == agent_id:
# router already bound to the agent we need
return False
if router.get('distributed'):
return False
if router.get('ha'):
return True
# legacy router case: router is already bound to some agent

View File

@ -250,46 +250,73 @@ class L3_DVRsch_db_mixin(l3agent_sch_db.L3AgentSchedulerDbMixin):
self.bind_snat_router(context, router_id, chosen_snat_agent)
return chosen_snat_agent
def unbind_snat_servicenode(self, context, router_id):
"""Unbind the snat router to the chosen l3 service agent."""
vm_ports = []
def unbind_snat(self, context, router_id, agent_id=None):
"""Unbind snat from the chosen l3 service agent.
Unbinds from any L3 agent hosting SNAT if passed agent_id is None
"""
with context.session.begin(subtransactions=True):
query = (context.session.
query(CentralizedSnatL3AgentBinding).
filter_by(router_id=router_id))
if agent_id:
query = query.filter_by(l3_agent_id=agent_id)
try:
binding = query.one()
except exc.NoResultFound:
LOG.debug('no snat router binding found for %s', router_id)
LOG.debug('no snat router binding found for router: %('
'router)s, agent: %(agent)s',
{'router': router_id, 'agent': agent_id or 'any'})
return
host = binding.l3_agent.host
subnet_ids = self.get_subnet_ids_on_router(context, router_id)
for subnet in subnet_ids:
vm_ports = (
self._core_plugin.get_ports_on_host_by_subnet(
context, host, subnet))
if vm_ports:
LOG.debug('One or more ports exist on the snat enabled '
'l3_agent host %(host)s and router_id %(id)s',
{'host': host, 'id': router_id})
break
agent_id = binding.l3_agent_id
LOG.debug('Delete binding of the SNAT router %(router_id)s '
'from agent %(id)s', {'router_id': router_id,
'id': agent_id})
context.session.delete(binding)
if not vm_ports:
query = (context.session.
query(l3agent_sch_db.RouterL3AgentBinding).
filter_by(router_id=router_id,
l3_agent_id=agent_id).
delete(synchronize_session=False))
self.l3_rpc_notifier.router_removed_from_agent(
context, router_id, host)
LOG.debug('Removed binding for router %(router_id)s and '
'agent %(id)s', {'router_id': router_id, 'id': agent_id})
return binding
def unbind_router_servicenode(self, context, router_id, binding):
"""Unbind the router from the chosen l3 service agent."""
port_found = False
with context.session.begin(subtransactions=True):
host = binding.l3_agent.host
subnet_ids = self.get_subnet_ids_on_router(context, router_id)
for subnet in subnet_ids:
ports = (
self._core_plugin.get_ports_on_host_by_subnet(
context, host, subnet))
for port in ports:
if (n_utils.is_dvr_serviced(port['device_owner'])):
port_found = True
LOG.debug('One or more ports exist on the snat '
'enabled l3_agent host %(host)s and '
'router_id %(id)s',
{'host': host, 'id': router_id})
break
agent_id = binding.l3_agent_id
if not port_found:
context.session.query(
l3agent_sch_db.RouterL3AgentBinding).filter_by(
router_id=router_id, l3_agent_id=agent_id).delete(
synchronize_session=False)
if not port_found:
self.l3_rpc_notifier.router_removed_from_agent(
context, router_id, host)
LOG.debug('Removed binding for router %(router_id)s and '
'agent %(agent_id)s',
{'router_id': router_id, 'agent_id': agent_id})
return port_found
def unbind_snat_servicenode(self, context, router_id):
"""Unbind snat AND the router from the current agent."""
with context.session.begin(subtransactions=True):
binding = self.unbind_snat(context, router_id)
if binding:
self.unbind_router_servicenode(context, router_id, binding)
def get_snat_bindings(self, context, router_ids):
"""Retrieves the dvr snat bindings for a router."""
@ -403,6 +430,48 @@ class L3_DVRsch_db_mixin(l3agent_sch_db.L3AgentSchedulerDbMixin):
return self._get_dvr_sync_data(context, host, agent,
router_ids=router_ids, active=True)
def check_agent_router_scheduling_needed(self, context, agent, router):
if router.get('distributed'):
if router['external_gateway_info']:
return not self.get_snat_bindings(context, [router['id']])
return False
return super(L3_DVRsch_db_mixin,
self).check_agent_router_scheduling_needed(
context, agent, router)
def create_router_to_agent_binding(self, context, agent, router):
"""Create router to agent binding."""
router_id = router['id']
agent_id = agent['id']
if router['external_gateway_info'] and self.router_scheduler and (
router.get('distributed')):
try:
self.bind_snat_router(context, router_id, agent)
self.bind_dvr_router_servicenode(context,
router_id, agent)
except db_exc.DBError:
raise l3agentscheduler.RouterSchedulingFailed(
router_id=router_id,
agent_id=agent_id)
else:
super(L3_DVRsch_db_mixin, self).create_router_to_agent_binding(
context, agent, router)
def remove_router_from_l3_agent(self, context, agent_id, router_id):
router = self.get_router(context, router_id)
if router['external_gateway_info'] and router.get('distributed'):
binding = self.unbind_snat(context, router_id, agent_id=agent_id)
if binding:
notification_not_sent = self.unbind_router_servicenode(context,
router_id, binding)
if notification_not_sent:
self.l3_rpc_notifier.routers_updated(
context, [router_id], schedule_routers=False)
else:
super(L3_DVRsch_db_mixin,
self).remove_router_from_l3_agent(
context, agent_id, router_id)
def _notify_l3_agent_new_port(resource, event, trigger, **kwargs):
LOG.debug('Received %(resource)s %(event)s', {

View File

@ -1049,6 +1049,47 @@ class OvsAgentSchedulerTestCase(OvsAgentSchedulerTestCaseBase):
self.adminContext, [r['id']])[0]['l3_agent']['host']
self.assertNotEqual(csnat_agent_host, new_csnat_agent_host)
def test_dvr_router_csnat_manual_rescheduling(self):
helpers.register_l3_agent(
host=L3_HOSTA, agent_mode=constants.L3_AGENT_MODE_DVR_SNAT)
helpers.register_l3_agent(
host=L3_HOSTB, agent_mode=constants.L3_AGENT_MODE_DVR_SNAT)
with self.subnet() as s:
net_id = s['subnet']['network_id']
self._set_net_external(net_id)
router = {'name': 'router1',
'external_gateway_info': {'network_id': net_id},
'admin_state_up': True,
'distributed': True}
r = self.l3plugin.create_router(self.adminContext,
{'router': router})
self.l3plugin.schedule_router(
self.adminContext, r['id'])
l3agents = self.l3plugin.list_l3_agents_hosting_router(
self.adminContext, r['id'])
self.assertEqual(2, len(l3agents['agents']))
csnat_agent = self.l3plugin.get_snat_bindings(
self.adminContext, [r['id']])[0]['l3_agent']
self.l3plugin.remove_router_from_l3_agent(
self.adminContext, csnat_agent['id'], r['id'])
l3agents = self.l3plugin.list_l3_agents_hosting_router(
self.adminContext, r['id'])
self.assertEqual(1, len(l3agents['agents']))
self.assertFalse(self.l3plugin.get_snat_bindings(
self.adminContext, [r['id']]))
self.l3plugin.add_router_to_l3_agent(
self.adminContext, csnat_agent['id'], r['id'])
l3agents = self._list_l3_agents_hosting_router(r['id'])
self.assertEqual(2, len(l3agents['agents']))
new_csnat_agent = self.l3plugin.get_snat_bindings(
self.adminContext, [r['id']])[0]['l3_agent']
self.assertEqual(csnat_agent['id'], new_csnat_agent['id'])
def test_router_sync_data(self):
with self.subnet() as s1,\
self.subnet(cidr='10.0.2.0/24') as s2,\