yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/azure/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 azure 16 17 import ( 18 "context" 19 "fmt" 20 "net/url" 21 "strings" 22 "time" 23 24 "yunion.io/x/jsonutils" 25 "yunion.io/x/log" 26 "yunion.io/x/pkg/errors" 27 "yunion.io/x/pkg/utils" 28 29 "yunion.io/x/cloudmux/pkg/apis" 30 api "yunion.io/x/cloudmux/pkg/apis/compute" 31 "yunion.io/x/cloudmux/pkg/cloudprovider" 32 "yunion.io/x/cloudmux/pkg/multicloud" 33 ) 34 35 type ImageStatusType string 36 37 const ( 38 ImageStatusCreating ImageStatusType = "Creating" 39 ImageStatusAvailable ImageStatusType = "Succeeded" 40 ImageStatusUnAvailable ImageStatusType = "UnAvailable" 41 ImageStatusCreateFailed ImageStatusType = "CreateFailed" 42 ) 43 44 type OperatingSystemStateTypes string 45 46 type ImageOSDisk struct { 47 OsType string `json:"osType,omitempty"` 48 OsState string `json:"osState,omitempty"` 49 Snapshot *SubResource `json:"snapshot,omitempty"` 50 ManagedDisk *SubResource 51 BlobURI string `json:"blobUri,omitempty"` 52 Caching string `json:"caching,omitempty"` 53 DiskSizeGB int32 `json:"diskSizeGB,omitzero"` 54 StorageAccountType string `json:"storageAccountType,omitempty"` 55 OperatingSystem string `json:"operatingSystem,omitempty"` 56 } 57 58 type ImageDataDisk struct { 59 Lun int32 60 Snapshot SubResource 61 ManagedDisk SubResource 62 BlobURI string 63 Caching string 64 DiskSizeGB int32 `json:"diskSizeGB,omitzero"` 65 StorageAccountType string 66 } 67 68 type ImageStorageProfile struct { 69 OsDisk ImageOSDisk `json:"osDisk,omitempty"` 70 DataDisks []ImageDataDisk `json:"dataDisks,omitempty"` 71 ZoneResilient bool `json:"zoneResilient,omitfalse"` 72 } 73 74 type SAutomaticOSUpgradeProperties struct { 75 AutomaticOSUpgradeSupported bool 76 } 77 78 type ImageProperties struct { 79 SourceVirtualMachine *SubResource 80 StorageProfile ImageStorageProfile `json:"storageProfile,omitempty"` 81 ProvisioningState ImageStatusType 82 HyperVGeneration string `json:"hyperVGeneration,omitempty"` 83 } 84 85 type SImage struct { 86 multicloud.SImageBase 87 AzureTags 88 storageCache *SStoragecache 89 90 Properties ImageProperties `json:"properties,omitempty"` 91 ID string `json:"id,omitempty"` 92 Name string 93 Type string 94 Location string 95 96 Publisher string 97 Offer string 98 Sku string 99 Version string 100 101 ImageType cloudprovider.TImageType 102 } 103 104 func (self *SImage) GetMinRamSizeMb() int { 105 return 0 106 } 107 108 func (self *SImage) GetSysTags() map[string]string { 109 data := map[string]string{} 110 osType := string(self.Properties.StorageProfile.OsDisk.OsType) 111 if len(osType) > 0 { 112 data["os_name"] = osType 113 } 114 return data 115 } 116 117 func (self *SImage) GetId() string { 118 return self.ID 119 } 120 121 func (self *SImage) GetName() string { 122 return self.Name 123 } 124 125 func (self *SImage) GetGlobalId() string { 126 return strings.ToLower(self.ID) 127 } 128 129 func (self *SImage) GetStatus() string { 130 switch self.Properties.ProvisioningState { 131 case "created": 132 return api.CACHED_IMAGE_STATUS_CACHING 133 case "Succeeded": 134 return api.CACHED_IMAGE_STATUS_ACTIVE 135 default: 136 log.Errorf("Unknow image status: %s", self.Properties.ProvisioningState) 137 return api.CACHED_IMAGE_STATUS_CACHE_FAILED 138 } 139 } 140 141 func (self *SImage) GetImageStatus() string { 142 switch self.Properties.ProvisioningState { 143 case "created": 144 return cloudprovider.IMAGE_STATUS_QUEUED 145 case "Succeeded": 146 return cloudprovider.IMAGE_STATUS_ACTIVE 147 default: 148 log.Errorf("Unknow image status: %s", self.Properties.ProvisioningState) 149 return cloudprovider.IMAGE_STATUS_KILLED 150 } 151 } 152 153 func (self *SImage) Refresh() error { 154 image, err := self.storageCache.region.GetImageById(self.ID) 155 if err != nil { 156 return err 157 } 158 return jsonutils.Update(self, image) 159 } 160 161 func (self *SImage) GetImageType() cloudprovider.TImageType { 162 return cloudprovider.TImageType(self.ImageType) 163 } 164 165 func (self *SImage) GetSizeByte() int64 { 166 return int64(self.Properties.StorageProfile.OsDisk.DiskSizeGB) * 1024 * 1024 * 1024 167 } 168 169 func (i *SImage) GetFullOsName() string { 170 return "" 171 } 172 173 func (self *SImage) GetOsType() cloudprovider.TOsType { 174 osType := self.Properties.StorageProfile.OsDisk.OsType 175 if len(osType) == 0 { 176 osType = publisherGetOsType(self.Publisher) 177 } 178 return cloudprovider.TOsType(osType) 179 } 180 181 func (self *SImage) GetOsArch() string { 182 if self.GetImageType() == cloudprovider.ImageTypeCustomized { 183 return apis.OS_ARCH_X86_64 184 } 185 return publisherGetOsArch(self.Publisher, self.Offer, self.Sku, self.Version) 186 } 187 188 func (self *SImage) GetOsDist() string { 189 if self.GetImageType() == cloudprovider.ImageTypeCustomized { 190 return "" 191 } 192 return publisherGetOsDist(self.Publisher, self.Offer, self.Sku, self.Version) 193 } 194 195 func (self *SImage) GetOsVersion() string { 196 return publisherGetOsVersion(self.Publisher, self.Offer, self.Sku, self.Version) 197 } 198 199 func (self *SImage) GetOsLang() string { 200 return "" 201 } 202 203 func (i *SImage) GetBios() cloudprovider.TBiosType { 204 if i.Properties.HyperVGeneration == "V2" { 205 return cloudprovider.UEFI 206 } else { 207 return cloudprovider.BIOS 208 } 209 } 210 211 func (self *SImage) GetMinOsDiskSizeGb() int { 212 if self.Properties.StorageProfile.OsDisk.DiskSizeGB > 0 { 213 return int(self.Properties.StorageProfile.OsDisk.DiskSizeGB) 214 } 215 return 30 216 } 217 218 func (self *SImage) GetImageFormat() string { 219 return "vhd" 220 } 221 222 func (self *SImage) GetCreatedAt() time.Time { 223 return time.Time{} 224 } 225 226 func (self *SImage) GetIStoragecache() cloudprovider.ICloudStoragecache { 227 return self.storageCache 228 } 229 230 func (self *SRegion) GetImageStatus(imageId string) (ImageStatusType, error) { 231 if image, err := self.GetImageById(imageId); err != nil { 232 return "", err 233 } else { 234 return image.Properties.ProvisioningState, nil 235 } 236 } 237 238 func isPrivateImageID(imageId string) bool { 239 return strings.HasPrefix(strings.ToLower(imageId), "/subscriptions/") 240 } 241 242 func (self *SRegion) GetImageById(imageId string) (SImage, error) { 243 if isPrivateImageID(imageId) { 244 return self.getPrivateImage(imageId) 245 } else { 246 return self.getOfferedImage(imageId) 247 } 248 } 249 250 func (self *SRegion) getPrivateImage(imageId string) (SImage, error) { 251 image := SImage{} 252 err := self.get(imageId, url.Values{}, &image) 253 if err != nil { 254 return image, err 255 } 256 return image, nil 257 } 258 259 func (self *SRegion) CreateImageByBlob(imageName, osType, blobURI string, diskSizeGB int32) (*SImage, error) { 260 if diskSizeGB < 1 || diskSizeGB > 4095 { 261 diskSizeGB = 30 262 } 263 image := SImage{ 264 Name: imageName, 265 Location: self.Name, 266 Properties: ImageProperties{ 267 StorageProfile: ImageStorageProfile{ 268 OsDisk: ImageOSDisk{ 269 OsType: osType, 270 OsState: "Generalized", 271 BlobURI: blobURI, 272 DiskSizeGB: diskSizeGB, 273 }, 274 }, 275 }, 276 Type: "Microsoft.Compute/images", 277 } 278 return &image, self.create("", jsonutils.Marshal(image), &image) 279 } 280 281 func (self *SRegion) CreateImage(snapshotId, imageName, osType, imageDesc string) (*SImage, error) { 282 image := SImage{ 283 Name: imageName, 284 Location: self.Name, 285 Properties: ImageProperties{ 286 StorageProfile: ImageStorageProfile{ 287 OsDisk: ImageOSDisk{ 288 OsType: osType, 289 OsState: "Generalized", 290 Snapshot: &SubResource{ 291 ID: snapshotId, 292 }, 293 }, 294 }, 295 }, 296 Type: "Microsoft.Compute/images", 297 } 298 return &image, self.create("", jsonutils.Marshal(image), &image) 299 } 300 301 func (self *SRegion) getOfferedImages(publishersFilter []string, offersFilter []string, skusFilter []string, verFilter []string, imageType cloudprovider.TImageType, latestVer bool) ([]SImage, error) { 302 images := make([]SImage, 0) 303 idMap, err := self.GetOfferedImageIDs(publishersFilter, offersFilter, skusFilter, verFilter, latestVer) 304 if err != nil { 305 return nil, err 306 } 307 for id, _image := range idMap { 308 image, err := self.getOfferedImage(id) 309 if err == nil { 310 image.ImageType = imageType 311 image.Properties.StorageProfile.OsDisk.DiskSizeGB = int32(_image.Properties.OsDiskImage.SizeInGb) 312 image.Properties.StorageProfile.OsDisk.OsType = _image.Properties.OsDiskImage.OperatingSystem 313 image.Properties.HyperVGeneration = _image.Properties.HyperVGeneration 314 images = append(images, image) 315 } 316 } 317 return images, nil 318 } 319 320 func (self *SRegion) GetOfferedImageIDs(publishersFilter []string, offersFilter []string, skusFilter []string, verFilter []string, latestVer bool) (map[string]SAzureImageResource, error) { 321 idMap := map[string]SAzureImageResource{} 322 publishers, err := self.GetImagePublishers(toLowerStringArray(publishersFilter)) 323 if err != nil { 324 return nil, err 325 } 326 for _, publisher := range publishers { 327 offers, err := self.getImageOffers(publisher, toLowerStringArray(offersFilter)) 328 if err != nil { 329 log.Errorf("failed to found offers for publisher %s error: %v", publisher, err) 330 if errors.Cause(err) != cloudprovider.ErrNotFound { 331 return nil, errors.Wrap(err, "getImageOffers") 332 } 333 continue 334 } 335 for _, offer := range offers { 336 skus, err := self.getImageSkus(publisher, offer, toLowerStringArray(skusFilter)) 337 if err != nil { 338 if errors.Cause(err) != cloudprovider.ErrNotFound { 339 return nil, errors.Wrap(err, "getImageSkus") 340 } 341 log.Errorf("failed to found skus for publisher %s offer %s error: %v", publisher, offer, err) 342 continue 343 } 344 for _, sku := range skus { 345 verFilter = toLowerStringArray(verFilter) 346 vers, err := self.getImageVersions(publisher, offer, sku, verFilter, latestVer) 347 if err != nil { 348 if errors.Cause(err) != cloudprovider.ErrNotFound { 349 return nil, errors.Wrap(err, "getImageVersions") 350 } 351 log.Errorf("failed to found publisher %s offer %s sku %s version error: %v", publisher, offer, sku, err) 352 continue 353 } 354 for _, ver := range vers { 355 idStr := strings.Join([]string{publisher, offer, sku, ver}, "/") 356 image, err := self.getImageDetail(publisher, offer, sku, ver) 357 if err != nil { 358 return nil, err 359 } 360 idMap[idStr] = image 361 } 362 } 363 } 364 } 365 return idMap, nil 366 } 367 368 func (self *SRegion) getPrivateImages() ([]SImage, error) { 369 result := []SImage{} 370 images := []SImage{} 371 err := self.client.list("Microsoft.Compute/images", url.Values{}, &images) 372 if err != nil { 373 return nil, err 374 } 375 for i := 0; i < len(images); i++ { 376 if images[i].Location == self.Name { 377 images[i].ImageType = cloudprovider.ImageTypeCustomized 378 result = append(result, images[i]) 379 } 380 } 381 return result, nil 382 } 383 384 func toLowerStringArray(input []string) []string { 385 output := make([]string, len(input)) 386 for i := range input { 387 output[i] = strings.ToLower(input[i]) 388 } 389 return output 390 } 391 392 func (self *SRegion) GetImages(imageType cloudprovider.TImageType) ([]SImage, error) { 393 images := make([]SImage, 0) 394 if len(imageType) == 0 { 395 ret, _ := self.getPrivateImages() 396 if len(ret) > 0 { 397 images = append(images, ret...) 398 } 399 ret, _ = self.getOfferedImages(knownPublishers, nil, nil, nil, cloudprovider.ImageTypeSystem, true) 400 if len(ret) > 0 { 401 images = append(images, ret...) 402 } 403 return images, nil 404 } 405 switch imageType { 406 case cloudprovider.ImageTypeCustomized: 407 return self.getPrivateImages() 408 case cloudprovider.ImageTypeSystem: 409 return self.getOfferedImages(knownPublishers, nil, nil, nil, cloudprovider.ImageTypeSystem, true) 410 default: 411 return self.getOfferedImages(nil, nil, nil, nil, cloudprovider.ImageTypeMarket, true) 412 } 413 } 414 415 func (self *SRegion) DeleteImage(imageId string) error { 416 return self.del(imageId) 417 } 418 419 func (self *SImage) GetBlobUri() string { 420 return self.Properties.StorageProfile.OsDisk.BlobURI 421 } 422 423 func (self *SImage) Delete(ctx context.Context) error { 424 return self.storageCache.region.DeleteImage(self.ID) 425 } 426 427 type SOsDiskImage struct { 428 OperatingSystem string `json:"operatingSystem"` 429 SizeInGb int `json:"sizeInGb"` 430 } 431 432 type SAzureImageResourceProperties struct { 433 ReplicaType string `json:"replicaType"` 434 OsDiskImage SOsDiskImage `json:"osDiskImage"` 435 HyperVGeneration string `json:"hyperVGeneration,omitempty"` 436 } 437 438 type SAzureImageResource struct { 439 Id string 440 Name string 441 Location string 442 Properties SAzureImageResourceProperties 443 } 444 445 func (region *SRegion) GetImagePublishers(filter []string) ([]string, error) { 446 publishers := make([]SAzureImageResource, 0) 447 // TODO 448 err := region.client.list(fmt.Sprintf("Microsoft.Compute/locations/%s/publishers", region.Name), url.Values{}, &publishers) 449 if err != nil { 450 return nil, err 451 } 452 ret := make([]string, 0) 453 for i := range publishers { 454 if len(filter) == 0 || utils.IsInStringArray(strings.ToLower(publishers[i].Name), filter) { 455 ret = append(ret, publishers[i].Name) 456 } 457 } 458 return ret, nil 459 } 460 461 func (region *SRegion) getImageOffers(publisher string, filter []string) ([]string, error) { 462 ret := make([]string, 0) 463 if driver, ok := publisherDrivers[strings.ToLower(publisher)]; ok { 464 offers := driver.GetOffers() 465 if len(offers) > 0 { 466 for _, offer := range offers { 467 if len(filter) == 0 || utils.IsInStringArray(strings.ToLower(offer), filter) { 468 ret = append(ret, offer) 469 } 470 } 471 return offers, nil 472 } 473 } else { 474 log.Warningf("failed to get publisher %s driver", publisher) 475 } 476 offers := make([]SAzureImageResource, 0) 477 err := region.client.list(fmt.Sprintf("Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers", region.Name, publisher), url.Values{}, &offers) 478 if err != nil { 479 return nil, err 480 } 481 for i := range offers { 482 if len(filter) == 0 || utils.IsInStringArray(strings.ToLower(offers[i].Name), filter) { 483 ret = append(ret, offers[i].Name) 484 } 485 } 486 return ret, nil 487 } 488 489 func (region *SRegion) getImageSkus(publisher string, offer string, filter []string) ([]string, error) { 490 ret := make([]string, 0) 491 if driver, ok := publisherDrivers[strings.ToLower(publisher)]; ok { 492 skus := driver.GetSkus(offer) 493 if len(skus) > 0 { 494 for _, sku := range skus { 495 if len(filter) == 0 || utils.IsInStringArray(strings.ToLower(sku), filter) { 496 ret = append(ret, sku) 497 } 498 } 499 return ret, nil 500 } 501 } 502 skus := make([]SAzureImageResource, 0) 503 err := region.client.list(fmt.Sprintf("Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus", region.Name, publisher, offer), url.Values{}, &skus) 504 if err != nil { 505 return nil, err 506 } 507 for i := range skus { 508 if len(filter) == 0 || utils.IsInStringArray(strings.ToLower(skus[i].Name), filter) { 509 ret = append(ret, skus[i].Name) 510 } 511 } 512 return ret, nil 513 } 514 515 func (region *SRegion) getImageVersions(publisher string, offer string, sku string, filter []string, latestVer bool) ([]string, error) { 516 vers := make([]SAzureImageResource, 0) 517 resource := fmt.Sprintf("Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus/%s/versions", region.Name, publisher, offer, sku) 518 params := url.Values{} 519 if latestVer { 520 params.Set("$top", "1") 521 params.Set("orderby", "name desc") 522 } 523 err := region.client.list(resource, params, &vers) 524 if err != nil { 525 return nil, err 526 } 527 ret := make([]string, 0) 528 for i := range vers { 529 if len(filter) == 0 || utils.IsInStringArray(strings.ToLower(vers[i].Name), filter) { 530 ret = append(ret, vers[i].Name) 531 } 532 } 533 return ret, nil 534 } 535 536 func (region *SRegion) getImageDetail(publisher string, offer string, sku string, version string) (SAzureImageResource, error) { 537 image := SAzureImageResource{} 538 id := "/Subscriptions/" + region.client.subscriptionId + 539 "/Providers/Microsoft.Compute/locations/" + region.Name + 540 "/publishers/" + publisher + 541 "/artifacttypes/vmimage/offers/" + offer + 542 "/skus/" + sku + 543 "/versions/" + version 544 return image, region.get(id, url.Values{}, &image) 545 } 546 547 func (region *SRegion) getOfferedImage(offerId string) (SImage, error) { 548 image := SImage{} 549 550 parts := strings.Split(offerId, "/") 551 if len(parts) < 4 { 552 return image, fmt.Errorf("invalid image ID %s", offerId) 553 } 554 publisher := parts[0] 555 offer := parts[1] 556 sku := parts[2] 557 version := parts[3] 558 for _publish := range publisherDrivers { 559 if strings.ToLower(_publish) == publisher { 560 publisher = _publish 561 break 562 } 563 } 564 image.ID = offerId 565 image.Location = region.Name 566 image.Type = "Microsoft.Compute/vmimage" 567 image.Name = publisherGetName(publisher, offer, sku, version) 568 image.Publisher = publisher 569 image.Offer = offer 570 image.Sku = sku 571 image.Version = version 572 image.Properties.ProvisioningState = ImageStatusAvailable 573 _image, err := region.getImageDetail(publisher, offer, sku, version) 574 if err == nil { 575 image.Properties.StorageProfile.OsDisk.DiskSizeGB = int32(_image.Properties.OsDiskImage.SizeInGb) 576 image.Properties.StorageProfile.OsDisk.OperatingSystem = _image.Properties.OsDiskImage.OperatingSystem 577 image.Properties.HyperVGeneration = _image.Properties.HyperVGeneration 578 } 579 return image, nil 580 } 581 582 func (region *SRegion) getOfferedImageId(image *SImage) (string, error) { 583 if isPrivateImageID(image.ID) { 584 return image.ID, nil 585 } 586 _image, err := region.getImageDetail(image.Publisher, image.Offer, image.Sku, image.Version) 587 if err != nil { 588 log.Errorf("failed to get offered image ID from %s error: %v", jsonutils.Marshal(image).PrettyString(), err) 589 return "", err 590 } 591 return _image.Id, nil 592 } 593 594 func (image *SImage) getImageReference() ImageReference { 595 if isPrivateImageID(image.ID) { 596 return ImageReference{ 597 ID: image.ID, 598 } 599 } else { 600 return ImageReference{ 601 Sku: image.Sku, 602 Publisher: image.Publisher, 603 Version: image.Version, 604 Offer: image.Offer, 605 } 606 } 607 }