diff --git a/neutron/agent/linux/ovs_lib.py b/neutron/agent/linux/ovs_lib.py index 097630707a..6a4e7c652c 100644 --- a/neutron/agent/linux/ovs_lib.py +++ b/neutron/agent/linux/ovs_lib.py @@ -44,10 +44,52 @@ class VifPort: self.switch.br_name) -class OVSBridge: - def __init__(self, br_name, root_helper): - self.br_name = br_name +class BaseOVS(object): + + def __init__(self, root_helper): self.root_helper = root_helper + + def run_vsctl(self, args, check_error=False): + full_args = ["ovs-vsctl", "--timeout=2"] + args + try: + return utils.execute(full_args, root_helper=self.root_helper) + except Exception as e: + LOG.error(_("Unable to execute %(cmd)s. Exception: %(exception)s"), + {'cmd': full_args, 'exception': e}) + if check_error: + raise + + def add_bridge(self, bridge_name): + self.run_vsctl(["--", "--may-exist", "add-br", bridge_name]) + return OVSBridge(bridge_name, self.root_helper) + + def delete_bridge(self, bridge_name): + self.run_vsctl(["--", "--if-exists", "del-br", bridge_name]) + + def bridge_exists(self, bridge_name): + try: + self.run_vsctl(['br-exists', bridge_name], check_error=True) + except RuntimeError as e: + if 'Exit code: 2\n' in str(e): + return False + raise + return True + + def get_bridge_name_for_port_name(self, port_name): + try: + return self.run_vsctl(['port-to-br', port_name], check_error=True) + except RuntimeError as e: + if 'Exit code: 1\n' not in str(e): + raise + + def port_exists(self, port_name): + return bool(self.get_bridge_name_for_port_name(port_name)) + + +class OVSBridge(BaseOVS): + def __init__(self, br_name, root_helper): + super(OVSBridge, self).__init__(root_helper) + self.br_name = br_name self.re_id = self.re_compile_id() self.defer_apply_flows = False self.deferred_flows = {'add': '', 'mod': '', 'del': ''} @@ -65,17 +107,15 @@ class OVSBridge: 'port': port}) return re.compile(_re, re.M | re.X) - def run_vsctl(self, args): - full_args = ["ovs-vsctl", "--timeout=2"] + args - try: - return utils.execute(full_args, root_helper=self.root_helper) - except Exception as e: - LOG.error(_("Unable to execute %(cmd)s. Exception: %(exception)s"), - {'cmd': full_args, 'exception': e}) + def create(self): + self.add_bridge(self.br_name) + + def destroy(self): + self.delete_bridge(self.br_name) def reset_bridge(self): - self.run_vsctl(["--", "--if-exists", "del-br", self.br_name]) - self.run_vsctl(["add-br", self.br_name]) + self.destroy() + self.create() def add_port(self, port_name): self.run_vsctl(["--", "--may-exist", "add-port", self.br_name, diff --git a/neutron/tests/unit/openvswitch/test_ovs_lib.py b/neutron/tests/unit/openvswitch/test_ovs_lib.py index d4fef53e6a..415cd14a96 100644 --- a/neutron/tests/unit/openvswitch/test_ovs_lib.py +++ b/neutron/tests/unit/openvswitch/test_ovs_lib.py @@ -26,6 +26,79 @@ from neutron.openstack.common import uuidutils from neutron.tests import base +class TestBaseOVS(base.BaseTestCase): + + def setUp(self): + super(TestBaseOVS, self).setUp() + self.root_helper = 'sudo' + self.ovs = ovs_lib.BaseOVS(self.root_helper) + self.br_name = 'bridge1' + + def test_add_bridge(self): + with mock.patch.object(self.ovs, 'run_vsctl') as mock_vsctl: + bridge = self.ovs.add_bridge(self.br_name) + + mock_vsctl.assert_called_with(["--", "--may-exist", + "add-br", self.br_name]) + self.assertEqual(bridge.br_name, self.br_name) + self.assertEqual(bridge.root_helper, self.ovs.root_helper) + + def test_delete_bridge(self): + with mock.patch.object(self.ovs, 'run_vsctl') as mock_vsctl: + self.ovs.delete_bridge(self.br_name) + mock_vsctl.assert_called_with(["--", "--if-exists", "del-br", + self.br_name]) + + def test_bridge_exists_returns_true(self): + with mock.patch.object(self.ovs, 'run_vsctl') as mock_vsctl: + self.assertTrue(self.ovs.bridge_exists(self.br_name)) + mock_vsctl.assert_called_with(['br-exists', self.br_name], + check_error=True) + + def test_bridge_exists_returns_false_for_exit_code_2(self): + with mock.patch.object(self.ovs, 'run_vsctl', + side_effect=RuntimeError('Exit code: 2\n')): + self.assertFalse(self.ovs.bridge_exists('bridge1')) + + def test_bridge_exists_raises_unknown_exception(self): + with mock.patch.object(self.ovs, 'run_vsctl', + side_effect=RuntimeError()): + with testtools.ExpectedException(RuntimeError): + self.ovs.bridge_exists('bridge1') + + def test_get_bridge_name_for_port_name_returns_bridge_for_valid_port(self): + port_name = 'bar' + with mock.patch.object(self.ovs, 'run_vsctl', + return_value=self.br_name) as mock_vsctl: + bridge = self.ovs.get_bridge_name_for_port_name(port_name) + self.assertEqual(bridge, self.br_name) + mock_vsctl.assert_called_with(['port-to-br', port_name], + check_error=True) + + def test_get_bridge_name_for_port_name_returns_none_for_exit_code_1(self): + with mock.patch.object(self.ovs, 'run_vsctl', + side_effect=RuntimeError('Exit code: 1\n')): + self.assertFalse(self.ovs.get_bridge_name_for_port_name('bridge1')) + + def test_get_bridge_name_for_port_name_raises_unknown_exception(self): + with mock.patch.object(self.ovs, 'run_vsctl', + side_effect=RuntimeError()): + with testtools.ExpectedException(RuntimeError): + self.ovs.get_bridge_name_for_port_name('bridge1') + + def _test_port_exists(self, br_name, result): + with mock.patch.object(self.ovs, + 'get_bridge_name_for_port_name', + return_value=br_name): + self.assertEqual(self.ovs.port_exists('bar'), result) + + def test_port_exists_returns_true_for_bridge_name(self): + self._test_port_exists(self.br_name, True) + + def test_port_exists_returns_false_for_none(self): + self._test_port_exists(None, False) + + class OVS_Lib_Test(base.BaseTestCase): """A test suite to excercise the OVS libraries shared by Neutron agents. @@ -66,12 +139,23 @@ class OVS_Lib_Test(base.BaseTestCase): self.mox.VerifyAll() + def test_create(self): + self.br.add_bridge(self.BR_NAME) + self.mox.ReplayAll() + + self.br.create() + self.mox.VerifyAll() + + def test_destroy(self): + self.br.delete_bridge(self.BR_NAME) + self.mox.ReplayAll() + + self.br.destroy() + self.mox.VerifyAll() + def test_reset_bridge(self): - utils.execute(["ovs-vsctl", self.TO, "--", - "--if-exists", "del-br", self.BR_NAME], - root_helper=self.root_helper) - utils.execute(["ovs-vsctl", self.TO, "add-br", self.BR_NAME], - root_helper=self.root_helper) + self.br.destroy() + self.br.create() self.mox.ReplayAll() self.br.reset_bridge()