github.com/vmware/govmomi@v0.43.0/vmdk/disk_info_test.go (about) 1 /* 2 Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package vmdk_test 18 19 import ( 20 "bytes" 21 "context" 22 "os" 23 "path" 24 "strings" 25 "testing" 26 27 "github.com/stretchr/testify/assert" 28 29 "github.com/vmware/govmomi/find" 30 "github.com/vmware/govmomi/object" 31 "github.com/vmware/govmomi/simulator" 32 "github.com/vmware/govmomi/vim25" 33 "github.com/vmware/govmomi/vim25/methods" 34 "github.com/vmware/govmomi/vim25/mo" 35 "github.com/vmware/govmomi/vim25/soap" 36 "github.com/vmware/govmomi/vim25/types" 37 "github.com/vmware/govmomi/vmdk" 38 ) 39 40 func TestGetVirtualDiskInfoByUUID(t *testing.T) { 41 42 type testCase struct { 43 name string 44 ctx context.Context 45 client *vim25.Client 46 mo mo.VirtualMachine 47 fetchProperties bool 48 diskUUID string 49 diskInfo vmdk.VirtualDiskInfo 50 err string 51 } 52 53 t.Run("w cached properties", func(t *testing.T) { 54 55 const ( 56 deviceKey = 1000 57 diskUUID = "123" 58 fileName = "[datastore] path/to.vmdk" 59 tenGiBInBytes = 10 * 1024 * 1024 * 1024 60 ) 61 62 getDisk := func(backing types.BaseVirtualDeviceBackingInfo) *types.VirtualDisk { 63 return &types.VirtualDisk{ 64 VirtualDevice: types.VirtualDevice{ 65 Key: deviceKey, 66 Backing: backing, 67 }, 68 CapacityInBytes: tenGiBInBytes, 69 } 70 } 71 72 getDiskInfo := func() vmdk.VirtualDiskInfo { 73 return vmdk.VirtualDiskInfo{ 74 CapacityInBytes: tenGiBInBytes, 75 DeviceKey: deviceKey, 76 FileName: fileName, 77 Size: (1 * 1024 * 1024 * 1024) + 950, 78 UniqueSize: (5 * 1024 * 1024) + 100, 79 } 80 } 81 82 getLayoutEx := func() *types.VirtualMachineFileLayoutEx { 83 return &types.VirtualMachineFileLayoutEx{ 84 Disk: []types.VirtualMachineFileLayoutExDiskLayout{ 85 { 86 Key: 1000, 87 Chain: []types.VirtualMachineFileLayoutExDiskUnit{ 88 { 89 FileKey: []int32{ 90 4, 91 5, 92 }, 93 }, 94 }, 95 }, 96 }, 97 File: []types.VirtualMachineFileLayoutExFileInfo{ 98 { 99 Key: 4, 100 Size: 1 * 1024 * 1024 * 1024, // 1 GiB 101 UniqueSize: 5 * 1024 * 1024, // 500 MiB 102 }, 103 { 104 Key: 5, 105 Size: 950, 106 UniqueSize: 100, 107 }, 108 }, 109 } 110 } 111 112 testCases := []testCase{ 113 { 114 name: "diskUUID is empty", 115 err: "diskUUID is empty", 116 }, 117 { 118 name: "no matching disks", 119 mo: mo.VirtualMachine{ 120 Config: &types.VirtualMachineConfigInfo{ 121 Hardware: types.VirtualHardware{ 122 Device: []types.BaseVirtualDevice{}, 123 }, 124 }, 125 LayoutEx: &types.VirtualMachineFileLayoutEx{ 126 File: []types.VirtualMachineFileLayoutExFileInfo{}, 127 Disk: []types.VirtualMachineFileLayoutExDiskLayout{}, 128 }, 129 }, 130 diskUUID: diskUUID, 131 err: "disk not found with uuid \"123\"", 132 }, 133 { 134 name: "one disk w VirtualDiskFlatVer2BackingInfo", 135 mo: mo.VirtualMachine{ 136 Config: &types.VirtualMachineConfigInfo{ 137 Hardware: types.VirtualHardware{ 138 Device: []types.BaseVirtualDevice{ 139 getDisk(&types.VirtualDiskFlatVer2BackingInfo{ 140 VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{ 141 FileName: fileName, 142 }, 143 Uuid: diskUUID, 144 }), 145 }, 146 }, 147 }, 148 LayoutEx: getLayoutEx(), 149 }, 150 diskUUID: diskUUID, 151 diskInfo: getDiskInfo(), 152 }, 153 { 154 name: "one disk w VirtualDiskSeSparseBackingInfo", 155 mo: mo.VirtualMachine{ 156 Config: &types.VirtualMachineConfigInfo{ 157 Hardware: types.VirtualHardware{ 158 Device: []types.BaseVirtualDevice{ 159 getDisk(&types.VirtualDiskSeSparseBackingInfo{ 160 VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{ 161 FileName: fileName, 162 }, 163 Uuid: diskUUID, 164 }), 165 }, 166 }, 167 }, 168 LayoutEx: getLayoutEx(), 169 }, 170 diskUUID: diskUUID, 171 diskInfo: getDiskInfo(), 172 }, 173 { 174 name: "one disk w VirtualDiskRawDiskMappingVer1BackingInfo", 175 mo: mo.VirtualMachine{ 176 Config: &types.VirtualMachineConfigInfo{ 177 Hardware: types.VirtualHardware{ 178 Device: []types.BaseVirtualDevice{ 179 getDisk(&types.VirtualDiskRawDiskMappingVer1BackingInfo{ 180 VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{ 181 FileName: fileName, 182 }, 183 Uuid: diskUUID, 184 }), 185 }, 186 }, 187 }, 188 LayoutEx: getLayoutEx(), 189 }, 190 diskUUID: diskUUID, 191 diskInfo: getDiskInfo(), 192 }, 193 { 194 name: "one disk w VirtualDiskSparseVer2BackingInfo", 195 mo: mo.VirtualMachine{ 196 Config: &types.VirtualMachineConfigInfo{ 197 Hardware: types.VirtualHardware{ 198 Device: []types.BaseVirtualDevice{ 199 getDisk(&types.VirtualDiskSparseVer2BackingInfo{ 200 VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{ 201 FileName: fileName, 202 }, 203 Uuid: diskUUID, 204 }), 205 }, 206 }, 207 }, 208 LayoutEx: getLayoutEx(), 209 }, 210 diskUUID: diskUUID, 211 diskInfo: getDiskInfo(), 212 }, 213 { 214 name: "one disk w VirtualDiskRawDiskVer2BackingInfo", 215 mo: mo.VirtualMachine{ 216 Config: &types.VirtualMachineConfigInfo{ 217 Hardware: types.VirtualHardware{ 218 Device: []types.BaseVirtualDevice{ 219 getDisk(&types.VirtualDiskRawDiskVer2BackingInfo{ 220 DescriptorFileName: fileName, 221 Uuid: diskUUID, 222 }), 223 }, 224 }, 225 }, 226 LayoutEx: getLayoutEx(), 227 }, 228 diskUUID: diskUUID, 229 diskInfo: getDiskInfo(), 230 }, 231 { 232 name: "one disk w multiple chain entries", 233 mo: mo.VirtualMachine{ 234 Config: &types.VirtualMachineConfigInfo{ 235 Hardware: types.VirtualHardware{ 236 Device: []types.BaseVirtualDevice{ 237 getDisk(&types.VirtualDiskFlatVer2BackingInfo{ 238 VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{ 239 FileName: fileName, 240 }, 241 Uuid: diskUUID, 242 }), 243 }, 244 }, 245 }, 246 LayoutEx: &types.VirtualMachineFileLayoutEx{ 247 Disk: []types.VirtualMachineFileLayoutExDiskLayout{ 248 { 249 Key: deviceKey, 250 Chain: []types.VirtualMachineFileLayoutExDiskUnit{ 251 { 252 FileKey: []int32{ 253 4, 254 5, 255 }, 256 }, 257 { 258 FileKey: []int32{ 259 6, 260 7, 261 }, 262 }, 263 { 264 FileKey: []int32{ 265 8, 266 }, 267 }, 268 }, 269 }, 270 }, 271 File: []types.VirtualMachineFileLayoutExFileInfo{ 272 { 273 Key: 4, 274 Size: 1 * 1024 * 1024 * 1024, // 1 GiB 275 UniqueSize: 5 * 1024 * 1024, // 500 MiB 276 }, 277 { 278 Key: 5, 279 Size: 950, 280 UniqueSize: 100, 281 }, 282 { 283 Key: 6, 284 Size: 500, 285 UniqueSize: 100, 286 }, 287 { 288 Key: 7, 289 Size: 500, 290 UniqueSize: 200, 291 }, 292 { 293 Key: 8, 294 Size: 1000, 295 UniqueSize: 300, 296 }, 297 }, 298 }, 299 }, 300 diskUUID: diskUUID, 301 diskInfo: vmdk.VirtualDiskInfo{ 302 CapacityInBytes: tenGiBInBytes, 303 DeviceKey: deviceKey, 304 FileName: fileName, 305 Size: (1 * 1024 * 1024 * 1024) + 950 + 500 + 500 + 1000, 306 UniqueSize: (5 * 1024 * 1024) + 100 + 100 + 200 + 300, 307 }, 308 }, 309 } 310 311 for i := range testCases { 312 tc := testCases[i] 313 t.Run(tc.name, func(t *testing.T) { 314 dii, err := vmdk.GetVirtualDiskInfoByUUID( 315 nil, nil, tc.mo, false, tc.diskUUID) 316 317 if tc.err != "" { 318 assert.EqualError(t, err, tc.err) 319 } 320 assert.Equal(t, tc.diskInfo, dii) 321 }) 322 } 323 }) 324 325 t.Run("fetch properties", func(t *testing.T) { 326 simulator.Test(func(ctx context.Context, c *vim25.Client) { 327 328 pc := &propertyCollectorWithFault{} 329 pc.Self = c.ServiceContent.PropertyCollector 330 simulator.Map.Put(pc) 331 332 finder := find.NewFinder(c, true) 333 datacenter, err := finder.DefaultDatacenter(ctx) 334 if err != nil { 335 t.Fatalf("default datacenter not found: %s", err) 336 } 337 finder.SetDatacenter(datacenter) 338 vmList, err := finder.VirtualMachineList(ctx, "*") 339 if len(vmList) == 0 { 340 t.Fatal("vmList == 0") 341 } 342 vm := vmList[0] 343 344 var moVM mo.VirtualMachine 345 if err := vm.Properties( 346 ctx, 347 vm.Reference(), 348 []string{"config", "layoutEx"}, 349 &moVM); err != nil { 350 351 t.Fatal(err) 352 } 353 354 devs := object.VirtualDeviceList(moVM.Config.Hardware.Device) 355 disks := devs.SelectByType(&types.VirtualDisk{}) 356 if len(disks) == 0 { 357 t.Fatal("disks == 0") 358 } 359 360 var ( 361 diskUUID string 362 datastoreRef types.ManagedObjectReference 363 disk = disks[0].(*types.VirtualDisk) 364 diskBacking = disk.Backing 365 diskInfo = vmdk.VirtualDiskInfo{ 366 CapacityInBytes: disk.CapacityInBytes, 367 DeviceKey: disk.Key, 368 } 369 ) 370 371 switch tb := disk.Backing.(type) { 372 case *types.VirtualDiskFlatVer2BackingInfo: 373 diskUUID = tb.Uuid 374 diskInfo.FileName = tb.FileName 375 datastoreRef = *tb.Datastore 376 default: 377 t.Fatalf("unsupported disk backing: %T", disk.Backing) 378 } 379 380 datastore := object.NewDatastore(c, datastoreRef) 381 var moDatastore mo.Datastore 382 if err := datastore.Properties( 383 ctx, 384 datastore.Reference(), 385 []string{"info"}, 386 &moDatastore); err != nil { 387 388 t.Fatal(err) 389 } 390 391 datastorePath := moDatastore.Info.GetDatastoreInfo().Url 392 var diskPath object.DatastorePath 393 if !diskPath.FromString(diskInfo.FileName) { 394 t.Fatalf("invalid disk file name: %q", diskInfo.FileName) 395 } 396 397 const vmdkSize = 500 398 399 assert.NoError(t, os.WriteFile( 400 path.Join(datastorePath, diskPath.Path), 401 bytes.Repeat([]byte{1}, vmdkSize), 402 os.ModeAppend)) 403 assert.NoError(t, vm.RefreshStorageInfo(ctx)) 404 405 diskInfo.Size = vmdkSize 406 diskInfo.UniqueSize = vmdkSize 407 408 testCases := []testCase{ 409 { 410 name: "ctx is nil", 411 ctx: nil, 412 client: c, 413 diskUUID: diskUUID, 414 err: "ctx is nil", 415 }, 416 { 417 name: "client is nil", 418 ctx: context.Background(), 419 client: nil, 420 diskUUID: diskUUID, 421 err: "client is nil", 422 }, 423 { 424 name: "failed to retrieve properties", 425 ctx: context.Background(), 426 client: c, 427 diskUUID: diskUUID, 428 mo: mo.VirtualMachine{ 429 ManagedEntity: mo.ManagedEntity{ 430 ExtensibleManagedObject: mo.ExtensibleManagedObject{ 431 Self: vm.Reference(), 432 }, 433 }, 434 }, 435 err: "failed to retrieve properties: ServerFaultCode: InvalidArgument", 436 }, 437 { 438 name: "fetchProperties is false but cached properties are missing", 439 ctx: context.Background(), 440 client: c, 441 diskUUID: diskUUID, 442 diskInfo: diskInfo, 443 fetchProperties: false, 444 mo: mo.VirtualMachine{ 445 ManagedEntity: mo.ManagedEntity{ 446 ExtensibleManagedObject: mo.ExtensibleManagedObject{ 447 Self: vm.Reference(), 448 }, 449 }, 450 }, 451 }, 452 { 453 name: "fetchProperties is true and cached properties are stale", 454 ctx: context.Background(), 455 client: c, 456 diskUUID: diskUUID, 457 diskInfo: diskInfo, 458 fetchProperties: true, 459 mo: mo.VirtualMachine{ 460 ManagedEntity: mo.ManagedEntity{ 461 ExtensibleManagedObject: mo.ExtensibleManagedObject{ 462 Self: vm.Reference(), 463 }, 464 }, 465 Config: &types.VirtualMachineConfigInfo{ 466 Hardware: types.VirtualHardware{ 467 Device: []types.BaseVirtualDevice{ 468 &types.VirtualDisk{ 469 VirtualDevice: types.VirtualDevice{ 470 Key: diskInfo.DeviceKey, 471 Backing: diskBacking, 472 }, 473 CapacityInBytes: 500, 474 }, 475 }, 476 }, 477 }, 478 }, 479 }, 480 } 481 482 for i := range testCases { 483 tc := testCases[i] 484 t.Run(tc.name, func(t *testing.T) { 485 if strings.HasPrefix(tc.err, "failed to retrieve properties:") { 486 propertyCollectorShouldFault = true 487 defer func() { 488 propertyCollectorShouldFault = false 489 }() 490 } 491 492 dii, err := vmdk.GetVirtualDiskInfoByUUID( 493 tc.ctx, 494 tc.client, 495 tc.mo, 496 tc.fetchProperties, 497 tc.diskUUID) 498 499 if tc.err != "" { 500 assert.EqualError(t, err, tc.err) 501 } 502 assert.Equal(t, tc.diskInfo, dii) 503 }) 504 } 505 506 }) 507 }) 508 } 509 510 var propertyCollectorShouldFault bool 511 512 type propertyCollectorWithFault struct { 513 simulator.PropertyCollector 514 } 515 516 func (pc *propertyCollectorWithFault) RetrievePropertiesEx( 517 ctx *simulator.Context, 518 req *types.RetrievePropertiesEx) soap.HasFault { 519 520 if propertyCollectorShouldFault { 521 return &methods.RetrievePropertiesExBody{ 522 Fault_: simulator.Fault("", &types.InvalidArgument{}), 523 } 524 } 525 526 return pc.PropertyCollector.RetrievePropertiesEx(ctx, req) 527 }