yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/esxi/storage.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  	"encoding/binary"
    21  	"fmt"
    22  	"io"
    23  	"io/ioutil"
    24  	"math/rand"
    25  	"net/http"
    26  	"net/url"
    27  	"os"
    28  	"path"
    29  	"path/filepath"
    30  	"regexp"
    31  	"strconv"
    32  	"strings"
    33  	"text/template"
    34  	"time"
    35  	"unicode"
    36  
    37  	"github.com/vmware/govmomi/object"
    38  	"github.com/vmware/govmomi/ovf"
    39  	"github.com/vmware/govmomi/property"
    40  	"github.com/vmware/govmomi/vim25/mo"
    41  	"github.com/vmware/govmomi/vim25/progress"
    42  	"github.com/vmware/govmomi/vim25/soap"
    43  	"github.com/vmware/govmomi/vim25/types"
    44  
    45  	"yunion.io/x/jsonutils"
    46  	"yunion.io/x/log"
    47  	"yunion.io/x/pkg/errors"
    48  
    49  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    50  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    51  	"yunion.io/x/cloudmux/pkg/multicloud"
    52  	"yunion.io/x/onecloud/pkg/util/vmdkutils"
    53  )
    54  
    55  var DATASTORE_PROPS = []string{"name", "parent", "info", "summary", "host", "vm"}
    56  
    57  type SDatastore struct {
    58  	multicloud.SStorageBase
    59  	SManagedObject
    60  
    61  	// vms []cloudprovider.ICloudVM
    62  
    63  	ihosts []cloudprovider.ICloudHost
    64  	idisks []cloudprovider.ICloudDisk
    65  
    66  	storageCache *SDatastoreImageCache
    67  }
    68  
    69  func NewDatastore(manager *SESXiClient, ds *mo.Datastore, dc *SDatacenter) *SDatastore {
    70  	return &SDatastore{SManagedObject: newManagedObject(manager, ds, dc)}
    71  }
    72  
    73  func (self *SDatastore) getDatastore() *mo.Datastore {
    74  	return self.object.(*mo.Datastore)
    75  }
    76  
    77  func (self *SDatastore) GetGlobalId() string {
    78  	volId, err := self.getVolumeId()
    79  	if err != nil {
    80  		log.Errorf("get datastore global ID error %s", err)
    81  	}
    82  	return volId
    83  }
    84  
    85  func (self *SDatastore) GetName() string {
    86  	volName, err := self.getVolumeName()
    87  	if err != nil {
    88  		log.Fatalf("datastore get name error %s", err)
    89  	}
    90  	return fmt.Sprintf("%s-%s", self.getVolumeType(), volName)
    91  }
    92  
    93  func (self *SDatastore) GetRelName() string {
    94  	return self.getDatastore().Info.GetDatastoreInfo().Name
    95  }
    96  
    97  func (self *SDatastore) GetCapacityMB() int64 {
    98  	moStore := self.getDatastore()
    99  	return moStore.Summary.Capacity / 1024 / 1024
   100  }
   101  
   102  func (self *SDatastore) GetCapacityUsedMB() int64 {
   103  	moStore := self.getDatastore()
   104  	return self.GetCapacityMB() - moStore.Summary.FreeSpace/1024/1024
   105  }
   106  
   107  func (self *SDatastore) GetEnabled() bool {
   108  	return true
   109  }
   110  
   111  func (self *SDatastore) GetStatus() string {
   112  	if self.getDatastore().Summary.Accessible {
   113  		return api.STORAGE_ONLINE
   114  	} else {
   115  		return api.STORAGE_OFFLINE
   116  	}
   117  }
   118  
   119  func (self *SDatastore) Refresh() error {
   120  	base := self.SManagedObject
   121  	var moObj mo.Datastore
   122  	err := self.manager.reference2Object(self.object.Reference(), DATASTORE_PROPS, &moObj)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	base.object = &moObj
   127  	*self = SDatastore{}
   128  	self.SManagedObject = base
   129  	return nil
   130  }
   131  
   132  func (self *SDatastore) IsEmulated() bool {
   133  	return false
   134  }
   135  
   136  func (self *SDatastore) getVolumeId() (string, error) {
   137  	moStore := self.getDatastore()
   138  	switch fsInfo := moStore.Info.(type) {
   139  	case *types.VmfsDatastoreInfo:
   140  		if fsInfo.Vmfs.Local == nil || *fsInfo.Vmfs.Local {
   141  			host, err := self.getLocalHost()
   142  			if err == nil {
   143  				return fmt.Sprintf("%s:%s", host.GetAccessIp(), fsInfo.Vmfs.Uuid), nil
   144  			}
   145  		}
   146  		return fsInfo.Vmfs.Uuid, nil
   147  	case *types.NasDatastoreInfo:
   148  		return fmt.Sprintf("%s:%s", fsInfo.Nas.RemoteHost, fsInfo.Nas.RemotePath), nil
   149  	}
   150  	if moStore.Summary.Type == "vsan" {
   151  		vsanId := moStore.Summary.Url
   152  		vsanId = vsanId[strings.Index(vsanId, "vsan:"):]
   153  		endIdx := len(vsanId)
   154  		for ; endIdx >= 0 && vsanId[endIdx-1] == '/'; endIdx -= 1 {
   155  		}
   156  		return vsanId[:endIdx], nil
   157  	}
   158  	log.Fatalf("unsupported volume type %#v", moStore.Info)
   159  	return "", cloudprovider.ErrNotImplemented
   160  }
   161  
   162  func (self *SDatastore) getVolumeType() string {
   163  	return self.getDatastore().Summary.Type
   164  }
   165  
   166  func (self *SDatastore) getVolumeName() (string, error) {
   167  	moStore := self.getDatastore()
   168  
   169  	if self.isLocalVMFS() {
   170  		host, err := self.getLocalHost()
   171  		if err != nil {
   172  			return "", err
   173  		}
   174  		return fmt.Sprintf("%s-%s", host.GetAccessIp(), moStore.Info.GetDatastoreInfo().Name), nil
   175  	}
   176  	dc, err := self.GetDatacenter()
   177  	if err != nil {
   178  		return "", nil
   179  	}
   180  	return fmt.Sprintf("%s-%s", dc.GetName(), moStore.Info.GetDatastoreInfo().Name), nil
   181  }
   182  
   183  func (self *SDatastore) getAttachedHosts() ([]cloudprovider.ICloudHost, error) {
   184  	ihosts := make([]cloudprovider.ICloudHost, 0)
   185  
   186  	moStore := self.getDatastore()
   187  	for i := 0; i < len(moStore.Host); i += 1 {
   188  		idstr := moRefId(moStore.Host[i].Key)
   189  		host, err := self.datacenter.GetIHostByMoId(idstr)
   190  		if err != nil {
   191  			return nil, err
   192  		}
   193  		ihosts = append(ihosts, host)
   194  	}
   195  
   196  	return ihosts, nil
   197  }
   198  
   199  func (self *SDatastore) getCachedAttachedHosts() ([]cloudprovider.ICloudHost, error) {
   200  	if self.ihosts == nil {
   201  		var err error
   202  		self.ihosts, err = self.getAttachedHosts()
   203  		if err != nil {
   204  			return nil, err
   205  		}
   206  	}
   207  	return self.ihosts, nil
   208  }
   209  
   210  func (self *SDatastore) GetAttachedHosts() ([]cloudprovider.ICloudHost, error) {
   211  	return self.getCachedAttachedHosts()
   212  }
   213  
   214  func (self *SDatastore) getLocalHost() (cloudprovider.ICloudHost, error) {
   215  	hosts, err := self.GetAttachedHosts()
   216  	if err != nil {
   217  		return nil, errors.Wrap(err, "self.GetAttachedHosts")
   218  	}
   219  	if len(hosts) == 1 {
   220  		return hosts[0], nil
   221  	}
   222  	return nil, cloudprovider.ErrInvalidStatus
   223  }
   224  
   225  func (self *SDatastore) GetIStoragecache() cloudprovider.ICloudStoragecache {
   226  	if self.isLocalVMFS() {
   227  		ihost, err := self.getLocalHost()
   228  		if err != nil {
   229  			log.Errorf("GetIStoragecache getLocalHost fail %s", err)
   230  			return nil
   231  		}
   232  		host := ihost.(*SHost)
   233  		sc, err := host.getLocalStorageCache()
   234  		if err != nil {
   235  			log.Errorf("GetIStoragecache getLocalStorageCache fail %s", err)
   236  			return nil
   237  		}
   238  		return sc
   239  	} else {
   240  		return self.getStorageCache()
   241  	}
   242  }
   243  
   244  func (self *SDatastore) getStorageCache() *SDatastoreImageCache {
   245  	if self.storageCache == nil {
   246  		self.storageCache = &SDatastoreImageCache{
   247  			datastore: self,
   248  		}
   249  	}
   250  	return self.storageCache
   251  }
   252  
   253  func (self *SDatastore) GetIZone() cloudprovider.ICloudZone {
   254  	return nil
   255  }
   256  
   257  func (self *SDatastore) FetchNoTemplateVMs() ([]*SVirtualMachine, error) {
   258  	mods := self.getDatastore()
   259  	filter := property.Filter{}
   260  	filter["datastore"] = mods.Reference()
   261  	return self.datacenter.fetchVMsWithFilter(filter)
   262  }
   263  
   264  func (self *SDatastore) FetchTemplateVMs() ([]*SVirtualMachine, error) {
   265  	mods := self.getDatastore()
   266  	filter := property.Filter{}
   267  	filter["config.template"] = true
   268  	filter["datastore"] = mods.Reference()
   269  	return self.datacenter.fetchVMsWithFilter(filter)
   270  }
   271  
   272  func (self *SDatastore) FetchTemplateVMById(id string) (*SVirtualMachine, error) {
   273  	mods := self.getDatastore()
   274  	filter := property.Filter{}
   275  	uuid := toTemplateUuid(id)
   276  	filter["summary.config.uuid"] = uuid
   277  	filter["config.template"] = true
   278  	filter["datastore"] = mods.Reference()
   279  	vms, err := self.datacenter.fetchVMsWithFilter(filter)
   280  	if err != nil {
   281  		return nil, err
   282  	}
   283  	if len(vms) == 0 {
   284  		return nil, errors.ErrNotFound
   285  	}
   286  	return vms[0], nil
   287  }
   288  
   289  func (self *SDatastore) FetchFakeTempateVMById(id string, regex string) (*SVirtualMachine, error) {
   290  	mods := self.getDatastore()
   291  	filter := property.Filter{}
   292  	uuid := toTemplateUuid(id)
   293  	filter["summary.config.uuid"] = uuid
   294  	filter["datastore"] = mods.Reference()
   295  	filter["summary.runtime.powerState"] = types.VirtualMachinePowerStatePoweredOff
   296  	filter["config.template"] = false
   297  	movms, err := self.datacenter.fetchMoVms(filter, []string{"name"})
   298  	if err != nil {
   299  		return nil, errors.Wrap(err, "unable to fetch mo.VirtualMachines")
   300  	}
   301  	vms, err := self.datacenter.fetchFakeTemplateVMs(movms, regex)
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  	if len(vms) == 0 {
   306  		return nil, errors.ErrNotFound
   307  	}
   308  	return vms[0], nil
   309  }
   310  
   311  func (self *SDatastore) FetchFakeTempateVMs(regex string) ([]*SVirtualMachine, error) {
   312  	mods := self.getDatastore()
   313  	filter := property.Filter{}
   314  	filter["datastore"] = mods.Reference()
   315  	filter["summary.runtime.powerState"] = types.VirtualMachinePowerStatePoweredOff
   316  	filter["config.template"] = false
   317  	movms, err := self.datacenter.fetchMoVms(filter, []string{"name"})
   318  	if err != nil {
   319  		return nil, errors.Wrap(err, "unable to fetch mo.VirtualMachines")
   320  	}
   321  	return self.datacenter.fetchFakeTemplateVMs(movms, regex)
   322  }
   323  
   324  func (self *SDatastore) getVMs() ([]cloudprovider.ICloudVM, error) {
   325  	dc, err := self.GetDatacenter()
   326  	if err != nil {
   327  		log.Errorf("SDatastore GetDatacenter fail %s", err)
   328  		return nil, err
   329  	}
   330  	vms := self.getDatastore().Vm
   331  	if len(vms) == 0 {
   332  		return nil, nil
   333  	}
   334  	svms, err := dc.fetchVmsFromCache(vms)
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  	ret := make([]cloudprovider.ICloudVM, len(svms))
   339  	for i := range svms {
   340  		ret[i] = svms[i]
   341  	}
   342  	return ret, err
   343  }
   344  
   345  func (self *SDatastore) GetIDiskById(idStr string) (cloudprovider.ICloudDisk, error) {
   346  	vms, err := self.getVMs()
   347  	if err != nil {
   348  		log.Errorf("self.getVMs fail %s", err)
   349  		return nil, err
   350  	}
   351  	for i := 0; i < len(vms); i += 1 {
   352  		vm := vms[i].(*SVirtualMachine)
   353  		disk, err := vm.GetIDiskById(idStr)
   354  		if err == nil {
   355  			return disk, nil
   356  		}
   357  	}
   358  	return nil, cloudprovider.ErrNotFound
   359  }
   360  
   361  func (self *SDatastore) fetchDisks() error {
   362  	vms, err := self.getVMs()
   363  	if err != nil {
   364  		return err
   365  	}
   366  	allDisks := make([]cloudprovider.ICloudDisk, 0)
   367  	for i := 0; i < len(vms); i += 1 {
   368  		disks, err := vms[i].GetIDisks()
   369  		if err != nil {
   370  			return err
   371  		}
   372  		allDisks = append(allDisks, disks...)
   373  	}
   374  	self.idisks = allDisks
   375  	return nil
   376  }
   377  
   378  func (self *SDatastore) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
   379  	if self.idisks != nil {
   380  		return self.idisks, nil
   381  	}
   382  	err := self.fetchDisks()
   383  	if err != nil {
   384  		return nil, err
   385  	}
   386  	return self.idisks, nil
   387  }
   388  
   389  func (self *SDatastore) isLocalVMFS() bool {
   390  	moStore := self.getDatastore()
   391  	switch vmfsInfo := moStore.Info.(type) {
   392  	case *types.VmfsDatastoreInfo:
   393  		if vmfsInfo.Vmfs.Local == nil || *vmfsInfo.Vmfs.Local == true {
   394  			_, err := self.getLocalHost()
   395  			if err == nil {
   396  				return true
   397  			}
   398  		}
   399  	}
   400  	return false
   401  }
   402  
   403  func (self *SDatastore) GetStorageType() string {
   404  	moStore := self.getDatastore()
   405  	t := strings.ToLower(moStore.Summary.Type)
   406  	switch t {
   407  	case "vmfs":
   408  		if self.isLocalVMFS() {
   409  			return api.STORAGE_LOCAL
   410  		} else {
   411  			return api.STORAGE_NAS
   412  		}
   413  	case "nfs", "nfs41":
   414  		return api.STORAGE_NFS
   415  	case "vsan":
   416  		return api.STORAGE_VSAN
   417  	case "cifs":
   418  		return api.STORAGE_CIFS
   419  	default:
   420  		log.Fatalf("unsupported datastore type %s", moStore.Summary.Type)
   421  		return ""
   422  	}
   423  }
   424  
   425  func (self *SDatastore) GetMediumType() string {
   426  	moStore := self.getDatastore()
   427  	vmfsInfo, ok := moStore.Info.(*types.VmfsDatastoreInfo)
   428  	if ok && vmfsInfo.Vmfs.Ssd != nil && *vmfsInfo.Vmfs.Ssd {
   429  		return api.DISK_TYPE_SSD
   430  	}
   431  	return api.DISK_TYPE_ROTATE
   432  }
   433  
   434  func (self *SDatastore) GetStorageConf() jsonutils.JSONObject {
   435  	conf := jsonutils.NewDict()
   436  	conf.Add(jsonutils.NewString(self.GetName()), "name")
   437  	conf.Add(jsonutils.NewString(self.GetGlobalId()), "id")
   438  	conf.Add(jsonutils.NewString(self.GetDatacenterPathString()), "dc_path")
   439  	volId, err := self.getVolumeId()
   440  	if err != nil {
   441  		log.Errorf("getVaolumeId fail %s", err)
   442  	}
   443  	conf.Add(jsonutils.NewString(volId), "volume_id")
   444  
   445  	volType := self.getVolumeType()
   446  	conf.Add(jsonutils.NewString(volType), "volume_type")
   447  
   448  	volName, err := self.getVolumeName()
   449  	if err != nil {
   450  		log.Errorf("getVaolumeName fail %s", err)
   451  	}
   452  	conf.Add(jsonutils.NewString(volName), "volume_name")
   453  	return conf
   454  }
   455  
   456  const dsPrefix = "ds://"
   457  
   458  func (self *SDatastore) GetUrl() string {
   459  	url := self.getDatastore().Info.GetDatastoreInfo().Url
   460  	if strings.HasPrefix(url, dsPrefix) {
   461  		url = url[len(dsPrefix):]
   462  	}
   463  	return url
   464  }
   465  
   466  func (self *SDatastore) GetMountPoint() string {
   467  	return self.GetUrl()
   468  }
   469  
   470  func (self *SDatastore) HasFile(remotePath string) bool {
   471  	dsName := fmt.Sprintf("[%s]", self.SManagedObject.GetName())
   472  	if strings.HasPrefix(remotePath, dsName) {
   473  		return true
   474  	} else {
   475  		return false
   476  	}
   477  }
   478  
   479  func (self *SDatastore) cleanPath(remotePath string) string {
   480  	dsName := fmt.Sprintf("[%s]", self.SManagedObject.GetName())
   481  	dsUrl := self.GetUrl()
   482  	if strings.HasPrefix(remotePath, dsName) {
   483  		remotePath = remotePath[len(dsName):]
   484  	} else if strings.HasPrefix(remotePath, dsUrl) {
   485  		remotePath = remotePath[len(dsUrl):]
   486  	}
   487  	return strings.TrimSpace(remotePath)
   488  }
   489  
   490  func pathEscape(path string) string {
   491  	segs := strings.Split(path, "/")
   492  	for i := 0; i < len(segs); i += 1 {
   493  		segs[i] = url.PathEscape(segs[i])
   494  	}
   495  	return strings.Join(segs, "/")
   496  }
   497  
   498  func (self *SDatastore) GetPathUrl(remotePath string) string {
   499  	remotePath = self.cleanPath(remotePath)
   500  	if len(remotePath) == 0 || remotePath[0] != '/' {
   501  		remotePath = fmt.Sprintf("/%s", remotePath)
   502  	}
   503  	httpUrl := fmt.Sprintf("%s/folder%s", self.getManagerUri(), pathEscape(remotePath))
   504  	params := jsonutils.NewDict()
   505  	params.Add(jsonutils.NewString(self.SManagedObject.GetName()), "dsName")
   506  	params.Add(jsonutils.NewString(self.GetDatacenterPathString()), "dcPath")
   507  
   508  	return fmt.Sprintf("%s?%s", httpUrl, params.QueryString())
   509  }
   510  
   511  func (self *SDatastore) getPathString(path string) string {
   512  	for len(path) > 0 && path[0] == '/' {
   513  		path = path[1:]
   514  	}
   515  	return fmt.Sprintf("[%s] %s", self.SManagedObject.GetName(), path)
   516  }
   517  
   518  func (self *SDatastore) GetFullPath(remotePath string) string {
   519  	remotePath = self.cleanPath(remotePath)
   520  	return path.Join(self.GetUrl(), remotePath)
   521  }
   522  
   523  func (self *SDatastore) CreateIDisk(conf *cloudprovider.DiskCreateConfig) (cloudprovider.ICloudDisk, error) {
   524  	return nil, cloudprovider.ErrNotImplemented
   525  }
   526  
   527  func (self *SDatastore) FileGetContent(ctx context.Context, remotePath string) ([]byte, error) {
   528  	url := self.GetPathUrl(remotePath)
   529  
   530  	req, err := http.NewRequest("GET", url, nil)
   531  	if err != nil {
   532  		return nil, err
   533  	}
   534  
   535  	var bytes []byte
   536  
   537  	err = self.manager.client.Do(ctx, req, func(resp *http.Response) error {
   538  		if resp.StatusCode == 404 {
   539  			return cloudprovider.ErrNotFound
   540  		}
   541  		if resp.StatusCode >= 400 {
   542  			return fmt.Errorf("%s", resp.Status)
   543  		}
   544  		cont, err := ioutil.ReadAll(resp.Body)
   545  		if err != nil {
   546  			return err
   547  		}
   548  		bytes = cont
   549  		return nil
   550  	})
   551  
   552  	return bytes, err
   553  }
   554  
   555  type SDatastoreFileInfo struct {
   556  	Url      string
   557  	Name     string
   558  	Date     time.Time
   559  	FileType string
   560  	Size     uint64
   561  }
   562  
   563  const (
   564  	fileListPattern = `<tr><td><a href="(?P<url>[\w\d:#@%/;$()~_?\+-=\\\.&]+)">(?P<name>[^<]+)<\/a></td><td align="right">(?P<date>[^<]+)</td><td align="right">(?P<size>[^<]+)</td></tr>`
   565  	fileDateFormat  = "02-Jan-2006 15:04"
   566  	fileDateFormat2 = "Mon, 2 Jan 2006 15:04:05 GMT"
   567  )
   568  
   569  var (
   570  	fileListRegexp = regexp.MustCompile(fileListPattern)
   571  )
   572  
   573  func (self *SDatastore) ListDir(ctx context.Context, remotePath string) ([]SDatastoreFileInfo, error) {
   574  	listContent, err := self.FileGetContent(ctx, remotePath)
   575  	if err != nil {
   576  		return nil, err
   577  	}
   578  	ret := make([]SDatastoreFileInfo, 0)
   579  	matches := fileListRegexp.FindAllStringSubmatch(string(listContent), -1)
   580  	for r := 0; r < len(matches); r += 1 {
   581  		url := strings.TrimSpace(matches[r][1])
   582  		name := strings.TrimSpace(matches[r][2])
   583  		dateStr := strings.TrimSpace(matches[r][3])
   584  		sizeStr := strings.TrimSpace(matches[r][4])
   585  		var ftype string
   586  		var size uint64
   587  		if sizeStr == "-" {
   588  			ftype = "dir"
   589  			size = 0
   590  		} else {
   591  			ftype = "file"
   592  			size, _ = strconv.ParseUint(sizeStr, 10, 64)
   593  		}
   594  		date, err := time.Parse(fileDateFormat, dateStr)
   595  		if err != nil {
   596  			return nil, err
   597  		}
   598  		info := SDatastoreFileInfo{
   599  			Url:      url,
   600  			Name:     name,
   601  			FileType: ftype,
   602  			Size:     size,
   603  			Date:     date,
   604  		}
   605  		ret = append(ret, info)
   606  	}
   607  
   608  	return ret, nil
   609  }
   610  
   611  func (self *SDatastore) listPath(b *object.HostDatastoreBrowser, path string, spec types.HostDatastoreBrowserSearchSpec) ([]types.HostDatastoreBrowserSearchResults, error) {
   612  	ctx := context.TODO()
   613  
   614  	path = self.getDatastoreObj().Path(path)
   615  
   616  	search := b.SearchDatastore
   617  
   618  	task, err := search(ctx, path, &spec)
   619  	if err != nil {
   620  		return nil, err
   621  	}
   622  
   623  	info, err := task.WaitForResult(ctx, nil)
   624  	if err != nil {
   625  		return nil, err
   626  	}
   627  
   628  	switch r := info.Result.(type) {
   629  	case types.HostDatastoreBrowserSearchResults:
   630  		return []types.HostDatastoreBrowserSearchResults{r}, nil
   631  	case types.ArrayOfHostDatastoreBrowserSearchResults:
   632  		return r.HostDatastoreBrowserSearchResults, nil
   633  	default:
   634  		return nil, errors.Error(fmt.Sprintf("unknown result type: %T", r))
   635  	}
   636  
   637  }
   638  
   639  func (self *SDatastore) ListPath(ctx context.Context, remotePath string) ([]types.HostDatastoreBrowserSearchResults, error) {
   640  	//types.HostDatastoreBrowserSearchResults
   641  	ds := self.getDatastoreObj()
   642  
   643  	b, err := ds.Browser(ctx)
   644  	if err != nil {
   645  		return nil, err
   646  	}
   647  
   648  	ret := make([]types.HostDatastoreBrowserSearchResults, 0)
   649  
   650  	spec := types.HostDatastoreBrowserSearchSpec{
   651  		MatchPattern: []string{"*"},
   652  		Details: &types.FileQueryFlags{
   653  			FileType:     true,
   654  			FileSize:     true,
   655  			FileOwner:    types.NewBool(true), // TODO: omitempty is generated, but seems to be required
   656  			Modification: true,
   657  		},
   658  	}
   659  
   660  	for i := 0; ; i++ {
   661  		r, err := self.listPath(b, remotePath, spec)
   662  		if err != nil {
   663  			// Treat the argument as a match pattern if not found as directory
   664  			if i == 0 && types.IsFileNotFound(err) || isInvalid(err) {
   665  				spec.MatchPattern[0] = path.Base(remotePath)
   666  				remotePath = path.Dir(remotePath)
   667  				continue
   668  			}
   669  			if types.IsFileNotFound(err) {
   670  				return nil, errors.ErrNotFound
   671  			}
   672  			return nil, err
   673  		}
   674  		if i == 1 && len(r) == 1 && len(r[0].File) == 0 {
   675  			return nil, errors.ErrNotFound
   676  		}
   677  		for n := range r {
   678  			ret = append(ret, r[n])
   679  		}
   680  		break
   681  	}
   682  	return ret, nil
   683  }
   684  
   685  func isInvalid(err error) bool {
   686  	if f, ok := err.(types.HasFault); ok {
   687  		switch f.Fault().(type) {
   688  		case *types.InvalidArgument:
   689  			return true
   690  		}
   691  	}
   692  
   693  	return false
   694  }
   695  
   696  func (self *SDatastore) CheckFile(ctx context.Context, remotePath string) (*SDatastoreFileInfo, error) {
   697  	url := self.GetPathUrl(remotePath)
   698  
   699  	req, err := http.NewRequest("HEAD", url, nil)
   700  	if err != nil {
   701  		return nil, err
   702  	}
   703  
   704  	var size uint64
   705  	var date time.Time
   706  
   707  	err = self.manager.client.Do(ctx, req, func(resp *http.Response) error {
   708  		if resp.StatusCode >= 400 {
   709  			if resp.StatusCode == 404 {
   710  				return cloudprovider.ErrNotFound
   711  			}
   712  			return fmt.Errorf("%s", resp.Status)
   713  		}
   714  		sizeStr := resp.Header.Get("Content-Length")
   715  		size, _ = strconv.ParseUint(sizeStr, 10, 64)
   716  
   717  		dateStr := resp.Header.Get("Date")
   718  		date, _ = time.Parse(fileDateFormat2, dateStr)
   719  		return nil
   720  	})
   721  
   722  	if err != nil {
   723  		return nil, err
   724  	}
   725  	return &SDatastoreFileInfo{Date: date, Size: size}, nil
   726  }
   727  
   728  func (self *SDatastore) Download(ctx context.Context, remotePath string, writer io.Writer) error {
   729  	url := self.GetPathUrl(remotePath)
   730  
   731  	req, err := http.NewRequest("GET", url, nil)
   732  	if err != nil {
   733  		return err
   734  	}
   735  
   736  	err = self.manager.client.Do(ctx, req, func(resp *http.Response) error {
   737  		if resp.StatusCode >= 400 {
   738  			return fmt.Errorf("%s", resp.Status)
   739  		}
   740  		buffer := make([]byte, 4096)
   741  		for {
   742  			rn, re := resp.Body.Read(buffer)
   743  			if rn > 0 {
   744  				wo := 0
   745  				for wo < rn {
   746  					wn, we := writer.Write(buffer[wo:rn])
   747  					if we != nil {
   748  						return we
   749  					}
   750  					wo += wn
   751  				}
   752  			}
   753  			if re != nil {
   754  				if re != io.EOF {
   755  					return re
   756  				} else {
   757  					break
   758  				}
   759  			}
   760  		}
   761  		return nil
   762  	})
   763  
   764  	return err
   765  }
   766  
   767  func (self *SDatastore) Upload(ctx context.Context, remotePath string, body io.Reader) error {
   768  	url := self.GetPathUrl(remotePath)
   769  
   770  	req, err := http.NewRequest("PUT", url, body)
   771  	if err != nil {
   772  		return err
   773  	}
   774  
   775  	err = self.manager.client.Do(ctx, req, func(resp *http.Response) error {
   776  		if resp.StatusCode >= 400 {
   777  			return fmt.Errorf("%s", resp.Status)
   778  		}
   779  		_, err := ioutil.ReadAll(resp.Body)
   780  		if err != nil {
   781  			return err
   782  		}
   783  		// log.Debugf("upload respose %s", buffer)
   784  		return nil
   785  	})
   786  
   787  	return err
   788  }
   789  
   790  func (self *SDatastore) FilePutContent(ctx context.Context, remotePath string, content string) error {
   791  	return self.Upload(ctx, remotePath, strings.NewReader(content))
   792  }
   793  
   794  // Delete2 can delete file from this Datastore.
   795  // isNamespace: remotePath is uuid of namespace on vsan datastore
   796  // force: ignore nonexistent files and arguments
   797  func (self *SDatastore) Delete2(ctx context.Context, remotePath string, isNamespace, force bool) error {
   798  	var err error
   799  	ds := self.getDatastoreObj()
   800  	dc := self.datacenter.getObjectDatacenter()
   801  	if isNamespace {
   802  		nm := object.NewDatastoreNamespaceManager(self.manager.client.Client)
   803  		err = nm.DeleteDirectory(ctx, dc, remotePath)
   804  	} else {
   805  		fm := ds.NewFileManager(dc, force)
   806  		err = fm.Delete(ctx, remotePath)
   807  	}
   808  
   809  	if err != nil && types.IsFileNotFound(err) && force {
   810  		// Ignore error
   811  		return nil
   812  	}
   813  	return err
   814  }
   815  
   816  func (self *SDatastore) Delete(ctx context.Context, remotePath string) error {
   817  	url := self.GetPathUrl(remotePath)
   818  
   819  	req, err := http.NewRequest("DELETE", url, nil)
   820  	if err != nil {
   821  		return err
   822  	}
   823  
   824  	err = self.manager.client.Do(ctx, req, func(resp *http.Response) error {
   825  		if resp.StatusCode >= 400 {
   826  			return fmt.Errorf("%s", resp.Status)
   827  		}
   828  		_, err := ioutil.ReadAll(resp.Body)
   829  		if err != nil {
   830  			return err
   831  		}
   832  		// log.Debugf("delete respose %s", buffer)
   833  		return nil
   834  	})
   835  
   836  	return err
   837  }
   838  
   839  func (self *SDatastore) DeleteVmdk(ctx context.Context, remotePath string) error {
   840  	info, err := self.CheckFile(ctx, remotePath)
   841  	if err != nil {
   842  		return err
   843  	}
   844  	if info.Size > 4096 {
   845  		return fmt.Errorf("not a valid vmdk file")
   846  	}
   847  	vmdkContent, err := self.FileGetContent(ctx, remotePath)
   848  	if err != nil {
   849  		return err
   850  	}
   851  	vmdkInfo, err := vmdkutils.Parse(string(vmdkContent))
   852  	if err != nil {
   853  		return err
   854  	}
   855  	err = self.Delete(ctx, remotePath)
   856  	if err != nil {
   857  		return err
   858  	}
   859  	if len(vmdkInfo.ExtentFile) > 0 {
   860  		err = self.Delete(ctx, path.Join(path.Dir(remotePath), vmdkInfo.ExtentFile))
   861  		if err != nil {
   862  			return err
   863  		}
   864  	}
   865  	return nil
   866  }
   867  
   868  func (self *SDatastore) GetVmdkInfo(ctx context.Context, remotePath string) (*vmdkutils.SVMDKInfo, error) {
   869  	vmdkContent, err := self.FileGetContent(ctx, remotePath)
   870  	if err != nil {
   871  		return nil, err
   872  	}
   873  	return vmdkutils.Parse(string(vmdkContent))
   874  }
   875  
   876  func (self *SDatastore) CheckVmdk(ctx context.Context, remotePath string) error {
   877  	dm := object.NewVirtualDiskManager(self.manager.client.Client)
   878  
   879  	dc, err := self.GetDatacenter()
   880  	if err != nil {
   881  		return err
   882  	}
   883  
   884  	dcObj := dc.getObjectDatacenter()
   885  
   886  	infoList, err := dm.QueryVirtualDiskInfo(ctx, self.getPathString(remotePath), dcObj, true)
   887  	if err != nil {
   888  		return err
   889  	}
   890  
   891  	log.Debugf("%#v", infoList)
   892  	return nil
   893  }
   894  
   895  func (self *SDatastore) getDatastoreObj() *object.Datastore {
   896  	od := object.NewDatastore(self.manager.client.Client, self.getDatastore().Self)
   897  	od.DatacenterPath = self.GetDatacenterPathString()
   898  	od.InventoryPath = fmt.Sprintf("%s/%s", od.DatacenterPath, self.SManagedObject.GetName())
   899  	return od
   900  }
   901  
   902  func (self *SDatastore) MakeDir(remotePath string) error {
   903  	remotePath = self.cleanPath(remotePath)
   904  
   905  	m := object.NewFileManager(self.manager.client.Client)
   906  	path := fmt.Sprintf("[%s] %s", self.GetRelName(), remotePath)
   907  	return m.MakeDirectory(self.manager.context, path, self.datacenter.getObjectDatacenter(), true)
   908  }
   909  
   910  func (self *SDatastore) RemoveDir(ctx context.Context, remotePath string) error {
   911  	dnm := object.NewDatastoreNamespaceManager(self.manager.client.Client)
   912  
   913  	remotePath = self.GetFullPath(remotePath)
   914  
   915  	dc, err := self.GetDatacenter()
   916  	if err != nil {
   917  		return err
   918  	}
   919  
   920  	dcObj := dc.getObjectDatacenter()
   921  
   922  	return dnm.DeleteDirectory(ctx, dcObj, remotePath)
   923  }
   924  
   925  // CheckDirC will check that Dir 'remotePath' is exist, if not, create one.
   926  func (self *SDatastore) CheckDirC(remotePath string) error {
   927  	_, err := self.CheckFile(self.manager.context, remotePath)
   928  	if err == nil {
   929  		return nil
   930  	}
   931  	if errors.Cause(err) != cloudprovider.ErrNotFound {
   932  		return err
   933  	}
   934  	return self.MakeDir(remotePath)
   935  
   936  }
   937  
   938  func (self *SDatastore) IsSysDiskStore() bool {
   939  	return true
   940  }
   941  
   942  func (self *SDatastore) MoveVmdk(ctx context.Context, srcPath string, dstPath string) error {
   943  	dm := object.NewVirtualDiskManager(self.manager.client.Client)
   944  	defer dm.Destroy(ctx)
   945  
   946  	srcUrl := self.GetPathUrl(srcPath)
   947  	dstUrl := self.GetPathUrl(dstPath)
   948  	task, err := dm.MoveVirtualDisk(ctx, srcUrl, nil, dstUrl, nil, true)
   949  	if err != nil {
   950  		return err
   951  	}
   952  	return task.Wait(ctx)
   953  }
   954  
   955  // domainName will abandon the rune which should't disapear
   956  func (self *SDatastore) domainName(name string) string {
   957  	var b bytes.Buffer
   958  	for _, r := range name {
   959  		if b.Len() == 0 {
   960  			if unicode.IsLetter(r) {
   961  				b.WriteRune(r)
   962  			}
   963  		} else {
   964  			if unicode.IsDigit(r) || unicode.IsLetter(r) || r == '-' {
   965  				b.WriteRune(r)
   966  			}
   967  		}
   968  	}
   969  	return strings.TrimRight(b.String(), "-")
   970  }
   971  
   972  // ImportVMDK will upload local vmdk 'diskFile' to the 'remotePath' of remote datastore
   973  func (self *SDatastore) ImportVMDK(ctx context.Context, diskFile, remotePath string, host *SHost) error {
   974  	name := fmt.Sprintf("yunioncloud.%s%d", self.domainName(remotePath)[:20], rand.Int())
   975  	vm, err := self.ImportVM(ctx, diskFile, name, host)
   976  	if err != nil {
   977  		return errors.Wrap(err, "SDatastore.ImportVM")
   978  	}
   979  
   980  	defer func() {
   981  		task, err := vm.Destroy(ctx)
   982  		if err != nil {
   983  			log.Errorf("vm.Destory: %s", err)
   984  			return
   985  		}
   986  
   987  		if err = task.Wait(ctx); err != nil {
   988  			log.Errorf("task.Wait: %s", err)
   989  		}
   990  	}()
   991  
   992  	// check if 'image_cache' is esixt
   993  	err = self.CheckDirC("image_cache")
   994  	if err != nil {
   995  		return errors.Wrap(err, "SDatastore.CheckDirC")
   996  	}
   997  
   998  	fm := self.getDatastoreObj().NewFileManager(self.datacenter.getObjectDatacenter(), true)
   999  
  1000  	// if image_cache not exist
  1001  	return fm.Move(ctx, fmt.Sprintf("[%s] %s/%s.vmdk", self.GetRelName(), name, name), fmt.Sprintf("[%s] %s",
  1002  		self.GetRelName(), remotePath))
  1003  }
  1004  
  1005  func (self *SDatastore) ImportISO(ctx context.Context, isoFile, remotePath string, host *SHost) error {
  1006  	p := soap.DefaultUpload
  1007  	ds := self.getDatastoreObj()
  1008  	return ds.UploadFile(ctx, isoFile, remotePath, &p)
  1009  }
  1010  
  1011  var (
  1012  	ErrInvalidFormat = errors.Error("vmdk: invalid format (must be streamOptimized)")
  1013  )
  1014  
  1015  // info is used to inspect a vmdk and generate an ovf template
  1016  type info struct {
  1017  	Header struct {
  1018  		MagicNumber uint32
  1019  		Version     uint32
  1020  		Flags       uint32
  1021  		Capacity    uint64
  1022  	}
  1023  
  1024  	Capacity   uint64
  1025  	Size       int64
  1026  	Name       string
  1027  	ImportName string
  1028  }
  1029  
  1030  // stat looks at the vmdk header to make sure the format is streamOptimized and
  1031  // extracts the disk capacity required to properly generate the ovf descriptor.
  1032  func stat(name string) (*info, error) {
  1033  	f, err := os.Open(name)
  1034  	if err != nil {
  1035  		return nil, err
  1036  	}
  1037  
  1038  	var (
  1039  		di  info
  1040  		buf bytes.Buffer
  1041  	)
  1042  
  1043  	_, err = io.CopyN(&buf, f, int64(binary.Size(di.Header)))
  1044  	fi, _ := f.Stat()
  1045  	_ = f.Close()
  1046  	if err != nil {
  1047  		return nil, err
  1048  	}
  1049  
  1050  	err = binary.Read(&buf, binary.LittleEndian, &di.Header)
  1051  	if err != nil {
  1052  		return nil, err
  1053  	}
  1054  
  1055  	if di.Header.MagicNumber != 0x564d444b { // SPARSE_MAGICNUMBER
  1056  		return nil, ErrInvalidFormat
  1057  	}
  1058  
  1059  	if di.Header.Flags&(1<<16) == 0 { // SPARSEFLAG_COMPRESSED
  1060  		// Needs to be converted, for example:
  1061  		//   vmware-vdiskmanager -r src.vmdk -t 5 dst.vmdk
  1062  		//   qemu-img convert -O vmdk -o subformat=streamOptimized src.vmdk dst.vmdk
  1063  		return nil, ErrInvalidFormat
  1064  	}
  1065  
  1066  	di.Capacity = di.Header.Capacity * 512 // VMDK_SECTOR_SIZE
  1067  	di.Size = fi.Size()
  1068  	di.Name = filepath.Base(name)
  1069  	di.ImportName = strings.TrimSuffix(di.Name, ".vmdk")
  1070  
  1071  	return &di, nil
  1072  }
  1073  
  1074  // ovf returns an expanded descriptor template
  1075  func (di *info) ovf() (string, error) {
  1076  	var buf bytes.Buffer
  1077  
  1078  	tmpl, err := template.ParseFiles("/opt/yunion/share/vmware/ovf.xml")
  1079  	if err != nil {
  1080  		return "", err
  1081  	}
  1082  
  1083  	err = tmpl.Execute(&buf, di)
  1084  	if err != nil {
  1085  		return "", err
  1086  	}
  1087  
  1088  	return buf.String(), nil
  1089  }
  1090  
  1091  // ImportParams contains the set of optional params to the Import function.
  1092  // Note that "optional" may depend on environment, such as ESX or vCenter.
  1093  type ImportParams struct {
  1094  	Path       string
  1095  	Logger     progress.Sinker
  1096  	Type       types.VirtualDiskType
  1097  	Force      bool
  1098  	Datacenter *object.Datacenter
  1099  	Pool       *object.ResourcePool
  1100  	Folder     *object.Folder
  1101  	Host       *object.HostSystem
  1102  }
  1103  
  1104  // ImportVM will import a vm by uploading a local vmdk
  1105  func (self *SDatastore) ImportVM(ctx context.Context, diskFile, name string, host *SHost) (*object.VirtualMachine, error) {
  1106  
  1107  	var (
  1108  		c         = self.manager.client.Client
  1109  		datastore = self.getDatastoreObj()
  1110  	)
  1111  
  1112  	m := ovf.NewManager(c)
  1113  
  1114  	disk, err := stat(diskFile)
  1115  	if err != nil {
  1116  		return nil, errors.Wrap(err, "stat")
  1117  	}
  1118  
  1119  	disk.ImportName = name
  1120  
  1121  	// Expand the ovf template
  1122  	descriptor, err := disk.ovf()
  1123  	if err != nil {
  1124  		return nil, errors.Wrap(err, "disk.ovf")
  1125  	}
  1126  
  1127  	folders, err := self.datacenter.getObjectDatacenter().Folders(ctx)
  1128  	if err != nil {
  1129  		return nil, errors.Wrap(err, "Folders")
  1130  	}
  1131  	pool, err := host.GetResourcePool()
  1132  	if err != nil {
  1133  		return nil, errors.Wrap(err, "getResourcePool")
  1134  	}
  1135  
  1136  	kind := types.VirtualDiskTypeThin
  1137  
  1138  	params := types.OvfCreateImportSpecParams{
  1139  		DiskProvisioning: string(kind),
  1140  		EntityName:       disk.ImportName,
  1141  	}
  1142  
  1143  	spec, err := m.CreateImportSpec(ctx, descriptor, pool, datastore, params)
  1144  	if err != nil {
  1145  		return nil, err
  1146  	}
  1147  	if spec.Error != nil {
  1148  		return nil, errors.Error(spec.Error[0].LocalizedMessage)
  1149  	}
  1150  
  1151  	lease, err := pool.ImportVApp(ctx, spec.ImportSpec, folders.VmFolder, host.GetoHostSystem())
  1152  	if err != nil {
  1153  		return nil, err
  1154  	}
  1155  
  1156  	info, err := lease.Wait(ctx, spec.FileItem)
  1157  	if err != nil {
  1158  		return nil, err
  1159  	}
  1160  
  1161  	f, err := os.Open(diskFile)
  1162  	if err != nil {
  1163  		return nil, err
  1164  	}
  1165  
  1166  	lr := newLeaseLogger("upload vmdk", 5)
  1167  
  1168  	lr.Log()
  1169  	defer lr.End()
  1170  
  1171  	opts := soap.Upload{
  1172  		ContentLength: disk.Size,
  1173  		Progress:      lr,
  1174  	}
  1175  
  1176  	u := lease.StartUpdater(ctx, info)
  1177  	defer u.Done()
  1178  
  1179  	item := info.Items[0] // we only have 1 disk to upload
  1180  
  1181  	err = lease.Upload(ctx, item, f, opts)
  1182  
  1183  	_ = f.Close()
  1184  
  1185  	if err != nil {
  1186  		return nil, errors.Wrap(err, "lease.Upload")
  1187  	}
  1188  
  1189  	if err = lease.Complete(ctx); err != nil {
  1190  		log.Debugf("lease complete error: %s, sleep 1s and try again", err)
  1191  		time.Sleep(time.Second)
  1192  		if err = lease.Complete(ctx); err != nil {
  1193  			return nil, errors.Wrap(err, "lease.Complete")
  1194  		}
  1195  	}
  1196  
  1197  	return object.NewVirtualMachine(c, info.Entity), nil
  1198  }