fix(ablauf): angelegte ID merken, bevor auf Proxmox gewartet wird

Brach das Warten auf Klonen oder Anlegen ab, legte Proxmox den Gast trotzdem fertig an – und „Aufräumen“ wusste nichts von ihm (CT 114 im Feldtest).

Refs #020

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 16:02:57 +02:00
co-authored by Claude Opus 5.5
parent bc13b94c7e
commit ca9d6de446
4 changed files with 64 additions and 50 deletions
+1
View File
@@ -14,6 +14,7 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
- LXC: `nesting` ist für Container standardmäßig an, wie in der Proxmox-Oberfläche – Debian 12 und Ubuntu 22.04 (systemd 252/249) brauchen es; abwählbar im Assistenten und mit `--nesting=false` (#020) - LXC: `nesting` ist für Container standardmäßig an, wie in der Proxmox-Oberfläche – Debian 12 und Ubuntu 22.04 (systemd 252/249) brauchen es; abwählbar im Assistenten und mit `--nesting=false` (#020)
### Behoben ### Behoben
- „Aufräumen“ kannte einen Gast nicht, wenn das Warten auf Klonen bzw. Anlegen abbrach, Proxmox ihn aber fertig anlegte – die ID wird jetzt gemerkt, sobald Proxmox den Auftrag angenommen hat (#020)
- Warten auf Proxmox-Tasks bricht bei vorübergehend fehlschlagenden Statusabfragen nicht mehr ab, sondern fragt bis zum Zeitlimit des Tasks weiter; Verbindungsaufbau darf 30 s statt 10 s dauern (#020) - Warten auf Proxmox-Tasks bricht bei vorübergehend fehlschlagenden Statusabfragen nicht mehr ab, sondern fragt bis zum Zeitlimit des Tasks weiter; Verbindungsaufbau darf 30 s statt 10 s dauern (#020)
- Gast-Benutzer, der wie eine vorhandene Gruppe heißt (z. B. „admin“ auf Ubuntu 22.04), ließ die Provisionierung mit Code 9 abbrechen; die Gruppe wird jetzt Primärgruppe (#020) - Gast-Benutzer, der wie eine vorhandene Gruppe heißt (z. B. „admin“ auf Ubuntu 22.04), ließ die Provisionierung mit Code 9 abbrechen; die Gruppe wird jetzt Primärgruppe (#020)
- `pnpm test:container` lief seit pnpm 12 nicht mehr (`pnpm -s` gibt es dort nicht) (#020) - `pnpm test:container` lief seit pnpm 12 nicht mehr (`pnpm -s` gibt es dort nicht) (#020)
+5 -6
View File
@@ -245,9 +245,7 @@ export function lxcAusrollSchritte(e: LxcAusrollEingabe, stand: LxcAusrollStand
ausfuehren: async () => { ausfuehren: async () => {
if (stand.weg === "gast") bereitstellung = await e.bereitstellung(); if (stand.weg === "gast") bereitstellung = await e.bereitstellung();
const featureListe = [features.nesting ? "nesting=1" : null].filter(Boolean).join(","); const featureListe = [features.nesting ? "nesting=1" : null].filter(Boolean).join(",");
await aufgabeAbwarten( const upid = await client.post<string>(pfad`/nodes/${stand.knoten!}/lxc`, {
client,
await client.post<string>(pfad`/nodes/${stand.knoten!}/lxc`, {
vmid: stand.vmid!, vmid: stand.vmid!,
hostname: a.name, hostname: a.name,
ostemplate: stand.ostemplate!, ostemplate: stand.ostemplate!,
@@ -266,10 +264,11 @@ export function lxcAusrollSchritte(e: LxcAusrollEingabe, stand: LxcAusrollStand
onboot: a.startBeiBoot, onboot: a.startBeiBoot,
tags: ["proxdeploy", d.id, ...a.tags].join(";"), tags: ["proxdeploy", d.id, ...a.tags].join(";"),
description: `Ausgerollt mit ProxDeploy (Auftrag ${e.auftragKurzId})\nDistribution: ${d.name}\nProfile: ${e.profile.map((p) => p.profil.id).join(", ") || "–"}`, description: `Ausgerollt mit ProxDeploy (Auftrag ${e.auftragKurzId})\nDistribution: ${d.name}\nProfile: ${e.profile.map((p) => p.profil.id).join(", ") || "–"}`,
}), });
{ zeitlimitMs: 15 * 60 * 1000 }, // Sofort merken, nicht erst nach dem Warten: bricht das Warten ab, legt Proxmox den
); // Container trotzdem fertig an – „Aufräumen“ muss ihn dann kennen.
await e.angelegt({ vmid: stand.vmid }); await e.angelegt({ vmid: stand.vmid });
await aufgabeAbwarten(client, upid, { zeitlimitMs: 15 * 60 * 1000 });
if (features.keyctl) { if (features.keyctl) {
const ssh = e.sshFuer(stand.knoten!)!; const ssh = e.sshFuer(stand.knoten!)!;
if (!client.trocken) { if (!client.trocken) {
+10 -11
View File
@@ -234,10 +234,8 @@ export function vmAusrollSchritte(e: VmAusrollEingabe, stand: VmAusrollStand = {
titel: "Klonen", titel: "Klonen",
ausfuehren: async (k) => { ausfuehren: async (k) => {
const tpl = stand.template!; const tpl = stand.template!;
const klonen = async (vmid: number) => const klonen = async (vmid: number) => {
await aufgabeAbwarten( const upid = await client.post<string>(pfad`/nodes/${tpl.knoten}/qemu/${tpl.vmid}/clone`, {
client,
await client.post<string>(pfad`/nodes/${tpl.knoten}/qemu/${tpl.vmid}/clone`, {
newid: vmid, newid: vmid,
name: a.name, name: a.name,
// Schon beim Klonen: daran erkennt „Aufräumen“, dass die VM zu diesem Auftrag gehört. // Schon beim Klonen: daran erkennt „Aufräumen“, dass die VM zu diesem Auftrag gehört.
@@ -245,8 +243,11 @@ export function vmAusrollSchritte(e: VmAusrollEingabe, stand: VmAusrollStand = {
full: true, full: true,
target: stand.knoten!, target: stand.knoten!,
storage: a.storage, storage: a.storage,
}), });
{ // Sofort merken, nicht erst nach dem Warten: der Klon läuft auf dem Node weiter, auch
// wenn das Warten abbricht – „Aufräumen“ muss die VM dann kennen.
await e.angelegt({ vmid });
await aufgabeAbwarten(client, upid, {
zeitlimitMs: 60 * 60 * 1000, zeitlimitMs: 60 * 60 * 1000,
intervallMs: 2000, intervallMs: 2000,
// „transferred 1.2 GiB of 3.5 GiB (35.16%)“ – auf langsamen Storages dauert ein Voll-Klon. // „transferred 1.2 GiB of 3.5 GiB (35.16%)“ – auf langsamen Storages dauert ein Voll-Klon.
@@ -255,11 +256,10 @@ export function vmAusrollSchritte(e: VmAusrollEingabe, stand: VmAusrollStand = {
.map((z) => /transferred (.+) of (.+) \((\d+(?:\.\d+)?)%\)/.exec(z)) .map((z) => /transferred (.+) of (.+) \((\d+(?:\.\d+)?)%\)/.exec(z))
.filter(Boolean) .filter(Boolean)
.at(-1); .at(-1);
if (letzte) if (letzte) void k.detail(`${letzte[1]} von ${letzte[2]} (${Math.round(Number(letzte[3]))} %)`);
void k.detail(`${letzte[1]} von ${letzte[2]} (${Math.round(Number(letzte[3]))} %)`);
}, },
}, });
); };
try { try {
await klonen(stand.vmid!); await klonen(stand.vmid!);
} catch (fehler) { } catch (fehler) {
@@ -273,7 +273,6 @@ export function vmAusrollSchritte(e: VmAusrollEingabe, stand: VmAusrollStand = {
await k.log(`VMID war inzwischen belegt – neuer Versuch mit ${stand.vmid}`, "warn"); await k.log(`VMID war inzwischen belegt – neuer Versuch mit ${stand.vmid}`, "warn");
await klonen(stand.vmid); await klonen(stand.vmid);
} }
await e.angelegt({ vmid: stand.vmid });
return { detail: `full clone ${tpl.vmid} → ${stand.vmid}` }; return { detail: `full clone ${tpl.vmid} → ${stand.vmid}` };
}, },
}, },
+15
View File
@@ -127,6 +127,21 @@ async function eingabe(
} }
describe("LXC ausrollen", () => { describe("LXC ausrollen", () => {
it("merkt sich die CTID schon nach dem Absenden – auch wenn die Anlage danach scheitert", async () => {
const { mock, client } = await aufbauen({ templateLokal: ["debian-13-standard_13.6-1_amd64.tar.zst"] });
mock.route("GET", /\/tasks\/[^/]+vzcreate[^/]*\/status$/, () => ({
data: { status: "stopped", exitstatus: "unable to create CT 130 - storage full" },
}));
const { e } = await eingabe(client);
const gemerkt: Array<Record<string, unknown>> = [];
const ergebnis = await phasenAusfuehren(
lxcAusrollSchritte({ ...e, angelegt: async (x) => void gemerkt.push(x) }),
stumm,
);
expect(ergebnis.ok).toBe(false);
expect(gemerkt).toContainEqual({ vmid: 130 });
});
it("mit Node-SSH: neuestes Template laden, anlegen mit nesting (docker), DHCP-IP, pct exec mit Skript auf stdin", async () => { it("mit Node-SSH: neuestes Template laden, anlegen mit nesting (docker), DHCP-IP, pct exec mit Skript auf stdin", async () => {
const { mock, client } = await aufbauen({ dhcpNachAbfragen: 2 }); const { mock, client } = await aufbauen({ dhcpNachAbfragen: 2 });
const befehle: Array<{ befehl: string; eingabe?: string }> = []; const befehle: Array<{ befehl: string; eingabe?: string }> = [];