118 lines
4.3 KiB
PHP
118 lines
4.3 KiB
PHP
<?php
|
|
|
|
namespace App\Provisioning\Steps\Customer;
|
|
|
|
use App\Models\ProvisioningRun;
|
|
use App\Provisioning\StepResult;
|
|
use App\Services\Proxmox\ProxmoxClient;
|
|
|
|
/**
|
|
* The machine is back — check that it came back as the machine that was sold,
|
|
* and only then stop saying a restart is due.
|
|
*
|
|
* `restart_required_since` is the one thing a customer and an operator both
|
|
* read to answer "am I paying for cores I do not have". Clearing it because
|
|
* somebody pressed a button, or because a run reached its last step, would turn
|
|
* it from a fact into a rumour — and the rumour points the wrong way, because it
|
|
* says the problem is gone.
|
|
*
|
|
* So the proof is taken from the machine. Proxmox's status/current reports what
|
|
* the RUNNING guest has (`cpus`, `maxmem`), not what its definition says, and
|
|
* those two disagree for exactly as long as a pending restart lasts. If the
|
|
* guest is back with at least what the instance row promises, the flag goes; if
|
|
* it is back smaller, something did not take and the run fails loudly rather
|
|
* than quietly declaring success.
|
|
*
|
|
* Where an older Proxmox reports neither figure, the fallback is the pipeline
|
|
* itself: this step only runs after ShutDownVirtualMachine saw the guest
|
|
* genuinely stopped and StartVirtualMachine started it again, and a stopped
|
|
* machine reads its definition when it starts. That is the same reasoning
|
|
* ResizeVirtualMachine already uses to decide there is nothing pending on a
|
|
* stopped guest.
|
|
*/
|
|
class CompleteRestart extends CustomerStep
|
|
{
|
|
/** How long the guest may take to show up as running before this gives up. */
|
|
private const RUNNING_DEADLINE = 120;
|
|
|
|
public function __construct(private ProxmoxClient $pve) {}
|
|
|
|
public function key(): string
|
|
{
|
|
return 'complete_restart';
|
|
}
|
|
|
|
public function maxDuration(): int
|
|
{
|
|
return 180;
|
|
}
|
|
|
|
public function execute(ProvisioningRun $run): StepResult
|
|
{
|
|
$instance = $this->instance($run);
|
|
|
|
if ($instance === null) {
|
|
return StepResult::fail('no_instance');
|
|
}
|
|
|
|
$node = (string) $run->context('node');
|
|
$vmid = (int) $run->context('vmid');
|
|
$status = $this->pve->forHost($instance->host)->vmStatus($node, $vmid);
|
|
|
|
if (($status['status'] ?? '') !== 'running') {
|
|
if ($run->started_at !== null && $run->started_at->copy()->addSeconds(self::RUNNING_DEADLINE)->isPast()) {
|
|
return StepResult::fail('restart_did_not_come_back');
|
|
}
|
|
|
|
return StepResult::poll(5, 'waiting for the machine to come back');
|
|
}
|
|
|
|
if (($short = $this->shortfall($status, $instance->cores, $instance->ram_mb)) !== null) {
|
|
return StepResult::fail('restart_applied_nothing: '.$short);
|
|
}
|
|
|
|
// Only here. The machine is running, its guest agent has answered (the
|
|
// step before this one), and it is running on what was sold.
|
|
if ($instance->restartIsPending()) {
|
|
$instance->update(['restart_required_since' => null]);
|
|
}
|
|
|
|
$run->events()->create([
|
|
'step' => $this->key(),
|
|
'attempt' => $run->attempt,
|
|
'outcome' => 'info',
|
|
'message' => "Die Maschine läuft wieder mit {$instance->cores} vCPU und {$instance->ram_mb} MB RAM. "
|
|
.'Der offene Neustart ist damit erledigt.',
|
|
]);
|
|
|
|
return StepResult::advance();
|
|
}
|
|
|
|
/**
|
|
* What the running guest is short of, or null when it is not short of
|
|
* anything we can see.
|
|
*
|
|
* Compared with `>=` rather than `===`: Proxmox rounds memory and a host may
|
|
* legitimately give a guest more than was asked for, and refusing a machine
|
|
* for being too big would be a strange way to protect a customer.
|
|
*
|
|
* @param array<string, mixed> $status
|
|
*/
|
|
private function shortfall(array $status, ?int $cores, ?int $ramMb): ?string
|
|
{
|
|
if (isset($status['cpus']) && $cores > 0 && (int) $status['cpus'] < $cores) {
|
|
return "{$status['cpus']} statt {$cores} vCPU";
|
|
}
|
|
|
|
if (isset($status['maxmem']) && $ramMb > 0) {
|
|
$actualMb = (int) ((int) $status['maxmem'] / 1048576);
|
|
|
|
if ($actualMb < $ramMb) {
|
|
return "{$actualMb} statt {$ramMb} MB RAM";
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|