option('dry-run'); $obergrenze = max(1, (int) $this->option('limit')); $eingereiht = 0; $uebersprungen = 0; $warten = 0; // In der Abfrage eingegrenzt für die Datenbank, entschieden von // isDue() für die Regel — dieselbe Arbeitsteilung wie beim Nachbarn // EndDueServices. Die Abfrage ist ein Index, keine zweite Meinung: es // gibt genau eine Stelle, die sagt, wann eine Instanz fällig ist, und // die steht in der Aktion. $kandidaten = Instance::query() ->with('host') ->where('status', 'ended') ->whereNull('torn_down_at') ->whereNotNull('service_ends_at') ->where('service_ends_at', '<', now()->subDays(ArchiveAndTearDown::GRACE_DAYS)) // Siehe Kopf: noch nie gescheiterte zuerst, damit ein Dauerfall die // Obergrenze nicht Nacht für Nacht allein belegt. ->orderByRaw('(teardown_error is null) desc') ->orderBy('service_ends_at') ->cursor(); foreach ($kandidaten as $instance) { if (! $abbau->isDue($instance)) { continue; } if ($instance->host === null || $instance->vmid === null) { $uebersprungen++; $this->uebersprungen($instance, $probelauf); continue; } // Erst hier, nicht in der Abfrage: eine übersprungene Instanz kostet // die Warteschlange nichts und soll deshalb auch keinen der fünf // Plätze verbrauchen. if ($eingereiht >= $obergrenze) { $warten++; continue; } $this->line(($probelauf ? '[Probelauf] ' : '') ."{$instance->subdomain}: Laufzeit endete am " .$instance->service_ends_at->local()->isoFormat('LL') .' — Abbau auf '.($instance->host->name ?? '?').' (VMID '.$instance->vmid.')'); if ($probelauf) { $eingereiht++; continue; } try { TearDownInstance::dispatch($instance->uuid); } catch (Throwable $e) { // Ein Redis, das gerade nicht antwortet, darf nicht die // restlichen Instanzen dieses Laufes mitnehmen — dieselbe // Regel, nach der die Aktion selbst nie wirft. $uebersprungen++; $this->error("{$instance->subdomain}: liess sich nicht einreihen — ".$e->getMessage()); continue; } $eingereiht++; } $this->info($probelauf ? "Probelauf: {$eingereiht} Instanz(en) würden abgebaut, {$uebersprungen} übersprungen, " ."{$warten} warten auf den nächsten Lauf. Nichts wurde geändert." : "{$eingereiht} Instanz(en) zum Abbau eingereiht, {$uebersprungen} übersprungen, " ."{$warten} warten auf den nächsten Lauf."); return self::SUCCESS; } /** * Eine Instanz, zu der keine erreichbare Maschine mehr gehört. * * Der Grund wird nicht nur gesagt, sondern vermerkt — ausser im Probelauf. * Eine Zeile im Protokoll des Zeitplans liest niemand; `teardown_error` ist * das Feld, das die Konsole über der Instanzliste anzeigt. Ohne den Vermerk * bliebe diese Instanz für immer in der Auswahl stehen, würde jede Nacht * erneut übersprungen, und niemand erführe davon. * * Wortgleich mit dem, was `ArchiveAndTearDown` in derselben Lage wirft: es * ist derselbe Sachverhalt, und zwei Formulierungen dafür wären zwei * Sachverhalte, sobald jemand nach dem Text sucht. */ private function uebersprungen(Instance $instance, bool $probelauf): void { $grund = 'Zu dieser Instanz gehört keine erreichbare Maschine mehr (Host oder VMID fehlt). ' .'Es wird nichts gesichert und nichts gelöscht.'; $this->warn(($probelauf ? '[Probelauf] ' : '')."{$instance->subdomain}: übersprungen — ".$grund); if ($probelauf) { return; } try { $instance->update(['teardown_error' => $grund]); } catch (Throwable) { // Der Vermerk liess sich nicht schreiben. Gesagt ist er trotzdem, // und angefasst hat dieser Zweig ohnehin keine Maschine. } } }