目录
前文列表
VMware 虚拟化编程(1) — VMDK/VDDK/VixDiskLib/VADP 概念简析
VMware 虚拟化编程(2) — 虚拟磁盘文件类型详解
VMware 虚拟化编程(3) —VMware vSphere Web Service API 解析
VMware 虚拟化编程(4) — VDDK 安装
VMware 虚拟化编程(5) — VixDiskLib 虚拟磁盘库详解之一
VMware 虚拟化编程(6) — VixDiskLib 虚拟磁盘库详解之二
VMware 虚拟化编程(7) — VixDiskLib 虚拟磁盘库详解之三
VMware 虚拟化编程(8) — 多线程中的 VixDiskLib
VMware 虚拟化编程(9) — VMware 虚拟机的快照
VMware 虚拟化编程(10) — VMware 数据块修改跟踪技术 CBT
VMware 虚拟化编程(11) — VMware 虚拟机的全量备份与增量备份方案
VMware 虚拟化编程(12) — VixDiskLib Sample 程序使用
VMware 虚拟化编程(13) — VMware 虚拟机的备份方案设计
VMware 虚拟化编程(14) — VDDK 的高级传输模式详解
将已存在的虚拟机恢复到指定时间点
- 使用 vSphere WS API 连接到 VC/ESXi。
- 使用 vSphere WS API 关闭恢复目标虚拟机的电源,因为 VixDiskLib 对正在运行的虚拟机磁盘没有写权限。
- 使用 VixDiskLib 连接到恢复目标虚拟机的虚拟磁盘,如果使用 SAN、HotAdd、NBDSSL 高级传输模式来进行连接,还需要在执行恢复数据操作前创建一个临时快照。对于 SAN 传输模式,如果在创建临时快照前就已经存在快照了,那么你就需要在创建临时快照前确保移除了之前的所有快照,否则 SAN 传输模式的还原会失败。
- 使用 VixDiskLib 的 VixDiskLib_Write 将备份数据恢复到虚拟磁盘。恢复磁盘数据的前提是需要获得恢复目标虚拟磁盘当前实际的全称(包括一些序列号),否则备份数据无法正确定位到恢复目标虚拟磁盘,因为当前恢复目标虚拟机的虚拟磁盘可能从一个或多个快照继承而来。VixDiskLib_Write 函数以偶数扇区为单位传输数据,且传输长度必须为扇区大小的偶数倍。
- 如果创建了临时快照,还需要使用 vSphere WS API 删除快照。尤其对于 SAN 传输模式,还需要先恢复临时快照,然后再删除临时快照。如果没有恢复临时快照,就会因为 CID 不匹配导致无法删除临时快照。如果临时快照没有删除,会导致虚拟机不能开机,而且这个临时快照还会引起后续备份的还原问题。
- 使用 vSphere WS API 开启恢复目标虚拟机的电源。
恢复为新建虚拟机 (灾难恢复)
- 使用 vSphere WS API 连接 VC/ESXi
- 使用 vSphere WS API 来创建新的虚拟机和虚拟磁盘,需要使用在备份时保存的虚拟机配置信息(VirtualMachineConfigInfo)。
- 使用 VixDiskLib 的 VixDiskLib_WriteMetadata 写入虚拟磁盘元数据。
- 使用 VixDiskLib 的 VixDiskLib_Write 将备份数据恢复到新建的虚拟磁盘。
- 使用 vSphere WS API 开启新建虚拟机的电源。
恢复细节
恢复增量备份数据
- 使用 vSphere WS API 关闭虚拟机电源。
- 使用 vSphere WS API 来创建新的虚拟机和虚拟磁盘,需要使用在备份时保存的虚拟机配置信息(VirtualMachineConfigInfo)。
- 使用 VixDiskLib 的 VixDiskLib_Write 把全量备份数据恢复到新建的虚拟磁盘中作为基准虚拟磁盘(Base Disk),进行全量恢复的期间,需要关闭 CBT。
- 使用 vSphere WS API 创建一个快照,这在高级传输模式下是必须的。
- 对于 SAN 传输模式的恢复,也需要禁用 CBT,否则增量数据的 VixDiskLib_Write 写操作不可用,这是因为文件系统需要统计精简置备虚拟磁盘(Thin)的分配状况以及进行延迟清零(Lazy Zeroed)的操作。
- 使用 VixDiskLib 的 VixDiskLib_Write 依次恢复增量备份数据到虚拟磁盘。
- 如果你向前还原,还原时某些相同的扇区可能会被写入多次。
- 如果你向后还原,必须手动记录哪些扇区已经更新过了,以避免再次写入更旧的数据。
- 在增量备份时,需要保存调用 QueryChangedDiskAreas 获取的相应「已修改数据库偏移量」,以此来得知要恢复哪些虚拟磁盘的扇区。
- 建议将备份数据直接恢复到基准虚拟磁盘,避免创建创建重做日志文件。
以 RDM 的方式创建虚拟磁盘
使用 vSphere WS API 中的 CreateVM_Task 创建 RDM 磁盘时,需要用到了一个可用且未被占用的 LUN 设备。在组装 VirtualDiskRawDiskMappingVer1BackInfo 类型虚拟磁盘的 backingInfo 时,我们可以按照下述方法来获取 LUN 设备的相关信息,并以此组装创建 RDM 磁盘所需要的参数:
调用 QueryConfigTarget 获得 ConfigTarget.ScsiDisk.Disk.CanonicalName 属性值,并设置到
VirtualDiskRawDiskMappingVer1BackInfo.deviceName
属性中。调用 QueryConfgiTarget 获得 ConfigTarget.ScsiDisk.Disk.uuid 属性值,并设置到
VirtualDiskRawDiskMappingVer1BackInfo.lunUuid
属性中。
NOTE:有些开发者可能会使用 ESXi configInfo 对象中的 LUN uuid。需要注意的是,这样做很可能会导致错误,因为实际可用的 LUN uuid 是由 Datastore 指定的,而非 ESXi。
创建虚拟机
在实际调用 vSphere WS API 中的 CreateVM_Task 新建一个虚拟机前,需要组装一个 VirtualMachineConfigSpec 配置数据集来描述虚拟机的各项配置以及虚拟设备。大部分需要的信息都可以从 VirtualMachine Managed Object 的 VirtualMachineConfigInfo 得到,其中的 config.hardware.device 就包含了虚拟机所有虚拟设备配置信息。不同设备间的关系使用 key 的值表示,它是设备的唯一标识符。除此之外,每个设备还都拥有 controllerKey 属性,controllerKey 的值就是设备所连接的控制器的唯一标识符。我们在组装 VirtualMachineConfigSpec 时,应该使用一个负整数作为临时的 controllerKey 值,以此来保证临时 controllerKey 值不会和实际分配给这些控制器的值发送冲突。当虚拟设备关联到默认设备时,controllerKey 值应该重置为对应控制器的 key 值。
一般的,为了恢复一个新的虚拟机,我们会在备份时保留备份目标虚拟机的 VirtualMachineConfigInfo 数据,为了恢复出「一模一样」的虚拟机,甚至有些开发者会完整的将其保留。但实际上,VirtualMachineConfigInfo 中的一些数据是不需要的,而且如果强行引用到 VirtualMachineConfigSpec 中的话,还会导致虚拟机创建失败。例如,包含了「Default Devices」的 VirtualMachineConfigSpec 就会创建失败,「Default Devices」列表如下,这些设备的信息都是无需组装到 VirtualMachineConfigInfo 中的。但是,除此之外的其他控制器和虚拟设备就必须组装到 VirtualMachineConfigSpec 中了。
- vim.vm.device.VirtualIDEController
- vim.vm.device.VirtualPS2Controller
- vim.vm.device.VirtualPCIController
- vim.vm.device.VirtualSIOController
- vim.vm.device.VirtualKeybord
- vim.vm.device.VirtualVMCIDevice
- vim.vm.device.VirtualPointingDevice
还有一些设备的信息如果被提供了的话也可能会出问题,组装 VirtualMachineConfigSpec 的要点如下:
- 从 VirtualMachineConfigSpec 中排除「Default Devices」以及 VirtualController.device 的属性域
- 设置 VirtualDisk.FlatVer2BackingInfo 类型虚拟磁盘的 parent backing(父磁盘) 信息为 null
- 将 VirtualMachineConfigInfo 中的 cpuFeatureMask 属性域下的 HostCpuIdInfo 数组项转换为 VirtualMachineCpuIdInfoSpec 的 ArrayUpdateSpec 属性值,并且添加 ArrayUpdateOperation::add 项。
Sample of VirtualMachineConfigSpec
// beginning of VirtualMachineConfigSpec, ends several pages later
{
dynamicType = <unset>,
changeVersion = <unset>,
//This is the display name of the VM
name = “My New VM“,
version = "vmx-04",
uuid = <unset>,
instanceUuid = <unset>,
npivWorldWideNameType = <unset>,
npivDesiredNodeWwns = <unset>,
npivDesiredPortWwns = <unset>,
npivTemporaryDisabled = <unset>,
npivOnNonRdmDisks = <unset>,
npivWorldWideNameOp = <unset>,
locationId = <unset>,
// This is advisory, the disk determines the O/S
guestId = "winNetStandardGuest",
alternateGuestName = "Microsoft Windows Server 2008, Enterprise Edition",
annotation = <unset>,
files = (vim.vm.FileInfo) {
dynamicType = <unset>,
vmPathName = "[plat004-local]",
snapshotDirectory = "[plat004-local]",
suspendDirectory = <unset>,
logDirectory = <unset>,
},
tools = (vim.vm.ToolsConfigInfo) {
dynamicType = <unset>,
toolsVersion = <unset>,
afterPowerOn = true,
afterResume = true,
beforeGuestStandby = true,
beforeGuestShutdown = true,
beforeGuestReboot = true,
toolsUpgradePolicy = <unset>,
pendingCustomization = <unset>,
syncTimeWithHost = <unset>,
},
flags = (vim.vm.FlagInfo) {
dynamicType = <unset>,
disableAcceleration = <unset>,
enableLogging = <unset>,
useToe = <unset>,
runWithDebugInfo = <unset>,
monitorType = <unset>,
htSharing = <unset>,
snapshotDisabled = <unset>,
snapshotLocked = <unset>,
diskUuidEnabled = <unset>,
virtualMmuUsage = <unset>,
snapshotPowerOffBehavior = "powerOff",
recordReplayEnabled = <unset>,
},
consolePreferences = (vim.vm.ConsolePreferences) null,
powerOpInfo = (vim.vm.DefaultPowerOpInfo) {
dynamicType = <unset>,
powerOffType = "preset",
suspendType = "preset",
resetType = "preset",
defaultPowerOffType = <unset>,
defaultSuspendType = <unset>,
defaultResetType = <unset>,
standbyAction = "powerOnSuspend",
},
// the number of CPUs
numCPUs = 1,
// the number of memory megabytes
memoryMB = 256,
memoryHotAddEnabled = <unset>,
cpuHotAddEnabled = <unset>,
cpuHotRemoveEnabled = <unset>,
deviceChange = (vim.vm.device.VirtualDeviceSpec) [
(vim.vm.device.VirtualDeviceSpec) {
dynamicType = <unset>,
operation = "add",
fileOperation = <unset>,
// CDROM
device = (vim.vm.device.VirtualCdrom) {
dynamicType = <unset>,
// key number of CDROM
key = -42,
deviceInfo = (vim.Description) null,
backing = (vim.vm.device.VirtualCdrom.RemotePassthroughBackingInfo) {
dynamicType = <unset>,
deviceName = "",
useAutoDetect = <unset>,
exclusive = false,
},
connectable = (vim.vm.device.VirtualDevice.ConnectInfo) {
dynamicType = <unset>,
startConnected = false,
allowGuestControl = true,
connected = false,
},
// connects to this controller
controllerKey = 200,
unitNumber = 0,
},
},
(vim.vm.device.VirtualDeviceSpec) {
dynamicType = <unset>,
operation = "add",
fileOperation = <unset>,
// SCSI controller
device = (vim.vm.device.VirtualLsiLogicController) {
dynamicType = <unset>,
// key number of SCSI controller
key = -44,
deviceInfo = (vim.Description) null,
backing = (vim.vm.device.VirtualDevice.BackingInfo) null,
connectable = (vim.vm.device.VirtualDevice.ConnectInfo) null,
controllerKey = <unset>,
unitNumber = <unset>,
busNumber = 0,
hotAddRemove = <unset>,
sharedBus = "noSharing",
scsiCtlrUnitNumber = <unset>,
}, },
(vim.vm.device.VirtualDeviceSpec) {
dynamicType = <unset>,
operation = "add",
fileOperation = <unset>,
// Network controller
device = (vim.vm.device.VirtualPCNet32) {
dynamicType = <unset>,
// key number of Network controller
key = -48,
deviceInfo = (vim.Description) null,
backing = (vim.vm.device.VirtualEthernetCard.NetworkBackingInfo) {
dynamicType = <unset>,
deviceName = "Virtual Machine Network",
useAutoDetect = <unset>,
network = <unset>,
inPassthroughMode = <unset>,
},
connectable = (vim.vm.device.VirtualDevice.ConnectInfo) {
dynamicType = <unset>,
startConnected = true,
allowGuestControl = true,
connected = true,
},
controllerKey = <unset>,
unitNumber = <unset>,
addressType = "generated",
macAddress = <unset>,
wakeOnLanEnabled = true,
}, },
(vim.vm.device.VirtualDeviceSpec) {
dynamicType = <unset>,
operation = "add",
fileOperation = "create",
// SCSI disk one
device = (vim.vm.device.VirtualDisk) {
dynamicType = <unset>,
// key number for SCSI disk one
key = -1000000,
deviceInfo = (vim.Description) null,
backing = (vim.vm.device.VirtualDisk.FlatVer2BackingInfo) {
dynamicType = <unset>,
fileName = "",
datastore = <unset>,
diskMode = "persistent",
split = false,
writeThrough = false,
thinProvisioned = <unset>,
eagerlyScrub = <unset>,
uuid = <unset>,
contentId = <unset>,
changeId = <unset>,
parent = (vim.vm.device.VirtualDisk.FlatVer2BackingInfo) null,
},
connectable = (vim.vm.device.VirtualDevice.ConnectInfo) {
dynamicType = <unset>,
startConnected = true,
allowGuestControl = false,
connected = true,
},
// controller for SCSI disk one
controllerKey = -44,
unitNumber = 0,
// size in MB SCSI disk one
capacityInKB = 524288,
committedSpace = <unset>,
shares = (vim.SharesInfo) null,
}, },
(vim.vm.device.VirtualDeviceSpec) {
dynamicType = <unset>,
operation = "add",
fileOperation = "create",
// SCSI disk two
device = (vim.vm.device.VirtualDisk) {
dynamicType = <unset>,
// key number of SCSI disk two
key = -100,
deviceInfo = (vim.Description) null,
backing = (vim.vm.device.VirtualDisk.FlatVer2BackingInfo) {
dynamicType = <unset>,
fileName = "",
datastore = <unset>,
diskMode = "persistent",
split = false,
writeThrough = false,
thinProvisioned = <unset>,
eagerlyScrub = <unset>,
uuid = <unset>,
contentId = <unset>,
changeId = <unset>,
parent = (vim.vm.device.VirtualDisk.FlatVer2BackingInfo) null,
},
connectable = (vim.vm.device.VirtualDevice.ConnectInfo) {
dynamicType = <unset>,
startConnected = true,
allowGuestControl = false,
connected = true,
},
// controller for SCSI disk two
controllerKey = -44,
unitNumber = 1,
// size in MB SCSI disk two
capacityInKB = 131072,
committedSpace = <unset>,
shares = (vim.SharesInfo) null,
}, }
},
cpuAllocation = (vim.ResourceAllocationInfo) {
dynamicType = <unset>,
reservation = 0,
expandableReservation = <unset>,
limit = <unset>,
shares = (vim.SharesInfo) {
dynamicType = <unset>,
shares = 100,
level = "normal",
},
overheadLimit = <unset>,
},
memoryAllocation = (vim.ResourceAllocationInfo) {
dynamicType = <unset>,
reservation = 0,
expandableReservation = <unset>,
limit = <unset>,
shares = (vim.SharesInfo) {
dynamicType = <unset>,
shares = 100,
level = "normal",
},
overheadLimit = <unset>,
},
cpuAffinity = (vim.vm.AffinityInfo) null,
memoryAffinity = (vim.vm.AffinityInfo) null,
networkShaper = (vim.vm.NetworkShaperInfo) null,
swapPlacement = <unset>,
swapDirectory = <unset>,
preserveSwapOnPowerOff = <unset>,
bootOptions = (vim.vm.BootOptions) null,
appliance = (vim.vService.ConfigSpec) null,
ftInfo = (vim.vm.FaultToleranceConfigInfo) null,
applianceConfigRemoved = <unset>,
vAssertsEnabled = <unset>,
changeTrackingEnabled = <unset>,
}
// end of VirtualMachineConfigSpec
Demo of VirtualMachineConfigSpec
// Duplicate virtual machine configuration
VirtualMachineConfigSpec configSpec = new VirtualMachineConfigSpec();
// Set the VM values
configSpec.setName("My New VM");
configSpec.setVersion("vmx-04");
configSpec.setGuestId("winNetStandardGuest");
configSpec.setNumCPUs(1);
configSpec.setMemoryMB(256);
// Set up file storage info
VirtualMachineFileInfo vmfi = new VirtualMachineFileInfo(); vmfi.setVmPathName("[plat004-local]");
configSpec.setFiles(vmfi);
vmfi.setSnapshotDirectory("[plat004-local]");
// Set up tools config info
ToolsConfigInfo tools = new ToolsConfigInfo();
configSpec.setTools(tools);
tools.setAfterPowerOn(new Boolean(true));
tools.setAfterResume(new Boolean(true));
tools.setBeforeGuestStandby(new Boolean(true));
tools.setBeforeGuestShutdown(new Boolean(true));
tools.setBeforeGuestReboot(new Boolean(true));
// Set flags
VirtualMachineFlagInfo flags = new VirtualMachineFlagInfo(); configSpec.setFlags(flags);
flags.setSnapshotPowerOffBehavior("powerOff");
// Set power op info
VirtualMachineDefaultPowerOpInfo powerInfo = new VirtualMachineDefaultPowerOpInfo(); configSpec.setPowerOpInfo(powerInfo);
powerInfo.setPowerOffType("preset");
powerInfo.setSuspendType("preset");
powerInfo.setResetType("preset");
powerInfo.setStandbyAction("powerOnSuspend");
// Now add in the devices
VirtualDeviceConfigSpec[] deviceConfigSpec = new VirtualDeviceConfigSpec [5]; configSpec.setDeviceChange(deviceConfigSpec);
// Formulate the CDROM deviceConfigSpec[0].setOperation(VirtualDeviceConfigSpecOperation.add);
VirtualCdrom cdrom = new VirtualCdrom();
VirtualCdromIsoBackingInfo cdDeviceBacking = new VirtualCdromRemotePassthroughBackingInfo(); cdDeviceBacking.setDatastore(datastoreRef);
cdrom.setBacking(cdDeviceBacking);
cdrom.setKey(-42);
cdrom.setControllerKey(new Integer(-200)); // Older Java required type for optional properties cdrom.setUnitNumber(new Integer(0));
deviceConfigSpec[0].setDevice(cdrom);
// Formulate the SCSI controller deviceConfigSpec[1].setOperation(VirtualDeviceConfigSpecOperation.add); VirtualLsiLogicController scsiCtrl = new VirtualLsiLogicController(); scsiCtrl.setBusNumber(0);
deviceConfigSpec[1].setDevice(scsiCtrl);
scsiCtrl.setKey(-44);
scsiCtrl.setSharedBus(VirtualSCSISharing.noSharing);
// Formulate SCSI disk one deviceConfigSpec[2].setFileOperation(VirtualDeviceConfigSpecFileOperation.create); deviceConfigSpec[2].setOperation(VirtualDeviceConfigSpecOperation.add); VirtualDisk disk = new VirtualDisk();
VirtualDiskFlatVer2BackingInfo diskfileBacking = new VirtualDiskFlatVer2BackingInfo(); diskfileBacking.setDatastore(datastoreRef);
diskfileBacking.setFileName(volumeName);
diskfileBacking.setDiskMode("persistent");
diskfileBacking.setSplit(new Boolean(false));
diskfileBacking.setWriteThrough(new Boolean(false));
disk.setKey(-1000000);
disk.setControllerKey(new Integer(-44));
disk.setUnitNumber(new Integer(0));
disk.setBacking(diskfileBacking);
disk.setCapacityInKB(524288);
deviceConfigSpec[2].setDevice(disk);
// Formulate SCSI disk two deviceConfigSpec[3].setFileOperation(VirtualDeviceConfigSpecFileOperation.create); deviceConfigSpec[3].setOperation(VirtualDeviceConfigSpecOperation.add);
VirtualDisk disk2 = new VirtualDisk();
VirtualDiskFlatVer2BackingInfo diskfileBacking2 = new VirtualDiskFlatVer2BackingInfo(); diskfileBacking2.setDatastore(datastoreRef);
diskfileBacking2.setFileName(volumeName);
diskfileBacking2.setDiskMode("persistent");
diskfileBacking2.setSplit(new Boolean(false));
diskfileBacking2.setWriteThrough(new Boolean(false));
disk2.setKey(-100);
disk2.setControllerKey(new Integer(-44));
disk2.setUnitNumber(new Integer(1));
disk2.setBacking(diskfileBacking2);
disk2.setCapacityInKB(131072);
deviceConfigSpec[3].setDevice(disk2);
// Finally, formulate the NIC deviceConfigSpec[4].setOperation(VirtualDeviceConfigSpecOperation.add); com.VMware.vim.VirtualEthernetCard nic = new VirtualPCNet32(); VirtualEthernetCardNetworkBackingInfo nicBacking = new VirtualEthernetCardNetworkBackingInfo(); nicBacking.setNetwork(networkRef);
nicBacking.setDeviceName(networkName);
nic.setAddressType("generated");
nic.setBacking(nicBacking);
nic.setKey(-48);
deviceConfigSpec[4].setDevice(nic);
// Now that it is all put together, create the virtual machine.
// Note that folderMo, resourcePool, and hostMo, are moRefs to
// the Folder, ResourcePool, and Host where the VM is to be created.
ManagedObjectReference taskMoRef =
serviceConnection.getService().createVM_Task(folderMo, configSpec, resourcePool, hostMo);