yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/aws/image.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 "fmt" 20 "time" 21 22 "github.com/aws/aws-sdk-go/service/ec2" 23 24 "yunion.io/x/jsonutils" 25 "yunion.io/x/log" 26 "yunion.io/x/pkg/errors" 27 "yunion.io/x/pkg/util/timeutils" 28 29 api "yunion.io/x/cloudmux/pkg/apis/compute" 30 "yunion.io/x/cloudmux/pkg/cloudprovider" 31 "yunion.io/x/cloudmux/pkg/multicloud" 32 "yunion.io/x/onecloud/pkg/util/imagetools" 33 ) 34 35 type ImageStatusType string 36 37 const ( 38 ImageStatusCreating ImageStatusType = "pending" 39 ImageStatusAvailable ImageStatusType = "available" 40 ImageStatusCreateFailed ImageStatusType = "failed" 41 42 ImageImportStatusCompleted = "completed" 43 ImageImportStatusUncompleted = "uncompleted" 44 ImageImportStatusError = "error" 45 ImageImportStatusDeleted = "deleted" 46 ) 47 48 type TImageOwnerType string 49 50 const ( 51 ImageOwnerTypeSystem = TImageOwnerType("system") 52 ImageOwnerTypeSelf = TImageOwnerType("self") 53 ImageOwnerTypeOther = TImageOwnerType("other") 54 ) 55 56 var ( 57 ImageOwnerAll = []TImageOwnerType(nil) 58 ImageOwnerSelf = []TImageOwnerType{ImageOwnerTypeSelf} 59 ImageOwnerSystem = []TImageOwnerType{ImageOwnerTypeSystem} 60 ImageOwnerSelfSystem = []TImageOwnerType{ImageOwnerTypeSystem, ImageOwnerTypeSelf} 61 ) 62 63 type ImageImportTask struct { 64 multicloud.SResourceBase 65 region *SRegion 66 67 ImageId string 68 RegionId string 69 TaskId string 70 Status string 71 } 72 73 type RootDevice struct { 74 SnapshotId string 75 Size int // GB 76 Category string // VolumeType 77 } 78 79 type SImage struct { 80 multicloud.SImageBase 81 AwsTags 82 storageCache *SStoragecache 83 84 // normalized image info 85 imgInfo *imagetools.ImageInfo 86 87 Architecture string 88 CreationTime time.Time 89 Description string 90 ImageId string 91 ImageName string 92 OSType string 93 ImageType cloudprovider.TImageType 94 // IsSupportCloudinit bool 95 EnaSupport bool 96 Platform string 97 SizeGB int 98 Status ImageStatusType 99 OwnerType string 100 // Usage string 101 RootDevice RootDevice 102 RootDeviceName string 103 // devices 104 BlockDevicesNames []string 105 106 Public bool 107 Hypervisor string 108 VirtualizationType string 109 OwnerId string 110 111 ProductCodes []*ec2.ProductCode 112 113 OSVersion string 114 OSDist string 115 OSBuildId string 116 } 117 118 func (self *ImageImportTask) GetId() string { 119 return self.TaskId 120 } 121 122 func (self *ImageImportTask) GetName() string { 123 return self.GetId() 124 } 125 126 func (self *ImageImportTask) GetGlobalId() string { 127 return self.GetId() 128 } 129 130 func (self *ImageImportTask) Refresh() error { 131 ec2Client, err := self.region.getEc2Client() 132 if err != nil { 133 return errors.Wrap(err, "getEc2Client") 134 } 135 ret, err := ec2Client.DescribeImportImageTasks(&ec2.DescribeImportImageTasksInput{ImportTaskIds: []*string{&self.TaskId}}) 136 if err != nil { 137 log.Errorf("DescribeImportImageTasks %s", err) 138 return errors.Wrap(err, "ImageImportTask.Refresh.DescribeImportImageTasks") 139 } 140 141 err = FillZero(ret) 142 if err != nil { 143 log.Errorf("DescribeImportImageTask.FillZero %s", err) 144 return errors.Wrap(err, "ImageImportTask.Refresh.FillZero") 145 } 146 147 // 打印上传进度 148 log.Debugf("DescribeImportImage Task %s", ret.String()) 149 for _, item := range ret.ImportImageTasks { 150 if StrVal(item.ImportTaskId) == self.TaskId { 151 self.ImageId = StrVal(item.ImageId) 152 self.Status = StrVal(item.Status) 153 } 154 } 155 156 return nil 157 } 158 159 func (self *ImageImportTask) IsEmulated() bool { 160 return true 161 } 162 163 func (self *ImageImportTask) GetStatus() string { 164 self.Refresh() 165 if self.Status == "completed" { 166 return ImageImportStatusCompleted 167 } else if self.Status == "deleted" { 168 return ImageImportStatusDeleted 169 } else { 170 return ImageImportStatusUncompleted 171 } 172 } 173 174 func (self *SImage) GetMinRamSizeMb() int { 175 return 0 176 } 177 178 func (self *SImage) GetId() string { 179 return self.ImageId 180 } 181 182 func (self *SImage) GetName() string { 183 if len(self.ImageName) > 0 { 184 return self.ImageName 185 } 186 187 return self.GetId() 188 } 189 190 func (self *SImage) GetGlobalId() string { 191 return self.ImageId 192 } 193 194 func (self *SImage) GetStatus() string { 195 switch self.Status { 196 case ImageStatusCreating: 197 return api.CACHED_IMAGE_STATUS_CACHING 198 case ImageStatusAvailable: 199 return api.CACHED_IMAGE_STATUS_ACTIVE 200 case ImageStatusCreateFailed: 201 return api.CACHED_IMAGE_STATUS_CACHE_FAILED 202 default: 203 return api.CACHED_IMAGE_STATUS_CACHE_FAILED 204 } 205 } 206 207 func (self *SImage) GetImageStatus() string { 208 switch self.Status { 209 case ImageStatusCreating: 210 return cloudprovider.IMAGE_STATUS_QUEUED 211 case ImageStatusAvailable: 212 return cloudprovider.IMAGE_STATUS_ACTIVE 213 case ImageStatusCreateFailed: 214 return cloudprovider.IMAGE_STATUS_KILLED 215 default: 216 return cloudprovider.IMAGE_STATUS_KILLED 217 } 218 } 219 220 func (self *SImage) Refresh() error { 221 new, err := self.storageCache.region.GetImage(self.ImageId) 222 if err != nil { 223 return err 224 } 225 return jsonutils.Update(self, new) 226 } 227 228 func (self *SImage) GetImageType() cloudprovider.TImageType { 229 return self.ImageType 230 } 231 232 func (self *SImage) GetSizeByte() int64 { 233 return int64(self.SizeGB) * 1024 * 1024 * 1024 234 } 235 236 func (self *SImage) getNormalizedImageInfo() *imagetools.ImageInfo { 237 if self.imgInfo == nil { 238 imgInfo := imagetools.NormalizeImageInfo("", self.Architecture, self.OSType, self.OSDist, self.OSVersion) 239 self.imgInfo = &imgInfo 240 } 241 return self.imgInfo 242 } 243 244 func (self *SImage) GetOsType() cloudprovider.TOsType { 245 return cloudprovider.TOsType(self.getNormalizedImageInfo().OsType) 246 } 247 248 func (self *SImage) GetOsArch() string { 249 return self.getNormalizedImageInfo().OsArch 250 } 251 252 func (self *SImage) GetOsDist() string { 253 return self.getNormalizedImageInfo().OsDistro 254 } 255 256 func (self *SImage) GetOsVersion() string { 257 return self.getNormalizedImageInfo().OsVersion 258 } 259 260 func (self *SImage) GetOsLang() string { 261 return self.getNormalizedImageInfo().OsLang 262 } 263 264 func (self *SImage) GetBios() cloudprovider.TBiosType { 265 return cloudprovider.ToBiosType(self.getNormalizedImageInfo().OsBios) 266 } 267 268 func (self *SImage) GetFullOsName() string { 269 return self.getNormalizedImageInfo().GetFullOsName() 270 } 271 272 func (self *SImage) GetMinOsDiskSizeGb() int { 273 return self.SizeGB 274 } 275 276 func (self *SImage) GetImageFormat() string { 277 return "vhd" 278 } 279 280 func (self *SImage) GetCreatedAt() time.Time { 281 return self.CreationTime 282 } 283 284 func (self *SImage) IsEmulated() bool { 285 return false 286 } 287 288 func (self *SImage) Delete(ctx context.Context) error { 289 // todo: implement me 290 return self.storageCache.region.DeleteImage(self.ImageId) 291 } 292 293 func (self *SImage) GetIStoragecache() cloudprovider.ICloudStoragecache { 294 return self.storageCache 295 } 296 297 func (self *SRegion) ImportImage(name string, osArch string, osType string, osDist string, diskFormat string, bucket string, key string) (*ImageImportTask, error) { 298 params := &ec2.ImportImageInput{} 299 params.SetArchitecture(osArch) 300 params.SetHypervisor("xen") // todo: osType? 301 params.SetPlatform(osType) // Linux|Windows 302 // https://docs.aws.amazon.com/zh_cn/vm-import/latest/userguide/vmimport-image-import.html#import-vm-image 303 params.SetRoleName("vmimport") 304 container := &ec2.ImageDiskContainer{} 305 container.SetDescription(fmt.Sprintf("vmimport %s - %s", name, osDist)) 306 container.SetFormat(diskFormat) 307 container.SetDeviceName("/dev/sda") // default /dev/sda 308 bkt := &ec2.UserBucket{S3Bucket: &bucket, S3Key: &key} 309 container.SetUserBucket(bkt) 310 params.SetDiskContainers([]*ec2.ImageDiskContainer{container}) 311 params.SetLicenseType("BYOL") // todo: AWS? 312 ec2Client, err := self.getEc2Client() 313 if err != nil { 314 return nil, errors.Wrap(err, "getEc2Client") 315 } 316 ret, err := ec2Client.ImportImage(params) 317 if err != nil { 318 return nil, errors.Wrap(err, "ImportImage") 319 } 320 log.Debugf("ImportImage task: %s", ret.String()) 321 return &ImageImportTask{ImageId: StrVal(ret.ImageId), RegionId: self.RegionId, TaskId: *ret.ImportTaskId, Status: StrVal(ret.Status), region: self}, nil 322 } 323 324 type ImageExportTask struct { 325 ImageId string 326 RegionId string 327 TaskId string 328 } 329 330 func (self *SRegion) ExportImage(instanceId string, imageId string) (*ImageExportTask, error) { 331 params := &ec2.CreateInstanceExportTaskInput{} 332 params.SetInstanceId(instanceId) 333 params.SetDescription(fmt.Sprintf("image %s export from aws", imageId)) 334 params.SetTargetEnvironment("vmware") 335 spec := &ec2.ExportToS3TaskSpecification{} 336 spec.SetContainerFormat("ova") 337 spec.SetDiskImageFormat("RAW") 338 spec.SetS3Bucket("imgcache-onecloud") 339 params.SetExportToS3Task(spec) 340 ec2Client, err := self.getEc2Client() 341 if err != nil { 342 return nil, errors.Wrap(err, "getEc2Client") 343 } 344 ret, err := ec2Client.CreateInstanceExportTask(params) 345 if err != nil { 346 return nil, errors.Wrap(err, "CreateInstanceExportTask") 347 } 348 349 return &ImageExportTask{ImageId: imageId, RegionId: self.RegionId, TaskId: *ret.ExportTask.ExportTaskId}, nil 350 } 351 352 func (self *SRegion) GetImage(imageId string) (*SImage, error) { 353 if len(imageId) == 0 { 354 return nil, fmt.Errorf("GetImage image id should not be empty") 355 } 356 357 images, err := self.getImages("", ImageOwnerAll, []string{imageId}, "", "", nil, "") 358 if err != nil { 359 return nil, errors.Wrap(err, "getImages") 360 } 361 if len(images) == 0 { 362 return nil, errors.Wrap(cloudprovider.ErrNotFound, "getImages") 363 } 364 return &images[0], nil 365 } 366 367 func (self *SRegion) GetImageByName(name string, owners []TImageOwnerType) (*SImage, error) { 368 if len(name) == 0 { 369 return nil, fmt.Errorf("image name should not be empty") 370 } 371 372 images, err := self.getImages("", owners, nil, name, "hvm", nil, "") 373 if err != nil { 374 return nil, errors.Wrap(err, "getImages") 375 } 376 if len(images) == 0 { 377 return nil, errors.Wrap(cloudprovider.ErrNotFound, "getImages") 378 } 379 380 log.Debugf("%d image found match name %s", len(images), name) 381 return &images[0], nil 382 } 383 384 func (self *SRegion) GetImageStatus(imageId string) (ImageStatusType, error) { 385 image, err := self.GetImage(imageId) 386 if err != nil { 387 return "", err 388 } 389 return image.Status, nil 390 } 391 392 func getRootDiskSize(image *ec2.Image) (int, error) { 393 rootDeivce := *image.RootDeviceName 394 for _, volume := range image.BlockDeviceMappings { 395 if len(rootDeivce) > 0 && *volume.DeviceName == rootDeivce && volume.Ebs != nil && volume.Ebs.VolumeSize != nil { 396 return int(*volume.Ebs.VolumeSize), nil 397 } 398 } 399 return 0, fmt.Errorf("image size not found: %s", image.String()) 400 } 401 402 func getLatestImage(images []SImage) SImage { 403 var latestBuild string 404 latestBuildIdx := -1 405 for i := range images { 406 if latestBuildIdx < 0 || comapreImageBuildIds(latestBuild, images[i]) < 0 { 407 latestBuild = images[i].OSBuildId 408 latestBuildIdx = i 409 } 410 } 411 return images[latestBuildIdx] 412 } 413 414 func (self *SRegion) GetImages(status ImageStatusType, owners []TImageOwnerType, imageId []string, name string, virtualizationType string, ownerIds []string, volumeType string, latest bool) ([]SImage, error) { 415 images, err := self.getImages(status, owners, imageId, name, virtualizationType, ownerIds, volumeType) 416 if err != nil { 417 return nil, errors.Wrap(err, "getImages") 418 } 419 if !latest { 420 return images, err 421 } 422 noVersionImages := make([]SImage, 0) 423 versionedImages := make(map[string][]SImage) 424 for i := range images { 425 key := fmt.Sprintf("%s%s", images[i].OSDist, images[i].OSVersion) 426 if len(key) == 0 { 427 noVersionImages = append(noVersionImages, images[i]) 428 continue 429 } 430 if _, ok := versionedImages[key]; !ok { 431 versionedImages[key] = make([]SImage, 0) 432 } 433 versionedImages[key] = append(versionedImages[key], images[i]) 434 } 435 for key := range versionedImages { 436 noVersionImages = append(noVersionImages, getLatestImage(versionedImages[key])) 437 } 438 return noVersionImages, nil 439 } 440 441 func (self *SRegion) getImages(status ImageStatusType, owners []TImageOwnerType, imageId []string, name string, virtualizationType string, ownerIds []string, volumeType string) ([]SImage, error) { 442 params := &ec2.DescribeImagesInput{} 443 filters := make([]*ec2.Filter, 0) 444 if len(status) > 0 { 445 filters = AppendSingleValueFilter(filters, "state", string(status)) 446 } 447 448 if len(name) > 0 { 449 filters = AppendSingleValueFilter(filters, "name", name) 450 } 451 452 if len(virtualizationType) > 0 { 453 filters = AppendSingleValueFilter(filters, "virtualization-type", virtualizationType) 454 } 455 456 if len(volumeType) > 0 { 457 filters = AppendSingleValueFilter(filters, "block-device-mapping.volume-type", volumeType) 458 } 459 460 filters = AppendSingleValueFilter(filters, "image-type", "machine") 461 462 if len(owners) > 0 || len(ownerIds) > 0 { 463 params.SetOwners(imageOwnerTypes2Strings(owners, ownerIds)) 464 } 465 466 if len(imageId) > 0 { 467 params.SetImageIds(ConvertedList(imageId)) 468 } 469 470 if len(filters) > 0 { 471 params.SetFilters(filters) 472 } 473 474 ec2Client, err := self.getEc2Client() 475 if err != nil { 476 return nil, errors.Wrap(err, "getEc2Client") 477 } 478 ret, err := ec2Client.DescribeImages(params) 479 err = parseNotFoundError(err) 480 if err != nil { 481 return nil, errors.Wrap(err, "parseNotFoundError") 482 } 483 484 images := []SImage{} 485 for i := range ret.Images { 486 image := ret.Images[i] 487 488 if err := FillZero(image); err != nil { 489 return nil, errors.Wrap(err, "FillZero.image") 490 } 491 492 tagspec := TagSpec{} 493 tagspec.LoadingEc2Tags(image.Tags) 494 495 size, err := getRootDiskSize(image) 496 if err != nil { 497 // fail to get disk size, ignore the image 498 /// log.Debugln(err) 499 continue 500 } 501 502 var rootDevice RootDevice 503 devicesName := []string{} 504 for _, block := range image.BlockDeviceMappings { 505 if len(*image.RootDeviceName) > 0 && *block.DeviceName == *image.RootDeviceName { 506 rootDevice.SnapshotId = *block.Ebs.SnapshotId 507 rootDevice.Category = *block.Ebs.VolumeType 508 rootDevice.Size = int(*block.Ebs.VolumeSize) 509 } 510 511 devicesName = append(devicesName, *block.DeviceName) 512 } 513 514 osType := "" 515 if StrVal(image.Platform) != "windows" { 516 osType = "Linux" 517 } else { 518 osType = "Windows" 519 } 520 521 createTime, _ := timeutils.ParseTimeStr(*image.CreationDate) 522 523 name := tagspec.GetNameTag() 524 if len(name) == 0 && image.Name != nil { 525 name = *image.Name 526 } 527 528 sImage := SImage{ 529 storageCache: self.getStoragecache(), 530 Architecture: *image.Architecture, 531 Description: *image.Description, 532 ImageId: *image.ImageId, 533 Public: *image.Public, 534 ImageName: name, 535 OSType: osType, 536 // ImageType: *image.ImageType, 537 OwnerType: *image.ImageOwnerAlias, 538 EnaSupport: *image.EnaSupport, 539 Platform: *image.Platform, 540 RootDeviceName: *image.RootDeviceName, 541 BlockDevicesNames: devicesName, 542 Status: ImageStatusType(*image.State), 543 CreationTime: createTime, 544 SizeGB: size, 545 RootDevice: rootDevice, 546 VirtualizationType: *image.VirtualizationType, 547 Hypervisor: *image.Hypervisor, 548 ProductCodes: image.ProductCodes, 549 OwnerId: *image.OwnerId, 550 } 551 sImage.ImageType = getImageType(sImage) 552 sImage.OSType = getImageOSType(sImage) 553 sImage.OSDist = getImageOSDist(sImage) 554 sImage.OSVersion = getImageOSVersion(sImage) 555 sImage.OSBuildId = getImageOSBuildID(sImage) 556 images = append(images, sImage) 557 } 558 559 return images, nil 560 } 561 562 func (self *SRegion) DeleteImage(imageId string) error { 563 params := &ec2.DeregisterImageInput{} 564 params.SetImageId(imageId) 565 ec2Client, err := self.getEc2Client() 566 if err != nil { 567 return errors.Wrap(err, "getEc2Client") 568 } 569 _, err = ec2Client.DeregisterImage(params) 570 return errors.Wrap(err, "DeregisterImage") 571 } 572 573 func (self *SRegion) addTags(resId string, key string, value string) error { 574 input := &ec2.CreateTagsInput{} 575 input.SetResources([]*string{&resId}) 576 tag := ec2.Tag{} 577 tag.Key = &key 578 tag.Value = &value 579 input.SetTags([]*ec2.Tag{&tag}) 580 ec2Client, err := self.getEc2Client() 581 if err != nil { 582 return errors.Wrap(err, "getEc2Client") 583 } 584 _, err = ec2Client.CreateTags(input) 585 if err != nil { 586 return errors.Wrap(err, "CreateTags") 587 } 588 return nil 589 }