fix(esxi-agent): pve agent (#24768)

This commit is contained in:
屈轩
2026-05-06 17:15:37 +08:00
committed by GitHub
parent 44ae4b68df
commit 000f066e8c
46 changed files with 2270 additions and 1145 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
FROM registry.cn-beijing.aliyuncs.com/yunionio/host-deployer-base:1.5.9
FROM registry.cn-beijing.aliyuncs.com/yunionio/host-deployer-base:1.5.11
LABEL maintainer="Yaoqi Wan wanyaoqi@yunionyun.com"
+2 -2
View File
@@ -108,12 +108,12 @@ require (
k8s.io/cri-api v0.28.15
k8s.io/klog/v2 v2.90.1
moul.io/http2curl/v2 v2.3.0
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260416100244-303b3c130516
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260506084929-0e885263a936
yunion.io/x/executor v0.0.0-20260312022053-f538abd2b005
yunion.io/x/jsonutils v1.0.1-0.20250507052344-1abcf4f443b1
yunion.io/x/log v1.0.1-0.20240305175729-7cf2d6cd5a91
yunion.io/x/ovsdb v0.0.0-20230306173834-f164f413a900
yunion.io/x/pkg v1.10.4-0.20260224071200-5c269fa62429
yunion.io/x/pkg v1.10.4-0.20260422030155-01b100134978
yunion.io/x/s3cli v0.0.0-20241221171442-1c11599d28e1
yunion.io/x/sqlchemy v1.1.3-0.20251231025938-b0a38f6e9fab
yunion.io/x/structarg v0.0.0-20231017124457-df4d5009457c
+4 -4
View File
@@ -1785,8 +1785,8 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260416100244-303b3c130516 h1:Fq+hzBZMrIuPP+AX8x8ffQr2Kk3v2UHvsGl9wtG4Lic=
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260416100244-303b3c130516/go.mod h1:TbMuTWxcTdyL2Usn+WQdIw0jdlBYO66SzPRY8LZ3Wj0=
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260506084929-0e885263a936 h1:93yJirDxKfSvliBCHfwQgc/fHS33qC+YHnbhj/lXFww=
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260506084929-0e885263a936/go.mod h1:TbMuTWxcTdyL2Usn+WQdIw0jdlBYO66SzPRY8LZ3Wj0=
yunion.io/x/executor v0.0.0-20260312022053-f538abd2b005 h1:3sWwcjGXGjG9mLBWa7AyLq+QSi0udTAx21pfVQRFMBE=
yunion.io/x/executor v0.0.0-20260312022053-f538abd2b005/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws=
yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051/go.mod h1:4N0/RVzsYL3kH3WE/H1BjUQdFiWu50JGCFQuuy+Z634=
@@ -1800,8 +1800,8 @@ yunion.io/x/ovsdb v0.0.0-20230306173834-f164f413a900 h1:Hu/4ERvoWaN6aiFs4h4/yvVB
yunion.io/x/ovsdb v0.0.0-20230306173834-f164f413a900/go.mod h1:0vLkNEhlmA64HViPBAnSTUMrx5QP1CLsxXmxDKQ80tc=
yunion.io/x/pkg v0.0.0-20190620104149-945c25821dbf/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
yunion.io/x/pkg v0.0.0-20190628082551-f4033ba2ea30/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
yunion.io/x/pkg v1.10.4-0.20260224071200-5c269fa62429 h1:yskc2HFnBZXEzfYeQBFdL2gXzVfdPQ8yT4qVDyuhajM=
yunion.io/x/pkg v1.10.4-0.20260224071200-5c269fa62429/go.mod h1:0Bwxqd9MA3ACi119/l02FprY/o9gHahmYC2bsSbnVpM=
yunion.io/x/pkg v1.10.4-0.20260422030155-01b100134978 h1:+BS9DValwi0xHa92xrB+RR6Cxp7bDbXVysa/KiC18rY=
yunion.io/x/pkg v1.10.4-0.20260422030155-01b100134978/go.mod h1:0Bwxqd9MA3ACi119/l02FprY/o9gHahmYC2bsSbnVpM=
yunion.io/x/s3cli v0.0.0-20241221171442-1c11599d28e1 h1:1KJ3YYinydPHpDEQRXdr/T8SYcKZ5Er+m489H+PnaQ4=
yunion.io/x/s3cli v0.0.0-20241221171442-1c11599d28e1/go.mod h1:0iFKpOs1y4lbCxeOmq3Xx/0AcQoewVPwj62eRluioEo=
yunion.io/x/sqlchemy v1.1.3-0.20251231025938-b0a38f6e9fab h1:5m/bSzW3uTAk83rp9eethbYsxJFYInFVeU1RDkteW4E=
+28 -26
View File
@@ -70,33 +70,35 @@ type GuestdiskUpdateInput struct {
}
type GuestdiskJsonDesc struct {
DiskId string `json:"disk_id"`
Driver string `json:"driver"`
CacheMode string `json:"cache_mode"`
AioMode string `json:"aio_mode"`
Iops int `json:"iops"`
Throughput int `json:"throughput"`
Bps int `json:"bps"`
Size int `json:"size"`
TemplateId string `json:"template_id"`
ImagePath string `json:"image_path"`
StorageId string `json:"storage_id"`
StorageType string `json:"storage_type"`
Migrating bool `json:"migrating"`
Path string `json:"path"`
Format string `json:"format"`
Index int8 `json:"index"`
BootIndex *int8 `json:"boot_index"`
MergeSnapshot bool `json:"merge_snapshot"`
Fs string `json:"fs"`
Mountpoint string `json:"mountpoint"`
Dev string `json:"dev"`
IsSSD bool `json:"is_ssd"`
NumQueues uint8 `json:"num_queues"`
AutoReset bool `json:"auto_reset"`
PCIPath string `json:"pci_path"`
DiskId string `json:"disk_id"`
Driver string `json:"driver"`
CacheMode string `json:"cache_mode"`
AioMode string `json:"aio_mode"`
Iops int `json:"iops"`
Throughput int `json:"throughput"`
Bps int `json:"bps"`
Size int `json:"size"`
TemplateId string `json:"template_id"`
ImagePath string `json:"image_path"`
StorageId string `json:"storage_id"`
StoragecacheId string `json:"storagecache_id"`
StorageExternalId string `json:"storage_external_id"`
StorageType string `json:"storage_type"`
Migrating bool `json:"migrating"`
Path string `json:"path"`
Format string `json:"format"`
Index int8 `json:"index"`
BootIndex *int8 `json:"boot_index"`
MergeSnapshot bool `json:"merge_snapshot"`
Fs string `json:"fs"`
Mountpoint string `json:"mountpoint"`
Dev string `json:"dev"`
IsSSD bool `json:"is_ssd"`
NumQueues uint8 `json:"num_queues"`
AutoReset bool `json:"auto_reset"`
PCIPath string `json:"pci_path"`
// esxi
// esxi, proxmox
ImageInfo struct {
ImageType string `json:"image_type"`
ImageExternalId string `json:"image_external_id"`
+19 -18
View File
@@ -927,24 +927,25 @@ type ServerUpdateInput struct {
}
type GuestJsonDesc struct {
Name string `json:"name"`
Hostname string `json:"hostname"`
Description string `json:"description"`
UUID string `json:"uuid"`
Mem int `json:"mem"`
CpuSockets int `json:"cpu_sockets"`
Cpu int `json:"cpu"`
Vga string `json:"vga"`
Vdi string `json:"vdi"`
Machine string `json:"machine"`
Bios string `json:"bios"`
BootOrder string `json:"boot_order"`
SrcIpCheck bool `json:"src_ip_check"`
SrcMacCheck bool `json:"src_mac_check"`
IsMaster *bool `json:"is_master"`
IsSlave *bool `json:"is_slave"`
IsVolatileHost bool `json:"is_volatile_host"`
HostId string `json:"host_id"`
Name string `json:"name"`
Hostname string `json:"hostname"`
Description string `json:"description"`
UUID string `json:"uuid"`
Mem int `json:"mem"`
CpuSockets int `json:"cpu_sockets"`
Cpu int `json:"cpu"`
Vga string `json:"vga"`
Vdi string `json:"vdi"`
Machine string `json:"machine"`
Bios string `json:"bios"`
BootOrder string `json:"boot_order"`
SrcIpCheck bool `json:"src_ip_check"`
SrcMacCheck bool `json:"src_mac_check"`
IsMaster *bool `json:"is_master"`
IsSlave *bool `json:"is_slave"`
IsVolatileHost bool `json:"is_volatile_host"`
ExternalImageId string `json:"external_image_id"`
HostId string `json:"host_id"`
// 宿主机管理IP
HostAccessIp string `json:"host_access_ip"`
// 宿主机公网IP(如果有)
+68 -3
View File
@@ -19,7 +19,12 @@ import (
"fmt"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/gotypes"
"yunion.io/x/pkg/util/cloudinit"
"yunion.io/x/pkg/util/httputils"
"yunion.io/x/pkg/util/rbacscope"
"yunion.io/x/pkg/utils"
@@ -50,7 +55,7 @@ func (self *SProxmoxGuestDriver) GetHypervisor() string {
}
func (self *SProxmoxGuestDriver) GetProvider() string {
return api.CLOUD_PROVIDER_PROXMOX
return api.CLOUD_PROVIDER_ONECLOUD
}
func (self *SProxmoxGuestDriver) GetMinimalSysDiskSizeGb() int {
@@ -77,7 +82,7 @@ func (self *SProxmoxGuestDriver) GetInstanceCapability() cloudprovider.SInstance
func (self *SProxmoxGuestDriver) GetComputeQuotaKeys(scope rbacscope.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys {
keys := models.SComputeResourceKeys{}
keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId)
keys.CloudEnv = api.CLOUD_ENV_PRIVATE_CLOUD
keys.CloudEnv = api.CLOUD_ENV_ON_PREMISE
keys.Provider = api.CLOUD_PROVIDER_PROXMOX
keys.Brand = api.CLOUD_PROVIDER_PROXMOX
keys.Hypervisor = api.HYPERVISOR_PROXMOX
@@ -141,7 +146,7 @@ func (self *SProxmoxGuestDriver) GetChangeInstanceTypeStatus() ([]string, error)
}
func (self *SProxmoxGuestDriver) GetRebuildRootStatus() ([]string, error) {
return []string{}, cloudprovider.ErrNotSupported
return []string{api.VM_READY}, nil
}
func (self *SProxmoxGuestDriver) GetDeployStatus() ([]string, error) {
@@ -188,3 +193,63 @@ func (self *SProxmoxGuestDriver) IsSupportCdrom(guest *models.SGuest) (bool, err
func (self *SProxmoxGuestDriver) RequestRemoteUpdate(ctx context.Context, guest *models.SGuest, userCred mcclient.TokenCredential, replaceTags bool) error {
return nil
}
func (self *SProxmoxGuestDriver) GetJsonDescAtHost(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, host *models.SHost, params *jsonutils.JSONDict) (jsonutils.JSONObject, error) {
desc := guest.GetJsonDescAtHypervisor(ctx, host)
if len(desc.Disks) > 0 && len(desc.Disks[0].TemplateId) == 0 {
cdrom := guest.GetCdrom()
if !gotypes.IsNil(cdrom) {
storagecachedimage := models.StoragecachedimageManager.GetStoragecachedimage(desc.Disks[0].StoragecacheId, cdrom.ImageId)
if storagecachedimage != nil {
desc.ExternalImageId = storagecachedimage.ExternalId
}
}
}
return jsonutils.Marshal(desc), nil
}
func (self *SProxmoxGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error {
config, err := guest.GetDeployConfigOnHost(ctx, task.GetUserCred(), host, task.GetParams())
if err != nil {
return errors.Wrap(err, "GetDeployConfigOnHost")
}
log.Debugf("RequestDeployGuestOnHost: %s", config)
if !host.IsEsxiAgentReady() {
return fmt.Errorf("No ESXi agent host")
}
diskCat := guest.CategorizeDisks()
if diskCat.Root == nil {
return fmt.Errorf("no root disk???")
}
storage, _ := diskCat.Root.GetStorage()
if storage == nil {
return fmt.Errorf("root disk has no storage???")
}
config.Add(jsonutils.NewString(host.ExternalId), "host_id")
config.Add(jsonutils.NewString(host.AccessIp), "host_ip")
tags, _ := guest.GetAllUserMetadata()
config.Set("tags", jsonutils.Marshal(tags))
account := host.GetCloudaccount()
accessInfo, err := account.GetVCenterAccessInfo(storage.ExternalId)
if err != nil {
return err
}
config.Add(jsonutils.Marshal(accessInfo), "datastore")
config.Add(jsonutils.NewString(guest.ExternalId), "guest_ext_id")
url := "/disks/proxmox/deploy"
body := jsonutils.NewDict()
body.Add(config, "disk")
header := task.GetTaskRequestHeader()
_, err = host.EsxiRequest(ctx, httputils.POST, url, header, body)
return err
}
+1 -17
View File
@@ -86,23 +86,7 @@ func (self *SESXiHostDriver) CheckAndSetCacheImage(ctx context.Context, userCred
}
}
type contentStruct struct {
ImageId string
HostId string
HostIp string
SrcHostIp string
SrcPath string
SrcDatastore vcenter.SVCenterAccessInfo
Datastore vcenter.SVCenterAccessInfo
Format string
IsForce bool
StoragecacheId string
ImageType string
ImageExternalId string
StorageCacheHostIp string
}
content := contentStruct{}
content := vcenter.ImageCacheInput{}
content.ImageId = imageId
content.HostId = host.Id
content.HostIp = host.AccessIp
+89 -140
View File
@@ -17,23 +17,16 @@ package hostdrivers
import (
"context"
"fmt"
"io"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"yunion.io/x/cloudmux/pkg/multicloud/esxi/vcenter"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/gotypes"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/compute/models"
"yunion.io/x/onecloud/pkg/compute/options"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
modules "yunion.io/x/onecloud/pkg/mcclient/modules/image"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/httputils"
)
type SProxmoxHostDriver struct {
@@ -54,7 +47,7 @@ func (self *SProxmoxHostDriver) GetHypervisor() string {
}
func (self *SProxmoxHostDriver) GetProvider() string {
return api.CLOUD_PROVIDER_PROXMOX
return api.CLOUD_PROVIDER_ONECLOUD
}
func (self *SProxmoxHostDriver) ValidateDiskSize(storage *models.SStorage, sizeGb int) error {
@@ -68,145 +61,101 @@ func (driver *SProxmoxHostDriver) GetStoragecacheQuota(host *models.SHost) int {
func (self *SProxmoxHostDriver) CheckAndSetCacheImage(ctx context.Context, userCred mcclient.TokenCredential, host *models.SHost, storageCache *models.SStoragecache, task taskman.ITask) error {
input := api.CacheImageInput{}
task.GetParams().Unmarshal(&input)
opts := &cloudprovider.SImageCreateOption{}
task.GetParams().Unmarshal(&opts)
if len(input.ImageId) == 0 {
return fmt.Errorf("no image_id params")
}
if input.Format != "iso" {
return fmt.Errorf("invalid image format %s", input.Format)
obj, err := models.CachedimageManager.FetchById(input.ImageId)
if err != nil {
return err
}
cacheImage := obj.(*models.SCachedimage)
cacheInput := vcenter.ImageCacheInput{}
cacheInput.ImageId = input.ImageId
cacheInput.ImageName = cacheImage.Name
cacheInput.ImageExternalId = cacheImage.ExternalId
cacheInput.Format = input.Format
cacheInput.IsForce = input.IsForce
cacheInput.ImageType = cacheImage.ImageType
cacheInput.HostId = host.ExternalId
cacheInput.HostIp = host.AccessIp
cacheInput.Datastore, err = host.GetCloudaccount().GetVCenterAccessInfo(storageCache.ManagerId)
if err != nil {
return err
}
imageSize := int64(0)
if !host.IsEsxiAgentReady() {
return fmt.Errorf("fail to find valid ESXi agent")
}
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
body := jsonutils.NewDict()
body.Add(jsonutils.Marshal(&cacheInput), "disk")
lockman.LockRawObject(ctx, models.CachedimageManager.Keyword(), fmt.Sprintf("%s-%s", storageCache.Id, input.ImageId))
defer lockman.ReleaseRawObject(ctx, models.CachedimageManager.Keyword(), fmt.Sprintf("%s-%s", storageCache.Id, input.ImageId))
header := task.GetTaskRequestHeader()
log.Debugf("XXX Hold lockman key %p cachedimages %s-%s", ctx, storageCache.Id, input.ImageId)
url := "/proxmox/disks/image_cache"
image, err := models.CachedimageManager.GetCachedimageById(ctx, userCred, input.ImageId, false)
if err != nil {
return nil, errors.Wrapf(err, "CachedimageManager.FetchById(%s)", input.ImageId)
}
if len(image.ExternalId) > 0 {
storages, err := image.GetStorages()
if err != nil {
return nil, err
}
find := false
for i := range storages {
iStorage, _ := storages[i].GetIStorage(ctx)
if gotypes.IsNil(iStorage) {
continue
}
iCache := iStorage.GetIStoragecache()
if gotypes.IsNil(iCache) {
continue
}
iImage, err := iCache.GetIImageById(image.ExternalId)
if err == nil {
imageSize = iImage.GetSizeByte()
find = true
break
}
}
if !find {
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "%s", image.ExternalId)
}
opts.ExternalId = image.ExternalId
} else {
var guest *models.SGuest
if len(input.ServerId) > 0 {
server, _ := models.GuestManager.FetchById(input.ServerId)
if server != nil {
guest = server.(*models.SGuest)
}
}
callback := func(progress float32) {
guestInfo := ""
if guest != nil {
guest.SetProgress(progress)
guestInfo = fmt.Sprintf(" for server %s ", guest.Name)
}
log.Infof("Upload image %s from storagecache %s%s status: %.2f%%", opts.ImageName, storageCache.Name, guestInfo, progress)
}
storages, err := host.GetStorages()
if err != nil {
return nil, errors.Wrapf(err, "GetStorages")
}
opts.ExternalId, err = func() (string, error) {
s := auth.GetAdminSession(ctx, options.Options.Region)
info, err := modules.Images.Get(s, input.ImageId, nil)
if err != nil {
return "", errors.Wrapf(err, "Images.Get(%s)", input.ImageId)
}
opts.Description, _ = info.GetString("description")
opts.Checksum, _ = info.GetString("checksum")
minDiskMb, _ := info.Int("min_disk")
opts.MinDiskMb = int(minDiskMb)
minRamMb, _ := info.Int("min_ram")
opts.MinRamMb = int(minRamMb)
opts.TmpPath = options.Options.TempPath
opts.GetReader = func(imageId, format string) (io.Reader, int64, error) {
_, reader, sizeByte, err := modules.Images.Download(s, imageId, format, false)
return reader, sizeByte, err
}
for i := range storages {
cache := storages[i].GetStoragecache()
iCache, _ := cache.GetIStorageCache(ctx)
if gotypes.IsNil(iCache) {
continue
}
ret, err := iCache.UploadImage(ctx, opts, callback)
if err != nil {
if errors.Cause(err) == cloudprovider.ErrNotSupported {
continue
}
return "", errors.Wrapf(err, "UploadImage")
}
region, err := host.GetRegion()
if err != nil {
return ret, nil
}
obj, err := models.CachedimageManager.FetchById(input.ImageId)
if err != nil {
return ret, errors.Wrapf(err, "CachedimageManager.FetchById")
}
cachedImage := obj.(*models.SCachedimage)
db.Update(cachedImage, func() error {
cachedImage.ExternalId = ret
return nil
})
cache.SyncCloudImages(ctx, userCred, iCache, region, true)
return ret, nil
}
return "", fmt.Errorf("no valid storagecache for upload image")
}()
if err != nil {
return nil, err
}
log.Infof("upload image %s id: %s", opts.ImageName, image.ExternalId)
}
ret := jsonutils.NewDict()
ret.Add(jsonutils.NewString(opts.ExternalId), "image_id")
ret.Add(jsonutils.NewInt(imageSize), "size")
return ret, nil
})
_, err = host.EsxiRequest(ctx, httputils.POST, url, header, body)
if err != nil {
return err
}
return nil
}
func (self *SProxmoxHostDriver) RequestResizeDiskOnHost(ctx context.Context, host *models.SHost, storage *models.SStorage, disk *models.SDisk, sizeMb int64, task taskman.ITask) error {
guest := disk.GetGuest()
if guest == nil {
return fmt.Errorf("unable to find guest has disk %s", disk.GetId())
}
iVm, err := guest.GetIVM(ctx)
if err != nil {
return errors.Wrapf(err, "GetIVM")
}
if iVm.GetStatus() == api.VM_RUNNING {
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
disks, err := iVm.GetIDisks()
if err != nil {
return nil, errors.Wrapf(err, "GetIDisk")
}
for i := range disks {
if disks[i].GetGlobalId() == disk.ExternalId {
err = disks[i].Resize(ctx, sizeMb)
if err != nil {
return nil, errors.Wrapf(err, "Resize")
}
return jsonutils.Marshal(map[string]int64{"disk_size": sizeMb}), nil
}
}
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "disk %s", disk.Name)
})
return nil
}
spec := struct {
HostInfo vcenter.SVCenterAccessInfo
VMId string
DiskId string
SizeMb int64
}{}
account := host.GetCloudaccount()
accessInfo, err := account.GetVCenterAccessInfo(host.ExternalId)
if err != nil {
return err
}
spec.HostInfo = accessInfo
spec.DiskId = disk.GetExternalId()
spec.VMId = guest.GetExternalId()
spec.SizeMb = sizeMb
body := jsonutils.NewDict()
body.Add(jsonutils.Marshal(spec), "disk")
url := fmt.Sprintf("/disks/proxmox/resize/%s", disk.Id)
header := task.GetTaskRequestHeader()
_, err = host.EsxiRequest(ctx, httputils.POST, url, header, body)
return err
}
+5 -10
View File
@@ -19,7 +19,6 @@ import (
"fmt"
"sort"
"yunion.io/x/cloudmux/pkg/multicloud/esxi"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
@@ -58,20 +57,16 @@ var ipMaskLen int8 = 24
func (account *SCloudaccount) PrepareEsxiHostNetwork(ctx context.Context, userCred mcclient.TokenCredential, zoneId string) error {
cProvider, err := account.GetProvider(ctx)
if err != nil {
return errors.Wrap(err, "account.GetProvider")
return errors.Wrap(err, "GetProvider")
}
// fetch esxiclient
iregion, err := cProvider.GetOnPremiseIRegion()
region, err := cProvider.GetOnPremiseIRegion()
if err != nil {
return errors.Wrap(err, "cProvider.GetOnPremiseIRegion")
return errors.Wrap(err, "GetOnPremiseIRegion")
}
esxiClient, ok := iregion.(*esxi.SESXiClient)
if !ok {
return errors.Wrap(httperrors.ErrNotSupported, "not a esxi provider")
}
iHosts, err := esxiClient.GetIHosts()
iHosts, err := region.GetIHosts()
if err != nil {
return errors.Wrap(err, "esxiClient.GetIHosts")
return errors.Wrap(err, "GetIHosts")
}
hostIps := make([]netutils.IPV4Addr, 0)
+4
View File
@@ -208,14 +208,18 @@ func (self *SGuestdisk) GetDiskJsonDescAtHost(ctx context.Context, host *SHost,
Bps: self.Bps,
Size: disk.DiskSize,
PCIPath: disk.PCIPath,
StorageId: disk.StorageId,
}
desc.TemplateId = disk.GetTemplateId()
storage, _ := disk.GetStorage()
desc.StorageType = storage.StorageType
desc.StorageExternalId = storage.GetExternalId()
desc.StoragecacheId = storage.StoragecacheId
if len(desc.TemplateId) > 0 {
storagecacheimg := StoragecachedimageManager.GetStoragecachedimage(storage.StoragecacheId, desc.TemplateId)
if storagecacheimg != nil {
desc.ImagePath = storagecacheimg.Path
desc.ImageInfo.ImageExternalId = storagecacheimg.ExternalId
}
}
if utils.IsInStringArray(host.HostType, []string{api.HOST_TYPE_HYPERVISOR, api.HOST_TYPE_CONTAINER}) {
+10 -9
View File
@@ -1749,14 +1749,6 @@ func (manager *SGuestManager) validateCreateData(
return nil, errors.Wrap(err, "checkGuestImage")
}
if len(input.PreferManager) > 0 && len(input.Provider) == 0 {
providerObj, err := CloudproviderManager.FetchById(input.PreferManager)
if err != nil {
return nil, errors.Wrapf(err, "zone fetch by id %s", input.PreferZone)
}
provider := providerObj.(*SCloudprovider)
input.Provider = provider.Provider
}
if len(input.PreferZone) > 0 && len(input.Provider) == 0 {
zoneObj, err := ZoneManager.FetchById(input.PreferZone)
if err != nil {
@@ -1773,6 +1765,15 @@ func (manager *SGuestManager) validateCreateData(
region := regionObj.(*SCloudregion)
input.Provider = region.Provider
}
// pve, vmware provider is OneCloud
if len(input.PreferManager) > 0 && len(input.Provider) == 0 {
providerObj, err := CloudproviderManager.FetchById(input.PreferManager)
if err != nil {
return nil, errors.Wrapf(err, "zone fetch by id %s", input.PreferZone)
}
provider := providerObj.(*SCloudprovider)
input.Provider = provider.Provider
}
if len(input.Provider) == 0 {
input.Provider = api.CLOUD_PROVIDER_ONECLOUD
}
@@ -1958,7 +1959,7 @@ func (manager *SGuestManager) validateCreateData(
return nil, err
}
optionSystemHypervisor := []string{api.HYPERVISOR_KVM, api.HYPERVISOR_ESXI, api.HYPERVISOR_POD}
optionSystemHypervisor := []string{api.HYPERVISOR_KVM, api.HYPERVISOR_ESXI, api.HYPERVISOR_PROXMOX, api.HYPERVISOR_POD}
if !utils.IsInStringArray(input.Hypervisor, optionSystemHypervisor) && len(input.Disks[0].ImageId) == 0 && len(input.Disks[0].SnapshotId) == 0 && input.Cdrom == "" {
return nil, httperrors.NewBadRequestError("Miss operating system???")
@@ -41,8 +41,8 @@ func init() {
func (self *CloudAccountSyncInfoTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
cloudaccount := obj.(*models.SCloudaccount)
if cloudaccount.Provider == api.CLOUD_PROVIDER_VMWARE {
cloudaccount.SetStatus(ctx, self.UserCred, api.CLOUD_PROVIDER_SYNC_NETWORK, "StartSyncVMwareNetworkTask")
if cloudaccount.Provider == api.CLOUD_PROVIDER_VMWARE || cloudaccount.Provider == api.CLOUD_PROVIDER_PROXMOX {
cloudaccount.SetStatus(ctx, self.UserCred, api.CLOUD_PROVIDER_SYNC_NETWORK, "StartSyncOnPremiseNetworkTask")
zone, _ := self.Params.GetString("zone")
err := cloudaccount.PrepareEsxiHostNetwork(ctx, self.UserCred, zone)
if err != nil {
+21 -6
View File
@@ -20,6 +20,7 @@ import (
"net"
"net/http"
"yunion.io/x/cloudmux/pkg/multicloud/esxi/vcenter"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/apis/compute"
@@ -47,9 +48,11 @@ var (
type SEsxiAgent struct {
agent.SBaseAgent
agentImageCache *storageman.SAgentImageCacheManager
AgentStorage *storageman.SAgentStorage
ListenNic netutils2.SNetInterface
agentImageCache *storageman.SAgentImageCacheManager
agentProxmoxCache *storageman.SAgentProxmoxCacheManager
AgentStorage *storageman.SAgentStorage
ProxmoxStorage *storageman.SProxmoxStorage
ListenNic netutils2.SNetInterface
}
func NewEsxiAgent() (*SEsxiAgent, error) {
@@ -110,8 +113,11 @@ func (ea *SEsxiAgent) Start() error {
}
// add agent image cache
ea.agentImageCache = storageman.NewAgentImageCacheManager(ea.CacheManager)
ea.agentProxmoxCache = storageman.NewAgentProxmoxCacheManager(ea.CacheManager)
ea.AgentStorage = storageman.NewAgentStorage(&storageman.SStorageManager{LocalStorageImagecacheManager: ea.CacheManager},
ea, options.Options.AgentTempPath)
ea.ProxmoxStorage = storageman.NewProxmoxStorage(&storageman.SStorageManager{LocalStorageImagecacheManager: ea.CacheManager},
ea, options.Options.AgentTempPath)
cronManager := cronman.InitCronJobManager(false, options.Options.CronJobWorkerCount, options.Options.TimeZone)
err = cronManager.AddJobEveryFewDays(
@@ -147,6 +153,11 @@ func (agent *SEsxiAgent) AddImageCacheHandler(prefix string, app *appsrv.Applica
auth.Authenticate(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
performImageCache(ctx, w, r, agent.agentImageCache.PrefetchImageCache)
}))
app.AddHandler("POST",
fmt.Sprintf("%s/proxmox/disks/image_cache", prefix),
auth.Authenticate(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
performImageCache(ctx, w, r, agent.agentProxmoxCache.PrefetchProxmoxImageCache)
}))
app.AddHandler("DELETE",
fmt.Sprintf("%s/disks/image_cache", prefix),
auth.Authenticate(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
@@ -162,13 +173,17 @@ func performImageCache(
) {
_, _, body := appsrv.FetchEnv(ctx, w, r)
disk, err := body.Get("disk")
input := struct {
Disk vcenter.ImageCacheInput
}{}
err := body.Unmarshal(&input)
if err != nil {
httperrors.MissingParameterError(ctx, w, "disk")
httperrors.BadRequestError(ctx, w, "unmarshal disk %s", err)
return
}
hostutils.DelayTask(ctx, performTask, disk)
hostutils.DelayTask(ctx, performTask, &input.Disk)
hostutils.ResponseOk(ctx, w)
}
+1 -1
View File
@@ -39,6 +39,7 @@ const (
func InitHandlers(app *appsrv.Application) {
initESXIHandler(app)
initProxmoxHandler(app)
}
var defaultHandler = func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
@@ -55,7 +56,6 @@ func AgentPrefix(action string) string {
}
func initESXIHandler(app *appsrv.Application) {
app.AddHandler("POST", AgentPrefix("upload"), auth.Authenticate(uploadHandler))
app.AddHandler("POST", AgentPrefix("deploy"), auth.Authenticate(deployHandler))
app.AddHandler("POST", IdAgentPrefix("delete"), auth.Authenticate(deleteHandler))
+124
View File
@@ -0,0 +1,124 @@
// 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 handler
import (
"context"
"fmt"
"net/http"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/esxi"
"yunion.io/x/onecloud/pkg/hostman/hostutils"
"yunion.io/x/onecloud/pkg/hostman/storageman"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/pkg/appctx"
"yunion.io/x/pkg/errors"
)
const (
PROXMOX_PREFIX = "disks/proxmox"
)
var defaultProxmoxHandler = func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
httperrors.NotImplementedError(ctx, w, "")
return
}
func IdProxmoxPrefix(action string) string {
return fmt.Sprintf("%s/%s/<disk_id>", PROXMOX_PREFIX, action)
}
func ProxmoxPrefix(action string) string {
return fmt.Sprintf("%s/%s", PROXMOX_PREFIX, action)
}
func initProxmoxHandler(app *appsrv.Application) {
app.AddHandler("POST", ProxmoxPrefix("deploy"), auth.Authenticate(deployProxmoxHandler))
app.AddHandler("POST", IdProxmoxPrefix("delete"), auth.Authenticate(deleteProxmoxHandler))
app.AddHandler("POST", IdProxmoxPrefix("resize"), auth.Authenticate(resizeProxmoxHandler))
}
func deployProxmoxHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
log.Debugf("enter deployProxmoxHandler")
_, _, body := appsrv.FetchEnv(ctx, w, r)
disk, err := body.Get("disk")
if err != nil {
httperrors.MissingParameterError(ctx, w, "miss disk")
return
}
hostutils.DelayTask(ctx, esxi.EsxiAgent.ProxmoxStorage.AgentDeployGuest, disk)
hostutils.ResponseOk(ctx, w)
}
func deleteProxmoxHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
params, _, _ := appsrv.FetchEnv(ctx, w, r)
diskId := params["<disk_id>"]
disk, err := esxi.EsxiAgent.ProxmoxStorage.GetDiskById(diskId)
if err != nil {
httperrors.GeneralServerError(ctx, w, errors.Wrapf(err, "GetDiskById(%s)", diskId))
return
}
if taskId := ctx.Value(appctx.APP_CONTEXT_KEY_TASK_ID); taskId == nil {
if disk != nil {
_, err := disk.Delete(ctx, nil)
if err != nil {
httperrors.GeneralServerError(ctx, w, err)
return
}
hostutils.ResponseOk(ctx, w)
return
}
}
hostutils.ResponseOk(ctx, w)
hostutils.DelayTask(ctx, disk.Delete, nil)
}
func resizeProxmoxHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
disk, diskInfo, err := proxmoxDiskAndDiskInfo(ctx, w, r)
if err != nil {
httperrors.GeneralServerError(ctx, w, err)
return
}
resizeDiskInfo := &storageman.SDiskResizeInput{
DiskInfo: diskInfo,
}
resizeFunc := func(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
input, ok := params.(*storageman.SDiskResizeInput)
if !ok {
return nil, hostutils.ParamsError
}
return disk.Resize(ctx, input)
}
hostutils.DelayTask(ctx, resizeFunc, resizeDiskInfo)
hostutils.ResponseOk(ctx, w)
}
func proxmoxDiskAndDiskInfo(ctx context.Context, w http.ResponseWriter, r *http.Request) (storageman.IDisk, jsonutils.JSONObject, error) {
params, _, body := appsrv.FetchEnv(ctx, w, r)
diskId := params["<disk_id>"]
disk, err := esxi.EsxiAgent.ProxmoxStorage.GetDiskById(diskId)
if err != nil {
return nil, nil, httperrors.NewGeneralError(errors.Wrapf(err, "GetDiskById(%s)", diskId))
}
diskInfo, err := body.Get("disk")
if err != nil {
return nil, nil, httperrors.NewMissingParameterError("miss disk")
}
return disk, diskInfo, nil
}
+2
View File
@@ -50,6 +50,8 @@ func GetIDisk(params DiskParams, driver string, readOnly bool) (IDisk, error) {
case comapi.HYPERVISOR_ESXI:
// ESXI does not support encrypted disk
return NewVDDKDisk(params.VddkInfo, params.DiskInfo.Path, driver, readOnly)
case comapi.HYPERVISOR_PROXMOX:
return NewNbdkitDisk(params.VddkInfo, params.DiskInfo.Path, driver, readOnly)
default:
return NewKVMGuestDisk(params.DiskInfo, driver, readOnly)
}
+269
View File
@@ -0,0 +1,269 @@
// 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 diskutils
import (
"fmt"
"regexp"
"strconv"
"strings"
"time"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver"
"yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
"yunion.io/x/onecloud/pkg/util/qemuimg"
)
type NbdkitDisk struct {
Host string
Port int
User string
Passwd string
DiskPath string
Proc *Command
NbdURI string
kvmDisk *SKVMGuestDisk
readOnly bool
deployDriver string
}
func NewNbdkitDisk(vddkInfo *apis.VDDKConInfo, diskPath, deployDriver string, readOnly bool) (*NbdkitDisk, error) {
return &NbdkitDisk{
Host: vddkInfo.Host,
Port: int(vddkInfo.Port),
User: vddkInfo.User,
Passwd: vddkInfo.Passwd,
DiskPath: diskPath,
deployDriver: deployDriver,
readOnly: readOnly,
}, nil
}
func (vd *NbdkitDisk) Cleanup() {
if vd.kvmDisk != nil {
vd.kvmDisk.Cleanup()
vd.kvmDisk = nil
}
}
func (vd *NbdkitDisk) Connect(*apis.GuestDesc) error {
flatFile, err := vd.ConnectBlockDevice()
if err != nil {
return errors.Wrap(err, "ConnectBlockDevice")
}
vd.kvmDisk, err = NewKVMGuestDisk(qemuimg.SImageInfo{Path: flatFile}, vd.deployDriver, vd.readOnly)
if err != nil {
vd.DisconnectBlockDevice()
return errors.Wrap(err, "NewKVMGuestDisk")
}
err = vd.kvmDisk.Connect(nil)
if err != nil {
vd.DisconnectBlockDevice()
return errors.Wrap(err, "kvmDisk connect")
}
return nil
}
func (vd *NbdkitDisk) ConnectWithDiskId(desc *apis.GuestDesc, diskId string) error {
return vd.Connect(desc)
}
func (vd *NbdkitDisk) Disconnect() error {
if vd.kvmDisk != nil {
if err := vd.kvmDisk.Disconnect(); err != nil {
log.Errorf("kvm disk disconnect failed %s", err)
}
vd.kvmDisk.Cleanup()
vd.kvmDisk = nil
}
return vd.DisconnectBlockDevice()
}
func (vd *NbdkitDisk) MountRootfs() (fsdriver.IRootFsDriver, error) {
if vd.kvmDisk == nil {
return nil, fmt.Errorf("kvmDisk is nil")
}
return vd.kvmDisk.MountRootfs()
}
func (vd *NbdkitDisk) UmountRootfs(fd fsdriver.IRootFsDriver) error {
if vd.kvmDisk == nil {
return nil
}
return vd.kvmDisk.UmountRootfs(fd)
}
func (vd *NbdkitDisk) Mount() (err error) {
_, err = vd.ConnectBlockDevice()
return err
}
func (vd *NbdkitDisk) Umount() error {
if vd.Proc != nil {
if !vd.Proc.Exited() {
if err := vd.Proc.Kill(); err != nil {
log.Errorf("kill nbdkit process failed: %s", err)
}
}
if err := vd.Proc.Wait(); err != nil {
log.Warningf("wait nbdkit process failed: %s", err)
}
vd.Proc = nil
}
vd.NbdURI = ""
return nil
}
func (vd *NbdkitDisk) ExecProg() error {
args := []string{
"--foreground",
"--exit-with-parent",
"-v",
"ssh",
"verify-remote-host=false",
fmt.Sprintf("host=%s", vd.Host),
fmt.Sprintf("user=%s", vd.User),
fmt.Sprintf("path=%s", vd.DiskPath),
}
if vd.readOnly {
args = append([]string{"-r"}, args...)
}
if vd.Port > 0 {
args = append(args, fmt.Sprintf("port=%d", vd.Port))
}
if len(vd.Passwd) > 0 {
args = append(args, fmt.Sprintf("password=%s", vd.Passwd))
}
cmd := NewCommand("nbdkit", args...)
log.Debugf("command to mount: %s", cmd)
vd.Proc = cmd
vd.NbdURI = ""
err := vd.Proc.Start()
if err != nil {
return errors.Wrap(err, "vd.Proc.Start")
}
return nil
}
func (vd *NbdkitDisk) WaitMounted() error {
timeout := time.After(30 * time.Second)
for {
select {
case <-timeout:
return errors.Errorf("wait nbdkit ready timeout: %s", vd.Proc.stdouterr.String())
default:
if vd.Proc != nil {
output := vd.Proc.stdouterr.String()
if nbdURI, ok := parseNbdkitOutputNbdURI(output); ok {
vd.NbdURI = nbdURI
return nil
}
}
if vd.Proc != nil && vd.Proc.Exited() {
backup := vd.Proc.stdouterr.String()
return errors.Errorf("nbdkit exited unexpectedly: %s", backup)
}
time.Sleep(200 * time.Millisecond)
}
}
}
var (
nbdkitURIRegex = regexp.MustCompile(`nbd://(?:127\.0\.0\.1|localhost):(\d{2,5})`)
nbdkitPortRegexList = []*regexp.Regexp{
regexp.MustCompile(`(?:127\.0\.0\.1|localhost):(\d{2,5})`),
regexp.MustCompile(`\bbound\s+to\s+IP\s+address\s+[^\s:]+:(\d{2,5})\b`),
regexp.MustCompile(`\bport(?:\s+number)?\s*[=:]?\s*(\d{2,5})\b`),
}
)
// nbdkit: debug: bound to IP address <any>:10809 (2 socket(s))
func parseNbdkitOutputNbdURI(output string) (string, bool) {
if matched := nbdkitURIRegex.FindStringSubmatch(output); len(matched) == 2 {
port, err := strconv.Atoi(matched[1])
if err == nil && port > 0 {
return fmt.Sprintf("nbd://127.0.0.1:%d/", port), true
}
}
for _, regex := range nbdkitPortRegexList {
if matched := regex.FindStringSubmatch(output); len(matched) == 2 {
port, err := strconv.Atoi(matched[1])
if err == nil && port > 0 {
return fmt.Sprintf("nbd://127.0.0.1:%d/", port), true
}
}
}
if idx := strings.Index(output, "nbd://"); idx >= 0 {
candidate := output[idx:]
if end := strings.IndexAny(candidate, " \n\r\t"); end >= 0 {
candidate = candidate[:end]
}
if strings.HasPrefix(candidate, "nbd://") && strings.Contains(candidate, ":") {
parts := strings.Split(candidate, ":")
port, err := strconv.Atoi(parts[len(parts)-1])
if err == nil && port > 0 {
return fmt.Sprintf("nbd://127.0.0.1:%d/", port), true
}
}
}
return "", false
}
// ConnectBlockDevice starts nbdkit ssh plugin and returns a local NBD URI.
func (vd *NbdkitDisk) ConnectBlockDevice() (string, error) {
if vd.Proc != nil && !vd.Proc.Exited() && len(vd.NbdURI) > 0 {
return vd.NbdURI, nil
}
if err := vd.ExecProg(); err != nil {
return "", errors.Wrap(err, "ExecProg")
}
if err := vd.WaitMounted(); err != nil {
return "", errors.Wrap(err, "WaitMounted")
}
log.Infof("disk mounted by nbdkit over %s", vd.NbdURI)
return vd.NbdURI, nil
}
func (vd *NbdkitDisk) DisconnectBlockDevice() error {
if vd.Proc != nil {
return vd.Umount()
}
return fmt.Errorf("nbdkit disk has not connected")
}
func (vd *NbdkitDisk) DeployGuestfs(req *apis.DeployParams) (res *apis.DeployGuestFsResponse, err error) {
return vd.kvmDisk.DeployGuestfs(req)
}
func (d *NbdkitDisk) ResizeFs(req *apis.ResizeFsParams) (*apis.Empty, error) {
return d.kvmDisk.ResizeFs(req)
}
func (d *NbdkitDisk) FormatFs(req *apis.FormatFsParams) (*apis.Empty, error) {
return d.kvmDisk.FormatFs(req)
}
func (d *NbdkitDisk) SaveToGlance(req *apis.SaveToGlanceParams) (*apis.SaveToGlanceResponse, error) {
return d.kvmDisk.SaveToGlance(req)
}
func (d *NbdkitDisk) ProbeImageInfo(req *apis.ProbeImageInfoPramas) (*apis.ImageInfo, error) {
return d.kvmDisk.ProbeImageInfo(req)
}
+47
View File
@@ -0,0 +1,47 @@
package diskutils
import "testing"
func TestParseNbdkitOutputNbdURI(t *testing.T) {
cases := []struct {
name string
output string
want string
ok bool
}{
{
name: "bound to any ip address",
output: "nbdkit: debug: bound to IP address <any>:10809 (2 socket(s))",
want: "nbd://127.0.0.1:10809/",
ok: true,
},
{
name: "direct nbd uri",
output: "ready at nbd://localhost:10810",
want: "nbd://127.0.0.1:10810/",
ok: true,
},
{
name: "port key value",
output: "nbdkit: listening port=10811",
want: "nbd://127.0.0.1:10811/",
ok: true,
},
{
name: "cannot parse",
output: "nbdkit: debug: waiting for connection",
want: "",
ok: false,
},
}
for _, tc := range cases {
got, ok := parseNbdkitOutputNbdURI(tc.output)
if ok != tc.ok {
t.Fatalf("%s: ok mismatch, got %v want %v", tc.name, ok, tc.ok)
}
if got != tc.want {
t.Fatalf("%s: uri mismatch, got %q want %q", tc.name, got, tc.want)
}
}
}
+127
View File
@@ -0,0 +1,127 @@
// 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 storageman
import (
"context"
"strings"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"yunion.io/x/cloudmux/pkg/multicloud/esxi/vcenter"
"yunion.io/x/cloudmux/pkg/multicloud/proxmox"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/apis/compute"
deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
"yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient"
"yunion.io/x/onecloud/pkg/hostman/hostutils"
"yunion.io/x/onecloud/pkg/httperrors"
)
type SProxmoxDisk struct {
SLocalDisk
}
func NewProxmoxDisk(storage IStorage, id string) *SProxmoxDisk {
return &SProxmoxDisk{*NewLocalDisk(storage, id)}
}
func (sd *SProxmoxDisk) PrepareSaveToGlance(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
p, ok := params.(PrepareSaveToGlanceParams)
if !ok {
return nil, errors.Wrap(hostutils.ParamsError, "Resize params format error")
}
storage := sd.Storage.(*SAgentStorage)
return storage.PrepareSaveToGlance(ctx, p.TaskId, p.DiskInfo)
}
func (sd *SProxmoxDisk) Resize(ctx context.Context, params *SDiskResizeInput) (jsonutils.JSONObject, error) {
body := params.DiskInfo
type sResize struct {
SizeMb int64 `json:"size_mb"`
HostInfo vcenter.SVCenterAccessInfo
VMId string `json:"vm_id"`
DiskId string `json:"disk_id"`
}
resize := sResize{}
err := body.Unmarshal(&resize)
if err != nil {
return nil, errors.Wrapf(err, "%s: unmarshal to sResize", hostutils.ParamsError)
}
client, err := proxmox.NewProxmoxClientFromAccessInfo(&resize.HostInfo)
if err != nil {
return nil, httperrors.NewInputParameterError("info of host_info error")
}
host, err := client.GetHost(resize.HostInfo.PrivateId)
if err != nil {
return nil, errors.Wrapf(err, "fail to find host by ip %s", resize.HostInfo.PrivateId)
}
ivm, err := host.GetIVMById(resize.VMId)
if err != nil {
return nil, errors.Wrapf(err, "fail to find vm by ID %s", resize.VMId)
}
disks, err := ivm.GetIDisks()
if err != nil {
return nil, errors.Wrapf(err, "fail to get idisks of vm %s", resize.VMId)
}
var disk cloudprovider.ICloudDisk = nil
for i := range disks {
if disks[i].GetGlobalId() == resize.DiskId {
disk = disks[i]
break
}
}
if disk == nil {
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "fail to find disk by ID %s", resize.DiskId)
}
err = disk.Resize(ctx, resize.SizeMb)
if err != nil {
return nil, errors.Wrapf(err, "fail to resize disk %s", resize.DiskId)
}
desc := jsonutils.NewDict()
desc.Add(jsonutils.NewInt(resize.SizeMb), "disk_size")
// try to resize partition
err = resizeProxmoxPartition(ctx, host, disk, resize.HostInfo)
if err != nil {
log.Errorf("unable to ResizePartition: %v", err)
}
return desc, nil
}
func resizeProxmoxPartition(ctx context.Context, host *proxmox.SHost, disk cloudprovider.ICloudDisk, accessInfo vcenter.SVCenterAccessInfo) error {
diskPath := disk.GetAccessPath()
diskInfo := deployapi.DiskInfo{
Path: diskPath,
}
vddkInfo := deployapi.VDDKConInfo{
Host: host.GetAccessIp(),
Port: 22,
User: strings.TrimSuffix(accessInfo.Account, "@pam"),
Passwd: accessInfo.Password,
}
_, err := deployclient.GetDeployClient().ResizeFs(ctx, &deployapi.ResizeFsParams{
DiskInfo: &diskInfo,
Hypervisor: compute.HYPERVISOR_PROXMOX,
VddkInfo: &vddkInfo,
})
if err != nil {
return errors.Wrap(err, "unable to ResizeFs")
}
return nil
}
@@ -41,44 +41,23 @@ func NewAgentImageCacheManager(manger IImageCacheManger) *SAgentImageCacheManage
return &SAgentImageCacheManager{manger}
}
type sImageCacheData struct {
ImageId string
HostId string
HostIp string
SrcHostIp string
SrcPath string
SrcDatastore vcenter.SVCenterAccessInfo
Datastore vcenter.SVCenterAccessInfo
Format string
IsForce bool
StoragecacheId string
ImageType string
ImageExternalId string
StorageCacheHostIp string
}
func (c *SAgentImageCacheManager) PrefetchImageCache(ctx context.Context, data interface{}) (jsonutils.JSONObject, error) {
dataDict, ok := data.(*jsonutils.JSONDict)
input, ok := data.(*vcenter.ImageCacheInput)
if !ok {
return nil, errors.Wrap(hostutils.ParamsError, "PrefetchImageCache data format error")
}
idata := new(sImageCacheData)
err := dataDict.Unmarshal(idata)
if err != nil {
return nil, errors.Wrap(err, "%s: unmarshal to sImageCacheData error")
lockman.LockRawObject(ctx, input.HostId, input.ImageId)
defer lockman.ReleaseRawObject(ctx, input.HostId, input.ImageId)
if cloudprovider.TImageType(input.ImageType) == cloudprovider.ImageTypeSystem {
return c.perfetchTemplateVMImageCache(ctx, input)
}
lockman.LockRawObject(ctx, idata.HostId, idata.ImageId)
defer lockman.ReleaseRawObject(ctx, idata.HostId, idata.ImageId)
if cloudprovider.TImageType(idata.ImageType) == cloudprovider.ImageTypeSystem {
return c.perfetchTemplateVMImageCache(ctx, idata)
if len(input.SrcHostIp) != 0 {
return c.prefetchImageCacheByCopy(ctx, input)
}
if len(idata.SrcHostIp) != 0 {
return c.prefetchImageCacheByCopy(ctx, idata)
}
return c.prefetchImageCacheByUpload(ctx, idata, dataDict)
return c.prefetchImageCacheByUpload(ctx, input)
}
func (c *SAgentImageCacheManager) prefetchImageCacheByCopy(ctx context.Context, data *sImageCacheData) (jsonutils.JSONObject, error) {
func (c *SAgentImageCacheManager) prefetchImageCacheByCopy(ctx context.Context, data *vcenter.ImageCacheInput) (jsonutils.JSONObject, error) {
client, err := esxi.NewESXiClientFromAccessInfo(ctx, &data.Datastore)
if err != nil {
return nil, err
@@ -147,39 +126,39 @@ func (c *SAgentImageCacheManager) prefetchImageCacheByCopy(ctx context.Context,
return ret, nil
}
func (c *SAgentImageCacheManager) prefetchImageCacheByUpload(ctx context.Context, data *sImageCacheData,
origin *jsonutils.JSONDict) (jsonutils.JSONObject, error) {
input := api.CacheImageInput{}
origin.Unmarshal(&input)
localImage, err := c.imageCacheManger.PrefetchImageCache(ctx, input)
func (c *SAgentImageCacheManager) prefetchImageCacheByUpload(ctx context.Context, input *vcenter.ImageCacheInput) (jsonutils.JSONObject, error) {
data := api.CacheImageInput{}
err := jsonutils.Update(&data, input)
if err != nil {
return nil, errors.Wrap(err, "jsonutils.Update")
}
localImage, err := c.imageCacheManger.PrefetchImageCache(ctx, data)
if err != nil {
return nil, err
}
localImgPath, _ := localImage.GetString("path")
//localImgSize, _ := localImage.Int("size")
client, err := esxi.NewESXiClientFromAccessInfo(ctx, &data.Datastore)
client, err := esxi.NewESXiClientFromAccessInfo(ctx, &input.Datastore)
if err != nil {
return nil, errors.Wrap(err, "esxi.NewESXiClientFromJson")
}
host, err := client.FindHostByIp(data.HostIp)
host, err := client.FindHostByIp(input.HostIp)
if err != nil {
return nil, err
}
ds, err := host.FindDataStoreById(data.Datastore.PrivateId)
ds, err := host.FindDataStoreById(input.Datastore.PrivateId)
if err != nil {
return nil, errors.Wrap(err, "SHost.FindDataStoreById")
}
format, _ := origin.GetString("format")
if format == "" {
format = "vmdk"
if input.Format == "" {
input.Format = "vmdk"
}
remotePath := fmt.Sprintf("image_cache/%s.%s", data.ImageId, format)
remotePath := fmt.Sprintf("image_cache/%s.%s", input.ImageId, input.Format)
// check if dst image is exist
exists := false
if format == "vmdk" {
if input.Format == "vmdk" {
err = ds.CheckVmdk(ctx, remotePath)
if err != nil {
log.Debugf("ds.CheckVmdk failed: %s", err)
@@ -197,8 +176,8 @@ func (c *SAgentImageCacheManager) prefetchImageCacheByUpload(ctx context.Context
}
}
log.Debugf("exist: %t, remotePath: %s", exists, remotePath)
if !exists || data.IsForce {
if format == "iso" {
if !exists || input.IsForce {
if input.Format == "iso" {
err := ds.ImportISO(ctx, localImgPath, remotePath)
if err != nil {
return nil, errors.Wrapf(err, "SDatastore.ImportISO %s -> %s", localImage, remotePath)
@@ -214,7 +193,7 @@ func (c *SAgentImageCacheManager) prefetchImageCacheByUpload(ctx context.Context
remoteImg := localImage.(*jsonutils.JSONDict)
remoteImg.Add(jsonutils.NewString(remotePath), "path")
_, err = hostutils.RemoteStoragecacheCacheImage(ctx, data.StoragecacheId, data.ImageId, "active", remotePath)
_, err = hostutils.RemoteStoragecacheCacheImage(ctx, input.StoragecacheId, input.ImageId, "active", remotePath)
if err != nil {
return nil, err
}
@@ -222,7 +201,7 @@ func (c *SAgentImageCacheManager) prefetchImageCacheByUpload(ctx context.Context
return remoteImg, nil
}
func (c *SAgentImageCacheManager) perfetchTemplateVMImageCache(ctx context.Context, data *sImageCacheData) (jsonutils.JSONObject, error) {
func (c *SAgentImageCacheManager) perfetchTemplateVMImageCache(ctx context.Context, data *vcenter.ImageCacheInput) (jsonutils.JSONObject, error) {
client, err := esxi.NewESXiClientFromAccessInfo(ctx, &data.Datastore)
if err != nil {
return nil, errors.Wrap(err, "esxi.NewESXiClientFromJson")
@@ -237,29 +216,22 @@ func (c *SAgentImageCacheManager) perfetchTemplateVMImageCache(ctx context.Conte
}
func (c *SAgentImageCacheManager) DeleteImageCache(ctx context.Context, data interface{}) (jsonutils.JSONObject, error) {
dataDict, ok := data.(*jsonutils.JSONDict)
input, ok := data.(*vcenter.ImageCacheInput)
if !ok {
return nil, errors.Wrap(hostutils.ParamsError, "DeleteImageCache data format error")
}
var (
imageID, _ = dataDict.GetString("image_id")
hostIP, _ = dataDict.GetString("host_ip")
dsInfo, _ = dataDict.Get("ds_info")
)
client, _, err := esxi.NewESXiClientFromJson(ctx, dsInfo)
client, _, err := esxi.NewESXiClientFromJson(ctx, jsonutils.Marshal(input.Datastore))
if err != nil {
return nil, err
}
host, err := client.FindHostByIp(hostIP)
host, err := client.FindHostByIp(input.HostIp)
if err != nil {
return nil, err
}
dsID, _ := dsInfo.GetString("private_id")
ds, err := host.FindDataStoreById(dsID)
ds, err := host.FindDataStoreById(input.Datastore.PrivateId)
if err != nil {
return nil, err
}
remotePath := fmt.Sprintf("image_cache/%s.vmdk", imageID)
remotePath := fmt.Sprintf("image_cache/%s.vmdk", input.ImageId)
return nil, ds.Delete(ctx, remotePath)
}
@@ -0,0 +1,171 @@
// 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 storageman
import (
"context"
"os"
"strings"
"time"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"yunion.io/x/cloudmux/pkg/multicloud/esxi/vcenter"
"yunion.io/x/cloudmux/pkg/multicloud/proxmox"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/gotypes"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/hostman/hostutils"
)
type SAgentProxmoxCacheManager struct {
imageCacheManger IImageCacheManger
}
func NewAgentProxmoxCacheManager(manger IImageCacheManger) *SAgentProxmoxCacheManager {
return &SAgentProxmoxCacheManager{manger}
}
func (c *SAgentProxmoxCacheManager) PrefetchProxmoxImageCache(ctx context.Context, data interface{}) (jsonutils.JSONObject, error) {
input, ok := data.(*vcenter.ImageCacheInput)
if !ok {
return nil, errors.Wrap(hostutils.ParamsError, "PrefetchImageCache data format error")
}
lockman.LockRawObject(ctx, input.HostId, input.ImageId)
defer lockman.ReleaseRawObject(ctx, input.HostId, input.ImageId)
if cloudprovider.TImageType(input.ImageType) == cloudprovider.ImageTypeSystem {
return c.prefetchProxmoxTemplateVMImageCache(ctx, input)
}
return c.prefetchProxmoxImageCacheByUpload(ctx, input)
}
func (c *SAgentProxmoxCacheManager) prefetchProxmoxTemplateVMImageCache(ctx context.Context, data *vcenter.ImageCacheInput) (jsonutils.JSONObject, error) {
client, err := proxmox.NewProxmoxClientFromAccessInfo(&data.Datastore)
if err != nil {
return nil, errors.Wrap(err, "esxi.NewESXiClientFromJson")
}
images, err := client.GetTemplateImages("")
if err != nil {
return nil, errors.Wrapf(err, "GetClusterVmResources")
}
for i := range images {
if images[i].GetGlobalId() == data.ImageExternalId {
ret := jsonutils.NewDict()
ret.Set("image_id", jsonutils.NewString(images[i].GetGlobalId()))
return ret, nil
}
}
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "no template image found for %q", data.ImageExternalId)
}
func (c *SAgentProxmoxCacheManager) prefetchProxmoxImageCacheByUpload(ctx context.Context, input *vcenter.ImageCacheInput) (jsonutils.JSONObject, error) {
data := api.CacheImageInput{}
err := jsonutils.Update(&data, input)
if err != nil {
return nil, errors.Wrap(err, "jsonutils.Update")
}
localImage, err := c.imageCacheManger.PrefetchImageCache(ctx, data)
if err != nil {
return nil, errors.Wrapf(err, "PrefetchImageCache %s", input.ImageId)
}
localImgPath, _ := localImage.GetString("path")
client, err := proxmox.NewProxmoxClientFromAccessInfo(&input.Datastore)
if err != nil {
return nil, errors.Wrap(err, "esxi.NewESXiClientFromJson")
}
host, err := client.GetHost(input.HostId)
if err != nil {
return nil, errors.Wrapf(err, "GetHost %s", input.HostId)
}
storages, err := client.GetStoragesByHost(host.Node)
if err != nil {
return nil, errors.Wrapf(err, "GetStorage %s", input.StoragecacheId)
}
if len(input.Format) == 0 {
input.Format = "qcow2"
}
content := "import"
if input.Format == "iso" {
content = "iso"
}
ret := jsonutils.NewDict()
var storage *proxmox.SStorage = nil
maxSize := int64(0)
for i := range storages {
if strings.Contains(storages[i].Content, content) {
image, err := storages[i].SearchImage(content, input.ImageId, input.ImageName, input.Format)
if err != nil {
if errors.Cause(err) != cloudprovider.ErrNotFound {
return nil, errors.Wrapf(err, "SearchImage %s", input.ImageId)
}
if storages[i].MaxDisk-storages[i].Disk > maxSize {
maxSize = storages[i].MaxDisk - storages[i].Disk
storage = &storages[i]
}
continue
}
ret.Set("path", jsonutils.NewString(image.Volid))
ret.Set("name", jsonutils.NewString(image.GetName()))
ret.Set("image_id", jsonutils.NewString(image.GetGlobalId()))
ret.Set("size", jsonutils.NewInt(image.Size))
return ret, nil
}
}
if gotypes.IsNil(storage) {
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "no valid storage found")
}
file, err := os.Open(localImgPath)
if err != nil {
return nil, errors.Wrapf(err, "Open %s", localImgPath)
}
defer file.Close()
err = client.ImportImage(host.Node, storage.Storage, input.ImageId, input.Format, file)
if err != nil {
return nil, errors.Wrapf(err, "ImportImage %s", input.ImageId)
}
var image *proxmox.SImage = nil
for i := 0; i < 3; i++ {
image, err = storage.SearchImage(content, input.ImageId, input.ImageName, input.Format)
if err != nil {
if errors.Cause(err) != cloudprovider.ErrNotFound {
return nil, errors.Wrapf(err, "SearchImage %s", input.ImageId)
}
time.Sleep(time.Second * 10)
continue
}
break
}
if gotypes.IsNil(image) {
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "search image %s %s %s %s after upload", content, input.ImageId, input.ImageName, input.Format)
}
ret.Set("name", jsonutils.NewString(image.GetName()))
ret.Set("image_id", jsonutils.NewString(image.GetGlobalId()))
ret.Set("size", jsonutils.NewInt(image.Size))
return ret, nil
}
+1
View File
@@ -236,6 +236,7 @@ func (self esxiVm) GetName() string {
type SHostDatastore struct {
HostIp string
HostId string
Datastore vcenter.SVCenterAccessInfo
}
+353
View File
@@ -0,0 +1,353 @@
// 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 storageman
import (
"context"
"fmt"
"os"
"strings"
"time"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"yunion.io/x/cloudmux/pkg/multicloud/proxmox"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/agent/iagent"
deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
"yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient"
"yunion.io/x/onecloud/pkg/hostman/hostutils"
"yunion.io/x/onecloud/pkg/hostman/options"
"yunion.io/x/onecloud/pkg/util/logclient"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/seclib"
"yunion.io/x/pkg/utils"
)
type SProxmoxStorage struct {
SLocalStorage
agent iagent.IAgent
}
func NewProxmoxStorage(manager *SStorageManager, agent iagent.IAgent, path string) *SProxmoxStorage {
s := &SProxmoxStorage{SLocalStorage: *NewLocalStorage(manager, path, 0)}
s.agent = agent
s.checkDirC(path)
return s
}
func (ps *SProxmoxStorage) GetDiskById(diskId string) (IDisk, error) {
return NewProxmoxDisk(ps, diskId), nil
}
func (ps *SProxmoxStorage) isExist(dir string) bool {
_, err := os.Stat(dir)
if err != nil {
if os.IsExist(err) {
return true
}
return false
}
return true
}
func (ps *SProxmoxStorage) checkDirC(dir string) error {
if ps.isExist(dir) {
return nil
}
return os.MkdirAll(dir, 0770)
}
func (ps *SProxmoxStorage) AgentDeployGuest(ctx context.Context, data interface{}) (jsonutils.JSONObject, error) {
dataDict := data.(*jsonutils.JSONDict)
log.Debugf("dataDict: %s", dataDict)
deployInfo := SDeployInfo{}
err := dataDict.Unmarshal(&deployInfo)
if err != nil {
return nil, errors.Wrap(err, "Unmarshal DeployInfo")
}
var vm cloudprovider.ICloudVM = nil
var needDeploy = true
switch deployInfo.Action {
case "create":
vm, needDeploy, err = ps.agentCreateGuest(ctx, dataDict)
if err != nil {
return nil, errors.Wrap(err, "agentCreateGuest")
}
case "rebuild":
vm, err = ps.agentRebuildRoot(ctx, dataDict)
if err != nil {
return nil, errors.Wrap(err, "agentRebuildRoot")
}
case "deploy":
vm, err = ps.agentGetVm(ctx, dataDict)
if err != nil {
return nil, errors.Wrap(err, "agentGetVm")
}
default:
return nil, errors.Errorf("invalid action: %s", deployInfo.Action)
}
disks, err := vm.GetIDisks()
if err != nil {
return nil, errors.Wrap(err, "VM.GetIDisks")
}
if len(disks) == 0 {
return nil, errors.Error(fmt.Sprintf("no such disks for vm %s", vm.GetId()))
}
var deploy *deployapi.DeployGuestFsResponse
if needDeploy {
accountPassword, _ := utils.DescryptAESBase64(deployInfo.Datastore.VcenterId, deployInfo.Datastore.Password)
vddkInfo := deployapi.VDDKConInfo{
Host: deployInfo.HostIp,
Port: 22,
User: strings.TrimSuffix(deployInfo.Datastore.Account, "@pam"),
Passwd: accountPassword,
}
guestDesc := deployapi.GuestDesc{}
err = dataDict.Unmarshal(&guestDesc, "desc")
if err != nil {
return nil, errors.Wrapf(err, "%s: unmarshal to guestDesc", hostutils.ParamsError.Error())
}
guestDesc.Hypervisor = api.HYPERVISOR_PROXMOX
key := deployapi.SSHKeys{
PublicKey: deployInfo.PublicKey,
DeletePublicKey: deployInfo.DeletePublicKey,
AdminPublicKey: deployInfo.AdminPublicKey,
ProjectPublicKey: deployInfo.ProjectPublicKey,
}
deployArray := make([]*deployapi.DeployContent, 0)
for _, deploy := range deployInfo.Deploys {
deployArray = append(deployArray, &deployapi.DeployContent{
Path: deploy.Path,
Content: deploy.Content,
Action: deploy.Action,
})
}
isRandomPassword := false
passwd := deployInfo.Password
resetPassword := deployInfo.ResetPassword
if resetPassword && len(passwd) == 0 {
passwd = seclib.RandomPassword(12)
isRandomPassword = true
}
if deployInfo.DeployTelegraf && deployInfo.TelegrafConf == "" {
return nil, errors.Errorf("missing telegraf_conf")
}
deployInformation := deployapi.NewDeployInfo(&key, deployArray, passwd, isRandomPassword, true, false,
options.HostOptions.LinuxDefaultRootUser, options.HostOptions.WindowsDefaultAdminUser,
deployInfo.EnableCloudInit, deployInfo.LoginAccount, deployInfo.DeployTelegraf, deployInfo.TelegrafConf,
deployInfo.Desc.UserData,
)
rootPath := disks[0].GetAccessPath()
log.Debugf("deployInfo: %s", jsonutils.Marshal(deployInfo))
deploy, err = deployclient.GetDeployClient().DeployGuestFs(ctx, &deployapi.DeployParams{
DiskInfo: &deployapi.DiskInfo{
Path: rootPath,
},
GuestDesc: &guestDesc,
DeployInfo: deployInformation,
VddkInfo: &vddkInfo,
})
if err != nil {
model := &esxiVm{}
dataDict.Unmarshal(model, "desc")
logclient.AddSimpleActionLog(model, logclient.ACT_VM_DEPLOY, errors.Wrapf(err, "DeployGuestFs"), hostutils.GetComputeSession(context.Background()).GetToken(), false)
log.Errorf("unable to DeployGuestFs: %v", err)
}
}
array := jsonutils.NewArray()
for i := range disks {
disk := disks[i]
diskId := disk.GetId()
diskDict := jsonutils.NewDict()
diskDict.Add(jsonutils.NewString(diskId), "disk_id")
diskDict.Add(jsonutils.NewString(disk.GetGlobalId()), "uuid")
diskDict.Add(jsonutils.NewInt(int64(disk.GetDiskSizeMB())), "size")
diskDict.Add(jsonutils.NewString(disk.GetAccessPath()), "path")
diskDict.Add(jsonutils.NewString(disk.GetCacheMode()), "cache_mode")
diskDict.Add(jsonutils.NewString(disk.GetDiskType()), "disk_type")
diskDict.Add(jsonutils.NewString(disk.GetDriver()), "driver")
array.Add(diskDict)
}
updated := jsonutils.NewDict()
updated.Add(array, "disks")
updated.Add(jsonutils.NewString(vm.GetGlobalId()), "uuid")
var ret jsonutils.JSONObject
if deploy != nil {
ret = jsonutils.Marshal(deploy)
} else {
ret = jsonutils.NewDict()
}
ret.(*jsonutils.JSONDict).Update(updated)
return ret, nil
}
func (ps *SProxmoxStorage) agentCreateGuest(ctx context.Context, data jsonutils.JSONObject) (cloudprovider.ICloudVM, bool, error) {
hd := SHostDatastore{}
err := data.Unmarshal(&hd)
if err != nil {
return nil, false, errors.Wrap(err, hostutils.ParamsError.Error())
}
needDeploy := true
client, err := proxmox.NewProxmoxClientFromAccessInfo(&hd.Datastore)
if err != nil {
return nil, false, errors.Wrap(err, "proxmox.NewProxmoxClientFromAccessInfo")
}
host, err := client.GetHost(hd.HostId)
if err != nil {
return nil, false, errors.Wrap(err, "GetHost")
}
desc := api.GuestJsonDesc{}
err = data.Unmarshal(&desc, "desc")
if err != nil {
return nil, false, errors.Wrap(err, hostutils.ParamsError.Error())
}
createParam := cloudprovider.SManagedVMCreateConfig{
Name: desc.Name,
Description: desc.Description,
ExternalImageId: desc.ExternalImageId,
Cpu: desc.Cpu,
MemoryMB: desc.Mem,
SysDisk: cloudprovider.SDiskInfo{},
DataDisks: make([]cloudprovider.SDiskInfo, 0),
KeypairName: desc.Keypair,
PublicKey: desc.Pubkey,
}
if len(desc.Nics) > 0 {
createParam.IpAddr = desc.Nics[0].Ip
}
for i := range desc.Disks {
if i == 0 {
createParam.SysDisk = cloudprovider.SDiskInfo{
StorageExternalId: desc.Disks[i].StorageExternalId,
StorageType: desc.Disks[i].StorageType,
SizeGB: desc.Disks[i].Size / 1024,
}
if len(desc.Disks[i].ImageInfo.ImageExternalId) > 0 {
createParam.ExternalImageId = desc.Disks[i].ImageInfo.ImageExternalId
}
} else {
createParam.DataDisks = append(createParam.DataDisks, cloudprovider.SDiskInfo{
StorageExternalId: desc.Disks[i].StorageExternalId,
StorageType: desc.Disks[i].StorageType,
SizeGB: desc.Disks[i].Size / 1024,
})
}
}
if strings.HasSuffix(createParam.ExternalImageId, ".iso") {
needDeploy = false
}
vm, err := host.CreateVM(&createParam)
if err != nil {
return nil, false, errors.Wrap(err, "SHost.CreateVM2")
}
cloudprovider.Wait(3*time.Second, 2*time.Minute, func() (bool, error) {
disks, err := vm.GetIDisks()
if err != nil {
return false, errors.Wrap(err, "VM.GetIDisks")
}
if len(disks) > 0 {
return true, nil
}
log.Debugf("wait for vm %s disks ready", vm.GetId())
return false, nil
})
return vm, needDeploy, nil
}
func (ps *SProxmoxStorage) agentRebuildRoot(ctx context.Context, data jsonutils.JSONObject) (cloudprovider.ICloudVM, error) {
type sRebuildParam struct {
SHostDatastore
GuestExtId string
Desc struct {
Disks []struct {
SizeMb int `json:"size"`
ImageInfo struct {
ImageExternalId string
}
}
}
}
rp := sRebuildParam{}
err := data.Unmarshal(&rp)
if err != nil {
return nil, errors.Wrap(err, hostutils.ParamsError.Error())
}
client, err := proxmox.NewProxmoxClientFromAccessInfo(&rp.Datastore)
if err != nil {
return nil, errors.Wrap(err, "as.getHostAndDatastore")
}
host, err := client.GetHost(rp.HostId)
if err != nil {
return nil, errors.Wrap(err, "GetHost")
}
vm, err := host.GetIVMById(rp.GuestExtId)
if err != nil {
return nil, errors.Wrapf(err, "host.GetIVMById of id '%s'", rp.GuestExtId)
}
sysSizeGB, imageId := 0, ""
if len(rp.Desc.Disks) > 0 {
sysSizeGB = rp.Desc.Disks[0].SizeMb / 1024
imageId = rp.Desc.Disks[0].ImageInfo.ImageExternalId
}
_, err = vm.RebuildRoot(ctx, &cloudprovider.SManagedVMRebuildRootConfig{
ImageId: imageId,
SysSizeGB: sysSizeGB,
})
if err != nil {
return nil, errors.Wrap(err, "VM.RebuildRoot")
}
return vm, nil
}
func (ps *SProxmoxStorage) agentGetVm(ctx context.Context, data jsonutils.JSONObject) (cloudprovider.ICloudVM, error) {
type sRebuildParam struct {
SHostDatastore
GuestExtId string
}
rp := sRebuildParam{}
err := data.Unmarshal(&rp)
if err != nil {
return nil, errors.Wrap(err, hostutils.ParamsError.Error())
}
client, err := proxmox.NewProxmoxClientFromAccessInfo(&rp.Datastore)
if err != nil {
return nil, errors.Wrap(err, "proxmox.NewProxmoxClientFromAccessInfo")
}
host, err := client.GetHost(rp.HostId)
if err != nil {
return nil, errors.Wrap(err, "GetHost")
}
vm, err := host.GetIVMById(rp.GuestExtId)
if err != nil {
return nil, errors.Wrapf(err, "host.GetIVMById of id '%s'", rp.GuestExtId)
}
return vm, nil
}
@@ -282,7 +282,7 @@ func IsNetworkAvailable(
} else {
if !isMatchServerType(n.SNetwork) {
return FailReason{
Reason: fmt.Sprintf("Network %s type %s match", n.Name, n.ServerType),
Reason: fmt.Sprintf("Network %s type %s not match %s", n.Name, n.ServerType, netTypes),
Type: NetworkTypeMatch,
}
}
+2 -2
View File
@@ -2603,7 +2603,7 @@ sigs.k8s.io/structured-merge-diff/v4/value
# sigs.k8s.io/yaml v1.3.0
## explicit; go 1.12
sigs.k8s.io/yaml
# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260416100244-303b3c130516
# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260506084929-0e885263a936
## explicit; go 1.24
yunion.io/x/cloudmux/pkg/apis
yunion.io/x/cloudmux/pkg/apis/billing
@@ -2715,7 +2715,7 @@ yunion.io/x/log/hooks
yunion.io/x/ovsdb/cli_util
yunion.io/x/ovsdb/schema/ovn_nb
yunion.io/x/ovsdb/types
# yunion.io/x/pkg v1.10.4-0.20260224071200-5c269fa62429
# yunion.io/x/pkg v1.10.4-0.20260422030155-01b100134978
## explicit; go 1.18
yunion.io/x/pkg/appctx
yunion.io/x/pkg/errors
+1 -1
View File
@@ -62,7 +62,7 @@ func (self *SStoragecache) IsEmulated() bool {
}
func (self *SStoragecache) GetICustomizedCloudImages() ([]cloudprovider.ICloudImage, error) {
images, err := self.region.GetImages("", ImageOwnerSelf, nil, "", "hvm", nil, "", true)
images, err := self.region.GetImages("", ImageOwnerSelf, nil, "", "hvm", nil, "", false)
if err != nil {
return nil, errors.Wrapf(err, "GetImages")
}
+17
View File
@@ -22,3 +22,20 @@ type SVCenterAccessInfo struct {
Password string
PrivateId string
}
type ImageCacheInput struct {
ImageName string
ImageId string
HostId string
HostIp string
SrcHostIp string
SrcPath string
SrcDatastore SVCenterAccessInfo
Datastore SVCenterAccessInfo
Format string
IsForce bool
StoragecacheId string
ImageType string
ImageExternalId string
StorageCacheHostIp string
}
+7 -5
View File
@@ -64,19 +64,20 @@ type SVmResource struct {
VmId int
Id string
Name string
Size int64
Node string
NodeId string
Status string
Template bool
}
func (self *SRegion) GetClusterAllResources() ([]SClusterResource, error) {
func (self *SProxmoxClient) GetClusterAllResources() ([]SClusterResource, error) {
resources := []SClusterResource{}
err := self.get("/cluster/resources", url.Values{}, &resources)
return resources, err
}
func (self *SRegion) GetClusterResources(resType string) ([]SClusterResource, error) {
func (self *SProxmoxClient) GetClusterResources(resType string) ([]SClusterResource, error) {
resources := []SClusterResource{}
params := url.Values{}
if len(resType) > 0 {
@@ -86,7 +87,7 @@ func (self *SRegion) GetClusterResources(resType string) ([]SClusterResource, er
return resources, err
}
func (self *SRegion) GetClusterNodeResources() (map[string]SNodeResource, error) {
func (self *SProxmoxClient) GetClusterNodeResources() (map[string]SNodeResource, error) {
resources := []SClusterResource{}
nodeResources := map[string]SNodeResource{}
params := url.Values{}
@@ -110,7 +111,7 @@ func (self *SRegion) GetClusterNodeResources() (map[string]SNodeResource, error)
return nodeResources, nil
}
func (self *SRegion) GetClusterVmResources() (map[int]SVmResource, error) {
func (self *SProxmoxClient) GetClusterVmResources() (map[int]SVmResource, error) {
resources := []SClusterResource{}
VmResources := map[int]SVmResource{}
err := self.get("/cluster/resources", url.Values{}, &resources)
@@ -127,6 +128,7 @@ func (self *SRegion) GetClusterVmResources() (map[int]SVmResource, error) {
Node: res.Node,
NodeId: fmt.Sprintf("node/%s", res.Node),
Status: res.Status,
Size: res.Maxdisk,
Template: Itob(res.Template),
}
@@ -137,7 +139,7 @@ func (self *SRegion) GetClusterVmResources() (map[int]SVmResource, error) {
return VmResources, nil
}
func (self *SRegion) GetClusterVmMaxId() int {
func (self *SProxmoxClient) GetClusterVmMaxId() int {
resources := []SClusterResource{}
idxs := []int{}
err := self.get("/cluster/resources", url.Values{}, &resources)
+30 -6
View File
@@ -37,6 +37,7 @@ type SDisk struct {
Size int64 `json:"size"`
Vmid string
VolId string `json:"volid"`
Path string `json:"path"`
Name string `json:"name"`
Parent string `json:"parent"`
Content string `json:"content"`
@@ -69,7 +70,7 @@ func (self *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (clou
}
func (self *SDisk) Refresh() error {
disks, err := self.storage.zone.region.GetDisks(self.storage.Node, self.storage.Storage)
disks, err := self.storage.cli.GetDisks(self.storage.Node, self.storage.Storage)
if err != nil {
return err
}
@@ -135,7 +136,7 @@ func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error)
}
func (self *SDisk) Resize(ctx context.Context, sizeMb int64) error {
vm, err := self.storage.zone.region.GetInstance(self.Vmid)
vm, err := self.storage.cli.GetInstance(self.Vmid)
if err != nil {
return errors.Wrapf(err, "GetInstance")
}
@@ -147,7 +148,7 @@ func (self *SDisk) Resize(ctx context.Context, sizeMb int64) error {
if disk.DiskId != self.VolId {
continue
}
return self.storage.zone.region.ResizeDisk(vm.Node, self.Vmid, disk.Driver, int(sizeMb-int64(self.GetDiskSizeMB()))/1024)
return self.storage.cli.ResizeDisk(vm.Node, self.Vmid, disk.Driver, int(sizeMb-int64(self.GetDiskSizeMB()))/1024)
}
}
return errors.Wrapf(cloudprovider.ErrNotFound, "%s", self.VolId)
@@ -158,7 +159,20 @@ func (self *SDisk) GetTemplateId() string {
}
func (self *SDisk) GetAccessPath() string {
return ""
if self.storage != nil {
disk, err := self.storage.cli.GetDisk(self.storage.Node, self.storage.Storage, self.VolId)
if err == nil && len(disk.Path) > 0 {
return disk.Path
}
}
if len(self.Path) > 0 {
return self.Path
}
infos := strings.SplitN(self.VolId, ":", 2)
if len(infos) == 2 && len(infos[1]) > 0 {
return infos[1]
}
return self.VolId
}
func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) {
@@ -173,7 +187,7 @@ func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
return []cloudprovider.ICloudSnapshot{}, nil
}
func (self *SRegion) GetDisks(node, storageName string) ([]SDisk, error) {
func (self *SProxmoxClient) GetDisks(node, storageName string) ([]SDisk, error) {
vols := []SDisk{}
params := url.Values{}
params.Set("content", "images")
@@ -185,7 +199,17 @@ func (self *SRegion) GetDisks(node, storageName string) ([]SDisk, error) {
return vols, nil
}
func (self *SRegion) ResizeDisk(node string, vmId string, driver string, sizeGb int) error {
func (self *SProxmoxClient) GetDisk(node, storageName, volume string) (*SDisk, error) {
vol := SDisk{}
res := fmt.Sprintf("/nodes/%s/storage/%s/content/%s", node, storageName, url.PathEscape(volume))
err := self.get(res, nil, &vol)
if err != nil {
return nil, err
}
return &vol, nil
}
func (self *SProxmoxClient) ResizeDisk(node string, vmId string, driver string, sizeGb int) error {
body := map[string]interface{}{
"disk": driver,
"size": fmt.Sprintf("+%dG", sizeGb),
+205
View File
@@ -0,0 +1,205 @@
// 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 proxmox
import (
"yunion.io/x/pkg/errors"
api "yunion.io/x/cloudmux/pkg/apis/compute"
"yunion.io/x/cloudmux/pkg/cloudprovider"
)
func (self *SProxmoxClient) GetIVMById(id string) (cloudprovider.ICloudVM, error) {
vm, err := self.GetInstance(id)
if err != nil {
return nil, err
}
return vm, nil
}
func (cli *SProxmoxClient) GetIHosts() ([]cloudprovider.ICloudHost, error) {
hosts, err := cli.GetHosts()
if err != nil {
return nil, err
}
ret := []cloudprovider.ICloudHost{}
for i := range hosts {
hosts[i].cli = cli
ret = append(ret, &hosts[i])
}
return ret, nil
}
func (self *SProxmoxClient) GetIHostById(id string) (cloudprovider.ICloudHost, error) {
host, err := self.GetHost(id)
if err != nil {
return nil, err
}
host.cli = self
return host, nil
}
func (self *SProxmoxClient) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) {
host, err := self.GetHosts()
if err != nil {
return nil, err
}
ret := []cloudprovider.ICloudStoragecache{}
for i := range host {
ret = append(ret, &SStoragecache{
client: self,
Node: host[i].Node,
})
}
return ret, nil
}
func (self *SProxmoxClient) GetIStoragecacheById(id string) (cloudprovider.ICloudStoragecache, error) {
caches, err := self.GetIStoragecaches()
if err != nil {
return nil, cloudprovider.ErrNotSupported
}
for i := range caches {
if caches[i].GetGlobalId() == id {
return caches[i], nil
}
}
return nil, cloudprovider.ErrNotFound
}
func (client *SProxmoxClient) GetIVMs() ([]cloudprovider.ICloudVM, error) {
vms, err := client.GetInstances("")
if err != nil {
return nil, errors.Wrapf(err, "GetInstances")
}
ret := []cloudprovider.ICloudVM{}
for i := range vms {
ret = append(ret, &vms[i])
}
return ret, nil
}
func (client *SProxmoxClient) GetIZones() ([]cloudprovider.ICloudZone, error) {
return nil, errors.ErrNotSupported
}
func (client *SProxmoxClient) GetIZoneById(id string) (cloudprovider.ICloudZone, error) {
return nil, errors.ErrNotSupported
}
func (client *SProxmoxClient) CreateIVpc(opts *cloudprovider.VpcCreateOptions) (cloudprovider.ICloudVpc, error) {
return nil, errors.ErrNotSupported
}
func (client *SProxmoxClient) GetISecurityGroupById(id string) (cloudprovider.ICloudSecurityGroup, error) {
return nil, errors.ErrNotSupported
}
func (client *SProxmoxClient) CreateISecurityGroup(conf *cloudprovider.SecurityGroupCreateInput) (cloudprovider.ICloudSecurityGroup, error) {
return nil, errors.ErrNotSupported
}
func (client *SProxmoxClient) GetCloudEnv() string {
return ""
}
func (client *SProxmoxClient) GetGeographicInfo() cloudprovider.SGeographicInfo {
return cloudprovider.SGeographicInfo{}
}
func (client *SProxmoxClient) Refresh() error {
return nil
}
func (client *SProxmoxClient) IsEmulated() bool {
return true
}
func (client *SProxmoxClient) GetSysTags() map[string]string {
return nil
}
func (client *SProxmoxClient) GetTags() (map[string]string, error) {
return nil, errors.Wrap(errors.ErrNotImplemented, "GetTags")
}
func (client *SProxmoxClient) GetISkus() ([]cloudprovider.ICloudSku, error) {
return nil, cloudprovider.ErrNotSupported
}
func (client *SProxmoxClient) GetIVpcs() ([]cloudprovider.ICloudVpc, error) {
vpc := SVpc{
client: client,
}
return []cloudprovider.ICloudVpc{&vpc}, nil
}
func (client *SProxmoxClient) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) {
return nil, errors.ErrNotSupported
}
func (client *SProxmoxClient) GetUUID() string {
return client.host
}
func (client *SProxmoxClient) GetId() string {
return client.GetUUID()
}
func (client *SProxmoxClient) GetName() string {
return client.cpcfg.Name
}
func (client *SProxmoxClient) GetGlobalId() string {
return client.GetUUID()
}
func (client *SProxmoxClient) GetStatus() string {
return api.CLOUD_REGION_STATUS_INSERVER
}
func (client *SProxmoxClient) GetI18n() cloudprovider.SModelI18nTable {
table := cloudprovider.SModelI18nTable{}
return table
}
func (client *SProxmoxClient) GetProvider() string {
return api.CLOUD_PROVIDER_PROXMOX
}
func (cli *SProxmoxClient) GetIStorages() ([]cloudprovider.ICloudStorage, error) {
storages, err := cli.GetStorages()
if err != nil {
return nil, err
}
ret := []cloudprovider.ICloudStorage{}
for i := range storages {
ret = append(ret, &storages[i])
}
return ret, nil
}
func (cli *SProxmoxClient) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) {
iStorages, err := cli.GetIStorages()
if err != nil {
return nil, err
}
for i := 0; i < len(iStorages); i += 1 {
if iStorages[i].GetGlobalId() == id {
return iStorages[i], nil
}
}
return nil, cloudprovider.ErrNotFound
}
+45 -38
View File
@@ -22,6 +22,7 @@ import (
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/cloudmux/pkg/apis"
@@ -33,7 +34,7 @@ import (
type SHost struct {
multicloud.SHostBase
ProxmoxTags
zone *SZone
cli *SProxmoxClient
Id string
Node string
@@ -122,7 +123,7 @@ func (self *SHost) GetAccessIp() string {
ret := []struct {
Address string
}{}
err := self.zone.region.get(network, url.Values{}, &ret)
err := self.cli.get(network, url.Values{}, &ret)
if err != nil {
return ""
}
@@ -200,21 +201,21 @@ func (self *SHost) GetVersion() string {
func (self *SHost) CreateVM(opts *cloudprovider.SManagedVMCreateConfig) (cloudprovider.ICloudVM, error) {
vmId := self.zone.region.GetClusterVmMaxId()
vmId := self.cli.GetClusterVmMaxId()
if vmId == -1 {
return nil, errors.Errorf("failed to get vm number by %d", vmId)
}
vmId++
storage, err := self.zone.region.GetStorage(opts.SysDisk.StorageExternalId)
storage, err := self.cli.GetStorage(opts.SysDisk.StorageExternalId)
if err != nil {
return nil, errors.Wrapf(err, "GetStorage")
}
body := map[string]interface{}{
"vmid": vmId,
"name": opts.Name,
"ide2": fmt.Sprintf("%s,media=cdrom", opts.ExternalImageId),
"vmid": vmId,
"name": opts.Name,
//"ide2": fmt.Sprintf("%s,media=cdrom", opts.ExternalImageId),
"ostype": "other",
"sockets": 1,
"cores": opts.Cpu,
@@ -225,25 +226,36 @@ func (self *SHost) CreateVM(opts *cloudprovider.SManagedVMCreateConfig) (cloudpr
"description": opts.OsDistribution,
"scsihw": "virtio-scsi-pci",
"net0": "virtio,bridge=vmbr0,firewall=1",
"scsi0": fmt.Sprintf("%s:%d", storage.Storage, opts.SysDisk.SizeGB),
}
sysIndex := 0
if strings.HasSuffix(opts.ExternalImageId, ".iso") { // iso image
body["ide2"] = fmt.Sprintf("%s,media=cdrom", opts.ExternalImageId)
body["scsi0"] = fmt.Sprintf("%s:%d", storage.Storage, opts.SysDisk.SizeGB)
sysIndex = 1
} else if strings.HasSuffix(opts.ExternalImageId, "qemu/") { // template image
//body["ide2"] = fmt.Sprintf("%s,media=disk", opts.ExternalImageId)
} else {
body["scsi0"] = fmt.Sprintf("%s:0,import-from=%s,iothread=on", storage.Storage, opts.ExternalImageId)
}
for i, disk := range opts.DataDisks {
storage, err := self.zone.region.GetStorage(disk.StorageExternalId)
storage, err := self.cli.GetStorage(disk.StorageExternalId)
if err != nil {
return nil, err
}
body[fmt.Sprintf("scsi%d", i+1)] = fmt.Sprintf("%s:%d", storage.Storage, opts.SysDisk.SizeGB)
body[fmt.Sprintf("scsi%d", i+sysIndex)] = fmt.Sprintf("%s:%d", storage.Storage, opts.SysDisk.SizeGB)
}
log.Debugf("opts: %s params: %s", jsonutils.Marshal(opts), jsonutils.Marshal(body))
res := fmt.Sprintf("/nodes/%s/qemu", self.Node)
_, err = self.zone.region.post(res, jsonutils.Marshal(body))
_, err = self.cli.post(res, jsonutils.Marshal(body))
if err != nil {
return nil, err
}
vmIdRet := strconv.Itoa(vmId)
cloudprovider.Wait(time.Second*5, time.Minute, func() (bool, error) {
_, err := self.zone.region.GetInstance(vmIdRet)
_, err := self.cli.GetInstance(vmIdRet)
if err != nil {
if errors.Cause(err) == cloudprovider.ErrNotFound {
return false, nil
@@ -253,7 +265,7 @@ func (self *SHost) CreateVM(opts *cloudprovider.SManagedVMCreateConfig) (cloudpr
return true, nil
})
vm, err := self.zone.region.GetInstance(vmIdRet)
vm, err := self.cli.GetInstance(vmIdRet)
if err != nil {
return nil, err
}
@@ -263,15 +275,22 @@ func (self *SHost) CreateVM(opts *cloudprovider.SManagedVMCreateConfig) (cloudpr
}
func (host *SHost) GetIHostNics() ([]cloudprovider.ICloudHostNetInterface, error) {
wires, err := host.getIWires()
wires, err := host.cli.GetWires(host.Node)
if err != nil {
return nil, errors.Wrap(err, "getIWires")
return nil, err
}
return cloudprovider.GetHostNetifs(host, wires), nil
ret := []cloudprovider.ICloudHostNetInterface{}
for i := range wires {
nic := &SHostNic{
wire: &wires[i],
}
ret = append(ret, nic)
}
return ret, nil
}
func (self *SHost) GetIVMs() ([]cloudprovider.ICloudVM, error) {
vms, err := self.zone.region.GetInstances(self.Id)
vms, err := self.cli.GetInstances(self.Id)
if err != nil {
return nil, errors.Wrapf(err, "GetInstances")
}
@@ -284,7 +303,7 @@ func (self *SHost) GetIVMs() ([]cloudprovider.ICloudVM, error) {
}
func (self *SHost) GetIVMById(id string) (cloudprovider.ICloudVM, error) {
vm, err := self.zone.region.GetInstance(id)
vm, err := self.cli.GetInstance(id)
if err != nil {
return nil, err
}
@@ -296,43 +315,30 @@ func (self *SHost) GetIVMById(id string) (cloudprovider.ICloudVM, error) {
return vm, nil
}
func (self *SHost) getIWires() ([]cloudprovider.ICloudWire, error) {
wires, err := self.zone.region.GetWires()
if err != nil {
return nil, err
}
ret := []cloudprovider.ICloudWire{}
for i := range wires {
wires[i].region = self.zone.region
ret = append(ret, &wires[i])
}
return ret, nil
}
func (self *SHost) GetIStorages() ([]cloudprovider.ICloudStorage, error) {
storages, err := self.zone.region.GetStoragesByHost(self.Node)
storages, err := self.cli.GetStoragesByHost(self.Node)
if err != nil {
return nil, err
}
ret := []cloudprovider.ICloudStorage{}
for i := range storages {
storages[i].zone = self.zone
storages[i].cli = self.cli
ret = append(ret, &storages[i])
}
return ret, nil
}
func (self *SHost) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) {
storage, err := self.zone.region.GetStorage(id)
storage, err := self.cli.GetStorage(id)
if err != nil {
return nil, err
}
storage.zone = self.zone
storage.cli = self.cli
return storage, nil
}
func (self *SRegion) GetHosts() ([]SHost, error) {
func (self *SProxmoxClient) GetHosts() ([]SHost, error) {
hosts := []SHost{}
resources, err := self.GetClusterNodeResources()
if err != nil {
@@ -348,14 +354,15 @@ func (self *SRegion) GetHosts() ([]SHost, error) {
}
host.Id = res.Id
host.Node = res.Node
host.cli = self
hosts = append(hosts, *host)
}
return hosts, nil
}
func (self *SRegion) GetHost(id string) (*SHost, error) {
ret := &SHost{}
func (self *SProxmoxClient) GetHost(id string) (*SHost, error) {
ret := &SHost{cli: self}
nodeName := ""
//"id": "node/nodeNAME",
+89
View File
@@ -0,0 +1,89 @@
// 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 proxmox
import (
"yunion.io/x/cloudmux/pkg/cloudprovider"
"yunion.io/x/cloudmux/pkg/multicloud"
"yunion.io/x/pkg/tristate"
)
type SHostNic struct {
multicloud.SResourceBase
ProxmoxTags
client *SProxmoxClient
wire *SWire
}
func (self *SHostNic) GetId() string {
return self.wire.Id
}
func (self *SHostNic) GetName() string {
return self.wire.GetName()
}
func (self *SHostNic) GetGlobalId() string {
return self.wire.GetGlobalId()
}
func (self *SHostNic) GetDevice() string {
return "device"
}
func (self *SHostNic) GetDriver() string {
return "virtio"
}
func (self *SHostNic) GetBridge() string {
return self.wire.Iface
}
func (self *SHostNic) GetMac() string {
return cloudprovider.HashIdsMac(self.wire.GetGlobalId())
}
func (self *SHostNic) GetVlanId() int {
return self.wire.VlanId
}
func (self *SHostNic) GetIndex() int8 {
return int8(self.wire.Index)
}
func (self *SHostNic) IsLinkUp() tristate.TriState {
if self.wire.Active == 1 {
return tristate.True
}
return tristate.False
}
func (self *SHostNic) GetIpAddr() string {
return self.wire.Address
}
func (self *SHostNic) GetMtu() int32 {
return int32(self.wire.Mtu)
}
func (self *SHostNic) GetNicType() string {
return self.wire.Type
}
func (self *SHostNic) GetIWire() cloudprovider.ICloudWire {
return self.wire
}
+11 -19
View File
@@ -16,8 +16,6 @@ package proxmox
import (
"context"
"fmt"
"net/url"
"yunion.io/x/pkg/util/imagetools"
@@ -33,6 +31,9 @@ type SImage struct {
imageInfo *imagetools.ImageInfo
// for template image
Name string
Volid string
Size int64
Ctime int64
@@ -49,6 +50,9 @@ func (self *SImage) GetId() string {
}
func (self *SImage) GetName() string {
if len(self.Name) > 0 {
return self.Name
}
return self.Volid
}
@@ -82,7 +86,11 @@ func (self *SImage) GetSizeByte() int64 {
func (img *SImage) getNormalizedImageInfo() *imagetools.ImageInfo {
if img.imageInfo == nil {
imgInfo := imagetools.NormalizeImageInfo(img.Volid, "", "", "", "")
name := img.Name
if len(name) == 0 {
name = img.Volid
}
imgInfo := imagetools.NormalizeImageInfo(name, "", "", "", "")
img.imageInfo = &imgInfo
}
return img.imageInfo
@@ -126,19 +134,3 @@ func (self *SImage) GetMinOsDiskSizeGb() int {
func (self *SImage) GetImageFormat() string {
return self.Format
}
func (self *SProxmoxClient) GetImages(node, storageName string) ([]SImage, error) {
images := []SImage{}
params := url.Values{}
params.Set("content", "iso")
path := fmt.Sprintf("/nodes/%s/storage/%s/content", node, storageName)
err := self.get(path, params, &images)
if err != nil {
return nil, err
}
return images, nil
}
func (self *SRegion) GetImages(node, storageName string) ([]SImage, error) {
return self.client.GetImages(node, storageName)
}
+167 -36
View File
@@ -25,6 +25,7 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/fileutils"
"yunion.io/x/pkg/util/osprofile"
"yunion.io/x/pkg/utils"
@@ -93,6 +94,7 @@ type VmBase struct {
Searchdomain string `json:"searchdomain"`
Nameserver string `json:"nameserver"`
Sshkeys string `json:"sshkeys"`
Digest string `json:"digest,omitempty"`
}
type SInstanceDisk struct {
@@ -110,9 +112,10 @@ type SInstance struct {
PowerState string
Node string
VmID int `json:"vmid"`
VmId int `json:"vmid"`
Name string `json:"name"`
Description string `json:"desc"`
Digest string `json:"digest,omitempty"`
Pool string `json:"pool,omitempty"`
Bios string `json:"bios"`
EFIDisk QemuDevice `json:"efidisk,omitempty"`
@@ -139,6 +142,7 @@ type SInstance struct {
QemuDisks map[string][]struct {
Driver string
DiskId string
Size int64
} `json:"disk"`
QemuUnusedDisks QemuDevices `json:"unused_disk"`
QemuVga QemuDevice `json:"vga,omitempty"`
@@ -177,7 +181,7 @@ func (self *SInstance) GetName() string {
}
func (self *SInstance) GetId() string {
return strconv.Itoa(self.VmID)
return strconv.Itoa(self.VmId)
}
func (self *SInstance) GetGlobalId() string {
@@ -185,8 +189,8 @@ func (self *SInstance) GetGlobalId() string {
}
func (self *SInstance) Refresh() error {
id := strconv.Itoa(int(self.VmID))
ins, err := self.host.zone.region.GetInstance(id)
id := strconv.Itoa(int(self.VmId))
ins, err := self.host.cli.GetInstance(id)
if err != nil {
return err
}
@@ -201,14 +205,14 @@ func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error {
func (self *SInstance) CreateDisk(ctx context.Context, opts *cloudprovider.GuestDiskCreateOptions) (string, error) {
body := map[string]string{}
params := url.Values{}
storage, err := self.host.zone.region.GetStorage(opts.StorageId)
storage, err := self.host.cli.GetStorage(opts.StorageId)
if err != nil {
return "", err
}
driver := fmt.Sprintf("scsi%d", opts.Idx)
body[driver] = fmt.Sprintf("%s:%d", storage.Storage, opts.SizeMb/1024)
res := fmt.Sprintf("/nodes/%s/qemu/%d/config", self.Node, self.VmID)
err = self.host.zone.region.put(res, params, jsonutils.Marshal(body))
res := fmt.Sprintf("/nodes/%s/qemu/%d/config", self.Node, self.VmId)
err = self.host.cli.put(res, params, jsonutils.Marshal(body))
if err != nil {
return "", err
}
@@ -224,7 +228,7 @@ func (self *SInstance) CreateDisk(ctx context.Context, opts *cloudprovider.Guest
if disks[i].Driver != driver {
continue
}
volumes, err := self.host.zone.region.GetDisks(self.Node, storage.Storage)
volumes, err := self.host.cli.GetDisks(self.Node, storage.Storage)
if err != nil {
return "", err
}
@@ -240,15 +244,15 @@ func (self *SInstance) CreateDisk(ctx context.Context, opts *cloudprovider.Guest
}
func (self *SInstance) ChangeConfig(ctx context.Context, opts *cloudprovider.SManagedVMChangeConfig) error {
return self.host.zone.region.ChangeConfig(self.VmID, opts.Cpu, opts.MemoryMB)
return self.host.cli.ChangeConfig(self.VmId, opts.Cpu, opts.MemoryMB)
}
func (self *SInstance) DeleteVM(ctx context.Context) error {
return self.host.zone.region.DeleteVM(self.VmID)
return self.host.cli.DeleteVM(self.VmId)
}
func (self *SInstance) DeployVM(ctx context.Context, opts *cloudprovider.SInstanceDeployOptions) error {
return self.host.zone.region.ResetVmPassword(self.VmID, opts.Username, opts.Password)
return self.host.cli.ResetVmPassword(self.VmId, opts.Username, opts.Password)
}
func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error {
@@ -267,7 +271,7 @@ func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error {
}
for _, disk := range disks {
if disk.DiskId == volId {
return self.host.zone.region.DetachDisk(self.Node, self.VmID, disk.Driver)
return self.host.cli.DetachDisk(self.Node, self.VmId, disk.Driver)
}
}
}
@@ -295,7 +299,7 @@ func (self *SInstance) GetHypervisor() string {
}
func (self *SInstance) VMIdExists(vmId int) (bool, error) {
resources, err := self.host.zone.region.GetClusterVmResources()
resources, err := self.host.cli.GetClusterVmResources()
if err != nil {
return false, err
}
@@ -306,7 +310,7 @@ func (self *SInstance) VMIdExists(vmId int) (bool, error) {
func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
ret := []cloudprovider.ICloudDisk{}
ins, err := self.host.zone.region.GetInstance(fmt.Sprintf("%d", self.VmID))
ins, err := self.host.cli.GetInstance(fmt.Sprintf("%d", self.VmId))
if err != nil {
return nil, err
}
@@ -318,11 +322,11 @@ func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
}
diskIds = append(diskIds, disks[i].DiskId)
}
disks, err := self.host.zone.region.GetDisks(self.host.Node, storageName)
disks, err := self.host.cli.GetDisks(self.host.Node, storageName)
if err != nil {
return nil, errors.Wrapf(err, "GetDisks")
}
storages, err := self.host.zone.region.GetStoragesByHost(self.Node)
storages, err := self.host.cli.GetStoragesByHost(self.Node)
if err != nil {
return nil, err
}
@@ -419,13 +423,13 @@ func (self *SInstance) GetProjectId() string {
}
func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
vnc, err := self.host.zone.region.GetVNCInfo(self.Node, self.VmID)
vnc, err := self.host.cli.GetVNCInfo(self.Node, self.VmId)
if err != nil {
return nil, err
}
ret := &cloudprovider.ServerVncOutput{}
ret.Protocol = "vnc"
ret.Host = self.host.zone.region.client.host
ret.Host = self.host.cli.host
ret.Port = int64(vnc.Port)
ret.Password = vnc.Ticket
ret.Hypervisor = api.HYPERVISOR_PROXMOX
@@ -448,8 +452,122 @@ func (self *SInstance) GetVdi() string {
return "vnc"
}
func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
return "", cloudprovider.ErrNotSupported
func (ins *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
sysDriver, storageName, sysDiskId := "", "", ""
for _storageName, disks := range ins.QemuDisks {
for _, disk := range disks {
if disk.Driver == "scsi0" {
sysDriver = disk.Driver
storageName = _storageName
sysDiskId = disk.DiskId
break
}
}
if len(sysDriver) > 0 {
break
}
}
if len(sysDriver) == 0 {
return "", errors.Wrapf(cloudprovider.ErrNotFound, "sys driver not found")
}
storages, err := ins.host.cli.GetStoragesByHost(ins.Node)
if err != nil {
return "", errors.Wrapf(err, "GetStoragesByHost")
}
var storage *SStorage
for i := range storages {
if storages[i].Storage == storageName {
storage = &storages[i]
}
}
if storage == nil {
return "", errors.Wrapf(cloudprovider.ErrNotFound, "storage %s not found", storageName)
}
defer func() {
err = ins.host.cli.ResetBootOrder(ins.Node, ins.VmId, sysDriver)
if err != nil {
log.Errorf("ResetBootOrder %s%d%s %s", ins.Node, ins.VmId, sysDriver, err)
}
}()
err = ins.host.cli.DetachDisk(ins.Node, ins.VmId, sysDriver)
if err != nil {
return "", errors.Wrapf(err, "DetachDisk %s", sysDriver)
}
err = storage.CreateSystemDisk(ins.Node, ins.VmId, sysDriver, desc.ImageId, int(ins.DiskSize))
if err != nil {
ins.host.cli.AttachUnuseDisk(ins.Node, ins.VmId, sysDriver, sysDiskId)
return "", errors.Wrapf(err, "CreateSystemDisk %s", sysDriver)
}
err = ins.host.cli.RemoveUnuseDisk(ins.Node, ins.VmId)
if err != nil {
log.Errorf("RemoveUnuseDisk %s", err)
}
return "", nil
}
func (cli *SProxmoxClient) AttachUnuseDisk(node string, vmId int, driver, diskId string) error {
vm, err := cli.GetInstance(strconv.Itoa(vmId))
if err != nil {
return err
}
res := fmt.Sprintf("/nodes/%s/qemu/%d/config", node, vmId)
err = cli.put(res, url.Values{}, jsonutils.Marshal(map[string]interface{}{
driver: diskId,
"digest": vm.Digest,
}))
if err != nil {
return err
}
return nil
}
func (cli *SProxmoxClient) ResetBootOrder(node string, vmId int, driver string) error {
vm, err := cli.GetInstance(strconv.Itoa(vmId))
if err != nil {
return err
}
res := fmt.Sprintf("/nodes/%s/qemu/%d/config", node, vmId)
err = cli.put(res, url.Values{}, jsonutils.Marshal(map[string]interface{}{
"boot": fmt.Sprintf("order=%s", driver),
"digest": vm.Digest,
}))
if err != nil {
return err
}
return nil
}
func (cli *SProxmoxClient) RemoveUnuseDisk(node string, vmId int) error {
res := fmt.Sprintf("/nodes/%s/qemu/%d/config", node, vmId)
_, err := cli.post(res, jsonutils.Marshal(map[string]interface{}{
"delete": "unused0",
"background_delay": 5,
}))
return err
}
func (storage *SStorage) CreateSystemDisk(node string, vmId int, driver string, imageId string, sizeGb int) error {
vm, err := storage.cli.GetInstance(strconv.Itoa(vmId))
if err != nil {
return err
}
body := map[string]interface{}{
driver: fmt.Sprintf("%s:%d,import-from=%s", storage.Storage, sizeGb, imageId),
"background_delay": 5,
"digest": vm.Digest,
}
res := fmt.Sprintf("/nodes/%s/qemu/%d/config", node, vmId)
_, err = storage.cli.post(res, jsonutils.Marshal(body))
if err != nil {
return err
}
return nil
}
func (self *SInstance) GetSecurityGroupIds() ([]string, error) {
@@ -461,14 +579,14 @@ func (self *SInstance) SetSecurityGroups(secgroupIds []string) error {
}
func (self *SInstance) StartVM(ctx context.Context) error {
return self.host.zone.region.StartVm(self.VmID)
return self.host.cli.StartVm(self.VmId)
}
func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
if self.GetStatus() == api.VM_READY {
return nil
}
return self.host.zone.region.StopVm(self.VmID)
return self.host.cli.StopVm(self.VmId)
}
func (self *SInstance) UpdateUserData(userData string) error {
@@ -489,7 +607,7 @@ func (confMap QemuDevice) readDeviceConfig(confList []string) error {
return nil
}
func (self *SRegion) GetVmAgentNetworkInterfaces(node string, VmId int) (map[string]string, error) {
func (self *SProxmoxClient) GetVmAgentNetworkInterfaces(node string, VmId int) (map[string]string, error) {
intermediates := []Intermediate{}
ipMap := map[string]string{}
res := fmt.Sprintf("/nodes/%s/qemu/%d/agent/network-get-interfaces", node, VmId)
@@ -509,7 +627,7 @@ func (self *SRegion) GetVmAgentNetworkInterfaces(node string, VmId int) (map[str
return ipMap, nil
}
func (self *SRegion) GetVmPowerStatus(node string, VmId int) string {
func (self *SProxmoxClient) GetVmPowerStatus(node string, VmId int) string {
current := map[string]string{}
res := fmt.Sprintf("/nodes/%s/qemu/%d/status/current", node, VmId)
err := self.get(res, url.Values{}, &current)
@@ -525,7 +643,7 @@ func (self *SRegion) GetVmPowerStatus(node string, VmId int) string {
return power
}
func (self *SRegion) GetQemuConfig(node string, VmId int) (*SInstance, error) {
func (self *SProxmoxClient) GetQemuConfig(node string, VmId int) (*SInstance, error) {
res := fmt.Sprintf("/nodes/%s/qemu/%d/config", node, VmId)
vmConfig := map[string]interface{}{}
vmBase := &VmBase{
@@ -557,7 +675,8 @@ func (self *SRegion) GetQemuConfig(node string, VmId int) (*SInstance, error) {
}
config := SInstance{
VmID: int(VmId),
VmId: int(VmId),
Digest: vmBase.Digest,
Name: vmBase.Name,
Description: strings.TrimSpace(vmBase.Description),
Tags: strings.TrimSpace(vmBase.Tags),
@@ -584,6 +703,7 @@ func (self *SRegion) GetQemuConfig(node string, VmId int) (*SInstance, error) {
QemuDisks: map[string][]struct {
Driver string
DiskId string
Size int64
}{},
QemuUnusedDisks: QemuDevices{},
QemuVga: QemuDevice{},
@@ -632,20 +752,28 @@ func (self *SRegion) GetQemuConfig(node string, VmId int) (*SInstance, error) {
continue
}
size := int(0)
if sizeStr, ok := diskConfMap["size"].(string); ok {
size, _ = fileutils.GetSizeGb(sizeStr, 'G', 1024)
}
storageName, _ := ParseSubConf(diskConfMap["volume"].(string), ":")
_, ok := config.QemuDisks[storageName]
if !ok {
config.QemuDisks[storageName] = []struct {
Driver string
DiskId string
Size int64
}{}
}
config.QemuDisks[storageName] = append(config.QemuDisks[storageName], struct {
Driver string
DiskId string
Size int64
}{
Driver: driver,
DiskId: diskConfMap["volume"].(string),
Size: int64(size * 1024 * 1024 * 1024),
})
}
@@ -801,7 +929,7 @@ func (self *SRegion) GetQemuConfig(node string, VmId int) (*SInstance, error) {
}
func (self *SRegion) GetInstances(hostId string) ([]SInstance, error) {
func (self *SProxmoxClient) GetInstances(hostId string) ([]SInstance, error) {
ret := []SInstance{}
resources, err := self.GetClusterVmResources()
if err != nil {
@@ -809,6 +937,9 @@ func (self *SRegion) GetInstances(hostId string) ([]SInstance, error) {
}
for _, res := range resources {
if res.Template {
continue
}
if res.NodeId == hostId || len(hostId) == 0 {
instance, err := self.GetQemuConfig(res.Node, res.VmId)
if err != nil {
@@ -822,7 +953,7 @@ func (self *SRegion) GetInstances(hostId string) ([]SInstance, error) {
return ret, nil
}
func (self *SRegion) GetInstance(id string) (*SInstance, error) {
func (self *SProxmoxClient) GetInstance(id string) (*SInstance, error) {
resources, err := self.GetClusterVmResources()
if err != nil {
return nil, err
@@ -839,7 +970,7 @@ func (self *SRegion) GetInstance(id string) (*SInstance, error) {
return self.GetQemuConfig(nodeName, vmId)
}
func (self *SRegion) StartVm(vmId int) error {
func (self *SProxmoxClient) StartVm(vmId int) error {
resources, err := self.GetClusterVmResources()
if err != nil {
return err
@@ -860,7 +991,7 @@ func (self *SRegion) StartVm(vmId int) error {
}
func (self *SRegion) StopVm(vmId int) error {
func (self *SProxmoxClient) StopVm(vmId int) error {
resources, err := self.GetClusterVmResources()
if err != nil {
return err
@@ -880,7 +1011,7 @@ func (self *SRegion) StopVm(vmId int) error {
return err
}
func (self *SRegion) DetachDisk(node string, vmId int, driver string) error {
func (self *SProxmoxClient) DetachDisk(node string, vmId int, driver string) error {
body := map[string]string{}
params := url.Values{}
body["delete"] = driver
@@ -888,7 +1019,7 @@ func (self *SRegion) DetachDisk(node string, vmId int, driver string) error {
return self.put(res, params, jsonutils.Marshal(body))
}
func (self *SRegion) ChangeConfig(vmId int, cpu int, memMb int) error {
func (self *SProxmoxClient) ChangeConfig(vmId int, cpu int, memMb int) error {
vm, err := self.GetInstance(strconv.Itoa(int(vmId)))
body := map[string]interface{}{}
if err != nil {
@@ -919,7 +1050,7 @@ func (self *SRegion) ChangeConfig(vmId int, cpu int, memMb int) error {
return self.put(res, params, jsonutils.Marshal(body))
}
func (self *SRegion) ResetVmPassword(vmId int, username, password string) error {
func (self *SProxmoxClient) ResetVmPassword(vmId int, username, password string) error {
resources, err := self.GetClusterVmResources()
if err != nil {
return err
@@ -943,7 +1074,7 @@ func (self *SRegion) ResetVmPassword(vmId int, username, password string) error
}
func (self *SRegion) DeleteVM(vmId int) error {
func (self *SProxmoxClient) DeleteVM(vmId int) error {
id := strconv.Itoa(int(vmId))
vm1, err := self.GetInstance(id)
if err != nil {
@@ -956,7 +1087,7 @@ func (self *SRegion) DeleteVM(vmId int) error {
return self.del(res, params, nil)
}
func (self *SRegion) GenVM(name, node string, cores, memMB int) (*SInstance, error) {
func (self *SProxmoxClient) GenVM(name, node string, cores, memMB int) (*SInstance, error) {
vmId := self.GetClusterVmMaxId()
if vmId == -1 {
@@ -1000,7 +1131,7 @@ type InstanceVnc struct {
Cert string
}
func (self *SRegion) GetVNCInfo(node string, vmId int) (*InstanceVnc, error) {
func (self *SProxmoxClient) GetVNCInfo(node string, vmId int) (*InstanceVnc, error) {
res := fmt.Sprintf("/nodes/%s/qemu/%d/vncproxy", node, vmId)
resp, err := self.post(res, map[string]interface{}{
"websocket": "1",
-187
View File
@@ -1,187 +0,0 @@
// 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 proxmox
import (
"fmt"
"net/url"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/netutils"
"yunion.io/x/pkg/util/rbacscope"
api "yunion.io/x/cloudmux/pkg/apis/compute"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"yunion.io/x/cloudmux/pkg/multicloud"
)
type SNetwork struct {
multicloud.SNetworkBase
ProxmoxTags
wire *SWire
Id string
Priority int `json:"priority"`
Families []string `json:"families"`
Method string `json:"method"`
Autostart int `json:"autostart,omitempty"`
Iface string `json:"iface"`
BridgeFd string `json:"bridge_fd,omitempty"`
Method6 string `json:"method6"`
BridgePorts string `json:"bridge_ports,omitempty"`
Type string `json:"type"`
Active int `json:"active"`
BridgeStp string `json:"bridge_stp,omitempty"`
BridgeVids string `json:"bridge_vids,omitempty"`
Netmask string `json:"netmask,omitempty"`
Address string `json:"address,omitempty"`
BridgeVlanAware int `json:"bridge_vlan_aware,omitempty"`
Gateway string `json:"gateway,omitempty"`
Cidr string `json:"cidr,omitempty"`
Exists int `json:"exists,omitempty"`
Options []string `json:"options,omitempty"`
}
func (self *SNetwork) GetName() string {
return self.Iface
}
func (self *SNetwork) GetId() string {
return self.Id
}
func (self *SNetwork) GetGlobalId() string {
return self.GetId()
}
func (self *SNetwork) Refresh() error {
ret, err := self.wire.region.GetNetwork(self.Id)
if err != nil {
return err
}
return jsonutils.Update(self, ret)
}
func (self *SNetwork) Delete() error {
return cloudprovider.ErrNotFound
}
func (self *SNetwork) GetAllocTimeoutSeconds() int {
return 120 // 2 minutes
}
func (self *SNetwork) GetGateway() string {
return self.Gateway
}
func (self *SNetwork) GetIWire() cloudprovider.ICloudWire {
return self.wire
}
func (self *SNetwork) GetIpStart() string {
pref, _ := netutils.NewIPV4Prefix(self.Cidr)
startIp := pref.Address.NetAddr(pref.MaskLen) // 0
startIp = startIp.StepUp() // 1
startIp = startIp.StepUp() // 2
return startIp.String()
}
func (self *SNetwork) GetIpEnd() string {
pref, _ := netutils.NewIPV4Prefix(self.Cidr)
endIp := pref.Address.BroadcastAddr(pref.MaskLen) // 255
endIp = endIp.StepDown() // 254
endIp = endIp.StepDown() // 253
endIp = endIp.StepDown() // 252
return endIp.String()
}
func (self *SNetwork) Contains(_ip string) bool {
start, _ := netutils.NewIPV4Addr(self.GetIpStart())
end, _ := netutils.NewIPV4Addr(self.GetIpEnd())
ip, _ := netutils.NewIPV4Addr(_ip)
return netutils.NewIPV4AddrRange(start, end).Contains(ip)
}
func (self *SNetwork) GetIpMask() int8 {
pref, _ := netutils.NewIPV4Prefix(self.Cidr)
return pref.MaskLen
}
func (self *SNetwork) GetProjectId() string {
return ""
}
func (self *SNetwork) GetPublicScope() rbacscope.TRbacScope {
return rbacscope.ScopeDomain
}
func (self *SNetwork) GetServerType() string {
return api.NETWORK_TYPE_GUEST
}
func (self *SNetwork) GetStatus() string {
return api.NETWORK_STATUS_AVAILABLE
}
func (self *SRegion) GetNetworks() ([]SNetwork, error) {
ret := []SNetwork{}
resources, err := self.GetClusterNodeResources()
if err != nil {
return nil, err
}
for _, res := range resources {
networks := []SNetwork{}
urlNetwork := fmt.Sprintf("/nodes/%s/network", res.Node)
self.get(urlNetwork, url.Values{}, &networks)
for _, iface := range networks {
id := fmt.Sprintf("network/%s/%s", res.Node, iface.Iface)
iface.Id = id
ret = append(ret, iface)
}
}
return ret, nil
}
func (self *SRegion) GetNetwork(id string) (*SNetwork, error) {
ret := &SNetwork{}
//"id": "network/nodeNAME/iface",
splited := strings.Split(id, "/")
nodeName := ""
networkName := ""
if len(splited) == 3 {
nodeName = splited[1]
networkName = splited[2]
} else {
return nil, errors.Errorf("failed to get network by %s", id)
}
res := fmt.Sprintf("/nodes/%s/network/%s", nodeName, networkName)
err := self.get(res, url.Values{}, ret)
ret.Id = id
return ret, err
}
+1 -1
View File
@@ -33,7 +33,7 @@ type SInstanceNic struct {
}
func (self *SInstanceNic) GetId() string {
return fmt.Sprintf("%d/%s", self.ins.VmID, self.NicId)
return fmt.Sprintf("%d/%s", self.ins.VmId, self.NicId)
}
func (self *SInstanceNic) GetIP() string {
+8 -16
View File
@@ -31,7 +31,7 @@ import (
)
type SProxmoxProviderFactory struct {
cloudprovider.SPrivateCloudBaseProviderFactory
cloudprovider.SPremiseBaseProviderFactory
}
func (self *SProxmoxProviderFactory) GetId() string {
@@ -143,10 +143,6 @@ type SProxmoxProvider struct {
client *proxmox.SProxmoxClient
}
func (self *SProxmoxProvider) GetCloudRegionExternalIdPrefix() string {
return self.client.GetCloudRegionExternalIdPrefix()
}
func (self *SProxmoxProvider) GetSysInfo() (jsonutils.JSONObject, error) {
return jsonutils.NewDict(), nil
}
@@ -164,20 +160,11 @@ func (self *SProxmoxProvider) GetAccountId() string {
}
func (self *SProxmoxProvider) GetIRegions() ([]cloudprovider.ICloudRegion, error) {
return self.client.GetIRegions()
return nil, cloudprovider.ErrNotSupported
}
func (self *SProxmoxProvider) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) {
regions, err := self.GetIRegions()
if err != nil {
return nil, err
}
for i := range regions {
if regions[i].GetGlobalId() == id {
return regions[i], nil
}
}
return nil, cloudprovider.ErrNotFound
return nil, cloudprovider.ErrNotSupported
}
func (self *SProxmoxProvider) GetBalance() (*cloudprovider.SBalanceInfo, error) {
@@ -188,6 +175,10 @@ func (self *SProxmoxProvider) GetBalance() (*cloudprovider.SBalanceInfo, error)
}, cloudprovider.ErrNotSupported
}
func (self *SProxmoxProvider) GetOnPremiseIRegion() (cloudprovider.ICloudRegion, error) {
return self.client, nil
}
func (self *SProxmoxProvider) GetIProjects() ([]cloudprovider.ICloudProject, error) {
return []cloudprovider.ICloudProject{}, nil
}
@@ -207,3 +198,4 @@ func (self *SProxmoxProvider) GetObjectCannedAcls(regionId string) []string {
func (self *SProxmoxProvider) GetCapabilities() []string {
return self.client.GetCapabilities()
}
+49 -53
View File
@@ -25,15 +25,17 @@ import (
"net/url"
"path/filepath"
"strings"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/httputils"
"yunion.io/x/pkg/utils"
api "yunion.io/x/cloudmux/pkg/apis/compute"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"yunion.io/x/cloudmux/pkg/multicloud"
"yunion.io/x/cloudmux/pkg/multicloud/esxi/vcenter"
)
const (
@@ -42,6 +44,11 @@ const (
)
type SProxmoxClient struct {
multicloud.SRegion
multicloud.SRegionEipBase
multicloud.SNoObjectStorageRegion
multicloud.SRegionLbBase
*ProxmoxClientConfig
}
@@ -81,6 +88,27 @@ func (self *ProxmoxClientConfig) CloudproviderConfig(cpcfg cloudprovider.Provide
return self
}
func NewProxmoxClientFromAccessInfo(accessInfo *vcenter.SVCenterAccessInfo) (*SProxmoxClient, error) {
if len(accessInfo.VcenterId) > 0 {
tmp, err := utils.DescryptAESBase64(accessInfo.VcenterId, accessInfo.Password)
if err == nil {
accessInfo.Password = tmp
}
}
client, err := NewProxmoxClient(
NewProxmoxClientConfig(
accessInfo.Account,
accessInfo.Password,
accessInfo.Host,
accessInfo.Port,
),
)
if err != nil {
return nil, err
}
return client, nil
}
func NewProxmoxClient(cfg *ProxmoxClientConfig) (*SProxmoxClient, error) {
client := &SProxmoxClient{
@@ -120,22 +148,11 @@ func (self *SProxmoxClient) auth() error {
return nil
}
func (self *SProxmoxClient) GetRegion() *SRegion {
region := &SRegion{client: self}
return region
}
func (self *SProxmoxClient) GetRegions() ([]SRegion, error) {
ret := []SRegion{}
ret = append(ret, SRegion{client: self})
return ret, nil
}
type ProxmoxError struct {
Url string
params jsonutils.JSONObject
Message string
Code int
Params []string
Errors string
Status string
}
@@ -148,7 +165,7 @@ func (ce *ProxmoxError) ParseErrorFromJsonResponse(statusCode int, status string
ce.Status = status
if body != nil {
body.Unmarshal(ce)
log.Errorf("%s status: %s(%d) error: %v", ce.Url, status, statusCode, body.PrettyString())
log.Errorf("%s status: %s(%d) params: %s error: %s", ce.Url, status, statusCode, jsonutils.Marshal(ce.params).String(), body.String())
}
if ce.Code == 0 && statusCode > 0 {
ce.Code = statusCode
@@ -289,7 +306,7 @@ func (cli *SProxmoxClient) __jsonRequest(method httputils.THttpMethod, res strin
}
req.SetHeader(header)
oe := &ProxmoxError{Url: url}
oe := &ProxmoxError{Url: url, params: jsonutils.Marshal(params)}
_, resp, err := client.Send(context.Background(), req, oe, cli.debug)
if err != nil {
return nil, err
@@ -298,9 +315,9 @@ func (cli *SProxmoxClient) __jsonRequest(method httputils.THttpMethod, res strin
return resp, nil
}
func (cli *SProxmoxClient) upload(node, storageName, filename string, reader io.Reader) (*SImage, error) {
if !strings.HasSuffix(filename, ".iso") {
filename = filename + ".iso"
func (cli *SProxmoxClient) upload(node, storageName, filename, format string, reader io.Reader) error {
if !strings.HasSuffix(filename, format) {
filename = filename + "." + format
}
filename = filepath.Base(filename)
client := cli.getDefaultClient()
@@ -309,22 +326,26 @@ func (cli *SProxmoxClient) upload(node, storageName, filename string, reader io.
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
err := writer.WriteField("content", "iso")
content := "iso"
if format != "iso" {
content = "import"
}
err := writer.WriteField("content", content)
if err != nil {
return nil, err
return errors.Wrapf(err, "WriteField")
}
part, err := writer.CreateFormFile("filename", filename)
if err != nil {
return nil, err
return errors.Wrapf(err, "CreateFormFile")
}
_, err = io.Copy(part, reader)
if err != nil {
return nil, errors.Wrapf(err, "io.Copy")
return errors.Wrapf(err, "io.Copy")
}
writer.Close()
req, err := http.NewRequest("POST", url, body)
if err != nil {
return nil, err
return errors.Wrapf(err, "NewRequest")
}
req.Header.Set("Content-Type", writer.FormDataContentType())
@@ -336,40 +357,22 @@ func (cli *SProxmoxClient) upload(node, storageName, filename string, reader io.
resp, err := client.Do(req)
if err != nil {
return nil, err
return errors.Wrapf(err, "Do")
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
return errors.Wrapf(err, "ReadAll")
}
obj, err := jsonutils.Parse(data)
if err != nil {
return nil, err
return errors.Wrapf(err, "Parse")
}
if obj.Contains("errors") {
return nil, fmt.Errorf("%s", string(data))
return errors.Wrapf(fmt.Errorf("%s", string(data)), "errors")
}
now := time.Now()
for now.Sub(time.Now()) < time.Minute*1 {
images, err := cli.GetImages(node, storageName)
if err != nil {
return nil, errors.Wrapf(err, "GetImageStatus")
}
for i := range images {
if strings.HasSuffix(images[i].Volid, filename) {
return &images[i], nil
}
}
time.Sleep(time.Second * 10)
}
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "after upload")
}
func (cli *SProxmoxClient) GetCloudRegionExternalIdPrefix() string {
return fmt.Sprintf("%s/%s", CLOUD_PROVIDER_PROXMOX, cli.cpcfg.Id)
return nil
}
func (self *SProxmoxClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
@@ -385,13 +388,6 @@ func (self *SProxmoxClient) GetAccountId() string {
return self.host
}
func (self *SProxmoxClient) GetIRegions() ([]cloudprovider.ICloudRegion, error) {
ret := []cloudprovider.ICloudRegion{}
region := self.GetRegion()
ret = append(ret, region)
return ret, nil
}
func (self *SProxmoxClient) GetCapabilities() []string {
ret := []string{
cloudprovider.CLOUD_CAPABILITY_COMPUTE,
-229
View File
@@ -1,229 +0,0 @@
// 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 proxmox
import (
"fmt"
"net/url"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
api "yunion.io/x/cloudmux/pkg/apis/compute"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"yunion.io/x/cloudmux/pkg/multicloud"
)
type SRegion struct {
multicloud.SRegion
multicloud.SRegionEipBase
multicloud.SNoObjectStorageRegion
multicloud.SRegionLbBase
client *SProxmoxClient
}
func (self *SRegion) GetClient() *SProxmoxClient {
return self.client
}
func (self *SRegion) GetName() string {
return self.client.cpcfg.Name
}
func (self *SRegion) GetI18n() cloudprovider.SModelI18nTable {
table := cloudprovider.SModelI18nTable{}
table["name"] = cloudprovider.NewSModelI18nEntry(self.GetName()).CN(self.GetName())
return table
}
func (self *SRegion) GetId() string {
return fmt.Sprintf("%s/%s", CLOUD_PROVIDER_PROXMOX, self.client.cpcfg.Id)
}
func (self *SRegion) GetGlobalId() string {
return self.GetId()
}
func (self *SRegion) GetProvider() string {
return CLOUD_PROVIDER_PROXMOX
}
func (self *SRegion) GetCloudEnv() string {
return ""
}
func (self *SRegion) GetGeographicInfo() cloudprovider.SGeographicInfo {
return cloudprovider.SGeographicInfo{}
}
func (self *SRegion) GetStatus() string {
return api.CLOUD_REGION_STATUS_INSERVER
}
func (self *SRegion) Refresh() error {
return nil
}
func (self *SRegion) GetISecurityGroupById(secgroupId string) (cloudprovider.ICloudSecurityGroup, error) {
return nil, cloudprovider.ErrNotSupported
}
func (self *SRegion) CreateISecurityGroup(conf *cloudprovider.SecurityGroupCreateInput) (cloudprovider.ICloudSecurityGroup, error) {
return nil, cloudprovider.ErrNotSupported
}
func (self *SRegion) CreateIVpc(conf *cloudprovider.VpcCreateOptions) (cloudprovider.ICloudVpc, error) {
return nil, cloudprovider.ErrNotSupported
}
func (self *SRegion) GetCapabilities() []string {
return self.client.GetCapabilities()
}
func (self *SRegion) getVpc() *SVpc {
return &SVpc{region: self}
}
func (self *SRegion) GetIVpcs() ([]cloudprovider.ICloudVpc, error) {
vpc := self.getVpc()
return []cloudprovider.ICloudVpc{vpc}, nil
}
func (self *SRegion) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) {
vpcs, err := self.GetIVpcs()
if err != nil {
return nil, err
}
for i := range vpcs {
if vpcs[i].GetGlobalId() == id {
return vpcs[i], nil
}
}
return nil, cloudprovider.ErrNotFound
}
func (self *SRegion) GetIZoneById(id string) (cloudprovider.ICloudZone, error) {
zone, err := self.GetZone()
if err != nil {
return nil, errors.Wrapf(err, "GetZone()")
}
return zone, nil
}
func (self *SRegion) GetIZones() ([]cloudprovider.ICloudZone, error) {
zone, err := self.GetZone()
if err != nil {
return nil, err
}
ret := []cloudprovider.ICloudZone{}
zone.region = self
ret = append(ret, zone)
return ret, nil
}
func (self *SRegion) GetIVMById(id string) (cloudprovider.ICloudVM, error) {
vm, err := self.GetInstance(id)
if err != nil {
return nil, err
}
return vm, nil
}
func (self *SRegion) GetIHostById(id string) (cloudprovider.ICloudHost, error) {
host, err := self.GetHost(id)
if err != nil {
return nil, err
}
zone, err := self.GetZone()
if err != nil {
return nil, err
}
host.zone = zone
return host, nil
}
func (self *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) {
storages, err := self.GetStorages()
if err != nil {
return nil, err
}
ret := []cloudprovider.ICloudStoragecache{}
localMap := map[string]bool{}
isShared := false
for i := range storages {
storage := &SStoragecache{
region: self,
Node: storages[i].Node,
isShare: storages[i].Shared == 1,
}
if !isShared && storage.isShare {
ret = append(ret, storage)
isShared = true
continue
}
_, ok := localMap[storages[i].Node]
if !ok {
localMap[storages[i].Node] = true
ret = append(ret, storage)
}
}
return ret, nil
}
func (self *SRegion) GetIStoragecacheById(id string) (cloudprovider.ICloudStoragecache, error) {
caches, err := self.GetIStoragecaches()
if err != nil {
return nil, cloudprovider.ErrNotSupported
}
for i := range caches {
if caches[i].GetGlobalId() == id {
return caches[i], nil
}
}
return nil, cloudprovider.ErrNotFound
}
func (self *SRegion) get(res string, params url.Values, retVal interface{}) error {
return self.client.get(res, params, retVal)
}
func (self *SRegion) getAgent(res string, params url.Values, retVal interface{}) error {
return self.client.getAgent(res, params, retVal)
}
func (self *SRegion) post(res string, params interface{}) (jsonutils.JSONObject, error) {
return self.client.post(res, params)
}
func (self *SRegion) put(res string, params url.Values, body jsonutils.JSONObject) error {
return self.client.put(res, params, body)
}
func (self *SRegion) del(res string, params url.Values, retVal interface{}) error {
return self.client.del(res, params, retVal)
}
func (region *SRegion) GetIVMs() ([]cloudprovider.ICloudVM, error) {
vms, err := region.GetInstances("")
if err != nil {
return nil, errors.Wrapf(err, "GetInstances")
}
ret := []cloudprovider.ICloudVM{}
for i := range vms {
ret = append(ret, &vms[i])
}
return ret, nil
}
+68 -23
View File
@@ -16,6 +16,8 @@ package proxmox
import (
"fmt"
"io"
"net/url"
"strings"
"yunion.io/x/jsonutils"
@@ -30,7 +32,7 @@ type SStorage struct {
multicloud.SStorageBase
ProxmoxTags
zone *SZone
cli *SProxmoxClient
Storage string `json:"storage"`
Status string
@@ -63,7 +65,7 @@ func (self *SStorage) GetGlobalId() string {
}
func (self *SStorage) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
disks, err := self.zone.region.GetDisks(self.Node, self.Storage)
disks, err := self.cli.GetDisks(self.Node, self.Storage)
if err != nil {
return nil, err
}
@@ -109,9 +111,8 @@ func (self *SStorage) GetIDiskById(id string) (cloudprovider.ICloudDisk, error)
func (self *SStorage) GetIStoragecache() cloudprovider.ICloudStoragecache {
return &SStoragecache{
region: self.zone.region,
Node: self.Node,
isShare: self.Shared == 1,
client: self.cli,
Node: self.Node,
}
}
@@ -131,7 +132,7 @@ func (self *SStorage) GetStatus() string {
}
func (self *SStorage) Refresh() error {
ret, err := self.zone.region.GetStorage(self.GetGlobalId())
ret, err := self.cli.GetStorage(self.GetGlobalId())
if err != nil {
return err
}
@@ -139,7 +140,7 @@ func (self *SStorage) Refresh() error {
}
func (self *SStorage) GetIZone() cloudprovider.ICloudZone {
return self.zone
return nil
}
func (self *SStorage) GetStorageConf() jsonutils.JSONObject {
@@ -154,20 +155,7 @@ func (self *SStorage) IsSysDiskStore() bool {
return true
}
func (self *SRegion) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) {
storage, err := self.GetStorage(id)
if err != nil {
return nil, err
}
zone, err := self.GetZone()
if err != nil {
return nil, err
}
storage.zone = zone
return storage, nil
}
func (self *SRegion) GetStorages() ([]SStorage, error) {
func (self *SProxmoxClient) GetStorages() ([]SStorage, error) {
storages := []SStorage{}
resources, err := self.GetClusterResources("storage")
if err != nil {
@@ -182,6 +170,7 @@ func (self *SRegion) GetStorages() ([]SStorage, error) {
storageMap := map[string]bool{}
ret := []SStorage{}
for i := range storages {
storages[i].cli = self
if storages[i].Shared == 0 {
ret = append(ret, storages[i])
continue
@@ -195,7 +184,7 @@ func (self *SRegion) GetStorages() ([]SStorage, error) {
return ret, nil
}
func (self *SRegion) GetStoragesByHost(node string) ([]SStorage, error) {
func (self *SProxmoxClient) GetStoragesByHost(node string) ([]SStorage, error) {
storages, err := self.GetStorages()
if err != nil {
return nil, err
@@ -209,7 +198,63 @@ func (self *SRegion) GetStoragesByHost(node string) ([]SStorage, error) {
return ret, nil
}
func (self *SRegion) GetStorage(id string) (*SStorage, error) {
func (self *SProxmoxClient) GetImages(host, storage, content string) ([]SImage, error) {
images := []SImage{}
params := url.Values{}
params.Add("content", content)
path := fmt.Sprintf("/nodes/%s/storage/%s/content", host, storage)
err := self.get(path, params, &images)
if err != nil {
return nil, err
}
return images, nil
}
func (self *SStorage) GetImages() ([]SImage, error) {
ret := []SImage{}
for _, content := range []string{"iso", "import"} {
if !strings.Contains(self.Content, content) {
continue
}
images, err := self.cli.GetImages(self.Node, self.Storage, content)
if err != nil {
return nil, err
}
ret = append(ret, images...)
}
return ret, nil
}
func (self *SStorage) SearchImage(content, imageId, imageName, format string) (*SImage, error) {
files, err := self.cli.GetImages(self.Node, self.Storage, content)
if err != nil {
return nil, err
}
name1 := fmt.Sprintf("%s:%s/%s", self.Storage, content, imageName)
if !strings.HasSuffix(name1, format) {
name1 = fmt.Sprintf("%s.%s", name1, format)
}
name2 := fmt.Sprintf("%s:%s/%s", self.Storage, content, imageId)
if !strings.HasSuffix(name2, format) {
name2 = fmt.Sprintf("%s.%s", name2, format)
}
for i := range files {
if files[i].Volid == name1 || files[i].Volid == name2 {
return &files[i], nil
}
}
return nil, cloudprovider.ErrNotFound
}
func (self *SProxmoxClient) ImportImage(host, storage, imagId, format string, reader io.Reader) error {
err := self.upload(host, storage, imagId, format, reader)
if err != nil {
return err
}
return nil
}
func (self *SProxmoxClient) GetStorage(id string) (*SStorage, error) {
storages, err := self.GetStorages()
if err != nil {
return nil, err
+30 -60
View File
@@ -17,68 +17,67 @@ package proxmox
import (
"context"
"fmt"
"io"
"strings"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"yunion.io/x/cloudmux/pkg/multicloud"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/qemuimgfmt"
)
type SStoragecache struct {
multicloud.SResourceBase
ProxmoxTags
region *SRegion
Node string
isShare bool
client *SProxmoxClient
Node string
}
func (self *SStoragecache) GetGlobalId() string {
if self.isShare {
return fmt.Sprintf("%s-share", self.region.GetGlobalId())
}
return fmt.Sprintf("%s-%s", self.region.GetGlobalId(), self.Node)
return fmt.Sprintf("%s-%s", self.client.GetGlobalId(), self.Node)
}
func (self *SStoragecache) GetId() string {
return self.region.GetId()
return self.client.GetId()
}
func (self *SStoragecache) GetName() string {
if self.isShare {
return fmt.Sprintf("%s-share", self.region.GetName())
}
return fmt.Sprintf("%s-%s", self.region.GetName(), self.Node)
return fmt.Sprintf("%s-%s", self.client.GetName(), self.Node)
}
func (self *SStoragecache) GetStatus() string {
return "available"
}
func (self *SStoragecache) GetICloudImages() ([]cloudprovider.ICloudImage, error) {
ret := []cloudprovider.ICloudImage{}
storages, err := self.region.GetStorages()
func (self *SProxmoxClient) GetTemplateImages(node string) ([]SImage, error) {
ret := []SImage{}
vms, err := self.GetClusterVmResources()
if err != nil {
return nil, err
}
for i := range storages {
if !strings.Contains(storages[i].Content, "iso") {
for i := range vms {
if !vms[i].Template {
continue
}
if (self.isShare && storages[i].Shared != 1) || (!self.isShare && storages[i].Node != self.Node) {
if len(node) > 0 && vms[i].Node != node {
continue
}
images, err := self.region.GetImages(storages[i].Node, storages[i].Storage)
if err != nil {
return nil, err
}
for i := range images {
images[i].cache = self
ret = append(ret, &images[i])
image := SImage{
Name: vms[i].Name,
Volid: vms[i].Id,
Size: vms[i].Size,
}
ret = append(ret, image)
}
return ret, nil
}
func (self *SStoragecache) GetICloudImages() ([]cloudprovider.ICloudImage, error) {
ret := []cloudprovider.ICloudImage{}
images, err := self.client.GetTemplateImages(self.Node)
if err != nil {
return nil, err
}
for i := range images {
images[i].cache = self
ret = append(ret, &images[i])
}
return ret, nil
}
@@ -105,34 +104,5 @@ func (self *SStoragecache) GetPath() string {
}
func (self *SStoragecache) UploadImage(ctx context.Context, opts *cloudprovider.SImageCreateOption, callback func(float32)) (string, error) {
reader, sizeByte, err := opts.GetReader(opts.ImageId, string(qemuimgfmt.ISO))
if err != nil {
return "", errors.Wrapf(err, "GetReader")
}
storages, err := self.region.GetStorages()
if err != nil {
return "", err
}
for i := range storages {
if (self.isShare && storages[i].Shared == 0) || (!self.isShare && storages[i].Shared == 1) {
continue
}
if !strings.Contains(storages[i].Content, "iso") {
continue
}
if storages[i].MaxDisk-storages[i].Disk < sizeByte {
continue
}
log.Debugf("upload image %s for %s %s", opts.ImageName, storages[i].Node, storages[i].Storage)
image, err := self.region.UploadImage(storages[i].Node, storages[i].Storage, opts.ImageName, reader)
if err != nil {
return "", errors.Wrapf(err, "UploadImage")
}
return image.GetGlobalId(), nil
}
return "", fmt.Errorf("no valid shared storage for upload")
}
func (self *SRegion) UploadImage(node, storage, filename string, reader io.Reader) (*SImage, error) {
return self.client.upload(node, storage, filename, reader)
return "", cloudprovider.ErrNotSupported
}
+16 -10
View File
@@ -24,15 +24,15 @@ type SVpc struct {
multicloud.SVpc
ProxmoxTags
region *SRegion
client *SProxmoxClient
}
func (self *SVpc) GetName() string {
return "Default"
return self.client.GetName()
}
func (self *SVpc) GetId() string {
return self.region.GetId()
return self.client.GetId()
}
func (self *SVpc) GetGlobalId() string {
@@ -64,24 +64,30 @@ func (self *SVpc) GetISecurityGroups() ([]cloudprovider.ICloudSecurityGroup, err
}
func (self *SVpc) GetIWires() ([]cloudprovider.ICloudWire, error) {
wires, err := self.region.GetWires()
nodes, err := self.client.GetHosts()
if err != nil {
return nil, err
}
ret := []cloudprovider.ICloudWire{}
for i := range wires {
wires[i].region = self.region
ret = append(ret, &wires[i])
for i := range nodes {
wires, err := self.client.GetWires(nodes[i].Node)
if err != nil {
return nil, err
}
for j := range wires {
wires[j].client = self.client
ret = append(ret, &wires[j])
}
}
return ret, nil
}
func (self *SVpc) GetIWireById(id string) (cloudprovider.ICloudWire, error) {
wire, err := self.region.GetWire()
wire, err := self.client.GetWire(id)
if err != nil {
return nil, err
}
wire.region = self.region
wire.client = self.client
return wire, nil
}
@@ -90,7 +96,7 @@ func (self *SVpc) GetIsDefault() bool {
}
func (self *SVpc) GetRegion() cloudprovider.ICloudRegion {
return self.region
return self.client
}
func (self *SVpc) GetStatus() string {
+77 -28
View File
@@ -15,28 +15,59 @@
package proxmox
import (
"fmt"
"net/url"
"strings"
api "yunion.io/x/cloudmux/pkg/apis/compute"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"yunion.io/x/cloudmux/pkg/multicloud"
"yunion.io/x/pkg/errors"
)
type SWire struct {
multicloud.SResourceBase
ProxmoxTags
region *SRegion
client *SProxmoxClient
Id string
Node string
Index int8
Priority int `json:"priority"`
Families []string `json:"families"`
Method string `json:"method"`
Autostart int `json:"autostart,omitempty"`
Iface string `json:"iface"`
BridgeFd string `json:"bridge_fd,omitempty"`
Method6 string `json:"method6"`
BridgePorts string `json:"bridge_ports,omitempty"`
Type string `json:"type"`
Active int `json:"active"`
Mtu int `json:"mtu,omitempty"`
BridgeStp string `json:"bridge_stp,omitempty"`
BridgeVids string `json:"bridge_vids,omitempty"`
Netmask string `json:"netmask,omitempty"`
Address string `json:"address,omitempty"`
BridgeVlanAware int `json:"bridge_vlan_aware,omitempty"`
Gateway string `json:"gateway,omitempty"`
Cidr string `json:"cidr,omitempty"`
Exists int `json:"exists,omitempty"`
Options []string `json:"options,omitempty"`
VlanId int `json:"vlan-id,omitempty"`
}
func (self *SWire) GetId() string {
return self.region.GetId()
return self.Id
}
func (self *SWire) GetName() string {
return "Default"
return fmt.Sprintf("%s-%s", self.Node, self.Iface)
}
func (self *SWire) GetGlobalId() string {
return self.GetId()
return self.Id
}
func (self *SWire) CreateINetwork(opts *cloudprovider.SNetworkCreateOptions) (cloudprovider.ICloudNetwork, error) {
@@ -48,48 +79,66 @@ func (self *SWire) GetBandwidth() int {
}
func (self *SWire) GetINetworks() ([]cloudprovider.ICloudNetwork, error) {
nets, err := self.region.GetNetworks()
if err != nil {
return nil, err
}
ret := []cloudprovider.ICloudNetwork{}
for i := range nets {
nets[i].wire = self
ret = append(ret, &nets[i])
}
return ret, nil
return nil, errors.ErrNotSupported
}
func (self *SWire) GetINetworkById(id string) (cloudprovider.ICloudNetwork, error) {
net, err := self.region.GetNetwork(id)
if err != nil {
return nil, err
}
net.wire = self
return net, nil
return nil, errors.ErrNotSupported
}
func (self *SWire) GetIVpc() cloudprovider.ICloudVpc {
return self.region.getVpc()
return &SVpc{
client: self.client,
}
}
func (self *SWire) GetIZone() cloudprovider.ICloudZone {
zone, _ := self.region.GetZone()
return zone
return nil
}
func (self *SWire) GetStatus() string {
return api.WIRE_STATUS_AVAILABLE
}
func (self *SRegion) GetWires() ([]SWire, error) {
func (self *SProxmoxClient) GetWires(node string) ([]SWire, error) {
ret := []SWire{}
wire := &SWire{region: self}
ret = append(ret, *wire)
urlNetwork := fmt.Sprintf("/nodes/%s/network", node)
self.get(urlNetwork, url.Values{}, &ret)
for i := range ret {
ret[i].Id = fmt.Sprintf("network/%s/%s", node, ret[i].Iface)
ret[i].Node = node
ret[i].client = self
ret[i].Index = int8(i)
}
return ret, nil
}
func (self *SRegion) GetWire() (*SWire, error) {
ret := &SWire{region: self}
func (self *SProxmoxClient) GetWire(id string) (*SWire, error) {
ret := &SWire{}
//"id": "network/nodeNAME/iface",
splited := strings.Split(id, "/")
nodeName := ""
networkName := ""
if len(splited) == 3 {
nodeName = splited[1]
networkName = splited[2]
} else {
return nil, errors.Errorf("failed to get network by %s", id)
}
res := fmt.Sprintf("/nodes/%s/network/%s", nodeName, networkName)
err := self.get(res, url.Values{}, ret)
if err != nil {
return nil, err
}
ret.Id = fmt.Sprintf("network/%s/%s", nodeName, networkName)
ret.Node = nodeName
ret.client = self
return ret, nil
}
+7 -113
View File
@@ -18,25 +18,9 @@ import (
"net/url"
"yunion.io/x/pkg/errors"
api "yunion.io/x/cloudmux/pkg/apis/compute"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"yunion.io/x/cloudmux/pkg/multicloud"
)
type SZone struct {
multicloud.SResourceBase
ProxmoxTags
region *SRegion
Name string
Nodes int
Quorate int
Version int
}
type ClusterStatus struct {
type SCluster struct {
Nodes int `json:"nodes,omitempty"`
Quorate int `json:"quorate,omitempty"`
Version int `json:"version,omitempty"`
@@ -50,108 +34,18 @@ type ClusterStatus struct {
IP string `json:"ip,omitempty"`
}
func (self *SZone) GetId() string {
return self.Name
}
func (self *SZone) GetGlobalId() string {
return self.Name
}
func (self *SZone) GetName() string {
return self.Name
}
func (self *SZone) GetIRegion() cloudprovider.ICloudRegion {
return self.region
}
func (self *SZone) GetStatus() string {
return api.ZONE_ENABLE
}
func (self *SZone) GetI18n() cloudprovider.SModelI18nTable {
table := cloudprovider.SModelI18nTable{}
table["name"] = cloudprovider.NewSModelI18nEntry(self.GetName()).CN(self.GetName())
return table
}
func (self *SZone) GetIHostById(id string) (cloudprovider.ICloudHost, error) {
host, err := self.region.GetHost(id)
if err != nil {
return nil, err
}
host.zone = self
return host, nil
}
func (self *SZone) GetIHosts() ([]cloudprovider.ICloudHost, error) {
hosts, err := self.region.GetHosts()
if err != nil {
return nil, err
}
ret := []cloudprovider.ICloudHost{}
for i := range hosts {
hosts[i].zone = self
ret = append(ret, &hosts[i])
}
return ret, nil
}
func (self *SZone) GetIStorages() ([]cloudprovider.ICloudStorage, error) {
storages, err := self.region.GetStorages()
if err != nil {
return nil, err
}
ret := []cloudprovider.ICloudStorage{}
for i := range storages {
storages[i].zone = self
ret = append(ret, &storages[i])
}
return ret, nil
}
func (self *SZone) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) {
storage, err := self.region.GetStorage(id)
if err != nil {
return nil, errors.Wrapf(err, "GetStorage %s", id)
}
storage.zone = self
return storage, nil
}
func (self *SRegion) GetZone() (*SZone, error) {
ret := &SZone{region: self}
css := []ClusterStatus{}
noneClusterMsg := true
nodeNum := 0
func (self *SProxmoxClient) GetCluster() (*SCluster, error) {
css := []SCluster{}
err := self.get("/cluster/status", url.Values{}, &css)
if err != nil {
return nil, err
}
for _, cs := range css {
if cs.Type == "cluster" {
ret.Name = cs.Name
ret.Nodes = cs.Nodes
ret.Quorate = cs.Quorate
ret.Version = cs.Version
noneClusterMsg = false
break
} else {
nodeNum++
for i := range css {
if css[i].Type == "cluster" {
return &css[i], nil
}
}
if noneClusterMsg == true {
ret.Name = "cluster"
ret.Nodes = nodeNum
}
return ret, nil
return nil, errors.Wrapf(errors.ErrNotFound, "GetCluster")
}
+41 -8
View File
@@ -17,11 +17,14 @@ package compare
import (
"fmt"
"strings"
"sync"
"yunion.io/x/pkg/errors"
)
type SyncResult struct {
mu sync.Mutex `json:"-"`
AddCnt int `json:"add_cnt,omitzero"`
AddErrCnt int `json:"add_err_cnt,omitzero"`
UpdateCnt int `json:"update_cnt,omitzero"`
@@ -31,41 +34,59 @@ type SyncResult struct {
errors []error
}
func (self *SyncResult) Error(msg error) {
func (self *SyncResult) appendError(msg error) {
if self.errors == nil {
self.errors = make([]error, 0)
}
self.errors = append(self.errors, msg)
}
func (self *SyncResult) Error(msg error) {
self.mu.Lock()
defer self.mu.Unlock()
self.appendError(msg)
}
func (self *SyncResult) Add() {
self.mu.Lock()
defer self.mu.Unlock()
self.AddCnt += 1
}
func (self *SyncResult) AddError(msg error) {
self.mu.Lock()
defer self.mu.Unlock()
self.AddErrCnt += 1
self.Error(msg)
self.appendError(msg)
}
func (self *SyncResult) Update() {
self.mu.Lock()
defer self.mu.Unlock()
self.UpdateCnt += 1
}
func (self *SyncResult) UpdateError(msg error) {
self.mu.Lock()
defer self.mu.Unlock()
self.UpdateErrCnt += 1
self.Error(msg)
self.appendError(msg)
}
func (self *SyncResult) Delete() {
self.mu.Lock()
defer self.mu.Unlock()
self.DelCnt += 1
}
func (self *SyncResult) DeleteError(msg error) {
self.mu.Lock()
defer self.mu.Unlock()
self.DelErrCnt += 1
self.Error(msg)
self.appendError(msg)
}
func (self *SyncResult) AllError() error {
func (self *SyncResult) allErrorUnlocked() error {
msgs := make(map[string]bool)
for _, e := range self.errors {
msg := e.Error()
@@ -80,11 +101,21 @@ func (self *SyncResult) AllError() error {
return fmt.Errorf(strings.Join(ret, ";"))
}
func (self *SyncResult) AllError() error {
self.mu.Lock()
defer self.mu.Unlock()
return self.allErrorUnlocked()
}
func (self *SyncResult) IsError() bool {
return self.errors != nil && len(self.errors) > 0
self.mu.Lock()
defer self.mu.Unlock()
return len(self.errors) > 0
}
func (self *SyncResult) IsGenerateError() bool {
self.mu.Lock()
defer self.mu.Unlock()
for _, err := range self.errors {
if e := errors.Cause(err); e != errors.ErrNotImplemented && e != errors.ErrNotSupported && e != errors.ErrNotEmpty {
return true
@@ -94,9 +125,11 @@ func (self *SyncResult) IsGenerateError() bool {
}
func (self *SyncResult) Result() string {
self.mu.Lock()
defer self.mu.Unlock()
msg := fmt.Sprintf("removed %d failed %d updated %d failed %d added %d failed %d", self.DelCnt, self.DelErrCnt, self.UpdateCnt, self.UpdateErrCnt, self.AddCnt, self.AddErrCnt)
if self.IsError() {
msg = fmt.Sprintf("%s;%s", msg, self.AllError())
if len(self.errors) > 0 {
msg = fmt.Sprintf("%s;%s", msg, self.allErrorUnlocked())
}
return msg
}
+17 -10
View File
@@ -156,7 +156,7 @@ func normalizeOsDistribution(osDist string, imageName string) string {
return OS_DIST_KYLIN
} else if strings.Contains(osDist, "uos") {
return OS_DIST_UOS
} else if strings.Contains(osDist, "windows") || regexp.MustCompile(".+win(xp|7|8|10|11|2003|2008|2012|2016|2019|2022)*").MatchString(osDist) {
} else if strings.Contains(osDist, "windows") || regexp.MustCompile("(^|[^a-z0-9])win(xp|7|8|10|11|2003|2008|2012|2016|2019|2022)([^a-z0-9]|$)").MatchString(osDist) {
for _, ver := range []string{"2003", "2008", "2012", "2016", "2019", "2022"} {
if strings.Contains(osDist, ver) {
return OS_DIST_WINDOWS_SERVER
@@ -197,18 +197,18 @@ var imageVersions = map[string][]string{
OS_DIST_OPEN_EULER: {"2.0 SP1", "2.0 SP2", "2.0 SP3", "2.0 SP8", "3.0", "22.03", "23.09"},
OS_DIST_EULER_OS: {"2"},
// 阿里云Linux:补充1代和2/3代版本
OS_DIST_ALIYUN: {"1", "2.1903", "3.2104", "3.2304"},
OS_DIST_ALIYUN: {"1", "2.1903", "2", "3.2104", "3.2304", "3"},
// 阿里云轻量版:补充完整版本
OS_DIST_ALIBABA_CLOUD_LINUX: {"2.1903", "3.2104", "3.2304", "3.2404"},
OS_DIST_ALIBABA_CLOUD_LINUX: {"2.1903", "2", "3.2104", "3.2304", "3.2404", "3"},
// 龙蜥OS:补充7/8系列完整小版本
OS_DIST_ANOLIS: {"7.6", "7.9", "8.2", "8.4", "8.6", "8.8", "9.0", "9.2"},
OS_DIST_ANOLIS: {"7.6", "7.9", "7", "8.2", "8.4", "8.6", "8.8", "8", "9.0", "9.2", "9"},
// Rocky Linux:补充8/9全系列小版本
OS_DIST_ROCKY_LINUX: {"8.5", "8.6", "8.7", "8.8", "8.9", "8.10", "9.0", "9.1", "9.2", "9.3", "9.4", "9.5"},
OS_DIST_ROCKY_LINUX: {"8.5", "8.6", "8.7", "8.8", "8.9", "8.10", "8", "9.0", "9.1", "9.2", "9.3", "9.4", "9.5", "9"},
// Fedora:补充近年主流版本(33到40)
OS_DIST_FEDORA: {"33", "34", "35", "36", "37", "38", "39", "40"},
// AlmaLinux:补充8/9全系列
OS_DIST_ALMA_LINUX: {"8.5", "8.6", "8.7", "8.8", "8.9", "8.10", "9.0", "9.1", "9.2", "9.3", "9.4", "9.5"},
OS_DIST_ALMA_LINUX: {"8.5", "8.6", "8.7", "8.8", "8.9", "8.10", "8", "9.0", "9.1", "9.2", "9.3", "9.4", "9.5", "9"},
// Amazon Linux:补充1/2/2023版本
OS_DIST_AMAZON_LINUX: {"2022", "2023", "1", "2"},
@@ -242,7 +242,7 @@ func normalizeOsVersion(imageName string, osDist string, osVersion string) strin
parts = append(parts, fmt.Sprintf(`(?P<verstr>%s[.\d]*)`, version), "")
regexpStr := strings.Join(parts, `[\s-_]*`)
m := regexp.MustCompile(regexpStr).FindAllStringSubmatch(strings.ToLower(imageName), -1)
if m != nil && len(m) > 0 && len(m[0]) > 1 {
if len(m) > 0 && len(m[0]) > 1 {
verStr := m[0][1]
if strings.HasPrefix(verStr, version) && len(verStr) > len(version) && !strings.HasPrefix(verStr, version+".") {
verStr = version + "." + verStr[len(version):]
@@ -251,11 +251,18 @@ func normalizeOsVersion(imageName string, osDist string, osVersion string) strin
}
}
}
for i := len(versions) - 1; i > 0; i-- {
if strings.Contains(imageName, versions[i]) {
return versions[i]
imageNameLower := strings.ToLower(imageName)
bestMatch := ""
for i := 0; i < len(versions); i++ {
version := strings.ToLower(versions[i])
pattern := fmt.Sprintf(`(^|[^0-9])%s([^0-9]|$)`, regexp.QuoteMeta(version))
if regexp.MustCompile(pattern).MatchString(imageNameLower) && len(versions[i]) > len(bestMatch) {
bestMatch = versions[i]
}
}
if len(bestMatch) > 0 {
return bestMatch
}
}
return ""
}