RiverMeadow.Source/SourceUtil/SourceUtil.psm1
using module '../../Common/Result' Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath Util | Join-Path -ChildPath Util) Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath Preflight | Join-Path -ChildPath Preflight) function Get-RMSourcesForCurrentProject { param() $LoginStatus = Test-UserLoggedIn if ($LoginStatus.ReturnCode -eq [RMReturn]::ERROR) { return $LoginStatus } $CurrentProjectId = Get-Variable -Name "RMContext-CurrentProjectId" -ValueOnly $Result = @() $Response = Get-RMSourcesInternal -OrganizationId $CurrentProjectId -PageNumber 0 $Result += $Response for ($index = 1; $index -lt $Response.page.totalPages; $index++) { $Result += Get-RMSourcesInternal -OrganizationId $CurrentProjectId -PageNumber $index } return $Result } function Get-RMSourcesInternal { param( [string] $OrganizationId, [int] $PageNumber ) $RMLoginResult = Get-Variable -Name "RMContext-UserLogin" $Uri = Get-Variable -Name "RMContext-ReactorURI" $Headers = @{ Accept = "application/rm+json" "X-Auth-Token" = $RMLoginResult.Value.token } $Params = @{ Method = "Get" Uri = $Uri.Value + "/organizations/" + $OrganizationId + "/sources?size=25&page=" + $PageNumber + "&sort=created_at%2Cdesc" Headers = $Headers } return Invoke-RMRestMethod -Params $Params } function Get-RMSourceById { param( [string] $SourceId ) $RMLoginResult = Get-Variable -Name "RMContext-UserLogin" $Uri = Get-Variable -Name "RMContext-ReactorURI" $Headers = @{ Accept = "application/rm+json" "X-Auth-Token" = $RMLoginResult.Value.token } $Params = @{ Method = "Get" Uri = $Uri.Value + "/sources/" + $SourceId Headers = $Headers } return Invoke-RMRestMethod -Params $Params } function Get-MountPoint { param( [System.Object] $Source ) $MountPoints = @() if ("windows" -ieq $Source.os_type) { foreach ($Mount in $Source.attributes.storage.mounts.psobject.properties.value) { $MountPoints += @{mount_point = $Mount.path} } } else { foreach ($Mount in $Source.attributes.storage.mounts.psobject.properties.value) { if ("disk" -ieq $Mount.nature -or "subvolume" -ieq $Mount.nature -and "squashfs" -ine $Mount.fs_type) { $MountPoints += @{mount_point = $Mount.path} } } } return $MountPoints } function Get-RMSourceByIP { param( [string] $IPAddress ) $CurrentProjectId = Get-Variable -Name "RMContext-CurrentProjectId" -ValueOnly $PageIndex = 0 do { $Sources = Get-RMSourcesInternal -OrganizationId $CurrentProjectId -PageNumber $PageIndex foreach ($Source in $Sources.content) { if ($IPAddress -eq $Source.host) { return $Source } } $PageIndex++ } while ($PageIndex -lt $Sources.page.totalPages) $ProjectName = Get-Variable -Name "RMContext-CurrentProjectName" -ValueOnly $OrgName = Get-Variable -Name "RMContext-CurrentOrganizationName" -ValueOnly throw [System.Management.Automation.ItemNotFoundException]::new(` "Source with IP address/VM name '$IPAddress' does not exist in project '$ProjectName' of organization '$OrgName', cannot start the migration. Please add the source in the project and try again.") } function Start-RMSourcePreflight { param ( [System.Object] $Source, [System.Object] $CloudAccount, [string] $AccountType ) $RequestAttributes = @{ "type" = "source" "resource_id"= $Source.id "overrides"= @{} } $RequestTargetCloudAttributes = @{ "type" = "target_cloud" "resource_id" = $CloudAccount.id "migrate_netapp_files" = $false "cloud_type" = $AccountType } $SourceAttributesRequest = @($RequestAttributes, $RequestTargetCloudAttributes) $SourceAttributesRequestJson = ConvertTo-Json $SourceAttributesRequest $RMLoginResult = Get-Variable -Name "RMContext-UserLogin" -ValueOnly $Uri = Get-Variable -Name "RMContext-ReactorURI" -ValueOnly $Headers = @{ Accept = "application/rm+json" "X-Auth-Token" = $RMLoginResult.token } $Params = @{ Method = "Post" Uri = $Uri + "/preflights" Body = $SourceAttributesRequestJson ContentType = "application/json" Headers = $Headers } Write-Output "Starting source preflight..." | Out-Host return Invoke-RMRestMethod -Params $Params } function Get-RMSourceWithAttribute { param( [System.Object] $Source, [System.Object] $CloudAccount, [string] $AccountType, [bool] $IgnoreValidationErrors, [RMMigrationReturn] $RMMigrationReturn ) if ($Source.attribute_state -eq "running") { throw "Source attribute collection state is 'running', please wait for the attribute collection to complete." } Update-RMSourceWithAppliance -SourceId $Source.id -ApplianceId $CloudAccount.appliance.id $Response = Start-RMSourcePreflight -Source $Source -CloudAccount $CloudAccount -AccountType $AccountType Write-Output "Waiting for source preflight to complete..." | Out-Host $PreflightResult = Watch-RMPreflightStatus -PreflightId $Response.preflights[0].id ` -TimeOutMessage "Timed out waiting for collection to complete" $Source = Get-RMSourceById -SourceId $Source.id if (![string]::IsNullOrEmpty($Source.attribute_error)) { $PreflightID = $Response.preflights[0].id $AttributeCollectionError = $Source.attribute_error throw "Source attribute collection with ID: $PreflightID has failed with error $AttributeCollectionError" } $ShouldExit, $PreflightWarning, $PreflightError = Out-RMPreflight -PreflightResult $PreflightResult -IgnoreValidationErrors $IgnoreValidationErrors -RMMigrationReturn $RMMigrationReturn $OverrideExistingMigrationWarning = $false $OverrideExistingMigrationError = $false if ($PreflightWarning.keys -contains "Conflicting Migration Attempts") { $OverrideExistingMigrationWarning = $true } if ($PreflightError.keys -contains "Conflicting Migration Attempts") { $OverrideExistingMigrationError = $true if (1 -eq $PreflightError.Count) { $ShouldExit = $false } } return $Source, $ShouldExit, $OverrideExistingMigrationWarning, $OverrideExistingMigrationError } function Update-RMSourceWithAppliance { param ( [System.Object] $SourceId, [string] $ApplianceId ) $RequestAttributes = @{ "data_only_migration" = $false "appliance_id" = $ApplianceId } $RequestAttributesJson = ConvertTo-Json $RequestAttributes $RMLoginResult = Get-Variable -Name "RMContext-UserLogin" -ValueOnly $Uri = Get-Variable -Name "RMContext-ReactorURI" -ValueOnly $Headers = @{ Accept = "application/rm+json" "X-Auth-Token" = $RMLoginResult.token } $Params = @{ Method = "Put" Uri = $Uri + "/sources/" + $SourceId Body = $RequestAttributesJson ContentType = "application/json" Headers = $Headers } Invoke-RMRestMethod -Params $Params | Out-Null } function Read-RMSource { param( [string] $UserMessage, [string] $ParameterName, [bool] $IsRequired ) while ($true) { $SourceIP = Read-RMIPAddress -UserMessage $UserMessage -ParameterName $ParameterName -IsRequired $IsRequired try { $Source = Get-RMSourceByIP -IPAddress $SourceIP } catch [System.Management.Automation.ItemNotFoundException] { $ProjectName = Get-Variable -Name "RMContext-CurrentProjectName" -ValueOnly $OrgName = Get-Variable -Name "RMContext-CurrentOrganizationName" -ValueOnly Write-RMError -Message "Source with IP address '$SourceIP' does not exist in project '$ProjectName' of organization '$OrgName'." continue } return $Source } } function Read-RMVMBasedSource { param( [string] $UserMessage, [string] $ParameterName, [bool] $IsRequired ) while ($true) { $SourceVMName = Read-RMString -UserMessage $UserMessage -ParameterName $ParameterName -IsRequired $IsRequired try { $Source = Get-RMSourceByIP -IPAddress $SourceVMName if ($Source.collection_type -ine "VM") { Write-RMError -Message "Source '$SourceVMName' is not a VM-Based source, to migrate this source, please use the cmdlet 'Start-RMVSphereOSBasedMigration'." continue } } catch [System.Management.Automation.ItemNotFoundException] { $ProjectName = Get-Variable -Name "RMContext-CurrentProjectName" -ValueOnly $OrgName = Get-Variable -Name "RMContext-CurrentOrganizationName" -ValueOnly Write-RMError -Message "Source with VM name '$SourceVMName' does not exist in project '$ProjectName' of organization '$OrgName'." continue } return $Source } } function Get-RMVMBasedSelectedDisk { param( [System.Object] $Source, [string[]] $ExcludedDiskLabel ) $MountPoints = @() for ($Index = 0; $Index -lt $Source.attributes.storage.vm_disks.Count; $Index++) { if ($ExcludedDiskLabel -contains $Source.attributes.storage.vm_disks[$Index].label) { continue } $MountPoints += @{mount_point = $Index.ToString()} } return $MountPoints } function Get-RMSelectedDiskByDiskLabel { param( [string[]] $DiskLabel, [System.Object] $Source ) $MountPoints = @() for ($Index = 0; $Index -lt $Source.attributes.storage.vm_disks.Count; $Index++) { if ($DiskLabel -contains $Source.attributes.storage.vm_disks[$Index].label) { $MountPoints += @{mount_point = $Index.ToString()} } } return $MountPoints } function Get-RMVMDiskProperty { param ( [System.Object] $Source ) $Result = @() foreach ($VMDisk in $Source.attributes.storage.vm_disks) { $Size = [math]::round($VMDisk.size_kb/(1024 * 1024), 2) $Result += $VMDisk.label + " (Size: $Size GiB)" } return $Result } function Get-RMSelectedDisk { param( [System.Object] $Source ) $DiskLabels = @() $Source.attributes.storage.vm_disks | ForEach-Object -Process { $DiskLabels += $_.label } while ($true) { $ReadTokens = Read-RMToken -UserMessage "Enter labels of the disks to be excluded, separated by commas" ` -DefaultValue "None" -ParameterName "Disk label(s)" -Separator "," -IsRequired $false # Read-RMToken returns empty string when default None is used by the user, not ideal, should return empty array if ([string]::IsNullOrWhiteSpace($ReadTokens)) { return Get-RMVMBasedSelectedDisk -Source $Source -ExcludedDiskLabel $DisksToExclude } $DisksToExclude = @() foreach ($Token in $ReadTokens) { $Token = $Token.Trim('"') $Token = $Token.Trim("'") $DisksToExclude += $Token } $Result = Test-RMNonExistentMountPoints -SourceMountPoints $DiskLabels -UserInputMountPoints $DisksToExclude if ($Result.Count -gt 0) { $ResultAsString = $Result -join ", " Write-RMError -Message "Disk labels '$ResultAsString' does not exist on source and hence cannot be excluded, please try again." continue } if ($DisksToExclude.Count -eq $Source.attributes.storage.vm_disks.Count) { Write-RMError -Message "Cannot exclude all the disks, please try again." continue } return Get-RMVMBasedSelectedDisk -Source $Source -ExcludedDiskLabel $DisksToExclude } } function Test-RMSourceHasDynamicDisks { param( [System.Object] $Source ) if ($Source.os_type -ine "windows") { return $false } foreach ($Disk in $Source.attributes.storage.disks.psobject.Properties.Value) { if ($null -ne $Disk.flags -and $Disk.flags -contains "dynamic_disk") { return $true } } return $false } function Confirm-RMSource { param ( [string] $SourceId ) $ErrorString = "" $Source = Get-RMSourceById -SourceId $SourceId $ProjectName = Get-Variable -Name "RMContext-CurrentProjectName" -ValueOnly $OrgName = Get-Variable -Name "RMContext-CurrentOrganizationName" -ValueOnly if ($null -eq $Source -or $Source.is_deleted) { if ($null -ne $Source) { $IPAddress = $Source.host $ErrorString = "Source with IP address/VM name '$IPAddress' does not exist in project '$ProjectName' of organization '$OrgName', cannot start the differential migration." } else { $ErrorString = "Source with ID '$SourceId' does not exist in project '$ProjectName' of organization '$OrgName', cannot start the differential migration." } } else { $ProjectId = Get-Variable -Name "RMContext-CurrentProjectId" -ValueOnly $IPAddress = $Source.host if ($Source.organization_id -ne $ProjectId) { $ErrorString = "Source with IP address/VM name '$IPAddress' does not exist in project '$ProjectName' of organization '$OrgName', if the source exists in a different project then switch to that project using the cmdlet 'Switch-RMProject' and then try again." } } return $Source, $ErrorString } # No Export-ModuleMember is being used which will automatically export all the functions # of this module and we want all the functions to be exported. |