PoorMans-DRS.ps1

 avatar
ch384n1
powershell
4 days ago
9.6 kB
69
No Index
<#
.SYNOPSIS
    A script to balance VMs across hosts in a vSphere cluster based on memory usage.
    
.DESCRIPTION
    This script connects to a vCenter server, retrieves memory consumption statistics for hosts in the specified cluster, and migrates VMs to balance memory usage. It continuously checks the balance and migrates VMs until the memory usage across hosts is balanced within ±5%. The script logs the progress and status of tasks to the console.
    
.PARAMETER vCenterServer
    The address of the vCenter server to connect to.

.PARAMETER clusterName
    The name of the cluster within the vCenter server where VM balancing will occur.

.EXAMPLE
    .\PoorMans-DRS.ps1 -vCenterServer "vcenter.domain.com" -clusterName "Cluster 01"
    
    Connects to the specified vCenter server and balances VMs across the specified cluster based on memory usage.

#>

param (
    [Parameter(Mandatory = $true, HelpMessage = "Specify the vCenter server address.")]
    [ValidateNotNullOrEmpty()]
    [string]$vCenterServer,

    [Parameter(Mandatory = $true, HelpMessage = "Specify the cluster name.")]
    [ValidateNotNullOrEmpty()]
    [string]$clusterName
)

# Connect to vCenter
Connect-VIServer -Server $vCenterServer

# Get all hosts in the specified cluster
$cluster = Get-Cluster -Name $clusterName
$vmHosts = Get-VMHost -Location $cluster

# Create a reference list of all powered-on VMs and their memory usage
$vmList = Get-VM -Location $cluster | Where-Object { $_.PowerState -eq 'PoweredOn' } | Select-Object Name, MemoryGB, @{Name="VMHostName";Expression={$_.VMHost.Name}}
Write-Host "Retrieved reference list of all powered-on VMs and their memory usage."

# Function to get the current consumed memory for all hosts in the cluster
function Get-ClusterConsumedMemory {
    param (
        $VMHosts
    )
    # Retrieve real-time consumed memory for each host
    $memoryUsage = $VMHosts | Get-Stat -Stat "mem.consumed.average" -Realtime -MaxSamples 1
    $hostMemory = @{}

    foreach ($stat in $memoryUsage) {
        $hostName = $stat.Entity.Name
        $consumedMemory = [math]::Round($stat.Value, 2)
        $hostMemory[$hostName] = $consumedMemory
        Write-Host "Consumed Memory for Host ${hostName}: ${consumedMemory} MB"
    }

    return $hostMemory
}

# Function to display memory usage as percentages
function DisplayMemoryPercentages {
    param (
        $memoryUsage
    )
    $totalMemory = ($memoryUsage.Values | Measure-Object -Sum).Sum
    foreach ($currentHost in $memoryUsage.Keys) {
        $percentage = ($memoryUsage[$currentHost] / $totalMemory) * 100
        Write-Host "Consumed Memory for Host ${currentHost}: ${memoryUsage[$currentHost]} $([math]::Round($percentage, 2))%"
    }
    $consumedDifference = [math]::Abs(($memoryUsage.Values | Measure-Object -Maximum).Maximum - ($memoryUsage.Values | Measure-Object -Minimum).Minimum)
    $differencePercentage = ($consumedDifference / $totalMemory) * 100
    Write-Host "Updated Consumed Memory Difference: $([math]::Round($differencePercentage, 2))%"
}

# Function to simulate the impact of a migration and check if it improves balance
function ShouldMoveVM {
    param (
        $sourceHostMemory,
        $destinationHostMemory,
        $vmMemory
    )

    # Calculate the current difference and the difference if the VM is moved
    $currentDifference = [math]::Abs($sourceHostMemory - $destinationHostMemory)
    $newSourceMemory = $sourceHostMemory - $vmMemory
    $newDestinationMemory = $destinationHostMemory + $vmMemory
    $newDifference = [math]::Abs($newSourceMemory - $newDestinationMemory)

    # Only move the VM if it reduces the memory difference
    return $newDifference -lt $currentDifference
}

# Function to move VMs to balance memory usage
function Wait-ForTask {
    param (
        [Parameter(Mandatory=$true)]
        [string]$TaskId
    )

    $maxRetries = 10
    $retryInterval = 10
    $retryCount = 0

    while ($retryCount -lt $maxRetries) {
        Start-Sleep -Seconds $retryInterval

        try {
            $task = Get-Task -Id $TaskId -ErrorAction Stop
            $taskState = $task.State
            Write-Host "Current task state: $taskState"

            if ($taskState -eq 'Success') {
                Write-Host "Task completed successfully."
                return
            }
            elseif ($taskState -eq 'Error') {
                Write-Host "Task failed."
                return
            }
        }
        catch {
            if ($_.Exception.Message -match "The object 'vim.Task:task-[0-9]+' has already been deleted or has not been completely created") {
                Write-Host "Task ID $TaskId is no longer valid or has not been fully created."
                # Break out if task is deleted or invalid
                return
            }
            else {
                Write-Host "Error retrieving task state: $_"
            }
        }

        # Increment retry count
        $retryCount++
        # Increase delay between retries
        $retryInterval = [math]::Min($retryInterval * 2, 120)
    }

    Write-Host "Exceeded maximum retries. Task may be in an unknown state."
}

