Files
portal-api/app/Jobs/VirtualMachine/Create/CreateVirtualMachineJob.php
T
baodev0 5d3ed57b32 feat: Enhance vCenter integration with resource pool and folder support
- Added methods to ResourceService for fetching resource pools and updated existing methods to include datacenter filtering.
- Modified CreateVirtualMachine to accept folder and resource pool parameters from product data.
- Updated various jobs to synchronize resource pools and include them in the provisioning process.
- Introduced new ModuleResourcePool model and migration for resource pool management.
- Enhanced existing models (ModuleDatastore, ModuleNetwork, Os) to include relationships with vCenter and resource pools.
- Added netmask field to IpPool and updated related logic for IP reservation.
- Improved error handling and logging in VM creation and IP reservation processes.
- Updated API routes to include sync functionality for vCenter.
2026-07-21 21:37:28 +07:00

240 lines
8.8 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;
$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,
'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,
]);
$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;
}
}
}