diff --git a/neutron/cmd/linuxbridge_cleanup.py b/neutron/cmd/linuxbridge_cleanup.py new file mode 100644 index 00000000000..3ecb315ab2d --- /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 a79c1ff20b0..143345dc6bb 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 c0c7b18ea76..eb6169e9743 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 00000000000..a3c13ddc1be --- /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 63f730f8c21..f3af67e0b17 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 49486dfcf68..48847423319 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