mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-21 00:24:07 +08:00
988 lines
24 KiB
Go
988 lines
24 KiB
Go
// Copyright 2019 Yunion
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package esxi
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"math/rand"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
"unicode"
|
|
|
|
"github.com/vmware/govmomi/object"
|
|
"github.com/vmware/govmomi/ovf"
|
|
"github.com/vmware/govmomi/vim25/mo"
|
|
"github.com/vmware/govmomi/vim25/progress"
|
|
"github.com/vmware/govmomi/vim25/soap"
|
|
"github.com/vmware/govmomi/vim25/types"
|
|
|
|
"yunion.io/x/jsonutils"
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/pkg/errors"
|
|
|
|
api "yunion.io/x/onecloud/pkg/apis/compute"
|
|
"yunion.io/x/onecloud/pkg/cloudprovider"
|
|
"yunion.io/x/onecloud/pkg/util/vmdkutils"
|
|
)
|
|
|
|
var DATASTORE_PROPS = []string{"name", "parent", "info", "summary", "host", "vm"}
|
|
|
|
type SDatastore struct {
|
|
SManagedObject
|
|
|
|
// vms []cloudprovider.ICloudVM
|
|
|
|
ihosts []cloudprovider.ICloudHost
|
|
|
|
storageCache *SDatastoreImageCache
|
|
}
|
|
|
|
func NewDatastore(manager *SESXiClient, ds *mo.Datastore, dc *SDatacenter) *SDatastore {
|
|
return &SDatastore{SManagedObject: newManagedObject(manager, ds, dc)}
|
|
}
|
|
|
|
func (self *SDatastore) GetMetadata() *jsonutils.JSONDict {
|
|
return nil
|
|
}
|
|
|
|
func (self *SDatastore) getDatastore() *mo.Datastore {
|
|
return self.object.(*mo.Datastore)
|
|
}
|
|
|
|
func (self *SDatastore) GetGlobalId() string {
|
|
volId, err := self.getVolumeId()
|
|
if err != nil {
|
|
log.Errorf("get datastore global ID error %s", err)
|
|
}
|
|
return volId
|
|
}
|
|
|
|
func (self *SDatastore) GetName() string {
|
|
volName, err := self.getVolumeName()
|
|
if err != nil {
|
|
log.Fatalf("datastore get name error %s", err)
|
|
}
|
|
return fmt.Sprintf("%s-%s", self.getVolumeType(), volName)
|
|
}
|
|
|
|
func (self *SDatastore) GetRelName() string {
|
|
return self.getDatastore().Info.GetDatastoreInfo().Name
|
|
}
|
|
|
|
func (self *SDatastore) GetCapacityMB() int64 {
|
|
moStore := self.getDatastore()
|
|
return moStore.Summary.Capacity / 1024 / 1024
|
|
}
|
|
|
|
func (self *SDatastore) GetEnabled() bool {
|
|
return true
|
|
}
|
|
|
|
func (self *SDatastore) GetStatus() string {
|
|
if self.getDatastore().Summary.Accessible {
|
|
return api.STORAGE_ONLINE
|
|
} else {
|
|
return api.STORAGE_OFFLINE
|
|
}
|
|
}
|
|
|
|
func (self *SDatastore) Refresh() error {
|
|
base := self.SManagedObject
|
|
var moObj mo.Datastore
|
|
err := self.manager.reference2Object(self.object.Reference(), DATASTORE_PROPS, &moObj)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
base.object = &moObj
|
|
*self = SDatastore{}
|
|
self.SManagedObject = base
|
|
return nil
|
|
}
|
|
|
|
func (self *SDatastore) IsEmulated() bool {
|
|
return false
|
|
}
|
|
|
|
func (self *SDatastore) getVolumeId() (string, error) {
|
|
moStore := self.getDatastore()
|
|
switch fsInfo := moStore.Info.(type) {
|
|
case *types.VmfsDatastoreInfo:
|
|
if fsInfo.Vmfs.Local == nil || *fsInfo.Vmfs.Local {
|
|
host, err := self.getLocalHost()
|
|
if err == nil {
|
|
return fmt.Sprintf("%s:%s", host.GetAccessIp(), fsInfo.Vmfs.Uuid), nil
|
|
}
|
|
}
|
|
return fsInfo.Vmfs.Uuid, nil
|
|
case *types.NasDatastoreInfo:
|
|
return fmt.Sprintf("%s:%s", fsInfo.Nas.RemoteHost, fsInfo.Nas.RemotePath), nil
|
|
}
|
|
if moStore.Summary.Type == "vsan" {
|
|
vsanId := moStore.Summary.Url
|
|
vsanId = vsanId[strings.Index(vsanId, "vsan:"):]
|
|
endIdx := len(vsanId)
|
|
for ; endIdx >= 0 && vsanId[endIdx-1] == '/'; endIdx -= 1 {
|
|
}
|
|
return vsanId[:endIdx], nil
|
|
}
|
|
log.Fatalf("unsupported volume type %#v", moStore.Info)
|
|
return "", cloudprovider.ErrNotImplemented
|
|
}
|
|
|
|
func (self *SDatastore) getVolumeType() string {
|
|
return self.getDatastore().Summary.Type
|
|
}
|
|
|
|
func (self *SDatastore) getVolumeName() (string, error) {
|
|
moStore := self.getDatastore()
|
|
|
|
if self.isLocalVMFS() {
|
|
host, err := self.getLocalHost()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("%s-%s", host.GetAccessIp(), moStore.Info.GetDatastoreInfo().Name), nil
|
|
}
|
|
dc, err := self.GetDatacenter()
|
|
if err != nil {
|
|
return "", nil
|
|
}
|
|
return fmt.Sprintf("%s-%s", dc.GetName(), moStore.Info.GetDatastoreInfo().Name), nil
|
|
}
|
|
|
|
func (self *SDatastore) getAttachedHosts() ([]cloudprovider.ICloudHost, error) {
|
|
ihosts := make([]cloudprovider.ICloudHost, 0)
|
|
|
|
moStore := self.getDatastore()
|
|
for i := 0; i < len(moStore.Host); i += 1 {
|
|
idstr := moRefId(moStore.Host[i].Key)
|
|
host, err := self.datacenter.GetIHostByMoId(idstr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ihosts = append(ihosts, host)
|
|
}
|
|
|
|
return ihosts, nil
|
|
}
|
|
|
|
func (self *SDatastore) getCachedAttachedHosts() ([]cloudprovider.ICloudHost, error) {
|
|
if self.ihosts == nil {
|
|
var err error
|
|
self.ihosts, err = self.getAttachedHosts()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return self.ihosts, nil
|
|
}
|
|
|
|
func (self *SDatastore) GetAttachedHosts() ([]cloudprovider.ICloudHost, error) {
|
|
return self.getCachedAttachedHosts()
|
|
}
|
|
|
|
func (self *SDatastore) getLocalHost() (cloudprovider.ICloudHost, error) {
|
|
hosts, err := self.GetAttachedHosts()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "self.GetAttachedHosts")
|
|
}
|
|
if len(hosts) == 1 {
|
|
return hosts[0], nil
|
|
}
|
|
return nil, cloudprovider.ErrInvalidStatus
|
|
}
|
|
|
|
func (self *SDatastore) GetIStoragecache() cloudprovider.ICloudStoragecache {
|
|
if self.isLocalVMFS() {
|
|
ihost, err := self.getLocalHost()
|
|
if err != nil {
|
|
log.Errorf("GetIStoragecache getLocalHost fail %s", err)
|
|
return nil
|
|
}
|
|
host := ihost.(*SHost)
|
|
sc, err := host.getLocalStorageCache()
|
|
if err != nil {
|
|
log.Errorf("GetIStoragecache getLocalStorageCache fail %s", err)
|
|
return nil
|
|
}
|
|
return sc
|
|
} else {
|
|
return self.getStorageCache()
|
|
}
|
|
}
|
|
|
|
func (self *SDatastore) getStorageCache() *SDatastoreImageCache {
|
|
if self.storageCache == nil {
|
|
self.storageCache = &SDatastoreImageCache{
|
|
datastore: self,
|
|
}
|
|
}
|
|
return self.storageCache
|
|
}
|
|
|
|
func (self *SDatastore) GetIZone() cloudprovider.ICloudZone {
|
|
return nil
|
|
}
|
|
|
|
func (self *SDatastore) getVMs() ([]cloudprovider.ICloudVM, error) {
|
|
dc, err := self.GetDatacenter()
|
|
if err != nil {
|
|
log.Errorf("SDatastore GetDatacenter fail %s", err)
|
|
return nil, err
|
|
}
|
|
vms := self.getDatastore().Vm
|
|
if len(vms) == 0 {
|
|
return nil, nil
|
|
}
|
|
ret, _, err := dc.fetchVms(vms, false)
|
|
return ret, err
|
|
}
|
|
|
|
func (self *SDatastore) GetIDiskById(idStr string) (cloudprovider.ICloudDisk, error) {
|
|
vms, err := self.getVMs()
|
|
if err != nil {
|
|
log.Errorf("self.getVMs fail %s", err)
|
|
return nil, err
|
|
}
|
|
for i := 0; i < len(vms); i += 1 {
|
|
vm := vms[i].(*SVirtualMachine)
|
|
disk, err := vm.GetIDiskById(idStr)
|
|
if err == nil {
|
|
return disk, nil
|
|
}
|
|
}
|
|
return nil, cloudprovider.ErrNotFound
|
|
}
|
|
|
|
func (self *SDatastore) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
|
|
vms, err := self.getVMs()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
allDisks := make([]cloudprovider.ICloudDisk, 0)
|
|
for i := 0; i < len(vms); i += 1 {
|
|
disks, err := vms[i].GetIDisks()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
allDisks = append(allDisks, disks...)
|
|
}
|
|
return allDisks, nil
|
|
}
|
|
|
|
func (self *SDatastore) isLocalVMFS() bool {
|
|
moStore := self.getDatastore()
|
|
switch vmfsInfo := moStore.Info.(type) {
|
|
case *types.VmfsDatastoreInfo:
|
|
if vmfsInfo.Vmfs.Local == nil || *vmfsInfo.Vmfs.Local == true {
|
|
_, err := self.getLocalHost()
|
|
if err == nil {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (self *SDatastore) GetStorageType() string {
|
|
moStore := self.getDatastore()
|
|
switch strings.ToLower(moStore.Summary.Type) {
|
|
case "vmfs":
|
|
if self.isLocalVMFS() {
|
|
return api.STORAGE_LOCAL
|
|
} else {
|
|
return api.STORAGE_NAS
|
|
}
|
|
case "nfs", "nfs41", "cifs", "vsan":
|
|
return api.STORAGE_NAS
|
|
default:
|
|
log.Fatalf("unsupported datastore type %s", moStore.Summary.Type)
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func (self *SDatastore) GetMediumType() string {
|
|
moStore := self.getDatastore()
|
|
vmfsInfo, ok := moStore.Info.(*types.VmfsDatastoreInfo)
|
|
if ok && vmfsInfo.Vmfs.Ssd != nil && *vmfsInfo.Vmfs.Ssd {
|
|
return api.DISK_TYPE_SSD
|
|
}
|
|
return api.DISK_TYPE_ROTATE
|
|
}
|
|
|
|
func (self *SDatastore) GetStorageConf() jsonutils.JSONObject {
|
|
conf := jsonutils.NewDict()
|
|
conf.Add(jsonutils.NewString(self.GetName()), "name")
|
|
conf.Add(jsonutils.NewString(self.GetGlobalId()), "id")
|
|
conf.Add(jsonutils.NewString(self.GetDatacenterPathString()), "dc_path")
|
|
volId, err := self.getVolumeId()
|
|
if err != nil {
|
|
log.Errorf("getVaolumeId fail %s", err)
|
|
}
|
|
conf.Add(jsonutils.NewString(volId), "volume_id")
|
|
|
|
volType := self.getVolumeType()
|
|
conf.Add(jsonutils.NewString(volType), "volume_type")
|
|
|
|
volName, err := self.getVolumeName()
|
|
if err != nil {
|
|
log.Errorf("getVaolumeName fail %s", err)
|
|
}
|
|
conf.Add(jsonutils.NewString(volName), "volume_name")
|
|
return conf
|
|
}
|
|
|
|
const dsPrefix = "ds://"
|
|
|
|
func (self *SDatastore) GetUrl() string {
|
|
url := self.getDatastore().Info.GetDatastoreInfo().Url
|
|
if strings.HasPrefix(url, dsPrefix) {
|
|
url = url[len(dsPrefix):]
|
|
}
|
|
return url
|
|
}
|
|
|
|
func (self *SDatastore) GetMountPoint() string {
|
|
return self.GetUrl()
|
|
}
|
|
|
|
func (self *SDatastore) HasFile(remotePath string) bool {
|
|
dsName := fmt.Sprintf("[%s]", self.SManagedObject.GetName())
|
|
if strings.HasPrefix(remotePath, dsName) {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (self *SDatastore) cleanPath(remotePath string) string {
|
|
dsName := fmt.Sprintf("[%s]", self.SManagedObject.GetName())
|
|
dsUrl := self.GetUrl()
|
|
if strings.HasPrefix(remotePath, dsName) {
|
|
remotePath = remotePath[len(dsName):]
|
|
} else if strings.HasPrefix(remotePath, dsUrl) {
|
|
remotePath = remotePath[len(dsUrl):]
|
|
}
|
|
return strings.TrimSpace(remotePath)
|
|
}
|
|
|
|
func pathEscape(path string) string {
|
|
segs := strings.Split(path, "/")
|
|
for i := 0; i < len(segs); i += 1 {
|
|
segs[i] = url.PathEscape(segs[i])
|
|
}
|
|
return strings.Join(segs, "/")
|
|
}
|
|
|
|
func (self *SDatastore) GetPathUrl(remotePath string) string {
|
|
remotePath = self.cleanPath(remotePath)
|
|
if len(remotePath) == 0 || remotePath[0] != '/' {
|
|
remotePath = fmt.Sprintf("/%s", remotePath)
|
|
}
|
|
httpUrl := fmt.Sprintf("%s/folder%s", self.getManagerUri(), pathEscape(remotePath))
|
|
params := jsonutils.NewDict()
|
|
params.Add(jsonutils.NewString(self.SManagedObject.GetName()), "dsName")
|
|
params.Add(jsonutils.NewString(self.GetDatacenterPathString()), "dcPath")
|
|
|
|
return fmt.Sprintf("%s?%s", httpUrl, params.QueryString())
|
|
}
|
|
|
|
func (self *SDatastore) getPathString(path string) string {
|
|
for len(path) > 0 && path[0] == '/' {
|
|
path = path[1:]
|
|
}
|
|
return fmt.Sprintf("[%s] %s", self.SManagedObject.GetName(), path)
|
|
}
|
|
|
|
func (self *SDatastore) GetFullPath(remotePath string) string {
|
|
remotePath = self.cleanPath(remotePath)
|
|
return path.Join(self.GetUrl(), remotePath)
|
|
}
|
|
|
|
func (self *SDatastore) CreateIDisk(name string, sizeGb int, desc string) (cloudprovider.ICloudDisk, error) {
|
|
return nil, cloudprovider.ErrNotImplemented
|
|
}
|
|
|
|
func (self *SDatastore) FileGetContent(ctx context.Context, remotePath string) ([]byte, error) {
|
|
url := self.GetPathUrl(remotePath)
|
|
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var bytes []byte
|
|
|
|
err = self.manager.client.Do(ctx, req, func(resp *http.Response) error {
|
|
if resp.StatusCode == 404 {
|
|
return cloudprovider.ErrNotFound
|
|
}
|
|
if resp.StatusCode >= 400 {
|
|
return fmt.Errorf("%s", resp.Status)
|
|
}
|
|
cont, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bytes = cont
|
|
return nil
|
|
})
|
|
|
|
return bytes, err
|
|
}
|
|
|
|
type SDatastoreFileInfo struct {
|
|
Url string
|
|
Name string
|
|
Date time.Time
|
|
FileType string
|
|
Size uint64
|
|
}
|
|
|
|
const (
|
|
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>`
|
|
fileDateFormat = "02-Jan-2006 15:04"
|
|
fileDateFormat2 = "Mon, 2 Jan 2006 15:04:05 GMT"
|
|
)
|
|
|
|
var (
|
|
fileListRegexp = regexp.MustCompile(fileListPattern)
|
|
)
|
|
|
|
func (self *SDatastore) ListDir(ctx context.Context, remotePath string) ([]SDatastoreFileInfo, error) {
|
|
listContent, err := self.FileGetContent(ctx, remotePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret := make([]SDatastoreFileInfo, 0)
|
|
matches := fileListRegexp.FindAllStringSubmatch(string(listContent), -1)
|
|
for r := 0; r < len(matches); r += 1 {
|
|
url := strings.TrimSpace(matches[r][1])
|
|
name := strings.TrimSpace(matches[r][2])
|
|
dateStr := strings.TrimSpace(matches[r][3])
|
|
sizeStr := strings.TrimSpace(matches[r][4])
|
|
var ftype string
|
|
var size uint64
|
|
if sizeStr == "-" {
|
|
ftype = "dir"
|
|
size = 0
|
|
} else {
|
|
ftype = "file"
|
|
size, _ = strconv.ParseUint(sizeStr, 10, 64)
|
|
}
|
|
date, err := time.Parse(fileDateFormat, dateStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
info := SDatastoreFileInfo{
|
|
Url: url,
|
|
Name: name,
|
|
FileType: ftype,
|
|
Size: size,
|
|
Date: date,
|
|
}
|
|
ret = append(ret, info)
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func (self *SDatastore) CheckFile(ctx context.Context, remotePath string) (*SDatastoreFileInfo, error) {
|
|
url := self.GetPathUrl(remotePath)
|
|
|
|
req, err := http.NewRequest("HEAD", url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var size uint64
|
|
var date time.Time
|
|
|
|
err = self.manager.client.Do(ctx, req, func(resp *http.Response) error {
|
|
if resp.StatusCode >= 400 {
|
|
if resp.StatusCode == 404 {
|
|
return cloudprovider.ErrNotFound
|
|
}
|
|
return fmt.Errorf("%s", resp.Status)
|
|
}
|
|
sizeStr := resp.Header.Get("Content-Length")
|
|
size, _ = strconv.ParseUint(sizeStr, 10, 64)
|
|
|
|
dateStr := resp.Header.Get("Date")
|
|
date, _ = time.Parse(fileDateFormat2, dateStr)
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &SDatastoreFileInfo{Date: date, Size: size}, nil
|
|
}
|
|
|
|
func (self *SDatastore) Download(ctx context.Context, remotePath string, writer io.Writer) error {
|
|
url := self.GetPathUrl(remotePath)
|
|
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = self.manager.client.Do(ctx, req, func(resp *http.Response) error {
|
|
if resp.StatusCode >= 400 {
|
|
return fmt.Errorf("%s", resp.Status)
|
|
}
|
|
buffer := make([]byte, 4096)
|
|
for {
|
|
rn, re := resp.Body.Read(buffer)
|
|
if rn > 0 {
|
|
wo := 0
|
|
for wo < rn {
|
|
wn, we := writer.Write(buffer[wo:rn])
|
|
if we != nil {
|
|
return we
|
|
}
|
|
wo += wn
|
|
}
|
|
}
|
|
if re != nil {
|
|
if re != io.EOF {
|
|
return re
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (self *SDatastore) Upload(ctx context.Context, remotePath string, body io.Reader) error {
|
|
url := self.GetPathUrl(remotePath)
|
|
|
|
req, err := http.NewRequest("PUT", url, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = self.manager.client.Do(ctx, req, func(resp *http.Response) error {
|
|
if resp.StatusCode >= 400 {
|
|
return fmt.Errorf("%s", resp.Status)
|
|
}
|
|
_, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// log.Debugf("upload respose %s", buffer)
|
|
return nil
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (self *SDatastore) FilePutContent(ctx context.Context, remotePath string, content string) error {
|
|
return self.Upload(ctx, remotePath, strings.NewReader(content))
|
|
}
|
|
|
|
func (self *SDatastore) Delete(ctx context.Context, remotePath string) error {
|
|
url := self.GetPathUrl(remotePath)
|
|
|
|
req, err := http.NewRequest("DELETE", url, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = self.manager.client.Do(ctx, req, func(resp *http.Response) error {
|
|
if resp.StatusCode >= 400 {
|
|
return fmt.Errorf("%s", resp.Status)
|
|
}
|
|
_, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// log.Debugf("delete respose %s", buffer)
|
|
return nil
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (self *SDatastore) DeleteVmdk(ctx context.Context, remotePath string) error {
|
|
info, err := self.CheckFile(ctx, remotePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.Size > 4096 {
|
|
return fmt.Errorf("not a valid vmdk file")
|
|
}
|
|
vmdkContent, err := self.FileGetContent(ctx, remotePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
vmdkInfo, err := vmdkutils.Parse(string(vmdkContent))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = self.Delete(ctx, remotePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(vmdkInfo.ExtentFile) > 0 {
|
|
err = self.Delete(ctx, path.Join(path.Dir(remotePath), vmdkInfo.ExtentFile))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (self *SDatastore) GetVmdkInfo(ctx context.Context, remotePath string) (*vmdkutils.SVMDKInfo, error) {
|
|
vmdkContent, err := self.FileGetContent(ctx, remotePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return vmdkutils.Parse(string(vmdkContent))
|
|
}
|
|
|
|
func (self *SDatastore) CheckVmdk(ctx context.Context, remotePath string) error {
|
|
dm := object.NewVirtualDiskManager(self.manager.client.Client)
|
|
defer dm.Destroy(ctx)
|
|
|
|
dc, err := self.GetDatacenter()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dcObj := dc.getObjectDatacenter()
|
|
|
|
infoList, err := dm.QueryVirtualDiskInfo(ctx, self.getPathString(remotePath), dcObj, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debugf("%#v", infoList)
|
|
return nil
|
|
}
|
|
|
|
func (self *SDatastore) getDatastoreObj() *object.Datastore {
|
|
return object.NewDatastore(self.manager.client.Client, self.getDatastore().Self)
|
|
}
|
|
|
|
func (self *SDatastore) MakeDir(remotePath string) error {
|
|
remotePath = self.cleanPath(remotePath)
|
|
|
|
m := object.NewFileManager(self.manager.client.Client)
|
|
path := fmt.Sprintf("[%s] %s", self.GetRelName(), remotePath)
|
|
return m.MakeDirectory(self.manager.context, path, self.datacenter.getObjectDatacenter(), true)
|
|
}
|
|
|
|
func (self *SDatastore) RemoveDir(ctx context.Context, remotePath string) error {
|
|
dnm := object.NewDatastoreNamespaceManager(self.manager.client.Client)
|
|
|
|
remotePath = self.GetFullPath(remotePath)
|
|
|
|
dc, err := self.GetDatacenter()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dcObj := dc.getObjectDatacenter()
|
|
|
|
return dnm.DeleteDirectory(ctx, dcObj, remotePath)
|
|
}
|
|
|
|
// CheckDirC will check that Dir 'remotePath' is exist, if not, create one.
|
|
func (self *SDatastore) CheckDirC(remotePath string) error {
|
|
_, err := self.CheckFile(self.manager.context, remotePath)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if errors.Cause(err) != cloudprovider.ErrNotFound {
|
|
return err
|
|
}
|
|
return self.MakeDir(remotePath)
|
|
|
|
}
|
|
|
|
func (self *SDatastore) IsSysDiskStore() bool {
|
|
return true
|
|
}
|
|
|
|
func (self *SDatastore) MoveVmdk(ctx context.Context, srcPath string, dstPath string) error {
|
|
dm := object.NewVirtualDiskManager(self.manager.client.Client)
|
|
defer dm.Destroy(ctx)
|
|
|
|
srcUrl := self.GetPathUrl(srcPath)
|
|
dstUrl := self.GetPathUrl(dstPath)
|
|
task, err := dm.MoveVirtualDisk(ctx, srcUrl, nil, dstUrl, nil, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return task.Wait(ctx)
|
|
}
|
|
|
|
// domainName will abandon the rune which should't disapear
|
|
func (self *SDatastore) domainName(name string) string {
|
|
var b bytes.Buffer
|
|
for _, r := range name {
|
|
if b.Len() == 0 {
|
|
if unicode.IsLetter(r) {
|
|
b.WriteRune(r)
|
|
}
|
|
} else {
|
|
if unicode.IsDigit(r) || unicode.IsLetter(r) || r == '-' {
|
|
b.WriteRune(r)
|
|
}
|
|
}
|
|
}
|
|
return strings.TrimRight(b.String(), "-")
|
|
}
|
|
|
|
// ImportVMDK will upload local vmdk 'diskFile' to the 'remotePath' of remote datastore
|
|
func (self *SDatastore) ImportVMDK(ctx context.Context, diskFile, remotePath string, host *SHost) error {
|
|
name := fmt.Sprintf("yunioncloud.%s%d", self.domainName(remotePath)[:20], rand.Int())
|
|
vm, err := self.ImportVM(ctx, diskFile, name, host)
|
|
if err != nil {
|
|
return errors.Wrap(err, "SDatastore.ImportVM")
|
|
}
|
|
|
|
defer func() {
|
|
task, err := vm.Destroy(ctx)
|
|
if err != nil {
|
|
log.Errorf("vm.Destory: %s", err)
|
|
return
|
|
}
|
|
|
|
if err = task.Wait(ctx); err != nil {
|
|
log.Errorf("task.Wait: %s", err)
|
|
}
|
|
}()
|
|
|
|
// check if 'image_cache' is esixt
|
|
err = self.CheckDirC("image_cache")
|
|
if err != nil {
|
|
return errors.Wrap(err, "SDatastore.CheckDirC")
|
|
}
|
|
|
|
fm := self.getDatastoreObj().NewFileManager(self.datacenter.getObjectDatacenter(), true)
|
|
|
|
// if image_cache not exist
|
|
return fm.Move(ctx, fmt.Sprintf("[%s] %s/%s.vmdk", self.GetRelName(), name, name), fmt.Sprintf("[%s] %s",
|
|
self.GetRelName(), remotePath))
|
|
}
|
|
|
|
var (
|
|
ErrInvalidFormat = errors.Error("vmdk: invalid format (must be streamOptimized)")
|
|
)
|
|
|
|
// info is used to inspect a vmdk and generate an ovf template
|
|
type info struct {
|
|
Header struct {
|
|
MagicNumber uint32
|
|
Version uint32
|
|
Flags uint32
|
|
Capacity uint64
|
|
}
|
|
|
|
Capacity uint64
|
|
Size int64
|
|
Name string
|
|
ImportName string
|
|
}
|
|
|
|
// stat looks at the vmdk header to make sure the format is streamOptimized and
|
|
// extracts the disk capacity required to properly generate the ovf descriptor.
|
|
func stat(name string) (*info, error) {
|
|
f, err := os.Open(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var (
|
|
di info
|
|
buf bytes.Buffer
|
|
)
|
|
|
|
_, err = io.CopyN(&buf, f, int64(binary.Size(di.Header)))
|
|
fi, _ := f.Stat()
|
|
_ = f.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = binary.Read(&buf, binary.LittleEndian, &di.Header)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if di.Header.MagicNumber != 0x564d444b { // SPARSE_MAGICNUMBER
|
|
return nil, ErrInvalidFormat
|
|
}
|
|
|
|
if di.Header.Flags&(1<<16) == 0 { // SPARSEFLAG_COMPRESSED
|
|
// Needs to be converted, for example:
|
|
// vmware-vdiskmanager -r src.vmdk -t 5 dst.vmdk
|
|
// qemu-img convert -O vmdk -o subformat=streamOptimized src.vmdk dst.vmdk
|
|
return nil, ErrInvalidFormat
|
|
}
|
|
|
|
di.Capacity = di.Header.Capacity * 512 // VMDK_SECTOR_SIZE
|
|
di.Size = fi.Size()
|
|
di.Name = filepath.Base(name)
|
|
di.ImportName = strings.TrimSuffix(di.Name, ".vmdk")
|
|
|
|
return &di, nil
|
|
}
|
|
|
|
// ovf returns an expanded descriptor template
|
|
func (di *info) ovf() (string, error) {
|
|
var buf bytes.Buffer
|
|
|
|
tmpl, err := template.ParseFiles("/opt/yunion/share/vmware/ovf.xml")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
err = tmpl.Execute(&buf, di)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return buf.String(), nil
|
|
}
|
|
|
|
// ImportParams contains the set of optional params to the Import function.
|
|
// Note that "optional" may depend on environment, such as ESX or vCenter.
|
|
type ImportParams struct {
|
|
Path string
|
|
Logger progress.Sinker
|
|
Type types.VirtualDiskType
|
|
Force bool
|
|
Datacenter *object.Datacenter
|
|
Pool *object.ResourcePool
|
|
Folder *object.Folder
|
|
Host *object.HostSystem
|
|
}
|
|
|
|
// ImportVM will import a vm by uploading a local vmdk
|
|
func (self *SDatastore) ImportVM(ctx context.Context, diskFile, name string, host *SHost) (*object.VirtualMachine, error) {
|
|
|
|
var (
|
|
c = self.manager.client.Client
|
|
datastore = self.getDatastoreObj()
|
|
)
|
|
|
|
m := ovf.NewManager(c)
|
|
|
|
disk, err := stat(diskFile)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "stat")
|
|
}
|
|
|
|
disk.ImportName = name
|
|
|
|
// Expand the ovf template
|
|
descriptor, err := disk.ovf()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "disk.ovf")
|
|
}
|
|
|
|
folders, err := self.datacenter.getObjectDatacenter().Folders(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Folders")
|
|
}
|
|
pool, err := host.GetResourcePool()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "getResourcePool")
|
|
}
|
|
|
|
kind := types.VirtualDiskTypeThin
|
|
|
|
params := types.OvfCreateImportSpecParams{
|
|
DiskProvisioning: string(kind),
|
|
EntityName: disk.ImportName,
|
|
}
|
|
|
|
spec, err := m.CreateImportSpec(ctx, descriptor, pool, datastore, params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if spec.Error != nil {
|
|
return nil, errors.Error(spec.Error[0].LocalizedMessage)
|
|
}
|
|
|
|
lease, err := pool.ImportVApp(ctx, spec.ImportSpec, folders.VmFolder, host.GetoHostSystem())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
info, err := lease.Wait(ctx, spec.FileItem)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
f, err := os.Open(diskFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lr := newLeaseLogger("upload vmdk", 5)
|
|
|
|
lr.Log()
|
|
defer lr.End()
|
|
|
|
opts := soap.Upload{
|
|
ContentLength: disk.Size,
|
|
Progress: lr,
|
|
}
|
|
|
|
u := lease.StartUpdater(ctx, info)
|
|
defer u.Done()
|
|
|
|
item := info.Items[0] // we only have 1 disk to upload
|
|
|
|
err = lease.Upload(ctx, item, f, opts)
|
|
|
|
_ = f.Close()
|
|
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "lease.Upload")
|
|
}
|
|
|
|
if err = lease.Complete(ctx); err != nil {
|
|
log.Debugf("lease complete error: %s, sleep 1s and try again", err)
|
|
time.Sleep(time.Second)
|
|
if err = lease.Complete(ctx); err != nil {
|
|
return nil, errors.Wrap(err, "lease.Complete")
|
|
}
|
|
}
|
|
|
|
return object.NewVirtualMachine(c, info.Entity), nil
|
|
}
|