- setting.require_complete_profile (mặc định bật), trả qua GET v1/setting - bulkDeleteServices xoá mềm invoice nâng cấp + order mồ côi (ServiceOrderRecorder::discardOrder) - createService trỏ dịch vụ sang order mới thì bỏ order cũ - SyncVirtualMachine cập nhật name + gắn virtual_machine_id cho module_vmware Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
241 lines
9.3 KiB
PHP
241 lines
9.3 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs\VirtualMachine\Sync;
|
|
|
|
use App\Http\Services\Telegram;
|
|
use App\Http\Services\vCenter as ServiceVCenter;
|
|
use App\Models\ModuleDatacenter;
|
|
use App\Models\ModuleHost;
|
|
use App\Models\ModuleVmware;
|
|
use App\Models\VirtualMachines;
|
|
use App\Models\vCenter;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Str;
|
|
use Throwable;
|
|
|
|
class SyncVirtualMachine implements ShouldQueue
|
|
{
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
protected $vCenter;
|
|
protected $telegram;
|
|
|
|
/**
|
|
* Create a new job instance.
|
|
*/
|
|
public function __construct()
|
|
{
|
|
// Keep constructor empty or minimal to ensure lightweight serialization
|
|
}
|
|
|
|
/**
|
|
* Execute the job.
|
|
*/
|
|
public function handle()
|
|
{
|
|
$this->vCenter = new ServiceVCenter;
|
|
$this->telegram = new Telegram();
|
|
|
|
try {
|
|
$data = vCenter::select('id', 'host', 'user', 'password', 'token')->get();
|
|
|
|
$vCenterCount = 0;
|
|
$vmTotalCount = 0;
|
|
|
|
$totalDeletedCount = 0;
|
|
|
|
foreach ($data as $item) {
|
|
Log::info("Processing vCenter: {$item->host}");
|
|
$this->vCenter->apiUri = $item->host;
|
|
|
|
// Always login to get a fresh token
|
|
$token = $this->vCenter->loginServer($item);
|
|
if ($token) {
|
|
$item->token = $token;
|
|
$item->save();
|
|
$this->vCenter->token = $token;
|
|
} else {
|
|
$this->vCenter->token = null;
|
|
Log::error("Login failed for {$item->host}");
|
|
}
|
|
|
|
if (!$this->vCenter->token) {
|
|
continue;
|
|
}
|
|
|
|
$syncedVmIds = [];
|
|
$fetchFailed = false;
|
|
|
|
$datacenters = ModuleDatacenter::where('vcenter_id', $item->id)->get();
|
|
if ($datacenters->count() > 0) {
|
|
foreach ($datacenters as $dc) {
|
|
// Get all hosts for this datacenter
|
|
$hosts = ModuleHost::where('module_datacenter_id', $dc->id)->get();
|
|
|
|
if ($hosts->count() > 0) {
|
|
foreach ($hosts as $host) {
|
|
Log::info("Fetching VMs for host: {$host->host_id} in datacenter: {$dc->name}");
|
|
$vms = $this->vCenter->getAllVirtualMachine($dc->datacenter_id, $host->host_id);
|
|
$vmCount = $this->processVMs($vms, $item, $dc->id, $host->id, $syncedVmIds, $fetchFailed);
|
|
$vmTotalCount += $vmCount;
|
|
}
|
|
} else {
|
|
// Fallback: Sync VMs by datacenter only
|
|
Log::info("No hosts found for datacenter {$dc->name}, fetching VMs by datacenter only");
|
|
$vms = $this->vCenter->getAllVirtualMachine($dc->datacenter_id);
|
|
$vmCount = $this->processVMs($vms, $item, $dc->id, null, $syncedVmIds, $fetchFailed);
|
|
$vmTotalCount += $vmCount;
|
|
}
|
|
}
|
|
} else {
|
|
// Fallback: Sync all VMs without datacenter context
|
|
Log::info("No datacenters found, fetching all VMs for {$item->host}");
|
|
$vms = $this->vCenter->getAllVirtualMachine();
|
|
$vmCount = $this->processVMs($vms, $item, null, null, $syncedVmIds, $fetchFailed);
|
|
$vmTotalCount += $vmCount;
|
|
}
|
|
|
|
// Cleanup stale VMs in DB that no longer exist on vCenter.
|
|
// Only run when all fetches succeeded to avoid wiping data on transient failures.
|
|
if (!$fetchFailed) {
|
|
$datacenterIds = $datacenters->pluck('id')->all();
|
|
$deletedCount = $this->cleanupStaleVMs($datacenterIds, $syncedVmIds, $item);
|
|
$totalDeletedCount += $deletedCount;
|
|
} else {
|
|
Log::warning("Skip cleanup for vCenter {$item->host} because some VM fetches failed.");
|
|
}
|
|
|
|
$vCenterCount++; // Increment vCenter synced count
|
|
}
|
|
|
|
// Notify success via Telegram
|
|
$this->telegram->SendJobRunInfo('SyncVirtualMachine', 'Success', [
|
|
'message' => 'Sync VMs completed successfully',
|
|
'synced_vcenter_count' => $vCenterCount,
|
|
'synced_vm_count' => $vmTotalCount,
|
|
'deleted_vm_count' => $totalDeletedCount,
|
|
]);
|
|
|
|
} catch (Throwable $e) {
|
|
Log::error("SyncVirtualMachine Job Failed: " . $e->getMessage());
|
|
// Gửi log lỗi qua Telegram
|
|
if (isset($this->telegram)) {
|
|
$this->telegram->SendJobRunInfo('SyncVirtualMachine', 'Failed', [
|
|
'error' => $e->getMessage(),
|
|
'file' => $e->getFile(),
|
|
'line' => $e->getLine(),
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function processVMs($vms, $vCenterItem, $datacenterId, $hostId, array &$syncedVmIds, bool &$fetchFailed)
|
|
{
|
|
// If token expired, retry login (handled inside loop in previous logic, but simpler here if we just handle the array check)
|
|
if ($vms === 'Login_Again') {
|
|
Log::info("Token expired, retrying login for {$vCenterItem->host}");
|
|
$token = $this->vCenter->loginServer($vCenterItem);
|
|
if ($token) {
|
|
$vCenterItem->token = $token;
|
|
$vCenterItem->save();
|
|
$this->vCenter->token = $token;
|
|
// Retry fetch
|
|
$datacenter = $datacenterId ? ModuleDatacenter::find($datacenterId) : null;
|
|
$host = $hostId ? ModuleHost::find($hostId) : null;
|
|
$vms = $this->vCenter->getAllVirtualMachine(
|
|
$datacenter ? $datacenter->datacenter_id : '',
|
|
$host ? $host->host_id : ''
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!is_array($vms)) {
|
|
Log::warning("Failed to fetch VMs or empty response. Response: " . json_encode($vms));
|
|
$fetchFailed = true;
|
|
return 0;
|
|
}
|
|
|
|
Log::info("Fetched " . count($vms) . " VMs");
|
|
$count = 0;
|
|
foreach ($vms as $vmData) {
|
|
$vmObj = VirtualMachines::firstOrNew(['vm_id' => $vmData['vm']]);
|
|
|
|
if (!$vmObj->exists) {
|
|
$vmObj->uuid = Str::uuid();
|
|
}
|
|
|
|
$vmObj->name = $vmData['name'];
|
|
$vmObj->power_state = $vmData['power_state'];
|
|
$vmObj->cpu = $vmData['cpu_count'] ?? 0;
|
|
$vmObj->ram_mb = $vmData['memory_size_MiB'] ?? 0;
|
|
$vmObj->datacenter_id = $datacenterId;
|
|
$vmObj->host_id = $hostId;
|
|
$vmObj->folder = $vmData['folder'] ?? null;
|
|
$vmObj->last_synced_at = now();
|
|
|
|
$vmObj->save();
|
|
$this->syncAssignedVm($vmObj);
|
|
$syncedVmIds[] = $vmData['vm'];
|
|
$count++;
|
|
}
|
|
return $count;
|
|
}
|
|
|
|
private function syncAssignedVm(VirtualMachines $vm): void
|
|
{
|
|
ModuleVmware::query()
|
|
->where(fn ($query) => $query
|
|
->where('virtual_machine_id', $vm->id)
|
|
->orWhere(fn ($legacy) => $legacy->whereNull('virtual_machine_id')->where('vmware_id', $vm->vm_id)))
|
|
->where(fn ($query) => $query
|
|
->whereNull('virtual_machine_id')
|
|
->orWhereNull('name')
|
|
->orWhere('name', '!=', $vm->name))
|
|
->update(['name' => $vm->name, 'virtual_machine_id' => $vm->id]);
|
|
}
|
|
|
|
/**
|
|
* Delete VMs in DB that belong to the given vCenter but no longer exist on vCenter.
|
|
*/
|
|
private function cleanupStaleVMs(array $datacenterIds, array $syncedVmIds, $vCenterItem): int
|
|
{
|
|
$syncedVmIds = array_values(array_unique($syncedVmIds));
|
|
|
|
if (!empty($datacenterIds)) {
|
|
$query = VirtualMachines::whereIn('datacenter_id', $datacenterIds);
|
|
} else {
|
|
// No datacenter context: fallback - chỉ xoá khi đã sync được ít nhất 1 VM
|
|
// để tránh xoá nhầm khi vCenter rỗng do lỗi.
|
|
if (empty($syncedVmIds)) {
|
|
Log::warning("Skip cleanup for {$vCenterItem->host}: no datacenters and no synced VMs.");
|
|
return 0;
|
|
}
|
|
$query = VirtualMachines::query();
|
|
}
|
|
|
|
if (!empty($syncedVmIds)) {
|
|
$query->whereNotIn('vm_id', $syncedVmIds);
|
|
}
|
|
|
|
$staleVms = $query->get(['id', 'vm_id', 'name']);
|
|
|
|
if ($staleVms->isEmpty()) {
|
|
return 0;
|
|
}
|
|
|
|
foreach ($staleVms as $stale) {
|
|
Log::info("Deleting stale VM removed from vCenter {$vCenterItem->host}: {$stale->name} ({$stale->vm_id})");
|
|
}
|
|
|
|
$deleted = VirtualMachines::whereIn('id', $staleVms->pluck('id')->all())->delete();
|
|
Log::info("Cleanup vCenter {$vCenterItem->host}: deleted {$deleted} stale VM(s).");
|
|
|
|
return (int) $deleted;
|
|
}
|
|
}
|