Refactor VM lifecycle management and enhance validation
- Introduced a new method `installed_kernel` in `ImageStore` to handle kernel verification and checksum validation. - Updated `Installer` to include additional packages: `nftables` and `conntrack`. - Refined `LifecycleService` to improve error handling and state management during VM creation, starting, stopping, and deletion. - Enhanced port management with new methods for setting and syncing ports, including validation for port rules. - Improved error handling in `StateStore` to raise specific `NotFoundError` and `ConflictError` exceptions. - Updated network management to ensure proper TAP device handling and firewall integration. - Enhanced API responses in `vms.py` to include policy status for better client-side handling. - Added validation for VM names and ports in `validation.py` to enforce stricter rules and prevent conflicts.
This commit is contained in:
+66
-1
@@ -9,7 +9,8 @@ from ipaddress import IPv4Address
|
||||
|
||||
from .config import Settings
|
||||
from .domain import VmRecord
|
||||
from .errors import TapCreationError, UvmError, ValidationError
|
||||
from .errors import ConflictError, TapCreationError, UvmError, ValidationError
|
||||
from .firewall import FirewallManager
|
||||
from .system import CommandRunner
|
||||
|
||||
|
||||
@@ -19,6 +20,7 @@ class NetworkManager:
|
||||
def __init__(self, settings: Settings, runner: CommandRunner) -> None:
|
||||
self._settings = settings
|
||||
self._runner = runner
|
||||
self.firewall = FirewallManager(settings, runner)
|
||||
|
||||
def allocate_ip(
|
||||
self,
|
||||
@@ -95,6 +97,69 @@ class NetworkManager:
|
||||
raise UvmError(f"could not determine whether existing interface {bridge} is a bridge") from error
|
||||
if kind != "bridge":
|
||||
raise UvmError(f"configured bridge {bridge} exists but is not a Linux bridge")
|
||||
if links[0]["linkinfo"].get("info_data", {}).get("stp_state", 0) != 0:
|
||||
raise UvmError("private ports require an STP-disabled UVM bridge")
|
||||
|
||||
def prepare_tap(self, vm: VmRecord) -> None:
|
||||
"""Create an owned, attached TAP down; policy must precede activation."""
|
||||
if self._link(vm.tap) is not None:
|
||||
raise ConflictError(f"TAP device already exists: {vm.tap}")
|
||||
try:
|
||||
self._runner.run(("ip", "tuntap", "add", "dev", vm.tap, "mode", "tap"), capture=True)
|
||||
vm.tap_owned = True
|
||||
self._runner.run(("ip", "link", "set", "dev", vm.tap, "alias", f"uvm:{vm.id}"), capture=True)
|
||||
self._runner.run(("ip", "link", "set", vm.tap, "master", vm.bridge or self._settings.bridge), capture=True)
|
||||
self._runner.run(("ip", "link", "set", "dev", vm.tap, "type", "bridge_slave",
|
||||
"group_fwd_mask", "16384"), capture=True)
|
||||
except BaseException:
|
||||
if vm.tap_owned:
|
||||
try:
|
||||
self.delete_tap(vm.tap)
|
||||
vm.tap_owned = False
|
||||
except UvmError:
|
||||
pass
|
||||
raise
|
||||
|
||||
def activate_tap(self, vm: VmRecord) -> None:
|
||||
if self._owned_link(vm) is None:
|
||||
raise UvmError(f"TAP device missing: {vm.tap}")
|
||||
self._runner.run(("ip", "link", "set", vm.tap, "up"), capture=True)
|
||||
|
||||
def deactivate_tap(self, vm: VmRecord) -> None:
|
||||
if self._owned_link(vm) is not None:
|
||||
self._runner.run(("ip", "link", "set", vm.tap, "down"), capture=True)
|
||||
|
||||
def delete_vm_tap(self, vm: VmRecord) -> None:
|
||||
if self._owned_link(vm) is not None:
|
||||
self.delete_tap(vm.tap)
|
||||
vm.tap_owned = False
|
||||
|
||||
def has_tap(self, vm: VmRecord) -> bool:
|
||||
return self._owned_link(vm) is not None
|
||||
|
||||
def _link(self, name: str) -> dict | None:
|
||||
result = self._runner.run(("ip", "-j", "-d", "link", "show"), capture=True)
|
||||
try:
|
||||
links = json.loads(result.stdout)
|
||||
if not isinstance(links, list) or any(not isinstance(link, dict) for link in links):
|
||||
raise ValueError("invalid interface inventory")
|
||||
matches = [link for link in links if link.get("ifname") == name]
|
||||
if len(matches) > 1:
|
||||
raise ValueError("ambiguous interface inventory")
|
||||
return matches[0] if matches else None
|
||||
except (ValueError, TypeError) as error:
|
||||
raise UvmError(f"cannot inspect network interfaces: {error}") from error
|
||||
|
||||
def _owned_link(self, vm: VmRecord) -> dict | None:
|
||||
link = self._link(vm.tap)
|
||||
if link is None:
|
||||
return None
|
||||
info = link.get("linkinfo", {})
|
||||
is_tap = info.get("info_kind") == "tun" and info.get("info_data", {}).get("type") == "tap"
|
||||
legacy = vm.ports is None and vm.tap_owned and not link.get("ifalias")
|
||||
if not is_tap or (link.get("ifalias") != f"uvm:{vm.id}" and not legacy):
|
||||
raise ConflictError(f"refusing to modify unowned TAP {vm.tap}")
|
||||
return link
|
||||
|
||||
def create_tap(self, name: str) -> None:
|
||||
existing = self._runner.run(("ip", "link", "show", name), check=False, capture=True)
|
||||
|
||||
Reference in New Issue
Block a user