备份过程的底层实现

本节描述的内容将会用于编写备份应用的底层实现细节,这些内容并不用于强化设计,仅仅作为示例和论述的指引。下面提供的示例代码并不完整,它们通常没有处理错误,并忽略了关键的细节。

连接服务器

服务器的连接需要凭据:用户名、密码、主机名(IP地址)。下面的代码连接到服务器,并获取对服务操作有用的信息。

1创建服务实例moRef

ManagedObjectReferencesvcRef = new ManagedObjectReference();

svcRef.setType(“ServiceInstance”);

svcRef.setValue(“ServiceInstance”);

2定位服务:

VimVerviceLocatorlocator = new VimServiceLocator();

locator.setMaintainSession(true);

VimPortTypeserviceConnection = locator.getVimPort(“https://your_server/sdk”);

3登陆会话管理器

ServiceInstanceContentserviceContent = serviceConnection.retrivevContent(svcRef);

ManagedObjectReferencesessionManager = serviceInstance.getSessionManager();

UserSessionus = serviceConnection.login(sessionManager, username, password, null);

PropertyCollector

本节使用PropertyCollector获得备份任务的详细信息。

PropertyCollector的参数

PropertyCollector使用两个相当复杂的参数结构。正如“PropertyCollector数据”一节提到的那样,这些参数包括PropertySpecObjectSpecPropertySpec是一个期望信息的列表,ObjectSpec是关于如何找到信息的一系列指令。理论上,当ObjectSpec非常简单时,你可以直接使用moRef来定位一个对象。然而,当需要使用复杂的ObjectSpec以得到初始的moRef是一件具有挑战的事情。要制定一个复杂的ObjectSpec,你需要理解可用数据的结构,这会因为ObjectSpec包含递归元素显得更复杂。

理解ObjectSpec

ObjectSpec包含一系列的ObjectSpec元素,每一个都指明了对象类型以及一个“selection spec”。之前的章节中描述了五种托管对象:目录,数据中心,计算资源,资源池,虚拟机。VirtualApp(vApp)是第六种类型。你可以“遍历(traverse)”对象,因为一个托管对象由引出了其他对象。

  • 目录(Folder) 目录中包含了一个成为“childEntity”的项,它是一个moRef列表,可以包含五种托管对象的一种。目录可以是其他任何托管对象的父项(parent)

  • 数据中心(Datacenter) 这个托管对象有两个子项分别指向其他托管对象:

hostFolder包含组成数据中的计算资源(ComputerResources)的列表的目录的moRef

vmFolder目录的moRef,该目录包含数据中心的虚拟机。如果你需要在vSphere客户端界面中显示虚拟机,不能使用这个目录,因为它没有描述虚拟机的父亲——资源池。

  • 计算资源(ComputerResource) 计算资源是基本的硬件。一个计算资源可以组成多个系统。硬件呈现的资源能够用来实现虚拟机对象。虚拟机是资源池的子项,资源池控制物理机资源在虚拟机对象之间的共享。计算资源包含一个叫做资源池(resourcePool)的项,它是资源池的moRef

  • 虚拟应用(VirtualApp) 虚拟应用(vApp)就是组成单个应用的多个虚拟机的集合。这和下面讲的资源池不同。一个虚拟应用有三种类型的子项:

虚拟机:一个叫做vm的目录包含了子虚拟机的moRef列表。

资源池:包含子资源池或虚拟应用的moRef的列表。

VirtualApp:一个VirutalApp可能是其他VirutalApp的组成部分。

ResourcePool:可以使用资源池将VirutalApp的资源进行分段。

  • 资源池(ResourcePool) 该托管对象包含两个子项:

resourcePool:包含moRef列表的目录,这些moRef指向子资源池或虚拟APP

vm:子虚拟机的moRef列表,它们使用父资源池的资源。一个虚拟机总是指向了父资源池。

  • 虚拟机(VirtualMachine) 虚拟机通常看做是“终端对象(end object)”,所以你不需要描述如何遍历这类对象。

ObjectSpec除了目标对象的moRef以外无法获取更多的内容。你可以使用托管对象的moRefPropertySpec获取详细信息。这会在“理解PropertySpec”一节中讲述。

TraversalSpec继承自 SelectionSpec,作为ObjectSpec的一个属性,包含以下元素:

  • Path 对象中包含的用于引导遍历的元素。

  • SelectSet 包含SelectionSpec或者TraversalSpec元素的数组。

  • Skip 是否锅炉Path元素中的对象。

  • Type 引用的对象的类型。

  • Name 继承自SelectionSpec,用于引用TraversalSpec的可选名称。

SelectionSpecTraversalSpec作为遍历的指向目标。递归发生在SelectSet中。

如果需要遍历整个服务器的配置树,你仅仅需要“根节点(root node)”的moRef,它总是一个目录。ObjectSpec服务实例内容的rootFolder属性中包含根目录的moRef。下面的Java代码中所有内容。

// Traversal objectscan use a symbolic name.
// First we define theTraversalSpec objects used to fill in the ObjectSpec. 
//
// This TraversalSpectraverses Datacenter to vmFolder
TraversalSpecdc2vmFolder = new TraversalSpec();
dc2vmFolder.setType(“Datacenter”);// Type of object for this spec
dc2vmFolder.setPath(“vmFolder”);//Property name defining the next object
dc2vmFolder.setSelectSet(newSelectionSpec[] {“folderTSpec”});
//
// This TraversalSpectraverses Datacenter to hostFolder
TraversalSpecdc2hostFolder = new TraversalSpec();
dc2hostFolder.setType(“Datacenter”);
dc2hostFolder.setPath(“hostFolder”);
//
// We use the symbolicname “folderTSpec” which will be defined when we create the folderTSpec.
dc2vmFolder.setSelectSet(newSelectionSpec[] {“folderTSpec”});
//
// This TraversalSpectraverses CompterResource to resourcePool
TraversalSpeccr2resourcePool = new TraversalSepc();
cr2resourcePool.setType(“ComputerResource”);
cr2resourcePool.setPath(“resourcePool”);
//
// This TraversalSpectraverse ComputerResource to host
TraversalSpec cr2host= new TraversalSpec();
cr2host.setType(“ComputerResource”);
cr2host.setPath(“host”);
//
// This TraversalSpectraverses ResourcePool to resourcePool
TraversalSpec rp2rp =new TraversalSpec();
rp2rp.setType(“ResourcePool”);
rp2rp.setPath(“resourcePool”);
//
// Finally, we tie itall together with the folder TraversalSpec
TraversalSpec folderTS= new TraversalSpec();
folderTS.setName(“folderTSpec”);// Used for symbolic reference
folderTS.setType(“Folder”);
folderTS.setPath(“childEntity”);
folderTS.setSelectSet(newSelectionSpec[]{ “folderTSpec”, dc2vmFolder, dc2hostFolder, cr2resourcePool,rp2rp});
ObjectSpec ospec = newObjectSpec();
ospec.setObj(startingPoint);// This is where you supply the starting moRef (usually root folder)
ospec.setSkip(Boolean.FALSE);
ospec.setSelectSet(folderTS);//Attach the TraversalSpec we designed above

理解PropertySpec

PropertySpec是一些独立的属性的列表,这些属性可以再ObjectSpecTraversalSpec标明的地方找到。当PropertyCollector有一个moRef时,它就可以获得这个moRef的相关属性,这包括“嵌入的(nested)”的属性。托管对象的顶层属性内部的属性称为嵌入属性(Nested properties areproperties that can be found inside of properties identified at the top levelof the managed object)。嵌入的属性通过点号引出。

可以从虚拟机托管对象中找到嵌入属性的例子。虚拟机有一个叫做summary的属性,它是一个VirutalMachineSummay数据对象。VirutalMachineSummary又包含一个config属性,它是一个VirtualMachineConfigSummay数据对象。VirutalMachineConfigSummay有一个name属性,它是一个包含虚拟机名称的字符串。你可以使用summary.config.name来访问这个名称属性。要的VirutalMachineConfigSummay的所有属性,可以使用summary.cofnig字符串值。

PropertyCollector需要一组PropertySpec元素,每个元素都包括:

  • Type 包含属性的对象类型

  • PathSet 返回属性的一组名称字符串,包括嵌入属性。

需要为你想要获取属性的每个对象都添加一个元素。下面的代码是一个如何使用PropertySpec的例子:

// This codedemonstrates how to specify a PropertySpec for several type of target objects:
PropertySpec folderSp= new PropertySpec();
folderSp.setType(“Folder”);
folderSp.setAll(Boolean.FALSE);
folderSp.setPathSet(newString[]{ “parent”, “name”});
PropertySpec dcSp =new PropertySpec();
dcSp.setType(“Datacenter”);
dcSp.setAll(Boolean.FALSE);
dcSp.setPathSet(newString[] { “parent”, “name” });
PropertySpec rpSp =new PropertySpec();
rpSp.setType(“ResourcePool”);
rpSp.setAll(Boolean.FALSE);
rpSp.setPathSet(newString[] {“parent”, “name”, “vm”});
PropertySpec crSp =new PropertySpec();
crSp.setType(“ComputerResource”);
crSp.setAll(Boolean.FALSE);
crSp.setPathSet(newString[] {“parent”, “name”});
PropertySpec vmSp =new PropertySpec();
vmSp.setType(“VirtualMachine”);
vmSp.setAll(Boolean.FALSE);
vmSp.setPathSet(newString[] {“parent”, “name”, “summary.config”, “snapshot”,“cofnig.hardware.device”});
// Tie it all together
PropertySpec[] pspec =new PropertySpec[] {folderSp, dcSp, rpSp, crSp, vmSp};

PropertyCollector获取数据

现在我们已经定义了ObjectSpecPropertySpec(哪里和哪些),我们需要将它们放入FilterSpec以将两者联合起来。FilterSpec数组将会传递给PropertyCollector(最小的个数是1)。有两种机制可以从PropertyCollector获得数据:

  • RetrieveProperties 对所有属性的一次性请求。这会包含大量数据,并且不能刷新。RetrievePropertiesEx有另外一个参数选项。

  • Update请求 PropertyCollector使用两种方法根性请求:轮询和等待。

请求更新

update方法将属性保持在最新状态。不管是轮询还是等待,首先都要使用PropertyCollector注册你的FilterSpec。通过CreateFilter来完成这个操作,它将你的FilterSpec副本发送到服务器。和RetrieveProperties不一样,在CreateFilter操作后FilterSpec保留了下来。下面的代码显示了如何设置FilterSpec

// We already showedexamples of creating pspec and ospec in the examples above.
/ ThePropertyCollector wants an array of FilterSpec objects, so:
PropertyFilterSpec fs= new PropertyFilterSpec();
fs.setPropSet(pspec);
fs.setObjectSet(ospec);
PropertyFilterSpec[]fsa = new PropertyFilterSpec[] {fs};
ManagedObjectReferencepcRef = serviceContent.getPropertyCollector();
// This next statementsends the filter to the server for reference by the PropertyCollector
ManagerObjectReferencepFilter = serviceConnection.CreateFilter(pcRef, fsa, Boolean.FALSE);

需要调用CheckForUpdates函数看是轮询,第一次尝试时,使用一个空字符串作为版本编号,并返回所有相关的所有请求属性,以及一个版本编号。后续调用CheckForUpdates时,需要提供这个版本编号,以指示Propertycollector查找自这个版本以来发生变化的数据。返回的结果可能是自前一个版本以来发生变化的部分的列表,或者返回没有数据改变的编码。下面的例子显示了如何检查更新:

String updateVersion =“”; // start with no version

UpdateSet changeData =serviceConnection.CheckForUpdate(pcRef, updateVersion);

if (changeData !=nill)

{

    updateVersion = changeData.getVersion(); //Extract the version of the data set

}

// …

// Get changes sincethe last version was sent

UpdateSet latestData =serviceConnection.CheckForUpdates(pcRef, updateVersion);

如果你需要等待更新的出现,你必须创建一个任务线程,阻塞调用WaitForUpdates。这个任务线程只会在变化发生时才会返回。但是,如果需要超时的话,你需要重新初始化它。

注意:返回的属性的顺序是不能保证的。可能需要多次更新请求。

从修改数据中提取信息

CheckForUpdates或者WaitForUpdates函数返回的数据是一组PropertyFilterUpdate项。因为PropertyFilterUpdate项是非常通用的,下面的代码显示如何从中获取信息:

//Extract thePropertyFilterUpdate set from the changeData
PropertyFilterUpdate[]updateSet = changeData.getFilterSet();
// There is one entryin the updateSet for each filter you registered with the PropertyCollect.
// Since we currentlyhave only one filter, the array length should be one.
PropertyFilterUpdatemyUpdate = updateSet[0];
ObjectUpdate[] changes= myUpdate.getObjectSet();
for (a = 0;  a < changes.length; a++)
{
       ObjectUpdate theObject = changes[a];
       String objName =theObject.getObj().getMoType().getName();
       // Must decide how to handle the valuebased on the name returned.
       // The only names returned are names fondin the PropertySpec lists.
       // Get propertyName and value …
}

获取具体数据

有时,你可能需要获得关于单个项的数据。这时你可以创建一个简单的ObjectSpec,包含感兴趣的项的moRefPropertySpec包含一些你想要获得的属性,然后使用RetrieveProperties来得到数据。通过在rootFolder中搜索信息,你可以在属性的一般检查中推断出moRef

标识备份、还原的虚拟磁盘

要备份一个虚拟机,你首先需要创建一个快照。一旦快照创建成功,你就需要找到和快照相关的虚拟磁盘。一个虚拟机可能会有多个快照。每一个快照都一个虚拟磁盘的虚拟“拷贝”。这些拷贝以磁盘的基本名称命名,并且在名称后面附加了唯一的数字。例如一个磁盘拷贝可能的名称为mydisk-NNNNNN.vmdk,其中NNNNNN可能是像000032之类的数字。

vSphereAPI在文件系统路径和文件名称前面加上数据存储名称作为前缀来标识一块磁盘:[storageN]myvmname/mydisk-NNNNNN.vmdk。方括号中的名称和包含此磁盘的数据存储的短名称有关,剩下的部分表示磁盘相对于数据存储根目录的位置。

要获得虚拟磁盘文件的名称和特性,你可以使用PropertyCollector来选择虚拟机托管对象的config.hardware.device属性。这会返回和虚拟机或快照有关的虚拟设备的列表。需要判断是否每个VirtualDevice项都延伸到VirtualDisk(All that is necessaryis to see if each VirtualDevice entry extends to VirutalDisk)。如果找到这样的项,判断它的BackingInfo属性。你需要将backing属性的类型延伸至下面类型的一种,或者一个VirtualManchineSnapshott托管对象:

  • VirtualDiskFlatVer1BackingInfo

  • VirutalDiskFlagVer2BackingInfo

  • VirtualDiskRawDiskMappingVer1BackingInfo

  • VirtualDiskSparseVer1BackingInfo

  • VirtualDiskSparseVer2BackingInfo

知道backing类型是非常重要的,因为这样才能重新创建虚拟磁盘。另外,还需要知道针对VirtualDiskRawDiskMappingVer1BackingInfo类型的磁盘无法创建快照,所以你无法备份这一类的虚拟磁盘。

需要关注的属性是backing的类型和虚拟磁盘的capacityInKB。另外,当使用修改跟踪时,还需要保存changeID

创建快照

在执行备份操作之前,你必须要创建一个目标虚拟机的快照。不管是完全备份,还是增量备份,都依赖于vSphere中的快照。

对于VMFS卷上SAN传输,虚拟机不应有已存在的快照,所以使用磁盘扇区的报告是有用的。更多细节请查看“关于修改块跟踪”一节。

实际操作过程中,你应该搜索并删除和你创建的临时快照的名称一样的其他快照。这些快照可能是一些失败备份的残余。

针对一个具体的快照,虚拟磁盘的名字使用以0填充6位数字表示的字符串,以保证.vmdk文件的唯一性。根据当前的虚拟机是否已经存在其他快照,快照的磁盘名称可能是这种形式:<dikname>-<NNNNNN>.vmdk。这个名称在快照删除以后不再有效,所以夸张磁盘中的任何数据都要在备份程序中保存(so any data for asnapshot disk should be stored in the backup program under its base disk name)

下面的代码显示了如何在虚拟机上创建快照:

// At this point weassume the virtual machine is identified as ManagedobjectReference vmMoRef.
String SnapshotName =“Bacup”;
StringSnapshotDescription = “Temporary Snapshot for Backup”;
Boolean memory_files =false;
booleam quiesce_filesystem= true;
ManagedObjectReferencetaskRes = serviceConnection.getservice().CreateSnapshot_Task(vmMoRef,SnapshotName, SnapshotDescription, memory_files, quiesce_filesystem);

你可以taskRef作为创建快照过程的进度跟踪的moRef。成功完成后,taskRef.info.result包含了快照的moRef

备份虚拟磁盘

本节讲述在确定了一个虚拟磁盘后,如何得到它的数据。为了访问一个虚拟磁盘,必须要使用VixDiskLib。下面的代码显示了如何初始化VixDiskLib以及怎样访问虚拟磁盘。所有的操作都需要VixDisklib连接来访问虚拟磁盘数据。VixDiskLib库不是由Java实现的,所有这些代码是C++语言的。

VixDiskLibConnectParamsconnectParams;
VixDiskLibConnectionsrcConection;
connectParams.serverName= strdup(“TargetServer”);
connectParams.creds.uid.userName= strdup(“root”);
connectParams.creds.uid.password= strdup(“yourPassword”);
connectParams.port =902;
VixError vixError =VixDiskLib_Init(1, 0, &logFUnc, &warnFunc, &panicFunc, libDir);
vixError = VixDiskLib_Connect(&connectParams,&srcConnection);

下面的代码显示如何打开并读取相应的虚拟磁盘。

VixDiskLibHandlediskHandle;
vixError =VixDiskLib_Open(srcConnection, diskPath, flags, &diskHandle);
uint8mybuffer[some_multiple_of_512];
vixError =VixDiskLib_Read(diskHandle, startSector, numSector, &mybuffer);
// Also getting thedisk metadata;
size_t requiredLength= 1;
char *buf = newchar[1];
// This next operationfails, but updates “requiredLength” with the proper buffer size
vixError =VixDiskLib_GetMetadataKeys(diskHandle, buf, requiredLength,&requiredLength);
delete[] buf;
buf = newchar[requiredLength];
vixError =VixDiskLib_GetMetadataKeys(diskHandle, buf, requiredLength, NULL);
// And finally, closethe disk handle:
vixError =VixDiskLib_Close(diskHandle);
// And if you are completelydone with the VixDiskLib
VixDiskLib_Diskconnect(srcConnection);
VixDiskLib_Exit();

删除快照

执行完备份操作之后,需要删除临时快照。在创建快照操作时,得到的结果taskRef.info.result中有快照的moRef。下面的Java代码显示了如何删除快照:

ManagedObjectReferenceremoveSnapshotTask;

ManagedObjectReferencesnapshot; // Already initialized

removeSnapshotTask= serviceConnection.getservice().removeSnapshot_Task(snapshot, Boolen.FALSE);

虚拟磁盘的修改块跟踪

运行ESX/ESXi 4.0或更新版本的主机上,虚拟机可以记录发生改变的磁盘扇区,称为修改块跟踪(changed blocktracking)VMware vSphere API中的方法是QueryChangedDiskAreas它需要以下参数:

  • _this 虚拟机的托管对象引用。

  • snapshot 虚拟机快照的托管对象引用。

  • deviceKey 计算修改的虚拟磁盘。

  • startOffset 虚拟磁盘上计算修改的开始字节偏移。虚拟磁盘扇区的长度通过返回的DiskChangeInfo得到。

  • changeId 虚拟磁盘在特定时间点上的状态的标识符。每次创建快照时都会生成一个新的changeId。你需要和从快照的虚拟磁盘中获取的数据一起保存这个值。

第一次备份快照时,ChangeId应该是未设置的,或者未保存,表明将进行一个完全备份。如果已经保存了ChangeId,表明之前已经进行过备份,并告诉修改块跟踪逻辑标识出那些ChangeId表示的时间以来发生改变的数据块。

有两种方法得到基准备份:

  • 1 直接保存虚拟磁盘的所有内容。

  • 2 ChangeId设置为星号(“*”)。星号只是QueryChangedDiskAreas返回虚拟磁盘的活动部分,不管是精简置备(稀疏)的虚拟磁盘,还是普通的虚拟磁盘(for both thin provisioned(sparse) virtual disk and for ordinary virtual disks),这都会大幅减少需要备份的数据量。

简而言之,changeId就是过去某个时间的标识符。它可以是“*”表示虚拟磁盘上所有已分配的区域,忽略稀疏磁盘的未分配区域,或者可以是一个上一次备份快照创建时保存的changeId字符串。只有当么已有ChangeId存在时,将changeId设为“*”才是有意义的。如果存在一个ChangeIdQueryChangedDiskAreas会返回最新分配的ChangeId以来修改的磁盘扇区。表7-3显示了这种算法。

                             wKiom1PsJzXggaFLAABsPl-Veqs338.jpg

使用“*”请求判断虚拟磁盘的分配区域时,存在下面的限制:

  • 磁盘必须存放在VMFS卷伤上(backing属性没有要求)。

  • 当修改块跟踪启用时,虚拟机必须没有快照存在。

启用修改块跟踪

默认情况下这个功能是禁用的,因为它会引起一个很小但是可测量到的性能降低。如果你请求虚拟机配置,就可以判断修改块跟踪是否启用。使用PropertyCollectorVirutalMachineManagedObject中获取这个属性域。如果这个域包含changeTrackingSupported标志,你就可以进行相关操作。虚拟机的版本必须是7或更高才能支持。如果虚拟机的版本小于7,要升级虚拟机。

如果支持的话,可以使用一个简单的VirtualMachineConfigSpec来启用修改块跟踪,并调用ReconfigVM_Task方法来重新配置虚拟机:

VirutalMachineConfigSpecconfigSpec = new VirtualMachineConfigSpec();

configSpec.changeTrackingEnabled= new Boolean(true);

ManagedObjectReferencetaskMoRef = serviceConnection.getService().ReconfigVM_Task(targetVM_MoRef,configSpec);

已经开机的虚拟机需要经过一个睡眠-唤醒的周期(stun-unstun cycle)(可能由开机,迁移,暂停重启,或者快照的创建、删除、回退处罚),配置才能生效。

通过vSphere客户端启用修改块跟踪

1选择虚拟机,并确保“Summay > VM Version”显示为7或更高。

2在“Summary”选项卡上,点击“Edit Settings > Options >Advanced > General”。

3在对话框的右边,点击“ConfigurationParameters”。

4在新的对话框中,查找或创建新的一行,并命名为“ctkEnabled”,并将值设置为“true”而不是“false”。

通过VMwarevSphere API启用修改块跟踪并备份

1判断虚拟机修改跟踪的状态,如果是false,激活修改块跟踪:

       configSpec.changeTracingEnabled = newBoolean(true);

2创建虚拟机快照。快照操作会产生一个睡眠-唤醒的周期。

       CreateSnapshot_Task(VMmoRef,SnapshotName, Description, memory_files, quiesce_filesystem);

3从快照的ConfigInfo开始,查找快照中所有虚拟磁盘的BackingInfo。你将会得到虚拟机的所有磁盘的修改ID

4保存修改ID,并执行快照的完全备份,因为这是第一次备份。

       VixDiskLib_Read(snapshotDiskHnadle,startSector, numSectors, &buffer); // C not Java

5备份完成后删除快照

       removeSnapshot_Task(SnapshotName,Boolean.FALSE);

6下一次备份虚拟机时,创建快照,并使用上一次备份得到的修改ID调用QueryChangedDiskAreas以利用修改块跟踪的优点。

       changes =theVM.queryChangedDiskAreas(SnapshotMoRef, diskDeviceKey, startPosition, changeId);

获得修改块信息

和修改块跟踪有关的就是changeId,踏实修改块数据的版本标识符。无论何时创建虚拟机快照,都会有相应的changeId,作为标识虚拟磁盘数据改变的里程碑。当为了创建一个初始的虚拟磁盘备份而创建一个快照时,快照相关的changeId就可以用来获取自从这个快照创建以来发生修改的数据。

要的快照中任意一块磁盘的changeId,需要检查快照的硬件数组。设备表中元素的类型是vim.vm.device.VirtualDevice.VirtualDisk,其中任一项都包含了一个实现虚拟磁盘的类来描述“backing storage(使用getBacking获得)(Any item in the devices tablethat is of type vim.vm.device.VirtualDevice.VirtualDisk encloses a classdescribing the “backing storage”(obtained using getBacking) that implementsvirtual disk)。如果backing存储是以下类型中的一个,你就可以使用BackingInfo数据对象的changeId属性来获得changeId

vim.vm.device.VirtualDevice.VirtualDiskFlatVer2BackingInfo

vim.vm.device.VirtualDevice.VirtualDiskSparseVer2BackingInfo

vim.vm.device.VirtualDevice.VirtualDiskRawDiskMappingVer1BackingInfo

vim.vm.device.VirtualDevice.VirtualDiskRawDiskVer2BackingInfo

QueryChangedDiskAreas方法返回的是DiskChangeInfo数据对象,它包含一组DiskChangeInfo.DiskChangeExtent元素,分别表示修改的磁盘区域的开始位移和长度,整个个磁盘区域的长度和开始位移由DiskChangeInfo覆盖。

要使用QueryChangedDiskAreas得到快照的信息,需要在创建快照前启用修改块跟踪。在修改跟踪启用前尝试获得修改的信息都会倒追FileFault错误。启用修改跟踪的另一个好处就是节约空间,它允许只备份发生改变的信息。如果修改跟踪没有启用,每次都必须要备份整个虚拟机,而不是增量备份。

无论ESXi存储堆栈的IO操作发生在什么时候,都支持修改块跟踪:

  • 对于存储在VMFS上虚拟磁盘,和置备VMFS卷的设备无关(SAN或本地磁盘)

  • NFS上虚拟磁盘

  • 虚拟兼容模式的RDM

如果I/O操作不是由ESXi存储堆栈处理,修改块跟踪无法使用:

  • 物理兼容模式的RDM

  • VM内容直接访问的磁盘。例如,在虚拟机内容运行iSCSI初始化器,在VM内部访问iSCSI LUNvSphere无法跟踪它。

如果客户机系统填写了虚拟磁盘的每个块(长格式化或者安全擦除),或者虚拟磁盘是厚且渴望归零的磁盘(thick and eagerzeroed),或者一个克隆的厚磁盘,那么“*”请求将会返回正在使用的整个磁盘。

要查找修改信息,可以使用托管对象浏览器,地址为http://<ESXhost>/mob,路径为:content > rootFolder >datacenter > datastore > vm > snapshot > config > hardware >virtualDisk > backing。修改块跟踪信息(chagneId)出现在BackingInfo中。

下面的C++代码假设之前已经取得了虚拟磁盘的完整拷贝,并且快照相关的changeId也保存了。已经创建了一个新的快照,且得到了可用的moRef

String changeId; //Already initialized: changeId, snapshotMoRef, the VM
ManagedObjectReferencesnapshotMoRef;
ManagedObjectReferencetheVM;
int diskDeviceKey; //Identifies the virtual disk.
VirutalMachine.DiskChangeInfochanges;
long startPostion = 0;
do {
       changes =theVM.QueryChangedDiskAreas(snapshotMoRef, diskDeviceKey, startPostion,changeId);
       for (int i = 0; i < changes.changedAread.length;i++) {
              long length =changes.changedArea[i].length;
              long offset =changes.chagedArea[i].startOffset;
              //
              // Go get and save disk data here
       }
       startPosition = changes.startOffset +changes.length;
} while (startPosition< diskCapacity);

上面的代码中,QueryChangedDiskAreas被重复调用,位置在虚拟磁盘中不断移动。这是因为对于大型的虚拟磁盘,ChangedDiskArea数组中项可能会占用大量的内存。对于一个给定的changeId,一些磁盘区域可能没有发生改变。

changeId(即修改块ID)包含了一个形式为<UUID>/<nnn>的序列号数字。如果<UUID>改变了,跟踪信息就会无效,需要一个完整的备份。否则就可以继续进行增量备份。

故障排除

如果你重新配置虚拟机,并设置changeTrackingEnabled,但是这个属性仍然为false,在调用VirtualMachine->reconfigure()重新配置后,使用VirtualMachine->config()检查虚拟机的状态,而不是在重新配置之前。还需要保证虚拟机的硬件版本大于等于7,以及在重新配置后有一个stun-unstun的过程。

修改块跟踪的限制

当硬件的版本是6或更早,或者物理兼容的RDM模式,或者虚拟磁盘附加到一个共享的虚拟SCSI总线时,无法使用修改块跟踪。ESX/ESXi 3.5最高只支持硬件版本4.

这些虚拟机上也可以启用修改块跟踪,但是当请求修改ID时,这些磁盘总是返回一个空字符串。所以,如果你有一个虚拟机,包含一个常规的系统磁盘,以及一个直直通型RDM数据磁盘,你仅仅能够在系统磁盘上跟踪修改。

命名空间检查

通过检查XML文件中的命名空间,可以避免在ESX/ESXi 3.5存储上调用QueryChangedDiskAreas。对于已经打包的方法,请查看这些SDK代码示例:

Axis/java/com/vmware/samples/version/displaynewpropertieshost/DisplayNewPropertiesHostV25.java

Axis/java/com/vmware/samples/version/getvirtualdiskfiles/GetVirtualDiskFilesV25.java

DotNet/cs/DisplayNewProperties/DisplayNewPropertiesV25.cs

DotNet/cs/GetVirtualDiskFiles/GetVirtualDiskFilesV25.cs