Functions/Private/Get-StorageMigrationRuleImpact.ps1
|
function Get-StorageMigrationRuleImpact { <# .SYNOPSIS Evaluates whether a proposed storage migration (moving a VM's VHDs to a different CSV) would break, fix, or be neutral with respect to the configured storage affinity / anti-affinity rules. .DESCRIPTION Storage analogue of Get-MigrationRuleImpact. For each storage rule that references the VM being migrated, simulates the post-migration CSV 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) .OUTPUTS PSCustomObject: HasHardViolation, HasSoftViolation, FixesViolation, HardReasons[], SoftReasons[], FixReasons[] #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $VMName, [Parameter(Mandatory)] [string] $DestinationCsvName, [Parameter(Mandatory)] [PSCustomObject] $Snapshot, [PSCustomObject[]] $RuleSet ) $empty = [PSCustomObject]@{ HasHardViolation = $false HasSoftViolation = $false FixesViolation = $false HardReasons = @() SoftReasons = @() FixReasons = @() } if (-not $RuleSet -or $RuleSet.Count -eq 0) { return $empty } $pathToName = @{} foreach ($csv in $Snapshot.CSVs) { $pathToName[$csv.Path] = $csv.Name } $vmCsv = @{} foreach ($vm in $Snapshot.VMs) { $vmCsv[$vm.VMName] = if ($pathToName.ContainsKey($vm.PrimaryCSV)) { $pathToName[$vm.PrimaryCSV] } else { $vm.PrimaryCSV } } $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 { $vmCsv.ContainsKey($_) }) if ($activeVMs.Count -eq 0) { continue } $severity = if ($rule.Enforced) { 'enforced' } else { 'soft' } switch ($rule.Type) { 'VmVmCsvAffinity' { # All members must share one CSV. $currentCsvs = @($activeVMs | ForEach-Object { $vmCsv[$_] } | Select-Object -Unique) $simCsvs = @($activeVMs | ForEach-Object { if ($_ -eq $VMName) { $DestinationCsvName } else { $vmCsv[$_] } } | Select-Object -Unique) $wasSatisfied = $currentCsvs.Count -le 1 $willSatisfied = $simCsvs.Count -le 1 if ($wasSatisfied -and -not $willSatisfied) { $msg = "Breaks $severity storage affinity rule '$($rule.Name)' — members would span CSVs: $($simCsvs -join ', ')" if ($rule.Enforced) { $hardReasons.Add($msg) } else { $softReasons.Add($msg) } } elseif (-not $wasSatisfied -and $willSatisfied) { $fixReasons.Add("Satisfies storage affinity rule '$($rule.Name)' — all members consolidated on CSV '$DestinationCsvName'") } } 'VmVmCsvAntiAffinity' { # No two members may share a CSV. $currentConflicts = @($activeVMs | Group-Object { $vmCsv[$_] } | Where-Object { $_.Count -gt 1 }) $simConflicts = @($activeVMs | Group-Object { if ($_ -eq $VMName) { $DestinationCsvName } else { $vmCsv[$_] } } | 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 storage anti-affinity rule '$($rule.Name)' — '$VMName' would share CSV '$DestinationCsvName' with '$peer'" if ($rule.Enforced) { $hardReasons.Add($msg) } else { $softReasons.Add($msg) } } elseif (-not $wasSatisfied -and $willSatisfied) { $fixReasons.Add("Satisfies storage anti-affinity rule '$($rule.Name)' — '$VMName' separated from all group members") } } 'VmCsvAffinity' { # VM's storage must reside on one of the listed CSVs. $wasSatisfied = $rule.CSVs -contains $vmCsv[$VMName] $willSatisfied = $rule.CSVs -contains $DestinationCsvName if ($wasSatisfied -and -not $willSatisfied) { $msg = "Breaks $severity CSV-affinity rule '$($rule.Name)' — '$DestinationCsvName' is not in [$(($rule.CSVs) -join ', ')]" if ($rule.Enforced) { $hardReasons.Add($msg) } else { $softReasons.Add($msg) } } elseif (-not $wasSatisfied -and $willSatisfied) { $fixReasons.Add("Satisfies CSV-affinity rule '$($rule.Name)' — '$VMName' moves onto allowed CSV '$DestinationCsvName'") } } 'VmCsvAntiAffinity' { # VM's storage must NOT reside on any of the listed CSVs. $wasSatisfied = $rule.CSVs -notcontains $vmCsv[$VMName] $willSatisfied = $rule.CSVs -notcontains $DestinationCsvName if ($wasSatisfied -and -not $willSatisfied) { $msg = "Breaks $severity CSV-anti-affinity rule '$($rule.Name)' — '$DestinationCsvName' is an excluded CSV" if ($rule.Enforced) { $hardReasons.Add($msg) } else { $softReasons.Add($msg) } } elseif (-not $wasSatisfied -and $willSatisfied) { $fixReasons.Add("Satisfies CSV-anti-affinity rule '$($rule.Name)' — '$VMName' moves off excluded CSV") } } } } [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() } } |