diff --git a/CHANGELOG.md b/CHANGELOG.md index ce63b3e..20a299e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- VMID-Allokator-Migration mit transaktionssicherer Reservation pro Cluster angelegt. - VMs-und-SSH-Keys-Migration mit Ressourcen-Mapping und eindeutigem Proxmox-VMID-Index angelegt. - Cluster-Migration fuer Proxmox-Verbindungen mit `encrypted_token bytea` angelegt. - Projekte-und-Quotas-Migration mit Defaults und Constraints angelegt. diff --git a/TODO.md b/TODO.md index aec39d2..20c9bdc 100644 --- a/TODO.md +++ b/TODO.md @@ -54,6 +54,11 @@ Arbeitsliste auf Basis von `proxmox-console-entwicklungsplan.md`. Die Entwurfsda - [x] VMs referenzieren eindeutig Projekt und Cluster - [x] Unique-Index auf `(cluster_id, proxmox_vmid)` gesetzt - [x] Ressourcen- und Pflichtfeld-Checks gesetzt +- [x] E2-T06: VMID-Allokator + - [x] Clusterweite Allocator-Tabelle angelegt + - [x] VMID-Reservationen vor Proxmox-Calls modelliert + - [x] Transaktionssichere Funktion `reserve_next_vmid(cluster_id)` mit `SELECT ... FOR UPDATE` angelegt + - [x] Concurrency-Test mit 50 parallelen Reservierungen lokal verifiziert ## MVP-Backlog @@ -97,3 +102,4 @@ Arbeitsliste auf Basis von `proxmox-console-entwicklungsplan.md`. Die Entwurfsda - 2026-06-10: Projekte-und-Quotas-Migration `0003_projects_and_quotas` angelegt und lokal gegen Supabase verifiziert. - 2026-06-10: Cluster-Migration `0004_clusters` angelegt und lokal gegen Supabase verifiziert. - 2026-06-10: VMs-und-SSH-Keys-Migration `0005_vms_and_ssh_keys` angelegt und lokal gegen Supabase verifiziert. +- 2026-06-10: VMID-Allokator-Migration `0006_vmid_allocator` angelegt und mit 50 parallelen Reservierungen lokal gegen Supabase verifiziert. diff --git a/migrations/0006_vmid_allocator.down.sql b/migrations/0006_vmid_allocator.down.sql new file mode 100644 index 0000000..6050b2b --- /dev/null +++ b/migrations/0006_vmid_allocator.down.sql @@ -0,0 +1,6 @@ +DROP FUNCTION IF EXISTS public.reserve_next_vmid(uuid); +DROP INDEX IF EXISTS public.vmid_reservations_cluster_id_idx; +DROP INDEX IF EXISTS public.vmid_reservations_reserved_key; +DROP TABLE IF EXISTS public.vmid_reservations; +DROP TABLE IF EXISTS public.cluster_vmid_allocators; +DROP TYPE IF EXISTS public.vmid_reservation_status; diff --git a/migrations/0006_vmid_allocator.up.sql b/migrations/0006_vmid_allocator.up.sql new file mode 100644 index 0000000..af083c4 --- /dev/null +++ b/migrations/0006_vmid_allocator.up.sql @@ -0,0 +1,71 @@ +CREATE TYPE public.vmid_reservation_status AS ENUM ('reserved', 'released'); + +CREATE TABLE public.cluster_vmid_allocators ( + cluster_id uuid PRIMARY KEY REFERENCES public.clusters(id) ON DELETE CASCADE, + next_vmid integer NOT NULL DEFAULT 100, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + CONSTRAINT cluster_vmid_allocators_next_vmid_check CHECK (next_vmid > 0) +); + +CREATE TABLE public.vmid_reservations ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + cluster_id uuid NOT NULL REFERENCES public.clusters(id) ON DELETE CASCADE, + proxmox_vmid integer NOT NULL, + status public.vmid_reservation_status NOT NULL DEFAULT 'reserved', + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + CONSTRAINT vmid_reservations_proxmox_vmid_check CHECK (proxmox_vmid > 0) +); + +CREATE UNIQUE INDEX vmid_reservations_reserved_key + ON public.vmid_reservations (cluster_id, proxmox_vmid) + WHERE status = 'reserved'; + +CREATE INDEX vmid_reservations_cluster_id_idx ON public.vmid_reservations (cluster_id); + +CREATE OR REPLACE FUNCTION public.reserve_next_vmid(p_cluster_id uuid) +RETURNS integer +LANGUAGE plpgsql +AS $$ +DECLARE + candidate integer; +BEGIN + INSERT INTO public.cluster_vmid_allocators (cluster_id) + VALUES (p_cluster_id) + ON CONFLICT (cluster_id) DO NOTHING; + + SELECT next_vmid + INTO candidate + FROM public.cluster_vmid_allocators + WHERE cluster_id = p_cluster_id + FOR UPDATE; + + LOOP + EXIT WHEN NOT EXISTS ( + SELECT 1 + FROM public.vms + WHERE cluster_id = p_cluster_id + AND proxmox_vmid = candidate + ) AND NOT EXISTS ( + SELECT 1 + FROM public.vmid_reservations + WHERE cluster_id = p_cluster_id + AND proxmox_vmid = candidate + AND status = 'reserved' + ); + + candidate := candidate + 1; + END LOOP; + + INSERT INTO public.vmid_reservations (cluster_id, proxmox_vmid) + VALUES (p_cluster_id, candidate); + + UPDATE public.cluster_vmid_allocators + SET next_vmid = candidate + 1, + updated_at = now() + WHERE cluster_id = p_cluster_id; + + RETURN candidate; +END; +$$;