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 }