yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/ucloud/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 ucloud
    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  	"yunion.io/x/pkg/utils"
    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  	UcloudTags
    41  	region *SRegion
    42  }
    43  
    44  func GetBucketName(regionId string, imageId string) string {
    45  	return fmt.Sprintf("imgcache-%s-%s", strings.ToLower(regionId), strings.ToLower(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  	images, err := self.region.GetImages("", "Custom")
    78  	if err != nil {
    79  		return nil, errors.Wrapf(err, "GetImages")
    80  	}
    81  	ret := []cloudprovider.ICloudImage{}
    82  	for i := range images {
    83  		images[i].storageCache = self
    84  		ret = append(ret, &images[i])
    85  	}
    86  	return ret, nil
    87  }
    88  
    89  func (self *SStoragecache) GetIImageById(extId string) (cloudprovider.ICloudImage, error) {
    90  	image, err := self.region.GetImage(extId)
    91  	image.storageCache = self
    92  	return &image, err
    93  }
    94  
    95  func (self *SStoragecache) GetPath() string {
    96  	return ""
    97  }
    98  
    99  func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
   100  	return nil, cloudprovider.ErrNotSupported
   101  }
   102  
   103  func (self *SStoragecache) DownloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) {
   104  	return nil, cloudprovider.ErrNotImplemented
   105  }
   106  
   107  // https://docs.ucloud.cn/api/uhost-api/import_custom_image
   108  func (self *SStoragecache) UploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, callback func(progress float32)) (string, error) {
   109  	return self.uploadImage(ctx, userCred, image, callback)
   110  }
   111  
   112  func (self *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, callback func(progress float32)) (string, error) {
   113  	if len(image.OsVersion) == 0 {
   114  		return "", fmt.Errorf("uploadImage os version is empty")
   115  	}
   116  
   117  	bucketName := GetBucketName(self.region.GetId(), image.ImageId)
   118  
   119  	// create bucket
   120  	exist, err := self.region.IBucketExist(bucketName)
   121  	if err != nil {
   122  		return "", errors.Wrap(err, "self.region.IBucketExist")
   123  	}
   124  	if !exist {
   125  		err = self.region.CreateBucket(bucketName, "private")
   126  		if err != nil {
   127  			return "", errors.Wrap(err, "CreateBucket")
   128  		}
   129  	}
   130  	defer func() {
   131  		e := self.region.DeleteBucket(bucketName)
   132  		if e != nil {
   133  			log.Errorf("uploadImage delete bucket %s", e.Error())
   134  		}
   135  	}()
   136  
   137  	// upload to  ucloud
   138  	s := auth.GetAdminSession(ctx, options.Options.Region)
   139  	meta, reader, size, err := modules.Images.Download(s, image.ImageId, string(qemuimg.VMDK), false)
   140  	if err != nil {
   141  		return "", err
   142  	}
   143  	log.Debugf("Images meta data %s", meta)
   144  	minDiskMB, _ := meta.Int("min_disk")
   145  	minDiskGB := int64(math.Ceil(float64(minDiskMB) / 1024))
   146  
   147  	if minDiskGB < 40 {
   148  		minDiskGB = 40
   149  	} else if minDiskGB > 1024 {
   150  		minDiskGB = 1024
   151  	}
   152  	// size, _ := meta.Int("size")
   153  	md5, _ := meta.GetString("checksum")
   154  	diskFormat, _ := meta.GetString("disk_format")
   155  	// upload to ucloud
   156  	bucket, err := self.region.GetIBucketById(bucketName)
   157  	if err != nil {
   158  		return "", errors.Wrap(err, "GetIBucketByName")
   159  	}
   160  	file := SFile{
   161  		bucket: bucket.(*SBucket),
   162  		file:   multicloud.NewProgress(size, 98, reader, callback),
   163  
   164  		Size:     size,
   165  		FileName: image.ImageId,
   166  		Hash:     md5,
   167  	}
   168  
   169  	err = file.Upload()
   170  	if err != nil {
   171  		return "", err
   172  	}
   173  	defer func() {
   174  		e := file.Delete()
   175  		if e != nil {
   176  			log.Errorf("uploadImage delete object %s", e.Error())
   177  		}
   178  	}() // remove object
   179  
   180  	// check image name, avoid name conflict
   181  	imageBaseName := image.ImageId
   182  	if imageBaseName[0] >= '0' && imageBaseName[0] <= '9' {
   183  		imageBaseName = fmt.Sprintf("img%s", image.ImageId)
   184  	}
   185  	imageName := imageBaseName
   186  	nameIdx := 1
   187  
   188  	for {
   189  		_, err = self.region.GetImageByName(imageName)
   190  		if err != nil {
   191  			if errors.Cause(err) == cloudprovider.ErrNotFound {
   192  				break
   193  			} else {
   194  				return "", err
   195  			}
   196  		}
   197  
   198  		imageName = fmt.Sprintf("%s-%d", imageBaseName, nameIdx)
   199  		nameIdx += 1
   200  		log.Debugf("uploadImage Match remote name %s", imageName)
   201  	}
   202  
   203  	imgId, err := self.region.ImportImage(imageName, file.FetchFileUrl(), image.OsDistribution, image.OsVersion, diskFormat)
   204  
   205  	if err != nil {
   206  		log.Errorf("ImportImage error %s %s", file.FetchFileUrl(), err)
   207  		return "", err
   208  	}
   209  
   210  	// timeout: 1hour = 3600 seconds
   211  	err = cloudprovider.WaitCreated(60*time.Second, 3600*time.Second, func() bool {
   212  		image, err := self.region.GetImage(imgId)
   213  		if err == nil && image.State == "Available" {
   214  			return true
   215  		}
   216  
   217  		return false
   218  	})
   219  	if err != nil {
   220  		return "", errors.Wrap(err, "UploadImage")
   221  	}
   222  	if callback != nil {
   223  		callback(100)
   224  	}
   225  	return imgId, err
   226  }
   227  
   228  // https://docs.ucloud.cn/api/uhost-api/create_custom_image
   229  func (self *SRegion) createIImage(snapshotId, imageName, imageDesc string) (string, error) {
   230  	return "", cloudprovider.ErrNotSupported
   231  }
   232  
   233  /*func (self *SRegion) GetBucketDomain(name string) (string, error) {
   234  	params := NewUcloudParams()
   235  	params.Set("BucketName", name)
   236  
   237  	res := make([]SBucket, 0)
   238  	err := self.client.DoListAll("DescribeBucket", params, &res)
   239  	if err != nil {
   240  		return "", err
   241  	}
   242  
   243  	if len(res) == 1 && len(res[0].Domain.Src) >= 1 {
   244  		return res[0].Domain.Src[0], nil
   245  	} else {
   246  		return "", fmt.Errorf("GetBucketDomain failed. %v", res)
   247  	}
   248  }*/
   249  
   250  // https://docs.ucloud.cn/api/ufile-api/create_bucket
   251  func (self *SRegion) CreateBucket(name, bucketType string) error {
   252  	params := NewUcloudParams()
   253  	params.Set("BucketName", name)
   254  	params.Set("Type", bucketType)
   255  	err := self.DoAction("CreateBucket", params, nil)
   256  	if err != nil {
   257  		return err
   258  	}
   259  	self.client.invalidateIBuckets()
   260  	return nil
   261  }
   262  
   263  // https://docs.ucloud.cn/api/ufile-api/delete_bucket
   264  func (self *SRegion) DeleteBucket(name string) error {
   265  	params := NewUcloudParams()
   266  	params.Set("BucketName", name)
   267  	err := self.DoAction("DeleteBucket", params, nil)
   268  	if err != nil {
   269  		return err
   270  	}
   271  	self.client.invalidateIBuckets()
   272  	return nil
   273  }
   274  
   275  // https://docs.ucloud.cn/api/ufile-api/put_file
   276  func (self *SRegion) uploadObj(name string) error {
   277  	params := NewUcloudParams()
   278  	params.Set("BucketName", name)
   279  	return self.DoAction("PutFile", params, nil)
   280  }
   281  
   282  func (self *SRegion) GetImageByName(name string) (*SImage, error) {
   283  	images, err := self.GetImages("Custom", "")
   284  	if err != nil {
   285  		return nil, err
   286  	}
   287  
   288  	for i := range images {
   289  		if images[i].GetName() == name {
   290  			return &images[i], nil
   291  		}
   292  	}
   293  
   294  	return nil, cloudprovider.ErrNotFound
   295  }
   296  
   297  func normalizeOsType(osType string) string {
   298  	switch strings.ToLower(osType) {
   299  	case "centos":
   300  		return "CentOS"
   301  	case "ubuntu":
   302  		return "Ubuntu"
   303  	case "windows":
   304  		return "Windows"
   305  	case "redHat":
   306  		return "RedHat"
   307  	case "debian":
   308  		return "Debian"
   309  	default:
   310  		return "Other"
   311  	}
   312  }
   313  
   314  func normalizeOsName(normalizeOsType string, fullVersion string) string {
   315  	if utils.IsInStringArray(normalizeOsType, []string{"CentOS", "RedHat", "Debian"}) {
   316  		if len(fullVersion) >= 3 {
   317  			return fmt.Sprintf("%s %s 64位", normalizeOsType, fullVersion[0:3])
   318  		} else {
   319  			return fmt.Sprintf("%s %s.0 64位", normalizeOsType, fullVersion[0:1])
   320  		}
   321  	}
   322  
   323  	if normalizeOsType == "Ubuntu" {
   324  		if len(fullVersion) >= 5 {
   325  			return fmt.Sprintf("Ubuntu %s 64位", fullVersion[0:5])
   326  		} else if len(fullVersion) >= 2 {
   327  			return fmt.Sprintf("Ubuntu %s.04 64位", fullVersion[0:2])
   328  		} else {
   329  			// 默认猜一个?
   330  			return "Ubuntu 14.04 64位"
   331  		}
   332  	}
   333  
   334  	if normalizeOsType == "Windows" {
   335  		if len(fullVersion) >= 4 {
   336  			return fmt.Sprintf("Windows %s 64位", fullVersion[0:4])
   337  		} else {
   338  			// 默认猜一个?
   339  			return "Windows 2016 64位"
   340  		}
   341  	}
   342  
   343  	return "Other"
   344  }
   345  
   346  func normalizeDiskFormat(diskFormat string) (string, error) {
   347  	switch strings.ToLower(diskFormat) {
   348  	case "raw":
   349  		return "RAW", nil
   350  	case "vhd":
   351  		return "VHD", nil
   352  	case "vmdk":
   353  		return "VMDK", nil
   354  	case "qcow2":
   355  		return "qcow2", nil
   356  	default:
   357  		return "", fmt.Errorf("unsupported image format %s", diskFormat)
   358  	}
   359  }
   360  
   361  // https://docs.ucloud.cn/api/uhost-api/import_custom_image
   362  func (self *SRegion) ImportImage(name string, ufileUrl string, osType string, osVersion string, diskFormat string) (string, error) {
   363  	format, err := normalizeDiskFormat(diskFormat)
   364  	if err != nil {
   365  		return "", err
   366  	}
   367  
   368  	nOsType := normalizeOsType(osType)
   369  
   370  	params := NewUcloudParams()
   371  	params.Set("ImageName", name)
   372  	params.Set("UFileUrl", ufileUrl)
   373  	// 操作系统平台,比如CentOS、Ubuntu、Windows、RedHat等,请参考控制台的镜像版本;若导入控制台上没有的操作系统,参数为Other
   374  	params.Set("OsType", nOsType)
   375  	// 操作系统详细版本,请参考控制台的镜像版本;OsType为Other时,输入参数为Other
   376  	params.Set("OsName", normalizeOsName(nOsType, osVersion))
   377  	params.Set("Format", format)
   378  	params.Set("Auth", "True")
   379  	params.Set("ImageDescription", osVersion)
   380  
   381  	type SImageId struct {
   382  		ImageId string
   383  	}
   384  
   385  	ret := SImageId{}
   386  	log.Debugf("ImportImage with params %s", params.String())
   387  	err = self.DoAction("ImportCustomImage", params, &ret)
   388  	if err != nil {
   389  		return "", err
   390  	}
   391  
   392  	return ret.ImageId, nil
   393  }