From 448bae503713355e0c87b09f849166bb4bfec17a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Fri, 10 Apr 2026 16:53:45 +0800 Subject: [PATCH] fix(region): support query public cloud vm modification types (#24653) --- 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 ef67a61a33..6f79b92c60 100644 --- a/cmd/climc/shell/compute/servers.go +++ b/cmd/climc/shell/compute/servers.go @@ -138,6 +138,7 @@ func init() { cmd.Perform("kickstart-complete", &options.ServerKickstartCompleteOptions{}) cmd.Get("kickstart", new(options.ServerIdOptions)) 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 a2af6e14bc..892305c405 100644 --- a/go.mod +++ b/go.mod @@ -108,7 +108,7 @@ 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.20260409072734-6e765c0846d6 + yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260409123844-2f5e06efeade 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 a2017be6b5..8e4238d3ff 100644 --- a/go.sum +++ b/go.sum @@ -1785,8 +1785,8 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= -yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260409072734-6e765c0846d6 h1:1+DtEfMWn4AYP/GJMN3aatH2gyOzry0nb//ASMVS7WQ= -yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260409072734-6e765c0846d6/go.mod h1:TbMuTWxcTdyL2Usn+WQdIw0jdlBYO66SzPRY8LZ3Wj0= +yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260409123844-2f5e06efeade h1:8DBeLhYpDrOW7oP5s/mGlc458zAN0mZq8ZMbH5b+JTA= +yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260409123844-2f5e06efeade/go.mod h1:TbMuTWxcTdyL2Usn+WQdIw0jdlBYO66SzPRY8LZ3Wj0= yunion.io/x/executor v0.0.0-20260312022053-f538abd2b005 h1:3sWwcjGXGjG9mLBWa7AyLq+QSi0udTAx21pfVQRFMBE= yunion.io/x/executor v0.0.0-20260312022053-f538abd2b005/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws= yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051/go.mod h1:4N0/RVzsYL3kH3WE/H1BjUQdFiWu50JGCFQuuy+Z634= diff --git a/pkg/apis/compute/guests.go b/pkg/apis/compute/guests.go index d63a627f3a..775d4a8ffd 100644 --- a/pkg/apis/compute/guests.go +++ b/pkg/apis/compute/guests.go @@ -1572,3 +1572,11 @@ type ServerPerformStatusInput struct { apis.PerformStatusInput Containers map[string]*ContainerPerformStatusInput `json:"containers"` } + +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 f6d5aac25b..731c7e618b 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -98,6 +98,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 05fafa508a..f03334d8f8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2603,7 +2603,7 @@ sigs.k8s.io/structured-merge-diff/v4/value # sigs.k8s.io/yaml v1.3.0 ## explicit; go 1.12 sigs.k8s.io/yaml -# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260409072734-6e765c0846d6 +# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260409123844-2f5e06efeade ## explicit; go 1.24 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 6f3eb4f919..3e3e86104a 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/cloudprovider/instance.go +++ b/vendor/yunion.io/x/cloudmux/pkg/cloudprovider/instance.go @@ -388,3 +388,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 24fe85ac4d..1ef34fab6c 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/cloudprovider/resources.go +++ b/vendor/yunion.io/x/cloudmux/pkg/cloudprovider/resources.go @@ -389,6 +389,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 cc34c99850..6f0a8ad5a5 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 a106b5b0fa..a9c5545964 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/instance_base.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/instance_base.go @@ -106,3 +106,7 @@ func (instance *SInstanceBase) GetIsolateDeviceIds() ([]string, error) { func (instance *SInstanceBase) GetContainers() ([]cloudprovider.ICloudContainer, error) { return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "GetContainers") } + +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 64fa6a9070..e197fbf369 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/remotefile/instance.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/remotefile/instance.go @@ -296,3 +296,7 @@ func (self *SInstance) GetIsolateDeviceIds() ([]string, error) { func (self *SInstance) GetContainers() ([]cloudprovider.ICloudContainer, error) { return nil, cloudprovider.ErrNotSupported } + +func (self *SInstance) GetModificationTypes() ([]cloudprovider.SInstanceModificationType, error) { + return nil, cloudprovider.ErrNotSupported +}