124 lines
4.2 KiB
PHP
124 lines
4.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\VirtualMachine\Rebuild;
|
|
|
|
use App\DTOs\VirtualMachine\Rebuild\RebuildContext;
|
|
use App\Http\Services\vCenter as VCenterService;
|
|
use App\Models\ModuleVmware;
|
|
use App\Models\Service;
|
|
use App\Models\vCenter as VCenterModel;
|
|
use App\Models\VirtualMachines;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* VmRebuildSwapService
|
|
*
|
|
* After the new VM has passed every validation step, this service:
|
|
* 1. Performs a final health check against the new VM in vCenter.
|
|
* 2. Atomically swaps DB references from the old vmware_id to the new one.
|
|
* 3. Deletes the old VM from vCenter — and ONLY then.
|
|
*
|
|
* If health check fails, nothing is swapped and the old VM is preserved
|
|
* untouched so a manual recovery is possible.
|
|
*/
|
|
final class VmRebuildSwapService
|
|
{
|
|
public function __construct(private readonly VCenterService $vCenter)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Health-check the new VM. Throws on failure (no rollback needed — old VM
|
|
* still owns the DB).
|
|
*/
|
|
public function healthCheck(RebuildContext $ctx): void
|
|
{
|
|
if ($ctx->newVmId === null) {
|
|
throw new \RuntimeException('Rebuild swap: newVmId is null');
|
|
}
|
|
|
|
$vcenter = VCenterModel::find($ctx->vcenterId);
|
|
if (!$vcenter || !$this->vCenter->loginServer($vcenter)) {
|
|
throw new \RuntimeException(
|
|
"Rebuild swap: cannot login to vCenter #{$ctx->vcenterId}"
|
|
);
|
|
}
|
|
|
|
$detail = $this->vCenter->GetDetailVirtualMachine($ctx->newVmId);
|
|
if (($detail['error'] ?? true) === true) {
|
|
throw new \RuntimeException(
|
|
'Rebuild swap: GetDetailVirtualMachine failed — ' . json_encode($detail)
|
|
);
|
|
}
|
|
|
|
$power = $detail['data']['power_state'] ?? null;
|
|
if ($power !== 'POWERED_ON') {
|
|
throw new \RuntimeException(
|
|
"Rebuild swap: new VM not POWERED_ON (got '{$power}')"
|
|
);
|
|
}
|
|
|
|
$ipInfo = $this->vCenter->GetIPAddressVirtualMachine($ctx->newVmId);
|
|
$guestIp = (string) ($ipInfo['data']['ip_address'] ?? '');
|
|
if ($guestIp === '') {
|
|
throw new \RuntimeException('Rebuild swap: new VM reports no guest IP');
|
|
}
|
|
|
|
// Soft check — guest may take a moment to settle on the new IP, but
|
|
// it should at least be reporting *something* on the right subnet.
|
|
Log::info('VmRebuildSwapService: health check passed', [
|
|
'module_vmware_id' => $ctx->moduleVmwareId,
|
|
'new_vm_id' => $ctx->newVmId,
|
|
'guest_ip' => $guestIp,
|
|
'expected_ip' => $ctx->ip,
|
|
'power_state' => $power,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Atomically swap DB references:
|
|
* - module_vmware.vmware_id → new vSphere id
|
|
* - module_vmware.os_id → new OS id
|
|
* - virtual_machines.vm_id → new vSphere id (if a row exists)
|
|
*
|
|
* Preserved: services row (incl. vm_id FK), order_item_id, customer_id,
|
|
* ModuleIpAddress, ServiceAddons.
|
|
*/
|
|
public function swapDatabaseReferences(RebuildContext $ctx): void
|
|
{
|
|
if ($ctx->newVmId === null) {
|
|
throw new \RuntimeException('Rebuild swap: newVmId is null');
|
|
}
|
|
|
|
DB::transaction(function () use ($ctx) {
|
|
ModuleVmware::where('id', $ctx->moduleVmwareId)->update([
|
|
'vmware_id' => $ctx->newVmId,
|
|
'os_id' => $ctx->newOsId,
|
|
'status' => 1,
|
|
]);
|
|
|
|
VirtualMachines::where('vm_id', $ctx->oldVmId)->update([
|
|
'vm_id' => $ctx->newVmId,
|
|
'ip' => $ctx->ip,
|
|
]);
|
|
|
|
if ($ctx->serviceId) {
|
|
// Keep the existing Service intact — just make sure it stays
|
|
// marked active and its server_name reflects the VM.
|
|
Service::where('id', $ctx->serviceId)->update([
|
|
'status' => 'active',
|
|
]);
|
|
}
|
|
});
|
|
|
|
Log::info('VmRebuildSwapService: DB references swapped', [
|
|
'module_vmware_id' => $ctx->moduleVmwareId,
|
|
'old_vm_id' => $ctx->oldVmId,
|
|
'new_vm_id' => $ctx->newVmId,
|
|
]);
|
|
}
|
|
}
|