Skip to content

Commit 35e6c54

Browse files
committed
Add a backup and restore step on the netplans directory
1 parent cbdb66f commit 35e6c54

File tree

4 files changed

+314
-1
lines changed

4 files changed

+314
-1
lines changed

providers/base/bin/wifi_client_test_netplan.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,7 @@ def main():
512512
else:
513513
print("Connection test failed\n")
514514
print_journal_entries(start_time, renderer)
515+
515516
if not test_result:
516517
raise SystemExit(1)
517518

providers/base/bin/wifi_nmcli_test.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,17 @@
1818
import subprocess as sp
1919
import sys
2020
import shlex
21+
from pathlib import Path
22+
import shutil
23+
import glob
2124

2225
from packaging import version as version_parser
2326

2427
from checkbox_support.helpers.retry import retry
2528
from gateway_ping_test import ping
2629

30+
BACKUP_DIR = "/tmp/netplan_backup" # hardcoded so that no one can send "/"
31+
NETPLAN_DIR = "/lib/netplan"
2732

2833
print = functools.partial(print, flush=True)
2934

@@ -386,6 +391,100 @@ def parser_args():
386391
return args
387392

388393

394+
def backup_netplan_files(backup_dir: str, netplan_dir: str) -> bool:
395+
"""
396+
Backup netplan YAML files from /etc/netplan/ to a
397+
temporary directory, if there are.
398+
399+
Returns:
400+
bool: True if the operation succeded.
401+
"""
402+
netplan_pattern = os.path.join(netplan_dir, "*.yaml")
403+
404+
# Find all netplan YAML files
405+
yaml_files = glob.glob(netplan_pattern)
406+
407+
if not yaml_files:
408+
print("No netplan YAML files found")
409+
return True
410+
411+
# Create temporary directory
412+
Path(backup_dir).mkdir(parents=True, exist_ok=True)
413+
414+
# Copy each file to temp directory
415+
for yaml_file in yaml_files:
416+
filename = os.path.basename(yaml_file)
417+
temp_path = os.path.join(backup_dir, filename)
418+
shutil.copy2(yaml_file, temp_path)
419+
# Then copy ownership
420+
st = os.stat(yaml_file)
421+
os.chown(temp_path, st.st_uid, st.st_gid)
422+
print(f"Backed up: {yaml_file} -> {temp_path}")
423+
424+
print(f"Netplan files backed up to: {backup_dir}")
425+
return True
426+
427+
428+
def restore_netplan_files(backup_dir: str, netplan_dir: str) -> bool:
429+
"""
430+
Restore netplan YAML files from backup directory to /etc/netplan/.
431+
432+
Returns:
433+
bool: True if restoration successful, False otherwise
434+
"""
435+
if not backup_dir or not os.path.exists(backup_dir):
436+
print(f"Backup directory does not exist: {netplan_dir}")
437+
return False
438+
439+
# Clean up existing netplan files first
440+
existing_files = glob.glob(os.path.join(netplan_dir, "*.yaml"))
441+
for existing_file in existing_files:
442+
os.remove(existing_file)
443+
print(f"Removed: {existing_file}")
444+
445+
# Find all YAML files in backup directory
446+
backup_pattern = os.path.join(backup_dir, "*.yaml")
447+
backup_files = glob.glob(backup_pattern)
448+
449+
if not backup_files:
450+
print("No netplan files found in backup directory")
451+
return False
452+
453+
try:
454+
# Restore each file
455+
for backup_file in backup_files:
456+
filename = os.path.basename(backup_file)
457+
target_path = os.path.join(netplan_dir, filename)
458+
shutil.copy2(backup_file, target_path)
459+
# Then copy ownership
460+
st = os.stat(backup_file)
461+
os.chown(target_path, st.st_uid, st.st_gid)
462+
print(f"Restored: {backup_file} -> {target_path}")
463+
464+
print("Netplan files restored successfully")
465+
return True
466+
467+
except Exception as e:
468+
print(f"Error during restoration: {e}")
469+
return False
470+
471+
472+
def cleanup_netplan_backup(backup_dir: str) -> bool:
473+
"""
474+
Clean up temporary backup directory.
475+
"""
476+
if not os.path.exists(backup_dir):
477+
return True
478+
479+
try:
480+
shutil.rmtree(backup_dir)
481+
print(f"Cleaned up backup directory: {backup_dir}")
482+
return True
483+
except Exception as e:
484+
print(f"Error cleaning up backup: {e}")
485+
return False
486+
487+
389488
@retry(max_attempts=5, delay=60)
390489
def main():
391490
args = parser_args()
@@ -408,6 +507,11 @@ def main():
408507
)
409508