function Move-VMs {
    param (
        [ref]$memoryUsage,
        [ref]$vmReference,
        $memoryToMove,
        [ref]$isBalanced
    )

    # Continuously select the most and least utilized hosts based on updated usage
    while (-not $isBalanced.Value) {
        # Find the most and least utilized hosts dynamically
        $sourceHost = ($memoryUsage.Value.GetEnumerator() | Sort-Object Value -Descending | Select-Object -First 1).Key
        $destinationHost = ($memoryUsage.Value.GetEnumerator() | Sort-Object Value | Select-Object -First 1).Key

        # Select VMs on the source host sorted by memory consumption
        $vmsOnSource = $vmReference.Value | Where-Object { $_.VMHostName -eq $sourceHost } | Sort-Object -Property MemoryGB -Descending
        $movedMemory = 0

        foreach ($vm in $vmsOnSource) {
            $vmMemoryMB = $vm.MemoryGB * 1024
            # Check if moving this VM will improve the balance
            if (-not (ShouldMoveVM -sourceHostMemory $memoryUsage.Value[$sourceHost] -destinationHostMemory $memoryUsage.Value[$destinationHost] -vmMemory $vmMemoryMB)) {
                Write-Host "Skipping migration of VM $($vm.Name) as it does not improve balance."
                continue
            }

            if ($movedMemory -ge $memoryToMove) { break }

            # Attempt to move the VM
            try {
                Write-Host "Migrating VM $($vm.Name) with $($vm.MemoryGB) GB from $($sourceHost) to $($destinationHost)"
                $moveTask = Move-VM -VM (Get-VM -Name $vm.Name) -Destination (Get-VMHost -Name $destinationHost) -Confirm:$false -RunAsync

                Write-Host "Task ID captured: $($moveTask.Id)"
                
                # Call the Wait-ForTask function
                Wait-ForTask -TaskId $moveTask.Id

                # Remove the migrated VM from the reference list
                $vmReference.Value = $vmReference.Value | Where-Object { $_.Name -ne $vm.Name }
                $movedMemory += $vmMemoryMB

                # Recalculate the memory difference after each migration
                $memoryUsage.Value = Get-ClusterConsumedMemory -VMHosts $vmHosts
                DisplayMemoryPercentages -memoryUsage $memoryUsage.Value

                # Check if balance is achieved
                $consumedDifference = [math]::Abs(($memoryUsage.Value.Values | Measure-Object -Maximum).Maximum - ($memoryUsage.Value.Values | Measure-Object -Minimum).Minimum)
                $totalMemory = ($memoryUsage.Value.Values | Measure-Object -Sum).Sum
                $differencePercentage = ($consumedDifference / $totalMemory) * 100

                if ($differencePercentage -le 5) {
                    Write-Host "Hosts are balanced within ±5% memory usage difference. Stopping further migrations."
                    $isBalanced.Value = $true
                    break
                }
            }
            catch {
                Write-Host "An error occurred while moving VM $($vm.Name): $_"
            }

            # Check if balance was achieved
            if ($isBalanced.Value) { break }
        }

        Write-Host "Total memory moved: $movedMemory MB"
    }
}

# Main balancing loop
$maxIterations = 10  # Set a limit to the number of iterations to prevent endless running
$iteration = 0
$isBalanced = $false  # Flag to indicate when balance is achieved
$memoryUsage = [ref](Get-ClusterConsumedMemory -VMHosts $vmHosts)

while ($iteration -lt $maxIterations -and -not $isBalanced) {
    # Display the memory usage percentages
    DisplayMemoryPercentages -memoryUsage $memoryUsage.Value

    # Calculate the memory that needs to be moved to balance the difference
    $memoryToMove = [math]::Round(($memoryUsage.Value.Values | Measure-Object -Maximum).Maximum - ($memoryUsage.Value.Values | Measure-Object -Minimum).Minimum) / 2
    Write-Host "Memory needed to move to balance: $memoryToMove MB"

    # Balance by migrating VMs from the host with the most consumed memory to the least
    Move-VMs -memoryUsage $memoryUsage -vmReference ([ref]$vmList) -memoryToMove $memoryToMove -isBalanced ([ref]$isBalanced)

    # Exit the loop if balance is achieved
    if ($isBalanced) { break }

    # Increment iteration count and pause to allow stats to update
    $iteration++
    Start-Sleep -Seconds 30  # Adjust the sleep time as needed to allow stats to update
}

# Disconnect from vCenter
Disconnect-VIServer -Server $vCenterServer -Confirm:$false
Editor is loading...
Leave a Comment