From 27f60c314bc9de5d81571de1437f93ca232f1382 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Mathieu=20Gagne=CC=81?= Date: Tue, 8 Sep 2015 17:07:07 -0400 Subject: [PATCH] Add neutron-linuxbridge-cleanup util Removal of empty bridges have been disabled [1] to fix a race condition between Nova and Neutron where a bridge would be removed if the only instance using it is rebooted. This means empty bridges will pile up over time. This script can be used to periodically remove empty bridges by running it on compute nodes. Note: Usage of this script can still trigger the original race condition. It should be used when you don't expect anyone do be doing operations on their instances. [1] Commit 8dd8a7d93564168b98fa2350eedf56acede42b0f DocImpact: Add neutron-linuxbridge-cleanup util Related-bug: #1328546 Closes-bug: #1497027 Co-Authored-By: Cedric Brandily Change-Id: Ieb2796381579ad295abf361ce483d979a53d2bd6 --- neutron/cmd/linuxbridge_cleanup.py | 76 ++++++++++++++++ neutron/tests/common/net_helpers.py | 6 +- .../tests/contrib/functional-testing.filters | 3 + .../cmd/test_linuxbridge_cleanup.py | 89 +++++++++++++++++++ neutron/tests/tools.py | 18 ++++ setup.cfg | 1 + 6 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 neutron/cmd/linuxbridge_cleanup.py create mode 100644 neutron/tests/functional/cmd/test_linuxbridge_cleanup.py diff --git a/neutron/cmd/linuxbridge_cleanup.py b/neutron/cmd/linuxbridge_cleanup.py new file mode 100644 index 000000000..3ecb315ab --- /dev/null +++ b/neutron/cmd/linuxbridge_cleanup.py @@ -0,0 +1,76 @@ +# 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 sys + +from oslo_config import cfg +from oslo_log import log as logging + +from neutron.common import config +from neutron.common import utils as n_utils +from neutron.i18n import _LE, _LI +from neutron.plugins.ml2.drivers.linuxbridge.agent \ + import linuxbridge_neutron_agent + + +LOG = logging.getLogger(__name__) + + +def remove_empty_bridges(): + try: + interface_mappings = n_utils.parse_mappings( + cfg.CONF.LINUX_BRIDGE.physical_interface_mappings) + except ValueError as e: + LOG.error(_LE("Parsing physical_interface_mappings failed: %s."), e) + sys.exit(1) + LOG.info(_LI("Interface mappings: %s."), interface_mappings) + + try: + bridge_mappings = n_utils.parse_mappings( + cfg.CONF.LINUX_BRIDGE.bridge_mappings) + except ValueError as e: + LOG.error(_LE("Parsing bridge_mappings failed: %s."), e) + sys.exit(1) + LOG.info(_LI("Bridge mappings: %s."), bridge_mappings) + + lb_manager = linuxbridge_neutron_agent.LinuxBridgeManager( + bridge_mappings, interface_mappings) + + # NOTE(mgagne) Don't remove pre-existing user-defined bridges + bridge_names = set(lb_manager.get_all_neutron_bridges()) + bridge_names -= set(bridge_mappings.values()) + + for bridge_name in bridge_names: + if lb_manager.get_tap_devices_count(bridge_name): + continue + + try: + lb_manager.delete_bridge(bridge_name) + LOG.info(_LI("Linux bridge %s deleted"), bridge_name) + except RuntimeError: + LOG.exception(_LE("Linux bridge %s delete failed"), bridge_name) + LOG.info(_LI("Linux bridge cleanup completed successfully")) + + +def main(): + """Main method for cleaning up empty linux bridges. + + This tool deletes every empty linux bridge managed by linuxbridge agent + (brq.* linux bridges) except thes ones defined using bridge_mappings option + in section LINUX_BRIDGE (created by deployers). + + This tool should not be called during an instance create, migrate, etc. as + it can delete a linux bridge about to be used by nova. + """ + cfg.CONF(sys.argv[1:]) + config.setup_logging() + remove_empty_bridges() diff --git a/neutron/tests/common/net_helpers.py b/neutron/tests/common/net_helpers.py index a79c1ff20..143345dc6 100644 --- a/neutron/tests/common/net_helpers.py +++ b/neutron/tests/common/net_helpers.py @@ -518,10 +518,14 @@ class LinuxBridgeFixture(fixtures.Fixture): :type namespace: str """ + def __init__(self, prefix=BR_PREFIX): + super(LinuxBridgeFixture, self).__init__() + self.prefix = prefix + def _setUp(self): self.namespace = self.useFixture(NamespaceFixture()).name self.bridge = common_base.create_resource( - BR_PREFIX, + self.prefix, bridge_lib.BridgeDevice.addbr, namespace=self.namespace) self.addCleanup(self.bridge.delbr) diff --git a/neutron/tests/contrib/functional-testing.filters b/neutron/tests/contrib/functional-testing.filters index c0c7b18ea..eb6169e97 100644 --- a/neutron/tests/contrib/functional-testing.filters +++ b/neutron/tests/contrib/functional-testing.filters @@ -18,3 +18,6 @@ nc_kill: KillFilter, root, nc, -9 ncbsd_kill: KillFilter, root, nc.openbsd, -9 ncat_kill: KillFilter, root, ncat, -9 ss_filter: CommandFilter, ss, root + +# enable neutron-linuxbridge-cleanup from namespace +lb_cleanup_filter: RegExpFilter, neutron-linuxbridge-cleanup, root, neutron-linuxbridge-cleanup, --config-file, .* diff --git a/neutron/tests/functional/cmd/test_linuxbridge_cleanup.py b/neutron/tests/functional/cmd/test_linuxbridge_cleanup.py new file mode 100644 index 000000000..a3c13ddc1 --- /dev/null +++ b/neutron/tests/functional/cmd/test_linuxbridge_cleanup.py @@ -0,0 +1,89 @@ +# Copyright (c) 2015 Thales Services SAS +# 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 fixtures +import mock + +from neutron.agent.linux import ip_lib +from neutron.common import constants +from neutron.plugins.ml2.drivers.linuxbridge.agent import \ + linuxbridge_neutron_agent as lb_agent +from neutron.tests.common import config_fixtures +from neutron.tests.common import net_helpers +from neutron.tests.functional import base +from neutron.tests import tools + + +class LinuxbridgeCleanupTest(base.BaseSudoTestCase): + + def _test_linuxbridge_cleanup(self, bridge_exists, callback): + br_fixture = self.useFixture( + tools.SafeCleanupFixture( + net_helpers.LinuxBridgeFixture( + prefix=lb_agent.BRIDGE_NAME_PREFIX))).fixture + + config = callback(br_fixture) + + temp_dir = self.useFixture(fixtures.TempDir()).path + conf = self.useFixture(config_fixtures.ConfigFileFixture( + base_filename='neutron.conf', + config=config, + temp_dir=temp_dir)) + + cmd = 'neutron-linuxbridge-cleanup', '--config-file', conf.filename + ip_wrapper = ip_lib.IPWrapper(br_fixture.namespace) + ip_wrapper.netns.execute(cmd) + + self.assertEqual(bridge_exists, ip_lib.device_exists( + br_fixture.bridge.name, br_fixture.namespace)) + + def test_cleanup_empty_bridge(self): + + def callback(br_fixture): + return config_fixtures.ConfigDict() + + self._test_linuxbridge_cleanup(False, callback) + + def test_no_cleanup_bridge_with_tap(self): + + def callback(br_fixture): + # TODO(cbrandily): refactor net_helpers to avoid mocking it + mock.patch.object( + net_helpers, 'VETH0_PREFIX', + new_callable=mock.PropertyMock( + return_value=constants.TAP_DEVICE_PREFIX + '0')).start() + mock.patch.object( + net_helpers, 'VETH1_PREFIX', + new_callable=mock.PropertyMock( + return_value=constants.TAP_DEVICE_PREFIX + '1')).start() + + self.useFixture( + tools.SafeCleanupFixture( + net_helpers.LinuxBridgePortFixture( + br_fixture.bridge, br_fixture.namespace))) + return config_fixtures.ConfigDict() + + self._test_linuxbridge_cleanup(True, callback) + + def test_no_cleanup_bridge_in_bridge_mappings(self): + + def callback(br_fixture): + br_name = br_fixture.bridge.name + conf = config_fixtures.ConfigDict() + conf.update( + {'LINUX_BRIDGE': {'bridge_mappings': 'physnet:%s' % br_name}}) + return conf + + self._test_linuxbridge_cleanup(True, callback) diff --git a/neutron/tests/tools.py b/neutron/tests/tools.py index 63f730f8c..f3af67e0b 100644 --- a/neutron/tests/tools.py +++ b/neutron/tests/tools.py @@ -65,6 +65,24 @@ class WarningsFixture(fixtures.Fixture): "always", category=wtype, module='^neutron\\.') +class SafeCleanupFixture(fixtures.Fixture): + """Catch errors in daughter fixture cleanup.""" + + def __init__(self, fixture): + self.fixture = fixture + + def _setUp(self): + + def cleanUp(): + try: + self.fixture.cleanUp() + except Exception: + pass + + self.fixture.setUp() + self.addCleanup(cleanUp) + + """setup_mock_calls and verify_mock_calls are convenient methods to setup a sequence of mock calls. diff --git a/setup.cfg b/setup.cfg index 49486dfcf..488474233 100644 --- a/setup.cfg +++ b/setup.cfg @@ -82,6 +82,7 @@ console_scripts = neutron-ipset-cleanup = neutron.cmd.ipset_cleanup:main neutron-l3-agent = neutron.cmd.eventlet.agents.l3:main neutron-linuxbridge-agent = neutron.plugins.ml2.drivers.linuxbridge.agent.linuxbridge_neutron_agent:main + neutron-linuxbridge-cleanup = neutron.cmd.linuxbridge_cleanup:main neutron-metadata-agent = neutron.cmd.eventlet.agents.metadata:main neutron-mlnx-agent = neutron.cmd.eventlet.plugins.mlnx_neutron_agent:main neutron-netns-cleanup = neutron.cmd.netns_cleanup:main -- 2.45.2