feat: Add storage management for VM creation and resizing

- Introduced storage parameter in VmCreateRequest and VmResizeRequest models.
- Implemented storage validation to ensure minimum and maximum limits.
- Enhanced CLI to support storage options during VM creation and resizing.
- Updated lifecycle management to handle pending resize operations for VM resources.
- Added methods for resizing VM disks and validating storage capacity.
- Updated API endpoints to accommodate new storage functionality.
- Refactored related validation and configuration logic to support storage management.
This commit is contained in:
2026-09-21 19:16:33 +05:30
parent 781ee753ec
commit fb80ce7c8a
38 changed files with 728 additions and 98 deletions
+17 -1
View File
@@ -5,13 +5,15 @@ from __future__ import annotations
import math
import re
from collections.abc import Sequence
from decimal import Decimal, InvalidOperation
from decimal import Decimal, InvalidOperation, ROUND_CEILING
from .config import MAX_STORAGE_MIB, MIN_STORAGE_MIB
from .errors import ValidationError
from .domain import PortRule
_RAM_PATTERN = re.compile(r"\s*(\d+(?:\.\d+)?)\s*([BKMG]i?B?)?\s*", re.IGNORECASE)
_STORAGE_PATTERN = re.compile(r"\s*(\d+(?:\.\d+)?)\s*([BKMGT]i?B?)?\s*", re.IGNORECASE)
_USERNAME_PATTERN = re.compile(r"[a-z_][a-z0-9_-]{0,31}\$?")
_MIB_FACTORS = {
"b": Decimal(1) / Decimal(1024 * 1024),
@@ -62,6 +64,20 @@ def parse_cpu(value: str) -> float:
return cpu
def parse_storage(value: str) -> int:
"""Parse GiB-default storage, rounding capacity up to a whole MiB."""
match = _STORAGE_PATTERN.fullmatch(value)
if match is None:
raise ValidationError(f"invalid storage value: {value}; use e.g. 30G, 50G, or 1T")
factors = {**_MIB_FACTORS, **dict.fromkeys(("t", "tb", "ti", "tib"), Decimal(1024 * 1024))}
amount = Decimal(match.group(1)) * factors[(match.group(2) or "g").lower()]
if amount < MIN_STORAGE_MIB:
raise ValidationError("storage must be at least 30 GiB")
if amount > MAX_STORAGE_MIB:
raise ValidationError(f"storage must not exceed {MAX_STORAGE_MIB} MiB")
return int(amount.to_integral_value(rounding=ROUND_CEILING))
def parse_username(value: str) -> str:
"""Validate a conventional Linux account name."""