// 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 apsara import ( "context" "fmt" "sort" "strings" "time" "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/billing" "yunion.io/x/pkg/util/imagetools" "yunion.io/x/pkg/util/osprofile" "yunion.io/x/pkg/util/seclib" "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" ) const ( // Running:运行中 //Starting:启动中 //Stopping:停止中 //Stopped:已停止 InstanceStatusStopped = "Stopped" InstanceStatusRunning = "Running" InstanceStatusStopping = "Stopping" InstanceStatusStarting = "Starting" ) type SDedicatedHostAttribute struct { DedicatedHostId string DedicatedHostName string } type SIpAddress struct { IpAddress []string } type SNetworkInterfaces struct { NetworkInterface []SNetworkInterface } type SOperationLocks struct { LockReason []string } type SSecurityGroupIds struct { SecurityGroupId []string } // {"NatIpAddress":"","PrivateIpAddress":{"IpAddress":["192.168.220.214"]},"VSwitchId":"vsw-2ze9cqwza4upoyujq1thd","VpcId":"vpc-2zer4jy8ix3i8f0coc5uw"} type SVpcAttributes struct { NatIpAddress string PrivateIpAddress SIpAddress VSwitchId string VpcId string } type SInstance struct { multicloud.SInstanceBase ApsaraTags host *SHost osInfo *imagetools.ImageInfo // idisks []cloudprovider.ICloudDisk AutoReleaseTime string ClusterId string Cpu int CreationTime time.Time DedicatedHostAttribute SDedicatedHostAttribute Description string DeviceAvailable bool EipAddress SEipAddress ExpiredTime time.Time GPUAmount int GPUSpec string HostName string ImageId string InnerIpAddress SIpAddress InstanceChargeType TChargeType InstanceId string InstanceName string InstanceNetworkType string InstanceType string InstanceTypeFamily string InternetChargeType TInternetChargeType InternetMaxBandwidthIn int InternetMaxBandwidthOut int IoOptimized bool KeyPairName string Memory int NetworkInterfaces SNetworkInterfaces OSName string OSType string OperationLocks SOperationLocks PublicIpAddress SIpAddress Recyclable bool RegionId string SaleCycle string SecurityGroupIds SSecurityGroupIds SerialNumber string SpotPriceLimit string SpotStrategy string StartTime time.Time Status string StoppedMode string VlanId string VpcAttributes SVpcAttributes ZoneId string DepartmentInfo } // {"AutoReleaseTime":"","ClusterId":"","Cpu":1,"CreationTime":"2018-05-23T07:58Z","DedicatedHostAttribute":{"DedicatedHostId":"","DedicatedHostName":""},"Description":"","DeviceAvailable":true,"EipAddress":{"AllocationId":"","InternetChargeType":"","IpAddress":""},"ExpiredTime":"2018-05-30T16:00Z","GPUAmount":0,"GPUSpec":"","HostName":"iZ2ze57isp1ali72tzkjowZ","ImageId":"centos_7_04_64_20G_alibase_201701015.vhd","InnerIpAddress":{"IpAddress":[]},"InstanceChargeType":"PrePaid","InstanceId":"i-2ze57isp1ali72tzkjow","InstanceName":"gaoxianqi-test-7days","InstanceNetworkType":"vpc","InstanceType":"ecs.t5-lc2m1.nano","InstanceTypeFamily":"ecs.t5","InternetChargeType":"PayByBandwidth","InternetMaxBandwidthIn":-1,"InternetMaxBandwidthOut":0,"IoOptimized":true,"Memory":512,"NetworkInterfaces":{"NetworkInterface":[{"MacAddress":"00:16:3e:10:f0:c9","NetworkInterfaceId":"eni-2zecqsagtpztl6x5hu2r","PrimaryIpAddress":"192.168.220.214"}]},"OSName":"CentOS 7.4 64位","OSType":"linux","OperationLocks":{"LockReason":[]},"PublicIpAddress":{"IpAddress":[]},"Recyclable":false,"RegionId":"cn-beijing","ResourceGroupId":"","SaleCycle":"Week","SecurityGroupIds":{"SecurityGroupId":["sg-2zecqsagtpztl6x9zynl"]},"SerialNumber":"df05d9b4-df3d-4400-88d1-5f843f0dd088","SpotPriceLimit":0.000000,"SpotStrategy":"NoSpot","StartTime":"2018-05-23T07:58Z","Status":"Running","StoppedMode":"Not-applicable","VlanId":"","VpcAttributes":{"NatIpAddress":"","PrivateIpAddress":{"IpAddress":["192.168.220.214"]},"VSwitchId":"vsw-2ze9cqwza4upoyujq1thd","VpcId":"vpc-2zer4jy8ix3i8f0coc5uw"},"ZoneId":"cn-beijing-f"} func (self *SRegion) GetInstances(zoneId string, ids []string, offset int, limit int) ([]SInstance, int, error) { if limit > 50 || limit <= 0 { limit = 50 } params := make(map[string]string) params["RegionId"] = self.RegionId params["PageSize"] = fmt.Sprintf("%d", limit) params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) if len(zoneId) > 0 { params["ZoneId"] = zoneId } if ids != nil && len(ids) > 0 { params["InstanceIds"] = jsonutils.Marshal(ids).String() } body, err := self.ecsRequest("DescribeInstances", params) if err != nil { log.Errorf("GetInstances fail %s", err) return nil, 0, err } instances := make([]SInstance, 0) err = body.Unmarshal(&instances, "Instances", "Instance") if err != nil { log.Errorf("Unmarshal security group details fail %s", err) return nil, 0, err } total, _ := body.Int("TotalCount") return instances, int(total), nil } func (self *SInstance) GetSecurityGroupIds() ([]string, error) { return self.SecurityGroupIds.SecurityGroupId, nil } func (self *SInstance) GetIHost() cloudprovider.ICloudHost { return self.host } func (self *SInstance) GetId() string { return self.InstanceId } func (self *SInstance) GetName() string { if len(self.InstanceName) > 0 { return self.InstanceName } return self.InstanceId } func (self *SInstance) GetHostname() string { return self.HostName } func (self *SInstance) GetGlobalId() string { return self.InstanceId } func (self *SInstance) IsEmulated() bool { return false } func (self *SInstance) GetInstanceType() string { return self.InstanceType } func (self *SInstance) GetInternetMaxBandwidthOut() int { return self.InternetMaxBandwidthOut } func (self *SInstance) getVpc() (*SVpc, error) { return self.host.zone.region.getVpc(self.VpcAttributes.VpcId) } type byAttachedTime []SDisk func (a byAttachedTime) Len() int { return len(a) } func (a byAttachedTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a byAttachedTime) Less(i, j int) bool { switch a[i].GetDiskType() { case api.DISK_TYPE_SYS: return true case api.DISK_TYPE_SWAP: switch a[j].GetDiskType() { case api.DISK_TYPE_SYS: return false case api.DISK_TYPE_DATA: return true } case api.DISK_TYPE_DATA: if a[j].GetDiskType() != api.DISK_TYPE_DATA { return false } } return a[i].AttachedTime.Before(a[j].AttachedTime) } func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { disks := []SDisk{} disks, err := self.host.zone.region.GetDisks(self.InstanceId, "", "", nil, "") if err != nil { return nil, errors.Wrapf(err, "GetDisks for %s", self.InstanceId) } sort.Sort(byAttachedTime(disks)) idisks := make([]cloudprovider.ICloudDisk, len(disks)) for i := 0; i < len(disks); i += 1 { store, err := self.host.zone.getStorageByCategory(disks[i].Category) if err != nil { return nil, err } disks[i].storage = store idisks[i] = &disks[i] } return idisks, nil } func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) { var ( networkInterfaces = self.NetworkInterfaces.NetworkInterface nics []cloudprovider.ICloudNic ) for _, networkInterface := range networkInterfaces { nic := SInstanceNic{ instance: self, id: networkInterface.NetworkInterfaceId, ipAddr: networkInterface.PrimaryIpAddress, macAddr: networkInterface.MacAddress, } nics = append(nics, &nic) } return nics, nil } func (self *SInstance) GetVcpuCount() int { return self.Cpu } func (self *SInstance) GetVmemSizeMB() int { return self.Memory } func (self *SInstance) GetBootOrder() string { return "dcn" } func (self *SInstance) GetVga() string { return "std" } func (self *SInstance) GetVdi() string { return "vnc" } func (self *SInstance) GetOsType() cloudprovider.TOsType { return cloudprovider.TOsType(osprofile.NormalizeOSType(self.OSType)) } func (self *SInstance) GetFullOsName() string { return self.OSName } func (ins *SInstance) getNormalizedOsInfo() *imagetools.ImageInfo { if ins.osInfo == nil { osInfo := imagetools.NormalizeImageInfo(ins.OSName, "", ins.OSType, "", "") ins.osInfo = &osInfo } return ins.osInfo } func (ins *SInstance) GetBios() cloudprovider.TBiosType { return cloudprovider.ToBiosType(ins.getNormalizedOsInfo().OsBios) } func (ins *SInstance) GetOsArch() string { return ins.getNormalizedOsInfo().OsArch } func (ins *SInstance) GetOsDist() string { return ins.getNormalizedOsInfo().OsDistro } func (ins *SInstance) GetOsVersion() string { return ins.getNormalizedOsInfo().OsVersion } func (ins *SInstance) GetOsLang() string { return ins.getNormalizedOsInfo().OsLang } func (self *SInstance) GetMachine() string { return "pc" } func (self *SInstance) GetStatus() string { // Running:运行中 //Starting:启动中 //Stopping:停止中 //Stopped:已停止 switch self.Status { case InstanceStatusRunning: return api.VM_RUNNING case InstanceStatusStarting: return api.VM_STARTING case InstanceStatusStopping: return api.VM_STOPPING case InstanceStatusStopped: return api.VM_READY default: return api.VM_UNKNOWN } } func (self *SInstance) Refresh() error { new, err := self.host.zone.region.GetInstance(self.InstanceId) if err != nil { return err } return jsonutils.Update(self, new) } func (self *SInstance) GetHypervisor() string { return api.HYPERVISOR_APSARA } func (self *SInstance) StartVM(ctx context.Context) error { timeout := 300 * time.Second interval := 15 * time.Second startTime := time.Now() for time.Now().Sub(startTime) < timeout { err := self.Refresh() if err != nil { return err } log.Debugf("status %s expect %s", self.GetStatus(), api.VM_RUNNING) if self.GetStatus() == api.VM_RUNNING { return nil } else if self.GetStatus() == api.VM_READY { err := self.host.zone.region.StartVM(self.InstanceId) if err != nil { return err } } time.Sleep(interval) } return cloudprovider.ErrTimeout } func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { err := self.host.zone.region.StopVM(self.InstanceId, opts.IsForce) if err != nil { return err } return cloudprovider.WaitStatus(self, api.VM_READY, 10*time.Second, 300*time.Second) // 5mintues } func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) { url, err := self.host.zone.region.GetInstanceVNCUrl(self.InstanceId) if err != nil { return nil, err } passwd := seclib.RandomPassword(6) err = self.host.zone.region.ModifyInstanceVNCUrlPassword(self.InstanceId, passwd) if err != nil { return nil, err } ret := &cloudprovider.ServerVncOutput{ Url: url, Password: passwd, Protocol: "apsara", InstanceId: self.InstanceId, Hypervisor: api.HYPERVISOR_APSARA, } return ret, nil } func (self *SInstance) UpdateVM(ctx context.Context, input cloudprovider.SInstanceUpdateOptions) error { return self.host.zone.region.UpdateVM(self.InstanceId, input) } func (self *SInstance) DeployVM(ctx context.Context, opts *cloudprovider.SInstanceDeployOptions) error { return self.host.zone.region.DeployVM(self.InstanceId, opts) } func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) { keypair := "" if len(desc.PublicKey) > 0 { var err error keypair, err = self.host.zone.region.syncKeypair(desc.PublicKey) if err != nil { return "", err } } diskId, err := self.host.zone.region.ReplaceSystemDisk(self.InstanceId, desc.ImageId, desc.Password, keypair, desc.SysSizeGB) if err != nil { return "", err } return diskId, nil } func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error { if len(config.InstanceType) > 0 { return self.host.zone.region.ChangeVMConfig2(self.ZoneId, self.InstanceId, config.InstanceType, nil) } return self.host.zone.region.ChangeVMConfig(self.ZoneId, self.InstanceId, config.Cpu, config.MemoryMB, nil) } func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error { return self.host.zone.region.AttachDisk(self.InstanceId, diskId) } func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error { return cloudprovider.RetryOnError( func() error { return self.host.zone.region.DetachDisk(self.InstanceId, diskId) }, []string{ `"Code":"InvalidOperation.Conflict"`, }, 4) } func (self *SRegion) GetInstance(instanceId string) (*SInstance, error) { instances, _, err := self.GetInstances("", []string{instanceId}, 0, 1) if err != nil { return nil, err } if len(instances) == 0 { return nil, cloudprovider.ErrNotFound } return &instances[0], nil } func (self *SRegion) CreateInstance(name, hostname string, imageId string, instanceType string, securityGroupIds []string, zoneId string, desc string, passwd string, disks []SDisk, vSwitchId string, ipAddr string, keypair string, userData string, bc *billing.SBillingCycle, projectId, osType string, tags map[string]string, ) (string, error) { params := make(map[string]string) params["RegionId"] = self.RegionId params["ImageId"] = imageId params["InstanceType"] = instanceType for _, id := range securityGroupIds { params["SecurityGroupId"] = id } params["ZoneId"] = zoneId params["InstanceName"] = name if len(hostname) > 0 { params["HostName"] = hostname } params["Description"] = desc params["InternetChargeType"] = "PayByTraffic" params["InternetMaxBandwidthIn"] = "200" params["InternetMaxBandwidthOut"] = "100" if len(passwd) > 0 { params["Password"] = passwd } else { params["PasswordInherit"] = "True" } if len(projectId) > 0 { params["ResourceGroupId"] = projectId } //{"Code":"InvalidSystemDiskCategory.ValueNotSupported","HostId":"ecs.apsaracs.com","Message":"The specified parameter 'SystemDisk.Category' is not support IoOptimized Instance. Valid Values: cloud_efficiency;cloud_ssd. ","RequestId":"9C9A4E99-5196-42A2-80B6-4762F8F75C90"} params["IoOptimized"] = "optimized" for i, d := range disks { if i == 0 { params["SystemDisk.Category"] = d.Category if d.Category == api.STORAGE_CLOUD_ESSD_PL2 { params["SystemDisk.Category"] = api.STORAGE_CLOUD_ESSD params["SystemDisk.PerformanceLevel"] = "PL2" } if d.Category == api.STORAGE_CLOUD_ESSD_PL3 { params["SystemDisk.Category"] = api.STORAGE_CLOUD_ESSD params["SystemDisk.PerformanceLevel"] = "PL3" } params["SystemDisk.Size"] = fmt.Sprintf("%d", d.Size) params["SystemDisk.DiskName"] = d.GetName() params["SystemDisk.Description"] = d.Description } else { params[fmt.Sprintf("DataDisk.%d.Size", i)] = fmt.Sprintf("%d", d.Size) params[fmt.Sprintf("DataDisk.%d.Category", i)] = d.Category if d.Category == api.STORAGE_CLOUD_ESSD_PL2 { params[fmt.Sprintf("DataDisk.%d.Category", i)] = api.STORAGE_CLOUD_ESSD params[fmt.Sprintf("DataDisk.%d..PerformanceLevel", i)] = "PL2" } if d.Category == api.STORAGE_CLOUD_ESSD_PL3 { params[fmt.Sprintf("DataDisk.%d.Category", i)] = api.STORAGE_CLOUD_ESSD params[fmt.Sprintf("DataDisk.%d..PerformanceLevel", i)] = "PL3" } params[fmt.Sprintf("DataDisk.%d.DiskName", i)] = d.GetName() params[fmt.Sprintf("DataDisk.%d.Description", i)] = d.Description params[fmt.Sprintf("DataDisk.%d.Encrypted", i)] = "false" } } params["VSwitchId"] = vSwitchId params["PrivateIpAddress"] = ipAddr if len(keypair) > 0 { params["KeyPairName"] = keypair } if len(userData) > 0 { params["UserData"] = userData } if len(tags) > 0 { tagIdx := 0 for k, v := range tags { params[fmt.Sprintf("Tag.%d.Key", tagIdx)] = k params[fmt.Sprintf("Tag.%d.Value", tagIdx)] = v tagIdx += 1 } } if bc != nil { params["InstanceChargeType"] = "PrePaid" err := billingCycle2Params(bc, params) if err != nil { return "", err } if bc.AutoRenew { params["AutoRenew"] = "true" params["AutoRenewPeriod"] = "1" } else { params["AutoRenew"] = "False" } } else { params["InstanceChargeType"] = "PostPaid" params["SpotStrategy"] = "NoSpot" } params["ClientToken"] = utils.GenRequestId(20) body, err := self.ecsRequest("CreateInstance", params) if err != nil { log.Errorf("CreateInstance fail %s", err) return "", err } instanceId, _ := body.GetString("InstanceId") return instanceId, nil } func (self *SRegion) doStartVM(instanceId string) error { return self.instanceOperation(instanceId, "StartInstance", nil) } func (self *SRegion) doStopVM(instanceId string, isForce bool) error { params := make(map[string]string) if isForce { params["ForceStop"] = "true" } else { params["ForceStop"] = "false" } params["StoppedMode"] = "KeepCharging" return self.instanceOperation(instanceId, "StopInstance", params) } func (self *SRegion) doDeleteVM(instanceId string) error { params := make(map[string]string) params["TerminateSubscription"] = "true" // terminate expired prepaid instance params["Force"] = "true" return self.instanceOperation(instanceId, "DeleteInstance", params) } /*func (self *SRegion) waitInstanceStatus(instanceId string, target string, interval time.Duration, timeout time.Duration) error { startTime := time.Now() for time.Now().Sub(startTime) < timeout { status, err := self.GetInstanceStatus(instanceId) if err != nil { return err } if status == target { return nil } time.Sleep(interval) } return cloudprovider.ErrTimeout } func (self *SInstance) waitStatus(target string, interval time.Duration, timeout time.Duration) error { return self.host.zone.region.waitInstanceStatus(self.InstanceId, target, interval, timeout) }*/ func (self *SRegion) StartVM(instanceId string) error { status, err := self.GetInstanceStatus(instanceId) if err != nil { log.Errorf("Fail to get instance status on StartVM: %s", err) return err } if status != InstanceStatusStopped { log.Errorf("StartVM: vm status is %s expect %s", status, InstanceStatusStopped) return cloudprovider.ErrInvalidStatus } return self.doStartVM(instanceId) // if err != nil { // return err // } // return self.waitInstanceStatus(instanceId, InstanceStatusRunning, time.Second*5, time.Second*180) // 3 minutes to timeout } func (self *SRegion) StopVM(instanceId string, isForce bool) error { status, err := self.GetInstanceStatus(instanceId) if err != nil { log.Errorf("Fail to get instance status on StopVM: %s", err) return err } if status == InstanceStatusStopped { return nil } if status != InstanceStatusRunning { log.Errorf("StopVM: vm status is %s expect %s", status, InstanceStatusRunning) return cloudprovider.ErrInvalidStatus } return self.doStopVM(instanceId, isForce) // if err != nil { // return err // } // return self.waitInstanceStatus(instanceId, InstanceStatusStopped, time.Second*10, time.Second*300) // 5 minutes to timeout } func (self *SRegion) DeleteVM(instanceId string) error { status, err := self.GetInstanceStatus(instanceId) if err != nil { log.Errorf("Fail to get instance status on DeleteVM: %s", err) return err } log.Debugf("Instance status on delete is %s", status) if status != InstanceStatusStopped { log.Warningf("DeleteVM: vm status is %s expect %s", status, InstanceStatusStopped) } return self.doDeleteVM(instanceId) // if err != nil { // return err // } // err = self.waitInstanceStatus(instanceId, InstanceStatusRunning, time.Second*10, time.Second*300) // 5 minutes to timeout // if err == cloudprovider.ErrNotFound { // return nil // } else if err == nil { // return cloudprovider.ErrTimeout // } else { // return err // } } func (self *SRegion) DeployVM(instanceId string, opts *cloudprovider.SInstanceDeployOptions) error { instance, err := self.GetInstance(instanceId) if err != nil { return err } // 修改密钥时直接返回 if opts.DeleteKeypair { err = self.DetachKeyPair(instanceId, instance.KeyPairName) if err != nil { return err } } var keypairName string if len(opts.PublicKey) > 0 { var err error keypairName, err = self.syncKeypair(opts.PublicKey) if err != nil { return err } err = self.AttachKeypair(instanceId, keypairName) if err != nil { return err } } // 指定密码的情况下,使用指定的密码 if len(opts.Password) > 0 { params := make(map[string]string) params["Password"] = opts.Password return self.modifyInstanceAttribute(instanceId, params) } return nil } func (self *SInstance) DeleteVM(ctx context.Context) error { for { err := self.host.zone.region.DeleteVM(self.InstanceId) if err != nil { if isError(err, "IncorrectInstanceStatus.Initializing") { log.Infof("The instance is initializing, try later ...") time.Sleep(10 * time.Second) } else { log.Errorf("DeleteVM fail: %s", err) return err } } else { break } } return cloudprovider.WaitDeleted(self, 10*time.Second, 300*time.Second) // 5minutes } func (self *SRegion) UpdateVM(instanceId string, input cloudprovider.SInstanceUpdateOptions) error { /* api: ModifyInstanceAttribute https://help.apsara.com/document_detail/25503.html?spm=a2c4g.11186623.4.1.DrgpjW */ params := make(map[string]string) params["InstanceName"] = input.NAME params["Description"] = input.Description return self.modifyInstanceAttribute(instanceId, params) } func (self *SRegion) modifyInstanceAttribute(instanceId string, params map[string]string) error { params["x-acs-instanceid"] = instanceId return self.instanceOperation(instanceId, "ModifyInstanceAttribute", params) } func (self *SRegion) ReplaceSystemDisk(instanceId string, imageId string, passwd string, keypairName string, sysDiskSizeGB int) (string, error) { params := make(map[string]string) params["RegionId"] = self.RegionId params["InstanceId"] = instanceId params["ImageId"] = imageId if len(passwd) > 0 { params["Password"] = passwd } else { params["PasswordInherit"] = "True" } if len(keypairName) > 0 { params["KeyPairName"] = keypairName } if sysDiskSizeGB > 0 { params["SystemDisk.Size"] = fmt.Sprintf("%d", sysDiskSizeGB) } params["x-acs-instanceid"] = instanceId body, err := self.ecsRequest("ReplaceSystemDisk", params) if err != nil { return "", err } // log.Debugf("%s", body.String()) return body.GetString("DiskId") } func (self *SRegion) ChangeVMConfig(zoneId string, instanceId string, ncpu int, vmem int, disks []*SDisk) error { // todo: support change disk config? params := make(map[string]string) instanceTypes, e := self.GetMatchInstanceTypes(ncpu, vmem, 0, zoneId) if e != nil { return e } for _, instancetype := range instanceTypes { params["InstanceType"] = instancetype.InstanceTypeId params["ClientToken"] = utils.GenRequestId(20) if err := self.instanceOperation(instanceId, "ModifyInstanceSpec", params); err != nil { log.Errorf("Failed for %s: %s", instancetype.InstanceTypeId, err) } else { return nil } } return fmt.Errorf("Failed to change vm config, specification not supported") } func (self *SRegion) ChangeVMConfig2(zoneId string, instanceId string, instanceType string, disks []*SDisk) error { // todo: support change disk config? params := make(map[string]string) params["InstanceType"] = instanceType params["ClientToken"] = utils.GenRequestId(20) if err := self.instanceOperation(instanceId, "ModifyInstanceSpec", params); err != nil { log.Errorf("Failed for %s: %s", instanceType, err) return fmt.Errorf("Failed to change vm config, specification not supported") } else { return nil } } func (self *SRegion) DetachDisk(instanceId string, diskId string) error { params := make(map[string]string) params["InstanceId"] = instanceId params["DiskId"] = diskId log.Infof("Detach instance %s disk %s", instanceId, diskId) params["x-acs-instanceid"] = instanceId _, err := self.ecsRequest("DetachDisk", params) if err != nil { if strings.Contains(err.Error(), "The specified disk has not been attached on the specified instance") { return nil } return errors.Wrap(err, "DetachDisk") } return nil } func (self *SRegion) AttachDisk(instanceId string, diskId string) error { params := make(map[string]string) params["InstanceId"] = instanceId params["DiskId"] = diskId params["x-acs-instanceid"] = instanceId _, err := self.ecsRequest("AttachDisk", params) if err != nil { log.Errorf("AttachDisk %s to %s fail %s", diskId, instanceId, err) return err } return nil } func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { if len(self.EipAddress.IpAddress) > 0 { return self.host.zone.region.GetEip(self.EipAddress.AllocationId) } if len(self.PublicIpAddress.IpAddress) > 0 { eip := SEipAddress{} eip.region = self.host.zone.region eip.IpAddress = self.PublicIpAddress.IpAddress[0] eip.InstanceId = self.InstanceId eip.InstanceType = EIP_INSTANCE_TYPE_ECS eip.Status = EIP_STATUS_INUSE eip.AllocationId = self.InstanceId // fixed eip.AllocationTime = self.CreationTime eip.Bandwidth = self.InternetMaxBandwidthOut eip.InternetChargeType = self.InternetChargeType return &eip, nil } return nil, nil } func (self *SInstance) SetSecurityGroups(secgroupIds []string) error { return self.host.zone.region.SetSecurityGroups(secgroupIds, self.InstanceId) } func (self *SInstance) GetBillingType() string { return convertChargeType(self.InstanceChargeType) } func (self *SInstance) GetCreatedAt() time.Time { return self.CreationTime } func (self *SInstance) UpdateUserData(userData string) error { return self.host.zone.region.updateInstance(self.InstanceId, "", "", "", "", userData) } func (self *SInstance) Renew(bc billing.SBillingCycle) error { return self.host.zone.region.RenewInstance(self.InstanceId, bc) } func billingCycle2Params(bc *billing.SBillingCycle, params map[string]string) error { if bc.GetMonths() > 0 { params["PeriodUnit"] = "Month" params["Period"] = fmt.Sprintf("%d", bc.GetMonths()) } else if bc.GetWeeks() > 0 { params["PeriodUnit"] = "Week" params["Period"] = fmt.Sprintf("%d", bc.GetWeeks()) } else { return fmt.Errorf("invalid renew time period %s", bc.String()) } return nil } func (region *SRegion) RenewInstance(instanceId string, bc billing.SBillingCycle) error { params := make(map[string]string) params["InstanceId"] = instanceId err := billingCycle2Params(&bc, params) if err != nil { return err } params["ClientToken"] = utils.GenRequestId(20) params["x-acs-instanceid"] = instanceId _, err = region.ecsRequest("RenewInstance", params) if err != nil { log.Errorf("RenewInstance fail %s", err) return err } return nil } func (self *SInstance) GetError() error { return nil } func (region *SRegion) ConvertPublicIpToEip(instanceId string) error { params := make(map[string]string) params["InstanceId"] = instanceId params["RegionId"] = region.RegionId _, err := region.ecsRequest("ConvertNatPublicIpToEip", params) return err } func (self *SInstance) ConvertPublicIpToEip() error { return self.host.zone.region.ConvertPublicIpToEip(self.InstanceId) } func (region *SRegion) SetInstanceAutoRenew(instanceId string, autoRenew bool) error { params := make(map[string]string) params["InstanceId"] = instanceId params["RegionId"] = region.RegionId if autoRenew { params["RenewalStatus"] = "AutoRenewal" params["Duration"] = "1" } else { params["RenewalStatus"] = "Normal" } params["x-acs-instanceid"] = instanceId _, err := region.ecsRequest("ModifyInstanceAutoRenewAttribute", params) return err } type SAutoRenewAttr struct { Duration int AutoRenewEnabled bool RenewalStatus string PeriodUnit string } func (region *SRegion) GetInstanceAutoRenewAttribute(instanceId string) (*SAutoRenewAttr, error) { params := make(map[string]string) params["InstanceId"] = instanceId params["RegionId"] = region.RegionId resp, err := region.ecsRequest("DescribeInstanceAutoRenewAttribute", params) if err != nil { return nil, errors.Wrap(err, "DescribeInstanceAutoRenewAttribute") } attr := []SAutoRenewAttr{} err = resp.Unmarshal(&attr, "InstanceRenewAttributes", "InstanceRenewAttribute") if err != nil { return nil, errors.Wrap(err, "resp.Unmarshal") } if len(attr) == 1 { return &attr[0], nil } return nil, fmt.Errorf("get %d auto renew info", len(attr)) } func (self *SInstance) IsAutoRenew() bool { attr, err := self.host.zone.region.GetInstanceAutoRenewAttribute(self.InstanceId) if err != nil { log.Errorf("failed to get instance %s auto renew info", self.InstanceId) return false } return attr.AutoRenewEnabled } func (self *SInstance) SetAutoRenew(bc billing.SBillingCycle) error { return self.host.zone.region.SetInstanceAutoRenew(self.InstanceId, bc.AutoRenew) } func (self *SInstance) SetTags(tags map[string]string, replace bool) error { return self.host.zone.region.SetResourceTags("ecs", "instance", []string{self.InstanceId}, tags, replace) }