yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/esxi/manager.go (about)

     1  // Copyright 2019 Yunion
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package esxi
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"net/http"
    22  	"net/http/httputil"
    23  	"net/url"
    24  	"reflect"
    25  	"strings"
    26  	"sync"
    27  
    28  	xj "github.com/basgys/goxml2json"
    29  	"github.com/fatih/color"
    30  	"github.com/vmware/govmomi"
    31  	"github.com/vmware/govmomi/object"
    32  	"github.com/vmware/govmomi/property"
    33  	"github.com/vmware/govmomi/session"
    34  	"github.com/vmware/govmomi/view"
    35  	"github.com/vmware/govmomi/vim25"
    36  	"github.com/vmware/govmomi/vim25/methods"
    37  	"github.com/vmware/govmomi/vim25/mo"
    38  	"github.com/vmware/govmomi/vim25/soap"
    39  	"github.com/vmware/govmomi/vim25/types"
    40  	"moul.io/http2curl/v2"
    41  
    42  	"yunion.io/x/jsonutils"
    43  	"yunion.io/x/log"
    44  	"yunion.io/x/pkg/errors"
    45  	"yunion.io/x/pkg/utils"
    46  
    47  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    48  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    49  	"yunion.io/x/cloudmux/pkg/multicloud"
    50  	"yunion.io/x/cloudmux/pkg/multicloud/esxi/vcenter"
    51  	"yunion.io/x/onecloud/pkg/util/httputils"
    52  )
    53  
    54  const (
    55  	CLOUD_PROVIDER_VMWARE = api.CLOUD_PROVIDER_VMWARE
    56  )
    57  
    58  var (
    59  	defaultDc mo.Datacenter
    60  
    61  	defaultDcId = "esxi-default-datacenter"
    62  )
    63  
    64  func init() {
    65  	defaultDc.ManagedEntity.Name = "Datacenter"
    66  	defaultDc.ManagedEntity.ExtensibleManagedObject.Self.Type = "Datacenter"
    67  	defaultDc.ManagedEntity.ExtensibleManagedObject.Self.Value = defaultDcId
    68  }
    69  
    70  type ESXiClientConfig struct {
    71  	cpcfg cloudprovider.ProviderConfig
    72  
    73  	host     string
    74  	port     int
    75  	account  string
    76  	password string
    77  
    78  	managed bool
    79  	debug   bool
    80  	format  string
    81  }
    82  
    83  func NewESXiClientConfig(host string, port int, account, password string) *ESXiClientConfig {
    84  	cfg := &ESXiClientConfig{
    85  		host:     host,
    86  		port:     port,
    87  		account:  account,
    88  		password: password,
    89  	}
    90  	return cfg
    91  }
    92  
    93  func (cfg *ESXiClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *ESXiClientConfig {
    94  	cfg.cpcfg = cpcfg
    95  	return cfg
    96  }
    97  
    98  func (cfg *ESXiClientConfig) Debug(debug bool) *ESXiClientConfig {
    99  	cfg.debug = debug
   100  	return cfg
   101  }
   102  
   103  func (cfg *ESXiClientConfig) Format(format string) *ESXiClientConfig {
   104  	cfg.format = format
   105  	return cfg
   106  }
   107  
   108  func (cfg *ESXiClientConfig) Managed(managed bool) *ESXiClientConfig {
   109  	cfg.managed = managed
   110  	return cfg
   111  }
   112  
   113  type SESXiClient struct {
   114  	*ESXiClientConfig
   115  
   116  	cloudprovider.SFakeOnPremiseRegion
   117  	multicloud.SRegion
   118  	multicloud.SNoObjectStorageRegion
   119  
   120  	client  *govmomi.Client
   121  	context context.Context
   122  
   123  	datacenters     []*SDatacenter
   124  	networkQueryMap *sync.Map
   125  }
   126  
   127  func NewESXiClient(cfg *ESXiClientConfig) (*SESXiClient, error) {
   128  	cfg.Managed(true)
   129  	return NewESXiClient2(cfg)
   130  }
   131  
   132  func NewESXiClient2(cfg *ESXiClientConfig) (*SESXiClient, error) {
   133  	cli := &SESXiClient{
   134  		ESXiClientConfig: cfg,
   135  		context:          context.Background(),
   136  	}
   137  
   138  	err := cli.connect()
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  
   143  	if !cli.IsVCenter() {
   144  		err := cli.checkHostManagedByVCenter()
   145  		if err != nil {
   146  			if cfg.managed {
   147  				cli.disconnect()
   148  				return nil, err
   149  			} else {
   150  				log.Warningf("%s", err)
   151  			}
   152  		}
   153  	}
   154  	return cli, nil
   155  }
   156  
   157  func NewESXiClientFromJson(ctx context.Context, input jsonutils.JSONObject) (*SESXiClient, *vcenter.SVCenterAccessInfo, error) {
   158  	accessInfo := new(vcenter.SVCenterAccessInfo)
   159  	err := input.Unmarshal(accessInfo)
   160  	if err != nil {
   161  		return nil, nil, errors.Wrapf(err, "unmarshal SVCenterAccessInfo: %s", input)
   162  	}
   163  	c, err := NewESXiClientFromAccessInfo(ctx, accessInfo)
   164  	return c, accessInfo, err
   165  }
   166  
   167  func NewESXiClientFromAccessInfo(ctx context.Context, accessInfo *vcenter.SVCenterAccessInfo) (*SESXiClient, error) {
   168  	if len(accessInfo.VcenterId) > 0 {
   169  		tmp, err := utils.DescryptAESBase64(accessInfo.VcenterId, accessInfo.Password)
   170  		if err == nil {
   171  			accessInfo.Password = tmp
   172  		}
   173  	}
   174  	client, err := NewESXiClient(
   175  		NewESXiClientConfig(
   176  			accessInfo.Host,
   177  			accessInfo.Port,
   178  			accessInfo.Account,
   179  			accessInfo.Password,
   180  		).Managed(true),
   181  	)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  	if ctx == nil {
   186  		ctx = context.Background()
   187  	}
   188  	client.context = ctx
   189  	return client, nil
   190  }
   191  
   192  func (cli *SESXiClient) getUrl() string {
   193  	if cli.port == 443 || cli.port == 0 {
   194  		return fmt.Sprintf("https://%s", cli.host)
   195  	} else {
   196  		return fmt.Sprintf("https://%s:%d", cli.host, cli.port)
   197  	}
   198  }
   199  
   200  func (cli *SESXiClient) url() string {
   201  	return fmt.Sprintf("%s/sdk", cli.getUrl())
   202  }
   203  
   204  var (
   205  	red    = color.New(color.FgRed, color.Bold).PrintlnFunc()
   206  	green  = color.New(color.FgGreen, color.Bold).PrintlnFunc()
   207  	yellow = color.New(color.FgYellow, color.Bold).PrintlnFunc()
   208  	cyan   = color.New(color.FgHiCyan, color.Bold).PrintlnFunc()
   209  )
   210  
   211  func (cli *SESXiClient) connect() error {
   212  	u, err := url.Parse(cli.url())
   213  	if err != nil {
   214  		return fmt.Errorf("Illegal url %s: %s", cli.url(), err)
   215  	}
   216  
   217  	var govmcli *govmomi.Client
   218  	{
   219  		insecure := true
   220  		soapCli := soap.NewClient(u, insecure)
   221  		httpClient := &soapCli.Client
   222  		transport := httputils.GetAdaptiveTransport(true)
   223  		transport.Proxy = cli.cpcfg.ProxyFunc
   224  		httpClient.Transport = cloudprovider.GetCheckTransport(transport, func(req *http.Request) (func(resp *http.Response), error) {
   225  			if cli.debug {
   226  				dump, _ := httputil.DumpRequestOut(req, false)
   227  				yellow(string(dump))
   228  				// 忽略掉上传文件的请求,避免大量日志输出
   229  				if req.Header.Get("Content-Type") != "application/octet-stream" {
   230  					curlCmd, _ := http2curl.GetCurlCommand(req)
   231  					cyan("CURL:", curlCmd, "\n")
   232  				}
   233  			}
   234  			respCheck := func(resp *http.Response) {
   235  				if cli.debug {
   236  					dump, _ := httputil.DumpResponse(resp, true)
   237  					body := string(dump)
   238  					if cli.format == "json" {
   239  						obj, err := xj.Convert(bytes.NewReader(dump))
   240  						if err == nil {
   241  							body = string(obj.String())
   242  						}
   243  					}
   244  					if resp.StatusCode < 300 {
   245  						green(body)
   246  					} else if resp.StatusCode < 400 {
   247  						yellow(body)
   248  					} else {
   249  						red(body)
   250  					}
   251  				}
   252  			}
   253  			return respCheck, nil
   254  		})
   255  		vimCli, err := vim25.NewClient(cli.context, soapCli)
   256  		if err != nil {
   257  			return err
   258  		}
   259  		govmcli = &govmomi.Client{
   260  			Client:         vimCli,
   261  			SessionManager: session.NewManager(vimCli),
   262  		}
   263  	}
   264  
   265  	userinfo := url.UserPassword(cli.account, cli.password)
   266  
   267  	err = govmcli.Login(cli.context, userinfo)
   268  	if err != nil {
   269  		return err
   270  	}
   271  
   272  	cli.client = govmcli
   273  
   274  	defaultDc.ManagedEntity.Parent = &cli.client.ServiceContent.RootFolder
   275  
   276  	return nil
   277  }
   278  
   279  func (cli *SESXiClient) disconnect() error {
   280  	if cli.client != nil {
   281  		return cli.client.Logout(cli.context)
   282  	}
   283  	return nil
   284  }
   285  
   286  func (cli *SESXiClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
   287  	err := cli.connect()
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  	subAccount := cloudprovider.SSubAccount{
   292  		Account:      cli.account,
   293  		Name:         cli.cpcfg.Name,
   294  		HealthStatus: api.CLOUD_PROVIDER_HEALTH_NORMAL,
   295  	}
   296  	return []cloudprovider.SSubAccount{subAccount}, nil
   297  }
   298  
   299  func (cli *SESXiClient) GetAccountId() string {
   300  	return fmt.Sprintf("%s@%s:%d", cli.account, cli.host, cli.port)
   301  }
   302  
   303  func (cli *SESXiClient) GetI18n() cloudprovider.SModelI18nTable {
   304  	table := cloudprovider.SModelI18nTable{}
   305  	table["name"] = cloudprovider.NewSModelI18nEntry(cli.GetName()).CN(cli.GetName())
   306  	return table
   307  }
   308  
   309  func (cli *SESXiClient) GetVersion() string {
   310  	return cli.client.ServiceContent.About.Version
   311  }
   312  
   313  func (cli *SESXiClient) About() jsonutils.JSONObject {
   314  	about := jsonutils.Marshal(&cli.client.ServiceContent.About)
   315  	aboutDict := about.(*jsonutils.JSONDict)
   316  	aboutDict.Add(jsonutils.NewString(cli.getEndpointType()), "endpoint_type")
   317  	return aboutDict
   318  }
   319  
   320  func (cli *SESXiClient) getEndpointType() string {
   321  	if cli.IsVCenter() {
   322  		return "VCenter"
   323  	} else {
   324  		return "ESXi"
   325  	}
   326  }
   327  
   328  func (cli *SESXiClient) GetUUID() string {
   329  	about := cli.client.ServiceContent.About
   330  	return about.InstanceUuid
   331  }
   332  
   333  func (cli *SESXiClient) fetchDatacentersFromHosts() error {
   334  	var hosts []mo.HostSystem
   335  	err := cli.scanAllMObjects(HOST_SYSTEM_PROPS, &hosts)
   336  	if err != nil {
   337  		return errors.Wrap(err, "cli.scanAllMObjects host")
   338  	}
   339  	dcList := make([]*SDatacenter, 0)
   340  	for i := 0; i < len(hosts); i += 1 {
   341  		me := newManagedObject(cli, &hosts[i], nil)
   342  		dcme := me.findInParents("Datacenter")
   343  		if dcme == nil {
   344  			return cloudprovider.ErrNotFound
   345  		}
   346  		_, err := findDatacenterByMoId(dcList, dcme.Self.Value)
   347  		if err == nil {
   348  			continue
   349  		}
   350  		var moDc mo.Datacenter
   351  		err = cli.reference2Object(dcme.Self, DATACENTER_PROPS, &moDc)
   352  		if err != nil {
   353  			return errors.Wrap(err, "cli.reference2Object")
   354  		}
   355  		dc, err := cli.newDatacenterFromMo(&moDc)
   356  		if err != nil {
   357  			return errors.Wrap(err, "cli.newDatacenterFromMo")
   358  		}
   359  		dcList = append(dcList, dc)
   360  	}
   361  	cli.datacenters = dcList
   362  	return nil
   363  }
   364  
   365  func (cli *SESXiClient) fetchFakeDatacenter() error {
   366  	dc, err := cli.newDatacenterFromMo(&defaultDc)
   367  	if err != nil {
   368  		return errors.Wrap(err, "newDatacenterFromMo")
   369  	}
   370  	cli.datacenters = []*SDatacenter{dc}
   371  	return nil
   372  }
   373  
   374  func (cli *SESXiClient) fetchDatacenters() error {
   375  	var dcs []mo.Datacenter
   376  	err := cli.scanAllMObjects(DATACENTER_PROPS, &dcs)
   377  	if err != nil {
   378  		// log.Debugf("cli.scanAllMObjects datacenter fail %s, try cli.fetchDatacentersFromHosts", err)
   379  		// err := cli.fetchDatacentersFromHosts()
   380  		// if err != nil {
   381  		log.Debugf("cli.fetchDatacentersFromHosts fail %s, try cli.fetchFakeDatacenter", err)
   382  		return cli.fetchFakeDatacenter()
   383  		// }
   384  	}
   385  	cli.datacenters = make([]*SDatacenter, len(dcs))
   386  	for i := 0; i < len(dcs); i += 1 {
   387  		dc, err := cli.newDatacenterFromMo(&dcs[i])
   388  		if err != nil {
   389  			return errors.Wrap(err, "cli.newDatacenterFromMo")
   390  		}
   391  		cli.datacenters[i] = dc
   392  	}
   393  	return nil
   394  }
   395  
   396  func (cli *SESXiClient) newDatacenterFromMo(mo *mo.Datacenter) (*SDatacenter, error) {
   397  	dc := newDatacenter(cli, mo)
   398  	err := dc.scanHosts()
   399  	if err != nil {
   400  		return nil, errors.Wrap(err, "dc.scanHosts")
   401  	}
   402  	// err = dc.scanDatastores()
   403  	// if err != nil {
   404  	// 	return nil, errors.Wrap(err, "dc.scanDatastores")
   405  	// }
   406  	return dc, nil
   407  }
   408  
   409  func (cli *SESXiClient) scanAllMObjects(props []string, dst interface{}) error {
   410  	return cli.scanMObjects(cli.client.ServiceContent.RootFolder, props, dst)
   411  }
   412  
   413  func (cli *SESXiClient) SearchVM(id string) (*SVirtualMachine, error) {
   414  	filter := property.Filter{}
   415  	filter["summary.config.uuid"] = id
   416  	var movms []mo.VirtualMachine
   417  	err := cli.scanMObjectsWithFilter(cli.client.ServiceContent.RootFolder, VIRTUAL_MACHINE_PROPS, &movms, filter)
   418  	if err != nil {
   419  		return nil, err
   420  	}
   421  	if len(movms) == 0 {
   422  		return nil, errors.ErrNotFound
   423  	}
   424  	vm := NewVirtualMachine(cli, &movms[0], nil)
   425  	dc, err := vm.fetchDatacenter()
   426  	if err != nil {
   427  		return nil, errors.Wrap(err, "fetchDatacenter")
   428  	}
   429  	vm.datacenter = dc
   430  	return vm, nil
   431  }
   432  
   433  func (cli *SESXiClient) SearchTemplateVM(id string) (*SVirtualMachine, error) {
   434  	filter := property.Filter{}
   435  	uuid := toTemplateUuid(id)
   436  	filter["summary.config.uuid"] = uuid
   437  	var movms []mo.VirtualMachine
   438  	err := cli.scanMObjectsWithFilter(cli.client.ServiceContent.RootFolder, VIRTUAL_MACHINE_PROPS, &movms, filter)
   439  	if err != nil {
   440  		return nil, err
   441  	}
   442  	if len(movms) == 0 {
   443  		return nil, errors.ErrNotFound
   444  	}
   445  	vm := NewVirtualMachine(cli, &movms[0], nil)
   446  	if !vm.IsTemplate() {
   447  		return nil, errors.ErrNotFound
   448  	}
   449  	dc, err := vm.fetchDatacenter()
   450  	if err != nil {
   451  		return nil, errors.Wrap(err, "fetchDatacenter")
   452  	}
   453  	vm.datacenter = dc
   454  	return vm, nil
   455  }
   456  
   457  func (cli *SESXiClient) scanMObjectsWithFilter(folder types.ManagedObjectReference, props []string, dst interface{}, filter property.Filter) error {
   458  	dstValue := reflect.Indirect(reflect.ValueOf(dst))
   459  	dstType := dstValue.Type()
   460  	dstEleType := dstType.Elem()
   461  
   462  	resType := dstEleType.Name()
   463  	m := view.NewManager(cli.client.Client)
   464  
   465  	v, err := m.CreateContainerView(cli.context, folder, []string{resType}, true)
   466  	if err != nil {
   467  		return errors.Wrapf(err, "m.CreateContainerView %s", resType)
   468  	}
   469  
   470  	defer v.Destroy(cli.context)
   471  
   472  	err = v.RetrieveWithFilter(cli.context, []string{resType}, props, dst, filter)
   473  	if err != nil {
   474  		// hack
   475  		if strings.Contains(err.Error(), "object references is empty") {
   476  			return nil
   477  		}
   478  		return errors.Wrapf(err, "v.RetrieveWithFilter %s", resType)
   479  	}
   480  
   481  	return nil
   482  }
   483  
   484  func (cli *SESXiClient) scanMObjects(folder types.ManagedObjectReference, props []string, dst interface{}) error {
   485  	dstValue := reflect.Indirect(reflect.ValueOf(dst))
   486  	dstType := dstValue.Type()
   487  	dstEleType := dstType.Elem()
   488  
   489  	resType := dstEleType.Name()
   490  	m := view.NewManager(cli.client.Client)
   491  
   492  	v, err := m.CreateContainerView(cli.context, folder, []string{resType}, true)
   493  	if err != nil {
   494  		return errors.Wrapf(err, "m.CreateContainerView %s", resType)
   495  	}
   496  
   497  	defer v.Destroy(cli.context)
   498  
   499  	err = v.Retrieve(cli.context, []string{resType}, props, dst)
   500  	if err != nil {
   501  		return errors.Wrapf(err, "v.Retrieve %s", resType)
   502  	}
   503  
   504  	return nil
   505  }
   506  
   507  func (cli *SESXiClient) references2Objects(refs []types.ManagedObjectReference, props []string, dst interface{}) error {
   508  	pc := property.DefaultCollector(cli.client.Client)
   509  	err := pc.Retrieve(cli.context, refs, props, dst)
   510  	if err != nil {
   511  		log.Errorf("pc.Retrieve fail %s", err)
   512  		return err
   513  	}
   514  	return nil
   515  }
   516  
   517  func (cli *SESXiClient) reference2Object(ref types.ManagedObjectReference, props []string, dst interface{}) error {
   518  	pc := property.DefaultCollector(cli.client.Client)
   519  	err := pc.RetrieveOne(cli.context, ref, props, dst)
   520  	if err != nil {
   521  		return errors.Wrap(err, "pc.RetrieveOne")
   522  	}
   523  	return nil
   524  }
   525  
   526  func (cli *SESXiClient) GetDatacenters() ([]*SDatacenter, error) {
   527  	if cli.datacenters == nil {
   528  		err := cli.fetchDatacenters()
   529  		if err != nil {
   530  			return nil, err
   531  		}
   532  	}
   533  	return cli.datacenters, nil
   534  }
   535  
   536  func (cli *SESXiClient) FindDatacenterByMoId(dcId string) (*SDatacenter, error) {
   537  	dcs, err := cli.GetDatacenters()
   538  	if err != nil {
   539  		return nil, err
   540  	}
   541  	return findDatacenterByMoId(dcs, dcId)
   542  }
   543  
   544  func findDatacenterByMoId(dcs []*SDatacenter, dcId string) (*SDatacenter, error) {
   545  	for i := 0; i < len(dcs); i += 1 {
   546  		if dcs[i].GetId() == dcId {
   547  			return dcs[i], nil
   548  		}
   549  		// defaultDcId means no premision to get datacenter, so return fake dc
   550  		if dcs[i].GetId() == defaultDcId {
   551  			return dcs[i], nil
   552  		}
   553  	}
   554  	return nil, cloudprovider.ErrNotFound
   555  }
   556  
   557  func (cli *SESXiClient) GetIProjects() ([]cloudprovider.ICloudProject, error) {
   558  	dcs, err := cli.GetDatacenters()
   559  	if err != nil {
   560  		return nil, errors.Wrap(err, "GetDatacenters")
   561  	}
   562  	ret := []cloudprovider.ICloudProject{}
   563  	for i := 0; i < len(dcs); i++ {
   564  		iprojects, err := dcs[i].GetResourcePools()
   565  		if err != nil {
   566  			return nil, errors.Wrap(err, "GetResourcePools")
   567  		}
   568  		ret = append(ret, iprojects...)
   569  	}
   570  	return ret, nil
   571  }
   572  
   573  func (cli *SESXiClient) FindHostByMoId(moId string) (cloudprovider.ICloudHost, error) {
   574  	dcs, err := cli.GetDatacenters()
   575  	if err != nil {
   576  		return nil, err
   577  	}
   578  	for i := 0; i < len(dcs); i += 1 {
   579  		ihost, err := dcs[i].GetIHostByMoId(moId)
   580  		if err == nil {
   581  			return ihost, nil
   582  		}
   583  	}
   584  	return nil, cloudprovider.ErrNotFound
   585  }
   586  
   587  func (cli *SESXiClient) getPrivateId(idStr string) string {
   588  	if len(cli.cpcfg.Id) > 0 && strings.HasPrefix(idStr, cli.cpcfg.Id) {
   589  		idStr = idStr[len(cli.cpcfg.Id)+1:]
   590  	}
   591  	return idStr
   592  }
   593  
   594  func (cli *SESXiClient) checkHostManagedByVCenter() error {
   595  	host, err := cli.FindHostByIp(cli.host)
   596  	if err != nil {
   597  		if errors.Cause(err) == errors.ErrNotFound {
   598  			// host might be behind a NAT
   599  			return nil
   600  		}
   601  		return errors.Wrap(err, "cli.FindHostByIp")
   602  	}
   603  	if host.IsManagedByVCenter() {
   604  		return fmt.Errorf("ESXi host is managed by vcenter %s, please connect to vcenter instead for full management functions!", host.GetManagementServerIp())
   605  	}
   606  	return nil
   607  }
   608  
   609  func (cli *SESXiClient) IsHostIpExists(hostIp string) (bool, error) {
   610  	searchIndex := object.NewSearchIndex(cli.client.Client)
   611  
   612  	hostRef, err := searchIndex.FindByIp(cli.context, nil, cli.getPrivateId(hostIp), false)
   613  	if err != nil {
   614  		log.Errorf("searchIndex.FindByIp fail %s", err)
   615  		return false, err
   616  	}
   617  	if hostRef == nil {
   618  		return false, nil
   619  	}
   620  	return true, nil
   621  }
   622  
   623  func (cli *SESXiClient) FindHostByIp(hostIp string) (*SHost, error) {
   624  	searchIndex := object.NewSearchIndex(cli.client.Client)
   625  
   626  	hostRef, err := searchIndex.FindByIp(cli.context, nil, cli.getPrivateId(hostIp), false)
   627  	if err != nil {
   628  		log.Errorf("searchIndex.FindByIp fail %s", err)
   629  		return nil, errors.Wrap(err, "searchIndex.FindByIp")
   630  	}
   631  
   632  	if hostRef == nil {
   633  		return nil, errors.Wrapf(errors.ErrNotFound, "cannot find %s", cli.getPrivateId(hostIp))
   634  	}
   635  
   636  	var host mo.HostSystem
   637  	err = cli.reference2Object(hostRef.Reference(), HOST_SYSTEM_PROPS, &host)
   638  	if err != nil {
   639  		log.Errorf("reference2Object fail %s", err)
   640  		return nil, errors.Wrap(err, "cli.reference2Object")
   641  	}
   642  
   643  	h := NewHost(cli, &host, nil)
   644  	if h == nil {
   645  		return nil, errors.Wrap(errors.ErrInvalidStatus, "empty host mo")
   646  	}
   647  	return h, nil
   648  }
   649  
   650  func (cli *SESXiClient) acquireCloneTicket() (string, error) {
   651  	manager := session.NewManager(cli.client.Client)
   652  	return manager.AcquireCloneTicket(cli.context)
   653  }
   654  
   655  func (cli *SESXiClient) IsVCenter() bool {
   656  	return cli.client.Client.IsVC()
   657  }
   658  
   659  func (cli *SESXiClient) IsValid() bool {
   660  	return cli.client.Client.Valid()
   661  }
   662  
   663  func (cli *SESXiClient) GetCapabilities() []string {
   664  	caps := []string{
   665  		cloudprovider.CLOUD_CAPABILITY_PROJECT,
   666  		cloudprovider.CLOUD_CAPABILITY_COMPUTE,
   667  		// cloudprovider.CLOUD_CAPABILITY_NETWORK,
   668  		// cloudprovider.CLOUD_CAPABILITY_LOADBALANCER,
   669  		// cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
   670  		// cloudprovider.CLOUD_CAPABILITY_RDS,
   671  		// cloudprovider.CLOUD_CAPABILITY_CACHE,
   672  		// cloudprovider.CLOUD_CAPABILITY_EVENT,
   673  	}
   674  	return caps
   675  }
   676  
   677  func (cli *SESXiClient) FindVMByPrivateID(idstr string) (*SVirtualMachine, error) {
   678  	searchIndex := object.NewSearchIndex(cli.client.Client)
   679  	instanceUuid := true
   680  	vmRef, err := searchIndex.FindByUuid(cli.context, nil, idstr, true, &instanceUuid)
   681  	if err != nil {
   682  		return nil, errors.Wrap(err, "searchIndex.FindByUuid fail")
   683  	}
   684  	if vmRef == nil {
   685  		return nil, fmt.Errorf("cannot find %s", idstr)
   686  	}
   687  	var vm mo.VirtualMachine
   688  	err = cli.reference2Object(vmRef.Reference(), VIRTUAL_MACHINE_PROPS, &vm)
   689  	if err != nil {
   690  		return nil, errors.Wrap(err, "reference2Object fail")
   691  	}
   692  
   693  	ret := NewVirtualMachine(cli, &vm, nil)
   694  	if ret == nil {
   695  		return nil, errors.Error("invalid vm")
   696  	}
   697  	return ret, nil
   698  }
   699  
   700  func (cli *SESXiClient) DoExtendDiskOnline(_vm *SVirtualMachine, _disk *SVirtualDisk, newSizeMb int64) error {
   701  	disk := _disk.getVirtualDisk()
   702  	disk.CapacityInKB = newSizeMb * 1024
   703  	devSepc := types.VirtualDeviceConfigSpec{Operation: types.VirtualDeviceConfigSpecOperationEdit, Device: disk}
   704  	spec := types.VirtualMachineConfigSpec{DeviceChange: []types.BaseVirtualDeviceConfigSpec{&devSepc}}
   705  	vm := object.NewVirtualMachine(cli.client.Client, _vm.getVirtualMachine().Reference())
   706  	task, err := vm.Reconfigure(cli.context, spec)
   707  	if err != nil {
   708  		return errors.Wrapf(err, "vm reconfigure failed")
   709  	}
   710  	return task.Wait(cli.context)
   711  }
   712  
   713  func (cli *SESXiClient) ExtendDisk(url string, newSizeMb int64) error {
   714  	param := types.ExtendVirtualDisk_Task{
   715  		This:          *cli.client.Client.ServiceContent.VirtualDiskManager,
   716  		Name:          url,
   717  		NewCapacityKb: newSizeMb * 1024,
   718  	}
   719  	response, err := methods.ExtendVirtualDisk_Task(cli.context, cli.client, &param)
   720  	if err != nil {
   721  		return errors.Wrapf(err, "extend virtualdisk task failed")
   722  	}
   723  	log.Debugf("extend virtual disk task response: %s", response.Returnval.String())
   724  	return nil
   725  }
   726  
   727  func (cli *SESXiClient) CopyDisk(ctx context.Context, src, dst string, isForce bool) error {
   728  	dm := object.NewVirtualDiskManager(cli.client.Client)
   729  	task, err := dm.CopyVirtualDisk(ctx, src, nil, dst, nil, nil, isForce)
   730  	if err != nil {
   731  		return errors.Wrap(err, "CopyVirtualDisk")
   732  	}
   733  	return task.Wait(ctx)
   734  }
   735  
   736  func (cli *SESXiClient) MoveDisk(ctx context.Context, src, dst string, isForce bool) error {
   737  	dm := object.NewVirtualDiskManager(cli.client.Client)
   738  	task, err := dm.MoveVirtualDisk(ctx, src, nil, dst, nil, isForce)
   739  	if err != nil {
   740  		return errors.Wrap(err, "MoveVirtualDisk")
   741  	}
   742  	return task.Wait(ctx)
   743  }