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); });