Files
portal-api/app/Http/Controllers/Api/Service/Concerns/ManagesVmNetwork.php
T
2026-10-08 11:25:28 +07:00

244 lines
8.6 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\Concerns;
use App\Actions\VM\ChangePortVMAction;
use App\Http\Services\vCenter;
use App\Jobs\VirtualMachine\ChangeIpVMJob;
use App\Models\IpPool;
use App\Models\ModuleVmware;
use App\Models\Service;
use App\Services\VirtualMachine\VmIpReservationService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
trait ManagesVmNetwork
{
/**
* GET /v1/system/vm-ip-options
*
* Query: server_name string Service.server_name
* Lists free IP pool entries in the same network as the VM's current IP.
*/
public function ipOptions(Request $request): JsonResponse
{
$validated = $request->validate([
'server_name' => ['required', 'string', 'max:255'],
]);
$service = $this->findOwnedServiceByServerName($validated['server_name'], $request);
if ($service instanceof JsonResponse) {
return $service;
}
$vm = $service->resolveVmware();
if (!$vm) {
return response()->json(['success' => false, 'message' => 'Không tìm thấy VM cho dịch vụ này.'], 404);
}
$networkId = $vm->ipAddress?->module_network_id;
if (!$networkId) {
return response()->json(['success' => false, 'message' => 'Không xác định được mạng của VM.'], 422);
}
$ips = app(VmIpReservationService::class)->freeIpsInNetwork($networkId);
return response()->json([
'success' => true,
'data' => $ips->map(fn (IpPool $ip) => [
'ip_address' => $ip->ip_address,
'gateway' => $ip->gateway,
])->values(),
]);
}
/**
* 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 — must be a free IP from the VM's network pool
*/
/**
* Dispatches ChangeIpVMJob — returns immediately, runs async.
*/
public function changeIp(Request $request, VmIpReservationService $ipReservation): JsonResponse
{
$validated = $request->validate([
'server_name' => ['required', 'string', 'max:255'],
'ip_address' => ['required', 'ip'],
'dns' => ['nullable', 'ip'],
'guest_user' => ['required', 'string', 'max:255'],
'guest_password' => ['required', 'string', 'max:255'],
]);
$service = $this->findOwnedServiceByServerName($validated['server_name'], $request);
if ($service instanceof JsonResponse) {
return $service;
}
$vm = $service->resolveVmware();
if (!$vm) {
return response()->json(['success' => false, 'message' => 'Không tìm thấy VM cho dịch vụ này.'], 404);
}
$networkId = $vm->ipAddress?->module_network_id;
if (!$networkId) {
return response()->json(['success' => false, 'message' => 'Không xác định được mạng của VM.'], 422);
}
$reservation = $ipReservation->reserveInNetwork($networkId, $validated['ip_address'], $service->id);
if (!$reservation) {
return response()->json(['success' => false, 'message' => 'IP không thuộc pool khả dụng hoặc đã được sử dụng.'], 422);
}
$oldIpPoolId = IpPool::where('module_network_id', $networkId)
->where('service_id', $service->id)
->where('status', 'assigned')
->where('id', '!=', $reservation['ip_pool_id'])
->value('id');
// 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';
$dns = $validated['dns'] ?? '8.8.8.8';
// ponytail: don't release — lock expires after 5 min while the async job runs
$lock = $this->acquireVmLock($vm, 300);
if (!$lock) {
$ipReservation->releaseByPoolId($reservation['ip_pool_id']);
return $this->vmLocked();
}
ChangeIpVMJob::dispatch(
$vm->id,
$validated['ip_address'],
$reservation['gateway'],
$osType,
$dns,
$validated['guest_user'],
$validated['guest_password'],
$reservation['ip_pool_id'],
$oldIpPoolId,
);
$this->activityLogger->log(
event: 'change_ip',
logName: 'VM',
description: "Customer {$request->user()->email} yêu cầu đổi IP VM: {$vm->name} → {$validated['ip_address']}",
subject: $vm,
newValues: ['ip_address' => $validated['ip_address'], 'gateway' => $reservation['gateway'], 'os_type' => $osType],
);
return response()->json([
'success' => true,
'message' => 'Yêu cầu đổi IP đã được ghi nhận. Hệ thống sẽ xử lý trong vài phút.',
]);
}
/**
* POST /v1/system/vm-change-port
*
* Body:
* product int|string ModuleVmware.id|vmware_id|service_uuid
* new_port int New RDP port (1024–65535)
*/
public function changePort(Request $request, ChangePortVMAction $action): JsonResponse
{
$validated = $request->validate([
'product' => ['required'],
'new_port' => ['required', 'integer', 'min:1024', 'max:65535'],
]);
$vm = $this->resolveVmById($validated['product'], $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
$lock = $this->acquireVmLock($vm);
if (!$lock) {
return $this->vmLocked();
}
try {
$result = $action->execute($vm, (string) $validated['new_port']);
} finally {
$lock->release();
}
// 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);
if ($ok) {
$this->activityLogger->log(
event: 'change_port',
logName: 'VM',
description: "Customer {$request->user()->email} đổi cổng RDP VM: {$vm->name} → port {$validated['new_port']}",
subject: $vm,
newValues: ['new_port' => $validated['new_port']],
);
}
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);
}
\Illuminate\Support\Facades\Log::error('changePort: unexpected vCenter response', [
'vm_id' => $vm->id,
'result' => $result,
]);
return response()->json([
'success' => false,
'message' => 'Phản hồi không hợp lệ từ vCenter.',
], 400);
}
/**
* Look up a Service by server_name, verify ownership and active status.
* Returns JsonResponse on error, Service on success.
*/
private function findOwnedServiceByServerName(string $serverName, Request $request): Service|JsonResponse
{
$customer = $request->user();
$service = Service::with([
'vmware.vCenter',
'vmware.ipAddress.host.datacenter.vCenter',
'orderItem.order.vmWare.vCenter',
'orderItem.order.vmWare.ipAddress.host.datacenter.vCenter',
'orderItem.os.osGroup',
])
->where('server_name', $serverName)
->where('customer_id', $customer->id)
->first();
if (!$service) {
return response()->json(['success' => false, 'message' => 'Không tìm thấy dịch vụ.'], 404);
}
if ($service->status !== 'active') {
return response()->json(['success' => false, 'message' => 'Dịch vụ không ở trạng thái hoạt động, không thể thao tác.'], 403);
}
return $service;
}
}