for('CreateVirtualMachineJob'); try { $customerId = $this->item->order->customer_id; $lock = VmProvisionLock::updateOrCreate( ['order_item_id' => $this->item->id], ['customer_id' => $customerId, 'status' => 'running'], ); ServiceProvisionLog::forOrderItem($this->item->id, 'provision_started', $this->isRetry ? 'Bắt đầu khởi tạo lại máy chủ' : 'Bắt đầu khởi tạo máy chủ', ['is_retry' => $this->isRetry]); $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; ServiceProvisionLog::forOrderItem($this->item->id, 'provision_step', 'Đã giữ IP và bắt đầu clone VM', ['ip' => $reservedIp]); $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 VmProvisionException( $errorTranslator->translate($result, $this->item->server_name), $errorTranslator->detail($result), ); } 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, [ 'cpu' => (int) $product->cpu + $addonData['cpu'], 'ram_mb' => ((int) $product->ram + $addonData['ram']) * 1024, 'disk_gb' => (int) $product->disk + $addonData['disk'], ]); $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']); Service::where('order_item_id', $this->item->id) ->whereIn('status', ['pending', 'provisioning']) ->update(['status' => 'failed']); $adminDetail = $e instanceof VmProvisionException ? $e->adminDetail : $e->getMessage(); ServiceProvisionLog::forOrderItem($this->item->id, 'provision_failed', 'Khởi tạo máy chủ thất bại — ' . $adminDetail, ['stage' => 'create_vm']); $vmLogger->exception('Create VM job failed', $e, [ 'order_item_id' => $this->item->id, 'is_retry' => $this->isRetry, 'admin_detail' => $adminDetail, ]); $failedService = Service::where('order_item_id', $this->item->id)->first(); if ($failedService) { app(ActivityLogger::class)->log( 'provision_failed', 'service', 'Khởi tạo máy ảo thất bại — ' . $adminDetail, $failedService, ); } if (!$this->isRetry) { $refunded = $refund->refund($this->item, $e->getMessage()); $notification->failed($this->item, $e->getMessage() . ($refunded ? ' (đã tự động hoàn tiền cho khách)' : '')); } return false; } } }