Exportar e Importar solamente la configuración de una máquina virtual

Hola

El proceso de exportación de una máquina virtual consiste en el “empaquetado” de todos los ficheros que conforman una máquina virtual y su copia a un cierto path dentro del host en el que se lleva a cabo la acción. El proceso, que debe llevarse a cabo con la máquina detenida, no la modifica ni la destruye, y tiene por objeto el poder trasladar la máquina, manual o automatizadamente, a otro host para recuperarla posteriormente en caso de, por ejemplo, haber querido prescindir de ella o ante la necesidad de reinstalar un host. Este mecanismo de exportación/importación que podemos llevar a cabo en el Hyper-V Manager está detrás de algunas acciones que están disponibles en System Center Virtual Machine Manager, como llevarnos la máquina virtual a la librería o mover máquinas entre hosts stand-alone (no clusterizados). En ellos, la máquina virtual se exporta, se copia a una carpeta compartida de la librería o al host destino donde se importará, y por último se borra la máquina virtual en el origen.

En la primera versión de Hyper-V existía una casilla para exportar únicamente la configuración de la máquina virtual. La idea era poder importar la VM posteriormente, asumiendo que los discos virtuales, snapshots y ficheros de estado no se han visto alterados en el almacenamiento. Esta es la alternativa purista y soportada a registrar a mano en Hyper-V una máquina virtual a partir de su fichero de configuración, y lo que debería estar detrás de cualquier estrategia de recuperación de desastres que emprendamos en base a replicas del almacenamiento (técnicas de backup/restore aparte, o mejor dicho, además)

En Hyper-V R2 e Hyper-V R2 SP1 esa opción desapareció de la interfaz gráfica, quedándonos como alternativa el hacerlo desde programación o scripting. Aquí tenéis la información más relevante al respecto:

Las APIs

Aunque esto está bastante bien explicado en los enlaces segundo y último de los mencionados arriba, vamos a hacer un pequeño resumen. Los métodos de exportación e importación están definidos en la clase Msvm_VirtualSystemManagementService como ExportVirtualSystemEx e ImportVirtualSystemEx respectivamente. ExportVirtualSystemEx utiliza como parámetro una instancia de la clase Msvm_VirtualSystemExportSettingData para representar a la máquina virtual que se quiere exportar, mientras que ImportVirtualSystemEx utiliza como parámetro una instancia de la clase Msvm_VirtualSystemImportSettingData, que se obtiene mediante una llamada al método GetVirtualSystemImportSettingData. Si miramos la definición de las clases, observaremos que la correspondiente a la importación es más rica en posibilidades de la de exportación:

class Msvm_VirtualSystemExportSettingData : CIM_SettingData
{
string Description;
string Caption;
string InstanceID;
string ElementName;
boolean CopyVmStorage;
boolean CopyVmRuntimeInformation;
boolean CreateVmExportSubdirectory;
uint8 CopySnapshotConfiguration;
string SnapshotVirtualSystem;
};

Es decir, que al exportar podemos decir si queremos copiar los VHDs, si queremos crear una subcarpeta en el path indicado para la exportación con el nombre de la VM, si queremos guardar su estado de ejecución y si queremos almacenar también la información de snapshots. Poniendo estos valores a True o False podemos controlar que parte de la VM queremos llevarnos al directorio de exportación. Exportar solo la configuración de la VM supone, de entrada, poner a CopyVmStorage = False

Sin embargo, lo anterior tiene un efecto colateral. Al generar el .exp, y pese a que almacenamos los paths originales de los VHDs, no los seguimos considerando como válidos, ya que no tenemos la certeza de que al importar sigan en el mismo sitio. Lo mismo sucede con las snapshots y con los las conexiones a los switches virtuales. Manejaremos esa situación en la clase que gobierna el proceso de importación:

class Msvm_VirtualSystemImportSettingData : CIM_SettingData
{
string Description;
string Caption;
string InstanceID;
string ElementName;
boolean GenerateNewId;
boolean CreateCopy;
string Name;
string SourceVmDataRoot;
string SourceSnapshotDataRoot;
string SourceVhdDataRoot;
string TargetVmDataRoot;
string TargetSnapshotDataRoot;
string TargetVhdDataRoot;
string SecurityScope;
string CurrentResourcePaths[];
string SourceResourcePaths[];
string TargetResourcePaths[];
string SourceNetworkConnections[];
string TargetNetworkConnections[];
};

