Fabian Deutsch has uploaded a new change for review. Change subject: [DRAFT] network: Add bonding support ......................................................................
[DRAFT] network: Add bonding support This patch adds support for nic bonding. OVIRT_BOND_NAME, OVIRT_BOND_SLAVES and OVIRT_BOND_OPTIONS are new keywords which can be used to create a bond device. Bug-Url: https://bugzilla.redhat.com/show_bug.cgi?id=831318 Change-Id: I9ec23904942649baa031a61f194ee782b63019b0 Signed-off-by: Fabian Deutsch <[email protected]> --- M src/ovirt/node/config/defaults.py M src/ovirt/node/config/network.py M tests/nose/network_config.py 3 files changed, 206 insertions(+), 20 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-node refs/changes/14/15714/1 diff --git a/src/ovirt/node/config/defaults.py b/src/ovirt/node/config/defaults.py index bdc0dd6..4283c6d 100644 --- a/src/ovirt/node/config/defaults.py +++ b/src/ovirt/node/config/defaults.py @@ -310,7 +310,12 @@ def commit(self): m = Network().retrieve() aug = AugeasWrapper() - topology = NetworkLayout().retrieve()["topology"] + + bond = NicBonding().retrieve() + if bond["slaves"]: + NicBonding().transaction().commit() + + topology = NetworkLayout().retrieve()["layout"] if topology == "bridged": self.__write_bridged_config() else: @@ -373,6 +378,8 @@ slave_cfg.vlan = "yes" if m["vlanid"] else None slave_cfg.onboot = "yes" + self.__assign_bond_master(slave_cfg) + # save()includes persisting bridge_cfg.save() slave_cfg.save() @@ -389,7 +396,17 @@ nic_cfg.device = nic_ifname nic_cfg.hwaddr = NIC(m["iface"]).hwaddr + self.__assign_bond_master(nic_cfg) + nic_cfg.save() + + def __assign_bond_master(self, cfg): + m = Network().retrieve() + m_bond = NicBonding().retrieve() + if m_bond["options"]: + if m["iface"] != m_bond["name"]: + raise RuntimeError("Bond configured but not used") + cfg.bonding_opts = m_bond["options"] class PersistMacNicMapping(utils.Transaction.Element): title = "Persist MAC-NIC Mappings" @@ -420,6 +437,63 @@ return tx +class NicBonding(NodeConfigFileSection): + """Create a bonding device + - OVIRT_BOND + + >>> from ovirt.node.utils import fs + >>> n = NicBonding(fs.FakeFs.File("dst")) + >>> n.update("bond0", ["ens1", "ens2", "ens3"], "mode=4") + >>> data = sorted(n.retrieve().items()) + >>> data[:2] + [('name', 'bond0'), ('options', 'mode=4')] + >>> data [2:] + [('slaves', ['ens1', 'ens2', 'ens3'])] + """ + keys = ("OVIRT_BOND_NAME", + "OVIRT_BOND_SLAVES", + "OVIRT_BOND_OPTIONS") + + @NodeConfigFileSection.map_and_update_defaults_decorator + def update(self, name, slaves, options): + if not name.startswith("bond"): + raise RuntimeError("Bond ifname must start with 'bond'") + assert type(slaves) is list + return {"OVIRT_BOND_SLAVES": ",".join(slaves) if slaves else None} + + def retrieve(self): + cfg = super(NicBonding, self).retrieve() + cfg.update({"slaves": (cfg["slaves"].split(",") if cfg["slaves"] + else None)}) + return cfg + + def configure_8023ad(self, name, slaves): + return self.update(name, slaves, "mode=4") + + def transaction(self): + bond = NicBonding().retrieve() + + class WriteSlaveConfigs(utils.Transaction.Element): + title = "Writing bond slaves configuration" + + def commit(self): + m = Network().retrieve() + if m["iface"] in bond["slaves"]: + raise RuntimeError("Bond slave can not be used as " + + "primary device") + + for slave in bond["slaves"]: + slave_cfg = NicConfig(slave) + slave_cfg.device = slave + slave_cfg.slave = "yes" + slave_cfg.master = bond["name"] + slave_cfg.onboot = "yes" + slave_cfg.save() + + tx = utils.Transaction("Writing bond configuration") + tx.append(WriteSlaveConfigs()) + return tx + class NetworkLayout(NodeConfigFileSection): """Sets the network topology - OVIRT_NETWORK_TOPOLOGY @@ -428,19 +502,19 @@ >>> n = NetworkLayout(fs.FakeFs.File("dst")) >>> n.update("bridged") >>> sorted(n.retrieve().items()) - [('topology', 'bridged')] + [('layout', 'bridged')] """ keys = ("OVIRT_NETWORK_LAYOUT",) - known_topologies = ["bridged", - # bridged way, a bridge is created for BOOTIF + known_layouts = ["bridged", + # bridged way, a bridge is created for BOOTIF - "direct" - # The BOOTIF NIC is configured directly - ] + "direct" + # The BOOTIF NIC is configured directly + ] @NodeConfigFileSection.map_and_update_defaults_decorator - def update(self, topology): - assert topology in self.known_topologies + def update(self, layout): + assert layout in self.known_layouts def configure_bridged(self): return self.update("bridged") diff --git a/src/ovirt/node/config/network.py b/src/ovirt/node/config/network.py index dc1c4a1..b694955 100644 --- a/src/ovirt/node/config/network.py +++ b/src/ovirt/node/config/network.py @@ -60,6 +60,10 @@ peerntp = None peerdns = None + master = None + slave = None + bonding_opts = None + vlan_parent = None _backend = None @@ -67,7 +71,8 @@ "gateway", "vlan", "device", "onboot", "hwaddr", "ipv6init", "ipv6forwarding", "ipv6_autoconf", "dhcpv6c", "ipv6addr", "ipv6_defaultgw", "delay", - "peerntp", "peerdns"] + "peerntp", "peerdns", + "master", "slave", "bonding_opts"] def __init__(self, ifname): super(NicConfig, self).__init__() diff --git a/tests/nose/network_config.py b/tests/nose/network_config.py index faba753..eeecd29 100644 --- a/tests/nose/network_config.py +++ b/tests/nose/network_config.py @@ -36,8 +36,6 @@ FakeFs.erase() def test_basic(self): - """Ensure that FakeFs is working - """ FakeFs.erase() with patch("ovirt.node.utils.fs.File", FakeFs.File): f = fs.File("new-file") @@ -64,8 +62,6 @@ FakeFs.erase() def test_dhcp(self, *args, **kwargs): - """Test BridgedNIC with DHCP configuration file creation - """ m = defaults.Network() mt = defaults.NetworkLayout() @@ -84,8 +80,6 @@ ('PEERNTP', 'yes'), ('TYPE', 'Bridge')]) def test_static(self, *args, **kwargs): - """Test BridgedNIC with static IP configuration file creation - """ m = defaults.Network() mt = defaults.NetworkLayout() @@ -128,10 +122,9 @@ """Test bridgeless with DHCP configuration file creation """ mt = defaults.NetworkLayout() - mt.configure_direct() - m = defaults.Network() + mt.configure_direct() m.configure_dhcp("eth0") run_tx_by_name(m.transaction(), "WriteConfiguration") @@ -147,10 +140,9 @@ """Test bridgeless with static IP configuration file creation """ mt = defaults.NetworkLayout() - mt.configure_direct() - m = defaults.Network() + mt.configure_direct() m.configure_static("ens1", "192.168.122.42", "255.255.255.0", "192.168.122.1", None) @@ -168,6 +160,121 @@ assert "brens1" not in FakeFs.filemap +@patch("ovirt.node.utils.fs.File", FakeFs.File) [email protected](UdevNICInfo, "vendor") [email protected](UdevNICInfo, "devtype") [email protected](SysfsNICInfo, "hwaddr", "th:em:ac:ad:dr") +class TestBond(): + """Test bonding configuration + """ + def setUp(self): + FakeFs.erase() + FakeFs.File("/etc/default/ovirt").touch() + + def tearDown(self): + FakeFs.erase() + + def test_direct_dhcp(self, *args, **kwargs): + mb = defaults.NicBonding() + mt = defaults.NetworkLayout() + m = defaults.Network() + + mb.configure_8023ad("bond0", ["ens1", "ens2", "ens3"]) + m.configure_dhcp("bond0") + mt.configure_direct() + + run_tx_by_name(m.transaction(), "WriteConfiguration") + + assert_ifcfg_has_items("bond0", + [('BONDING_OPTS', 'mode=4'), + ('BOOTPROTO', 'dhcp'), + ('DEVICE', 'bond0'), + ('HWADDR', 'th:em:ac:ad:dr'), + ('ONBOOT', 'yes'), ('PEERNTP', 'yes')]) + + assert_ifcfg_has_items("ens1", + [('DEVICE', 'ens1'), ('MASTER', 'bond0'), + ('ONBOOT', 'yes'), ('SLAVE', 'yes')]) + + assert_ifcfg_has_items("ens2", + [('DEVICE', 'ens2'), ('MASTER', 'bond0'), + ('ONBOOT', 'yes'), ('SLAVE', 'yes')]) + + assert_ifcfg_has_items("ens3", + [('DEVICE', 'ens3'), ('MASTER', 'bond0'), + ('ONBOOT', 'yes'), ('SLAVE', 'yes')]) + + assert len(FakeFs.filemap) == (1 + 1 + 3) + + def test_bridged_dhcp(self, *args, **kwargs): + mb = defaults.NicBonding() + mt = defaults.NetworkLayout() + m = defaults.Network() + + mb.configure_8023ad("bond0", ["ens1", "ens2", "ens3"]) + m.configure_dhcp("bond0") + mt.configure_bridged() + + run_tx_by_name(m.transaction(), "WriteConfiguration") + + assert_ifcfg_has_items("bond0", + [('BONDING_OPTS', 'mode=4'), + ('BRIDGE', 'brbond0'), + ('DEVICE', 'bond0'), + ('HWADDR', 'th:em:ac:ad:dr'), + ('ONBOOT', 'yes')]) + + assert_ifcfg_has_items("ens1", + [('DEVICE', 'ens1'), ('MASTER', 'bond0'), + ('ONBOOT', 'yes'), ('SLAVE', 'yes')]) + + assert_ifcfg_has_items("ens2", + [('DEVICE', 'ens2'), ('MASTER', 'bond0'), + ('ONBOOT', 'yes'), ('SLAVE', 'yes')]) + + assert_ifcfg_has_items("ens3", + [('DEVICE', 'ens3'), ('MASTER', 'bond0'), + ('ONBOOT', 'yes'), ('SLAVE', 'yes')]) + + assert_ifcfg_has_items("brbond0", + [('BOOTPROTO', 'dhcp'), + ('DELAY', '0'), + ('DEVICE', 'brbond0'), + ('ONBOOT', 'yes'), + ('PEERNTP', 'yes'), + ('TYPE', 'Bridge')]) + + assert len(FakeFs.filemap) == (1 + 1 + 3 + 1) + + def test_bond_slave_as_primary(self, *args, **kwargs): + mb = defaults.NicBonding() + m = defaults.Network() + + # ens1 is used as a slave, but then also as a primary device + # this doesn't work + mb.configure_8023ad("bond0", ["ens1", "ens2", "ens3"]) + m.configure_dhcp("ens1") + + try: + run_tx_by_name(m.transaction(), "WriteConfiguration") + except RuntimeError as e: + assert e.message == ("Bond slave can not be used as " + + "primary device") + + def test_unused_bond(self, *args, **kwargs): + mb = defaults.NicBonding() + m = defaults.Network() + + # bond0 is created, but ens42 is used + mb.configure_8023ad("bond0", ["ens1", "ens2", "ens3"]) + m.configure_dhcp("ens42") + + try: + run_tx_by_name(m.transaction(), "WriteConfiguration") + except RuntimeError as e: + assert e.message == "Bond configured but not used" + + def run_tx_by_name(txs, name): tx = None for _tx in txs: -- To view, visit http://gerrit.ovirt.org/15714 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I9ec23904942649baa031a61f194ee782b63019b0 Gerrit-PatchSet: 1 Gerrit-Project: ovirt-node Gerrit-Branch: master Gerrit-Owner: Fabian Deutsch <[email protected]> _______________________________________________ node-patches mailing list [email protected] http://lists.ovirt.org/mailman/listinfo/node-patches
