114 lines
4.0 KiB
PHP
114 lines
4.0 KiB
PHP
<?php
|
|
|
|
namespace App\Provisioning\Steps\Customer;
|
|
|
|
use App\Models\Instance;
|
|
use App\Models\ProvisioningRun;
|
|
use App\Provisioning\StepResult;
|
|
use App\Services\Monitoring\MonitoringClient;
|
|
use App\Services\Proxmox\ProxmoxClient;
|
|
use App\Support\ProvisioningSettings;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Backup and monitoring, after a plan change — checked, and almost always left
|
|
* alone.
|
|
*
|
|
* Neither of them depends on the package today. Every instance is backed up
|
|
* nightly at 02:00 whatever it costs, and the monitoring check is
|
|
* `https://{subdomain}/status.php`, which a plan change does not move. So the
|
|
* honest thing for this step to do in the ordinary case is nothing: deleting and
|
|
* re-creating a vzdump job to arrive at the same schedule would be remote work
|
|
* on a live host in exchange for a second job to keep in step with, and
|
|
* re-registering a monitor would leave two checks alerting on one instance.
|
|
*
|
|
* It is a step rather than an omission for two reasons. It is the named place a
|
|
* plan-dependent backup or monitoring rule goes the day there is one — the
|
|
* alternative being that somebody adds it to whichever step they happen to be
|
|
* reading. And there is one real gap it closes: an instance whose build
|
|
* registered neither, because monitoring was unreachable and the run went on
|
|
* DEGRADED, or because the build was interrupted after these steps. A plan
|
|
* change is a reasonable moment to notice that and put it right.
|
|
*/
|
|
class SettlePlanServices extends CustomerStep
|
|
{
|
|
public function __construct(
|
|
private ProxmoxClient $pve,
|
|
private MonitoringClient $monitoring,
|
|
) {}
|
|
|
|
public function key(): string
|
|
{
|
|
return 'settle_plan_services';
|
|
}
|
|
|
|
public function maxDuration(): int
|
|
{
|
|
return 120;
|
|
}
|
|
|
|
public function execute(ProvisioningRun $run): StepResult
|
|
{
|
|
$instance = $this->instance($run);
|
|
|
|
if ($instance === null) {
|
|
return StepResult::fail('no_instance');
|
|
}
|
|
|
|
// Every package, the same nightly window — see the class docblock for
|
|
// why this is read from one place rather than derived from the plan.
|
|
$schedule = '02:00';
|
|
|
|
if (! $instance->backups()->exists()) {
|
|
$jobId = $this->pve->forHost($instance->host)
|
|
->createBackupJob((string) $run->context('node'), (int) $instance->vmid, $schedule);
|
|
|
|
// Local row before anything else can fail, exactly as RegisterBackup
|
|
// does it, so a retry finds the job rather than creating a second.
|
|
$instance->backups()->firstOrCreate(
|
|
['external_job_id' => $jobId],
|
|
['schedule' => $schedule, 'status' => 'scheduled'],
|
|
);
|
|
}
|
|
|
|
$this->settleMonitoring($run, $instance);
|
|
|
|
return StepResult::advance();
|
|
}
|
|
|
|
/**
|
|
* Monitoring is observability, not the product.
|
|
*
|
|
* The same judgement RegisterMonitoring makes during a build applies here
|
|
* with more force: a customer's plan change must not fail because Kuma is
|
|
* down. The gap is recorded on the run and the change goes through.
|
|
*/
|
|
private function settleMonitoring(ProvisioningRun $run, Instance $instance): void
|
|
{
|
|
$url = 'https://'.$instance->subdomain.'.'.ProvisioningSettings::dnsZone().'/status.php';
|
|
|
|
if ($instance->monitoringTargets()->where('url', $url)->exists()) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$targetId = $this->monitoring->registerTarget($instance->subdomain, $url);
|
|
} catch (Throwable $e) {
|
|
$run->events()->create([
|
|
'step' => $this->key(),
|
|
'attempt' => $run->attempt,
|
|
'outcome' => 'info',
|
|
'message' => 'Monitoring übersprungen (Dienst nicht erreichbar): '.$e->getMessage(),
|
|
]);
|
|
|
|
return;
|
|
}
|
|
|
|
$instance->monitoringTargets()->firstOrCreate(
|
|
['external_id' => $targetId],
|
|
// 'unknown', not 'up': registering a check is not passing one.
|
|
['url' => $url, 'status' => 'unknown'],
|
|
);
|
|
}
|
|
}
|