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:
parent
9546ee7d92
commit
0496f95a13
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -1 +1 @@
|
|||
8a6d8bdae39
|
||||
2b4c2465d44b
|
||||
|
|
|
@ -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()
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,\
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue