Private/SizingEstimator.ps1

# Copyright (c) 2026 Broadcom. All Rights Reserved.
# Broadcom Confidential. The term "Broadcom" refers to Broadcom Inc.
# and/or its subsidiaries.
#
# =============================================================================
#
# SOFTWARE LICENSE AGREEMENT
#
# Copyright (c) CA, Inc. All rights reserved.
#
# You are hereby granted a non-exclusive, worldwide, royalty-free license
# under CA, Inc.'s copyrights to use, copy, modify, and distribute this
# software in source code or binary form for use in connection with CA, Inc.
# products.
#
# This copyright notice shall be included in all copies or substantial
# portions of the software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
# =============================================================================
#region SizingEstimator

function Get-VcfCheckSizingReferenceData {

    <#
        .SYNOPSIS
        Loads the vCPU/RAM/Disk reference data for VCF 9.1.1 management/workload domain
        components, keyed by component then size tier.

        .DESCRIPTION
        Reads Data/Sizing/VmsSizingReferences911.json - the authoritative sizing reference
        data checked into the codebase.

        .PARAMETER Path
        Path to the reference-data JSON file. Defaults to
        Data/Sizing/VmsSizingReferences911.json under the module root.

        .OUTPUTS
        [Hashtable] component name -> { cpuCores, memoryGb, storageGb, ... } keyed by size tier.
    #>


    [CmdletBinding()]
    [OutputType([Hashtable])]
    Param (
        [Parameter(Mandatory = $false)] [String]$Path
    )

    if (-not $Path) {
        $moduleRoot = Split-Path -Parent $PSScriptRoot
        $Path = Join-Path -Path $moduleRoot -ChildPath 'Data/Sizing/VmsSizingReferences911.json'
    }

    if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
        throw [System.InvalidOperationException]::new("Sizing reference data not found at `"$Path`".")
    }

    Test-VcfCheckStrictJson -Path $Path

    try {
        return Get-Content -LiteralPath $Path -Raw -ErrorAction Stop | ConvertFrom-Json -Depth 10 -AsHashtable -ErrorAction Stop
    } catch {
        throw [System.InvalidOperationException]::new("`"$Path`" is not valid JSON: $($_.Exception.Message)")
    }
}
function Get-VcfCheckBrownfieldTreatmentData {

    <#
        .SYNOPSIS
        Loads the per-component brownfield upgrade treatment rules used to compute the 5.2 ->
        9.1.1 worst-case resource delta.

        .DESCRIPTION
        Reads Data/Sizing/ComponentUpgradeCoexistence.json, which classifies each management/
        workload domain component as 'excluded' (in-place upgrade or post-upgrade-only, no delta
        contribution), 'netNew' (brand-new appliance, counted once), or 'temporaryDouble'
        (old and new appliance coexist during migration - currently only vCenter).

        .PARAMETER Path
        Path to the treatment-rules JSON file. Defaults to
        Data/Sizing/ComponentUpgradeCoexistence.json under the module root.

        .OUTPUTS
        [Hashtable] with a 'components' key (component name -> treatment metadata) and a
        'vcenterSizeThresholds' key (tier -> capacity/resource requirement).
    #>


    [CmdletBinding()]
    [OutputType([Hashtable])]
    Param (
        [Parameter(Mandatory = $false)] [String]$Path
    )

    if (-not $Path) {
        $moduleRoot = Split-Path -Parent $PSScriptRoot
        $Path = Join-Path -Path $moduleRoot -ChildPath 'Data/Sizing/ComponentUpgradeCoexistence.json'
    }

    if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
        throw [System.InvalidOperationException]::new("Brownfield treatment data not found at `"$Path`".")
    }

    Test-VcfCheckStrictJson -Path $Path

    try {
        return Get-Content -LiteralPath $Path -Raw -ErrorAction Stop | ConvertFrom-Json -Depth 10 -AsHashtable -ErrorAction Stop
    } catch {
        throw [System.InvalidOperationException]::new("`"$Path`" is not valid JSON: $($_.Exception.Message)")
    }
}
function Get-VcfCheckVCenterSizeTier {

    <#
        .SYNOPSIS
        Derives the vCenter size tier that covers a live host/VM count, per
        vcenterSizeThresholds in ComponentUpgradeCoexistence.json.

        .DESCRIPTION
        Walks the tiers in ascending order and returns the first whose maxHosts and
        maxVirtualMachines both cover the supplied counts. Falls back to the largest
        defined tier if none of them cover the counts (rather than throwing), since an
        oversized live environment should still produce a usable, if under-sized, estimate.
        Never recommends the "Tiny" tier - Broadcom scopes Tiny to proof-of-concept
        deployments only, so the smallest tier this function will return is "Small".

        .PARAMETER HostCount
        Live ESX host count against the management domain's vCenter.

        .PARAMETER VirtualMachineCount
        Live VM count against the management domain's vCenter.

        .PARAMETER TreatmentData
        Output of Get-VcfCheckBrownfieldTreatmentData.

        .OUTPUTS
        [String] the matching tier's "size" value (e.g. "Medium").
    #>


    [CmdletBinding()]
    [OutputType([String])]
    Param (
        [Parameter(Mandatory = $true)] [Int]$HostCount,
        [Parameter(Mandatory = $true)] [Int]$VirtualMachineCount,
        [Parameter(Mandatory = $true)] [Hashtable]$TreatmentData
    )

    $tiers = $TreatmentData['vcenterSizeThresholds']['tiers'] | Where-Object { $_['size'] -ne 'Tiny' }
    foreach ($tier in $tiers) {
        if ($HostCount -le $tier['maxHosts'] -and $VirtualMachineCount -le $tier['maxVirtualMachines']) {
            return $tier['size']
        }
    }

    return $tiers[-1]['size']
}
function Get-VcfCheckManagementDomainSizingEstimate {

    <#
        .SYNOPSIS
        Computes the worst-case vCPU/RAM/Disk delta needed to upgrade a management domain's
        components to VCF 9.1.1, on top of whatever the domain's cluster is already using.

        .DESCRIPTION
        For each selected component, looks up its brownfield treatment ('excluded', 'netNew', or
        'temporaryDouble'). Excluded components (in-place upgrades, or 9.1.1-only constructs that
        never coexist with anything during the transition) contribute nothing. Every other
        component contributes exactly one new appliance's worth of resources - the arithmetic is
        identical for 'netNew' and 'temporaryDouble' because the existing appliance (if any) is
        already counted in the cluster's current utilization, which this delta is meant to sit on
        top of; 'temporaryDouble' only differs in how its size is determined (derived from live
        inventory, e.g. vCenter) rather than freely chosen by the caller.

        .PARAMETER Selections
        Array of objects/hashtables, each with ComponentKey (matches a top-level key in the
        reference data and treatment data), SizeKey (matches a size tier under that component,
        case-insensitive), and optional NodeCount (defaults to 1). For a component whose
        treatment has sizeConstraint "derivedFromLiveInventoryCount" (currently only vCenter),
        SizeKey may be omitted in favor of HostCount/VirtualMachineCount, from which the tier is
        derived via Get-VcfCheckVCenterSizeTier - an explicit SizeKey always takes precedence.

        .PARAMETER ReferenceData
        Output of Get-VcfCheckSizingReferenceData.

        .PARAMETER TreatmentData
        Output of Get-VcfCheckBrownfieldTreatmentData.

        .OUTPUTS
        [PSObject] with TotalVCpu, TotalMemoryGb, TotalStorageGb, and a Components array of
        per-selection breakdown rows.
    #>


    [CmdletBinding()]
    [OutputType([PSObject])]
    Param (
        [Parameter(Mandatory = $true)] [Object[]]$Selections,
        [Parameter(Mandatory = $true)] [Hashtable]$ReferenceData,
        [Parameter(Mandatory = $true)] [Hashtable]$TreatmentData
    )

    $components = $TreatmentData['components']
    $rows = @()
    $totalVCpu = 0.0
    $totalMemoryGb = 0.0
    $totalStorageGb = 0.0

    foreach ($selection in $Selections) {
        $componentKey = $selection.ComponentKey
        $sizeKey = $selection.SizeKey
        $nodeCount = if ($selection.NodeCount) { $selection.NodeCount } else { 1 }

        if (-not $components.ContainsKey($componentKey)) {
            throw [System.InvalidOperationException]::new("Unknown sizing component `"$componentKey`" - no brownfield treatment is defined for it.")
        }
        $treatment = $components[$componentKey]['treatment']

        if (-not $sizeKey -and $components[$componentKey]['sizeConstraint'] -eq 'derivedFromLiveInventoryCount' -and $null -ne $selection.HostCount -and $null -ne $selection.VirtualMachineCount) {
            $sizeKey = Get-VcfCheckVCenterSizeTier -HostCount $selection.HostCount -VirtualMachineCount $selection.VirtualMachineCount -TreatmentData $TreatmentData
        }

        if ($treatment -eq 'excluded') {
            $rows += [PSCustomObject]@{
                ComponentKey = $componentKey
                Treatment    = $treatment
                VCpu         = 0.0
                MemoryGb     = 0.0
                StorageGb    = 0.0
            }
            continue
        }

        if ($components[$componentKey]['referenceDataMissing']) {
            throw [System.InvalidOperationException]::new("Component `"$componentKey`" has no vCPU/RAM/Disk reference data extracted yet (see ComponentUpgradeCoexistence.json's referenceDataMissing flag) - cannot include it in the estimate.")
        }
        $referenceKey = if ($components[$componentKey]['referenceKey']) { $components[$componentKey]['referenceKey'] } else { $componentKey }
        if (-not $ReferenceData.ContainsKey($referenceKey)) {
            throw [System.InvalidOperationException]::new("No sizing reference data found for component `"$componentKey`" (reference key `"$referenceKey`").")
        }
        $componentData = $ReferenceData[$referenceKey]
        $sizeKeyLower = $sizeKey.ToLowerInvariant()
        if (-not $componentData['cpuCores'].ContainsKey($sizeKeyLower)) {
            throw [System.InvalidOperationException]::new("Component `"$componentKey`" has no size tier `"$sizeKey`" in the sizing reference data.")
        }

        $vCpu = [Double]$componentData['cpuCores'][$sizeKeyLower] * $nodeCount
        $memoryGb = [Double]$componentData['memoryGb'][$sizeKeyLower] * $nodeCount

        $storageDict = $componentData['storageGb']
        $storageDictKey = @($storageDict.Keys | Where-Object { $_ -ieq $sizeKeyLower })[0]
        if (-not $storageDictKey) {
            # Some components (currently only vcenter) key storageGb by a compound
            # "<size><StorageSizeKey>" (e.g. "mediumDefault"/"mediumLStorage"/"mediumXLStorage")
            # because VCDB storage size is a second, independently-chosen dimension - see the
            # sizeConstraint note on managementDomainVcenter/workloadDomainVcenter in
            # ComponentUpgradeCoexistence.json. Defaults to "Default" when the caller doesn't
            # specify one.
            $storageSizeKey = if ($selection.StorageSizeKey) { $selection.StorageSizeKey } else { 'Default' }
            $compoundKey = "$sizeKey$storageSizeKey"
            $storageDictKey = @($storageDict.Keys | Where-Object { $_ -ieq $compoundKey })[0]
        }
        if (-not $storageDictKey) {
            throw [System.InvalidOperationException]::new("Component `"$componentKey`" has no storage size entry matching `"$sizeKey`" (or `"$sizeKey$($selection.StorageSizeKey ?? 'Default')`") in the sizing reference data.")
        }
        $storageGb = [Double]$storageDict[$storageDictKey] * $nodeCount

        $totalVCpu += $vCpu
        $totalMemoryGb += $memoryGb
        $totalStorageGb += $storageGb

        $rows += [PSCustomObject]@{
            ComponentKey = $componentKey
            Treatment    = $treatment
            SizeKey      = $sizeKeyLower
            NodeCount    = $nodeCount
            VCpu         = $vCpu
            MemoryGb     = $memoryGb
            StorageGb    = $storageGb
        }
    }

    return [PSCustomObject]@{
        TotalVCpu      = $totalVCpu
        TotalMemoryGb  = $totalMemoryGb
        TotalStorageGb = $totalStorageGb
        Components     = $rows
    }
}
function Write-VcfCheckSizingProgress {

    <#
        .SYNOPSIS
        Records the sizing-detection step currently in progress, for both the debug log and
        Start-VcfCheckServer.py's timeout handler.

        .DESCRIPTION
        Invoke-VcfCheckSizingDetect.ps1 runs as a one-shot subprocess with a fixed overall
        timeout; if it's killed for exceeding that timeout, the only trace of what it was doing
        is whatever it last logged. A slow step (e.g. a Supervisor check stuck on a failing TLS
        handshake) never gets to log its own completion, so the log alone can't say which step
        was still running when the process was killed. Writing the in-progress step to a small
        file the parent process can read after a timeout closes that gap.

        .PARAMETER Step
        Short human-readable description of the check in progress (e.g. "checking Supervisor presence").

        .PARAMETER DomainName
        Domain the step is running against.

        .PARAMETER VCenterFqdn
        vCenter FQDN the step is running against.
    #>


    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)] [String]$Step,
        [Parameter(Mandatory = $false)] [String]$DomainName,
        [Parameter(Mandatory = $false)] [String]$VCenterFqdn
    )

    Write-LogMessage -Type DEBUG -Message "Sizing detection progress: $Step (domain `"$DomainName`", vCenter `"$VCenterFqdn`")."

    $progressFilePath = $env:VCFCHECK_SIZING_PROGRESS_FILE
    if ([String]::IsNullOrWhiteSpace($progressFilePath)) {
        return
    }
    try {
        @{
            step         = $Step
            domainName   = $DomainName
            vcenterFqdn  = $VCenterFqdn
            timestampUtc = (Get-Date).ToUniversalTime().ToString('o')
        } | ConvertTo-Json -Compress | Set-Content -LiteralPath $progressFilePath -Encoding UTF8 -Force
    } catch {
        Write-LogMessage -Type DEBUG -Message "Sizing detection: could not write progress file `"$progressFilePath`": $($_.Exception.Message)"
    }
}
function Get-VcfCheckSizingSnapshotForVCenter {

    <#
        .SYNOPSIS
        Gathers the live host/VM counts, Supervisor presence, and appliance sizing snapshot for
        one already-reachable vCenter, for the Upgrade Resource Estimator's live Detect feature.

        .DESCRIPTION
        Shared by the management domain and every workload domain vCenter in
        Invoke-VcfCheckSizingDetect.ps1 - the snapshot shape (current/actual/recommended tier)
        is identical regardless of which kind of domain the vCenter belongs to.

        .PARAMETER DomainName
        Name of the domain this vCenter belongs to, carried through into the snapshot for display.

        .PARAMETER VCenterFqdn
        The vCenter FQDN to gather the snapshot for. Must already be connected via
        Connect-VcfCheckVCenter.

        .PARAMETER ApplianceInventoryServers
        FQDNs of every already-connected vCenter to search for this vCenter's own appliance VM,
        in search order. VCF deploys every vCenter appliance - including workload domain ones -
        onto the management domain's cluster, so a workload domain vCenter's own appliance VM
        usually is not visible in its own inventory and must be found via the management vCenter
        instead.

        .PARAMETER TreatmentData
        Output of Get-VcfCheckBrownfieldTreatmentData.

        .OUTPUTS
        [Hashtable] the vCenter sizing snapshot: { domainName, hostCount, virtualMachineCount,
        supervisorPresent, vcenterFqdn, currentSizeTier, currentSizeMaxHosts,
        currentSizeMaxVirtualMachines, currentSizeStorageGb, actualCpuCores, actualMemoryGb,
        actualStorageGb, actualSizeTier, actualStorageSizeKey, recommendedSizeTier }.
        "actualSizeTier" is null when the appliance's own VM couldn't be identified in its
        inventory, or when its specs don't match any known tier (a non-standard/manually-resized
        appliance). "actualStorageSizeKey" is which of the VCSA deployment wizard's fixed disk
        presets ("Default", "LStorage", "XLStorage") the appliance's actual disk total matches
        for its actualSizeTier - null when actualSizeTier itself is null, or when the disk was
        customized to a size outside all three presets (VCSA does not support arbitrary disk
        sizes; see Broadcom KB326287).
    #>


    [CmdletBinding()]
    [OutputType([Hashtable])]
    Param (
        [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$DomainName,
        [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$VCenterFqdn,
        [Parameter(Mandatory = $true)] [String[]]$ApplianceInventoryServers,
        [Parameter(Mandatory = $true)] [Hashtable]$TreatmentData
    )

    Write-VcfCheckSizingProgress -Step 'reading host inventory' -DomainName $DomainName -VCenterFqdn $VCenterFqdn
    $hostCount = @(Get-VcfCheckVMHostInventory -Server $VCenterFqdn).Count

    Write-VcfCheckSizingProgress -Step 'reading virtual machine inventory' -DomainName $DomainName -VCenterFqdn $VCenterFqdn
    $virtualMachineCount = @(Get-VcfCheckVMInventory -Server $VCenterFqdn).Count

    Write-VcfCheckSizingProgress -Step 'checking Supervisor presence' -DomainName $DomainName -VCenterFqdn $VCenterFqdn
    $supervisorPresent = $false
    try {
        $supervisorClusters = @(Get-VcfCheckSupervisorCluster -Server $VCenterFqdn)
        $supervisorPresent = $supervisorClusters.Count -gt 0
    } catch {
        Write-LogMessage -Type WARNING -Message "Sizing detection: could not determine Supervisor presence on `"$VCenterFqdn`" - defaulting to not present: $($_.Exception.Message)"
    }

    $currentSizeTier = Get-VcfCheckVCenterSizeTier -HostCount $hostCount -VirtualMachineCount $virtualMachineCount -TreatmentData $TreatmentData
    $currentTierData = @($TreatmentData['vcenterSizeThresholds']['tiers'] | Where-Object { $_['size'] -eq $currentSizeTier })[0]
    $tierOrder = @($TreatmentData['vcenterSizeThresholds']['tiers'] | ForEach-Object { $_['size'] })

    # Match the vCenter appliance VM's own measured vCPU/RAM against the reference tiers to
    # report what it is actually running today, as distinct from what the live host/VM count
    # requires - a manually-resized or otherwise non-standard appliance won't match any tier.
    $actualCpuCores = $null
    $actualMemoryGb = $null
    $actualStorageGb = $null
    $actualSizeTier = $null
    $actualStorageSizeKey = $null
    Write-VcfCheckSizingProgress -Step 'identifying the vCenter appliance VM' -DomainName $DomainName -VCenterFqdn $VCenterFqdn
    try {
        $vcenterVm = $null
        foreach ($inventoryServer in $ApplianceInventoryServers) {
            $vcenterVm = @(Get-VcfCheckVMInventory -Server $inventoryServer | Where-Object { $_.Guest -and $_.Guest.HostName -and $_.Guest.HostName -ieq $VCenterFqdn }) | Select-Object -First 1
            if ($vcenterVm) {
                break
            }
        }
        if ($vcenterVm) {
            $actualCpuCores = [Int]$vcenterVm.NumCpu
            $actualMemoryGb = [Math]::Round([Double]$vcenterVm.MemoryGB, 0)
            $actualStorageGb = [Math]::Round([Double](Get-VcfCheckHardDiskInventoryForVM -VM $vcenterVm | Measure-Object -Property CapacityGB -Sum).Sum, 0)

            $vcenterRef = (Get-VcfCheckSizingReferenceData)['vcenter']
            foreach ($tierName in $tierOrder) {
                $tierKey = $tierName.ToLowerInvariant()
                if ($vcenterRef['cpuCores'][$tierKey] -eq $actualCpuCores -and $vcenterRef['memoryGb'][$tierKey] -eq $actualMemoryGb) {
                    $actualSizeTier = $tierName
                    break
                }
            }
            if (-not $actualSizeTier) {
                Write-LogMessage -Type WARNING -Message "Sizing detection: `"$VCenterFqdn`" appliance ($actualCpuCores vCPU / $actualMemoryGb GB RAM) does not match any known vCenter size tier - reporting as non-standard."
            } else {
                # The VCSA deployment wizard only ever creates one of a fixed set of disk
                # layouts per t-shirt size (Default/Large Storage/X-Large Storage) - it does not
                # support arbitrary disk sizes (see Broadcom KB326287). If the appliance's actual
                # disk total doesn't exactly match one of its own tier's presets, its disk was
                # customized outside those presets, so a "we need N GB more" delta is misleading:
                # growing it during the upgrade would actually mean moving up to the next preset.
                # Each preset's exact GB total has changed release to release (e.g. vSphere 8.0's
                # Small Default is 694 GB vs. 9.1's 734 GB), so an appliance still on an older
                # release's preset size is matched against every known release's values, not just
                # the current reference data's - otherwise a perfectly standard older appliance
                # would be wrongly reported as customized/disjoint. A disk up to 5% smaller than
                # a preset's GB total is still treated as that preset rather than "customized" -
                # thin-provisioned/rounded appliances routinely report a hair under the nominal
                # preset size without ever having been deliberately resized.
                $storageTierKey = $actualSizeTier.ToLowerInvariant()
                $storageGbSets = @($vcenterRef['storageGb'], $vcenterRef['storageGbLegacy80'])
                foreach ($presetSuffix in @('Default', 'LStorage', 'XLStorage')) {
                    if (@($storageGbSets | Where-Object {
                        $presetGb = $_[$storageTierKey + $presetSuffix]
                        $presetGb -and $actualStorageGb -le $presetGb -and $actualStorageGb -ge ($presetGb * 0.95)
                    }).Count -gt 0) {
                        $actualStorageSizeKey = $presetSuffix
                        break
                    }
                }
                if (-not $actualStorageSizeKey) {
                    Write-LogMessage -Type WARNING -Message "Sizing detection: `"$VCenterFqdn`" appliance disk ($actualStorageGb GB) does not match any `"$actualSizeTier`" storage preset (Default/Large Storage/X-Large Storage) - reporting as a customized disk size."
                }
            }
        } else {
            Write-LogMessage -Type WARNING -Message "Sizing detection: could not identify the vCenter appliance VM for `"$VCenterFqdn`" in its own inventory by guest hostname - actual appliance size cannot be determined."
        }
    } catch {
        Write-LogMessage -Type WARNING -Message "Sizing detection: could not read the vCenter appliance VM's vCPU/RAM/disk for `"$VCenterFqdn`": $($_.Exception.Message)"
    }

    # "We size to whichever offers the larger appliance" - an appliance already running bigger
    # than the live host/VM count requires (or vice versa, an undersized one) should recommend
    # upgrading to the larger of the two, not just the inventory floor.
    $recommendedSizeTier = $currentSizeTier
    if ($actualSizeTier -and [Array]::IndexOf($tierOrder, $actualSizeTier) -gt [Array]::IndexOf($tierOrder, $currentSizeTier)) {
        $recommendedSizeTier = $actualSizeTier
    }

    Write-LogMessage -Type INFO -Message "Sizing detection completed for `"$VCenterFqdn`" (domain `"$DomainName`"): hostCount=$hostCount, virtualMachineCount=$virtualMachineCount, supervisorPresent=$supervisorPresent, currentSizeTier=$currentSizeTier, actualSizeTier=$actualSizeTier, recommendedSizeTier=$recommendedSizeTier."
    return @{
        domainName                    = $DomainName
        hostCount                     = $hostCount
        virtualMachineCount           = $virtualMachineCount
        supervisorPresent             = $supervisorPresent
        vcenterFqdn                   = $VCenterFqdn
        currentSizeTier               = $currentSizeTier
        currentSizeMaxHosts           = $currentTierData['maxHosts']
        currentSizeMaxVirtualMachines = $currentTierData['maxVirtualMachines']
        currentSizeStorageGb          = $currentTierData['storageGb']
        actualCpuCores                = $actualCpuCores
        actualMemoryGb                = $actualMemoryGb
        actualStorageGb               = $actualStorageGb
        actualSizeTier                = $actualSizeTier
        actualStorageSizeKey          = $actualStorageSizeKey
        recommendedSizeTier           = $recommendedSizeTier
    }
}