yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/openstack/image.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 openstack 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "net/url" 22 "strings" 23 "time" 24 25 "github.com/pkg/errors" 26 27 "yunion.io/x/jsonutils" 28 29 api "yunion.io/x/cloudmux/pkg/apis/compute" 30 "yunion.io/x/cloudmux/pkg/cloudprovider" 31 "yunion.io/x/cloudmux/pkg/multicloud" 32 "yunion.io/x/onecloud/pkg/util/imagetools" 33 "yunion.io/x/onecloud/pkg/util/qemuimg" 34 "yunion.io/x/onecloud/pkg/util/rbacutils" 35 ) 36 37 const ( 38 QUEUED = "queued" // The Image service reserved an image ID for the image in the catalog but did not yet upload any image data. 39 SAVING = "saving" // The Image service is in the process of saving the raw data for the image into the backing store. 40 ACTIVE = "active" // The image is active and ready for consumption in the Image service. 41 KILLED = "killed" // An image data upload error occurred. 42 DELETED = "deleted" // The Image service retains information about the image but the image is no longer available for use. 43 PENDING_DELETE = "pending_delete" // Similar to the deleted status. An image in this state is not recoverable. 44 DEACTIVATED = "deactivated" // The image data is not available for use. 45 UPLOADING = "uploading" // Data has been staged as part of the interoperable image import process. It is not yet available for use. (Since Image API 2.6) 46 IMPORTING = "importing" // The image data is being processed as part of the interoperable image import process, but is not yet available for use. (Since Image API 2.6) 47 ) 48 49 type SImage struct { 50 multicloud.SImageBase 51 storageCache *SStoragecache 52 OpenStackTags 53 54 // normalized image info 55 imgInfo *imagetools.ImageInfo 56 57 Status string 58 Name string 59 Tags []string 60 ContainerFormat string 61 CreatedAt time.Time 62 DiskFormat string 63 UpdatedAt time.Time 64 Visibility string 65 Self string 66 MinDisk int 67 Protected bool 68 Id string 69 File string 70 Checksum string 71 OsHashAlgo string 72 OsHashValue string 73 OsHidden bool 74 OsDistro string 75 OsType string 76 Owner string 77 Size int 78 MinRAM int 79 Schema string 80 VirtualSize int 81 visibility string 82 } 83 84 func (image *SImage) GetMinRamSizeMb() int { 85 return image.MinRAM 86 } 87 88 func (region *SRegion) GetImages(name string, status string, imageId string) ([]SImage, error) { 89 query := url.Values{} 90 if len(status) > 0 { 91 query.Set("status", status) 92 } 93 if len(name) > 0 { 94 query.Set("name", name) 95 } 96 if len(imageId) > 0 { 97 query.Set("id", imageId) 98 } 99 images := []SImage{} 100 resource := "/v2/images" 101 marker := "" 102 for { 103 if len(marker) > 0 { 104 query.Set("marker", marker) 105 } 106 resp, err := region.imageList(resource, query) 107 if err != nil { 108 return nil, errors.Wrap(err, "imageList") 109 } 110 part := struct { 111 Images []SImage 112 Next string 113 }{} 114 err = resp.Unmarshal(&part) 115 if err != nil { 116 return nil, errors.Wrap(err, "resp.Unmarshal") 117 } 118 images = append(images, part.Images...) 119 if len(part.Next) == 0 { 120 break 121 } 122 if len(part.Next) > 0 { 123 href, err := url.Parse(part.Next) 124 if err != nil { 125 marker = "" 126 } else { 127 marker = href.Query().Get("marker") 128 } 129 } 130 if len(marker) == 0 { 131 break 132 } 133 } 134 return images, nil 135 } 136 137 func (image *SImage) GetId() string { 138 return image.Id 139 } 140 141 func (image *SImage) GetName() string { 142 return image.Name 143 } 144 145 func (image *SImage) IsEmulated() bool { 146 return false 147 } 148 149 func (image *SImage) GetGlobalId() string { 150 return image.Id 151 } 152 153 func (image *SImage) Delete(ctx context.Context) error { 154 return image.storageCache.region.DeleteImage(image.Id) 155 } 156 157 func (image *SImage) GetStatus() string { 158 switch image.Status { 159 case QUEUED, SAVING, UPLOADING, IMPORTING: 160 return api.CACHED_IMAGE_STATUS_CACHING 161 case ACTIVE: 162 return api.CACHED_IMAGE_STATUS_ACTIVE 163 case DELETED, DEACTIVATED, PENDING_DELETE, KILLED: 164 return api.CACHED_IMAGE_STATUS_CACHE_FAILED 165 default: 166 return api.CACHED_IMAGE_STATUS_CACHE_FAILED 167 } 168 } 169 170 func (image *SImage) GetImageStatus() string { 171 switch image.Status { 172 case QUEUED, SAVING, UPLOADING, IMPORTING: 173 return cloudprovider.IMAGE_STATUS_SAVING 174 case ACTIVE: 175 return cloudprovider.IMAGE_STATUS_ACTIVE 176 case DELETED, DEACTIVATED, PENDING_DELETE: 177 return cloudprovider.IMAGE_STATUS_DELETED 178 case KILLED: 179 return cloudprovider.IMAGE_STATUS_KILLED 180 default: 181 return cloudprovider.IMAGE_STATUS_DELETED 182 } 183 } 184 185 func (image *SImage) Refresh() error { 186 _image, err := image.storageCache.region.GetImage(image.Id) 187 if err != nil { 188 return errors.Wrap(err, "GetImage") 189 } 190 return jsonutils.Update(image, _image) 191 } 192 193 func (image *SImage) GetImageType() cloudprovider.TImageType { 194 return cloudprovider.ImageTypeSystem 195 } 196 197 func (image *SImage) GetPublicScope() rbacutils.TRbacScope { 198 switch image.Visibility { 199 case "private": 200 return rbacutils.ScopeNone 201 default: 202 return rbacutils.ScopeSystem 203 } 204 } 205 206 func (image *SImage) GetProjectId() string { 207 return image.Owner 208 } 209 210 func (image *SImage) GetSizeByte() int64 { 211 return int64(image.Size) 212 } 213 214 func (self *SImage) getNormalizedImageInfo() *imagetools.ImageInfo { 215 if self.imgInfo == nil { 216 imgInfo := imagetools.NormalizeImageInfo(self.Name, "", self.OsType, "", "") 217 self.imgInfo = &imgInfo 218 } 219 220 return self.imgInfo 221 } 222 223 func (image *SImage) GetFullOsName() string { 224 return image.Name 225 } 226 227 func (image *SImage) GetOsType() cloudprovider.TOsType { 228 return cloudprovider.TOsType(image.getNormalizedImageInfo().OsType) 229 } 230 231 func (image *SImage) GetOsDist() string { 232 return image.getNormalizedImageInfo().OsDistro 233 } 234 235 func (image *SImage) GetOsVersion() string { 236 return image.getNormalizedImageInfo().OsVersion 237 } 238 239 func (image *SImage) GetOsArch() string { 240 return image.getNormalizedImageInfo().OsArch 241 } 242 243 func (image *SImage) GetOsLang() string { 244 return image.getNormalizedImageInfo().OsLang 245 } 246 247 func (image *SImage) GetBios() cloudprovider.TBiosType { 248 return cloudprovider.ToBiosType(image.getNormalizedImageInfo().OsBios) 249 } 250 251 func (image *SImage) GetMinOsDiskSizeGb() int { 252 if image.MinDisk > 0 { 253 return image.MinDisk 254 } 255 return 30 256 } 257 258 func (image *SImage) GetImageFormat() string { 259 return image.DiskFormat 260 } 261 262 func (image *SImage) GetCreatedAt() time.Time { 263 return image.CreatedAt 264 } 265 266 func (region *SRegion) GetImage(imageId string) (*SImage, error) { 267 resource := "/v2/images/" + imageId 268 resp, err := region.imageGet(resource) 269 if err != nil { 270 return nil, errors.Wrapf(err, "imageGet(%s)", resource) 271 } 272 image := &SImage{} 273 err = resp.Unmarshal(image) 274 if err != nil { 275 return nil, errors.Wrapf(err, "resp.Unmarshal") 276 } 277 return image, nil 278 } 279 280 func (image *SImage) GetIStoragecache() cloudprovider.ICloudStoragecache { 281 return image.storageCache 282 } 283 284 func (region *SRegion) DeleteImage(imageId string) error { 285 _, err := region.imageDelete("/v2/images/" + imageId) 286 return err 287 } 288 289 func (region *SRegion) GetImageStatus(imageId string) (string, error) { 290 image, err := region.GetImage(imageId) 291 if err != nil { 292 return "", err 293 } 294 return image.Status, nil 295 } 296 297 func (region *SRegion) GetImageByName(name string) (*SImage, error) { 298 images, err := region.GetImages(name, "", "") 299 if err != nil { 300 return nil, err 301 } 302 if len(images) == 0 { 303 return nil, cloudprovider.ErrNotFound 304 } 305 return &images[0], nil 306 } 307 308 func (region *SRegion) CreateImage(imageName string, osType string, osDist string, minDiskGb int, minRam int, size int64, body io.Reader, callback func(progress float32)) (*SImage, error) { 309 params := map[string]interface{}{ 310 "container_format": "bare", 311 "disk_format": string(qemuimg.QCOW2), 312 "name": imageName, 313 "min_disk": minDiskGb, 314 "min_ram": minRam, 315 "os_type": strings.ToLower(osType), 316 "os_distro": osDist, 317 "hw_qemu_guest_agent": "yes", 318 } 319 320 resp, err := region.imagePost("/v2/images", params) 321 if err != nil { 322 return nil, errors.Wrap(err, "imagePost") 323 } 324 image := &SImage{} 325 err = resp.Unmarshal(image) 326 if err != nil { 327 return nil, errors.Wrap(err, "resp.Unmarshal") 328 } 329 url := fmt.Sprintf("/v2/images/%s/file", image.Id) 330 err = region.imageUpload(url, size, body, callback) 331 if err != nil { 332 return nil, errors.Wrap(err, "imageUpload") 333 } 334 return image, nil 335 }