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:
@@ -0,0 +1,138 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user