Files
portal-api/app/Jobs/VirtualMachine/Create/CreateVirtualMachineJob.php
T

482 lines
20 KiB
PHP

<?php
namespace App\Jobs\VirtualMachine\Create;
use App\Http\Facade\SignRSA;
use App\Http\Services\Telegram;
use App\Http\Services\vCenter;
use App\Jobs\Mail\Customer\VMCreationFailedJob;
use App\Jobs\VirtualMachine\Create\AssignIPJob;
use App\Jobs\VirtualMachine\Create\ConfigCLICommand;
use App\Models\OrderItem;
use App\Models\Os;
use App\Models\ModuleVmware;
use App\Models\ModuleIpAddress;
use App\Models\IpPool;
use App\Models\Log as CustomerLog;
use App\Models\Service;
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\Facades\Http;
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, Telegram $telegram)
{
try {
$product = $this->item->getProductAttribute();
if (!$product) {
$errorMessage = "Không tìm thấy sản phẩm từ OrderItem #{$this->item->id}";
Log::warning($errorMessage);
$telegram->SendErrorMessage($errorMessage, 'CreateVirtualMachineJob - Missing Product');
if (!$this->isRetry) {
$this->sendFailureNotification($errorMessage);
$this->writeCustomerFailureLog($errorMessage);
}
return false;
}
$osModel = Os::with('osGroup.host.datacenter.vcenter')->find($this->item->os_id);
if (!$osModel) {
$errorMessage = "Không tìm thấy OS id={$this->item->os_id} trong OrderItem #{$this->item->id}. os_id không hợp lệ.";
Log::error($errorMessage);
$telegram->SendErrorMessage([
'error' => $errorMessage,
'os_id' => $this->item->os_id,
'order_item_id' => $this->item->id,
'product_id' => $product->id,
], 'CreateVirtualMachineJob - Invalid OS ID');
if (!$this->isRetry) {
$this->sendFailureNotification($errorMessage);
$this->writeCustomerFailureLog($errorMessage);
}
return;
}
$template = $osModel->template_id;
$host = $osModel->osGroup?->host; // từ os_group.module_host_id
$datacenter = $host?->datacenter;
$vcenter = $datacenter?->vcenter;
if (!$vcenter || !$host || !$datacenter) {
$errorMessage = "Thiếu thông tin host/datacenter/vcenter từ sản phẩm ID #{$product->id}";
Log::error($errorMessage);
$telegram->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);
$this->writeCustomerFailureLog($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);
$telegram->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);
$this->writeCustomerFailureLog($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) {
foreach ($this->item->addons as $orderAddon) {
$addon = $orderAddon->addonItems;
if (!$addon)
continue;
$addonName = strtolower(trim($addon->name));
$quantity = (int) $orderAddon->qty;
switch ($addonName) {
case 'cpu':
$addonData['cpu'] += $quantity;
break;
case 'ram':
$addonData['ram'] += $quantity;
break;
case 'disk':
case 'ssd':
$addonData['disk'] += $quantity;
break;
}
}
}
$result = $vCenter->CreateVirtualMachine([
'item' => $this->item,
'host' => $host,
'datacenter' => $datacenter,
'vcenter' => $vcenter,
'template' => $template,
'addon' => $addonData,
]);
if (is_array($result) && ($result['error'] ?? false)) {
$errorMessage = 'Tạo máy ảo thất bại: ' . json_encode($result);
Log::warning($errorMessage);
$telegram->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);
$this->writeCustomerFailureLog($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);
$telegram->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);
$this->writeCustomerFailureLog($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]);
}
// Start VM ngay sau khi tạo thành công (sử dụng API trực tiếp)
$startResult = $this->startVirtualMachineDirectly($vCenter, $vmId);
if (is_array($startResult) && $startResult['error'] === false) {
// 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
}
// Lưu dữ liệu VM vào database
try {
$customer = $this->item->order->customer;
$serverName = $this->item->server_name ?? 'VM-' . $vmId;
$networkId = $product->ipAddress?->module_network_id ?? 1;
$availableIP = $this->getAvailableIPFromPool($networkId);
$moduleIpAddress = ModuleIpAddress::create([
'vm_name' => $serverName,
'module_host_id' => $host->id,
'module_network_id' => $networkId,
'ip' => $availableIP,
'gateway' => $product->ipAddress?->gateway ?? '0.0.0.0',
'netmask' => $product->ipAddress?->netmask ?? '255.255.255.0',
'dns' => $product->ipAddress?->dns ?? '8.8.8.8',
'status' => 1 // 1 = Assigned
]);
$moduleVmware = ModuleVmware::create([
'name' => $serverName,
'module_ip_address_id' => $moduleIpAddress->id,
'port' => 22,
'order_id' => $this->item->order->id,
'first_payment' => $this->item->order->first_payment ?? 0,
'next_payment' => $this->item->order->next_payment ?? 0,
'next_due_date' => $this->item->order->next_due_date ?? null,
'next_invoice' => $this->item->order->next_invoice ?? 0,
'user' => 'Administrator',
'password' => '',
'hostname' => $serverName,
'vmware_id' => $vmId,
'os_id' => $this->item->os_id ?? 1,
'customer_id' => $customer->id,
'status' => 1
]);
AssignIPJob::dispatch($vmId, $moduleVmware->id, $moduleIpAddress->ip)
->delay(now()->addMinutes(5));
// Lưu Service record để track dịch vụ của khách hàng
Service::create([
'customer_id' => $customer->id,
'product_id' => $this->item->product_id,
'order_item_id' => $this->item->id,
'invoice_id' => $invoice?->id,
'vm_id' => $vmId,
'server_name' => $serverName,
'status' => 'active',
'auto_renew' => false,
'started_at' => now(),
'expires_at' => $this->item->months > 0
? now()->addMonths($this->item->months)
: null,
]);
} catch (\Exception $e) {
Log::error('❌ Lỗi khi lưu dữ liệu VM và IP address: ' . $e->getMessage(), [
'vmware_id' => $vmId,
'order_item_id' => $this->item->id,
'error' => $e->getMessage()
]);
}
} 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()
]);
}
}
$telegram->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
]
);
$this->writeCustomerSuccessLog();
try {
$order = $this->item->order;
$customer = $order->customer;
broadcast(new \App\Events\OrderProvisioned(
orderId: $order->id,
orderNum: $order->order_num,
customerName: trim($customer->firstname . ' ' . $customer->lastname),
vmId: (string) $vmId,
success: true,
message: 'Máy ảo đã được khởi tạo thành công cho khách hàng ' . trim($customer->firstname . ' ' . $customer->lastname) . ' (Đơn #' . $order->order_num . ')',
));
} catch (\Throwable $e) {
Log::warning('Không thể broadcast OrderProvisioned: ' . $e->getMessage());
}
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()
]);
$telegram->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);
$this->writeCustomerFailureLog($errorMessage);
}
return false;
}
}
private function writeCustomerSuccessLog(): void
{
try {
$order = $this->item->order;
$customer = $order?->customer;
if (!$order || !$customer) {
return;
}
CustomerLog::create([
'agent' => 'system',
'client_ip' => null,
'customer_id' => $customer->id,
'description' => 'Khởi tạo dịch vụ thành công cho đơn hàng #' . $order->order_num
. ' (product_id: ' . $this->item->product_id . ', os_id: ' . $this->item->os_id . ').',
]);
} catch (\Throwable $e) {
Log::warning('Không thể ghi log thành công khởi tạo VM: ' . $e->getMessage());
}
}
private function writeCustomerFailureLog(string $errorMessage): void
{
try {
$order = $this->item->order;
$customer = $order?->customer;
if (!$order || !$customer) {
return;
}
CustomerLog::create([
'agent' => 'system',
'client_ip' => null,
'customer_id' => $customer->id,
'description' => 'Khởi tạo dịch vụ thất bại cho đơn hàng #' . $order->order_num
. ' (product_id: ' . $this->item->product_id . ', os_id: ' . $this->item->os_id . ').'
. ' Lỗi: ' . $errorMessage . '. '
. 'Nếu có lỗi xảy ra khi khởi tạo dịch vụ quý khách nhấn khởi tạo tại đây, hoặc quý khách có thể liên hệ với kỹ thuật support@tgs.com.vn',
]);
} catch (\Throwable $e) {
Log::warning('Không thể ghi log thất bại khởi tạo VM: ' . $e->getMessage());
}
}
private function getAvailableIPFromPool(int $networkId): string
{
// Lấy tất cả IP từ IPPool theo network_id, order by ip_address
$ipPoolList = IpPool::where('module_network_id', $networkId)
->orderBy('ip_address')
->get();
if ($ipPoolList->isEmpty()) {
// Nếu không có IP trong pool, trả về IP default
return '0.0.0.0';
}
$assignedIPs = ModuleIpAddress::where('module_network_id', $networkId)
->pluck('ip')
->toArray();
foreach ($ipPoolList as $ipPool) {
if (!in_array($ipPool->ip_address, $assignedIPs)) {
return $ipPool->ip_address;
}
}
return '0.0.0.0';
}
/**
* Start VM directly using vCenter API
*/
private function startVirtualMachineDirectly(vCenter $vCenter, string $vmId): array|string
{
try {
// Kiểm tra xem đã có session chưa
$check = $vCenter->checkApiExits();
if (!$check) {
return 'Login_Again';
}
// Gọi API start VM trực tiếp
$response = Http::withoutVerifying()
->withHeaders([
'vmware-api-session-id' => $vCenter->token
])
->post("https://{$vCenter->apiUri}/rest/vcenter/vm/{$vmId}/power/start");
if ($response->status() === 200) {
return [
'error' => false,
'data' => 'success'
];
}
return [
'error' => true,
'data' => $response->body()
];
} catch (\Exception $e) {
Log::error('Lỗi khi start VM: ' . $e->getMessage());
return [
'error' => true,
'data' => $e->getMessage()
];
}
}
/**
* 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());
}
}
}