github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/vsphere/internal/vsphereclient/createvm_test.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package vsphereclient 5 6 import ( 7 "bytes" 8 "fmt" 9 "io" 10 "net/http" 11 "time" 12 13 "github.com/juju/clock/testclock" 14 "github.com/juju/testing" 15 jc "github.com/juju/testing/checkers" 16 "github.com/vmware/govmomi/object" 17 "github.com/vmware/govmomi/vim25/mo" 18 "github.com/vmware/govmomi/vim25/types" 19 "golang.org/x/net/context" 20 gc "gopkg.in/check.v1" 21 22 "github.com/juju/juju/core/constraints" 23 "github.com/juju/juju/provider/vsphere/internal/ovatest" 24 coretesting "github.com/juju/juju/testing" 25 ) 26 27 func (s *clientSuite) TestCreateTemplateVM(c *gc.C) { 28 var statusUpdates []string 29 statusUpdatesCh := make(chan string, 4) 30 dequeueStatusUpdates := func() { 31 for { 32 select { 33 case <-statusUpdatesCh: 34 default: 35 return 36 } 37 } 38 } 39 client := s.newFakeClient(&s.roundTripper, "dc0") 40 args := baseImportOVAParameters(c, client) 41 testClock := args.StatusUpdateParams.Clock.(*testclock.Clock) 42 s.onImageUpload = func(r *http.Request) { 43 dequeueStatusUpdates() 44 45 // Wait 1.5 seconds, which is long enough to trigger the 46 // status update timer. 47 testClock.WaitAdvance(1500*time.Millisecond, coretesting.LongWait, 1) 48 49 // Waiting for the status update here guarantees that a report is 50 // available, since we don't update status until that is true. 51 <-statusUpdatesCh 52 53 s.onImageUpload = nil 54 } 55 args.StatusUpdateParams.UpdateProgress = func(status string) { 56 statusUpdatesCh <- status 57 statusUpdates = append(statusUpdates, status) 58 } 59 60 _, err := client.CreateTemplateVM(context.Background(), args) 61 c.Assert(err, jc.ErrorIsNil) 62 c.Assert(statusUpdates, jc.DeepEquals, []string{ 63 fmt.Sprintf(`creating template VM "juju-template-%s"`, args.OVASHA256), 64 "streaming vmdk: 100.00% (0B/s)", 65 }) 66 c.Assert(s.uploadRequests, gc.HasLen, 1) 67 contents, err := io.ReadAll(s.uploadRequests[0].Body) 68 c.Assert(err, jc.ErrorIsNil) 69 c.Assert(string(contents), gc.Equals, "FakeVmdkContent") 70 71 templateCisp := baseCisp() 72 templateCisp.EntityName = args.TemplateName 73 s.roundTripper.CheckCalls(c, []testing.StubCall{ 74 {FuncName: "CreateImportSpec", Args: []interface{}{ 75 UbuntuOVF, 76 types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"}, 77 templateCisp, 78 }}, 79 {FuncName: "ImportVApp", Args: []interface{}{ 80 &types.VirtualMachineImportSpec{ 81 ConfigSpec: types.VirtualMachineConfigSpec{ 82 Name: "vm-name", 83 }, 84 }, 85 }}, 86 {FuncName: "CreatePropertyCollector", Args: nil}, 87 {FuncName: "CreateFilter", Args: nil}, 88 {FuncName: "WaitForUpdatesEx", Args: nil}, 89 {FuncName: "HttpNfcLeaseComplete", Args: []interface{}{"FakeLease"}}, 90 {FuncName: "ReconfigVM_Task", Args: []interface{}{ 91 types.VirtualMachineConfigSpec{ 92 ExtraConfig: []types.BaseOptionValue{ 93 &types.OptionValue{Key: ArchTag, Value: "amd64"}, 94 }, 95 }, 96 }}, 97 {FuncName: "CreatePropertyCollector", Args: nil}, 98 {FuncName: "CreateFilter", Args: nil}, 99 {FuncName: "WaitForUpdatesEx", Args: nil}, 100 {FuncName: "MarkAsTemplate", Args: []interface{}{"FakeVm0"}}, 101 }) 102 } 103 104 func (s *clientSuite) TestCreateVirtualMachine(c *gc.C) { 105 var statusUpdates []string 106 statusUpdatesCh := make(chan string, 4) 107 dequeueStatusUpdates := func() { 108 for { 109 select { 110 case <-statusUpdatesCh: 111 default: 112 return 113 } 114 } 115 } 116 client := s.newFakeClient(&s.roundTripper, "dc0") 117 118 args := baseCreateVirtualMachineParams(c, client) 119 testClock := args.StatusUpdateParams.Clock.(*testclock.Clock) 120 s.onImageUpload = func(r *http.Request) { 121 dequeueStatusUpdates() 122 123 // Wait 1.5 seconds, which is long enough to trigger the 124 // status update timer. 125 testClock.WaitAdvance(1500*time.Millisecond, coretesting.LongWait, 1) 126 127 // Waiting for the status update here guarantees that a report is 128 // available, since we don't update status until that is true. 129 <-statusUpdatesCh 130 131 s.onImageUpload = nil 132 } 133 args.StatusUpdateParams.UpdateProgress = func(status string) { 134 statusUpdatesCh <- status 135 statusUpdates = append(statusUpdates, status) 136 } 137 138 _, err := client.CreateVirtualMachine(context.Background(), args) 139 c.Assert(err, jc.ErrorIsNil) 140 c.Assert(statusUpdates, jc.DeepEquals, []string{ 141 "cloning template", 142 "VM cloned", 143 "powering on", 144 }) 145 146 datastore := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"} 147 s.roundTripper.CheckCalls(c, []testing.StubCall{ 148 retrievePropertiesStubCall("FakeRootFolder"), 149 retrievePropertiesStubCall("FakeRootFolder"), 150 retrievePropertiesStubCall("FakeRootFolder"), 151 retrievePropertiesStubCall("FakeRootFolder"), 152 retrievePropertiesStubCall("FakeDatacenter"), 153 retrievePropertiesStubCall("FakeRootFolder"), 154 retrievePropertiesStubCall("FakeDatacenter"), 155 retrievePropertiesStubCall("FakeVmFolder"), 156 retrievePropertiesStubCall("FakeHostFolder"), 157 retrievePropertiesStubCall("network-0", "network-1"), 158 retrievePropertiesStubCall("onetwork-0"), 159 retrievePropertiesStubCall("dvportgroup-0"), 160 retrievePropertiesStubCall("FakeVm0"), 161 retrievePropertiesStubCall("FakeVm0"), 162 {FuncName: "CloneVM_Task", Args: []interface{}{ 163 types.ManagedObjectReference{ 164 Type: "Folder", Value: "FakeControllerVmFolder", 165 }, 166 "vm-0", 167 &types.VirtualMachineConfigSpec{ 168 ExtraConfig: []types.BaseOptionValue{ 169 &types.OptionValue{Key: "k", Value: "v"}, 170 }, 171 Flags: &types.VirtualMachineFlagInfo{ 172 DiskUuidEnabled: newBool(true), 173 }, 174 VAppConfig: &types.VmConfigSpec{ 175 Property: []types.VAppPropertySpec{{ 176 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 177 Info: &types.VAppPropertyInfo{Key: 1, Value: "vm-0"}, 178 }, { 179 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 180 Info: &types.VAppPropertyInfo{Key: 4, Value: "baz"}, 181 }}, 182 }, 183 }, 184 types.VirtualMachineRelocateSpec{ 185 Pool: &args.ResourcePool, 186 Datastore: &datastore, 187 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 188 { 189 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{ 190 EagerlyScrub: newBool(false), 191 ThinProvisioned: newBool(true), 192 }, 193 DiskId: 0, 194 Datastore: datastore, 195 }, 196 }, 197 }, 198 }}, 199 {FuncName: "CreatePropertyCollector", Args: nil}, 200 {FuncName: "CreateFilter", Args: nil}, 201 {FuncName: "WaitForUpdatesEx", Args: nil}, 202 {FuncName: "PowerOnVM_Task", Args: nil}, 203 {FuncName: "CreatePropertyCollector", Args: nil}, 204 {FuncName: "CreateFilter", Args: nil}, 205 {FuncName: "WaitForUpdatesEx", Args: nil}, 206 retrievePropertiesStubCall(""), 207 }) 208 } 209 210 func (s *clientSuite) TestCreateVirtualMachineForceHWVersion(c *gc.C) { 211 client := s.newFakeClient(&s.roundTripper, "dc0") 212 args := baseCreateVirtualMachineParams(c, client) 213 args.ForceVMHardwareVersion = 11 214 args.ComputeResource.EnvironmentBrowser = &types.ManagedObjectReference{ 215 Type: "EnvironmentBrowser", 216 Value: "FakeEnvironmentBrowser", 217 } 218 _, err := client.CreateVirtualMachine(context.Background(), args) 219 c.Assert(err, jc.ErrorIsNil) 220 221 s.roundTripper.CheckCall(c, 18, "RetrieveProperties", "FakeVm1") 222 s.roundTripper.CheckCall(c, 19, "QueryConfigOption", "FakeEnvironmentBrowser") 223 // Mock server max version is vmx-13 224 // Mock template VM version is vmx-10 225 // We requested vmx-11. This should match the call to UpgradeVM_Task. 226 s.roundTripper.CheckCall(c, 20, "UpgradeVM_Task", "vmx-11") 227 } 228 229 func (s *clientSuite) TestCreateVirtualMachineNoDiskUUID(c *gc.C) { 230 client := s.newFakeClient(&s.roundTripper, "dc0") 231 args := baseCreateVirtualMachineParams(c, client) 232 args.EnableDiskUUID = false 233 _, err := client.CreateVirtualMachine(context.Background(), args) 234 c.Assert(err, jc.ErrorIsNil) 235 236 datastore := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"} 237 s.roundTripper.CheckCall( 238 c, 14, "CloneVM_Task", 239 types.ManagedObjectReference{ 240 Type: "Folder", Value: "FakeControllerVmFolder", 241 }, 242 "vm-0", &types.VirtualMachineConfigSpec{ 243 ExtraConfig: []types.BaseOptionValue{ 244 &types.OptionValue{Key: "k", Value: "v"}, 245 }, 246 Flags: &types.VirtualMachineFlagInfo{DiskUuidEnabled: newBool(args.EnableDiskUUID)}, 247 VAppConfig: &types.VmConfigSpec{ 248 Property: []types.VAppPropertySpec{{ 249 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 250 Info: &types.VAppPropertyInfo{Key: 1, Value: "vm-0"}, 251 }, { 252 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 253 Info: &types.VAppPropertyInfo{Key: 4, Value: "baz"}, 254 }}, 255 }, 256 }, types.VirtualMachineRelocateSpec{ 257 Pool: &args.ResourcePool, 258 Datastore: &datastore, 259 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 260 { 261 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{ 262 EagerlyScrub: newBool(false), 263 ThinProvisioned: newBool(true), 264 }, 265 DiskId: 0, 266 Datastore: datastore, 267 }, 268 }, 269 }) 270 } 271 272 func (s *clientSuite) TestCreateVirtualMachineThickDiskProvisioning(c *gc.C) { 273 client := s.newFakeClient(&s.roundTripper, "dc0") 274 args := baseCreateVirtualMachineParams(c, client) 275 args.DiskProvisioningType = DiskTypeThickLazyZero 276 _, err := client.CreateVirtualMachine(context.Background(), args) 277 c.Assert(err, jc.ErrorIsNil) 278 279 datastore := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"} 280 281 s.roundTripper.CheckCall( 282 c, 14, "CloneVM_Task", 283 types.ManagedObjectReference{ 284 Type: "Folder", Value: "FakeControllerVmFolder", 285 }, 286 "vm-0", &types.VirtualMachineConfigSpec{ 287 ExtraConfig: []types.BaseOptionValue{ 288 &types.OptionValue{Key: "k", Value: "v"}, 289 }, 290 Flags: &types.VirtualMachineFlagInfo{DiskUuidEnabled: newBool(true)}, 291 VAppConfig: &types.VmConfigSpec{ 292 Property: []types.VAppPropertySpec{{ 293 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 294 Info: &types.VAppPropertyInfo{Key: 1, Value: "vm-0"}, 295 }, { 296 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 297 Info: &types.VAppPropertyInfo{Key: 4, Value: "baz"}, 298 }}, 299 }, 300 }, types.VirtualMachineRelocateSpec{ 301 Pool: &args.ResourcePool, 302 Datastore: &datastore, 303 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 304 { 305 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{ 306 // Thick disk provisioning, lazy zeros 307 EagerlyScrub: newBool(false), 308 ThinProvisioned: newBool(false), 309 }, 310 DiskId: 0, 311 Datastore: datastore, 312 }, 313 }, 314 }) 315 } 316 317 func (s *clientSuite) TestCreateVirtualMachineThickEagerZeroDiskProvisioning(c *gc.C) { 318 client := s.newFakeClient(&s.roundTripper, "dc0") 319 args := baseCreateVirtualMachineParams(c, client) 320 args.DiskProvisioningType = DiskTypeThick 321 322 _, err := client.CreateVirtualMachine(context.Background(), args) 323 c.Assert(err, jc.ErrorIsNil) 324 325 datastore := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"} 326 327 s.roundTripper.CheckCall( 328 c, 14, "CloneVM_Task", 329 types.ManagedObjectReference{ 330 Type: "Folder", Value: "FakeControllerVmFolder", 331 }, 332 "vm-0", &types.VirtualMachineConfigSpec{ 333 ExtraConfig: []types.BaseOptionValue{ 334 &types.OptionValue{Key: "k", Value: "v"}, 335 }, 336 Flags: &types.VirtualMachineFlagInfo{DiskUuidEnabled: newBool(true)}, 337 VAppConfig: &types.VmConfigSpec{ 338 Property: []types.VAppPropertySpec{{ 339 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 340 Info: &types.VAppPropertyInfo{Key: 1, Value: "vm-0"}, 341 }, { 342 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 343 Info: &types.VAppPropertyInfo{Key: 4, Value: "baz"}, 344 }}, 345 }, 346 }, types.VirtualMachineRelocateSpec{ 347 Pool: &args.ResourcePool, 348 Datastore: &datastore, 349 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 350 { 351 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{ 352 // Thick disk provisioning, eager zeros 353 EagerlyScrub: newBool(true), 354 ThinProvisioned: newBool(false), 355 }, 356 DiskId: 0, 357 Datastore: datastore, 358 }, 359 }, 360 }) 361 } 362 363 func (s *clientSuite) TestCreateVirtualMachineThinDiskProvisioning(c *gc.C) { 364 client := s.newFakeClient(&s.roundTripper, "dc0") 365 args := baseCreateVirtualMachineParams(c, client) 366 args.DiskProvisioningType = DiskTypeThin 367 368 _, err := client.CreateVirtualMachine(context.Background(), args) 369 c.Assert(err, jc.ErrorIsNil) 370 371 datastore := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"} 372 373 s.roundTripper.CheckCall(c, 14, "CloneVM_Task", types.ManagedObjectReference{Type: "Folder", Value: "FakeControllerVmFolder"}, "vm-0", &types.VirtualMachineConfigSpec{ 374 ExtraConfig: []types.BaseOptionValue{ 375 &types.OptionValue{Key: "k", Value: "v"}, 376 }, 377 Flags: &types.VirtualMachineFlagInfo{DiskUuidEnabled: newBool(true)}, 378 VAppConfig: &types.VmConfigSpec{ 379 Property: []types.VAppPropertySpec{{ 380 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 381 Info: &types.VAppPropertyInfo{Key: 1, Value: "vm-0"}, 382 }, { 383 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 384 Info: &types.VAppPropertyInfo{Key: 4, Value: "baz"}, 385 }}, 386 }, 387 }, types.VirtualMachineRelocateSpec{ 388 Pool: &args.ResourcePool, 389 Datastore: &datastore, 390 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 391 { 392 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{ 393 // Thin disk provisioning 394 EagerlyScrub: newBool(false), 395 ThinProvisioned: newBool(true), 396 }, 397 DiskId: 0, 398 Datastore: datastore, 399 }, 400 }, 401 }) 402 } 403 404 func (s *clientSuite) TestCreateVirtualMachineDatastoreSpecified(c *gc.C) { 405 client := s.newFakeClient(&s.roundTripper, "dc0") 406 args := baseCreateVirtualMachineParams(c, client) 407 datastore := "datastore1" 408 args.Constraints.RootDiskSource = &datastore 409 args.ComputeResource.Datastore = []types.ManagedObjectReference{{ 410 Type: "Datastore", 411 Value: "FakeDatastore2", 412 }, { 413 Type: "Datastore", 414 Value: "FakeDatastore1", 415 }} 416 417 _, err := client.CreateVirtualMachine(context.Background(), args) 418 c.Assert(err, jc.ErrorIsNil) 419 420 datastoreLocation := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"} 421 s.roundTripper.CheckCall( 422 c, 14, "CloneVM_Task", types.ManagedObjectReference{ 423 Type: "Folder", Value: "FakeControllerVmFolder", 424 }, 425 "vm-0", &types.VirtualMachineConfigSpec{ 426 ExtraConfig: []types.BaseOptionValue{ 427 &types.OptionValue{Key: "k", Value: "v"}, 428 }, 429 Flags: &types.VirtualMachineFlagInfo{DiskUuidEnabled: newBool(true)}, 430 VAppConfig: &types.VmConfigSpec{ 431 Property: []types.VAppPropertySpec{{ 432 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 433 Info: &types.VAppPropertyInfo{Key: 1, Value: "vm-0"}, 434 }, { 435 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 436 Info: &types.VAppPropertyInfo{Key: 4, Value: "baz"}, 437 }}, 438 }, 439 }, types.VirtualMachineRelocateSpec{ 440 Pool: &args.ResourcePool, 441 Datastore: &datastoreLocation, 442 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 443 { 444 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{ 445 EagerlyScrub: newBool(false), 446 ThinProvisioned: newBool(true), 447 }, 448 DiskId: 0, 449 Datastore: datastoreLocation, 450 }, 451 }, 452 }) 453 } 454 455 func (s *clientSuite) TestGetTargetDatastoreDatastoreNotFound(c *gc.C) { 456 client := s.newFakeClient(&s.roundTripper, "dc0") 457 args := baseCreateVirtualMachineParams(c, client) 458 datastore := "datastore3" 459 460 _, err := client.GetTargetDatastore(context.Background(), args.ComputeResource, datastore) 461 c.Assert(err, gc.ErrorMatches, `could not find datastore "datastore3", datastore\(s\) accessible: "datastore2"`) 462 } 463 464 func (s *clientSuite) TestGetTargetDatastoreDatastoreNoneAccessible(c *gc.C) { 465 client := s.newFakeClient(&s.roundTripper, "dc0") 466 args := baseCreateVirtualMachineParams(c, client) 467 args.ComputeResource.Datastore = []types.ManagedObjectReference{{ 468 Type: "Datastore", 469 Value: "FakeDatastore1", 470 }} 471 472 _, err := client.GetTargetDatastore(context.Background(), args.ComputeResource, args.Datastore.Name()) 473 c.Assert(err, gc.ErrorMatches, "no accessible datastores available") 474 } 475 476 func (s *clientSuite) TestGetTargetDatastoreDatastoreNotFoundWithMultipleAvailable(c *gc.C) { 477 client := s.newFakeClient(&s.roundTripper, "dc0") 478 args := baseCreateVirtualMachineParams(c, client) 479 datastore := "datastore3" 480 481 s.roundTripper.updateContents("FakeDatastore1", 482 []types.ObjectContent{{ 483 Obj: types.ManagedObjectReference{ 484 Type: "Datastore", 485 Value: "FakeDatastore1", 486 }, 487 PropSet: []types.DynamicProperty{ 488 {Name: "name", Val: "datastore1"}, 489 {Name: "summary.accessible", Val: true}, 490 }, 491 }}, 492 ) 493 494 _, err := client.GetTargetDatastore(context.Background(), args.ComputeResource, datastore) 495 c.Assert(err, gc.ErrorMatches, `could not find datastore "datastore3", datastore\(s\) accessible: "datastore1", "datastore2"`) 496 } 497 498 func (s *clientSuite) TestGetTargetDatastoreDatastoreNotFoundWithNoAvailable(c *gc.C) { 499 client := s.newFakeClient(&s.roundTripper, "dc0") 500 args := baseCreateVirtualMachineParams(c, client) 501 datastore := "datastore3" 502 503 s.roundTripper.updateContents("FakeDatastore2", 504 []types.ObjectContent{{ 505 Obj: types.ManagedObjectReference{ 506 Type: "Datastore", 507 Value: "FakeDatastore2", 508 }, 509 PropSet: []types.DynamicProperty{ 510 {Name: "name", Val: "datastore2"}, 511 {Name: "summary.accessible", Val: false}, 512 }, 513 }}, 514 ) 515 516 _, err := client.GetTargetDatastore(context.Background(), args.ComputeResource, datastore) 517 c.Assert(err, gc.ErrorMatches, `no accessible datastores available`) 518 } 519 520 func (s *clientSuite) TestCreateVirtualMachineMultipleNetworksSpecifiedFirstDefault(c *gc.C) { 521 client := s.newFakeClient(&s.roundTripper, "dc0") 522 args := baseCreateVirtualMachineParams(c, client) 523 args.NetworkDevices = []NetworkDevice{ 524 {MAC: "00:50:56:11:22:33"}, 525 {Network: "arpa"}, 526 } 527 528 _, err := client.CreateVirtualMachine(context.Background(), args) 529 c.Assert(err, jc.ErrorIsNil) 530 531 var networkDevice1, networkDevice2 types.VirtualVmxnet3 532 wakeOnLan := true 533 networkDevice1.Key = -1 534 networkDevice1.WakeOnLanEnabled = &wakeOnLan 535 networkDevice1.Connectable = &types.VirtualDeviceConnectInfo{ 536 StartConnected: true, 537 AllowGuestControl: true, 538 } 539 networkDevice1.AddressType = "Manual" 540 networkDevice1.MacAddress = "00:50:56:11:22:33" 541 networkDevice1.Backing = &types.VirtualEthernetCardNetworkBackingInfo{ 542 VirtualDeviceDeviceBackingInfo: types.VirtualDeviceDeviceBackingInfo{ 543 DeviceName: "VM Network", 544 }, 545 } 546 547 networkDevice2.Key = -2 548 networkDevice2.WakeOnLanEnabled = &wakeOnLan 549 networkDevice2.Connectable = &types.VirtualDeviceConnectInfo{ 550 StartConnected: true, 551 AllowGuestControl: true, 552 } 553 networkDevice2.Backing = &types.VirtualEthernetCardNetworkBackingInfo{ 554 VirtualDeviceDeviceBackingInfo: types.VirtualDeviceDeviceBackingInfo{ 555 DeviceName: "arpa", 556 }, 557 } 558 datastore := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"} 559 s.roundTripper.CheckCall( 560 c, 14, "CloneVM_Task", 561 types.ManagedObjectReference{ 562 Type: "Folder", Value: "FakeControllerVmFolder", 563 }, 564 "vm-0", &types.VirtualMachineConfigSpec{ 565 ExtraConfig: []types.BaseOptionValue{ 566 &types.OptionValue{Key: "k", Value: "v"}, 567 }, 568 DeviceChange: []types.BaseVirtualDeviceConfigSpec{ 569 &types.VirtualDeviceConfigSpec{ 570 Operation: "add", 571 Device: &networkDevice1, 572 }, 573 &types.VirtualDeviceConfigSpec{ 574 Operation: "add", 575 Device: &networkDevice2, 576 }, 577 }, 578 Flags: &types.VirtualMachineFlagInfo{DiskUuidEnabled: newBool(true)}, 579 VAppConfig: &types.VmConfigSpec{ 580 Property: []types.VAppPropertySpec{{ 581 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 582 Info: &types.VAppPropertyInfo{Key: 1, Value: "vm-0"}, 583 }, { 584 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 585 Info: &types.VAppPropertyInfo{Key: 4, Value: "baz"}, 586 }}, 587 }, 588 }, types.VirtualMachineRelocateSpec{ 589 Pool: &args.ResourcePool, 590 Datastore: &datastore, 591 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 592 { 593 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{ 594 EagerlyScrub: newBool(false), 595 ThinProvisioned: newBool(true), 596 }, 597 DiskId: 0, 598 Datastore: datastore, 599 }, 600 }, 601 }) 602 } 603 604 func (s *clientSuite) TestCreateVirtualMachineNetworkSpecifiedDVPortgroup(c *gc.C) { 605 client := s.newFakeClient(&s.roundTripper, "dc0") 606 args := baseCreateVirtualMachineParams(c, client) 607 args.NetworkDevices = []NetworkDevice{ 608 {Network: "yoink"}, 609 } 610 611 _, err := client.CreateVirtualMachine(context.Background(), args) 612 c.Assert(err, jc.ErrorIsNil) 613 614 var networkDevice types.VirtualVmxnet3 615 wakeOnLan := true 616 networkDevice.Key = -1 617 networkDevice.WakeOnLanEnabled = &wakeOnLan 618 networkDevice.Connectable = &types.VirtualDeviceConnectInfo{ 619 StartConnected: true, 620 AllowGuestControl: true, 621 } 622 networkDevice.Backing = &types.VirtualEthernetCardDistributedVirtualPortBackingInfo{ 623 Port: types.DistributedVirtualSwitchPortConnection{ 624 SwitchUuid: "yup", 625 PortgroupKey: "hole", 626 }, 627 } 628 629 retrieveDVSCall := retrievePropertiesStubCall("dvs-0") 630 s.roundTripper.CheckCall(c, 12, retrieveDVSCall.FuncName, retrieveDVSCall.Args...) 631 632 datastore := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"} 633 // When the external network is a distributed virtual portgroup, 634 // we must make an additional RetrieveProperties call to fetch 635 // the DVS's UUID. This bumps the ImportVApp position by one. 636 s.roundTripper.CheckCall( 637 c, 15, "CloneVM_Task", 638 types.ManagedObjectReference{ 639 Type: "Folder", Value: "FakeControllerVmFolder", 640 }, 641 "vm-0", &types.VirtualMachineConfigSpec{ 642 ExtraConfig: []types.BaseOptionValue{ 643 &types.OptionValue{Key: "k", Value: "v"}, 644 }, 645 DeviceChange: []types.BaseVirtualDeviceConfigSpec{ 646 &types.VirtualDeviceConfigSpec{ 647 Operation: "add", 648 Device: &networkDevice, 649 }, 650 }, 651 Flags: &types.VirtualMachineFlagInfo{DiskUuidEnabled: newBool(true)}, 652 VAppConfig: &types.VmConfigSpec{ 653 Property: []types.VAppPropertySpec{{ 654 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 655 Info: &types.VAppPropertyInfo{Key: 1, Value: "vm-0"}, 656 }, { 657 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 658 Info: &types.VAppPropertyInfo{Key: 4, Value: "baz"}, 659 }}, 660 }, 661 }, types.VirtualMachineRelocateSpec{ 662 Pool: &args.ResourcePool, 663 Datastore: &datastore, 664 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 665 { 666 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{ 667 EagerlyScrub: newBool(false), 668 ThinProvisioned: newBool(true), 669 }, 670 DiskId: 0, 671 Datastore: datastore, 672 }, 673 }, 674 }) 675 } 676 677 func (s *clientSuite) TestCreateVirtualMachineNetworkNotFound(c *gc.C) { 678 client := s.newFakeClient(&s.roundTripper, "dc0") 679 args := baseCreateVirtualMachineParams(c, client) 680 args.NetworkDevices = []NetworkDevice{ 681 {Network: "fourtytwo"}, 682 } 683 684 _, err := client.CreateVirtualMachine(context.Background(), args) 685 c.Assert(err, gc.ErrorMatches, `cloning template VM: building clone VM config: network "fourtytwo" not found`) 686 } 687 688 func (s *clientSuite) TestCreateVirtualMachineInvalidMAC(c *gc.C) { 689 client := s.newFakeClient(&s.roundTripper, "dc0") 690 args := baseCreateVirtualMachineParams(c, client) 691 args.NetworkDevices = []NetworkDevice{ 692 {MAC: "00:11:22:33:44:55"}, 693 } 694 695 _, err := client.CreateVirtualMachine(context.Background(), args) 696 c.Assert(err, gc.ErrorMatches, `cloning template VM: building clone VM config: adding network device 0 - network VM Network: invalid MAC address: "00:11:22:33:44:55"`) 697 } 698 699 func (s *clientSuite) TestCreateVirtualMachineRootDiskSize(c *gc.C) { 700 client := s.newFakeClient(&s.roundTripper, "dc0") 701 args := baseCreateVirtualMachineParams(c, client) 702 rootDisk := uint64(1024 * 20) // 20 GiB 703 args.Constraints.RootDisk = &rootDisk 704 705 _, err := client.CreateVirtualMachine(context.Background(), args) 706 c.Assert(err, jc.ErrorIsNil) 707 708 s.roundTripper.CheckCall(c, 19, "ReconfigVM_Task", types.VirtualMachineConfigSpec{ 709 DeviceChange: []types.BaseVirtualDeviceConfigSpec{ 710 &types.VirtualDeviceConfigSpec{ 711 Operation: types.VirtualDeviceConfigSpecOperationEdit, 712 FileOperation: "", 713 Device: &types.VirtualDisk{ 714 VirtualDevice: types.VirtualDevice{ 715 Backing: &types.VirtualDiskFlatVer2BackingInfo{ 716 VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{ 717 FileName: "disk.vmdk", 718 }, 719 }, 720 }, 721 CapacityInKB: 1024 * 1024 * 20, // 20 GiB 722 }, 723 }, 724 }, 725 }) 726 } 727 728 func (s *clientSuite) TestCreateVirtualMachineWithCustomizedVMFolder(c *gc.C) { 729 client := s.newFakeClient(&s.roundTripper, "dc0") 730 args := baseCreateVirtualMachineParams(c, client) 731 rootDisk := uint64(1024 * 20) // 20 GiB 732 args.Constraints.RootDisk = &rootDisk 733 734 args.Folder = "k8s" 735 736 _, err := client.CreateVirtualMachine(context.Background(), args) 737 c.Assert(err, jc.ErrorIsNil) 738 739 datastore := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"} 740 // The template import and the create from template have been split in two separate 741 // functions. We now have to check the folder passed to CloneVm to determine if the 742 // correct folder was selected when testing CreateVirtualMachine(). 743 s.roundTripper.CheckCall( 744 c, 14, "CloneVM_Task", 745 types.ManagedObjectReference{Type: "Folder", Value: "FakeK8sVMFolder"}, 746 "vm-0", &types.VirtualMachineConfigSpec{ 747 ExtraConfig: []types.BaseOptionValue{ 748 &types.OptionValue{Key: "k", Value: "v"}, 749 }, 750 Flags: &types.VirtualMachineFlagInfo{DiskUuidEnabled: newBool(args.EnableDiskUUID)}, 751 VAppConfig: &types.VmConfigSpec{ 752 Property: []types.VAppPropertySpec{{ 753 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 754 Info: &types.VAppPropertyInfo{Key: 1, Value: "vm-0"}, 755 }, { 756 ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"}, 757 Info: &types.VAppPropertyInfo{Key: 4, Value: "baz"}, 758 }}, 759 }, 760 }, types.VirtualMachineRelocateSpec{ 761 Pool: &args.ResourcePool, 762 Datastore: &datastore, 763 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 764 { 765 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{ 766 EagerlyScrub: newBool(false), 767 ThinProvisioned: newBool(true), 768 }, 769 DiskId: 0, 770 Datastore: datastore, 771 }, 772 }, 773 }) 774 } 775 776 func (s *clientSuite) TestVerifyMAC(c *gc.C) { 777 var testData = []struct { 778 Mac string 779 Result bool 780 }{ 781 {"foo:bar:baz", false}, 782 {"00:22:55:11:34:11", false}, 783 {"00:50:56:123:11:11", false}, 784 {"00:50:56:40:12:23", false}, 785 {"00:50:56:3f:ff:ff", true}, 786 {"00:50:56:12:34:56", true}, 787 {"00:50:56:2A:eB:Cd", true}, 788 {"00:50:56:2a:xy:cd", false}, 789 {"00:50:560:2a:xy:cd", false}, 790 } 791 for i, test := range testData { 792 c.Logf("test #%d: MAC=%s expected %s", i, test.Mac, test.Result) 793 c.Check(VerifyMAC(test.Mac), gc.Equals, test.Result) 794 } 795 } 796 797 func baseImportOVAParameters(c *gc.C, client *Client) ImportOVAParameters { 798 readOVA := func() (string, io.ReadCloser, error) { 799 r := bytes.NewReader(ovatest.FakeOVAContents()) 800 return "fake-ova-location", io.NopCloser(r), nil 801 } 802 fakeSHA256 := ovatest.FakeOVASHA256() 803 fakeDS := types.ManagedObjectReference{ 804 Type: "Datastore", 805 Value: "FakeDatastore1", 806 } 807 return ImportOVAParameters{ 808 ReadOVA: readOVA, 809 OVASHA256: fakeSHA256, 810 StatusUpdateParams: StatusUpdateParams{ 811 UpdateProgress: func(status string) {}, 812 UpdateProgressInterval: time.Second, 813 Clock: testclock.NewClock(time.Time{}), 814 }, 815 ResourcePool: types.ManagedObjectReference{ 816 Type: "ResourcePool", 817 Value: "FakeResourcePool1", 818 }, 819 TemplateName: "juju-template-" + fakeSHA256, 820 Arch: "amd64", 821 Series: "xenial", 822 DestinationFolder: &object.Folder{ 823 Common: object.Common{ 824 InventoryPath: "/dc0/vm/juju-vmdks/ctrl/xenial", 825 }, 826 }, 827 Datastore: object.NewDatastore(client.client.Client, fakeDS), 828 } 829 } 830 831 func baseCreateVirtualMachineParams(c *gc.C, client *Client) CreateVirtualMachineParams { 832 fakeVM := types.ManagedObjectReference{ 833 Type: "VirtualMachine", 834 Value: "FakeVm0", 835 } 836 837 fakeDS := types.ManagedObjectReference{ 838 Type: "Datastore", 839 Value: "FakeDatastore1", 840 } 841 842 return CreateVirtualMachineParams{ 843 Name: "vm-0", 844 Folder: "foo", 845 Series: "xenial", 846 UserData: "baz", 847 ComputeResource: &mo.ComputeResource{ 848 ResourcePool: &types.ManagedObjectReference{ 849 Type: "ResourcePool", 850 Value: "FakeResourcePool1", 851 }, 852 Datastore: []types.ManagedObjectReference{{ 853 Type: "Datastore", 854 Value: "FakeDatastore1", 855 }, { 856 Type: "Datastore", 857 Value: "FakeDatastore2", 858 }}, 859 Network: []types.ManagedObjectReference{{ 860 Type: "Network", 861 Value: "network-0", 862 }, { 863 Type: "Network", 864 Value: "network-1", 865 }, { 866 Type: "OpaqueNetwork", 867 Value: "onetwork-0", 868 }, { 869 Type: "DistributedVirtualPortgroup", 870 Value: "dvportgroup-0", 871 }}, 872 }, 873 ResourcePool: types.ManagedObjectReference{ 874 Type: "ResourcePool", 875 Value: "FakeResourcePool1", 876 }, 877 Metadata: map[string]string{"k": "v"}, 878 Constraints: constraints.Value{}, 879 StatusUpdateParams: StatusUpdateParams{ 880 UpdateProgress: func(status string) {}, 881 UpdateProgressInterval: time.Second, 882 Clock: testclock.NewClock(time.Time{}), 883 }, 884 EnableDiskUUID: true, 885 DiskProvisioningType: DiskTypeThin, 886 VMTemplate: object.NewVirtualMachine(client.client.Client, fakeVM), 887 Datastore: object.NewDatastore(client.client.Client, fakeDS), 888 } 889 } 890 891 func baseCisp() types.OvfCreateImportSpecParams { 892 return types.OvfCreateImportSpecParams{ 893 EntityName: "vm-0", 894 } 895 } 896 897 func newBool(v bool) *bool { 898 return &v 899 }