Only schedule routers from drivers that need it

This adjusts the L3 scheduler framework to ask the L3 plugin if
a given router should be scheduled before scheduling it. To maintain
backwards compatibility, this new method is implemented to return True
in the base class.

The L3 plugin with flavor support overrides this to lookup the driver
associated with a router and check if the driver requires the L3
scheduling framework. This will allows the coexistence of flavors that
needs scheduling and flavors that don't.

Change-Id: I17a64c59eaf5d8605ff8ec2a29e491673be960e7
Implements: blueprint multi-l3-backends
This commit is contained in:
Kevin Benton 2016-08-20 01:56:13 -07:00
parent 5f849165a6
commit b5fe13afa8
13 changed files with 95 additions and 2 deletions

View File

@ -204,6 +204,9 @@ class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
def add_router_to_l3_agent(self, context, agent_id, router_id):
"""Add a l3 agent to host a router."""
if not self.router_supports_scheduling(context, router_id):
raise l3agentscheduler.RouterDoesntSupportScheduling(
router_id=router_id)
with context.session.begin(subtransactions=True):
router = self.get_router(context, router_id)
agent = self._get_agent(context, agent_id)

View File

@ -348,6 +348,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
# implementation of l3 services
return
if not l3_plugin.router_supports_scheduling(context, router_id):
return
cur_agents = l3_plugin.list_l3_agents_hosting_router(
context, router_id)['agents']
for agent in cur_agents:

View File

@ -184,6 +184,10 @@ class DVRL3CannotRemoveFromDvrAgent(exceptions.Conflict):
"an agent in 'dvr' mode.")
class RouterDoesntSupportScheduling(exceptions.Conflict):
message = _("Router %(router_id)s does not support agent scheduling.")
@six.add_metaclass(abc.ABCMeta)
class L3AgentSchedulerPluginBase(object):
"""REST API to operate the l3 agent scheduler.
@ -207,6 +211,10 @@ class L3AgentSchedulerPluginBase(object):
def list_l3_agents_hosting_router(self, context, router_id):
pass
def router_supports_scheduling(self, context, router_id):
"""Override this method to conditionally schedule routers."""
return True
def notify(context, action, router_id, agent_id):
info = {'id': agent_id, 'router_id': router_id}

View File

@ -108,9 +108,11 @@ class L3Scheduler(object):
filters = {'id': router_ids,
'status': [constants.ROUTER_STATUS_ACTIVE]}
routers = plugin.get_routers(context, filters=filters)
return self._filter_unscheduled_routers(context, plugin, routers)
result = self._filter_unscheduled_routers(context, plugin, routers)
else:
return self._get_unscheduled_routers(context, plugin)
result = self._get_unscheduled_routers(context, plugin)
return [r for r in result
if plugin.router_supports_scheduling(context, r['id'])]
def _get_routers_can_schedule(self, context, plugin, routers, l3_agent):
"""Get the subset of routers that can be scheduled on the L3 agent."""
@ -223,6 +225,8 @@ class L3Scheduler(object):
def _schedule_router(self, plugin, context, router_id,
candidates=None):
if not plugin.router_supports_scheduling(context, router_id):
return
sync_router = plugin.get_router(context, router_id)
candidates = candidates or self._get_candidates(
plugin, context, sync_router)

View File

@ -103,6 +103,9 @@ class L3RouterPlugin(service_base.ServicePluginBase,
" between (L2) Neutron networks and access to external"
" networks via a NAT gateway.")
def router_supports_scheduling(self, context, router_id):
return self.l3_driver_controller.uses_scheduler(context, router_id)
def create_floatingip(self, context, floatingip):
"""Create floating IP.

View File

