[node-patches] Change in ovirt-node[master]: [DRAFT] network: Add bonding support

fabiand at fedoraproject.org fabiand at fedoraproject.org
Fri Jun 14 10:35:51 UTC 2013


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 <fabiand at fedoraproject.org>
---
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
 
 
+ at patch("ovirt.node.utils.fs.File", FakeFs.File)
+ at patch.object(UdevNICInfo, "vendor")
+ at patch.object(UdevNICInfo, "devtype")
+ at patch.object(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 <fabiand at fedoraproject.org>



More information about the node-patches mailing list