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