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