RiverMeadow.Release.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-RMVMSourceInternal { param ( [string] $OrganizationId, [string] $VMFolderPath, [guid] $ApplianceId, [string] $VMName, [string] $CollectionType, [int] $PageNumber ) $RMLoginResult = Get-Variable -Name "RMContext-UserLogin" $Uri = Get-Variable -Name "RMContext-ReactorURI" -ValueOnly $Headers = @{ Accept = "application/rm+json" "X-Auth-Token" = $RMLoginResult.Value.token } $VMName = [System.Web.HttpUtility]::UrlEncode($VMName) if (![string]::IsNullOrWhiteSpace($VMFolderPath)) { $Uri = $Uri + "/organizations/" + $OrganizationId + "/sources?size=25&page=" + $PageNumber + "&sort=created_at%2Cdesc" + "&vm_folder_path=" + $VMFolderPath ` + "&appliance_id=" + $ApplianceId + "&source_ip=" + $VMName + "&collection_type=" + $CollectionType } else { $Uri = $Uri + "/organizations/" + $OrganizationId + "/sources?size=25&page=" + $PageNumber + "&sort=created_at%2Cdesc" + "&appliance_id=" + $ApplianceId ` + "&source_ip=" + $VMName + "&collection_type=" + $CollectionType } $Params = @{ Method = "Get" Uri = $Uri Headers = $Headers } return Invoke-RMRestMethod -Params $Params } 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-RMSourceByVMName { param ( [string] $VMName, [string] $SourceVMFolderPath, [system.Object] $CloudAccount, [bool] $IsInteractive ) $CurrentProjectId = Get-Variable -Name "RMContext-CurrentProjectId" -ValueOnly $PageIndex = 0 while ($true) { $Response = Get-RMVMSourceInternal -OrganizationId $CurrentProjectId -PageNumber $PageIndex -VMFolderPath $SourceVMFolderPath ` -ApplianceId $CloudAccount.appliance.id -VMName $VMName -CollectionType "VM" $Sources = $Response.Content if ($IsInteractive) { if ($Sources.Count -gt 1) { if ([string]::IsNullOrWhitespace($SourceVMFolderPath)) { Write-RMError -Message "More than one VM was found with the VM name '$VMName', please provide the source VM folder path." $SourceVMFolderPath = Read-RMString -UserMessage "Enter the source VM folder path" -ParameterName 'Source VM Folder Path' ` -IsRequired $false -DefaultValue 'None' $PageIndex = 0 continue } } } else { if ($Sources.Count -gt 1) { if ([string]::IsNullOrWhitespace($SourceVMFolderPath)) { Throw [System.Data.DuplicateNameException]::new(` "More than one VM was found with the VM name '$VMName', please provide the parameter 'SourceVMFolderPath'.") } } } $PageIndex++ if ($Sources.Count -eq 1) { return $Sources[0] } elseif ($Sources.Count -eq 0 || $PageIndex -ge $Response.page.totalPages) { break; } } $TargetCloud = $CloudAccount.name if ([string]::IsNullOrWhitespace($SourceVMFolderPath)) { throw [System.Management.Automation.ItemNotFoundException]::new(` "Could not find the source with the VM name '$VMName' that was discovered using the target cloud '$TargetCloud'.") } else { throw [System.Management.Automation.ItemNotFoundException]::new(` "Could not find the source with the VM name '$VMName' in the VM folder '$SourceVMFolderPath' that was discovered using the target cloud '$TargetCloud'." ) } } 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 [System.InvalidOperationException]::New( "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 [System.Management.Automation.JobFailedException]::New( "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, [System.Object] $CloudAccount, [bool] $IsRequired ) while ($true) { $SourceVMName = Read-RMString -UserMessage $UserMessage -ParameterName $ParameterName -IsRequired $IsRequired -InvalidCharacter "/", " " $SourceVMFolderPath = Read-RMString -UserMessage "Enter the source VM folder path" -ParameterName 'Source VM Folder Path' ` -IsRequired $false -DefaultValue 'None' -InvalidCharacter "/", " " try { $Source = Get-RMSourceByVMName -VMName $SourceVMName -SourceVMFolderPath $SourceVMFolderPath ` -CloudAccount $CloudAccount -IsInteractive $true } catch [System.Management.Automation.ItemNotFoundException] { Write-RMError -Message $PSItem.Exception.Message 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. |