github.com/erriapo/terraform@v0.6.12-0.20160203182612-0340ea72354f/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:     []string{"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  }