Skip to content

Commit b7c1a16

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

File tree

2 files changed

+278
-0
lines changed

2 files changed

+278
-0
lines changed

providers/base/bin/wifi_client_test_netplan.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import shutil
2424
import ipaddress
2525
import yaml
26+
from pathlib import Path
2627

2728
from contextlib import contextmanager
2829

@@ -50,6 +51,8 @@ def print_cmd(cmd):
5051

5152
NETPLAN_CFG_PATHS = ("/etc/netplan", "/lib/netplan", "/run/netplan")
5253
NETPLAN_TEST_CFG = "/etc/netplan/99-CREATED-BY-CHECKBOX.yaml"
54+
BACKUP_PATH = "/tmp/netplan_backup" # hardcoded so that no one can send "/"
55+
NETPLAN_DIR = NETPLAN_CFG_PATHS[1]
5356

5457

5558
def netplan_renderer():
@@ -482,6 +485,94 @@ def handle_test_np_config(args):
482485
delete_test_config()
483486

484487

488+
def backup_netplan_files() -> bool:
489+
"""
490+
Backup netplan YAML files from /etc/netplan/ to a
491+
temporary directory, if there are.
492+
493+
Returns:
494+
bool: True if the operation succeded.
495+
"""
496+
netplan_pattern = os.path.join(NETPLAN_DIR, "*.yaml")
497+
498+
# Find all netplan YAML files
499+
yaml_files = glob.glob(netplan_pattern)
500+
501+
if not yaml_files:
502+
print("No netplan YAML files found")
503+
return True
504+
505+
# Create temporary directory
506+
Path(BACKUP_PATH).mkdir(parents=True, exist_ok=True)
507+
508+
# Copy each file to temp directory
509+
for yaml_file in yaml_files:
510+
filename = os.path.basename(yaml_file)
511+
temp_path = os.path.join(BACKUP_PATH, filename)
512+
shutil.copy2(yaml_file, temp_path)
513+
print(f"Backed up: {yaml_file} -> {temp_path}")
514+
515+
print(f"Netplan files backed up to: {BACKUP_PATH}")
516+
return True
517+
518+
519+
def restore_netplan_files() -> bool:
520+
"""
521+
Restore netplan YAML files from backup directory to /etc/netplan/.
522+
523+
Returns:
524+
bool: True if restoration successful, False otherwise
525+
"""
526+
if not BACKUP_PATH or not os.path.exists(BACKUP_PATH):
527+
print(f"Backup directory does not exist: {BACKUP_PATH}")
528+
return False
529+
530+
# Clean up existing netplan files first
531+
existing_files = glob.glob(os.path.join(NETPLAN_DIR, "*.yaml"))
532+
for existing_file in existing_files:
533+
os.remove(existing_file)
534+
print(f"Removed: {existing_file}")
535+
536+
# Find all YAML files in backup directory
537+
backup_pattern = os.path.join(BACKUP_PATH, "*.yaml")
538+
backup_files = glob.glob(backup_pattern)
539+
540+
if not backup_files:
541+
print("No netplan files found in backup directory")
542+
return False
543+
544+
try:
545+
# Restore each file
546+
for backup_file in backup_files:
547+
filename = os.path.basename(backup_file)
548+
target_path = os.path.join(NETPLAN_DIR, filename)
549+
shutil.copy2(backup_file, target_path)
550+
print(f"Restored: {backup_file} -> {target_path}")
551+
552+
print("Netplan files restored successfully")
553+
return True
554+
555+
except Exception as e:
556+
print(f"Error during restoration: {e}")
557+
return False
558+
559+
560+
def cleanup_backup() -> bool:
561+
"""
562+
Clean up temporary backup directory.
563+
"""
564+
if not os.path.exists(BACKUP_PATH):
565+
return True
566+
567+
try:
568+
shutil.rmtree(BACKUP_PATH)
569+
print(f"Cleaned up backup directory: {BACKUP_PATH}")
570+
return True
571+
except Exception as e:
572+
print(f"Error cleaning up backup: {e}")
573+
return False
574+
575+
485576
def main():
486577
args = parse_args()
487578

