diff --git a/neutron/db/l3_agentschedulers_db.py b/neutron/db/l3_agentschedulers_db.py index 1e4c75a6b01..a6d619398b7 100644 --- a/neutron/db/l3_agentschedulers_db.py +++ b/neutron/db/l3_agentschedulers_db.py @@ -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) diff --git a/neutron/db/l3_db.py b/neutron/db/l3_db.py index 6e47b334b19..a8695a1b76a 100644 --- a/neutron/db/l3_db.py +++ b/neutron/db/l3_db.py @@ -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: diff --git a/neutron/extensions/l3agentscheduler.py b/neutron/extensions/l3agentscheduler.py index f67ca0f51f7..d70f88af481 100644 --- a/neutron/extensions/l3agentscheduler.py +++ b/neutron/extensions/l3agentscheduler.py @@ -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} diff --git a/neutron/scheduler/l3_agent_scheduler.py b/neutron/scheduler/l3_agent_scheduler.py index e9046dcfe09..02f4aeaa1ac 100644 --- a/neutron/scheduler/l3_agent_scheduler.py +++ b/neutron/scheduler/l3_agent_scheduler.py @@ -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) diff --git a/neutron/services/l3_router/l3_router_plugin.py b/neutron/services/l3_router/l3_router_plugin.py index fb1d97a50a1..6525490eaf3 100644 --- a/neutron/services/l3_router/l3_router_plugin.py +++ b/neutron/services/l3_router/l3_router_plugin.py @@ -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. diff --git a/neutron/services/l3_router/service_providers/base.py b/neutron/services/l3_router/service_providers/base.py index b2a414ed469..47bccd9beb1 100644 --- a/neutron/services/l3_router/service_providers/base.py +++ b/neutron/services/l3_router/service_providers/base.py @@ -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 diff --git a/neutron/services/l3_router/service_providers/driver_controller.py b/neutron/services/l3_router/service_providers/driver_controller.py index 96ef0c4c544..540f52a75a6 100644 --- a/neutron/services/l3_router/service_providers/driver_controller.py +++ b/neutron/services/l3_router/service_providers/driver_controller.py @@ -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): diff --git a/neutron/services/l3_router/service_providers/dvr.py b/neutron/services/l3_router/service_providers/dvr.py index 38027694f1f..47cc01e5a13 100644 --- a/neutron/services/l3_router/service_providers/dvr.py +++ b/neutron/services/l3_router/service_providers/dvr.py @@ -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 diff --git a/neutron/services/l3_router/service_providers/ha.py b/neutron/services/l3_router/service_providers/ha.py index ee7e94edfc5..2090e7e5b68 100644 --- a/neutron/services/l3_router/service_providers/ha.py +++ b/neutron/services/l3_router/service_providers/ha.py @@ -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 diff --git a/neutron/services/l3_router/service_providers/single_node.py b/neutron/services/l3_router/service_providers/single_node.py index 8c6c9c5e6ab..c8292952267 100644 --- a/neutron/services/l3_router/service_providers/single_node.py +++ b/neutron/services/l3_router/service_providers/single_node.py @@ -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 diff --git a/neutron/tests/unit/extensions/test_l3.py b/neutron/tests/unit/extensions/test_l3.py index e47bc417427..cafacfd677b 100644 --- a/neutron/tests/unit/extensions/test_l3.py +++ b/neutron/tests/unit/extensions/test_l3.py @@ -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: diff --git a/neutron/tests/unit/scheduler/test_l3_agent_scheduler.py b/neutron/tests/unit/scheduler/test_l3_agent_scheduler.py index 82057ee2a12..0244c0ed385 100644 --- a/neutron/tests/unit/scheduler/test_l3_agent_scheduler.py +++ b/neutron/tests/unit/scheduler/test_l3_agent_scheduler.py @@ -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() diff --git a/neutron/tests/unit/services/l3_router/service_providers/test_driver_controller.py b/neutron/tests/unit/services/l3_router/service_providers/test_driver_controller.py index 9068f3d78b6..4db99c23d21 100644 --- a/neutron/tests/unit/services/l3_router/service_providers/test_driver_controller.py +++ b/neutron/tests/unit/services/l3_router/service_providers/test_driver_controller.py @@ -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()