diff --git a/build/docker/Dockerfile.host-deployer b/build/docker/Dockerfile.host-deployer index a11c34e796..ae66116250 100644 --- a/build/docker/Dockerfile.host-deployer +++ b/build/docker/Dockerfile.host-deployer @@ -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" diff --git a/go.mod b/go.mod index 2bfb07c23f..68d2fedbdd 100644 --- a/go.mod +++ b/go.mod @@ -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.20260416100248-1e78d55e2b71 + yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260506084933-057ce2bd5a0f 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 diff --git a/go.sum b/go.sum index 2f8a0f45c6..b41921ff18 100644 --- a/go.sum +++ b/go.sum @@ -1783,8 +1783,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.20260416100248-1e78d55e2b71 h1:ObA2AVgIvicxhlQFsW6vrmWKAqTIV9FDpxrpKU0E538= -yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260416100248-1e78d55e2b71/go.mod h1:rCz5Nds6Zi/5uaj51oRDYdj5fZ5uULmKbuBAgNLuI+w= +yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260506084933-057ce2bd5a0f h1:pfc38lKmHWSzCd0mZcHok5hBp4+dHcvVYV+SgDWRrWI= +yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260506084933-057ce2bd5a0f/go.mod h1:rCz5Nds6Zi/5uaj51oRDYdj5fZ5uULmKbuBAgNLuI+w= 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= @@ -1798,8 +1798,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= diff --git a/pkg/apis/compute/guest_disk.go b/pkg/apis/compute/guest_disk.go index d8943323e1..b4c8cf26f1 100644 --- a/pkg/apis/compute/guest_disk.go +++ b/pkg/apis/compute/guest_disk.go @@ -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"` diff --git a/pkg/apis/compute/guests.go b/pkg/apis/compute/guests.go index 4e906c5f8b..a60645306d 100644 --- a/pkg/apis/compute/guests.go +++ b/pkg/apis/compute/guests.go @@ -924,24 +924,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(如果有) diff --git a/pkg/compute/guestdrivers/proxmox.go b/pkg/compute/guestdrivers/proxmox.go index acda8ea515..ec4fd17703 100644 --- a/pkg/compute/guestdrivers/proxmox.go +++ b/pkg/compute/guestdrivers/proxmox.go @@ -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 +} diff --git a/pkg/compute/hostdrivers/esxi.go b/pkg/compute/hostdrivers/esxi.go index fab3f4ae0c..d61dbdb188 100644 --- a/pkg/compute/hostdrivers/esxi.go +++ b/pkg/compute/hostdrivers/esxi.go @@ -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 diff --git a/pkg/compute/hostdrivers/proxmox.go b/pkg/compute/hostdrivers/proxmox.go index 6cc978f917..8306db7966 100644 --- a/pkg/compute/hostdrivers/proxmox.go +++ b/pkg/compute/hostdrivers/proxmox.go @@ -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 +} diff --git a/pkg/compute/models/cloudaccount_vmware.go b/pkg/compute/models/cloudaccount_vmware.go index ed141ba9e1..d0266ea2f1 100644 --- a/pkg/compute/models/cloudaccount_vmware.go +++ b/pkg/compute/models/cloudaccount_vmware.go @@ -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) diff --git a/pkg/compute/models/guestdisks.go b/pkg/compute/models/guestdisks.go index b6df02842d..50b6108881 100644 --- a/pkg/compute/models/guestdisks.go +++ b/pkg/compute/models/guestdisks.go @@ -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}) { diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 67720d54f3..a1d443f06c 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -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???") diff --git a/pkg/compute/tasks/cloudaccount/cloud_account_sync_task.go b/pkg/compute/tasks/cloudaccount/cloud_account_sync_task.go index 36140e9c23..b1c609a9f2 100644 --- a/pkg/compute/tasks/cloudaccount/cloud_account_sync_task.go +++ b/pkg/compute/tasks/cloudaccount/cloud_account_sync_task.go @@ -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 { diff --git a/pkg/esxi/agent.go b/pkg/esxi/agent.go index b20ebf35cc..ca2261c2ce 100644 --- a/pkg/esxi/agent.go +++ b/pkg/esxi/agent.go @@ -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) } diff --git a/pkg/esxi/handler/handlers.go b/pkg/esxi/handler/handlers.go index b29f2a3b36..4a1014c15c 100644 --- a/pkg/esxi/handler/handlers.go +++ b/pkg/esxi/handler/handlers.go @@ -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)) diff --git a/pkg/esxi/handler/proxmox.go b/pkg/esxi/handler/proxmox.go new file mode 100644 index 0000000000..9143cb224d --- /dev/null +++ b/pkg/esxi/handler/proxmox.go @@ -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/", 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, 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, 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 +} diff --git a/pkg/hostman/diskutils/interface.go b/pkg/hostman/diskutils/interface.go index b3b3a5d528..b526cc4e1f 100644 --- a/pkg/hostman/diskutils/interface.go +++ b/pkg/hostman/diskutils/interface.go @@ -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) } diff --git a/pkg/hostman/diskutils/nbdkit.go b/pkg/hostman/diskutils/nbdkit.go new file mode 100644 index 0000000000..101048df1d --- /dev/null +++ b/pkg/hostman/diskutils/nbdkit.go @@ -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 :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) +} diff --git a/pkg/hostman/diskutils/nbdkit_test.go b/pkg/hostman/diskutils/nbdkit_test.go new file mode 100644 index 0000000000..7f3103d721 --- /dev/null +++ b/pkg/hostman/diskutils/nbdkit_test.go @@ -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 :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) + } + } +} diff --git a/pkg/hostman/storageman/disk_proxmox.go b/pkg/hostman/storageman/disk_proxmox.go new file mode 100644 index 0000000000..6e916cbd06 --- /dev/null +++ b/pkg/hostman/storageman/disk_proxmox.go @@ -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 +} diff --git a/pkg/hostman/storageman/imagecachemanager_agent.go b/pkg/hostman/storageman/imagecachemanager_agent.go index dec79402c2..ed5db7a6e6 100644 --- a/pkg/hostman/storageman/imagecachemanager_agent.go +++ b/pkg/hostman/storageman/imagecachemanager_agent.go @@ -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) } diff --git a/pkg/hostman/storageman/imagecachemanager_proxmox.go b/pkg/hostman/storageman/imagecachemanager_proxmox.go new file mode 100644 index 0000000000..92ac6ced2b --- /dev/null +++ b/pkg/hostman/storageman/imagecachemanager_proxmox.go @@ -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 +} diff --git a/pkg/hostman/storageman/storage_agent.go b/pkg/hostman/storageman/storage_agent.go index 3d75fd229a..5c3fdb58cb 100644 --- a/pkg/hostman/storageman/storage_agent.go +++ b/pkg/hostman/storageman/storage_agent.go @@ -236,6 +236,7 @@ func (self esxiVm) GetName() string { type SHostDatastore struct { HostIp string + HostId string Datastore vcenter.SVCenterAccessInfo } diff --git a/pkg/hostman/storageman/storage_proxmox.go b/pkg/hostman/storageman/storage_proxmox.go new file mode 100644 index 0000000000..d89febaf82 --- /dev/null +++ b/pkg/hostman/storageman/storage_proxmox.go @@ -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 +} diff --git a/pkg/scheduler/algorithm/predicates/network_predicate.go b/pkg/scheduler/algorithm/predicates/network_predicate.go index 13e46772c9..f9a4210e46 100644 --- a/pkg/scheduler/algorithm/predicates/network_predicate.go +++ b/pkg/scheduler/algorithm/predicates/network_predicate.go @@ -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, } } diff --git a/vendor/modules.txt b/vendor/modules.txt index 38f5e9a107..c5a67cf3e0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2598,7 +2598,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.20260416100248-1e78d55e2b71 +# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260506084933-057ce2bd5a0f ## explicit; go 1.24 yunion.io/x/cloudmux/pkg/apis yunion.io/x/cloudmux/pkg/apis/billing @@ -2710,7 +2710,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 diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/aws/storagecache.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/aws/storagecache.go index 8e7db1b5c2..a8776a1dde 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/aws/storagecache.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/aws/storagecache.go @@ -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") } diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/esxi/vcenter/vcenter.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/esxi/vcenter/vcenter.go index 8fc05d8288..fea26f9a7b 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/esxi/vcenter/vcenter.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/esxi/vcenter/vcenter.go @@ -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 +} diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/cluster.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/cluster.go index c537568ac2..ca184d32ff 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/cluster.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/cluster.go @@ -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) diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/disk.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/disk.go index f9b93139bb..d05521cf8d 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/disk.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/disk.go @@ -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), diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/fakeregion.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/fakeregion.go new file mode 100644 index 0000000000..a641b6f4e6 --- /dev/null +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/fakeregion.go @@ -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 +} diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/host.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/host.go index fab77caf2b..bb0ce794f0 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/host.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/host.go @@ -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", diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/hostnic.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/hostnic.go new file mode 100644 index 0000000000..385da0a185 --- /dev/null +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/hostnic.go @@ -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 +} diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/image.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/image.go index 1f07242e89..82fa02ce19 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/image.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/image.go @@ -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) -} diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/instance.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/instance.go index f2664bafcd..6758e42477 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/instance.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/instance.go @@ -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{}, ¤t) @@ -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", diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/network.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/network.go deleted file mode 100644 index 15bf38ccaa..0000000000 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/network.go +++ /dev/null @@ -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 -} diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/nic.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/nic.go index a60d01304a..7f8e7d26b5 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/nic.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/nic.go @@ -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 { diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/provider/provider.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/provider/provider.go index 2c64f12b56..974c398ae4 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/provider/provider.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/provider/provider.go @@ -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() } + diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/proxmox.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/proxmox.go index 1b432a80ea..a56faf589c 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/proxmox.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/proxmox.go @@ -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, diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/region.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/region.go deleted file mode 100644 index 16132e0db1..0000000000 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/region.go +++ /dev/null @@ -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 -} diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/storage.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/storage.go index 6bb88b553c..b4d906c57f 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/storage.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/storage.go @@ -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 diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/storagecache.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/storagecache.go index 2040e2aa9d..6e9defa571 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/storagecache.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/storagecache.go @@ -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 } diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/vpc.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/vpc.go index 380ddd2e30..bbd3850d74 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/vpc.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/vpc.go @@ -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 { diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/wire.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/wire.go index b4df4cf519..49c5891b18 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/wire.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/wire.go @@ -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 } diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/zone.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/zone.go index 924a07553d..8930af5f96 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/zone.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/proxmox/zone.go @@ -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") } diff --git a/vendor/yunion.io/x/pkg/util/compare/syncresults.go b/vendor/yunion.io/x/pkg/util/compare/syncresults.go index 3d56ab9174..89cf6abf55 100644 --- a/vendor/yunion.io/x/pkg/util/compare/syncresults.go +++ b/vendor/yunion.io/x/pkg/util/compare/syncresults.go @@ -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 } diff --git a/vendor/yunion.io/x/pkg/util/imagetools/imagetools.go b/vendor/yunion.io/x/pkg/util/imagetools/imagetools.go index efa666789d..82b16c5bf0 100644 --- a/vendor/yunion.io/x/pkg/util/imagetools/imagetools.go +++ b/vendor/yunion.io/x/pkg/util/imagetools/imagetools.go @@ -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%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 "" }