yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/hcso/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 hcso
    16  
    17  import (
    18  	"context"
    19  	"strings"
    20  	"time"
    21  
    22  	"yunion.io/x/jsonutils"
    23  	"yunion.io/x/log"
    24  	"yunion.io/x/pkg/errors"
    25  
    26  	billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
    27  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    28  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    29  	"yunion.io/x/cloudmux/pkg/multicloud"
    30  	"yunion.io/x/cloudmux/pkg/multicloud/huawei"
    31  )
    32  
    33  /*
    34  华为云云硬盘
    35  ======创建==========
    36  1.磁盘只能挂载到同一可用区的云服务器内,创建后不支持更换可用区
    37  2.计费模式 包年包月/按需计费
    38  3.*支持自动备份
    39  
    40  
    41  共享盘 和 普通盘:https://support.huaweicloud.com/productdesc-evs/zh-cn_topic_0032860759.html
    42  根据是否支持挂载至多台云服务器可以将云硬盘分为非共享云硬盘和共享云硬盘。
    43  一个非共享云硬盘只能挂载至一台云服务器,而一个共享云硬盘可以同时挂载至多台云服务器。
    44  单个共享云硬盘最多可同时挂载给16个云服务器。目前,共享云硬盘只适用于数据盘,不支持系统盘。
    45  */
    46  
    47  type Attachment struct {
    48  	ServerID     string `json:"server_id"`
    49  	AttachmentID string `json:"attachment_id"`
    50  	AttachedAt   string `json:"attached_at"`
    51  	HostName     string `json:"host_name"`
    52  	VolumeID     string `json:"volume_id"`
    53  	Device       string `json:"device"`
    54  	ID           string `json:"id"`
    55  }
    56  
    57  type DiskMeta struct {
    58  	ResourceSpecCode string `json:"resourceSpecCode"`
    59  	Billing          string `json:"billing"`
    60  	ResourceType     string `json:"resourceType"`
    61  	AttachedMode     string `json:"attached_mode"`
    62  	Readonly         string `json:"readonly"`
    63  }
    64  
    65  type VolumeImageMetadata struct {
    66  	QuickStart             string `json:"__quick_start"`
    67  	ContainerFormat        string `json:"container_format"`
    68  	MinRAM                 string `json:"min_ram"`
    69  	ImageName              string `json:"image_name"`
    70  	ImageID                string `json:"image_id"`
    71  	OSType                 string `json:"__os_type"`
    72  	OSFeatureList          string `json:"__os_feature_list"`
    73  	MinDisk                string `json:"min_disk"`
    74  	SupportKVM             string `json:"__support_kvm"`
    75  	VirtualEnvType         string `json:"virtual_env_type"`
    76  	SizeGB                 string `json:"size"`
    77  	OSVersion              string `json:"__os_version"`
    78  	OSBit                  string `json:"__os_bit"`
    79  	SupportKVMHi1822Hiovs  string `json:"__support_kvm_hi1822_hiovs"`
    80  	SupportXen             string `json:"__support_xen"`
    81  	Description            string `json:"__description"`
    82  	Imagetype              string `json:"__imagetype"`
    83  	DiskFormat             string `json:"disk_format"`
    84  	ImageSourceType        string `json:"__image_source_type"`
    85  	Checksum               string `json:"checksum"`
    86  	Isregistered           string `json:"__isregistered"`
    87  	HwVifMultiqueueEnabled string `json:"hw_vif_multiqueue_enabled"`
    88  	Platform               string `json:"__platform"`
    89  }
    90  
    91  // https://support.huaweicloud.com/api-evs/zh-cn_topic_0124881427.html
    92  type SDisk struct {
    93  	storage *SStorage
    94  	multicloud.SDisk
    95  	huawei.HuaweiDiskTags
    96  	details *SResourceDetail
    97  
    98  	ID                  string              `json:"id"`
    99  	Name                string              `json:"name"`
   100  	Status              string              `json:"status"`
   101  	Attachments         []Attachment        `json:"attachments"`
   102  	Description         string              `json:"description"`
   103  	SizeGB              int                 `json:"size"`
   104  	Metadata            DiskMeta            `json:"metadata"`
   105  	Encrypted           bool                `json:"encrypted"`
   106  	Bootable            string              `json:"bootable"`
   107  	Multiattach         bool                `json:"multiattach"`
   108  	AvailabilityZone    string              `json:"availability_zone"`
   109  	SourceVolid         string              `json:"source_volid"`
   110  	SnapshotID          string              `json:"snapshot_id"`
   111  	CreatedAt           time.Time           `json:"created_at"`
   112  	VolumeType          string              `json:"volume_type"`
   113  	VolumeImageMetadata VolumeImageMetadata `json:"volume_image_metadata"`
   114  	ReplicationStatus   string              `json:"replication_status"`
   115  	UserID              string              `json:"user_id"`
   116  	ConsistencygroupID  string              `json:"consistencygroup_id"`
   117  	UpdatedAt           string              `json:"updated_at"`
   118  	EnterpriseProjectId string
   119  
   120  	ExpiredTime time.Time
   121  }
   122  
   123  func (self *SDisk) GetId() string {
   124  	return self.ID
   125  }
   126  
   127  func (self *SDisk) GetName() string {
   128  	if len(self.Name) == 0 {
   129  		return self.ID
   130  	}
   131  
   132  	return self.Name
   133  }
   134  
   135  func (self *SDisk) GetGlobalId() string {
   136  	return self.ID
   137  }
   138  
   139  func (self *SDisk) GetStatus() string {
   140  	// https://support.huaweicloud.com/api-evs/zh-cn_topic_0051803385.html
   141  	switch self.Status {
   142  	case "creating", "downloading":
   143  		return api.DISK_ALLOCATING
   144  	case "available", "in-use":
   145  		return api.DISK_READY
   146  	case "error":
   147  		return api.DISK_ALLOC_FAILED
   148  	case "attaching":
   149  		return api.DISK_ATTACHING
   150  	case "detaching":
   151  		return api.DISK_DETACHING
   152  	case "restoring-backup":
   153  		return api.DISK_REBUILD
   154  	case "backing-up":
   155  		return api.DISK_BACKUP_STARTALLOC // ?
   156  	case "error_restoring":
   157  		return api.DISK_BACKUP_ALLOC_FAILED
   158  	case "uploading":
   159  		return api.DISK_SAVING //?
   160  	case "extending":
   161  		return api.DISK_RESIZING
   162  	case "error_extending":
   163  		return api.DISK_ALLOC_FAILED // ?
   164  	case "deleting":
   165  		return api.DISK_DEALLOC //?
   166  	case "error_deleting":
   167  		return api.DISK_DEALLOC_FAILED // ?
   168  	case "rollbacking":
   169  		return api.DISK_REBUILD
   170  	case "error_rollbacking":
   171  		return api.DISK_UNKNOWN
   172  	default:
   173  		return api.DISK_UNKNOWN
   174  	}
   175  }
   176  
   177  func (self *SDisk) Refresh() error {
   178  	new, err := self.storage.zone.region.GetDisk(self.GetId())
   179  	if err != nil {
   180  		return err
   181  	}
   182  	return jsonutils.Update(self, new)
   183  }
   184  
   185  func (self *SDisk) IsEmulated() bool {
   186  	return false
   187  }
   188  
   189  func (self *SDisk) GetBillingType() string {
   190  	return billing_api.BILLING_TYPE_POSTPAID
   191  }
   192  
   193  func (self *SDisk) GetCreatedAt() time.Time {
   194  	return self.CreatedAt
   195  }
   196  
   197  func (self *SDisk) GetExpiredAt() time.Time {
   198  	var expiredTime time.Time
   199  	return expiredTime
   200  }
   201  
   202  func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) {
   203  	return self.storage, nil
   204  }
   205  
   206  func (self *SDisk) GetDiskFormat() string {
   207  	// self.volume_type ?
   208  	return "vhd"
   209  }
   210  
   211  func (self *SDisk) GetDiskSizeMB() int {
   212  	return int(self.SizeGB * 1024)
   213  }
   214  
   215  func (self *SDisk) checkAutoDelete(attachments []Attachment) bool {
   216  	autodelete := false
   217  	for _, attach := range attachments {
   218  		if len(attach.ServerID) > 0 {
   219  			// todo : 忽略错误??
   220  			vm, err := self.storage.zone.region.GetInstanceByID(attach.ServerID)
   221  			if err != nil {
   222  				volumes := vm.OSExtendedVolumesVolumesAttached
   223  				for _, vol := range volumes {
   224  					if vol.ID == self.ID && strings.ToLower(vol.DeleteOnTermination) == "true" {
   225  						autodelete = true
   226  					}
   227  				}
   228  			}
   229  
   230  			break
   231  		}
   232  	}
   233  
   234  	return autodelete
   235  }
   236  
   237  func (self *SDisk) GetIsAutoDelete() bool {
   238  	if len(self.Attachments) > 0 {
   239  		return self.checkAutoDelete(self.Attachments)
   240  	}
   241  
   242  	return false
   243  }
   244  
   245  func (self *SDisk) GetTemplateId() string {
   246  	return self.VolumeImageMetadata.ImageID
   247  }
   248  
   249  // Bootable 表示硬盘是否为启动盘。
   250  // 启动盘 != 系统盘(必须是启动盘且挂载在root device上)
   251  func (self *SDisk) GetDiskType() string {
   252  	if self.Bootable == "true" {
   253  		return api.DISK_TYPE_SYS
   254  	} else {
   255  		return api.DISK_TYPE_DATA
   256  	}
   257  }
   258  
   259  func (self *SDisk) GetFsFormat() string {
   260  	return ""
   261  }
   262  
   263  func (self *SDisk) GetIsNonPersistent() bool {
   264  	return false
   265  }
   266  
   267  func (self *SDisk) GetDriver() string {
   268  	// https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762431.html
   269  	// scsi or vbd?
   270  	// todo: implement me
   271  	return "scsi"
   272  }
   273  
   274  func (self *SDisk) GetCacheMode() string {
   275  	return "none"
   276  }
   277  
   278  func (self *SDisk) GetMountpoint() string {
   279  	if len(self.Attachments) > 0 {
   280  		return self.Attachments[0].Device
   281  	}
   282  
   283  	return ""
   284  }
   285  
   286  func (self *SDisk) GetMountServerId() string {
   287  	if len(self.Attachments) > 0 {
   288  		return self.Attachments[0].ServerID
   289  	}
   290  
   291  	return ""
   292  }
   293  
   294  func (self *SDisk) GetAccessPath() string {
   295  	return ""
   296  }
   297  
   298  func (self *SDisk) Delete(ctx context.Context) error {
   299  	disk, err := self.storage.zone.region.GetDisk(self.GetId())
   300  	if err != nil {
   301  		if errors.Cause(err) == cloudprovider.ErrNotFound {
   302  			return nil
   303  		}
   304  		return err
   305  	}
   306  	if disk.Status != "deleting" {
   307  		// 等待硬盘ready
   308  		cloudprovider.WaitStatus(self, api.DISK_READY, 5*time.Second, 60*time.Second)
   309  		err := self.storage.zone.region.DeleteDisk(self.GetId())
   310  		if err != nil {
   311  			return err
   312  		}
   313  	}
   314  
   315  	return cloudprovider.WaitDeleted(self, 10*time.Second, 120*time.Second)
   316  }
   317  
   318  func (self *SDisk) CreateISnapshot(ctx context.Context, name string, desc string) (cloudprovider.ICloudSnapshot, error) {
   319  	if snapshotId, err := self.storage.zone.region.CreateSnapshot(self.GetId(), name, desc); err != nil {
   320  		log.Errorf("createSnapshot fail %s", err)
   321  		return nil, err
   322  	} else if snapshot, err := self.getSnapshot(snapshotId); err != nil {
   323  		return nil, err
   324  	} else {
   325  		snapshot.region = self.storage.zone.region
   326  		if err := cloudprovider.WaitStatus(snapshot, api.SNAPSHOT_READY, 15*time.Second, 3600*time.Second); err != nil {
   327  			return nil, err
   328  		}
   329  		return snapshot, nil
   330  	}
   331  }
   332  
   333  func (self *SDisk) getSnapshot(snapshotId string) (*SSnapshot, error) {
   334  	snapshot, err := self.storage.zone.region.GetSnapshotById(snapshotId)
   335  	return &snapshot, err
   336  }
   337  
   338  func (self *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) {
   339  	snapshot, err := self.getSnapshot(snapshotId)
   340  	return snapshot, err
   341  }
   342  
   343  func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
   344  	snapshots, err := self.storage.zone.region.GetSnapshots(self.ID, "")
   345  	if err != nil {
   346  		return nil, err
   347  	}
   348  
   349  	isnapshots := make([]cloudprovider.ICloudSnapshot, len(snapshots))
   350  	for i := 0; i < len(snapshots); i++ {
   351  		isnapshots[i] = &snapshots[i]
   352  	}
   353  	return isnapshots, nil
   354  }
   355  
   356  func (self *SDisk) Resize(ctx context.Context, newSizeMB int64) error {
   357  	err := cloudprovider.WaitStatus(self, api.DISK_READY, 5*time.Second, 60*time.Second)
   358  	if err != nil {
   359  		return err
   360  	}
   361  
   362  	sizeGb := newSizeMB / 1024
   363  	err = self.storage.zone.region.resizeDisk(self.GetId(), sizeGb)
   364  	if err != nil {
   365  		return err
   366  	}
   367  
   368  	return cloudprovider.WaitStatusWithDelay(self, api.DISK_READY, 15*time.Second, 5*time.Second, 60*time.Second)
   369  }
   370  
   371  func (self *SDisk) Detach() error {
   372  	err := self.storage.zone.region.DetachDisk(self.GetMountServerId(), self.GetId())
   373  	if err != nil {
   374  		log.Debugf("detach server %s disk %s failed: %s", self.GetMountServerId(), self.GetId(), err)
   375  		return err
   376  	}
   377  
   378  	return cloudprovider.WaitCreated(5*time.Second, 60*time.Second, func() bool {
   379  		err := self.Refresh()
   380  		if err != nil {
   381  			log.Debugln(err)
   382  			return false
   383  		}
   384  
   385  		if self.Status == "available" {
   386  			return true
   387  		}
   388  
   389  		return false
   390  	})
   391  }
   392  
   393  func (self *SDisk) Attach(device string) error {
   394  	err := self.storage.zone.region.AttachDisk(self.GetMountServerId(), self.GetId(), device)
   395  	if err != nil {
   396  		log.Debugf("attach server %s disk %s failed: %s", self.GetMountServerId(), self.GetId(), err)
   397  		return err
   398  	}
   399  
   400  	return cloudprovider.WaitStatusWithDelay(self, api.DISK_READY, 10*time.Second, 5*time.Second, 60*time.Second)
   401  }
   402  
   403  // 在线卸载磁盘 https://support.huaweicloud.com/usermanual-ecs/zh-cn_topic_0036046828.html
   404  // 对于挂载在系统盘盘位(也就是“/dev/sda”或“/dev/vda”挂载点)上的磁盘,当前仅支持离线卸载
   405  func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) {
   406  	mountpoint := self.GetMountpoint()
   407  	if len(mountpoint) > 0 {
   408  		err := self.Detach()
   409  		if err != nil {
   410  			return "", err
   411  		}
   412  	}
   413  
   414  	diskId, err := self.storage.zone.region.resetDisk(self.GetId(), snapshotId)
   415  	if err != nil {
   416  		return diskId, err
   417  	}
   418  
   419  	err = cloudprovider.WaitStatus(self, api.DISK_READY, 5*time.Second, 300*time.Second)
   420  	if err != nil {
   421  		return "", err
   422  	}
   423  
   424  	if len(mountpoint) > 0 {
   425  		err := self.Attach(mountpoint)
   426  		if err != nil {
   427  			return "", err
   428  		}
   429  	}
   430  
   431  	return diskId, nil
   432  }
   433  
   434  // 华为云不支持重置
   435  func (self *SDisk) Rebuild(ctx context.Context) error {
   436  	return cloudprovider.ErrNotSupported
   437  }
   438  
   439  func (self *SRegion) GetDisk(diskId string) (*SDisk, error) {
   440  	if len(diskId) == 0 {
   441  		return nil, cloudprovider.ErrNotFound
   442  	}
   443  	var disk SDisk
   444  	err := DoGet(self.ecsClient.Disks.Get, diskId, nil, &disk)
   445  	return &disk, err
   446  }
   447  
   448  // https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762430.html
   449  func (self *SRegion) GetDisks(zoneId string) ([]SDisk, error) {
   450  	queries := map[string]string{}
   451  	if len(zoneId) > 0 {
   452  		queries["availability_zone"] = zoneId
   453  	}
   454  
   455  	disks := make([]SDisk, 0)
   456  	err := doListAllWithOffset(self.ecsClient.Disks.List, queries, &disks)
   457  	return disks, err
   458  }
   459  
   460  // https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762427.html
   461  func (self *SRegion) CreateDisk(zoneId string, category string, name string, sizeGb int, snapshotId string, desc string, projectId string) (string, error) {
   462  	params := jsonutils.NewDict()
   463  	volumeObj := jsonutils.NewDict()
   464  	volumeObj.Add(jsonutils.NewString(name), "name")
   465  	volumeObj.Add(jsonutils.NewString(zoneId), "availability_zone")
   466  	volumeObj.Add(jsonutils.NewString(desc), "description")
   467  	volumeObj.Add(jsonutils.NewString(category), "volume_type")
   468  	volumeObj.Add(jsonutils.NewInt(int64(sizeGb)), "size")
   469  	if len(snapshotId) > 0 {
   470  		volumeObj.Add(jsonutils.NewString(snapshotId), "snapshot_id")
   471  	}
   472  	if len(projectId) > 0 {
   473  		volumeObj.Add(jsonutils.NewString(projectId), "enterprise_project_id")
   474  	}
   475  
   476  	params.Add(volumeObj, "volume")
   477  	// 目前只支持创建按需资源,返回job id。 如果创建包年包月资源则返回order id
   478  	_id, err := self.ecsClient.Disks.AsyncCreate(params)
   479  	if err != nil {
   480  		log.Debugf("AsyncCreate with params: %s", params)
   481  		return "", errors.Wrap(err, "AsyncCreate")
   482  	}
   483  
   484  	// 按需计费
   485  	volumeId, err := self.GetTaskEntityID(self.ecsClient.Disks.ServiceType(), _id, "volume_id")
   486  	if err != nil {
   487  		return "", errors.Wrap(err, "GetAllSubTaskEntityIDs")
   488  	}
   489  
   490  	if len(volumeId) == 0 {
   491  		return "", errors.Errorf("CreateInstance job %s result is emtpy", _id)
   492  	} else {
   493  		return volumeId, nil
   494  	}
   495  }
   496  
   497  // https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762428.html
   498  // 默认删除云硬盘关联的所有快照
   499  func (self *SRegion) DeleteDisk(diskId string) error {
   500  	return DoDeleteWithSpec(self.ecsClient.Disks.DeleteInContextWithSpec, nil, diskId, "", nil, nil)
   501  }
   502  
   503  /*
   504  扩容状态为available的云硬盘时,没有约束限制。
   505  扩容状态为in-use的云硬盘时,有以下约束:
   506  不支持共享云硬盘,即multiattach参数值必须为false。
   507  云硬盘所挂载的云服务器状态必须为ACTIVE、PAUSED、SUSPENDED、SHUTOFF才支持扩容
   508  */
   509  func (self *SRegion) resizeDisk(diskId string, sizeGB int64) error {
   510  	params := jsonutils.NewDict()
   511  	osExtendObj := jsonutils.NewDict()
   512  	osExtendObj.Add(jsonutils.NewInt(sizeGB), "new_size") // GB
   513  	params.Add(osExtendObj, "os-extend")
   514  	_, err := self.ecsClient.Disks.PerformAction2("action", diskId, params, "")
   515  	return err
   516  }
   517  
   518  /*
   519  https://support.huaweicloud.com/api-evs/zh-cn_topic_0051408629.html
   520  只支持快照回滚到源云硬盘,不支持快照回滚到其它指定云硬盘。
   521  只有云硬盘状态处于“available”或“error_rollbacking”状态才允许快照回滚到源云硬盘。
   522  */
   523  func (self *SRegion) resetDisk(diskId, snapshotId string) (string, error) {
   524  	params := jsonutils.NewDict()
   525  	rollbackObj := jsonutils.NewDict()
   526  	rollbackObj.Add(jsonutils.NewString(diskId), "volume_id")
   527  	params.Add(rollbackObj, "rollback")
   528  	_, err := self.ecsClient.OsSnapshots.PerformAction2("rollback", snapshotId, params, "")
   529  	return diskId, err
   530  }
   531  
   532  func (self *SDisk) GetProjectId() string {
   533  	return self.EnterpriseProjectId
   534  }