yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/aws/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 aws 16 17 import ( 18 "context" 19 "encoding/base64" 20 "fmt" 21 "strings" 22 "time" 23 24 "github.com/aws/aws-sdk-go/service/ec2" 25 26 "yunion.io/x/jsonutils" 27 "yunion.io/x/log" 28 "yunion.io/x/pkg/errors" 29 "yunion.io/x/pkg/util/osprofile" 30 "yunion.io/x/pkg/utils" 31 32 "yunion.io/x/cloudmux/pkg/apis" 33 billing_api "yunion.io/x/cloudmux/pkg/apis/billing" 34 api "yunion.io/x/cloudmux/pkg/apis/compute" 35 "yunion.io/x/cloudmux/pkg/cloudprovider" 36 "yunion.io/x/onecloud/pkg/compute/options" 37 "yunion.io/x/cloudmux/pkg/multicloud" 38 "yunion.io/x/onecloud/pkg/util/billing" 39 "yunion.io/x/onecloud/pkg/util/cloudinit" 40 ) 41 42 const ( 43 InstanceStatusPending = "pending" 44 InstanceStatusRunning = "running" 45 InstanceStatusShutting = "shutting-down" 46 InstanceStatusTerminated = "terminated" 47 InstanceStatusStopping = "stopping" 48 InstanceStatusStopped = "stopped" 49 ) 50 51 type InstanceChargeType string 52 53 type SIpAddress struct { 54 IpAddress []string 55 } 56 57 type SSecurityGroupIds struct { 58 SecurityGroupId []string 59 } 60 61 type SVpcAttributes struct { 62 PrivateIpAddress SIpAddress 63 NetworkId string // subnet id 64 VpcId string 65 } 66 67 type SInstance struct { 68 multicloud.SInstanceBase 69 70 host *SHost 71 img *SImage 72 RegionId string 73 ZoneId string 74 InstanceId string 75 ImageId string 76 77 HostName string 78 InstanceName string 79 InstanceType string 80 Cpu int 81 Memory int // MB 82 IoOptimized bool 83 KeyPairName string 84 CreationTime time.Time // LaunchTime 85 ExpiredTime time.Time 86 ProductCodes []string 87 PublicDNSName string 88 InnerIpAddress SIpAddress 89 PublicIpAddress SIpAddress 90 RootDeviceName string 91 Status string // state 92 VlanId string // subnet ID ? 93 VpcAttributes SVpcAttributes 94 SecurityGroupIds SSecurityGroupIds 95 NetworkInterfaces []SNetworkInterface 96 EipAddress SEipAddress 97 Disks []string 98 DeviceNames []string 99 OSName string 100 OSType string 101 Description string 102 InternetMaxBandwidthOut int 103 Throughput int 104 105 TagSpec TagSpec 106 107 // 这些貌似都没啥用 108 // AutoReleaseTime string 109 // DeviceAvailable bool 110 // GPUAmount int 111 // GPUSpec string 112 // InstanceChargeType InstanceChargeType 113 // InstanceNetworkType string 114 // InstanceTypeFamily string 115 // InternetChargeType string 116 // InternetMaxBandwidthIn int 117 // InternetMaxBandwidthOut int 118 // OperationLocks SOperationLocks 119 // Recyclable bool 120 // SerialNumber string 121 // SpotPriceLimit string 122 // SpotStrategy string 123 // StartTime time.Time 124 // StoppedMode string 125 } 126 127 func (self *SInstance) UpdateUserData(userData string) error { 128 udata := &ec2.BlobAttributeValue{} 129 udata.SetValue([]byte(userData)) 130 131 input := &ec2.ModifyInstanceAttributeInput{} 132 input.SetUserData(udata) 133 input.SetInstanceId(self.GetId()) 134 ec2Client, err := self.host.zone.region.getEc2Client() 135 if err != nil { 136 return errors.Wrap(err, "getEc2Client") 137 } 138 _, err = ec2Client.ModifyInstanceAttribute(input) 139 if err != nil { 140 return errors.Wrap(err, "ModifyInstanceAttribute") 141 } 142 143 return nil 144 } 145 146 func (self *SInstance) GetUserData() (string, error) { 147 input := &ec2.DescribeInstanceAttributeInput{} 148 input.SetInstanceId(self.GetId()) 149 input.SetAttribute("userData") 150 ec2Client, err := self.host.zone.region.getEc2Client() 151 if err != nil { 152 return "", errors.Wrap(err, "getEc2Client") 153 } 154 ret, err := ec2Client.DescribeInstanceAttribute(input) 155 if err != nil { 156 return "", err 157 } 158 159 d := StrVal(ret.UserData.Value) 160 udata, err := base64.StdEncoding.DecodeString(d) 161 if err != nil { 162 return "", fmt.Errorf("GetUserData decode user data %s", err) 163 } 164 165 return string(udata), nil 166 } 167 168 func (self *SInstance) GetId() string { 169 return self.InstanceId 170 } 171 172 func (self *SInstance) GetName() string { 173 if len(self.InstanceName) > 0 { 174 return self.InstanceName 175 } 176 177 return self.GetId() 178 } 179 180 func (self *SInstance) GetHostname() string { 181 return self.GetName() 182 } 183 184 func (self *SInstance) GetGlobalId() string { 185 return self.InstanceId 186 } 187 188 func (self *SInstance) GetStatus() string { 189 switch self.Status { 190 case InstanceStatusRunning: 191 return api.VM_RUNNING 192 case InstanceStatusPending: // todo: pending ? 193 return api.VM_STARTING 194 case InstanceStatusStopping: 195 return api.VM_STOPPING 196 case InstanceStatusStopped: 197 return api.VM_READY 198 default: 199 return api.VM_UNKNOWN 200 } 201 } 202 203 func (self *SInstance) Refresh() error { 204 new, err := self.host.zone.region.GetInstance(self.InstanceId) 205 if err != nil { 206 return err 207 } 208 return jsonutils.Update(self, new) 209 } 210 211 func (self *SInstance) IsEmulated() bool { 212 return false 213 } 214 215 func (self *SInstance) GetInstanceType() string { 216 return self.InstanceType 217 } 218 219 func (self *SInstance) GetSecurityGroupIds() ([]string, error) { 220 return self.SecurityGroupIds.SecurityGroupId, nil 221 } 222 223 func (self *SInstance) GetTags() (map[string]string, error) { 224 return self.TagSpec.GetTags() 225 } 226 227 func (self *SInstance) GetSysTags() map[string]string { 228 return map[string]string{} 229 } 230 231 func (self *SInstance) GetBillingType() string { 232 // todo: implement me 233 return billing_api.BILLING_TYPE_POSTPAID 234 } 235 236 func (self *SInstance) GetCreatedAt() time.Time { 237 return self.CreationTime 238 } 239 240 func (self *SInstance) GetExpiredAt() time.Time { 241 return self.ExpiredTime 242 } 243 244 func (self *SInstance) GetIHost() cloudprovider.ICloudHost { 245 return self.host 246 } 247 248 func (self *SInstance) GetThroughput() int { 249 return self.Throughput 250 } 251 252 func (self *SInstance) GetInternetMaxBandwidthOut() int { 253 return self.InternetMaxBandwidthOut 254 } 255 256 func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { 257 disks, _, err := self.host.zone.region.GetDisks(self.InstanceId, "", "", nil, 0, 0) 258 if err != nil { 259 log.Errorf("fetchDisks fail %s", err) 260 return nil, errors.Wrap(err, "GetDisks") 261 } 262 263 idisks := make([]cloudprovider.ICloudDisk, len(disks)) 264 for i := 0; i < len(disks); i += 1 { 265 store, err := self.host.zone.getStorageByCategory(disks[i].Category) 266 if err != nil { 267 return nil, errors.Wrap(err, "getStorageByCategory") 268 } 269 disks[i].storage = store 270 idisks[i] = &disks[i] 271 } 272 return idisks, nil 273 } 274 275 func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) { 276 var ( 277 networkInterfaces = self.NetworkInterfaces 278 nics = make([]cloudprovider.ICloudNic, 0) 279 ) 280 for _, networkInterface := range networkInterfaces { 281 nic := SInstanceNic{ 282 instance: self, 283 id: networkInterface.NetworkInterfaceId, 284 ipAddr: networkInterface.PrivateIpAddress, 285 macAddr: networkInterface.MacAddress, 286 } 287 nics = append(nics, &nic) 288 } 289 return nics, nil 290 } 291 292 func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { 293 if len(self.EipAddress.PublicIp) > 0 { 294 return self.host.zone.region.GetEipByIpAddress(self.EipAddress.PublicIp) 295 } 296 if len(self.PublicIpAddress.IpAddress) > 0 { 297 eip := SEipAddress{region: self.host.zone.region} 298 eip.region = self.host.zone.region 299 eip.PublicIp = self.PublicIpAddress.IpAddress[0] 300 eip.InstanceId = self.InstanceId 301 eip.AllocationId = self.InstanceId // fixed. AllocationId等于InstanceId即表示为 仿真EIP。 302 return &eip, nil 303 } 304 return nil, nil 305 } 306 307 func (self *SInstance) GetVcpuCount() int { 308 return self.Cpu 309 } 310 311 func (self *SInstance) GetVmemSizeMB() int { 312 return self.Memory 313 } 314 315 func (self *SInstance) GetBootOrder() string { 316 return "dcn" 317 } 318 319 func (self *SInstance) GetVga() string { 320 return "std" 321 } 322 323 func (self *SInstance) GetVdi() string { 324 return "vnc" 325 } 326 327 func (self *SInstance) GetOsType() cloudprovider.TOsType { 328 return cloudprovider.TOsType(osprofile.NormalizeOSType(self.OSType)) 329 } 330 331 func (self *SInstance) GetFullOsName() string { 332 return self.OSName 333 } 334 335 func (self *SInstance) GetBios() cloudprovider.TBiosType { 336 img, err := self.GetImage() 337 if err != nil { 338 log.Errorf("GetImage fail %s", err) 339 return cloudprovider.BIOS 340 } 341 return img.GetBios() 342 } 343 344 func (self *SInstance) GetOsArch() string { 345 img, err := self.GetImage() 346 if err != nil { 347 log.Errorf("GetImage fail %s", err) 348 return apis.OS_ARCH_X86_64 349 } 350 return img.GetOsArch() 351 } 352 353 func (self *SInstance) GetOsDist() string { 354 img, err := self.GetImage() 355 if err != nil { 356 log.Errorf("GetImage fail %s", err) 357 return "" 358 } 359 return img.GetOsDist() 360 } 361 362 func (self *SInstance) GetOsVersion() string { 363 img, err := self.GetImage() 364 if err != nil { 365 log.Errorf("GetImage fail %s", err) 366 return "" 367 } 368 return img.GetOsVersion() 369 } 370 371 func (self *SInstance) GetOsLang() string { 372 img, err := self.GetImage() 373 if err != nil { 374 log.Errorf("GetImage fail %s", err) 375 return "" 376 } 377 return img.GetOsLang() 378 } 379 380 func (self *SInstance) GetMachine() string { 381 return "pc" 382 } 383 384 func (self *SInstance) AssignSecurityGroup(secgroupId string) error { 385 return self.SetSecurityGroups([]string{secgroupId}) 386 } 387 388 func (self *SInstance) SetSecurityGroups(secgroupIds []string) error { 389 ids := []*string{} 390 for i := 0; i < len(secgroupIds); i++ { 391 ids = append(ids, &secgroupIds[i]) 392 } 393 return self.host.zone.region.assignSecurityGroups(ids, self.InstanceId) 394 } 395 396 func (self *SInstance) GetHypervisor() string { 397 return api.HYPERVISOR_AWS 398 } 399 400 func (self *SInstance) StartVM(ctx context.Context) error { 401 timeout := 300 * time.Second 402 interval := 15 * time.Second 403 404 startTime := time.Now() 405 for time.Now().Sub(startTime) < timeout { 406 err := self.Refresh() 407 if err != nil { 408 return err 409 } 410 411 if self.GetStatus() == api.VM_RUNNING { 412 return nil 413 } else if self.GetStatus() == api.VM_READY { 414 err := self.host.zone.region.StartVM(self.InstanceId) 415 if err != nil { 416 return err 417 } 418 } 419 time.Sleep(interval) 420 } 421 return cloudprovider.ErrTimeout 422 } 423 424 func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { 425 err := self.host.zone.region.StopVM(self.InstanceId, opts.IsForce) 426 if err != nil { 427 return err 428 } 429 return cloudprovider.WaitStatus(self, api.VM_READY, 10*time.Second, 300*time.Second) // 5mintues 430 } 431 432 func (self *SInstance) DeleteVM(ctx context.Context) error { 433 for { 434 err := self.host.zone.region.DeleteVM(self.InstanceId) 435 if err != nil && self.Status != InstanceStatusTerminated { 436 return err 437 } else { 438 break 439 } 440 } 441 442 params := &ec2.DescribeInstancesInput{InstanceIds: []*string{&self.InstanceId}} 443 ec2Client, err := self.host.zone.region.getEc2Client() 444 if err != nil { 445 return errors.Wrap(err, "getEc2Client") 446 } 447 448 return ec2Client.WaitUntilInstanceTerminated(params) 449 } 450 451 func (self *SInstance) UpdateVM(ctx context.Context, name string) error { 452 addTags := map[string]string{} 453 addTags["Name"] = name 454 Arn := self.GetArn() 455 err := self.host.zone.region.TagResources([]string{Arn}, addTags) 456 if err != nil { 457 return errors.Wrapf(err, "self.host.zone.region.TagResources([]string{%s}, %s)", Arn, jsonutils.Marshal(addTags).String()) 458 } 459 return nil 460 } 461 462 func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) { 463 udata, err := self.GetUserData() 464 if err != nil { 465 return "", err 466 } 467 468 // compare sysSizeGB 469 image, err := self.host.zone.region.GetImage(desc.ImageId) 470 if err != nil { 471 return "", err 472 } else { 473 minSizeGB := image.GetMinOsDiskSizeGb() 474 if minSizeGB > desc.SysSizeGB { 475 desc.SysSizeGB = minSizeGB 476 } 477 } 478 479 // upload keypair 480 keypairName := self.KeyPairName 481 if len(desc.PublicKey) > 0 { 482 keypairName, err = self.host.zone.region.syncKeypair(desc.PublicKey) 483 if err != nil { 484 return "", fmt.Errorf("RebuildRoot.syncKeypair %s", err) 485 } 486 } 487 488 userdata := "" 489 srcOsType := strings.ToLower(string(self.GetOsType())) 490 destOsType := strings.ToLower(string(image.GetOsType())) 491 winOS := strings.ToLower(osprofile.OS_TYPE_WINDOWS) 492 493 cloudconfig := &cloudinit.SCloudConfig{} 494 if srcOsType != winOS && len(udata) > 0 { 495 _cloudconfig, err := cloudinit.ParseUserDataBase64(udata) 496 if err != nil { 497 // 忽略无效的用户数据 498 log.Debugf("RebuildRoot invalid instance user data %s", udata) 499 } else { 500 cloudconfig = _cloudconfig 501 } 502 } 503 504 if (srcOsType != winOS && destOsType != winOS) || (srcOsType == winOS && destOsType != winOS) { 505 // linux/windows to linux 506 loginUser := cloudinit.NewUser(api.VM_AWS_DEFAULT_LOGIN_USER) 507 loginUser.SudoPolicy(cloudinit.USER_SUDO_NOPASSWD) 508 if len(desc.PublicKey) > 0 { 509 loginUser.SshKey(desc.PublicKey) 510 cloudconfig.MergeUser(loginUser) 511 } else if len(desc.Password) > 0 { 512 cloudconfig.SshPwauth = cloudinit.SSH_PASSWORD_AUTH_ON 513 loginUser.Password(desc.Password) 514 cloudconfig.MergeUser(loginUser) 515 } 516 517 userdata = cloudconfig.UserDataBase64() 518 } else { 519 // linux/windows to windows 520 data := "" 521 if len(desc.Password) > 0 { 522 cloudconfig.SshPwauth = cloudinit.SSH_PASSWORD_AUTH_ON 523 loginUser := cloudinit.NewUser(api.VM_AWS_DEFAULT_WINDOWS_LOGIN_USER) 524 loginUser.SudoPolicy(cloudinit.USER_SUDO_NOPASSWD) 525 loginUser.Password(desc.Password) 526 cloudconfig.MergeUser(loginUser) 527 data = fmt.Sprintf("<powershell>%s</powershell>", cloudconfig.UserDataPowerShell()) 528 } else { 529 if len(udata) > 0 { 530 data = fmt.Sprintf("<powershell>%s</powershell>", udata) 531 } 532 } 533 534 userdata = base64.StdEncoding.EncodeToString([]byte(data)) 535 } 536 537 diskId, err := self.host.zone.region.ReplaceSystemDisk(ctx, self.InstanceId, image, desc.SysSizeGB, keypairName, userdata) 538 if err != nil { 539 return "", err 540 } 541 542 return diskId, nil 543 } 544 545 func (self *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error { 546 return self.host.zone.region.DeployVM(self.InstanceId, name, password, publicKey, deleteKeypair, description) 547 } 548 549 func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error { 550 if len(config.InstanceType) > 0 { 551 return self.ChangeConfig2(ctx, config.InstanceType) 552 } 553 return errors.Wrap(errors.ErrClient, "Instance.ChangeConfig.InstanceTypeIsEmpty") 554 } 555 556 func (self *SInstance) ChangeConfig2(ctx context.Context, instanceType string) error { 557 return self.host.zone.region.ChangeVMConfig2(self.ZoneId, self.InstanceId, instanceType, nil) 558 } 559 560 func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) { 561 return nil, cloudprovider.ErrNotSupported 562 } 563 564 func (self *SInstance) GetImage() (*SImage, error) { 565 if self.img != nil { 566 return self.img, nil 567 } 568 569 img, err := self.host.zone.region.GetImage(self.ImageId) 570 if err != nil { 571 return nil, errors.Wrap(err, "GetImage") 572 } 573 574 self.img = img 575 return self.img, nil 576 } 577 578 func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error { 579 img, err := self.GetImage() 580 if err != nil { 581 return errors.Wrap(err, "GetImage") 582 } 583 584 // mix in image block device names 585 for i := range img.BlockDevicesNames { 586 if !utils.IsInStringArray(img.BlockDevicesNames[i], self.DeviceNames) { 587 self.DeviceNames = append(self.DeviceNames, img.BlockDevicesNames[i]) 588 } 589 } 590 591 name, err := NextDeviceName(self.DeviceNames) 592 if err != nil { 593 return err 594 } 595 596 err = self.host.zone.region.AttachDisk(self.InstanceId, diskId, name) 597 if err != nil { 598 return err 599 } 600 601 self.DeviceNames = append(self.DeviceNames, name) 602 return nil 603 } 604 605 func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error { 606 return self.host.zone.region.DetachDisk(self.InstanceId, diskId) 607 } 608 609 func (self *SInstance) getVpc() (*SVpc, error) { 610 return self.host.zone.region.getVpc(self.VpcAttributes.VpcId) 611 } 612 613 func (self *SRegion) GetInstances(zoneId string, ids []string, offset int, limit int) ([]SInstance, int, error) { 614 params := &ec2.DescribeInstancesInput{} 615 filters := make([]*ec2.Filter, 0) 616 if len(zoneId) > 0 { 617 filters = AppendSingleValueFilter(filters, "availability-zone", zoneId) 618 } 619 620 if len(ids) > 0 { 621 params = params.SetInstanceIds(ConvertedList(ids)) 622 } 623 624 if len(filters) > 0 { 625 params = params.SetFilters(filters) 626 } 627 628 ec2Client, err := self.getEc2Client() 629 if err != nil { 630 return nil, 0, errors.Wrap(err, "getEc2Client") 631 } 632 res, err := ec2Client.DescribeInstances(params) 633 if err != nil { 634 if strings.Contains(err.Error(), "InvalidInstanceID.NotFound") { 635 return nil, 0, errors.Wrap(cloudprovider.ErrNotFound, "DescribeInstances") 636 } else { 637 return nil, 0, err 638 } 639 } 640 641 instances := []SInstance{} 642 for _, reservation := range res.Reservations { 643 for _, instance := range reservation.Instances { 644 if err := FillZero(instance); err != nil { 645 return nil, 0, err 646 } 647 648 // 不同步已经terminated的主机 649 if *instance.State.Name == ec2.InstanceStateNameTerminated { 650 continue 651 } 652 653 tagspec := TagSpec{} 654 tagspec.LoadingEc2Tags(instance.Tags) 655 656 disks := []string{} 657 devicenames := []string{} 658 for _, d := range instance.BlockDeviceMappings { 659 if d.Ebs != nil && d.Ebs.VolumeId != nil { 660 disks = append(disks, *d.Ebs.VolumeId) 661 devicenames = append(devicenames, *d.DeviceName) 662 } 663 } 664 665 var secgroups SSecurityGroupIds 666 for _, s := range instance.SecurityGroups { 667 if s.GroupId != nil { 668 secgroups.SecurityGroupId = append(secgroups.SecurityGroupId, *s.GroupId) 669 } 670 } 671 672 networkInterfaces := []SNetworkInterface{} 673 eipAddress := SEipAddress{} 674 for _, n := range instance.NetworkInterfaces { 675 i := SNetworkInterface{ 676 MacAddress: *n.MacAddress, 677 NetworkInterfaceId: *n.NetworkInterfaceId, 678 PrivateIpAddress: *n.PrivateIpAddress, 679 } 680 networkInterfaces = append(networkInterfaces, i) 681 682 // todo: 可能有多个EIP的情况。目前只支持一个EIP 683 if n.Association != nil && StrVal(n.Association.IpOwnerId) != "amazon" { 684 if eipAddress.PublicIp == "" && len(StrVal(n.Association.PublicIp)) > 0 { 685 eipAddress.PublicIp = *n.Association.PublicIp 686 } 687 } 688 } 689 690 var vpcattr SVpcAttributes 691 vpcattr.VpcId = *instance.VpcId 692 vpcattr.PrivateIpAddress = SIpAddress{[]string{*instance.PrivateIpAddress}} 693 vpcattr.NetworkId = *instance.SubnetId 694 695 var productCodes []string 696 for _, p := range instance.ProductCodes { 697 productCodes = append(productCodes, *p.ProductCodeId) 698 } 699 700 publicIpAddress := SIpAddress{} 701 if len(*instance.PublicIpAddress) > 0 { 702 publicIpAddress.IpAddress = []string{*instance.PublicIpAddress} 703 } 704 705 innerIpAddress := SIpAddress{} 706 if len(*instance.PrivateIpAddress) > 0 { 707 innerIpAddress.IpAddress = []string{*instance.PrivateIpAddress} 708 } 709 710 szone, err := self.getZoneById(*instance.Placement.AvailabilityZone) 711 if err != nil { 712 log.Errorf("getZoneById %s fail %s", *instance.Placement.AvailabilityZone, err) 713 return nil, 0, err 714 } 715 716 osType := "Linux" 717 if instance.Platform != nil && len(*instance.Platform) > 0 { 718 osType = *instance.Platform 719 } 720 721 host := szone.getHost() 722 vcpu := int(*instance.CpuOptions.CoreCount) * int(*instance.CpuOptions.ThreadsPerCore) 723 sinstance := SInstance{ 724 RegionId: self.RegionId, 725 host: host, 726 ZoneId: *instance.Placement.AvailabilityZone, 727 InstanceId: *instance.InstanceId, 728 ImageId: *instance.ImageId, 729 InstanceType: *instance.InstanceType, 730 Cpu: vcpu, 731 IoOptimized: *instance.EbsOptimized, 732 KeyPairName: *instance.KeyName, 733 CreationTime: *instance.LaunchTime, 734 PublicDNSName: *instance.PublicDnsName, 735 RootDeviceName: *instance.RootDeviceName, 736 Status: *instance.State.Name, 737 InnerIpAddress: innerIpAddress, 738 PublicIpAddress: publicIpAddress, 739 EipAddress: eipAddress, 740 InstanceName: tagspec.GetNameTag(), 741 Description: tagspec.GetDescTag(), 742 Disks: disks, 743 DeviceNames: devicenames, 744 SecurityGroupIds: secgroups, 745 NetworkInterfaces: networkInterfaces, 746 VpcAttributes: vpcattr, 747 ProductCodes: productCodes, 748 OSName: osType, // todo: 这里在model层回写OSName信息 749 OSType: osType, 750 751 TagSpec: tagspec, 752 // ExpiredTime: 753 // VlanId: 754 // OSType: 755 } 756 757 instances = append(instances, sinstance) 758 } 759 } 760 761 return instances, len(instances), nil 762 } 763 764 func (self *SRegion) GetInstance(instanceId string) (*SInstance, error) { 765 if len(instanceId) == 0 { 766 return nil, fmt.Errorf("GetInstance instanceId should not be empty.") 767 } 768 769 instances, _, err := self.GetInstances("", []string{instanceId}, 0, 1) 770 if err != nil { 771 log.Errorf("GetInstances %s: %s", instanceId, err) 772 return nil, errors.Wrap(err, "GetInstances") 773 } 774 if len(instances) == 0 { 775 return nil, errors.Wrap(cloudprovider.ErrNotFound, "GetInstances") 776 } 777 return &instances[0], nil 778 } 779 780 func (self *SRegion) GetInstanceIdByImageId(imageId string) (string, error) { 781 params := &ec2.DescribeInstancesInput{} 782 filters := []*ec2.Filter{} 783 filters = AppendSingleValueFilter(filters, "image-id", imageId) 784 params.SetFilters(filters) 785 786 ec2Client, err := self.getEc2Client() 787 if err != nil { 788 return "", errors.Wrap(err, "getEc2Client") 789 } 790 ret, err := ec2Client.DescribeInstances(params) 791 if err != nil { 792 return "", err 793 } 794 795 for _, item := range ret.Reservations { 796 for _, instance := range item.Instances { 797 return *instance.InstanceId, nil 798 } 799 } 800 return "", fmt.Errorf("instance launch with image %s not found", imageId) 801 } 802 803 func (self *SRegion) CreateInstance(name string, image *SImage, instanceType string, SubnetId string, securityGroupId string, 804 zoneId string, desc string, disks []SDisk, ipAddr string, 805 keypair string, userData string, ntags map[string]string, 806 ) (string, error) { 807 var count int64 = 1 808 // disk 809 blockDevices := []*ec2.BlockDeviceMapping{} 810 for i := range disks { 811 var ebs ec2.EbsBlockDevice 812 var deviceName string 813 var err error 814 disk := disks[i] 815 816 if i == 0 { 817 var size int64 818 deleteOnTermination := true 819 size = int64(disk.Size) 820 ebs = ec2.EbsBlockDevice{ 821 DeleteOnTermination: &deleteOnTermination, 822 // The st1 volume type cannot be used for boot volumes. Please use a supported boot volume type: standard,io1,gp2. 823 // the encrypted flag cannot be specified since device /dev/sda1 has a snapshot specified. 824 // Encrypted: &disk.Encrypted, 825 VolumeSize: &size, 826 VolumeType: &disk.Category, 827 } 828 829 if len(image.RootDeviceName) > 0 { 830 deviceName = image.RootDeviceName 831 } else { 832 deviceName = fmt.Sprintf("/dev/sda1") 833 } 834 } else { 835 var size int64 836 size = int64(disk.Size) 837 ebs = ec2.EbsBlockDevice{ 838 DeleteOnTermination: &disk.DeleteWithInstance, 839 Encrypted: &disk.Encrypted, 840 VolumeSize: &size, 841 VolumeType: &disk.Category, 842 } 843 844 deviceName, err = NextDeviceName(image.BlockDevicesNames) 845 if err != nil { 846 return "", errors.Wrap(err, "NextDeviceName") 847 } 848 } 849 850 if iops := GenDiskIops(disk.Category, disk.Size); iops > 0 { 851 ebs.SetIops(iops) 852 } 853 854 blockDevice := &ec2.BlockDeviceMapping{ 855 DeviceName: &deviceName, 856 Ebs: &ebs, 857 } 858 859 blockDevices = append(blockDevices, blockDevice) 860 } 861 862 // tags 863 tags := TagSpec{ResourceType: "instance"} 864 tags.SetNameTag(name) 865 tags.SetDescTag(desc) 866 867 if len(ntags) > 0 { 868 for k, v := range ntags { 869 tags.SetTag(k, v) 870 } 871 } 872 873 ec2TagSpec, err := tags.GetTagSpecifications() 874 if err != nil { 875 return "", err 876 } 877 878 imgId := image.GetId() 879 params := ec2.RunInstancesInput{ 880 ImageId: &imgId, 881 InstanceType: &instanceType, 882 MaxCount: &count, 883 MinCount: &count, 884 BlockDeviceMappings: blockDevices, 885 Placement: &ec2.Placement{AvailabilityZone: &zoneId}, 886 TagSpecifications: []*ec2.TagSpecification{ec2TagSpec}, 887 } 888 params.Monitoring = &ec2.RunInstancesMonitoringEnabled{ 889 Enabled: &options.Options.EnableAwsMonitorAgent, 890 } 891 892 // keypair 893 if len(keypair) > 0 { 894 params.SetKeyName(keypair) 895 } 896 897 // user data 898 if len(userData) > 0 { 899 params.SetUserData(userData) 900 } 901 902 // ip address 903 if len(ipAddr) > 0 { 904 params.SetPrivateIpAddress(ipAddr) 905 } 906 907 // subnet id 908 if len(SubnetId) > 0 { 909 params.SetSubnetId(SubnetId) 910 } 911 912 // security group 913 if len(securityGroupId) > 0 { 914 params.SetSecurityGroupIds([]*string{&securityGroupId}) 915 } 916 917 ec2Client, err := self.getEc2Client() 918 if err != nil { 919 return "", errors.Wrap(err, "getEc2Client") 920 } 921 res, err := ec2Client.RunInstances(¶ms) 922 if err != nil { 923 log.Errorf("CreateInstance fail %s", err) 924 return "", err 925 } 926 927 if len(res.Instances) == 1 { 928 return *res.Instances[0].InstanceId, nil 929 } else { 930 msg := fmt.Sprintf("CreateInstance fail: %d instance created. ", len(res.Instances)) 931 log.Errorf(msg) 932 return "", fmt.Errorf(msg) 933 } 934 } 935 936 func (self *SRegion) GetInstanceStatus(instanceId string) (string, error) { 937 instance, err := self.GetInstance(instanceId) 938 if err != nil { 939 return "", err 940 } 941 return instance.Status, nil 942 } 943 944 func (self *SRegion) StartVM(instanceId string) error { 945 params := &ec2.StartInstancesInput{} 946 params.SetInstanceIds([]*string{&instanceId}) 947 ec2Client, err := self.getEc2Client() 948 if err != nil { 949 return errors.Wrap(err, "getEc2Client") 950 } 951 _, err = ec2Client.StartInstances(params) 952 return errors.Wrap(err, "StartInstances") 953 } 954 955 func (self *SRegion) StopVM(instanceId string, isForce bool) error { 956 params := &ec2.StopInstancesInput{} 957 params.SetInstanceIds([]*string{&instanceId}) 958 ec2Client, err := self.getEc2Client() 959 if err != nil { 960 return errors.Wrap(err, "getEc2Client") 961 } 962 _, err = ec2Client.StopInstances(params) 963 return errors.Wrap(err, "StopInstances") 964 } 965 966 func (self *SRegion) DeleteVM(instanceId string) error { 967 // 检查删除保护状态.如果已开启则先关闭删除保护再进行删除操作 968 protect, err := self.deleteProtectStatusVM(instanceId) 969 if err != nil { 970 return err 971 } 972 973 if protect { 974 log.Warningf("DeleteVM instance %s which termination protect is in open status", instanceId) 975 err = self.deleteProtectVM(instanceId, false) 976 if err != nil { 977 return err 978 } 979 } 980 981 params := &ec2.TerminateInstancesInput{} 982 params.SetInstanceIds([]*string{&instanceId}) 983 ec2Client, err := self.getEc2Client() 984 if err != nil { 985 return errors.Wrap(err, "getEc2Client") 986 } 987 _, err = ec2Client.TerminateInstances(params) 988 return errors.Wrap(err, "TerminateInstances") 989 } 990 991 func (self *SRegion) DeployVM(instanceId string, name string, password string, keypairName string, deleteKeypair bool, description string) error { 992 params := &ec2.CreateTagsInput{} 993 params.SetResources([]*string{&instanceId}) 994 tagspec := TagSpec{ResourceType: "instance"} 995 996 if len(keypairName) > 0 { 997 return fmt.Errorf("aws not support reset publickey") 998 } 999 1000 if len(password) > 0 { 1001 return fmt.Errorf("aws not support set password, use publickey instead") 1002 } 1003 1004 if deleteKeypair { 1005 return fmt.Errorf("aws not support delete publickey") 1006 } 1007 1008 if len(name) > 0 { 1009 tagspec.SetNameTag(name) 1010 } 1011 1012 if len(description) > 0 { 1013 tagspec.SetDescTag(description) 1014 } 1015 1016 ec2Client, err := self.getEc2Client() 1017 if err != nil { 1018 return errors.Wrap(err, "getEc2Client") 1019 } 1020 1021 ec2Tag, _ := tagspec.GetTagSpecifications() 1022 if len(ec2Tag.Tags) > 0 { 1023 params.SetTags(ec2Tag.Tags) 1024 _, err := ec2Client.CreateTags(params) 1025 if err != nil { 1026 return err 1027 } 1028 } else { 1029 log.Debugf("no changes") 1030 } 1031 1032 return nil 1033 } 1034 1035 func (self *SRegion) UpdateVM(instanceId string, hostname string) error { 1036 // https://docs.aws.amazon.com/zh_cn/AWSEC2/latest/UserGuide/set-hostname.html 1037 return cloudprovider.ErrNotSupported 1038 } 1039 1040 func (self *SRegion) ReplaceSystemDisk(ctx context.Context, instanceId string, image *SImage, sysDiskSizeGB int, keypair string, userdata string) (string, error) { 1041 instance, err := self.GetInstance(instanceId) 1042 if err != nil { 1043 return "", err 1044 } 1045 disks, _, err := self.GetDisks(instanceId, instance.ZoneId, "", nil, 0, 0) 1046 if err != nil { 1047 return "", err 1048 } 1049 1050 var rootDisk *SDisk 1051 for _, disk := range disks { 1052 if disk.Type == api.DISK_TYPE_SYS { 1053 rootDisk = &disk 1054 break 1055 } 1056 } 1057 1058 if rootDisk == nil { 1059 return "", fmt.Errorf("can not find root disk of instance %s", instanceId) 1060 } 1061 log.Debugf("ReplaceSystemDisk replace root disk %s", rootDisk.DiskId) 1062 1063 subnetId := "" 1064 if len(instance.VpcAttributes.NetworkId) > 0 { 1065 subnetId = instance.VpcAttributes.NetworkId 1066 } 1067 1068 // create tmp server 1069 tempName := fmt.Sprintf("__tmp_%s", instance.GetName()) 1070 _id, err := self.CreateInstance(tempName, 1071 image, 1072 instance.InstanceType, 1073 subnetId, 1074 "", 1075 instance.ZoneId, 1076 instance.Description, 1077 []SDisk{{Size: sysDiskSizeGB, Category: rootDisk.Category}}, 1078 "", 1079 keypair, 1080 userdata, 1081 nil, 1082 ) 1083 if err == nil { 1084 defer self.DeleteVM(_id) 1085 } else { 1086 log.Debugf("ReplaceSystemDisk create temp server failed. %s", err) 1087 return "", fmt.Errorf("ReplaceSystemDisk create temp server failed.") 1088 } 1089 1090 ec2Client, err := self.getEc2Client() 1091 if err != nil { 1092 return "", errors.Wrap(err, "getEc2Client") 1093 } 1094 1095 ec2Client.WaitUntilInstanceRunning(&ec2.DescribeInstancesInput{InstanceIds: []*string{&_id}}) 1096 err = self.StopVM(_id, true) 1097 if err != nil { 1098 log.Debugf("ReplaceSystemDisk stop temp server failed %s", err) 1099 return "", fmt.Errorf("ReplaceSystemDisk stop temp server failed") 1100 } 1101 ec2Client.WaitUntilInstanceStopped(&ec2.DescribeInstancesInput{InstanceIds: []*string{&_id}}) 1102 1103 // detach disks 1104 tempInstance, err := self.GetInstance(_id) 1105 if err != nil { 1106 log.Debugf("ReplaceSystemDisk get temp server failed %s", err) 1107 return "", fmt.Errorf("ReplaceSystemDisk get temp server failed") 1108 } 1109 1110 err = self.DetachDisk(instance.GetId(), rootDisk.DiskId) 1111 if err != nil { 1112 log.Debugf("ReplaceSystemDisk detach disk %s: %s", rootDisk.DiskId, err) 1113 return "", err 1114 } 1115 1116 err = self.DetachDisk(tempInstance.GetId(), tempInstance.Disks[0]) 1117 if err != nil { 1118 log.Debugf("ReplaceSystemDisk detach disk %s: %s", tempInstance.Disks[0], err) 1119 return "", err 1120 } 1121 ec2Client.WaitUntilVolumeAvailable(&ec2.DescribeVolumesInput{VolumeIds: []*string{&rootDisk.DiskId}}) 1122 ec2Client.WaitUntilVolumeAvailable(&ec2.DescribeVolumesInput{VolumeIds: []*string{&tempInstance.Disks[0]}}) 1123 1124 err = self.AttachDisk(instance.GetId(), tempInstance.Disks[0], rootDisk.Device) 1125 if err != nil { 1126 log.Debugf("ReplaceSystemDisk attach disk %s: %s", tempInstance.Disks[0], err) 1127 return "", err 1128 } 1129 ec2Client.WaitUntilInstanceStopped(&ec2.DescribeInstancesInput{InstanceIds: []*string{&instanceId}}) 1130 ec2Client.WaitUntilVolumeInUse(&ec2.DescribeVolumesInput{VolumeIds: []*string{&tempInstance.Disks[0]}}) 1131 1132 userdataText, err := base64.StdEncoding.DecodeString(userdata) 1133 if err != nil { 1134 return "", errors.Wrap(err, "SRegion.ReplaceSystemDisk.DecodeString") 1135 } 1136 err = instance.UpdateUserData(string(userdataText)) 1137 if err != nil { 1138 log.Debugf("ReplaceSystemDisk update user data %s", err) 1139 return "", fmt.Errorf("ReplaceSystemDisk update user data failed") 1140 } 1141 1142 err = self.DeleteDisk(rootDisk.DiskId) 1143 if err != nil { 1144 log.Debugf("ReplaceSystemDisk delete old disk %s: %s", rootDisk.DiskId, err) 1145 } 1146 return tempInstance.Disks[0], nil 1147 } 1148 1149 func (self *SRegion) ChangeVMConfig2(zoneId string, instanceId string, instanceType string, disks []*SDisk) error { 1150 params := &ec2.ModifyInstanceAttributeInput{} 1151 params.SetInstanceId(instanceId) 1152 1153 t := &ec2.AttributeValue{Value: &instanceType} 1154 params.SetInstanceType(t) 1155 1156 ec2Client, err := self.getEc2Client() 1157 if err != nil { 1158 return errors.Wrap(err, "getEc2Client") 1159 } 1160 _, err = ec2Client.ModifyInstanceAttribute(params) 1161 if err != nil { 1162 return fmt.Errorf("Failed to change vm config, specification not supported. %s", err.Error()) 1163 } else { 1164 return nil 1165 } 1166 } 1167 1168 func (self *SRegion) DetachDisk(instanceId string, diskId string) error { 1169 params := &ec2.DetachVolumeInput{} 1170 params.SetInstanceId(instanceId) 1171 params.SetVolumeId(diskId) 1172 log.Debugf("DetachDisk %s", params.String()) 1173 1174 ec2Client, err := self.getEc2Client() 1175 if err != nil { 1176 return errors.Wrap(err, "getEc2Client") 1177 } 1178 1179 _, err = ec2Client.DetachVolume(params) 1180 if err != nil { 1181 if strings.Contains(err.Error(), fmt.Sprintf("'%s'is in the 'available' state", diskId)) { 1182 return nil 1183 } 1184 //InvalidVolume.NotFound: The volume 'vol-0a9eeda0a70a8d7fe' does not exist 1185 if strings.Contains(err.Error(), "InvalidVolume.NotFound") { 1186 return nil 1187 } 1188 return errors.Wrap(err, "ec2Client.DetachVolume") 1189 } 1190 return nil 1191 } 1192 1193 func (self *SRegion) AttachDisk(instanceId string, diskId string, deviceName string) error { 1194 params := &ec2.AttachVolumeInput{} 1195 params.SetInstanceId(instanceId) 1196 params.SetVolumeId(diskId) 1197 params.SetDevice(deviceName) 1198 log.Debugf("AttachDisk %s", params.String()) 1199 ec2Client, err := self.getEc2Client() 1200 if err != nil { 1201 return errors.Wrap(err, "getEc2Client") 1202 } 1203 _, err = ec2Client.AttachVolume(params) 1204 return errors.Wrap(err, "AttachVolume") 1205 } 1206 1207 func (self *SRegion) deleteProtectStatusVM(instanceId string) (bool, error) { 1208 p := &ec2.DescribeInstanceAttributeInput{} 1209 p.SetInstanceId(instanceId) 1210 p.SetAttribute("disableApiTermination") 1211 ec2Client, err := self.getEc2Client() 1212 if err != nil { 1213 return false, errors.Wrap(err, "getEc2Client") 1214 } 1215 ret, err := ec2Client.DescribeInstanceAttribute(p) 1216 if err != nil { 1217 return false, err 1218 } 1219 1220 return *ret.DisableApiTermination.Value, nil 1221 } 1222 1223 func (self *SRegion) deleteProtectVM(instanceId string, disableDelete bool) error { 1224 p2 := &ec2.ModifyInstanceAttributeInput{ 1225 DisableApiTermination: &ec2.AttributeBooleanValue{Value: &disableDelete}, 1226 InstanceId: &instanceId, 1227 } 1228 ec2Client, err := self.getEc2Client() 1229 if err != nil { 1230 return errors.Wrap(err, "getEc2Client") 1231 } 1232 _, err = ec2Client.ModifyInstanceAttribute(p2) 1233 return errors.Wrap(err, "ModifyInstanceAttribute") 1234 } 1235 1236 func (self *SRegion) getPasswordData(instanceId string) (string, error) { 1237 params := &ec2.GetPasswordDataInput{} 1238 params.SetInstanceId(instanceId) 1239 1240 ec2Client, err := self.getEc2Client() 1241 if err != nil { 1242 return "", errors.Wrap(err, "getEc2Client") 1243 } 1244 ret, err := ec2Client.GetPasswordData(params) 1245 if err != nil { 1246 return "", err 1247 } 1248 1249 return *ret.PasswordData, nil 1250 } 1251 1252 func (self *SInstance) Renew(bc billing.SBillingCycle) error { 1253 return cloudprovider.ErrNotSupported 1254 } 1255 1256 func (self *SInstance) GetProjectId() string { 1257 return "" 1258 } 1259 1260 func (self *SInstance) GetError() error { 1261 return nil 1262 } 1263 1264 func (self *SInstance) SetTags(tags map[string]string, replace bool) error { 1265 ec2Client, err := self.host.zone.region.getEc2Client() 1266 if err != nil { 1267 return errors.Wrap(err, "getEc2Client") 1268 } 1269 oldTagsJson, err := FetchTags(ec2Client, self.InstanceId) 1270 if err != nil { 1271 return errors.Wrapf(err, "FetchTags(self.host.zone.region.ec2Client, %s)", self.InstanceId) 1272 } 1273 oldTags := map[string]string{} 1274 err = oldTagsJson.Unmarshal(oldTags) 1275 if err != nil { 1276 return errors.Wrapf(err, "(%s).Unmarshal(oldTags)", oldTagsJson.String()) 1277 } 1278 addTags := map[string]string{} 1279 for k, v := range tags { 1280 if strings.HasPrefix(k, "aws:") { 1281 return errors.Wrap(cloudprovider.ErrNotSupported, "The aws: prefix is reserved for AWS use") 1282 } 1283 if _, ok := oldTags[k]; !ok { 1284 addTags[k] = v 1285 } else { 1286 if oldTags[k] != v { 1287 addTags[k] = v 1288 } 1289 } 1290 } 1291 delTags := []string{} 1292 if replace { 1293 for k := range oldTags { 1294 if _, ok := tags[k]; !ok { 1295 if !strings.HasPrefix(k, "aws:") && k != "Name" { 1296 delTags = append(delTags, k) 1297 } 1298 } 1299 } 1300 } 1301 Arn := self.GetArn() 1302 err = self.host.zone.region.UntagResources([]string{Arn}, delTags) 1303 if err != nil { 1304 return errors.Wrapf(err, "self.host.zone.region.UntagResources([]string{%s}, %s)", Arn, jsonutils.Marshal(delTags).String()) 1305 } 1306 delete(addTags, "Name") 1307 err = self.host.zone.region.TagResources([]string{Arn}, addTags) 1308 if err != nil { 1309 return errors.Wrapf(err, "self.host.zone.region.TagResources([]string{%s}, %s)", Arn, jsonutils.Marshal(addTags).String()) 1310 } 1311 return nil 1312 } 1313 1314 func (self *SInstance) GetAccountId() string { 1315 identity, err := self.host.zone.region.client.GetCallerIdentity() 1316 if err != nil { 1317 log.Errorf(err.Error() + "self.region.client.GetCallerIdentity()") 1318 return "" 1319 } 1320 return identity.Account 1321 } 1322 1323 func (self *SInstance) GetArn() string { 1324 partition := "" 1325 switch self.host.zone.region.client.GetAccessEnv() { 1326 case api.CLOUD_ACCESS_ENV_AWS_GLOBAL: 1327 partition = "aws" 1328 case api.CLOUD_ACCESS_ENV_AWS_CHINA: 1329 partition = "aws-cn" 1330 default: 1331 partition = "aws" 1332 } 1333 return fmt.Sprintf("arn:%s:ec2:%s:%s:instance/%s", partition, self.host.zone.region.GetId(), self.GetAccountId(), self.InstanceId) 1334 } 1335 1336 func (self *SRegion) SaveImage(instanceId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) { 1337 params := map[string]string{ 1338 "Description": opts.Notes, 1339 "InstanceId": instanceId, 1340 "Name": opts.Name, 1341 } 1342 ret := struct { 1343 ImageId string `xml:"imageId"` 1344 }{} 1345 err := self.ec2Request("CreateImage", params, &ret) 1346 if err != nil { 1347 return nil, errors.Wrapf(err, "CreateImage") 1348 } 1349 err = cloudprovider.Wait(time.Second*10, time.Minute*5, func() (bool, error) { 1350 _, err := self.GetImage(ret.ImageId) 1351 if err != nil { 1352 if errors.Cause(err) == cloudprovider.ErrNotFound { 1353 return false, nil 1354 } 1355 return false, errors.Wrapf(err, "GetImage(%s)", ret.ImageId) 1356 } 1357 return true, nil 1358 }) 1359 if err != nil { 1360 return nil, errors.Wrapf(err, "wait for image created") 1361 } 1362 image, err := self.GetImage(ret.ImageId) 1363 if err != nil { 1364 return nil, errors.Wrapf(err, "GetImage(%s)", ret.ImageId) 1365 } 1366 image.storageCache = self.getStoragecache() 1367 return image, nil 1368 } 1369 1370 func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) { 1371 image, err := self.host.zone.region.SaveImage(self.InstanceId, opts) 1372 if err != nil { 1373 return nil, errors.Wrapf(err, "SaveImage") 1374 } 1375 return image, nil 1376 }