yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/azure/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 azure 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "io/ioutil" 22 "net/http" 23 "os" 24 "strings" 25 26 "yunion.io/x/jsonutils" 27 "yunion.io/x/log" 28 "yunion.io/x/pkg/errors" 29 30 "yunion.io/x/cloudmux/pkg/cloudprovider" 31 "yunion.io/x/onecloud/pkg/compute/options" 32 "yunion.io/x/onecloud/pkg/mcclient" 33 "yunion.io/x/onecloud/pkg/mcclient/auth" 34 modules "yunion.io/x/onecloud/pkg/mcclient/modules/image" 35 "yunion.io/x/cloudmux/pkg/multicloud" 36 "yunion.io/x/onecloud/pkg/util/fileutils2" 37 "yunion.io/x/onecloud/pkg/util/qemuimg" 38 ) 39 40 const ( 41 DefaultStorageAccount string = "image" 42 DefaultContainer string = "image-cache" 43 44 DefaultReadBlockSize int64 = 4 * 1024 * 1024 45 ) 46 47 type SStoragecache struct { 48 multicloud.SResourceBase 49 AzureTags 50 region *SRegion 51 } 52 53 func (self *SStoragecache) GetId() string { 54 return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Id, self.region.GetId()) 55 } 56 57 func (self *SStoragecache) GetName() string { 58 return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Name, self.region.GetId()) 59 } 60 61 func (self *SStoragecache) GetStatus() string { 62 return "available" 63 } 64 65 func (self *SStoragecache) Refresh() error { 66 return nil 67 } 68 69 func (self *SStoragecache) GetGlobalId() string { 70 return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Id, self.region.GetGlobalId()) 71 } 72 73 func (self *SStoragecache) IsEmulated() bool { 74 return false 75 } 76 77 func (self *SStoragecache) GetICloudImages() ([]cloudprovider.ICloudImage, error) { 78 return nil, cloudprovider.ErrNotImplemented 79 } 80 81 func (self *SStoragecache) GetICustomizedCloudImages() ([]cloudprovider.ICloudImage, error) { 82 images, err := self.region.GetImages(cloudprovider.ImageTypeCustomized) 83 if err != nil { 84 return nil, errors.Wrapf(err, "GetImages") 85 } 86 ret := []cloudprovider.ICloudImage{} 87 for i := 0; i < len(images); i++ { 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.GetImageById(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(progress float32)) (string, error) { 108 err := os.MkdirAll(options.Options.TempPath, os.ModePerm) 109 if err != nil { 110 log.Warningf("failed to create tmp path %s error: %v", options.Options.TempPath, err) 111 } 112 return self.uploadImage(ctx, userCred, image, options.Options.TempPath, callback) 113 } 114 115 func (self *SStoragecache) checkStorageAccount() (*SStorageAccount, error) { 116 storageaccounts, err := self.region.ListStorageAccounts() 117 if err != nil { 118 return nil, errors.Wrap(err, "ListStorageAccounts") 119 } 120 if len(storageaccounts) == 0 { 121 storageaccount, err := self.region.CreateStorageAccount(self.region.Name) 122 if err != nil { 123 return nil, errors.Wrap(err, "CreateStorageAccount") 124 } 125 return storageaccount, nil 126 } 127 for i := 0; i < len(storageaccounts); i++ { 128 if id, ok := storageaccounts[i].Tags["id"]; ok && id == self.region.Name { 129 return &storageaccounts[i], nil 130 } 131 } 132 133 storageaccount := &storageaccounts[0] 134 if storageaccount.Tags == nil { 135 storageaccount.Tags = map[string]string{} 136 } 137 storageaccount.Tags["id"] = self.region.Name 138 err = self.region.update(jsonutils.Marshal(storageaccount), nil) 139 if err != nil { 140 return nil, errors.Wrapf(err, "Update(%s)", jsonutils.Marshal(storageaccount).String()) 141 } 142 return storageaccount, nil 143 } 144 145 func (self *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, tmpPath string, callback func(progress float32)) (string, error) { 146 s := auth.GetAdminSession(ctx, options.Options.Region) 147 meta, reader, sizeBytes, err := modules.Images.Download(s, image.ImageId, string(qemuimg.VHD), false) 148 if err != nil { 149 return "", err 150 } 151 // { 152 // "checksum":"d0ab0450979977c6ada8d85066a6e484", 153 // "container_format":"bare", 154 // "created_at":"2018-08-10T04:18:07", 155 // "deleted":"False", 156 // "disk_format":"vhd", 157 // "id":"64189033-3ad4-413c-b074-6bf0b6be8508", 158 // "is_public":"False", 159 // "min_disk":"0", 160 // "min_ram":"0", 161 // "name":"centos-7.3.1611-20180104.vhd", 162 // "owner":"5124d80475434da8b41fee48d5be94df", 163 // "properties":{ 164 // "os_arch":"x86_64", 165 // "os_distribution":"CentOS", 166 // "os_type":"Linux", 167 // "os_version":"7.3.1611-VHD" 168 // }, 169 // "protected":"False", 170 // "size":"2028505088", 171 // "status":"active", 172 // "updated_at":"2018-08-10T04:20:59" 173 // } 174 log.Infof("meta data %s", meta) 175 176 imageNameOnBlob, _ := meta.GetString("name") 177 if !strings.HasSuffix(imageNameOnBlob, ".vhd") { 178 imageNameOnBlob = fmt.Sprintf("%s.vhd", imageNameOnBlob) 179 } 180 tmpFile := fmt.Sprintf("%s/%s", tmpPath, imageNameOnBlob) 181 defer os.Remove(tmpFile) 182 f, err := os.Create(tmpFile) 183 if err != nil { 184 return "", errors.Wrap(err, "os.Create(tmpFile)") 185 } 186 defer f.Close() 187 188 // 下载占33% 189 r := multicloud.NewProgress(sizeBytes, 33, reader, callback) 190 if _, err := io.Copy(f, r); err != nil { 191 return "", errors.Wrap(err, "io.Copy(f, reader)") 192 } 193 194 storageaccount, err := self.checkStorageAccount() 195 if err != nil { 196 return "", errors.Wrap(err, "self.checkStorageAccount") 197 } 198 199 blobURI, err := storageaccount.UploadFile("image-cache", tmpFile, callback) 200 if err != nil { 201 return "", errors.Wrap(err, "storageaccount.UploadFile") 202 } 203 204 // size, _ := meta.Int("size") 205 206 img, err := self.region.CreateImageByBlob(image.ImageId, image.OsType, blobURI, int32(sizeBytes>>30)) 207 if err != nil { 208 return "", errors.Wrap(err, "CreateImageByBlob") 209 } 210 if callback != nil { 211 callback(100) 212 } 213 return img.GetGlobalId(), nil 214 } 215 216 func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) { 217 if image, err := self.region.CreateImage(snapshotId, imageName, osType, imageDesc); err != nil { 218 return nil, err 219 } else { 220 image.storageCache = self 221 return image, nil 222 } 223 } 224 225 func (self *SStoragecache) DownloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) { 226 return self.downloadImage(userCred, imageId, extId, path) 227 } 228 229 func (self *SStoragecache) downloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) { 230 // TODO: need to fix scenarios where image is a public image 231 // XXX Qiu Jian 232 if image, err := self.region.getPrivateImage(extId); err != nil { 233 return nil, err 234 } else if snapshotId := image.Properties.StorageProfile.OsDisk.Snapshot.ID; len(snapshotId) == 0 { 235 return nil, cloudprovider.ErrNotFound 236 } else if uri, err := self.region.GrantAccessSnapshot(snapshotId); err != nil { 237 return nil, err 238 } else if resp, err := http.Get(uri); err != nil { 239 return nil, err 240 } else { 241 tmpImageFile, err := ioutil.TempFile(path, "temp") 242 if err != nil { 243 return nil, err 244 } 245 defer tmpImageFile.Close() 246 defer os.Remove(tmpImageFile.Name()) 247 { 248 sf := fileutils2.NewSparseFileWriter(tmpImageFile) 249 data := make([]byte, DefaultReadBlockSize) 250 written := int64(0) 251 for { 252 n, err := resp.Body.Read(data) 253 if n > 0 { 254 if n, err := sf.Write(data); err != nil { 255 return nil, err 256 } else { 257 written += int64(n) 258 } 259 } else if err == io.EOF { 260 if written <= resp.ContentLength { 261 return nil, fmt.Errorf("got eof: expecting %d bytes, got %d", resp.ContentLength, written) 262 } 263 break 264 } else { 265 return nil, err 266 } 267 } 268 if err := sf.PreClose(); err != nil { 269 return nil, err 270 } 271 } 272 273 if _, err := tmpImageFile.Seek(0, os.SEEK_SET); err != nil { 274 return nil, errors.Wrap(err, "seek") 275 } 276 s := auth.GetAdminSession(context.Background(), options.Options.Region) 277 params := jsonutils.Marshal(map[string]string{"image_id": imageId, "disk-format": "raw"}) 278 if result, err := modules.Images.Upload(s, params, tmpImageFile, resp.ContentLength); err != nil { 279 return nil, err 280 } else { 281 return result, nil 282 } 283 284 } 285 } 286 287 func (region *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) { 288 storageCache := region.getStoragecache() 289 return []cloudprovider.ICloudStoragecache{storageCache}, nil 290 }