diff --git a/neutron/db/l3_dvr_db.py b/neutron/db/l3_dvr_db.py index 8b4b4de09c4..b38c9860b4a 100644 --- a/neutron/db/l3_dvr_db.py +++ b/neutron/db/l3_dvr_db.py @@ -15,6 +15,7 @@ import collections from neutron_lib.api.definitions import l3 as l3_apidef from neutron_lib.api.definitions import portbindings +from neutron_lib.api.definitions import portbindings_extended from neutron_lib.api import validators from neutron_lib.callbacks import events from neutron_lib.callbacks import exceptions @@ -66,6 +67,18 @@ def is_admin_state_down_necessary(): return _IS_ADMIN_STATE_DOWN_NECESSARY +# TODO(slaweq): this should be moved to neutron_lib.plugins.utils module +def is_port_bound(port): + active_binding = plugin_utils.get_port_binding_by_status_and_host( + port.get("port_bindings", []), const.ACTIVE) + if not active_binding: + LOG.warning("Binding for port %s was not found.", port) + return False + return active_binding[portbindings_extended.VIF_TYPE] not in [ + portbindings.VIF_TYPE_UNBOUND, + portbindings.VIF_TYPE_BINDING_FAILED] + + @registry.has_registry_receivers class DVRResourceOperationHandler(object): """Contains callbacks for DVR operations. @@ -1204,10 +1217,11 @@ class L3_NAT_with_dvr_db_mixin(_DVRAgentInterfaceMixin, def get_ports_under_dvr_connected_subnet(self, context, subnet_id): query = dvr_mac_db.get_ports_query_by_subnet_and_ip(context, subnet_id) + ports = [p for p in query.all() if is_port_bound(p)] return [ self.l3plugin._core_plugin._make_port_dict( port, process_extensions=False) - for port in query.all() + for port in ports ] diff --git a/neutron/tests/unit/db/test_l3_dvr_db.py b/neutron/tests/unit/db/test_l3_dvr_db.py index f362a751b69..59fb1f8dd84 100644 --- a/neutron/tests/unit/db/test_l3_dvr_db.py +++ b/neutron/tests/unit/db/test_l3_dvr_db.py @@ -1282,6 +1282,35 @@ class L3DvrTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase): self.assertTrue( self.mixin.is_router_distributed(self.ctx, router_id)) + @mock.patch.object(l3_dvr_db, "is_port_bound") + def test_get_ports_under_dvr_connected_subnet(self, is_port_bound_mock): + router_dict = {'name': 'test_router', 'admin_state_up': True, + 'distributed': True} + router = self._create_router(router_dict) + with self.network() as network,\ + self.subnet(network=network) as subnet: + fake_bound_ports_ids = [] + + def fake_is_port_bound(port): + return port['id'] in fake_bound_ports_ids + + is_port_bound_mock.side_effect = fake_is_port_bound + + for _ in range(4): + port_res = self.create_port( + network['network']['id'], + {'fixed_ips': [{'subnet_id': subnet['subnet']['id']}]}) + port_id = self.deserialize(self.fmt, port_res)['port']['id'] + if len(fake_bound_ports_ids) < 2: + fake_bound_ports_ids.append(port_id) + + self.mixin.add_router_interface(self.ctx, router['id'], + {'subnet_id': subnet['subnet']['id']}) + dvr_subnet_ports = self.mixin.get_ports_under_dvr_connected_subnet( + self.ctx, subnet['subnet']['id']) + dvr_subnet_ports_ids = [p['id'] for p in dvr_subnet_ports] + self.assertItemsEqual(fake_bound_ports_ids, dvr_subnet_ports_ids) + @mock.patch.object(plugin_utils, 'can_port_be_bound_to_virtual_bridge', return_value=True) def test__get_assoc_data_valid_vnic_type(self, *args):