feat: Add IP address and DNS fields to module_vmware table
- Introduced `ip` and `dns` columns to the `module_vmware` table to store assigned IP addresses and DNS configurations. - Implemented `ChangePasswordViaSshJob` for secure password changes via SSH. - Created `ChangePasswordJob` for handling password changes during VM provisioning. - Developed `ChangePasswordViaVsphereJob` to reset VM guest OS passwords using PowerShell scripts executed on the vSphere host. - Added `SendVmCredentialsEmailJob` to send final VM credentials to customers. - Implemented `WaitVmReadyJob` to poll vSphere API until VMware Tools are running and guest IP is available. - Created `VmProvisionService` for managing VM provisioning tasks, including password generation and updates. - Developed `VsphereHostSshService` for executing PowerShell scripts on the vSphere host via SSH. - Added `IpPoolSeeder` to populate IP pools for VM service initialization.
This commit is contained in:
@@ -6,8 +6,9 @@ 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\Jobs\Mail\SendInformationVirtualMachine;
|
||||
use App\Jobs\VirtualMachine\Provision\ChangePasswordViaVsphereJob;
|
||||
use App\Services\VmProvisionService;
|
||||
use App\Models\OrderItem;
|
||||
use App\Models\Os;
|
||||
use App\Models\ModuleVmware;
|
||||
@@ -15,6 +16,7 @@ use App\Models\ModuleIpAddress;
|
||||
use App\Models\IpPool;
|
||||
use App\Models\Log as CustomerLog;
|
||||
use App\Models\Service;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
@@ -27,6 +29,12 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/** Cho phép job chạy tối đa 10 phút (VM clone có thể mất 5-8 phút) */
|
||||
public int $timeout = 600;
|
||||
|
||||
/** Không retry tự động — lỗi VM cần admin kiểm tra thủ công */
|
||||
public int $tries = 1;
|
||||
|
||||
public OrderItem $item;
|
||||
public bool $isRetry = false;
|
||||
|
||||
@@ -152,6 +160,12 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
// Xác định OS family để chọn username và luồng cấu hình phù hợp
|
||||
$osGroupName = $osModel->osGroup?->name ?? '';
|
||||
$isWindows = str_contains(strtolower($osGroupName), 'windows');
|
||||
$vmUser = $isWindows ? 'administrator' : 'root';
|
||||
$vmPort = $isWindows ? 3389 : 22;
|
||||
|
||||
$result = $vCenter->CreateVirtualMachine([
|
||||
'item' => $this->item,
|
||||
'host' => $host,
|
||||
@@ -205,10 +219,7 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
// vm_id được lưu vào tbl.services qua ModuleVmwareObserver::created()
|
||||
$startResult = $this->startVirtualMachineDirectly($vCenter, $vmId);
|
||||
|
||||
if (is_array($startResult) && $startResult['error'] === false) {
|
||||
$configJob = new ConfigCLICommand($vmId, $this->item, $vCenter);
|
||||
dispatch($configJob)->delay(now()->addMinutes(2)); // Chờ 2 phút để VM boot hoàn toàn
|
||||
}
|
||||
// VM started — SSH vào vSphere host để đổi mật khẩu qua PS1 script
|
||||
|
||||
try {
|
||||
$customer = $this->item->order->customer;
|
||||
@@ -232,20 +243,19 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
$firstPayment = (int) $this->item->price * ($months ?: 1);
|
||||
$nextDueDate = $months > 0 ? now()->addMonths($months)->timestamp : null;
|
||||
|
||||
$initialPassword = 'TGS@' . \Illuminate\Support\Str::random(8);
|
||||
|
||||
$moduleVmware = ModuleVmware::create([
|
||||
'name' => $serverName,
|
||||
'module_ip_address' => $moduleIpAddress->id,
|
||||
'port' => 22,
|
||||
'ip' => $availableIP,
|
||||
'port' => $vmPort,
|
||||
'order_id' => $this->item->order->id,
|
||||
'first_payment' => $firstPayment,
|
||||
'next_payment' => $firstPayment,
|
||||
'next_due_date' => $nextDueDate,
|
||||
'next_invoice' => $nextDueDate,
|
||||
'user' => 'administrator',
|
||||
'password' => $initialPassword,
|
||||
'plain_password' => $initialPassword,
|
||||
'user' => $vmUser,
|
||||
'password' => VmProvisionService::TEMPLATE_PASSWORD,
|
||||
'plain_password' => VmProvisionService::TEMPLATE_PASSWORD,
|
||||
'hostname' => $serverName,
|
||||
'vmware_id' => $vmId,
|
||||
'vcenter_id' => $vcenter->id,
|
||||
@@ -254,11 +264,21 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
// Explicit sync: update services table directly (belt + suspenders vs observer)
|
||||
// Sync services table
|
||||
$this->syncServiceRecord($moduleVmware);
|
||||
|
||||
AssignIPJob::dispatch($vmId, $moduleVmware->id, $moduleIpAddress->ip)
|
||||
->delay(now()->addMinutes(5));
|
||||
// SSH → vSphere host → PS1 → change password → send email
|
||||
ChangePasswordViaVsphereJob::dispatch(
|
||||
$moduleVmware->id,
|
||||
$vmId,
|
||||
$isWindows ? VmProvisionService::OS_WINDOWS : VmProvisionService::OS_LINUX,
|
||||
);
|
||||
|
||||
Log::info('ChangePasswordViaVsphereJob dispatched', [
|
||||
'module_vmware_id' => $moduleVmware->id,
|
||||
'vm_id' => $vmId,
|
||||
'os_family' => $isWindows ? 'WINDOWS' : 'LINUX',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('❌ Lỗi khi lưu dữ liệu VM và IP address: ' . $e->getMessage(), [
|
||||
'vmware_id' => $vmId,
|
||||
@@ -380,26 +400,31 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
// Lấy tất cả IPs đã được assign trong network này
|
||||
$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;
|
||||
}
|
||||
// Lấy ngẫu nhiên 1 IP từ pool chưa được assign
|
||||
$available = IpPool::where('module_network_id', $networkId)
|
||||
->when(!empty($assignedIPs), fn ($q) => $q->whereNotIn('ip_address', $assignedIPs))
|
||||
->inRandomOrder()
|
||||
->first();
|
||||
|
||||
if (!$available) {
|
||||
Log::warning('getAvailableIPFromPool: không còn IP trống trong pool', [
|
||||
'network_id' => $networkId,
|
||||
'assigned_count' => count($assignedIPs),
|
||||
]);
|
||||
return '0.0.0.0';
|
||||
}
|
||||
return '0.0.0.0';
|
||||
|
||||
Log::info('getAvailableIPFromPool: IP được chọn', [
|
||||
'network_id' => $networkId,
|
||||
'ip' => $available->ip_address,
|
||||
]);
|
||||
|
||||
return $available->ip_address;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -504,4 +529,20 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gửi email thông báo VM đã khởi tạo thành công cho khách hàng.
|
||||
* Gọi ngay sau khi tạo module_vmware — cho cả Linux lẫn Windows.
|
||||
*/
|
||||
private function getBillingCycleLabel(?int $months): string
|
||||
{
|
||||
return match ($months) {
|
||||
1 => 'Hàng tháng',
|
||||
3 => '3 Tháng',
|
||||
6 => '6 Tháng',
|
||||
12 => 'Hàng năm',
|
||||
24 => '2 Năm',
|
||||
default => $months . ' Tháng',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user