La clave esta en manejar los parámetros CreateCopy, Current*, Source* y Target* en función del significado que tienen cada uno, que podéis encontrar en los enlaces de arriba. Como vemos, algunos de ellos son arrays, donde se almacenan todos los paths de todos los VHDs que puede tener la máquina virtual. en función de la situación a manejar y de nuestras intenciones deberemos de manejarlos de manera acorde. Pero de entrada, pensemos que los paths que dejamos en el .exp al exportar sin copiar los VHDs se encuentran almacenados en SourceResourcePaths

Hasta aquí un resumen de la teoría. Deberíamos de haber dejado claro que exportar es fácil, e importar tiene sus consideraciones, sobre todo si las letras de unidad/paths de los VHDs y los nombres de los switches van a cambiar. Aquí reside la dificultad, pero también abre un buen abanico de posibilidades

Script para Exportar solo la configuración de la máquina virtual en Powershell

Para evitar que mi compañero Ben me acuse de plagio, y porque dado que todo System Center maneja mejor Powershell que vbscript, vamos a ver un ejemplo para exportar solamente la configuración de una máquina virtual en powershell. Simplemente usamos como parámetros el nombre de la máquina virtual y el path donde crearemos una subcarpeta con dicho nombre para almacenar la información de exportación, sin los VHDs. En amarillo lo importante:

# ExportVMConfig.ps1
# Usage: ./ExportVMConfig.ps1 <VMName> <Path to store export dir>
# Example: ./ExportVMConfig.ps1 "Test" "d:\vmexports"

param([parameter(Mandatory=$TRUE,ValueFromPipeline=$TRUE)] $VMName, [parameter(Mandatory=$TRUE,ValueFromPipeline=$TRUE)] $expDir)
 
# Get VM, VMMS  & Export Settings Objects

$ns = 'root\virtualization'
$vmms = gwmi -n $ns Msvm_VirtualSystemManagementService
$vm = gwmi -n $ns Msvm_ComputerSystem | ?{$_.ElementName -eq $VMName}
$exp = @($vm.GetRelated('Msvm_VirtualSystemExportSettingData'))[0]
 
# Don't Export VHDs & saved state data. Create a folder for VM's export data
 
$exp.CopyVmStorage = $false
$exp.CopyVmRuntimeInformation = $false
$exp.CreateVmExportSubdirectory = $true

#Perform Export
 
$out = $vmms.ExportVirtualSystemEx($vm.Path.Path, $expDir, $exp.GetText(1))
 
#Perform Job handling if necessary
 
if ($out.ReturnValue -eq 4096){
    $task = [Wmi]$out.Job;
    while($task.JobState -eq 3 -or $task.JobState -eq 4){
        $task.Get();
        sleep 1;
        }
    if ($task.JobState -ne 7){
        "Error exporting VM " + $task.ErrorDescription;
        }            
    else {
        "Export completed successfully..."
        }
    }
elseif ($out.ReturnValue -ne 0) {
    "Export failed with error : " + $out.ReturnValue;
    }
else {
    "Export completed successfully..."
    }

Si estamos haciendo esto como parte de una estrategia de recuperación ante desastres, ni que decir tiene que lo siguiente es poner ese path de exportación a buen recaudo, tal y como estaremos haciendo con los datastores que almacenan los VHDs. Generalmente esto se hace en base a alguna tecnología de replicación de almacenamiento, porque para copiar los contenidos de la VM a través e la red, lo mejor será usar la vía de la copia de seguridad en base a la tecnología VSS.

Script para Importar solo la configuración de la máquina virtual en Powershell

