diff --git a/bin/quantum-ovs-cleanup b/bin/quantum-ovs-cleanup new file mode 100755 index 000000000..096b8a73d --- /dev/null +++ b/bin/quantum-ovs-cleanup @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2012 Openstack, LLC. +# 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 os +import sys +sys.path.insert(0, os.getcwd()) + +from quantum.agent.ovs_cleanup_util import main + + +main() diff --git a/quantum/agent/linux/ovs_lib.py b/quantum/agent/linux/ovs_lib.py index 00f6586b0..6e19602fa 100644 --- a/quantum/agent/linux/ovs_lib.py +++ b/quantum/agent/linux/ovs_lib.py @@ -271,6 +271,15 @@ class OVSBridge: LOG.info(_("Unable to parse regex results. Exception: %s"), e) return + def delete_ports(self, all_ports=False): + if all_ports: + port_names = self.get_port_name_list() + else: + port_names = (port.port_name for port in self.get_vif_ports()) + + for port_name in port_names: + self.delete_port(port_name) + def get_bridge_for_iface(root_helper, iface): args = ["ovs-vsctl", "--timeout=2", "iface-to-br", iface] @@ -279,3 +288,12 @@ def get_bridge_for_iface(root_helper, iface): except Exception, e: LOG.error(_("iface %s not found. Exception: %s"), iface, e) return None + + +def get_bridges(root_helper): + args = ["ovs-vsctl", "--timeout=2", "list-br"] + try: + return utils.execute(args, root_helper=root_helper).strip().split("\n") + except Exception, e: + LOG.error(_("Unable to retrieve bridges. Exception: %s"), e) + return [] diff --git a/quantum/agent/ovs_cleanup_util.py b/quantum/agent/ovs_cleanup_util.py new file mode 100644 index 000000000..46bb3de7f --- /dev/null +++ b/quantum/agent/ovs_cleanup_util.py @@ -0,0 +1,75 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2012 OpenStack LLC. +# 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 quantum.agent import l3_agent +from quantum.agent.linux import interface +from quantum.agent.linux import ovs_lib +from quantum.common import config +from quantum.openstack.common import cfg +from quantum.openstack.common import log as logging + + +LOG = logging.getLogger(__name__) + + +def setup_conf(): + """Setup the cfg for the clean up utility. + + Use separate setup_conf for the utility because there are many options + from the main config that do not apply during clean-up. + """ + opts = [ + cfg.BoolOpt('ovs_all_ports', + default=False, + help='True deletes all ports on the bridge. False deletes ' + 'those created by Quantum.'), + ] + + conf = cfg.CommonConfigOpts() + conf.register_opts(opts) + conf.register_opts(l3_agent.L3NATAgent.OPTS) + conf.register_opts(interface.OPTS) + config.setup_logging(conf) + return conf + + +def main(): + """Main method for cleaning up OVS bridges. + + The utility cleans up the integration bridges used by Quantum. + """ + + conf = setup_conf() + conf(sys.argv) + + configuration_bridges = set([conf.ovs_integration_bridge, + conf.external_network_bridge]) + ovs_bridges = set(ovs_lib.get_bridges(conf.root_helper)) + + if conf.ovs_all_ports: + bridges = ovs_bridges + else: + bridges = configuration_bridges & ovs_bridges + + for bridge in bridges: + LOG.info(_("Cleaning %s"), bridge) + ovs = ovs_lib.OVSBridge(bridge, conf.root_helper) + ovs.delete_ports(all_ports=conf.ovs_all_ports) + + LOG.info(_("OVS cleanup completed successfully")) diff --git a/quantum/tests/unit/openvswitch/test_ovs_lib.py b/quantum/tests/unit/openvswitch/test_ovs_lib.py index 52d8fbc73..d84780da9 100644 --- a/quantum/tests/unit/openvswitch/test_ovs_lib.py +++ b/quantum/tests/unit/openvswitch/test_ovs_lib.py @@ -313,3 +313,37 @@ class OVS_Lib_Test(unittest.TestCase): self.mox.ReplayAll() self.assertIsNone(ovs_lib.get_bridge_for_iface(root_helper, iface)) self.mox.VerifyAll() + + def test_delete_all_ports(self): + self.mox.StubOutWithMock(self.br, 'get_port_name_list') + self.br.get_port_name_list().AndReturn(['port1']) + self.mox.StubOutWithMock(self.br, 'delete_port') + self.br.delete_port('port1') + self.mox.ReplayAll() + self.br.delete_ports(all_ports=True) + self.mox.VerifyAll() + + def test_delete_quantum_ports(self): + port1 = ovs_lib.VifPort('tap1234', 1, uuidutils.generate_uuid(), + 'ca:fe:de:ad:be:ef', 'br') + port2 = ovs_lib.VifPort('tap5678', 2, uuidutils.generate_uuid(), + 'ca:ee:de:ad:be:ef', 'br') + ports = [port1, port2] + self.mox.StubOutWithMock(self.br, 'get_vif_ports') + self.br.get_vif_ports().AndReturn([port1, port2]) + self.mox.StubOutWithMock(self.br, 'delete_port') + self.br.delete_port('tap1234') + self.br.delete_port('tap5678') + self.mox.ReplayAll() + self.br.delete_ports(all_ports=False) + self.mox.VerifyAll() + + def test_get_bridges(self): + bridges = ['br-int', 'br-ex'] + root_helper = 'sudo' + utils.execute(["ovs-vsctl", self.TO, "list-br"], + root_helper=root_helper).AndReturn('br-int\nbr-ex\n') + + self.mox.ReplayAll() + self.assertEqual(ovs_lib.get_bridges(root_helper), bridges) + self.mox.VerifyAll() diff --git a/quantum/tests/unit/test_agent_ovs_cleanup.py b/quantum/tests/unit/test_agent_ovs_cleanup.py new file mode 100644 index 000000000..77a60cc69 --- /dev/null +++ b/quantum/tests/unit/test_agent_ovs_cleanup.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2012 OpenStack LLC. +# 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 mock +import unittest2 as unittest + +from quantum.agent.linux import ovs_lib +from quantum.agent import ovs_cleanup_util as util +from quantum.openstack.common import uuidutils + + +class TestOVSCleanup(unittest.TestCase): + def test_setup_conf(self): + with mock.patch('quantum.common.config.setup_logging'): + conf = util.setup_conf() + self.assertEqual(conf.external_network_bridge, 'br-ex') + self.assertEqual(conf.ovs_integration_bridge, 'br-int') + self.assertFalse(conf.ovs_all_ports) + + def test_main(self): + with mock.patch('quantum.common.config.setup_logging'): + br_patch = mock.patch('quantum.agent.linux.ovs_lib.get_bridges') + with br_patch as mock_get_bridges: + mock_get_bridges.return_value = ['br-int', 'br-ex'] + with mock.patch( + 'quantum.agent.linux.ovs_lib.OVSBridge') as ovs: + util.main() + ovs.assert_has_calls([mock.call().delete_ports( + all_ports=False)]) diff --git a/setup.py b/setup.py index dbcd40593..d9c23547c 100644 --- a/setup.py +++ b/setup.py @@ -139,6 +139,7 @@ setuptools.setup( 'quantum.plugins.nec.agent.nec_quantum_agent:main', 'quantum-server = quantum.server:main', 'quantum-debug = quantum.debug.shell:main', + 'quantum-ovs-cleanup = quantum.agent.ovs_cleanup_util:main', ] }, )