diff --git a/neutron/plugins/ml2/drivers/l2pop/mech_driver.py b/neutron/plugins/ml2/drivers/l2pop/mech_driver.py index 615f53b5608..f2c20fa1054 100644 --- a/neutron/plugins/ml2/drivers/l2pop/mech_driver.py +++ b/neutron/plugins/ml2/drivers/l2pop/mech_driver.py @@ -248,6 +248,14 @@ class L2populationMechanismDriver(api.MechanismDriver): return agents + def agent_restarted(self, context): + agent_host = context.host + session = db_api.get_reader_session() + agent = l2pop_db.get_agent_by_host(session, agent_host) + if l2pop_db.get_agent_uptime(agent) < cfg.CONF.l2pop.agent_boot_time: + return True + return False + def update_port_down(self, context): port = context.current agent_host = context.host @@ -296,9 +304,8 @@ class L2populationMechanismDriver(api.MechanismDriver): # with high concurrency more than 1 port may be activated on an agent # at the same time (like VM port + a DVR port) so checking for 1 or 2 is_first_port = agent_active_ports in (1, 2) - if is_first_port or (l2pop_db.get_agent_uptime(agent) < - cfg.CONF.l2pop.agent_boot_time): - # First port(s) activated on current agent in this network, + if is_first_port or self.agent_restarted(context): + # First port activated on current agent in this network, # we have to provide it with the whole list of fdb entries agent_fdb_entries = self._create_agent_fdb(session, agent, diff --git a/neutron/plugins/ml2/rpc.py b/neutron/plugins/ml2/rpc.py index bf3f47154cf..441b1856bf4 100644 --- a/neutron/plugins/ml2/rpc.py +++ b/neutron/plugins/ml2/rpc.py @@ -304,24 +304,28 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin): port = ml2_db.get_port(rpc_context, port_id) if not port: return - # NOTE: DVR ports are already handled and updated through l2pop - # and so we don't need to update it again here - if port['device_owner'] == n_const.DEVICE_OWNER_DVR_INTERFACE: - return port_context = plugin.get_bound_port_context( - rpc_context, port_id) + rpc_context, port_id, host) if not port_context: # port deleted return + # NOTE: DVR ports are already handled and updated through l2pop + # and so we don't need to update it again here. But, l2pop did not + # handle DVR ports while restart neutron-*-agent, we need to handle + # it here. + if (port['device_owner'] == n_const.DEVICE_OWNER_DVR_INTERFACE and + not l2pop_driver.obj.agent_restarted(port_context)): + return port = port_context.current - if (status == n_const.PORT_STATUS_ACTIVE and + if (port['device_owner'] != n_const.DEVICE_OWNER_DVR_INTERFACE and + status == n_const.PORT_STATUS_ACTIVE and port[portbindings.HOST_ID] != host and not l3_hamode_db.is_ha_router_port(rpc_context, port['device_owner'], port['device_id'])): # don't setup ACTIVE forwarding entries unless bound to this - # host or if it's an HA port (which is special-cased in the - # mech driver) + # host or if it's an HA or DVR port (which is special-cased in + # the mech driver) return port_context.current['status'] = status port_context.current[portbindings.HOST_ID] = host diff --git a/neutron/tests/unit/plugins/ml2/drivers/l2pop/test_mech_driver.py b/neutron/tests/unit/plugins/ml2/drivers/l2pop/test_mech_driver.py index 18941e36896..d69dd0b95e9 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/l2pop/test_mech_driver.py +++ b/neutron/tests/unit/plugins/ml2/drivers/l2pop/test_mech_driver.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime + import mock from neutron_lib.api.definitions import port as port_def @@ -353,6 +355,37 @@ class TestL2PopulationRpcTestCase(test_plugin.Ml2PluginV2TestCase): self.mock_fanout.assert_called_with( mock.ANY, 'remove_fdb_entries', expected) + def test_ovs_agent_restarted_with_dvr_port(self): + plugin = directory.get_plugin() + router = self._create_dvr_router() + with mock.patch.object(l2pop_mech_driver.L2populationMechanismDriver, + 'agent_restarted', return_value=True): + with self.subnet(network=self._network, + enable_dhcp=False) as snet: + with self.port( + subnet=snet, + device_owner=constants.DEVICE_OWNER_DVR_INTERFACE)\ + as port: + port_id = port['port']['id'] + plugin.update_distributed_port_binding(self.adminContext, + port_id, {'port': {portbindings.HOST_ID: HOST_4, + 'device_id': router['id']}}) + port = self._show('ports', port_id) + self.assertEqual(portbindings.VIF_TYPE_DISTRIBUTED, + port['port'][portbindings.VIF_TYPE]) + self.callbacks.update_device_up(self.adminContext, + agent_id=HOST_4, + device=port_id, + host=HOST_4) + fanout_expected = {port['port']['network_id']: { + 'network_type': u'vxlan', + 'ports': { + u'20.0.0.4': [('00:00:00:00:00:00', '0.0.0.0')]}, + 'segment_id': 1}} + self.mock_fanout.assert_called_with(mock.ANY, + 'add_fdb_entries', + fanout_expected) + def test_ha_agents_with_dvr_rtr_does_not_get_other_fdb(self): router = self._create_dvr_router() directory.add_plugin(plugin_constants.L3, self.plugin) @@ -1454,3 +1487,25 @@ class TestL2PopulationMechDriver(base.BaseTestCase): mech_driver = l2pop_mech_driver.L2populationMechanismDriver() with testtools.ExpectedException(exceptions.InvalidInput): mech_driver.update_port_precommit(ctx) + + def test_agent_restarted(self): + mech_driver = l2pop_mech_driver.L2populationMechanismDriver() + ctx = mock.Mock() + ctx.host = "__host1__" + ctx._plugin_context = {} + agent = mock.Mock() + agent.started_at = datetime.datetime(2018, 5, 25, 15, 51, 20) + agent.heartbeat_timestamp = datetime.datetime(2018, 5, 25, 15, + 51, 50) + + with mock.patch.object(l2pop_db, 'get_agent_by_host', + return_value=agent): + res = mech_driver.agent_restarted(ctx) + self.assertTrue(res) + + agent.heartbeat_timestamp = datetime.datetime(2018, 5, 25, 15, + 58, 30) + with mock.patch.object(l2pop_db, 'get_agent_by_host', + return_value=agent): + res = mech_driver.agent_restarted(ctx) + self.assertFalse(res)