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 }