diff --git a/devstack/lib/dr b/devstack/lib/dr index 26e5f397..1fa94584 100644 --- a/devstack/lib/dr +++ b/devstack/lib/dr @@ -29,6 +29,12 @@ function configure_dr_agent_bgp_driver { iniset $DR_AGENT_BGP_CONF_FILE bgp bgp_speaker_driver $BGP_SPEAKER_DRIVER } +function configure_dr_agent_scheduler_driver { + if [ -n "$BGP_SCHEDULER_DRIVER" ] ; then + iniset $NEUTRON_CONF DEFAULT bgp_drscheduler_driver $BGP_SCHEDULER_DRIVER + fi +} + ############################# # Stack Install Section # ############################# @@ -61,6 +67,7 @@ function dr_post_configure { if is_protocol_enabled BGP; then configure_dr_agent_bgp_config configure_dr_agent_bgp_driver + configure_dr_agent_scheduler_driver fi fi } diff --git a/neutron_dynamic_routing/api/rpc/callbacks/__init__.py b/neutron_dynamic_routing/api/rpc/callbacks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/neutron_dynamic_routing/api/rpc/callbacks/resources.py b/neutron_dynamic_routing/api/rpc/callbacks/resources.py new file mode 100644 index 00000000..f8c8fe3d --- /dev/null +++ b/neutron_dynamic_routing/api/rpc/callbacks/resources.py @@ -0,0 +1,13 @@ +# 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. + +BGP_SPEAKER = 'bgp_speaker' diff --git a/neutron_dynamic_routing/db/bgp_dragentscheduler_db.py b/neutron_dynamic_routing/db/bgp_dragentscheduler_db.py index 6e749085..93c73e62 100644 --- a/neutron_dynamic_routing/db/bgp_dragentscheduler_db.py +++ b/neutron_dynamic_routing/db/bgp_dragentscheduler_db.py @@ -75,7 +75,7 @@ class BgpDrAgentSchedulerDbMixin(bgp_dras_ext.BgpDrSchedulerPluginBase, def schedule_bgp_speaker(self, context, created_bgp_speaker): if self.bgp_drscheduler: - agents = self.bgp_drscheduler.schedule(context, + agents = self.bgp_drscheduler.schedule(self, context, created_bgp_speaker) for agent in agents: self._bgp_rpc.bgp_speaker_created(context, diff --git a/neutron_dynamic_routing/services/bgp/bgp_plugin.py b/neutron_dynamic_routing/services/bgp/bgp_plugin.py index 6b36e774..76f41ad9 100644 --- a/neutron_dynamic_routing/services/bgp/bgp_plugin.py +++ b/neutron_dynamic_routing/services/bgp/bgp_plugin.py @@ -27,6 +27,7 @@ from neutron.callbacks import resources from neutron.common import rpc as n_rpc from neutron_dynamic_routing.api.rpc.agentnotifiers import bgp_dr_rpc_agent_api # noqa +from neutron_dynamic_routing.api.rpc.callbacks import resources as dr_resources from neutron_dynamic_routing.api.rpc.handlers import bgp_speaker_rpc as bs_rpc from neutron_dynamic_routing.db import bgp_db from neutron_dynamic_routing.db import bgp_dragentscheduler_db @@ -115,6 +116,10 @@ class BgpPlugin(service_base.ServicePluginBase, def create_bgp_speaker(self, context, bgp_speaker): bgp_speaker = super(BgpPlugin, self).create_bgp_speaker(context, bgp_speaker) + payload = {'plugin': self, 'context': context, + 'bgp_speaker': bgp_speaker} + registry.notify(dr_resources.BGP_SPEAKER, events.AFTER_CREATE, + self, payload=payload) return bgp_speaker def update_bgp_speaker(self, context, bgp_speaker_id, bgp_speaker): diff --git a/neutron_dynamic_routing/services/bgp/scheduler/bgp_dragent_scheduler.py b/neutron_dynamic_routing/services/bgp/scheduler/bgp_dragent_scheduler.py index 82e82dbc..3db24086 100644 --- a/neutron_dynamic_routing/services/bgp/scheduler/bgp_dragent_scheduler.py +++ b/neutron_dynamic_routing/services/bgp/scheduler/bgp_dragent_scheduler.py @@ -13,17 +13,21 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron_lib import context as nl_context from oslo_db import exception as db_exc from oslo_log import log as logging from sqlalchemy.orm import exc from sqlalchemy import sql +from neutron.callbacks import events +from neutron.callbacks import registry from neutron.db import agents_db from neutron.db.models import agent as agent_model from neutron.scheduler import base_resource_filter from neutron.scheduler import base_scheduler from neutron_dynamic_routing._i18n import _LI, _LW +from neutron_dynamic_routing.api.rpc.callbacks import resources as dr_resources from neutron_dynamic_routing.db import bgp_db from neutron_dynamic_routing.db import bgp_dragentscheduler_db as bgp_dras_db from neutron_dynamic_routing.services.bgp.common import constants as bgp_consts @@ -123,6 +127,11 @@ class BgpDrAgentFilter(base_resource_filter.BaseResourceFilter): class BgpDrAgentSchedulerBase(BgpDrAgentFilter): + def _register_callbacks(self): + registry.subscribe(self.schedule_bgp_speaker_callback, + dr_resources.BGP_SPEAKER, + events.AFTER_CREATE) + def schedule_unscheduled_bgp_speakers(self, context, host): """Schedule unscheduled BgpSpeaker to a BgpDrAgent. """ @@ -178,12 +187,19 @@ class BgpDrAgentSchedulerBase(BgpDrAgentFilter): no_agent_binding) return [bgp_speaker_id_[0] for bgp_speaker_id_ in query] + def schedule_bgp_speaker_callback(self, resource, event, trigger, payload): + plugin = payload['plugin'] + if event == events.AFTER_CREATE: + ctx = nl_context.get_admin_context() + plugin.schedule_bgp_speaker(ctx, payload['bgp_speaker']) + class ChanceScheduler(base_scheduler.BaseChanceScheduler, BgpDrAgentSchedulerBase): def __init__(self): super(ChanceScheduler, self).__init__(self) + self._register_callbacks() class WeightScheduler(base_scheduler.BaseWeightScheduler, @@ -191,3 +207,4 @@ class WeightScheduler(base_scheduler.BaseWeightScheduler, def __init__(self): super(WeightScheduler, self).__init__(self) + self._register_callbacks() diff --git a/neutron_dynamic_routing/tests/unit/services/bgp/scheduler/test_bgp_dragent_scheduler.py b/neutron_dynamic_routing/tests/unit/services/bgp/scheduler/test_bgp_dragent_scheduler.py index e80eeb65..c5559d71 100644 --- a/neutron_dynamic_routing/tests/unit/services/bgp/scheduler/test_bgp_dragent_scheduler.py +++ b/neutron_dynamic_routing/tests/unit/services/bgp/scheduler/test_bgp_dragent_scheduler.py @@ -13,15 +13,20 @@ # License for the specific language governing permissions and limitations # under the License. +import mock import testscenarios from neutron_lib import context from oslo_utils import importutils +from neutron.callbacks import events +from neutron.callbacks import registry from neutron.tests.unit import testlib_api +from neutron_dynamic_routing.api.rpc.callbacks import resources as dr_resources from neutron_dynamic_routing.db import bgp_db from neutron_dynamic_routing.db import bgp_dragentscheduler_db as bgp_dras_db +from neutron_dynamic_routing.services.bgp import bgp_plugin from neutron_dynamic_routing.services.bgp.scheduler import bgp_dragent_scheduler as bgp_dras # noqa from neutron_dynamic_routing.tests.common import helpers @@ -72,6 +77,67 @@ class TestBgpDrAgentSchedulerBaseTestCase(testlib_api.SqlTestCase): self.assertEqual(bgp_speaker_id, result.bgp_speaker_id) +class TestSchedulerCallback(TestBgpDrAgentSchedulerBaseTestCase): + + def setUp(self): + super(TestSchedulerCallback, self).setUp() + bgp_notify_p = mock.patch('neutron_dynamic_routing.api.rpc.' + 'agentnotifiers.bgp_dr_rpc_agent_api.' + 'BgpDrAgentNotifyApi') + bgp_notify_p.start() + rpc_conn_p = mock.patch('neutron.common.rpc.create_connection') + rpc_conn_p.start() + admin_ctx_p = mock.patch('neutron_lib.context.get_admin_context') + admin_ctx_p = mock.patch('neutron_lib.context.get_admin_context') + self.admin_ctx_m = admin_ctx_p.start() + self.admin_ctx_m.return_value = self.ctx + self.plugin = bgp_plugin.BgpPlugin() + self.scheduler = bgp_dras.ChanceScheduler() + + def _create_test_payload(self, context='test_ctx'): + bgp_speaker = {'id': '11111111-2222-3333-4444-555555555555'} + payload = {'plugin': self.plugin, 'context': context, + 'bgp_speaker': bgp_speaker} + return payload + + def test__register_callbacks(self): + with mock.patch.object(registry, 'subscribe') as subscribe: + scheduler = bgp_dras.ChanceScheduler() + expected_calls = [ + mock.call(scheduler.schedule_bgp_speaker_callback, + dr_resources.BGP_SPEAKER, events.AFTER_CREATE), + ] + self.assertEqual(subscribe.call_args_list, expected_calls) + with mock.patch.object(registry, 'subscribe') as subscribe: + scheduler = bgp_dras.WeightScheduler() + expected_calls = [ + mock.call(scheduler.schedule_bgp_speaker_callback, + dr_resources.BGP_SPEAKER, events.AFTER_CREATE), + ] + self.assertEqual(subscribe.call_args_list, expected_calls) + + def test_schedule_bgp_speaker_callback_with_valid_event(self): + payload = self._create_test_payload() + with mock.patch.object(self.plugin, + 'schedule_bgp_speaker') as sched_bgp: + self.scheduler.schedule_bgp_speaker_callback( + dr_resources.BGP_SPEAKER, + events.AFTER_CREATE, + self.scheduler, payload) + sched_bgp.assert_called_once_with(self.ctx, + payload['bgp_speaker']) + + def test_schedule_bgp_speaker_callback_with_invalid_event(self): + payload = self._create_test_payload() + with mock.patch.object(self.plugin, + 'schedule_bgp_speaker') as sched_bgp: + self.scheduler.schedule_bgp_speaker_callback( + dr_resources.BGP_SPEAKER, + events.BEFORE_CREATE, + self.scheduler, payload) + sched_bgp.assert_not_called() + + class TestBgpDrAgentScheduler(TestBgpDrAgentSchedulerBaseTestCase, bgp_db.BgpDbMixin): diff --git a/neutron_dynamic_routing/tests/unit/services/bgp/test_bgp_plugin.py b/neutron_dynamic_routing/tests/unit/services/bgp/test_bgp_plugin.py new file mode 100644 index 00000000..66ef8754 --- /dev/null +++ b/neutron_dynamic_routing/tests/unit/services/bgp/test_bgp_plugin.py @@ -0,0 +1,88 @@ +# Copyright (C) 2017 VA Linux Systems Japan K.K. +# Copyright (C) 2017 Fumihiko Kakuma +# All Rights Reserved. +# +# 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. + +import mock + +from neutron.callbacks import events +from neutron.callbacks import registry +from neutron.callbacks import resources +from neutron.tests import base + +from neutron_dynamic_routing.api.rpc.callbacks import resources as dr_resources +from neutron_dynamic_routing.db import bgp_db +from neutron_dynamic_routing.services.bgp import bgp_plugin + + +class TestBgpPlugin(base.BaseTestCase): + + def setUp(self): + super(TestBgpPlugin, self).setUp() + bgp_notify_p = mock.patch('neutron_dynamic_routing.api.rpc.' + 'agentnotifiers.bgp_dr_rpc_agent_api.' + 'BgpDrAgentNotifyApi') + bgp_notify_p.start() + rpc_conn_p = mock.patch('neutron.common.rpc.create_connection') + rpc_conn_p.start() + admin_ctx_p = mock.patch('neutron_lib.context.get_admin_context') + self.admin_ctx_m = admin_ctx_p.start() + self.fake_admin_ctx = mock.Mock() + self.admin_ctx_m.return_value = self.fake_admin_ctx + self.plugin = bgp_plugin.BgpPlugin() + + def _create_test_payload(self, context='test_ctx'): + bgp_speaker = {'id': '11111111-2222-3333-4444-555555555555'} + payload = {'plugin': self.plugin, 'context': context, + 'bgp_speaker': bgp_speaker} + return payload + + def test__register_callbacks(self): + with mock.patch.object(registry, 'subscribe') as subscribe: + plugin = bgp_plugin.BgpPlugin() + expected_calls = [ + mock.call(plugin.bgp_drscheduler.schedule_bgp_speaker_callback, + dr_resources.BGP_SPEAKER, events.AFTER_CREATE), + mock.call(plugin.floatingip_update_callback, + resources.FLOATING_IP, events.AFTER_UPDATE), + mock.call(plugin.router_interface_callback, + resources.ROUTER_INTERFACE, events.AFTER_CREATE), + mock.call(plugin.router_interface_callback, + resources.ROUTER_INTERFACE, events.BEFORE_CREATE), + mock.call(plugin.router_interface_callback, + resources.ROUTER_INTERFACE, events.AFTER_DELETE), + mock.call(plugin.router_gateway_callback, + resources.ROUTER_GATEWAY, events.AFTER_CREATE), + mock.call(plugin.router_gateway_callback, + resources.ROUTER_GATEWAY, events.AFTER_DELETE), + ] + self.assertEqual(subscribe.call_args_list, expected_calls) + + def test_create_bgp_speaker(self): + test_context = 'create_bgp_context' + test_bgp_speaker = {'id': None} + payload = self._create_test_payload(context=test_context) + with mock.patch.object(bgp_db.BgpDbMixin, + 'create_bgp_speaker') as create_bgp_sp: + with mock.patch.object(registry, 'notify') as notify: + create_bgp_sp.return_value = payload['bgp_speaker'] + self.assertEqual(self.plugin.create_bgp_speaker( + test_context, test_bgp_speaker), + payload['bgp_speaker']) + create_bgp_sp.assert_called_once_with(test_context, + test_bgp_speaker) + notify.assert_called_once_with(dr_resources.BGP_SPEAKER, + events.AFTER_CREATE, + self.plugin, + payload=payload)