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  }