feat: Implement IP assignment and verification for VM guests; add related PowerShell scripts

This commit is contained in:
pbao9
2026-04-18 08:51:02 +07:00
parent f4145088e1
commit 8aef607e1f
8 changed files with 777 additions and 59 deletions
@@ -2,21 +2,16 @@
namespace App\Jobs\VirtualMachine\Create;
use App\Http\Facade\SignRSA;
use App\Http\Services\Telegram;
use App\Http\Services\vCenter;
use App\Jobs\Mail\Customer\VMCreationFailedJob;
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;
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;
@@ -222,31 +217,19 @@ class CreateVirtualMachineJob implements ShouldQueue
// VM started — SSH vào vSphere host để đổi mật khẩu qua PS1 script
try {
$customer = $this->item->order->customer;
$customer = $this->item->order->customer;
$serverName = $this->item->server_name ?? 'VM-' . $vmId;
$networkId = $product->ipAddress?->module_network_id ?? 1;
$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);
$months = (int) ($this->item->months ?? 0);
$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([
'name' => $serverName,
'module_ip_address' => $moduleIpAddress->id,
'ip' => $availableIP,
'module_ip_address' => null, // set by AssignIpToGuestJob
'ip' => null, // set by AssignIpToGuestJob
'port' => $vmPort,
'order_id' => $this->item->order->id,
'first_payment' => $firstPayment,
@@ -267,7 +250,9 @@ class CreateVirtualMachineJob implements ShouldQueue
// Sync services table
$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(
$moduleVmware->id,
$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
*/
@@ -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\Services\VmProvisionService;
use App\Services\VsphereHostSshService;
use App\Jobs\VirtualMachine\Provision\AssignIpToGuestJob;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
@@ -29,8 +30,9 @@ use Illuminate\Support\Facades\Log;
* [4] SSH into vSphere host
* [5] Execute changePassword{Linux|Windows}.ps1 with 5 params
* [6] Parse "SUCCESS" / "FAIL" from script output
* [7] On SUCCESS → save encrypted password to DB, dispatch email
* [8] On FAIL → throw exception → job retries
* [7] On SUCCESS → save encrypted password to DB
* [8] Dispatch AssignIpToGuestJob with the new guest password
* [9] On FAIL → throw exception → job retries
*/
class ChangePasswordViaVsphereJob implements ShouldQueue
{
@@ -83,11 +85,16 @@ class ChangePasswordViaVsphereJob implements ShouldQueue
// [6] Parse output — throw on FAIL so job retries
$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);
// [8] Send credentials email (loads fresh VM data from DB)
SendVmCredentialsEmailJob::dispatch($this->moduleVmwareId);
// [8] Assign real IP into guest OS using the new password
AssignIpToGuestJob::dispatch(
$this->moduleVmwareId,
$this->vmId,
$this->osFamily,
$newPassword, // guest_password — MUST use new password, not template
);
Log::info('ChangePasswordViaVsphereJob: completed', [
'module_vmware_id' => $this->moduleVmwareId,
+112
View File
@@ -28,6 +28,12 @@ final class VsphereHostSshService
private const SCRIPT_LINUX = 'changePasswordLinux.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 MAX_RETRIES = 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.
* Retries up to MAX_RETRIES times on connection failure.
+107
View File
@@ -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
}
+80
View File
@@ -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
}
+63
View File
@@ -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
}
+58
View File
@@ -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
}