Skip to content

Commit c551cf4

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

File tree

3 files changed

+319
-0
lines changed

3 files changed

+319
-0
lines changed

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("Backed up: {} -> {}".format(yaml_file, temp_path))
423+
424+
print("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("Backup directory does not exist: {}".format(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("Removed: {}".format(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("Restored: {} -> {}".format(backup_file, target_path))
463+
464+
print("Netplan files restored successfully")
465+
return True
466+
467+
except Exception as e:
468+
print("Error during restoration: {}".format(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("Cleaned up backup directory: {}".format(backup_dir))
482+
return True
483+
except Exception as e:
484+
print("Error cleaning up backup: {}".format(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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io
2525
import sys
2626
from unittest.mock import call
27+
2728
from wifi_client_test_netplan import (
2829
netplan_renderer,
2930
check_and_get_renderer,

providers/base/tests/test_wifi_nmcli_test.py

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import unittest
2121
from subprocess import CalledProcessError
2222
from unittest.mock import patch, call, MagicMock
23+
from pathlib import Path
24+
import shutil
2325

2426
from checkbox_support.helpers.retry import mock_retry
2527

@@ -42,6 +44,9 @@
4244
perform_ping_test,
4345
parser_args,
4446
main,
47+
restore_netplan_files,
48+
backup_netplan_files,
49+
cleanup_netplan_backup,
4550
)
4651

4752

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

0 commit comments

Comments
 (0)