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