newVmId === null) { throw new RuntimeException('Rebuild IP: newVmId is null'); } if ($ctx->ip === '') { throw new RuntimeException('Rebuild IP: missing existing IP in context'); } // Load ModuleVmware + relations EXACTLY like AssignIpToGuestJob does // so we read parameters from the same DB locations. $moduleVmware = ModuleVmware::with([ 'order.orderItems.product.ipAddress', 'vcenter', ])->find($ctx->moduleVmwareId); if (!$moduleVmware) { throw new RuntimeException( "Rebuild IP: ModuleVmware #{$ctx->moduleVmwareId} not found" ); } // DNS comes from product.ipAddress.dns — same source AssignIpToGuestJob uses. $orderItem = $moduleVmware->order?->orderItems?->first(); $dns = $orderItem?->product?->ipAddress?->dns ?: ($ctx->dns ?: '8.8.8.8'); // Host id is osGroup.host.id of the NEW OS — same source AssignIpToGuestJob uses // (it loads Os->osGroup->host for whatever OS is on the order item). $osModel = Os::with('osGroup.host')->find($ctx->newOsId); $hostId = $osModel?->osGroup?->host?->id ?? $ctx->hostId ?? 1; // Gateway + CIDR come from the ip_pools row already assigned to this // service — same DB source the create flow reads at reservation time. // For rebuild we never reserve; we just look it up by ip_address. $ipPool = IpPool::where('ip_address', $ctx->ip)->first(); $gateway = (string) ($ipPool?->gateway ?? $ctx->gateway); $cidr = (string) ((int) ($ipPool?->cidr ?? $ctx->cidr ?: 24)); if ($gateway === '') { throw new RuntimeException( "Rebuild IP: cannot resolve gateway for IP {$ctx->ip} (no ip_pools row and context empty)" ); } // vCenter credentials — identical decode path as AssignIpToGuestJob. $vcenter = $moduleVmware->vcenter ?? VCenterModel::find($ctx->vcenterId) ?? VCenterModel::first(); if (!$vcenter) { throw new RuntimeException('Rebuild IP: no vCenter record available'); } $vcenterHost = (string) $vcenter->host; $vcenterUser = (string) $vcenter->user; $vcenterPass = SignRSA::DecryptFunc($vcenter->password); // Guest OS default admin user per OS family — same convention. $guestUser = strtoupper($ctx->osFamily) === 'WINDOWS' ? 'Administrator' : 'root'; Log::info('VmRebuildIpService: assigning existing IP to new VM', [ 'module_vmware_id' => $ctx->moduleVmwareId, 'new_vm_id' => $ctx->newVmId, 'ip' => $ctx->ip, 'gateway' => $gateway, 'cidr' => $cidr, 'dns' => $dns, 'host_id' => $hostId, 'os_family' => $ctx->osFamily, 'guest_user' => $guestUser, ]); $lastAssignOutput = ''; $lastVerifyOutput = ''; for ($attempt = 1; $attempt <= self::MAX_ATTEMPTS; $attempt++) { // [4] Call assignIP{Linux|Windows}.ps1 — IDENTICAL invocation // to AssignIpToGuestJob, same VsphereHostSshService::assignIp(). $lastAssignOutput = $this->ssh->assignIp( vmId: $ctx->newVmId, ip: $ctx->ip, gateway: $gateway, cidr: $cidr, vcenterHost: $vcenterHost, vcenterUser: $vcenterUser, vcenterPassword: $vcenterPass, guestPassword: VmRebuildService::TEMPLATE_PASSWORD, osFamily: $ctx->osFamily, dns: $dns, guestUser: $guestUser, ); if (!$this->isSuccess($lastAssignOutput)) { Log::warning('VmRebuildIpService: assignIp FAIL', [ 'ip' => $ctx->ip, 'attempt' => $attempt, 'new_vm_id' => $ctx->newVmId, 'output' => trim($lastAssignOutput), ]); if ($attempt < self::MAX_ATTEMPTS) { sleep(self::ATTEMPT_DELAY); continue; } throw new RuntimeException( 'Rebuild IP: assign script failed after ' . self::MAX_ATTEMPTS . ' attempts — last output: ' . trim($lastAssignOutput), ); } if (strtoupper($ctx->osFamily) === VmRebuildService::OS_WINDOWS) { Log::info('VmRebuildIpService: Windows IP assigned', [ 'module_vmware_id' => $ctx->moduleVmwareId, 'new_vm_id' => $ctx->newVmId, 'ip' => $ctx->ip, 'attempt' => $attempt, ]); return; } // [6] Call verifyIP{Linux|Windows}.ps1 — same as AssignIpToGuestJob. $lastVerifyOutput = $this->ssh->verifyIpInGuest( vmId: $ctx->newVmId, ip: $ctx->ip, vcenterHost: $vcenterHost, vcenterUser: $vcenterUser, vcenterPassword: $vcenterPass, guestPassword: VmRebuildService::TEMPLATE_PASSWORD, osFamily: $ctx->osFamily, guestUser: $guestUser, ); if ($this->isSuccess($lastVerifyOutput)) { Log::info('VmRebuildIpService: IP assigned and verified', [ 'module_vmware_id' => $ctx->moduleVmwareId, 'new_vm_id' => $ctx->newVmId, 'ip' => $ctx->ip, 'attempt' => $attempt, ]); return; } Log::warning('VmRebuildIpService: verifyIpInGuest FAIL', [ 'ip' => $ctx->ip, 'attempt' => $attempt, 'new_vm_id' => $ctx->newVmId, 'output' => trim($lastVerifyOutput), ]); if ($attempt < self::MAX_ATTEMPTS) { sleep(self::ATTEMPT_DELAY); } } throw new RuntimeException( 'Rebuild IP: verify script failed after ' . self::MAX_ATTEMPTS . ' attempts — last output: ' . trim($lastVerifyOutput), ); } /** PS1 scripts return JSON {"status":"ok"} or plain SUCCESS — same parser as AssignIpToGuestJob. */ private function isSuccess(string $output): bool { if (str_contains(strtoupper(trim($output)), 'SUCCESS')) { return true; } $data = json_decode(trim($output), true); return is_array($data) && ($data['status'] ?? '') === 'ok'; } }