Files
portal-api/app/Jobs/VirtualMachine/Create/CreateVirtualMachineJob.php
baodev0 3ed33ea7e4 Refactor VM creation and configuration process
- Updated VpsServiceResource to return product UUID instead of ID.
- Modified VirtualMachineService to utilize product data for CPU, RAM, and disk settings.
- Replaced AssignIpToGuestJob with UpdateVmConfigJob in CreateVirtualMachineJob to streamline VM configuration after cloning.
- Adjusted AssignIpToGuestJob to remove unnecessary UpdateVmConfigJob dispatch and focus on IP assignment.
- Enhanced UpdateVmConfigJob to handle CPU/RAM configuration and power cycling more effectively.
- Introduced ChangeIpVMJob for handling customer-initiated IP changes asynchronously.
- Updated InvoiceRepository to set invoice due date based on maximum months instead of a fixed 7 days.
- Improved web design quote PDF layout for better readability.
- Added Times-Roman font metrics for PDF generation.
2026-07-24 09:29:43 +07:00

227 lines
8.5 KiB
PHP
Raw Permalink 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\UpdateVmConfigJob;
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\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,
VmModuleService $moduleService,
VmServiceSyncService $serviceSync,
VmNotificationService $notification,
VmVcenterErrorTranslator $errorTranslator,
VmIpReservationService $ipReservation,
): bool {
$reservation = null;
$ipPoolId = null;
$lock = null;
$vmLogger->for('CreateVirtualMachineJob');
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,
'datastore_id' => $infra->datastore?->id,
'vcenter_id' => $infra->vcenter->id,
'folder' => $infra->folderId,
'resource_pool' => $infra->resourcePoolId,
]);
$result = $vCenter->CreateVirtualMachine([
'item' => $this->item,
'product' => $product,
'host' => $infra->host,
'datacenter' => $infra->datacenter,
'vcenter' => $infra->vcenter,
'template' => $infra->template,
'addon' => $addonData,
'datastore' => $infra->datastore,
'folder' => $infra->folderId,
'resource_pool' => $infra->resourcePoolId,
]);
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,
]);
$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,
]);
// VM is left powered off after clone. UpdateVmConfigJob owns power-on:
// if the order has a CPU/RAM addon it powers off (no-op, already off),
// resizes, then powers on; otherwise it just powers on. Either way it
// dispatches AssignIpToGuestJob next, which needs the VM running.
UpdateVmConfigJob::dispatch(
$moduleVmware->id,
(string) $vmId,
$infra->isWindows ? VmProvisionService::OS_WINDOWS : VmProvisionService::OS_LINUX,
reservationId: $reservation->id,
reservedIpPoolId: $ipPoolId,
)->onQueue('default');
$vmLogger->success('UpdateVmConfigJob 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;
}
}
}