client = $client; $this->powerShellService = $powerShellService; } public function getAllVirtualMachine($datacenter = '', $host = '') { if ($this->client->checkApiExits()) { $params = []; if (!empty($datacenter)) { $params['filter.datacenters'] = $datacenter; } if (!empty($host)) { $params['filter.hosts'] = $host; } $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->client->token ]) ->get("https://{$this->client->apiUri}/rest/vcenter/vm", $params)->json(); if (!isset($response['type'])) { return $response['value']; } return false; } return "Login_Again"; } public function CreateVirtualMachine($product_data): array|string { $vcenterModel = $product_data['vcenter']; Log::info('Vcenter Info', ['data' => $vcenterModel]); if (!$this->client->loginServer($vcenterModel)) { Log::error('Failed to create VM: API check failed, requires login.'); return 'Login_Again'; } $userName = customer()?->email; $item = $product_data['item']; $host = $product_data['host']; $datacenter = $product_data['datacenter']; $template = $product_data['template']; $addon = $product_data['addon'] ?? ['cpu' => 0, 'ram' => 0, 'disk' => 0]; $base_cpu = $item->cpu ?? ($host->cpu ?? 1); $base_ram = $item->ram ?? ($host->ram ?? 1); $base_disk = $item->disk ?? ($host->disk ?? 20); $base_cpu = max(1, (int) $base_cpu); $base_ram = max(1, (int) $base_ram); $base_disk = max(20, (int) $base_disk); Log::info("Cấu hình cơ bản", [ 'base_cpu' => $base_cpu, 'base_ram' => $base_ram, 'base_disk' => $base_disk, 'addon' => $addon ]); /** @var VmProviderSettingsService $providerSettings */ $providerSettings = app(VmProviderSettingsService::class); $datastore = $product_data['datastore'] ?? null; if (!$datastore) { throw new Exception("Host id={$host->id} chưa có datastore active để tạo VM."); } $datastoreId = $datastore->datastore; $body = [ 'spec' => [ 'name' => $item->server_name ?? ($userName . '-vm-' . $item->id), 'source' => $template, 'placement' => [ 'datacenter' => $datacenter->datacenter_id, 'folder' => $product_data['folder'] ?? $providerSettings->defaultFolder(), 'host' => $host->host_id, 'resource_pool' => $product_data['resource_pool'] ?? $providerSettings->defaultResourcePool(), 'datastore' => $datastoreId, ], 'power_on' => false, 'fallback_to_datastore_default_policy' => true, ], ]; Log::info('Data payload', ['data' => $body]); try { Log::info('Bắt đầu clone VM với timeout 300 giây', ['template' => $template, 'target_name' => $body['spec']['name']]); $response = Http::withoutVerifying() ->timeout(300) ->connectTimeout(30) ->withHeaders([ 'vmware-api-session-id' => $this->client->token, 'Content-Type' => 'application/json', ]) ->post("https://{$this->client->apiUri}/rest/vcenter/vm?action=clone", $body); $responseData = $response->json(); if ($response->successful() && !isset($responseData['type'])) { $vmId = $responseData['value'] ?? 'UNKNOWN'; Log::info("VM clone thành công, VM ID: {$vmId}", [ 'addon_data' => $addon, 'base_cpu' => $base_cpu, 'base_ram' => $base_ram, 'has_cpu_addon' => $addon['cpu'] > 0, 'has_ram_addon' => $addon['ram'] > 0, 'has_disk_addon' => $addon['disk'] > 0 ]); if ($vmId !== 'UNKNOWN' && ($addon['cpu'] > 0 || $addon['ram'] > 0 || $addon['disk'] > 0)) { Log::info("Bắt đầu cập nhật addon cho VM {$vmId}"); $this->updateVMHardware($vmId, $addon, $base_cpu, $base_ram); } else { Log::info("Không có addon nào cần cập nhật cho VM {$vmId}"); } $discord = app(Discord::class); $addonMsg = ''; if (!empty($addon)) { $addonMsg = "\n**Addon:** " . ($addon['cpu'] ? "CPU +{$addon['cpu']} " : "") . ($addon['ram'] ? "RAM +{$addon['ram']}GB " : "") . ($addon['disk'] ? "Disk +{$addon['disk']}GB " : ""); } $discord->SendMessage( message: "**User:** {$item->server_name}\n**VM ID:** `{$vmId}`{$addonMsg}\n**Thời gian:** " . now()->format('d/m/Y H:i:s'), color: Discord::COLOR_SUCCESS, title: '🎉 Tạo máy ảo thành công' ); return ['error' => false, 'data' => $vmId]; } Log::error('VM clone failed on vCenter', [ 'status' => $response->status(), 'request' => $body, 'response' => $responseData ?? $response->body(), ]); return ['error' => true, 'data' => $responseData['value'] ?? $response->body()]; } catch (Exception $e) { Log::error('Exception when creating VM', ['message' => $e->getMessage()]); return ['error' => true, 'data' => $e->getMessage()]; } } private function updateVMHardware($vmId, $addon, $base_cpu, $base_ram): void { try { Log::info("=== BẮT ĐẦU CẬP NHẬT ADDON ==="); $new_cpu = $base_cpu + $addon['cpu']; $new_ram_mib = ($base_ram + $addon['ram']) * 1024; if ($addon['cpu'] > 0) { $this->updateVMCPU($vmId, $new_cpu); } if ($addon['ram'] > 0) { $this->updateVMMemory($vmId, $new_ram_mib); } // Disk update logic was missing in the snippet but present in the original method names? // Actually the original `updateVMHardware` in the snippet (step 134) calls `updateVMCPU` and `updateVMMemory`. // It gets `base_disk` but doesn't seem to call `updateVMDisk` inside `updateVMHardware`? // Let me re-read step 134. // ... `updateVMHardware` implementation ... // It calls `updateVMCPU` and `updateVMMemory`. // It calculates `$addon['disk']` in log, but doesn't call `updateVMDisk`. // Ah, line 943 defines `updateVMDisk`. // But `updateVMHardware` (lines 772-832) only calls `updateVMCPU` and `updateVMMemory`. // I will implement it exactly as the original. Log::info("=== HOÀN THÀNH CẬP NHẬT ADDON ==="); } catch (Exception $e) { Log::error("❌ Lỗi khi cập nhật cấu hình phần cứng VM {$vmId}: " . $e->getMessage()); } } private function updateVMCPU($vmId, $cpuCount): bool { try { $coresPerSocket = 1; if ($cpuCount > 1) { $maxCoresPerSocket = floor($cpuCount / 2); for ($i = $maxCoresPerSocket; $i >= 1; $i--) { if ($cpuCount % $i === 0) { $coresPerSocket = $i; break; } } if ($coresPerSocket === 1 && $cpuCount > 2) { $coresPerSocket = $cpuCount; } } $payload = [ 'spec' => [ 'count' => $cpuCount, 'cores_per_socket' => $coresPerSocket, 'hot_add_enabled' => true ] ]; $response = Http::withoutVerifying() ->timeout(30) ->withHeaders([ 'vmware-api-session-id' => $this->client->token, 'Content-Type' => 'application/json', ]) ->patch("https://{$this->client->apiUri}/rest/vcenter/vm/{$vmId}/hardware/cpu", $payload); if ($response->successful()) { Log::info("✅ API call cập nhật CPU thành công cho VM {$vmId}"); return true; } else { Log::error("❌ API call cập nhật CPU thất bại cho VM {$vmId}", [ 'status' => $response->status(), 'response' => $response->body() ]); return false; } } catch (Exception $e) { Log::error("❌ Exception khi cập nhật CPU cho VM {$vmId}: " . $e->getMessage()); return false; } } private function updateVMMemory($vmId, $ramSizeMB): bool { try { $payload = [ 'spec' => [ 'size_MiB' => $ramSizeMB ] ]; $response = Http::withoutVerifying() ->timeout(30) ->withHeaders([ 'vmware-api-session-id' => $this->client->token, 'Content-Type' => 'application/json', ]) ->patch("https://{$this->client->apiUri}/rest/vcenter/vm/{$vmId}/hardware/memory", $payload); if ($response->successful()) { Log::info("✅ API call cập nhật RAM thành công cho VM {$vmId}"); return true; } else { Log::error("❌ API call cập nhật RAM thất bại cho VM {$vmId}", [ 'status' => $response->status(), 'response' => $response->body() ]); return false; } } catch (Exception $e) { Log::error("❌ Exception khi cập nhật RAM cho VM {$vmId}: " . $e->getMessage()); return false; } } private function updateVMDisk($vmId, $diskSizeGB): bool { try { $diskSizeBytes = $diskSizeGB * 1024 * 1024 * 1024; $payload = [ 'spec' => [ 'capacity' => $diskSizeBytes ] ]; $diskResponse = Http::withoutVerifying() ->timeout(30) ->withHeaders([ 'vmware-api-session-id' => $this->client->token, ]) ->get("https://{$this->client->apiUri}/rest/vcenter/vm/{$vmId}/hardware/disk"); if (!$diskResponse->successful()) { return false; } $diskData = $diskResponse->json(); if (!isset($diskData['value']) || empty($diskData['value'])) { return false; } $firstDisk = $diskData['value'][0]; $diskKey = $firstDisk['key']; $response = Http::withoutVerifying() ->timeout(30) ->withHeaders([ 'vmware-api-session-id' => $this->client->token, 'Content-Type' => 'application/json', ]) ->patch("https://{$this->client->apiUri}/rest/vcenter/vm/{$vmId}/hardware/disk/{$diskKey}", $payload); if ($response->successful()) { return true; } else { return false; } } catch (Exception $e) { Log::error("Exception khi cập nhật Disk cho VM {$vmId}: " . $e->getMessage()); return false; } } public function CheckVirtualMachine($vmware_id = '') { if ($this->client->checkApiExits()) { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->client->token ]) ->get("https://{$this->client->apiUri}/rest/vcenter/vm/$vmware_id")->json(); if (!isset($response['type'])) { return $response['value']; } return false; } return "Login_Again"; } public function GetNetworkVirtualMachine($vmware_id = '') { $check = $this->client->checkApiExits(); if ($check) { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->client->token ]) ->get("https://{$this->client->apiUri}/rest/vcenter/vm/$vmware_id/guest/networking")->json(); if (!isset($response['type'])) { return $response['value']; } return false; } return "Login_Again"; } /** * Search VM by exact name in vCenter. * Returns the first matching VM array or null if not found. */ public function findVirtualMachineByName(string $vmName): ?array { $check = $this->client->checkApiExits(); if (!$check) { Log::warning('findVirtualMachineByName: API check failed'); return null; } $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->client->token ]) ->get("https://{$this->client->apiUri}/rest/vcenter/vm", [ 'filter.names.1' => $vmName, ]); if (!$response->successful()) { Log::warning('findVirtualMachineByName: HTTP failed', ['status' => $response->status()]); return null; } $json = $response->json(); if (isset($json['type']) || empty($json['value'])) { return null; } return $json['value'][0] ?? null; } public function GetDetailVirtualMachine($vmware_id = ''): array { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->client->token ]) ->get("https://{$this->client->apiUri}/rest/vcenter/vm/$vmware_id")->json(); if (!isset($response['type'])) { return [ 'error' => false, 'data' => $response['value'] ]; } return [ 'error' => true, 'message' => $response['value'] ]; } public function GetIPAddressVirtualMachine($vmware_id = ''): array { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->client->token ]) ->get("https://{$this->client->apiUri}/rest/vcenter/vm/$vmware_id/guest/identity")->json(); if (!isset($response['type'])) { return [ 'error' => false, 'data' => $response['value'] ]; } return [ 'error' => true, 'message' => $response['value'] ]; } public function GetVMStats($vmware_id = ''): array { $stats = [ 'cpu' => null, 'memory' => null, 'disk' => null, ]; try { $hardwareResponse = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->client->token ]) ->get("https://{$this->client->apiUri}/rest/vcenter/vm/$vmware_id/hardware")->json(); if (is_array($hardwareResponse) && !isset($hardwareResponse['type']) && isset($hardwareResponse['value'])) { foreach ($hardwareResponse['value'] as $hardware) { if (isset($hardware['label']) && $hardware['label'] === 'CPU') { $stats['cpu'] = [ 'cores' => $hardware['spec']['num_cores'] ?? 0, 'cores_per_socket' => $hardware['spec']['cores_per_socket'] ?? 0, ]; } if (isset($hardware['label']) && $hardware['label'] === 'Memory') { $stats['memory'] = [ 'size_MiB' => $hardware['spec']['size_MiB'] ?? 0, 'size_GB' => round(($hardware['spec']['size_MiB'] ?? 0) / 1024, 2), ]; } if (isset($hardware['label']) && $hardware['label'] === 'Hard disk') { $stats['disk'][] = [ 'capacity' => $hardware['capacity'] ?? 0, 'capacity_GB' => round(($hardware['capacity'] ?? 0) / (1024 * 1024 * 1024), 2), ]; } } } $guestMemoryResponse = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->client->token ]) ->get("https://{$this->client->apiUri}/rest/vcenter/vm/$vmware_id/guest/memory")->json(); if (is_array($guestMemoryResponse) && !isset($guestMemoryResponse['type']) && isset($guestMemoryResponse['value'])) { $stats['memory']['used_MiB'] = $guestMemoryResponse['value']['used'] ?? 0; $stats['memory']['used_GB'] = round(($guestMemoryResponse['value']['used'] ?? 0) / 1024, 2); $stats['memory']['usage_percent'] = $stats['memory']['size_MiB'] > 0 ? round(($guestMemoryResponse['value']['used'] ?? 0) / $stats['memory']['size_MiB'] * 100, 1) : 0; } $guestCpuResponse = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->client->token ]) ->get("https://{$this->client->apiUri}/rest/vcenter/vm/$vmware_id/guest/cpu")->json(); if (is_array($guestCpuResponse) && !isset($guestCpuResponse['type']) && isset($guestCpuResponse['value'])) { $stats['cpu']['used_percent'] = $guestCpuResponse['value']['used'] ?? 0; } return ['error' => false, 'data' => $stats]; } catch (\Exception $e) { return ['error' => true, 'message' => $e->getMessage(), 'data' => $stats]; } } public function ConfigNetworkVirtualMachine($vmware_id = '', $config = []): int { return Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->client->token ]) ->patch("https://{$this->client->apiUri}/rest/vcenter/vm/$vmware_id/hardware/ethernet/4000", $config) ->status(); } public function FindVirtualMachine($name = ''): string|bool { $check = $this->client->checkApiExits(); if ($check) { try { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->client->token ]) ->get( "https://{$this->client->apiUri}/rest/vcenter/vm?filter.names.1=$name" )->json(); } catch (ConnectionException $error) { return false; } if ($response['value']) { return $response['value'][0]['vm']; } return false; } return "Login_Again"; } public function reBuildVPS(string $vmName): array|string { if (!$this->client->checkApiExits()) { return "Login_Again"; } try { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->client->token ]) ->get("https://{$this->client->apiUri}/rest/vcenter/vm", [ 'filter.names' => $vmName ]); if (!$response->successful()) { return [ 'error' => true, 'message' => 'Failed to search for VM', 'details' => $response->json() ]; } $vmList = $response->json()['value'] ?? []; if (empty($vmList)) { return [ 'error' => true, 'message' => "VM not found with name: {$vmName}" ]; } $vmId = $vmList[0]['vm']; $detailResponse = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->client->token ]) ->get("https://{$this->client->apiUri}/rest/vcenter/vm/{$vmId}"); if (!$detailResponse->successful()) { return [ 'error' => true, 'message' => 'Failed to fetch VM details', 'details' => $detailResponse->json() ]; } $vmData = $detailResponse->json(); $guestOS = $vmData['value']['guest_OS'] ?? 'UNKNOWN'; $scriptPath = ''; $osUpper = strtoupper($guestOS); if (Str::contains($osUpper, 'WINDOWS')) { $scriptPath = 'C:\\CLISave\\Windows\\rebuild.ps1'; } elseif (Str::contains($osUpper, ['UBUNTU', 'LINUX', 'CENTOS', 'DEBIAN'])) { $scriptPath = 'C:\\CLISave\\Ubuntu\\rebuild.ps1'; } else { return [ 'error' => true, 'message' => "Unsupported OS for rebuild: {$guestOS}" ]; } $scriptDir = dirname($scriptPath); $scriptFile = basename($scriptPath); $cmd = " Write-Output 'STARTING_REBUILD'; \$vm = Get-VM -Name '$vmName'; cd '$scriptDir'; if (Test-Path '.\\$scriptFile') { Write-Output 'File found, executing...'; & '.\\$scriptFile' | Out-String } else { Write-Error 'File NOT FOUND: $scriptPath'; } Write-Output 'ENDING_REBUILD'; "; $result = $this->powerShellService->executePowerCLI($cmd); return [ 'error' => false, 'vm_name' => $vmName, 'vm_id' => $vmId, 'os' => $guestOS, 'script' => $scriptPath, 'result' => $result ]; } catch (\Exception $e) { Log::error("reBuildVPS Exception: " . $e->getMessage()); return [ 'error' => true, 'message' => $e->getMessage() ]; } } public function UpgradeMemoryVirtualMachine($vmware_id = '', $ram = 0): array|string { $check = $this->client->checkApiExits(); if ($check) { $RAMConfig = [ "spec" => [ "size_MiB" => $ram * 1024, ] ]; $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->client->token ]) ->patch("https://{$this->client->apiUri}/rest/vcenter/vm/$vmware_id/hardware/memory", $RAMConfig); if ($response->status() === 200) { return [ 'error' => false, 'data' => 'success' ]; } return [ 'error' => true, 'data' => $response['value'] ]; } return "Login_Again"; } public function UpgradeCPUVirtualMachine($vmware_id = '', $cpu = 0): array|string { $check = $this->client->checkApiExits(); if ($check) { $CPUConfig = [ "spec" => [ "cores_per_socket" => 1, "count" => $cpu ] ]; $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->client->token ]) ->patch("https://{$this->client->apiUri}/rest/vcenter/vm/$vmware_id/hardware/cpu", $CPUConfig); if ($response->status() === 200) { return [ 'error' => false, 'data' => 'success' ]; } return [ 'error' => true, 'data' => $response['value'] ]; } return "Login_Again"; } public function UpgradeDiskVirtualMachine($product, $disk = 0): array|string { $vmware_id = $product->vmware_id; $check = $this->client->checkApiExits(); if ($check) { try { Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Upgrade Disk with VirtualMachine ID # - $vmware_id"); Log::channel('Custom')->info("\r\n"); $Command = "\$vm = 'VirtualMachine-$vmware_id'; \$disksize = $disk; cd /; C:\CLISave\diskresize.ps1;"; Log::channel('Custom')->info("$Command"); $this->powerShellService->executePowerCLI($Command); Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("After run Command |" . now()); Log::channel('Custom')->info("==================================================================="); return [ 'error' => false ]; } catch (\Exception $error) { Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Error Log: "); Log::channel('Custom')->info($error->getMessage()); Log::channel('Custom')->info("==================================================================="); return [ 'error' => true, 'message' => $error->getMessage() ]; } } return "Login_Again"; } }