備份過程的底層實現
本節描述的內容將會用於編寫備份應用的底層實現細節,這些內容並不用於強化設計,僅僅作爲示例和論述的指引。下面提供的示例代碼並不完整,它們通常沒有處理錯誤,並忽略了關鍵的細節。
連接服務器
服務器的連接需要憑據:用戶名、密碼、主機名(或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數據”一節提到的那樣,這些參數包括PropertySpec和ObjectSpec。PropertySpec是一個期望信息的列表,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以外無法獲取更多的內容。你可以使用託管對象的moRef和PropertySpec獲取詳細信息。這會在“理解PropertySpec”一節中講述。
TraversalSpec繼承自 SelectionSpec,作爲ObjectSpec的一個屬性,包含以下元素:
Path —對象中包含的用於引導遍歷的元素。
SelectSet —包含SelectionSpec或者TraversalSpec元素的數組。
Skip —是否鍋爐Path元素中的對象。
Type —引用的對象的類型。
Name —繼承自SelectionSpec,用於引用TraversalSpec的可選名稱。
SelectionSpec和TraversalSpec作爲遍歷的指向目標。遞歸發生在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是一些獨立的屬性的列表,這些屬性可以再ObjectSpec和TraversalSpec標明的地方找到。當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獲取數據
現在我們已經定義了ObjectSpec和PropertySpec(哪裏和哪些),我們需要將它們放入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,包含感興趣的項的moRef。PropertySpec包含一些你想要獲得的屬性,然後使用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設爲“*”纔是有意義的。如果存在一個ChangeId,QueryChangedDiskAreas會返回最新分配的ChangeId以來修改的磁盤扇區。表7-3顯示了這種算法。
使用“*”請求判斷虛擬磁盤的分配區域時,存在下面的限制:
磁盤必須存放在VMFS卷傷上(backing屬性沒有要求)。
當修改塊跟蹤啓用時,虛擬機必須沒有快照存在。
啓用修改塊跟蹤
默認情況下這個功能是禁用的,因爲它會引起一個很小但是可測量到的性能降低。如果你請求虛擬機配置,就可以判斷修改塊跟蹤是否啓用。使用PropertyCollector從VirutalMachineManagedObject中獲取這個屬性域。如果這個域包含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 LUN,vSphere無法跟蹤它。
如果客戶機系統填寫了虛擬磁盤的每個塊(長格式化或者安全擦除),或者虛擬磁盤是厚且渴望歸零的磁盤(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