@ -46,10 +46,15 @@ class L3ServiceProvider(object):
The 'ha' and 'distributed' attributes below are used to determine if a
router request with the 'ha' or 'distributed' attribute can be supported
by this particular driver. These attributes must be present.
The 'use_integrated_agent_scheduler' flag indicates whether or not routers
which belong to the driver should be automatically scheduled using the L3
agent scheduler integrated into Neutron.
"""
ha_support = UNSUPPORTED
distributed_support = UNSUPPORTED
use_integrated_agent_scheduler = False
def __init__(self, l3plugin):
self.l3plugin = l3plugin

View File

@ -185,6 +185,11 @@ class DriverController(object):
"distributed=%(d)s and ha=%(h)s") % {'d': distributed, 'h': ha}
)
def uses_scheduler(self, context, router_id):
"""Returns True if the integrated L3 scheduler should be used."""
return (self._get_provider_for_router(context, router_id).
use_integrated_agent_scheduler)
class _LegacyPlusProviderConfiguration(
provider_configuration.ProviderConfiguration):

View File

@ -17,3 +17,4 @@ from neutron.services.l3_router.service_providers import base
class DvrDriver(base.L3ServiceProvider):
distributed_support = base.MANDATORY
use_integrated_agent_scheduler = True

View File

@ -17,3 +17,4 @@ from neutron.services.l3_router.service_providers import base
class HaDriver(base.L3ServiceProvider):
ha_support = base.MANDATORY
use_integrated_agent_scheduler = True

View File

@ -17,3 +17,4 @@ from neutron.services.l3_router.service_providers import base
class SingleNodeDriver(base.L3ServiceProvider):
"""Provider for single L3 agent routers."""
use_integrated_agent_scheduler = True

View File

@ -3454,6 +3454,23 @@ class L3NatDBIntAgentSchedulingTestCase(L3BaseForIntTests,
s2['subnet']['network_id'])
self._assert_router_on_agent(r['router']['id'], 'host2')
def test_router_update_gateway_scheduling_not_supported(self):
plugin = manager.NeutronManager.get_service_plugins().get(
service_constants.L3_ROUTER_NAT)
mock.patch.object(plugin, 'router_supports_scheduling',
return_value=False).start()
with self.router() as r:
with self.subnet() as s1:
with self.subnet() as s2:
self._set_net_external(s1['subnet']['network_id'])
self._set_net_external(s2['subnet']['network_id'])
# this should pass even though there are multiple
# external networks since no scheduling decision needs
# to be made
self._add_external_gateway_to_router(
r['router']['id'],
s1['subnet']['network_id'])
def test_router_update_gateway_no_eligible_l3_agent(self):
with self.router() as r:
with self.subnet() as s1:

View File

@ -25,6 +25,7 @@ from oslo_utils import importutils
from oslo_utils import timeutils
from sqlalchemy import orm
import testscenarios
import testtools
from neutron import context as n_context
from neutron.db import agents_db
@ -203,6 +204,21 @@ class L3SchedulerBaseTestCase(base.BaseTestCase):
mock_get.assert_called_once_with(mock.ANY, self.plugin)
self.assertEqual(expected_routers, unscheduled_routers)
def test__get_routers_to_schedule_excludes_unsupported(self):
routers = [
{'id': 'router_1'}, {'id': 'router_2'}, {'id': 'router_3'}
]
expected_routers = [{'id': 'router_2'}]
# exclude everything except for 2
self.plugin.router_supports_scheduling = lambda c, rid: rid[-1] == '2'
with mock.patch.object(self.scheduler,
'_get_unscheduled_routers') as mock_get:
mock_get.return_value = routers
unscheduled_routers = self.scheduler._get_routers_to_schedule(
mock.ANY, self.plugin)
mock_get.assert_called_once_with(mock.ANY, self.plugin)
self.assertEqual(expected_routers, unscheduled_routers)
def _test__get_routers_can_schedule(self, routers, agent, target_routers):
self.plugin.get_l3_agent_candidates.return_value = agent
result = self.scheduler._get_routers_can_schedule(
@ -398,6 +414,14 @@ class L3SchedulerTestBaseMixin(object):
self.adminContext, agent_id,
router['router']['id'])
def test__schedule_router_skips_unschedulable_routers(self):
mock.patch.object(self.plugin, 'router_supports_scheduling',
return_value=False).start()
scheduler = l3_agent_scheduler.ChanceScheduler()
self.assertIsNone(scheduler._schedule_router(self.plugin,
self.adminContext,
'router_id'))
def test_add_router_to_l3_agent_mismatch_error_dvr_to_legacy(self):
self._register_l3_agents()
self._prepare_l3_agent_dvr_move_exceptions(
@ -1573,6 +1597,15 @@ class L3AgentSchedulerDbMixinTestCase(L3HATestCaseMixin):
self.assertEqual({'agents': []},
self.plugin._get_agents_dict_for_router([]))
def test_router_doesnt_support_scheduling(self):
with mock.patch.object(self.plugin, 'router_supports_scheduling',
return_value=False):
agent = helpers.register_l3_agent(host='myhost_3')
with testtools.ExpectedException(
l3agent.RouterDoesntSupportScheduling):
self.plugin.add_router_to_l3_agent(
self.adminContext, agent.id, 'router_id')
def test_manual_add_ha_router_to_agent(self):
cfg.CONF.set_override('max_l3_agents_per_router', 2)
router, agents = self._setup_ha_router()

View File

@ -42,6 +42,16 @@ class TestDriverController(testlib_api.SqlTestCase):
self.dc._flavor_plugin_ref.get_flavor_next_provider.return_value = [
provider]
def test_uses_scheduler(self):
self._return_provider_for_flavor('dvrha')
router_db = mock.Mock()
router = dict(id='router_id', flavor_id='abc123')
self.dc._set_router_provider('router', 'PRECOMMIT_CREATE', self,
self.ctx, router, router_db)
self.assertTrue(self.dc.uses_scheduler(self.ctx, 'router_id'))
self.dc.drivers['dvrha'].use_integrated_agent_scheduler = False
self.assertFalse(self.dc.uses_scheduler(self.ctx, 'router_id'))
def test__set_router_provider_flavor_specified(self):
self._return_provider_for_flavor('dvrha')
router_db = mock.Mock()