feat: implement resource resizing for VMs with storage management
- Added KnownHostsManager to handle cleanup of SSH known_hosts entries upon VM deletion. - Introduced disk_size_bytes method in ImageStore to read logical disk capacity. - Enhanced LifecycleService to manage VM resource resizing, including CPU, RAM, and storage. - Updated application configuration to support known_hosts_files for SSH management. - Modified dashboard to include resource resizing options in the UI. - Improved error handling and validation for resource inputs in the dashboard. - Removed obsolete .pyc files from the repository.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from collections.abc import Callable, Iterable, Iterator
|
||||
@@ -80,6 +81,16 @@ class FakeHost:
|
||||
return [action for action, target in self.events if target == vm_id]
|
||||
|
||||
|
||||
class FakeKnownHosts:
|
||||
def __init__(self, host: FakeHost) -> None:
|
||||
self.host = host
|
||||
|
||||
def remove(self, vm: VmRecord) -> None:
|
||||
self.host.call("known_hosts.remove", vm.id)
|
||||
if Path(vm.socket).parent.exists():
|
||||
raise AssertionError("known_hosts cleanup must follow VM runtime deletion")
|
||||
|
||||
|
||||
class FakeFirewall:
|
||||
def __init__(self, host: FakeHost, network: FakeNetwork) -> None:
|
||||
self.host = host
|
||||
@@ -282,12 +293,26 @@ class LifecycleTestCase(unittest.TestCase):
|
||||
self.store = TrackedStateStore(self.settings)
|
||||
self.host = FakeHost(self.store)
|
||||
self.images = ImageStore(self.settings)
|
||||
self.disk_sizes: dict[Path, int] = {}
|
||||
self.enterContext(patch.object(self.images, "_copy_sparse", side_effect=shutil.copy2))
|
||||
self.filesystem_resize = self.enterContext(patch.object(
|
||||
self.images, "_resize_filesystem",
|
||||
side_effect=lambda disk, capacity: self.disk_sizes.update({disk: capacity * 1024 * 1024}),
|
||||
))
|
||||
self.enterContext(patch.object(
|
||||
self.images, "disk_size_bytes",
|
||||
side_effect=lambda disk: self.disk_sizes.get(disk, disk.stat().st_size),
|
||||
))
|
||||
self.resize_disk = self.enterContext(patch.object(
|
||||
self.images, "resize_vm_disk", side_effect=self.resize_vm_disk,
|
||||
))
|
||||
self.provision = self.enterContext(patch.object(self.images, "provision_credentials"))
|
||||
self.copy_disk = self.enterContext(patch.object(
|
||||
self.images, "create_vm_disk", wraps=self.images.create_vm_disk,
|
||||
))
|
||||
self.network = FakeNetwork(self.settings, self.host)
|
||||
self.process = FakeProcess(self.host)
|
||||
self.known_hosts = FakeKnownHosts(self.host)
|
||||
self.clients: dict[str, FakeClient] = {}
|
||||
self.service = LifecycleService(
|
||||
self.settings,
|
||||
@@ -295,6 +320,7 @@ class LifecycleTestCase(unittest.TestCase):
|
||||
self.images,
|
||||
self.network, # type: ignore[arg-type]
|
||||
self.process, # type: ignore[arg-type]
|
||||
known_hosts=self.known_hosts, # type: ignore[arg-type]
|
||||
client_factory=self.make_client, # type: ignore[arg-type]
|
||||
)
|
||||
self.require_root = self.enterContext(patch("uvm.lifecycle.require_root"))
|
||||
@@ -307,6 +333,13 @@ class LifecycleTestCase(unittest.TestCase):
|
||||
self.clients[client.vm_id] = client
|
||||
return client
|
||||
|
||||
def resize_vm_disk(self, disk: Path, storage_mib: int) -> None:
|
||||
vm_id = disk.parent.name
|
||||
self.host.call("images.resize_disk", vm_id)
|
||||
if self.process.observe(self.store.get(vm_id)) != "gone":
|
||||
raise AssertionError("cannot resize a running or ambiguous VM")
|
||||
self.disk_sizes[disk] = storage_mib * 1024 * 1024
|
||||
|
||||
def create_vm(self, **kwargs) -> VmRecord:
|
||||
return self.service.create(VmSpec(cpu=1, ram_mib=512, **kwargs))
|
||||
|
||||
@@ -510,6 +543,7 @@ class LifecycleTests(LifecycleTestCase):
|
||||
settings = replace(self.settings, **changed)
|
||||
service = LifecycleService(
|
||||
settings, self.store, self.images, self.network, self.process,
|
||||
known_hosts=self.known_hosts,
|
||||
client_factory=self.make_client,
|
||||
)
|
||||
self.host.events.clear()
|
||||
@@ -542,6 +576,7 @@ class LifecycleTests(LifecycleTestCase):
|
||||
settings = replace(self.settings, network=IPv4Network(network), gateway=IPv4Address(gateway))
|
||||
service = LifecycleService(
|
||||
settings, self.store, self.images, self.network, self.process,
|
||||
known_hosts=self.known_hosts,
|
||||
client_factory=self.make_client,
|
||||
)
|
||||
with (
|
||||
|
||||
Reference in New Issue
Block a user