Public/System/Set-VergeCluster.ps1

function Set-VergeCluster {
    <#
    .SYNOPSIS
        Modifies the configuration of a VergeOS cluster.

    .DESCRIPTION
        Set-VergeCluster modifies cluster settings such as CPU type, resource limits,
        power management, and other configuration options.

    .PARAMETER Cluster
        A cluster object from Get-VergeCluster. Accepts pipeline input.

    .PARAMETER Name
        The name of the cluster to modify.

    .PARAMETER Key
        The key (ID) of the cluster to modify.

    .PARAMETER NewName
        Rename the cluster to this new name.

    .PARAMETER Description
        Set the cluster description.

    .PARAMETER Enabled
        Enable or disable the cluster.

    .PARAMETER Compute
        Enable or disable compute workloads.

    .PARAMETER NestedVirtualization
        Enable or disable nested virtualization.

    .PARAMETER AllowNestedVirtMigration
        Allow live migration of VMs with nested virtualization.

    .PARAMETER AllowVGPUMigration
        Allow live migration of VMs with vGPU devices.

    .PARAMETER DefaultCPUType
        Set the default CPU type for VMs.

    .PARAMETER DisableCPUSecurityMitigations
        Disable CPU security mitigations.

    .PARAMETER DisableSMT
        Disable Simultaneous Multi-Threading.

    .PARAMETER EnableSplitLockDetection
        Enable split lock detection.

    .PARAMETER EnergyPerfPolicy
        Set the CPU energy-performance policy.

    .PARAMETER ScalingGovernor
        Set the CPU scaling governor.

    .PARAMETER RAMPerUnit
        Set RAM per billing unit in MB.

    .PARAMETER CoresPerUnit
        Set CPU cores per billing unit.

    .PARAMETER CostPerUnit
        Set cost per billing unit.

    .PARAMETER PricePerUnit
        Set price per billing unit.

    .PARAMETER MaxRAMPerVM
        Set maximum RAM allowed per VM in MB.

    .PARAMETER MaxCoresPerVM
        Set maximum CPU cores allowed per VM.

    .PARAMETER TargetRAMPercent
        Set target maximum RAM utilization percentage.

    .PARAMETER RAMOvercommitPercent
        Set percentage of reserve RAM to use for machines.

    .PARAMETER StorageCachePerNode
        Set storage cache per node in MB.

    .PARAMETER StorageBufferPerNode
        Set storage buffer per node in MB.

    .PARAMETER StorageHugepages
        Enable or disable hugepages for storage.

    .PARAMETER EnableNVMePowerManagement
        Enable or disable NVMe power management.

    .PARAMETER SwapTier
        Set storage tier for swap (-1 to disable).

    .PARAMETER SwapPerDrive
        Set swap space per drive in MB.

    .PARAMETER MaxCoreTemp
        Set maximum core temperature in Celsius.

    .PARAMETER CriticalCoreTemp
        Set critical core temperature in Celsius.

    .PARAMETER MaxCoreTempWarnPercent
        Set temperature warning threshold percentage.

    .PARAMETER DisableSleep
        Enable or disable CPU sleep state disabling.

    .PARAMETER LogFilter
        Set system log filter expression.

    .PARAMETER PassThru
        Return the modified cluster object.

    .PARAMETER Server
        The VergeOS connection to use. Defaults to the current default connection.

    .EXAMPLE
        Set-VergeCluster -Name "Production" -MaxRAMPerVM 131072 -MaxCoresPerVM 64

        Increases VM resource limits on the Production cluster.

    .EXAMPLE
        Get-VergeCluster -Name "Development" | Set-VergeCluster -NestedVirtualization $true

        Enables nested virtualization on the Development cluster.

    .EXAMPLE
        Set-VergeCluster -Name "OldName" -NewName "NewName" -PassThru

        Renames a cluster and returns the updated object.

    .EXAMPLE
        Set-VergeCluster -Name "Production" -DefaultCPUType "EPYC-Milan" -EnergyPerfPolicy Performance

        Updates CPU type and power policy.

    .OUTPUTS
        None by default. Verge.Cluster when -PassThru is specified.

    .NOTES
        Some changes may require node reboots to take effect.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', DefaultParameterSetName = 'ByName')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'ByCluster')]
        [PSTypeName('Verge.Cluster')]
        [PSCustomObject]$Cluster,

        [Parameter(Mandatory, Position = 0, ParameterSetName = 'ByName')]
        [string]$Name,

        [Parameter(Mandatory, ParameterSetName = 'ByKey')]
        [int]$Key,

        [Parameter()]
        [ValidateLength(1, 128)]
        [string]$NewName,

        [Parameter()]
        [ValidateLength(0, 2048)]
        [string]$Description,

        [Parameter()]
        [bool]$Enabled,

        [Parameter()]
        [bool]$Compute,

        [Parameter()]
        [bool]$NestedVirtualization,

        [Parameter()]
        [bool]$AllowNestedVirtMigration,

        [Parameter()]
        [bool]$AllowVGPUMigration,

        [Parameter()]
        [ValidateSet(
            'qemu64', 'kvm64', 'host',
            'Broadwell', 'Cascadelake-Server', 'Conroe', 'Cooperlake',
            'core2duo', 'coreduo', 'Denverton',
            'EPYC', 'EPYC-Genoa', 'EPYC-Milan', 'EPYC-Rome',
            'GraniteRapids', 'Haswell', 'Icelake-Server', 'IvyBridge',
            'KnightsMill', 'n270', 'Nehalem',
            'Opteron_G1', 'Opteron_G2', 'Opteron_G3', 'Opteron_G4', 'Opteron_G5',
            'Penryn', 'phenom', 'SandyBridge', 'SapphireRapids',
            'Skylake-Client', 'Skylake-Server', 'Snowridge', 'Westmere'
        )]
        [string]$DefaultCPUType,

        [Parameter()]
        [bool]$DisableCPUSecurityMitigations,

        [Parameter()]
        [bool]$DisableSMT,

        [Parameter()]
        [bool]$EnableSplitLockDetection,

        [Parameter()]
        [ValidateSet('Performance', 'BalancePerformance', 'BalancePower', 'Normal', 'Power')]
        [string]$EnergyPerfPolicy,

        [Parameter()]
        [ValidateSet('Performance', 'OnDemand', 'PowerSave')]
        [string]$ScalingGovernor,

        [Parameter()]
        [ValidateRange(0, 1048576)]
        [int]$RAMPerUnit,

        [Parameter()]
        [ValidateRange(0, 1024)]
        [int]$CoresPerUnit,

        [Parameter()]
        [ValidateRange(0, [double]::MaxValue)]
        [double]$CostPerUnit,

        [Parameter()]
        [ValidateRange(0, [double]::MaxValue)]
        [double]$PricePerUnit,

        [Parameter()]
        [ValidateRange(0, 1048576)]
        [int]$MaxRAMPerVM,

        [Parameter()]
        [ValidateRange(0, 1024)]
        [int]$MaxCoresPerVM,

        [Parameter()]
        [ValidateRange(0, 100)]
        [double]$TargetRAMPercent,

        [Parameter()]
        [ValidateRange(0, 100)]
        [double]$RAMOvercommitPercent,

        [Parameter()]
        [ValidateRange(0, 5000000)]
        [int]$StorageCachePerNode,

        [Parameter()]
        [ValidateRange(0, 5000000)]
        [int]$StorageBufferPerNode,

        [Parameter()]
        [bool]$StorageHugepages,

        [Parameter()]
        [bool]$EnableNVMePowerManagement,

        [Parameter()]
        [ValidateRange(-1, 5)]
        [int]$SwapTier,

        [Parameter()]
        [ValidateRange(0, 1000000)]
        [int]$SwapPerDrive,

        [Parameter()]
        [ValidateRange(0, 150)]
        [int]$MaxCoreTemp,

        [Parameter()]
        [ValidateRange(0, 150)]
        [int]$CriticalCoreTemp,

        [Parameter()]
        [ValidateRange(-1, 100)]
        [int]$MaxCoreTempWarnPercent,

        [Parameter()]
        [bool]$DisableSleep,

        [Parameter()]
        [string]$LogFilter,

        [Parameter()]
        [switch]$PassThru,

        [Parameter()]
        [object]$Server
    )

    begin {
        # Resolve connection
        if (-not $Server) {
            $Server = $script:DefaultConnection
        }
        if (-not $Server) {
            throw [System.InvalidOperationException]::new(
                'Not connected to VergeOS. Use Connect-VergeOS to establish a connection.'
            )
        }

        # Map friendly names to API values
        $energyPerfMap = @{
            'Performance'        = 'performance'
            'BalancePerformance' = 'balance-performance'
            'BalancePower'       = 'balance-power'
            'Normal'             = 'normal'
            'Power'              = 'power'
        }

        $scalingGovMap = @{
            'Performance' = 'performance'
            'OnDemand'    = 'ondemand'
            'PowerSave'   = 'powersave'
        }
    }

    process {
        # Resolve cluster based on parameter set
        $targetCluster = switch ($PSCmdlet.ParameterSetName) {
            'ByName' {
                Get-VergeCluster -Name $Name -Server $Server | Select-Object -First 1
            }
            'ByKey' {
                Get-VergeCluster -Key $Key -Server $Server
            }
            'ByCluster' {
                $Cluster
            }
        }

        if (-not $targetCluster) {
            Write-Error -Message "Cluster not found" -ErrorId 'ClusterNotFound'
            return
        }

        # Build the update body with only specified parameters
        $body = @{}
        $changes = [System.Collections.Generic.List[string]]::new()

        if ($PSBoundParameters.ContainsKey('NewName')) {
            $body['name'] = $NewName
            $changes.Add("Name: $($targetCluster.Name) -> $NewName")
        }

        if ($PSBoundParameters.ContainsKey('Description')) {
            $body['description'] = $Description
            $changes.Add("Description updated")
        }

        if ($PSBoundParameters.ContainsKey('Enabled')) {
            $body['enabled'] = $Enabled
            $changes.Add("Enabled: $Enabled")
        }

        if ($PSBoundParameters.ContainsKey('Compute')) {
            $body['compute'] = $Compute
            $changes.Add("Compute: $Compute")
        }

        if ($PSBoundParameters.ContainsKey('NestedVirtualization')) {
            $body['kvm_nested'] = $NestedVirtualization
            $changes.Add("Nested Virtualization: $NestedVirtualization")
        }

        if ($PSBoundParameters.ContainsKey('AllowNestedVirtMigration')) {
            $body['allow_nested_virt_migration'] = $AllowNestedVirtMigration
            $changes.Add("Allow Nested Virt Migration: $AllowNestedVirtMigration")
        }

        if ($PSBoundParameters.ContainsKey('AllowVGPUMigration')) {
            $body['allow_vgpu_migration'] = $AllowVGPUMigration
            $changes.Add("Allow vGPU Migration: $AllowVGPUMigration")
        }

        if ($PSBoundParameters.ContainsKey('DefaultCPUType')) {
            $body['default_cpu'] = $DefaultCPUType
            $changes.Add("Default CPU Type: $DefaultCPUType")
        }

        if ($PSBoundParameters.ContainsKey('DisableCPUSecurityMitigations')) {
            $body['disable_cpu_security_mitigations'] = $DisableCPUSecurityMitigations
            $changes.Add("Disable CPU Security Mitigations: $DisableCPUSecurityMitigations")
        }

        if ($PSBoundParameters.ContainsKey('DisableSMT')) {
            $body['disable_smt'] = $DisableSMT
            $changes.Add("Disable SMT: $DisableSMT")
        }

        if ($PSBoundParameters.ContainsKey('EnableSplitLockDetection')) {
            $body['enable_split_lock_detection'] = $EnableSplitLockDetection
            $changes.Add("Enable Split Lock Detection: $EnableSplitLockDetection")
        }

        if ($PSBoundParameters.ContainsKey('EnergyPerfPolicy')) {
            $body['x86_energy_perf_policy'] = $energyPerfMap[$EnergyPerfPolicy]
            $changes.Add("Energy Perf Policy: $EnergyPerfPolicy")
        }

        if ($PSBoundParameters.ContainsKey('ScalingGovernor')) {
            $body['scaling_governor'] = $scalingGovMap[$ScalingGovernor]
            $changes.Add("Scaling Governor: $ScalingGovernor")
        }

        if ($PSBoundParameters.ContainsKey('RAMPerUnit')) {
            $body['ram_per_unit'] = $RAMPerUnit
            $changes.Add("RAM Per Unit: ${RAMPerUnit}MB")
        }

        if ($PSBoundParameters.ContainsKey('CoresPerUnit')) {
            $body['cores_per_unit'] = $CoresPerUnit
            $changes.Add("Cores Per Unit: $CoresPerUnit")
        }

        if ($PSBoundParameters.ContainsKey('CostPerUnit')) {
            $body['cost_per_unit'] = $CostPerUnit
            $changes.Add("Cost Per Unit: $CostPerUnit")
        }

        if ($PSBoundParameters.ContainsKey('PricePerUnit')) {
            $body['price_per_unit'] = $PricePerUnit
            $changes.Add("Price Per Unit: $PricePerUnit")
        }

        if ($PSBoundParameters.ContainsKey('MaxRAMPerVM')) {
            $body['max_ram_per_vm'] = $MaxRAMPerVM
            $changes.Add("Max RAM Per VM: ${MaxRAMPerVM}MB")
        }

        if ($PSBoundParameters.ContainsKey('MaxCoresPerVM')) {
            $body['max_cores_per_vm'] = $MaxCoresPerVM
            $changes.Add("Max Cores Per VM: $MaxCoresPerVM")
        }

        if ($PSBoundParameters.ContainsKey('TargetRAMPercent')) {
            $body['target_ram_pct'] = $TargetRAMPercent
            $changes.Add("Target RAM Percent: $TargetRAMPercent%")
        }

        if ($PSBoundParameters.ContainsKey('RAMOvercommitPercent')) {
            $body['ram_overcommit_pct'] = $RAMOvercommitPercent
            $changes.Add("RAM Overcommit Percent: $RAMOvercommitPercent%")
        }

        if ($PSBoundParameters.ContainsKey('StorageCachePerNode')) {
            $body['storage_cachesize'] = $StorageCachePerNode
            $changes.Add("Storage Cache Per Node: ${StorageCachePerNode}MB")
        }

        if ($PSBoundParameters.ContainsKey('StorageBufferPerNode')) {
            $body['storage_buffersize'] = $StorageBufferPerNode
            $changes.Add("Storage Buffer Per Node: ${StorageBufferPerNode}MB")
        }

        if ($PSBoundParameters.ContainsKey('StorageHugepages')) {
            $body['storage_hugepages'] = $StorageHugepages
            $changes.Add("Storage Hugepages: $StorageHugepages")
        }

        if ($PSBoundParameters.ContainsKey('EnableNVMePowerManagement')) {
            $body['enable_nvme_power_management'] = $EnableNVMePowerManagement
            $changes.Add("Enable NVMe Power Management: $EnableNVMePowerManagement")
        }

        if ($PSBoundParameters.ContainsKey('SwapTier')) {
            $body['swap_tier'] = $SwapTier
            $changes.Add("Swap Tier: $SwapTier")
        }

        if ($PSBoundParameters.ContainsKey('SwapPerDrive')) {
            $body['swap_per_drive'] = $SwapPerDrive
            $changes.Add("Swap Per Drive: ${SwapPerDrive}MB")
        }

        if ($PSBoundParameters.ContainsKey('MaxCoreTemp')) {
            $body['max_core_temp'] = $MaxCoreTemp
            $changes.Add("Max Core Temp: ${MaxCoreTemp}°C")
        }

        if ($PSBoundParameters.ContainsKey('CriticalCoreTemp')) {
            $body['critical_core_temp'] = $CriticalCoreTemp
            $changes.Add("Critical Core Temp: ${CriticalCoreTemp}°C")
        }

        if ($PSBoundParameters.ContainsKey('MaxCoreTempWarnPercent')) {
            $body['max_core_temp_warn_perc'] = $MaxCoreTempWarnPercent
            $changes.Add("Max Core Temp Warn Percent: $MaxCoreTempWarnPercent%")
        }

        if ($PSBoundParameters.ContainsKey('DisableSleep')) {
            $body['disable_sleep'] = $DisableSleep
            $changes.Add("Disable Sleep: $DisableSleep")
        }

        if ($PSBoundParameters.ContainsKey('LogFilter')) {
            $body['log_filter'] = $LogFilter
            $changes.Add("Log Filter: $LogFilter")
        }

        # Check if there are any changes to make
        if ($body.Count -eq 0) {
            Write-Warning "No changes specified for cluster '$($targetCluster.Name)'"
            if ($PassThru) {
                Write-Output $targetCluster
            }
            return
        }

        # Build change summary for confirmation
        $changeSummary = $changes -join ', '

        if ($PSCmdlet.ShouldProcess($targetCluster.Name, "Modify Cluster ($changeSummary)")) {
            try {
                Write-Verbose "Modifying cluster '$($targetCluster.Name)' (Key: $($targetCluster.Key))"
                Write-Verbose "Changes: $changeSummary"

                $response = Invoke-VergeAPI -Method PUT -Endpoint "clusters/$($targetCluster.Key)" -Body $body -Connection $Server

                Write-Verbose "Cluster '$($targetCluster.Name)' modified successfully"

                if ($PassThru) {
                    # Return the updated cluster
                    Start-Sleep -Milliseconds 500
                    Get-VergeCluster -Key $targetCluster.Key -Server $Server
                }
            }
            catch {
                Write-Error -Message "Failed to modify cluster '$($targetCluster.Name)': $($_.Exception.Message)" -ErrorId 'ClusterModifyFailed'
            }
        }
    }
}