Add script which can clean patch ports between bridges

Once provider bridges are brought up, they should not send any traffic
to the integration bridge until ovs-agent configures the bridges.
When node is rebooted, patch ports to br-int are preserved because they
are stored in ovsdb.

This patch adds script which can removes such patch ports. This script can
be used e.g. in some systemd unit files or ifcfg scripts and be run
during the node boot process.

Co-authored-by: Jakub Libosvar <jlibosva@redhat.com>
Co-authored-by: Yatin Karel <ykarel@redhat.com>
Co-authored-by: Bogdan Dobrelya <bdobreli@redhat.com>
Change-Id: I99e33e8c9a5146dcae3ac9d98aae0284d4d80f20
This commit is contained in:
Slawek Kaplonski 2020-12-08 13:55:19 +01:00
parent 8d6c301301
commit 86f6ea347a
2 changed files with 183 additions and 0 deletions

@ -0,0 +1,90 @@
# Copyright 2020 Red Hat, Inc.
# 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 sys
from neutron_lib.plugins import utils as p_utils
from neutron_lib.utils import helpers
from oslo_config import cfg
from oslo_log import log as logging
from neutron.agent.common import ovs_lib
from neutron.common import config as common_config
from neutron.conf.agent import common as agent_config
from neutron.conf.plugins.ml2.drivers import ovs_conf
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
LOG = logging.getLogger(__name__)
def get_patch_port_names(bridge_name):
int_if_name = p_utils.get_interface_name(
bridge_name, prefix=constants.PEER_INTEGRATION_PREFIX)
phys_if_name = p_utils.get_interface_name(
bridge_name, prefix=constants.PEER_PHYSICAL_PREFIX)
return int_if_name, phys_if_name
class PatchPortCleaner(object):
def __init__(self, config):
LOG.debug("Get OVS bridge mappings")
mappings = helpers.parse_mappings(config.OVS.bridge_mappings)
self.bridges = [ovs_lib.OVSBridge(bridge)
for bridge in mappings.values()]
self.int_br = ovs_lib.OVSBridge(config.OVS.integration_bridge)
def destroy_patch_ports(self):
if (not self.int_br.bridge_exists(self.int_br.br_name) or
self.flows_configured()):
# integration bridge hasn't been created by agent yet or it's been
# already configured by the agent
return
for bridge in self.bridges:
try:
LOG.debug("Remove patch port from bridge %s", bridge.br_name)
self._remove_patch_ports_from_int_br(bridge)
except Exception as e:
LOG.error("Failed to remove patch port from bridge %s: %s",
bridge.br_name, e)
def _remove_patch_ports_from_int_br(self, bridge):
int_if_name, phys_if_name = get_patch_port_names(
bridge.br_name)
int_type = self.int_br.db_get_val(
"Interface", int_if_name, "type", log_errors=False)
if int_type == 'patch':
self.int_br.delete_port(int_if_name)
bridge.delete_port(phys_if_name)
def flows_configured(self):
"""Return True if the integration bridge has flows already configured.
"""
LOG.debug("Get configured flows for integration bridge %s",
self.int_br.br_name)
return bool(self.int_br.dump_flows_for(table=constants.CANARY_TABLE))
def main():
common_config.init(sys.argv[1:])
ovs_conf.register_ovs_agent_opts()
common_config.setup_logging()
agent_config.setup_privsep()
port_cleaner = PatchPortCleaner(cfg.CONF)
port_cleaner.destroy_patch_ports()
if __name__ == "__main__":
main()

@ -0,0 +1,93 @@
# Copyright 2020 Red Hat, Inc.
# 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.
from neutron_lib import constants as n_const
from oslo_config import cfg
from neutron.cmd import destroy_patch_ports
from neutron.common import utils
from neutron.conf.plugins.ml2.drivers import ovs_conf
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
from neutron.tests.common import net_helpers
from neutron.tests.functional import base
class TestDestroyPatchPorts(base.BaseSudoTestCase):
def setUp(self):
super(TestDestroyPatchPorts, self).setUp()
self.int_br = self.useFixture(net_helpers.OVSBridgeFixture()).bridge
bridge_mappings = {}
self.bridges = []
for network in ('foo', 'bar'):
bridge = self.useFixture(net_helpers.OVSBridgeFixture()).bridge
self._create_patch_ports_to_int_br(bridge)
self.bridges.append(bridge)
bridge_mappings[network] = bridge.br_name
self.config = self._create_config_file(bridge_mappings)
def _create_config_file(self, bridge_mappings):
config = cfg.ConfigOpts()
ovs_conf.register_ovs_agent_opts(config)
config.set_override('integration_bridge', self.int_br.br_name, "OVS")
config.set_override(
'bridge_mappings',
','.join(["%s:%s" % (net, br)
for net, br in bridge_mappings.items()]),
"OVS")
return config
def _create_patch_ports_to_int_br(self, bridge):
int_if_name, phys_if_name = destroy_patch_ports.get_patch_port_names(
bridge.br_name)
self.int_br.add_patch_port(
int_if_name, constants.NONEXISTENT_PEER)
bridge.add_patch_port(
phys_if_name, constants.NONEXISTENT_PEER)
self.int_br.set_db_attribute(
'Interface', int_if_name, 'options', {'peer': phys_if_name})
bridge.set_db_attribute(
'Interface', phys_if_name, 'options', {'peer': int_if_name})
def _has_patch_ports(self, bridge):
int_if_name, phys_if_name = destroy_patch_ports.get_patch_port_names(
bridge.br_name)
return (bridge.port_exists(phys_if_name) and
self.int_br.port_exists(int_if_name))
def _assert_has_all_ports(self):
self.assertTrue(all(self._has_patch_ports(bridge)
for bridge in self.bridges))
def test_destroy_patch_ports(self):
self._assert_has_all_ports()
cleaner = destroy_patch_ports.PatchPortCleaner(self.config)
cleaner.destroy_patch_ports()
self.assertFalse(any(self._has_patch_ports(bridge)
for bridge in self.bridges))
def test_destroy_patch_ports_no_int_br(self):
name = utils.get_rand_name(
max_length=n_const.DEVICE_NAME_MAX_LEN)
self.config.set_override('integration_bridge', name, "OVS")
cleaner = destroy_patch_ports.PatchPortCleaner(self.config)
cleaner.destroy_patch_ports()
def test_destroy_patch_ports_canary_flow_on_int_br(self):
self.int_br.add_flow(table=constants.CANARY_TABLE, actions="drop")
self._assert_has_all_ports()
cleaner = destroy_patch_ports.PatchPortCleaner(self.config)
cleaner.destroy_patch_ports()
self._assert_has_all_ports()