Functions/Private/Get-MigrationRuleImpact.ps1
|
function Get-MigrationRuleImpact { <# .SYNOPSIS Evaluates whether a proposed live migration would break, fix, or be neutral with respect to the configured affinity and anti-affinity rules. .DESCRIPTION For each rule that references the VM being migrated, the function simulates the post-migration placement and compares it to the current placement: Break (currently satisfied → will be violated): Hard rule → HasHardViolation = $true (caller should exclude this destination) Soft rule → HasSoftViolation = $true (caller should apply a score penalty) Fix (currently violated → will be satisfied): → FixesViolation = $true (caller should apply a score bonus) Neutral (no change in satisfaction status) → no flags set. .PARAMETER Placement Optional VMName → HostNode hashtable to evaluate against instead of the snapshot's own placement. Planners pass their simulated placement here so that migrations already planned earlier in the same pass are taken into account — otherwise two moves that are each rule-safe against the original placement could together break a hard rule. .OUTPUTS PSCustomObject: HasHardViolation, HasSoftViolation, FixesViolation, HardReasons[], SoftReasons[], FixReasons[] #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $VMName, [Parameter(Mandatory)] [string] $DestinationNode, [Parameter(Mandatory)] [PSCustomObject] $Snapshot, [PSCustomObject[]] $RuleSet, [hashtable] $Placement ) $empty = [PSCustomObject]@{ HasHardViolation = $false HasSoftViolation = $false FixesViolation = $false HardReasons = @() SoftReasons = @() FixReasons = @() } if (-not $RuleSet -or $RuleSet.Count -eq 0) { return $empty } # Current placement: VMName → HostNode (simulated placement when supplied) $vmHost = @{} if ($null -ne $Placement) { foreach ($key in $Placement.Keys) { $vmHost[$key] = $Placement[$key] } } else { foreach ($vm in $Snapshot.VMs) { $vmHost[$vm.VMName] = $vm.HostNode } } $hardReasons = [System.Collections.Generic.List[string]]::new() $softReasons = [System.Collections.Generic.List[string]]::new() $fixReasons = [System.Collections.Generic.List[string]]::new() foreach ($rule in $RuleSet) { if ($rule.VMs -notcontains $VMName) { continue } $activeVMs = @($rule.VMs | Where-Object { $vmHost.ContainsKey($_) }) if ($activeVMs.Count -eq 0) { continue } $severity = if ($rule.Enforced) { 'enforced' } else { 'soft' } switch ($rule.Type) { 'VmVmAffinity' { # All members must share one host. $currentHosts = @($activeVMs | ForEach-Object { $vmHost[$_] } | Select-Object -Unique) $simHosts = @($activeVMs | ForEach-Object { if ($_ -eq $VMName) { $DestinationNode } else { $vmHost[$_] } } | Select-Object -Unique) $wasSatisfied = $currentHosts.Count -le 1 $willSatisfied = $simHosts.Count -le 1 if ($wasSatisfied -and -not $willSatisfied) { $msg = "Breaks $severity affinity rule '$($rule.Name)' — members would span: $($simHosts -join ', ')" if ($rule.Enforced) { $hardReasons.Add($msg) } else { $softReasons.Add($msg) } } elseif (-not $wasSatisfied -and $willSatisfied) { $fixReasons.Add("Satisfies affinity rule '$($rule.Name)' — all members consolidated on '$DestinationNode'") } } 'VmVmAntiAffinity' { # No two members may share a host. $currentConflicts = @($activeVMs | Group-Object { $vmHost[$_] } | Where-Object { $_.Count -gt 1 }) $simConflicts = @($activeVMs | Group-Object { if ($_ -eq $VMName) { $DestinationNode } else { $vmHost[$_] } } | Where-Object { $_.Count -gt 1 }) $wasSatisfied = $currentConflicts.Count -eq 0 $willSatisfied = $simConflicts.Count -eq 0 if ($wasSatisfied -and -not $willSatisfied) { $peer = ($simConflicts[0].Group | Where-Object { $_ -ne $VMName })[0] $msg = "Breaks $severity anti-affinity rule '$($rule.Name)' — '$VMName' would share '$DestinationNode' with '$peer'" if ($rule.Enforced) { $hardReasons.Add($msg) } else { $softReasons.Add($msg) } } elseif (-not $wasSatisfied -and $willSatisfied) { $fixReasons.Add("Satisfies anti-affinity rule '$($rule.Name)' — '$VMName' separated from all group members") } } 'VmHostAffinity' { # VM must reside on one of the listed hosts. $wasSatisfied = $rule.Hosts -contains $vmHost[$VMName] $willSatisfied = $rule.Hosts -contains $DestinationNode if ($wasSatisfied -and -not $willSatisfied) { $msg = "Breaks $severity host-affinity rule '$($rule.Name)' — '$DestinationNode' is not in [$(($rule.Hosts) -join ', ')]" if ($rule.Enforced) { $hardReasons.Add($msg) } else { $softReasons.Add($msg) } } elseif (-not $wasSatisfied -and $willSatisfied) { $fixReasons.Add("Satisfies host-affinity rule '$($rule.Name)' — '$VMName' moves onto allowed host '$DestinationNode'") } } 'VmHostAntiAffinity' { # VM must NOT reside on any of the listed hosts. $wasSatisfied = $rule.Hosts -notcontains $vmHost[$VMName] $willSatisfied = $rule.Hosts -notcontains $DestinationNode if ($wasSatisfied -and -not $willSatisfied) { $msg = "Breaks $severity host-anti-affinity rule '$($rule.Name)' — '$DestinationNode' is an excluded host" if ($rule.Enforced) { $hardReasons.Add($msg) } else { $softReasons.Add($msg) } } elseif (-not $wasSatisfied -and $willSatisfied) { $fixReasons.Add("Satisfies host-anti-affinity rule '$($rule.Name)' — '$VMName' moves off excluded host") } } } } [PSCustomObject]@{ HasHardViolation = $hardReasons.Count -gt 0 HasSoftViolation = $softReasons.Count -gt 0 FixesViolation = $fixReasons.Count -gt 0 HardReasons = $hardReasons.ToArray() SoftReasons = $softReasons.ToArray() FixReasons = $fixReasons.ToArray() } } |