github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/vsphere/internal/vsphereclient/client.go (about)

     1  // Copyright 2015-2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package vsphereclient
     5  
     6  import (
     7  	"context"
     8  	"net/url"
     9  	"path"
    10  	"strings"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	"github.com/vmware/govmomi"
    15  	"github.com/vmware/govmomi/find"
    16  	"github.com/vmware/govmomi/list"
    17  	"github.com/vmware/govmomi/object"
    18  	"github.com/vmware/govmomi/property"
    19  	"github.com/vmware/govmomi/vim25/methods"
    20  	"github.com/vmware/govmomi/vim25/mo"
    21  	"github.com/vmware/govmomi/vim25/soap"
    22  	"github.com/vmware/govmomi/vim25/types"
    23  )
    24  
    25  // Client encapsulates a vSphere client, exposing the subset of
    26  // functionality that we require in the Juju provider.
    27  type Client struct {
    28  	client     *govmomi.Client
    29  	datacenter string
    30  	logger     loggo.Logger
    31  }
    32  
    33  // Dial dials a new vSphere client connection using the given URL,
    34  // scoped to the specified dataceter. The resulting Client's Close
    35  // method must be called in order to release resources allocated by
    36  // Dial.
    37  func Dial(
    38  	ctx context.Context,
    39  	u *url.URL,
    40  	datacenter string,
    41  	logger loggo.Logger,
    42  ) (*Client, error) {
    43  	client, err := govmomi.NewClient(ctx, u, true)
    44  	if err != nil {
    45  		return nil, errors.Trace(err)
    46  	}
    47  	return &Client{client, datacenter, logger}, nil
    48  }
    49  
    50  // Close logs out and closes the client connection.
    51  func (c *Client) Close(ctx context.Context) error {
    52  	return c.client.Logout(ctx)
    53  }
    54  
    55  func (c *Client) lister(ref types.ManagedObjectReference) *list.Lister {
    56  	return &list.Lister{
    57  		Collector: property.DefaultCollector(c.client.Client),
    58  		Reference: ref,
    59  		All:       true,
    60  	}
    61  }
    62  
    63  func (c *Client) finder(ctx context.Context) (*find.Finder, *object.Datacenter, error) {
    64  	finder := find.NewFinder(c.client.Client, true)
    65  	datacenter, err := finder.Datacenter(ctx, c.datacenter)
    66  	if err != nil {
    67  		return nil, nil, errors.Trace(err)
    68  	}
    69  	finder.SetDatacenter(datacenter)
    70  	return finder, datacenter, nil
    71  }
    72  
    73  // RemoveVirtualMachines removes VMs matching the given path from the
    74  // system. The path may include wildcards, to match multiple VMs.
    75  func (c *Client) RemoveVirtualMachines(ctx context.Context, path string) error {
    76  	finder, _, err := c.finder(ctx)
    77  	if err != nil {
    78  		return errors.Trace(err)
    79  	}
    80  
    81  	vms, err := finder.VirtualMachineList(ctx, path)
    82  	if err != nil {
    83  		if _, ok := err.(*find.NotFoundError); ok {
    84  			c.logger.Debugf("no VMs matching path %q", path)
    85  			return nil
    86  		}
    87  		return errors.Annotatef(err, "listing VMs at %q", path)
    88  	}
    89  
    90  	// Retrieve VM details so we know which ones to power off.
    91  	refs := make([]types.ManagedObjectReference, len(vms))
    92  	for i, vm := range vms {
    93  		refs[i] = vm.Reference()
    94  	}
    95  	var mos []mo.VirtualMachine
    96  	if err := c.client.Retrieve(ctx, refs, nil, &mos); err != nil {
    97  		return errors.Annotate(err, "retrieving VM details")
    98  	}
    99  
   100  	// We run all tasks in parallel, and wait for them below.
   101  	var lastError error
   102  	tasks := make([]*object.Task, 0, len(vms)*2)
   103  	for i, vm := range vms {
   104  		if mos[i].Runtime.PowerState == types.VirtualMachinePowerStatePoweredOn {
   105  			c.logger.Debugf("powering off %q", vm.Name())
   106  			task, err := vm.PowerOff(ctx)
   107  			if err != nil {
   108  				lastError = errors.Annotatef(err, "powering off %q", vm.Name())
   109  				c.logger.Errorf(err.Error())
   110  				continue
   111  			}
   112  			tasks = append(tasks, task)
   113  		}
   114  		c.logger.Debugf("destroying %q", vm.Name())
   115  		task, err := vm.Destroy(ctx)
   116  		if err != nil {
   117  			lastError = errors.Annotatef(err, "destroying %q", vm.Name())
   118  			c.logger.Errorf(err.Error())
   119  			continue
   120  		}
   121  		tasks = append(tasks, task)
   122  	}
   123  
   124  	for _, task := range tasks {
   125  		_, err := task.WaitForResult(ctx, nil)
   126  		if err != nil && !isManagedObjectNotFound(err) {
   127  			lastError = err
   128  			c.logger.Errorf(err.Error())
   129  		}
   130  	}
   131  	return errors.Annotate(lastError, "failed to remove instances")
   132  }
   133  
   134  // VirtualMachines return list of all VMs in the system matching the given path.
   135  func (c *Client) VirtualMachines(ctx context.Context, path string) ([]*mo.VirtualMachine, error) {
   136  	finder, _, err := c.finder(ctx)
   137  	if err != nil {
   138  		return nil, errors.Trace(err)
   139  	}
   140  	items, err := finder.VirtualMachineList(ctx, path)
   141  	if err != nil {
   142  		if _, ok := err.(*find.NotFoundError); ok {
   143  			return nil, nil
   144  		}
   145  		return nil, errors.Annotate(err, "listing VMs")
   146  	}
   147  
   148  	vms := make([]*mo.VirtualMachine, len(items))
   149  	for i, item := range items {
   150  		var vm mo.VirtualMachine
   151  		err := c.client.RetrieveOne(ctx, item.Reference(), nil, &vm)
   152  		if err != nil {
   153  			return nil, errors.Trace(err)
   154  		}
   155  		vms[i] = &vm
   156  	}
   157  	return vms, nil
   158  }
   159  
   160  // ComputeResources returns list of all root compute resources in the system.
   161  func (c *Client) ComputeResources(ctx context.Context) ([]*mo.ComputeResource, error) {
   162  	_, datacenter, err := c.finder(ctx)
   163  	if err != nil {
   164  		return nil, errors.Trace(err)
   165  	}
   166  	folders, err := datacenter.Folders(ctx)
   167  	if err != nil {
   168  		return nil, errors.Trace(err)
   169  	}
   170  
   171  	es, err := c.lister(folders.HostFolder.Reference()).List(ctx)
   172  	if err != nil {
   173  		return nil, errors.Trace(err)
   174  	}
   175  
   176  	var cprs []*mo.ComputeResource
   177  	for _, e := range es {
   178  		switch o := e.Object.(type) {
   179  		case mo.ClusterComputeResource:
   180  			cprs = append(cprs, &o.ComputeResource)
   181  		case mo.ComputeResource:
   182  			cprs = append(cprs, &o)
   183  		}
   184  	}
   185  	return cprs, nil
   186  }
   187  
   188  // Datastores returns list of all datastores in the system.
   189  func (c *Client) Datastores(ctx context.Context) ([]*mo.Datastore, error) {
   190  	_, datacenter, err := c.finder(ctx)
   191  	if err != nil {
   192  		return nil, errors.Trace(err)
   193  	}
   194  	folders, err := datacenter.Folders(ctx)
   195  	if err != nil {
   196  		return nil, errors.Trace(err)
   197  	}
   198  
   199  	es, err := c.lister(folders.DatastoreFolder.Reference()).List(ctx)
   200  	if err != nil {
   201  		return nil, errors.Trace(err)
   202  	}
   203  
   204  	var datastores []*mo.Datastore
   205  	for _, e := range es {
   206  		switch o := e.Object.(type) {
   207  		case mo.Datastore:
   208  			datastores = append(datastores, &o)
   209  		}
   210  	}
   211  	return datastores, nil
   212  }
   213  
   214  // EnsureVMFolder creates the a VM folder with the given path if it doesn't
   215  // already exist.
   216  func (c *Client) EnsureVMFolder(ctx context.Context, folderPath string) (*object.Folder, error) {
   217  	finder, datacenter, err := c.finder(ctx)
   218  	if err != nil {
   219  		return nil, errors.Trace(err)
   220  	}
   221  	folders, err := datacenter.Folders(ctx)
   222  	if err != nil {
   223  		return nil, errors.Trace(err)
   224  	}
   225  
   226  	createFolder := func(parent *object.Folder, name string) (*object.Folder, error) {
   227  		folder, err := parent.CreateFolder(ctx, name)
   228  		if err != nil && soap.IsSoapFault(err) {
   229  			switch soap.ToSoapFault(err).VimFault().(type) {
   230  			case types.DuplicateName:
   231  				return finder.Folder(ctx, parent.InventoryPath+"/"+name)
   232  			}
   233  		}
   234  		return folder, err
   235  	}
   236  
   237  	parentFolder := folders.VmFolder
   238  	for _, name := range strings.Split(folderPath, "/") {
   239  		folder, err := createFolder(parentFolder, name)
   240  		if err != nil {
   241  			return nil, errors.Annotatef(
   242  				err, "creating folder %q in %q",
   243  				name, parentFolder.InventoryPath,
   244  			)
   245  		}
   246  		parentFolder = folder
   247  	}
   248  	return parentFolder, nil
   249  }
   250  
   251  // DestroyVMFolder destroys a folder rooted at the datacenter's base VM folder.
   252  func (c *Client) DestroyVMFolder(ctx context.Context, folderPath string) error {
   253  	finder, datacenter, err := c.finder(ctx)
   254  	if err != nil {
   255  		return errors.Trace(err)
   256  	}
   257  	folders, err := datacenter.Folders(ctx)
   258  	if err != nil {
   259  		return errors.Trace(err)
   260  	}
   261  	folderPath = path.Join(folders.VmFolder.InventoryPath, folderPath)
   262  	folder, err := finder.Folder(ctx, folderPath)
   263  	if err != nil {
   264  		if _, ok := err.(*find.NotFoundError); ok {
   265  			return nil
   266  		}
   267  		return errors.Trace(err)
   268  	}
   269  
   270  	task, err := folder.Destroy(ctx)
   271  	if err != nil {
   272  		return errors.Trace(err)
   273  	}
   274  	_, err = task.WaitForResult(ctx, nil)
   275  	if err != nil && !isManagedObjectNotFound(err) {
   276  		return errors.Trace(err)
   277  	}
   278  	return nil
   279  }
   280  
   281  // MoveVMFolderInto moves one VM folder into another.
   282  func (c *Client) MoveVMFolderInto(ctx context.Context, parentPath, childPath string) error {
   283  	finder, datacenter, err := c.finder(ctx)
   284  	if err != nil {
   285  		return errors.Trace(err)
   286  	}
   287  	folders, err := datacenter.Folders(ctx)
   288  	if err != nil {
   289  		return errors.Trace(err)
   290  	}
   291  
   292  	parentPath = path.Join(folders.VmFolder.InventoryPath, parentPath)
   293  	childPath = path.Join(folders.VmFolder.InventoryPath, childPath)
   294  	parent, err := finder.Folder(ctx, parentPath)
   295  	if err != nil {
   296  		return errors.Trace(err)
   297  	}
   298  	child, err := finder.Folder(ctx, childPath)
   299  	if err != nil {
   300  		return errors.Trace(err)
   301  	}
   302  
   303  	task, err := parent.MoveInto(ctx, []types.ManagedObjectReference{child.Reference()})
   304  	if err != nil {
   305  		return errors.Trace(err)
   306  	}
   307  	if _, err := task.WaitForResult(ctx, nil); err != nil {
   308  		return errors.Trace(err)
   309  	}
   310  	return nil
   311  }
   312  
   313  // MoveVMsInto moves a set of VMs into a folder.
   314  func (c *Client) MoveVMsInto(
   315  	ctx context.Context,
   316  	folderPath string,
   317  	vms ...types.ManagedObjectReference,
   318  ) error {
   319  	finder, datacenter, err := c.finder(ctx)
   320  	if err != nil {
   321  		return errors.Trace(err)
   322  	}
   323  	folders, err := datacenter.Folders(ctx)
   324  	if err != nil {
   325  		return errors.Trace(err)
   326  	}
   327  	folderPath = path.Join(folders.VmFolder.InventoryPath, folderPath)
   328  	folder, err := finder.Folder(ctx, folderPath)
   329  	if err != nil {
   330  		return errors.Trace(err)
   331  	}
   332  
   333  	task, err := folder.MoveInto(ctx, vms)
   334  	if err != nil {
   335  		return errors.Trace(err)
   336  	}
   337  	if _, err := task.WaitForResult(ctx, nil); err != nil {
   338  		return errors.Trace(err)
   339  	}
   340  	return nil
   341  }
   342  
   343  // UpdateVirtualMachineExtraConfig updates the "ExtraConfig" attributes
   344  // of the specified virtual machine. Keys with empty values will be
   345  // removed from the config; existing keys that are unspecified in the
   346  // map will be untouched.
   347  func (c *Client) UpdateVirtualMachineExtraConfig(
   348  	ctx context.Context,
   349  	vmInfo *mo.VirtualMachine,
   350  	metadata map[string]string,
   351  ) error {
   352  	var spec types.VirtualMachineConfigSpec
   353  	for k, v := range metadata {
   354  		opt := &types.OptionValue{Key: k, Value: v}
   355  		spec.ExtraConfig = append(spec.ExtraConfig, opt)
   356  	}
   357  	vm := object.NewVirtualMachine(c.client.Client, vmInfo.Reference())
   358  	task, err := vm.Reconfigure(ctx, spec)
   359  	if err != nil {
   360  		return errors.Annotate(err, "reconfiguring VM")
   361  	}
   362  	if _, err := task.WaitForResult(ctx, nil); err != nil {
   363  		return errors.Annotate(err, "reconfiguring VM")
   364  	}
   365  	return nil
   366  }
   367  
   368  // DeleteDatastoreFile deletes a file or directory in the datastore.
   369  func (c *Client) DeleteDatastoreFile(ctx context.Context, datastorePath string) error {
   370  	_, datacenter, err := c.finder(ctx)
   371  	if err != nil {
   372  		return errors.Trace(err)
   373  	}
   374  	fileManager := object.NewFileManager(c.client.Client)
   375  	deleteTask, err := fileManager.DeleteDatastoreFile(ctx, datastorePath, datacenter)
   376  	if err != nil {
   377  		return errors.Trace(err)
   378  	}
   379  	if _, err := deleteTask.WaitForResult(ctx, nil); err != nil {
   380  		if types.IsFileNotFound(err) {
   381  			return nil
   382  		}
   383  		return errors.Trace(err)
   384  	}
   385  	return nil
   386  }
   387  
   388  func (c *Client) destroyVM(
   389  	ctx context.Context,
   390  	vm *object.VirtualMachine,
   391  	taskWaiter *taskWaiter,
   392  ) error {
   393  	task, err := vm.Destroy(ctx)
   394  	if err != nil {
   395  		return errors.Trace(err)
   396  	}
   397  	_, err = taskWaiter.waitTask(ctx, task, "destroying VM")
   398  	return errors.Trace(err)
   399  }
   400  
   401  func (c *Client) cloneVM(
   402  	ctx context.Context,
   403  	srcVM *object.VirtualMachine,
   404  	dstName string,
   405  	vmFolder *object.Folder,
   406  	taskWaiter *taskWaiter,
   407  ) (*object.VirtualMachine, error) {
   408  	task, err := srcVM.Clone(ctx, vmFolder, dstName, types.VirtualMachineCloneSpec{
   409  		Config:   &types.VirtualMachineConfigSpec{},
   410  		Location: types.VirtualMachineRelocateSpec{},
   411  	})
   412  	if err != nil {
   413  		return nil, errors.Trace(err)
   414  	}
   415  	info, err := taskWaiter.waitTask(ctx, task, "cloning VM")
   416  	if err != nil {
   417  		return nil, err
   418  	}
   419  	return object.NewVirtualMachine(c.client.Client, info.Result.(types.ManagedObjectReference)), nil
   420  }
   421  
   422  func (c *Client) extendDisk(
   423  	ctx context.Context,
   424  	datacenter *object.Datacenter,
   425  	datastorePath string,
   426  	capacityKB int64,
   427  	taskWaiter *taskWaiter,
   428  ) error {
   429  	// NOTE(axw) there's no ExtendVirtualDisk on the disk manager type,
   430  	// hence why we're dealing with request types directly. Send a patch
   431  	// to govmomi to add this to VirtualDiskManager.
   432  
   433  	diskManager := object.NewVirtualDiskManager(c.client.Client)
   434  	dcref := datacenter.Reference()
   435  	req := types.ExtendVirtualDisk_Task{
   436  		This:          diskManager.Reference(),
   437  		Name:          datastorePath,
   438  		Datacenter:    &dcref,
   439  		NewCapacityKb: capacityKB,
   440  	}
   441  
   442  	res, err := methods.ExtendVirtualDisk_Task(ctx, c.client.Client, &req)
   443  	if err != nil {
   444  		return errors.Trace(err)
   445  	}
   446  	task := object.NewTask(c.client.Client, res.Returnval)
   447  	_, err = taskWaiter.waitTask(ctx, task, "extending disk")
   448  	return errors.Trace(err)
   449  }
   450  
   451  func (c *Client) detachDisk(
   452  	ctx context.Context,
   453  	vm *object.VirtualMachine,
   454  	taskWaiter *taskWaiter,
   455  ) (string, error) {
   456  
   457  	var mo mo.VirtualMachine
   458  	if err := c.client.RetrieveOne(ctx, vm.Reference(), []string{"config.hardware"}, &mo); err != nil {
   459  		return "", errors.Trace(err)
   460  	}
   461  
   462  	var spec types.VirtualMachineConfigSpec
   463  	var vmdkDatastorePath string
   464  	for _, dev := range mo.Config.Hardware.Device {
   465  		dev, ok := dev.(*types.VirtualDisk)
   466  		if !ok {
   467  			continue
   468  		}
   469  		backing, ok := dev.Backing.(types.BaseVirtualDeviceFileBackingInfo)
   470  		if !ok {
   471  			continue
   472  		}
   473  		vmdkDatastorePath = backing.GetVirtualDeviceFileBackingInfo().FileName
   474  		spec.DeviceChange = []types.BaseVirtualDeviceConfigSpec{
   475  			&types.VirtualDeviceConfigSpec{
   476  				Operation: types.VirtualDeviceConfigSpecOperationRemove,
   477  				Device:    dev,
   478  			},
   479  		}
   480  		break
   481  	}
   482  	if len(spec.DeviceChange) != 1 {
   483  		return "", errors.New("disk device not found")
   484  	}
   485  
   486  	task, err := vm.Reconfigure(ctx, spec)
   487  	if err != nil {
   488  		return "", errors.Trace(err)
   489  	}
   490  	if _, err := taskWaiter.waitTask(ctx, task, "detaching disk"); err != nil {
   491  		return "", errors.Trace(err)
   492  	}
   493  	return vmdkDatastorePath, nil
   494  }
   495  
   496  func isManagedObjectNotFound(err error) bool {
   497  	if f, ok := err.(types.HasFault); ok {
   498  		switch f.Fault().(type) {
   499  		case *types.ManagedObjectNotFound:
   500  			return true
   501  		}
   502  	}
   503  	return false
   504  }