yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/azure/disk.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  	"strconv"
    22  	"strings"
    23  	"time"
    24  
    25  	"yunion.io/x/jsonutils"
    26  	"yunion.io/x/log"
    27  	"yunion.io/x/pkg/errors"
    28  
    29  	billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
    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 DiskSku struct {
    36  	Name string `json:"name,omitempty"`
    37  	Tier string `json:"tier,omitempty"`
    38  }
    39  
    40  type ImageDiskReference struct {
    41  	ID  string
    42  	Lun int32 `json:"lun,omitempty"`
    43  }
    44  
    45  type CreationData struct {
    46  	CreateOption     string `json:"createOption,omitempty"`
    47  	StorageAccountID string
    48  	// ImageReference   *ImageDiskReference `json:"imageReference,omitempty"`
    49  	ImageReference   *ImageReference `json:"imageReference,omitempty"`
    50  	SourceURI        string          `json:"sourceUri,omitempty"`
    51  	SourceResourceID string          `json:"sourceResourceId,omitempty"`
    52  }
    53  
    54  type TAzureInt32 string
    55  
    56  func (ai TAzureInt32) Int32() int32 {
    57  	num, _ := strconv.Atoi(strings.Trim(string(ai), "\t"))
    58  	return int32(num)
    59  }
    60  
    61  type DiskProperties struct {
    62  	TimeCreated       time.Time    `json:"timeCreated,omitempty"`
    63  	OsType            string       `json:"osType,omitempty"`
    64  	CreationData      CreationData `json:"creationData,omitempty"`
    65  	DiskSizeGB        TAzureInt32  `json:"diskSizeGB,omitempty"`
    66  	ProvisioningState string       `json:"provisioningState,omitempty"`
    67  	DiskState         string       `json:"diskState,omitempty"`
    68  }
    69  
    70  type SDisk struct {
    71  	storage *SStorage
    72  	multicloud.SDisk
    73  	AzureTags
    74  
    75  	ManagedBy  string         `json:"managedBy,omitempty"`
    76  	Sku        DiskSku        `json:"sku,omitempty"`
    77  	Zones      []string       `json:"zones,omitempty"`
    78  	ID         string         `json:"id,omitempty"`
    79  	Name       string         `json:"name,omitempty"`
    80  	Type       string         `json:"type,omitempty"`
    81  	Location   string         `json:"location,omitempty"`
    82  	Properties DiskProperties `json:"properties,omitempty"`
    83  }
    84  
    85  func (self *SRegion) CreateDisk(storageType string, name string, sizeGb int32, imageId, snapshotId, resourceGroup string) (*SDisk, error) {
    86  	params := jsonutils.Marshal(map[string]interface{}{
    87  		"Name":     name,
    88  		"Location": self.Name,
    89  		"Sku": map[string]string{
    90  			"Name": storageType,
    91  		},
    92  		"Type": "Microsoft.Compute/disks",
    93  	}).(*jsonutils.JSONDict)
    94  	properties := map[string]interface{}{
    95  		"CreationData": map[string]string{
    96  			"CreateOption": "Empty",
    97  		},
    98  		"DiskSizeGB": sizeGb,
    99  	}
   100  	if len(imageId) > 0 {
   101  		image, err := self.GetImageById(imageId)
   102  		if err != nil {
   103  			return nil, errors.Wrapf(err, "GetImageById(%s)", imageId)
   104  		}
   105  		// 通过镜像创建的磁盘只能传ID参数,不能通过sku,offer等参数创建.
   106  		imageId, err := self.getOfferedImageId(&image)
   107  		if err != nil {
   108  			return nil, errors.Wrapf(err, "getOfferedImageId")
   109  		}
   110  		properties = map[string]interface{}{
   111  			"CreationData": map[string]interface{}{
   112  				"CreateOption": "FromImage",
   113  				"ImageReference": map[string]string{
   114  					"Id": imageId,
   115  				},
   116  			},
   117  		}
   118  	} else if len(snapshotId) > 0 {
   119  		properties = map[string]interface{}{
   120  			"CreationData": map[string]interface{}{
   121  				"CreateOption":     "Copy",
   122  				"sourceResourceId": snapshotId,
   123  			},
   124  		}
   125  	}
   126  	params.Add(jsonutils.Marshal(properties), "Properties")
   127  	disk := &SDisk{}
   128  	return disk, self.create(resourceGroup, params, disk)
   129  }
   130  
   131  func (self *SRegion) DeleteDisk(diskId string) error {
   132  	return cloudprovider.Wait(time.Second*5, time.Minute*5, func() (bool, error) {
   133  		err := self.del(diskId)
   134  		if err == nil {
   135  			return true, nil
   136  		}
   137  		// Disk vdisk_stress-testvm-azure-1-1_1555940308395625000 is attached to VM /subscriptions/d4f0ec08-3e28-4ae5-bdf9-3dc7c5b0eeca/resourceGroups/Default/providers/Microsoft.Compute/virtualMachines/stress-testvm-azure-1.
   138  		// 更换系统盘后,数据未刷新会出现如上错误,多尝试几次即可
   139  		if strings.Contains(err.Error(), "is attached to VM") {
   140  			return false, nil
   141  		}
   142  		return false, err
   143  	})
   144  }
   145  
   146  func (self *SRegion) ResizeDisk(diskId string, sizeGb int32) error {
   147  	disk, err := self.GetDisk(diskId)
   148  	if err != nil {
   149  		return err
   150  	}
   151  	disk.Properties.DiskSizeGB = TAzureInt32(fmt.Sprintf("%d", sizeGb))
   152  	disk.Properties.ProvisioningState = ""
   153  	return self.update(jsonutils.Marshal(disk), nil)
   154  }
   155  
   156  func (self *SRegion) GetDisk(diskId string) (*SDisk, error) {
   157  	disk := SDisk{}
   158  	return &disk, self.get(diskId, url.Values{}, &disk)
   159  }
   160  
   161  func (self *SRegion) GetDisks() ([]SDisk, error) {
   162  	disks := []SDisk{}
   163  	err := self.list("Microsoft.Compute/disks", url.Values{}, &disks)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  	return disks, nil
   168  }
   169  
   170  func (self *SDisk) GetTags() (map[string]string, error) {
   171  	return self.Tags, nil
   172  }
   173  
   174  func (self *SDisk) GetStatus() string {
   175  	// 为了不统计这种磁盘挂载率, 单独设置一个状态
   176  	if self.Properties.DiskState == "ActiveSAS" {
   177  		return self.Properties.DiskState
   178  	}
   179  	status := self.Properties.ProvisioningState
   180  	switch status {
   181  	case "Updating":
   182  		return api.DISK_ALLOCATING
   183  	case "Succeeded":
   184  		return api.DISK_READY
   185  	default:
   186  		log.Errorf("Unknow azure disk %s status: %s", self.ID, status)
   187  		return api.DISK_UNKNOWN
   188  	}
   189  }
   190  
   191  func (self *SDisk) GetId() string {
   192  	return self.ID
   193  }
   194  
   195  func (self *SDisk) Refresh() error {
   196  	disk, err := self.storage.zone.region.GetDisk(self.ID)
   197  	if err != nil {
   198  		return errors.Wrapf(err, "GetDisk(%s)", self.ID)
   199  	}
   200  	return jsonutils.Update(self, disk)
   201  }
   202  
   203  func (self *SDisk) Delete(ctx context.Context) error {
   204  	return self.storage.zone.region.DeleteDisk(self.ID)
   205  }
   206  
   207  func (self *SDisk) Resize(ctx context.Context, sizeMb int64) error {
   208  	return self.storage.zone.region.ResizeDisk(self.ID, int32(sizeMb/1024))
   209  }
   210  
   211  func (self *SDisk) GetName() string {
   212  	if len(self.Name) > 0 {
   213  		return self.Name
   214  	}
   215  	return self.ID
   216  }
   217  
   218  func (self *SDisk) GetGlobalId() string {
   219  	return strings.ToLower(self.ID)
   220  }
   221  
   222  func (self *SDisk) IsEmulated() bool {
   223  	return false
   224  }
   225  
   226  func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) {
   227  	return self.storage, nil
   228  }
   229  
   230  func (self *SDisk) GetFsFormat() string {
   231  	return ""
   232  }
   233  
   234  func (self *SDisk) GetIsNonPersistent() bool {
   235  	return false
   236  }
   237  
   238  func (self *SDisk) GetDriver() string {
   239  	return "scsi"
   240  }
   241  
   242  func (self *SDisk) GetCacheMode() string {
   243  	return "none"
   244  }
   245  
   246  func (self *SDisk) GetMountpoint() string {
   247  	return ""
   248  }
   249  
   250  func (self *SDisk) GetDiskFormat() string {
   251  	return "vhd"
   252  }
   253  
   254  func (self *SDisk) GetDiskSizeMB() int {
   255  	return int(self.Properties.DiskSizeGB.Int32()) * 1024
   256  }
   257  
   258  func (self *SDisk) GetIsAutoDelete() bool {
   259  	return false
   260  }
   261  
   262  func (self *SDisk) GetTemplateId() string {
   263  	if self.Properties.CreationData.ImageReference != nil {
   264  		return self.Properties.CreationData.ImageReference.ID
   265  	}
   266  	return ""
   267  }
   268  
   269  func (self *SDisk) GetDiskType() string {
   270  	if len(self.Properties.OsType) > 0 {
   271  		return api.DISK_TYPE_SYS
   272  	}
   273  	return api.DISK_TYPE_DATA
   274  }
   275  
   276  func (self *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) {
   277  	snapshot, err := self.storage.zone.region.CreateSnapshot(self.ID, name, desc)
   278  	if err != nil {
   279  		return nil, errors.Wrapf(err, "CreateSnapshot")
   280  	}
   281  	return snapshot, nil
   282  }
   283  
   284  func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
   285  	snapshots, err := self.storage.zone.region.ListSnapshots()
   286  	if err != nil {
   287  		return nil, errors.Wrapf(err, "ListSnapshots")
   288  	}
   289  	ret := []cloudprovider.ICloudSnapshot{}
   290  	for i := range snapshots {
   291  		if strings.ToLower(snapshots[i].Properties.CreationData.SourceResourceID) == strings.ToLower(self.ID) {
   292  			snapshots[i].region = self.storage.zone.region
   293  			ret = append(ret, &snapshots[i])
   294  		}
   295  	}
   296  	return ret, nil
   297  }
   298  
   299  func (self *SDisk) GetBillingType() string {
   300  	return billing_api.BILLING_TYPE_POSTPAID
   301  }
   302  
   303  func (self *SDisk) GetCreatedAt() time.Time {
   304  	return self.Properties.TimeCreated
   305  }
   306  
   307  func (self *SDisk) GetExpiredAt() time.Time {
   308  	return time.Time{}
   309  }
   310  
   311  func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) {
   312  	if self.Properties.DiskState != "Unattached" {
   313  		return "", fmt.Errorf("Azure reset disk needs to be done in the Unattached state, current status: %s", self.Properties.DiskState)
   314  	}
   315  	disk, err := self.storage.zone.region.CreateDisk(self.Sku.Name, self.Name, 0, "", snapshotId, self.GetProjectId())
   316  	if err != nil {
   317  		return "", errors.Wrap(err, "CreateDisk")
   318  	}
   319  	err = self.storage.zone.region.DeleteDisk(self.ID)
   320  	if err != nil {
   321  		log.Warningf("delete old disk %s error: %v", self.ID, err)
   322  	}
   323  	return disk.ID, nil
   324  }
   325  
   326  func (disk *SDisk) GetAccessPath() string {
   327  	return ""
   328  }
   329  
   330  func (self *SDisk) Rebuild(ctx context.Context) error {
   331  	// TODO
   332  	return cloudprovider.ErrNotSupported
   333  }
   334  
   335  func (self *SDisk) GetProjectId() string {
   336  	return getResourceGroup(self.ID)
   337  }