github.com/leeprovoost/terraform@v0.6.10-0.20160119085442-96f3f76118e7/builtin/providers/vsphere/resource_vsphere_virtual_machine.go (about) 1 package vsphere 2 3 import ( 4 "fmt" 5 "log" 6 "net" 7 "strings" 8 "time" 9 10 "github.com/hashicorp/terraform/helper/resource" 11 "github.com/hashicorp/terraform/helper/schema" 12 "github.com/vmware/govmomi" 13 "github.com/vmware/govmomi/find" 14 "github.com/vmware/govmomi/object" 15 "github.com/vmware/govmomi/property" 16 "github.com/vmware/govmomi/vim25/mo" 17 "github.com/vmware/govmomi/vim25/types" 18 "golang.org/x/net/context" 19 ) 20 21 var DefaultDNSSuffixes = []string{ 22 "vsphere.local", 23 } 24 25 var DefaultDNSServers = []string{ 26 "8.8.8.8", 27 "8.8.4.4", 28 } 29 30 type networkInterface struct { 31 deviceName string 32 label string 33 ipv4Address string 34 ipv4PrefixLength int 35 ipv6Address string 36 ipv6PrefixLength int 37 adapterType string // TODO: Make "adapter_type" argument 38 } 39 40 type hardDisk struct { 41 size int64 42 iops int64 43 } 44 45 type virtualMachine struct { 46 name string 47 folder string 48 datacenter string 49 cluster string 50 resourcePool string 51 datastore string 52 vcpu int 53 memoryMb int64 54 template string 55 networkInterfaces []networkInterface 56 hardDisks []hardDisk 57 gateway string 58 domain string 59 timeZone string 60 dnsSuffixes []string 61 dnsServers []string 62 customConfigurations map[string](types.AnyType) 63 } 64 65 func (v virtualMachine) Path() string { 66 return vmPath(v.folder, v.name) 67 } 68 69 func vmPath(folder string, name string) string { 70 var path string 71 if len(folder) > 0 { 72 path += folder + "/" 73 } 74 return path + name 75 } 76 77 func resourceVSphereVirtualMachine() *schema.Resource { 78 return &schema.Resource{ 79 Create: resourceVSphereVirtualMachineCreate, 80 Read: resourceVSphereVirtualMachineRead, 81 Delete: resourceVSphereVirtualMachineDelete, 82 83 Schema: map[string]*schema.Schema{ 84 "name": &schema.Schema{ 85 Type: schema.TypeString, 86 Required: true, 87 ForceNew: true, 88 }, 89 90 "folder": &schema.Schema{ 91 Type: schema.TypeString, 92 Optional: true, 93 ForceNew: true, 94 }, 95 96 "vcpu": &schema.Schema{ 97 Type: schema.TypeInt, 98 Required: true, 99 ForceNew: true, 100 }, 101 102 "memory": &schema.Schema{ 103 Type: schema.TypeInt, 104 Required: true, 105 ForceNew: true, 106 }, 107 108 "datacenter": &schema.Schema{ 109 Type: schema.TypeString, 110 Optional: true, 111 ForceNew: true, 112 }, 113 114 "cluster": &schema.Schema{ 115 Type: schema.TypeString, 116 Optional: true, 117 ForceNew: true, 118 }, 119 120 "resource_pool": &schema.Schema{ 121 Type: schema.TypeString, 122 Optional: true, 123 ForceNew: true, 124 }, 125 126 "gateway": &schema.Schema{ 127 Type: schema.TypeString, 128 Optional: true, 129 ForceNew: true, 130 }, 131 132 "domain": &schema.Schema{ 133 Type: schema.TypeString, 134 Optional: true, 135 ForceNew: true, 136 Default: "vsphere.local", 137 }, 138 139 "time_zone": &schema.Schema{ 140 Type: schema.TypeString, 141 Optional: true, 142 ForceNew: true, 143 Default: "Etc/UTC", 144 }, 145 146 "dns_suffixes": &schema.Schema{ 147 Type: schema.TypeList, 148 Optional: true, 149 Elem: &schema.Schema{Type: schema.TypeString}, 150 ForceNew: true, 151 }, 152 153 "dns_servers": &schema.Schema{ 154 Type: schema.TypeList, 155 Optional: true, 156 Elem: &schema.Schema{Type: schema.TypeString}, 157 ForceNew: true, 158 }, 159 160 "custom_configuration_parameters": &schema.Schema{ 161 Type: schema.TypeMap, 162 Optional: true, 163 ForceNew: true, 164 }, 165 166 "network_interface": &schema.Schema{ 167 Type: schema.TypeList, 168 Required: true, 169 ForceNew: true, 170 Elem: &schema.Resource{ 171 Schema: map[string]*schema.Schema{ 172 "label": &schema.Schema{ 173 Type: schema.TypeString, 174 Required: true, 175 ForceNew: true, 176 }, 177 178 "ip_address": &schema.Schema{ 179 Type: schema.TypeString, 180 Optional: true, 181 Computed: true, 182 Deprecated: "Please use ipv4_address", 183 }, 184 185 "subnet_mask": &schema.Schema{ 186 Type: schema.TypeString, 187 Optional: true, 188 Computed: true, 189 Deprecated: "Please use ipv4_prefix_length", 190 }, 191 192 "ipv4_address": &schema.Schema{ 193 Type: schema.TypeString, 194 Optional: true, 195 Computed: true, 196 }, 197 198 "ipv4_prefix_length": &schema.Schema{ 199 Type: schema.TypeInt, 200 Optional: true, 201 Computed: true, 202 }, 203 204 // TODO: Imprement ipv6 parameters to be optional 205 "ipv6_address": &schema.Schema{ 206 Type: schema.TypeString, 207 Computed: true, 208 ForceNew: true, 209 }, 210 211 "ipv6_prefix_length": &schema.Schema{ 212 Type: schema.TypeInt, 213 Computed: true, 214 ForceNew: true, 215 }, 216 217 "adapter_type": &schema.Schema{ 218 Type: schema.TypeString, 219 Optional: true, 220 ForceNew: true, 221 }, 222 }, 223 }, 224 }, 225 226 "disk": &schema.Schema{ 227 Type: schema.TypeList, 228 Required: true, 229 ForceNew: true, 230 Elem: &schema.Resource{ 231 Schema: map[string]*schema.Schema{ 232 "template": &schema.Schema{ 233 Type: schema.TypeString, 234 Optional: true, 235 ForceNew: true, 236 }, 237 238 "datastore": &schema.Schema{ 239 Type: schema.TypeString, 240 Optional: true, 241 ForceNew: true, 242 }, 243 244 "size": &schema.Schema{ 245 Type: schema.TypeInt, 246 Optional: true, 247 ForceNew: true, 248 }, 249 250 "iops": &schema.Schema{ 251 Type: schema.TypeInt, 252 Optional: true, 253 ForceNew: true, 254 }, 255 }, 256 }, 257 }, 258 259 "boot_delay": &schema.Schema{ 260 Type: schema.TypeInt, 261 Optional: true, 262 ForceNew: true, 263 }, 264 }, 265 } 266 } 267 268 func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{}) error { 269 client := meta.(*govmomi.Client) 270 271 vm := virtualMachine{ 272 name: d.Get("name").(string), 273 vcpu: d.Get("vcpu").(int), 274 memoryMb: int64(d.Get("memory").(int)), 275 } 276 277 if v, ok := d.GetOk("folder"); ok { 278 vm.folder = v.(string) 279 } 280 281 if v, ok := d.GetOk("datacenter"); ok { 282 vm.datacenter = v.(string) 283 } 284 285 if v, ok := d.GetOk("cluster"); ok { 286 vm.cluster = v.(string) 287 } 288 289 if v, ok := d.GetOk("resource_pool"); ok { 290 vm.resourcePool = v.(string) 291 } 292 293 if v, ok := d.GetOk("gateway"); ok { 294 vm.gateway = v.(string) 295 } 296 297 if v, ok := d.GetOk("domain"); ok { 298 vm.domain = v.(string) 299 } 300 301 if v, ok := d.GetOk("time_zone"); ok { 302 vm.timeZone = v.(string) 303 } 304 305 if raw, ok := d.GetOk("dns_suffixes"); ok { 306 for _, v := range raw.([]interface{}) { 307 vm.dnsSuffixes = append(vm.dnsSuffixes, v.(string)) 308 } 309 } else { 310 vm.dnsSuffixes = DefaultDNSSuffixes 311 } 312 313 if raw, ok := d.GetOk("dns_servers"); ok { 314 for _, v := range raw.([]interface{}) { 315 vm.dnsServers = append(vm.dnsServers, v.(string)) 316 } 317 } else { 318 vm.dnsServers = DefaultDNSServers 319 } 320 321 if vL, ok := d.GetOk("custom_configuration_parameters"); ok { 322 if custom_configs, ok := vL.(map[string]interface{}); ok { 323 custom := make(map[string]types.AnyType) 324 for k, v := range custom_configs { 325 custom[k] = v 326 } 327 vm.customConfigurations = custom 328 log.Printf("[DEBUG] custom_configuration_parameters init: %v", vm.customConfigurations) 329 } 330 } 331 332 if vL, ok := d.GetOk("network_interface"); ok { 333 networks := make([]networkInterface, len(vL.([]interface{}))) 334 for i, v := range vL.([]interface{}) { 335 network := v.(map[string]interface{}) 336 networks[i].label = network["label"].(string) 337 if v, ok := network["ip_address"].(string); ok && v != "" { 338 networks[i].ipv4Address = v 339 } 340 if v, ok := network["subnet_mask"].(string); ok && v != "" { 341 ip := net.ParseIP(v).To4() 342 if ip != nil { 343 mask := net.IPv4Mask(ip[0], ip[1], ip[2], ip[3]) 344 pl, _ := mask.Size() 345 networks[i].ipv4PrefixLength = pl 346 } else { 347 return fmt.Errorf("subnet_mask parameter is invalid.") 348 } 349 } 350 if v, ok := network["ipv4_address"].(string); ok && v != "" { 351 networks[i].ipv4Address = v 352 } 353 if v, ok := network["ipv4_prefix_length"].(int); ok && v != 0 { 354 networks[i].ipv4PrefixLength = v 355 } 356 } 357 vm.networkInterfaces = networks 358 log.Printf("[DEBUG] network_interface init: %v", networks) 359 } 360 361 if vL, ok := d.GetOk("disk"); ok { 362 disks := make([]hardDisk, len(vL.([]interface{}))) 363 for i, v := range vL.([]interface{}) { 364 disk := v.(map[string]interface{}) 365 if i == 0 { 366 if v, ok := disk["template"].(string); ok && v != "" { 367 vm.template = v 368 } else { 369 if v, ok := disk["size"].(int); ok && v != 0 { 370 disks[i].size = int64(v) 371 } else { 372 return fmt.Errorf("If template argument is not specified, size argument is required.") 373 } 374 } 375 if v, ok := disk["datastore"].(string); ok && v != "" { 376 vm.datastore = v 377 } 378 } else { 379 if v, ok := disk["size"].(int); ok && v != 0 { 380 disks[i].size = int64(v) 381 } else { 382 return fmt.Errorf("Size argument is required.") 383 } 384 } 385 if v, ok := disk["iops"].(int); ok && v != 0 { 386 disks[i].iops = int64(v) 387 } 388 } 389 vm.hardDisks = disks 390 log.Printf("[DEBUG] disk init: %v", disks) 391 } 392 393 if vm.template != "" { 394 err := vm.deployVirtualMachine(client) 395 if err != nil { 396 return err 397 } 398 } else { 399 err := vm.createVirtualMachine(client) 400 if err != nil { 401 return err 402 } 403 } 404 405 if _, ok := d.GetOk("network_interface.0.ipv4_address"); !ok { 406 if v, ok := d.GetOk("boot_delay"); ok { 407 stateConf := &resource.StateChangeConf{ 408 Pending: []string{"pending"}, 409 Target: "active", 410 Refresh: waitForNetworkingActive(client, vm.datacenter, vm.Path()), 411 Timeout: 600 * time.Second, 412 Delay: time.Duration(v.(int)) * time.Second, 413 MinTimeout: 2 * time.Second, 414 } 415 416 _, err := stateConf.WaitForState() 417 if err != nil { 418 return err 419 } 420 } 421 } 422 d.SetId(vm.Path()) 423 log.Printf("[INFO] Created virtual machine: %s", d.Id()) 424 425 return resourceVSphereVirtualMachineRead(d, meta) 426 } 427 428 func resourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{}) error { 429 430 log.Printf("[DEBUG] reading virtual machine: %#v", d) 431 client := meta.(*govmomi.Client) 432 dc, err := getDatacenter(client, d.Get("datacenter").(string)) 433 if err != nil { 434 return err 435 } 436 finder := find.NewFinder(client.Client, true) 437 finder = finder.SetDatacenter(dc) 438 439 vm, err := finder.VirtualMachine(context.TODO(), d.Id()) 440 if err != nil { 441 d.SetId("") 442 return nil 443 } 444 445 var mvm mo.VirtualMachine 446 447 collector := property.DefaultCollector(client.Client) 448 if err := collector.RetrieveOne(context.TODO(), vm.Reference(), []string{"guest", "summary", "datastore"}, &mvm); err != nil { 449 return err 450 } 451 452 log.Printf("[DEBUG] %#v", dc) 453 log.Printf("[DEBUG] %#v", mvm.Summary.Config) 454 log.Printf("[DEBUG] %#v", mvm.Guest.Net) 455 456 networkInterfaces := make([]map[string]interface{}, 0) 457 for _, v := range mvm.Guest.Net { 458 if v.DeviceConfigId >= 0 { 459 log.Printf("[DEBUG] %#v", v.Network) 460 networkInterface := make(map[string]interface{}) 461 networkInterface["label"] = v.Network 462 for _, ip := range v.IpConfig.IpAddress { 463 p := net.ParseIP(ip.IpAddress) 464 if p.To4() != nil { 465 log.Printf("[DEBUG] %#v", p.String()) 466 log.Printf("[DEBUG] %#v", ip.PrefixLength) 467 networkInterface["ipv4_address"] = p.String() 468 networkInterface["ipv4_prefix_length"] = ip.PrefixLength 469 } else if p.To16() != nil { 470 log.Printf("[DEBUG] %#v", p.String()) 471 log.Printf("[DEBUG] %#v", ip.PrefixLength) 472 networkInterface["ipv6_address"] = p.String() 473 networkInterface["ipv6_prefix_length"] = ip.PrefixLength 474 } 475 log.Printf("[DEBUG] networkInterface: %#v", networkInterface) 476 } 477 log.Printf("[DEBUG] networkInterface: %#v", networkInterface) 478 networkInterfaces = append(networkInterfaces, networkInterface) 479 } 480 } 481 log.Printf("[DEBUG] networkInterfaces: %#v", networkInterfaces) 482 err = d.Set("network_interface", networkInterfaces) 483 if err != nil { 484 return fmt.Errorf("Invalid network interfaces to set: %#v", networkInterfaces) 485 } 486 487 var rootDatastore string 488 for _, v := range mvm.Datastore { 489 var md mo.Datastore 490 if err := collector.RetrieveOne(context.TODO(), v, []string{"name", "parent"}, &md); err != nil { 491 return err 492 } 493 if md.Parent.Type == "StoragePod" { 494 var msp mo.StoragePod 495 if err := collector.RetrieveOne(context.TODO(), *md.Parent, []string{"name"}, &msp); err != nil { 496 return err 497 } 498 rootDatastore = msp.Name 499 log.Printf("[DEBUG] %#v", msp.Name) 500 } else { 501 rootDatastore = md.Name 502 log.Printf("[DEBUG] %#v", md.Name) 503 } 504 break 505 } 506 507 d.Set("datacenter", dc) 508 d.Set("memory", mvm.Summary.Config.MemorySizeMB) 509 d.Set("cpu", mvm.Summary.Config.NumCpu) 510 d.Set("datastore", rootDatastore) 511 512 return nil 513 } 514 515 func resourceVSphereVirtualMachineDelete(d *schema.ResourceData, meta interface{}) error { 516 client := meta.(*govmomi.Client) 517 dc, err := getDatacenter(client, d.Get("datacenter").(string)) 518 if err != nil { 519 return err 520 } 521 finder := find.NewFinder(client.Client, true) 522 finder = finder.SetDatacenter(dc) 523 524 vm, err := finder.VirtualMachine(context.TODO(), vmPath(d.Get("folder").(string), d.Get("name").(string))) 525 if err != nil { 526 return err 527 } 528 529 log.Printf("[INFO] Deleting virtual machine: %s", d.Id()) 530 531 task, err := vm.PowerOff(context.TODO()) 532 if err != nil { 533 return err 534 } 535 536 err = task.Wait(context.TODO()) 537 if err != nil { 538 return err 539 } 540 541 task, err = vm.Destroy(context.TODO()) 542 if err != nil { 543 return err 544 } 545 546 err = task.Wait(context.TODO()) 547 if err != nil { 548 return err 549 } 550 551 d.SetId("") 552 return nil 553 } 554 555 func waitForNetworkingActive(client *govmomi.Client, datacenter, name string) resource.StateRefreshFunc { 556 return func() (interface{}, string, error) { 557 dc, err := getDatacenter(client, datacenter) 558 if err != nil { 559 log.Printf("[ERROR] %#v", err) 560 return nil, "", err 561 } 562 finder := find.NewFinder(client.Client, true) 563 finder = finder.SetDatacenter(dc) 564 565 vm, err := finder.VirtualMachine(context.TODO(), name) 566 if err != nil { 567 log.Printf("[ERROR] %#v", err) 568 return nil, "", err 569 } 570 571 var mvm mo.VirtualMachine 572 collector := property.DefaultCollector(client.Client) 573 if err := collector.RetrieveOne(context.TODO(), vm.Reference(), []string{"summary"}, &mvm); err != nil { 574 log.Printf("[ERROR] %#v", err) 575 return nil, "", err 576 } 577 578 if mvm.Summary.Guest.IpAddress != "" { 579 log.Printf("[DEBUG] IP address with DHCP: %v", mvm.Summary.Guest.IpAddress) 580 return mvm.Summary, "active", err 581 } else { 582 log.Printf("[DEBUG] Waiting for IP address") 583 return nil, "pending", err 584 } 585 } 586 } 587 588 // addHardDisk adds a new Hard Disk to the VirtualMachine. 589 func addHardDisk(vm *object.VirtualMachine, size, iops int64, diskType string) error { 590 devices, err := vm.Device(context.TODO()) 591 if err != nil { 592 return err 593 } 594 log.Printf("[DEBUG] vm devices: %#v\n", devices) 595 596 controller, err := devices.FindDiskController("scsi") 597 if err != nil { 598 return err 599 } 600 log.Printf("[DEBUG] disk controller: %#v\n", controller) 601 602 disk := devices.CreateDisk(controller, "") 603 existing := devices.SelectByBackingInfo(disk.Backing) 604 log.Printf("[DEBUG] disk: %#v\n", disk) 605 606 if len(existing) == 0 { 607 disk.CapacityInKB = int64(size * 1024 * 1024) 608 if iops != 0 { 609 disk.StorageIOAllocation = &types.StorageIOAllocationInfo{ 610 Limit: iops, 611 } 612 } 613 backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo) 614 615 if diskType == "eager_zeroed" { 616 // eager zeroed thick virtual disk 617 backing.ThinProvisioned = types.NewBool(false) 618 backing.EagerlyScrub = types.NewBool(true) 619 } else if diskType == "thin" { 620 // thin provisioned virtual disk 621 backing.ThinProvisioned = types.NewBool(true) 622 } 623 624 log.Printf("[DEBUG] addHardDisk: %#v\n", disk) 625 log.Printf("[DEBUG] addHardDisk: %#v\n", disk.CapacityInKB) 626 627 return vm.AddDevice(context.TODO(), disk) 628 } else { 629 log.Printf("[DEBUG] addHardDisk: Disk already present.\n") 630 631 return nil 632 } 633 } 634 635 // buildNetworkDevice builds VirtualDeviceConfigSpec for Network Device. 636 func buildNetworkDevice(f *find.Finder, label, adapterType string) (*types.VirtualDeviceConfigSpec, error) { 637 network, err := f.Network(context.TODO(), "*"+label) 638 if err != nil { 639 return nil, err 640 } 641 642 backing, err := network.EthernetCardBackingInfo(context.TODO()) 643 if err != nil { 644 return nil, err 645 } 646 647 if adapterType == "vmxnet3" { 648 return &types.VirtualDeviceConfigSpec{ 649 Operation: types.VirtualDeviceConfigSpecOperationAdd, 650 Device: &types.VirtualVmxnet3{ 651 types.VirtualVmxnet{ 652 types.VirtualEthernetCard{ 653 VirtualDevice: types.VirtualDevice{ 654 Key: -1, 655 Backing: backing, 656 }, 657 AddressType: string(types.VirtualEthernetCardMacTypeGenerated), 658 }, 659 }, 660 }, 661 }, nil 662 } else if adapterType == "e1000" { 663 return &types.VirtualDeviceConfigSpec{ 664 Operation: types.VirtualDeviceConfigSpecOperationAdd, 665 Device: &types.VirtualE1000{ 666 types.VirtualEthernetCard{ 667 VirtualDevice: types.VirtualDevice{ 668 Key: -1, 669 Backing: backing, 670 }, 671 AddressType: string(types.VirtualEthernetCardMacTypeGenerated), 672 }, 673 }, 674 }, nil 675 } else { 676 return nil, fmt.Errorf("Invalid network adapter type.") 677 } 678 } 679 680 // buildVMRelocateSpec builds VirtualMachineRelocateSpec to set a place for a new VirtualMachine. 681 func buildVMRelocateSpec(rp *object.ResourcePool, ds *object.Datastore, vm *object.VirtualMachine) (types.VirtualMachineRelocateSpec, error) { 682 var key int 683 684 devices, err := vm.Device(context.TODO()) 685 if err != nil { 686 return types.VirtualMachineRelocateSpec{}, err 687 } 688 for _, d := range devices { 689 if devices.Type(d) == "disk" { 690 key = d.GetVirtualDevice().Key 691 } 692 } 693 694 rpr := rp.Reference() 695 dsr := ds.Reference() 696 return types.VirtualMachineRelocateSpec{ 697 Datastore: &dsr, 698 Pool: &rpr, 699 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 700 types.VirtualMachineRelocateSpecDiskLocator{ 701 Datastore: dsr, 702 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{ 703 DiskMode: "persistent", 704 ThinProvisioned: types.NewBool(false), 705 EagerlyScrub: types.NewBool(true), 706 }, 707 DiskId: key, 708 }, 709 }, 710 }, nil 711 } 712 713 // getDatastoreObject gets datastore object. 714 func getDatastoreObject(client *govmomi.Client, f *object.DatacenterFolders, name string) (types.ManagedObjectReference, error) { 715 s := object.NewSearchIndex(client.Client) 716 ref, err := s.FindChild(context.TODO(), f.DatastoreFolder, name) 717 if err != nil { 718 return types.ManagedObjectReference{}, err 719 } 720 if ref == nil { 721 return types.ManagedObjectReference{}, fmt.Errorf("Datastore '%s' not found.", name) 722 } 723 log.Printf("[DEBUG] getDatastoreObject: reference: %#v", ref) 724 return ref.Reference(), nil 725 } 726 727 // buildStoragePlacementSpecCreate builds StoragePlacementSpec for create action. 728 func buildStoragePlacementSpecCreate(f *object.DatacenterFolders, rp *object.ResourcePool, storagePod object.StoragePod, configSpec types.VirtualMachineConfigSpec) types.StoragePlacementSpec { 729 vmfr := f.VmFolder.Reference() 730 rpr := rp.Reference() 731 spr := storagePod.Reference() 732 733 sps := types.StoragePlacementSpec{ 734 Type: "create", 735 ConfigSpec: &configSpec, 736 PodSelectionSpec: types.StorageDrsPodSelectionSpec{ 737 StoragePod: &spr, 738 }, 739 Folder: &vmfr, 740 ResourcePool: &rpr, 741 } 742 log.Printf("[DEBUG] findDatastore: StoragePlacementSpec: %#v\n", sps) 743 return sps 744 } 745 746 // buildStoragePlacementSpecClone builds StoragePlacementSpec for clone action. 747 func buildStoragePlacementSpecClone(c *govmomi.Client, f *object.DatacenterFolders, vm *object.VirtualMachine, rp *object.ResourcePool, storagePod object.StoragePod) types.StoragePlacementSpec { 748 vmr := vm.Reference() 749 vmfr := f.VmFolder.Reference() 750 rpr := rp.Reference() 751 spr := storagePod.Reference() 752 753 var o mo.VirtualMachine 754 err := vm.Properties(context.TODO(), vmr, []string{"datastore"}, &o) 755 if err != nil { 756 return types.StoragePlacementSpec{} 757 } 758 ds := object.NewDatastore(c.Client, o.Datastore[0]) 759 log.Printf("[DEBUG] findDatastore: datastore: %#v\n", ds) 760 761 devices, err := vm.Device(context.TODO()) 762 if err != nil { 763 return types.StoragePlacementSpec{} 764 } 765 766 var key int 767 for _, d := range devices.SelectByType((*types.VirtualDisk)(nil)) { 768 key = d.GetVirtualDevice().Key 769 log.Printf("[DEBUG] findDatastore: virtual devices: %#v\n", d.GetVirtualDevice()) 770 } 771 772 sps := types.StoragePlacementSpec{ 773 Type: "clone", 774 Vm: &vmr, 775 PodSelectionSpec: types.StorageDrsPodSelectionSpec{ 776 StoragePod: &spr, 777 }, 778 CloneSpec: &types.VirtualMachineCloneSpec{ 779 Location: types.VirtualMachineRelocateSpec{ 780 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 781 types.VirtualMachineRelocateSpecDiskLocator{ 782 Datastore: ds.Reference(), 783 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{}, 784 DiskId: key, 785 }, 786 }, 787 Pool: &rpr, 788 }, 789 PowerOn: false, 790 Template: false, 791 }, 792 CloneName: "dummy", 793 Folder: &vmfr, 794 } 795 return sps 796 } 797 798 // findDatastore finds Datastore object. 799 func findDatastore(c *govmomi.Client, sps types.StoragePlacementSpec) (*object.Datastore, error) { 800 var datastore *object.Datastore 801 log.Printf("[DEBUG] findDatastore: StoragePlacementSpec: %#v\n", sps) 802 803 srm := object.NewStorageResourceManager(c.Client) 804 rds, err := srm.RecommendDatastores(context.TODO(), sps) 805 if err != nil { 806 return nil, err 807 } 808 log.Printf("[DEBUG] findDatastore: recommendDatastores: %#v\n", rds) 809 810 spa := rds.Recommendations[0].Action[0].(*types.StoragePlacementAction) 811 datastore = object.NewDatastore(c.Client, spa.Destination) 812 log.Printf("[DEBUG] findDatastore: datastore: %#v", datastore) 813 814 return datastore, nil 815 } 816 817 // createVirtualMachine creates a new VirtualMachine. 818 func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error { 819 dc, err := getDatacenter(c, vm.datacenter) 820 821 if err != nil { 822 return err 823 } 824 finder := find.NewFinder(c.Client, true) 825 finder = finder.SetDatacenter(dc) 826 827 var resourcePool *object.ResourcePool 828 if vm.resourcePool == "" { 829 if vm.cluster == "" { 830 resourcePool, err = finder.DefaultResourcePool(context.TODO()) 831 if err != nil { 832 return err 833 } 834 } else { 835 resourcePool, err = finder.ResourcePool(context.TODO(), "*"+vm.cluster+"/Resources") 836 if err != nil { 837 return err 838 } 839 } 840 } else { 841 resourcePool, err = finder.ResourcePool(context.TODO(), vm.resourcePool) 842 if err != nil { 843 return err 844 } 845 } 846 log.Printf("[DEBUG] resource pool: %#v", resourcePool) 847 848 dcFolders, err := dc.Folders(context.TODO()) 849 if err != nil { 850 return err 851 } 852 853 log.Printf("[DEBUG] folder: %#v", vm.folder) 854 folder := dcFolders.VmFolder 855 if len(vm.folder) > 0 { 856 si := object.NewSearchIndex(c.Client) 857 folderRef, err := si.FindByInventoryPath( 858 context.TODO(), fmt.Sprintf("%v/vm/%v", vm.datacenter, vm.folder)) 859 if err != nil { 860 return fmt.Errorf("Error reading folder %s: %s", vm.folder, err) 861 } else if folderRef == nil { 862 return fmt.Errorf("Cannot find folder %s", vm.folder) 863 } else { 864 folder = folderRef.(*object.Folder) 865 } 866 } 867 868 // network 869 networkDevices := []types.BaseVirtualDeviceConfigSpec{} 870 for _, network := range vm.networkInterfaces { 871 // network device 872 nd, err := buildNetworkDevice(finder, network.label, "e1000") 873 if err != nil { 874 return err 875 } 876 networkDevices = append(networkDevices, nd) 877 } 878 879 // make config spec 880 configSpec := types.VirtualMachineConfigSpec{ 881 GuestId: "otherLinux64Guest", 882 Name: vm.name, 883 NumCPUs: vm.vcpu, 884 NumCoresPerSocket: 1, 885 MemoryMB: vm.memoryMb, 886 DeviceChange: networkDevices, 887 } 888 log.Printf("[DEBUG] virtual machine config spec: %v", configSpec) 889 890 // make ExtraConfig 891 log.Printf("[DEBUG] virtual machine Extra Config spec start") 892 if len(vm.customConfigurations) > 0 { 893 var ov []types.BaseOptionValue 894 for k, v := range vm.customConfigurations { 895 key := k 896 value := v 897 o := types.OptionValue{ 898 Key: key, 899 Value: &value, 900 } 901 log.Printf("[DEBUG] virtual machine Extra Config spec: %s,%s", k, v) 902 ov = append(ov, &o) 903 } 904 configSpec.ExtraConfig = ov 905 log.Printf("[DEBUG] virtual machine Extra Config spec: %v", configSpec.ExtraConfig) 906 } 907 908 var datastore *object.Datastore 909 if vm.datastore == "" { 910 datastore, err = finder.DefaultDatastore(context.TODO()) 911 if err != nil { 912 return err 913 } 914 } else { 915 datastore, err = finder.Datastore(context.TODO(), vm.datastore) 916 if err != nil { 917 // TODO: datastore cluster support in govmomi finder function 918 d, err := getDatastoreObject(c, dcFolders, vm.datastore) 919 if err != nil { 920 return err 921 } 922 923 if d.Type == "StoragePod" { 924 sp := object.StoragePod{ 925 object.NewFolder(c.Client, d), 926 } 927 sps := buildStoragePlacementSpecCreate(dcFolders, resourcePool, sp, configSpec) 928 datastore, err = findDatastore(c, sps) 929 if err != nil { 930 return err 931 } 932 } else { 933 datastore = object.NewDatastore(c.Client, d) 934 } 935 } 936 } 937 938 log.Printf("[DEBUG] datastore: %#v", datastore) 939 940 var mds mo.Datastore 941 if err = datastore.Properties(context.TODO(), datastore.Reference(), []string{"name"}, &mds); err != nil { 942 return err 943 } 944 log.Printf("[DEBUG] datastore: %#v", mds.Name) 945 scsi, err := object.SCSIControllerTypes().CreateSCSIController("scsi") 946 if err != nil { 947 log.Printf("[ERROR] %s", err) 948 } 949 950 configSpec.DeviceChange = append(configSpec.DeviceChange, &types.VirtualDeviceConfigSpec{ 951 Operation: types.VirtualDeviceConfigSpecOperationAdd, 952 Device: scsi, 953 }) 954 configSpec.Files = &types.VirtualMachineFileInfo{VmPathName: fmt.Sprintf("[%s]", mds.Name)} 955 956 task, err := folder.CreateVM(context.TODO(), configSpec, resourcePool, nil) 957 if err != nil { 958 log.Printf("[ERROR] %s", err) 959 } 960 961 err = task.Wait(context.TODO()) 962 if err != nil { 963 log.Printf("[ERROR] %s", err) 964 } 965 966 newVM, err := finder.VirtualMachine(context.TODO(), vm.Path()) 967 if err != nil { 968 return err 969 } 970 log.Printf("[DEBUG] new vm: %v", newVM) 971 972 log.Printf("[DEBUG] add hard disk: %v", vm.hardDisks) 973 for _, hd := range vm.hardDisks { 974 log.Printf("[DEBUG] add hard disk: %v", hd.size) 975 log.Printf("[DEBUG] add hard disk: %v", hd.iops) 976 err = addHardDisk(newVM, hd.size, hd.iops, "thin") 977 if err != nil { 978 return err 979 } 980 } 981 return nil 982 } 983 984 // deployVirtualMachine deploys a new VirtualMachine. 985 func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { 986 dc, err := getDatacenter(c, vm.datacenter) 987 if err != nil { 988 return err 989 } 990 finder := find.NewFinder(c.Client, true) 991 finder = finder.SetDatacenter(dc) 992 993 template, err := finder.VirtualMachine(context.TODO(), vm.template) 994 if err != nil { 995 return err 996 } 997 log.Printf("[DEBUG] template: %#v", template) 998 999 var resourcePool *object.ResourcePool 1000 if vm.resourcePool == "" { 1001 if vm.cluster == "" { 1002 resourcePool, err = finder.DefaultResourcePool(context.TODO()) 1003 if err != nil { 1004 return err 1005 } 1006 } else { 1007 resourcePool, err = finder.ResourcePool(context.TODO(), "*"+vm.cluster+"/Resources") 1008 if err != nil { 1009 return err 1010 } 1011 } 1012 } else { 1013 resourcePool, err = finder.ResourcePool(context.TODO(), vm.resourcePool) 1014 if err != nil { 1015 return err 1016 } 1017 } 1018 log.Printf("[DEBUG] resource pool: %#v", resourcePool) 1019 1020 dcFolders, err := dc.Folders(context.TODO()) 1021 if err != nil { 1022 return err 1023 } 1024 1025 log.Printf("[DEBUG] folder: %#v", vm.folder) 1026 folder := dcFolders.VmFolder 1027 if len(vm.folder) > 0 { 1028 si := object.NewSearchIndex(c.Client) 1029 folderRef, err := si.FindByInventoryPath( 1030 context.TODO(), fmt.Sprintf("%v/vm/%v", vm.datacenter, vm.folder)) 1031 if err != nil { 1032 return fmt.Errorf("Error reading folder %s: %s", vm.folder, err) 1033 } else if folderRef == nil { 1034 return fmt.Errorf("Cannot find folder %s", vm.folder) 1035 } else { 1036 folder = folderRef.(*object.Folder) 1037 } 1038 } 1039 1040 var datastore *object.Datastore 1041 if vm.datastore == "" { 1042 datastore, err = finder.DefaultDatastore(context.TODO()) 1043 if err != nil { 1044 return err 1045 } 1046 } else { 1047 datastore, err = finder.Datastore(context.TODO(), vm.datastore) 1048 if err != nil { 1049 // TODO: datastore cluster support in govmomi finder function 1050 d, err := getDatastoreObject(c, dcFolders, vm.datastore) 1051 if err != nil { 1052 return err 1053 } 1054 1055 if d.Type == "StoragePod" { 1056 sp := object.StoragePod{ 1057 object.NewFolder(c.Client, d), 1058 } 1059 sps := buildStoragePlacementSpecClone(c, dcFolders, template, resourcePool, sp) 1060 datastore, err = findDatastore(c, sps) 1061 if err != nil { 1062 return err 1063 } 1064 } else { 1065 datastore = object.NewDatastore(c.Client, d) 1066 } 1067 } 1068 } 1069 log.Printf("[DEBUG] datastore: %#v", datastore) 1070 1071 relocateSpec, err := buildVMRelocateSpec(resourcePool, datastore, template) 1072 if err != nil { 1073 return err 1074 } 1075 log.Printf("[DEBUG] relocate spec: %v", relocateSpec) 1076 1077 // network 1078 networkDevices := []types.BaseVirtualDeviceConfigSpec{} 1079 networkConfigs := []types.CustomizationAdapterMapping{} 1080 for _, network := range vm.networkInterfaces { 1081 // network device 1082 nd, err := buildNetworkDevice(finder, network.label, "vmxnet3") 1083 if err != nil { 1084 return err 1085 } 1086 networkDevices = append(networkDevices, nd) 1087 1088 // TODO: IPv6 support 1089 var ipSetting types.CustomizationIPSettings 1090 if network.ipv4Address == "" { 1091 ipSetting = types.CustomizationIPSettings{ 1092 Ip: &types.CustomizationDhcpIpGenerator{}, 1093 } 1094 } else { 1095 if network.ipv4PrefixLength == 0 { 1096 return fmt.Errorf("Error: ipv4_prefix_length argument is empty.") 1097 } 1098 m := net.CIDRMask(network.ipv4PrefixLength, 32) 1099 sm := net.IPv4(m[0], m[1], m[2], m[3]) 1100 subnetMask := sm.String() 1101 log.Printf("[DEBUG] gateway: %v", vm.gateway) 1102 log.Printf("[DEBUG] ipv4 address: %v", network.ipv4Address) 1103 log.Printf("[DEBUG] ipv4 prefix length: %v", network.ipv4PrefixLength) 1104 log.Printf("[DEBUG] ipv4 subnet mask: %v", subnetMask) 1105 ipSetting = types.CustomizationIPSettings{ 1106 Gateway: []string{ 1107 vm.gateway, 1108 }, 1109 Ip: &types.CustomizationFixedIp{ 1110 IpAddress: network.ipv4Address, 1111 }, 1112 SubnetMask: subnetMask, 1113 } 1114 } 1115 1116 // network config 1117 config := types.CustomizationAdapterMapping{ 1118 Adapter: ipSetting, 1119 } 1120 networkConfigs = append(networkConfigs, config) 1121 } 1122 log.Printf("[DEBUG] network configs: %v", networkConfigs[0].Adapter) 1123 1124 // make config spec 1125 configSpec := types.VirtualMachineConfigSpec{ 1126 NumCPUs: vm.vcpu, 1127 NumCoresPerSocket: 1, 1128 MemoryMB: vm.memoryMb, 1129 } 1130 log.Printf("[DEBUG] virtual machine config spec: %v", configSpec) 1131 1132 log.Printf("[DEBUG] starting extra custom config spec: %v", vm.customConfigurations) 1133 1134 // make ExtraConfig 1135 if len(vm.customConfigurations) > 0 { 1136 var ov []types.BaseOptionValue 1137 for k, v := range vm.customConfigurations { 1138 key := k 1139 value := v 1140 o := types.OptionValue{ 1141 Key: key, 1142 Value: &value, 1143 } 1144 ov = append(ov, &o) 1145 } 1146 configSpec.ExtraConfig = ov 1147 log.Printf("[DEBUG] virtual machine Extra Config spec: %v", configSpec.ExtraConfig) 1148 } 1149 1150 // create CustomizationSpec 1151 customSpec := types.CustomizationSpec{ 1152 Identity: &types.CustomizationLinuxPrep{ 1153 HostName: &types.CustomizationFixedName{ 1154 Name: strings.Split(vm.name, ".")[0], 1155 }, 1156 Domain: vm.domain, 1157 TimeZone: vm.timeZone, 1158 HwClockUTC: types.NewBool(true), 1159 }, 1160 GlobalIPSettings: types.CustomizationGlobalIPSettings{ 1161 DnsSuffixList: vm.dnsSuffixes, 1162 DnsServerList: vm.dnsServers, 1163 }, 1164 NicSettingMap: networkConfigs, 1165 } 1166 log.Printf("[DEBUG] custom spec: %v", customSpec) 1167 1168 // make vm clone spec 1169 cloneSpec := types.VirtualMachineCloneSpec{ 1170 Location: relocateSpec, 1171 Template: false, 1172 Config: &configSpec, 1173 PowerOn: false, 1174 } 1175 log.Printf("[DEBUG] clone spec: %v", cloneSpec) 1176 1177 task, err := template.Clone(context.TODO(), folder, vm.name, cloneSpec) 1178 if err != nil { 1179 return err 1180 } 1181 1182 _, err = task.WaitForResult(context.TODO(), nil) 1183 if err != nil { 1184 return err 1185 } 1186 1187 newVM, err := finder.VirtualMachine(context.TODO(), vm.Path()) 1188 if err != nil { 1189 return err 1190 } 1191 log.Printf("[DEBUG] new vm: %v", newVM) 1192 1193 devices, err := newVM.Device(context.TODO()) 1194 if err != nil { 1195 log.Printf("[DEBUG] Template devices can't be found") 1196 return err 1197 } 1198 1199 for _, dvc := range devices { 1200 // Issue 3559/3560: Delete all ethernet devices to add the correct ones later 1201 if devices.Type(dvc) == "ethernet" { 1202 err := newVM.RemoveDevice(context.TODO(), dvc) 1203 if err != nil { 1204 return err 1205 } 1206 } 1207 } 1208 // Add Network devices 1209 for _, dvc := range networkDevices { 1210 err := newVM.AddDevice( 1211 context.TODO(), dvc.GetVirtualDeviceConfigSpec().Device) 1212 if err != nil { 1213 return err 1214 } 1215 } 1216 1217 taskb, err := newVM.Customize(context.TODO(), customSpec) 1218 if err != nil { 1219 return err 1220 } 1221 1222 _, err = taskb.WaitForResult(context.TODO(), nil) 1223 if err != nil { 1224 return err 1225 } 1226 log.Printf("[DEBUG]VM customization finished") 1227 1228 for i := 1; i < len(vm.hardDisks); i++ { 1229 err = addHardDisk(newVM, vm.hardDisks[i].size, vm.hardDisks[i].iops, "eager_zeroed") 1230 if err != nil { 1231 return err 1232 } 1233 } 1234 log.Printf("[DEBUG] virtual machine config spec: %v", configSpec) 1235 1236 newVM.PowerOn(context.TODO()) 1237 1238 ip, err := newVM.WaitForIP(context.TODO()) 1239 if err != nil { 1240 return err 1241 } 1242 log.Printf("[DEBUG] ip address: %v", ip) 1243 1244 return nil 1245 }