89 lines
3.0 KiB
PHP
89 lines
3.0 KiB
PHP
<?php
|
|
|
|
namespace App\Provisioning\Steps\Customer;
|
|
|
|
use App\Models\ProvisioningRun;
|
|
use App\Models\RunResource;
|
|
use App\Provisioning\StepResult;
|
|
use App\Services\Proxmox\ProxmoxClient;
|
|
|
|
class CloneVirtualMachine extends CustomerStep
|
|
{
|
|
public function __construct(private ProxmoxClient $pve) {}
|
|
|
|
public function key(): string
|
|
{
|
|
return 'clone_vm';
|
|
}
|
|
|
|
public function maxDuration(): int
|
|
{
|
|
return 900;
|
|
}
|
|
|
|
public function execute(ProvisioningRun $run): StepResult
|
|
{
|
|
$instance = $this->instance($run);
|
|
$node = (string) $run->context('node');
|
|
$plan = $this->plan($run);
|
|
$templateVmid = (int) ($plan['template_vmid'] ?? 0);
|
|
|
|
if ($templateVmid === 0) {
|
|
return StepResult::fail('template_missing');
|
|
}
|
|
|
|
$pve = $this->pve->forHost($instance->host);
|
|
$name = 'nc-'.$instance->subdomain;
|
|
|
|
// Reserve a deterministic vmid + breadcrumb BEFORE the clone call, so a
|
|
// crash never leads a retry to mint a NEW vmid (orphaning the first clone).
|
|
if (! $this->hasResource($run, 'vmid')) {
|
|
$vmid = $pve->nextVmid();
|
|
$instance->update(['vmid' => $vmid, 'status' => 'provisioning']);
|
|
$run->mergeContext(['vmid' => $vmid]);
|
|
$this->recordResource($run, $instance->host, 'vmid', (string) $vmid);
|
|
}
|
|
|
|
$vmid = (int) $run->context('vmid');
|
|
|
|
// Clone once, keyed to the reserved vmid; record the task ref immediately.
|
|
$upid = $run->context('clone_upid');
|
|
if ($upid === null) {
|
|
// Crash-recovery: the VM exists but the clone task ref was lost, so we
|
|
// can't confirm the clone succeeded. If it's still locked, keep waiting;
|
|
// once unlocked, destroy the possibly-incomplete VM and re-clone cleanly.
|
|
if ($pve->vmExists($node, $vmid)) {
|
|
if (! empty($pve->vmStatus($node, $vmid)['lock'])) {
|
|
return StepResult::poll(10, 'finishing recovered clone');
|
|
}
|
|
|
|
$pve->deleteVm($node, $vmid);
|
|
RunResource::query()->where('run_id', $run->id)->where('kind', 'vmid')->delete();
|
|
$run->forgetContext('vmid');
|
|
$run->forgetContext('clone_upid');
|
|
|
|
return StepResult::retry(5, 're-cloning after a lost clone task reference');
|
|
}
|
|
|
|
$upid = $pve->cloneVm($node, $templateVmid, $vmid, $name);
|
|
$run->mergeContext(['clone_upid' => $upid]);
|
|
}
|
|
|
|
return $this->pollClone($pve, $node, (string) $upid);
|
|
}
|
|
|
|
private function pollClone(ProxmoxClient $pve, string $node, string $upid): StepResult
|
|
{
|
|
$task = $pve->taskStatus($node, $upid);
|
|
|
|
if (($task['status'] ?? '') === 'running') {
|
|
return StepResult::poll(10, 'cloning template');
|
|
}
|
|
if (($task['exitstatus'] ?? 'OK') !== 'OK') {
|
|
return StepResult::fail('clone_failed: '.($task['exitstatus'] ?? 'unknown'));
|
|
}
|
|
|
|
return StepResult::advance();
|
|
}
|
|
}
|