Functions/Private/Test-StorageAffinityCompliance.ps1
|
function Test-StorageAffinityCompliance { <# .SYNOPSIS Checks the current VM-to-CSV storage placement against all configured storage affinity rules and returns a list of violations. .DESCRIPTION Storage analogue of Test-AffinityCompliance. Operates on a storage snapshot (Get-StorageSnapshot) instead of a compute snapshot, and only evaluates the four storage rule types: VmVmCsvAffinity — listed VMs' storage must share one CSV. VmVmCsvAntiAffinity — no two listed VMs may share a CSV. VmCsvAffinity — listed VMs' storage must be on one of the named CSVs. VmCsvAntiAffinity — listed VMs' storage must never be on the named CSVs. .OUTPUTS Array of PSCustomObjects: RuleId, RuleName, Type, Enforced, VMs, Description. #> [CmdletBinding()] param( [Parameter(Mandatory)] [PSCustomObject] $Snapshot, [PSCustomObject[]] $RuleSet ) if (-not $RuleSet -or $RuleSet.Count -eq 0) { return ,@() } $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 } } $violations = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($rule in $RuleSet) { $activeVMs = @($rule.VMs | Where-Object { $vmCsv.ContainsKey($_) }) if ($activeVMs.Count -eq 0) { continue } switch ($rule.Type) { 'VmVmCsvAffinity' { $csvGroups = @($activeVMs | Group-Object { $vmCsv[$_] }) if ($csvGroups.Count -gt 1) { $csvList = $csvGroups | Select-Object -ExpandProperty Name $violations.Add([PSCustomObject]@{ RuleId = $rule.RuleId RuleName = $rule.Name Type = $rule.Type Enforced = $rule.Enforced VMs = $activeVMs Description = "Storage affinity group '$($rule.Name)' is spread across $($csvGroups.Count) CSV(s): $($csvList -join ', ')" }) } } 'VmVmCsvAntiAffinity' { $conflicts = @($activeVMs | Group-Object { $vmCsv[$_] } | Where-Object { $_.Count -gt 1 }) foreach ($conflict in $conflicts) { $violations.Add([PSCustomObject]@{ RuleId = $rule.RuleId RuleName = $rule.Name Type = $rule.Type Enforced = $rule.Enforced VMs = @($conflict.Group) Description = "Storage anti-affinity rule '$($rule.Name)': '$($conflict.Group -join "', '")' co-located on CSV '$($conflict.Name)'" }) } } 'VmCsvAffinity' { foreach ($vm in $activeVMs) { if ($rule.CSVs -notcontains $vmCsv[$vm]) { $violations.Add([PSCustomObject]@{ RuleId = $rule.RuleId RuleName = $rule.Name Type = $rule.Type Enforced = $rule.Enforced VMs = @($vm) Description = "CSV-affinity rule '$($rule.Name)': '$vm' storage is on '$($vmCsv[$vm])' — allowed: [$($rule.CSVs -join ', ')]" }) } } } 'VmCsvAntiAffinity' { foreach ($vm in $activeVMs) { if ($rule.CSVs -contains $vmCsv[$vm]) { $violations.Add([PSCustomObject]@{ RuleId = $rule.RuleId RuleName = $rule.Name Type = $rule.Type Enforced = $rule.Enforced VMs = @($vm) Description = "CSV-anti-affinity rule '$($rule.Name)': '$vm' storage is on excluded CSV '$($vmCsv[$vm])'" }) } } } } } return $violations.ToArray() } |