Files
nest/uvm/routers/vms.py
T
kstyagi d8a25209b9 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.
2026-09-09 17:27:50 +05:30

84 lines
2.9 KiB
Python

"""VM lifecycle endpoints backed by the existing UVM lifecycle service."""
from __future__ import annotations
from fastapi import APIRouter, Depends, status
from ..api_models import DestroyResponse, VmCreateRequest, VmResponse, vm_response
from ..app import Application
from ..domain import PortRule, VmSpec
from ..validation import parse_cpu, parse_name, parse_password, parse_ports, parse_ram, parse_username
from .dependencies import get_authorized_application
router = APIRouter(prefix="/vms", tags=["vms"])
@router.get("", response_model=list[VmResponse])
def list_vms(
application: Application = Depends(get_authorized_application),
) -> list[VmResponse]:
return [
vm_response(
listed.vm,
observed_status=listed.observed_status,
policy_status=listed.policy_status,
)
for listed in application.lifecycle.list_vms()
]
@router.post("", response_model=VmResponse, status_code=status.HTTP_201_CREATED)
def create_vm(
request: VmCreateRequest,
application: Application = Depends(get_authorized_application),
) -> VmResponse:
ports = parse_ports([f"{rule.port}:{rule.protocol}" for rule in request.ports or ()])
if not request.no_default_ports and not any(rule.port == 22 for rule in ports):
ports = tuple(sorted((*ports, PortRule(22)), key=lambda rule: rule.port))
vm = application.lifecycle.create(
VmSpec(
cpu=parse_cpu(str(request.cpu)),
ram_mib=parse_ram(str(request.ram)),
guest_ip=request.guest_ip,
username=parse_username(request.username),
password=parse_password(request.password.get_secret_value()),
name=parse_name(request.name) if request.name is not None else None,
ports=ports,
)
)
policy_status = "unmanaged" if vm.ports is None else "unknown"
if vm.ports is not None and vm.status == "running" and vm.operation is None:
policy_status = "enforced"
return vm_response(vm, policy_status=policy_status)
@router.post("/{vm_id}/stop", response_model=VmResponse)
def stop_vm(
vm_id: str,
application: Application = Depends(get_authorized_application),
) -> VmResponse:
return vm_response(application.lifecycle.stop(vm_id), policy_status="inactive")
@router.delete("/{vm_id}", response_model=DestroyResponse)
def destroy_vm(
vm_id: str,
application: Application = Depends(get_authorized_application),
) -> DestroyResponse:
vm = application.lifecycle.destroy(vm_id)
return DestroyResponse(id=vm.id)
@router.get("/{identifier}", response_model=VmResponse)
def get_vm(
identifier: str,
application: Application = Depends(get_authorized_application),
) -> VmResponse:
listed = application.lifecycle.inspect(identifier)
return vm_response(
listed.vm,
observed_status=listed.observed_status,
policy_status=listed.policy_status,
)