feat: enhance guest network configuration and recovery process for UVM
This commit is contained in:
+52
-1
@@ -307,6 +307,9 @@ class LifecycleTestCase(unittest.TestCase):
|
||||
self.images, "resize_vm_disk", side_effect=self.resize_vm_disk,
|
||||
))
|
||||
self.provision = self.enterContext(patch.object(self.images, "provision_credentials"))
|
||||
self.configure_network = self.enterContext(patch.object(
|
||||
self.images, "configure_guest_network", side_effect=self.configure_guest_network,
|
||||
))
|
||||
self.copy_disk = self.enterContext(patch.object(
|
||||
self.images, "create_vm_disk", wraps=self.images.create_vm_disk,
|
||||
))
|
||||
@@ -340,6 +343,14 @@ class LifecycleTestCase(unittest.TestCase):
|
||||
raise AssertionError("cannot resize a running or ambiguous VM")
|
||||
self.disk_sizes[disk] = storage_mib * 1024 * 1024
|
||||
|
||||
def configure_guest_network(self, disk: Path, guest_ip: str) -> None:
|
||||
vm = self.store.get(disk.parent.name)
|
||||
self.host.call("images.configure_network", vm.id)
|
||||
if self.process.observe(vm) != "gone":
|
||||
raise AssertionError("cannot configure a running or ambiguous VM disk")
|
||||
if guest_ip != vm.guest_ip:
|
||||
raise AssertionError("guest network must use the VM's retained address")
|
||||
|
||||
def create_vm(self, **kwargs) -> VmRecord:
|
||||
return self.service.create(VmSpec(cpu=1, ram_mib=512, **kwargs))
|
||||
|
||||
@@ -481,7 +492,7 @@ class LifecycleTests(LifecycleTestCase):
|
||||
def test_boot_persists_intent_and_identity_with_policy_before_tap_and_vmm(self) -> None:
|
||||
vm = self.create_vm()
|
||||
self.assert_call_order(
|
||||
vm, "network.prepare_tap", "firewall.apply", "firewall.readback",
|
||||
vm, "images.configure_network", "network.prepare_tap", "firewall.apply", "firewall.readback",
|
||||
"network.activate_tap", "process.start", "process.spawn", "process.ready",
|
||||
"client.configure", "firewall.status",
|
||||
)
|
||||
@@ -499,6 +510,46 @@ class LifecycleTests(LifecycleTestCase):
|
||||
self.assertEqual(spawned.operation, "configuring")
|
||||
self.assertEqual(self.host.records["client.configure", vm.id].pid, vm.pid)
|
||||
|
||||
def test_guest_network_configuration_failure_rolls_back_before_marking_the_disk_ready(self) -> None:
|
||||
self.host.failures["images.configure_network", None] = UvmError("guest network verification failed")
|
||||
with self.assertRaisesRegex(UvmError, "guest network verification failed"):
|
||||
self.create_vm()
|
||||
self.assertEqual(self.store.load().vms, {})
|
||||
self.assertFalse(self.network.created_taps)
|
||||
self.assertFalse(self.process.started)
|
||||
self.assertEqual(list(self.settings.vms_dir.iterdir()), [])
|
||||
|
||||
def test_retained_guest_network_is_configured_after_exit_and_before_boot(self) -> None:
|
||||
vm = self.create_vm()
|
||||
self.host.events.clear()
|
||||
self.service.restart(vm.id)
|
||||
self.assert_call_order(vm, "images.configure_network", "process.start", "client.configure")
|
||||
actions = self.host.actions(vm.id)
|
||||
self.assertLess(max(index for index, action in enumerate(actions) if action == "process.gone"),
|
||||
actions.index("images.configure_network"))
|
||||
self.configure_network.assert_called_with(Path(vm.disk), vm.guest_ip)
|
||||
self.assertEqual(self.provision.call_count, 1)
|
||||
|
||||
def test_live_start_does_not_modify_the_guest_disk(self) -> None:
|
||||
vm = self.create_vm()
|
||||
self.configure_network.reset_mock()
|
||||
self.service.start(vm.id)
|
||||
self.configure_network.assert_not_called()
|
||||
|
||||
def test_failed_retained_network_configuration_keeps_disk_for_retry(self) -> None:
|
||||
vm = self.create_vm()
|
||||
self.service.stop(vm.id)
|
||||
disk = Path(vm.disk)
|
||||
disk.write_bytes(b"retained data and credentials")
|
||||
self.host.failures["images.configure_network", vm.id] = UvmError("network verification failed")
|
||||
with self.assertRaisesRegex(UvmError, "network verification failed"):
|
||||
self.service.start(vm.id)
|
||||
self.assertEqual(disk.read_bytes(), b"retained data and credentials")
|
||||
self.assertEqual(self.process.observe(self.store.get(vm.id)), "gone")
|
||||
self.assertEqual(len(self.process.started), 1)
|
||||
self.assertEqual(self.service.start(vm.id).status, "running")
|
||||
self.assertEqual(self.provision.call_count, 1)
|
||||
|
||||
def test_failed_initial_policy_never_activates_tap_or_starts_vmm(self) -> None:
|
||||
self.host.failures["firewall.apply", None] = UvmError("policy unavailable")
|
||||
with self.assertRaisesRegex(UvmError, "policy unavailable"):
|
||||
|
||||
Reference in New Issue
Block a user