Files
cloudpods/pkg/util/esxi/storage.go
T

676 lines
16 KiB
Go

package esxi
import (
"context"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"path"
"regexp"
"strconv"
"strings"
"time"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/govmomi/vim25/mo"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/compute/models"
"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.Fatalf("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) GetCapacityMB() int {
moStore := self.getDatastore()
return int(moStore.Summary.Capacity / 1024 / 1024)
}
func (self *SDatastore) GetEnabled() bool {
return true
}
func (self *SDatastore) GetStatus() string {
if self.getDatastore().Summary.Accessible {
return models.STORAGE_ONLINE
} else {
return models.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()
vmfsInfo, ok := moStore.Info.(*types.VmfsDatastoreInfo)
if ok {
if vmfsInfo.Vmfs.Local != nil && *vmfsInfo.Vmfs.Local {
host, err := self.getLocalHost()
if err != nil {
return "", err
}
return fmt.Sprintf("%s:%s", host.GetAccessIp(), vmfsInfo.Vmfs.Uuid), nil
} else {
return vmfsInfo.Vmfs.Uuid, nil
}
}
nasInfo, ok := moStore.Info.(*types.NasDatastoreInfo)
if ok {
return fmt.Sprintf("%s:%s", nasInfo.Nas.RemoteHost, nasInfo.Nas.RemotePath), nil
}
if moStore.Summary.Type == "vsan" {
vsanId := moStore.Summary.Url
vsanId = vsanId[strings.Index(vsanId, "vsan:"):]
endIdx := len(vsanId)
for ; 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, err
}
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
}
return dc.fetchVms(vms)
}
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()
vmfsInfo, ok := moStore.Info.(*types.VmfsDatastoreInfo)
if ok && vmfsInfo.Vmfs.Local != nil && *vmfsInfo.Vmfs.Local {
return true
}
return false
}
func (self *SDatastore) GetStorageType() string {
moStore := self.getDatastore()
switch moStore.Summary.Type {
case "VMFS":
if self.isLocalVMFS() {
return models.STORAGE_LOCAL
} else {
return models.STORAGE_NAS
}
case "NFS", "NFS41", "CIFS", "vsan":
return models.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 models.DISK_TYPE_SSD
}
return models.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
}
func (self *SDatastore) GetManagerId() string {
return self.manager.providerId
}
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) 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 >= 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) 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(ctx context.Context, remotePath string) (string, error) {
dnm := object.NewDatastoreNamespaceManager(self.manager.client.Client)
remotePath = self.cleanPath(remotePath)
objDS := self.getDatastoreObj()
return dnm.CreateDirectory(ctx, objDS, remotePath, "")
}
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)
}