Files
portal-api/app/Http/Controllers/Api/Service/VmController.php
T
pbao9 bb73d6c5a2 Refactor vCenter snapshot handling and VM operations
- Introduced a new method `resolveVCenterFromProduct` in `SnapshotService` to streamline vCenter resolution from product data.
- Updated `CreateSnapshot`, `RemoveSnapshot`, and `RevertSnapshot` jobs to utilize the new vCenter resolution method.
- Enhanced the `VmController` to support UUID-based service identification for VM operations.
- Added new actions for changing VM IP and RDP port, encapsulating the logic for these operations.
- Improved error handling and logging for VM operations, including detailed responses for API consumers.
- Removed unnecessary relationships in model queries to optimize performance.
2026-04-15 23:38:38 +07:00

292 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Http\Controllers\Api\Service;
use App\Actions\VM\ChangeIpVMAction;
use App\Actions\VM\ChangePortVMAction;
use App\Actions\VM\PowerOffVMAction;
use App\Actions\VM\PowerOnVMAction;
use App\Http\Controllers\Controller;
use App\Http\Services\Telegram;
use App\Models\ModuleVmware;
use App\Models\Service;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class VmController extends Controller
{
// -------------------------------------------------------------------------
// Power ON
// -------------------------------------------------------------------------
/**
* POST /v1/system/vm-power-on
*
* Body: { "product": <ModuleVmware.id> }
*/
public function powerOn(Request $request, PowerOnVMAction $action): JsonResponse
{
$validated = $request->validate(['product' => ['required', 'integer']]);
$vm = $this->resolveVm((int) $validated['product'], $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
$result = $action->execute($vm);
return $this->vmResponse($result, 'Đã gửi lệnh bật VM.');
}
// -------------------------------------------------------------------------
// Power OFF
// -------------------------------------------------------------------------
/**
* POST /v1/system/vm-power-off
*
* Body: { "product": <ModuleVmware.id> }
*/
public function powerOff(Request $request, PowerOffVMAction $action): JsonResponse
{
$validated = $request->validate(['product' => ['required', 'integer']]);
$vm = $this->resolveVm((int) $validated['product'], $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
$result = $action->execute($vm);
return $this->vmResponse($result, 'Đã gửi lệnh tắt VM.');
}
// -------------------------------------------------------------------------
// Change IP
// -------------------------------------------------------------------------
/**
* POST /v1/system/vm-change-ip
*
* Body:
* server_name string Service.server_name (e.g. "xasdf.com.vnx")
* ip_address string New IP to assign
* gateway string (optional) Gateway — falls back to existing IP record
*/
public function changeIp(Request $request, ChangeIpVMAction $action): JsonResponse
{
$validated = $request->validate([
'server_name' => ['required', 'string', 'max:255'],
'ip_address' => ['required', 'ip'],
'gateway' => ['nullable', 'ip'],
'dns' => ['nullable', 'ip'],
'guest_user' => ['required', 'string', 'max:255'],
'guest_password' => ['required', 'string', 'max:255'],
]);
$customer = $request->user();
// Look up Service by server_name + owner
// VM chain: Service → orderItem → order → vmWare (same as ServiceClientDetailResource)
$service = Service::with([
'orderItem.order.vmWare.vCenter',
'orderItem.order.vmWare.ipAddress.host.datacenter.vCenter',
'orderItem.os.osGroup',
])
->where('server_name', $validated['server_name'])
->where('customer_id', $customer->id)
->first();
if (!$service) {
return response()->json(['success' => false, 'message' => 'Không tìm thấy dịch vụ.'], 404);
}
$vm = $service->orderItem?->order?->vmWare?->first();
if (!$vm) {
return response()->json(['success' => false, 'message' => 'Không tìm thấy VM cho dịch vụ này.'], 404);
}
// Auto-detect OS type from the service's order item OS group
$osGroup = strtolower($service->orderItem?->os?->osGroup?->name ?? '');
$osType = (str_contains($osGroup, 'linux') || str_contains($osGroup, 'ubuntu') || str_contains($osGroup, 'centos'))
? 'linux'
: 'windows';
$gateway = $validated['gateway'] ?? '';
$dns = $validated['dns'] ?? '8.8.8.8';
$raw = $action->execute(
$vm,
$validated['ip_address'],
$gateway,
$osType,
$dns,
$validated['guest_user'],
$validated['guest_password']
);
// Always log the raw PowerShell output for debugging
Log::channel('Custom')->info('[changeIp] raw output', [
'server_name' => $validated['server_name'],
'ip_address' => $validated['ip_address'],
'gateway' => $gateway,
'os_type' => $osType,
'vm_name' => $vm->name,
'vmware_id' => $vm->vmware_id,
'raw' => $raw,
]);
$telegram = new Telegram();
if ($raw === 'Login_Again' || $raw === 'vCenter_Not_Found') {
$errMsg = $raw === 'vCenter_Not_Found'
? 'Không tìm thấy vCenter cho VM này.'
: 'Không thể đăng nhập vào vCenter.';
$telegram->SendErrorMessage("❌ Đổi IP thất bại [{$osType}]\n"
. "🖥 VM: {$vm->name} ({$vm->vmware_id})\n"
. "📍 IP mới: {$validated['ip_address']}\n"
. "🔗 GW: {$gateway}\n"
. "🚫 Lỗi: {$errMsg}");
return response()->json(['success' => false, 'message' => $errMsg], 400);
}
if (str_starts_with((string) $raw, 'Error:') || str_contains((string) $raw, '[STDERR]')) {
$telegram->SendErrorMessage("❌ Đổi IP thất bại [{$osType}]\n"
. "🖥 VM: {$vm->name} ({$vm->vmware_id})\n"
. "📍 IP mới: {$validated['ip_address']}\n"
. "🔗 GW: {$gateway}\n"
. "📋 Output:\n" . mb_substr((string) $raw, 0, 800));
return response()->json(['success' => false, 'message' => 'Lệnh đổi IP thất bại. Admin đã được thông báo.', 'data' => ['raw' => $raw]], 400);
}
// Empty raw = script chạy không có output → coi là thành công (PowerShell script không echo)
// Nhưng vẫn gửi cảnh báo để theo dõi
if (trim((string) $raw) === '') {
$telegram->SendMessage("⚠️ Đổi IP [{$osType}] - Script chạy nhưng không có output\n"
. "🖥 VM: {$vm->name} ({$vm->vmware_id})\n"
. "📍 IP mới: {$validated['ip_address']}\n"
. "🔗 GW: {$gateway}\n"
. "ℹ️ Kiểm tra lại VM để xác nhận IP đã thay đổi chưa.");
}
return response()->json([
'success' => true,
'message' => 'Đã gửi lệnh đổi IP.',
'data' => ['raw' => $raw],
]);
}
// -------------------------------------------------------------------------
// Change Port (RDP)
// -------------------------------------------------------------------------
/**
* POST /v1/system/vm-change-port
*
* Body:
* product int ModuleVmware.id
* new_port int New RDP port (1024–65535)
*/
public function changePort(Request $request, ChangePortVMAction $action): JsonResponse
{
$validated = $request->validate([
'product' => ['required', 'integer'],
'new_port' => ['required', 'integer', 'min:1024', 'max:65535'],
]);
$vm = $this->resolveVm((int) $validated['product'], $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
$result = $action->execute($vm, (string) $validated['new_port']);
// changeRDPPortWindows returns array on success/failure or string on login fail
if ($result === 'Login_Again' || $result === 'Login_Fail') {
return response()->json([
'success' => false,
'message' => 'Không thể đăng nhập vào vCenter.',
], 400);
}
if (is_array($result)) {
$ok = isset($result['success'])
? (bool) $result['success']
: !($result['error'] ?? true);
return response()->json([
'success' => $ok,
'message' => $ok ? 'Đổi cổng RDP thành công.' : ($result['message'] ?? 'Đổi cổng thất bại.'),
'data' => $result,
], $ok ? 200 : 400);
}
return response()->json([
'success' => false,
'message' => 'Phản hồi không hợp lệ từ vCenter.',
'data' => ['raw' => $result],
], 400);
}
// -------------------------------------------------------------------------
// Shared helpers
// -------------------------------------------------------------------------
/**
* Load a ModuleVmware by ID, verify the authenticated user owns it.
* Returns JsonResponse on error, ModuleVmware on success.
*/
private function resolveVm(int $id, Request $request): ModuleVmware|JsonResponse
{
$vm = ModuleVmware::with(['vCenter', 'ipAddress.host.datacenter.vCenter'])
->find($id);
if (!$vm) {
return response()->json(['success' => false, 'message' => 'Không tìm thấy VM.'], 404);
}
if ((int) $vm->customer_id !== (int) $request->user()->id) {
return response()->json(['success' => false, 'message' => 'Bạn không có quyền thao tác VM này.'], 403);
}
return $vm;
}
/**
* Normalise the array|string return value from Power ON/OFF actions.
*/
private function vmResponse(array|string $result, string $successMessage): JsonResponse
{
if ($result === 'Login_Again') {
return response()->json([
'success' => false,
'message' => 'Không thể đăng nhập vào vCenter.',
], 400);
}
if (is_string($result)) {
return response()->json(['success' => false, 'message' => $result], 400);
}
if ($result['error'] ?? true) {
return response()->json([
'success' => false,
'message' => $result['data'] ?? 'Thao tác thất bại.',
'data' => $result,
], 400);
}
return response()->json([
'success' => true,
'message' => $successMessage,
'data' => $result['data'] ?? 'success',
]);
}
}