feat: VM metrics via portal-monitor integration

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
baodev0
2026-10-17 13:04:35 +07:00
co-authored by Claude Sonnet 5.5
parent 0b08689ea4
commit 9ed77a85fd
15 changed files with 546 additions and 43 deletions
@@ -0,0 +1,48 @@
<?php
namespace App\Http\Controllers\Api\Internal;
use App\Http\Controllers\Controller;
use App\Http\Facade\SignRSA;
use App\Models\vCenter;
use Illuminate\Http\JsonResponse;
class PortalMonitorVCenterController extends Controller
{
public function __invoke(): JsonResponse
{
$vcenter = vCenter::query()
->where('status', 1)
->orderBy('id')
->first();
if (!$vcenter) {
return response()->json([
'error' => true,
'message' => 'Active vCenter configuration is unavailable',
], 503);
}
$username = trim((string) $vcenter->user);
$password = SignRSA::DecryptFunc($vcenter->password);
$host = trim((string) $vcenter->host);
$port = 443;
if ($host === '' || $port < 1 || $username === '' || $password === '') {
return response()->json([
'error' => true,
'message' => 'Active vCenter configuration is incomplete',
], 503);
}
return response()->json([
'success' => true,
'data' => [
'host' => $host,
'port' => $port,
'username' => $username,
'password' => $password,
],
])->header('Cache-Control', 'no-store, private');
}
}
@@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers\Api\Service\Concerns;
use App\Models\VirtualMachines;
use App\Services\VmMetricsService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use RuntimeException;
trait ManagesVmMetrics
{
public function metrics(Request $request, VmMetricsService $metricsService): JsonResponse
{
$productId = $request->input('product');
if (!$productId) {
return response()->json(['success' => false, 'message' => 'Thiếu product id.'], 422);
}
$vm = $this->resolveVmById($productId, $request, requireActiveService: false);
if ($vm instanceof JsonResponse) {
return $vm;
}
try {
$inventory = VirtualMachines::where('vm_id', $vm->vmware_id)->first(['ram_mb', 'cpu']);
$metrics = $metricsService->get((string) $vm->vmware_id, $inventory?->ram_mb, $inventory?->cpu);
} catch (RuntimeException $exception) {
$status = $exception->getCode() === 404 ? 404 : 502;
return response()->json([
'success' => false,
'message' => $status === 404
? 'Chưa có dữ liệu giám sát cho VM.'
: 'Không thể tải dữ liệu giám sát VM.',
], $status);
}
return response()->json(['success' => true, 'data' => $metrics]);
}
}
@@ -12,6 +12,7 @@ use Illuminate\Support\Facades\Cache;
use App\Http\Controllers\Api\Service\Concerns\ManagesVmPower;
use App\Http\Controllers\Api\Service\Concerns\ManagesVmNetwork;
use App\Http\Controllers\Api\Service\Concerns\ManagesVmAccess;
use App\Http\Controllers\Api\Service\Concerns\ManagesVmMetrics;
class VmController extends Controller
{
@@ -19,6 +20,7 @@ class VmController extends Controller
use ManagesVmPower;
use ManagesVmNetwork;
use ManagesVmAccess;
use ManagesVmMetrics;
public function __construct(protected ActivityLogger $activityLogger) {}
// -------------------------------------------------------------------------
@@ -6,12 +6,18 @@ 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');
@@ -264,54 +270,22 @@ class VCenterManagerController extends Controller
return response()->json(['error' => true, 'message' => 'VM is not powered on'], 400);
}
$vcenter = $vm->datacenter?->vCenter ?? $vm->host?->datacenter?->vCenter;
try {
$stats = $this->vmMetrics->get($vmwareId, $vm->ram_mb, $vm->cpu);
} catch (RuntimeException $exception) {
$status = $exception->getCode() === 404 ? 404 : 502;
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);
}
$result = $vCenterService->GetVMStats($vmwareId);
if ($result['error'] ?? true) {
return response()->json([
'error' => true,
'message' => $result['message'] ?? 'Failed to fetch VM stats',
], 400);
'message' => $status === 404
? 'VM metrics are unavailable'
: 'Unable to fetch VM metrics',
], $status);
}
$stats = $result['data'];
$cpuPercent = $stats['cpu']['used_percent'] ?? 0;
$memPercent = $stats['memory']['usage_percent'] ?? 0;
// ponytail: overall_status is a CPU/RAM usage threshold heuristic, not real vCenter
// health/alarms — that needs the SOAP EventManager/AlarmManager, not wired up in this
// codebase (REST Automation API only). Upgrade path: add a SOAP client and read
// triggeredAlarmState off the VM's ManagedEntity if per-alarm detail is ever needed.
$overallStatus = match (true) {
$cpuPercent >= 90 || $memPercent >= 90 => 'red',
$cpuPercent >= 75 || $memPercent >= 75 => 'yellow',
default => 'green',
};
return response()->json([
'error' => false,
'data' => [
'overall_status' => $overallStatus,
'cpu_usage_percent' => round($cpuPercent, 1),
'mem_usage_percent' => round($memPercent, 1),
'mem_usage_mb' => $stats['memory']['used_MiB'] ?? 0,
'mem_total_mb' => $stats['memory']['size_MiB'] ?? 0,
// ponytail: no uptime source wired up (would need SOAP PerfManager
// sys.uptime.latest); frontend renders "—" when null.
'uptime_seconds' => null,
'alarms' => [],
],
'data' => $stats,
]);
}