410509
if args.func:
510+
# backup the test plans, because nmcli corrupts them
511+
# and debsums will complain afterwards
512+
# This is ugly. Ideally, nmcli should be patched instead
513+
backup_netplan_files(BACKUP_DIR, NETPLAN_DIR)
514+
411515
delete_test_ap_ssid_connection()
412516
activated_uuid = get_nm_activate_connection()
413517
turn_down_nm_connections()
@@ -423,6 +527,8 @@ def main():
423527
finally:
424528
turn_up_connection(activated_uuid)
425529
delete_test_ap_ssid_connection()
530+
restore_netplan_files(BACKUP_DIR, NETPLAN_DIR)
531+
cleanup_netplan_backup(BACKUP_DIR)
426532

427533

428534
if __name__ == "__main__":

providers/base/tests/test_wifi_client_test_netplan.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
import io
2525
import sys
2626
from unittest.mock import call
27+
from pathlib import Path
28+
import shutil
29+
2730
from wifi_client_test_netplan import (
2831
netplan_renderer,
2932
check_and_get_renderer,
@@ -40,7 +43,6 @@
4043
get_interface_info,
4144
)
4245

43-
4446
class WifiClientTestNetplanTests(TestCase):
4547
@patch(
4648
"wifi_client_test_netplan.get_series",

providers/base/tests/test_wifi_nmcli_test.py

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@
4242
perform_ping_test,
4343
parser_args,
4444
main,
45+
restore_netplan_files,
46+
backup_netplan_files,
47+
cleanup_netplan_backup,
4548
)
4649

4750

