fix(region): pve create

This commit is contained in:
ioito
2023-06-20 11:48:04 +08:00
parent 888a8afc2e
commit 2b9d01dbf2
50 changed files with 840 additions and 758 deletions
+1 -1
View File
@@ -83,7 +83,7 @@ require (
k8s.io/client-go v0.19.3
k8s.io/cluster-bootstrap v0.19.3
moul.io/http2curl/v2 v2.3.0
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20230627023024-a5d9d998e793
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20230629073725-ad3f1b174b7a
yunion.io/x/executor v0.0.0-20211018100936-39a2cd966656
yunion.io/x/jsonutils v1.0.1-0.20230613121553-0f3b41e2ef19
yunion.io/x/log v1.0.1-0.20230411060016-feb3f46ab361
+2 -2
View File
@@ -1173,8 +1173,8 @@ sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20230627023024-a5d9d998e793 h1:743vsYYy7PoEV0ksv9dWtPfzaoNSLQI5phOuTu3efPQ=
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20230627023024-a5d9d998e793/go.mod h1:crMeQeaNaZefTXfXbQkoj5SStggqkSNVABHtYBFjM3Y=
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20230629073725-ad3f1b174b7a h1:5wnOJ8TiD8njxrOS6UrtSkit7nrf5sEcGRRs0gpqMkg=
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20230629073725-ad3f1b174b7a/go.mod h1:crMeQeaNaZefTXfXbQkoj5SStggqkSNVABHtYBFjM3Y=
yunion.io/x/executor v0.0.0-20211018100936-39a2cd966656 h1:0zlZD5uhZoIHgLVAWCz2aHaYk2ZrNsACCYD7R6EIBII=
yunion.io/x/executor v0.0.0-20211018100936-39a2cd966656/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws=
yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051/go.mod h1:4N0/RVzsYL3kH3WE/H1BjUQdFiWu50JGCFQuuy+Z634=
+1
View File
@@ -35,4 +35,5 @@ const (
APSARA = "apsara"
JDCLOUD = "jdcloud"
CLOUDPODS = "cloudpods"
PROXMOX = "proxmox"
)
@@ -139,6 +139,25 @@ func (self *SManagedVirtualizedGuestDriver) GetJsonDescAtHost(ctx context.Contex
}
}
if len(config.ExternalImageId) == 0 {
image, err := func() (*models.SCachedimage, error) {
cdrom := guest.GetCdrom()
if cdrom == nil {
return nil, fmt.Errorf("empty cdrom")
}
return cdrom.GetImage()
}()
if err != nil {
log.Errorf("get cachdimage error: %v", err)
} else {
config.ExternalImageId = image.ExternalId
config.OsDistribution, _ = image.Info.GetString("properties", "os_distribution")
config.OsVersion, _ = image.Info.GetString("properties", "os_version")
config.OsType, _ = image.Info.GetString("properties", "os_type")
config.ImageType = image.ImageType
}
}
// 避免因同步包年包月实例billing_cycle失败,导致重置虚拟机密码异常
if guest.BillingType == billing_api.BILLING_TYPE_PREPAID && len(guest.BillingCycle) > 0 {
bc, err := billing.ParseBillingCycle(guest.BillingCycle)
@@ -639,6 +658,7 @@ func (self *SManagedVirtualizedGuestDriver) RemoteDeployGuestForCreate(ctx conte
return false, errors.Wrap(err, "iVM.GetIDisks")
}
ret = len(idisks)
log.Debugf("wait vm disk ready, expect %d disks, return %d disks", expect, ret)
if ret >= expect { // 有可能自定义镜像里面也有磁盘,会导致返回的磁盘多于创建时的磁盘
return true, nil
}
+40 -3
View File
@@ -16,9 +16,12 @@ package guestdrivers
import (
"context"
"fmt"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"yunion.io/x/pkg/util/cloudinit"
"yunion.io/x/pkg/util/rbacscope"
"yunion.io/x/pkg/utils"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
@@ -85,6 +88,16 @@ func (self *SProxmoxGuestDriver) GetGuestInitialStateAfterCreate() string {
return api.VM_READY
}
func (self *SProxmoxGuestDriver) ValidateResizeDisk(guest *models.SGuest, disk *models.SDisk, storage *models.SStorage) error {
if !utils.IsInStringArray(guest.Status, []string{api.VM_READY, api.VM_RUNNING}) {
return fmt.Errorf("Cannot resize disk when guest in status %s", guest.Status)
}
if disk.DiskSize/1024%1 > 0 {
return fmt.Errorf("Resize disk size must be an integer multiple of 1G")
}
return nil
}
func (self *SProxmoxGuestDriver) GetDefaultSysDiskBackend() string {
return ""
}
@@ -102,8 +115,21 @@ func (self *SProxmoxGuestDriver) GetMaxSecurityGroupCount() int {
return 0
}
func (self *SProxmoxGuestDriver) RequestGuestHotAddIso(ctx context.Context, guest *models.SGuest, path string, boot bool, task taskman.ITask) error {
task.ScheduleRun(nil)
return nil
}
func (self *SProxmoxGuestDriver) DoGuestCreateDisksTask(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
subtask, err := taskman.TaskManager.NewTask(ctx, "ProxmoxGuestCreateDiskTask", guest, task.GetUserCred(), task.GetParams(), task.GetTaskId(), "", nil)
if err != nil {
return err
}
return subtask.ScheduleRun(nil)
}
func (self *SProxmoxGuestDriver) GetDetachDiskStatus() ([]string, error) {
return []string{api.VM_READY}, nil
return []string{api.VM_READY, api.VM_RUNNING}, nil
}
func (self *SProxmoxGuestDriver) GetAttachDiskStatus() ([]string, error) {
@@ -111,7 +137,7 @@ func (self *SProxmoxGuestDriver) GetAttachDiskStatus() ([]string, error) {
}
func (self *SProxmoxGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
return []string{api.VM_READY}, nil
return []string{api.VM_READY, api.VM_RUNNING}, nil
}
func (self *SProxmoxGuestDriver) GetRebuildRootStatus() ([]string, error) {
@@ -126,12 +152,23 @@ func (self *SProxmoxGuestDriver) ValidateCreateEip(ctx context.Context, userCred
return httperrors.NewInputParameterError("%s not support create eip", self.GetHypervisor())
}
func (self *SProxmoxGuestDriver) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, input *api.ServerCreateInput) (*api.ServerCreateInput, error) {
driver := models.GetDriver(input.Hypervisor)
if len(input.UserData) > 0 && driver != nil && driver.IsNeedInjectPasswordByCloudInit() {
_, err := cloudinit.ParseUserData(input.UserData)
if err != nil {
return nil, err
}
}
return input, nil
}
func (self *SProxmoxGuestDriver) IsSupportEip() bool {
return false
}
func (self *SProxmoxGuestDriver) IsSupportCdrom(guest *models.SGuest) (bool, error) {
return false, nil
return true, nil
}
func (self *SProxmoxGuestDriver) RequestRemoteUpdate(ctx context.Context, guest *models.SGuest, userCred mcclient.TokenCredential, replaceTags bool) error {
+1 -1
View File
@@ -52,7 +52,7 @@ func (self *SBaremetalHostDriver) IsDisableImageCache(host *models.SHost) (bool,
return agent.DisableImageCache, nil
}
func (self *SBaremetalHostDriver) CheckAndSetCacheImage(ctx context.Context, host *models.SHost, storageCache *models.SStoragecache, task taskman.ITask) error {
func (self *SBaremetalHostDriver) CheckAndSetCacheImage(ctx context.Context, userCred mcclient.TokenCredential, host *models.SHost, storageCache *models.SStoragecache, task taskman.ITask) error {
input := api.CacheImageInput{}
task.GetParams().Unmarshal(&input)
_, err := models.CachedimageManager.FetchById(input.ImageId)
+1 -1
View File
@@ -52,7 +52,7 @@ func (self *SESXiHostDriver) ValidateDiskSize(storage *models.SStorage, sizeGb i
return nil
}
func (self *SESXiHostDriver) CheckAndSetCacheImage(ctx context.Context, host *models.SHost, storageCache *models.SStoragecache, task taskman.ITask) error {
func (self *SESXiHostDriver) CheckAndSetCacheImage(ctx context.Context, userCred mcclient.TokenCredential, host *models.SHost, storageCache *models.SStoragecache, task taskman.ITask) error {
params := task.GetParams()
imageId, err := params.GetString("image_id")
if err != nil {
+1 -1
View File
@@ -161,7 +161,7 @@ func (self *SKVMHostDriver) ValidateDiskSize(storage *models.SStorage, sizeGb in
return nil
}
func (self *SKVMHostDriver) CheckAndSetCacheImage(ctx context.Context, host *models.SHost, storageCache *models.SStoragecache, task taskman.ITask) error {
func (self *SKVMHostDriver) CheckAndSetCacheImage(ctx context.Context, userCred mcclient.TokenCredential, host *models.SHost, storageCache *models.SStoragecache, task taskman.ITask) error {
input := api.CacheImageInput{}
task.GetParams().Unmarshal(&input)
+3 -3
View File
@@ -44,7 +44,7 @@ type SManagedVirtualizationHostDriver struct {
SVirtualizationHostDriver
}
func (self *SManagedVirtualizationHostDriver) CheckAndSetCacheImage(ctx context.Context, host *models.SHost, storageCache *models.SStoragecache, task taskman.ITask) error {
func (self *SManagedVirtualizationHostDriver) CheckAndSetCacheImage(ctx context.Context, userCred mcclient.TokenCredential, host *models.SHost, storageCache *models.SStoragecache, task taskman.ITask) error {
input := api.CacheImageInput{}
task.GetParams().Unmarshal(&input)
image := &cloudprovider.SImageCreateOption{}
@@ -61,8 +61,8 @@ func (self *SManagedVirtualizationHostDriver) CheckAndSetCacheImage(ctx context.
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
lockman.LockRawObject(ctx, "cachedimages", fmt.Sprintf("%s-%s", storageCache.Id, image.ImageId))
defer lockman.ReleaseRawObject(ctx, "cachedimages", fmt.Sprintf("%s-%s", storageCache.Id, image.ImageId))
lockman.LockRawObject(ctx, models.CachedimageManager.Keyword(), fmt.Sprintf("%s-%s", storageCache.Id, image.ImageId))
defer lockman.ReleaseRawObject(ctx, models.CachedimageManager.Keyword(), fmt.Sprintf("%s-%s", storageCache.Id, image.ImageId))
log.Debugf("XXX Hold lockman key %p cachedimages %s-%s", ctx, storageCache.Id, image.ImageId)
+163
View File
@@ -15,8 +15,25 @@
package hostdrivers
import (
"context"
"fmt"
"io"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"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"
)
type SProxmoxHostDriver struct {
@@ -43,3 +60,149 @@ func (self *SProxmoxHostDriver) ValidateDiskSize(storage *models.SStorage, sizeG
func (driver *SProxmoxHostDriver) GetStoragecacheQuota(host *models.SHost) int {
return 100
}
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)
}
imageSize := int64(0)
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
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))
log.Debugf("XXX Hold lockman key %p cachedimages %s-%s", ctx, storageCache.Id, input.ImageId)
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, 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
})
return nil
}
+1 -1
View File
@@ -2096,7 +2096,7 @@ func syncPublicCloudProviderInfo(
syncResults.Add(CachedimageManager, result)
msg := result.Result()
log.Infof("syncCloudImages result: %s", msg)
log.Infof("syncCloudImages for %s result: %s", storageCachePairs[i].local.GetName(), msg)
}
}
}
+12
View File
@@ -19,6 +19,7 @@ import (
"time"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
@@ -121,6 +122,17 @@ func (self *SGuestcdrom) ejectIso() bool {
}
}
func (self *SGuestcdrom) GetImage() (*SCachedimage, error) {
if len(self.ImageId) == 0 {
return nil, fmt.Errorf("empty image_id")
}
image, err := CachedimageManager.FetchById(self.ImageId)
if err != nil {
return nil, errors.Wrapf(err, "CachedimageManager.FetchById(%s)", self.ImageId)
}
return image.(*SCachedimage), nil
}
func (self *SGuestcdrom) GetDetails() string {
if len(self.ImageId) > 0 {
if self.Size > 0 {
+1 -1
View File
@@ -29,7 +29,7 @@ type IHostDriver interface {
GetHostType() string
GetHypervisor() string
CheckAndSetCacheImage(ctx context.Context, host *SHost, storagecache *SStoragecache, task taskman.ITask) error
CheckAndSetCacheImage(ctx context.Context, userCred mcclient.TokenCredential, host *SHost, storagecache *SStoragecache, task taskman.ITask) error
RequestUncacheImage(ctx context.Context, host *SHost, storageCache *SStoragecache, task taskman.ITask) error
ValidateUpdateDisk(ctx context.Context, userCred mcclient.TokenCredential, input api.DiskUpdateInput) (api.DiskUpdateInput, error)
+7
View File
@@ -892,6 +892,13 @@ func (self *SHost) GetHoststorages() []SHoststorage {
return hoststorages
}
func (self *SHost) GetStorages() ([]SStorage, error) {
sq := HoststorageManager.Query("storage_id").Equals("host_id", self.Id).SubQuery()
q := StorageManager.Query().In("id", sq)
storages := []SStorage{}
return storages, db.FetchModelObjects(StorageManager, q, &storages)
}
func (self *SHost) GetHoststorageOfId(storageId string) *SHoststorage {
hoststorage := SHoststorage{}
hoststorage.SetModelManager(HoststorageManager, &hoststorage)
+4 -4
View File
@@ -241,11 +241,9 @@ func (self *ManagedGuestCreateDiskTask) OnManagedDiskPrepared(ctx context.Contex
self.SetStageComplete(ctx, nil)
}
/*
func (self *ManagedGuestCreateDiskTask) OnConfigSyncComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
self.SetStageComplete(ctx, nil)
type ProxmoxGuestCreateDiskTask struct {
ESXiGuestCreateDiskTask
}
*/
type ESXiGuestCreateDiskTask struct {
SGuestCreateDiskBaseTask
@@ -294,6 +292,7 @@ func (self *ESXiGuestCreateDiskTask) OnInit(ctx context.Context, obj db.IStandal
SizeMb: disk.DiskSize,
UUID: disk.Id,
Driver: d.Driver,
Idx: d.Index,
StorageId: storage.GetExternalId(),
}
_, err = ivm.CreateDisk(ctx, &opts)
@@ -445,5 +444,6 @@ func init() {
taskman.RegisterTask(KVMGuestCreateDiskTask{})
taskman.RegisterTask(ManagedGuestCreateDiskTask{})
taskman.RegisterTask(ESXiGuestCreateDiskTask{})
taskman.RegisterTask(ProxmoxGuestCreateDiskTask{})
taskman.RegisterTask(NutanixGuestCreateDiskTask{})
}
+2 -1
View File
@@ -18,6 +18,7 @@ import (
"context"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
@@ -78,7 +79,7 @@ func (self *GuestInsertIsoTask) OnIsoPrepareComplete(ctx context.Context, obj db
cdromOrdinal, _ := self.Params.Int("cdrom_ordinal")
size, err := data.Int("size")
if err != nil {
self.SetStageFailed(ctx, jsonutils.NewString(err.Error()))
self.SetStageFailed(ctx, jsonutils.NewString(errors.Wrapf(err, "get image size").Error()))
return
}
name, _ := data.GetString("name")
@@ -80,7 +80,7 @@ func (self *StorageCacheImageTask) OnRelinquishLeastUsedCachedImageComplete(ctx
}
}
err = host.GetHostDriver().CheckAndSetCacheImage(ctx, host, storageCache, self)
err = host.GetHostDriver().CheckAndSetCacheImage(ctx, self.UserCred, host, storageCache, self)
if err != nil {
errData := taskman.Error2TaskData(err)
self.OnImageCacheCompleteFailed(ctx, storageCache, errData)
+2 -2
View File
@@ -44,7 +44,7 @@ type ServerListOptions struct {
Gpu *bool `help:"Show gpu servers"`
Secgroup string `help:"Secgroup ID or Name"`
AdminSecgroup string `help:"AdminSecgroup ID or Name"`
Hypervisor string `help:"Show server of hypervisor" choices:"kvm|esxi|container|baremetal|aliyun|azure|aws|huawei|ucloud|zstack|openstack|google|ctyun|incloudsphere|nutanix|bingocloud|cloudpods|ecloud|jdcloud|remotefile|h3c|hcs|hcso|hcsop"`
Hypervisor string `help:"Show server of hypervisor" choices:"kvm|esxi|container|baremetal|aliyun|azure|aws|huawei|ucloud|zstack|openstack|google|ctyun|incloudsphere|nutanix|bingocloud|cloudpods|ecloud|jdcloud|remotefile|h3c|hcs|hcso|hcsop|proxmox"`
Region string `help:"Show servers in cloudregion"`
WithEip *bool `help:"Show Servers with EIP"`
WithoutEip *bool `help:"Show Servers without EIP"`
@@ -249,7 +249,7 @@ type ServerConfigs struct {
Host string `help:"Preferred host where virtual server should be created" json:"prefer_host"`
BackupHost string `help:"Perfered host where virtual backup server should be created"`
Hypervisor string `help:"Hypervisor type" choices:"kvm|esxi|baremetal|container|aliyun|azure|qcloud|aws|huawei|openstack|ucloud|zstack|google|ctyun|incloudsphere|bingocloud|cloudpods|ecloud|jdcloud|remotefile|h3c|hcs|hcso|hcsop"`
Hypervisor string `help:"Hypervisor type" choices:"kvm|esxi|baremetal|container|aliyun|azure|qcloud|aws|huawei|openstack|ucloud|zstack|google|ctyun|incloudsphere|bingocloud|cloudpods|ecloud|jdcloud|remotefile|h3c|hcs|hcso|hcsop|proxmox"`
ResourceType string `help:"Resource type" choices:"shared|prepaid|dedicated"`
Backup bool `help:"Create server with backup server"`
AutoSwitchToBackupOnHostDown bool `help:"Auto switch to backup server on host down"`
+1 -1
View File
@@ -222,7 +222,7 @@ func handleServerRemoteConsole(ctx context.Context, w http.ResponseWriter, r *ht
case session.ALIYUN, session.QCLOUD, session.OPENSTACK,
session.VMRC, session.ZSTACK, session.CTYUN,
session.HUAWEI, session.HCS, session.APSARA,
session.JDCLOUD, session.CLOUDPODS:
session.JDCLOUD, session.CLOUDPODS, session.PROXMOX:
responsePublicCloudConsole(ctx, info, w)
case session.VNC, session.SPICE, session.WMKS:
handleDataSession(ctx, info, w, url.Values{"password": {info.GetPassword()}}, true)
+1 -1
View File
@@ -58,7 +58,7 @@ func (s *ConnectionServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch protocol {
case session.VNC, session.SPICE:
info := sessionObj.ISessionData.(*session.RemoteConsoleInfo)
if info.Hypervisor == api.HYPERVISOR_OPENSTACK {
if info.Hypervisor == api.HYPERVISOR_OPENSTACK || info.Hypervisor == api.HYPERVISOR_PROXMOX {
srv, err = NewWebsocketProxyServer(sessionObj)
} else {
srv, err = NewWebsockifyServer(sessionObj)
+2 -1
View File
@@ -45,6 +45,7 @@ const (
APSARA = api.APSARA
JDCLOUD = api.JDCLOUD
CLOUDPODS = api.CLOUDPODS
PROXMOX = api.PROXMOX
)
type RemoteConsoleInfo struct {
@@ -123,7 +124,7 @@ func (info *RemoteConsoleInfo) GetConnectParams() (string, error) {
return info.getQcloudURL()
case CLOUDPODS:
return info.getCloudpodsURL()
case OPENSTACK, VMRC, ZSTACK, CTYUN, HUAWEI, HCS, JDCLOUD:
case OPENSTACK, VMRC, ZSTACK, CTYUN, HUAWEI, HCS, JDCLOUD, PROXMOX:
return info.Url, nil
default:
return "", fmt.Errorf("Can't convert protocol %s to connect params", info.Protocol)
+1 -1
View File
@@ -1440,7 +1440,7 @@ sigs.k8s.io/structured-merge-diff/v4/value
# sigs.k8s.io/yaml v1.2.0
## explicit; go 1.12
sigs.k8s.io/yaml
# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20230627023024-a5d9d998e793
# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20230629073725-ad3f1b174b7a
## explicit; go 1.18
yunion.io/x/cloudmux/pkg/apis
yunion.io/x/cloudmux/pkg/apis/billing
+1
View File
@@ -108,6 +108,7 @@ type GuestDiskCreateOptions struct {
SizeMb int
UUID string
Driver string
Idx int
StorageId string
}
-2
View File
@@ -247,8 +247,6 @@ type ICloudStoragecache interface {
GetPath() string
CreateIImage(snapshotId, imageName, osType, imageDesc string) (ICloudImage, error)
UploadImage(ctx context.Context, image *SImageCreateOption, callback func(float32)) (string, error)
}
-16
View File
@@ -211,22 +211,6 @@ func (self *SStoragecache) uploadImage(ctx context.Context, image *cloudprovider
return task.ImageId, nil
}
func (self *SStoragecache) CreateIImage(snapshoutId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
if imageId, err := self.region.createIImage(snapshoutId, imageName, imageDesc); err != nil {
return nil, err
} else if image, err := self.region.GetImage(imageId); err != nil {
return nil, err
} else {
image.storageCache = self
iimage := make([]cloudprovider.ICloudImage, 1)
iimage[0] = image
if err := cloudprovider.WaitStatus(iimage[0], cloudprovider.IMAGE_STATUS_ACTIVE, 15*time.Second, 3600*time.Second); err != nil {
return nil, err
}
return iimage[0], nil
}
}
func (self *SRegion) CheckBucket(bucketName string) (*oss.Bucket, error) {
return self.checkBucket(bucketName)
}
-16
View File
@@ -233,22 +233,6 @@ func (self *SStoragecache) uploadImage(ctx context.Context, image *cloudprovider
return task.ImageId, nil
}
func (self *SStoragecache) CreateIImage(snapshoutId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
if imageId, err := self.region.createIImage(snapshoutId, imageName, imageDesc); err != nil {
return nil, err
} else if image, err := self.region.GetImage(imageId); err != nil {
return nil, err
} else {
image.storageCache = self
iimage := make([]cloudprovider.ICloudImage, 1)
iimage[0] = image
if err := cloudprovider.WaitStatus(iimage[0], cloudprovider.IMAGE_STATUS_ACTIVE, 15*time.Second, 3600*time.Second); err != nil {
return nil, err
}
return iimage[0], nil
}
}
func (self *SRegion) CreateImage(snapshoutId, imageName, imageDesc string) (string, error) {
return self.createIImage(snapshoutId, imageName, imageDesc)
}
-21
View File
@@ -98,27 +98,6 @@ func (self *SStoragecache) GetPath() string {
return ""
}
func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
imageId, err := self.region.createIImage(snapshotId, imageName, imageDesc)
if err != nil {
log.Errorf("createIImage %s %s %s: %s", snapshotId, imageName, imageDesc, err)
return nil, errors.Wrap(err, "createIImage")
}
image, err := self.region.GetImage(imageId)
if err != nil {
log.Errorf("GetImage %s: %s", imageId, err)
return nil, errors.Wrap(err, "GetImage")
}
image.storageCache = self
iimage := make([]cloudprovider.ICloudImage, 1)
iimage[0] = image
//todo : implement me
if err := cloudprovider.WaitStatus(iimage[0], "avaliable", 15*time.Second, 3600*time.Second); err != nil {
return nil, errors.Wrap(err, "WaitStatus.iimage")
}
return iimage[0], nil
}
func (self *SStoragecache) DownloadImage(imageId string, extId string, path string) (jsonutils.JSONObject, error) {
return self.downloadImage(imageId, extId)
}
-9
View File
@@ -184,15 +184,6 @@ func (self *SStoragecache) uploadImage(ctx context.Context, image *cloudprovider
return img.GetGlobalId(), nil
}
func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
if image, err := self.region.CreateImage(snapshotId, imageName, osType, imageDesc); err != nil {
return nil, err
} else {
image.storageCache = self
return image, nil
}
}
func (self *SStoragecache) DownloadImage(imageId string, extId string, path string) (jsonutils.JSONObject, error) {
return self.downloadImage(imageId, extId, path)
}
@@ -55,10 +55,6 @@ func (self *SStoragecache) GetStatus() string {
return "available"
}
func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
return nil, cloudprovider.ErrNotImplemented
}
func (self *SStoragecache) UploadImage(ctx context.Context, image *cloudprovider.SImageCreateOption, callback func(float32)) (string, error) {
return "", cloudprovider.ErrNotImplemented
}
-4
View File
@@ -31,10 +31,6 @@ type SStoragecache struct {
region *SRegion
}
func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
return nil, cloudprovider.ErrNotSupported
}
func (self *SStoragecache) UploadImage(ctx context.Context, image *cloudprovider.SImageCreateOption, update func(progress float32)) (string, error) {
return "", cloudprovider.ErrNotSupported
}
-4
View File
@@ -30,10 +30,6 @@ type SStoragecache struct {
region *SRegion
}
func (sc *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
return nil, cloudprovider.ErrNotSupported
}
func (sc *SStoragecache) UploadImage(ctx context.Context, image *cloudprovider.SImageCreateOption, callback func(progress float32)) (string, error) {
return "", cloudprovider.ErrNotSupported
}
-4
View File
@@ -277,10 +277,6 @@ func (self *SDatastoreImageCache) GetIImageById(extId string) (cloudprovider.ICl
return nil, cloudprovider.ErrNotFound
}
func (self *SDatastoreImageCache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
return nil, cloudprovider.ErrNotImplemented
}
func (self *SDatastoreImageCache) UploadImage(ctx context.Context, image *cloudprovider.SImageCreateOption, callback func(progress float32)) (string, error) {
return "", cloudprovider.ErrNotImplemented
}
-4
View File
@@ -168,10 +168,6 @@ func (cache *SStoragecache) uploadImage(ctx context.Context, image *cloudprovide
return _image.GetGlobalId(), nil
}
func (cache *SStoragecache) CreateIImage(snapshoutId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
return nil, cloudprovider.ErrNotImplemented
}
func (region *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) {
cache := &SStoragecache{region: region}
return []cloudprovider.ICloudStoragecache{cache}, nil
-19
View File
@@ -119,25 +119,6 @@ func (self *SStoragecache) GetPath() string {
return ""
}
// 目前支持使用vhd、zvhd、vmdk、qcow2、raw、zvhd2、vhdx、qcow、vdi或qed格式镜像文件创建私有镜像。
// 快速通道功能可快速完成镜像制作,但镜像文件需转换为raw或zvhd2格式并完成镜像优化。
// https://support.huaweicloud.com/api-ims/zh-cn_topic_0083905788.html
func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
if imageId, err := self.region.createIImage(snapshotId, imageName, imageDesc); err != nil {
return nil, err
} else if image, err := self.region.GetImage(imageId); err != nil {
return nil, err
} else {
image.storageCache = self
iimage := make([]cloudprovider.ICloudImage, 1)
iimage[0] = image
if err := cloudprovider.WaitStatus(iimage[0], "avaliable", 15*time.Second, 3600*time.Second); err != nil {
return nil, err
}
return iimage[0], nil
}
}
func (self *SStoragecache) UploadImage(ctx context.Context, image *cloudprovider.SImageCreateOption, callback func(progress float32)) (string, error) {
return self.uploadImage(ctx, image, callback)
}
-19
View File
@@ -96,25 +96,6 @@ func (self *SStoragecache) GetPath() string {
return ""
}
// 目前支持使用vhd、zvhd、vmdk、qcow2、raw、zvhd2、vhdx、qcow、vdi或qed格式镜像文件创建私有镜像。
// 快速通道功能可快速完成镜像制作,但镜像文件需转换为raw或zvhd2格式并完成镜像优化。
// https://support.huaweicloud.com/api-ims/zh-cn_topic_0083905788.html
func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
if imageId, err := self.region.createIImage(snapshotId, imageName, imageDesc); err != nil {
return nil, err
} else if image, err := self.region.GetImage(imageId); err != nil {
return nil, err
} else {
image.storageCache = self
iimage := make([]cloudprovider.ICloudImage, 1)
iimage[0] = image
if err := cloudprovider.WaitStatus(iimage[0], "avaliable", 15*time.Second, 3600*time.Second); err != nil {
return nil, err
}
return iimage[0], nil
}
}
func (self *SStoragecache) UploadImage(ctx context.Context, image *cloudprovider.SImageCreateOption, callback func(progress float32)) (string, error) {
return self.uploadImage(ctx, image, callback)
}
-4
View File
@@ -91,10 +91,6 @@ func (sc *SStoragecache) GetPath() string {
return ""
}
func (sc *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
return nil, cloudprovider.ErrNotSupported
}
func (sc *SStoragecache) UploadImage(ctx context.Context, image *cloudprovider.SImageCreateOption, callback func(progress float32)) (string, error) {
return "", cloudprovider.ErrNotSupported
}
-4
View File
@@ -81,10 +81,6 @@ func (self *SStoragecache) GetPath() string {
return ""
}
func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
return nil, cloudprovider.ErrNotImplemented
}
func (self *SStoragecache) UploadImage(ctx context.Context, opts *cloudprovider.SImageCreateOption, callback func(float32)) (string, error) {
reader, size, err := opts.GetReader(opts.ImageId, string(qemuimgfmt.QCOW2))
if err != nil {
-4
View File
@@ -141,7 +141,3 @@ func (cache *SStoragecache) uploadImage(ctx context.Context, image *cloudprovide
return img.Id, nil
}
func (cache *SStoragecache) CreateIImage(snapshoutId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
return nil, cloudprovider.ErrNotImplemented
}
+15 -27
View File
@@ -47,11 +47,12 @@ type SClusterResource struct {
}
type SStorageResource struct {
Id string
Path string
Node string
Name string
Shared int
Id string
Path string
Node string
Name string
Shared int
Content string
}
type SNodeResource struct {
@@ -75,35 +76,22 @@ func (self *SRegion) GetClusterAllResources() ([]SClusterResource, error) {
return resources, err
}
func (self *SRegion) GetClusterStoragesResources() (map[string]SStorageResource, error) {
func (self *SRegion) GetClusterResources(resType string) ([]SClusterResource, error) {
resources := []SClusterResource{}
storageResources := map[string]SStorageResource{}
err := self.get("/cluster/resources", url.Values{}, &resources)
if err != nil {
return nil, err
params := url.Values{}
if len(resType) > 0 {
params.Set("type", resType)
}
for _, res := range resources {
if res.Type == "storage" {
sres := SStorageResource{
Id: res.Id,
Path: fmt.Sprintf("/nodes/%s/storage/%s", res.Node, res.Storage),
Node: res.Node,
Name: res.Storage,
Shared: res.Shared,
}
storageResources[sres.Name] = sres
}
}
return storageResources, nil
err := self.get("/cluster/resources", params, &resources)
return resources, err
}
func (self *SRegion) GetClusterNodeResources() (map[string]SNodeResource, error) {
resources := []SClusterResource{}
nodeResources := map[string]SNodeResource{}
err := self.get("/cluster/resources", url.Values{}, &resources)
params := url.Values{}
params.Set("type", "node")
err := self.get("/cluster/resources", params, &resources)
if err != nil {
return nil, err
}
+60 -100
View File
@@ -20,29 +20,22 @@ import (
"net/url"
"strings"
"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"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
)
type SDisk struct {
multicloud.SDisk
ProxmoxTags
region *SRegion
Storage string
Node string
DiskDriver string
DriverIdx int
VmId int
CacheMode string
storage *SStorage
Format string `json:"format"`
Size int64 `json:"size"`
Vmid string
VolId string `json:"volid"`
Name string `json:"name"`
Parent string `json:"parent"`
@@ -50,7 +43,14 @@ type SDisk struct {
}
func (self *SDisk) GetName() string {
return self.Name
if len(self.Name) > 0 {
return self.Name
}
info := strings.Split(self.VolId, ":")
if len(info) == 2 {
return info[1]
}
return self.VolId
}
func (self *SDisk) GetId() string {
@@ -58,19 +58,36 @@ func (self *SDisk) GetId() string {
}
func (self *SDisk) GetGlobalId() string {
return self.GetId()
if self.storage.Shared == 1 {
return fmt.Sprintf("%s|%s", self.storage.Storage, self.VolId)
}
return fmt.Sprintf("%s|%s|%s", self.storage.Node, self.storage.Storage, self.VolId)
}
func (self *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) {
return nil, cloudprovider.ErrNotSupported
}
func (self *SDisk) Refresh() error {
disks, err := self.storage.zone.region.GetDisks(self.storage.Node, self.storage.Storage)
if err != nil {
return err
}
for i := range disks {
disks[i].storage = self.storage
if disks[i].GetGlobalId() == self.GetGlobalId() {
return jsonutils.Update(self, disks[i])
}
}
return errors.Wrapf(cloudprovider.ErrNotFound, self.VolId)
}
func (self *SDisk) Delete(ctx context.Context) error {
return cloudprovider.ErrNotImplemented
}
func (self *SDisk) GetCacheMode() string {
return self.CacheMode
return "none"
}
func (self *SDisk) GetFsFormat() string {
@@ -82,7 +99,7 @@ func (self *SDisk) GetIsNonPersistent() bool {
}
func (self *SDisk) GetDriver() string {
return self.DiskDriver
return "virto"
}
func (self *SDisk) GetDiskType() string {
@@ -118,7 +135,22 @@ func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error)
}
func (self *SDisk) Resize(ctx context.Context, sizeMb int64) error {
return self.region.ResizeDisk(self.VolId, int(sizeMb/1024))
vm, err := self.storage.zone.region.GetInstance(self.Vmid)
if err != nil {
return errors.Wrapf(err, "GetInstance")
}
for _storageName, disks := range vm.QemuDisks {
if _storageName != self.storage.Storage {
continue
}
for _, disk := range disks {
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 errors.Wrapf(cloudprovider.ErrNotFound, self.VolId)
}
func (self *SDisk) GetTemplateId() string {
@@ -130,8 +162,7 @@ func (self *SDisk) GetAccessPath() string {
}
func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) {
DataStoreId := fmt.Sprintf("storage/%s/%s", self.Node, self.Storage)
return self.region.GetStorage(DataStoreId)
return self.storage, nil
}
func (self *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) {
@@ -142,95 +173,24 @@ func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
return []cloudprovider.ICloudSnapshot{}, nil
}
func (self *SRegion) GetDisks(storageId string) ([]SDisk, error) {
func (self *SRegion) GetDisks(node, storageName string) ([]SDisk, error) {
vols := []SDisk{}
disks := []SDisk{}
splited := strings.Split(storageId, "/")
nodeName := ""
storageName := ""
if len(splited) == 3 {
nodeName, storageName = splited[1], splited[2]
}
res := fmt.Sprintf("/nodes/%s/storage/%s/content", nodeName, storageName)
err := self.get(res, url.Values{}, &vols)
params := url.Values{}
params.Set("content", "images")
res := fmt.Sprintf("/nodes/%s/storage/%s/content", node, storageName)
err := self.get(res, params, &vols)
if err != nil {
return nil, err
}
for i := range vols {
_, diskName := ParseSubConf(vols[i].VolId, ":")
if err != nil {
continue
}
vols[i].Storage = storageName
vols[i].Node = nodeName
vols[i].Name = diskName.(string)
disks = append(disks, vols[i])
}
return disks, nil
return vols, nil
}
func (self *SRegion) GetDisk(Id string) (*SDisk, error) {
vols := []SDisk{}
nodeName := ""
storageName, diskName := ParseSubConf(Id, ":")
resources, err := self.GetClusterStoragesResources()
if err != nil {
return nil, err
}
if res, ok := resources[storageName]; !ok {
return nil, errors.Errorf("self.GetDisk")
} else {
nodeName = res.Node
}
res := fmt.Sprintf("/nodes/%s/storage/%s/content", nodeName, storageName)
err = self.get(res, url.Values{}, &vols)
if err != nil {
return nil, errors.Wrapf(err, "self.GetDisk")
}
for _, vol := range vols {
if vol.VolId == Id {
ret := &SDisk{
region: self,
Storage: storageName,
Node: nodeName,
Format: vol.Format,
Size: vol.Size,
VolId: vol.VolId,
Name: diskName.(string),
Parent: vol.Parent,
VmId: vol.VmId,
Content: vol.Content,
}
return ret, nil
}
}
return nil, errors.Errorf("self.GetDisk failed to get disk by %s", Id)
}
func (self *SRegion) ResizeDisk(id string, sizeGb int) error {
disk, err := self.GetDisk(id)
if err != nil {
return errors.Wrapf(err, "GetDisk(%s)", id)
}
// not support unmount disk
if disk.VmId < 1 {
return nil
}
func (self *SRegion) ResizeDisk(node string, vmId string, driver string, sizeGb int) error {
body := map[string]interface{}{
"disk": fmt.Sprintf("%s%d", disk.DiskDriver, disk.DriverIdx),
"size": sizeGb,
"disk": driver,
"size": fmt.Sprintf("+%dG", sizeGb),
}
res := fmt.Sprintf("/nodes/%s/qemu/%d/resize", disk.Node, disk.VmId)
return self.put(res, nil, jsonutils.Marshal(body), nil)
res := fmt.Sprintf("/nodes/%s/qemu/%s/resize", node, vmId)
return self.put(res, nil, jsonutils.Marshal(body))
}
+17 -13
View File
@@ -194,16 +194,18 @@ func (self *SHost) CreateVM(opts *cloudprovider.SManagedVMCreateConfig) (cloudpr
vmId := self.zone.region.GetClusterVmMaxId()
if vmId == -1 {
return nil, errors.Errorf("failed to get vm number by %d", vmId)
} else {
vmId++
}
vmId++
splited := strings.Split(opts.SysDisk.StorageExternalId, "/")
storage := splited[2]
storage, err := self.zone.region.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),
"ostype": "other",
"sockets": 1,
"cores": opts.Cpu,
@@ -213,11 +215,19 @@ func (self *SHost) CreateVM(opts *cloudprovider.SManagedVMCreateConfig) (cloudpr
"memory": opts.MemoryMB,
"description": opts.OsDistribution,
"scsihw": "virtio-scsi-pci",
"scsi0": fmt.Sprintf("%s:%d", storage, opts.SysDisk.SizeGB),
"net0": "virtio,bridge=vmbr0,firewall=1",
"scsi0": fmt.Sprintf("%s:%d", storage.Storage, opts.SysDisk.SizeGB),
}
for i, disk := range opts.DataDisks {
storage, err := self.zone.region.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)
}
res := fmt.Sprintf("/nodes/%s/qemu", self.Node)
_, err := self.zone.region.post(res, jsonutils.Marshal(body))
_, err = self.zone.region.post(res, jsonutils.Marshal(body))
if err != nil {
return nil, err
}
@@ -228,12 +238,6 @@ func (self *SHost) CreateVM(opts *cloudprovider.SManagedVMCreateConfig) (cloudpr
return nil, err
}
for k, _ := range vm.QemuDisks {
_, diskName := ParseSubConf(k, ":")
opts.SysDisk.Name = diskName.(string)
break
}
vm.host = self
return vm, nil
}
@@ -282,7 +286,7 @@ func (self *SHost) GetIWires() ([]cloudprovider.ICloudWire, error) {
}
func (self *SHost) GetIStorages() ([]cloudprovider.ICloudStorage, error) {
storages, err := self.zone.region.GetStoragesByHost(self.Id)
storages, err := self.zone.region.GetStoragesByHost(self.Node)
if err != nil {
return nil, err
}
+20 -125
View File
@@ -18,11 +18,7 @@ import (
"context"
"fmt"
"net/url"
"regexp"
"strconv"
"strings"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/imagetools"
api "yunion.io/x/cloudmux/pkg/apis/compute"
@@ -37,11 +33,11 @@ type SImage struct {
imageInfo *imagetools.ImageInfo
VmId int
Node string
Name string
Format string
SizeGB float64
Volid string
Size int64
Ctime int64
Content string
Format string
}
func (self *SImage) GetMinRamSizeMb() int {
@@ -49,11 +45,11 @@ func (self *SImage) GetMinRamSizeMb() int {
}
func (self *SImage) GetId() string {
return fmt.Sprintf("%d", self.VmId)
return self.Volid
}
func (self *SImage) GetName() string {
return self.Name
return self.Volid
}
func (self *SImage) Delete(ctx context.Context) error {
@@ -81,12 +77,12 @@ func (self *SImage) GetImageType() cloudprovider.TImageType {
}
func (self *SImage) GetSizeByte() int64 {
return int64(self.SizeGB * 1024 * 1024)
return self.Size
}
func (img *SImage) getNormalizedImageInfo() *imagetools.ImageInfo {
if img.imageInfo == nil {
imgInfo := imagetools.NormalizeImageInfo(img.Name, "", "", "", "")
imgInfo := imagetools.NormalizeImageInfo(img.Volid, "", "", "", "")
img.imageInfo = &imgInfo
}
return img.imageInfo
@@ -113,7 +109,7 @@ func (img *SImage) GetOsLang() string {
}
func (img *SImage) GetFullOsName() string {
return img.Name
return ""
}
func (img *SImage) GetBios() cloudprovider.TBiosType {
@@ -128,122 +124,21 @@ func (self *SImage) GetMinOsDiskSizeGb() int {
}
func (self *SImage) GetImageFormat() string {
return "raw"
return self.Format
}
func (self *SRegion) GetImageList() ([]SImage, error) {
ret := []SImage{}
resources, err := self.GetClusterVmResources()
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
}
for _, vm := range resources {
if vm.Template == true {
image := SImage{
VmId: vm.VmId,
Name: vm.Name,
Node: vm.Node,
}
res := fmt.Sprintf("/nodes/%s/qemu/%d/config", image.Node, image.VmId)
vmConfig := map[string]interface{}{}
err := self.get(res, url.Values{}, &vmConfig)
if err != nil {
return nil, err
}
diskNames := []string{}
for k := range vmConfig {
if diskName := regexp.MustCompile(`(virtio|scsi|sata)\d+`).FindStringSubmatch(k); len(diskName) > 0 {
diskNames = append(diskNames, diskName[0])
}
}
for _, diskName := range diskNames {
diskConfStr := vmConfig[diskName].(string)
diskConfMap := ParsePMConf(diskConfStr, "volume")
if diskConfMap["volume"].(string) == "none" {
continue
}
if diskConfMap["media"] != nil {
continue
}
storageName, fileName := ParseSubConf(diskConfMap["volume"].(string), ":")
diskConfMap["storage"] = storageName
diskConfMap["file"] = fileName
// cloud-init disks not always have the size sent by the API, which results in a crash
if diskConfMap["size"] == nil && strings.Contains(fileName.(string), "cloudinit") {
diskConfMap["size"] = "4M" // default cloud-init disk size
}
image.SizeGB += DiskSizeGB(diskConfMap["size"])
}
ret = append(ret, image)
}
}
return ret, nil
return images, nil
}
func (self *SRegion) GetImage(id string) (*SImage, error) {
image := &SImage{}
vmId, err := strconv.Atoi(id)
if err != nil {
return nil, err
}
resources, err := self.GetClusterVmResources()
if err != nil {
return nil, err
}
if resources[vmId].Template == false {
return nil, errors.Errorf("self.GetDisk")
}
image.VmId = resources[vmId].VmId
image.Name = resources[vmId].Name
image.Node = resources[vmId].Node
res := fmt.Sprintf("/nodes/%s/qemu/%d/config", image.Node, image.VmId)
vmConfig := map[string]interface{}{}
err = self.get(res, url.Values{}, &vmConfig)
if err != nil {
return nil, err
}
diskNames := []string{}
for k := range vmConfig {
if diskName := regexp.MustCompile(`(virtio|scsi|sata)\d+`).FindStringSubmatch(k); len(diskName) > 0 {
diskNames = append(diskNames, diskName[0])
}
}
for _, diskName := range diskNames {
diskConfStr := vmConfig[diskName].(string)
diskConfMap := ParsePMConf(diskConfStr, "volume")
if diskConfMap["volume"].(string) == "none" || diskConfMap["media"].(string) == "cdrom" {
continue
}
storageName, fileName := ParseSubConf(diskConfMap["volume"].(string), ":")
diskConfMap["storage"] = storageName
diskConfMap["file"] = fileName
// cloud-init disks not always have the size sent by the API, which results in a crash
if diskConfMap["size"] == nil && strings.Contains(fileName.(string), "cloudinit") {
diskConfMap["size"] = "4M" // default cloud-init disk size
}
var sizeInTerabytes = regexp.MustCompile(`[0-9]+T`)
// Convert to gigabytes if disk size was received in terabytes
matched := sizeInTerabytes.MatchString(diskConfMap["size"].(string))
if matched {
image.SizeGB += DiskSizeGB(diskConfMap["size"])
}
}
return image, nil
func (self *SRegion) GetImages(node, storageName string) ([]SImage, error) {
return self.client.GetImages(node, storageName)
}
+208 -194
View File
@@ -20,7 +20,6 @@ import (
"fmt"
"net/url"
"regexp"
"sort"
"strconv"
"strings"
@@ -28,6 +27,7 @@ import (
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/osprofile"
"yunion.io/x/pkg/utils"
api "yunion.io/x/cloudmux/pkg/apis/compute"
"yunion.io/x/cloudmux/pkg/cloudprovider"
@@ -95,6 +95,11 @@ type VmBase struct {
Sshkeys string `json:"sshkeys"`
}
type SInstanceDisk struct {
Storage string
VolId string
}
type SInstance struct {
multicloud.SInstanceBase
ProxmoxTags
@@ -105,34 +110,37 @@ type SInstance struct {
PowerState string
Node string
VmID int `json:"vmid"`
Name string `json:"name"`
Description string `json:"desc"`
Pool string `json:"pool,omitempty"`
Bios string `json:"bios"`
EFIDisk QemuDevice `json:"efidisk,omitempty"`
Machine string `json:"machine,omitempty"`
Onboot bool `json:"onboot"`
Startup string `json:"startup,omitempty"`
Tablet bool `json:"tablet"`
Agent int `json:"agent"`
Memory int `json:"memory"`
Balloon int `json:"balloon"`
QemuOs string `json:"ostype"`
QemuCores int `json:"cores"`
QemuSockets int `json:"sockets"`
QemuVcpus int `json:"vcpus"`
QemuCpu string `json:"cpu"`
QemuNuma bool `json:"numa"`
QemuKVM bool `json:"kvm"`
Hotplug string `json:"hotplug"`
QemuIso string `json:"iso"`
QemuPxe bool `json:"pxe"`
FullClone *int `json:"fullclone"`
Boot string `json:"boot"`
BootDisk string `json:"bootdisk,omitempty"`
Scsihw string `json:"scsihw,omitempty"`
QemuDisks QemuDevices `json:"disk"`
VmID int `json:"vmid"`
Name string `json:"name"`
Description string `json:"desc"`
Pool string `json:"pool,omitempty"`
Bios string `json:"bios"`
EFIDisk QemuDevice `json:"efidisk,omitempty"`
Machine string `json:"machine,omitempty"`
Onboot bool `json:"onboot"`
Startup string `json:"startup,omitempty"`
Tablet bool `json:"tablet"`
Agent int `json:"agent"`
Memory int `json:"memory"`
Balloon int `json:"balloon"`
QemuOs string `json:"ostype"`
QemuCores int `json:"cores"`
QemuSockets int `json:"sockets"`
QemuVcpus int `json:"vcpus"`
QemuCpu string `json:"cpu"`
QemuNuma bool `json:"numa"`
QemuKVM bool `json:"kvm"`
Hotplug string `json:"hotplug"`
QemuIso string `json:"iso"`
QemuPxe bool `json:"pxe"`
FullClone *int `json:"fullclone"`
Boot string `json:"boot"`
BootDisk string `json:"bootdisk,omitempty"`
Scsihw string `json:"scsihw,omitempty"`
QemuDisks map[string][]struct {
Driver string
DiskId string
} `json:"disk"`
QemuUnusedDisks QemuDevices `json:"unused_disk"`
QemuVga QemuDevice `json:"vga,omitempty"`
QemuSerials QemuDevices `json:"serial,omitempty"`
@@ -180,6 +188,7 @@ func (self *SInstance) Refresh() error {
if err != nil {
return err
}
self.QemuDisks = ins.QemuDisks
return jsonutils.Update(self, ins)
}
@@ -188,11 +197,48 @@ func (self *SInstance) AssignSecurityGroup(id string) error {
}
func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error {
return self.host.zone.region.AttachDisk(self.VmID, diskId)
return cloudprovider.ErrNotSupported
}
func (self *SInstance) CreateDisk(ctx context.Context, opts *cloudprovider.GuestDiskCreateOptions) (string, error) {
return "", cloudprovider.ErrNotSupported
body := map[string]string{}
params := url.Values{}
storage, err := self.host.zone.region.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))
if err != nil {
return "", err
}
err = self.Refresh()
if err != nil {
return "", err
}
for storageName, disks := range self.QemuDisks {
if storageName != storage.Storage {
continue
}
for i := range disks {
if disks[i].Driver != driver {
continue
}
volumes, err := self.host.zone.region.GetDisks(self.Node, storage.Storage)
if err != nil {
return "", err
}
for i := range volumes {
volumes[i].storage = storage
if strings.HasSuffix(volumes[i].GetGlobalId(), "|"+volumes[i].VolId) {
return volumes[i].GetGlobalId(), nil
}
}
}
}
return "", errors.Wrapf(cloudprovider.ErrNotFound, "after created")
}
func (self *SInstance) ChangeConfig(ctx context.Context, opts *cloudprovider.SManagedVMChangeConfig) error {
@@ -208,7 +254,26 @@ func (self *SInstance) DeployVM(ctx context.Context, name string, username strin
}
func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error {
return self.host.zone.region.DetachDisk(self.VmID, diskId)
diskInfo := strings.Split(diskId, "|")
storageName, volId := "", ""
if len(diskInfo) == 2 {
storageName, volId = diskInfo[0], diskInfo[1]
} else if len(diskInfo) == 3 {
storageName, volId = diskInfo[1], diskInfo[2]
} else {
return fmt.Errorf("invalid diskId %s", diskId)
}
for _storageName, disks := range self.QemuDisks {
if storageName != _storageName {
continue
}
for _, disk := range disks {
if disk.DiskId == volId {
return self.host.zone.region.DetachDisk(self.Node, self.VmID, disk.Driver)
}
}
}
return nil
}
func (self *SInstance) GetBios() cloudprovider.TBiosType {
@@ -243,32 +308,42 @@ func (self *SInstance) VMIdExists(vmId int) (bool, error) {
func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
ret := []cloudprovider.ICloudDisk{}
id := self.VmID
exist, err := self.VMIdExists(self.VmID)
ins, err := self.host.zone.region.GetInstance(fmt.Sprintf("%d", self.VmID))
if err != nil {
return nil, err
}
if exist == false {
return nil, nil
}
for k, v := range self.QemuDisks {
disk, err := self.host.zone.region.GetDisk(k)
for storageName, disks := range ins.QemuDisks {
diskIds := []string{}
for i := range disks {
if strings.HasSuffix(disks[i].DiskId, ".iso") {
continue
}
diskIds = append(diskIds, disks[i].DiskId)
}
disks, err := self.host.zone.region.GetDisks(self.host.Node, storageName)
if err != nil {
continue
return nil, errors.Wrapf(err, "GetDisks")
}
disk.VmId = id
disk.DiskDriver = v["type"].(string)
idx, _ := strconv.ParseInt(fmt.Sprintf("%d", v["slot"]), 10, 64)
disk.DriverIdx = int(idx)
if cache, ok := v["cache"].(string); ok {
disk.CacheMode = cache
storages, err := self.host.zone.region.GetStoragesByHost(self.Node)
if err != nil {
return nil, err
}
var storage *SStorage
for i := range storages {
if storages[i].Storage == storageName {
storage = &storages[i]
}
}
if storage == nil {
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "search storage %s", storageName)
}
for i := range disks {
if utils.IsInStringArray(disks[i].VolId, diskIds) {
disks[i].storage = storage
ret = append(ret, &disks[i])
}
}
ret = append(ret, disk)
}
return ret, nil
}
@@ -343,7 +418,18 @@ func (self *SInstance) GetProjectId() string {
}
func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
return nil, cloudprovider.ErrNotSupported
vnc, err := self.host.zone.region.GetVNCInfo(self.Node, self.VmID)
if err != nil {
return nil, err
}
ret := &cloudprovider.ServerVncOutput{}
params := url.Values{}
params.Set("port", fmt.Sprintf("%d", vnc.Port))
params.Set("vncticket", vnc.Ticket)
ret.Url = fmt.Sprintf("wss://%s:%d/api2/json/nodes/%s/qemu/%d/vncwebsocket?%s", self.host.zone.region.client.host, self.host.zone.region.client.port, self.Node, self.VmID, params.Encode())
ret.Protocol = "vnc"
ret.Hypervisor = api.HYPERVISOR_PROXMOX
return ret, nil
}
func (self *SInstance) GetVcpuCount() int {
@@ -438,7 +524,6 @@ func (self *SRegion) GetVmPowerStatus(node string, VmId int) string {
}
func (self *SRegion) GetQemuConfig(node string, VmId int) (*SInstance, error) {
//ret := &SInstance{}
res := fmt.Sprintf("/nodes/%s/qemu/%d/config", node, VmId)
vmConfig := map[string]interface{}{}
vmBase := &VmBase{
@@ -473,31 +558,34 @@ func (self *SRegion) GetQemuConfig(node string, VmId int) (*SInstance, error) {
}
config := SInstance{
VmID: int(VmId),
Name: vmBase.Name,
Description: strings.TrimSpace(vmBase.Description),
Tags: strings.TrimSpace(vmBase.Tags),
Args: strings.TrimSpace(vmBase.Args),
Bios: vmBase.Bios,
EFIDisk: QemuDevice{},
Machine: vmBase.Machine,
Onboot: Itob(vmBase.OnBoot),
Startup: vmBase.Startup,
Tablet: Itob(vmBase.Tablet),
QemuOs: vmBase.Ostype,
Memory: int(vmBase.Memory),
QemuCores: int(vmBase.Cores),
QemuSockets: int(vmBase.Sockets),
QemuCpu: vmBase.Cpu,
QemuNuma: Itob(vmBase.Numa),
QemuKVM: Itob(vmBase.Kvm),
Hotplug: vmBase.Hotplug,
QemuVlanTag: -1,
Boot: vmBase.Boot,
BootDisk: vmBase.Bootdisk,
Scsihw: vmBase.Scsihw,
Hookscript: vmBase.Hookscript,
QemuDisks: QemuDevices{},
VmID: int(VmId),
Name: vmBase.Name,
Description: strings.TrimSpace(vmBase.Description),
Tags: strings.TrimSpace(vmBase.Tags),
Args: strings.TrimSpace(vmBase.Args),
Bios: vmBase.Bios,
EFIDisk: QemuDevice{},
Machine: vmBase.Machine,
Onboot: Itob(vmBase.OnBoot),
Startup: vmBase.Startup,
Tablet: Itob(vmBase.Tablet),
QemuOs: vmBase.Ostype,
Memory: int(vmBase.Memory),
QemuCores: int(vmBase.Cores),
QemuSockets: int(vmBase.Sockets),
QemuCpu: vmBase.Cpu,
QemuNuma: Itob(vmBase.Numa),
QemuKVM: Itob(vmBase.Kvm),
Hotplug: vmBase.Hotplug,
QemuVlanTag: -1,
Boot: vmBase.Boot,
BootDisk: vmBase.Bootdisk,
Scsihw: vmBase.Scsihw,
Hookscript: vmBase.Hookscript,
QemuDisks: map[string][]struct {
Driver string
DiskId string
}{},
QemuUnusedDisks: QemuDevices{},
QemuVga: QemuDevice{},
QemuNetworks: []SInstanceNic{},
@@ -543,51 +631,36 @@ func (self *SRegion) GetQemuConfig(node string, VmId int) (*SInstance, error) {
config.PowerState = self.GetVmPowerStatus(node, VmId)
// Add disks.
diskNames := []string{}
diskNames := map[string]string{}
for k := range vmConfig {
if diskName := rxDiskName.FindStringSubmatch(k); len(diskName) > 0 {
diskNames = append(diskNames, diskName[0])
diskNames[k] = diskName[0]
}
}
for _, diskName := range diskNames {
for driver, diskName := range diskNames {
diskConfStr := vmConfig[diskName].(string)
id := rxDeviceID.FindStringSubmatch(diskName)
diskID, _ := strconv.Atoi(id[0])
diskType := rxDiskType.FindStringSubmatch(diskName)[0]
diskConfMap := ParsePMConf(diskConfStr, "volume")
if diskConfMap["volume"].(string) == "none" {
continue
}
diskConfMap["slot"] = diskID
diskConfMap["type"] = diskType
storageName, fileName := ParseSubConf(diskConfMap["volume"].(string), ":")
diskConfMap["storage"] = storageName
diskConfMap["file"] = fileName
volId := diskConfMap["volume"].(string)
// cloud-init disks not always have the size sent by the API, which results in a crash
if diskConfMap["size"] == nil && strings.Contains(fileName.(string), "cloudinit") {
diskConfMap["size"] = "4M" // default cloud-init disk size
}
var sizeInTerabytes = regexp.MustCompile(`[0-9]+T`)
// Convert to gigabytes if disk size was received in terabytes
matched := sizeInTerabytes.MatchString(diskConfMap["size"].(string))
if matched {
diskConfMap["size"] = fmt.Sprintf("%.0fG", DiskSizeGB(diskConfMap["size"]))
}
// And device config to disks map.
if len(diskConfMap) > 0 {
config.QemuDisks[volId] = diskConfMap
storageName, _ := ParseSubConf(diskConfMap["volume"].(string), ":")
_, ok := config.QemuDisks[storageName]
if !ok {
config.QemuDisks[storageName] = []struct {
Driver string
DiskId string
}{}
}
config.QemuDisks[storageName] = append(config.QemuDisks[storageName], struct {
Driver string
DiskId string
}{
Driver: driver,
DiskId: diskConfMap["volume"].(string),
})
}
// Add unused disks
@@ -820,90 +893,12 @@ func (self *SRegion) StopVm(vmId int) error {
return err
}
func (self *SRegion) AttachDisk(vmId int, diskId string) error {
id := strconv.Itoa(int(vmId))
vm1, err := self.GetInstance(id)
if err != nil {
return errors.Wrapf(err, "GetInstance(%d)", vmId)
}
if _, ok := vm1.QemuUnusedDisks[diskId]; !ok {
return nil
}
slotsArr := []int{}
for _, v := range vm1.QemuDisks {
if v["type"] == "scsi" {
slotIdx := v["slot"].(int)
slotsArr = append(slotsArr, slotIdx)
}
}
sort.Ints(slotsArr)
minSlot := slotsArr[0]
for idx, _ := range slotsArr {
if slotsArr[idx] == minSlot {
minSlot++
} else {
break
}
}
func (self *SRegion) DetachDisk(node string, vmId int, driver string) error {
body := map[string]string{}
params := url.Values{}
diskName := fmt.Sprintf("scsi%d", minSlot)
body[diskName] = diskId
res := fmt.Sprintf("/nodes/%s/qemu/%d/config", vm1.Node, vm1.VmID)
err = self.put(res, params, jsonutils.Marshal(body), nil)
if err != nil {
return errors.Wrapf(err, "GetInstance(%d) self.put", vmId)
}
//clear
vm1.QemuDisks = make(map[string]map[string]interface{})
vm1.QemuUnusedDisks = make(map[string]map[string]interface{})
vm2, err := self.GetInstance(id)
if err != nil {
return errors.Wrapf(err, "GetInstance(%d) vm2", vmId)
}
vm1.QemuDisks = vm2.QemuDisks
vm1.QemuUnusedDisks = vm2.QemuUnusedDisks
return nil
}
func (self *SRegion) DetachDisk(vmId int, diskId string) error {
id := strconv.Itoa(int(vmId))
vm1, err := self.GetInstance(id)
if err != nil {
return errors.Wrapf(err, "GetInstance(%d)", vmId)
}
if v, ok := vm1.QemuDisks[diskId]; !ok {
return nil
} else {
diskName := fmt.Sprintf("%s%d", v["type"].(string), v["slot"].(int))
body := map[string]string{}
params := url.Values{}
body["delete"] = diskName
res := fmt.Sprintf("/nodes/%s/qemu/%d/config", vm1.Node, vm1.VmID)
err := self.put(res, params, jsonutils.Marshal(body), nil)
if err != nil {
return errors.Wrapf(err, "GetInstance(%d) self.put", vmId)
}
//clear
vm1.QemuDisks = make(map[string]map[string]interface{})
vm1.QemuUnusedDisks = make(map[string]map[string]interface{})
vm2, err := self.GetInstance(id)
if err != nil {
return errors.Wrapf(err, "GetInstance(%d) vm2", vmId)
}
vm1.QemuDisks = vm2.QemuDisks
vm1.QemuUnusedDisks = vm2.QemuUnusedDisks
return nil
}
body["delete"] = driver
res := fmt.Sprintf("/nodes/%s/qemu/%d/config", node, vmId)
return self.put(res, params, jsonutils.Marshal(body))
}
func (self *SRegion) ChangeConfig(vmId int, cpu int, memMb int) error {
@@ -934,7 +929,7 @@ func (self *SRegion) ChangeConfig(vmId int, cpu int, memMb int) error {
params := url.Values{}
res := fmt.Sprintf("/nodes/%s/qemu/%d/config", vm.Node, vmId)
return self.put(res, params, jsonutils.Marshal(body), nil)
return self.put(res, params, jsonutils.Marshal(body))
}
func (self *SRegion) ResetVmPassword(vmId int, username, password string) error {
@@ -957,7 +952,7 @@ func (self *SRegion) ResetVmPassword(vmId int, username, password string) error
}
res := fmt.Sprintf("/nodes/%s/qemu/%d/agent/set-user-password", nodeName, vmId)
return self.put(res, params, jsonutils.Marshal(body), nil)
return self.put(res, params, jsonutils.Marshal(body))
}
@@ -973,6 +968,7 @@ func (self *SRegion) DeleteVM(vmId int) error {
res := fmt.Sprintf("/nodes/%s/qemu/%d", vm1.Node, vmId)
return self.del(res, params, nil)
}
func (self *SRegion) GenVM(name, node string, cores, memMB int) (*SInstance, error) {
vmId := self.GetClusterVmMaxId()
@@ -1010,3 +1006,21 @@ func (self *SRegion) GenVM(name, node string, cores, memMB int) (*SInstance, err
return vm, nil
}
type InstanceVnc struct {
Port int
Ticket string
Cert string
}
func (self *SRegion) 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{}{})
if err != nil {
return nil, err
}
ret := struct {
Data InstanceVnc
}{}
return &ret.Data, resp.Unmarshal(&ret)
}
+97 -17
View File
@@ -15,12 +15,18 @@
package proxmox
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"path/filepath"
"strings"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
@@ -130,6 +136,7 @@ type ProxmoxError struct {
Message string
Code int
Params []string
Errors string
}
func (self ProxmoxError) Error() string {
@@ -144,8 +151,7 @@ func (ce *ProxmoxError) ParseErrorFromJsonResponse(statusCode int, body jsonutil
if ce.Code == 0 && statusCode > 0 {
ce.Code = statusCode
}
if ce.Code == 404 || ce.Code == 400 || ce.Code == 500 {
log.Errorf("code: %d", ce.Code)
if ce.Code == 404 {
return errors.Wrap(cloudprovider.ErrNotFound, ce.Error())
}
return ce
@@ -177,16 +183,18 @@ func (cli *SProxmoxClient) post(res string, params interface{}) (jsonutils.JSONO
if err != nil {
return resp, err
}
taskId, err := resp.GetString("data")
if err != nil {
ret := struct {
TaskId string
}{}
resp.Unmarshal(&ret)
if len(ret.TaskId) > 0 && ret.TaskId != "null" {
_, err = cli.waitTask(ret.TaskId)
return resp, err
}
_, err = cli.waitTask(taskId)
return resp, err
return resp, nil
}
func (cli *SProxmoxClient) put(res string, params url.Values, body jsonutils.JSONObject, retVal interface{}) error {
func (cli *SProxmoxClient) put(res string, params url.Values, body jsonutils.JSONObject) error {
if params != nil {
res = fmt.Sprintf("%s?%s", res, params.Encode())
}
@@ -194,16 +202,21 @@ func (cli *SProxmoxClient) put(res string, params url.Values, body jsonutils.JSO
if err != nil {
return err
}
taskId, err := resp.GetString("data")
if err != nil {
ret := struct {
TaskId string
}{}
resp.Unmarshal(&ret)
if len(ret.TaskId) > 0 && ret.TaskId != "null" {
_, err = cli.waitTask(ret.TaskId)
return err
}
_, err = cli.waitTask(taskId)
return err
return nil
}
func (cli *SProxmoxClient) get(res string, params url.Values, retVal interface{}) error {
if len(params) > 0 {
res = fmt.Sprintf("%s?%s", res, params.Encode())
}
resp, err := cli._jsonRequest(httputils.GET, res, nil)
if err != nil {
return err
@@ -273,19 +286,86 @@ func (cli *SProxmoxClient) __jsonRequest(method httputils.THttpMethod, res strin
header.Set("CSRFPreventionToken", cli.csrfToken)
}
//header.Set("Content-Type", "application/x-www-form-urlencoded")
//header.Set("Accept", "application/json")
req.SetHeader(header)
oe := &ProxmoxError{}
_, resp, err := client.Send(context.Background(), req, oe, cli.debug)
if err != nil {
return nil, err
return nil, errors.Wrapf(err, "with params: %v", params)
}
return resp, nil
}
func (cli *SProxmoxClient) upload(node, storageName, filename string, reader io.Reader) (*SImage, error) {
if !strings.HasSuffix(filename, ".iso") {
filename = filename + ".iso"
}
filename = filepath.Base(filename)
client := cli.getDefaultClient()
res := fmt.Sprintf("/nodes/%s/storage/%s/upload", node, storageName)
url := fmt.Sprintf("%s/%s", cli.authURL, strings.TrimPrefix(res, "/"))
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
err := writer.WriteField("content", "iso")
if err != nil {
return nil, err
}
part, err := writer.CreateFormFile("filename", filename)
if err != nil {
return nil, err
}
_, err = io.Copy(part, reader)
if err != nil {
return nil, errors.Wrapf(err, "io.Copy")
}
writer.Close()
req, err := http.NewRequest("POST", url, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", writer.FormDataContentType())
if len(cli.csrfToken) > 0 && len(cli.csrfToken) > 0 && res != AUTH_ADDR {
req.Header.Set("Cookie", "PVEAuthCookie="+cli.authTicket)
req.Header.Set("CSRFPreventionToken", cli.csrfToken)
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
obj, err := jsonutils.Parse(data)
if err != nil {
return nil, err
}
if obj.Contains("errors") {
return nil, fmt.Errorf(string(data))
}
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 (self *SProxmoxClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
subAccount := cloudprovider.SSubAccount{}
subAccount.Name = self.cpcfg.Name
+23 -6
View File
@@ -160,13 +160,30 @@ func (self *SRegion) GetIHostById(id string) (cloudprovider.ICloudHost, error) {
}
func (self *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) {
zone, err := self.GetZone()
storages, err := self.GetStorages()
if err != nil {
return nil, cloudprovider.ErrNotSupported
return nil, err
}
ret := []cloudprovider.ICloudStoragecache{}
cache := &SStoragecache{zone: zone}
ret = append(ret, cache)
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
}
@@ -195,8 +212,8 @@ func (self *SRegion) post(res string, params interface{}) (jsonutils.JSONObject,
return self.client.post(res, params)
}
func (self *SRegion) put(res string, params url.Values, body jsonutils.JSONObject, retVal interface{}) error {
return self.client.put(res, params, body, retVal)
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 {
+68 -78
View File
@@ -16,10 +16,10 @@ package proxmox
import (
"fmt"
"net/url"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
api "yunion.io/x/cloudmux/pkg/apis/compute"
"yunion.io/x/cloudmux/pkg/cloudprovider"
@@ -32,27 +32,30 @@ type SStorage struct {
zone *SZone
Id string
Node string
Storage string `json:"storage"`
Status string
Id string
Node string
Total int64 `json:"total"`
Storage string `json:"storage"`
Shared int `json:"shared"`
Used int64 `json:"used"`
Content string `json:"content"`
Active int `json:"active"`
UsedFraction float64 `json:"used_fraction"`
Avail int64 `json:"avail"`
Enabled int `json:"enabled"`
Type string `json:"type"`
Shared int `json:"shared"`
Content string `json:"content"`
MaxDisk int64 `json:"maxdisk"`
Disk int64 `json:"disk"`
PluginType string `json:"plugintype"`
}
func (self *SStorage) GetName() string {
if self.Shared == 0 {
return fmt.Sprintf("%s-%s", self.Node, self.Storage)
}
return self.Storage
}
func (self *SStorage) GetId() string {
return self.Id
if self.Shared == 0 {
return self.Id
}
return self.Storage
}
func (self *SStorage) GetGlobalId() string {
@@ -60,13 +63,13 @@ func (self *SStorage) GetGlobalId() string {
}
func (self *SStorage) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
disks, err := self.zone.region.GetDisks(self.Id)
disks, err := self.zone.region.GetDisks(self.Node, self.Storage)
if err != nil {
return nil, err
}
ret := []cloudprovider.ICloudDisk{}
for i := range disks {
disks[i].region = self.zone.region
disks[i].storage = self
ret = append(ret, &disks[i])
}
return ret, nil
@@ -77,29 +80,39 @@ func (self *SStorage) CreateIDisk(conf *cloudprovider.DiskCreateConfig) (cloudpr
}
func (self *SStorage) GetCapacityMB() int64 {
return int64(self.Total / 1024 / 1024)
return int64(self.MaxDisk / 1024 / 1024)
}
func (self *SStorage) GetCapacityUsedMB() int64 {
return int64(self.Used / 1024 / 1024)
return int64(self.Disk / 1024 / 1024)
}
func (self *SStorage) GetEnabled() bool {
return true
if strings.Contains(self.Content, "images") {
return true
}
return false
}
func (self *SStorage) GetIDiskById(id string) (cloudprovider.ICloudDisk, error) {
disk, err := self.zone.region.GetDisk(id)
disks, err := self.GetIDisks()
if err != nil {
return nil, cloudprovider.ErrNotFound
return nil, err
}
return disk, nil
for i := range disks {
if disks[i].GetGlobalId() == id {
return disks[i], nil
}
}
return nil, errors.Wrapf(cloudprovider.ErrNotFound, id)
}
func (self *SStorage) GetIStoragecache() cloudprovider.ICloudStoragecache {
cache := &SStoragecache{zone: self.zone}
return cache
return &SStoragecache{
region: self.zone.region,
Node: self.Node,
isShare: self.Shared == 1,
}
}
func (self *SStorage) GetMediumType() string {
@@ -111,6 +124,9 @@ func (self *SStorage) GetMountPoint() string {
}
func (self *SStorage) GetStatus() string {
if self.Status != "available" {
return api.STORAGE_OFFLINE
}
return api.STORAGE_ONLINE
}
@@ -131,7 +147,7 @@ func (self *SStorage) GetStorageConf() jsonutils.JSONObject {
}
func (self *SStorage) GetStorageType() string {
return strings.ToLower(self.Type)
return strings.ToLower(self.PluginType)
}
func (self *SStorage) IsSysDiskStore() bool {
@@ -153,78 +169,52 @@ func (self *SRegion) GetIStorageById(id string) (cloudprovider.ICloudStorage, er
func (self *SRegion) GetStorages() ([]SStorage, error) {
storages := []SStorage{}
resources, err := self.GetClusterStoragesResources()
resources, err := self.GetClusterResources("storage")
if err != nil {
return nil, err
}
for _, res := range resources {
storage := &SStorage{}
status := fmt.Sprintf("%s/status", res.Path)
err := self.get(status, url.Values{}, storage)
if err != nil {
return nil, err
}
jsonutils.Update(&storages, resources)
storage.Id = res.Id
storage.Node = res.Node
// not support storageCache, so chanege the type name.
if storage.Type == "rbd" {
storage.Type = "cephrbd"
storageMap := map[string]bool{}
ret := []SStorage{}
for i := range storages {
if storages[i].Shared == 0 {
ret = append(ret, storages[i])
continue
}
if storage.Storage == "" {
storage.Storage = res.Name
if _, ok := storageMap[storages[i].Storage]; !ok {
ret = append(ret, storages[i])
storageMap[storages[i].Storage] = true
}
storages = append(storages, *storage)
}
return storages, nil
return ret, nil
}
func (self *SRegion) GetStoragesByHost(hostId string) ([]SStorage, error) {
storages := []SStorage{}
nodeName := ""
splited := strings.Split(hostId, "/")
nodeName = splited[1]
res := fmt.Sprintf("/nodes/%s/storage", nodeName)
err := self.get(res, url.Values{}, &storages)
func (self *SRegion) GetStoragesByHost(node string) ([]SStorage, error) {
storages, err := self.GetStorages()
if err != nil {
return nil, err
}
ret := []SStorage{}
for i := range storages {
id := fmt.Sprintf("storage/%s/%s", nodeName, storages[i].Storage)
storages[i].Node = nodeName
storages[i].Id = id
if storages[i].Type == "rbd" {
storages[i].Type = "cephrbd"
if storages[i].Shared == 1 || storages[i].Node == node {
ret = append(ret, storages[i])
}
}
return storages, nil
return ret, nil
}
func (self *SRegion) GetStorage(id string) (*SStorage, error) {
ret := &SStorage{}
//"id": "storage/nodeNAME/strogeNAME",
splited := strings.Split(id, "/")
nodeName := ""
storageName := ""
if len(splited) == 3 {
nodeName, storageName = splited[1], splited[2]
storages, err := self.GetStorages()
if err != nil {
return nil, err
}
status := fmt.Sprintf("/nodes/%s/storage/%s/status", nodeName, storageName)
err := self.get(status, url.Values{}, ret)
ret.Id = id
ret.Node = nodeName
if ret.Type == "rbd" {
ret.Type = "cephrbd"
for i := range storages {
if storages[i].GetGlobalId() == id {
return &storages[i], nil
}
}
return ret, err
return nil, errors.Wrapf(cloudprovider.ErrNotFound, id)
}
+63 -13
View File
@@ -16,28 +16,42 @@ 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
zone *SZone
region *SRegion
Node string
isShare bool
}
func (self *SStoragecache) GetGlobalId() string {
return self.zone.GetGlobalId()
if self.isShare {
return fmt.Sprintf("%s-share", self.region.GetGlobalId())
}
return fmt.Sprintf("%s-%s", self.region.GetGlobalId(), self.Node)
}
func (self *SStoragecache) GetId() string {
return self.zone.GetId()
return self.region.GetId()
}
func (self *SStoragecache) GetName() string {
return self.zone.GetName()
if self.isShare {
return fmt.Sprintf("%s-share", self.region.GetName())
}
return fmt.Sprintf("%s-%s", self.region.GetName(), self.Node)
}
func (self *SStoragecache) GetStatus() string {
@@ -46,15 +60,26 @@ func (self *SStoragecache) GetStatus() string {
func (self *SStoragecache) GetICloudImages() ([]cloudprovider.ICloudImage, error) {
ret := []cloudprovider.ICloudImage{}
images, err := self.zone.region.GetImageList()
storages, err := self.region.GetStorages()
if err != nil {
return nil, err
}
for i := range images {
images[i].cache = self
ret = append(ret, &images[i])
for i := range storages {
if !strings.Contains(storages[i].Content, "iso") {
continue
}
if (self.isShare && storages[i].Shared != 1) || (!self.isShare && storages[i].Node != self.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])
}
}
return ret, nil
}
@@ -79,10 +104,35 @@ func (self *SStoragecache) GetPath() string {
return ""
}
func (self *SStoragecache) CreateIImage(snpId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
return nil, cloudprovider.ErrNotSupported
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 *SStoragecache) UploadImage(ctx context.Context, image *cloudprovider.SImageCreateOption, callback func(float32)) (string, error) {
return "", cloudprovider.ErrNotSupported
func (self *SRegion) UploadImage(node, storage, filename string, reader io.Reader) (*SImage, error) {
return self.client.upload(node, storage, filename, reader)
}
-17
View File
@@ -59,23 +59,6 @@ func (self *SStoragecache) IsEmulated() bool {
return false
}
func (self *SStoragecache) CreateIImage(snapshoutId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
// if imageId, err := self.region.createIImage(snapshoutId, imageName, imageDesc); err != nil {
// return nil, err
// } else if image, err := self.region.GetImage(imageId); err != nil {
// return nil, err
// } else {
// image.storageCache = self
// iimage := make([]cloudprovider.ICloudImage, 1)
// iimage[0] = image
// if err := cloudprovider.WaitStatus(iimage[0], compute.IMAGE_STATUS_ACTIVE, 15*time.Second, 3600*time.Second); err != nil {
// return nil, err
// }
// return iimage[0], nil
// }
return nil, nil
}
func (self *SStoragecache) GetICloudImages() ([]cloudprovider.ICloudImage, error) {
return nil, cloudprovider.ErrNotImplemented
}
-4
View File
@@ -91,10 +91,6 @@ func (self *SStoragecache) GetPath() string {
return ""
}
func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
return nil, cloudprovider.ErrNotSupported
}
// https://docs.ucloud.cn/api/uhost-api/import_custom_image
func (self *SStoragecache) UploadImage(ctx context.Context, image *cloudprovider.SImageCreateOption, callback func(progress float32)) (string, error) {
return self.uploadImage(ctx, image, callback)
-4
View File
@@ -121,7 +121,3 @@ func (self *SStoragecache) uploadImage(ctx context.Context, image *cloudprovider
}
return img.UUID, err
}
func (scache *SStoragecache) CreateIImage(snapshoutId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
return nil, cloudprovider.ErrNotImplemented
}