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