Files
portal-api/app/Jobs/VirtualMachine/Create/CreateVirtualMachineJob.php
T
2025-07-24 16:18:27 +07:00

405 lines
18 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\Jobs\VirtualMachine\Create;
use App\Http\Facade\SignRSA;
use App\Http\Services\Discord;
use App\Http\Services\vCenter;
use App\Jobs\Mail\Customer\VMCreationFailedJob;
use App\Models\OrderItem;
use App\Models\Os;
use App\Services\VMDataService;
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;
class CreateVirtualMachineJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public OrderItem $item;
public bool $isRetry = false;
/**
* Create a new job instance.
*/
public function __construct(OrderItem $item, bool $isRetry = false)
{
$this->item = $item;
$this->isRetry = $isRetry;
}
/**
* Execute the job.
*/
public function handle(vCenter $vCenter, Discord $discord, VMDataService $vmDataService)
{
try {
$product = $this->item->getProductAttribute(); // dùng helper trong OrderItem
if (!$product) {
$errorMessage = "Không tìm thấy sản phẩm từ OrderItem #{$this->item->id}";
Log::warning($errorMessage);
$discord->SendErrorMessage($errorMessage, 'CreateVirtualMachineJob - Missing Product');
if (!$this->isRetry) {
$this->sendFailureNotification($errorMessage);
}
return false;
}
$host = $product->host; // từ module_host_id
$datacenter = $host?->datacenter;
$vcenter = $datacenter?->vcenter;
$osModel = Os::find($this->item->os_id);
$template = $osModel?->template_id;
if (!$vcenter || !$host || !$datacenter) {
$errorMessage = "Thiếu thông tin host/datacenter/vcenter từ sản phẩm ID #{$product->id}";
Log::error($errorMessage);
$discord->SendErrorMessage([
'error' => $errorMessage,
'product_id' => $product->id,
'order_item_id' => $this->item->id,
'host_id' => $host?->id,
'datacenter_id' => $datacenter?->id,
'vcenter_id' => $vcenter?->id
], 'CreateVirtualMachineJob - Missing Infrastructure');
if (!$this->isRetry) {
$this->sendFailureNotification($errorMessage);
}
return;
}
// Login to vCenter
if (!$vCenter->loginServer($vcenter)) {
$errorMessage = "Login vào vCenter thất bại với host: {$vcenter->host}";
Log::error($errorMessage);
$discord->SendErrorMessage([
'error' => $errorMessage,
'vcenter_host' => $vcenter->host,
'vcenter_user' => $vcenter->user,
'product_id' => $product->id,
'order_item_id' => $this->item->id
], 'CreateVirtualMachineJob - vCenter Login Failed');
if (!$this->isRetry) {
$this->sendFailureNotification($errorMessage);
}
return false;
}
// Lấy thông tin addon (CPU, RAM)
$addonData = [
'cpu' => 0,
'ram' => 0,
'disk' => 0
];
// Lấy addon từ order item
if ($this->item->addons) {
Log::info("🔍 Tìm thấy addons cho order item", [
'order_item_id' => $this->item->id,
'addons_count' => $this->item->addons->count(),
'addons' => $this->item->addons->map(function($addon) {
return [
'id' => $addon->id,
'addon_id' => $addon->addon_id,
'qty' => $addon->qty,
'price' => $addon->price,
'addon_name' => $addon->addonItems?->name ?? 'Unknown'
];
})
]);
foreach ($this->item->addons as $orderAddon) {
$addon = $orderAddon->addonItems;
if (!$addon) {
Log::warning("⚠️ Addon không tìm thấy addonItems", [
'order_addon_id' => $orderAddon->id,
'addon_id' => $orderAddon->addon_id
]);
continue;
}
$addonName = strtolower(trim($addon->name));
$quantity = (int) $orderAddon->qty;
Log::info("🔍 Xử lý addon", [
'addon_name' => $addon->name,
'addon_name_lower' => $addonName,
'quantity' => $quantity,
'addon_id' => $addon->id
]);
switch ($addonName) {
case 'cpu':
$addonData['cpu'] += $quantity;
Log::info("✅ Thêm CPU addon: +{$quantity}");
break;
case 'ram':
$addonData['ram'] += $quantity;
Log::info("✅ Thêm RAM addon: +{$quantity}GB");
break;
case 'disk':
case 'ssd':
$addonData['disk'] += $quantity;
Log::info("✅ Thêm Disk addon: +{$quantity}GB");
break;
default:
Log::warning("⚠️ Addon không được nhận diện: {$addonName}");
break;
}
}
} else {
Log::info("ℹ️ Không có addons cho order item {$this->item->id}");
}
Log::info('📊 Thông tin addon được xử lý', [
'order_item_id' => $this->item->id,
'addon_data' => $addonData,
'addons_count' => $this->item->addons?->count() ?? 0,
'has_addons' => !empty($addonData) && array_sum($addonData) > 0
]);
$result = $vCenter->CreateVirtualMachine([
'item' => $this->item,
'host' => $host,
'datacenter' => $datacenter,
'vcenter' => $vcenter,
'template' => $template,
'addon' => $addonData,
]);
Log::info('Thông tin vCenter khi tạo máy ảo', [
'product' => $product,
'item' => $this->item,
'host_id' => $host?->id,
'host_name' => $host?->name,
'host_vcenter_id' => $host?->vcenter_id,
'datacenter_id' => $datacenter?->id,
'datacenter_name' => $datacenter?->name,
'datacenter_vcenter_id' => $datacenter?->vcenter_id,
'vcenter' => $vcenter,
'vcenter_id' => $vcenter?->id,
'vcenter_host' => $vcenter?->host,
'vcenter_user' => $vcenter?->user,
'vcenter_password' => SignRSA::DecryptFunc($vcenter?->password)
]);
if (is_array($result) && ($result['error'] ?? false)) {
$errorMessage = 'Tạo máy ảo thất bại: ' . json_encode($result);
Log::warning($errorMessage);
$discord->SendErrorMessage([
'error' => $result['error'] ?? 'Unknown error',
'result' => $result,
'product_id' => $product->id,
'order_item_id' => $this->item->id,
'host_name' => $host?->name,
'datacenter_name' => $datacenter?->name,
'vcenter_host' => $vcenter?->host
], 'CreateVirtualMachineJob - VM Creation Failed');
if (!$this->isRetry) {
$this->sendFailureNotification($errorMessage);
}
return false;
} elseif ($result === 'Login_Again') {
$errorMessage = 'Tạo máy ảo thất bại: cần đăng nhập lại.';
Log::error($errorMessage);
$discord->SendErrorMessage([
'error' => $errorMessage,
'product_id' => $product->id,
'order_item_id' => $this->item->id,
'vcenter_host' => $vcenter?->host
], 'CreateVirtualMachineJob - vCenter Re-login Required');
if (!$this->isRetry) {
$this->sendFailureNotification($errorMessage);
}
return false;
} else {
Log::info('Tạo máy ảo thành công: ' . json_encode($result));
// Lấy vm_id từ kết quả
$vmId = is_array($result) ? ($result['data'] ?? 'unknown') : $result;
if ($vmId !== 'unknown' && $vmId !== 'UNKNOWN') {
try {
// Lưu vm_id vào bảng invoice
$invoice = \App\Models\Invoice::where('order_id', $this->item->order->id)->first();
if ($invoice) {
$invoice->update(['vm_id' => $vmId]);
Log::info('✅ Đã lưu vm_id vào bảng invoice', [
'vm_id' => $vmId,
'invoice_id' => $invoice->id,
'order_id' => $this->item->order->id
]);
} else {
Log::warning('⚠️ Không tìm thấy invoice cho order', [
'order_id' => $this->item->order->id,
'vm_id' => $vmId
]);
}
// Start VM ngay sau khi tạo thành công
Log::info('🚀 Bắt đầu khởi động VM sau khi tạo thành công', [
'vm_id' => $vmId,
'order_item_id' => $this->item->id
]);
$startResult = $vCenter->StartVirtualMachine($vmId);
if ($startResult === 'Login_Again') {
// Thử đăng nhập lại và start VM
Log::info('🔄 Thử đăng nhập lại vCenter để start VM', [
'vm_id' => $vmId,
'vcenter_host' => $vcenter->host
]);
if ($vCenter->loginServer($vcenter)) {
$startResult = $vCenter->StartVirtualMachine($vmId);
Log::info('✅ Đăng nhập lại thành công, thử start VM lần nữa', [
'vm_id' => $vmId,
'start_result' => $startResult
]);
} else {
Log::error('❌ Không thể đăng nhập lại vCenter để start VM', [
'vm_id' => $vmId,
'vcenter_host' => $vcenter->host
]);
}
}
if (is_array($startResult) && $startResult['error'] === false) {
Log::info('✅ Khởi động VM thành công', [
'vm_id' => $vmId,
'order_item_id' => $this->item->id,
'result' => $startResult['data']
]);
// Dispatch ConfigCLICommand để cấu hình VM và lấy password
$configJob = new ConfigCLICommand($vmId, $this->item, $vCenter);
dispatch($configJob)->delay(now()->addMinutes(2)); // Chờ 2 phút để VM boot hoàn toàn
Log::info('✅ Đã dispatch ConfigCLICommand job', [
'vm_id' => $vmId,
'order_item_id' => $this->item->id
]);
} else {
Log::warning('⚠️ Khởi động VM thất bại', [
'vm_id' => $vmId,
'order_item_id' => $this->item->id,
'error' => $startResult['data']
]);
}
// Lưu dữ liệu VM và IP address vào database
$ipAddresses = [];
if (is_array($result)) {
$ipAddresses = $result['ip_addresses'] ?? [];
}
$saveResult = $vmDataService->saveVMData($this->item, $vmId, $ipAddresses);
if ($saveResult['success']) {
Log::info('✅ Lưu dữ liệu VM và IP address thành công', [
'vm_id' => $saveResult['vm']->id,
'vmware_id' => $vmId,
'ip_address_id' => $saveResult['ip_address']->id,
'order_item_id' => $this->item->id
]);
} else {
Log::error('❌ Lỗi khi lưu dữ liệu VM và IP address', [
'vmware_id' => $vmId,
'error' => $saveResult['message'],
'order_item_id' => $this->item->id
]);
}
} catch (\Exception $e) {
Log::error('❌ Lỗi khi lưu dữ liệu VM hoặc start VM: ' . $e->getMessage(), [
'vm_id' => $vmId,
'order_id' => $this->item->order->id,
'trace' => $e->getTraceAsString()
]);
}
}
// Log thông tin addon đã được áp dụng
if (!empty($addonData) && array_sum($addonData) > 0) {
Log::info('Addon đã được áp dụng thành công', [
'vm_id' => $vmId,
'addon_data' => $addonData,
'order_item_id' => $this->item->id
]);
}
// Gửi thông báo thành công
$discord->SendJobRunInfo(
'CreateVirtualMachineJob',
'success',
[
'product_id' => $product->id,
'order_item_id' => $this->item->id,
'result' => $result,
'addon_applied' => !empty($addonData) && array_sum($addonData) > 0,
'vm_id_saved' => $vmId !== 'unknown' && $vmId !== 'UNKNOWN',
'vm_started' => isset($startResult) && is_array($startResult) && $startResult['error'] === false
]
);
return true;
}
} catch (\Throwable $e) {
$errorMessage = 'Lỗi khi tạo máy ảo (Job): ' . $e->getMessage();
Log::error($errorMessage, [
'exception' => $e,
'order_item_id' => $this->item->id,
'trace' => $e->getTraceAsString()
]);
$discord->SendErrorMessage([
'error' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'order_item_id' => $this->item->id,
'trace' => substr($e->getTraceAsString(), 0, 500) // Giới hạn độ dài trace
], 'CreateVirtualMachineJob - Exception');
if (!$this->isRetry) {
$this->sendFailureNotification($errorMessage);
}
return false;
}
}
/**
* Send failure notification to customer
*/
private function sendFailureNotification(string $errorMessage): void
{
try {
$customer = $this->item->order->customer;
$product = $this->item->getProductAttribute();
VMCreationFailedJob::dispatch([
'customer_email' => $customer->email,
'customer_name' => trim($customer->firstname . ' ' . $customer->lastname),
'product_name' => $product?->name ?? 'Unknown Product',
'order_id' => $this->item->order->id,
'order_num' => $this->item->order->order_num,
'error_message' => $errorMessage,
'order_item_id' => $this->item->id
]);
} catch (\Throwable $e) {
Log::error('Không thể gửi email thông báo lỗi VM: ' . $e->getMessage());
}
}
}