diff --git a/neutron/agent/l3/ha_router.py b/neutron/agent/l3/ha_router.py index 53d7213444a..5eab948fdb6 100644 --- a/neutron/agent/l3/ha_router.py +++ b/neutron/agent/l3/ha_router.py @@ -385,7 +385,9 @@ class HaRouter(router.RouterInfo): def process(self, agent): super(HaRouter, self).process(agent) - if self.ha_port: + self.ha_port = self.router.get(n_consts.HA_INTERFACE_KEY) + if (self.ha_port and + self.ha_port['status'] == n_consts.PORT_STATUS_ACTIVE): self.enable_keepalived() @common_utils.synchronized('enable_radvd') diff --git a/neutron/db/l3_hascheduler_db.py b/neutron/db/l3_hascheduler_db.py index 7acb1c80f72..b2ddf2aaa7d 100644 --- a/neutron/db/l3_hascheduler_db.py +++ b/neutron/db/l3_hascheduler_db.py @@ -12,13 +12,20 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron_lib import constants from sqlalchemy import func from sqlalchemy import sql +from neutron.callbacks import events +from neutron.callbacks import registry +from neutron.callbacks import resources from neutron.db import agents_db from neutron.db import l3_agentschedulers_db as l3_sch_db from neutron.db import l3_attrs_db from neutron.db import l3_db +from neutron.extensions import portbindings +from neutron import manager +from neutron.plugins.common import constants as service_constants class L3_HA_scheduler_db_mixin(l3_sch_db.AZL3AgentSchedulerDbMixin): @@ -81,3 +88,25 @@ class L3_HA_scheduler_db_mixin(l3_sch_db.AZL3AgentSchedulerDbMixin): bindings = [(binding.l3_agent, None) for binding in bindings] return self._get_agents_dict_for_router(bindings) + + +def _notify_l3_agent_ha_port_update(resource, event, trigger, **kwargs): + new_port = kwargs.get('port') + original_port = kwargs.get('original_port') + context = kwargs.get('context') + host = new_port[portbindings.HOST_ID] + + if new_port and original_port and host: + new_device_owner = new_port.get('device_owner', '') + if (new_device_owner == constants.DEVICE_OWNER_ROUTER_HA_INTF and + new_port['status'] == constants.PORT_STATUS_ACTIVE and + original_port['status'] != new_port['status']): + l3plugin = manager.NeutronManager.get_service_plugins().get( + service_constants.L3_ROUTER_NAT) + l3plugin.l3_rpc_notifier.routers_updated_on_host( + context, [new_port['device_id']], host) + + +def subscribe(): + registry.subscribe( + _notify_l3_agent_ha_port_update, resources.PORT, events.AFTER_UPDATE) diff --git a/neutron/services/l3_router/l3_router_plugin.py b/neutron/services/l3_router/l3_router_plugin.py index 45651b71f08..fb1d97a50a1 100644 --- a/neutron/services/l3_router/l3_router_plugin.py +++ b/neutron/services/l3_router/l3_router_plugin.py @@ -30,6 +30,7 @@ from neutron.db import l3_dvr_ha_scheduler_db from neutron.db import l3_dvrscheduler_db from neutron.db import l3_gwmode_db from neutron.db import l3_hamode_db +from neutron.db import l3_hascheduler_db from neutron.extensions import l3 from neutron.plugins.common import constants from neutron.quota import resource_registry @@ -72,6 +73,8 @@ class L3RouterPlugin(service_base.ServicePluginBase, super(L3RouterPlugin, self).__init__() if 'dvr' in self.supported_extension_aliases: l3_dvrscheduler_db.subscribe() + if 'l3-ha' in self.supported_extension_aliases: + l3_hascheduler_db.subscribe() self.agent_notifiers.update( {n_const.AGENT_TYPE_L3: l3_rpc_agent_api.L3AgentNotifyAPI()}) diff --git a/neutron/tests/functional/agent/l3/test_ha_router.py b/neutron/tests/functional/agent/l3/test_ha_router.py index b5efeb8297a..4d4382da9f1 100644 --- a/neutron/tests/functional/agent/l3/test_ha_router.py +++ b/neutron/tests/functional/agent/l3/test_ha_router.py @@ -273,6 +273,18 @@ class L3HATestCase(framework.L3AgentTestFramework): # call the configure_fip_addresses directly here router.configure_fip_addresses(interface_name) + def test_ha_port_status_update(self): + router_info = self.generate_router_info(enable_ha=True) + router_info[constants.HA_INTERFACE_KEY]['status'] = ( + constants.PORT_STATUS_DOWN) + router1 = self.manage_router(self.agent, router_info) + common_utils.wait_until_true(lambda: router1.ha_state == 'backup') + + router1.router[constants.HA_INTERFACE_KEY]['status'] = ( + constants.PORT_STATUS_ACTIVE) + self.agent._process_updated_router(router1.router) + common_utils.wait_until_true(lambda: router1.ha_state == 'master') + class L3HATestFailover(framework.L3AgentTestFramework): diff --git a/neutron/tests/unit/plugins/ml2/test_plugin.py b/neutron/tests/unit/plugins/ml2/test_plugin.py index 061bb051840..c970d545267 100644 --- a/neutron/tests/unit/plugins/ml2/test_plugin.py +++ b/neutron/tests/unit/plugins/ml2/test_plugin.py @@ -58,6 +58,7 @@ from neutron.plugins.ml2.drivers import type_vlan from neutron.plugins.ml2 import managers from neutron.plugins.ml2 import models from neutron.plugins.ml2 import plugin as ml2_plugin +from neutron.services.l3_router import l3_router_plugin from neutron.services.qos import qos_consts from neutron.services.segments import db as segments_plugin_db from neutron.services.segments import plugin as segments_plugin @@ -83,6 +84,7 @@ PLUGIN_NAME = 'ml2' DEVICE_OWNER_COMPUTE = constants.DEVICE_OWNER_COMPUTE_PREFIX + 'fake' HOST = 'fake_host' +TEST_ROUTER_ID = 'router_id' # TODO(marun) - Move to somewhere common for reuse @@ -708,6 +710,26 @@ class TestMl2PortsV2(test_plugin.TestPortsV2, Ml2PluginV2TestCase): self.assertEqual('DOWN', port['port']['status']) self.assertEqual('DOWN', self.port_create_status) + def test_update_port_status_notify_port_event_after_update(self): + ctx = context.get_admin_context() + plugin = ml2_plugin.Ml2Plugin() + # enable subscription for events + l3_router_plugin.L3RouterPlugin() + l3plugin = manager.NeutronManager.get_service_plugins().get( + p_const.L3_ROUTER_NAT) + host_arg = {portbindings.HOST_ID: HOST} + with mock.patch.object(l3plugin.l3_rpc_notifier, + 'routers_updated_on_host') as mock_updated: + with self.port(device_owner=constants.DEVICE_OWNER_ROUTER_HA_INTF, + device_id=TEST_ROUTER_ID, + arg_list=(portbindings.HOST_ID,), + **host_arg) as port: + plugin.update_port_status( + ctx, port['port']['id'], + constants.PORT_STATUS_ACTIVE, host=HOST) + mock_updated.assert_called_once_with( + mock.ANY, [TEST_ROUTER_ID], HOST) + def test_update_port_status_short_id(self): ctx = context.get_admin_context() plugin = manager.NeutronManager.get_plugin()