405 lines
18 KiB
PHP
405 lines
18 KiB
PHP
<?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());
|
||
}
|
||
}
|
||
}
|