Files
portal-api/app/Http/Controllers/Api/Admin/VCenter/VCenterController.php
T

363 lines
16 KiB
PHP

<?php
namespace App\Http\Controllers\Api\Admin\VCenter;
use App\Http\Controllers\Controller;
use App\Jobs\VirtualMachine\Sync\SyncAllPackage;
use App\Models\ModuleDatacenter;
use App\Models\ModuleDatastore;
use App\Models\ModuleFolder;
use App\Models\ModuleHost;
use App\Models\ModuleNetwork;
use App\Models\vCenter;
use App\Models\VirtualMachines;
use App\Http\Services\vCenter as ServiceVCenter;
use App\Http\Facade\SignRSA;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class VCenterController extends Controller
{
public function store(Request $request): JsonResponse
{
$request->validate([
'host' => 'required|string|max:255',
'user' => 'required|string|max:255',
'password' => 'required|string|max:255',
]);
$vc = vCenter::create([
'host' => $request->input('host'),
'user' => $request->input('user'),
'password' => SignRSA::EncryptFunc($request->input('password')),
'status' => 1,
]);
return response()->json(['error' => false, 'data' => $vc], 201);
}
public function consoleTicket(Request $request, ServiceVCenter $serviceVCenter): JsonResponse
{
$vmDbId = $request->integer('vm_id');
if (!$vmDbId) {
return response()->json(['error' => true, 'message' => 'Thiếu vm_id'], 422);
}
$vm = VirtualMachines::with('datacenter.vCenter')->find($vmDbId);
if (!$vm) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy VM'], 404);
}
$vcenter = $vm->datacenter?->vCenter ?? null;
if (!$vcenter) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy thông tin vCenter'], 404);
}
if (!$serviceVCenter->loginServer($vcenter)) {
return response()->json(['error' => true, 'message' => 'Không thể đăng nhập vào vCenter'], 500);
}
$ticket = $serviceVCenter->GetConsoleTickets($vm->vm_id);
if ($ticket === 'Login_Again') {
if ($serviceVCenter->loginServer($vcenter)) {
$ticket = $serviceVCenter->GetConsoleTickets($vm->vm_id);
} else {
return response()->json(['error' => true, 'message' => 'Không thể đăng nhập lại vào vCenter'], 500);
}
}
if (is_string($ticket)) {
return response()->json(['error' => true, 'message' => "Lấy ticket thất bại: $ticket"], 500);
}
if ($ticket['error']) {
return response()->json(['error' => true, 'message' => $ticket['data']], 500);
}
return response()->json([
'error' => false,
'data' => $ticket['data'],
'host' => $vcenter->host,
'vmware-api-session-id' => $serviceVCenter->token,
])->cookie('vmware-api-session-id', $serviceVCenter->token, 60, '/', null, false, false);
}
public function sync(): JsonResponse
{
dispatch(new SyncAllPackage())->delay(now()->addSeconds(5));
return response()->json([
'error' => false,
'message' => 'Sync job dispatched, running in background.',
]);
}
public function storeNode(Request $request, string $type): JsonResponse
{
$rules = match ($type) {
'datacenter' => [
'vcenter_id' => 'required|integer|exists:module_vcenter,id',
'name' => 'required|string|max:255',
'datacenter_id' => 'nullable|string|max:255',
],
'host' => [
'module_datacenter_id' => 'required|integer|exists:module_datacenter,id',
'name' => 'required|string|max:255',
'host_id' => 'nullable|string|max:255',
'template' => 'nullable|string|max:500',
'console' => 'nullable|string|max:500',
'iso_path' => 'nullable|string|max:500',
],
'network' => [
'module_host_id' => 'required|integer|exists:module_host,id',
'name' => 'required|string|max:255',
'network' => 'nullable|string|max:255',
'type' => 'nullable|string|max:255',
],
default => abort(422, 'Không hỗ trợ tạo node loại này'),
};
$data = $request->validate($rules);
$data['status'] = 1;
$model = match ($type) {
'datacenter' => ModuleDatacenter::create($data),
'host' => ModuleHost::create($data),
'network' => ModuleNetwork::create($data),
};
return response()->json(['error' => false, 'data' => $model], 201);
}
public function updateNode(Request $request, string $type, int $id): JsonResponse
{
$model = $this->resolveModel($type, $id);
$rules = match ($type) {
'vcenter' => ['host' => 'sometimes|string|max:255', 'user' => 'sometimes|string|max:255', 'password' => 'sometimes|string|max:255', 'status' => 'sometimes|integer|in:0,1'],
'datacenter' => ['name' => 'sometimes|string|max:255', 'datacenter_id' => 'sometimes|string|max:255'],
'host' => ['name' => 'sometimes|string|max:255', 'host_id' => 'sometimes|string|max:255', 'template' => 'sometimes|nullable|string|max:500', 'console' => 'sometimes|nullable|string|max:500', 'iso_path' => 'sometimes|nullable|string|max:500'],
'datastore' => ['name' => 'sometimes|string|max:255', 'datastore' => 'sometimes|string|max:255', 'type' => 'sometimes|nullable|string|max:255', 'space' => 'sometimes|nullable|integer', 'status' => 'sometimes|integer|in:0,1'],
'network' => ['name' => 'sometimes|string|max:255', 'network' => 'sometimes|string|max:255', 'type' => 'sometimes|nullable|string|max:255'],
'folder' => ['name' => 'sometimes|string|max:255', 'folder' => 'sometimes|string|max:255', 'type' => 'sometimes|nullable|string|max:255'],
'virtual_machine' => ['name' => 'sometimes|string|max:255', 'vm_id' => 'sometimes|string|max:255'],
default => [],
};
$request->validate($rules);
$data = $request->only(array_keys($rules));
if ($type === 'vcenter' && isset($data['password'])) {
$data['password'] = SignRSA::EncryptFunc($data['password']);
}
$model->update($data);
return response()->json(['error' => false, 'data' => $model]);
}
public function destroyNode(string $type, int $id): JsonResponse
{
$model = $this->resolveModel($type, $id);
$childCheck = match ($type) {
'vcenter' => $model->datacenters()->exists(),
'datacenter' => ModuleHost::where('module_datacenter_id', $id)->exists(),
'host' => $model->datastore()->exists() || $model->network()->exists(),
default => false,
};
if ($childCheck) {
return response()->json([
'error' => true,
'message' => 'Không thể xoá vì vẫn còn dữ liệu liên kết bên dưới.',
], 422);
}
$model->delete();
return response()->json(['error' => false, 'message' => 'Đã xoá thành công.']);
}
public function tree(Request $request): JsonResponse
{
$vcenters = vCenter::with(['datacenters'])->get();
$allHosts = ModuleHost::with(['datastore', 'network'])->get()->groupBy('module_datacenter_id');
$allFolders = ModuleFolder::all()->groupBy('module_datacenter_id');
// VMs with a host assignment — grouped by host DB id
$allVMs = VirtualMachines::with('vmware')->whereNotNull('host_id')->get()->groupBy('host_id');
// VMs without a host assignment (synced before host_id was tracked) — grouped by datacenter DB id
$orphanVMs = VirtualMachines::with('vmware')->whereNull('host_id')->get()->groupBy('datacenter_id');
$tree = $vcenters->map(function ($vc) use ($allHosts, $allFolders, $allVMs, $orphanVMs) {
$dcGroups = ($vc->datacenters ?? collect())->groupBy('datacenter_id');
$datacenters = $dcGroups->map(function ($dcGroup) use ($allHosts, $allFolders, $allVMs, $orphanVMs) {
$dc = $dcGroup->first();
$dcIds = $dcGroup->pluck('id');
$uniqueHosts = $dcIds->flatMap(fn($id) => $allHosts->get($id) ?? collect())
->unique('host_id')->values();
// Networks — deduplicated at datacenter level
$dcNetworks = $uniqueHosts
->flatMap(fn($host) => $host->network ?? collect())
->unique('network')
->map(fn($net) => [
'id' => 'network-' . $net->id,
'name' => $net->name,
'type' => 'network',
'meta' => [
'db_id' => $net->id,
'network' => $net->network,
'net_type' => $net->type,
'module_host_id' => $net->module_host_id,
],
]);
// Hosts — VMs placed under their host, then Datastores
$dcHosts = $uniqueHosts->map(function ($host) use ($allVMs) {
$hostVMs = ($allVMs->get($host->id) ?? collect())
->map(fn($vm) => [
'id' => 'virtual_machine-' . $vm->id,
'name' => $vm->name,
'type' => 'virtual_machine',
'meta' => [
'db_id' => $vm->id,
'vmware_db_id' => $vm->vmware?->id,
'uuid' => $vm->uuid,
'vm_id' => $vm->vm_id,
'ip' => $vm->ip,
'username' => $vm->vmware?->user,
'password' => $vm->vmware?->plain_password,
'power_state' => $vm->power_state,
'folder' => $vm->folder,
'cpu' => $vm->cpu,
'ram_mb' => $vm->ram_mb,
'is_template' => $vm->is_template,
'is_system' => $vm->is_system,
'last_synced_at' => $vm->last_synced_at,
],
'children' => [],
]);
$datastores = ($host->datastore ?? collect())
->unique('datastore')
->map(fn($ds) => [
'id' => 'datastore-' . $ds->id,
'name' => $ds->name,
'type' => 'datastore',
'meta' => [
'db_id' => $ds->id,
'datastore' => $ds->datastore,
'store_type' => $ds->type,
'space' => $ds->space,
'space_gb' => $ds->space ? round($ds->space / 1024, 1) : null,
'status' => $ds->status,
'module_host_id' => $ds->module_host_id,
],
]);
return [
'id' => 'host-' . $host->id,
'name' => $host->name,
'type' => 'host',
'meta' => [
'db_id' => $host->id,
'host_id' => $host->host_id,
'template' => $host->template,
'console' => $host->console,
'iso_path' => $host->iso_path,
'module_datacenter_id' => $host->module_datacenter_id,
'vm_count' => $hostVMs->count(),
],
// VMs first, then Datastores — correct vSphere order
'children' => array_values($hostVMs->concat($datastores)->toArray()),
];
});
// Folders — datacenter level
$dcFolders = $dcIds
->flatMap(fn($id) => $allFolders->get($id) ?? collect())
->unique('folder')
->map(fn($f) => [
'id' => 'folder-' . $f->id,
'name' => $f->name,
'type' => 'folder',
'meta' => [
'db_id' => $f->id,
'folder' => $f->folder,
'folder_type' => $f->type,
'module_datacenter_id' => $f->module_datacenter_id,
],
]);
// Orphan VMs — no host_id, grouped directly under datacenter
$dcOrphanVMs = $dcIds
->flatMap(fn($id) => $orphanVMs->get($id) ?? collect())
->unique('id')
->map(fn($vm) => [
'id' => 'virtual_machine-' . $vm->id,
'name' => $vm->name,
'type' => 'virtual_machine',
'meta' => [
'db_id' => $vm->id,
'vmware_db_id' => $vm->vmware?->id,
'uuid' => $vm->uuid,
'vm_id' => $vm->vm_id,
'ip' => $vm->ip,
'username' => $vm->vmware?->user,
'password' => $vm->vmware?->plain_password,
'power_state' => $vm->power_state,
'folder' => $vm->folder,
'cpu' => $vm->cpu,
'ram_mb' => $vm->ram_mb,
'is_template' => $vm->is_template,
'is_system' => $vm->is_system,
'last_synced_at' => $vm->last_synced_at,
],
'children' => [],
]);
return [
'id' => 'datacenter-' . $dc->id,
'name' => $dc->name,
'type' => 'datacenter',
'meta' => [
'db_id' => $dc->id,
'datacenter_id' => $dc->datacenter_id,
'vcenter_id' => $dc->vcenter_id,
],
// vSphere order: Hosts → Networks → Folders → Orphan VMs
'children' => array_values($dcHosts->concat($dcNetworks)->concat($dcFolders)->concat($dcOrphanVMs)->toArray()),
];
})->values();
return [
'id' => 'vcenter-' . $vc->id,
'name' => $vc->host,
'type' => 'vcenter',
'meta' => ['db_id' => $vc->id, 'host' => $vc->host, 'status' => $vc->status],
'children' => array_values($datacenters->toArray()),
];
});
return response()->json(['error' => false, 'data' => $tree->values()]);
}
private function resolveModel(string $type, int $id)
{
return match ($type) {
'vcenter' => vCenter::findOrFail($id),
'datacenter' => ModuleDatacenter::findOrFail($id),
'host' => ModuleHost::findOrFail($id),
'datastore' => ModuleDatastore::findOrFail($id),
'network' => ModuleNetwork::findOrFail($id),
'folder' => ModuleFolder::findOrFail($id),
'virtual_machine' => VirtualMachines::findOrFail($id),
default => abort(404, 'Unknown node type'),
};
}
}