yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/aliyun/instance.go (about) 1 // Copyright 2019 Yunion 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package aliyun 16 17 import ( 18 "context" 19 "fmt" 20 "sort" 21 "strings" 22 "time" 23 24 "yunion.io/x/jsonutils" 25 "yunion.io/x/log" 26 "yunion.io/x/pkg/errors" 27 "yunion.io/x/pkg/util/osprofile" 28 "yunion.io/x/pkg/util/seclib" 29 "yunion.io/x/pkg/utils" 30 31 billing_api "yunion.io/x/cloudmux/pkg/apis/billing" 32 api "yunion.io/x/cloudmux/pkg/apis/compute" 33 "yunion.io/x/cloudmux/pkg/cloudprovider" 34 "yunion.io/x/cloudmux/pkg/multicloud" 35 "yunion.io/x/onecloud/pkg/util/billing" 36 "yunion.io/x/onecloud/pkg/util/imagetools" 37 ) 38 39 const ( 40 // Running:运行中 41 //Starting:启动中 42 //Stopping:停止中 43 //Stopped:已停止 44 45 InstanceStatusStopped = "Stopped" 46 InstanceStatusRunning = "Running" 47 InstanceStatusStopping = "Stopping" 48 InstanceStatusStarting = "Starting" 49 ) 50 51 type SDedicatedHostAttribute struct { 52 DedicatedHostId string 53 DedicatedHostName string 54 } 55 56 type SIpAddress struct { 57 IpAddress []string 58 } 59 60 type SNetworkInterfaces struct { 61 NetworkInterface []SNetworkInterface 62 } 63 64 type SOperationLocks struct { 65 LockReason []string 66 } 67 68 type SSecurityGroupIds struct { 69 SecurityGroupId []string 70 } 71 72 // {"NatIpAddress":"","PrivateIpAddress":{"IpAddress":["192.168.220.214"]},"VSwitchId":"vsw-2ze9cqwza4upoyujq1thd","VpcId":"vpc-2zer4jy8ix3i8f0coc5uw"} 73 74 type SVpcAttributes struct { 75 NatIpAddress string 76 PrivateIpAddress SIpAddress 77 VSwitchId string 78 VpcId string 79 } 80 81 type SInstance struct { 82 multicloud.SInstanceBase 83 AliyunTags 84 85 host *SHost 86 87 osInfo *imagetools.ImageInfo 88 89 // idisks []cloudprovider.ICloudDisk 90 91 AutoReleaseTime string 92 ClusterId string 93 Cpu int 94 CreationTime time.Time 95 DedicatedHostAttribute SDedicatedHostAttribute 96 Description string 97 DeviceAvailable bool 98 EipAddress SEipAddress 99 ExpiredTime time.Time 100 GPUAmount int 101 GPUSpec string 102 HostName string 103 ImageId string 104 InnerIpAddress SIpAddress 105 InstanceChargeType TChargeType 106 InstanceId string 107 InstanceName string 108 InstanceNetworkType string 109 InstanceType string 110 InstanceTypeFamily string 111 InternetChargeType TInternetChargeType 112 InternetMaxBandwidthIn int 113 InternetMaxBandwidthOut int 114 IoOptimized bool 115 KeyPairName string 116 Memory int 117 NetworkInterfaces SNetworkInterfaces 118 OSName string 119 OSType string 120 OperationLocks SOperationLocks 121 PublicIpAddress SIpAddress 122 Recyclable bool 123 RegionId string 124 ResourceGroupId string 125 SaleCycle string 126 SecurityGroupIds SSecurityGroupIds 127 SerialNumber string 128 SpotPriceLimit string 129 SpotStrategy string 130 StartTime time.Time 131 Status string 132 StoppedMode string 133 VlanId string 134 VpcAttributes SVpcAttributes 135 ZoneId string 136 Throughput int 137 } 138 139 // {"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"} 140 141 func (self *SRegion) GetInstances(zoneId string, ids []string, offset int, limit int) ([]SInstance, int, error) { 142 if limit > 50 || limit <= 0 { 143 limit = 50 144 } 145 params := make(map[string]string) 146 params["RegionId"] = self.RegionId 147 params["PageSize"] = fmt.Sprintf("%d", limit) 148 params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) 149 150 if len(zoneId) > 0 { 151 params["ZoneId"] = zoneId 152 } 153 154 if ids != nil && len(ids) > 0 { 155 params["InstanceIds"] = jsonutils.Marshal(ids).String() 156 } 157 158 body, err := self.ecsRequest("DescribeInstances", params) 159 if err != nil { 160 log.Errorf("GetInstances fail %s", err) 161 return nil, 0, err 162 } 163 164 instances := make([]SInstance, 0) 165 err = body.Unmarshal(&instances, "Instances", "Instance") 166 if err != nil { 167 log.Errorf("Unmarshal security group details fail %s", err) 168 return nil, 0, err 169 } 170 total, _ := body.Int("TotalCount") 171 return instances, int(total), nil 172 } 173 174 func (self *SInstance) GetSecurityGroupIds() ([]string, error) { 175 return self.SecurityGroupIds.SecurityGroupId, nil 176 } 177 178 func (self *SInstance) GetIHost() cloudprovider.ICloudHost { 179 return self.host 180 } 181 182 func (self *SInstance) GetId() string { 183 return self.InstanceId 184 } 185 186 func (self *SInstance) GetName() string { 187 if len(self.InstanceName) > 0 { 188 return self.InstanceName 189 } 190 return self.InstanceId 191 } 192 193 func (self *SInstance) GetHostname() string { 194 return self.HostName 195 } 196 197 func (self *SInstance) GetGlobalId() string { 198 return self.InstanceId 199 } 200 201 func (self *SInstance) IsEmulated() bool { 202 return false 203 } 204 205 func (self *SInstance) GetInstanceType() string { 206 return self.InstanceType 207 } 208 209 func (self *SInstance) getVpc() (*SVpc, error) { 210 return self.host.zone.region.getVpc(self.VpcAttributes.VpcId) 211 } 212 213 type byAttachedTime []SDisk 214 215 func (a byAttachedTime) Len() int { return len(a) } 216 func (a byAttachedTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 217 func (a byAttachedTime) Less(i, j int) bool { 218 switch a[i].GetDiskType() { 219 case api.DISK_TYPE_SYS: 220 return true 221 case api.DISK_TYPE_SWAP: 222 switch a[j].GetDiskType() { 223 case api.DISK_TYPE_SYS: 224 return false 225 case api.DISK_TYPE_DATA: 226 return true 227 } 228 case api.DISK_TYPE_DATA: 229 if a[j].GetDiskType() != api.DISK_TYPE_DATA { 230 return false 231 } 232 } 233 return a[i].AttachedTime.Before(a[j].AttachedTime) 234 } 235 236 func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { 237 disks := []SDisk{} 238 for { 239 part, total, err := self.host.zone.region.GetDisks(self.InstanceId, "", "", nil, len(disks), 50) 240 if err != nil { 241 return nil, errors.Wrapf(err, "GetDisks for %s", self.InstanceId) 242 } 243 disks = append(disks, part...) 244 if len(disks) >= total { 245 break 246 } 247 } 248 249 sort.Sort(byAttachedTime(disks)) 250 251 log.Debugf("%s", jsonutils.Marshal(&disks)) 252 253 idisks := make([]cloudprovider.ICloudDisk, len(disks)) 254 for i := 0; i < len(disks); i += 1 { 255 store, err := self.host.zone.getStorageByCategory(disks[i].Category) 256 if err != nil { 257 return nil, err 258 } 259 disks[i].storage = store 260 idisks[i] = &disks[i] 261 } 262 return idisks, nil 263 } 264 265 func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) { 266 var ( 267 networkInterfaces = self.NetworkInterfaces.NetworkInterface 268 nics []cloudprovider.ICloudNic 269 ) 270 for _, networkInterface := range networkInterfaces { 271 nic := SInstanceNic{ 272 instance: self, 273 id: networkInterface.NetworkInterfaceId, 274 ipAddr: networkInterface.PrimaryIpAddress, 275 macAddr: networkInterface.MacAddress, 276 } 277 nics = append(nics, &nic) 278 } 279 for _, classicIp := range self.InnerIpAddress.IpAddress { 280 nic := SInstanceNic{ 281 instance: self, 282 id: fmt.Sprintf("%s-%s", self.InstanceId, classicIp), 283 ipAddr: classicIp, 284 classic: true, 285 } 286 nics = append(nics, &nic) 287 } 288 return nics, nil 289 } 290 291 func (self *SInstance) GetVcpuCount() int { 292 return self.Cpu 293 } 294 295 func (self *SInstance) GetVmemSizeMB() int { 296 return self.Memory 297 } 298 299 func (self *SInstance) GetBootOrder() string { 300 return "dcn" 301 } 302 303 func (self *SInstance) GetVga() string { 304 return "std" 305 } 306 307 func (self *SInstance) GetVdi() string { 308 return "vnc" 309 } 310 311 func (ins *SInstance) getNormalizedOsInfo() *imagetools.ImageInfo { 312 if ins.osInfo == nil { 313 osInfo := imagetools.NormalizeImageInfo(ins.OSName, "", ins.OSType, "", "") 314 ins.osInfo = &osInfo 315 } 316 return ins.osInfo 317 } 318 319 func (self *SInstance) GetOsType() cloudprovider.TOsType { 320 return cloudprovider.TOsType(osprofile.NormalizeOSType(self.OSType)) 321 } 322 323 func (self *SInstance) GetFullOsName() string { 324 return self.OSName 325 } 326 327 func (ins *SInstance) GetBios() cloudprovider.TBiosType { 328 return cloudprovider.ToBiosType(ins.getNormalizedOsInfo().OsBios) 329 } 330 331 func (ins *SInstance) GetOsArch() string { 332 return ins.getNormalizedOsInfo().OsArch 333 } 334 335 func (ins *SInstance) GetOsDist() string { 336 return ins.getNormalizedOsInfo().OsDistro 337 } 338 339 func (ins *SInstance) GetOsVersion() string { 340 return ins.getNormalizedOsInfo().OsVersion 341 } 342 343 func (ins *SInstance) GetOsLang() string { 344 return ins.getNormalizedOsInfo().OsLang 345 } 346 347 func (self *SInstance) GetMachine() string { 348 return "pc" 349 } 350 351 func (self *SInstance) GetStatus() string { 352 // Running:运行中 353 //Starting:启动中 354 //Stopping:停止中 355 //Stopped:已停止 356 switch self.Status { 357 case InstanceStatusRunning: 358 return api.VM_RUNNING 359 case InstanceStatusStarting: 360 return api.VM_STARTING 361 case InstanceStatusStopping: 362 return api.VM_STOPPING 363 case InstanceStatusStopped: 364 return api.VM_READY 365 default: 366 return api.VM_UNKNOWN 367 } 368 } 369 370 func (self *SInstance) Refresh() error { 371 ins, err := self.host.zone.region.GetInstance(self.InstanceId) 372 if err != nil { 373 return err 374 } 375 return jsonutils.Update(self, ins) 376 } 377 378 /* 379 func (self *SInstance) GetRemoteStatus() string { 380 // Running:运行中 381 //Starting:启动中 382 //Stopping:停止中 383 //Stopped:已停止 384 switch self.Status { 385 case InstanceStatusRunning: 386 return cloudprovider.CloudVMStatusRunning 387 case InstanceStatusStarting: 388 return cloudprovider.CloudVMStatusStopped 389 case InstanceStatusStopping: 390 return cloudprovider.CloudVMStatusRunning 391 case InstanceStatusStopped: 392 return cloudprovider.CloudVMStatusStopped 393 default: 394 return cloudprovider.CloudVMStatusOther 395 } 396 } 397 */ 398 399 func (self *SInstance) GetHypervisor() string { 400 return api.HYPERVISOR_ALIYUN 401 } 402 403 func (self *SInstance) StartVM(ctx context.Context) error { 404 timeout := 300 * time.Second 405 interval := 15 * time.Second 406 407 startTime := time.Now() 408 for time.Now().Sub(startTime) < timeout { 409 err := self.Refresh() 410 if err != nil { 411 return err 412 } 413 log.Debugf("status %s expect %s", self.GetStatus(), api.VM_RUNNING) 414 if self.GetStatus() == api.VM_RUNNING { 415 return nil 416 } else if self.GetStatus() == api.VM_READY { 417 err := self.host.zone.region.StartVM(self.InstanceId) 418 if err != nil { 419 return err 420 } 421 } 422 time.Sleep(interval) 423 } 424 return cloudprovider.ErrTimeout 425 } 426 427 func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { 428 err := self.host.zone.region.StopVM(self.InstanceId, opts.IsForce, opts.StopCharging) 429 if err != nil { 430 return err 431 } 432 return cloudprovider.WaitStatus(self, api.VM_READY, 10*time.Second, 300*time.Second) // 5mintues 433 } 434 435 func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) { 436 url, err := self.host.zone.region.GetInstanceVNCUrl(self.InstanceId) 437 if err != nil { 438 return nil, err 439 } 440 passwd := seclib.RandomPassword(6) 441 err = self.host.zone.region.ModifyInstanceVNCUrlPassword(self.InstanceId, passwd) 442 if err != nil { 443 return nil, err 444 } 445 ret := &cloudprovider.ServerVncOutput{ 446 Url: url, 447 Password: passwd, 448 Protocol: "aliyun", 449 InstanceId: self.InstanceId, 450 Hypervisor: api.HYPERVISOR_ALIYUN, 451 } 452 return ret, nil 453 } 454 455 func (self *SInstance) UpdateVM(ctx context.Context, name string) error { 456 return self.host.zone.region.UpdateVM(self.InstanceId, name, self.OSType) 457 } 458 459 func (self *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error { 460 var keypairName string 461 if len(publicKey) > 0 { 462 var err error 463 keypairName, err = self.host.zone.region.syncKeypair(publicKey) 464 if err != nil { 465 return err 466 } 467 } 468 469 return self.host.zone.region.DeployVM(self.InstanceId, name, password, keypairName, deleteKeypair, description) 470 } 471 472 func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) { 473 keypair := "" 474 if len(desc.PublicKey) > 0 { 475 var err error 476 keypair, err = self.host.zone.region.syncKeypair(desc.PublicKey) 477 if err != nil { 478 return "", err 479 } 480 } 481 diskId, err := self.host.zone.region.ReplaceSystemDisk(self.InstanceId, desc.ImageId, desc.Password, keypair, desc.SysSizeGB) 482 if err != nil { 483 return "", err 484 } 485 486 return diskId, nil 487 } 488 489 func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error { 490 isDowngrade, isPrepaid := false, self.GetBillingType() == billing_api.BILLING_TYPE_PREPAID 491 if (self.GetVcpuCount() > config.Cpu && config.Cpu > 0) || (self.GetVmemSizeMB() > config.MemoryMB && config.MemoryMB > 0) { 492 isDowngrade = true 493 } 494 495 instanceTypes := []string{} 496 497 if len(config.InstanceType) > 0 { 498 instanceTypes = []string{config.InstanceType} 499 } else { 500 specs, err := self.host.zone.region.GetMatchInstanceTypes(config.Cpu, config.MemoryMB, 0, self.ZoneId) 501 if err != nil { 502 return errors.Wrapf(err, "GetMatchInstanceTypes") 503 } 504 for _, spec := range specs { 505 instanceTypes = append(instanceTypes, spec.InstanceTypeId) 506 } 507 } 508 509 var err error 510 for _, instanceType := range instanceTypes { 511 if isPrepaid { 512 err = self.host.zone.region.ChangePrepaidVMConfig(self.InstanceId, instanceType, isDowngrade) 513 if err != nil { 514 log.Errorf("ChangePrepaidVMConfig %s error: %v", instanceType, err) 515 continue 516 } 517 } else { 518 err = self.host.zone.region.ChangeVMConfig(self.InstanceId, instanceType) 519 if err != nil { 520 log.Errorf("ChangeVMConfig %s error: %v", instanceType, err) 521 continue 522 } 523 } 524 return nil 525 } 526 if err != nil { 527 return errors.Wrapf(err, "ChangeVMConfig") 528 } 529 530 return fmt.Errorf("Failed to change vm config, specification not supported") 531 } 532 533 func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error { 534 return self.host.zone.region.AttachDisk(self.InstanceId, diskId) 535 } 536 537 func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error { 538 return cloudprovider.RetryOnError( 539 func() error { 540 return self.host.zone.region.DetachDisk(self.InstanceId, diskId) 541 }, 542 []string{ 543 `"Code":"InvalidOperation.Conflict"`, 544 }, 545 4) 546 } 547 548 func (self *SRegion) GetInstance(instanceId string) (*SInstance, error) { 549 instances, _, err := self.GetInstances("", []string{instanceId}, 0, 1) 550 if err != nil { 551 return nil, err 552 } 553 if len(instances) == 0 { 554 return nil, cloudprovider.ErrNotFound 555 } 556 return &instances[0], nil 557 } 558 559 func (self *SRegion) CreateInstance(name, hostname string, imageId string, instanceType string, securityGroupId string, 560 zoneId string, desc string, passwd string, disks []SDisk, vSwitchId string, ipAddr string, 561 keypair string, userData string, bc *billing.SBillingCycle, projectId, osType string, 562 tags map[string]string, publicIp cloudprovider.SPublicIpInfo, 563 ) (string, error) { 564 params := make(map[string]string) 565 params["RegionId"] = self.RegionId 566 params["ImageId"] = imageId 567 params["InstanceType"] = instanceType 568 params["SecurityGroupId"] = securityGroupId 569 params["ZoneId"] = zoneId 570 params["InstanceName"] = name 571 if len(hostname) > 0 { 572 params["HostName"] = hostname 573 } 574 params["Description"] = desc 575 params["InternetChargeType"] = "PayByTraffic" 576 params["InternetMaxBandwidthIn"] = "200" 577 params["InternetMaxBandwidthOut"] = "100" 578 if publicIp.PublicIpBw > 0 { 579 params["InternetMaxBandwidthOut"] = fmt.Sprintf("%d", publicIp.PublicIpBw) 580 } 581 if publicIp.PublicIpChargeType == cloudprovider.ElasticipChargeTypeByBandwidth { 582 params["InternetChargeType"] = "PayByBandwidth" 583 } 584 if len(passwd) > 0 { 585 params["Password"] = passwd 586 } else { 587 params["PasswordInherit"] = "True" 588 } 589 590 if len(projectId) > 0 { 591 params["ResourceGroupId"] = projectId 592 } 593 //{"Code":"InvalidSystemDiskCategory.ValueNotSupported","HostId":"ecs.aliyuncs.com","Message":"The specified parameter 'SystemDisk.Category' is not support IoOptimized Instance. Valid Values: cloud_efficiency;cloud_ssd. ","RequestId":"9C9A4E99-5196-42A2-80B6-4762F8F75C90"} 594 params["IoOptimized"] = "optimized" 595 for i, d := range disks { 596 if i == 0 { 597 params["SystemDisk.Category"] = d.Category 598 if d.Category == api.STORAGE_CLOUD_ESSD_PL2 { 599 params["SystemDisk.Category"] = api.STORAGE_CLOUD_ESSD 600 params["SystemDisk.PerformanceLevel"] = "PL2" 601 } 602 if d.Category == api.STORAGE_CLOUD_ESSD_PL3 { 603 params["SystemDisk.Category"] = api.STORAGE_CLOUD_ESSD 604 params["SystemDisk.PerformanceLevel"] = "PL3" 605 } 606 params["SystemDisk.Size"] = fmt.Sprintf("%d", d.Size) 607 params["SystemDisk.DiskName"] = d.GetName() 608 params["SystemDisk.Description"] = d.Description 609 } else { 610 params[fmt.Sprintf("DataDisk.%d.Size", i)] = fmt.Sprintf("%d", d.Size) 611 params[fmt.Sprintf("DataDisk.%d.Category", i)] = d.Category 612 if d.Category == api.STORAGE_CLOUD_ESSD_PL2 { 613 params[fmt.Sprintf("DataDisk.%d.Category", i)] = api.STORAGE_CLOUD_ESSD 614 params[fmt.Sprintf("DataDisk.%d..PerformanceLevel", i)] = "PL2" 615 } 616 if d.Category == api.STORAGE_CLOUD_ESSD_PL3 { 617 params[fmt.Sprintf("DataDisk.%d.Category", i)] = api.STORAGE_CLOUD_ESSD 618 params[fmt.Sprintf("DataDisk.%d..PerformanceLevel", i)] = "PL3" 619 } 620 params[fmt.Sprintf("DataDisk.%d.DiskName", i)] = d.GetName() 621 params[fmt.Sprintf("DataDisk.%d.Description", i)] = d.Description 622 params[fmt.Sprintf("DataDisk.%d.Encrypted", i)] = "false" 623 } 624 } 625 params["VSwitchId"] = vSwitchId 626 params["PrivateIpAddress"] = ipAddr 627 628 if len(keypair) > 0 { 629 params["KeyPairName"] = keypair 630 } 631 632 if len(userData) > 0 { 633 params["UserData"] = userData 634 } 635 636 if len(tags) > 0 { 637 tagIdx := 1 638 for k, v := range tags { 639 params[fmt.Sprintf("Tag.%d.Key", tagIdx)] = k 640 params[fmt.Sprintf("Tag.%d.Value", tagIdx)] = v 641 tagIdx += 1 642 } 643 } 644 645 if bc != nil { 646 params["InstanceChargeType"] = "PrePaid" 647 err := billingCycle2Params(bc, params) 648 if err != nil { 649 return "", err 650 } 651 if bc.AutoRenew { 652 params["AutoRenew"] = "true" 653 params["AutoRenewPeriod"] = "1" 654 } else { 655 params["AutoRenew"] = "False" 656 } 657 } else { 658 params["InstanceChargeType"] = "PostPaid" 659 params["SpotStrategy"] = "NoSpot" 660 } 661 662 params["ClientToken"] = utils.GenRequestId(20) 663 664 body, err := self.ecsRequest("CreateInstance", params) 665 if err != nil { 666 log.Errorf("CreateInstance fail %s", err) 667 return "", err 668 } 669 instanceId, _ := body.GetString("InstanceId") 670 return instanceId, nil 671 } 672 673 func (self *SRegion) AllocatePublicIpAddress(instanceId string) (string, error) { 674 params := map[string]string{ 675 "InstanceId": instanceId, 676 } 677 resp, err := self.ecsRequest("AllocatePublicIpAddress", params) 678 if err != nil { 679 return "", errors.Wrapf(err, "AllocatePublicIpAddress") 680 } 681 return resp.GetString("IpAddress") 682 } 683 684 func (self *SInstance) AllocatePublicIpAddress() (string, error) { 685 return self.host.zone.region.AllocatePublicIpAddress(self.InstanceId) 686 } 687 688 func (self *SRegion) doStartVM(instanceId string) error { 689 return self.instanceOperation(instanceId, "StartInstance", nil) 690 } 691 692 func (self *SRegion) doStopVM(instanceId string, isForce, stopCharging bool) error { 693 params := make(map[string]string) 694 if isForce { 695 params["ForceStop"] = "true" 696 } else { 697 params["ForceStop"] = "false" 698 } 699 params["StoppedMode"] = "KeepCharging" 700 if stopCharging { 701 params["StoppedMode"] = "StopCharging" 702 } 703 return self.instanceOperation(instanceId, "StopInstance", params) 704 } 705 706 func (self *SRegion) doDeleteVM(instanceId string) error { 707 params := make(map[string]string) 708 params["TerminateSubscription"] = "true" // terminate expired prepaid instance 709 params["Force"] = "true" 710 return self.instanceOperation(instanceId, "DeleteInstance", params) 711 } 712 713 /*func (self *SRegion) waitInstanceStatus(instanceId string, target string, interval time.Duration, timeout time.Duration) error { 714 startTime := time.Now() 715 for time.Now().Sub(startTime) < timeout { 716 status, err := self.GetInstanceStatus(instanceId) 717 if err != nil { 718 return err 719 } 720 if status == target { 721 return nil 722 } 723 time.Sleep(interval) 724 } 725 return cloudprovider.ErrTimeout 726 } 727 728 func (self *SInstance) waitStatus(target string, interval time.Duration, timeout time.Duration) error { 729 return self.host.zone.region.waitInstanceStatus(self.InstanceId, target, interval, timeout) 730 }*/ 731 732 func (self *SRegion) StartVM(instanceId string) error { 733 status, err := self.GetInstanceStatus(instanceId) 734 if err != nil { 735 log.Errorf("Fail to get instance status on StartVM: %s", err) 736 return err 737 } 738 if status != InstanceStatusStopped { 739 log.Errorf("StartVM: vm status is %s expect %s", status, InstanceStatusStopped) 740 return cloudprovider.ErrInvalidStatus 741 } 742 return self.doStartVM(instanceId) 743 // if err != nil { 744 // return err 745 // } 746 // return self.waitInstanceStatus(instanceId, InstanceStatusRunning, time.Second*5, time.Second*180) // 3 minutes to timeout 747 } 748 749 func (self *SRegion) StopVM(instanceId string, isForce, stopCharging bool) error { 750 status, err := self.GetInstanceStatus(instanceId) 751 if err != nil { 752 log.Errorf("Fail to get instance status on StopVM: %s", err) 753 return err 754 } 755 if status == InstanceStatusStopped { 756 return nil 757 } 758 if status != InstanceStatusRunning { 759 log.Errorf("StopVM: vm status is %s expect %s", status, InstanceStatusRunning) 760 return cloudprovider.ErrInvalidStatus 761 } 762 return self.doStopVM(instanceId, isForce, stopCharging) 763 // if err != nil { 764 // return err 765 // } 766 // return self.waitInstanceStatus(instanceId, InstanceStatusStopped, time.Second*10, time.Second*300) // 5 minutes to timeout 767 } 768 769 func (self *SRegion) DeleteVM(instanceId string) error { 770 status, err := self.GetInstanceStatus(instanceId) 771 if err != nil { 772 log.Errorf("Fail to get instance status on DeleteVM: %s", err) 773 return err 774 } 775 log.Debugf("Instance status on delete is %s", status) 776 if status != InstanceStatusStopped { 777 log.Warningf("DeleteVM: vm status is %s expect %s", status, InstanceStatusStopped) 778 } 779 return self.doDeleteVM(instanceId) 780 // if err != nil { 781 // return err 782 // } 783 // err = self.waitInstanceStatus(instanceId, InstanceStatusRunning, time.Second*10, time.Second*300) // 5 minutes to timeout 784 // if err == cloudprovider.ErrNotFound { 785 // return nil 786 // } else if err == nil { 787 // return cloudprovider.ErrTimeout 788 // } else { 789 // return err 790 // } 791 } 792 793 func (self *SRegion) DeployVM(instanceId string, name string, password string, keypairName string, deleteKeypair bool, description string) error { 794 instance, err := self.GetInstance(instanceId) 795 if err != nil { 796 return err 797 } 798 799 // 修改密钥时直接返回 800 if deleteKeypair { 801 err = self.DetachKeyPair(instanceId, instance.KeyPairName) 802 if err != nil { 803 return err 804 } 805 } 806 807 if len(keypairName) > 0 { 808 err = self.AttachKeypair(instanceId, keypairName) 809 if err != nil { 810 return err 811 } 812 } 813 814 params := make(map[string]string) 815 816 // if resetPassword { 817 // params["Password"] = seclib2.RandomPassword2(12) 818 // } 819 // 指定密码的情况下,使用指定的密码 820 if len(password) > 0 { 821 params["Password"] = password 822 } 823 824 if len(name) > 0 && instance.InstanceName != name { 825 params["InstanceName"] = name 826 } 827 828 if len(description) > 0 && instance.Description != description { 829 params["Description"] = description 830 } 831 832 if len(params) > 0 { 833 return self.modifyInstanceAttribute(instanceId, params) 834 } else { 835 return nil 836 } 837 } 838 839 func (self *SInstance) DeleteVM(ctx context.Context) error { 840 for { 841 err := self.host.zone.region.DeleteVM(self.InstanceId) 842 if err != nil { 843 if isError(err, "IncorrectInstanceStatus.Initializing") { 844 log.Infof("The instance is initializing, try later ...") 845 time.Sleep(10 * time.Second) 846 } else { 847 log.Errorf("DeleteVM fail: %s", err) 848 return err 849 } 850 } else { 851 break 852 } 853 } 854 return cloudprovider.WaitDeleted(self, 10*time.Second, 300*time.Second) // 5minutes 855 } 856 857 func (self *SRegion) UpdateVM(instanceId string, name, osType string) error { 858 /* 859 api: ModifyInstanceAttribute 860 https://help.aliyun.com/document_detail/25503.html?spm=a2c4g.11186623.4.1.DrgpjW 861 */ 862 params := make(map[string]string) 863 params["InstanceName"] = name 864 return self.modifyInstanceAttribute(instanceId, params) 865 } 866 867 func (self *SRegion) modifyInstanceAttribute(instanceId string, params map[string]string) error { 868 return self.instanceOperation(instanceId, "ModifyInstanceAttribute", params) 869 } 870 871 func (self *SRegion) ReplaceSystemDisk(instanceId string, imageId string, passwd string, keypairName string, sysDiskSizeGB int) (string, error) { 872 params := make(map[string]string) 873 params["RegionId"] = self.RegionId 874 params["InstanceId"] = instanceId 875 params["ImageId"] = imageId 876 if len(passwd) > 0 { 877 params["Password"] = passwd 878 } else { 879 params["PasswordInherit"] = "True" 880 } 881 if len(keypairName) > 0 { 882 params["KeyPairName"] = keypairName 883 } 884 if sysDiskSizeGB > 0 { 885 params["SystemDisk.Size"] = fmt.Sprintf("%d", sysDiskSizeGB) 886 } 887 body, err := self.ecsRequest("ReplaceSystemDisk", params) 888 if err != nil { 889 return "", err 890 } 891 // log.Debugf("%s", body.String()) 892 return body.GetString("DiskId") 893 } 894 895 func (self *SRegion) ChangePrepaidVMConfig(instanceId string, instanceType string, isDowngrade bool) error { 896 // todo: support change disk config? 897 params := make(map[string]string) 898 params["InstanceType"] = instanceType 899 params["ClientToken"] = utils.GenRequestId(20) 900 if isDowngrade { 901 params["OperatorType"] = "downgrade" 902 } 903 err := self.instanceOperation(instanceId, "ModifyPrepayInstanceSpec", params) 904 if err != nil { 905 return errors.Wrapf(err, "ModifyPrepayInstanceSpec %s", instanceType) 906 } 907 return nil 908 } 909 910 func (self *SRegion) ChangeVMConfig(instanceId string, instanceType string) error { 911 // todo: support change disk config? 912 params := make(map[string]string) 913 params["InstanceType"] = instanceType 914 params["ClientToken"] = utils.GenRequestId(20) 915 err := self.instanceOperation(instanceId, "ModifyInstanceSpec", params) 916 if err != nil { 917 return errors.Wrapf(err, "ModifyInstanceSpec %s", instanceType) 918 } 919 return nil 920 } 921 922 func (self *SRegion) DetachDisk(instanceId string, diskId string) error { 923 params := make(map[string]string) 924 params["InstanceId"] = instanceId 925 params["DiskId"] = diskId 926 log.Infof("Detach instance %s disk %s", instanceId, diskId) 927 _, err := self.ecsRequest("DetachDisk", params) 928 if err != nil { 929 if strings.Contains(err.Error(), "The specified disk has not been attached on the specified instance") { 930 return nil 931 } 932 return errors.Wrap(err, "DetachDisk") 933 } 934 935 return nil 936 } 937 938 func (self *SRegion) AttachDisk(instanceId string, diskId string) error { 939 params := make(map[string]string) 940 params["InstanceId"] = instanceId 941 params["DiskId"] = diskId 942 _, err := self.ecsRequest("AttachDisk", params) 943 if err != nil { 944 log.Errorf("AttachDisk %s to %s fail %s", diskId, instanceId, err) 945 return err 946 } 947 948 return nil 949 } 950 951 func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { 952 if len(self.EipAddress.IpAddress) > 0 { 953 return self.host.zone.region.GetEip(self.EipAddress.AllocationId) 954 } 955 if len(self.PublicIpAddress.IpAddress) > 0 { 956 eip := SEipAddress{} 957 eip.region = self.host.zone.region 958 eip.IpAddress = self.PublicIpAddress.IpAddress[0] 959 eip.InstanceId = self.InstanceId 960 eip.InstanceType = EIP_INSTANCE_TYPE_ECS 961 eip.Status = EIP_STATUS_INUSE 962 eip.AllocationId = self.InstanceId // fixed 963 eip.AllocationTime = self.CreationTime 964 eip.Bandwidth = self.InternetMaxBandwidthOut 965 eip.ResourceGroupId = self.ResourceGroupId 966 eip.InternetChargeType = self.InternetChargeType 967 return &eip, nil 968 } 969 return nil, nil 970 } 971 972 func (self *SInstance) AssignSecurityGroup(secgroupId string) error { 973 return self.host.zone.region.AssignSecurityGroup(secgroupId, self.InstanceId) 974 } 975 976 func (self *SInstance) SetSecurityGroups(secgroupIds []string) error { 977 return self.host.zone.region.SetSecurityGroups(secgroupIds, self.InstanceId) 978 } 979 980 func (self *SInstance) GetBillingType() string { 981 return convertChargeType(self.InstanceChargeType) 982 } 983 984 func (self *SInstance) GetCreatedAt() time.Time { 985 return self.CreationTime 986 } 987 988 func (self *SInstance) GetExpiredAt() time.Time { 989 return convertExpiredAt(self.ExpiredTime) 990 } 991 992 func (self *SInstance) UpdateUserData(userData string) error { 993 return self.host.zone.region.updateInstance(self.InstanceId, "", "", "", "", userData) 994 } 995 996 func (self *SInstance) Renew(bc billing.SBillingCycle) error { 997 return self.host.zone.region.RenewInstance(self.InstanceId, bc) 998 } 999 1000 func (self *SInstance) GetThroughput() int { 1001 return self.Throughput 1002 } 1003 1004 func (self *SInstance) GetInternetMaxBandwidthOut() int { 1005 return self.InternetMaxBandwidthOut 1006 } 1007 1008 func billingCycle2Params(bc *billing.SBillingCycle, params map[string]string) error { 1009 if bc.GetMonths() > 0 { 1010 params["PeriodUnit"] = "Month" 1011 params["Period"] = fmt.Sprintf("%d", bc.GetMonths()) 1012 } else if bc.GetWeeks() > 0 { 1013 params["PeriodUnit"] = "Week" 1014 params["Period"] = fmt.Sprintf("%d", bc.GetWeeks()) 1015 } else { 1016 return fmt.Errorf("invalid renew time period %s", bc.String()) 1017 } 1018 return nil 1019 } 1020 1021 func (region *SRegion) RenewInstance(instanceId string, bc billing.SBillingCycle) error { 1022 params := make(map[string]string) 1023 params["InstanceId"] = instanceId 1024 err := billingCycle2Params(&bc, params) 1025 if err != nil { 1026 return err 1027 } 1028 params["ClientToken"] = utils.GenRequestId(20) 1029 _, err = region.ecsRequest("RenewInstance", params) 1030 if err != nil { 1031 log.Errorf("RenewInstance fail %s", err) 1032 return err 1033 } 1034 return nil 1035 } 1036 1037 func (self *SInstance) GetProjectId() string { 1038 return self.ResourceGroupId 1039 } 1040 1041 func (self *SInstance) GetError() error { 1042 return nil 1043 } 1044 1045 func (region *SRegion) ConvertPublicIpToEip(instanceId string) error { 1046 params := make(map[string]string) 1047 params["InstanceId"] = instanceId 1048 params["RegionId"] = region.RegionId 1049 _, err := region.ecsRequest("ConvertNatPublicIpToEip", params) 1050 return err 1051 } 1052 1053 func (self *SInstance) ConvertPublicIpToEip() error { 1054 err := self.host.zone.region.ConvertPublicIpToEip(self.InstanceId) 1055 if err != nil { 1056 return errors.Wrapf(err, "ConvertPublicIpToEip") 1057 } 1058 return cloudprovider.Wait(time.Second*5, time.Minute*5, func() (bool, error) { 1059 self.Refresh() 1060 iEip, err := self.GetIEIP() 1061 if err != nil { 1062 return false, errors.Wrapf(err, "GetIEIP") 1063 } 1064 if iEip == nil { 1065 return false, nil 1066 } 1067 if iEip.GetMode() == api.EIP_MODE_STANDALONE_EIP { 1068 return true, self.host.zone.region.VpcMoveResourceGroup("eip", self.ResourceGroupId, iEip.GetId()) 1069 } 1070 return false, nil 1071 }) 1072 } 1073 1074 func (self *SRegion) VpcMoveResourceGroup(resType, groupId, resId string) error { 1075 params := map[string]string{ 1076 "RegionId": self.RegionId, 1077 "ResourceType": resType, 1078 "NewResourceGroupId": groupId, 1079 "ResourceId": resId, 1080 } 1081 _, err := self.vpcRequest("MoveResourceGroup", params) 1082 return errors.Wrapf(err, "MoveResourceGroup") 1083 } 1084 1085 // https://help.aliyun.com/document_detail/52843.html 1086 func (region *SRegion) SetInstanceAutoRenew(instanceId string, bc billing.SBillingCycle) error { 1087 params := make(map[string]string) 1088 params["InstanceId"] = instanceId 1089 params["RegionId"] = region.RegionId 1090 if bc.AutoRenew { 1091 params["RenewalStatus"] = "AutoRenewal" 1092 switch bc.Unit { 1093 case billing.BillingCycleYear: 1094 params["Duration"] = fmt.Sprintf("%d", bc.GetYears()) 1095 params["PeriodUnit"] = "Year" 1096 case billing.BillingCycleMonth: 1097 params["Duration"] = fmt.Sprintf("%d", bc.GetMonths()) 1098 params["PeriodUnit"] = "Month" 1099 case billing.BillingCycleWeek: 1100 params["Duration"] = fmt.Sprintf("%d", bc.GetWeeks()) 1101 params["PeriodUnit"] = "Week" 1102 } 1103 } else { 1104 params["RenewalStatus"] = "Normal" 1105 } 1106 _, err := region.ecsRequest("ModifyInstanceAutoRenewAttribute", params) 1107 return err 1108 } 1109 1110 type SAutoRenewAttr struct { 1111 Duration int 1112 AutoRenewEnabled bool 1113 RenewalStatus string 1114 PeriodUnit string 1115 } 1116 1117 func (region *SRegion) GetInstanceAutoRenewAttribute(instanceId string) (*SAutoRenewAttr, error) { 1118 params := make(map[string]string) 1119 params["InstanceId"] = instanceId 1120 params["RegionId"] = region.RegionId 1121 resp, err := region.ecsRequest("DescribeInstanceAutoRenewAttribute", params) 1122 if err != nil { 1123 return nil, errors.Wrap(err, "DescribeInstanceAutoRenewAttribute") 1124 } 1125 attr := []SAutoRenewAttr{} 1126 err = resp.Unmarshal(&attr, "InstanceRenewAttributes", "InstanceRenewAttribute") 1127 if err != nil { 1128 return nil, errors.Wrap(err, "resp.Unmarshal") 1129 } 1130 if len(attr) == 1 { 1131 return &attr[0], nil 1132 } 1133 return nil, fmt.Errorf("get %d auto renew info", len(attr)) 1134 } 1135 1136 func (self *SInstance) IsAutoRenew() bool { 1137 attr, err := self.host.zone.region.GetInstanceAutoRenewAttribute(self.InstanceId) 1138 if err != nil { 1139 log.Errorf("failed to get instance %s auto renew info", self.InstanceId) 1140 return false 1141 } 1142 return attr.AutoRenewEnabled 1143 } 1144 1145 func (self *SInstance) SetAutoRenew(bc billing.SBillingCycle) error { 1146 return self.host.zone.region.SetInstanceAutoRenew(self.InstanceId, bc) 1147 } 1148 1149 func (self *SInstance) SetTags(tags map[string]string, replace bool) error { 1150 return self.host.zone.region.SetResourceTags(ALIYUN_SERVICE_ECS, "instance", self.InstanceId, tags, replace) 1151 } 1152 1153 func (self *SRegion) SaveImage(instanceId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) { 1154 params := map[string]string{ 1155 "InstanceId": instanceId, 1156 "ImageName": opts.Name, 1157 "Description": opts.Notes, 1158 "ClientToken": utils.GenRequestId(20), 1159 } 1160 resp, err := self.ecsRequest("CreateImage", params) 1161 if err != nil { 1162 return nil, errors.Wrapf(err, "CreateImage") 1163 } 1164 ret := struct{ ImageId string }{} 1165 err = resp.Unmarshal(&ret) 1166 if err != nil { 1167 return nil, errors.Wrapf(err, "resp.Unmarshal") 1168 } 1169 image, err := self.GetImage(ret.ImageId) 1170 if err != nil { 1171 return nil, errors.Wrapf(err, "GetImage(%s)", ret.ImageId) 1172 } 1173 image.storageCache = self.getStoragecache() 1174 return image, nil 1175 } 1176 1177 func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) { 1178 image, err := self.host.zone.region.SaveImage(self.InstanceId, opts) 1179 if err != nil { 1180 return nil, errors.Wrapf(err, "SaveImage(%s)", opts.Name) 1181 } 1182 return image, nil 1183 }