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