Public/metal.ps1
|
function Get-MetalHost { <# .SYNOPSIS Gets metal hosts from the Cloud Server. .DESCRIPTION Retrieves a list of metal hosts. Automatically handles token refresh. .PARAMETER Name Optional. Filter by metal host name. .PARAMETER ID Optional. Filter by metal host ID .EXAMPLE # Get all hosts Get-MetalHost .EXAMPLE # Get a host by ID Get-MetalHost -ID 5 .EXAMPLE # Get a host by name Get-MetalHost -Name si-storage-1 #> [CmdletBinding()] param( [Parameter(Mandatory = $false)] [string]$Name, [Parameter(Mandatory = $false)] [int]$ID ) # Build the URI $uri = "$($script:CloudConnection.BaseUri)/manifold-api/v2/metal/host" if ($Name) { $hosturi = "$uri/${Name}" $response = Invoke-CloudApiRequest -Uri $hosturi -Method Get return $response } else { # Use the helper function which handles token refresh automatically $response = Invoke-CloudApiRequest -Uri $uri -Method Get # Extract hosts from the response $hosts = $response.hosts # Filter by ID if specified - use PSBoundParameters to check if parameter was provided if ($PSBoundParameters.ContainsKey('ID')) { $hosts = $hosts | Where-Object { ($_.cloud_id -eq $ID) -and ($null -ne $_.cloud_id) } } return $hosts } } function Get-MetalLicense { <# .SYNOPSIS Gets metal license from the Cloud Server. .DESCRIPTION Retrieves the metal license. Automatically handles token refresh. .EXAMPLE # Get the current license status Get-MetalLicense# #> [CmdletBinding()] param( [Parameter(Mandatory = $false)] [string]$Name, [Parameter(Mandatory = $false)] [int]$ID ) # Build the URI $uri = "$($script:CloudConnection.BaseUri)/manifold-api/v2/metal/license" $producturi = "$($script:CloudConnection.BaseUri)/manifold-api/v2/metal" # Use the helper function which handles token refresh automatically $response = Invoke-CloudApiRequest -Uri $uri -Method Get $productresponse = Invoke-CloudApiRequest -Uri $producturi -Method Get $product = ($productresponse).product_name # Extract hosts from the response $license = $response if ($product -eq "HyperCloud") { write-host " " write-host "This is HyperCloud. License not required." -ForegroundColor Cyan write-host " " } else { return $license } if ($licensereq -eq $true) { return $license } } function Get-MetalCapacity { <# .SYNOPSIS Gets metal capacity from the Cloud Server. .DESCRIPTION Retrieves the metal Capacity. Automatically handles token refresh. .PARAMETER Graph Optional. Display capacity with visual percentage graphs .PARAMETER B Display memory and storage in Bytes .PARAMETER KB Display memory and storage in Kilobytes .PARAMETER MB Display memory and storage in Megabytes .PARAMETER GB Display memory and storage in Gigabytes .PARAMETER TB Display memory and storage in Terabytes .PARAMETER EB Display memory and storage in Exabytes .EXAMPLE # Get the capacity of the hosts Get-MetalCapacity .EXAMPLE # Get the capacity of the hosts and display what is used graphically Get-MetalCapacity -Graph .EXAMPLE # Get the capacity of the hosts in GB and display what is used graphically Get-MetalCapacity -Graph -GB .EXAMPLE # Get the capacity of the hosts in TB Get-MetalCapacity -TB #> [CmdletBinding()] param( [Parameter(Mandatory = $false)] [switch]$Graph, [Parameter(Mandatory = $false)] [switch]$B, [Parameter(Mandatory = $false)] [switch]$KB, [Parameter(Mandatory = $false)] [switch]$MB, [Parameter(Mandatory = $false)] [switch]$GB, [Parameter(Mandatory = $false)] [switch]$TB, [Parameter(Mandatory = $false)] [switch]$EB ) # Build the URI $uri = "$($script:CloudConnection.BaseUri)/manifold-api/v2/metal/capacity" # Use the helper function which handles token refresh automatically $response = Invoke-CloudApiRequest -Uri $uri -Method Get # Extract capacity from the response $metal = $response.capacity # Determine target unit and conversion factor $targetUnit = "native" $conversionFactors = @{ 'B' = @{ fromKB = 1024; fromMB = 1048576; label = 'B' } 'KB' = @{ fromKB = 1; fromMB = 1024; label = 'KB' } 'MB' = @{ fromKB = 1/1024; fromMB = 1; label = 'MB' } 'GB' = @{ fromKB = 1/1048576; fromMB = 1/1024; label = 'GB' } 'TB' = @{ fromKB = 1/1073741824; fromMB = 1/1048576; label = 'TB' } 'EB' = @{ fromKB = 1/1099511627776; fromMB = 1/1073741824; label = 'EB' } } if ($B) { $targetUnit = 'B' } elseif ($KB) { $targetUnit = 'KB' } elseif ($MB) { $targetUnit = 'MB' } elseif ($GB) { $targetUnit = 'GB' } elseif ($TB) { $targetUnit = 'TB' } elseif ($EB) { $targetUnit = 'EB' } else {$targetUnit = 'GB'} # Convert values if a unit is specified if ($targetUnit -ne "native") { # Convert values $convertedMetal = foreach ($item in $metal) { $newItem = $item.PSObject.Copy() # Determine decimal precision based on unit size $decimalPlaces = switch ($targetUnit) { 'B' { 0 } # Bytes - no decimals needed 'KB' { 0 } # Kilobytes - no decimals needed 'MB' { 2 } # Megabytes - 2 decimals 'GB' { 2 } # Gigabytes - 2 decimals 'TB' { 4 } # Terabytes - 4 decimals for precision 'EB' { 6 } # Exabytes - 6 decimals for very small numbers default { 2 } } if ($item.name -eq "memory") { # Memory is in KB, convert from KB $newItem.allocated = [math]::Round($item.allocated * $conversionFactors[$targetUnit].fromKB, $decimalPlaces) $newItem.total = [math]::Round($item.total * $conversionFactors[$targetUnit].fromKB, $decimalPlaces) $newItem.units = $conversionFactors[$targetUnit].label } elseif ($item.name -eq "storage") { # Storage is in MB, convert from MB $newItem.allocated = [math]::Round($item.allocated * $conversionFactors[$targetUnit].fromMB, $decimalPlaces) $newItem.total = [math]::Round($item.total * $conversionFactors[$targetUnit].fromMB, $decimalPlaces) $newItem.units = $conversionFactors[$targetUnit].label } $newItem } $metal = $convertedMetal # And update the formatting in the Graph section: if ($Graph) { Write-Host ("=" * 60) foreach ($item in $metal) { # Calculate percentage if ($item.total -and $item.total -gt 0) { $percent = [math]::Round(($item.allocated / $item.total) * 100) } else { $percent = 0 } # Format the name with proper spacing $nameDisplay = $item.name.PadRight(8) Write-Host "$nameDisplay" -NoNewline Show-PercentageGraphSolid -percent $percent # Determine format based on unit size $formatString = switch ($targetUnit) { 'B' { "N0" } # No decimals for Bytes 'KB' { "N0" } # No decimals for KB 'MB' { "N2" } # 2 decimals for MB 'GB' { "N2" } # 2 decimals for GB 'TB' { "N4" } # 4 decimals for TB 'EB' { "N6" } # 6 decimals for EB default { "N2" } } # Format numbers $allocatedFormatted = "{0:$formatString}" -f $item.allocated $totalFormatted = "{0:$formatString}" -f $item.total Write-Host " - $allocatedFormatted / $totalFormatted $($item.units)" } Write-Host "" } else { # Return normal object output return $metal } } } function Get-MetalStorage { <# .SYNOPSIS Gets metal storage (Ceph) information from the Cloud Server. .DESCRIPTION Retrieves metal storage cluster status including health, capacity, OSDs, and PGs. Automatically handles token refresh. .PARAMETER Graph Optional. Display storage metrics with visual percentage graphs .PARAMETER ShowFlags Optional. Display Ceph flags in a formatted table .PARAMETER GB Display storage in Gigabytes (default) .PARAMETER TB Display storage in Terabytes .EXAMPLE # Get storage information Get-MetalStorage .EXAMPLE # Get storage information and display it graphically Get-MetalStorage -Graph .EXAMPLE # Get storage flags currently applied Get-MetalStorage -ShowFlags .EXAMPLE # Get storage flags currently applied and show stats graphically Get-MetalStorage -Graph -ShowFlags .EXAMPLE # Get storage information in TB and display stats graphically Get-MetalStorage -Graph -TB #> [CmdletBinding()] param( [Parameter(Mandatory = $false)] [switch]$Graph, [Parameter(Mandatory = $false)] [switch]$ShowFlags, [Parameter(Mandatory = $false)] [switch]$GB, [Parameter(Mandatory = $false)] [switch]$TB ) # Build the URI $uri = "$($script:CloudConnection.BaseUri)/manifold-api/v2/metal/storage" # Use the helper function which handles token refresh automatically $response = Invoke-CloudApiRequest -Uri $uri -Method Get # Extract storage info from the response $storage = $response # If only ShowFlags is specified (without Graph), display flags table and return if ($ShowFlags -and -not $Graph) { if ($storage.flags) { Write-Host "`n--- Ceph Flags ---" -ForegroundColor Cyan # Create table of flags $flagTable = $storage.flags | ForEach-Object { [PSCustomObject]@{ Flag = $_.name Set = if ($_.is_set) { "Yes" } else { "No" } Type = $_.type Description = $_.description } } $flagTable | Format-Table -AutoSize -Wrap } else { Write-Host "No flags available" -ForegroundColor Gray } return } # Handle graphing if requested if ($Graph) { # Determine target unit and conversion factor $targetUnit = "GB" # Default to GB $conversionFactors = @{ 'GB' = @{ fromMB = 1/1024; label = 'GB'; decimals = 2; format = 'N2' } 'TB' = @{ fromMB = 1/1048576; label = 'TB'; decimals = 4; format = 'N4' } } if ($TB) { $targetUnit = 'TB' } $unitConfig = $conversionFactors[$targetUnit] Write-Host ("=" * 70) # Cluster Health $healthColor = switch ($storage.health) { 'HEALTH_OK' { 'Green' } 'HEALTH_WARN' { 'Yellow' } 'HEALTH_ERR' { 'Red' } default { 'White' } } Write-Host "`nCluster Health: $($storage.health)" -ForegroundColor $healthColor Write-Host "Version: $($storage.version_name) ($($storage.version_number))" -ForegroundColor Gray if ($storage.alert_count -gt 0) { Write-Host "Alerts: $($storage.alert_count)" -ForegroundColor Yellow } # Storage Capacity Write-Host "`n--- Storage Capacity ---" -ForegroundColor Cyan $usedConverted = [math]::Round($storage.storage_used_MB * $unitConfig.fromMB, $unitConfig.decimals) $totalConverted = [math]::Round($storage.storage_total_MB * $unitConfig.fromMB, $unitConfig.decimals) $freeConverted = $totalConverted - $usedConverted $utilizationPercent = [math]::Round($storage.storage_utilization_percent) Write-Host " capacity " -NoNewline Show-PercentageGraphSolid -percent $utilizationPercent Write-Host (" - {0:$($unitConfig.format)} / {1:$($unitConfig.format)} {2} ({3:$($unitConfig.format)} free)" -f $usedConverted, $totalConverted, $unitConfig.label, $freeConverted) # OSD Status Write-Host "`n--- OSD Status ---" -ForegroundColor Cyan # OSDs Up - show the HEALTHY percentage (up/total) $osdUpPercent = if ($storage.osd_count -gt 0) { [math]::Round(($storage.osd_up / $storage.osd_count) * 100) } else { 0 } Write-Host " up " -NoNewline Show-PercentageGraphSolid -percent $osdUpPercent -Invert Write-Host (" {0,3}% - {1,3} / {2,3} OSDs" -f $osdUpPercent, $storage.osd_up, $storage.osd_count) # OSDs In - show the HEALTHY percentage (in/total) $osdInPercent = if ($storage.osd_count -gt 0) { [math]::Round(($storage.osd_in / $storage.osd_count) * 100) } else { 0 } Write-Host " in " -NoNewline Show-PercentageGraphSolid -percent $osdInPercent -Invert Write-Host (" {0,3}% - {1,3} / {2,3} OSDs" -f $osdInPercent, $storage.osd_in, $storage.osd_count) # Show warnings for down/out OSDs if ($storage.osd_down -gt 0) { Write-Host " down - $($storage.osd_down) OSDs" -ForegroundColor Yellow } if ($storage.osd_out -gt 0) { Write-Host " out - $($storage.osd_out) OSDs" -ForegroundColor Yellow } # Placement Group Status Write-Host "`n--- Placement Groups ---" -ForegroundColor Cyan $activeCleanCount = if ($storage.pgs_by_state.'active+clean') { $storage.pgs_by_state.'active+clean' } else { 0 } # Show the HEALTHY percentage $pgHealthyPercent = if ($storage.pg_count -gt 0) { [math]::Round(($activeCleanCount / $storage.pg_count) * 100) } else { 0 } Write-Host " healthy " -NoNewline Show-PercentageGraphSolid -percent $pgHealthyPercent -Invert Write-Host (" {0,3}% - {1,5} / {2,5} PGs (active+clean)" -f $pgHealthyPercent, $activeCleanCount, $storage.pg_count) # Show unhealthy PGs if any $unhealthyPGs = $storage.pg_count - $activeCleanCount if ($unhealthyPGs -gt 0) { Write-Host " unhealthy PGs: $unhealthyPGs" -ForegroundColor Yellow # Show PG states foreach ($state in $storage.pgs_by_state.PSObject.Properties) { if ($state.Name -ne 'active+clean') { Write-Host " $($state.Name): $($state.Value)" -ForegroundColor Gray } } } # Pool and Object Info Write-Host "`n--- Cluster Statistics ---" -ForegroundColor Cyan Write-Host " Pools: $($storage.pool_count)" Write-Host " Objects: $("{0:N0}" -f $storage.object_count)" Write-Host " Avg PGs per OSD: $($storage.average_pgs_per_osd)" Write-Host " Monitors in quorum: $($storage.monitors_in_quorum)" # I/O Performance Write-Host "`n--- Current I/O ---" -ForegroundColor Cyan $readMBps = [math]::Round($storage.read_bytes_per_sec / 1048576, 2) $writeMBps = [math]::Round($storage.write_bytes_per_sec / 1048576, 2) Write-Host " Read: $("{0:N2}" -f $readMBps) MB/s" Write-Host " Write: $("{0:N2}" -f $writeMBps) MB/s" Write-Host " IOPS: $($storage.io_per_sec)" # Show flags if requested if ($ShowFlags -and $storage.flags) { Write-Host "`n--- Ceph Flags ---" -ForegroundColor Cyan # Create table of flags $flagTable = $storage.flags | ForEach-Object { [PSCustomObject]@{ Flag = $_.name Set = if ($_.is_set) { "Yes" } else { "No" } Type = $_.type Description = $_.description } } $flagTable | Format-Table -AutoSize -Wrap } Write-Host "" } else { # Return normal object output return $storage } } function Rename-MetalDatastore { <# .SYNOPSIS Rename a datastore on the Cloud Server. .DESCRIPTION Renames a datatore. Automatically handles token refresh. .PARAMETER Name Old/current name of the datastore .PARAMETER NewName The new name of the datastore .EXAMPLE # Rename datastore "OldName" to "new-name" Rename-CloudImage -Name "OldName" -NewName "new-name" #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Name, [Parameter(Mandatory = $true)] [string]$NewName ) $rename = @{ name = $NewName } $uri = "$($script:CloudConnection.BaseUri)/manifold-api/v2/metal/datastore/${Name}" $response = Invoke-CloudApiRequest -Uri $uri -Method Patch -Body $rename return $response } function Remove-MetalDatastore { <# .SYNOPSIS Removes a datastore from the Cloud Server. .DESCRIPTION Deletes a datastore by name. Automatically handles token refresh. .PARAMETER Name Required. Name of the datastore to remove .EXAMPLE # Remove a datastore and prompt for confirmation Remove-MetalDatastore -Name "TenantDatastore" .EXAMPLE # Remove a datastore and bypass the confirmation prompt Remove-MetalDatastore -Name "TenantDatastore" -Confirm:$false #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')] param( [Parameter(Mandatory = $true)] [string]$Name ) process { # Get the Datastore details for confirmation message $datastore = (Get-CloudDatastore -Name $Name).name if (-not $datastore) { Write-Warning "Datastore $Name not found." return } # Confirm before deletion if ($PSCmdlet.ShouldProcess("Datastore $datastore", "Remove Datastore")) { $uri = "$($script:CloudConnection.BaseUri)/manifold-api/v2/metal/datastore/$Name" Write-Verbose "Removing Datastore: $Name" try { $response = Invoke-CloudApiRequest -Uri $uri -Method Delete Write-Host "Datastore $Name removed successfully." -ForegroundColor Green return $response } catch { # Check for specific error messages if ($_.Exception.Message -match "not found|does not exist") { Write-Warning "Datastore $Name not found or already deleted." return } else { # Re-throw other errors throw } } } } } function New-MetalDatastore { <# .SYNOPSIS Creates a new datastore on the Cloud Server. .DESCRIPTION Creates a new datastore. Automatically handles token refresh. .PARAMETER Name Required. Name of the datastore. Cannot contain spaces .PARAMETER Protection Required. Protection scheme of the datastore. Valid options are triple_replication, ec4+2, ec8+3, ec8+4 .EXAMPLE # Creates a new datastore named "3REP-Datastore" that is triple-replicated New-MetalDatastore -Name "3REP-Datastore" -Protection triple_replication #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateScript({ if ($_ -match '\s') { throw "Name cannot contain spaces: $_" } return $true })] [string]$Name, [Parameter(Mandatory = $True)] [ValidateScript({ if ($_ -match '\s') { throw "Name cannot contain spaces: $_" } return $true })] [string]$Protection ) # Build the configuration - NAME goes INSIDE the template string $config = [PSCustomObject]@{ name = $Name scheme = $Protection } # Build the URI $uri = "$($script:CloudConnection.BaseUri)/manifold-api/v2/metal/datastore" Write-Verbose "Creating datastore '$Name' with protection scheme '$Protection'" Write-Verbose "Request body: $($config | ConvertTo-Json -Compress)" # Use the helper function which handles token refresh automatically $response = Invoke-CloudApiRequest -Uri $uri -Method Post -Body $config Write-Host "Datastore '$Name' created successfully." -ForegroundColor Green return $response } |