feat: Implement asynchronous VM power operations and refactor vCenter snapshot retrieval to use API calls.
This commit is contained in:
+175
-101
@@ -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
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user