diff --git a/CHANGELOG.md b/CHANGELOG.md index 84c6197..d7c46f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) ### 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) - 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) diff --git a/src/engine/ablaeufe/lxc-ausrollen.ts b/src/engine/ablaeufe/lxc-ausrollen.ts index 93dddb6..055a5fa 100644 --- a/src/engine/ablaeufe/lxc-ausrollen.ts +++ b/src/engine/ablaeufe/lxc-ausrollen.ts @@ -245,31 +245,30 @@ export function lxcAusrollSchritte(e: LxcAusrollEingabe, stand: LxcAusrollStand ausfuehren: async () => { if (stand.weg === "gast") bereitstellung = await e.bereitstellung(); const featureListe = [features.nesting ? "nesting=1" : null].filter(Boolean).join(","); - await aufgabeAbwarten( - client, - await client.post(pfad`/nodes/${stand.knoten!}/lxc`, { - vmid: stand.vmid!, - hostname: a.name, - ostemplate: stand.ostemplate!, - ostype: lxc!.ostype, - rootfs: `${a.storage}:${a.diskGb}`, - cores: a.cores, - memory: a.speicherMb, - swap: a.swapMb, - unprivileged: a.unprivilegiert, - ...(featureListe ? { features: featureListe } : {}), - net0: lxcNetz(a.netz), - ...(a.netz.dns.length > 0 ? { nameserver: a.netz.dns.join(" ") } : {}), - ...(a.netz.suchdomain ? { searchdomain: a.netz.suchdomain } : {}), - // Nur der Bereitstellungsschlüssel kommt an root – der Benutzer bekommt seine eigenen Schlüssel. - ...(bereitstellung ? { "ssh-public-keys": bereitstellung.oeffentlich } : {}), - onboot: a.startBeiBoot, - 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(", ") || "–"}`, - }), - { zeitlimitMs: 15 * 60 * 1000 }, - ); + const upid = await client.post(pfad`/nodes/${stand.knoten!}/lxc`, { + vmid: stand.vmid!, + hostname: a.name, + ostemplate: stand.ostemplate!, + ostype: lxc!.ostype, + rootfs: `${a.storage}:${a.diskGb}`, + cores: a.cores, + memory: a.speicherMb, + swap: a.swapMb, + unprivileged: a.unprivilegiert, + ...(featureListe ? { features: featureListe } : {}), + net0: lxcNetz(a.netz), + ...(a.netz.dns.length > 0 ? { nameserver: a.netz.dns.join(" ") } : {}), + ...(a.netz.suchdomain ? { searchdomain: a.netz.suchdomain } : {}), + // Nur der Bereitstellungsschlüssel kommt an root – der Benutzer bekommt seine eigenen Schlüssel. + ...(bereitstellung ? { "ssh-public-keys": bereitstellung.oeffentlich } : {}), + onboot: a.startBeiBoot, + 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(", ") || "–"}`, + }); + // 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 aufgabeAbwarten(client, upid, { zeitlimitMs: 15 * 60 * 1000 }); if (features.keyctl) { const ssh = e.sshFuer(stand.knoten!)!; if (!client.trocken) { diff --git a/src/engine/ablaeufe/vm-ausrollen.ts b/src/engine/ablaeufe/vm-ausrollen.ts index ccec6c0..5e004d4 100644 --- a/src/engine/ablaeufe/vm-ausrollen.ts +++ b/src/engine/ablaeufe/vm-ausrollen.ts @@ -234,32 +234,32 @@ export function vmAusrollSchritte(e: VmAusrollEingabe, stand: VmAusrollStand = { titel: "Klonen", ausfuehren: async (k) => { const tpl = stand.template!; - const klonen = async (vmid: number) => - await aufgabeAbwarten( - client, - await client.post(pfad`/nodes/${tpl.knoten}/qemu/${tpl.vmid}/clone`, { - newid: vmid, - name: a.name, - // Schon beim Klonen: daran erkennt „Aufräumen“, dass die VM zu diesem Auftrag gehört. - description: beschreibung(), - full: true, - target: stand.knoten!, - storage: a.storage, - }), - { - zeitlimitMs: 60 * 60 * 1000, - intervallMs: 2000, - // „transferred 1.2 GiB of 3.5 GiB (35.16%)“ – auf langsamen Storages dauert ein Voll-Klon. - beiLog: (zeilen) => { - const letzte = zeilen - .map((z) => /transferred (.+) of (.+) \((\d+(?:\.\d+)?)%\)/.exec(z)) - .filter(Boolean) - .at(-1); - if (letzte) - void k.detail(`${letzte[1]} von ${letzte[2]} (${Math.round(Number(letzte[3]))} %)`); - }, + const klonen = async (vmid: number) => { + const upid = await client.post(pfad`/nodes/${tpl.knoten}/qemu/${tpl.vmid}/clone`, { + newid: vmid, + name: a.name, + // Schon beim Klonen: daran erkennt „Aufräumen“, dass die VM zu diesem Auftrag gehört. + description: beschreibung(), + full: true, + target: stand.knoten!, + 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, + intervallMs: 2000, + // „transferred 1.2 GiB of 3.5 GiB (35.16%)“ – auf langsamen Storages dauert ein Voll-Klon. + beiLog: (zeilen) => { + const letzte = zeilen + .map((z) => /transferred (.+) of (.+) \((\d+(?:\.\d+)?)%\)/.exec(z)) + .filter(Boolean) + .at(-1); + if (letzte) void k.detail(`${letzte[1]} von ${letzte[2]} (${Math.round(Number(letzte[3]))} %)`); }, - ); + }); + }; try { await klonen(stand.vmid!); } 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 klonen(stand.vmid); } - await e.angelegt({ vmid: stand.vmid }); return { detail: `full clone ${tpl.vmid} → ${stand.vmid}` }; }, }, diff --git a/tests/engine/lxc-ausrollen.test.ts b/tests/engine/lxc-ausrollen.test.ts index 846f2ac..4688171 100644 --- a/tests/engine/lxc-ausrollen.test.ts +++ b/tests/engine/lxc-ausrollen.test.ts @@ -127,6 +127,21 @@ async function eingabe( } 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> = []; + 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 () => { const { mock, client } = await aufbauen({ dhcpNachAbfragen: 2 }); const befehle: Array<{ befehl: string; eingabe?: string }> = [];