From b970ed5bdac60c0fa227f2fddaa9b842ba4f51a7 Mon Sep 17 00:00:00 2001 From: Kevin Benton Date: Fri, 8 Apr 2016 17:52:14 -0700 Subject: [PATCH] Clear DVR MAC on last agent deletion from host Once all agents are deleted from a host, the DVR MAC generated for that host should be deleted as well to prevent a buildup of pointless flows generated in the OVS agent for hosts that don't exist. Closes-Bug: #1568206 Change-Id: I51e736aa0431980a595ecf810f148ca62d990d20 (cherry picked from commit 92527c2de2afaf4862fddc101143e4d02858924d) --- neutron/callbacks/resources.py | 1 + neutron/db/agents_db.py | 7 +++++- neutron/db/dvr_mac_db.py | 30 ++++++++++++++++++++++ neutron/tests/unit/db/test_dvr_mac_db.py | 32 ++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/neutron/callbacks/resources.py b/neutron/callbacks/resources.py index 42d3d2fc470..753d7a51151 100644 --- a/neutron/callbacks/resources.py +++ b/neutron/callbacks/resources.py @@ -11,6 +11,7 @@ # under the License. # String literals representing core resources. +AGENT = 'agent' EXTERNAL_NETWORK = 'external_network' FLOATING_IP = 'floating_ip' PORT = 'port' diff --git a/neutron/db/agents_db.py b/neutron/db/agents_db.py index eda656d1959..3e4e82ad5e8 100644 --- a/neutron/db/agents_db.py +++ b/neutron/db/agents_db.py @@ -31,6 +31,9 @@ from sqlalchemy import sql from neutron._i18n import _, _LE, _LI, _LW from neutron.api.rpc.callbacks import version_manager from neutron.api.v2 import attributes +from neutron.callbacks import events +from neutron.callbacks import registry +from neutron.callbacks import resources from neutron.common import constants from neutron import context from neutron.db import api as db_api @@ -251,8 +254,10 @@ class AgentDbMixin(ext_agent.AgentPluginBase, AgentAvailabilityZoneMixin): return self._fields(res, fields) def delete_agent(self, context, id): + agent = self._get_agent(context, id) + registry.notify(resources.AGENT, events.BEFORE_DELETE, self, + context=context, agent=agent) with context.session.begin(subtransactions=True): - agent = self._get_agent(context, id) context.session.delete(agent) def update_agent(self, context, id, agent): diff --git a/neutron/db/dvr_mac_db.py b/neutron/db/dvr_mac_db.py index d43ddc14d42..3087c07efeb 100644 --- a/neutron/db/dvr_mac_db.py +++ b/neutron/db/dvr_mac_db.py @@ -22,6 +22,9 @@ from sqlalchemy import or_ from sqlalchemy.orm import exc from neutron._i18n import _, _LE +from neutron.callbacks import events +from neutron.callbacks import registry +from neutron.callbacks import resources from neutron.common import constants from neutron.common import exceptions as n_exc from neutron.common import utils @@ -60,9 +63,36 @@ class DistributedVirtualRouterMacAddress(model_base.BASEV2): mac_address = sa.Column(sa.String(32), nullable=False, unique=True) +def _delete_mac_associated_with_agent(resource, event, trigger, context, agent, + **kwargs): + host = agent['host'] + plugin = manager.NeutronManager.get_plugin() + if [a for a in plugin.get_agents(context, filters={'host': [host]}) + if a['id'] != agent['id']]: + # there are still agents on this host, don't mess with the mac entry + # until they are all deleted. + return + try: + with context.session.begin(subtransactions=True): + entry = (context.session.query(DistributedVirtualRouterMacAddress). + filter(DistributedVirtualRouterMacAddress.host == host). + one()) + context.session.delete(entry) + except exc.NoResultFound: + return + # notify remaining agents so they cleanup flows + dvr_macs = plugin.get_dvr_mac_address_list(context) + plugin.notifier.dvr_mac_address_update(context, dvr_macs) + + class DVRDbMixin(ext_dvr.DVRMacAddressPluginBase): """Mixin class to add dvr mac address to db_plugin_base_v2.""" + def __new__(cls, *args, **kwargs): + registry.subscribe(_delete_mac_associated_with_agent, + resources.AGENT, events.BEFORE_DELETE) + return super(DVRDbMixin, cls).__new__(cls) + @property def plugin(self): try: diff --git a/neutron/tests/unit/db/test_dvr_mac_db.py b/neutron/tests/unit/db/test_dvr_mac_db.py index e7c3e54c770..5b7ce40299c 100644 --- a/neutron/tests/unit/db/test_dvr_mac_db.py +++ b/neutron/tests/unit/db/test_dvr_mac_db.py @@ -16,11 +16,15 @@ import mock from oslo_config import cfg +from neutron.callbacks import events +from neutron.callbacks import registry +from neutron.callbacks import resources from neutron.common import constants from neutron import context from neutron.db import dvr_mac_db from neutron.extensions import dvr from neutron.extensions import portbindings +from neutron import manager from neutron.tests.unit.plugins.ml2 import test_plugin @@ -75,6 +79,34 @@ class DvrDbMixinTestCase(test_plugin.Ml2PluginV2TestCase): self.ctx, "foo_host_2") self.assertEqual(new_retries, f.call_count) + def test_mac_not_cleared_on_agent_delete_event_with_remaining_agents(self): + plugin = manager.NeutronManager.get_plugin() + self._create_dvr_mac_entry('host_1', 'mac_1') + self._create_dvr_mac_entry('host_2', 'mac_2') + agent1 = {'host': 'host_1', 'id': 'a1'} + agent2 = {'host': 'host_1', 'id': 'a2'} + with mock.patch.object(plugin, 'get_agents', return_value=[agent2]): + with mock.patch.object(plugin, 'notifier') as notifier: + registry.notify(resources.AGENT, events.BEFORE_DELETE, self, + context=self.ctx, agent=agent1) + mac_list = self.mixin.get_dvr_mac_address_list(self.ctx) + self.assertEqual(2, len(mac_list)) + self.assertFalse(notifier.dvr_mac_address_update.called) + + def test_mac_cleared_on_agent_delete_event(self): + plugin = manager.NeutronManager.get_plugin() + self._create_dvr_mac_entry('host_1', 'mac_1') + self._create_dvr_mac_entry('host_2', 'mac_2') + agent = {'host': 'host_1', 'id': 'a1'} + with mock.patch.object(plugin, 'notifier') as notifier: + registry.notify(resources.AGENT, events.BEFORE_DELETE, self, + context=self.ctx, agent=agent) + mac_list = self.mixin.get_dvr_mac_address_list(self.ctx) + self.assertEqual(1, len(mac_list)) + self.assertEqual('host_2', mac_list[0]['host']) + notifier.dvr_mac_address_update.assert_called_once_with( + self.ctx, mac_list) + def test_get_dvr_mac_address_list(self): self._create_dvr_mac_entry('host_1', 'mac_1') self._create_dvr_mac_entry('host_2', 'mac_2')