feat: Implement comprehensive vCenter integration with new services for VM configuration, power management, templates, snapshots, and resource management, alongside infrastructure management and synchronization job updates.
This commit is contained in:
+83
-113
@@ -16,9 +16,9 @@ use phpseclib3\Net\SSH2;
|
||||
class vCenter
|
||||
{
|
||||
public ?string $token = null;
|
||||
public string $apiUri = '10.10.10.6';
|
||||
private string $username = 'administrator@vsphere.local';
|
||||
private string $password = 'Tgs@1238989$%^';
|
||||
public string $apiUri;
|
||||
private string $username;
|
||||
private string $password;
|
||||
private int $diskCapacity = 1073741824;
|
||||
private int $memory = 1024;
|
||||
|
||||
@@ -35,7 +35,7 @@ class vCenter
|
||||
|
||||
$response = Http::withoutVerifying()
|
||||
->withBasicAuth($this->username, $this->password)
|
||||
->post("https://{$this->apiUri}/rest/com/vmware/cis/session");
|
||||
->post("https://{$this->apiUri}/rest/com/vmware/cis/session", []);
|
||||
|
||||
// Log::info('Response login: ' . $response->body());
|
||||
|
||||
@@ -246,69 +246,38 @@ class vCenter
|
||||
|
||||
public function loadNetWork($data = [], $host_id = '')
|
||||
{
|
||||
|
||||
$this->username = $data->user;
|
||||
|
||||
$this->password = SignRSA::DecryptFunc($data->password);
|
||||
|
||||
$this->apiUri = $data->host;
|
||||
|
||||
$information = $this->loadInformation($host_id);
|
||||
// Pass the host_id directly to the API call
|
||||
$allNetwork = $this->getAllNetwork($host_id);
|
||||
|
||||
if ($information) {
|
||||
|
||||
if (is_array($information->Network)) {
|
||||
$information = $information->Network;
|
||||
} else {
|
||||
$information = array($information->Network);
|
||||
}
|
||||
|
||||
$netWork = [];
|
||||
$allNetwork = $this->getAllNetwork();
|
||||
if ($allNetwork && $allNetwork !== "Login_Again") {
|
||||
foreach ($allNetwork as $_subItem) {
|
||||
if (in_array($_subItem['network'], $information)) {
|
||||
$netWork[] = $_subItem;
|
||||
}
|
||||
}
|
||||
return $netWork;
|
||||
}
|
||||
return $allNetwork;
|
||||
}
|
||||
return $information;
|
||||
return $allNetwork;
|
||||
}
|
||||
|
||||
protected function loadInformation($host_id = '')
|
||||
private function getAllNetwork($host = '')
|
||||
{
|
||||
try {
|
||||
Log::info('host_id', ['data' => $host_id]);
|
||||
$cmd = "\$vmhost='$host_id'; cd /; C:\CLISave\getvmhostinfo.ps1;";
|
||||
$results = $this->Command($cmd);
|
||||
$check = $this->checkApiExits();
|
||||
if ($check) {
|
||||
|
||||
if (!$results) {
|
||||
return null;
|
||||
$params = [];
|
||||
if (!empty($host)) {
|
||||
$params['filter.hosts'] = $host;
|
||||
}
|
||||
|
||||
$startPos = strpos($results, '{');
|
||||
$endPos = strrpos($results, '}');
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->token
|
||||
])
|
||||
->get("https://$this->apiUri/rest/vcenter/network", $params)->json();
|
||||
|
||||
if ($startPos === false || $endPos === false) {
|
||||
return null;
|
||||
if (!isset($response['type'])) {
|
||||
return $response['value'];
|
||||
}
|
||||
$jsonData = substr($results, $startPos, $endPos - $startPos + 1);
|
||||
|
||||
$jsonData = trim($jsonData);
|
||||
|
||||
try {
|
||||
$decoded = json_decode($jsonData, false, 512, JSON_THROW_ON_ERROR);
|
||||
return $decoded;
|
||||
} catch (\JsonException $e) {
|
||||
return null;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Error in loadInformation: ' . $e->getMessage());
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
protected function Command($cmd)
|
||||
@@ -597,76 +566,77 @@ PS;
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function getAllNetwork()
|
||||
{
|
||||
$check = $this->checkApiExits();
|
||||
if ($check) {
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->token
|
||||
])
|
||||
->get("https://$this->apiUri/rest/vcenter/network")->json();
|
||||
if (!isset($response['type'])) {
|
||||
return $response['value'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
public function loadDatastore($data = [], $host_id = '')
|
||||
{
|
||||
Log::info('[vCenter] loadDatastore()', [
|
||||
'host' => $data->host,
|
||||
'esxi_host' => $host_id
|
||||
]);
|
||||
|
||||
$this->username = $data->user;
|
||||
$allDatastore = $this->getAllDataStore($host_id);
|
||||
|
||||
$this->password = SignRSA::DecryptFunc($data->password);
|
||||
|
||||
$this->apiUri = $data->host;
|
||||
|
||||
$information = $this->loadInformation($host_id);
|
||||
|
||||
if ($information) {
|
||||
if (is_array($information->Datastore)) {
|
||||
$information = $information->Datastore;
|
||||
} else {
|
||||
$information = array($information->Datastore);
|
||||
}
|
||||
|
||||
$dataStore = [];
|
||||
$allDatastore = $this->getAllDataStore();
|
||||
|
||||
if ($allDatastore && $allDatastore !== "Login_Again") {
|
||||
foreach ($allDatastore as $_item) {
|
||||
if (in_array($_item['datastore'], $information)) {
|
||||
$dataStore[] = $_item;
|
||||
}
|
||||
}
|
||||
return $dataStore;
|
||||
}
|
||||
return $allDatastore;
|
||||
if ($allDatastore === "Login_Again") {
|
||||
Log::warning('[vCenter] Token expired when getAllDataStore');
|
||||
return "Login_Again";
|
||||
}
|
||||
return $information;
|
||||
}
|
||||
|
||||
public function getAllDataStore()
|
||||
{
|
||||
$check = $this->checkApiExits();
|
||||
if ($check) {
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->token
|
||||
])
|
||||
->get("https://$this->apiUri/rest/vcenter/datastore")->json();
|
||||
if (!isset($response['type'])) {
|
||||
return $response['value'];
|
||||
}
|
||||
if (!$allDatastore) {
|
||||
Log::warning('[vCenter] getAllDataStore return false');
|
||||
return false;
|
||||
}
|
||||
return "Login_Again";
|
||||
|
||||
Log::info('[vCenter] Datastore count from API', [
|
||||
'count' => count($allDatastore)
|
||||
]);
|
||||
|
||||
return $allDatastore;
|
||||
}
|
||||
|
||||
|
||||
public function getAllDataStore($host = '')
|
||||
{
|
||||
Log::info('[vCenter] getAllDataStore call', [
|
||||
'api' => $this->apiUri,
|
||||
'host_filter' => $host
|
||||
]);
|
||||
|
||||
$check = $this->checkApiExits();
|
||||
|
||||
if (!$check) {
|
||||
Log::warning('[vCenter] checkApiExits = false');
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
$params = [];
|
||||
if (!empty($host)) {
|
||||
$params['filter.hosts'] = $host;
|
||||
}
|
||||
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->token
|
||||
])
|
||||
->get("https://$this->apiUri/rest/vcenter/datastore", $params);
|
||||
|
||||
Log::info('[vCenter] datastore raw response', [
|
||||
'status' => $response->status(),
|
||||
// 'body' => $response->body() // Comment out to reduce log noise if large
|
||||
]);
|
||||
|
||||
$json = $response->json();
|
||||
|
||||
if (!isset($json['type'])) {
|
||||
return $json['value'];
|
||||
}
|
||||
|
||||
Log::warning('[vCenter] API returned error', [
|
||||
'response' => $json
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function CreateVirtualMachine($product_data): array|string
|
||||
{
|
||||
$vcenterModel = $product_data['vcenter'];
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Services\vCenter;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use App\Http\Facade\SignRSA;
|
||||
use App\Models\Vcenter;
|
||||
use Exception;
|
||||
|
||||
class Client
|
||||
{
|
||||
public ?string $token = null;
|
||||
public string $apiUri;
|
||||
private string $username;
|
||||
private string $password;
|
||||
|
||||
public function loginServer(Vcenter $vcenterModel): bool|string
|
||||
{
|
||||
$this->apiUri = $vcenterModel->host;
|
||||
$this->username = $vcenterModel->user;
|
||||
$this->password = SignRSA::DecryptFunc($vcenterModel->password);
|
||||
|
||||
$response = Http::withoutVerifying()
|
||||
->withBasicAuth($this->username, $this->password)
|
||||
->post("https://{$this->apiUri}/rest/com/vmware/cis/session", []);
|
||||
|
||||
if ($response->successful()) {
|
||||
$json = $response->json();
|
||||
if (isset($json['value'])) {
|
||||
$this->token = $json['value'];
|
||||
$vcenterModel->update(['token' => $this->token]);
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
$body = $response->body();
|
||||
if (!empty($body)) {
|
||||
$this->token = $body;
|
||||
$vcenterModel->update(['token' => $this->token]);
|
||||
return $this->token;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkApiExits(): bool
|
||||
{
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->token
|
||||
])
|
||||
->get("https://$this->apiUri/rest/com/vmware/content/library")->json();
|
||||
|
||||
if (isset($response['type'])) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getUsername(): string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Services\vCenter;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PowerManagementService
|
||||
{
|
||||
protected Client $client;
|
||||
|
||||
public function __construct(Client $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
public function SuspendVirtualMachine($vmware_id = ''): array|string
|
||||
{
|
||||
if ($this->client->checkApiExits()) {
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->client->token
|
||||
])
|
||||
->post("https://{$this->client->apiUri}/rest/vcenter/vm/$vmware_id/power/suspend");
|
||||
if ($response->status() === 200) {
|
||||
return [
|
||||
'error' => false,
|
||||
'data' => 'success'
|
||||
];
|
||||
}
|
||||
return [
|
||||
'error' => true,
|
||||
'data' => $response['value']
|
||||
];
|
||||
}
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
public function RebootVirtualMachine($product, $vmware_id = ''): array|string
|
||||
{
|
||||
if ($this->client->loginServer($product->ipaddress->host->datacenter->vCenter)) {
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->client->token
|
||||
])
|
||||
->post("https://{$this->client->apiUri}/rest/vcenter/vm/$vmware_id/guest/power?action=reboot");
|
||||
if ($response->status() === 200) {
|
||||
return [
|
||||
'error' => false,
|
||||
'data' => 'success'
|
||||
];
|
||||
}
|
||||
return [
|
||||
'error' => true,
|
||||
'data' => $response['value']
|
||||
];
|
||||
}
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
public function StartVirtualMachine($product, $vmware_id = ''): array|string
|
||||
{
|
||||
if ($this->client->loginServer($product->ipaddress->host->datacenter->vCenter)) {
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->client->token
|
||||
])
|
||||
->post("https://{$this->client->apiUri}/rest/vcenter/vm/$vmware_id/power/start");
|
||||
if ($response->status() === 200) {
|
||||
return [
|
||||
'error' => false,
|
||||
'data' => 'success'
|
||||
];
|
||||
}
|
||||
return [
|
||||
'error' => true,
|
||||
'data' => $response['value']
|
||||
];
|
||||
}
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
public function ShutdownGuestVirtualMachine($product, $vmware_id = ''): array|string
|
||||
{
|
||||
if ($this->client->loginServer($product->ipaddress->host->datacenter->vCenter)) {
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->client->token
|
||||
])
|
||||
->post("https://{$this->client->apiUri}/rest/vcenter/vm/$vmware_id/guest/power?action=shutdown");
|
||||
if ($response->status() === 200) {
|
||||
return [
|
||||
'error' => false,
|
||||
'data' => 'success'
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'error' => true,
|
||||
'data' => $response['value']
|
||||
];
|
||||
}
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
public function PowerOffVirtualMachine($vmware_id = ''): array|string
|
||||
{
|
||||
if ($this->client->checkApiExits()) {
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->client->token
|
||||
])
|
||||
->post("https://{$this->client->apiUri}/rest/vcenter/vm/$vmware_id/power/stop");
|
||||
if ($response->status() === 200) {
|
||||
return [
|
||||
'error' => false,
|
||||
'data' => 'success'
|
||||
];
|
||||
}
|
||||
return [
|
||||
'error' => true,
|
||||
'data' => $response['value']
|
||||
];
|
||||
}
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
public function DeleteVirtualMachine($vmware_id = ''): array|string
|
||||
{
|
||||
if ($this->client->checkApiExits()) {
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->client->token
|
||||
])
|
||||
->delete("https://{$this->client->apiUri}/rest/vcenter/vm/$vmware_id");
|
||||
if ($response->status() === 200) {
|
||||
return [
|
||||
'error' => false,
|
||||
'data' => 'success'
|
||||
];
|
||||
}
|
||||
return [
|
||||
'error' => true,
|
||||
'data' => $response['value']
|
||||
];
|
||||
}
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
public function GetConsoleTickets($vmware_id = ''): array|string
|
||||
{
|
||||
if ($this->client->checkApiExits()) {
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->client->token
|
||||
])
|
||||
->post("https://{$this->client->apiUri}/api/vcenter/vm/$vmware_id/console/tickets", [
|
||||
"type" => "WEBMKS"
|
||||
])->json();
|
||||
if (!isset($response['error_type'])) {
|
||||
$start = strpos($response['ticket'], '/ticket/');
|
||||
$ticket = str_replace('/ticket/', '', substr($response['ticket'], $start));
|
||||
return [
|
||||
'error' => false,
|
||||
'data' => $ticket
|
||||
];
|
||||
}
|
||||
return [
|
||||
'error' => true,
|
||||
'data' => $response['messages']
|
||||
];
|
||||
}
|
||||
return "Login_Again";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Services\vCenter;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use phpseclib3\Net\SSH2;
|
||||
use Exception;
|
||||
use App\Models\InfraManagement;
|
||||
use App\Http\Facade\SignRSA;
|
||||
|
||||
class PowerShellService
|
||||
{
|
||||
protected Client $client;
|
||||
|
||||
public function __construct(Client $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
private function LoginAuth()
|
||||
{
|
||||
try {
|
||||
// Retrieve SSH/Jumphost credentials from database
|
||||
$infra = InfraManagement::where('type', 'jumphost')
|
||||
->where('status', true)
|
||||
->first();
|
||||
|
||||
if (!$infra) {
|
||||
Log::error('SSH Login Failed: No active jumphost configuration found in InfraManagement.');
|
||||
return false;
|
||||
}
|
||||
|
||||
$sshHost = $infra->host;
|
||||
$sshPort = 22;
|
||||
if (isset($infra->meta_data['port'])) {
|
||||
$sshPort = $infra->meta_data['port'];
|
||||
}
|
||||
|
||||
$sshUser = $infra->username;
|
||||
$sshPass = SignRSA::DecryptFunc($infra->password);
|
||||
|
||||
$connection = new SSH2($sshHost, $sshPort);
|
||||
|
||||
if ($connection->login($sshUser, $sshPass)) {
|
||||
$connection->setTimeout(0); // No timeout
|
||||
return $connection;
|
||||
}
|
||||
return false;
|
||||
} catch (Exception $error) {
|
||||
Log::error('SSH Login Failed: ' . $error->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function loginSSH(): bool
|
||||
{
|
||||
$conn = $this->LoginAuth();
|
||||
|
||||
if ($conn) {
|
||||
Log::info("✅ Connected to vCenter via SSH successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
Log::error("❌ Failed to connect to vCenter via SSH");
|
||||
return false;
|
||||
}
|
||||
|
||||
public function runPsCommand(string $psCommand): string
|
||||
{
|
||||
$auth = $this->LoginAuth();
|
||||
if (!$auth) {
|
||||
return "Login_Fail";
|
||||
}
|
||||
|
||||
// Action wrapper
|
||||
$action = 'powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass '
|
||||
. '-Command "' . str_replace('"', '\"', $psCommand) . '"';
|
||||
|
||||
try {
|
||||
$out = $auth->exec($action);
|
||||
$errOut = $auth->getStdError();
|
||||
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
|
||||
} catch (\Throwable $e) {
|
||||
return "Error: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public function executePowerCLI(string $cmd)
|
||||
{
|
||||
return $this->CommandRSP($cmd);
|
||||
}
|
||||
|
||||
protected function CommandRSP(string $cmd)
|
||||
{
|
||||
$auth = $this->LoginAuth();
|
||||
if (!$auth) {
|
||||
return "Login_Fail";
|
||||
}
|
||||
|
||||
$esc = fn(string $v) => str_replace("'", "''", $v);
|
||||
|
||||
// Get credentials from Client
|
||||
$server = $esc($this->client->apiUri ?? '');
|
||||
$user = $esc($this->client->getUsername() ?? '');
|
||||
$pass = $esc($this->client->getPassword() ?? '');
|
||||
|
||||
$psTemplate = <<<'PS'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
$InformationPreference = 'SilentlyContinue'
|
||||
$WarningPreference = 'SilentlyContinue'
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
Import-Module VMware.PowerCLI -ErrorAction Stop
|
||||
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$false | Out-Null
|
||||
Connect-VIServer -Server '<SERVER>' -User '<USER>' -Password '<PASS>' | Out-Null
|
||||
|
||||
& {
|
||||
<CMD>
|
||||
} | Out-String
|
||||
PS;
|
||||
|
||||
$psScript = strtr($psTemplate, [
|
||||
'<SERVER>' => $server,
|
||||
'<USER>' => $user,
|
||||
'<PASS>' => $pass,
|
||||
'<CMD>' => $cmd,
|
||||
]);
|
||||
|
||||
$encoded = base64_encode(mb_convert_encoding($psScript, 'UTF-16LE', 'UTF-8'));
|
||||
$action = "powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encoded";
|
||||
|
||||
try {
|
||||
$out = $auth->exec($action);
|
||||
$errOut = $auth->getStdError();
|
||||
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
|
||||
} catch (\Throwable $e) {
|
||||
return "Error: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
protected function CommandChangePort(string $cmd, bool $expectJson = false)
|
||||
{
|
||||
$auth = $this->LoginAuth();
|
||||
if (!$auth) {
|
||||
return "Login_Fail";
|
||||
}
|
||||
|
||||
$esc = fn(string $v) => str_replace("'", "''", $v);
|
||||
|
||||
$server = $esc($this->client->apiUri ?? '');
|
||||
$user = $esc($this->client->getUsername() ?? '');
|
||||
$pass = $esc($this->client->getPassword() ?? '');
|
||||
|
||||
$psTemplate = <<<'PS'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
$InformationPreference = 'SilentlyContinue'
|
||||
$WarningPreference = 'SilentlyContinue'
|
||||
$ErrorActionPreference = 'Stop'
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
Import-Module VMware.PowerCLI -ErrorAction Stop
|
||||
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$false | Out-Null
|
||||
Connect-VIServer -Server '<SERVER>' -User '<USER>' -Password '<PASS>' | Out-Null
|
||||
|
||||
& {
|
||||
<CMD>
|
||||
}
|
||||
PS;
|
||||
|
||||
$psScript = strtr($psTemplate, [
|
||||
'<SERVER>' => $server,
|
||||
'<USER>' => $user,
|
||||
'<PASS>' => $pass,
|
||||
'<CMD>' => $cmd,
|
||||
]);
|
||||
|
||||
$encoded = base64_encode(mb_convert_encoding($psScript, 'UTF-16LE', 'UTF-8'));
|
||||
$action = "powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encoded";
|
||||
|
||||
try {
|
||||
$out = $auth->exec($action);
|
||||
$errOut = $auth->getStdError();
|
||||
|
||||
if ($expectJson) {
|
||||
$trimmed = trim($out);
|
||||
if (preg_match('~(\{.*\}|\[.*\])\s*$~s', $trimmed, $m)) {
|
||||
return $m[1];
|
||||
}
|
||||
return $trimmed;
|
||||
}
|
||||
|
||||
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
|
||||
} catch (\Throwable $e) {
|
||||
return "Error: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public function resetGuestPasswordViaPs1(string $vmName, string $guestUser, string $guestPassword, string $newPassword, string $targetUser = 'Administrator'): array|string
|
||||
{
|
||||
$psFile = "C:\\CLISave\\AssignIP\\SetGuestPwd.ps1";
|
||||
$q = fn(string $v) => str_replace("'", "''", $v);
|
||||
|
||||
$args = " -VMName '" . $q($vmName) . "'";
|
||||
$args .= " -GuestUser '" . $q($guestUser) . "'";
|
||||
$args .= " -GuestPassword '" . $q($guestPassword) . "'";
|
||||
$args .= " -TargetUser '" . $q($targetUser) . "'";
|
||||
$args .= " -NewPassword '" . $q($newPassword) . "'";
|
||||
|
||||
$cmd = "& '$psFile'$args | Out-String";
|
||||
|
||||
$raw = $this->CommandRSP($cmd);
|
||||
if ($raw === 'Login_Fail' || $raw === false || $raw === null) {
|
||||
return "Login_Fail";
|
||||
}
|
||||
|
||||
$trim = trim($raw);
|
||||
$data = json_decode($trim, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
if (preg_match('/(\{.*\}|\[.*\])/s', $trim, $m)) {
|
||||
$data = json_decode($m[1], true);
|
||||
}
|
||||
}
|
||||
|
||||
return $data ?: $raw;
|
||||
}
|
||||
|
||||
public function changeRDPPortWindows(string $vmName, string $guestUser, string $guestPassword, string $newPort): array|string
|
||||
{
|
||||
$psFile = "C:\\CLISave\\AssignIP\\change-rdp-port.ps1";
|
||||
$q = fn(string $v) => str_replace("'", "''", $v);
|
||||
|
||||
$args = " -VMName '" . $q($vmName) . "'";
|
||||
$args .= " -GuestUser '" . $q($guestUser) . "'";
|
||||
$args .= " -GuestPassword '" . $q($guestPassword) . "'";
|
||||
$args .= " -NewPort " . (int) $newPort;
|
||||
|
||||
$cmd = "& '$psFile'$args";
|
||||
|
||||
$raw = $this->CommandChangePort($cmd, true);
|
||||
|
||||
if ($raw === 'Login_Fail' || $raw === false || $raw === null) {
|
||||
return "Login_Fail";
|
||||
}
|
||||
$raw = trim($raw);
|
||||
|
||||
if (!preg_match('~(\{(?:[^{}]|(?1))*\}|\[(?:[^\[\]]|(?0))*\])\s*$~s', $raw, $m)) {
|
||||
if (preg_match('~(\{.*\}|\[.*\])\s*$~s', $raw, $m)) {
|
||||
$raw = $m[1];
|
||||
}
|
||||
} else {
|
||||
$raw = $m[1];
|
||||
}
|
||||
|
||||
$data = json_decode($raw, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'JSON parsing failed',
|
||||
'raw_output' => $raw,
|
||||
'json_error' => json_last_error_msg()
|
||||
];
|
||||
}
|
||||
|
||||
if (isset($data[0])) {
|
||||
$data = $data[0];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Services\vCenter;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ResourceService
|
||||
{
|
||||
protected Client $client;
|
||||
|
||||
public function __construct(Client $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
public function getAllDatacenter()
|
||||
{
|
||||
if ($this->client->checkApiExits()) {
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->client->token
|
||||
])
|
||||
->get("https://{$this->client->apiUri}/rest/vcenter/datacenter")->json();
|
||||
|
||||
if (!isset($response['type'])) {
|
||||
return $response['value'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
public function getAllHost($datacenter = '')
|
||||
{
|
||||
if ($this->client->checkApiExits()) {
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->client->token
|
||||
])
|
||||
->get("https://{$this->client->apiUri}/rest/vcenter/host", [
|
||||
'filter.datacenters.1' => $datacenter
|
||||
])->json();
|
||||
|
||||
if (!isset($response['type'])) {
|
||||
return $response['value'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
public function getAllFolder($datacenter = '')
|
||||
{
|
||||
if ($this->client->checkApiExits()) {
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->client->token
|
||||
])
|
||||
->get("https://{$this->client->apiUri}/rest/vcenter/folder", [
|
||||
'filter.datacenters.1' => $datacenter,
|
||||
'filter.type' => "VIRTUAL_MACHINE",
|
||||
'filter.names' => "vm"
|
||||
])->json();
|
||||
|
||||
if (!isset($response['type'])) {
|
||||
return $response['value'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
public function getAllDataStore($host = '')
|
||||
{
|
||||
Log::info('[vCenter] getAllDataStore call', [
|
||||
'api' => $this->client->apiUri,
|
||||
'host_filter' => $host
|
||||
]);
|
||||
|
||||
if (!$this->client->checkApiExits()) {
|
||||
Log::warning('[vCenter] checkApiExits = false');
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
$params = [];
|
||||
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/datastore", $params);
|
||||
|
||||
$json = $response->json();
|
||||
|
||||
if (!isset($json['type'])) {
|
||||
return $json['value'];
|
||||
}
|
||||
|
||||
Log::warning('[vCenter] API returned error', [
|
||||
'response' => $json
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function loadDatastore($host_id = '')
|
||||
{
|
||||
Log::info('[vCenter] loadDatastore()', [
|
||||
'esxi_host' => $host_id
|
||||
]);
|
||||
|
||||
$allDatastore = $this->getAllDataStore($host_id);
|
||||
|
||||
if ($allDatastore === "Login_Again") {
|
||||
Log::warning('[vCenter] Token expired when getAllDataStore');
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
if (!$allDatastore) {
|
||||
Log::warning('[vCenter] getAllDataStore return false');
|
||||
return false;
|
||||
}
|
||||
|
||||
Log::info('[vCenter] Datastore count from API', [
|
||||
'count' => count($allDatastore)
|
||||
]);
|
||||
|
||||
return $allDatastore;
|
||||
}
|
||||
|
||||
public function getAllNetwork($host = '')
|
||||
{
|
||||
if ($this->client->checkApiExits()) {
|
||||
$params = [];
|
||||
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/network", $params)->json();
|
||||
|
||||
if (!isset($response['type'])) {
|
||||
return $response['value'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
public function loadNetWork($host_id = '')
|
||||
{
|
||||
return $this->getAllNetwork($host_id);
|
||||
}
|
||||
public function GetFolderByHost($datacenter = '')
|
||||
{
|
||||
return \App\Models\ModuleFolder::where('module_datacenter_id', $datacenter)->first();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Services\vCenter;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Http\Facade\SignRSA;
|
||||
|
||||
class SnapshotService
|
||||
{
|
||||
protected Client $client;
|
||||
protected PowerShellService $powerShellService;
|
||||
|
||||
public function __construct(Client $client, PowerShellService $powerShellService)
|
||||
{
|
||||
$this->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 = '<soapenv:Body>
|
||||
<Login xmlns="urn:vim25">
|
||||
<_this type="SessionManager">SessionManager</_this>
|
||||
<userName>' . htmlspecialchars($username) . '</userName>
|
||||
<password>' . htmlspecialchars($password) . '</password>
|
||||
</Login>
|
||||
</soapenv:Body>';
|
||||
|
||||
$result = $this->callSoap($body);
|
||||
return $result['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);
|
||||
|
||||
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 = '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:vim25">
|
||||
' . $soapBody . '
|
||||
</soapenv:Envelope>';
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Services\vCenter;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class TemplateService
|
||||
{
|
||||
protected Client $client;
|
||||
|
||||
public function __construct(Client $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
public function getAllTemplate()
|
||||
{
|
||||
if ($this->client->checkApiExits()) {
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->client->token
|
||||
])
|
||||
->get("https://{$this->client->apiUri}/rest/com/vmware/content/library")->json();
|
||||
|
||||
if (!isset($response['type'])) {
|
||||
return $response['value'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
public function getTemplatebyID($template = '')
|
||||
{
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->client->token
|
||||
])
|
||||
->get("https://{$this->client->apiUri}/rest/com/vmware/content/library/id:{$template}")->json();
|
||||
|
||||
if (!isset($response['type'])) {
|
||||
return $response['value']['storage_backings'][0]['datastore_id'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getTemplateFromDatastore($datastore = '')
|
||||
{
|
||||
$allTemplate = $this->getAllTemplate();
|
||||
if (!$allTemplate) {
|
||||
return false;
|
||||
}
|
||||
foreach ($allTemplate as $template) {
|
||||
$detailTemplate = $this->getTemplatebyID($template);
|
||||
if ($detailTemplate && $detailTemplate === $datastore) {
|
||||
return $template;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getVersionDetail($version_id = '')
|
||||
{
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->client->token
|
||||
])
|
||||
->get("https://{$this->client->apiUri}/rest/com/vmware/content/library/item/id:{$version_id}")->json();
|
||||
|
||||
if (!isset($response['type'])) {
|
||||
return $response['value'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getAllVersion($library = ''): bool|array|string
|
||||
{
|
||||
if ($this->client->checkApiExits()) {
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $this->client->token
|
||||
])
|
||||
->get("https://{$this->client->apiUri}/rest/com/vmware/content/library/item", [
|
||||
'library_id' => $library
|
||||
])->json();
|
||||
|
||||
if (!isset($response['type'])) {
|
||||
$library_item = [];
|
||||
foreach ($response['value'] as $value) {
|
||||
$value_detail = $this->getVersionDetail($value);
|
||||
if ($value_detail) {
|
||||
$library_item[] = [
|
||||
'name' => $value_detail['name'],
|
||||
'id' => $value_detail['id']
|
||||
];
|
||||
}
|
||||
}
|
||||
return $library_item;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return "Login_Again";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Services\vCenter;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
|
||||
class VMConfigurationService
|
||||
{
|
||||
protected Client $client;
|
||||
protected PowerShellService $powerShellService;
|
||||
|
||||
public function __construct(Client $client, PowerShellService $powerShellService)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->powerShellService = $powerShellService;
|
||||
}
|
||||
|
||||
private function setConfig($vcenterModel)
|
||||
{
|
||||
// Setup client with vCenter credentials
|
||||
$this->client->loginServer($vcenterModel);
|
||||
}
|
||||
|
||||
public function ConfigVirtualMachineUbuntu($vmware_id = '', $product = [], $password = ''): void
|
||||
{
|
||||
$this->setConfig($product->IpAddress->Host->Datacenter->vCenter);
|
||||
$VirtualMachine = "VirtualMachine-$vmware_id";
|
||||
$IPAddress = $product->IpAddress->ip;
|
||||
$Gateway = $product->IpAddress->gateway;
|
||||
$HostName = strtolower($product->hostname);
|
||||
|
||||
try {
|
||||
Log::channel('Custom')->info("===================================================================");
|
||||
Log::channel('Custom')->info("Configuring Ubuntu for VM #ID $VirtualMachine");
|
||||
|
||||
$cmd1 = "\$vm = get-vm -Id '$VirtualMachine';\$hostname ='$HostName'; \$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\createUbuntu1line.ps1;";
|
||||
Log::channel('Custom')->info($cmd1);
|
||||
$this->powerShellService->executePowerCLI($cmd1);
|
||||
|
||||
$cmd2 = "\$vm = get-vm -Id '$VirtualMachine'; \$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\changeLinux.ps1;";
|
||||
Log::channel('Custom')->info($cmd2);
|
||||
$this->powerShellService->executePowerCLI($cmd2);
|
||||
|
||||
Log::channel('Custom')->info("Configuration completed at " . Carbon::now());
|
||||
Log::channel('Custom')->info("===================================================================");
|
||||
} catch (Exception $error) {
|
||||
Log::channel('Custom')->error("Error configuring Ubuntu: " . $error->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function ConfigCentOSVersion8($vmware_id = '', $product = [], $password = ''): void
|
||||
{
|
||||
$this->setConfig($product->IpAddress->Host->Datacenter->vCenter);
|
||||
$VirtualMachine = "VirtualMachine-$vmware_id";
|
||||
$IPAddress = $product->IpAddress->ip;
|
||||
$Gateway = $product->IpAddress->gateway;
|
||||
$HostName = strtolower($product->hostname);
|
||||
|
||||
try {
|
||||
Log::channel('Custom')->info("Configuring CentOS 8 for VM #ID $VirtualMachine");
|
||||
|
||||
$cmd = "\$vm = get-vm -Id '$VirtualMachine';\$hostname ='$HostName'; \$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\createCentOS81line.ps1;";
|
||||
Log::channel('Custom')->info($cmd);
|
||||
$this->powerShellService->executePowerCLI($cmd);
|
||||
|
||||
Log::channel('Custom')->info("Configuration completed at " . Carbon::now());
|
||||
} catch (Exception $error) {
|
||||
Log::channel('Custom')->error("Error configuring CentOS 8: " . $error->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function ConfigCentOSVersion7($vmware_id = '', $product = [], $password = ''): void
|
||||
{
|
||||
$this->setConfig($product->IpAddress->Host->Datacenter->vCenter);
|
||||
$VirtualMachine = "VirtualMachine-$vmware_id";
|
||||
$IPAddress = $product->IpAddress->ip;
|
||||
$Gateway = $product->IpAddress->gateway;
|
||||
$HostName = strtolower($product->hostname);
|
||||
|
||||
try {
|
||||
Log::channel('Custom')->info("Configuring CentOS 7 for VM #ID $VirtualMachine");
|
||||
|
||||
$cmd = "\$vm = get-vm -Id '$VirtualMachine'; \$hostname ='$HostName';\$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\createCentOS71line.ps1;";
|
||||
Log::channel('Custom')->info($cmd);
|
||||
$this->powerShellService->executePowerCLI($cmd);
|
||||
|
||||
Log::channel('Custom')->info("Configuration completed at " . Carbon::now());
|
||||
} catch (Exception $error) {
|
||||
Log::channel('Custom')->error("Error configuring CentOS 7: " . $error->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function ConfigWindowsVersion10($vmware_id = '', $product = [], $password = ''): void
|
||||
{
|
||||
if (!$this->validateProductData($product, 'ConfigWindowsVersion10', $vmware_id)) return;
|
||||
|
||||
$this->setConfig($product->IpAddress->Host->Datacenter->vCenter);
|
||||
$VirtualMachine = "VirtualMachine-$vmware_id";
|
||||
$IPAddress = $product->IpAddress->ip;
|
||||
$Gateway = $product->IpAddress->gateway;
|
||||
$HostName = strtolower($product->hostname);
|
||||
|
||||
try {
|
||||
Log::channel('Custom')->info("Configuring Windows 10 for VM #ID $VirtualMachine");
|
||||
|
||||
$cmd = "\$vm = get-vm -Id '$VirtualMachine';\$hostname ='$HostName'; \$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\changeWindows10.ps1;";
|
||||
Log::channel('Custom')->info($cmd);
|
||||
$this->powerShellService->executePowerCLI($cmd);
|
||||
|
||||
Log::channel('Custom')->info("Configuration completed at " . Carbon::now());
|
||||
} catch (Exception $error) {
|
||||
Log::channel('Custom')->error("Error configuring Windows 10: " . $error->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function ConfigWindowsServer($vmware_id = '', $product = [], $password = ''): void
|
||||
{
|
||||
if (!$this->validateProductData($product, 'ConfigWindowsServer', $vmware_id)) return;
|
||||
|
||||
$this->setConfig($product->IpAddress->Host->Datacenter->vCenter);
|
||||
$VirtualMachine = "VirtualMachine-$vmware_id";
|
||||
$IPAddress = $product->IpAddress->ip;
|
||||
$Gateway = $product->IpAddress->gateway;
|
||||
$HostName = strtolower($product->hostname);
|
||||
|
||||
try {
|
||||
Log::channel('Custom')->info("Configuring Windows Server for VM #ID $VirtualMachine");
|
||||
|
||||
$cmd = "\$vm = get-vm -Id '$VirtualMachine';\$hostname ='$HostName'; \$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\changeWindows1line.ps1;";
|
||||
Log::channel('Custom')->info($cmd);
|
||||
$this->powerShellService->executePowerCLI($cmd);
|
||||
|
||||
Log::channel('Custom')->info("Configuration completed at " . Carbon::now());
|
||||
} catch (Exception $error) {
|
||||
Log::channel('Custom')->error("Error configuring Windows Server: " . $error->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function ConfigFixWindowsClick($vmware_id = '', $product = []): void
|
||||
{
|
||||
if (!$this->validateProductData($product, 'ConfigFixWindowsClick', $vmware_id)) return;
|
||||
|
||||
$this->setConfig($product->IpAddress->Host->Datacenter->vCenter);
|
||||
$VirtualMachine = "VirtualMachine-$vmware_id";
|
||||
|
||||
try {
|
||||
Log::channel('Custom')->info("Fixing Windows Click for VM #ID $VirtualMachine");
|
||||
|
||||
$cmd = "\$vm = get-vm -Id '$VirtualMachine'; cd /; C:\CLISave\FixTwoClickWindows.ps1;";
|
||||
Log::channel('Custom')->info($cmd);
|
||||
$this->powerShellService->executePowerCLI($cmd);
|
||||
|
||||
Log::channel('Custom')->info("Configuration completed at " . Carbon::now());
|
||||
} catch (Exception $error) {
|
||||
Log::channel('Custom')->error("Error fixing Windows Click: " . $error->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function ConfigRockyLinuxVersion9($vmware_id = '', $product = [], $password = ''): void
|
||||
{
|
||||
if (!$this->validateProductData($product, 'ConfigRockyLinuxVersion9', $vmware_id)) return;
|
||||
|
||||
$this->setConfig($product->IpAddress->Host->Datacenter->vCenter);
|
||||
$VirtualMachine = "VirtualMachine-$vmware_id";
|
||||
$IPAddress = $product->IpAddress->ip;
|
||||
$Gateway = $product->IpAddress->gateway;
|
||||
$HostName = strtolower($product->hostname);
|
||||
|
||||
try {
|
||||
Log::channel('Custom')->info("Configuring Rocky Linux 9 for VM #ID $VirtualMachine");
|
||||
|
||||
$cmd = "\$vm = get-vm -Id '$VirtualMachine'; \$hostname ='$HostName';\$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\createRocky1line.ps1;";
|
||||
Log::channel('Custom')->info($cmd);
|
||||
$this->powerShellService->executePowerCLI($cmd);
|
||||
|
||||
Log::channel('Custom')->info("Configuration completed at " . Carbon::now());
|
||||
} catch (Exception $error) {
|
||||
Log::channel('Custom')->error("Error configuring Rocky Linux 9: " . $error->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function validateProductData($product, $methodName, $vmware_id): bool
|
||||
{
|
||||
if (!$product || !$product->IpAddress || !$product->IpAddress->Host || !$product->IpAddress->Host->Datacenter || !$product->IpAddress->Host->Datacenter->vCenter) {
|
||||
Log::error("❌ $methodName: Missing required data", [
|
||||
'vmware_id' => $vmware_id,
|
||||
'product_id' => $product?->id ?? 'unknown',
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Services\vCenter;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use App\Http\Services\Discord;
|
||||
use App\Models\ModuleDatastore;
|
||||
use Exception;
|
||||
|
||||
class VirtualMachineService
|
||||
{
|
||||
protected Client $client;
|
||||
|
||||
public function __construct(Client $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
public function getAllVirtualMachine($datacenter = '')
|
||||
{
|
||||
if ($this->client->checkApiExits()) {
|
||||
$params = [];
|
||||
if (!empty($datacenter)) {
|
||||
$params['filter.datacenters'] = $datacenter;
|
||||
}
|
||||
|
||||
$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
|
||||
]);
|
||||
|
||||
$datastores = ModuleDatastore::where('module_host_id', $host->id)->get();
|
||||
|
||||
$datastoreId = $datastores->isNotEmpty()
|
||||
? $datastores->first()->datastore
|
||||
: 'datastore-3004';
|
||||
|
||||
$body = [
|
||||
'spec' => [
|
||||
'name' => $item->server_name ?? ($userName . '-vm-' . $item->id),
|
||||
'source' => $template,
|
||||
'placement' => [
|
||||
'datacenter' => $datacenter->datacenter_id,
|
||||
'folder' => 'group-v1008',
|
||||
'host' => $host->host_id,
|
||||
'resource_pool' => 'resgroup-3002',
|
||||
'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];
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,11 @@ class SyncDatastore implements ShouldQueue
|
||||
$this->telegram = new Telegram();
|
||||
}
|
||||
|
||||
|
||||
private function jobLog($message, $context = [])
|
||||
{
|
||||
Log::info('[SyncDatastore] ' . $message, $context);
|
||||
}
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
@@ -43,97 +48,112 @@ class SyncDatastore implements ShouldQueue
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
$this->jobLog('JOB START');
|
||||
|
||||
// Initialize services
|
||||
$client = new \App\Http\Services\vCenter\Client();
|
||||
$resourceService = new \App\Http\Services\vCenter\ResourceService($client);
|
||||
|
||||
foreach ($this->data as $item) {
|
||||
|
||||
$this->vCenter->apiUri = $item->host;
|
||||
$this->jobLog('Processing vCenter', [
|
||||
'vcenter_id' => $item->id,
|
||||
'host' => $item->host
|
||||
]);
|
||||
|
||||
if (!$item->token) {
|
||||
$token = $this->vCenter->loginServer($item);
|
||||
if ($token) {
|
||||
vCenter::where('id', $item->id)->update(['token' => $token]);
|
||||
|
||||
$this->vCenter->token = $token;
|
||||
}
|
||||
}
|
||||
$this->vCenter->token = $item->token;
|
||||
|
||||
if (!$this->vCenter->token) {
|
||||
// Login to vCenter
|
||||
// loginServer returns token string on success, false on failure.
|
||||
// It also updates the model instance passed to it.
|
||||
$token = $client->loginServer($item);
|
||||
|
||||
if (!$token) {
|
||||
$this->jobLog('Login failed');
|
||||
continue;
|
||||
}
|
||||
|
||||
$dataCenter = ModuleDatacenter::where('vcenter_id', $item->id)->select('id', 'datacenter_id')->get();
|
||||
$this->jobLog('Login success');
|
||||
|
||||
// ================= DATACENTER =================
|
||||
$dataCenter = ModuleDatacenter::where('vcenter_id', $item->id)
|
||||
->select('id', 'datacenter_id')
|
||||
->get();
|
||||
|
||||
$this->jobLog('Datacenter count', [
|
||||
'count' => $dataCenter->count()
|
||||
]);
|
||||
|
||||
foreach ($dataCenter as $_item) {
|
||||
$allHost = ModuleHost::where('module_datacenter_id', $_item->id)->select('id', 'name')->get();
|
||||
if ($allHost) {
|
||||
foreach ($allHost as $hostItem) {
|
||||
$datastore = $this->vCenter->loadDatastore($item, $hostItem->name);
|
||||
|
||||
if ($datastore === "Login_Again") {
|
||||
$token = $this->vCenter->loginServer($item);
|
||||
if ($token) {
|
||||
vCenter::where('id', $item->id)->update(['token' => $token]);
|
||||
$this->vCenter->token = $token;
|
||||
}
|
||||
$this->jobLog('Processing Datacenter', [
|
||||
'module_datacenter_id' => $_item->id
|
||||
]);
|
||||
|
||||
$allHost = ModuleHost::where('module_datacenter_id', $_item->id)
|
||||
->select('id', 'name', 'host_id')
|
||||
->get();
|
||||
|
||||
foreach ($allHost as $hostItem) {
|
||||
|
||||
$this->jobLog('Processing Host', [
|
||||
'host_id' => $hostItem->id,
|
||||
'host_name' => $hostItem->name,
|
||||
'vcenter_host_id' => $hostItem->host_id
|
||||
]);
|
||||
|
||||
// Use ResourceService to get datastores
|
||||
$datastore = $resourceService->loadDatastore($hostItem->host_id);
|
||||
|
||||
if ($datastore === "Login_Again") {
|
||||
$this->jobLog('Token expired → relogin');
|
||||
$token = $client->loginServer($item);
|
||||
|
||||
if ($token) {
|
||||
// Retry once
|
||||
$datastore = $resourceService->loadDatastore($hostItem->host_id);
|
||||
} else {
|
||||
$this->jobLog('Relogin failed');
|
||||
continue;
|
||||
}
|
||||
if ($datastore) {
|
||||
}
|
||||
|
||||
foreach ($datastore as $_subItem) {
|
||||
if (!$datastore || !is_array($datastore)) {
|
||||
$this->jobLog('No valid datastore returned', ['response' => $datastore]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ModuleDatastore::where('datastore', $_subItem['datastore'])->first()) {
|
||||
$this->jobLog('Datastore count from host', [
|
||||
'count' => count($datastore)
|
||||
]);
|
||||
|
||||
$template = $this->vCenter->getAllDataStore();
|
||||
|
||||
if ($template) {
|
||||
ModuleDatastore::create([
|
||||
'name' => $_subItem['name'],
|
||||
'datastore' => $_subItem['datastore'],
|
||||
'type' => $_subItem['type'],
|
||||
'space' => floor($_subItem['free_space'] / 1073741824),
|
||||
'module_host_id' => $hostItem->id
|
||||
]);
|
||||
ModuleHost::where('id', $hostItem->id)->update(['template' => $template]);
|
||||
} else {
|
||||
ModuleDatastore::create([
|
||||
'name' => $_subItem['name'],
|
||||
'datastore' => $_subItem['datastore'],
|
||||
'type' => $_subItem['type'],
|
||||
'space' => floor($_subItem['free_space'] / 1073741824),
|
||||
'module_host_id' => $hostItem->id
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
foreach ($datastore as $_subItem) {
|
||||
ModuleDatastore::updateOrCreate(
|
||||
[
|
||||
'datastore' => $_subItem['datastore'],
|
||||
'module_host_id' => $hostItem->id,
|
||||
],
|
||||
[
|
||||
'name' => $_subItem['name'],
|
||||
'type' => $_subItem['type'],
|
||||
'space' => floor($_subItem['free_space'] / 1073741824),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Notify success via Telegram
|
||||
$this->telegram->SendJobRunInfo('SyncDatastore', 'Success', [
|
||||
'message' => 'Sync datastore completed successfully',
|
||||
'synced_vcenters' => $this->data->count(),
|
||||
]);
|
||||
|
||||
$this->jobLog('JOB END SUCCESS');
|
||||
|
||||
} catch (\Throwable $exception) {
|
||||
// Ghi log Laravel
|
||||
Log::error('SyncDatastore Job failed', [
|
||||
'error' => $exception->getMessage(),
|
||||
'trace' => $exception->getTraceAsString(),
|
||||
]);
|
||||
|
||||
// Gửi log lỗi qua Telegram
|
||||
$this->telegram->SendJobRunInfo('SyncDatastore', 'Failed', [
|
||||
'error' => $exception->getMessage(),
|
||||
Log::error('SyncDatastore FAILED', [
|
||||
'message' => $exception->getMessage(),
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
'line' => $exception->getLine()
|
||||
]);
|
||||
|
||||
// Quăng lại lỗi để Laravel xử lý retry/failure
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -42,19 +42,16 @@ class SyncHost implements ShouldQueue
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
// Initialize new services
|
||||
$client = new \App\Http\Services\vCenter\Client();
|
||||
$resourceService = new \App\Http\Services\vCenter\ResourceService($client);
|
||||
$templateService = new \App\Http\Services\vCenter\TemplateService($client);
|
||||
|
||||
foreach ($this->data as $item) {
|
||||
$this->vCenter->apiUri = $item->host;
|
||||
// Login via Client service
|
||||
$token = $client->loginServer($item);
|
||||
|
||||
if (!$item->token) {
|
||||
$token = $this->vCenter->loginServer($item);
|
||||
if ($token) {
|
||||
vCenter::where('id', $item->id)->update(['token' => $token]);
|
||||
$this->vCenter->token = $token;
|
||||
}
|
||||
}
|
||||
$this->vCenter->token = $item->token;
|
||||
|
||||
if (!$this->vCenter->token) {
|
||||
if (!$token) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -63,23 +60,26 @@ class SyncHost implements ShouldQueue
|
||||
->get();
|
||||
|
||||
foreach ($dataCenter as $_item) {
|
||||
$host = $this->vCenter->getAllHost($_item['datacenter_id']);
|
||||
// Use ResourceService to get hosts
|
||||
$host = $resourceService->getAllHost($_item['datacenter_id']);
|
||||
|
||||
if ($host === "Login_Again") {
|
||||
$token = $this->vCenter->loginServer($item);
|
||||
$token = $client->loginServer($item);
|
||||
if ($token) {
|
||||
vCenter::where('id', $item->id)->update(['token' => $token]);
|
||||
$this->vCenter->token = $token;
|
||||
$host = $resourceService->getAllHost($_item['datacenter_id']);
|
||||
}
|
||||
}
|
||||
|
||||
$host = $this->vCenter->getAllHost($_item['datacenter_id']);
|
||||
if (!is_array($host)) {
|
||||
continue; // Skip if still invalid or Login_Again
|
||||
}
|
||||
|
||||
$template = $this->vCenter->getAllTemplate();
|
||||
// Use TemplateService
|
||||
$template = $templateService->getAllTemplate();
|
||||
|
||||
if ($host) {
|
||||
foreach ($host as $_subItem) {
|
||||
if (!ModuleHost::where('host_id', $_subItem['host'])->first()) {
|
||||
if (!ModuleHost::where('host_id', $_subItem['host'])->exists()) {
|
||||
ModuleHost::create([
|
||||
'name' => $_subItem['name'],
|
||||
'host_id' => $_subItem['host'],
|
||||
|
||||
@@ -57,39 +57,34 @@ class SyncNetwork implements ShouldQueue
|
||||
|
||||
$dataCenter = ModuleDatacenter::where('vcenter_id', $item->id)->select('id', 'datacenter_id')->get();
|
||||
foreach ($dataCenter as $_item) {
|
||||
$allHost = ModuleHost::where('module_datacenter_id',$_item->id)->select('id','name')->get();
|
||||
if ($allHost)
|
||||
{
|
||||
foreach ($allHost as $hostItem)
|
||||
{
|
||||
$allHost = ModuleHost::where('module_datacenter_id', $_item->id)
|
||||
->select('id', 'name', 'host_id')
|
||||
->get();
|
||||
if ($allHost) {
|
||||
foreach ($allHost as $hostItem) {
|
||||
|
||||
$network = $this->vCenter->loadNetWork($item,$hostItem->name);
|
||||
$network = $this->vCenter->loadNetWork($item, $hostItem->host_id);
|
||||
if ($network === "Login_Again") {
|
||||
$token = $this->vCenter->loginServer($item);
|
||||
if ($token)
|
||||
{
|
||||
vCenter::where('id',$item->id)->update(['token' => $token]);
|
||||
if ($token) {
|
||||
vCenter::where('id', $item->id)->update(['token' => $token]);
|
||||
$this->vCenter->token = $token;
|
||||
$network = $this->vCenter->loadNetWork($item,$hostItem->name);
|
||||
$network = $this->vCenter->loadNetWork($item, $hostItem->host_id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if ($network)
|
||||
{
|
||||
foreach ($network as $_subItem)
|
||||
{
|
||||
if (! ModuleNetwork::where('network',$_subItem['network'])->first())
|
||||
{
|
||||
ModuleNetwork::create([
|
||||
'name' => $_subItem['name'],
|
||||
if ($network) {
|
||||
foreach ($network as $_subItem) {
|
||||
ModuleNetwork::updateOrCreate(
|
||||
[
|
||||
'network' => $_subItem['network'],
|
||||
'type' => $_subItem['type'],
|
||||
'module_host_id' => $hostItem->id
|
||||
]);
|
||||
}
|
||||
|
||||
],
|
||||
[
|
||||
'name' => $_subItem['name'],
|
||||
'type' => $_subItem['type'],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class InfraManagement extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'infra_management';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'host',
|
||||
'username',
|
||||
'password',
|
||||
'token',
|
||||
'type', // e.g. vcenter, esxi
|
||||
'status',
|
||||
'description',
|
||||
'meta_data'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'meta_data' => 'array',
|
||||
'status' => 'boolean',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('infra_management', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name')->nullable();
|
||||
$table->string('host')->unique()->comment('IP or Hostname');
|
||||
$table->string('username');
|
||||
$table->longText('password'); // Should be encrypted
|
||||
$table->text('token')->nullable();
|
||||
$table->string('type')->default('vcenter')->comment('vcenter, esxi, etc.');
|
||||
$table->boolean('status')->default(true);
|
||||
$table->text('description')->nullable();
|
||||
$table->json('meta_data')->nullable(); // For extra config
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('infra_management');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\InfraManagement;
|
||||
use App\Http\Facade\SignRSA;
|
||||
|
||||
class InfraManagementSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
InfraManagement::updateOrCreate(
|
||||
['host' => '10.10.10.173'],
|
||||
[
|
||||
'name' => 'Jumphost 173',
|
||||
'username' => 'administrator',
|
||||
'password' => SignRSA::EncryptFunc('Thegioiso@123**$$8899TgTk2'),
|
||||
'type' => 'jumphost',
|
||||
'status' => true,
|
||||
'meta_data' => [
|
||||
'port' => 22
|
||||
],
|
||||
'description' => 'Main Jumphost for vCenter PowerCLI operations'
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class ProvinceWardSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$path = database_path('province_ward_import.sql');
|
||||
|
||||
if (File::exists($path)) {
|
||||
// Read and execute the SQL file
|
||||
$sql = File::get($path);
|
||||
|
||||
// Execute the SQL commands
|
||||
DB::unprepared($sql);
|
||||
|
||||
$this->command->info('Province/Ward data imported successfully from SQL file.');
|
||||
} else {
|
||||
$this->command->error('File database/province_ward_import.sql not found.');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user