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:
@@ -30,7 +30,7 @@ class CliTestCase(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.vm = replace(
|
||||
make_vm(), name="web", ports=(PortRule(22), PortRule(80, "HTTP")),
|
||||
status="running", disk_ready=True,
|
||||
status="running", disk_ready=True, storage_mib=30 * 1024,
|
||||
)
|
||||
self.lifecycle = create_autospec(LifecycleService, instance=True, spec_set=True)
|
||||
self.listed = ListedVm(vm=self.vm, observed_status="running", policy_status="enforced")
|
||||
@@ -45,8 +45,9 @@ class CliTestCase(unittest.TestCase):
|
||||
]
|
||||
self.lifecycle.create.side_effect = lambda spec: replace(
|
||||
self.vm, cpu=spec.cpu, ram_mib=spec.ram_mib, username=spec.username,
|
||||
password=spec.password, name=spec.name, ports=spec.ports,
|
||||
password=spec.password, name=spec.name, ports=spec.ports, storage_mib=spec.storage_mib,
|
||||
)
|
||||
self.lifecycle.resize.return_value = self.vm
|
||||
self.application = SimpleNamespace(
|
||||
settings=Settings(), lifecycle=self.lifecycle,
|
||||
state_store=Mock(spec_set=("operation_lock",)),
|
||||
@@ -186,10 +187,38 @@ class CliParsingTests(CliTestCase):
|
||||
self.assertEqual(self.invoke([])[0], 2)
|
||||
|
||||
|
||||
class CliResizeCommandTests(CliTestCase):
|
||||
def test_resize_forwards_resources_and_timeout_without_changing_identity(self) -> None:
|
||||
self.lifecycle.resize.return_value = replace(self.vm, cpu=2, ram_mib=4096, storage_mib=50 * 1024)
|
||||
code, stdout, stderr = self.invoke([
|
||||
"resize", "web", "--cpu", "2", "--ram", "4G", "--storage", "50G", "--timeout", "0.5", "--json",
|
||||
])
|
||||
self.assertEqual(code, 0, stderr)
|
||||
self.lifecycle.resize.assert_called_once_with("web", cpu=2, ram_mib=4096, storage_mib=51200, timeout=0.5)
|
||||
result = json.loads(stdout)
|
||||
self.assertEqual(result["storage_mib"], 51200)
|
||||
self.assertEqual(result["guest_ip"], self.vm.guest_ip)
|
||||
self.assertEqual(result["username"], self.vm.username)
|
||||
self.assertNotIn("password", result)
|
||||
self.assertNotIn(self.vm.password, stdout + stderr)
|
||||
|
||||
def test_partial_resize_preserves_omitted_fields(self) -> None:
|
||||
code, _stdout, stderr = self.invoke(["resize", "web", "--storage", "60G"])
|
||||
self.assertEqual(code, 0, stderr)
|
||||
self.lifecycle.resize.assert_called_once_with("web", cpu=None, ram_mib=None, storage_mib=61440, timeout=None)
|
||||
|
||||
def test_invalid_resize_requests_are_rejected_before_lifecycle_calls(self) -> None:
|
||||
for options in ([], ["--storage", "29G"], ["--ram", "127M"], ["--cpu", "nan"],
|
||||
["--cpu", "0"], ["--cpu", "2", "--timeout", "-1"], ["--username", "new-user"]):
|
||||
with self.subTest(options=options):
|
||||
self.assertEqual(self.invoke(["resize", "web", *options])[0], 2)
|
||||
self.lifecycle.resize.assert_not_called()
|
||||
|
||||
|
||||
class CliCreateCommandTests(CliTestCase):
|
||||
def test_create_combines_default_ports_and_stdin_credentials(self) -> None:
|
||||
code, stdout, stderr = self.invoke([
|
||||
"create", "--name", "web", "--cpu", "0.5", "--ram", "1G",
|
||||
"create", "--name", "web", "--cpu", "0.5", "--ram", "1G", "--storage", "50G",
|
||||
"--guest-ip", "10.42.0.8", "--username", "admin", "--password-stdin",
|
||||
"--ports", "80:http,443:https", "--ports", "8080:tcp", "--json",
|
||||
], stdin=io.StringIO(" stdin-secret \r\n"))
|
||||
@@ -199,6 +228,7 @@ class CliCreateCommandTests(CliTestCase):
|
||||
self.assertEqual(spec.name, "web")
|
||||
self.assertEqual(spec.cpu, 0.5)
|
||||
self.assertEqual(spec.ram_mib, 1024)
|
||||
self.assertEqual(spec.storage_mib, 50 * 1024)
|
||||
self.assertEqual(spec.guest_ip, IPv4Address("10.42.0.8"))
|
||||
self.assertEqual(spec.username, "admin")
|
||||
self.assertEqual(spec.password, " stdin-secret ")
|
||||
@@ -222,6 +252,7 @@ class CliCreateCommandTests(CliTestCase):
|
||||
code, stdout, stderr = self.invoke(["create", *options, "--json"])
|
||||
self.assertEqual(code, 0, stderr)
|
||||
self.assertEqual(self.lifecycle.create.call_args.args[0].ports, expected)
|
||||
self.assertEqual(self.lifecycle.create.call_args.args[0].storage_mib, 30 * 1024)
|
||||
self.assertNotIn("WARNING", stdout)
|
||||
self.assertIn("default password", stderr)
|
||||
self.assertEqual(json.loads(stdout)["policy_status"], "enforced")
|
||||
|
||||
Reference in New Issue
Block a user