From 2564061744a3671a2f780c6ee2e407cc76496ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Fri, 10 Apr 2026 16:54:10 +0800 Subject: [PATCH] fix(region): support query public cloud vm modification types (#24655) --- cmd/climc/shell/compute/servers.go | 1 + go.mod | 2 +- go.sum | 4 +- pkg/apis/compute/guests.go | 8 +++ pkg/compute/models/guest_actions.go | 23 ++++++++ vendor/modules.txt | 2 +- .../x/cloudmux/pkg/cloudprovider/instance.go | 4 ++ .../x/cloudmux/pkg/cloudprovider/resources.go | 2 + .../pkg/multicloud/aliyun/instance.go | 55 +++++++++++++++++++ .../pkg/multicloud/huawei/instance.go | 25 +++++++++ .../cloudmux/pkg/multicloud/instance_base.go | 4 ++ .../pkg/multicloud/qcloud/instance.go | 44 +++++++++++++++ .../pkg/multicloud/remotefile/instance.go | 4 ++ 13 files changed, 174 insertions(+), 4 deletions(-) diff --git a/cmd/climc/shell/compute/servers.go b/cmd/climc/shell/compute/servers.go index cbaf282a1b..ea6a749de7 100644 --- a/cmd/climc/shell/compute/servers.go +++ b/cmd/climc/shell/compute/servers.go @@ -131,6 +131,7 @@ func init() { cmd.BatchPerform("set-root-disk-matcher", &options.ServerSetRootDiskMatcher{}) cmd.Perform("disable-auto-merge-snapshot", &options.ServerDisableAutoMergeSnapshot{}) cmd.Get("vnc", new(options.ServerVncOptions)) + cmd.Get("modification-types", new(options.ServerIdOptions)) cmd.Get("desc", new(options.ServerIdOptions)) cmd.Get("status", new(options.ServerIdOptions)) cmd.Get("iso", new(options.ServerIdOptions)) diff --git a/go.mod b/go.mod index a65ddf0589..c248c43fcd 100644 --- a/go.mod +++ b/go.mod @@ -94,7 +94,7 @@ require ( k8s.io/client-go v0.26.1 k8s.io/cluster-bootstrap v0.19.3 moul.io/http2curl/v2 v2.3.0 - yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260409072655-7f5827365741 + yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260409123851-7b55f5f3ad92 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 diff --git a/go.sum b/go.sum index 105b979ec7..ac984a4bda 100644 --- a/go.sum +++ b/go.sum @@ -1090,8 +1090,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.20260409072655-7f5827365741 h1:F+I9SeZJpH477cIGTbjHiRT+hry+o3CnWbfwkiFG8LQ= -yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260409072655-7f5827365741/go.mod h1:GdAwZ78fiqj13i2r1zNuE/D6YOlhh2q4jZjuyHiZTq8= +yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260409123851-7b55f5f3ad92 h1:tklE5QK6iUhIeZiSmY8IVUjCcXNgQ1xfH8EaUgrZEDk= +yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260409123851-7b55f5f3ad92/go.mod h1:GdAwZ78fiqj13i2r1zNuE/D6YOlhh2q4jZjuyHiZTq8= 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= diff --git a/pkg/apis/compute/guests.go b/pkg/apis/compute/guests.go index 4eaf8e644f..7b299c7813 100644 --- a/pkg/apis/compute/guests.go +++ b/pkg/apis/compute/guests.go @@ -1400,3 +1400,11 @@ type ServerChangeBillingTypeInput struct { // required: true BillingType string `json:"billing_type"` } + +type ServerModificationType struct { + Name string `json:"name"` +} + +type ServerModificationTypesOutput struct { + ModificationTypes []ServerModificationType `json:"modification_types"` +} diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index ab9ce92c5b..4df1540e09 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -93,6 +93,29 @@ func (self *SGuest) GetDetailsVnc(ctx context.Context, userCred mcclient.TokenCr return ret, nil } +// 获取公有云可变更配置 +// 目前支持: 阿里云,腾讯云,华为云 +func (guest *SGuest) GetDetailsModificationTypes(ctx context.Context, userCred mcclient.TokenCredential, input jsonutils.JSONObject) (*api.ServerModificationTypesOutput, error) { + if len(guest.ExternalId) == 0 { + return nil, httperrors.NewNotFoundError("guest external id is empty") + } + vm, err := guest.GetIVM(ctx) + if err != nil { + return nil, errors.Wrapf(err, "GetIVM") + } + + ret := &api.ServerModificationTypesOutput{} + ret.ModificationTypes = make([]api.ServerModificationType, 0) + modificationTypes, err := vm.GetModificationTypes() + if err != nil { + return nil, errors.Wrapf(err, "GetModificationTypes") + } + for _, modificationType := range modificationTypes { + ret.ModificationTypes = append(ret.ModificationTypes, api.ServerModificationType{Name: modificationType.InstanceType}) + } + return ret, nil +} + func (self *SGuest) PreCheckPerformAction( ctx context.Context, userCred mcclient.TokenCredential, action string, query jsonutils.JSONObject, data jsonutils.JSONObject, diff --git a/vendor/modules.txt b/vendor/modules.txt index ca28e0427d..44833fac8c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1830,7 +1830,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.20260409072655-7f5827365741 +# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260409123851-7b55f5f3ad92 ## explicit; go 1.21 yunion.io/x/cloudmux/pkg/apis yunion.io/x/cloudmux/pkg/apis/billing diff --git a/vendor/yunion.io/x/cloudmux/pkg/cloudprovider/instance.go b/vendor/yunion.io/x/cloudmux/pkg/cloudprovider/instance.go index c513b332f2..3dc49d0012 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/cloudprovider/instance.go +++ b/vendor/yunion.io/x/cloudmux/pkg/cloudprovider/instance.go @@ -377,3 +377,7 @@ type SInstanceDeployOptions struct { DeleteKeypair bool UserData string } + +type SInstanceModificationType struct { + InstanceType string +} diff --git a/vendor/yunion.io/x/cloudmux/pkg/cloudprovider/resources.go b/vendor/yunion.io/x/cloudmux/pkg/cloudprovider/resources.go index 7099e122bc..0e9d32361f 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/cloudprovider/resources.go +++ b/vendor/yunion.io/x/cloudmux/pkg/cloudprovider/resources.go @@ -379,6 +379,8 @@ type ICloudVM interface { DeployVM(ctx context.Context, opts *SInstanceDeployOptions) error ChangeConfig(ctx context.Context, config *SManagedVMChangeConfig) error + // 获取实例可变更类型 + GetModificationTypes() ([]SInstanceModificationType, error) GetVNCInfo(input *ServerVncInput) (*ServerVncOutput, error) // 若有跟随主机删除的选项,需要设置为True diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/instance.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/instance.go index 317f233efa..b75f23d140 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/instance.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/instance.go @@ -522,6 +522,61 @@ func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.S return fmt.Errorf("Failed to change vm config, specification not supported") } +func (self *SInstance) GetModificationTypes() ([]cloudprovider.SInstanceModificationType, error) { + return self.host.zone.region.GetInstanceModificationTypes(self.InstanceId) +} + +func (self *SRegion) GetInstanceModificationTypes(instanceId string) ([]cloudprovider.SInstanceModificationType, error) { + params := map[string]string{ + "RegionId": self.RegionId, + "ResourceId": instanceId, + "DestinationResource": "InstanceType", + } + body, err := self.ecsRequest("DescribeResourcesModification", params) + if err != nil { + return nil, errors.Wrapf(err, "DescribeResourcesModification") + } + + parsed := struct { + AvailableZones struct { + AvailableZone []struct { + AvailableResources struct { + AvailableResource []struct { + SupportedResources struct { + SupportedResource []struct { + Value string + Status string + StatusCategory string + } + } + } + } + } + } + }{} + if err := body.Unmarshal(&parsed); err != nil { + return nil, errors.Wrapf(err, "body.Unmarshal") + } + + ret := make([]cloudprovider.SInstanceModificationType, 0) + seen := map[string]struct{}{} + for _, zone := range parsed.AvailableZones.AvailableZone { + for _, resource := range zone.AvailableResources.AvailableResource { + for _, supportedResource := range resource.SupportedResources.SupportedResource { + if len(supportedResource.Value) == 0 || len(supportedResource.Status) == 0 || strings.ToLower(supportedResource.Status) != "available" { + continue + } + if _, ok := seen[supportedResource.Value]; ok { + continue + } + seen[supportedResource.Value] = struct{}{} + ret = append(ret, cloudprovider.SInstanceModificationType{InstanceType: supportedResource.Value}) + } + } + } + return ret, nil +} + func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error { return self.host.zone.region.AttachDisk(self.InstanceId, diskId) } diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/huawei/instance.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/huawei/instance.go index c199b41a6f..e6b50d821b 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/huawei/instance.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/huawei/instance.go @@ -682,6 +682,31 @@ func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.S return fmt.Errorf("Failed to change vm config, specification not supported") } +func (self *SInstance) GetModificationTypes() ([]cloudprovider.SInstanceModificationType, error) { + return self.host.zone.region.GetInstanceModificationTypes(self.GetId()) +} + +// https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=ListResizeFlavors +func (self *SRegion) GetInstanceModificationTypes(instanceId string) ([]cloudprovider.SInstanceModificationType, error) { + params := url.Values{} + params.Set("instance_uuid", instanceId) + resp, err := self.list(SERVICE_ECS, "cloudservers/resize_flavors", params) + if err != nil { + return nil, errors.Wrapf(err, "ListResizeFlavors(%s)", instanceId) + } + flavors := []SInstanceType{} + err = resp.Unmarshal(&flavors, "flavors") + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal(flavors)") + } + + ret := make([]cloudprovider.SInstanceModificationType, 0, len(flavors)) + for _, flavor := range flavors { + ret = append(ret, cloudprovider.SInstanceModificationType{InstanceType: flavor.ID}) + } + return ret, nil +} + func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) { return self.host.zone.region.GetInstanceVNCUrl(self.GetId()) } diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/instance_base.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/instance_base.go index b3bc8e4ffe..8c4824ad9a 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/instance_base.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/instance_base.go @@ -98,3 +98,7 @@ func (ins *SInstanceBase) GetHealthStatus() string { func (instance *SInstanceBase) GetError() error { return nil } + +func (instance *SInstanceBase) GetModificationTypes() ([]cloudprovider.SInstanceModificationType, error) { + return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "GetModificationTypes") +} diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/instance.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/instance.go index 3390ff75ef..e2fd8ae6c4 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/instance.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/instance.go @@ -496,6 +496,50 @@ func (self *SInstance) ChangeConfig(ctx context.Context, opts *cloudprovider.SMa return self.host.zone.region.ChangeVMConfig(self.InstanceId, opts.InstanceType) } +func (self *SInstance) GetModificationTypes() ([]cloudprovider.SInstanceModificationType, error) { + return self.host.zone.region.GetInstanceModificationTypes(self.InstanceId) +} + +func (self *SRegion) GetInstanceModificationTypes(instanceId string) ([]cloudprovider.SInstanceModificationType, error) { + params := map[string]string{ + "Region": self.Region, + "InstanceIds.0": instanceId, + } + body, err := self.cvmRequest("DescribeInstancesModification", params, true) + if err != nil { + return nil, errors.Wrapf(err, "DescribeInstancesModification") + } + + resp := struct { + InstanceTypeConfigStatusSet []struct { + InstanceTypeConfig struct { + InstanceType string + Zone string + } + Status string + } + }{} + + if err := body.Unmarshal(&resp); err != nil { + return nil, errors.Wrapf(err, "body.Unmarshal") + } + + ret := make([]cloudprovider.SInstanceModificationType, 0) + seen := map[string]struct{}{} + for _, cfg := range resp.InstanceTypeConfigStatusSet { + if !strings.EqualFold(cfg.Status, "SELL") { + continue + } + + if _, ok := seen[cfg.InstanceTypeConfig.InstanceType]; ok { + continue + } + seen[cfg.InstanceTypeConfig.InstanceType] = struct{}{} + ret = append(ret, cloudprovider.SInstanceModificationType{InstanceType: cfg.InstanceTypeConfig.InstanceType}) + } + return ret, nil +} + func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error { return self.host.zone.region.AttachDisk(self.InstanceId, diskId) } diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/remotefile/instance.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/remotefile/instance.go index 588d539aba..f51091fc86 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/remotefile/instance.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/remotefile/instance.go @@ -288,3 +288,7 @@ func (self *SInstance) GetPowerStates() string { func (self *SInstance) GetHealthStatus() string { return "" } + +func (self *SInstance) GetModificationTypes() ([]cloudprovider.SInstanceModificationType, error) { + return nil, cloudprovider.ErrNotSupported +}