feat(vm): hoàn tiền khi cấp VM lỗi, theo dõi job queue, uuid dịch vụ cho ticket, chuẩn hoá RAM addon

- VmProvisionException/VmProvisionRefund xử lý lỗi cấp phát VM
- QueueJobInspector + QueueMonitorController xem chi tiết job
- Thêm service uuid cho ticket, thời lượng cho activity_logs
- Migration chuẩn hoá RAM addon sang MB, trạng thái deleting cho snapshot

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
baodev0
2026-10-30 11:54:05 +07:00
co-authored by Claude Sonnet 5.5
parent 7136bec265
commit 62596d9063
32 changed files with 514 additions and 107 deletions
+36 -67
View File
@@ -51,90 +51,59 @@ class VMJobs implements ShouldQueue
$vcenter = $product->IpAddress->Host->Datacenter->vCenter;
$serviceVCenter->setConnection($vcenter->host, $vcenter->token);
$vmwareId = $product->vmware_id;
$VirtualMachineStatus = null;
$status = 1;
switch ($this->action) {
case "Suspend":
$VirtualMachineStatus = $serviceVCenter->SuspendVirtualMachine($product->vmware_id);
if ($VirtualMachineStatus === "Login_Again") {
$token = $serviceVCenter->loginServer($product->IpAddress->Host->Datacenter->vCenter);
if ($token) {
vCenter::where('id', $product->IpAddress->Host->Datacenter->vCenter->id)->update(['token' => $token]);
$serviceVCenter->token = $token;
}
$VirtualMachineStatus = $serviceVCenter->SuspendVirtualMachine($product->vmware_id);
$call = function (callable $action) use ($serviceVCenter, $vcenter) {
$result = $action();
if ($result === 'Login_Again') {
$token = $serviceVCenter->loginServer($vcenter);
if ($token) {
vCenter::where('id', $vcenter->id)->update(['token' => $token]);
$serviceVCenter->token = $token;
}
$result = $action();
}
return $result;
};
$powerState = fn () => $call(fn () => $serviceVCenter->CheckVirtualMachine($vmwareId))['power_state'] ?? null;
switch ($this->action) {
case 'Suspend':
$VirtualMachineStatus = $call(fn () => $serviceVCenter->SuspendVirtualMachine($vmwareId));
$status = 2;
break;
case "Reboot":
$VirtualMachineStatus = $serviceVCenter->RebootVirtualMachine($product->vmware_id);
if ($VirtualMachineStatus === "Login_Again") {
$token = $serviceVCenter->loginServer($product->IpAddress->Host->Datacenter->vCenter);
if ($token) {
vCenter::where('id', $product->IpAddress->Host->Datacenter->vCenter->id)->update(['token' => $token]);
$serviceVCenter->token = $token;
}
$VirtualMachineStatus = $serviceVCenter->RebootVirtualMachine($product->vmware_id);
case 'Reboot':
$VirtualMachineStatus = $call(fn () => $serviceVCenter->RebootVirtualMachine($product, $vmwareId));
break;
case 'Start':
if ($powerState() !== 'POWERED_ON') {
$VirtualMachineStatus = $call(fn () => $serviceVCenter->StartVirtualMachine($product, $vmwareId));
}
break;
case "Start":
$VirtualMachineStatus = $serviceVCenter->CheckVirtualMachine($product->vmware_id);
if ($VirtualMachineStatus === "Login_Again") {
$token = $serviceVCenter->loginServer($product->IpAddress->Host->Datacenter->vCenter);
if ($token) {
vCenter::where('id', $product->IpAddress->Host->Datacenter->vCenter->id)->update(['token' => $token]);
$serviceVCenter->token = $token;
}
$VirtualMachineStatus = $serviceVCenter->CheckVirtualMachine($product->vmware_id);
}
if ($VirtualMachineStatus['data']['operations_ready'] === true) {
break;
}
$VirtualMachineStatus = $serviceVCenter->StartVirtualMachine($product->vmware_id);
break;
case "ShutdownGuest":
$VirtualMachineStatus = $serviceVCenter->CheckVirtualMachine($product->vmware_id);
if ($VirtualMachineStatus === "Login_Again") {
$token = $serviceVCenter->loginServer($product->IpAddress->Host->Datacenter->vCenter);
if ($token) {
vCenter::where('id', $product->IpAddress->Host->Datacenter->vCenter->id)->update(['token' => $token]);
$serviceVCenter->token = $token;
}
$VirtualMachineStatus = $serviceVCenter->CheckVirtualMachine($product->vmware_id);
}
if ($VirtualMachineStatus['data']['operations_ready'] === false) {
$status = 0;
break;
}
$VirtualMachineStatus = $serviceVCenter->ShutdownGuestVirtualMachine($product->vmware_id);
case 'ShutdownGuest':
$status = 0;
if ($powerState() !== 'POWERED_OFF') {
$VirtualMachineStatus = $call(fn () => $serviceVCenter->ShutdownGuestVirtualMachine($product, $vmwareId));
}
break;
case "PowerOff":
$VirtualMachineStatus = $serviceVCenter->CheckVirtualMachine($product->vmware_id);
if ($VirtualMachineStatus === "Login_Again") {
$token = $serviceVCenter->loginServer($product->IpAddress->Host->Datacenter->vCenter);
if ($token) {
vCenter::where('id', $product->IpAddress->Host->Datacenter->vCenter->id)->update(['token' => $token]);
$serviceVCenter->token = $token;
}
$VirtualMachineStatus = $serviceVCenter->CheckVirtualMachine($product->vmware_id);
}
if ($VirtualMachineStatus['data']['operations_ready'] === false) {
$status = 0;
break;
}
$VirtualMachineStatus = $serviceVCenter->PowerOffVirtualMachine($product->vmware_id);
case 'PowerOff':
$status = 0;
if ($powerState() !== 'POWERED_OFF') {
$VirtualMachineStatus = $call(fn () => $serviceVCenter->PowerOffVirtualMachine($vmwareId));
}
break;
}
if ($VirtualMachineStatus && $VirtualMachineStatus['error']) {
throw new \Exception(json_encode($VirtualMachineStatus['data']));
if (is_array($VirtualMachineStatus) && ($VirtualMachineStatus['error'] ?? false)) {
throw new \Exception(json_encode($VirtualMachineStatus['data'] ?? $VirtualMachineStatus));
}
if ($this->update) {
@@ -6,13 +6,17 @@ namespace App\Jobs\VirtualMachine\Create;
use App\Http\Services\vCenter;
use App\Jobs\VirtualMachine\Provision\UpdateVmConfigJob;
use App\Exceptions\VmProvisionException;
use App\Models\OrderItem;
use App\Models\Service;
use App\Models\VmProvisionLock;
use App\Services\ActivityLogger;
use App\Services\VirtualMachine\VmAddonResolver;
use App\Services\VirtualMachine\VmInfrastructureResolver;
use App\Services\VirtualMachine\VmIpReservationService;
use App\Services\VirtualMachine\VmModuleService;
use App\Services\VirtualMachine\VmNotificationService;
use App\Services\VirtualMachine\VmProvisionRefund;
use App\Services\VirtualMachine\VmProvisionLogger;
use App\Services\VirtualMachine\VmServiceSyncService;
use App\Services\VirtualMachine\VmVcenterErrorTranslator;
@@ -51,6 +55,7 @@ class CreateVirtualMachineJob implements ShouldQueue
VmNotificationService $notification,
VmVcenterErrorTranslator $errorTranslator,
VmIpReservationService $ipReservation,
VmProvisionRefund $refund,
): bool {
$reservation = null;
$ipPoolId = null;
@@ -141,8 +146,9 @@ class CreateVirtualMachineJob implements ShouldQueue
]);
if (is_array($result) && ($result['error'] ?? false)) {
throw new RuntimeException(
$errorTranslator->translate($result, $this->item->server_name)
throw new VmProvisionException(
$errorTranslator->translate($result, $this->item->server_name),
$errorTranslator->detail($result),
);
}
@@ -161,7 +167,11 @@ class CreateVirtualMachineJob implements ShouldQueue
'vm_id' => $vmId,
]);
$moduleVmware = $moduleService->create($this->item, $infra, $vmId);
$moduleVmware = $moduleService->create($this->item, $infra, $vmId, [
'cpu' => (int) $product->cpu + $addonData['cpu'],
'ram_mb' => ((int) $product->ram + $addonData['ram']) * 1024,
'disk_gb' => (int) $product->disk + $addonData['disk'],
]);
$vmLogger->success('ModuleVmware created', [
'module_vmware_id' => $moduleVmware->id,
@@ -210,14 +220,31 @@ class CreateVirtualMachineJob implements ShouldQueue
}
$lock?->update(['status' => 'failed']);
Service::where('order_item_id', $this->item->id)
->whereIn('status', ['pending', 'provisioning'])
->update(['status' => 'failed']);
$adminDetail = $e instanceof VmProvisionException ? $e->adminDetail : $e->getMessage();
$vmLogger->exception('Create VM job failed', $e, [
'order_item_id' => $this->item->id,
'is_retry' => $this->isRetry,
'admin_detail' => $adminDetail,
]);
$failedService = Service::where('order_item_id', $this->item->id)->first();
if ($failedService) {
app(ActivityLogger::class)->log(
'provision_failed',
'service',
'Khởi tạo máy ảo thất bại — ' . $adminDetail,
$failedService,
);
}
if (!$this->isRetry) {
$notification->failed($this->item, $e->getMessage());
$refunded = $refund->refund($this->item, $e->getMessage());
$notification->failed($this->item, $e->getMessage() . ($refunded ? ' (đã tự động hoàn tiền cho khách)' : ''));
}
return false;
@@ -206,8 +206,9 @@ class UpdateVmConfigJob implements ShouldQueue
'output' => trim($output),
]);
$moduleVmware->update(['cpu' => $totalCpu, 'ram_mb' => $totalRam * 1024]);
if ($isManualOverride) {
$moduleVmware->update(['cpu' => $totalCpu, 'ram_mb' => $totalRam * 1024]);
$this->syncServiceAddons($moduleVmware, $totalCpu, $totalRam, $logger);
return;
}
@@ -160,6 +160,10 @@ class RemoveSnapshot implements ShouldQueue
public function failed(\Throwable $exception)
{
ModuleSnapshot::where('id', $this->snapshotId)
->where('status', 'deleting')
->update(['status' => 'success', 'error_message' => $exception->getMessage()]);
Log::error('❌ Job RemoveSnapshot failed', [
'product_id' => $this->productId,
'snapshot_id' => $this->snapshotId,