#!/usr/bin/python3 -cimport os, sys; os.execv(os.path.dirname(sys.argv[1]) + "/../common/pywrap", sys.argv)## Copyright (C) 2013 Red Hat, Inc.# SPDX-License-Identifier: LGPL-2.1-or-laterimport netlibimport testlibfrom lib.constants import TEST_OS_DEFAULT@testlib.nondestructiveclass TestBonding(netlib.NetworkCase):def testBasic(self):b = self.browserself.login_and_go("/network")b.wait_visible("#networking")iface1 = "cockpit1"self.add_veth(iface1, dhcp_cidr="10.111.113.1/24", dhcp_range=['10.111.113.2', '10.111.113.254'])self.nm_activate_eth(iface1)iface2 = "cockpit2"self.add_veth(iface2, dhcp_cidr="10.111.114.1/24", dhcp_range=['10.111.114.2', '10.111.114.254'])self.nm_activate_eth(iface2)self.wait_for_iface(iface1)self.wait_for_iface(iface2)# Bond themb.click("button:contains('Add bond')")b.wait_visible("#network-bond-settings-dialog")# wait until dialog initializedb.wait_visible("#network-bond-settings-dialog button[aria-label=Close]")b.wait_visible("#network-bond-settings-mac-input")# menu is not visible by default, but gets initialized dynamicallyb.assert_pixels("#network-bond-settings-dialog", "networking-bond-settings-dialog",# help icon gets rendered with unstable noiseignore=["#bond-help-popup-button"])b.click("#network-bond-settings-dialog button[aria-label=Close]")b.wait_not_present("#network-bond-settings-dialog")b.click("button:contains('Add bond')")b.wait_visible("#network-bond-settings-dialog")b.click("#network-bond-settings-dialog #bond-help-popup-button")b.wait_in_text("#popover-bond-help-header", "Network bond")b.click("#network-bond-settings-dialog #bond-help-popup-button")b.wait_not_present("#popover-bond-help-header")b.set_input_text("#network-bond-settings-interface-name-input", "tbond")b.set_checked(f"input[data-iface='{iface1}']", val=True)b.set_checked(f"input[data-iface='{iface2}']", val=True)b.click("#network-bond-settings-dialog button:contains('Add')")b.wait_not_present("#network-bond-settings-dialog")# Check that the members are displayed and both Onb.wait_text("#network-interface-name", "tbond")b.wait_visible(f"#network-interface-members tr[data-interface='{iface1}']")self.wait_onoff(f"#network-interface-members tr[data-interface='{iface1}']", val=True)b.wait_visible(f"#network-interface-members tr[data-interface='{iface2}']")self.wait_onoff(f"#network-interface-members tr[data-interface='{iface2}']", val=True)b.wait_text_not("#network-interface-mac", "")# Check that link monitoring is correctly set upb.click("#network-interface-settings [data-label='Bond'] button")b.wait_val("#network-bond-settings-link-monitoring-select", "mii")b.click("#network-bond-settings-dialog button:contains('Cancel')")b.wait_not_present("#network-bond-settings-dialog")# Navigate to the first member, check it, navigate backb.click(f"#network-interface-members tr[data-interface='{iface1}'] button.pf-m-link")b.wait_in_text("#network-interface-name", iface1)b.wait_in_text("#network-interface-settings", "MTU")b.wait_not_in_text("#network-interface-settings", "IPv4")b.wait_not_in_text("#network-interface-settings", "IPv6")b.click("#network-interface-settings button:contains('tbond')")b.wait_in_text("#network-interface-name", "tbond")# Deactivate the bond and make sure it is still there after a# reload.self.wait_onoff("label[for='interface-switch']", val=False)self.wait_for_iface_setting('Status', 'Inactive')b.wait_not_present("#interface-switch:disabled")b.reload()b.enter_page("/network")b.wait_text("#network-interface-name", "tbond")b.wait_text("#network-interface-hw", "Bond")b.wait_visible(f"#network-interface-members tr[data-interface='{iface1}']")b.wait_visible(f"#network-interface-members tr[data-interface='{iface2}']")# Delete the bondb.click("#network-interface button:contains('Delete')")b.wait_visible("#networking")b.wait_not_present("#networking-interfaces tr[data-interface='tbond']")# Check that the former members are displayed and both Onself.wait_for_iface(iface1)self.wait_for_iface(iface2)def testNonDefaultSettings(self):b = self.browserm = self.machineiface1 = "cockpit1"self.add_veth(iface1, dhcp_cidr="10.111.113.1/24", dhcp_range=['10.111.113.2', '10.111.113.254'])testlib.wait(lambda: m.execute(f'nmcli device | grep {iface1} | grep -v unavailable'))iface2 = "cockpit2"self.add_veth(iface2, dhcp_cidr="10.111.114.1/24", dhcp_range=['10.111.114.2', '10.111.114.254'])testlib.wait(lambda: m.execute(f'nmcli device | grep {iface2} | grep -v unavailable'))m.execute(f"nmcli con add type ethernet ifname {iface1} con-name TEST1")m.execute(f"nmcli con add type ethernet ifname {iface2} con-name TEST2")self.login_and_go("/network")self.wait_for_iface(iface1)self.wait_for_iface(iface2)m.execute("nmcli con mod TEST1 ipv4.method link-local; nmcli con up TEST1")m.execute(f"nmcli dev dis {iface2}")b.wait_in_text(f"tr[data-interface='{iface1}'] td:nth-child(2)", "169.254.")b.wait_not_in_text(f"tr[data-interface='{iface1}'] td:nth-child(2)", "10.111.")b.wait_text(f"tr[data-interface='{iface2}'] td:nth-child(3)", "Inactive")b.click("button:contains('Add bond')")b.wait_visible("#network-bond-settings-dialog")b.set_input_text("#network-bond-settings-interface-name-input", "tbond")b.set_checked(f"input[data-iface='{iface1}']", val=True)b.set_checked(f"input[data-iface='{iface2}']", val=True)b.click("#network-bond-settings-dialog button:contains('Add')")b.wait_not_present("#network-bond-settings-dialog")b.wait_text("#network-interface-name", "tbond")self.wait_for_iface_setting('IPv4', 'Link local')def testRename(self):b = self.browserm = self.machineself.login_and_go("/network")# Wait until the page is really ready by waiting for some ethernet interface# to show up in some row.iface = m.execute("cd /sys/class/net; ls -d e* | head -n1").strip()b.wait_visible(f"tr[data-interface='{iface}']")iface1 = "cockpit1"self.add_veth(iface1, dhcp_cidr="10.111.113.1/24", dhcp_range=['10.111.113.2', '10.111.113.254'])self.nm_activate_eth(iface1)self.wait_for_iface(iface1)b.click("button:contains('Add bond')")b.wait_visible("#network-bond-settings-dialog")b.select_from_dropdown("#network-bond-settings-link-monitoring-select", "arp")b.set_input_text("#network-bond-settings-monitoring-targets-input", "1.1.1.1")b.set_input_text("#network-bond-settings-interface-name-input", "tbond")b.set_checked(f"input[data-iface='{iface1}']", val=True)b.click("#network-bond-settings-dialog button:contains('Add')")b.wait_not_present("#network-bond-settings-dialog")# Rename while it is activeself.addCleanup(m.execute, "nmcli dev delete tbond3000 2>/dev/null || true")b.wait_text("#network-interface-name", "tbond")self.wait_onoff("#network-interface .pf-v6-c-card__header", val=False)self.toggle_onoff("label[for='interface-switch']")self.wait_onoff("#network-interface .pf-v6-c-card__header", val=True)self.configure_iface_setting('Bond')b.wait_visible("#network-bond-settings-dialog")# Check that link monitoring is correctly set upb.wait_val("#network-bond-settings-link-monitoring-select", "arp")b.wait_val("#network-bond-settings-monitoring-targets-input", "1.1.1.1")b.set_input_text("#network-bond-settings-interface-name-input", "tbond3000")b.click("#network-bond-settings-dialog button:contains('Save')")b.wait_not_present("#network-bond-settings-dialog")b.wait_text("#network-interface-name", "tbond3000")# Check that the member is still there and activeb.wait_visible(f"#network-interface-members tr[data-interface='{iface1}']")self.wait_onoff(f"#network-interface-members tr[data-interface='{iface1}']", val=True)def testActive(self):b = self.browserself.login_and_go("/network")b.wait_visible("#networking")iface = "cockpit1"self.add_veth(iface, dhcp_cidr="10.111.112.2/20")self.nm_activate_eth(iface)self.wait_for_iface(iface)ip = b.text(f"#networking-interfaces tr[data-interface='{iface}'] td:nth-child(2)")# Put an active interface into a bond. The bond should get# the same IP as the active interface.b.click("button:contains('Add bond')")b.wait_visible("#network-bond-settings-dialog")b.set_input_text("#network-bond-settings-interface-name-input", "tbond")b.set_checked(f"input[data-iface='{iface}']", val=True)b.click("#network-bond-settings-mac-input input")b.click("li button:contains('(cockpit1)')")b.click("#network-bond-settings-dialog button:contains('Add')")b.wait_not_present("#network-bond-settings-dialog")# Check that it has the interface and the right IP addressb.wait_text("#network-interface-name", "tbond")b.wait_visible(f"#network-interface-members tr[data-interface='{iface}']")self.toggle_onoff("label[for='interface-switch']")b.wait_in_text("#network-interface .pf-v6-c-card:contains('tbond')", ip)def testPrimary(self):b = self.browserself.login_and_go("/network")b.wait_visible("#networking")iface = "cockpit"self.add_veth(iface, dhcp_cidr="10.111.112.2/20")self.nm_activate_eth(iface)self.wait_for_iface(iface)# Create a bondb.click("button:contains('Add bond')")b.wait_visible("#network-bond-settings-dialog")b.set_input_text("#network-bond-settings-interface-name-input", "tbond")# add interface to bondb.set_checked(f"input[data-iface='{iface}']", val=True)# select it as primaryb.select_from_dropdown("#network-bond-settings-primary-select", iface)# finish creating the bondb.click("#network-bond-settings-dialog button:contains('Add')")b.wait_not_present("#network-bond-settings-dialog")b.wait_text("#network-interface-name", "tbond")b.click("#network-interface #networking-edit-bond")# edit bond dialogb.wait_visible("#network-bond-settings-dialog")b.wait_val("#network-bond-settings-primary-select", iface)def testAmbiguousMember(self):b = self.browserm = self.machineself.login_and_go("/network")b.wait_visible("#networking")iface = "cockpit1"self.add_veth(iface, dhcp_cidr="10.111.113.2/20")self.nm_activate_eth(iface)self.wait_for_iface(iface)# Now 'iface' has a normal connectioncon_id = self.iface_con_id(iface)self.assertTrue(con_id)# Manually create a bond and make 'iface' its member via a# second connection. Cockpit should ignore that second# connection and still show iface as a normal interface.m.execute(f"nmcli con add type bond-slave ifname {iface} con-name bond0-member-1 master bond0")m.execute("nmcli con add type bond ifname bond0 con-name bond0")self.wait_for_iface("bond0", state="Configuring")self.wait_for_iface(iface)# Now activate 'iface' as a member. Cockpit should now ignore# the first connection and show 'iface' as a member of bond0.m.execute("nmcli con up bond0-member-1")b.wait_not_present(f"#networking-interfaces tr[data-interface='{iface}']")b.click("#networking-interfaces tr[data-interface='bond0'] button")b.wait_visible("#network-interface")b.wait_visible(f"#network-interface-members tr[data-interface='{iface}']")class TestBondingVirt(netlib.NetworkCase):provision = {"machine1": {"address": "10.111.113.1/20"},"machine2": {"image": TEST_OS_DEFAULT, "address": "10.111.113.2/20", "dhcp": True}}@testlib.skipImage("Main interface can't be managed", "debian-*", "ubuntu-*")def testMain(self):b = self.browserm = self.machineiface = self.get_iface(m.networking["mac"])self.login_and_go("/network")self.wait_for_iface(iface)# Put the main interface into a bond. Everything should keep working.b.click("button:contains('Add bond')")b.wait_visible("#network-bond-settings-dialog")b.set_input_text("#network-bond-settings-interface-name-input", "tbond")b.set_checked(f"input[data-iface='{iface}']", val=True)b.click("#network-bond-settings-dialog button:contains('Add')")b.wait_not_present("#network-bond-settings-dialog")# Check that it has the main connection and the right IP addressb.wait_text("#network-interface-name", "tbond")b.wait_visible(f"#network-interface-members tr[data-interface='{iface}']")b.wait_in_text("#network-interface .pf-v6-c-card:contains('tbond')", "10.111.113.1")# Delete the bondb.click("#network-interface button:contains('Delete')")b.wait_visible("#networking")b.wait_not_present("#networking-interfaces tr[data-interface='tbond']")b.wait_visible(f"#networking-interfaces tr[data-interface='{iface}']")@testlib.skipImage("no dhclient on OS image", "arch", "centos-10*", "rhel-10*")@testlib.skipImage("Main interface can't be managed", "debian-*", "ubuntu-*")@testlib.skipOstree("not using dhclient")def testSlowly(self):b = self.browserm = self.machineiface = self.get_iface(m.networking["mac"])self.ensure_nm_uses_dhclient()self.login_and_go("/network")self.wait_for_iface(iface)# Slow down DHCP enough that it would trigger a rollback.self.slow_down_dhclient(20)# Put the main interface into a bond. Everything should keep# working since checkpoints are not used and thus no rollback# is actually triggered.b.click("button:contains('Add bond')")b.wait_visible("#network-bond-settings-dialog")b.set_input_text("#network-bond-settings-interface-name-input", "tbond")b.set_checked(f"input[data-iface='{iface}']", val=True)b.click("#network-bond-settings-dialog button:contains('Add')")b.wait_not_present("#network-bond-settings-dialog")# Check that it has the main connection and the right IP addressb.wait_text("#network-interface-name", "tbond")b.wait_visible(f"#network-interface-members tr[data-interface='{iface}']")b.wait_in_text("#network-interface .pf-v6-c-card:contains('tbond')", "10.111.113.1")if __name__ == '__main__':testlib.test_main()