Files
portal-api/app/Jobs/VirtualMachine/Provision/WaitVmReadyJob.php
T
pbao9 5043bea79a 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.
2026-04-16 23:21:27 +07:00

139 lines
4.9 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
declare(strict_types=1);
namespace App\Jobs\VirtualMachine\Provision;
use App\Http\Services\vCenter as VCenterService;
use App\Services\VmProvisionService;
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;
/**
* WaitVmReadyJob
*
* Polls vSphere REST API every 15 s until VMware Tools is running
* and the guest IP is available. No direct TCP port check is required
* because password changes are performed via Invoke-VMScript (vSphere
* path), not direct SSH/WinRM into the VM.
*
* Max 40 attempts × 15 s ≈ 10 minutes before giving up.
* On success → dispatches ChangePasswordViaVsphereJob.
*/
class WaitVmReadyJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** 40 attempts × 15 s release delay ≈ 10 min total */
public int $tries = 40;
/** Each poll attempt must finish within 30 s */
public int $timeout = 30;
public function __construct(
public readonly int $moduleVmwareId,
public readonly string $vmId,
public readonly int $vcenterId,
public readonly string $osFamily, // VmProvisionService::OS_LINUX | OS_WINDOWS
public readonly string $username, // root | Administrator
) {}
// -------------------------------------------------------------------------
public function handle(VCenterService $vCenterService, VmProvisionService $provision): void
{
$attempt = $this->attempts();
Log::info('WaitVmReadyJob: polling', [
'vm_id' => $this->vmId,
'attempt' => $attempt,
'os_family' => $this->osFamily,
]);
// ── 1. Login to vCenter ───────────────────────────────────────────────
$vcenter = \App\Models\vCenter::find($this->vcenterId);
if (!$vcenter) {
Log::error('WaitVmReadyJob: vCenter model not found', ['vcenter_id' => $this->vcenterId]);
$this->fail(new \RuntimeException("vCenter id={$this->vcenterId} not found"));
return;
}
try {
$provision->loginVCenter($vcenter, $vCenterService);
} catch (\Throwable $e) {
Log::warning('WaitVmReadyJob: vCenter login failed, will retry', [
'vm_id' => $this->vmId,
'error' => $e->getMessage(),
]);
$this->releaseOrFail(15, $attempt);
return;
}
// ── 2. Check VMware Tools + guest IP ─────────────────────────────────
// Invoke-VMScript requires VMware Tools to be running inside the guest.
$guest = $provision->getVmGuestInfo($vCenterService, $this->vmId);
Log::info('WaitVmReadyJob: guest info', [
'vm_id' => $this->vmId,
'attempt' => $attempt,
'tools_running' => $guest['tools_running'],
'ip_address' => $guest['ip_address'],
]);
if (!$guest['tools_running'] || $guest['ip_address'] === null) {
Log::info('WaitVmReadyJob: VMware Tools not ready yet', ['vm_id' => $this->vmId]);
$this->releaseOrFail(15, $attempt);
return;
}
$guestIp = $guest['ip_address'];
// ── 3. VM ready — dispatch password-change job ────────────────────────
Log::info('WaitVmReadyJob: VMware Tools ready, dispatching ChangePasswordViaVsphereJob', [
'vm_id' => $this->vmId,
'guest_ip' => $guestIp,
]);
ChangePasswordViaVsphereJob::dispatch(
$this->moduleVmwareId,
$this->vmId,
$guestIp,
$this->osFamily,
$this->username,
);
}
// -------------------------------------------------------------------------
public function failed(\Throwable $e): void
{
Log::error('WaitVmReadyJob: exceeded max attempts, marking VM failed', [
'vm_id' => $this->vmId,
'module_vmware_id' => $this->moduleVmwareId,
'error' => $e->getMessage(),
]);
\App\Models\ModuleVmware::where('id', $this->moduleVmwareId)
->update(['status' => 0]);
}
// -------------------------------------------------------------------------
private function releaseOrFail(int $delaySeconds, int $attempt): void
{
if ($attempt >= $this->tries) {
$this->fail(new \RuntimeException(
"VM {$this->vmId} not ready after {$this->tries} attempts (~10 min)",
));
return;
}
$this->release($delaySeconds);
}
}