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  }