463 lines
16 KiB
PHP
463 lines
16 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\admin;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Mail\VmSuspendedMail;
|
|
use App\Models\ModuleVmware;
|
|
use App\Models\VirtualMachines;
|
|
use App\Services\VmMetricsService;
|
|
use Illuminate\Http\Request;
|
|
use App\Support\TlsHttpClientFactory;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use RuntimeException;
|
|
|
|
class VCenterManagerController extends Controller
|
|
{
|
|
public function __construct(private readonly VmMetricsService $vmMetrics)
|
|
{
|
|
}
|
|
|
|
public function powerState(Request $request)
|
|
{
|
|
$vmId = (int) $request->input('vm_id');
|
|
|
|
$vm = VirtualMachines::find($vmId);
|
|
|
|
if (!$vm) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
return response()->json(['error' => false, 'data' => ['power_state' => $vm->power_state]]);
|
|
}
|
|
|
|
public function powerOn(Request $request)
|
|
{
|
|
$vmId = (int) $request->input('vm_id');
|
|
|
|
$vm = VirtualMachines::find($vmId);
|
|
|
|
if (!$vm) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
$vmwareId = $vm->vm_id;
|
|
|
|
if (!$vmwareId) {
|
|
return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400);
|
|
}
|
|
|
|
if ($vm->power_state === 'POWERED_ON') {
|
|
return response()->json(['error' => true, 'message' => 'VM is already powered on'], 400);
|
|
}
|
|
|
|
$vcenter = null;
|
|
|
|
if ($vm->datacenter?->vCenter) {
|
|
$vcenter = $vm->datacenter->vCenter;
|
|
}
|
|
|
|
if (!$vcenter && $vm->host?->datacenter?->vCenter) {
|
|
$vcenter = $vm->host->datacenter->vCenter;
|
|
}
|
|
|
|
if (!$vcenter) {
|
|
return response()->json(['error' => true, 'message' => 'vCenter not found'], 400);
|
|
}
|
|
|
|
$vCenterService = new \App\Http\Services\vCenter();
|
|
|
|
if (!$vCenterService->loginServer($vcenter)) {
|
|
return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400);
|
|
}
|
|
|
|
$response = TlsHttpClientFactory::vCenter()
|
|
->withHeaders([
|
|
'vmware-api-session-id' => $vCenterService->token
|
|
])
|
|
->post("https://{$vCenterService->apiUri}/rest/vcenter/vm/$vmwareId/power/start");
|
|
|
|
if ($response->status() === 200) {
|
|
$vm->update(['power_state' => 'POWERED_ON']);
|
|
return response()->json(['error' => false, 'message' => 'Power on command sent successfully']);
|
|
}
|
|
|
|
$errorData = $response->json();
|
|
if (isset($errorData['error_type']) && $errorData['error_type'] === 'ALREADY_IN_DESIRED_STATE') {
|
|
$vm->update(['power_state' => 'POWERED_ON']);
|
|
return response()->json(['error' => false, 'message' => 'VM is already powered on']);
|
|
}
|
|
|
|
return response()->json(['error' => true, 'message' => $errorData['value'] ?? $errorData['error_type'] ?? 'Failed to power on VM'], 400);
|
|
}
|
|
|
|
public function powerOff(Request $request)
|
|
{
|
|
$vmId = (int) $request->input('vm_id');
|
|
|
|
$vm = VirtualMachines::find($vmId);
|
|
|
|
if (!$vm) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
$vmwareId = $vm->vm_id;
|
|
|
|
if (!$vmwareId) {
|
|
return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400);
|
|
}
|
|
|
|
if ($vm->power_state === 'POWERED_OFF') {
|
|
return response()->json(['error' => true, 'message' => 'VM is already powered off'], 400);
|
|
}
|
|
|
|
$vcenter = null;
|
|
|
|
if ($vm->datacenter?->vCenter) {
|
|
$vcenter = $vm->datacenter->vCenter;
|
|
}
|
|
|
|
if (!$vcenter && $vm->host?->datacenter?->vCenter) {
|
|
$vcenter = $vm->host->datacenter->vCenter;
|
|
}
|
|
|
|
if (!$vcenter) {
|
|
return response()->json(['error' => true, 'message' => 'vCenter not found'], 400);
|
|
}
|
|
|
|
$vCenterService = new \App\Http\Services\vCenter();
|
|
|
|
if (!$vCenterService->loginServer($vcenter)) {
|
|
return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400);
|
|
}
|
|
|
|
$response = TlsHttpClientFactory::vCenter()
|
|
->withHeaders([
|
|
'vmware-api-session-id' => $vCenterService->token
|
|
])
|
|
->post("https://{$vCenterService->apiUri}/rest/vcenter/vm/$vmwareId/power/stop");
|
|
|
|
if ($response->status() === 200) {
|
|
$vm->update(['power_state' => 'POWERED_OFF']);
|
|
return response()->json(['error' => false, 'message' => 'Power off command sent successfully']);
|
|
}
|
|
|
|
$errorData = $response->json();
|
|
if (isset($errorData['error_type']) && $errorData['error_type'] === 'ALREADY_IN_DESIRED_STATE') {
|
|
$vm->update(['power_state' => 'POWERED_OFF']);
|
|
return response()->json(['error' => false, 'message' => 'VM is already powered off']);
|
|
}
|
|
|
|
return response()->json(['error' => true, 'message' => $errorData['value'] ?? $errorData['error_type'] ?? 'Failed to power off VM'], 400);
|
|
}
|
|
|
|
public function restart(Request $request)
|
|
{
|
|
$vmId = (int) $request->input('vm_id');
|
|
|
|
$vm = VirtualMachines::find($vmId);
|
|
|
|
if (!$vm) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
$vmwareId = $vm->vm_id;
|
|
|
|
if (!$vmwareId) {
|
|
return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400);
|
|
}
|
|
|
|
$vcenter = $vm->datacenter?->vCenter ?? $vm->host?->datacenter?->vCenter;
|
|
|
|
if (!$vcenter) {
|
|
return response()->json(['error' => true, 'message' => 'vCenter not found'], 400);
|
|
}
|
|
|
|
$vCenterService = new \App\Http\Services\vCenter();
|
|
|
|
if (!$vCenterService->loginServer($vcenter)) {
|
|
return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400);
|
|
}
|
|
|
|
$response = TlsHttpClientFactory::vCenter()
|
|
->withHeaders(['vmware-api-session-id' => $vCenterService->token])
|
|
->post("https://{$vCenterService->apiUri}/rest/vcenter/vm/$vmwareId/power/reset");
|
|
|
|
if ($response->status() === 200) {
|
|
return response()->json(['error' => false, 'message' => 'Restart command sent successfully']);
|
|
}
|
|
|
|
$errorData = $response->json();
|
|
return response()->json([
|
|
'error' => true,
|
|
'message' => $errorData['value'] ?? $errorData['error_type'] ?? 'Failed to restart VM',
|
|
], 400);
|
|
}
|
|
|
|
public function snapshot(Request $request)
|
|
{
|
|
$request->validate([
|
|
'vm_id' => 'required|integer',
|
|
'name' => 'required|string|max:255',
|
|
'description' => 'nullable|string|max:1000',
|
|
]);
|
|
|
|
$vmId = (int) $request->input('vm_id');
|
|
|
|
$vm = VirtualMachines::find($vmId);
|
|
|
|
if (!$vm) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
$vmwareId = $vm->vm_id;
|
|
|
|
if (!$vmwareId) {
|
|
return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400);
|
|
}
|
|
|
|
$vcenter = $vm->datacenter?->vCenter ?? $vm->host?->datacenter?->vCenter;
|
|
|
|
if (!$vcenter) {
|
|
return response()->json(['error' => true, 'message' => 'vCenter not found'], 400);
|
|
}
|
|
|
|
$vCenterService = new \App\Http\Services\vCenter();
|
|
|
|
if (!$vCenterService->loginServer($vcenter)) {
|
|
return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400);
|
|
}
|
|
|
|
$response = TlsHttpClientFactory::vCenter()
|
|
->withHeaders(['vmware-api-session-id' => $vCenterService->token])
|
|
->post("https://{$vCenterService->apiUri}/rest/vcenter/vm/$vmwareId/snapshot", [
|
|
'spec' => [
|
|
'name' => $request->input('name'),
|
|
'description' => $request->input('description', ''),
|
|
'memory' => false,
|
|
'quiesce' => false,
|
|
],
|
|
]);
|
|
|
|
if ($response->status() === 200) {
|
|
return response()->json(['error' => false, 'message' => 'Snapshot created successfully']);
|
|
}
|
|
|
|
$errorData = $response->json();
|
|
return response()->json([
|
|
'error' => true,
|
|
'message' => $errorData['value'] ?? $errorData['error_type'] ?? 'Failed to create snapshot',
|
|
], 400);
|
|
}
|
|
|
|
public function stats(Request $request)
|
|
{
|
|
$vmId = (int) $request->input('vm_id');
|
|
|
|
$vm = VirtualMachines::find($vmId);
|
|
|
|
if (!$vm) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
$vmwareId = $vm->vm_id;
|
|
|
|
if (!$vmwareId) {
|
|
return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400);
|
|
}
|
|
|
|
if ($vm->power_state !== 'POWERED_ON') {
|
|
return response()->json(['error' => true, 'message' => 'VM is not powered on'], 400);
|
|
}
|
|
|
|
try {
|
|
$stats = $this->vmMetrics->get($vmwareId, $vm->ram_mb, $vm->cpu);
|
|
} catch (RuntimeException $exception) {
|
|
$status = $exception->getCode() === 404 ? 404 : 502;
|
|
|
|
return response()->json([
|
|
'error' => true,
|
|
'message' => $status === 404
|
|
? 'VM metrics are unavailable'
|
|
: 'Unable to fetch VM metrics',
|
|
], $status);
|
|
}
|
|
|
|
return response()->json([
|
|
'error' => false,
|
|
'data' => $stats,
|
|
]);
|
|
}
|
|
|
|
public function getPoweredOffVms()
|
|
{
|
|
$vms = VirtualMachines::whereNotNull('vm_id')
|
|
->where('power_state', 'POWERED_OFF')
|
|
->get()
|
|
->map(function ($vm) {
|
|
return [
|
|
'id' => $vm->id,
|
|
'name' => $vm->name,
|
|
'vm_id' => $vm->vm_id,
|
|
'power_state' => $vm->power_state,
|
|
];
|
|
});
|
|
|
|
return response()->json([
|
|
'error' => false,
|
|
'data' => $vms
|
|
]);
|
|
}
|
|
|
|
public function suspend(Request $request)
|
|
{
|
|
$vmId = (int) $request->input('vm_id');
|
|
|
|
// vm_id from frontend = services.vm_id = module_vmware.id (FK)
|
|
$vmware = ModuleVmware::with('vCenter')->find($vmId);
|
|
|
|
if (!$vmware) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
$vmwareId = $vmware->vmware_id;
|
|
|
|
if (!$vmwareId) {
|
|
return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400);
|
|
}
|
|
|
|
$vm = VirtualMachines::where('vm_id', $vmwareId)->first();
|
|
|
|
if ($vm?->power_state === 'SUSPENDED') {
|
|
return response()->json(['error' => true, 'message' => 'VM is already suspended'], 400);
|
|
}
|
|
|
|
$vcenter = $vmware->vCenter;
|
|
|
|
if (!$vcenter) {
|
|
return response()->json(['error' => true, 'message' => 'vCenter not found'], 400);
|
|
}
|
|
|
|
$vCenterService = new \App\Http\Services\vCenter();
|
|
|
|
if (!$vCenterService->loginServer($vcenter)) {
|
|
return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400);
|
|
}
|
|
|
|
$response = TlsHttpClientFactory::vCenter()
|
|
->withHeaders(['vmware-api-session-id' => $vCenterService->token])
|
|
->post("https://{$vCenterService->apiUri}/rest/vcenter/vm/$vmwareId/power/suspend");
|
|
|
|
if ($response->status() === 200) {
|
|
$vm?->update(['power_state' => 'SUSPENDED']);
|
|
$this->sendSuspendNotification($vmware, $vm);
|
|
return response()->json(['error' => false, 'message' => 'Suspend command sent successfully']);
|
|
}
|
|
|
|
$errorData = $response->json();
|
|
if (isset($errorData['error_type']) && $errorData['error_type'] === 'ALREADY_IN_DESIRED_STATE') {
|
|
$vm?->update(['power_state' => 'SUSPENDED']);
|
|
$this->sendSuspendNotification($vmware, $vm);
|
|
return response()->json(['error' => false, 'message' => 'VM is already suspended']);
|
|
}
|
|
|
|
return response()->json([
|
|
'error' => true,
|
|
'message' => $errorData['value'] ?? $errorData['error_type'] ?? 'Failed to suspend VM',
|
|
], 400);
|
|
}
|
|
|
|
public function updateConfig(Request $request)
|
|
{
|
|
$request->validate([
|
|
'vm_name' => 'required|string',
|
|
'cpu' => 'required|integer|min:1',
|
|
'ram' => 'required|integer|min:1',
|
|
]);
|
|
|
|
$vmware = ModuleVmware::where('name', $request->input('vm_name'))->first();
|
|
|
|
if (!$vmware) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
\App\Jobs\VirtualMachine\Provision\UpdateVmConfigJob::dispatch(
|
|
$vmware->id,
|
|
(string) $vmware->vmware_id,
|
|
'',
|
|
(int) $request->input('cpu'),
|
|
(int) $request->input('ram'),
|
|
);
|
|
|
|
return response()->json(['error' => false, 'message' => 'Đã gửi yêu cầu cập nhật cấu hình VM']);
|
|
}
|
|
|
|
public function deleteVM(Request $request)
|
|
{
|
|
$request->validate([
|
|
'vm_id' => 'required|integer',
|
|
'confirm_name' => 'required|string',
|
|
]);
|
|
|
|
$vmId = (int) $request->input('vm_id');
|
|
$vm = VirtualMachines::find($vmId);
|
|
|
|
if (!$vm) {
|
|
return response()->json(['error' => true, 'message' => 'VM not found'], 404);
|
|
}
|
|
|
|
if ($request->input('confirm_name') !== $vm->name) {
|
|
return response()->json(['error' => true, 'message' => "Tên xác nhận không khớp. Vui lòng nhập đúng: {$vm->name}"], 422);
|
|
}
|
|
|
|
if ($vm->power_state !== 'POWERED_OFF') {
|
|
return response()->json(['error' => true, 'message' => 'Vui lòng tắt máy ảo trước khi xoá'], 400);
|
|
}
|
|
|
|
$vcenter = $vm->datacenter?->vCenter ?? $vm->host?->datacenter?->vCenter;
|
|
|
|
if (!$vcenter) {
|
|
return response()->json(['error' => true, 'message' => 'vCenter not found'], 400);
|
|
}
|
|
|
|
$powerShellService = app(\App\Http\Services\vCenter\PowerShellService::class);
|
|
|
|
$q = fn(string $v) => str_replace("'", "''", $v);
|
|
$vmName = $vm->name;
|
|
$host = $vcenter->host;
|
|
$user = $vcenter->user;
|
|
$password = \App\Http\Facade\SignRSA::DecryptFunc($vcenter->password);
|
|
|
|
$command = "& 'C:\\CLISave\\DeleteVM\\deleteVM.ps1' -VmName '" . $q($vmName) . "' -VCenterHost '" . $q($host) . "' -VCenterUser '" . $q($user) . "' -VCenterPassword '" . $q($password) . "'";
|
|
$cmdResult = $powerShellService->runPsCommand($command);
|
|
|
|
if (strpos($cmdResult, 'Login_Fail') !== false) {
|
|
return response()->json(['error' => true, 'message' => 'SSH Login to PowerShell server failed'], 400);
|
|
}
|
|
|
|
if (strpos($cmdResult, 'SUCCESS') === false) {
|
|
return response()->json(['error' => true, 'message' => 'Xoá VM thất bại: ' . $cmdResult], 400);
|
|
}
|
|
|
|
$vm->delete();
|
|
|
|
return response()->json(['error' => false, 'message' => 'VM đã được xoá thành công']);
|
|
}
|
|
|
|
private function sendSuspendNotification(ModuleVmware $vmware, ?VirtualMachines $vm = null): void
|
|
{
|
|
$vmware->loadMissing('customer');
|
|
|
|
if (!$vmware->customer || !$vmware->customer->email) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
Mail::to($vmware->customer->email)
|
|
->queue(new VmSuspendedMail($vmware->customer, $vm?->name ?? $vmware->name, $vmware->vmware_id));
|
|
} catch (\Throwable) {
|
|
// Email failure must not block the suspend response
|
|
}
|
|
}
|
|
}
|