@@ -490,6 +581,7 @@ def main():
490581
start_time = datetime.datetime.now()
491582
renderer = check_and_get_renderer(args.renderer)
492583
args.renderer = renderer
584+
backup_netplan_files()
493585

494586
with handle_original_np_config():
495587
with handle_test_np_config(args):
@@ -512,6 +604,16 @@ def main():
512604
else:
513605
print("Connection test failed\n")
514606
print_journal_entries(start_time, renderer)
607+
608+
if restore_netplan_files():
609+
print("Files restored successfully")
610+
611+
# Clean up backup
612+
cleanup_backup()
613+
else:
614+
print("Failed to restore files")
615+
raise SystemExit(1)
616+
515617
if not test_result:
516618
raise SystemExit(1)
517619

providers/base/tests/test_wifi_client_test_netplan.py

Lines changed: 176 additions & 0 deletions
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,
@@ -38,6 +41,9 @@
3841
print_journal_entries,
3942
main,
4043
get_interface_info,
44+
restore_netplan_files,
45+
backup_netplan_files,
46+
cleanup_backup,
4147
)
4248

4349

@@ -931,3 +937,173 @@ def test_main_ping_test_failure(
931937
self.assertEqual(mock_apply.call_count, 2)
932938
self.assertEqual(mock_print_journal.call_count, 1)
933939
mock_ping.assert_called_once_with("wlan0", "networkd")
940+
941+
942+
# Mock BACKUP_PATH for testing
943+
BACKUP_PATH = "/tmp/test_netplan_backup"
944+
NETPLAN_DIR = "/tmp/lib/netplan"
945+
946+
947+
@patch("wifi_client_test_netplan.BACKUP_PATH", "/tmp/test_netplan_backup")
948+
@patch("wifi_client_test_netplan.NETPLAN_DIR", "/tmp/lib/netplan")
949+
class TestNetplanBackupFunctions(TestCase):
950+
951+
def setUp(self):
952+
self.assertEqual(BACKUP_PATH, "/tmp/test_netplan_backup")
953+
self.assertEqual(NETPLAN_DIR, "/tmp/lib/netplan")
954+
"""Set up test fixtures before each test method."""
955+
Path(BACKUP_PATH).mkdir(parents=True, exist_ok=True)
956+
Path(NETPLAN_DIR).mkdir(parents=True, exist_ok=True)
957+
958+
def tearDown(self):
959+
"""Clean up after each test method."""
960+
# Clean up test directories
961+
shutil.rmtree(Path(BACKUP_PATH), ignore_errors=True)
962+
shutil.rmtree(Path(NETPLAN_DIR), ignore_errors=True)
963+
964+
@patch("glob.glob")
965+
@patch("builtins.print")
966+
def test_backup_netplan_files_no_files_found(self, mock_print, mock_glob):
967+
"""Test backup when no YAML files are found."""
968+
mock_glob.return_value = []
969+
970+
result = backup_netplan_files()
971+
self.assertTrue(result)
972+
973+
@patch("glob.glob")
974+
@patch("shutil.copy2")
975+
@patch("pathlib.Path.mkdir")
976+
@patch("builtins.print")
977+
def test_backup_netplan_files_success(
978+
self, mock_print, mock_mkdir, mock_copy2, mock_glob
979+
):
980+
"""Test successful backup of netplan files."""
981+
mock_glob.return_value = [
982+
NETPLAN_DIR + "/config1.yaml",
983+
NETPLAN_DIR + "/config2.yaml",
984+
]
985+
986+
result = backup_netplan_files()
987+
988+
self.assertTrue(result)
989+
self.assertEqual(mock_copy2.call_count, 2)
990+
mock_mkdir.assert_called_once_with(parents=True, exist_ok=True)
991+
992+
@patch("os.path.exists")
993+
@patch("glob.glob")
994+
@patch("os.remove")
995+
@patch("os.makedirs")
996+
@patch("shutil.copy2")
997+
@patch("builtins.print")
998+
def test_restore_netplan_files_success(
999+
self,
1000+
mock_print,
1001+
mock_copy2,
1002+
mock_makedirs,
1003+
mock_remove,
1004+
mock_glob,
1005+
mock_exists,
1006+
):
1007+
"""Test successful restore of netplan files."""
1008+
mock_exists.return_value = True
1009+
mock_glob.side_effect = [
1010+
[NETPLAN_DIR + "/old1.yaml"], # Existing files to remove
1011+
[
1012+
f"{BACKUP_PATH}/config1.yaml",
1013+
f"{BACKUP_PATH}/config2.yaml",
1014+
], # Backup files
1015+
]
1016+
1017+
result = restore_netplan_files()
1018+
1019+
self.assertTrue(result)
1020+
mock_remove.assert_called_once_with(NETPLAN_DIR + "/old1.yaml")
1021+
self.assertEqual(mock_copy2.call_count, 2)
1022+
1023+
@patch("os.path.exists")
1024+
@patch("glob.glob")
1025+
@patch("os.remove")
1026+
@patch("shutil.copy2")
1027+
@patch("builtins.print")
1028+
def test_restore_netplan_files_copy_error(
1029+
self, mock_print, mock_copy2, mock_remove, mock_glob, mock_exists
1030+
):
1031+
"""Test restore when copy operation fails."""
1032+
mock_exists.return_value = True
1033+
mock_glob.side_effect = [[], [f"{BACKUP_PATH}/config1.yaml"]]
1034+
mock_copy2.side_effect = OSError("Permission denied")
1035+
1036+
result = restore_netplan_files()
1037+
1038+
self.assertFalse(result)
1039+
1040+
@patch("os.path.exists")
1041+
@patch("builtins.print")
1042+
def test_cleanup_backup_not_exists(self, mock_print, mock_exists):
1043+
"""Test cleanup when backup directory doesn't exist."""
1044+
mock_exists.return_value = False
1045+
1046+
result = cleanup_backup()
1047+
1048+
self.assertTrue(result)
1049+
1050+
@patch("os.path.exists")
1051+
@patch("shutil.rmtree")
1052+
@patch("builtins.print")
1053+
def test_cleanup_backup_success(
1054+
self, mock_print, mock_rmtree, mock_exists
1055+
):
1056+
"""Test successful cleanup of backup directory."""
1057+
mock_exists.return_value = True
1058+
1059+
result = cleanup_backup()
1060+
1061+
self.assertTrue(result)
1062+
mock_rmtree.assert_called_once_with(BACKUP_PATH)
1063+
1064+
@patch("os.path.exists")
1065+
@patch("shutil.rmtree")
1066+
@patch("builtins.print")
1067+
def test_cleanup_backup_error(self, mock_print, mock_rmtree, mock_exists):
1068+
"""Test cleanup when rmtree operation fails."""
1069+
mock_exists.return_value = True
1070+
mock_rmtree.side_effect = OSError("Permission denied")
1071+
1072+
result = cleanup_backup()
1073+
1074+
self.assertFalse(result)
1075+
1076+
@patch("os.path.exists")
1077+
@patch("glob.glob")
1078+
@patch("os.remove")
1079+
@patch("builtins.print")
1080+
def test_restore_netplan_files_remove_error(
1081+
self, mock_print, mock_remove, mock_glob, mock_exists
1082+
):
1083+
"""Test restore when removing existing files fails."""
1084+
mock_exists.return_value = True
1085+
mock_glob.side_effect = [
1086+
[NETPLAN_DIR + "/old1.yaml"], # Existing files to remove
1087+
[f"{BACKUP_PATH}/config1.yaml"], # Backup files
1088+
]
1089+
mock_remove.side_effect = OSError("Permission denied")
1090+
1091+
result = restore_netplan_files()
1092+
1093+
self.assertFalse(result)
1094+
1095+
@patch("os.path.exists")
1096+
@patch("glob.glob")
1097+
@patch("os.makedirs")
1098+
@patch("builtins.print")
1099+
def test_restore_netplan_files_makedirs_error(
1100+
self, mock_print, mock_makedirs, mock_glob, mock_exists
1101+
):
1102+
"""Test restore when makedirs operation fails."""
1103+
mock_exists.return_value = True
1104+
mock_glob.side_effect = [[], [f"{BACKUP_PATH}/config1.yaml"]]
1105+
mock_makedirs.side_effect = OSError("Permission denied")
1106+
1107+
result = restore_netplan_files()
1108+
1109+
self.assertFalse(result)

0 commit comments

Comments
 (0)