#!/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 reimport netlibimport testlib@testlib.skipImage("No network checkpoint support", "ubuntu-*")@testlib.nondestructiveclass TestNetworkingCheckpoints(netlib.NetworkCase):def testCheckpoint(self):b = self.browserm = self.machineiface = self.get_iface("52:54:00:12:34:56")if "debian" in m.image:self.sed_file("s/managed=false/managed=true/", "/etc/NetworkManager/NetworkManager.conf","systemctl restart NetworkManager")if m.image == "arch":self.sed_file("s/unmanaged-devices=interface-name:eth0//", "/etc/NetworkManager/conf.d/noauto.conf","systemctl restart NetworkManager")self.login_and_go("/network")self.nm_checkpoints_enable()self.wait_for_iface(iface, prefix="172.")self.select_iface(iface)b.wait_visible("#network-interface")# Disconnectself.wait_onoff("label[for='interface-switch']", val=True)self.toggle_onoff("label[for='interface-switch']")# Wait for dialog to appear and dismiss itwith b.wait_timeout(60):b.click("#confirm-breaking-change-popup button:contains('Keep connection')")b.wait_not_present("#confirm-breaking-change-popup")# Change IPself.configure_iface_setting('IPv4')b.wait_visible("#network-ip-settings-dialog")b.select_from_dropdown("#network-ip-settings-select-method", "manual")b.set_input_text('#network-ip-settings-address-0', "1.2.3.4")b.set_input_text('#network-ip-settings-netmask-0', "24")b.click("#network-ip-settings-save")with b.wait_timeout(60):b.click("#confirm-breaking-change-popup button:contains('Keep connection')")b.wait_not_present("#confirm-breaking-change-popup")@testlib.skipImage("Main interface settings are read-only", "debian-*")@testlib.skipImage("no dhclient on OS image", "arch", "centos-10*", "rhel-10*")@testlib.skipOstree("not using dhclient")def testCheckpointSlowRollback(self):b = self.browserm = self.machine# A slow rollback would normally cause the global health check# to fail during rollback and prevent showing the# #confirm-breaking-change-popup. We expect the global health# check failure to be ignored.## We test slow rollbacks by slowing down DHCP requests.## We need at least 60 seconds of network disconnection to let# the health check fail reliably. A rollback is started 7# seconds after disconnection, so we need to delay the DHCP# request by at least 53 seconds. However, NetworkManager has# a default DHCP timeout of 45 seconds, so we need to increase# that.## Sometimes, NetworkManager extends the DHCP timeout by an# additional 480 seconds grace period. This doesn't always# happen, so we don't rely on it.dhcp_delay = 60dhcp_timeout = 120# There are a couple of considerations for ordering the# following actions:## - Changing the main.dhcp config value requires a restart of NM.## - A restart of NM might run dhclient, and we don't want it# to be slowed down already at that point.## - After restarting NM, we need to wait for it to settle# again before messing with dhclient.## - Simply setting ipv4.dhcp_timeout is not enough if it# should be used immediately for a checkpoint rollback, see# https://bugzilla.redhat.com/show_bug.cgi?id=1690389. A# additional restart is enough.## So we set ipv4.dhcp_timeout and main.dhcp, do a restart, log# into the UI and wait for the expected interface to appear# and be active, and then slow down dhclient.iface = self.get_iface("52:54:00:12:34:56")con_id = self.iface_con_id(iface)m.execute(f'nmcli con mod "{con_id}" ipv4.dhcp-timeout {dhcp_timeout}')self.ensure_nm_uses_dhclient()self.login_and_go("/network")self.nm_checkpoints_enable()self.wait_for_iface(iface, prefix="172.")self.select_iface(iface)b.wait_visible("#network-interface")# checkpoints are realtime sensitive, avoid long NM operationsself.settle_cpu()self.slow_down_dhclient(dhcp_delay)# Disconnect and trigger a slow rollbackself.wait_onoff("label[for='interface-switch']", val=True)self.toggle_onoff("label[for='interface-switch']")with b.wait_timeout(120):b.click("#confirm-breaking-change-popup button:contains('Keep connection')")b.wait_not_present("#confirm-breaking-change-popup")def testNoRollback(self):b = self.browserm = self.machineself.login_and_go("/network")self.nm_checkpoints_enable()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)self.select_iface(iface)b.wait_visible("#network-interface")# Disconnectself.wait_onoff("label[for='interface-switch']", val=True)self.toggle_onoff("label[for='interface-switch']")# The checkpoint should be destroyed before it is being rolled# back.def checkpoint_was_destroyed():lines = m.execute("journalctl -u NetworkManager | grep op=").split("\n")last_checkpoint = Nonefor line in lines:match = re.search(r'op="checkpoint-create" arg="([^"]*)"', line)if match:last_checkpoint = match[1]if last_checkpoint:for line in lines:if f'op="checkpoint-destroy" arg="{last_checkpoint}"' in line:return Truereturn Falsetestlib.wait(checkpoint_was_destroyed)def testCheckpointSessionStorageEventSuccess(self):b = self.browserself.login_and_go("/network")self.nm_checkpoints_enable()b.wait_visible("#networking")iface = 'cockpit1'self.add_veth(iface, dhcp_cidr="10.111.114.2/20")self.nm_activate_eth(iface)self.wait_for_iface(iface)self.select_iface(iface)b.wait_visible("#network-interface")# Verify sessionStorage is initially false or not setinitial_value = b.call_js_func("window.sessionStorage.getItem", "cockpit_has_checkpoint")self.assertIn(initial_value, [None, "false"])# Change IP settings which will trigger a checkpointself.configure_iface_setting('IPv4')b.wait_visible("#network-ip-settings-dialog")b.select_from_dropdown("#network-ip-settings-select-method", "manual")b.set_input_text('#network-ip-settings-address-0', "10.111.114.100")b.set_input_text('#network-ip-settings-netmask-0', "20")# Click save, which triggers checkpoint creationb.click("#network-ip-settings-save")# Verify sessionStorage becomes true during checkpoint operationdef checkpoint_active():value = b.call_js_func("window.sessionStorage.getItem", "cockpit_has_checkpoint")return value == "true"testlib.wait(checkpoint_active)# Wait for operation to complete and sessionStorage to be cleareddef checkpoint_cleared():value = b.call_js_func("window.sessionStorage.getItem", "cockpit_has_checkpoint")return value == "false"testlib.wait(checkpoint_cleared)def testCheckpointSessionStorageEventRollback(self):b = self.browserm = self.machineself.login_and_go("/network")self.nm_checkpoints_enable()b.wait_visible("#networking")iface = self.get_iface("52:54:00:12:34:56")if "debian" in m.image:self.sed_file("s/managed=false/managed=true/", "/etc/NetworkManager/NetworkManager.conf","systemctl restart NetworkManager")if m.image == "arch":self.sed_file("s/unmanaged-devices=interface-name:eth0//", "/etc/NetworkManager/conf.d/noauto.conf","systemctl restart NetworkManager")self.wait_for_iface(iface, prefix="172.")self.select_iface(iface)b.wait_visible("#network-interface")# Verify sessionStorage is initially false or not setinitial_value = b.call_js_func("window.sessionStorage.getItem", "cockpit_has_checkpoint")self.assertIn(initial_value, [None, "false"])# Disconnect interface which will trigger checkpoint and should cause rollback dialogself.wait_onoff("label[for='interface-switch']", val=True)self.toggle_onoff("label[for='interface-switch']")# Verify sessionStorage becomes true during checkpoint operationdef checkpoint_active():value = b.call_js_func("window.sessionStorage.getItem", "cockpit_has_checkpoint")return value == "true"testlib.wait(checkpoint_active)# Wait for dialog to appear (this happens when checkpoint_destroy fails)with b.wait_timeout(60):b.wait_visible("#confirm-breaking-change-popup")# By the time dialog is visible, sessionStorage should already be clearedcheckpoint_cleared_value = b.call_js_func("window.sessionStorage.getItem", "cockpit_has_checkpoint")self.assertEqual(checkpoint_cleared_value, "false")# Keep the connection (rollback)b.click("#confirm-breaking-change-popup button:contains('Keep connection')")b.wait_not_present("#confirm-breaking-change-popup")if __name__ == '__main__':testlib.test_main()