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:
pbao9
2026-04-16 23:21:27 +07:00
parent d68fda2cf0
commit 5043bea79a
16 changed files with 1599 additions and 172 deletions
@@ -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',
};
}
}