69 lines
2.6 KiB
PHP
69 lines
2.6 KiB
PHP
<?php
|
|
|
|
namespace App\Provisioning\Jobs;
|
|
|
|
use App\Models\Host;
|
|
use App\Services\Proxmox\ProxmoxClient;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Asks every host whether it is still there.
|
|
*
|
|
* `hosts.last_seen_at` existed and drove the health dot in the console, and
|
|
* nothing ever wrote it after onboarding — RegisterCapacity stamped it once and
|
|
* that was the only write in the codebase. So every host read "offline" thirty
|
|
* minutes after it was added, permanently, and the dot meant nothing. That is
|
|
* also why host reachability could not appear on the status page: it was never
|
|
* being measured.
|
|
*
|
|
* nodeStatus() is the cheapest question the API answers, and it is the right
|
|
* one: it goes through the same credentials, the same network path and the same
|
|
* token that provisioning depends on. A host that answers this can be worked
|
|
* with; a host that cannot is down as far as this platform is concerned,
|
|
* whatever a separate ICMP probe would say.
|
|
*
|
|
* Runs on the provisioning queue, which is where the Proxmox credentials are
|
|
* usable.
|
|
*/
|
|
class PingHosts implements ShouldQueue
|
|
{
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->onQueue('provisioning');
|
|
}
|
|
|
|
public function handle(ProxmoxClient $proxmox): void
|
|
{
|
|
$hosts = Host::query()
|
|
// Hosts still being onboarded, or already retired, are not a
|
|
// statement about the platform's health.
|
|
->whereIn('status', ['active', 'error'])
|
|
->get();
|
|
|
|
foreach ($hosts as $host) {
|
|
try {
|
|
$proxmox->forHost($host)->nodeStatus($host->node);
|
|
|
|
// Only the timestamp. Whether a host is "active" or "error" is
|
|
// an operator's decision and a provisioning outcome — a
|
|
// heartbeat must not quietly promote a host somebody has
|
|
// deliberately taken out of service.
|
|
$host->forceFill(['last_seen_at' => now()])->save();
|
|
} catch (Throwable) {
|
|
// Deliberately silent, and deliberately not writing anything.
|
|
// Absence IS the signal: healthState() reads the age of the
|
|
// last successful answer, so a failure is recorded by the
|
|
// timestamp not moving. Writing a failure count here would be a
|
|
// second version of the same fact.
|
|
}
|
|
}
|
|
}
|
|
}
|