client = $client; $this->powerShellService = $powerShellService; } public function CreateSnapShot($product): string|array { try { // Note: credentials setup logic from original vCenter.php seemed redundant with Client logic, // but CreateSnapShot was overwriting $this->username/password. // Here we assume Client is used, or we need to update Client. // Client stores token. If we need username/password for something else, Client has them now via getters. // But CreateSnapshot used $product->ipaddress...->vCenter->user/password. // Re-login to ensure we have the right credentials for the vCenter associated with the product $vcenterModel = $product->ipaddress->host->datacenter->vCenter; $this->client->loginServer($vcenterModel); } catch (\Exception $e) { return ['error' => true, 'message' => 'Lỗi setup credentials: ' . $e->getMessage()]; } $vmware_name = (string) $product->name; // In original code, it checks checkApiExits(), if true proceed. if ($this->client->checkApiExits()) { try { $Snapshot = $vmware_name . "_" . date('d_m_Y_H_i_s'); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Create Snapshot with VirtualMachine Name - $vmware_name - Snap #$Snapshot"); $Command = "\$vm_name = '$vmware_name';\$name = '$Snapshot'; cd /; C:\CLISave\createsnapshot.ps1;"; Log::channel('Custom')->info("$Command"); $this->powerShellService->executePowerCLI($Command); $this->clearSnapshotCache($product); return [ 'error' => false, 'data' => $Snapshot ]; } catch (\Exception $error) { return [ 'error' => true, 'message' => $error->getMessage() ]; } } return "Login_Again"; } private function clearSnapshotCache($product): void { $vmware_name = (string) $product->name; $cacheKey = "snapshots_{$product->id}_{$vmware_name}"; Cache::forget($cacheKey); Log::channel('Custom')->info("🗑️ Cleared snapshot cache for VM: {$vmware_name}"); } public function getSnapShot($product): string|array { // ... (Similar logic, using SOAP) // Since SOAP logic is quite complex and self-contained in vCenter.php, // I'll copy it here but need to adapt $this->callSoap etc. // Actually, callSoap uses curl with manual implementation. // I should probably simplify or copy that helper too. // This method heavily relies on internal SOAP helpers. // For simplicity in this 'splitting' task, I will implement the public method // and add the private helpers it depends on. try { $vcenterModel = $product->ipaddress->host->datacenter->vCenter; $this->client->loginServer($vcenterModel); } catch (\Exception $e) { return ['error' => true, 'message' => 'Lỗi setup credentials: ' . $e->getMessage()]; } $vmware_name = (string) $product->name; $vmId = $product->vmware_id; $cacheKey = "snapshots_{$product->id}_{$vmware_name}"; $cachedResult = Cache::get($cacheKey); if ($cachedResult !== null) { return $cachedResult; } try { $sessionToken = $this->soapLogin(); if (!$sessionToken) { return "Login_Again"; } $snapshotInfo = $this->getVMSnapshotInfoSoap($sessionToken, $vmId); $snapshots = []; if (isset($snapshotInfo['rootSnapshotList'])) { $roots = $snapshotInfo['rootSnapshotList']; if (isset($roots['snapshot'])) { $snapshots = $this->flattenSnapshotTree($roots); } elseif (is_array($roots)) { foreach ($roots as $root) { $snapshots = array_merge($snapshots, $this->flattenSnapshotTree($root)); } } } $formattedSnapshots = []; foreach ($snapshots as $snap) { $formattedSnapshots[] = [ 'name' => $snap['name'], 'description' => $snap['description'] ?? '', 'created' => $snap['createTime'], 'id' => $snap['snapshot']['_'] ?? $snap['snapshot'], 'is_current' => false, 'parent_snapshot_id' => '', 'size_gb' => 0 ]; } $response = [ 'error' => false, 'data' => $formattedSnapshots ]; Cache::put($cacheKey, $response, now()->addMinutes(5)); return $response; } catch (\Exception $e) { Log::channel('Custom')->error("❌ SOAP Snapshot Error: " . $e->getMessage()); return [ 'error' => true, 'message' => 'Lỗi lấy snapshot qua SOAP: ' . $e->getMessage() ]; } } private function soapLogin() { $username = $this->client->getUsername(); $password = $this->client->getPassword(); $body = ' <_this type="SessionManager">SessionManager ' . htmlspecialchars($username) . ' ' . htmlspecialchars($password) . ' '; $result = $this->callSoap($body); return $result['cookie'] ?? null; } private function getVMSnapshotInfoSoap($cookie, $vmId) { $body = ' <_this type="PropertyCollector">propertyCollector VirtualMachine false snapshot ' . $vmId . ' '; $result = $this->callSoap($body, $cookie); if (isset($result['body'])) { try { $xml = simplexml_load_string($result['body']); if ($xml === false) return []; $xml->registerXPathNamespace('u', 'urn:vim25'); $json = json_encode($xml); $array = json_decode($json, true); return $this->findSnapshotPropertyInArray($array); } catch (\Exception $e) { return []; } } return []; } private function findSnapshotPropertyInArray($array) { if (isset($array['rootSnapshotList'])) { return $array; } foreach ($array as $key => $value) { if (is_array($value)) { $found = $this->findSnapshotPropertyInArray($value); if ($found) { return $found; } } } return []; } private function flattenSnapshotTree($node) { $list = []; $list[] = $node; if (isset($node['childSnapshotList'])) { $children = $node['childSnapshotList']; // Check if 'snapshot' key exists directly (single child wrapped) if (isset($children['snapshot'])) { // If it's an array of snapshots or single object? // XML conversion: if strictly one child, might be direct. if multiple, array. // But simplexml usually produces array if multiple same tags. // However, structure is usually childSnapshotList -> snapshot (array or single) $snaps = $children['snapshot']; // If $snaps has numeric keys, it's array. Else single. if (array_keys($snaps) !== range(0, count($snaps) - 1)) { $snaps = [$snaps]; } foreach ($snaps as $child) { $list = array_merge($list, $this->flattenSnapshotTree($child)); } } } return $list; } private function callSoap($soapBody, $cookie = null) { $curl = curl_init(); $host = $this->client->apiUri; $headers = [ 'Content-Type: text/xml', 'SOAPAction: "urn:vim25/2.0"' ]; if ($cookie) { $headers[] = "Cookie: $cookie"; } $payload = ' ' . $soapBody . ' '; curl_setopt_array($curl, array( CURLOPT_URL => "https://$host/sdk", CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => $headers, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_HEADER => true // We need headers for cookie )); $response = curl_exec($curl); $header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE); $header = substr($response, 0, $header_size); $body = substr($response, $header_size); curl_close($curl); // Parse cookie $cookieValue = null; if (preg_match_all('/^Set-Cookie:\s*([^;]*)/mi', $header, $matches)) { foreach ($matches[1] as $c) { if (strpos($c, 'vmware_soap_session') !== false) { $cookieValue = $c; break; } } } return ['body' => $body, 'cookie' => $cookieValue]; } }