github.com/coreos/mantle@v0.13.0/platform/api/esx/api.go (about)

     1  // Copyright (c) 2016 VMware, Inc. All Rights Reserved.
     2  // Copyright 2017 CoreOS, Inc.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package esx
    17  
    18  import (
    19  	"context"
    20  	"encoding/base64"
    21  	"errors"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"net"
    25  	"net/url"
    26  	"path"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/coreos/pkg/capnslog"
    31  	"github.com/vmware/govmomi"
    32  	"github.com/vmware/govmomi/find"
    33  	"github.com/vmware/govmomi/object"
    34  	"github.com/vmware/govmomi/ovf"
    35  	"github.com/vmware/govmomi/vim25/mo"
    36  	"github.com/vmware/govmomi/vim25/progress"
    37  	"github.com/vmware/govmomi/vim25/soap"
    38  	"github.com/vmware/govmomi/vim25/types"
    39  
    40  	"github.com/coreos/mantle/auth"
    41  	"github.com/coreos/mantle/platform"
    42  	"github.com/coreos/mantle/platform/conf"
    43  )
    44  
    45  type Options struct {
    46  	*platform.Options
    47  
    48  	// Config file. Defaults to $HOME/.config/esx.json
    49  	ConfigPath string
    50  	// Profile name
    51  	Profile string
    52  
    53  	Server     string
    54  	User       string
    55  	Password   string
    56  	BaseVMName string
    57  }
    58  
    59  var plog = capnslog.NewPackageLogger("github.com/coreos/mantle", "platform/api/esx")
    60  
    61  type API struct {
    62  	options *Options
    63  	client  *govmomi.Client
    64  	ctx     context.Context
    65  }
    66  
    67  type ESXMachine struct {
    68  	Name      string
    69  	IPAddress string
    70  }
    71  
    72  type ovfFileItem struct {
    73  	url  *url.URL
    74  	item types.OvfFileItem
    75  	ch   chan progress.Report
    76  }
    77  
    78  type serverResources struct {
    79  	finder       *find.Finder
    80  	datacenter   *object.Datacenter
    81  	resourcePool *object.ResourcePool
    82  	datastore    *object.Datastore
    83  	network      object.NetworkReference
    84  }
    85  
    86  func (a *API) getMachine(vm *object.VirtualMachine) (*ESXMachine, error) {
    87  	ctx := context.Background()
    88  	deadline, cancel := context.WithDeadline(ctx, time.Now().Add(1000*time.Second))
    89  	defer cancel()
    90  	ip, err := vm.WaitForNetIP(deadline, false)
    91  	if err != nil {
    92  		return nil, fmt.Errorf("waiting for net ip: %v", err)
    93  	}
    94  
    95  	var ipaddr string
    96  OUTER:
    97  	for _, ips := range ip {
    98  		for _, val := range ips {
    99  			addr := net.ParseIP(val)
   100  			if addr.IsLinkLocalMulticast() || addr.IsLinkLocalUnicast() {
   101  				continue
   102  			}
   103  			ipaddr = val
   104  			break OUTER
   105  		}
   106  	}
   107  
   108  	var mvm mo.VirtualMachine
   109  	err = vm.Properties(ctx, vm.Reference(), []string{"summary"}, &mvm)
   110  	if err != nil {
   111  		return nil, fmt.Errorf("getting machine reference: %v", err)
   112  	}
   113  
   114  	return &ESXMachine{
   115  		Name:      mvm.Summary.Config.Name,
   116  		IPAddress: ipaddr,
   117  	}, nil
   118  }
   119  
   120  func New(opts *Options) (*API, error) {
   121  	if opts.Server == "" || opts.User == "" || opts.Password == "" {
   122  		profiles, err := auth.ReadESXConfig(opts.ConfigPath)
   123  		if err != nil {
   124  			return nil, fmt.Errorf("couldn't read ESX config: %v", err)
   125  		}
   126  
   127  		if opts.Profile == "" {
   128  			opts.Profile = "default"
   129  		}
   130  		profile, ok := profiles[opts.Profile]
   131  		if !ok {
   132  			return nil, fmt.Errorf("no such profile %q", opts.Profile)
   133  		}
   134  		if opts.Server == "" {
   135  			opts.Server = profile.Server
   136  		}
   137  		if opts.User == "" {
   138  			opts.User = profile.User
   139  		}
   140  		if opts.Password == "" {
   141  			opts.Password = profile.Password
   142  		}
   143  	}
   144  
   145  	esxUrl := fmt.Sprintf("%s:%s@%s", opts.User, opts.Password, opts.Server)
   146  	u, err := soap.ParseURL(esxUrl)
   147  	if err != nil {
   148  		return nil, fmt.Errorf("parsing ESX URL: %v", err)
   149  	}
   150  
   151  	ctx := context.Background()
   152  
   153  	client, err := govmomi.NewClient(ctx, u, true)
   154  	if err != nil {
   155  		return nil, fmt.Errorf("connecting to ESX: %v", err)
   156  	}
   157  
   158  	return &API{
   159  		options: opts,
   160  		client:  client,
   161  		ctx:     ctx,
   162  	}, nil
   163  }
   164  
   165  func getNetworkDevice(net object.NetworkReference) (types.BaseVirtualDevice, error) {
   166  	backing, err := net.EthernetCardBackingInfo(context.TODO())
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	device, err := object.EthernetCardTypes().CreateEthernetCard("e1000", backing)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	return device, nil
   177  }
   178  
   179  func (a *API) buildCloneSpec(baseVM *object.VirtualMachine, folder *object.Folder, network object.NetworkReference, resourcePool *object.ResourcePool, datastore *object.Datastore, userdata string) (*types.VirtualMachineCloneSpec, error) {
   180  	devices, err := baseVM.Device(a.ctx)
   181  	if err != nil {
   182  		return nil, fmt.Errorf("couldn't get base VM devices: %v", err)
   183  	}
   184  	var card *types.VirtualEthernetCard
   185  	for _, device := range devices {
   186  		if c, ok := device.(types.BaseVirtualEthernetCard); ok {
   187  			card = c.GetVirtualEthernetCard()
   188  			break
   189  		}
   190  	}
   191  	if card == nil {
   192  		return nil, fmt.Errorf("No network device found.")
   193  	}
   194  
   195  	netDev, err := getNetworkDevice(network)
   196  	if err != nil {
   197  		return nil, fmt.Errorf("couldn't get new network backing device: %v", err)
   198  	}
   199  
   200  	card.Backing = netDev.(types.BaseVirtualEthernetCard).GetVirtualEthernetCard().Backing
   201  
   202  	folderRef := folder.Reference()
   203  	poolRef := resourcePool.Reference()
   204  	datastoreRef := datastore.Reference()
   205  
   206  	cloneSpec := &types.VirtualMachineCloneSpec{
   207  		Location: types.VirtualMachineRelocateSpec{
   208  			DeviceChange: []types.BaseVirtualDeviceConfigSpec{
   209  				&types.VirtualDeviceConfigSpec{
   210  					Operation: types.VirtualDeviceConfigSpecOperationEdit,
   211  					Device:    card,
   212  				},
   213  			},
   214  			Folder:    &folderRef,
   215  			Pool:      &poolRef,
   216  			Datastore: &datastoreRef,
   217  		},
   218  		PowerOn:  false,
   219  		Template: false,
   220  	}
   221  
   222  	return cloneSpec, nil
   223  }
   224  
   225  func (a *API) addSerialPort(vm *object.VirtualMachine) error {
   226  	devices, err := vm.Device(a.ctx)
   227  	if err != nil {
   228  		return fmt.Errorf("couldn't get devices for vm: %v", err)
   229  	}
   230  
   231  	d, err := devices.CreateSerialPort()
   232  	if err != nil {
   233  		return fmt.Errorf("couldn't create serial port: %v", err)
   234  	}
   235  
   236  	err = vm.AddDevice(a.ctx, d)
   237  	if err != nil {
   238  		return fmt.Errorf("couldn't add serial port to vm: %v", err)
   239  	}
   240  
   241  	// refresh devices
   242  	devices, err = vm.Device(a.ctx)
   243  	if err != nil {
   244  		return fmt.Errorf("couldn't get devices for vm: %v", err)
   245  	}
   246  
   247  	d, err = devices.FindSerialPort("")
   248  	if err != nil {
   249  		return fmt.Errorf("couldn't find serial port for vm: %v", err)
   250  	}
   251  
   252  	var mvm mo.VirtualMachine
   253  	err = vm.Properties(a.ctx, vm.Reference(), []string{"config.files.logDirectory"}, &mvm)
   254  	if err != nil {
   255  		return fmt.Errorf("couldn't get log directory: %v", err)
   256  	}
   257  	uri := path.Join(mvm.Config.Files.LogDirectory, "serial.out")
   258  
   259  	return vm.EditDevice(a.ctx, devices.ConnectSerialPort(d, uri, false, ""))
   260  }
   261  
   262  func (a *API) GetConsoleOutput(name string) (string, error) {
   263  	defaults, err := a.getServerDefaults()
   264  	if err != nil {
   265  		return "", fmt.Errorf("couldn't get server defaults: %v", err)
   266  	}
   267  
   268  	uri := fmt.Sprintf("%s/serial.out", name)
   269  
   270  	p := soap.DefaultDownload
   271  
   272  	f, _, err := defaults.datastore.Download(a.ctx, uri, &p)
   273  	if err != nil {
   274  		return "", fmt.Errorf("couldn't download console logs: %v", err)
   275  	}
   276  	defer f.Close()
   277  
   278  	buf, err := ioutil.ReadAll(f)
   279  	if err != nil {
   280  		return "", fmt.Errorf("couldn't read serial output: %v", err)
   281  	}
   282  
   283  	return string(buf), nil
   284  }
   285  
   286  func (a *API) CleanupDevice(name string) error {
   287  	defaults, err := a.getServerDefaults()
   288  	if err != nil {
   289  		return fmt.Errorf("couldn't get server defaults: %v", err)
   290  	}
   291  
   292  	_, err = defaults.finder.VirtualMachine(a.ctx, name)
   293  	if err == nil {
   294  		return fmt.Errorf("VM still exists")
   295  	}
   296  
   297  	fm := defaults.datastore.NewFileManager(defaults.datacenter, true)
   298  
   299  	// Remove the serial.out file
   300  	uri := fmt.Sprintf("%s/serial.out", name)
   301  	err = fm.DeleteFile(a.ctx, uri)
   302  	if err != nil && !strings.HasSuffix(err.Error(), "was not found") {
   303  		return fmt.Errorf("couldn't delete serial.out: %v", err)
   304  	}
   305  
   306  	// Remove the VM directory
   307  	err = fm.DeleteFile(a.ctx, name)
   308  	if err != nil && !strings.HasSuffix(err.Error(), "was not found") {
   309  		return fmt.Errorf("couldn't delete vm directory: %v", err)
   310  	}
   311  
   312  	return nil
   313  }
   314  
   315  func (a *API) CreateDevice(name string, conf *conf.Conf) (*ESXMachine, error) {
   316  	if a.options.BaseVMName == "" {
   317  		return nil, fmt.Errorf("Base VM Name must be supplied")
   318  	}
   319  
   320  	userdata := base64.StdEncoding.EncodeToString(conf.Bytes())
   321  
   322  	defaults, err := a.getServerDefaults()
   323  	if err != nil {
   324  		return nil, fmt.Errorf("couldn't get server defaults: %v", err)
   325  	}
   326  
   327  	baseVM, err := defaults.finder.VirtualMachine(a.ctx, a.options.BaseVMName)
   328  	if err != nil {
   329  		return nil, fmt.Errorf("couldn't find base VM: %v", err)
   330  	}
   331  
   332  	folders, err := defaults.datacenter.Folders(a.ctx)
   333  	if err != nil {
   334  		return nil, fmt.Errorf("getting datacenter folders: %v", err)
   335  	}
   336  	folder := folders.VmFolder
   337  
   338  	cloneSpec, err := a.buildCloneSpec(baseVM, folder, defaults.network, defaults.resourcePool, defaults.datastore, userdata)
   339  	if err != nil {
   340  		return nil, fmt.Errorf("failed building clone spec: %v", err)
   341  	}
   342  
   343  	task, err := baseVM.Clone(a.ctx, folder, name, *cloneSpec)
   344  	if err != nil {
   345  		return nil, fmt.Errorf("couldn't clone base VM: %v", err)
   346  	}
   347  
   348  	err = task.Wait(a.ctx)
   349  	if err != nil {
   350  		return nil, fmt.Errorf("clone base VM operation failed: %v", err)
   351  	}
   352  
   353  	vm, err := defaults.finder.VirtualMachine(a.ctx, name)
   354  	if err != nil {
   355  		return nil, fmt.Errorf("couldn't find cloned VM: %v", err)
   356  	}
   357  
   358  	err = a.addSerialPort(vm)
   359  	if err != nil {
   360  		return nil, fmt.Errorf("adding serial port: %v", err)
   361  	}
   362  
   363  	err = a.updateOVFEnv(vm, userdata)
   364  	if err != nil {
   365  		return nil, fmt.Errorf("setting guestinfo settings: %v", err)
   366  	}
   367  
   368  	err = a.startVM(vm)
   369  	if err != nil {
   370  		return nil, fmt.Errorf("starting vm: %v", err)
   371  	}
   372  
   373  	mach, err := a.getMachine(vm)
   374  	if err != nil {
   375  		return nil, fmt.Errorf("getting machine info: %v", err)
   376  	}
   377  
   378  	return mach, nil
   379  }
   380  
   381  func (a *API) CreateBaseDevice(name, ovaPath string) error {
   382  	if ovaPath == "" {
   383  		return fmt.Errorf("ova path cannot be empty")
   384  	}
   385  
   386  	defaults, err := a.getServerDefaults()
   387  	if err != nil {
   388  		return fmt.Errorf("getting ESX defaults: %v", err)
   389  	}
   390  
   391  	arch, cisr, err := a.buildCreateImportSpecRequest(name, ovaPath, defaults.finder, defaults.network, defaults.resourcePool, defaults.datastore)
   392  	if err != nil {
   393  		return fmt.Errorf("building CreateImportSpecRequest: %v", err)
   394  	}
   395  
   396  	folders, err := defaults.datacenter.Folders(a.ctx)
   397  	if err != nil {
   398  		return fmt.Errorf("getting datacenter folders: %v", err)
   399  	}
   400  	folder := folders.VmFolder
   401  
   402  	entity, err := a.uploadToResourcePool(arch, defaults.resourcePool, cisr, folder)
   403  	if err != nil {
   404  		return fmt.Errorf("uploading disks to ResourcePool: %v", err)
   405  	}
   406  
   407  	// object.NewVirtualMachine returns a VirtualMachine object but we don't
   408  	// need to do anything with the returned object so ignore it
   409  	_ = object.NewVirtualMachine(a.client.Client, *entity)
   410  
   411  	return nil
   412  }
   413  
   414  func (a *API) TerminateDevice(name string) error {
   415  	defaults, err := a.getServerDefaults()
   416  	if err != nil {
   417  		return fmt.Errorf("couldn't get server defaults: %v", err)
   418  	}
   419  
   420  	vm, err := defaults.finder.VirtualMachine(a.ctx, name)
   421  	if err != nil {
   422  		return fmt.Errorf("couldn't find VM: %v", err)
   423  	}
   424  
   425  	return a.deleteDevice(vm)
   426  }
   427  
   428  func (a *API) deleteDevice(vm *object.VirtualMachine) error {
   429  	task, err := vm.PowerOff(a.ctx)
   430  	if err != nil {
   431  		return fmt.Errorf("powering off vm: %v", err)
   432  	}
   433  
   434  	// We don't check for errors on this task because it will throw an error
   435  	// if the VM is already in a powered off state
   436  	_ = task.Wait(a.ctx)
   437  
   438  	task, err = vm.Destroy(a.ctx)
   439  	if err != nil {
   440  		return fmt.Errorf("destroying vm: %v", vm)
   441  	}
   442  
   443  	return task.Wait(a.ctx)
   444  }
   445  
   446  func (a *API) buildCreateImportSpecRequest(name string, ovaPath string, finder *find.Finder, defaultNetwork object.NetworkReference, resourcePool *object.ResourcePool, datastore *object.Datastore) (*archive, *types.OvfCreateImportSpecResult, error) {
   447  	var nets []types.OvfNetworkMapping
   448  	nets = append(nets, types.OvfNetworkMapping{
   449  		Name:    "mantle",
   450  		Network: defaultNetwork.Reference(),
   451  	})
   452  
   453  	arch := &archive{ovaPath}
   454  	envelope, err := arch.readEnvelope("*.ovf")
   455  	if err != nil {
   456  		return nil, nil, fmt.Errorf("reading envelope: %v", err)
   457  	}
   458  
   459  	ovfHandler := object.NewOvfManager(a.client.Client)
   460  	cisp := types.OvfCreateImportSpecParams{
   461  		EntityName: name,
   462  		OvfManagerCommonParams: types.OvfManagerCommonParams{
   463  			Locale: "US"},
   464  		PropertyMapping: []types.KeyValue{},
   465  		NetworkMapping:  networkMap(finder, envelope),
   466  	}
   467  
   468  	descriptor, err := arch.readOvf("*.ovf")
   469  	if err != nil {
   470  		return nil, nil, fmt.Errorf("reading ovf: %v", err)
   471  	}
   472  	cisr, err := ovfHandler.CreateImportSpec(a.ctx, string(descriptor), resourcePool, datastore, cisp)
   473  	if err != nil {
   474  		return nil, nil, err
   475  	}
   476  	if cisr.Error != nil {
   477  		return nil, nil, errors.New(cisr.Error[0].LocalizedMessage)
   478  	}
   479  	return arch, cisr, nil
   480  }
   481  
   482  func (a *API) getServerDefaults() (serverResources, error) {
   483  	finder := find.NewFinder(a.client.Client, true)
   484  	datacenter, err := finder.DefaultDatacenter(a.ctx)
   485  	if err != nil {
   486  		return serverResources{}, err
   487  	}
   488  	finder.SetDatacenter(datacenter)
   489  	resourcePool, err := finder.DefaultResourcePool(a.ctx)
   490  	if err != nil {
   491  		return serverResources{}, err
   492  	}
   493  	datastore, err := finder.DefaultDatastore(a.ctx)
   494  	if err != nil {
   495  		return serverResources{}, err
   496  	}
   497  
   498  	defaultNetwork, err := finder.DefaultNetwork(a.ctx)
   499  	if err != nil {
   500  		return serverResources{}, err
   501  	}
   502  
   503  	return serverResources{
   504  		finder:       finder,
   505  		datacenter:   datacenter,
   506  		resourcePool: resourcePool,
   507  		datastore:    datastore,
   508  		network:      defaultNetwork,
   509  	}, nil
   510  }
   511  
   512  func (a *API) uploadToResourcePool(arch *archive, resourcePool *object.ResourcePool, cisr *types.OvfCreateImportSpecResult, folder *object.Folder) (*types.ManagedObjectReference, error) {
   513  	lease, err := resourcePool.ImportVApp(a.ctx, cisr.ImportSpec, folder, nil)
   514  	if err != nil {
   515  		return nil, fmt.Errorf("importing vApp: %v", err)
   516  	}
   517  
   518  	info, err := lease.Wait(a.ctx)
   519  	if err != nil {
   520  		return nil, err
   521  	}
   522  
   523  	var items []ovfFileItem
   524  
   525  	for _, device := range info.DeviceUrl {
   526  		for _, item := range cisr.FileItem {
   527  			if device.ImportKey != item.DeviceId {
   528  				continue
   529  			}
   530  
   531  			u, err := a.client.Client.ParseURL(device.Url)
   532  			if err != nil {
   533  				return nil, err
   534  			}
   535  
   536  			i := ovfFileItem{
   537  				url:  u,
   538  				item: item,
   539  				ch:   make(chan progress.Report),
   540  			}
   541  
   542  			items = append(items, i)
   543  		}
   544  	}
   545  
   546  	upd := newLeaseUpdater(a.client.Client, lease, items)
   547  	defer upd.Done()
   548  
   549  	for _, i := range items {
   550  		err = a.upload(arch, lease, i)
   551  		if err != nil {
   552  			return nil, err
   553  		}
   554  	}
   555  
   556  	err = lease.HttpNfcLeaseComplete(a.ctx)
   557  	if err != nil {
   558  		return nil, err
   559  	}
   560  	return &info.Entity, nil
   561  }
   562  
   563  func networkMap(finder *find.Finder, e *ovf.Envelope) (p []types.OvfNetworkMapping) {
   564  	if e.Network != nil {
   565  		for _, net := range e.Network.Networks {
   566  			if n, err := finder.Network(context.TODO(), net.Name); err == nil {
   567  				p = append(p, types.OvfNetworkMapping{
   568  					Name:    net.Name,
   569  					Network: n.Reference(),
   570  				})
   571  			}
   572  		}
   573  	}
   574  	return
   575  }
   576  
   577  func (a *API) updateOVFEnv(vm *object.VirtualMachine, userdata string) error {
   578  	var property []types.VAppPropertySpec
   579  
   580  	var mvm mo.VirtualMachine
   581  	err := vm.Properties(a.ctx, vm.Reference(), []string{"config", "config.vAppConfig", "config.vAppConfig.property"}, &mvm)
   582  	if err != nil {
   583  		return fmt.Errorf("couldn't get config.vappconfig: %v", err)
   584  	}
   585  
   586  	for _, item := range mvm.Config.VAppConfig.(*types.VmConfigInfo).Property {
   587  		if item.Id == "guestinfo.coreos.config.data" {
   588  			property = append(property, types.VAppPropertySpec{
   589  				ArrayUpdateSpec: types.ArrayUpdateSpec{
   590  					Operation: types.ArrayUpdateOperationEdit,
   591  				},
   592  				Info: &types.VAppPropertyInfo{
   593  					Key:          item.Key,
   594  					Id:           item.Id,
   595  					DefaultValue: userdata,
   596  				},
   597  			})
   598  		} else if item.Id == "guestinfo.coreos.config.data.encoding" {
   599  			property = append(property, types.VAppPropertySpec{
   600  				ArrayUpdateSpec: types.ArrayUpdateSpec{
   601  					Operation: types.ArrayUpdateOperationEdit,
   602  				},
   603  				Info: &types.VAppPropertyInfo{
   604  					Key:          item.Key,
   605  					Id:           item.Id,
   606  					DefaultValue: "base64",
   607  				},
   608  			})
   609  		}
   610  	}
   611  
   612  	if len(property) != 2 {
   613  		return fmt.Errorf("couldn't find required vApp properties on vm")
   614  	}
   615  
   616  	task, err := vm.Reconfigure(a.ctx, types.VirtualMachineConfigSpec{
   617  		VAppConfig: &types.VmConfigSpec{
   618  			Property: property,
   619  		},
   620  	})
   621  
   622  	if err != nil {
   623  		return err
   624  	}
   625  
   626  	return task.Wait(a.ctx)
   627  }
   628  
   629  func (a *API) startVM(vm *object.VirtualMachine) error {
   630  	task, err := vm.PowerOn(a.ctx)
   631  	if err != nil {
   632  		return err
   633  	}
   634  
   635  	return task.Wait(a.ctx)
   636  }
   637  
   638  func (a *API) upload(arch *archive, lease *object.HttpNfcLease, ofi ovfFileItem) error {
   639  	item := ofi.item
   640  	file := item.Path
   641  
   642  	f, size, err := arch.open(file)
   643  	if err != nil {
   644  		return err
   645  	}
   646  	defer f.Close()
   647  
   648  	opts := soap.Upload{
   649  		ContentLength: size,
   650  		Progress:      nil,
   651  	}
   652  
   653  	// Non-disk files (such as .iso) use the PUT method.
   654  	// Overwrite: t header is also required in this case (ovftool does the same)
   655  	if item.Create {
   656  		opts.Method = "PUT"
   657  		opts.Headers = map[string]string{
   658  			"Overwrite": "t",
   659  		}
   660  	} else {
   661  		opts.Method = "POST"
   662  		opts.Type = "application/x-vnd.vmware-streamVmdk"
   663  	}
   664  
   665  	return a.client.Client.Upload(f, ofi.url, &opts)
   666  }
   667  
   668  func (a *API) PreflightCheck() error {
   669  	var mgr mo.SessionManager
   670  
   671  	c := a.client.Client
   672  
   673  	return mo.RetrieveProperties(context.Background(), c, c.ServiceContent.PropertyCollector, *c.ServiceContent.SessionManager, &mgr)
   674  }