yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/hcso/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 hcso 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 28 api "yunion.io/x/cloudmux/pkg/apis/compute" 29 "yunion.io/x/cloudmux/pkg/cloudprovider" 30 "yunion.io/x/cloudmux/pkg/multicloud" 31 "yunion.io/x/cloudmux/pkg/multicloud/huawei" 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/onecloud/pkg/util/qemuimg" 37 ) 38 39 type SStoragecache struct { 40 multicloud.SResourceBase 41 huawei.HuaweiTags 42 region *SRegion 43 iimages []cloudprovider.ICloudImage 44 } 45 46 func GetBucketName(regionId string, imageId string) string { 47 return fmt.Sprintf("imgcache-%s-%s", strings.ToLower(regionId), imageId) 48 } 49 50 func (self *SStoragecache) GetId() string { 51 return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Id, self.region.GetId()) 52 } 53 54 func (self *SStoragecache) GetName() string { 55 return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Name, self.region.GetId()) 56 } 57 58 func (self *SStoragecache) GetGlobalId() string { 59 return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Id, self.region.GetGlobalId()) 60 } 61 62 func (self *SStoragecache) GetStatus() string { 63 return "available" 64 } 65 66 func (self *SStoragecache) Refresh() error { 67 return nil 68 } 69 70 func (self *SStoragecache) IsEmulated() bool { 71 return false 72 } 73 74 func (self *SStoragecache) fetchImages() error { 75 imagesGold, err := self.region.GetImages("", ImageOwnerPublic, "", EnvFusionCompute) 76 if err != nil { 77 return err 78 } 79 80 imagesSelf, err := self.region.GetImages("", ImageOwnerSelf, "", EnvFusionCompute) 81 if err != nil { 82 return err 83 } 84 85 self.iimages = make([]cloudprovider.ICloudImage, len(imagesGold)+len(imagesSelf)) 86 for i := range imagesGold { 87 imagesGold[i].storageCache = self 88 self.iimages[i] = &imagesGold[i] 89 } 90 91 l := len(imagesGold) 92 for i := range imagesSelf { 93 imagesSelf[i].storageCache = self 94 self.iimages[i+l] = &imagesSelf[i] 95 } 96 return nil 97 } 98 99 func (self *SStoragecache) GetICloudImages() ([]cloudprovider.ICloudImage, error) { 100 if self.iimages == nil { 101 err := self.fetchImages() 102 if err != nil { 103 return nil, err 104 } 105 } 106 return self.iimages, nil 107 } 108 109 func (self *SStoragecache) GetICustomizedCloudImages() ([]cloudprovider.ICloudImage, error) { 110 return nil, cloudprovider.ErrNotImplemented 111 } 112 113 func (self *SStoragecache) GetIImageById(extId string) (cloudprovider.ICloudImage, error) { 114 image, err := self.region.GetImage(extId) 115 if err != nil { 116 return nil, errors.Wrap(err, "self.region.GetImage") 117 } 118 image.storageCache = self 119 return image, nil 120 } 121 122 func (self *SStoragecache) GetPath() string { 123 return "" 124 } 125 126 // 目前支持使用vhd、zvhd、vmdk、qcow2、raw、zvhd2、vhdx、qcow、vdi或qed格式镜像文件创建私有镜像。 127 // 快速通道功能可快速完成镜像制作,但镜像文件需转换为raw或zvhd2格式并完成镜像优化。 128 // https://support.huaweicloud.com/api-ims/zh-cn_topic_0083905788.html 129 func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) { 130 if imageId, err := self.region.createIImage(snapshotId, imageName, imageDesc); err != nil { 131 return nil, err 132 } else if image, err := self.region.GetImage(imageId); err != nil { 133 return nil, err 134 } else { 135 image.storageCache = self 136 iimage := make([]cloudprovider.ICloudImage, 1) 137 iimage[0] = image 138 if err := cloudprovider.WaitStatus(iimage[0], "avaliable", 15*time.Second, 3600*time.Second); err != nil { 139 return nil, err 140 } 141 return iimage[0], nil 142 } 143 } 144 145 func (self *SStoragecache) DownloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) { 146 return self.downloadImage(userCred, imageId, extId) 147 } 148 149 func (self *SStoragecache) downloadImage(userCred mcclient.TokenCredential, imageId string, extId string) (jsonutils.JSONObject, error) { 150 return nil, cloudprovider.ErrNotImplemented 151 } 152 153 func (self *SStoragecache) UploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, callback func(progress float32)) (string, error) { 154 return self.uploadImage(ctx, userCred, image, callback) 155 } 156 157 func (self *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, callback func(progress float32)) (string, error) { 158 bucketName := GetBucketName(self.region.GetId(), image.ImageId) 159 160 exist, _ := self.region.IBucketExist(bucketName) 161 if !exist { 162 err := self.region.CreateIBucket(bucketName, "", "") 163 if err != nil { 164 return "", errors.Wrap(err, "CreateIBucket") 165 } 166 } 167 defer self.region.DeleteIBucket(bucketName) 168 169 // upload to huawei cloud 170 s := auth.GetAdminSession(ctx, options.Options.Region) 171 meta, reader, sizeByte, err := modules.Images.Download(s, image.ImageId, string(qemuimg.VMDK), false) 172 if err != nil { 173 return "", errors.Wrap(err, "Images.Download") 174 } 175 log.Debugf("Images meta data %s", meta) 176 177 minDiskMB, _ := meta.Int("min_disk") 178 minDiskGB := int64(math.Ceil(float64(minDiskMB) / 1024)) 179 // 在使用OBS桶的外部镜像文件制作镜像时生效且为必选字段。取值为40~1024GB。 180 if minDiskGB < 40 { 181 minDiskGB = 40 182 } else if minDiskGB > 1024 { 183 minDiskGB = 1024 184 } 185 186 bucket, err := self.region.GetIBucketByName(bucketName) 187 if err != nil { 188 return "", errors.Wrapf(err, "GetIBucketByName %s", bucketName) 189 } 190 191 body := multicloud.NewProgress(sizeByte, 80, reader, callback) 192 err = cloudprovider.UploadObject(context.Background(), bucket, image.ImageId, 0, body, sizeByte, "", "", nil, false) 193 if err != nil { 194 return "", errors.Wrap(err, "cloudprovider.UploadObject") 195 } 196 197 defer bucket.DeleteObject(context.Background(), image.ImageId) 198 199 // check image name, avoid name conflict 200 imageBaseName := image.ImageId 201 if imageBaseName[0] >= '0' && imageBaseName[0] <= '9' { 202 imageBaseName = fmt.Sprintf("img%s", image.ImageId) 203 } 204 imageName := imageBaseName 205 nameIdx := 1 206 207 for { 208 _, err = self.region.GetImageByName(imageName) 209 if err != nil { 210 if errors.Cause(err) == cloudprovider.ErrNotFound { 211 break 212 } else { 213 return "", err 214 } 215 } 216 217 imageName = fmt.Sprintf("%s-%d", imageBaseName, nameIdx) 218 nameIdx += 1 219 log.Debugf("uploadImage Match remote name %s", imageName) 220 } 221 222 jobId, err := self.region.ImportImageJob(imageName, image.OsDistribution, image.OsVersion, image.OsArch, bucketName, image.ImageId, int64(minDiskGB)) 223 224 if err != nil { 225 log.Errorf("ImportImage error %s %s %s %s", jobId, image.ImageId, bucketName, err) 226 return "", err 227 } 228 229 // timeout: 1hour = 3600 seconds 230 serviceType := self.region.ecsClient.Images.ServiceType() 231 err = self.region.waitTaskStatus(serviceType, jobId, TASK_SUCCESS, 15*time.Second, 3600*time.Second) 232 if err != nil { 233 log.Errorf("waitTaskStatus %s", err) 234 return "", err 235 } 236 237 if callback != nil { 238 callback(100) 239 } 240 241 // https://support.huaweicloud.com/api-ims/zh-cn_topic_0022473688.html 242 return self.region.GetTaskEntityID(serviceType, jobId, "image_id") 243 } 244 245 func (self *SRegion) getStoragecache() *SStoragecache { 246 if self.storageCache == nil { 247 self.storageCache = &SStoragecache{region: self} 248 } 249 return self.storageCache 250 } 251 252 type SJob struct { 253 Status string `json:"status"` 254 Entities map[string]string `json:"entities"` 255 JobID string `json:"job_id"` 256 JobType string `json:"job_type"` 257 BeginTime string `json:"begin_time"` 258 EndTime string `json:"end_time"` 259 ErrorCode string `json:"error_code"` 260 FailReason string `json:"fail_reason"` 261 } 262 263 // https://support.huaweicloud.com/api-ims/zh-cn_topic_0020092109.html 264 func (self *SRegion) createIImage(snapshotId, imageName, imageDesc string) (string, error) { 265 snapshot, err := self.GetSnapshotById(snapshotId) 266 if err != nil { 267 return "", err 268 } 269 270 disk, err := self.GetDisk(snapshot.VolumeID) 271 if err != nil { 272 return "", err 273 } 274 275 if disk.GetDiskType() != api.DISK_TYPE_SYS { 276 return "", fmt.Errorf("disk type err, expected disk type %s", api.DISK_TYPE_SYS) 277 } 278 279 if len(disk.Attachments) == 0 { 280 return "", fmt.Errorf("disk is not attached.") 281 } 282 283 imageObj := jsonutils.NewDict() 284 imageObj.Add(jsonutils.NewString(disk.Attachments[0].ServerID), "instance_id") 285 imageObj.Add(jsonutils.NewString(imageName), "name") 286 imageObj.Add(jsonutils.NewString(imageDesc), "description") 287 288 ret, err := self.ecsClient.Images.PerformAction2("action", "", imageObj, "") 289 if err != nil { 290 return "", err 291 } 292 293 job := SJob{} 294 jobId, err := ret.GetString("job_id") 295 querys := map[string]string{"service_type": self.ecsClient.Images.ServiceType()} 296 err = DoGet(self.ecsClient.Jobs.Get, jobId, querys, &job) 297 if err != nil { 298 return "", err 299 } 300 301 if job.Status == "SUCCESS" { 302 imageId, exists := job.Entities["image_id"] 303 if exists { 304 return imageId, nil 305 } else { 306 return "", fmt.Errorf("image id not found in create image job %s", job.JobID) 307 } 308 } else { 309 return "", fmt.Errorf("create image failed, %s", job.FailReason) 310 } 311 312 } 313 314 func (self *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) { 315 storageCache := self.getStoragecache() 316 return []cloudprovider.ICloudStoragecache{storageCache}, nil 317 } 318 319 func (self *SRegion) GetIStoragecacheById(idstr string) (cloudprovider.ICloudStoragecache, error) { 320 storageCache := self.getStoragecache() 321 if storageCache.GetGlobalId() == idstr { 322 return storageCache, nil 323 } 324 return nil, cloudprovider.ErrNotFound 325 }