feat: Implement asynchronous VM power operations and refactor vCenter snapshot retrieval to use API calls.

This commit is contained in:
pbao9
2026-01-27 16:35:42 +07:00
parent bfb5f9f4a5
commit 057ba58760
16 changed files with 2869 additions and 2521 deletions
+175 -101
View File
@@ -861,13 +861,6 @@ PS;
'hot_add_enabled' => true
]
];
Log::info("📤 Payload CPU", [
'payload' => $payload,
'cpu_count' => $cpuCount,
'cores_per_socket' => $coresPerSocket
]);
$response = Http::withoutVerifying()
->timeout(30)
->withHeaders([
@@ -1627,120 +1620,201 @@ PS;
}
$vmware_name = (string) $product->name;
$vmId = $product->vmware_id;
// Check cache first
$cacheKey = "snapshots_{$product->id}_{$vmware_name}";
$cachedResult = Cache::get($cacheKey);
if ($cachedResult !== null) {
$executionTime = round((microtime(true) - $startTime) * 1000, 2);
Log::channel('Custom')->info("📸 Snapshot cache hit - Execution time: {$executionTime}ms");
Log::channel('Custom')->info("📸 Snapshot cache hit");
return $cachedResult;
}
$check = $this->checkApiExits();
try {
Log::channel('Custom')->info("🚀 Starting SOAP Snapshot Fetch for VM: $vmId ($vmware_name)");
if ($check) {
try {
Log::channel('Custom')->info("===================================================================");
Log::channel('Custom')->info("Get Snapshots for VirtualMachine Name - $vmware_name");
Log::channel('Custom')->info("\r\n");
// 1. SOAP Login
$sessionToken = $this->soapLogin();
if (!$sessionToken) {
return "Login_Again";
}
// Use the getsnapshot.ps1 script file
$Command = "\$vm_name = '$vmware_name'; cd /; C:\CLISave\getsnapshot.ps1;";
// 2. Retrieve Snapshot Info
$snapshotInfo = $this->getVMSnapshotInfoSoap($sessionToken, $vmId);
Log::channel('Custom')->info("$Command");
$commandStartTime = microtime(true);
$result = $this->Command($Command);
$commandTime = round((microtime(true) - $commandStartTime) * 1000, 2);
Log::channel('Custom')->info("\r\n");
Log::channel('Custom')->info("Command Result: " . $result);
Log::channel('Custom')->info("Command Execution Time: {$commandTime}ms");
Log::channel('Custom')->info("After run Command |" . Carbon::now());
Log::channel('Custom')->info("===================================================================");
// Parse result
if ($result && $result !== "Login_Fail") {
try {
// Clean up the result - remove any PowerShell formatting
$cleanResult = trim($result);
// Parse the text output from the script
$snapshots = $this->parseSnapshotTextOutput($cleanResult);
if (!empty($snapshots)) {
$response = [
'error' => false,
'data' => $snapshots
];
// Cache the result for 5 minutes
Cache::put($cacheKey, $response, now()->addMinutes(5));
$totalTime = round((microtime(true) - $startTime) * 1000, 2);
Log::channel('Custom')->info("📸 Snapshot retrieved successfully - Total time: {$totalTime}ms, Command time: {$commandTime}ms");
return $response;
} else {
$response = [
'error' => true,
'message' => 'Không tìm thấy snapshot nào hoặc lỗi parse dữ liệu',
'raw_result' => $result
];
$totalTime = round((microtime(true) - $startTime) * 1000, 2);
Log::channel('Custom')->info("📸 No snapshots found - Total time: {$totalTime}ms");
return $response;
}
} catch (\Exception $parseError) {
Log::channel('Custom')->info("Parse Error: " . $parseError->getMessage());
$response = [
'error' => true,
'message' => 'Lỗi parse kết quả snapshot: ' . $parseError->getMessage(),
'raw_result' => $result
];
$totalTime = round((microtime(true) - $startTime) * 1000, 2);
Log::channel('Custom')->info("📸 Parse error - Total time: {$totalTime}ms");
return $response;
// 3. Flatten Tree
$snapshots = [];
if (isset($snapshotInfo['rootSnapshotList'])) {
// Handle single object vs array in XML to JSON conversion
$roots = $snapshotInfo['rootSnapshotList'];
// Ensure it's an array if single
if (isset($roots['snapshot'])) {
// It's a single node structure, allow recurse
$snapshots = $this->flattenSnapshotTree($roots);
} elseif (is_array($roots)) {
foreach ($roots as $root) {
$snapshots = array_merge($snapshots, $this->flattenSnapshotTree($root));
}
} else {
$response = [
'error' => true,
'message' => 'Không thể thực thi lệnh hoặc đăng nhập thất bại',
'raw_result' => $result
];
$totalTime = round((microtime(true) - $startTime) * 1000, 2);
Log::channel('Custom')->info("📸 Command failed - Total time: {$totalTime}ms");
return $response;
}
} 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("===================================================================");
}
$totalTime = round((microtime(true) - $startTime) * 1000, 2);
Log::channel('Custom')->info("📸 Exception occurred - Total time: {$totalTime}ms");
return [
'error' => true,
'message' => $error->getMessage()
// Format similar to old output
$formattedSnapshots = [];
foreach ($snapshots as $snap) {
$formattedSnapshots[] = [
'name' => $snap['name'],
'description' => $snap['description'] ?? '',
'created' => $snap['createTime'],
'id' => $snap['snapshot']['_'] ?? $snap['snapshot'], // MO Ref Value
'is_current' => false, // Simplification, need logic to check against currentSnapshot
'parent_snapshot_id' => '', // Hard to determining flat parent in simple recursion without passing parent id
'size_gb' => 0 // SOAP doesnt always return size in tree
];
}
$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()
{
$body = '
<soapenv:Body>
<Login xmlns="urn:vim25">
<_this type="SessionManager">SessionManager</_this>
<userName>' . htmlspecialchars($this->username) . '</userName>
<password>' . htmlspecialchars($this->password) . '</password>
</Login>
</soapenv:Body>';
$response = $this->callSoap($body);
// Extract session cookie if needed, but Login typically sets checking cookie.
// Actually for Http Client, we need to grab the cookie "vmware_soap_session"
// But the callSoap helper needs to handle cookies.
return $response['cookie'] ?? null;
}
private function getVMSnapshotInfoSoap($cookie, $vmId)
{
$body = '
<soapenv:Body>
<RetrievePropertiesEx xmlns="urn:vim25">
<_this type="PropertyCollector">propertyCollector</_this>
<specSet>
<propSet>
<type>VirtualMachine</type>
<all>false</all>
<pathSet>snapshot</pathSet>
</propSet>
<objectSet>
<obj type="VirtualMachine">' . $vmId . '</obj>
</objectSet>
</specSet>
<options></options>
</RetrievePropertiesEx>
</soapenv:Body>';
$result = $this->callSoap($body, $cookie);
// Parse simple XML response to finding snapshot info
// This is tricky with Regex/SimpleXML. We need to extract the returnval.
if (isset($result['body'])) {
$xml = simplexml_load_string($result['body']);
$xml->registerXPathNamespace('u', 'urn:vim25');
// Navigate to returnval -> objects -> propSet -> val -> rootSnapshotList
// Path depends on exact response structure
// Simplified return for now:
$json = json_encode($xml);
$array = json_decode($json, true);
// Deep dive into array to find "snapshot" property
// This needs robust traversing
return $this->findSnapshotPropertyInArray($array);
}
return [];
}
private function findSnapshotPropertyInArray($array)
{
// Recursive search for 'rootSnapshotList' or 'currentSnapshot'
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 = [];
// Add self
$list[] = $node;
// Add children
if (isset($node['childSnapshotList'])) {
$children = $node['childSnapshotList'];
// Check if single child or array of children
if (isset($children['snapshot'])) {
$list = array_merge($list, $this->flattenSnapshotTree($children));
} else {
foreach ($children as $child) {
$list = array_merge($list, $this->flattenSnapshotTree($child));
}
}
}
return $list;
}
private function callSoap($soapBody, $cookie = null)
{
$envelope = '<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
' . $soapBody . '
</soapenv:Envelope>';
$headers = [
'Content-Type' => 'text/xml; charset=utf-8',
'SOAPAction' => 'urn:vim25/2005'
];
if ($cookie) {
$headers['Cookie'] = $cookie;
}
$totalTime = round((microtime(true) - $startTime) * 1000, 2);
Log::channel('Custom')->info("📸 Login again required - Total time: {$totalTime}ms");
$response = Http::withoutVerifying()
->withHeaders($headers)
->post("https://{$this->apiUri}/sdk", $envelope);
return "Login_Again";
$responseCookie = $response->header('Set-Cookie');
return [
'body' => $response->body(),
'cookie' => $responseCookie ? $responseCookie : $cookie
];
}