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 8dd8a7d935

DocImpact: Add neutron-linuxbridge-cleanup util
Related-bug: #1328546
Closes-bug: #1497027
Co-Authored-By: Cedric Brandily <zzelle@gmail.com>
Change-Id: Ieb2796381579ad295abf361ce483d979a53d2bd6
This commit is contained in:
Mathieu Gagné 2015-09-08 17:07:07 -04:00 committed by Cedric Brandily
parent 739dc16fe7
commit 27f60c314b
6 changed files with 192 additions and 1 deletions

View File

@ -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()

View File

@ -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)

View File

@ -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, .*

View File

@ -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)

View File

@ -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.

View File

@ -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