CluPilotCloud/tests/Feature/Provisioning/CollectHostLoadTest.php

87 lines
3.8 KiB
PHP

<?php
use App\Models\Host;
use App\Provisioning\Jobs\CollectHostLoad;
use App\Services\Proxmox\FakeProxmoxClient;
use App\Services\Proxmox\HostLoadSeries;
use App\Services\Proxmox\ProxmoxClient;
use Illuminate\Contracts\Queue\ShouldBeUnique;
/**
* Der Sammler holt die Stundenkurve dort, wo der Tunnel ist.
*
* Er existiert genau deshalb: nur der `provisioning`-Container hängt im
* WireGuard-Netz. Der `app`-Container, der die Konsole rendert, erreicht die
* Management-Adresse eines Hosts gar nicht — auf echter Hardware blieben
* deshalb alle Kacheln leer, während der Host sichtbar online war.
*/
it('runs on the queue that can actually reach a host', function () {
// Kein Detail: dieselbe Warteschlange wie PingHosts, aus demselben Grund.
expect((new CollectHostLoad)->queue)->toBe('provisioning');
});
it('leaves a series behind for every host worth asking', function () {
$s = fakeServices();
$s['pve']->rrd = [['time' => 1754035200, 'cpu' => 0.5, 'memused' => 1, 'memtotal' => 2]];
$host = Host::factory()->active()->create(['api_token_ref' => 'ref']);
(new CollectHostLoad)->handle(app(HostLoadSeries::class));
expect(app(HostLoadSeries::class)->forHost($host)['available'])->toBeTrue();
});
it('skips a host that has no token to ask with', function () {
// Ein Host mitten in der Übernahme hat noch keinen — ihn zu fragen wäre ein
// sicherer Fehlschlag je Minute, und eine Zeile im Protokoll dazu.
$s = fakeServices();
$s['pve']->rrd = [['time' => 1754035200, 'cpu' => 0.5, 'memused' => 1, 'memtotal' => 2]];
$host = Host::factory()->create(['status' => 'onboarding', 'api_token_ref' => null]);
(new CollectHostLoad)->handle(app(HostLoadSeries::class));
expect(app(HostLoadSeries::class)->forHost($host)['available'])->toBeFalse();
});
it('keeps collecting after one host refuses to answer', function () {
// Eine Flotte darf nicht daran hängen, dass der erste Host schweigt.
$s = fakeServices();
$s['pve']->rrd = [['time' => 1754035200, 'cpu' => 0.5, 'memused' => 1, 'memtotal' => 2]];
$silent = Host::factory()->active()->create(['api_token_ref' => 'ref-1', 'name' => 'aaa-still']);
$healthy = Host::factory()->active()->create(['api_token_ref' => 'ref-2', 'name' => 'zzz-antwortet']);
// Der Fake wirft für den ersten Host und antwortet für den zweiten.
app()->instance(ProxmoxClient::class, new class($silent->id) extends FakeProxmoxClient
{
public function __construct(private int $silentId)
{
$this->rrd = [['time' => 1754035200, 'cpu' => 0.5, 'memused' => 1, 'memtotal' => 2]];
}
public function nodeRrdData(string $node, string $timeframe = 'hour'): array
{
if ($this->host?->id === $this->silentId) {
throw new RuntimeException('proxmox unreachable');
}
return $this->rrd;
}
});
(new CollectHostLoad)->handle(app(HostLoadSeries::class));
expect(app(HostLoadSeries::class)->forHost($silent)['available'])->toBeFalse()
->and(app(HostLoadSeries::class)->forHost($healthy)['available'])->toBeTrue();
});
it('never lets a second collector queue up behind a slow one', function () {
// Die provisioning-Warteschlange ist DIESELBE, auf der Kunden-Bereitstellung
// läuft. Ein Host, der in die Zeitüberschreitung läuft, kostet 15 Sekunden;
// bei genug Hosts ist der minütliche Lauf noch nicht fertig, wenn der
// nächste kommt — und der Rückstau verzögert dann bezahlte Arbeit.
// Dasselbe Mittel wie bei CollectInstanceTraffic nebenan.
expect(new CollectHostLoad)->toBeInstanceOf(ShouldBeUnique::class)
// Und nur ein Versuch: eine verpasste Minute wird von der nächsten
// Minute geholt, nicht von einer Wiederholung derselben.
->and((new CollectHostLoad)->tries)->toBe(1);
});