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];
}
}