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:
@@ -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)
|
||||
|
||||
@@ -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<string>(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<string>(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) {
|
||||
|
||||
@@ -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<string>(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<string>(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}` };
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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<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 () => {
|
||||
const { mock, client } = await aufbauen({ dhcpNachAbfragen: 2 });
|
||||
const befehle: Array<{ befehl: string; eingabe?: string }> = [];
|
||||
|
||||
Reference in New Issue
Block a user