243 lines
8.6 KiB
PHP
243 lines
8.6 KiB
PHP
<?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->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);
|
||
}
|
||
|
||
$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->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);
|
||
}
|
||
|
||
$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();
|
||
|
||
// 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', $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;
|
||
}
|
||
}
|