feat: Implement IP assignment and verification for VM guests; add related PowerShell scripts
This commit is contained in:
@@ -2,21 +2,16 @@
|
|||||||
|
|
||||||
namespace App\Jobs\VirtualMachine\Create;
|
namespace App\Jobs\VirtualMachine\Create;
|
||||||
|
|
||||||
use App\Http\Facade\SignRSA;
|
|
||||||
use App\Http\Services\Telegram;
|
use App\Http\Services\Telegram;
|
||||||
use App\Http\Services\vCenter;
|
use App\Http\Services\vCenter;
|
||||||
use App\Jobs\Mail\Customer\VMCreationFailedJob;
|
use App\Jobs\Mail\Customer\VMCreationFailedJob;
|
||||||
use App\Jobs\Mail\SendInformationVirtualMachine;
|
|
||||||
use App\Jobs\VirtualMachine\Provision\ChangePasswordViaVsphereJob;
|
use App\Jobs\VirtualMachine\Provision\ChangePasswordViaVsphereJob;
|
||||||
use App\Services\VmProvisionService;
|
use App\Services\VmProvisionService;
|
||||||
use App\Models\OrderItem;
|
use App\Models\OrderItem;
|
||||||
use App\Models\Os;
|
use App\Models\Os;
|
||||||
use App\Models\ModuleVmware;
|
use App\Models\ModuleVmware;
|
||||||
use App\Models\ModuleIpAddress;
|
|
||||||
use App\Models\IpPool;
|
|
||||||
use App\Models\Log as CustomerLog;
|
use App\Models\Log as CustomerLog;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use App\Models\Setting;
|
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
@@ -222,31 +217,19 @@ class CreateVirtualMachineJob implements ShouldQueue
|
|||||||
// VM started — SSH vào vSphere host để đổi mật khẩu qua PS1 script
|
// VM started — SSH vào vSphere host để đổi mật khẩu qua PS1 script
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$customer = $this->item->order->customer;
|
$customer = $this->item->order->customer;
|
||||||
$serverName = $this->item->server_name ?? 'VM-' . $vmId;
|
$serverName = $this->item->server_name ?? 'VM-' . $vmId;
|
||||||
|
|
||||||
$networkId = $product->ipAddress?->module_network_id ?? 1;
|
$months = (int) ($this->item->months ?? 0);
|
||||||
$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
|
|
||||||
]);
|
|
||||||
|
|
||||||
$months = (int) ($this->item->months ?? 0);
|
|
||||||
$firstPayment = (int) $this->item->price * ($months ?: 1);
|
$firstPayment = (int) $this->item->price * ($months ?: 1);
|
||||||
$nextDueDate = $months > 0 ? now()->addMonths($months)->timestamp : null;
|
$nextDueDate = $months > 0 ? now()->addMonths($months)->timestamp : null;
|
||||||
|
|
||||||
|
// Create ModuleVmware without IP — AssignIpToGuestJob will
|
||||||
|
// pick, assign, and verify the real IP later, then write it to DB.
|
||||||
$moduleVmware = ModuleVmware::create([
|
$moduleVmware = ModuleVmware::create([
|
||||||
'name' => $serverName,
|
'name' => $serverName,
|
||||||
'module_ip_address' => $moduleIpAddress->id,
|
'module_ip_address' => null, // set by AssignIpToGuestJob
|
||||||
'ip' => $availableIP,
|
'ip' => null, // set by AssignIpToGuestJob
|
||||||
'port' => $vmPort,
|
'port' => $vmPort,
|
||||||
'order_id' => $this->item->order->id,
|
'order_id' => $this->item->order->id,
|
||||||
'first_payment' => $firstPayment,
|
'first_payment' => $firstPayment,
|
||||||
@@ -267,7 +250,9 @@ class CreateVirtualMachineJob implements ShouldQueue
|
|||||||
// Sync services table
|
// Sync services table
|
||||||
$this->syncServiceRecord($moduleVmware);
|
$this->syncServiceRecord($moduleVmware);
|
||||||
|
|
||||||
// SSH → vSphere host → PS1 → change password → send email
|
// Step 1 of guest provisioning: change template password.
|
||||||
|
// On success ChangePasswordViaVsphereJob dispatches AssignIpToGuestJob,
|
||||||
|
// which handles IP selection, assignment, verification, and email.
|
||||||
ChangePasswordViaVsphereJob::dispatch(
|
ChangePasswordViaVsphereJob::dispatch(
|
||||||
$moduleVmware->id,
|
$moduleVmware->id,
|
||||||
$vmId,
|
$vmId,
|
||||||
@@ -398,35 +383,6 @@ class CreateVirtualMachineJob implements ShouldQueue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getAvailableIPFromPool(int $networkId): string
|
|
||||||
{
|
|
||||||
// Lấy tất cả IPs đã được assign trong network này
|
|
||||||
$assignedIPs = ModuleIpAddress::where('module_network_id', $networkId)
|
|
||||||
->pluck('ip')
|
|
||||||
->toArray();
|
|
||||||
|
|
||||||
// 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';
|
|
||||||
}
|
|
||||||
|
|
||||||
Log::info('getAvailableIPFromPool: IP được chọn', [
|
|
||||||
'network_id' => $networkId,
|
|
||||||
'ip' => $available->ip_address,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $available->ip_address;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start VM directly using vCenter API
|
* Start VM directly using vCenter API
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,335 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Jobs\VirtualMachine\Provision;
|
||||||
|
|
||||||
|
use App\Http\Facade\SignRSA;
|
||||||
|
use App\Models\IpPool;
|
||||||
|
use App\Models\ModuleIpAddress;
|
||||||
|
use App\Models\ModuleVmware;
|
||||||
|
use App\Models\Os;
|
||||||
|
use App\Models\vCenter;
|
||||||
|
use App\Services\VsphereHostSshService;
|
||||||
|
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\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AssignIpToGuestJob
|
||||||
|
*
|
||||||
|
* Assigns a real static IP to the VM guest OS and verifies it is active.
|
||||||
|
* Only persists the IP to DB after successful guest-side verification.
|
||||||
|
*
|
||||||
|
* Flow:
|
||||||
|
* [1] Resolve network config from order item → product → ipAddress template
|
||||||
|
* [2] for attempt 1..MAX_ATTEMPTS:
|
||||||
|
* [3] Pick a free IP with DB transaction + lock (reserveIp)
|
||||||
|
* [4] Pre-check: ping IP — if reachable → release and try next
|
||||||
|
* [5] Call assignIPLinux.ps1 / assignIPWindows.ps1 via SSH
|
||||||
|
* [6] On FAIL → release IP, try next
|
||||||
|
* [7] Call verifyIPLinux.ps1 / verifyIPWindows.ps1 via SSH
|
||||||
|
* [8] On FAIL → release IP, try next
|
||||||
|
* [9] SUCCESS → persistIp() → dispatch SendVmCredentialsEmailJob
|
||||||
|
* [10] All attempts exhausted → mark VM inactive (status=0)
|
||||||
|
*/
|
||||||
|
class AssignIpToGuestJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/** All retries handled internally — no Laravel-level retry needed */
|
||||||
|
public int $tries = 1;
|
||||||
|
|
||||||
|
/** 5 attempts × ~90 s each + overhead */
|
||||||
|
public int $timeout = 600;
|
||||||
|
|
||||||
|
private const MAX_ATTEMPTS = 5;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public readonly int $moduleVmwareId,
|
||||||
|
public readonly string $vmId,
|
||||||
|
public readonly string $osFamily, // VmProvisionService::OS_LINUX | OS_WINDOWS
|
||||||
|
public readonly string $guestPassword, // New password from ChangePasswordViaVsphereJob
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
public function handle(VsphereHostSshService $ssh): void
|
||||||
|
{
|
||||||
|
Log::info('AssignIpToGuestJob: start', [
|
||||||
|
'module_vmware_id' => $this->moduleVmwareId,
|
||||||
|
'vm_id' => $this->vmId,
|
||||||
|
'os_family' => $this->osFamily,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Load ModuleVmware with the relations needed to derive network config
|
||||||
|
$moduleVmware = ModuleVmware::with([
|
||||||
|
'order.orderItems.product.ipAddress',
|
||||||
|
'vcenter',
|
||||||
|
])->find($this->moduleVmwareId);
|
||||||
|
|
||||||
|
if (!$moduleVmware) {
|
||||||
|
Log::error('AssignIpToGuestJob: ModuleVmware not found', [
|
||||||
|
'module_vmware_id' => $this->moduleVmwareId,
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve network config: product template IP → gateway, netmask, dns, networkId
|
||||||
|
$orderItem = $moduleVmware->order?->orderItems?->first();
|
||||||
|
$osModel = $orderItem?->os_id ? Os::with('osGroup.host')->find($orderItem->os_id) : null;
|
||||||
|
|
||||||
|
$networkId = $orderItem?->product?->ipAddress?->module_network_id ?? 1;
|
||||||
|
$gateway = $orderItem?->product?->ipAddress?->gateway ?? '0.0.0.0';
|
||||||
|
$netmask = $orderItem?->product?->ipAddress?->netmask ?? '255.255.255.0';
|
||||||
|
$dns = $orderItem?->product?->ipAddress?->dns ?? '8.8.8.8';
|
||||||
|
$hostId = $osModel?->osGroup?->host?->id ?? 1;
|
||||||
|
$cidr = (string) $this->netmaskToCidr($netmask);
|
||||||
|
|
||||||
|
// vCenter credentials
|
||||||
|
$vcenter = $moduleVmware->vcenter ?? vCenter::first();
|
||||||
|
$vcenterHost = $vcenter->host;
|
||||||
|
$vcenterUser = $vcenter->user;
|
||||||
|
$vcenterPass = SignRSA::DecryptFunc($vcenter->password);
|
||||||
|
|
||||||
|
// ─── Attempt loop ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
for ($attempt = 1; $attempt <= self::MAX_ATTEMPTS; $attempt++) {
|
||||||
|
Log::info('AssignIpToGuestJob: attempt', [
|
||||||
|
'attempt' => $attempt,
|
||||||
|
'module_vmware_id' => $this->moduleVmwareId,
|
||||||
|
'network_id' => $networkId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// [3] Reserve a free IP with transaction + row lock
|
||||||
|
$reservation = $this->reserveIp($networkId, $hostId, $gateway, $netmask, $dns);
|
||||||
|
|
||||||
|
if (!$reservation) {
|
||||||
|
Log::error('AssignIpToGuestJob: no free IPs in pool', [
|
||||||
|
'network_id' => $networkId,
|
||||||
|
'attempt' => $attempt,
|
||||||
|
]);
|
||||||
|
break; // No point retrying — no IPs available
|
||||||
|
}
|
||||||
|
|
||||||
|
$ip = $reservation->ip;
|
||||||
|
|
||||||
|
// [4] Pre-check: skip IPs that are already reachable (in use by another host)
|
||||||
|
if ($this->isIpReachable($ip)) {
|
||||||
|
Log::warning('AssignIpToGuestJob: IP reachable (likely in use), skipping', [
|
||||||
|
'ip' => $ip,
|
||||||
|
'attempt' => $attempt,
|
||||||
|
]);
|
||||||
|
$this->releaseIp($reservation);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [5] Assign IP into VM guest OS via PS1 script
|
||||||
|
$assignOutput = $ssh->assignIp(
|
||||||
|
vmId: $this->vmId,
|
||||||
|
ip: $ip,
|
||||||
|
gateway: $gateway,
|
||||||
|
cidr: $cidr,
|
||||||
|
vcenterHost: $vcenterHost,
|
||||||
|
vcenterUser: $vcenterUser,
|
||||||
|
vcenterPassword: $vcenterPass,
|
||||||
|
guestPassword: $this->guestPassword,
|
||||||
|
osFamily: $this->osFamily,
|
||||||
|
);
|
||||||
|
|
||||||
|
// [6] If assign failed → release and try next IP
|
||||||
|
if (!$this->isSuccess($assignOutput)) {
|
||||||
|
Log::warning('AssignIpToGuestJob: assignIp FAIL', [
|
||||||
|
'ip' => $ip,
|
||||||
|
'attempt' => $attempt,
|
||||||
|
'output' => trim($assignOutput),
|
||||||
|
]);
|
||||||
|
$this->releaseIp($reservation);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [7] Verify IP is active inside guest OS via PS1 script
|
||||||
|
$verifyOutput = $ssh->verifyIpInGuest(
|
||||||
|
vmId: $this->vmId,
|
||||||
|
ip: $ip,
|
||||||
|
vcenterHost: $vcenterHost,
|
||||||
|
vcenterUser: $vcenterUser,
|
||||||
|
vcenterPassword: $vcenterPass,
|
||||||
|
guestPassword: $this->guestPassword,
|
||||||
|
osFamily: $this->osFamily,
|
||||||
|
);
|
||||||
|
|
||||||
|
// [8] If verification failed → release and try next IP
|
||||||
|
if (!$this->isSuccess($verifyOutput)) {
|
||||||
|
Log::warning('AssignIpToGuestJob: verifyIpInGuest FAIL', [
|
||||||
|
'ip' => $ip,
|
||||||
|
'attempt' => $attempt,
|
||||||
|
'output' => trim($verifyOutput),
|
||||||
|
]);
|
||||||
|
$this->releaseIp($reservation);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [9] SUCCESS — persist confirmed IP to DB
|
||||||
|
$this->persistIp($reservation, $moduleVmware);
|
||||||
|
|
||||||
|
Log::info('AssignIpToGuestJob: IP assigned and verified', [
|
||||||
|
'module_vmware_id' => $this->moduleVmwareId,
|
||||||
|
'vm_id' => $this->vmId,
|
||||||
|
'ip' => $ip,
|
||||||
|
'attempt' => $attempt,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Dispatch credential email now that we have the real IP
|
||||||
|
SendVmCredentialsEmailJob::dispatch($this->moduleVmwareId);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [10] All attempts exhausted
|
||||||
|
Log::error('AssignIpToGuestJob: all attempts failed — marking VM inactive', [
|
||||||
|
'module_vmware_id' => $this->moduleVmwareId,
|
||||||
|
'vm_id' => $this->vmId,
|
||||||
|
'attempts' => self::MAX_ATTEMPTS,
|
||||||
|
]);
|
||||||
|
|
||||||
|
ModuleVmware::where('id', $this->moduleVmwareId)->update(['status' => 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function failed(\Throwable $e): void
|
||||||
|
{
|
||||||
|
Log::error('AssignIpToGuestJob: job failed with exception', [
|
||||||
|
'module_vmware_id' => $this->moduleVmwareId,
|
||||||
|
'vm_id' => $this->vmId,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
ModuleVmware::where('id', $this->moduleVmwareId)->update(['status' => 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// assignIp / verifyIp helpers
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reserve a free IP from the pool inside a DB transaction with row-level locks.
|
||||||
|
*
|
||||||
|
* Strategy:
|
||||||
|
* 1. Lock all existing ModuleIpAddress rows for the network → prevents double-pick
|
||||||
|
* 2. Lock the chosen IpPool row → prevents concurrent reservations
|
||||||
|
* 3. Create a pending ModuleIpAddress (status=0) to hold the slot
|
||||||
|
*
|
||||||
|
* Returns the pending ModuleIpAddress record, or null if pool is exhausted.
|
||||||
|
*/
|
||||||
|
private function reserveIp(
|
||||||
|
int $networkId,
|
||||||
|
int $hostId,
|
||||||
|
string $gateway,
|
||||||
|
string $netmask,
|
||||||
|
string $dns,
|
||||||
|
): ?ModuleIpAddress {
|
||||||
|
return DB::transaction(function () use ($networkId, $hostId, $gateway, $netmask, $dns) {
|
||||||
|
// Lock existing rows to prevent another job from picking the same IP
|
||||||
|
$usedIps = ModuleIpAddress::where('module_network_id', $networkId)
|
||||||
|
->lockForUpdate()
|
||||||
|
->pluck('ip')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
$ipPool = IpPool::where('module_network_id', $networkId)
|
||||||
|
->whereNotIn('ip_address', $usedIps)
|
||||||
|
->lockForUpdate()
|
||||||
|
->inRandomOrder()
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (!$ipPool) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a pending reservation — acts as a distributed lock on this IP
|
||||||
|
return ModuleIpAddress::create([
|
||||||
|
'vm_name' => '__pending__',
|
||||||
|
'module_host_id' => $hostId,
|
||||||
|
'module_network_id' => $networkId,
|
||||||
|
'ip' => $ipPool->ip_address,
|
||||||
|
'gateway' => $gateway,
|
||||||
|
'netmask' => $netmask,
|
||||||
|
'dns' => $dns,
|
||||||
|
'status' => 0, // 0 = pending/reserved, not yet confirmed
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release a failed reservation by deleting the pending ModuleIpAddress row.
|
||||||
|
* This returns the IP to the available pool.
|
||||||
|
*/
|
||||||
|
private function releaseIp(ModuleIpAddress $reservation): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$reservation->delete();
|
||||||
|
Log::info('AssignIpToGuestJob: IP reservation released', ['ip' => $reservation->ip]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::warning('AssignIpToGuestJob: could not release IP reservation', [
|
||||||
|
'ip' => $reservation->ip,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirm a successful IP assignment:
|
||||||
|
* - Update ModuleIpAddress: status → 1 (assigned), vm_name → real name
|
||||||
|
* - Update ModuleVmware: ip, dns, module_ip_address FK
|
||||||
|
*
|
||||||
|
* IP is only written to DB AFTER guest-side verification passes.
|
||||||
|
*/
|
||||||
|
private function persistIp(ModuleIpAddress $reservation, ModuleVmware $moduleVmware): void
|
||||||
|
{
|
||||||
|
DB::transaction(function () use ($reservation, $moduleVmware) {
|
||||||
|
$reservation->update([
|
||||||
|
'vm_name' => $moduleVmware->name ?? ('VM-' . $this->vmId),
|
||||||
|
'status' => 1, // assigned
|
||||||
|
]);
|
||||||
|
|
||||||
|
$moduleVmware->update([
|
||||||
|
'ip' => $reservation->ip,
|
||||||
|
'dns' => $reservation->dns,
|
||||||
|
'module_ip_address' => $reservation->id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// Micro-helpers
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ping the IP from the Laravel host (1 packet, 1 s timeout).
|
||||||
|
* Returns true if reachable → the IP is already in use by something else.
|
||||||
|
*/
|
||||||
|
private function isIpReachable(string $ip): bool
|
||||||
|
{
|
||||||
|
exec(sprintf('ping -c 1 -W 1 %s 2>/dev/null', escapeshellarg($ip)), $output, $exitCode);
|
||||||
|
return $exitCode === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** PS1 scripts output exactly "SUCCESS" or "FAIL". */
|
||||||
|
private function isSuccess(string $output): bool
|
||||||
|
{
|
||||||
|
return str_contains(strtoupper(trim($output)), 'SUCCESS');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert dotted-decimal netmask to CIDR prefix length (e.g. 255.255.255.0 → 24). */
|
||||||
|
private function netmaskToCidr(string $netmask): int
|
||||||
|
{
|
||||||
|
$bits = 0;
|
||||||
|
foreach (explode('.', $netmask) as $octet) {
|
||||||
|
$bits += substr_count(decbin((int) $octet), '1');
|
||||||
|
}
|
||||||
|
return $bits;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ use App\Models\ModuleVmware;
|
|||||||
use App\Models\vCenter;
|
use App\Models\vCenter;
|
||||||
use App\Services\VmProvisionService;
|
use App\Services\VmProvisionService;
|
||||||
use App\Services\VsphereHostSshService;
|
use App\Services\VsphereHostSshService;
|
||||||
|
use App\Jobs\VirtualMachine\Provision\AssignIpToGuestJob;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
@@ -29,8 +30,9 @@ use Illuminate\Support\Facades\Log;
|
|||||||
* [4] SSH into vSphere host
|
* [4] SSH into vSphere host
|
||||||
* [5] Execute changePassword{Linux|Windows}.ps1 with 5 params
|
* [5] Execute changePassword{Linux|Windows}.ps1 with 5 params
|
||||||
* [6] Parse "SUCCESS" / "FAIL" from script output
|
* [6] Parse "SUCCESS" / "FAIL" from script output
|
||||||
* [7] On SUCCESS → save encrypted password to DB, dispatch email
|
* [7] On SUCCESS → save encrypted password to DB
|
||||||
* [8] On FAIL → throw exception → job retries
|
* [8] Dispatch AssignIpToGuestJob with the new guest password
|
||||||
|
* [9] On FAIL → throw exception → job retries
|
||||||
*/
|
*/
|
||||||
class ChangePasswordViaVsphereJob implements ShouldQueue
|
class ChangePasswordViaVsphereJob implements ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -83,11 +85,16 @@ class ChangePasswordViaVsphereJob implements ShouldQueue
|
|||||||
// [6] Parse output — throw on FAIL so job retries
|
// [6] Parse output — throw on FAIL so job retries
|
||||||
$this->assertSuccess($output);
|
$this->assertSuccess($output);
|
||||||
|
|
||||||
// [7] Save encrypted + plain password; IP already set during VM creation
|
// [7] Save encrypted + plain password (IP will be set later by AssignIpToGuestJob)
|
||||||
$provision->updatePassword($this->moduleVmwareId, $newPassword);
|
$provision->updatePassword($this->moduleVmwareId, $newPassword);
|
||||||
|
|
||||||
// [8] Send credentials email (loads fresh VM data from DB)
|
// [8] Assign real IP into guest OS using the new password
|
||||||
SendVmCredentialsEmailJob::dispatch($this->moduleVmwareId);
|
AssignIpToGuestJob::dispatch(
|
||||||
|
$this->moduleVmwareId,
|
||||||
|
$this->vmId,
|
||||||
|
$this->osFamily,
|
||||||
|
$newPassword, // guest_password — MUST use new password, not template
|
||||||
|
);
|
||||||
|
|
||||||
Log::info('ChangePasswordViaVsphereJob: completed', [
|
Log::info('ChangePasswordViaVsphereJob: completed', [
|
||||||
'module_vmware_id' => $this->moduleVmwareId,
|
'module_vmware_id' => $this->moduleVmwareId,
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ final class VsphereHostSshService
|
|||||||
private const SCRIPT_LINUX = 'changePasswordLinux.ps1';
|
private const SCRIPT_LINUX = 'changePasswordLinux.ps1';
|
||||||
private const SCRIPT_WINDOWS = 'changePasswordWindows.ps1';
|
private const SCRIPT_WINDOWS = 'changePasswordWindows.ps1';
|
||||||
|
|
||||||
|
private const ASSIGN_DIR = 'C:\\CLISave\\AssignIP';
|
||||||
|
private const SCRIPT_ASSIGN_LINUX = 'assignIPLinux.ps1';
|
||||||
|
private const SCRIPT_ASSIGN_WINDOWS = 'assignIPWindows.ps1';
|
||||||
|
private const SCRIPT_VERIFY_LINUX = 'verifyIPLinux.ps1';
|
||||||
|
private const SCRIPT_VERIFY_WINDOWS = 'verifyIPWindows.ps1';
|
||||||
|
|
||||||
private const CONNECT_TIMEOUT = 20;
|
private const CONNECT_TIMEOUT = 20;
|
||||||
private const MAX_RETRIES = 5;
|
private const MAX_RETRIES = 5;
|
||||||
private const RETRY_DELAY = 5;
|
private const RETRY_DELAY = 5;
|
||||||
@@ -81,6 +87,112 @@ final class VsphereHostSshService
|
|||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a static IP to the VM guest OS via PS1 script.
|
||||||
|
* Calls assignIPLinux.ps1 or assignIPWindows.ps1 in C:\CLISave\AssignIP\.
|
||||||
|
*
|
||||||
|
* Script params: vm_id ip gateway cidr vcenter_host vcenter_user vcenter_password guest_password
|
||||||
|
* Script output: SUCCESS | FAIL
|
||||||
|
*/
|
||||||
|
public function assignIp(
|
||||||
|
string $vmId,
|
||||||
|
string $ip,
|
||||||
|
string $gateway,
|
||||||
|
string $cidr,
|
||||||
|
string $vcenterHost,
|
||||||
|
string $vcenterUser,
|
||||||
|
string $vcenterPassword,
|
||||||
|
string $guestPassword,
|
||||||
|
string $osFamily,
|
||||||
|
): string {
|
||||||
|
$script = strtoupper($osFamily) === VmProvisionService::OS_WINDOWS
|
||||||
|
? self::SCRIPT_ASSIGN_WINDOWS
|
||||||
|
: self::SCRIPT_ASSIGN_LINUX;
|
||||||
|
|
||||||
|
$remoteCmd = sprintf(
|
||||||
|
'cd %s && powershell -ExecutionPolicy Bypass -NonInteractive -File %s %s %s %s %s %s %s %s %s',
|
||||||
|
self::ASSIGN_DIR,
|
||||||
|
$script,
|
||||||
|
$this->warg($vmId),
|
||||||
|
$this->warg($ip),
|
||||||
|
$this->warg($gateway),
|
||||||
|
$this->warg($cidr),
|
||||||
|
$this->warg($vcenterHost),
|
||||||
|
$this->warg($vcenterUser),
|
||||||
|
$this->warg($vcenterPassword),
|
||||||
|
$this->warg($guestPassword),
|
||||||
|
);
|
||||||
|
|
||||||
|
Log::info('VsphereHostSshService: executing assignIp', [
|
||||||
|
'vm_id' => $vmId,
|
||||||
|
'ip' => $ip,
|
||||||
|
'script' => $script,
|
||||||
|
'os' => $osFamily,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$output = $this->sshExec($remoteCmd);
|
||||||
|
|
||||||
|
Log::info('VsphereHostSshService: assignIp output', [
|
||||||
|
'vm_id' => $vmId,
|
||||||
|
'ip' => $ip,
|
||||||
|
'output' => trim($output),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that an IP is active inside the VM guest OS.
|
||||||
|
* Calls verifyIPLinux.ps1 or verifyIPWindows.ps1 in C:\CLISave\AssignIP\.
|
||||||
|
*
|
||||||
|
* Script params: vm_id ip vcenter_host vcenter_user vcenter_password guest_password
|
||||||
|
* Script output: SUCCESS | FAIL
|
||||||
|
*/
|
||||||
|
public function verifyIpInGuest(
|
||||||
|
string $vmId,
|
||||||
|
string $ip,
|
||||||
|
string $vcenterHost,
|
||||||
|
string $vcenterUser,
|
||||||
|
string $vcenterPassword,
|
||||||
|
string $guestPassword,
|
||||||
|
string $osFamily,
|
||||||
|
): string {
|
||||||
|
$script = strtoupper($osFamily) === VmProvisionService::OS_WINDOWS
|
||||||
|
? self::SCRIPT_VERIFY_WINDOWS
|
||||||
|
: self::SCRIPT_VERIFY_LINUX;
|
||||||
|
|
||||||
|
$remoteCmd = sprintf(
|
||||||
|
'cd %s && powershell -ExecutionPolicy Bypass -NonInteractive -File %s %s %s %s %s %s %s',
|
||||||
|
self::ASSIGN_DIR,
|
||||||
|
$script,
|
||||||
|
$this->warg($vmId),
|
||||||
|
$this->warg($ip),
|
||||||
|
$this->warg($vcenterHost),
|
||||||
|
$this->warg($vcenterUser),
|
||||||
|
$this->warg($vcenterPassword),
|
||||||
|
$this->warg($guestPassword),
|
||||||
|
);
|
||||||
|
|
||||||
|
Log::info('VsphereHostSshService: executing verifyIpInGuest', [
|
||||||
|
'vm_id' => $vmId,
|
||||||
|
'ip' => $ip,
|
||||||
|
'script' => $script,
|
||||||
|
'os' => $osFamily,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$output = $this->sshExec($remoteCmd);
|
||||||
|
|
||||||
|
Log::info('VsphereHostSshService: verifyIpInGuest output', [
|
||||||
|
'vm_id' => $vmId,
|
||||||
|
'ip' => $ip,
|
||||||
|
'output' => trim($output),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a command on the vSphere SSH host via sshpass.
|
* Run a command on the vSphere SSH host via sshpass.
|
||||||
* Retries up to MAX_RETRIES times on connection failure.
|
* Retries up to MAX_RETRIES times on connection failure.
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
# assignIPLinux.ps1
|
||||||
|
# Deploy to: C:\CLISave\AssignIP\assignIPLinux.ps1 on the vSphere Windows host
|
||||||
|
#
|
||||||
|
# Usage: powershell -ExecutionPolicy Bypass -NonInteractive -File assignIPLinux.ps1 \
|
||||||
|
# <vm_id> <ip> <gateway> <cidr> <vcenter_host> <vcenter_user> <vcenter_password> <guest_password>
|
||||||
|
#
|
||||||
|
# Output: SUCCESS | FAIL (exactly one line)
|
||||||
|
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)][string]$VmId,
|
||||||
|
[Parameter(Mandatory=$true)][string]$Ip,
|
||||||
|
[Parameter(Mandatory=$true)][string]$Gateway,
|
||||||
|
[Parameter(Mandatory=$true)][string]$Cidr,
|
||||||
|
[Parameter(Mandatory=$true)][string]$VCenterHost,
|
||||||
|
[Parameter(Mandatory=$true)][string]$VCenterUser,
|
||||||
|
[Parameter(Mandatory=$true)][string]$VCenterPassword,
|
||||||
|
[Parameter(Mandatory=$true)][string]$GuestPassword
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
try {
|
||||||
|
Import-Module VMware.VimAutomation.Core -ErrorAction Stop
|
||||||
|
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false | Out-Null
|
||||||
|
Set-PowerCLIConfiguration -Scope User -ParticipateInCEIP $false -Confirm:$false | Out-Null
|
||||||
|
|
||||||
|
Connect-VIServer -Server $VCenterHost -User $VCenterUser -Password $VCenterPassword -Force -ErrorAction Stop | Out-Null
|
||||||
|
|
||||||
|
$vm = Get-VM -Id "VirtualMachine-$VmId" -ErrorAction Stop
|
||||||
|
|
||||||
|
# Bash script executed inside the Linux guest via VMware Tools (Invoke-VMScript)
|
||||||
|
$bashScript = @"
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Find the first usable non-loopback interface
|
||||||
|
IFACE=`$(ip -o link show | awk -F': ' '{print `$2}' | grep -Ev '^(lo|vir|docker|br-)' | head -1)
|
||||||
|
if [ -z "`$IFACE" ]; then
|
||||||
|
echo "ERROR: no usable network interface found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Apply static IP immediately (survives until reboot without persistence layer)
|
||||||
|
ip addr flush dev `$IFACE 2>/dev/null || true
|
||||||
|
ip addr add $Ip/$Cidr dev `$IFACE
|
||||||
|
ip link set `$IFACE up
|
||||||
|
ip route del default 2>/dev/null || true
|
||||||
|
ip route add default via $Gateway dev `$IFACE
|
||||||
|
|
||||||
|
# Persist via NetworkManager (RHEL/CentOS/Ubuntu 20+)
|
||||||
|
if command -v nmcli &>/dev/null; then
|
||||||
|
CON_NAME=`$(nmcli -t -f NAME con show 2>/dev/null | head -1)
|
||||||
|
if [ -n "`$CON_NAME" ]; then
|
||||||
|
nmcli con mod "`$CON_NAME" \
|
||||||
|
ipv4.method manual \
|
||||||
|
ipv4.addresses "$Ip/$Cidr" \
|
||||||
|
ipv4.gateway "$Gateway" 2>/dev/null || true
|
||||||
|
nmcli con up "`$CON_NAME" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
elif [ -d /etc/netplan ]; then
|
||||||
|
# Netplan fallback (Ubuntu 18+ without NM)
|
||||||
|
cat > /etc/netplan/99-static.yaml << 'YAML'
|
||||||
|
network:
|
||||||
|
version: 2
|
||||||
|
ethernets:
|
||||||
|
IFACE_PLACEHOLDER:
|
||||||
|
dhcp4: false
|
||||||
|
addresses: [IP_PLACEHOLDER/CIDR_PLACEHOLDER]
|
||||||
|
gateway4: GATEWAY_PLACEHOLDER
|
||||||
|
YAML
|
||||||
|
sed -i "s/IFACE_PLACEHOLDER/`$IFACE/g;s|IP_PLACEHOLDER/CIDR_PLACEHOLDER|$Ip/$Cidr|g;s/GATEWAY_PLACEHOLDER/$Gateway/g" /etc/netplan/99-static.yaml
|
||||||
|
netplan apply 2>/dev/null || true
|
||||||
|
elif [ -d /etc/sysconfig/network-scripts ]; then
|
||||||
|
# RHEL/CentOS legacy ifcfg
|
||||||
|
cat > /etc/sysconfig/network-scripts/ifcfg-`$IFACE << EOF
|
||||||
|
DEVICE=`$IFACE
|
||||||
|
BOOTPROTO=static
|
||||||
|
ONBOOT=yes
|
||||||
|
IPADDR=$Ip
|
||||||
|
PREFIX=$Cidr
|
||||||
|
GATEWAY=$Gateway
|
||||||
|
EOF
|
||||||
|
ifdown `$IFACE 2>/dev/null || true
|
||||||
|
ifup `$IFACE 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo SUCCESS
|
||||||
|
"@
|
||||||
|
|
||||||
|
$result = Invoke-VMScript -VM $vm `
|
||||||
|
-ScriptText $bashScript `
|
||||||
|
-GuestUser "root" `
|
||||||
|
-GuestPassword $GuestPassword `
|
||||||
|
-ScriptType Bash `
|
||||||
|
-ErrorAction Stop
|
||||||
|
|
||||||
|
if ($result.ScriptOutput -match "SUCCESS") {
|
||||||
|
Write-Output "SUCCESS"
|
||||||
|
} else {
|
||||||
|
Write-Output "FAIL"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Output "FAIL"
|
||||||
|
} finally {
|
||||||
|
Disconnect-VIServer -Confirm:$false -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# assignIPWindows.ps1
|
||||||
|
# Deploy to: C:\CLISave\AssignIP\assignIPWindows.ps1 on the vSphere Windows host
|
||||||
|
#
|
||||||
|
# Usage: powershell -ExecutionPolicy Bypass -NonInteractive -File assignIPWindows.ps1 \
|
||||||
|
# <vm_id> <ip> <gateway> <cidr> <vcenter_host> <vcenter_user> <vcenter_password> <guest_password>
|
||||||
|
#
|
||||||
|
# Output: SUCCESS | FAIL (exactly one line)
|
||||||
|
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)][string]$VmId,
|
||||||
|
[Parameter(Mandatory=$true)][string]$Ip,
|
||||||
|
[Parameter(Mandatory=$true)][string]$Gateway,
|
||||||
|
[Parameter(Mandatory=$true)][string]$Cidr,
|
||||||
|
[Parameter(Mandatory=$true)][string]$VCenterHost,
|
||||||
|
[Parameter(Mandatory=$true)][string]$VCenterUser,
|
||||||
|
[Parameter(Mandatory=$true)][string]$VCenterPassword,
|
||||||
|
[Parameter(Mandatory=$true)][string]$GuestPassword
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
try {
|
||||||
|
Import-Module VMware.VimAutomation.Core -ErrorAction Stop
|
||||||
|
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false | Out-Null
|
||||||
|
Set-PowerCLIConfiguration -Scope User -ParticipateInCEIP $false -Confirm:$false | Out-Null
|
||||||
|
|
||||||
|
Connect-VIServer -Server $VCenterHost -User $VCenterUser -Password $VCenterPassword -Force -ErrorAction Stop | Out-Null
|
||||||
|
|
||||||
|
$vm = Get-VM -Id "VirtualMachine-$VmId" -ErrorAction Stop
|
||||||
|
|
||||||
|
# PowerShell script executed inside the Windows guest via VMware Tools
|
||||||
|
$guestScript = @"
|
||||||
|
`$ErrorActionPreference = 'Stop'
|
||||||
|
try {
|
||||||
|
# Find the first active ethernet adapter that is not a loopback
|
||||||
|
`$adapter = Get-NetAdapter |
|
||||||
|
Where-Object { `$_.Status -eq 'Up' -and `$_.InterfaceDescription -notlike '*Loopback*' } |
|
||||||
|
Sort-Object -Property InterfaceIndex |
|
||||||
|
Select-Object -First 1
|
||||||
|
|
||||||
|
if (-not `$adapter) { throw 'No active network adapter found' }
|
||||||
|
|
||||||
|
# Remove existing IPv4 addresses and routes on this interface
|
||||||
|
`$adapter | Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||||||
|
Remove-NetIPAddress -Confirm:`$false -ErrorAction SilentlyContinue
|
||||||
|
Get-NetRoute -InterfaceIndex `$adapter.InterfaceIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||||||
|
Remove-NetRoute -Confirm:`$false -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# Set new static IP
|
||||||
|
New-NetIPAddress `
|
||||||
|
-InterfaceIndex `$adapter.InterfaceIndex `
|
||||||
|
-IPAddress '$Ip' `
|
||||||
|
-PrefixLength $Cidr `
|
||||||
|
-DefaultGateway '$Gateway' `
|
||||||
|
-ErrorAction Stop | Out-Null
|
||||||
|
|
||||||
|
Write-Output 'SUCCESS'
|
||||||
|
} catch {
|
||||||
|
Write-Output 'FAIL'
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
|
||||||
|
$result = Invoke-VMScript -VM $vm `
|
||||||
|
-ScriptText $guestScript `
|
||||||
|
-GuestUser "administrator" `
|
||||||
|
-GuestPassword $GuestPassword `
|
||||||
|
-ScriptType Powershell `
|
||||||
|
-ErrorAction Stop
|
||||||
|
|
||||||
|
if ($result.ScriptOutput -match "SUCCESS") {
|
||||||
|
Write-Output "SUCCESS"
|
||||||
|
} else {
|
||||||
|
Write-Output "FAIL"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Output "FAIL"
|
||||||
|
} finally {
|
||||||
|
Disconnect-VIServer -Confirm:$false -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# verifyIPLinux.ps1
|
||||||
|
# Deploy to: C:\CLISave\AssignIP\verifyIPLinux.ps1 on the vSphere Windows host
|
||||||
|
#
|
||||||
|
# Usage: powershell -ExecutionPolicy Bypass -NonInteractive -File verifyIPLinux.ps1 \
|
||||||
|
# <vm_id> <ip> <vcenter_host> <vcenter_user> <vcenter_password> <guest_password>
|
||||||
|
#
|
||||||
|
# Checks that the IP is configured and the interface is UP inside the Linux guest.
|
||||||
|
# Output: SUCCESS | FAIL (exactly one line)
|
||||||
|
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)][string]$VmId,
|
||||||
|
[Parameter(Mandatory=$true)][string]$Ip,
|
||||||
|
[Parameter(Mandatory=$true)][string]$VCenterHost,
|
||||||
|
[Parameter(Mandatory=$true)][string]$VCenterUser,
|
||||||
|
[Parameter(Mandatory=$true)][string]$VCenterPassword,
|
||||||
|
[Parameter(Mandatory=$true)][string]$GuestPassword
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
try {
|
||||||
|
Import-Module VMware.VimAutomation.Core -ErrorAction Stop
|
||||||
|
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false | Out-Null
|
||||||
|
Set-PowerCLIConfiguration -Scope User -ParticipateInCEIP $false -Confirm:$false | Out-Null
|
||||||
|
|
||||||
|
Connect-VIServer -Server $VCenterHost -User $VCenterUser -Password $VCenterPassword -Force -ErrorAction Stop | Out-Null
|
||||||
|
|
||||||
|
$vm = Get-VM -Id "VirtualMachine-$VmId" -ErrorAction Stop
|
||||||
|
|
||||||
|
# Bash snippet: verify IP exists on an active (UP) interface
|
||||||
|
$verifyScript = @"
|
||||||
|
#!/bin/bash
|
||||||
|
# Check the IP appears in 'ip a' output on an interface that is UP (state UP)
|
||||||
|
if ip a | grep -q '$Ip'; then
|
||||||
|
IFACE=`$(ip -o addr show | awk '/$Ip/{print `$2}' | head -1)
|
||||||
|
STATE=`$(cat /sys/class/net/`$IFACE/operstate 2>/dev/null || echo 'unknown')
|
||||||
|
if [ "`$STATE" = "up" ] || [ "`$STATE" = "unknown" ]; then
|
||||||
|
echo SUCCESS
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo FAIL
|
||||||
|
exit 1
|
||||||
|
"@
|
||||||
|
|
||||||
|
$result = Invoke-VMScript -VM $vm `
|
||||||
|
-ScriptText $verifyScript `
|
||||||
|
-GuestUser "root" `
|
||||||
|
-GuestPassword $GuestPassword `
|
||||||
|
-ScriptType Bash `
|
||||||
|
-ErrorAction Stop
|
||||||
|
|
||||||
|
if ($result.ScriptOutput -match "SUCCESS") {
|
||||||
|
Write-Output "SUCCESS"
|
||||||
|
} else {
|
||||||
|
Write-Output "FAIL"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Output "FAIL"
|
||||||
|
} finally {
|
||||||
|
Disconnect-VIServer -Confirm:$false -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# verifyIPWindows.ps1
|
||||||
|
# Deploy to: C:\CLISave\AssignIP\verifyIPWindows.ps1 on the vSphere Windows host
|
||||||
|
#
|
||||||
|
# Usage: powershell -ExecutionPolicy Bypass -NonInteractive -File verifyIPWindows.ps1 \
|
||||||
|
# <vm_id> <ip> <vcenter_host> <vcenter_user> <vcenter_password> <guest_password>
|
||||||
|
#
|
||||||
|
# Checks that the IP is configured and not in Duplicate state inside the Windows guest.
|
||||||
|
# Output: SUCCESS | FAIL (exactly one line)
|
||||||
|
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)][string]$VmId,
|
||||||
|
[Parameter(Mandatory=$true)][string]$Ip,
|
||||||
|
[Parameter(Mandatory=$true)][string]$VCenterHost,
|
||||||
|
[Parameter(Mandatory=$true)][string]$VCenterUser,
|
||||||
|
[Parameter(Mandatory=$true)][string]$VCenterPassword,
|
||||||
|
[Parameter(Mandatory=$true)][string]$GuestPassword
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
try {
|
||||||
|
Import-Module VMware.VimAutomation.Core -ErrorAction Stop
|
||||||
|
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false | Out-Null
|
||||||
|
Set-PowerCLIConfiguration -Scope User -ParticipateInCEIP $false -Confirm:$false | Out-Null
|
||||||
|
|
||||||
|
Connect-VIServer -Server $VCenterHost -User $VCenterUser -Password $VCenterPassword -Force -ErrorAction Stop | Out-Null
|
||||||
|
|
||||||
|
$vm = Get-VM -Id "VirtualMachine-$VmId" -ErrorAction Stop
|
||||||
|
|
||||||
|
# PowerShell snippet run inside the Windows guest
|
||||||
|
$guestScript = @"
|
||||||
|
`$ErrorActionPreference = 'SilentlyContinue'
|
||||||
|
`$addr = Get-NetIPAddress -IPAddress '$Ip' -ErrorAction SilentlyContinue
|
||||||
|
if (`$addr -and `$addr.AddressState -ne 'Duplicate' -and `$addr.AddressState -ne 'Invalid') {
|
||||||
|
Write-Output 'SUCCESS'
|
||||||
|
} else {
|
||||||
|
Write-Output 'FAIL'
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
|
||||||
|
$result = Invoke-VMScript -VM $vm `
|
||||||
|
-ScriptText $guestScript `
|
||||||
|
-GuestUser "administrator" `
|
||||||
|
-GuestPassword $GuestPassword `
|
||||||
|
-ScriptType Powershell `
|
||||||
|
-ErrorAction Stop
|
||||||
|
|
||||||
|
if ($result.ScriptOutput -match "SUCCESS") {
|
||||||
|
Write-Output "SUCCESS"
|
||||||
|
} else {
|
||||||
|
Write-Output "FAIL"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Output "FAIL"
|
||||||
|
} finally {
|
||||||
|
Disconnect-VIServer -Confirm:$false -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user