-`, fortlaufend je Rechenzentrum und * niemals wiederverwendet. Maschine, Proxmox-Node, DNS, /etc/hosts und Konsole * benutzen denselben. */ final class HostName { /** * Der Name, den der nächste Host in diesem Rechenzentrum bekäme. * * LIEST den Zähler, verbraucht ihn nicht: die Anlegen-Seite zeigt den Namen, * bevor gespeichert wird. Zwei gleichzeitig geöffnete Formulare zeigen * deshalb beide dieselbe Nummer, und der zweite bekommt beim Speichern die * nächste. Eine Reservierung beim Öffnen wäre die schlechtere Antwort — ein * abgebrochenes Formular hinterließe eine Lücke im Zähler, die niemand * wieder auffüllt. */ public static function preview(string $datacenterCode): string { $from = (int) (Datacenter::query() ->where('code', $datacenterCode) ->value('next_host_number') ?? 1); return self::free(self::label($datacenterCode), $from)[1]; } /** * Vergibt den Namen und verbraucht die Nummer. * * Gehört in eine Transaktion: die Sperre auf die Rechenzentrums-Zeile hält * nur bis zu deren Ende, und ohne sie holten sich zwei gleichzeitige * Anlegen-Vorgänge dieselbe Nummer. */ public static function claim(string $datacenterCode): string { $dc = Datacenter::query()->where('code', $datacenterCode)->lockForUpdate()->first(); if ($dc === null) { throw new RuntimeException("Kein Rechenzentrum mit dem Code {$datacenterCode}."); } [$number, $name] = self::free(self::label($datacenterCode), (int) $dc->next_host_number); $dc->update(['next_host_number' => $number + 1]); return $name; } /** * `fsn-03` → `fsn-03.node.clupilot.com`. * * Die PLATTFORM-Zone, nicht die Kundenzone: die zwei sind laut * OfficialDomains getrennt, und ein Host gehört auf die Seite der Plattform. */ public static function fqdn(string $name): string { return $name.'.node.'.config('provisioning.dns.platform_zone'); } /** * Rechenzentrums-Codes von vor der Verschärfung der Prüfregel können * Zeichen tragen, die eine DNS-Bezeichnung nicht darf. Ein Name, den der * Anbieter ablehnt, ließe den Host für immer namenlos. */ public static function label(string $datacenterCode): string { $label = trim(preg_replace('/[^a-z0-9-]+/', '-', strtolower($datacenterCode)) ?? '', '-'); return $label !== '' ? $label : 'node'; } /** * Ab dieser Nummer aufwärts der erste freie Name. * * Der Zähler allein reicht nicht: zwei Alt-Codes (`eu_west` und `eu-west`) * werden zur selben Bezeichnung und führen doch getrennte Zähler. Ohne * dieses Weiterzählen bekäme der zweite `eu-west-01` und liefe in den * eindeutigen Index — ein Abbruch beim Anlegen statt eines Namens. * * Nach unten geht es dabei nie: eine entfernte `fsn-02` wird nicht wieder * vergeben, weil der Zähler längst darüber steht. Dieses Weiterzählen ist * der Riegel, nicht der Zähler. * * `%02d` füllt zweistellig auf und wächst ab hundert von selbst weiter. * * @return array{0: int, 1: string} */ private static function free(string $label, int $from): array { $number = max(1, $from); while (Host::query()->where('name', sprintf('%s-%02d', $label, $number))->exists()) { $number++; } return [$number, sprintf('%s-%02d', $label, $number)]; } }