Files
portal-api/app/Jobs/VirtualMachine/Create/CreateVirtualMachineJob.php
T

232 lines
8.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Jobs\VirtualMachine\Create;
use App\Http\Services\vCenter;
use App\Jobs\VirtualMachine\Provision\AssignIpToGuestJob;
use App\Models\OrderItem;
use App\Models\VmProvisionLock;
use App\Services\VirtualMachine\VmAddonResolver;
use App\Services\VirtualMachine\VmInfrastructureResolver;
use App\Services\VirtualMachine\VmIpReservationService;
use App\Services\VirtualMachine\VmModuleService;
use App\Services\VirtualMachine\VmNotificationService;
use App\Services\VirtualMachine\VmPowerService;
use App\Services\VirtualMachine\VmProvisionLogger;
use App\Services\VirtualMachine\VmServiceSyncService;
use App\Services\VirtualMachine\VmVcenterErrorTranslator;
use App\Services\VmProvisionService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use RuntimeException;
use Throwable;
class CreateVirtualMachineJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** VM clone can take 5–8 minutes */
public int $timeout = 600;
/** No auto-retry — VM errors need manual review */
public int $tries = 1;
public function __construct(
public OrderItem $item,
public bool $isRetry = false,
) {
}
public function handle(
vCenter $vCenter,
VmProvisionLogger $vmLogger,
VmInfrastructureResolver $infraResolver,
VmAddonResolver $addonResolver,
VmPowerService $powerService,
VmModuleService $moduleService,
VmServiceSyncService $serviceSync,
VmNotificationService $notification,
VmVcenterErrorTranslator $errorTranslator,
VmIpReservationService $ipReservation,
): bool {
$reservation = null;
$ipPoolId = null;
$lock = null;
try {
$customerId = $this->item->order->customer_id;
$lock = VmProvisionLock::updateOrCreate(
['order_item_id' => $this->item->id],
['customer_id' => $customerId, 'status' => 'running'],
);
$vmLogger->step('Create VM job started', [
'order_item_id' => $this->item->id,
'is_retry' => $this->isRetry,
]);
$product = $this->item->getProductAttribute();
if (!$product) {
throw new RuntimeException("Không tìm thấy sản phẩm từ OrderItem #{$this->item->id}");
}
$infra = $infraResolver->resolve($this->item, $product);
$addonData = $addonResolver->resolve($this->item);
// Reserve IP BEFORE clone so VM name on vCenter, ModuleVmware.name
// and services.server_name all carry the format {ip}_{server_name}.
// Network comes from the active chain (active host → active network);
// product->ipAddress is no longer consulted for network selection.
if (!$infra->network) {
throw new RuntimeException("Active host id={$infra->host->id} chưa có network active.");
}
$networkId = $infra->network->id;
$dns = $product->ipAddress->dns ?? '8.8.8.8';
$picked = $ipReservation->reserve($networkId, $infra->host->id, $dns);
if (!$picked) {
throw new RuntimeException(
"Không còn IP free trong pool (network_id={$networkId}) để tạo VM."
);
}
$reservation = $picked['reservation'];
$ipPoolId = $picked['ip_pool_id'];
$reservedIp = $reservation->ip;
$originalName = $this->item->server_name ?? ('VM-' . $this->item->id);
$combinedName = "{$reservedIp}_{$originalName}";
$this->item->server_name = $combinedName;
$vmLogger->step('IP reserved for VM', [
'order_item_id' => $this->item->id,
'ip' => $reservedIp,
'ip_pool_id' => $ipPoolId,
'server_name' => $combinedName,
]);
if (!$vCenter->loginServer($infra->vcenter)) {
throw new RuntimeException("Login vào vCenter thất bại với host: {$infra->vcenter->host}");
}
$vmLogger->step('VM clone started', [
'order_item_id' => $this->item->id,
'product_id' => $product->id,
'template' => $infra->template,
'host_id' => $infra->host->id,
'vcenter_id' => $infra->vcenter->id,
]);
$result = $vCenter->CreateVirtualMachine([
'item' => $this->item,
'host' => $infra->host,
'datacenter' => $infra->datacenter,
'vcenter' => $infra->vcenter,
'template' => $infra->template,
'addon' => $addonData,
]);
if (is_array($result) && ($result['error'] ?? false)) {
throw new RuntimeException(
$errorTranslator->translate($result, $this->item->server_name)
);
}
if ($result === 'Login_Again') {
throw new RuntimeException('Tạo máy ảo thất bại: cần đăng nhập lại vCenter.');
}
$vmId = is_array($result) ? ($result['data'] ?? null) : $result;
if (!$vmId || in_array($vmId, ['unknown', 'UNKNOWN'], true)) {
throw new RuntimeException('Không lấy được vm_id sau khi tạo máy ảo.');
}
$vmLogger->success('VM clone success', [
'order_item_id' => $this->item->id,
'vm_id' => $vmId,
]);
$vmLogger->step('VM start started', ['vm_id' => $vmId]);
$startResult = $powerService->start($vCenter, $vmId);
if (is_array($startResult) && ($startResult['error'] ?? false)) {
throw new RuntimeException(
'Start VM thất bại: ' . json_encode($startResult['data'] ?? null)
);
}
if ($startResult === 'Login_Again') {
throw new RuntimeException('Start VM thất bại: cần đăng nhập lại vCenter.');
}
$vmLogger->success('VM start success', ['vm_id' => $vmId]);
$moduleVmware = $moduleService->create($this->item, $infra, $vmId);
$vmLogger->success('ModuleVmware created', [
'module_vmware_id' => $moduleVmware->id,
'vm_id' => $vmId,
]);
$serviceSync->sync($moduleVmware, $this->item);
$vmLogger->success('Service synced', [
'module_vmware_id' => $moduleVmware->id,
'order_item_id' => $this->item->id,
]);
AssignIpToGuestJob::dispatch(
$moduleVmware->id,
(string) $vmId,
$infra->isWindows ? VmProvisionService::OS_WINDOWS : VmProvisionService::OS_LINUX,
VmProvisionService::TEMPLATE_PASSWORD,
$reservation->id,
$ipPoolId,
)->onQueue('default');
$vmLogger->success('AssignIpToGuestJob dispatched', [
'module_vmware_id' => $moduleVmware->id,
'vm_id' => $vmId,
'os_family' => $infra->isWindows ? 'WINDOWS' : 'LINUX',
]);
$notification->success($this->item, $moduleVmware, (string) $vmId);
$vmLogger->success('Create VM job success', [
'order_item_id' => $this->item->id,
'module_vmware_id' => $moduleVmware->id,
'vm_id' => $vmId,
]);
$lock?->update(['status' => 'success']);
return true;
} catch (Throwable $e) {
if ($reservation) {
$ipReservation->release($reservation, $ipPoolId);
}
$lock?->update(['status' => 'failed']);
$vmLogger->exception('Create VM job failed', $e, [
'order_item_id' => $this->item->id,
'is_retry' => $this->isRetry,
]);
if (!$this->isRetry) {
$notification->failed($this->item, $e->getMessage());
}
return false;
}
}
}