yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/hcso/storagecache.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 hcso
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"math"
    21  	"strings"
    22  	"time"
    23  
    24  	"yunion.io/x/jsonutils"
    25  	"yunion.io/x/log"
    26  	"yunion.io/x/pkg/errors"
    27  
    28  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    29  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    30  	"yunion.io/x/cloudmux/pkg/multicloud"
    31  	"yunion.io/x/cloudmux/pkg/multicloud/huawei"
    32  	"yunion.io/x/onecloud/pkg/compute/options"
    33  	"yunion.io/x/onecloud/pkg/mcclient"
    34  	"yunion.io/x/onecloud/pkg/mcclient/auth"
    35  	modules "yunion.io/x/onecloud/pkg/mcclient/modules/image"
    36  	"yunion.io/x/onecloud/pkg/util/qemuimg"
    37  )
    38  
    39  type SStoragecache struct {
    40  	multicloud.SResourceBase
    41  	huawei.HuaweiTags
    42  	region  *SRegion
    43  	iimages []cloudprovider.ICloudImage
    44  }
    45  
    46  func GetBucketName(regionId string, imageId string) string {
    47  	return fmt.Sprintf("imgcache-%s-%s", strings.ToLower(regionId), imageId)
    48  }
    49  
    50  func (self *SStoragecache) GetId() string {
    51  	return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Id, self.region.GetId())
    52  }
    53  
    54  func (self *SStoragecache) GetName() string {
    55  	return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Name, self.region.GetId())
    56  }
    57  
    58  func (self *SStoragecache) GetGlobalId() string {
    59  	return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Id, self.region.GetGlobalId())
    60  }
    61  
    62  func (self *SStoragecache) GetStatus() string {
    63  	return "available"
    64  }
    65  
    66  func (self *SStoragecache) Refresh() error {
    67  	return nil
    68  }
    69  
    70  func (self *SStoragecache) IsEmulated() bool {
    71  	return false
    72  }
    73  
    74  func (self *SStoragecache) fetchImages() error {
    75  	imagesGold, err := self.region.GetImages("", ImageOwnerPublic, "", EnvFusionCompute)
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	imagesSelf, err := self.region.GetImages("", ImageOwnerSelf, "", EnvFusionCompute)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	self.iimages = make([]cloudprovider.ICloudImage, len(imagesGold)+len(imagesSelf))
    86  	for i := range imagesGold {
    87  		imagesGold[i].storageCache = self
    88  		self.iimages[i] = &imagesGold[i]
    89  	}
    90  
    91  	l := len(imagesGold)
    92  	for i := range imagesSelf {
    93  		imagesSelf[i].storageCache = self
    94  		self.iimages[i+l] = &imagesSelf[i]
    95  	}
    96  	return nil
    97  }
    98  
    99  func (self *SStoragecache) GetICloudImages() ([]cloudprovider.ICloudImage, error) {
   100  	if self.iimages == nil {
   101  		err := self.fetchImages()
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  	}
   106  	return self.iimages, nil
   107  }
   108  
   109  func (self *SStoragecache) GetICustomizedCloudImages() ([]cloudprovider.ICloudImage, error) {
   110  	return nil, cloudprovider.ErrNotImplemented
   111  }
   112  
   113  func (self *SStoragecache) GetIImageById(extId string) (cloudprovider.ICloudImage, error) {
   114  	image, err := self.region.GetImage(extId)
   115  	if err != nil {
   116  		return nil, errors.Wrap(err, "self.region.GetImage")
   117  	}
   118  	image.storageCache = self
   119  	return image, nil
   120  }
   121  
   122  func (self *SStoragecache) GetPath() string {
   123  	return ""
   124  }
   125  
   126  // 目前支持使用vhd、zvhd、vmdk、qcow2、raw、zvhd2、vhdx、qcow、vdi或qed格式镜像文件创建私有镜像。
   127  // 快速通道功能可快速完成镜像制作,但镜像文件需转换为raw或zvhd2格式并完成镜像优化。
   128  // https://support.huaweicloud.com/api-ims/zh-cn_topic_0083905788.html
   129  func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
   130  	if imageId, err := self.region.createIImage(snapshotId, imageName, imageDesc); err != nil {
   131  		return nil, err
   132  	} else if image, err := self.region.GetImage(imageId); err != nil {
   133  		return nil, err
   134  	} else {
   135  		image.storageCache = self
   136  		iimage := make([]cloudprovider.ICloudImage, 1)
   137  		iimage[0] = image
   138  		if err := cloudprovider.WaitStatus(iimage[0], "avaliable", 15*time.Second, 3600*time.Second); err != nil {
   139  			return nil, err
   140  		}
   141  		return iimage[0], nil
   142  	}
   143  }
   144  
   145  func (self *SStoragecache) DownloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) {
   146  	return self.downloadImage(userCred, imageId, extId)
   147  }
   148  
   149  func (self *SStoragecache) downloadImage(userCred mcclient.TokenCredential, imageId string, extId string) (jsonutils.JSONObject, error) {
   150  	return nil, cloudprovider.ErrNotImplemented
   151  }
   152  
   153  func (self *SStoragecache) UploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, callback func(progress float32)) (string, error) {
   154  	return self.uploadImage(ctx, userCred, image, callback)
   155  }
   156  
   157  func (self *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, callback func(progress float32)) (string, error) {
   158  	bucketName := GetBucketName(self.region.GetId(), image.ImageId)
   159  
   160  	exist, _ := self.region.IBucketExist(bucketName)
   161  	if !exist {
   162  		err := self.region.CreateIBucket(bucketName, "", "")
   163  		if err != nil {
   164  			return "", errors.Wrap(err, "CreateIBucket")
   165  		}
   166  	}
   167  	defer self.region.DeleteIBucket(bucketName)
   168  
   169  	// upload to huawei cloud
   170  	s := auth.GetAdminSession(ctx, options.Options.Region)
   171  	meta, reader, sizeByte, err := modules.Images.Download(s, image.ImageId, string(qemuimg.VMDK), false)
   172  	if err != nil {
   173  		return "", errors.Wrap(err, "Images.Download")
   174  	}
   175  	log.Debugf("Images meta data %s", meta)
   176  
   177  	minDiskMB, _ := meta.Int("min_disk")
   178  	minDiskGB := int64(math.Ceil(float64(minDiskMB) / 1024))
   179  	// 在使用OBS桶的外部镜像文件制作镜像时生效且为必选字段。取值为40~1024GB。
   180  	if minDiskGB < 40 {
   181  		minDiskGB = 40
   182  	} else if minDiskGB > 1024 {
   183  		minDiskGB = 1024
   184  	}
   185  
   186  	bucket, err := self.region.GetIBucketByName(bucketName)
   187  	if err != nil {
   188  		return "", errors.Wrapf(err, "GetIBucketByName %s", bucketName)
   189  	}
   190  
   191  	body := multicloud.NewProgress(sizeByte, 80, reader, callback)
   192  	err = cloudprovider.UploadObject(context.Background(), bucket, image.ImageId, 0, body, sizeByte, "", "", nil, false)
   193  	if err != nil {
   194  		return "", errors.Wrap(err, "cloudprovider.UploadObject")
   195  	}
   196  
   197  	defer bucket.DeleteObject(context.Background(), image.ImageId)
   198  
   199  	// check image name, avoid name conflict
   200  	imageBaseName := image.ImageId
   201  	if imageBaseName[0] >= '0' && imageBaseName[0] <= '9' {
   202  		imageBaseName = fmt.Sprintf("img%s", image.ImageId)
   203  	}
   204  	imageName := imageBaseName
   205  	nameIdx := 1
   206  
   207  	for {
   208  		_, err = self.region.GetImageByName(imageName)
   209  		if err != nil {
   210  			if errors.Cause(err) == cloudprovider.ErrNotFound {
   211  				break
   212  			} else {
   213  				return "", err
   214  			}
   215  		}
   216  
   217  		imageName = fmt.Sprintf("%s-%d", imageBaseName, nameIdx)
   218  		nameIdx += 1
   219  		log.Debugf("uploadImage Match remote name %s", imageName)
   220  	}
   221  
   222  	jobId, err := self.region.ImportImageJob(imageName, image.OsDistribution, image.OsVersion, image.OsArch, bucketName, image.ImageId, int64(minDiskGB))
   223  
   224  	if err != nil {
   225  		log.Errorf("ImportImage error %s %s %s %s", jobId, image.ImageId, bucketName, err)
   226  		return "", err
   227  	}
   228  
   229  	// timeout: 1hour = 3600 seconds
   230  	serviceType := self.region.ecsClient.Images.ServiceType()
   231  	err = self.region.waitTaskStatus(serviceType, jobId, TASK_SUCCESS, 15*time.Second, 3600*time.Second)
   232  	if err != nil {
   233  		log.Errorf("waitTaskStatus %s", err)
   234  		return "", err
   235  	}
   236  
   237  	if callback != nil {
   238  		callback(100)
   239  	}
   240  
   241  	// https://support.huaweicloud.com/api-ims/zh-cn_topic_0022473688.html
   242  	return self.region.GetTaskEntityID(serviceType, jobId, "image_id")
   243  }
   244  
   245  func (self *SRegion) getStoragecache() *SStoragecache {
   246  	if self.storageCache == nil {
   247  		self.storageCache = &SStoragecache{region: self}
   248  	}
   249  	return self.storageCache
   250  }
   251  
   252  type SJob struct {
   253  	Status     string            `json:"status"`
   254  	Entities   map[string]string `json:"entities"`
   255  	JobID      string            `json:"job_id"`
   256  	JobType    string            `json:"job_type"`
   257  	BeginTime  string            `json:"begin_time"`
   258  	EndTime    string            `json:"end_time"`
   259  	ErrorCode  string            `json:"error_code"`
   260  	FailReason string            `json:"fail_reason"`
   261  }
   262  
   263  // https://support.huaweicloud.com/api-ims/zh-cn_topic_0020092109.html
   264  func (self *SRegion) createIImage(snapshotId, imageName, imageDesc string) (string, error) {
   265  	snapshot, err := self.GetSnapshotById(snapshotId)
   266  	if err != nil {
   267  		return "", err
   268  	}
   269  
   270  	disk, err := self.GetDisk(snapshot.VolumeID)
   271  	if err != nil {
   272  		return "", err
   273  	}
   274  
   275  	if disk.GetDiskType() != api.DISK_TYPE_SYS {
   276  		return "", fmt.Errorf("disk type err, expected disk type %s", api.DISK_TYPE_SYS)
   277  	}
   278  
   279  	if len(disk.Attachments) == 0 {
   280  		return "", fmt.Errorf("disk is not attached.")
   281  	}
   282  
   283  	imageObj := jsonutils.NewDict()
   284  	imageObj.Add(jsonutils.NewString(disk.Attachments[0].ServerID), "instance_id")
   285  	imageObj.Add(jsonutils.NewString(imageName), "name")
   286  	imageObj.Add(jsonutils.NewString(imageDesc), "description")
   287  
   288  	ret, err := self.ecsClient.Images.PerformAction2("action", "", imageObj, "")
   289  	if err != nil {
   290  		return "", err
   291  	}
   292  
   293  	job := SJob{}
   294  	jobId, err := ret.GetString("job_id")
   295  	querys := map[string]string{"service_type": self.ecsClient.Images.ServiceType()}
   296  	err = DoGet(self.ecsClient.Jobs.Get, jobId, querys, &job)
   297  	if err != nil {
   298  		return "", err
   299  	}
   300  
   301  	if job.Status == "SUCCESS" {
   302  		imageId, exists := job.Entities["image_id"]
   303  		if exists {
   304  			return imageId, nil
   305  		} else {
   306  			return "", fmt.Errorf("image id not found in create image job %s", job.JobID)
   307  		}
   308  	} else {
   309  		return "", fmt.Errorf("create image failed, %s", job.FailReason)
   310  	}
   311  
   312  }
   313  
   314  func (self *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) {
   315  	storageCache := self.getStoragecache()
   316  	return []cloudprovider.ICloudStoragecache{storageCache}, nil
   317  }
   318  
   319  func (self *SRegion) GetIStoragecacheById(idstr string) (cloudprovider.ICloudStoragecache, error) {
   320  	storageCache := self.getStoragecache()
   321  	if storageCache.GetGlobalId() == idstr {
   322  		return storageCache, nil
   323  	}
   324  	return nil, cloudprovider.ErrNotFound
   325  }