115 lines
4.1 KiB
PHP
115 lines
4.1 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
use App\Models\Datacenter;
|
|
use App\Models\Host;
|
|
use RuntimeException;
|
|
|
|
/**
|
|
* Der Name eines Hosts — an dieser einen Stelle gebildet.
|
|
*
|
|
* Vorher gab es zwei: den getippten in `hosts.name`, den die Konsole zeigte,
|
|
* und den aus Rechenzentrum und Nummer gebauten in `hosts.dns_name`, den das
|
|
* DNS führte. Nur der zweite löste auf, und er stand in keiner einzigen
|
|
* Ansicht. Der Betreiber rief den Namen auf, den er selbst vergeben hatte, und
|
|
* stand vor einer Seite, die nicht lädt.
|
|
*
|
|
* Jetzt vergibt CluPilot ihn: `<rz>-<nn>`, 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)];
|
|
}
|
|
}
|