PureStorage.CommonUtil.ps1
<# Common Utility functions. #> $DEFAULT_PER_HOST_TIMEOUT_IN_SECONDS = 30 $MIN_TIMEOUT_IN_MINUTES = 10 $MAX_TIMEOUT_IN_MINUTES = 60 function Get-PfaHostFromVmHost { <# .SYNOPSIS Gets a FlashArray host object from a ESXi vmhost object .DESCRIPTION Takes in a vmhost and returns a matching FA host if found #> Param( [Parameter(Mandatory=$true,ValueFromPipeline=$True)] [VMware.VimAutomation.ViCore.Types.V1.Inventory.VMHost]$Esxi, [Parameter(Mandatory=$true,ValueFromPipeline=$True)] $Flasharray ) $iscsiadapter = $esxi | Get-VMHostHBA -Type iscsi | Where-Object {$_.Model -eq "iSCSI Software Adapter"} $fahosts = Get-Pfa2Host -Array $FlashArray | Where-Object {$_.IsLocal -eq $True} $ArrayName = Get-ArrayName -FlashArray $FlashArray if ($null -ne $iscsiadapter) { $iqn = $iscsiadapter.ExtensionData.IScsiName foreach ($fahost in $fahosts) { if ($fahost.iqns.count -ge 1) { foreach ($fahostiqn in $fahost.iqns) { if ($iqn.ToLower() -eq $fahostiqn.ToLower()) { $faHostMatch = $fahost break } } } } } if ($null -ne $faHostMatch) { return $faHostMatch } else { throw "No matching host for $($esxi.Name) could be found on the Pure Cloud Block Store $ArrayName" } } function Get-PfaHostGroupfromVcCluster { <# .SYNOPSIS Retrieves a FA host group from an ESXi cluster .DESCRIPTION Takes in a vCenter Cluster and retrieves corresonding host group #> Param( [Parameter(Mandatory=$true,ValueFromPipeline=$True)] [VMware.VimAutomation.ViCore.Types.V1.Inventory.Cluster]$Cluster, [Parameter(Mandatory=$True)] $Flasharray ) $esxiHosts = $cluster |Get-VMHost $faHostGroups = @() $faHostGroupNames = @() $ArrayName = Get-ArrayName -FlashArray $FlashArray foreach ($esxiHost in $esxiHosts) { try { $faHost = $esxiHost | Get-PfaHostFromVmHost -flasharray $flasharray if ($null -ne $faHost.HostGroup.Name) { if ($faHostGroupNames.contains($faHost.HostGroup.Name)) { continue } else { $faHostGroupNames += $faHost.HostGroup.Name $faHostGroup = Get-Pfa2HostGroup -Array $Flasharray -Name $($faHost.HostGroup.Name) -ErrorAction stop | Where-Object {$_.IsLocal -eq $True} $faHostGroups += $faHostGroup } } } catch{ continue } } if ($null -eq $faHostGroup) { throw "No host group found for cluster $($Cluster.Name) on $ArrayName." } if ($faHostGroups.count -gt 1) { Write-Warning -Message "Cluster $($Cluster.Name) spans more than one host group. The recommendation is to have only one host group per cluster" } return $faHostGroups } function Get-ArrayName { Param( [Parameter(Mandatory=$true)] $FlashArray ) $ArrayName = (Get-Pfa2Array -Array $Flasharray).Name return $ArrayName } function Get-AzureAuthHeader { param ( $AzContext ) $AzProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile $ProfileClient = New-Object -TypeName Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient -ArgumentList ($AzProfile) $Token = $profileClient.AcquireAccessToken($azContext.Subscription.TenantId) if (-not $Token) { throw "Failed to acquire Azure access token for tenant $($azContext.Subscription.TenantId)" } $AuthHeader = @{ 'Content-Type'='application/json' 'Authorization'='Bearer ' + $token.AccessToken } return $AuthHeader } function Get-AVSvCenterEndpoint { param ( [Hashtable] $AuthHeader, [String] $SubscriptionId, [String] $AvsResourceGroupName, [String] $AvsPrivateCloudName ) $RestUri = "https://management.azure.com/subscriptions/$($subscriptionId)/resourceGroups/$($avsResourceGroupName)/providers/Microsoft.AVS/privateClouds/$($avsPrivateCloudName)?api-version=2022-05-01" $Response = Invoke-RestMethod -Uri $restUri -Method Get -Headers $authHeader if (-not $Response) { throw "Failed to get AVS vCenter endpoint. Please make sure you have the right permission to access the AVS instance with subscriptionId $subscriptionId, resource group $avsResourceGroupName and private cloud $avsPrivateCloudName" } $VcsaEndpoint = $response.properties.endpoints.vcsa $VcsaIPAddress = [System.Uri]::new($vcsaEndpoint).Host return $vcsaIPAddress } function Get-AVSvCenterCredential { param ( [Hashtable] $AuthHeader, [String] $SubscriptionId, [String] $AvsResourceGroupName, [String] $AvsPrivateCloudName ) $RestUri = "https://management.azure.com/subscriptions/$($subscriptionId)/resourceGroups/$($avsResourceGroupName)/providers/Microsoft.AVS/privateClouds/$($avsPrivateCloudName)/listAdminCredentials?api-version=2022-05-01" $Response = Invoke-RestMethod -Uri $restUri -Method Post -Headers $AuthHeader if (-not $Response) { throw "Failed to get AVS vCenter credential. Please make sure you have the right permission to access the AVS instance with subscriptionId $subscriptionId, resource group $avsResourceGroupName and private cloud $avsPrivateCloudName" } return $Response } function Connect-AVSvCenter { param ( [String] $AVSResourceGroupName, [String] $AVSPrivateCloudName ) $AzContext = Get-AzContext $AzureSubscriptionId = $AzContext.Subscription.Id Write-Debug "The default subscriptionId $AzureSubscriptionId of the current Azure context will be used." $AuthHeader = Get-AzureAuthHeader -AzContext $AzContext $AVSvCenterCredential = Get-AVSvCenterCredential -AuthHeader $AuthHeader -SubscriptionId $AzureSubscriptionId -AVSResourceGroupName $AVSResourceGroupName -AVSPrivateCloudName $AVSPrivateCloudName $AVSvCenterEndpoint = Get-AVSvCenterEndpoint -AuthHeader $AuthHeader -SubscriptionId $AzureSubscriptionId -AVSResourceGroupName $AVSResourceGroupName -AVSPrivateCloudName $AVSPrivateCloudName $Credential = New-Object System.Management.Automation.PSCredential -ArgumentList ($AVSvCenterCredential.vCenterUsername, $(ConvertTo-SecureString $AVSvCenterCredential.vCenterPassword -AsPlainText -Force)) # Try to connect to vcenter everytime when a cmdlet is invoked by the user to make sure right avs instance is connected $vCenterServer = Connect-VIserver -server $AVSvCenterEndpoint -Credential $Credential -ErrorAction Stop return $vCenterServer } function Connect-PureCloudBlockStore { Param ( [Parameter(Mandatory=$false)] $PureCloudBlockStoreConnection ) if (-not $PureCloudBlockStoreConnection) { $fa = $global:PURE_AUTH_2_X if (-not $fa) { throw "Please login to Cloud Clock Store using: Connect-Pfa2Array" } } else { $fa = $PureCloudBlockStoreConnection } $ClientName = "PureStorage.CBS.AVS" $module = Get-Module -Name $ClientName if ($module) { $Version = $module.Version.ToString() } else { $Version = "0.0.0.0" } # if was able to connect, also update the user agent to track backup sdk telemetry $success = $fa.UpdateUserAgent($ClientName, $Version ); # this should not be a show stopper if(-not $success){ Write-Warning -Level WARN -FunctionName $FunctionName -Msg "Failed to set useragent" } return $fa } function Purge-AzureSecretWithRetry { Param ( [String] $KeyVaultName, [String] $SecretName ) $errorOccurred = $true $retryCount = 0 # Unfortunately we have to do retry because Remove-AzKeyVaultSecret is asyncronous. If purge happens directly after delete, "Secret is currently being deleted" error is thrown do { try { # To purge, use -InRemovedState parameter $retryCount = $retryCount + 1 Remove-AzkeyVaultSecret -VaultName $KeyVaultName -Name $SecretName -InRemovedState -Force -ErrorAction Stop $errorOccurred = $false } catch { $errorOccurred = $true if ($retryCount -gt 3) { throw } Start-Sleep -Seconds 5 } } while ($errorOccurred) } function Get-AvsClusterInformation { param ( [String] $AvsResourceGroupName, [String] $AvsPrivateCloudName, [String] $AvsClusterName ) $AzContext = Get-AzContext $AzureSubscriptionId = $AzContext.Subscription.Id Write-Debug "The default subscriptionId $AzureSubscriptionId of the current Azure context will be used." $RestUri = "https://management.azure.com/subscriptions/$($AzureSubscriptionId)/resourceGroups/$($avsResourceGroupName)/providers/Microsoft.AVS/privateClouds/$($avsPrivateCloudName)/clusters/$($avsClusterName)?api-version=2022-05-01" $Response = Invoke-AzRest -Uri $restUri -Method Get if (-not $Response -or $Response.StatusCode -ne 200) { throw "Failed to get AVS cluster information. Please make sure cluster $AvsClusterName exists. The cluster name would be case sensitive." } $result = $Response.Content | ConvertFrom-Json return $result } function Get-AvsClusterSku { param ( [String] $AvsResourceGroupName, [String] $AvsPrivateCloudName, [String] $AvsClusterName ) $result = Get-AvsClusterInformation -AvsResourceGroupName $AvsResourceGroupName -AvsPrivateCloudName $AvsPrivateCloudName -AvsClusterName $AvsClusterName return $result.sku.name } function Get-AVSClusterProvisionStatus { param ( [String] $AvsResourceGroupName, [String] $AvsPrivateCloudName, [String] $AvsClusterName ) $result = Get-AvsClusterInformation -AvsResourceGroupName $AvsResourceGroupName -AvsPrivateCloudName $AvsPrivateCloudName -AvsClusterName $AvsClusterName return $result.properties.provisioningState } $MPIO_SKUS = @( "av36", "av36t" "av36p", "av36pt" "av52", "av52t" ) function Test-MPIOProvisionStatus { param ( [Parameter(Mandatory=$true)] [String] $AvsResourceGroupName, [Parameter(Mandatory=$true)] [String] $AvsPrivateCloudName, [Parameter(Mandatory=$true)] [VMware.VimAutomation.ViCore.Types.V1.Inventory.Cluster]$Cluster ) $sku = Get-AvsClusterSku -AvsResourceGroupName $AvsResourceGroupName -AvsPrivateCloudName $AvsPrivateCloudName -AvsClusterName $Cluster.Name if ($sku -in $MPIO_SKUS) { Write-Host "MPIO is supported for cluster $Cluster. Checking vmk interfaces...." $VMHosts = Get-VMHost -Location $Cluster foreach ($VMHost in $VMHosts) { $MpioAdapterOne = Get-VMHostNetworkAdapter -VMHost $VMHost | Where-Object {$_.Name -eq "vmk5"} $MpioAdapterTwo = Get-VMHostNetworkAdapter -VMHost $VMHost | Where-Object {$_.Name -eq "vmk6"} if ($MpioAdapterOne -and $MpioAdapterTwo) { Write-Host "MPIO is ready on $VMHost..." } else { throw "The current provisioning status of MPIO is not ready for host $VMHost. Please check if VMkernel adapter 'vmk5' and 'vmk6' are ready for host $VMhost. If not, please wait for twenty minutes and try again." } } } else { Write-Host "MPIO is not available for SKU $sku. Skipping MPIO check for cluster $Cluster..." } } function Get-RunCommandNamedOutput { param ( [String] $RunCmdExecutionName, [String] $AvsResourceGroupName, [String] $AvsPrivateCloudName ) $AzContext = Get-AzContext $AzureSubscriptionId = $AzContext.Subscription.Id Write-Debug "The default subscriptionId $AzureSubscriptionId of the current Azure context will be used." $AuthHeader = Get-AzureAuthHeader -AzContext $AzContext $RestUri = “https://management.azure.com/subscriptions/$($AzureSubscriptionId)/resourceGroups/$($avsResourceGroupName)/providers/Microsoft.AVS/privateClouds/$($avsPrivateCloudName)/scriptExecutions/$($RunCmdExecutionName)?api-version=2021-12-01” $Response = Invoke-RestMethod -Uri $restUri -Method Get -Headers $AuthHeader return $Response.properties.namedOutputs } function Get-Timeout { param ( [Parameter(Mandatory=$true)] [string]$Cluster, [Parameter(Mandatory=$true)] $InputParams ) if ($InputParams.ContainsKey("TimeoutInMinutes")) { return $InputParams["TimeoutInMinutes"] } $HostCount = (Get-VMHost -Location $Cluster).Count $Timeout = [Math]::Ceiling(($HostCount * $DEFAULT_PER_HOST_TIMEOUT_IN_SECONDS)/60) if ($Timeout -lt $MIN_TIMEOUT_IN_MINUTES ) { $Timeout = $MIN_TIMEOUT_IN_MINUTES } elseif ($Timeout -gt $MAX_TIMEOUT_IN_MINUTES){ $Timeout = $MAX_TIMEOUT_IN_MINUTES } return $Timeout } function Test-RunCommandPackageAvailability { param ( [Parameter(Mandatory = $true)] [String] $SubscriptionId, [Parameter(Mandatory = $true)] [String] $RunCommandModule, [Parameter(Mandatory = $true)] [String] $RunCommandPackageVersion, [Parameter(Mandatory = $false)] [String] $AVSCloudName, [Parameter(Mandatory = $false)] [String] $AVSResourceGroup ) $Uri = "https://management.azure.com/subscriptions/$($SubscriptionId)/resourceGroups/$($AVSResourceGroup)/providers/Microsoft.AVS/privateClouds/$($AVSCloudName)/scriptPackages/$($RunCommandModule)@$($RunCommandPackageVersion)?api-version=2023-03-01" $Res = Invoke-AzRest -Uri $Uri -Method GET if ($Res.StatusCode -ne 200) { throw "Could not find '$($RunCommandModule)@$($RunCommandPackageVersion)' RunCommand package in Azure. Please make sure the package is available in Azure." } else { Write-Host "Found '$($RunCommandModule)@$($RunCommandPackageVersion)' RunCommand package in Azure..." } } |