yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/hcs/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 hcs
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net/url"
    21  	"strings"
    22  	"time"
    23  
    24  	"yunion.io/x/jsonutils"
    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  	multicloud.SDisk
    94  	huawei.HuaweiDiskTags
    95  	//details *SResourceDetail
    96  
    97  	region *SRegion
    98  
    99  	Id                  string              `json:"id"`
   100  	Name                string              `json:"name"`
   101  	Status              string              `json:"status"`
   102  	Attachments         []Attachment        `json:"attachments"`
   103  	Description         string              `json:"description"`
   104  	SizeGB              int                 `json:"size"`
   105  	Metadata            DiskMeta            `json:"metadata"`
   106  	Encrypted           bool                `json:"encrypted"`
   107  	Bootable            string              `json:"bootable"`
   108  	Multiattach         bool                `json:"multiattach"`
   109  	AvailabilityZone    string              `json:"availability_zone"`
   110  	SourceVolid         string              `json:"source_volid"`
   111  	SnapshotId          string              `json:"snapshot_id"`
   112  	CreatedAt           time.Time           `json:"created_at"`
   113  	VolumeType          string              `json:"volume_type"`
   114  	VolumeImageMetadata VolumeImageMetadata `json:"volume_image_metadata"`
   115  	ReplicationStatus   string              `json:"replication_status"`
   116  	UserId              string              `json:"user_id"`
   117  	ConsistencygroupId  string              `json:"consistencygroup_id"`
   118  	UpdatedAt           string              `json:"updated_at"`
   119  	EnterpriseProjectId string
   120  
   121  	ExpiredTime time.Time
   122  }
   123  
   124  func (self *SDisk) GetId() string {
   125  	return self.Id
   126  }
   127  
   128  func (self *SDisk) GetName() string {
   129  	if len(self.Name) == 0 {
   130  		return self.Id
   131  	}
   132  
   133  	return self.Name
   134  }
   135  
   136  func (self *SDisk) GetGlobalId() string {
   137  	return self.Id
   138  }
   139  
   140  func (self *SDisk) GetStatus() string {
   141  	// https://support.huaweicloud.com/api-evs/zh-cn_topic_0051803385.html
   142  	switch self.Status {
   143  	case "creating", "downloading":
   144  		return api.DISK_ALLOCATING
   145  	case "available", "in-use":
   146  		return api.DISK_READY
   147  	case "error":
   148  		return api.DISK_ALLOC_FAILED
   149  	case "attaching":
   150  		return api.DISK_ATTACHING
   151  	case "detaching":
   152  		return api.DISK_DETACHING
   153  	case "restoring-backup":
   154  		return api.DISK_REBUILD
   155  	case "backing-up":
   156  		return api.DISK_BACKUP_STARTALLOC // ?
   157  	case "error_restoring":
   158  		return api.DISK_BACKUP_ALLOC_FAILED
   159  	case "uploading":
   160  		return api.DISK_SAVING //?
   161  	case "extending":
   162  		return api.DISK_RESIZING
   163  	case "error_extending":
   164  		return api.DISK_ALLOC_FAILED // ?
   165  	case "deleting":
   166  		return api.DISK_DEALLOC //?
   167  	case "error_deleting":
   168  		return api.DISK_DEALLOC_FAILED // ?
   169  	case "rollbacking":
   170  		return api.DISK_REBUILD
   171  	case "error_rollbacking":
   172  		return api.DISK_UNKNOWN
   173  	default:
   174  		return api.DISK_UNKNOWN
   175  	}
   176  }
   177  
   178  func (self *SDisk) Refresh() error {
   179  	ret, err := self.region.GetDisk(self.Id)
   180  	if err != nil {
   181  		return err
   182  	}
   183  	return jsonutils.Update(self, ret)
   184  }
   185  
   186  func (self *SDisk) GetBillingType() string {
   187  	return billing_api.BILLING_TYPE_POSTPAID
   188  }
   189  
   190  func (self *SDisk) GetCreatedAt() time.Time {
   191  	return self.CreatedAt
   192  }
   193  
   194  func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) {
   195  	return nil, cloudprovider.ErrNotImplemented
   196  	//	return self.storage, nil
   197  }
   198  
   199  func (self *SDisk) GetDiskFormat() string {
   200  	return "vhd"
   201  }
   202  
   203  func (self *SDisk) GetDiskSizeMB() int {
   204  	return int(self.SizeGB * 1024)
   205  }
   206  
   207  func (self *SDisk) GetIsAutoDelete() bool {
   208  	return false
   209  }
   210  
   211  func (self *SDisk) GetTemplateId() string {
   212  	return self.VolumeImageMetadata.ImageId
   213  }
   214  
   215  // Bootable 表示硬盘是否为启动盘。
   216  // 启动盘 != 系统盘(必须是启动盘且挂载在root device上)
   217  func (self *SDisk) GetDiskType() string {
   218  	if self.Bootable == "true" {
   219  		return api.DISK_TYPE_SYS
   220  	}
   221  	return api.DISK_TYPE_DATA
   222  }
   223  
   224  func (self *SDisk) GetFsFormat() string {
   225  	return ""
   226  }
   227  
   228  func (self *SDisk) GetIsNonPersistent() bool {
   229  	return false
   230  }
   231  
   232  func (self *SDisk) GetDriver() string {
   233  	return "scsi"
   234  }
   235  
   236  func (self *SDisk) GetCacheMode() string {
   237  	return "none"
   238  }
   239  
   240  func (self *SDisk) GetMountpoint() string {
   241  	if len(self.Attachments) > 0 {
   242  		return self.Attachments[0].Device
   243  	}
   244  	return ""
   245  }
   246  
   247  func (self *SDisk) GetMountServerId() string {
   248  	if len(self.Attachments) > 0 {
   249  		return self.Attachments[0].ServerId
   250  	}
   251  	return ""
   252  }
   253  
   254  func (self *SDisk) GetAccessPath() string {
   255  	return ""
   256  }
   257  
   258  func (self *SDisk) Delete(ctx context.Context) error {
   259  	return self.region.DeleteDisk(self.Id)
   260  }
   261  
   262  func (self *SDisk) CreateISnapshot(ctx context.Context, name string, desc string) (cloudprovider.ICloudSnapshot, error) {
   263  	snap, err := self.region.CreateSnapshot(self.Id, name, desc)
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  	return snap, nil
   268  }
   269  
   270  func (self *SDisk) GetISnapshot(id string) (cloudprovider.ICloudSnapshot, error) {
   271  	snapshot, err := self.region.GetSnapshot(id)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  	return snapshot, nil
   276  }
   277  
   278  func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
   279  	snapshots, err := self.region.GetSnapshots(self.Id, "")
   280  	if err != nil {
   281  		return nil, err
   282  	}
   283  	ret := []cloudprovider.ICloudSnapshot{}
   284  	for i := 0; i < len(snapshots); i++ {
   285  		snapshots[i].region = self.region
   286  		ret = append(ret, &snapshots[i])
   287  	}
   288  	return ret, nil
   289  }
   290  
   291  func (self *SDisk) Resize(ctx context.Context, sizeMB int64) error {
   292  	return self.region.ResizeDisk(self.GetId(), sizeMB/1024)
   293  }
   294  
   295  func (self *SDisk) Detach() error {
   296  	return self.region.DetachDisk(self.GetMountServerId(), self.GetId())
   297  }
   298  
   299  func (self *SDisk) Attach(device string) error {
   300  	return self.region.AttachDisk(self.GetMountServerId(), self.GetId(), device)
   301  }
   302  
   303  // 在线卸载磁盘 https://support.huaweicloud.com/usermanual-ecs/zh-cn_topic_0036046828.html
   304  // 对于挂载在系统盘盘位(也就是“/dev/sda”或“/dev/vda”挂载点)上的磁盘,当前仅支持离线卸载
   305  func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) {
   306  	mountpoint := self.GetMountpoint()
   307  	if len(mountpoint) > 0 {
   308  		err := self.Detach()
   309  		if err != nil {
   310  			return "", err
   311  		}
   312  	}
   313  
   314  	err := self.region.ResetDisk(self.Id, snapshotId)
   315  	if err != nil {
   316  		return self.Id, err
   317  	}
   318  	err = cloudprovider.WaitStatus(self, api.DISK_READY, 5*time.Second, 300*time.Second)
   319  	if err != nil {
   320  		return "", err
   321  	}
   322  	if len(mountpoint) > 0 {
   323  		err := self.Attach(mountpoint)
   324  		if err != nil {
   325  			return "", err
   326  		}
   327  	}
   328  	return self.Id, nil
   329  }
   330  
   331  // 华为云不支持重置
   332  func (self *SDisk) Rebuild(ctx context.Context) error {
   333  	return cloudprovider.ErrNotSupported
   334  }
   335  
   336  func (self *SRegion) GetDisk(id string) (*SDisk, error) {
   337  	ret := &SDisk{region: self}
   338  	res := fmt.Sprintf("volumes/%s", id)
   339  	return ret, self.evsGet(res, ret)
   340  }
   341  
   342  // https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762430.html
   343  func (self *SRegion) GetDisks(zoneId string) ([]SDisk, error) {
   344  	params := url.Values{}
   345  	if len(zoneId) > 0 {
   346  		params.Set("availability_zone", zoneId)
   347  	}
   348  	disks := []SDisk{}
   349  	return disks, self.evsList("volumes/detail", params, &disks)
   350  }
   351  
   352  func (self *SRegion) CreateDisk(zoneId string, category string, name string, sizeGb int, snapshotId string, desc string, projectId string) (*SDisk, error) {
   353  	volume := map[string]interface{}{
   354  		"name":              name,
   355  		"availability_zone": zoneId,
   356  		"description":       desc,
   357  		"volume_type":       category,
   358  		"size":              sizeGb,
   359  	}
   360  	if len(snapshotId) > 0 {
   361  		volume["snapshot_id"] = snapshotId
   362  	}
   363  	if len(projectId) > 0 {
   364  		volume["enterprise_project_id"] = projectId
   365  	}
   366  	params := map[string]interface{}{
   367  		"volume": volume,
   368  	}
   369  	ret := &SDisk{region: self}
   370  	return ret, self.evsCreate("volumes", params, ret)
   371  }
   372  
   373  func (self *SRegion) DeleteDisk(id string) error {
   374  	res := fmt.Sprintf("volumes/%s", id)
   375  	return self.evsDelete(res)
   376  }
   377  
   378  func (self *SRegion) ResizeDisk(id string, sizeGb int64) error {
   379  	params := map[string]interface{}{
   380  		"os_extend": map[string]interface{}{
   381  			"new_size": sizeGb,
   382  		},
   383  	}
   384  	res := fmt.Sprintf("volumes/%s", id)
   385  	return self.evsPerform(res, "action", params)
   386  }
   387  
   388  func (self *SRegion) ResetDisk(diskId, snapshotId string) error {
   389  	params := map[string]interface{}{
   390  		"volume_id": diskId,
   391  	}
   392  	res := fmt.Sprintf("os-vendor-snapshots/%s", snapshotId)
   393  	return self.evsPerform(res, "rollback", params)
   394  }
   395  
   396  func (self *SDisk) GetProjectId() string {
   397  	return self.EnterpriseProjectId
   398  }
   399  
   400  func (self *SRegion) DetachDisk(instanceId string, diskId string) error {
   401  	res := fmt.Sprintf("servers/%s/os-volume_attachments/%s", instanceId, diskId)
   402  	return self.delete("ecs", "v2", res)
   403  }
   404  
   405  func (self *SRegion) AttachDisk(instanceId string, diskId string, device string) error {
   406  	params := map[string]interface{}{
   407  		"volumeAttachment": map[string]interface{}{
   408  			"volumeId": instanceId,
   409  			"device":   device,
   410  		},
   411  	}
   412  	res := fmt.Sprintf("servers/%s/os-volume_attachments", instanceId)
   413  	return self.perform("ecs", "v2", res, "action", params, nil)
   414  }
   415  
   416  type SDiskType struct {
   417  	ExtraSpecs ExtraSpecs `json:"extra_specs"`
   418  	Name       string     `json:"name"`
   419  	QosSpecsId string     `json:"qos_specs_id"`
   420  	Id         string     `json:"id"`
   421  	IsPublic   bool       `json:"is_public"`
   422  }
   423  
   424  func (self *SDiskType) IsAvaliableInZone(zoneId string) bool {
   425  	if strings.Contains(self.ExtraSpecs.HwAvailabilityZone, zoneId) {
   426  		return true
   427  	}
   428  	return false
   429  }
   430  
   431  type ExtraSpecs struct {
   432  	VolumeBackendName  string `json:"volume_backend_name"`
   433  	HwAvailabilityZone string `json:"HW:availability-zone"`
   434  }
   435  
   436  func (self *SRegion) GetDiskTypes() ([]SDiskType, error) {
   437  	ret := []SDiskType{}
   438  	return ret, self.get("evs", "v3", "types", &ret)
   439  }