Remove bindings of DVR routers to L3 agents on compute nodes

As described in the spec there is no need to explicitly bind
DVR router to each l3 agent running on compute nodes where
there are dvr serviceable ports - this brings complexity to the code,
makes it less readable and very hard to maintain (one could see how many
bugs were filed and fixed regarding dvr scheduling stuff already).
Also this brings scalability problems as time needed for router scheduling
grows linearly with the number of compute nodes.

The idea is to align dvr scheduling with legacy router scheduling:
only schedule SNAT portion of the router and use DB queries whenever
we need to know which compute nodes should host the router.

Implements blueprint improve-dvr-l3-agent-binding

Change-Id: I82c8d256c56bb16cdc1b1232ebb660d09909f9c6
This commit is contained in:
Oleg Bondarev 2015-11-26 13:47:49 +03:00 committed by Carl Baldwin
parent 9546ee7d92
commit 0496f95a13
9 changed files with 177 additions and 754 deletions

View File

@ -120,15 +120,6 @@ class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
agents_back_online.add(binding.l3_agent_id)
continue
agent_mode = self._get_agent_mode(binding.l3_agent)
if agent_mode == constants.L3_AGENT_MODE_DVR:
# rescheduling from l3 dvr agent on compute node doesn't
# make sense. Router will be removed from that agent once
# there are no dvr serviceable ports on that compute node
LOG.warn(_LW('L3 DVR agent on node %(host)s is down. '
'Not rescheduling from agent in \'dvr\' '
'mode.'), {'host': binding.l3_agent.host})
continue
LOG.warn(_LW(
"Rescheduling router %(router)s from agent %(agent)s "
"because the agent did not report to the server in "
@ -180,20 +171,12 @@ class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
is_suitable_agent = (
agentschedulers_db.services_available(agent['admin_state_up']) and
(self.get_l3_agent_candidates(context, router,
self.get_l3_agent_candidates(context, router,
[agent],
ignore_admin_state=True) or
self.get_snat_candidates(router, [agent]))
)
ignore_admin_state=True))
if not is_suitable_agent:
raise l3agentscheduler.InvalidL3Agent(id=agent['id'])
def check_l3_agent_router_binding(self, context, router_id, agent_id):
query = context.session.query(RouterL3AgentBinding)
bindings = query.filter_by(router_id=router_id,
l3_agent_id=agent_id).all()
return bool(bindings)
def check_agent_router_scheduling_needed(self, context, agent, router):
"""Check if the router scheduling is needed.
@ -496,43 +479,44 @@ class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
def get_l3_agent_candidates(self, context, sync_router, l3_agents,
ignore_admin_state=False):
"""Get the valid l3 agents for the router from a list of l3_agents."""
"""Get the valid l3 agents for the router from a list of l3_agents.
It will not return agents in 'dvr' mode for a dvr router as dvr
routers are not explicitly scheduled to l3 agents on compute nodes
"""
candidates = []
is_router_distributed = sync_router.get('distributed', False)
if is_router_distributed:
subnet_ids = self.get_subnet_ids_on_router(
context, sync_router['id'])
for l3_agent in l3_agents:
if not ignore_admin_state and not l3_agent.admin_state_up:
# ignore_admin_state True comes from manual scheduling
# where admin_state_up judgement is already done.
continue
agent_conf = self.get_configuration_dict(l3_agent)
agent_mode = agent_conf.get(constants.L3_AGENT_MODE,
constants.L3_AGENT_MODE_LEGACY)
if (agent_mode == constants.L3_AGENT_MODE_DVR or
(agent_mode == constants.L3_AGENT_MODE_LEGACY and
is_router_distributed)):
continue
router_id = agent_conf.get('router_id', None)
if router_id and router_id != sync_router['id']:
continue
handle_internal_only_routers = agent_conf.get(
'handle_internal_only_routers', True)
gateway_external_network_id = agent_conf.get(
'gateway_external_network_id', None)
agent_mode = agent_conf.get(constants.L3_AGENT_MODE,
constants.L3_AGENT_MODE_LEGACY)
if router_id and router_id != sync_router['id']:
continue
ex_net_id = (sync_router['external_gateway_info'] or {}).get(
'network_id')
if ((not ex_net_id and not handle_internal_only_routers) or
(ex_net_id and gateway_external_network_id and
ex_net_id != gateway_external_network_id)):
continue
if agent_mode in (
constants.L3_AGENT_MODE_LEGACY,
constants.L3_AGENT_MODE_DVR_SNAT) and (
not is_router_distributed):
candidates.append(l3_agent)
elif (is_router_distributed and subnet_ids and
agent_mode.startswith(constants.L3_AGENT_MODE_DVR) and (
self.check_dvr_serviceable_ports_on_host(
context, l3_agent['host'], subnet_ids))):
candidates.append(l3_agent)
candidates.append(l3_agent)
return candidates
def auto_schedule_routers(self, context, host, router_ids):

View File

@ -27,9 +27,9 @@ from neutron.callbacks import resources
from neutron.common import constants as l3_const
from neutron.common import exceptions as n_exc
from neutron.common import utils as n_utils
from neutron.db import l3_agentschedulers_db as l3_sched_db
from neutron.db import l3_attrs_db
from neutron.db import l3_db
from neutron.db import l3_dvrscheduler_db as l3_dvrsched_db
from neutron.extensions import l3
from neutron.extensions import portbindings
from neutron import manager
@ -371,15 +371,15 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
if removed_hosts:
agents = plugin.get_l3_agents(context,
filters={'host': removed_hosts})
binding_table = l3_dvrsched_db.CentralizedSnatL3AgentBinding
binding_table = l3_sched_db.RouterL3AgentBinding
snat_binding = context.session.query(binding_table).filter_by(
router_id=router_id).first()
for agent in agents:
is_this_snat_agent = (
snat_binding and snat_binding.l3_agent_id == agent['id'])
if not is_this_snat_agent:
plugin.remove_router_from_l3_agent(
context, agent['id'], router_id)
self.l3_rpc_notifier.router_removed_from_agent(
context, router_id, agent['host'])
is_multiple_prefix_csport = (
self._check_for_multiprefix_csnat_port_and_update(
@ -414,7 +414,7 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
if not routers:
return []
router_ids = [r['id'] for r in routers]
snat_binding = l3_dvrsched_db.CentralizedSnatL3AgentBinding
snat_binding = l3_sched_db.RouterL3AgentBinding
query = (context.session.query(snat_binding).
filter(snat_binding.router_id.in_(router_ids))).all()
bindings = dict((b.router_id, b) for b in query)

View File

@ -13,27 +13,18 @@
# License for the specific language governing permissions and limitations
# under the License.
import random
from oslo_db import exception as db_exc
from oslo_log import log as logging
import sqlalchemy as sa
from sqlalchemy import or_
from sqlalchemy import orm
from sqlalchemy.orm import joinedload
from neutron._i18n import _LI, _LW
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.callbacks import resources
from neutron.common import constants as n_const
from neutron.common import utils as n_utils
from neutron.db import agents_db
from neutron.db import agentschedulers_db
from neutron.db import l3_agentschedulers_db as l3agent_sch_db
from neutron.db import model_base
from neutron.db import models_v2
from neutron.extensions import l3agentscheduler
from neutron.extensions import portbindings
from neutron import manager
from neutron.plugins.common import constants as service_constants
@ -43,24 +34,6 @@ from neutron.plugins.ml2 import models as ml2_models
LOG = logging.getLogger(__name__)
class CentralizedSnatL3AgentBinding(model_base.BASEV2):
"""Represents binding between Neutron Centralized SNAT and L3 agents."""
__tablename__ = "csnat_l3_agent_bindings"
router_id = sa.Column(sa.String(36),
sa.ForeignKey("routers.id", ondelete='CASCADE'),
primary_key=True)
l3_agent_id = sa.Column(sa.String(36),
sa.ForeignKey("agents.id", ondelete='CASCADE'),
primary_key=True)
host_id = sa.Column(sa.String(255))
csnat_gw_port_id = sa.Column(sa.String(36),
sa.ForeignKey('ports.id', ondelete='CASCADE'))
l3_agent = orm.relationship(agents_db.Agent)
csnat_gw_port = orm.relationship(models_v2.Port)
class L3_DVRsch_db_mixin(l3agent_sch_db.L3AgentSchedulerDbMixin):
"""Mixin class for L3 DVR scheduler.
@ -117,18 +90,12 @@ class L3_DVRsch_db_mixin(l3agent_sch_db.L3AgentSchedulerDbMixin):
ips = port['fixed_ips']
router_ids = self.get_dvr_routers_by_portid(context, port['id'], ips)
if not router_ids:
return
for router_id in router_ids:
if not self.check_l3_agent_router_binding(
context, router_id, l3_agent_on_host['id']):
self.schedule_router(
context, router_id, candidates=[l3_agent_on_host])
LOG.debug('DVR: Handle new service_port on router: %s', router_id)
self.l3_rpc_notifier.routers_updated_on_host(
context, router_ids, port_host)
if router_ids:
LOG.debug('DVR: Handle new service port, host %(host)s, '
'router ids %(router_ids)s',
{'host': port_host, 'router_ids': router_ids})
self.l3_rpc_notifier.routers_updated_on_host(
context, router_ids, port_host)
def get_dvr_routers_by_portid(self, context, port_id, fixed_ips=None):
"""Gets the dvr routers on vmport subnets."""
@ -187,7 +154,7 @@ class L3_DVRsch_db_mixin(l3agent_sch_db.L3AgentSchedulerDbMixin):
removed_router_info = []
for router_id in router_ids:
snat_binding = context.session.query(
CentralizedSnatL3AgentBinding).filter_by(
l3agent_sch_db.RouterL3AgentBinding).filter_by(
router_id=router_id).filter_by(
l3_agent_id=agent.id).first()
if snat_binding:
@ -220,183 +187,6 @@ class L3_DVRsch_db_mixin(l3agent_sch_db.L3AgentSchedulerDbMixin):
info)
return removed_router_info
def bind_snat_router(self, context, router_id, chosen_agent):
"""Bind the router to the chosen l3 agent."""
with context.session.begin(subtransactions=True):
binding = CentralizedSnatL3AgentBinding()
binding.l3_agent = chosen_agent
binding.router_id = router_id
context.session.add(binding)
LOG.debug('SNAT Router %(router_id)s is scheduled to L3 agent '
'%(agent_id)s', {'router_id': router_id,
'agent_id': chosen_agent.id})
def bind_dvr_router_servicenode(self, context, router_id,
chosen_snat_agent):
"""Bind the IR router to service node if not already hosted."""
query = (context.session.query(l3agent_sch_db.RouterL3AgentBinding).
filter_by(router_id=router_id))
for bind in query:
if bind.l3_agent_id == chosen_snat_agent.id:
LOG.debug('Distributed Router %(router_id)s already hosted '
'on snat l3_agent %(snat_id)s',
{'router_id': router_id,
'snat_id': chosen_snat_agent.id})
return
with context.session.begin(subtransactions=True):
binding = l3agent_sch_db.RouterL3AgentBinding()
binding.l3_agent = chosen_snat_agent
binding.router_id = router_id
context.session.add(binding)
LOG.debug('Binding the distributed router %(router_id)s to '
'the snat agent %(snat_id)s',
{'router_id': router_id,
'snat_id': chosen_snat_agent.id})
def bind_snat_servicenode(self, context, router_id, snat_candidates):
"""Bind the snat router to the chosen l3 service agent."""
chosen_snat_agent = random.choice(snat_candidates)
self.bind_snat_router(context, router_id, chosen_snat_agent)
return chosen_snat_agent
def unbind_snat(self, context, router_id, agent_id=None):
"""Unbind snat from the chosen l3 service agent.
Unbinds from all L3 agents 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)
binding = query.first()
if not binding:
LOG.debug('no SNAT router binding found for router: '
'%(router)s, agent: %(agent)s',
{'router': router_id, 'agent': agent_id or 'any'})
return
query.delete()
LOG.debug('Deleted binding of the SNAT router %s', router_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."""
if not router_ids:
return []
query = context.session.query(CentralizedSnatL3AgentBinding)
query = query.options(joinedload('l3_agent')).filter(
CentralizedSnatL3AgentBinding.router_id.in_(router_ids))
return query.all()
def get_snat_candidates(self, sync_router, l3_agents):
"""Get the valid snat enabled l3 agents for the distributed router."""
candidates = []
is_router_distributed = sync_router.get('distributed', False)
if not is_router_distributed:
return candidates
for l3_agent in l3_agents:
if not l3_agent.admin_state_up:
continue
agent_conf = self.get_configuration_dict(l3_agent)
agent_mode = agent_conf.get(n_const.L3_AGENT_MODE,
n_const.L3_AGENT_MODE_LEGACY)
if agent_mode != n_const.L3_AGENT_MODE_DVR_SNAT:
continue
router_id = agent_conf.get('router_id', None)
if router_id and router_id != sync_router['id']:
continue
handle_internal_only_routers = agent_conf.get(
'handle_internal_only_routers', True)
gateway_external_network_id = agent_conf.get(
'gateway_external_network_id', None)
ex_net_id = (sync_router['external_gateway_info'] or {}).get(
'network_id')
if ((not ex_net_id and not handle_internal_only_routers) or
(ex_net_id and gateway_external_network_id and
ex_net_id != gateway_external_network_id)):
continue
candidates.append(l3_agent)
return candidates
def schedule_snat_router(self, context, router_id, sync_router):
"""Schedule the snat router on l3 service agent."""
active_l3_agents = self.get_l3_agents(context, active=True)
if not active_l3_agents:
LOG.warn(_LW('No active L3 agents found for SNAT'))
return
snat_candidates = self.get_snat_candidates(sync_router,
active_l3_agents)
if not snat_candidates:
LOG.warn(_LW('No candidates found for SNAT'))
return
else:
try:
chosen_agent = self.bind_snat_servicenode(
context, router_id, snat_candidates)
except db_exc.DBDuplicateEntry:
LOG.info(_LI("SNAT already bound to a service node."))
return
self.bind_dvr_router_servicenode(
context, router_id, chosen_agent)
return chosen_agent
def _unschedule_router(self, context, router_id, agents_ids):
router = self.get_router(context, router_id)
if router.get('distributed', False):
# for DVR router unscheduling means just unscheduling SNAT portion
self.unbind_snat_servicenode(context, router_id)
else:
super(L3_DVRsch_db_mixin, self)._unschedule_router(
context, router_id, agents_ids)
def _get_active_l3_agent_routers_sync_data(self, context, host, agent,
router_ids):
if n_utils.is_extension_supported(self, n_const.L3_HA_MODE_EXT_ALIAS):
@ -406,52 +196,6 @@ 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):
binding = None
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)
# binding only exists when agent mode is dvr_snat
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)
# Below Needs to be done when agent mode is legacy or dvr.
if not binding:
super(L3_DVRsch_db_mixin,
self).remove_router_from_l3_agent(
context, agent_id, router_id)
def get_hosts_to_notify(self, context, router_id):
"""Returns all hosts to send notification about router update"""
hosts = super(L3_DVRsch_db_mixin, self).get_hosts_to_notify(
@ -570,12 +314,9 @@ def _notify_port_delete(event, resource, trigger, **kwargs):
l3plugin = manager.NeutronManager.get_service_plugins().get(
service_constants.L3_ROUTER_NAT)
l3plugin.delete_arp_entry_for_dvr_service_port(context, port)
for router in removed_routers:
# we need admin context in case a tenant removes the last dvr
# serviceable port on a shared network owned by admin, where router
# is also owned by admin
l3plugin.remove_router_from_l3_agent(
context.elevated(), router['agent_id'], router['router_id'])
for info in removed_routers:
l3plugin.l3_rpc_notifier.router_removed_from_agent(
context, info['router_id'], info['host'])
def _notify_l3_agent_port_update(resource, event, trigger, **kwargs):

View File

@ -1 +1 @@
8a6d8bdae39
2b4c2465d44b

View File

@ -0,0 +1,76 @@
# Copyright 2015 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""DVR sheduling refactoring
Revision ID: 2b4c2465d44b
Revises: 8a6d8bdae39
Create Date: 2015-12-23 07:39:49.062767
"""
# revision identifiers, used by Alembic.
revision = '2b4c2465d44b'
down_revision = '8a6d8bdae39'
from alembic import op
import sqlalchemy as sa
ROUTER_ATTR_TABLE = 'router_extra_attributes'
ROUTER_BINDING_TABLE = 'routerl3agentbindings'
CSNAT_BINDING_TABLE = 'csnat_l3_agent_bindings'
def upgrade():
transfer_snat_bindings()
op.drop_table(CSNAT_BINDING_TABLE)
def transfer_snat_bindings():
router_attr_table = sa.Table(ROUTER_ATTR_TABLE,
sa.MetaData(),
sa.Column('router_id', sa.String(36)),
sa.Column('distributed', sa.Boolean),)
csnat_binding = sa.Table(CSNAT_BINDING_TABLE,
sa.MetaData(),
sa.Column('router_id', sa.String(36)),
sa.Column('l3_agent_id', sa.String(36)))
router_binding = sa.Table(ROUTER_BINDING_TABLE,
sa.MetaData(),
sa.Column('router_id', sa.String(36)),
sa.Column('l3_agent_id', sa.String(36)))
session = sa.orm.Session(bind=op.get_bind())
with session.begin(subtransactions=True):
# first delete all bindings for dvr routers from
# routerl3agentbindings as this might be bindings with l3 agents
# on compute nodes
for router_attr in session.query(
router_attr_table).filter(router_attr_table.c.distributed):
session.execute(router_binding.delete(
router_binding.c.router_id == router_attr.router_id))
# now routerl3agentbindings will only contain bindings for snat
# portion of the router
for csnat_binding in session.query(csnat_binding):
session.execute(
router_binding.insert().values(
router_id=csnat_binding.router_id,
l3_agent_id=csnat_binding.l3_agent_id))
# this commit is necessary to allow further operations
session.commit()

View File

@ -173,8 +173,7 @@ class L3Scheduler(object):
# active any time
current_l3_agents = plugin.get_l3_agents_hosting_routers(
context, [sync_router['id']], admin_state_up=True)
is_router_distributed = sync_router.get('distributed', False)
if current_l3_agents and not is_router_distributed:
if current_l3_agents:
LOG.debug('Router %(router_id)s has already been hosted '
'by L3 agent %(agent_id)s',
{'router_id': sync_router['id'],
@ -185,16 +184,14 @@ class L3Scheduler(object):
if not active_l3_agents:
LOG.warn(_LW('No active L3 agents'))
return []
potential_candidates = list(
set(active_l3_agents) - set(current_l3_agents))
new_l3agents = []
if potential_candidates:
new_l3agents = plugin.get_l3_agent_candidates(
context, sync_router, potential_candidates)
if not new_l3agents:
LOG.warn(_LW('No L3 agents can host the router %s'),
sync_router['id'])
return new_l3agents
candidates = plugin.get_l3_agent_candidates(context,
sync_router,
active_l3_agents)
if not candidates:
LOG.warn(_LW('No L3 agents can host the router %s'),
sync_router['id'])
return candidates
def _bind_routers(self, context, plugin, routers, l3_agent):
for router in routers:
@ -235,26 +232,7 @@ class L3Scheduler(object):
sync_router = plugin.get_router(context, router_id)
candidates = candidates or self._get_candidates(
plugin, context, sync_router)
chosen_agent = None
if sync_router.get('distributed', False):
for chosen_agent in candidates:
self.bind_router(context, router_id, chosen_agent)
# For Distributed routers check for SNAT Binding before
# calling the schedule_snat_router
snat_bindings = plugin.get_snat_bindings(context, [router_id])
router_gw_exists = sync_router.get('external_gateway_info', False)
if not snat_bindings and router_gw_exists:
# If GW exists for DVR routers and no SNAT binding
# call the schedule_snat_router
chosen_agent = plugin.schedule_snat_router(
context, router_id, sync_router)
elif not router_gw_exists and snat_bindings:
# If DVR router and no Gateway but SNAT Binding exists then
# call the unbind_snat_servicenode to unbind the snat service
# from agent
plugin.unbind_snat_servicenode(context, router_id)
elif not candidates:
if not candidates:
return
elif sync_router.get('ha', False):
chosen_agents = self._bind_ha_router(plugin, context,

View File

@ -455,10 +455,10 @@ class L3DvrTestCase(ml2_test_base.ML2TestFramework):
def test_update_vm_port_host_router_update(self):
# register l3 agents in dvr mode in addition to existing dvr_snat agent
HOST1 = 'host1'
dvr_agent1 = helpers.register_l3_agent(
helpers.register_l3_agent(
host=HOST1, agent_mode=constants.L3_AGENT_MODE_DVR)
HOST2 = 'host2'
dvr_agent2 = helpers.register_l3_agent(
helpers.register_l3_agent(
host=HOST2, agent_mode=constants.L3_AGENT_MODE_DVR)
router = self._create_router()
with self.subnet() as subnet:
@ -466,12 +466,6 @@ class L3DvrTestCase(ml2_test_base.ML2TestFramework):
self.context, router['id'],
{'subnet_id': subnet['subnet']['id']})
# since there are no vm ports on HOST, and the router
# has no external gateway at this point the router
# should neither be scheduled to dvr nor to dvr_snat agents
agents = self.l3_plugin.list_l3_agents_hosting_router(
self.context, router['id'])['agents']
self.assertEqual(0, len(agents))
with mock.patch.object(self.l3_plugin,
'_l3_rpc_notifier') as l3_notifier,\
self.port(subnet=subnet,
@ -482,12 +476,6 @@ class L3DvrTestCase(ml2_test_base.ML2TestFramework):
self.context, port['port']['id'],
{'port': {portbindings.HOST_ID: HOST1}})
# now router should be scheduled to agent on HOST1
agents = self.l3_plugin.list_l3_agents_hosting_router(
self.context, router['id'])['agents']
self.assertEqual(1, len(agents))
self.assertEqual(dvr_agent1['id'], agents[0]['id'])
# and notification should only be sent to the agent on HOST1
l3_notifier.routers_updated_on_host.assert_called_once_with(
self.context, {router['id']}, HOST1)
self.assertFalse(l3_notifier.routers_updated.called)
@ -497,11 +485,7 @@ class L3DvrTestCase(ml2_test_base.ML2TestFramework):
self.core_plugin.update_port(
self.context, port['port']['id'],
{'port': {portbindings.HOST_ID: HOST2}})
# now router should only be scheduled to dvr agent on host2
agents = self.l3_plugin.list_l3_agents_hosting_router(
self.context, router['id'])['agents']
self.assertEqual(1, len(agents))
self.assertEqual(dvr_agent2['id'], agents[0]['id'])
l3_notifier.routers_updated_on_host.assert_called_once_with(
self.context, {router['id']}, HOST2)
l3_notifier.router_removed_from_agent.assert_called_once_with(
@ -512,7 +496,7 @@ class L3DvrTestCase(ml2_test_base.ML2TestFramework):
# register l3 agent in dvr mode in addition to existing dvr_snat agent
HOST = 'host1'
non_admin_tenant = 'tenant1'
dvr_agent = helpers.register_l3_agent(
helpers.register_l3_agent(
host=HOST, agent_mode=constants.L3_AGENT_MODE_DVR)
router = self._create_router()
with self.network(shared=True) as net,\
@ -528,23 +512,11 @@ class L3DvrTestCase(ml2_test_base.ML2TestFramework):
self.context, router['id'],
{'subnet_id': subnet['subnet']['id']})
# router should be scheduled to agent on HOST
agents = self.l3_plugin.list_l3_agents_hosting_router(
self.context, router['id'])
self.assertEqual(1, len(agents['agents']))
self.assertEqual(dvr_agent['id'], agents['agents'][0]['id'])
notifier = self.l3_plugin.agent_notifiers[
constants.AGENT_TYPE_L3]
with mock.patch.object(
notifier, 'router_removed_from_agent') as remove_mock:
with mock.patch.object(self.l3_plugin.l3_rpc_notifier,
'router_removed_from_agent') as remove_mock:
ctx = context.Context(
'', non_admin_tenant) if non_admin_port else self.context
self._delete('ports', port['port']['id'], neutron_context=ctx)
# now when port is deleted the router should be unscheduled
agents = self.l3_plugin.list_l3_agents_hosting_router(
self.context, router['id'])
self.assertEqual(0, len(agents['agents']))
remove_mock.assert_called_once_with(
mock.ANY, router['id'], HOST)
@ -656,7 +628,6 @@ class L3DvrTestCase(ml2_test_base.ML2TestFramework):
router = self._create_router()
kwargs = {'arg_list': (external_net.EXTERNAL,),
external_net.EXTERNAL: True}
host = self.l3_agent['host']
with self.subnet() as subnet,\
self.network(**kwargs) as ext_net,\
self.subnet(network=ext_net, cidr='20.0.0.0/24'):
@ -670,9 +641,6 @@ class L3DvrTestCase(ml2_test_base.ML2TestFramework):
agents = self.l3_plugin.list_l3_agents_hosting_router(
self.context, router['id'])
self.assertEqual(1, len(agents['agents']))
csnat_agent_host = self.l3_plugin.get_snat_bindings(
self.context, [router['id']])[0]['l3_agent']['host']
self.assertEqual(host, csnat_agent_host)
with mock.patch.object(self.l3_plugin,
'_l3_rpc_notifier') as l3_notifier:
self.l3_plugin.remove_router_interface(
@ -706,9 +674,6 @@ class L3DvrTestCase(ml2_test_base.ML2TestFramework):
{'subnet_id': subnet['subnet']['id']})
# router should be scheduled to the dvr_snat l3 agent
csnat_agent_host = self.l3_plugin.get_snat_bindings(
self.context, [router['id']])[0]['l3_agent']['host']
self.assertEqual(self.l3_agent['host'], csnat_agent_host)
agents = self.l3_plugin.list_l3_agents_hosting_router(
self.context, router['id'])
self.assertEqual(1, len(agents['agents']))
@ -910,7 +875,7 @@ class L3DvrTestCase(ml2_test_base.ML2TestFramework):
def test_remove_router_interface(self):
HOST1 = 'host1'
dvr_agent = helpers.register_l3_agent(
helpers.register_l3_agent(
host=HOST1, agent_mode=constants.L3_AGENT_MODE_DVR)
router = self._create_router()
arg_list = (portbindings.HOST_ID,)
@ -929,18 +894,9 @@ class L3DvrTestCase(ml2_test_base.ML2TestFramework):
{'subnet_id': subnet['subnet']['id']})
self.l3_plugin.schedule_router(self.context, router['id'])
# router should be scheduled to the agent on HOST1
agents = self.l3_plugin.list_l3_agents_hosting_router(
self.context, router['id'])['agents']
self.assertEqual(1, len(agents))
self.assertEqual(dvr_agent['id'], agents[0]['id'])
self.l3_plugin.remove_router_interface(
self.context, router['id'],
{'subnet_id': subnet['subnet']['id']})
agents = self.l3_plugin.list_l3_agents_hosting_router(
self.context, router['id'])['agents']
self.assertEqual(0, len(agents))
l3_notifier.router_removed_from_agent.assert_called_once_with(
self.context, router['id'], HOST1)

View File

@ -774,47 +774,6 @@ class OvsAgentSchedulerTestCase(OvsAgentSchedulerTestCaseBase):
ret_b = l3_rpc_cb.sync_routers(self.adminContext, host=L3_HOSTB)
self.assertFalse(ret_b)
def test_router_is_not_rescheduled_from_dvr_agent(self):
with self.subnet() as s, \
mock.patch.object(
self.l3plugin,
'check_dvr_serviceable_ports_on_host') as port_exists:
net_id = s['subnet']['network_id']
self._set_net_external(net_id)
router = {'name': 'router1',
'admin_state_up': True,
'tenant_id': 'tenant_id',
'external_gateway_info': {'network_id': net_id},
'distributed': True}
r = self.l3plugin.create_router(
self.adminContext, {'router': router})
dvr_snat_agent, dvr_agent = self._register_dvr_agents()
port_exists.return_value = True
self.l3plugin.schedule_router(
self.adminContext, r['id'])
agents = self._list_l3_agents_hosting_router(r['id'])
self.assertEqual(2, len(agents['agents']))
self.assertIn(dvr_agent['host'],
[a['host'] for a in agents['agents']])
# router should not be unscheduled from dvr agent
self._take_down_agent_and_run_reschedule(dvr_agent['host'])
agents = self._list_l3_agents_hosting_router(r['id'])
self.assertEqual(2, len(agents['agents']))
self.assertIn(dvr_agent['host'],
[a['host'] for a in agents['agents']])
# another dvr_snat agent is needed to test that router is not
# unscheduled from dead dvr agent in case rescheduling between
# dvr_snat agents happens
helpers.register_l3_agent(
host='hostC', agent_mode=constants.L3_AGENT_MODE_DVR_SNAT)
self._take_down_agent_and_run_reschedule(dvr_snat_agent['host'])
agents = self._list_l3_agents_hosting_router(r['id'])
self.assertEqual(2, len(agents['agents']))
self.assertIn(dvr_agent['host'],
[a['host'] for a in agents['agents']])
def test_router_reschedule_succeeded_after_failed_notification(self):
l3_plugin = (manager.NeutronManager.get_service_plugins()
[service_constants.L3_ROUTER_NAT])
@ -1080,7 +1039,7 @@ class OvsAgentSchedulerTestCase(OvsAgentSchedulerTestCaseBase):
self._delete('routers', router['router']['id'])
self.assertEqual(0, len(l3agents))
def test_dvr_router_scheduling_to_all_needed_agents(self):
def test_dvr_router_scheduling_to_only_dvr_snat_agent(self):
self._register_dvr_agents()
with self.subnet() as s:
net_id = s['subnet']['network_id']
@ -1102,53 +1061,10 @@ class OvsAgentSchedulerTestCase(OvsAgentSchedulerTestCaseBase):
self.adminContext, r['id'])
l3agents = self._list_l3_agents_hosting_router(r['id'])
self.assertEqual(2, len(l3agents['agents']))
self.assertEqual({'dvr', 'dvr_snat'},
set([a['configurations']['agent_mode'] for a in
l3agents['agents']]))
def test_dvr_router_snat_scheduling_late_ext_gw_add(self):
"""Test snat scheduling for the case when dvr router is already
scheduled to all dvr_snat agents and then external gateway is added.
"""
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_int,\
self.subnet(cidr='20.0.0.0/24') as s_ext:
net_id = s_ext['subnet']['network_id']
self._set_net_external(net_id)
router = {'name': 'router1',
'tenant_id': 'tenant_id',
'admin_state_up': True,
'distributed': True}
r = self.l3plugin.create_router(self.adminContext,
{'router': router})
# add router interface first
self.l3plugin.add_router_interface(self.adminContext, r['id'],
{'subnet_id': s_int['subnet']['id']})
# Check if the router is not scheduled to any of the agents
l3agents = self._list_l3_agents_hosting_router(r['id'])
self.assertEqual(0, len(l3agents['agents']))
# check that snat is not scheduled as router is not connected to
# external network
snat_agents = self.l3plugin.get_snat_bindings(
self.adminContext, [r['id']])
self.assertEqual(0, len(snat_agents))
# connect router to external network
self.l3plugin.update_router(self.adminContext, r['id'],
{'router': {'external_gateway_info': {'network_id': net_id}}})
# router should still be scheduled to one of the dvr_snat agents
l3agents = self._list_l3_agents_hosting_router(r['id'])
self.assertEqual(1, len(l3agents['agents']))
# now snat portion should be scheduled as router is connected
# to external network
snat_agents = self.l3plugin.get_snat_bindings(
self.adminContext, [r['id']])
self.assertEqual(1, len(snat_agents))
self.assertEqual(1, len(l3agents['agents']))
agent = l3agents['agents'][0]
self.assertEqual('dvr_snat',
agent['configurations']['agent_mode'])
def test_dvr_router_csnat_rescheduling(self):
helpers.register_l3_agent(
@ -1170,16 +1086,14 @@ class OvsAgentSchedulerTestCase(OvsAgentSchedulerTestCaseBase):
self.adminContext, r['id'])
l3agents = self._list_l3_agents_hosting_router(r['id'])
self.assertEqual(1, len(l3agents['agents']))
csnat_agent_host = self.l3plugin.get_snat_bindings(
self.adminContext, [r['id']])[0]['l3_agent']['host']
self._take_down_agent_and_run_reschedule(csnat_agent_host)
agent_host = l3agents['agents'][0]['host']
self._take_down_agent_and_run_reschedule(agent_host)
l3agents = self._list_l3_agents_hosting_router(r['id'])
self.assertEqual(1, len(l3agents['agents']))
new_csnat_agent_host = self.l3plugin.get_snat_bindings(
self.adminContext, [r['id']])[0]['l3_agent']['host']
self.assertNotEqual(csnat_agent_host, new_csnat_agent_host)
new_agent_host = l3agents['agents'][0]['host']
self.assertNotEqual(agent_host, new_agent_host)
def test_dvr_router_csnat_manual_rescheduling(self):
def test_dvr_router_manual_rescheduling(self):
helpers.register_l3_agent(
host=L3_HOSTA, agent_mode=constants.L3_AGENT_MODE_DVR_SNAT)
helpers.register_l3_agent(
@ -1200,29 +1114,25 @@ class OvsAgentSchedulerTestCase(OvsAgentSchedulerTestCaseBase):
l3agents = self.l3plugin.list_l3_agents_hosting_router(
self.adminContext, r['id'])
self.assertEqual(1, len(l3agents['agents']))
csnat_agent = self.l3plugin.get_snat_bindings(
self.adminContext, [r['id']])[0]['l3_agent']
agent = l3agents['agents'][0]
# NOTE: Removing the router from the l3_agent will
# remove all the namespace since there is no other
# serviceable ports in the node that requires it.
self.l3plugin.remove_router_from_l3_agent(
self.adminContext, csnat_agent['id'], r['id'])
self.adminContext, agent['id'], r['id'])
l3agents = self.l3plugin.list_l3_agents_hosting_router(
self.adminContext, r['id'])
self.assertEqual(0, 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'])
self.adminContext, agent['id'], r['id'])
l3agents = self.l3plugin.list_l3_agents_hosting_router(
self.adminContext, r['id'])
self.assertEqual(1, 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'])
new_agent = l3agents['agents'][0]
self.assertEqual(agent['id'], new_agent['id'])
def test_router_sync_data(self):
with self.subnet() as s1,\

View File

@ -23,7 +23,6 @@ from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_utils import importutils
from oslo_utils import timeutils
from sqlalchemy.orm import query
import testscenarios
from neutron.common import constants
@ -320,28 +319,12 @@ class L3SchedulerBaseTestCase(base.BaseTestCase):
self):
plugin = mock.MagicMock()
# distributed router already hosted
plugin.get_l3_agents_hosting_routers.return_value = ['a1']
plugin.get_l3_agents_hosting_routers.return_value = [{'id': 'a1'}]
router = {'distributed': True, 'id': str(uuid.uuid4())}
plugin.get_l3_agents.return_value = ['a1']
self.scheduler._get_candidates(plugin, mock.MagicMock(), router)
self.assertFalse(plugin.get_l3_agent_candidates.called)
def test__get_candidates_calls_get_l3_agent_candidates_if_agent_available(
self):
plugin = mock.MagicMock()
# distributed router already hosted in two agent 'a1' and 'a2'
plugin.get_l3_agents_hosting_routers.return_value = ['a1', 'a2']
router = {'distributed': True, 'id': str(uuid.uuid4())}
# Available distributed agents
plugin.get_l3_agents.return_value = ['a1', 'a2', 'a3', 'a4', 'a5']
unscheduled_agents = ['a3', 'a4', 'a5']
plugin.get_l3_agent_candidates.return_value = ['a3', 'a4']
agents_returned = self.scheduler._get_candidates(
plugin, mock.MagicMock(), router)
plugin.get_l3_agent_candidates.called_once_with(
mock.ANY, router, unscheduled_agents)
self.assertEqual(['a3', 'a4'], sorted(agents_returned))
class L3SchedulerBaseMixin(object):
@ -558,26 +541,6 @@ class L3SchedulerTestBaseMixin(object):
]
plugin.assert_has_calls(expected_calls)
def test_schedule_dvr_router_with_snatbinding_no_gw(self):
scheduler, agent, plugin = self._prepare_schedule_dvr_tests()
sync_router = {'id': 'foo_router_id',
'distributed': True}
plugin.get_router.return_value = sync_router
with mock.patch.object(
plugin, 'get_snat_bindings', return_value=True),\
mock.patch.object(scheduler, 'bind_router'):
scheduler._schedule_router(
plugin, self.adminContext, 'foo_router_id', None)
expected_calls = [
mock.call.get_router(mock.ANY, 'foo_router_id'),
mock.call.get_l3_agents_hosting_routers(
mock.ANY, ['foo_router_id'], admin_state_up=True),
mock.call.get_l3_agents(mock.ANY, active=True),
mock.call.get_l3_agent_candidates(mock.ANY, sync_router, [agent]),
mock.call.unbind_snat_servicenode(mock.ANY, 'foo_router_id')
]
plugin.assert_has_calls(expected_calls)
def test_schedule_router_distributed(self):
scheduler, agent, plugin = self._prepare_schedule_dvr_tests()
sync_router = {
@ -589,21 +552,18 @@ class L3SchedulerTestBaseMixin(object):
}
}
plugin.get_router.return_value = sync_router
with mock.patch.object(
plugin, 'get_snat_bindings', return_value=False),\
mock.patch.object(scheduler, 'bind_router'):
with mock.patch.object(scheduler, 'bind_router'):
scheduler._schedule_router(
plugin, self.adminContext, 'foo_router_id', None)
expected_calls = [
mock.call.get_router(mock.ANY, 'foo_router_id'),
mock.call.get_l3_agents_hosting_routers(
mock.ANY, ['foo_router_id'], admin_state_up=True),
mock.call.get_l3_agents(mock.ANY, active=True),
mock.call.get_l3_agent_candidates(mock.ANY, sync_router, [agent]),
mock.call.schedule_snat_router(
mock.ANY, 'foo_router_id', sync_router),
]
plugin.assert_has_calls(expected_calls)
expected_calls = [
mock.call.get_router(mock.ANY, 'foo_router_id'),
mock.call.get_l3_agents_hosting_routers(
mock.ANY, ['foo_router_id'], admin_state_up=True),
mock.call.get_l3_agents(mock.ANY, active=True),
mock.call.get_l3_agent_candidates(mock.ANY, sync_router,
[agent]),
]
plugin.assert_has_calls(expected_calls)
def _test_schedule_bind_router(self, agent, router):
ctx = self.adminContext
@ -674,11 +634,11 @@ class L3SchedulerTestBaseMixin(object):
router['external_gateway_info'] = None
router['id'] = str(uuid.uuid4())
agent_list = [self.agent1, self.l3_dvr_agent]
# test dvr agent_mode case only dvr agent should be candidate
# test dvr agent_mode case no candidates
router['distributed'] = True
self.get_subnet_ids_on_router = mock.Mock()
self.check_dvr_serviceable_ports_on_host = mock.Mock(return_value=True)
self._check_get_l3_agent_candidates(router, agent_list, HOST_DVR)
self._check_get_l3_agent_candidates(router, agent_list, None, count=0)
def test_get_l3_agent_candidates_dvr_no_vms(self):
self._register_l3_dvr_agents()
@ -726,7 +686,7 @@ class L3SchedulerTestBaseMixin(object):
self.get_subnet_ids_on_router = mock.Mock()
self.check_dvr_serviceable_ports_on_host.return_value = False
self._check_get_l3_agent_candidates(
router, agent_list, HOST_DVR_SNAT, count=0)
router, agent_list, HOST_DVR_SNAT, count=1)
def test_get_l3_agent_candidates_centralized(self):
self._register_l3_dvr_agents()
@ -1038,11 +998,12 @@ class L3DvrSchedulerTestCase(testlib_api.SqlTestCase):
self.assertFalse(l3plugin.dvr_handle_new_service_port.called)
def test__notify_l3_agent_update_port_with_port_binding_change(self):
source_host = 'vm-host1'
kwargs = {
'context': self.adminContext,
'original_port': {
'id': str(uuid.uuid4()),
portbindings.HOST_ID: 'vm-host1',
portbindings.HOST_ID: source_host,
'device_owner': DEVICE_OWNER_COMPUTE,
},
'port': {
@ -1056,11 +1017,12 @@ class L3DvrSchedulerTestCase(testlib_api.SqlTestCase):
return_value={'L3_ROUTER_NAT': l3plugin}),\
mock.patch.object(l3plugin, 'get_dvr_routers_to_remove',
return_value=[{'agent_id': 'foo_agent',
'router_id': 'foo_id'}]):
'router_id': 'foo_id',
'host': source_host}]):
l3_dvrscheduler_db._notify_l3_agent_port_update(
'port', 'after_update', mock.ANY, **kwargs)
l3plugin.remove_router_from_l3_agent.assert_called_once_with(
mock.ANY, 'foo_agent', 'foo_id')
(l3plugin.l3_rpc_notifier.router_removed_from_agent.
assert_called_once_with(mock.ANY, 'foo_id', source_host))
self.assertEqual(
1, l3plugin.update_arp_entry_for_dvr_service_port.call_count)
self.assertEqual(
@ -1070,6 +1032,7 @@ class L3DvrSchedulerTestCase(testlib_api.SqlTestCase):
def test__notify_l3_agent_update_port_removing_routers(self):
port_id = 'fake-port'
source_host = 'vm-host'
kwargs = {
'context': self.adminContext,
'port': {
@ -1081,7 +1044,7 @@ class L3DvrSchedulerTestCase(testlib_api.SqlTestCase):
'mac_address_updated': False,
'original_port': {
'id': port_id,
portbindings.HOST_ID: 'vm-host',
portbindings.HOST_ID: source_host,
'device_id': 'vm-id',
'device_owner': DEVICE_OWNER_COMPUTE
}
@ -1098,7 +1061,8 @@ class L3DvrSchedulerTestCase(testlib_api.SqlTestCase):
return_value={'L3_ROUTER_NAT': l3plugin}),\
mock.patch.object(l3plugin, 'get_dvr_routers_to_remove',
return_value=[{'agent_id': 'foo_agent',
'router_id': 'foo_id'}]):
'router_id': 'foo_id',
'host': source_host}]):
l3_dvrscheduler_db._notify_l3_agent_port_update(
'port', 'after_update', plugin, **kwargs)
@ -1110,8 +1074,8 @@ class L3DvrSchedulerTestCase(testlib_api.SqlTestCase):
self.assertFalse(
l3plugin.dvr_handle_new_service_port.called)
l3plugin.remove_router_from_l3_agent.assert_called_once_with(
mock.ANY, 'foo_agent', 'foo_id')
(l3plugin.l3_rpc_notifier.router_removed_from_agent.
assert_called_once_with(mock.ANY, 'foo_id', source_host))
def test__notify_port_delete(self):
plugin = manager.NeutronManager.get_plugin()
@ -1127,7 +1091,9 @@ class L3DvrSchedulerTestCase(testlib_api.SqlTestCase):
'context': self.adminContext,
'port': mock.ANY,
'removed_routers': [
{'agent_id': 'foo_agent', 'router_id': 'foo_id'},
{'agent_id': 'foo_agent',
'router_id': 'foo_id',
'host': 'foo_host'},
],
}
l3_dvrscheduler_db._notify_port_delete(
@ -1135,8 +1101,8 @@ class L3DvrSchedulerTestCase(testlib_api.SqlTestCase):
l3plugin.delete_arp_entry_for_dvr_service_port.\
assert_called_once_with(
self.adminContext, mock.ANY)
l3plugin.remove_router_from_l3_agent.assert_called_once_with(
mock.ANY, 'foo_agent', 'foo_id')
(l3plugin.l3_rpc_notifier.router_removed_from_agent.
assert_called_once_with(mock.ANY, 'foo_id', 'foo_host'))
def test_dvr_handle_new_service_port(self):
port = {
@ -1278,194 +1244,6 @@ class L3DvrSchedulerTestCase(testlib_api.SqlTestCase):
}
return agent, router
def test_schedule_snat_router_duplicate_entry(self):
self._prepare_schedule_snat_tests()
with mock.patch.object(self.dut, 'get_l3_agents'),\
mock.patch.object(self.dut, 'get_snat_candidates'),\
mock.patch.object(
self.dut,
'bind_snat_servicenode',
side_effect=db_exc.DBDuplicateEntry()) as mock_bind_snat,\
mock.patch.object(
self.dut,
'bind_dvr_router_servicenode') as mock_bind_dvr:
self.dut.schedule_snat_router(self.adminContext, 'foo', 'bar')
self.assertTrue(mock_bind_snat.called)
self.assertFalse(mock_bind_dvr.called)
def test_schedule_snat_router_return_value(self):
agent, router = self._prepare_schedule_snat_tests()
with mock.patch.object(self.dut, 'get_l3_agents'),\
mock.patch.object(
self.dut,
'get_snat_candidates') as mock_snat_canidates,\
mock.patch.object(self.dut,
'bind_snat_servicenode') as mock_bind_snat,\
mock.patch.object(
self.dut,
'bind_dvr_router_servicenode') as mock_bind_dvr:
mock_snat_canidates.return_value = [agent]
mock_bind_snat.return_value = [agent]
mock_bind_dvr.return_value = [agent]
chosen_agent = self.dut.schedule_snat_router(
self.adminContext, 'foo_router_id', router)
self.assertEqual(chosen_agent, [agent])
def test_schedule_router_unbind_snat_servicenode_negativetest(self):
router = {
'id': 'foo_router_id',
'distributed': True
}
with mock.patch.object(self.dut, 'get_router') as mock_rd,\
mock.patch.object(self.dut,
'get_snat_bindings') as mock_snat_bind,\
mock.patch.object(self.dut,
'unbind_snat_servicenode') as mock_unbind:
mock_rd.return_value = router
mock_snat_bind.return_value = False
self.dut.schedule_snat_router(
self.adminContext, 'foo_router_id', router)
self.assertFalse(mock_unbind.called)
def test_schedule_snat_router_with_snat_candidates(self):
agent, router = self._prepare_schedule_snat_tests()
with mock.patch.object(query.Query, 'first') as mock_query,\
mock.patch.object(self.dut, 'get_l3_agents') as mock_agents,\
mock.patch.object(self.dut,
'get_snat_candidates') as mock_candidates,\
mock.patch.object(self.dut, 'get_router') as mock_rd,\
mock.patch.object(self.dut, 'bind_dvr_router_servicenode'),\
mock.patch.object(self.dut,
'bind_snat_servicenode') as mock_bind:
mock_rd.return_value = router
mock_query.return_value = []
mock_agents.return_value = [agent]
mock_candidates.return_value = [agent]
self.dut.schedule_snat_router(
self.adminContext, 'foo_router_id', mock.ANY)
mock_bind.assert_called_once_with(
self.adminContext, 'foo_router_id', [agent])
def test_unbind_snat_servicenode(self):
router_id = 'foo_router_id'
core_plugin = mock.PropertyMock()
type(self.dut)._core_plugin = core_plugin
(self.dut._core_plugin.get_ports_on_host_by_subnet.
return_value) = []
core_plugin.reset_mock()
l3_notifier = mock.PropertyMock()
type(self.dut).l3_rpc_notifier = l3_notifier
binding = l3_dvrscheduler_db.CentralizedSnatL3AgentBinding(
router_id=router_id, l3_agent_id='foo_l3_agent_id',
l3_agent=agents_db.Agent())
with mock.patch.object(query.Query, 'first') as mock_first,\
mock.patch.object(query.Query, 'delete') as mock_delete,\
mock.patch.object(
self.dut,
'get_subnet_ids_on_router') as mock_get_subnets:
mock_first.return_value = binding
mock_get_subnets.return_value = ['foo_subnet_id']
self.dut.unbind_snat_servicenode(self.adminContext, router_id)
mock_get_subnets.assert_called_with(self.adminContext, router_id)
self.assertTrue(mock_delete.call_count)
core_plugin.assert_called_once_with()
l3_notifier.assert_called_once_with()
def _test_remove_router_from_l3_agent_dvr_snat(self, ursn_return):
agent_id = 'dvr_snat_l3_agent_id'
router_id = 'dvr-router-1'
router = {
'id': router_id,
'distributed': True,
'external_gateway_info': {'network_id': str(uuid.uuid4()),
'enable_snat': True}
}
binding = l3_dvrscheduler_db.CentralizedSnatL3AgentBinding(
router_id=router_id, l3_agent_id=agent_id,
l3_agent=agents_db.Agent())
self.dut.l3_rpc_notifier = mock.Mock()
with mock.patch.object(self.dut, 'get_router') as mock_gr,\
mock.patch.object(self.dut, 'unbind_snat') as mock_us,\
mock.patch.object(
self.dut,
'unbind_router_servicenode') as mock_ursn,\
mock.patch('neutron.db.l3_agentschedulers_db.'
'L3AgentSchedulerDbMixin.'
'remove_router_from_l3_agent') as mock_super_rrl3a:
mock_gr.return_value = router
mock_us.return_value = binding
mock_ursn.return_value = ursn_return
self.dut.remove_router_from_l3_agent(self.adminContext,
agent_id,
router_id)
mock_gr.assert_called_once_with(self.adminContext, router_id)
us_params = {'agent_id': agent_id}
mock_us.assert_called_once_with(self.adminContext,
router_id,
**us_params)
mock_ursn.assert_called_once_with(self.adminContext,
router_id,
binding)
self.assertFalse(mock_super_rrl3a.called)
if ursn_return:
routers_updated_params = {'schedule_routers': False}
(self.dut.l3_rpc_notifier.routers_updated.
assert_called_once_with(self.adminContext,
[router_id],
**routers_updated_params))
else:
self.assertFalse(self.dut.l3_rpc_notifier.
routers_updated.called)
def test_remove_router_from_l3_agent_dvr_snat_mode(self):
self._test_remove_router_from_l3_agent_dvr_snat(True)
self._test_remove_router_from_l3_agent_dvr_snat(False)
def test_remove_router_from_l3_agent_dvr_mode(self):
agent_id = 'dvr_l3_agent_id'
router_id = 'dvr-router-1'
router = {
'id': router_id,
'distributed': True,
'external_gateway_info': {'network_id': str(uuid.uuid4()),
'enable_snat': True}
}
self.dut.l3_rpc_notifier = mock.Mock()
with mock.patch.object(self.dut, 'get_router') as mock_gr,\
mock.patch.object(self.dut, 'unbind_snat') as mock_us,\
mock.patch.object(
self.dut,
'unbind_router_servicenode') as mock_ursn,\
mock.patch('neutron.db.l3_agentschedulers_db.'
'L3AgentSchedulerDbMixin.'
'remove_router_from_l3_agent') as mock_super_rrl3a:
mock_gr.return_value = router
mock_us.return_value = None
mock_ursn.return_value = True
self.dut.remove_router_from_l3_agent(self.adminContext,
agent_id,
router_id)
mock_gr.assert_called_once_with(self.adminContext, router_id)
us_params = {'agent_id': agent_id}
mock_us.assert_called_once_with(self.adminContext,
router_id,
**us_params)
self.assertFalse(self.dut.l3_rpc_notifier.routers_updated.called)
self.assertFalse(mock_ursn.called)
mock_super_rrl3a.assert_called_with(self.adminContext,
agent_id,
router_id)
class L3HAPlugin(db_v2.NeutronDbPluginV2,
l3_hamode_db.L3_HA_NAT_db_mixin,