yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/aliyun/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 aliyun
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"os"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/aliyun/aliyun-oss-go-sdk/oss"
    26  
    27  	"yunion.io/x/jsonutils"
    28  	"yunion.io/x/log"
    29  	"yunion.io/x/pkg/errors"
    30  
    31  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    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/cloudmux/pkg/multicloud"
    37  	"yunion.io/x/onecloud/pkg/util/qemuimg"
    38  )
    39  
    40  type SStoragecache struct {
    41  	multicloud.SResourceBase
    42  	AliyunTags
    43  	region *SRegion
    44  }
    45  
    46  func (self *SStoragecache) GetId() string {
    47  	return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Id, self.region.GetId())
    48  }
    49  
    50  func (self *SStoragecache) GetName() string {
    51  	return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Name, self.region.GetId())
    52  }
    53  
    54  func (self *SStoragecache) GetStatus() string {
    55  	return "available"
    56  }
    57  
    58  func (self *SStoragecache) Refresh() error {
    59  	return nil
    60  }
    61  
    62  func (self *SStoragecache) GetGlobalId() string {
    63  	return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Id, self.region.GetGlobalId())
    64  }
    65  
    66  func (self *SStoragecache) IsEmulated() bool {
    67  	return false
    68  }
    69  
    70  func (self *SStoragecache) GetICloudImages() ([]cloudprovider.ICloudImage, error) {
    71  	return nil, cloudprovider.ErrNotImplemented
    72  }
    73  
    74  func (self *SStoragecache) GetICustomizedCloudImages() ([]cloudprovider.ICloudImage, error) {
    75  	images := make([]SImage, 0)
    76  	for {
    77  		parts, total, err := self.region.GetImages(ImageStatusType(""), ImageOwnerSelf, nil, "", len(images), 50)
    78  		if err != nil {
    79  			return nil, errors.Wrapf(err, "GetImages")
    80  		}
    81  		images = append(images, parts...)
    82  		if len(images) >= total {
    83  			break
    84  		}
    85  	}
    86  	ret := []cloudprovider.ICloudImage{}
    87  	for i := range images {
    88  		images[i].storageCache = self
    89  		ret = append(ret, &images[i])
    90  	}
    91  	return ret, nil
    92  }
    93  
    94  func (self *SStoragecache) GetIImageById(extId string) (cloudprovider.ICloudImage, error) {
    95  	img, err := self.region.GetImage(extId)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	img.storageCache = self
   100  	return img, nil
   101  }
   102  
   103  func (self *SStoragecache) GetPath() string {
   104  	return ""
   105  }
   106  
   107  func (self *SStoragecache) UploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, callback func(float32)) (string, error) {
   108  	if len(image.ExternalId) > 0 {
   109  		status, err := self.region.GetImageStatus(image.ExternalId)
   110  		if err != nil {
   111  			log.Errorf("GetImageStatus error %s", err)
   112  		}
   113  		// 不能直接删除 ImageStatusCreating 状态的image ,需要先取消importImage Task
   114  		if status == ImageStatusCreating {
   115  			err := self.region.CancelImageImportTasks()
   116  			if err != nil {
   117  				log.Errorln(err)
   118  			}
   119  		}
   120  	}
   121  	return self.uploadImage(ctx, userCred, image, callback)
   122  }
   123  
   124  func (self *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, callback func(float32)) (string, error) {
   125  	// first upload image to oss
   126  	s := auth.GetAdminSession(ctx, options.Options.Region)
   127  
   128  	meta, reader, sizeByte, err := modules.Images.Download(s, image.ImageId, string(qemuimg.QCOW2), false)
   129  	if err != nil {
   130  		return "", err
   131  	}
   132  	log.Infof("meta data %s", meta)
   133  
   134  	bucketName := strings.ToLower(fmt.Sprintf("imgcache-%s-%s", self.region.GetId(), image.ImageId))
   135  	exist, err := self.region.IBucketExist(bucketName)
   136  	if err != nil {
   137  		return "", errors.Wrapf(err, "IBucketExist(%s)", bucketName)
   138  	}
   139  	if !exist {
   140  		log.Debugf("Bucket %s not exists, to create ...", bucketName)
   141  		err = self.region.CreateIBucket(bucketName, "", "")
   142  		if err != nil {
   143  			return "", errors.Wrapf(err, "CreateIBucket %s", bucketName)
   144  		}
   145  	} else {
   146  		log.Debugf("Bucket %s exists", bucketName)
   147  	}
   148  
   149  	defer self.region.DeleteIBucket(bucketName) // remove bucket
   150  
   151  	bucket, err := self.region.GetIBucketByName(bucketName)
   152  	if err != nil {
   153  		return "", errors.Wrapf(err, "GetIBucketByName %s", bucketName)
   154  	}
   155  	log.Debugf("To upload image to bucket %s ...", bucketName)
   156  	body := multicloud.NewProgress(sizeByte, 80, reader, callback)
   157  	err = cloudprovider.UploadObject(context.Background(), bucket, image.ImageId, 0, body, sizeByte, "", "", nil, false)
   158  	if err != nil {
   159  		return "", errors.Wrapf(err, "UploadObject %s", image.ImageId)
   160  	}
   161  
   162  	defer bucket.DeleteObject(context.Background(), image.ImageId) // remove object
   163  
   164  	imageBaseName := image.ImageId
   165  	if imageBaseName[0] >= '0' && imageBaseName[0] <= '9' {
   166  		imageBaseName = fmt.Sprintf("img%s", image.ImageId)
   167  	}
   168  	imageName := imageBaseName
   169  	nameIdx := 1
   170  
   171  	// check image name, avoid name conflict
   172  	for {
   173  		_, err = self.region.GetImageByName(imageName)
   174  		if err != nil {
   175  			if errors.Cause(err) == cloudprovider.ErrNotFound {
   176  				break
   177  			} else {
   178  				return "", err
   179  			}
   180  		}
   181  		imageName = fmt.Sprintf("%s-%d", imageBaseName, nameIdx)
   182  		nameIdx += 1
   183  	}
   184  
   185  	log.Debugf("Import image %s", imageName)
   186  
   187  	// ensure privileges
   188  	err = self.region.GetClient().EnableImageImport()
   189  	if err != nil {
   190  		return "", errors.Wrapf(err, "EnableImageImport")
   191  	}
   192  
   193  	task, err := self.region.ImportImage(imageName, image.OsArch, image.OsType, image.OsDistribution, bucketName, image.ImageId)
   194  
   195  	if err != nil {
   196  		return "", errors.Wrapf(err, "ImportImage %s %s", image.ImageId, bucketName)
   197  	}
   198  
   199  	// timeout: 1hour = 3600 seconds
   200  	err = self.region.WaitTaskStatus(ImportImageTask, task.TaskId, TaskStatusFinished, 15*time.Second, 3600*time.Second, 80, 100, callback)
   201  	if err != nil {
   202  		return task.ImageId, errors.Wrapf(err, "waitTaskStatus")
   203  	}
   204  
   205  	return task.ImageId, nil
   206  }
   207  
   208  func (self *SStoragecache) CreateIImage(snapshoutId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
   209  	if imageId, err := self.region.createIImage(snapshoutId, imageName, imageDesc); err != nil {
   210  		return nil, err
   211  	} else if image, err := self.region.GetImage(imageId); err != nil {
   212  		return nil, err
   213  	} else {
   214  		image.storageCache = self
   215  		iimage := make([]cloudprovider.ICloudImage, 1)
   216  		iimage[0] = image
   217  		if err := cloudprovider.WaitStatus(iimage[0], cloudprovider.IMAGE_STATUS_ACTIVE, 15*time.Second, 3600*time.Second); err != nil {
   218  			return nil, err
   219  		}
   220  		return iimage[0], nil
   221  	}
   222  }
   223  
   224  func (self *SRegion) CheckBucket(bucketName string) (*oss.Bucket, error) {
   225  	return self.checkBucket(bucketName)
   226  }
   227  
   228  func (self *SRegion) checkBucket(bucketName string) (*oss.Bucket, error) {
   229  	oss, err := self.GetOssClient()
   230  	if err != nil {
   231  		log.Errorf("GetOssClient err %s", err)
   232  		return nil, err
   233  	}
   234  	if exist, err := oss.IsBucketExist(bucketName); err != nil {
   235  		log.Errorf("IsBucketExist err %s", err)
   236  		return nil, err
   237  	} else if !exist {
   238  		log.Debugf("Bucket %s not exists, to create ...", bucketName)
   239  		if err := oss.CreateBucket(bucketName); err != nil {
   240  			log.Errorf("Create bucket error %s", err)
   241  			return nil, err
   242  		}
   243  	}
   244  	log.Debugf("Bucket %s exists", bucketName)
   245  	if bucket, err := oss.Bucket(bucketName); err != nil {
   246  		log.Errorf("Bucket error %s %s", bucketName, err)
   247  		return nil, err
   248  	} else {
   249  		return bucket, nil
   250  	}
   251  }
   252  
   253  func (self *SRegion) CreateImage(snapshoutId, imageName, imageDesc string) (string, error) {
   254  	return self.createIImage(snapshoutId, imageName, imageDesc)
   255  }
   256  
   257  func (self *SRegion) createIImage(snapshoutId, imageName, imageDesc string) (string, error) {
   258  	params := make(map[string]string)
   259  	params["RegionId"] = self.RegionId
   260  	params["OssBucket"] = strings.ToLower(fmt.Sprintf("imgcache-%s", self.GetId()))
   261  	params["SnapshotId"] = snapshoutId
   262  	params["ImageName"] = imageName
   263  	params["Description"] = imageDesc
   264  
   265  	if _, err := self.checkBucket(params["OssBucket"]); err != nil {
   266  		return "", err
   267  	}
   268  
   269  	if body, err := self.ecsRequest("CreateImage", params); err != nil {
   270  		log.Errorf("CreateImage fail %s", err)
   271  		return "", err
   272  	} else {
   273  		log.Infof("%s", body)
   274  		return body.GetString("ImageId")
   275  	}
   276  }
   277  
   278  func (self *SStoragecache) DownloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) {
   279  	return self.downloadImage(userCred, imageId, extId, path)
   280  }
   281  
   282  // 定义进度条监听器。
   283  type OssProgressListener struct {
   284  }
   285  
   286  // 定义进度变更事件处理函数。
   287  func (listener *OssProgressListener) ProgressChanged(event *oss.ProgressEvent) {
   288  	switch event.EventType {
   289  	case oss.TransferStartedEvent:
   290  		log.Debugf("Transfer Started, ConsumedBytes: %d, TotalBytes %d.\n",
   291  			event.ConsumedBytes, event.TotalBytes)
   292  	case oss.TransferDataEvent:
   293  		log.Debugf("\rTransfer Data, ConsumedBytes: %d, TotalBytes %d, %d%%.",
   294  			event.ConsumedBytes, event.TotalBytes, event.ConsumedBytes*100/event.TotalBytes)
   295  	case oss.TransferCompletedEvent:
   296  		log.Debugf("\nTransfer Completed, ConsumedBytes: %d, TotalBytes %d.\n",
   297  			event.ConsumedBytes, event.TotalBytes)
   298  	case oss.TransferFailedEvent:
   299  		log.Debugf("\nTransfer Failed, ConsumedBytes: %d, TotalBytes %d.\n",
   300  			event.ConsumedBytes, event.TotalBytes)
   301  	default:
   302  	}
   303  }
   304  
   305  func (self *SStoragecache) downloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) {
   306  	err := self.region.GetClient().EnableImageExport()
   307  	if err != nil {
   308  		log.Errorf("fail to enable export privileges: %s", err)
   309  		return nil, err
   310  	}
   311  
   312  	tmpImageFile, err := ioutil.TempFile(path, extId)
   313  	if err != nil {
   314  		return nil, err
   315  	}
   316  	defer tmpImageFile.Close()
   317  	defer os.Remove(tmpImageFile.Name())
   318  	bucketName := strings.ToLower(fmt.Sprintf("imgcache-%s", self.region.GetId()))
   319  	if bucket, err := self.region.checkBucket(bucketName); err != nil {
   320  		return nil, err
   321  	} else if _, err := self.region.GetImage(extId); err != nil {
   322  		return nil, err
   323  	} else if task, err := self.region.ExportImage(extId, bucket); err != nil {
   324  		return nil, err
   325  	} else if err := self.region.waitTaskStatus(ExportImageTask, task.TaskId, TaskStatusFinished, 15*time.Second, 3600*time.Second); err != nil {
   326  		return nil, err
   327  	} else if imageList, err := bucket.ListObjects(oss.Prefix(fmt.Sprintf("%sexport", strings.Replace(extId, "-", "", -1)))); err != nil {
   328  		return nil, err
   329  	} else if len(imageList.Objects) != 1 {
   330  		return nil, fmt.Errorf("exported image not find")
   331  	} else if err := bucket.DownloadFile(imageList.Objects[0].Key, tmpImageFile.Name(), 12*1024*1024, oss.Routines(3), oss.Progress(&OssProgressListener{})); err != nil {
   332  		return nil, err
   333  	} else {
   334  		s := auth.GetAdminSession(context.Background(), options.Options.Region)
   335  		params := jsonutils.Marshal(map[string]string{"image_id": imageId, "disk-format": "raw"})
   336  		if result, err := modules.Images.Upload(s, params, tmpImageFile, imageList.Objects[0].Size); err != nil {
   337  			return nil, err
   338  		} else {
   339  			return result, nil
   340  		}
   341  	}
   342  }
   343  
   344  func (region *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) {
   345  	storageCache := region.getStoragecache()
   346  	return []cloudprovider.ICloudStoragecache{storageCache}, nil
   347  }
   348  
   349  func (region *SRegion) GetIStoragecacheById(id string) (cloudprovider.ICloudStoragecache, error) {
   350  	storageCache := region.getStoragecache()
   351  	if id == storageCache.GetGlobalId() {
   352  		return storageCache, nil
   353  	}
   354  	return nil, cloudprovider.ErrNotFound
   355  }