Functions/Private/Find-StorageMigrationCandidates.ps1
|
function Find-StorageMigrationCandidates { <# .SYNOPSIS Identifies VMs whose VHDs should be moved to a different CSV to balance storage space and I/O load across the cluster. .DESCRIPTION Algorithm: 1. Score every CSV with Measure-CsvHappiness. 2. Identify CSVs whose current (simulated) score is below the aggression-level happiness threshold, sorted most→least unhappy. 3. For each unhappy source CSV, evaluate every combination of (unscheduled VM on source, candidate destination CSV): • Candidate must have enough headroom after receiving the VM's VHDs: (FreeGB – vm.TotalVhdGB) >= MinFreeGBReserve. • Simulate source after VM departs → projectedSrcScore. • Simulate destination after VM arrives → projectedDstScore. • improvement = projectedSrcScore − currentSimSrcScore. 4. Pick the (VM, destination) pair with the highest improvement. 5. If improvement meets the aggression-level minimum, add to the plan. 6. Update simulated CSV state (FreeGB) before moving to the next source. The simulated state ensures the greedy planner does not over-commit a single destination CSV across multiple planned moves. .OUTPUTS List of PSCustomObjects: VMName, VMId, HostNode, SourceCSV, SourceCSVName, DestinationCSV, DestinationCSVName, VHDCount, TotalVhdGB, SourceFreeGBBefore, SourceFreeGBAfter, DestFreeGBBefore, DestFreeGBAfter, SourceScoreBefore, SourceScoreAfter, DestScoreBefore, DestScoreAfter, Improvement. #> [CmdletBinding()] param( [Parameter(Mandatory)] [PSCustomObject] $Snapshot, [ValidateRange(1, 5)] [int] $AggressionLevel = 3, [ValidateRange(0.0, 1.0)] [float] $SpaceWeight = 0.7, [ValidateRange(0.0, 1.0)] [float] $IoWeight = 0.3, [int] $MinFreeGBReserve = 50 ) $thresholds = @{ 1 = @{ Happiness = 30; Improvement = 40 } 2 = @{ Happiness = 40; Improvement = 30 } 3 = @{ Happiness = 50; Improvement = 20 } 4 = @{ Happiness = 60; Improvement = 15 } 5 = @{ Happiness = 70; Improvement = 10 } } $happinessThreshold = $thresholds[$AggressionLevel].Happiness $improvementThreshold = $thresholds[$AggressionLevel].Improvement # Initial scores — used for SourceScoreBefore / DestScoreBefore in output $initialScores = @{} foreach ($csv in $Snapshot.CSVs) { $s = Measure-CsvHappiness -CsvMetrics $csv -SpaceWeight $SpaceWeight -IoWeight $IoWeight $initialScores[$csv.Name] = $s.HappinessScore } # Mutable simulated CSV state keyed by CSV Name $simCsvs = @{} foreach ($csv in $Snapshot.CSVs) { $simCsvs[$csv.Name] = [PSCustomObject]@{ Name = $csv.Name Path = $csv.Path TotalGB = $csv.TotalGB FreeGB = $csv.FreeGB LatencyMs = $csv.LatencyMs ReadIOPS = $csv.ReadIOPS WriteIOPS = $csv.WriteIOPS } } # Path → Name mapping (VMs reference CSVs by path) $pathToName = @{} foreach ($csv in $Snapshot.CSVs) { $pathToName[$csv.Path] = $csv.Name } # Helper: score a simulated CSV object $scoreSimCsv = { param($sim) $proxy = [PSCustomObject]@{ Name = $sim.Name TotalGB = $sim.TotalGB FreeGB = $sim.FreeGB LatencyMs = $sim.LatencyMs } (Measure-CsvHappiness -CsvMetrics $proxy -SpaceWeight $SpaceWeight -IoWeight $IoWeight).HappinessScore } $scheduledVMs = [System.Collections.Generic.HashSet[string]]::new() $migrations = [System.Collections.Generic.List[PSCustomObject]]::new() # Collect unhappy CSVs — re-evaluated each outer iteration via simulated state $unhappyCsvNames = $initialScores.GetEnumerator() | Where-Object { $_.Value -lt $happinessThreshold } | Sort-Object Value | Select-Object -ExpandProperty Key foreach ($srcName in $unhappyCsvNames) { $simSrc = $simCsvs[$srcName] if (-not $simSrc) { continue } # Re-score against current simulated state; skip if already fixed $currentSrcScore = & $scoreSimCsv $simSrc if ($currentSrcScore -ge $happinessThreshold) { continue } # VMs whose primary storage is on this CSV and not yet scheduled $vmsOnSrc = $Snapshot.VMs | Where-Object { $pathToName[$_.PrimaryCSV] -eq $srcName -and -not $scheduledVMs.Contains($_.VMName) } if (-not $vmsOnSrc) { continue } $bestMigration = $null $bestImprovement = 0.0 foreach ($vm in $vmsOnSrc) { # Candidate destinations: enough headroom after receiving this VM $candidates = $simCsvs.Values | Where-Object { $_.Name -ne $srcName -and ($_.FreeGB - $vm.TotalVhdGB) -ge $MinFreeGBReserve } foreach ($dst in $candidates) { # Simulate source after VM departs $srcFreeAfter = $simSrc.FreeGB + $vm.TotalVhdGB $srcSimCopy = [PSCustomObject]@{ Name = $simSrc.Name; TotalGB = $simSrc.TotalGB FreeGB = $srcFreeAfter; LatencyMs = $simSrc.LatencyMs } $projectedSrcScore = (Measure-CsvHappiness -CsvMetrics $srcSimCopy -SpaceWeight $SpaceWeight -IoWeight $IoWeight).HappinessScore # Simulate destination after VM arrives $dstFreeAfter = $dst.FreeGB - $vm.TotalVhdGB $dstSimCopy = [PSCustomObject]@{ Name = $dst.Name; TotalGB = $dst.TotalGB FreeGB = $dstFreeAfter; LatencyMs = $dst.LatencyMs } $projectedDstScore = (Measure-CsvHappiness -CsvMetrics $dstSimCopy -SpaceWeight $SpaceWeight -IoWeight $IoWeight).HappinessScore $improvement = $projectedSrcScore - $currentSrcScore if ($improvement -gt $bestImprovement) { $bestImprovement = $improvement $bestMigration = [PSCustomObject]@{ VMName = $vm.VMName VMId = $vm.VMId HostNode = $vm.HostNode SourceCSV = $simSrc.Path SourceCSVName = $simSrc.Name DestinationCSV = $dst.Path DestinationCSVName = $dst.Name VHDCount = $vm.VHDs.Count TotalVhdGB = $vm.TotalVhdGB SourceFreeGBBefore = [Math]::Round($simSrc.FreeGB, 1) SourceFreeGBAfter = [Math]::Round($srcFreeAfter, 1) DestFreeGBBefore = [Math]::Round($dst.FreeGB, 1) DestFreeGBAfter = [Math]::Round($dstFreeAfter, 1) SourceScoreBefore = $initialScores[$srcName] SourceScoreAfter = [Math]::Round($projectedSrcScore, 1) DestScoreBefore = $initialScores[$dst.Name] DestScoreAfter = [Math]::Round($projectedDstScore, 1) Improvement = [Math]::Round($improvement, 1) } } } } if ($null -eq $bestMigration -or $bestImprovement -lt $improvementThreshold) { continue } $migrations.Add($bestMigration) [void]$scheduledVMs.Add($bestMigration.VMName) # Greedy state update $simCsvs[$bestMigration.SourceCSVName].FreeGB += $bestMigration.TotalVhdGB $simCsvs[$bestMigration.DestinationCSVName].FreeGB -= $bestMigration.TotalVhdGB } return $migrations } |