host/AzureRecoveryUtil/TestRunner/TestRunners.ps1 (634 lines of code) (raw):

 # Using this script to verify the pre-recvery steps # ================================================= # 1. Place all the vhds of a vm on a folder ( Download vmdks from esx server and convert them into vhds) # # 2. Create a config file with following information # SUBCRIPTIO_NAME=<Configure a scubscript to powershell and provide the subscription name here> # STORAGE_ACCOUNT=<Storage Account name inthe subscription.> # OS_VHD_NAME=<OS vhd name in vhds folder, ex: win2k12-os-disk.vhd> # VM_NAME=<Name for azure vm > # LOGIN_PWD=<VM login passwrd> # CLOUD_SERVICE_NAME=<cloud service name> # SOURCE_HOST_ID=<Scout agent host-id> # # 3. Provide the confg file path and vhds folder path as input arguemnt to this script. # Ex: .\TestRunners.ps1 "F:\vmdks\venu-w2k12\runner.conf" "F:\vmdks\venu-w2k12" "Windows" # # 4. Pass the OS type information as 3rd argument to the script. Allowed values are "Windows" or "Linux" # # Note-1: This script prompt you two times: # First, it will upload vhds, creadtes disks with it and then generates a recovery configuration # file and prompts you to replace disk name/guids with vhd name in that file. # # Second, it prompts you to verify the pre-recovery execution status. At this stage the custom script # is set for hydration-vm and it is beeing processed. So you can verify logs and pre-recovery steps # completion status and then hit enter to create the final recovered-vm with the disks. # # Note-2: Be careful in providing vm-names & cloud-service names. If they are not complient with their corresponding # naming rules then the operation may fail. # # Note-3: Make sure that the storage-account & container specified are valid and exist. And also makesure that the # container does not have any blobs belogs to previour executions of this script. If exist then delete them. # Its recomended to have a dedicated container for this test, and empty the container once the test is completed # so that it does not conflict with next test runs. # $global:subName = "Visual Studio Ultimate with MSDN" $global:storageAccount = "" $global:containerName = "uploads" $global:blob_base_uri = "" $global:osVHDName = "" $global:vmName = "" $global:cldSericeName = "" $global:winImageFilter = "Windows Server 2012 R2 Datacenter" $global:linuxImageFilter = "OpenLogic 6.6" $global:vmSize = "Medium" $global:vmUser = "venu" $global:vmUserPwd = "" $global:SrcHostId = "" $global:runnerConfigFile = "" $global:configFolder = "" $global:vhdDiskMappingString = "" $global:promptAfterCustomScript = "" $statusBlobName = "recoveryutiltestrunner.status" # # Validate command arguments and update the golbal variables. # if( $args.Count -ne 3 ) { # # Usage : <config-file-path> <vhds-folder> <Windows/Linux> # Write-Error "Argument Error: Invalid arguments" Write-Host "Usage: TestRunners.ps1 <config-file-path> <vhds-folder> <Windows/Linux>" exit 1 } # # Parse the config file and update the configuration parameters # if( Test-Path -Path $args[0] ) { $global:runnerConfigFile = $args[0] $global:configFolder = $(Get-ChildItem $global:runnerConfigFile).DirectoryName $content = Get-Content $args[0] foreach( $line in $content ) { if (($line.Length -ne 0 ) -and !$line.StartsWith("#")) { if($line.StartsWith("SUBCRIPTIO_NAME=")) { $global:subName = $($line.Remove(0,"SUBCRIPTIO_NAME=".Length)) } elseif($line.StartsWith("STORAGE_ACCOUNT=")) { $global:storageAccount = $line.Remove(0,"STORAGE_ACCOUNT=".Length) } elseif($line.StartsWith("OS_VHD_NAME=")) { $global:osVHDName = $line.Remove(0,"OS_VHD_NAME=".Length) } elseif($line.StartsWith("VM_NAME=")) { $global:vmName = $line.Remove(0,"VM_NAME=".Length) } elseif($line.StartsWith("CLOUD_SERVICE_NAME=")) { $global:cldSericeName = $line.Remove(0,"CLOUD_SERVICE_NAME=".Length) } elseif($line.StartsWith("SOURCE_HOST_ID=")) { $global:SrcHostId = $line.Remove(0,"SOURCE_HOST_ID=".Length) } elseif($line.StartsWith("VHD_DISK_ID_MAPPING=")) { $global:vhdDiskMappingString = $line.Remove(0,"VHD_DISK_ID_MAPPING=".Length) } elseif($line.StartsWith("PROMPT_AFTER_SCRIPT=")) { $global:promptAfterCustomScript = $line.Remove(0,"PROMPT_AFTER_SCRIPT=".Length) } elseif($line.StartsWith("LOGIN_PWD=")) { $global:vmUserPwd = $line.Remove(0,"LOGIN_PWD=".Length) } else { Write-Error "Invalid line format in config file: $line" exit 1 } } } } else { Write-Error "Argument error: config file path argument missing or invalid path" exit 1 } if( !$(Test-Path -Path $args[1] -PathType Container) ) { Write-Error "Argument error: invalid vhds folder path $args[1]" exit 1 } $global:vhdsPath = $args[1] $global:osType = $args[2] $global:blob_base_uri = "https://$global:storageAccount.blob.core.windows.net" $global:statusBlobSasUri = "" $global:azureDiskLunMap = [System.Collections.Hashtable]@{} $global:azureDisks = [System.Collections.ArrayList]@() $global:customeScriptFilesList = [System.Collections.ArrayList]@() $global:startupScriptName = "" # Local VHD & disk0id $global:vhdNameDiskIdMap = [System.Collections.Hashtable]@{} $diskEntries = $global:vhdDiskMappingString -split ";" foreach( $diskEntry in $diskEntries ) { $values = $diskEntry -split ":" if($values.Count -eq 2) { $global:vhdNameDiskIdMap.Add($values[0],$values[1]) } else { Write-Warning "Invalid token found in Vhd-Disk-id mapping value: $diskEntry" Write-Warning "Ignoring the token" } } # # Set azure subscription # Select-AzureSubscription $global:subName -Current Set-AzureSubscription -SubscriptionName $global:subName -CurrentStorageAccountName $global:storageAccount # # Functions region # function CleanupAndExit() { } function UploadVhdFilesAndCreateDisks( ) { $container_uri = "$global:blob_base_uri/$global:containerName/" if ( !$(Test-Path -Path "$global:vhdsPath" -PathType Container) ) { Write-Error "Invalid vhds folder path: $global:vhdsPath" exit 1 } $vhdfiles = Get-ChildItem "$global:vhdsPath\*.vhd" foreach($fileObj in $vhdfiles ) { Set-AzureStorageBlobContent -File $fileObj.FullName -BlobType Page -Container $global:containerName -Blob $fileObj.Name -Force >> $null if ( !$? ) { Write-Error "Error uploading vhd to azure" exit 1 } if ( $fileObj.Name -eq $global:osVHDName ) { Add-AzureDisk -DiskName $fileObj.Name -MediaLocation $($container_uri+$fileObj.Name) -Label $fileObj.Name -OS $global:osType >> $null } else { Add-AzureDisk -DiskName $fileObj.Name -MediaLocation $($container_uri+$fileObj.Name) -Label $fileObj.Name >> $null } if ( !$? ) { Write-Error "Error creating disk with the vhd $($fileObj.Name)" exit 1 } $global:azureDisks.Add($fileObj.Name) } } function UploadFile( $localFile ) { Set-AzureStorageBlobContent -File $localFile.FullName -BlobType Block -Container $global:containerName -Blob $localFile.Name -Force >> $null if ( !$? ) { Write-Error "Error uploading file to azure : $localFile.Name" exit 1 } } function UploadCustomScriptFiles( $confFilesFolder ) { if( $global:osType -eq "Windows") { $global:startupScriptName = "StartupScript.ps1" } else { $global:startupScriptName = "StartupScript.sh" } $global:customeScriptFilesList.Add("$global:startupScriptName") #upload StartupScript & RecoveryTools.zip files as well $confFilesList = Get-ChildItem "$confFilesFolder\*-$global:SrcHostId.*" if ( !$? ) { Write-Error "Config files not found" exit 1 } foreach ( $confFile in $confFilesList ) { UploadFile $confFile $global:customeScriptFilesList.Add("$($confFile.Name)") } #upload RecoveryTools.zip file $zipfileObj = Get-ChildItem "$confFilesFolder\AzureRecoveryTools.zip" if ( !$? ) { Write-Error "Recovery tools zip file is not found" exit 1 } UploadFile $zipfileObj $global:customeScriptFilesList.Add("$($zipfileObj.Name)") #upload StartupScript file $startupScriptFileObj = $(Get-ChildItem "$confFilesFolder\StartupScript.*" | select -First 1) if ( !$? ) { Write-Error "Startup script file is not found" exit 1 } UploadFile $startupScriptFileObj $global:customeScriptFilesList.Add("$($startupScriptFileObj.Name)") } function CreateStatusBlobSasTocken( ) { $blobmetadata = @{} #create a dummy file with 0 size $tmpFile = "$global:vhdsPath\tmpstatusfile.status" New-Item $tmpFile -ItemType file -Force if ( !$? ) { Write-Error "Error creating status file" exit 1 } Set-AzureStorageBlobContent -Blob $statusBlobName -Container $global:containerName -Metadata $blobmetadata -BlobType Page -File $tmpFile -Force >> $null if ( !$? ) { Write-Error "Error creating status file" exit 1 } #remove temp status file Remove-Item $tmpFile -Force $statusBlobObjUri = "$global:blob_base_uri/$global:containerName/$statusBlobName" $statusBlobSasToken = (Get-AzureStorageBlob -Blob $statusBlobName -Container $global:containerName | New-AzureStorageBlobSASToken -Permission rw) $global:statusBlobSasUri = "$statusBlobObjUri$statusBlobSasToken" } function CreateRecoveryConfigFile() { $recoveryConfFile = "$global:configFolder\azurerecovery-$global:SrcHostId.conf" # Generate the status blob sas uri CreateStatusBlobSasTocken "PreRecoveryExecutionBlobSasUri = $global:statusBlobSasUri" | Out-File -Encoding ascii -Force -Width 2048 -FilePath "$recoveryConfFile" "LogLevel = 4"| Out-File -Encoding ascii -Append -Force -Width 2048 -FilePath "$recoveryConfFile" # Add any other vmfig params here "[DiskMap]" | Out-File -Encoding ascii -Append -Force -Width 2048 -FilePath "$recoveryConfFile" foreach ( $disk in $global:azureDiskLunMap.GetEnumerator() ) { if( $global:vhdNameDiskIdMap.ContainsKey($disk.Key) ) { "$($global:vhdNameDiskIdMap.Item($disk.Key)) = $($disk.Value)" | Out-File -Encoding ascii -Append -Force -Width 2048 -FilePath "$recoveryConfFile" } else { Write-Warning "Key $($disk.Key) in vhd-name to disk-id map not found" } } Write-Host "Successfully created the recovery config file" } function CreateHydrationVM( ) { $imageLable = "" if( $osType -eq "Windows") { $imageLable = $global:winImageFilter } else { $imageLable = $global:linuxImageFilter } $image = Get-AzureVMImage | where { $_.Label -like "$imageLable*" } | sort PublishedDate -Descending | select -ExpandProperty ImageName -First 1 Write-Host "Hydration-VM Image: $image" # create vm configuration $($vm = New-AzureVMConfig -Name $vmName -InstanceSize $vmSize -ImageName $image) >> $null if( $osType -eq "Windows") { # add the provisioning configuration to vm config $($vm | Add-AzureProvisioningConfig -Windows -AdminUsername $global:vmUser -Password $global:vmUserPwd) >> $null # add the endpoints configuration to vm config #$vm | Add-AzureEndpoint -Name "Remote Desktop" -LocalPort 3389 -Protocol tcp -PublicPort 3389 } else { # add the provisioning configuration to vm config $($vm | Add-AzureProvisioningConfig -Linux -LinuxUser $global:vmUser -Password $global:vmUserPwd) >> $null # add the endpoints configuration to vm config # $vm | Add-AzureEndpoint -Name "SSH" -LocalPort 22 -Protocol tcp -PublicPort 22 } # Add data disks configuration to vm config $diskLunPos = 0 foreach ( $diskName in $global:azureDisks ) { $($vm | Add-AzureDataDisk -Import -DiskName "$diskName" -LUN $diskLunPos) >> $null $global:azureDiskLunMap.Add($diskName , $diskLunPos) $diskLunPos++ } $cldSvr = Get-AzureService -ServiceName $global:cldSericeName if( !$? ) { Write-Host "Creating cloud service: $global:cldSericeName" New-AzureService -ServiceName $global:cldSericeName -Location $(Get-AzureStorageAccount -StorageAccountName $global:storageAccount).Location if( !$? ) { Write-Error "Error creating cloud service for hydration-vm" exit 1 } } New-AzureVM -ServiceName $global:cldSericeName -VMs $vm -WaitForBoot if( !$? ) { Write-Error "Error creating hydration-vm" exit 1 } Write-Host "Successfully created the Hydration-VM and attached all the disks" } function SetCustomeScriptExtentionForWindows( ) { # Custom script for Windows Azure VM # Ref : http://azure.microsoft.com/blog/2014/04/24/automating-vm-customization-tasks-using-custom-script-extension/ # $vm = Get-AzureVM -ServiceName $global:cldSericeName -Name $global:vmName if( !$? ) { Write-Error "Error retreiving vm object for custome script extention" exit 1 } Set-AzureVMCustomScriptExtension -VM $vm -ContainerName $global:containerName -FileName $global:customeScriptFilesList -Run 'StartupScript.ps1' -Argument $global:SrcHostId | Update-AzureVM >> $null if( !$? ) { Write-Error "Error updating custome script extention" exit 1 } $extNotFound = $true do { $vm = Get-AzureVM -ServiceName $global:cldSericeName -Name $global:vmName if( !$? ) { Write-Error "Error retreiving vm object after custome script extension" exit 1 } $extIndex = 0 for( ; $extIndex -lt $vm.ResourceExtensionStatusList.Count; $extIndex++ ) { if( $vm.ResourceExtensionStatusList[$extIndex].HandlerName -ilike "Microsoft.Compute.CustomScriptExtension" ) { if( $vm.ResourceExtensionStatusList[$extIndex].ExtensionSettingStatus.Status -ilike "Success") { Write-Host "Custome script succeeded" $extNotFound = $false } elseif( $vm.ResourceExtensionStatusList[$extIndex].ExtensionSettingStatus.Status -ilike "Failed" ) { Write-Error "Custome script Failed" $vm.ResourceExtensionStatusList[$extIndex].ExtensionSettingStatus.SubStatusList exit 1 #$extNotFound = $false } else { Write-Host "Custom script current status: $($vm.ResourceExtensionStatusList[$extIndex].ExtensionSettingStatus.Status)" } break } } } while ($extNotFound) } function SetCustomeScriptExtentionForLinux( ) { # Custom script for Azure Linux VM # Ref: http://azure.microsoft.com/blog/2014/08/20/automate-linux-vm-customization-tasks-using-customscript-extension/ # $vm = Get-AzureVM -ServiceName $global:cldSericeName -Name $global:vmName if( !$? ) { Write-Error "Error retreiving vm object for custome script extention" exit 1 } $ExtensionName = 'CustomScriptForLinux' $Publisher = 'Microsoft.OSTCExtensions' $Version = '1.*' $containeruri = "$global:blob_base_uri/$global:containerName" $filesUris = "`"$containeruri/StartupScript.sh`",`"$containeruri/hostinfo-$global:SrcHostId.xml`",`"$containeruri/azurerecovery-$global:SrcHostId.conf`",`"$containeruri/AzureRecoveryTools.zip`"" $PublicConfiguration = "{`"fileUris`":[$filesUris], `"commandToExecute`": `"sh StartupScript.sh $global:SrcHostId`" }" $PrivateConfiguration = "{`"storageAccountName`": `"$global:storageAccount`",`"storageAccountKey`":`"$($(Get-AzureStorageKey –StorageAccountName $global:storageAccount).Primary)`"}" Set-AzureVMExtension -ExtensionName $ExtensionName -VM $vm -Publisher $Publisher -Version $Version -PrivateConfiguration $PrivateConfiguration -PublicConfiguration $PublicConfiguration | Update-AzureVM >> $null if( !$? ) { Write-Error "Error updating custome script extention" exit 1 } $extNotFound = $true do { $vm = Get-AzureVM -ServiceName $global:cldSericeName -Name $global:vmName if( !$? ) { Write-Error "Error retreiving vm object after custome script extension" exit 1 } $extIndex = 0 for( ; $extIndex -lt $vm.ResourceExtensionStatusList.Count; $extIndex++ ) { if( $vm.ResourceExtensionStatusList[$extIndex].HandlerName -ilike "Microsoft.OSTCExtensions.CustomScriptForLinux" ) { if( $vm.ResourceExtensionStatusList[$extIndex].ExtensionSettingStatus.Status -ilike "Success") { Write-Host "Custome script succeeded" $extNotFound = $false } elseif( $vm.ResourceExtensionStatusList[$extIndex].ExtensionSettingStatus.Status -ilike "Failed" ) { Write-Error "Custome script Failed" $vm.ResourceExtensionStatusList[$extIndex].ExtensionSettingStatus.SubStatusList exit 1 #$extNotFound = $false } else { Write-Host "Custom script current status: $($vm.ResourceExtensionStatusList[$extIndex].ExtensionSettingStatus.Status)" } break } } } while ($extNotFound) } function AwaitScriptCompletion() { Write-Host "Waiting for the execution status ..." $Status = "Pending" do { $metadata = $(Get-AzureStorageBlob -Blob $statusBlobName -Container $global:containerName).ICloudBlob.Metadata if($metadata.ContainsKey("ExecutionStatus")) { $Status = $metadata["ExecutionStatus"] } if( $Status -eq "Success" ) { break } elseif( $Status -eq "Failed" ) { Write-Error "Pre-Recovery steps execution failed. " foreach( $metadataItem in $metadata) { Write-Host "$($metadataItem.Key) : $($metadataItem.Value)" } exit 1 } else { Write-Host "$Status..." Start-Sleep -Seconds 30 } } while ( $true ) Write-Host "Execution status: $Status" if( $global:promptAfterCustomScript -icontains "yes" ) { Read-Host "Custome script has comleted the execution. Hit Enter to proceed :" } } function SetCustomeScriptExtention () { CreateRecoveryConfigFile UploadCustomScriptFiles "$global:configFolder" if( $osType -eq "Windows") { SetCustomeScriptExtentionForWindows } else { SetCustomeScriptExtentionForLinux } } function RemoveHydrationVM ( ) { Write-Host "Deleting the hydration-vm and its associated VHDs..." Remove-AzureVM -Name $global:vmName -ServiceName $global:cldSericeName -DeleteVHD >> $null if( !$? ) { Write-Error "Error deleting hydration-vm" exit 1 } Write-Host "Deleted the hydration-vm" } function DettachDataDisksFromHydrationVM ( ) { Write-Host "Dettaching disks from Hydration-VM..." $dataDisks = Get-AzureDataDisk -VM $(Get-AzureVM -Name $global:vmName -ServiceName $global:cldSericeName) # Assuming no other data disks attached to Hydration-VM for( [System.Int32]$diskIndex = 0; $diskIndex -lt $dataDisks.Count; $diskIndex++) { Get-AzureVM -ServiceName $global:cldSericeName -Name $global:vmName | Remove-AzureDataDisk -LUN $diskIndex | Update-AzureVM } # Verify that the disks are dittached completely from hydration-vm foreach( $disk in $global:azureDisks ) { Write-Host "Checking the disk $disk dettach status..." while( ![string]::IsNullOrEmpty($(Get-AzureDisk -DiskName $disk).AttachedTo) ) { Write-Host "Disk $disk is still attached to the VM. Going to wait for 60sec..." Start-Sleep -Seconds 60 } Write-Host "Disk $disk is dettached from the VM." } Write-Host "All the data-disks are dettached from Hydration-VM..." } function CreateRecoveredVM( ) { # Creating azure vm using powershell commands # Ref: https://azure.microsoft.com/en-in/documentation/articles/virtual-machines-ps-create-preconfigure-windows-vms/ # Write-Host "Creating the recovered VM ..." # create vm configuration $rec_vm = New-AzureVMConfig -Name $vmName -InstanceSize $vmSize -DiskName $global:osVHDName -HostCaching ReadWrite if( $osType -eq "Windows") { # add the endpoints configuration to vm config $rec_vm | Add-AzureEndpoint -Name "Remote Desktop" -LocalPort 3389 -Protocol tcp -PublicPort 3389 } else { # add the endpoints configuration to vm config $rec_vm | Add-AzureEndpoint -Name "SSH" -LocalPort 22 -Protocol tcp -PublicPort 22 } # Add data disks configuration to vm config $diskLunPos = 0 foreach ( $diskName in $azureDisks ) { if( $diskName -eq $global:osVHDName) { #skip os disk continue } $rec_vm | Add-AzureDataDisk -Import -DiskName "$diskName" -LUN $diskLunPos $diskLunPos++ } New-AzureVM -ServiceName $global:cldSericeName -VMs $rec_vm -WaitForBoot >> $null if( !$? ) { Write-Error "Error creating recovered vm" exit 1 } Write-Host "Successfully created the recovered VM" } function VerifyRecoveredVM() { # TODO: Verify recovery configuration Read-Host "Hit Enter after verifying the recovered vm: " } function Cleanup() { # # Delete the recovered vm and its associated vhds # Write-Host "Deleting the recovered-vm and its associated VHDs..." Remove-AzureVM -Name $global:vmName -ServiceName $global:cldSericeName -DeleteVHD >> $null if( !$? ) { Write-Error "Error deleting recovered-vm" exit 1 } Write-Host "Deleted the recovered-vm" # # Delete the custome script files uploaded to storage account. # $files = [System.Collections.ArrayList]@("hostinfo-$global:SrcHostId.xml","azurerecovery-$global:SrcHostId.conf","AzureRecoveryTools.zip","$statusBlobName") if ( $global:osType -match "Windows" ) { $files.Add("StartupScript.ps1") } else { $files.Add("StartupScript.sh") } foreach ( $blob in $files ) { Remove-AzureStorageBlob -Blob $blob -Container $global:containerName -Force >> $null } } # # Workflow # function Main ( ) { UploadVhdFilesAndCreateDisks CreateHydrationVM SetCustomeScriptExtention AwaitScriptCompletion DettachDataDisksFromHydrationVM RemoveHydrationVM CreateRecoveredVM VerifyRecoveredVM Cleanup } ### Main ### Main