Para importar la máquina virtual, debemos especificar lógicamente el path donde reside el fichero con la información, es decir. el .exp. Es muy importante darse cuenta de que, si no se usa el parámetro CreateCopy, y en este caso no lo podemos utilizar ya que no tenemos VHDs que copiar, la máquina virtual pasa a residir en ese mismo path, y además el .exp desaparece en favor del .xml resultante. Esto tiene dos efectos. Que no podemos volver a usar la información exportada, y que la máquina virtual se queda en un path que seguramente nosotros considerábamos temporal. La idea es copiar la información de exportación “cerca” de donde residan los VHDs, de manera que todos los componentes de la máquina virtual queden lo mas ordenados posibles

Así es que lo que vamos a usar como parámetros va a ser el nombre de la VM, el path a donde residen los ficheros de exportación y el path a donde queremos copiarlos. La importación se realizará realmente desde este último, que debería de coincidir con el path original que tenía la máquina virtual exportada. Cualquier cambio en esta lógica no debería ser un gran problema, como tampoco una manipulación más personalizada de los paths. Esto quizás merecería el desarrollo de una interfaz gráfica con un explorador de ficheros. Ahí dejo la idea)

# ImportMConfig.ps1
# Usage: ./ImportVMConfig.ps1 <VMName> <Path to export dir> <path to place VM Configuration Files>
# Example: ./ImportVMConfig.ps1 "Test" "d:\vmexports" "D:\VirtualMachines"

param([parameter(Mandatory=$TRUE,ValueFromPipeline=$TRUE)] $VMName, [parameter(Mandatory=$TRUE,ValueFromPipeline=$TRUE)] $expDir, [parameter(Mandatory=$TRUE,ValueFromPipeline=$TRUE)] $VMdestdir)

# Copy export data from export dir to the path where VM's files will reside, and perform the import from there

Copy-Item $expdir"\"$VMName -Destination $VMdestdir -recurse -ea "silentlycontinue"
 
# Get VM, VMMS  & Export Settings Objects

$ns = 'root\virtualization'
$vmms = gwmi -n $ns Msvm_VirtualSystemManagementService
$SettingData = $vmms.GetVirtualSystemImportSettingData($VMdestdir+"\"+$VMName).ImportSettingData

# Do not try to copy de VHDs, as we didn't exported them

$SettingData.CreateCopy = $False

# Copy CurrentResourcePaths to SourceResourcePaths, as we assume paths has not changed

$SettingData.SourceResourcePaths = $SettingData.CurrentResourcePaths

# Do the Import VM config Only

$out=$vmms.importVirtualSystemEx($VMdestdir+"\"+$VMName, $SettingData.PSBase.GetText("CimDtd20") )

#Perform Job handling if necessary
 
if ($out.ReturnValue -eq 4096){
    $task = [Wmi]$out.Job;
    while($task.JobState -eq 3 -or $task.JobState -eq 4){
        $task.Get();
        sleep 1;
        }
    if ($task.JobState -ne 7){
        "Error Importing VM " + $task.ErrorDescription;
        }            
    else {
        "Import completed successfully..."
        }
    }
elseif ($out.ReturnValue -ne 0) {
    "Import failed with error : " + $out.ReturnValue;
    }
else {
    "Import completed successfully..."
    }

No me meto en el jardín de llevar a cabo estas tareas sobre sistemas remotos. Digamos que en ambos scripts simplemente tendríamos que agregar el parámetro correspondiente al nombre del host remoto y agregarlo como valor del parámetro –computer en las llamadas a Get-WMIObject (p.e. gwmi –computername $hostremoto…).

DISCLAIMER: Los presentes scripts solamente han sido probados por mi, en un entorno de pruebas, y por tanto su correcto funcionamiento en cualquier situación no está garantizado. Aunque he intentado capturar gran parte de las posibles excepciones, estoy seguro de que puede haber situaciones que no estén contempladas. Si planteáis incluir estos scripts en vuestro maletín de herramientas, por favor, probadlos bien, y enviad cualquier problema o mejora que consideréis pertinente.

NOTA 1: Ardo en deseos de ver lo que es capaz de hacer con esto mi compañero Daniel Matey en base a workflows de DR de Opalis/System Cenyter Orchestrator

NOTA 2: Algo similar a esto es lo que está detrás a soluciones del tipo Snapshot+Remotecopy a nivel de almacenamiento como SnapMirror de NetApp (OnCommand Plug-in for Microsoft)

Saludos

David Cervigón