@@ -606,6 +609,9 @@ def test_main_open_no_aps_found(
606609
"TestSSID": {"Chan": "11", "Freq": "2462", "Signal": "80"},
607610
},
608611
)
612+
@patch("wifi_nmcli_test.backup_netplan_files")
613+
@patch("wifi_nmcli_test.restore_netplan_files")
614+
@patch("wifi_nmcli_test.cleanup_netplan_backup")
609615
@patch("wifi_nmcli_test.open_connection", return_value=0)
610616
@patch(
611617
"wifi_nmcli_test.sys.argv",
@@ -616,5 +622,203 @@ def test_main_open_aps_found(
616622
list_aps_mock,
617623
get_nm_activate_connection_mock,
618624
mock_open_connection,
625+
mock_cleanup_back,
626+
mock_rest_back,
627+
mock_cr_back,
619628
):
620629
main()
630+
631+
632+
class TestNetplanBackupFunctions(unittest.TestCase):
633+
def setUp(self):
634+
self.TEST_BACKUP_DIR = "/tmp/test_netplan_backup"
635+
self.TEST_NETPLAN_DIR = "/tmp/etc/netplan"
636+
"""Set up test fixtures before each test method."""
637+
Path(self.TEST_BACKUP_DIR).mkdir(parents=True, exist_ok=True)
638+
Path(self.TEST_NETPLAN_DIR).mkdir(parents=True, exist_ok=True)
639+
640+
def tearDown(self):
641+
"""Clean up after each test method."""
642+
# Clean up test directories
643+
shutil.rmtree(Path(self.TEST_BACKUP_DIR), ignore_errors=True)
644+
shutil.rmtree(Path(self.TEST_NETPLAN_DIR), ignore_errors=True)
645+
646+
@patch("glob.glob")
647+
@patch("builtins.print")
648+
def test_backup_netplan_files_no_files_found(self, mock_print, mock_glob):
649+
"""Test backup when no YAML files are found."""
650+
mock_glob.return_value = []
651+
652+
result = backup_netplan_files(
653+
self.TEST_BACKUP_DIR, self.TEST_NETPLAN_DIR
654+
)
655+
self.assertTrue(result)
656+
657+
@patch("os.chown")
658+
@patch("os.stat")
659+
@patch("glob.glob")
660+
@patch("shutil.copy2")
661+
@patch("pathlib.Path.mkdir")
662+
@patch("builtins.print")
663+
def test_backup_netplan_files_success(
664+
self,
665+
mock_print,
666+
mock_mkdir,
667+
mock_copy2,
668+
mock_glob,
669+
mock_stat,
670+
mock_chown,
671+
):
672+
"""Test successful backup of netplan files."""
673+
mock_glob.return_value = [
674+
self.TEST_NETPLAN_DIR + "/config1.yaml",
675+
self.TEST_NETPLAN_DIR + "/config2.yaml",
676+
]
677+
678+
result = backup_netplan_files(
679+
self.TEST_BACKUP_DIR, self.TEST_NETPLAN_DIR
680+
)
681+
682+
self.assertTrue(result)
683+
self.assertEqual(mock_copy2.call_count, 2)
684+
mock_mkdir.assert_called_once_with(parents=True, exist_ok=True)
685+
686+
@patch("os.chown")
687+
@patch("os.stat")
688+
@patch("os.path.exists")
689+
@patch("glob.glob")
690+
@patch("os.remove")
691+
@patch("os.makedirs")
692+
@patch("shutil.copy2")
693+
@patch("builtins.print")
694+
def test_restore_netplan_files_success(
695+
self,
696+
mock_print,
697+
mock_copy2,
698+
mock_makedirs,
699+
mock_remove,
700+
mock_glob,
701+
mock_exists,
702+
mock_stat,
703+
mock_chown,
704+
):
705+
"""Test successful restore of netplan files."""
706+
mock_exists.return_value = True
707+
mock_glob.side_effect = [
708+
[self.TEST_NETPLAN_DIR + "/old1.yaml"], # Existing files to remove
709+
[
710+
f"{self.TEST_BACKUP_DIR}/config1.yaml",
711+
f"{self.TEST_BACKUP_DIR}/config2.yaml",
712+
], # Backup files
713+
]
714+
715+
result = restore_netplan_files(
716+
self.TEST_BACKUP_DIR, self.TEST_NETPLAN_DIR
717+
)
718+
719+
self.assertTrue(result)
720+
mock_remove.assert_called_once_with(
721+
self.TEST_NETPLAN_DIR + "/old1.yaml"
722+
)
723+
self.assertEqual(mock_copy2.call_count, 2)
724+
725+
@patch("os.chown")
726+
@patch("os.stat")
727+
@patch("os.path.exists")
728+
@patch("glob.glob")
729+
@patch("os.remove")
730+
@patch("shutil.copy2")
731+
@patch("builtins.print")
732+
def test_restore_netplan_files_copy_error(
733+
self,
734+
mock_print,
735+
mock_copy2,
736+
mock_remove,
737+
mock_glob,
738+
mock_exists,
739+
mock_stat,
740+
mock_chown,
741+
):
742+
"""Test restore when copy operation fails."""
743+
mock_exists.return_value = True
744+
mock_glob.side_effect = [[], [f"{self.TEST_BACKUP_DIR}/config1.yaml"]]
745+
mock_copy2.side_effect = OSError("Permission denied")
746+
747+
result = restore_netplan_files(
748+
self.TEST_BACKUP_DIR, self.TEST_NETPLAN_DIR
749+
)
750+
751+
self.assertFalse(result)
752+
753+
@patch("os.path.exists")
754+
@patch("builtins.print")
755+
def test_cleanup_backup_not_exists(self, mock_print, mock_exists):
756+
"""Test cleanup when backup directory doesn't exist."""
757+
mock_exists.return_value = False
758+
759+
result = cleanup_netplan_backup(self.TEST_BACKUP_DIR)
760+
761+
self.assertTrue(result)
762+
763+
@patch("os.path.exists")
764+
@patch("shutil.rmtree")
765+
@patch("builtins.print")
766+
def test_cleanup_backup_success(
767+
self, mock_print, mock_rmtree, mock_exists
768+
):
769+
"""Test successful cleanup of backup directory."""
770+
mock_exists.return_value = True
771+
772+
result = cleanup_netplan_backup(self.TEST_BACKUP_DIR)
773+
774+
self.assertTrue(result)
775+
mock_rmtree.assert_called_once_with(self.TEST_BACKUP_DIR)
776+
777+
@patch("os.path.exists")
778+
@patch("shutil.rmtree")
779+
@patch("builtins.print")
780+
def test_cleanup_backup_error(self, mock_print, mock_rmtree, mock_exists):
781+
"""Test cleanup when rmtree operation fails."""
782+
mock_exists.return_value = True
783+
mock_rmtree.side_effect = OSError("Permission denied")
784+
785+
result = cleanup_netplan_backup(self.TEST_BACKUP_DIR)
786+
787+
self.assertFalse(result)
788+
789+
@patch("os.path.exists")
790+
@patch("glob.glob")
791+
@patch("os.remove")
792+
@patch("builtins.print")
793+
def test_restore_netplan_files_remove_error(
794+
self, mock_print, mock_remove, mock_glob, mock_exists
795+
):
796+
"""Test restore when removing existing files fails."""
797+
mock_exists.return_value = True
798+
mock_glob.side_effect = [
799+
[self.TEST_NETPLAN_DIR + "/old1.yaml"], # Existing files to remove
800+
[f"{self.TEST_BACKUP_DIR}/config1.yaml"], # Backup files
801+
]
802+
mock_remove.side_effect = OSError("Permission denied")
803+
with self.assertRaises(OSError):
804+
result = restore_netplan_files(
805+
self.TEST_BACKUP_DIR, self.TEST_NETPLAN_DIR
806+
)
807+
808+
@patch("os.path.exists")
809+
@patch("glob.glob")
810+
@patch("os.makedirs")
811+
@patch("builtins.print")
812+
def test_restore_netplan_files_makedirs_error(
813+
self, mock_print, mock_makedirs, mock_glob, mock_exists
814+
):
815+
"""Test restore when makedirs operation fails."""
816+
mock_exists.return_value = True
817+
mock_glob.side_effect = [[], [f"{self.TEST_BACKUP_DIR}/config1.yaml"]]
818+
mock_makedirs.side_effect = OSError("Permission denied")
819+
820+
result = restore_netplan_files(
821+
self.TEST_BACKUP_DIR, self.TEST_NETPLAN_DIR
822+
)
823+
824+
self.assertFalse(result)

0 commit comments

Comments
 (0)