yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/qcloud/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 qcloud
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"sort"
    21  	"strconv"
    22  	"strings"
    23  	"time"
    24  
    25  	"yunion.io/x/jsonutils"
    26  	"yunion.io/x/log"
    27  	"yunion.io/x/pkg/utils"
    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 Placement struct {
    36  	ProjectId int
    37  	Zone      string
    38  }
    39  
    40  type SDisk struct {
    41  	storage *SStorage
    42  	multicloud.SDisk
    43  	QcloudTags
    44  
    45  	Attached             bool
    46  	AutoRenewFlagError   bool
    47  	CreateTime           time.Time
    48  	DeadlineError        bool
    49  	DeadlineTime         time.Time
    50  	DifferDaysOfDeadline int
    51  	DiskChargeType       string
    52  	DiskId               string
    53  	DiskName             string
    54  	DiskSize             int
    55  	DiskState            string
    56  	DiskType             string
    57  	DiskUsage            string
    58  	Encrypt              bool
    59  	InstanceId           string
    60  	IsReturnable         bool
    61  	Placement            Placement
    62  	Portable             bool
    63  	RenewFlag            string
    64  	ReturnFailCode       int
    65  	RollbackPercent      int
    66  	Rollbacking          bool
    67  	SnapshotAbility      bool
    68  	DeleteWithInstance   bool
    69  }
    70  
    71  type SDiskSet []SDisk
    72  
    73  func (v SDiskSet) Len() int {
    74  	return len(v)
    75  }
    76  
    77  func (v SDiskSet) Swap(i, j int) {
    78  	v[i], v[j] = v[j], v[i]
    79  }
    80  
    81  func (v SDiskSet) Less(i, j int) bool {
    82  	if v[i].DiskUsage == "SYSTEM_DISK" || v[j].DiskUsage == "DATA_DISK" {
    83  		return true
    84  	}
    85  	return false
    86  }
    87  
    88  func (self *SRegion) GetDisks(instanceId string, zoneId string, category string, diskIds []string, offset int, limit int) ([]SDisk, int, error) {
    89  	if limit > 50 || limit <= 0 {
    90  		limit = 50
    91  	}
    92  	params := make(map[string]string)
    93  	params["Limit"] = fmt.Sprintf("%d", limit)
    94  	params["Offset"] = fmt.Sprintf("%d", offset)
    95  	filter := 0
    96  
    97  	if len(zoneId) > 0 {
    98  		params[fmt.Sprintf("Filters.%d.Name", filter)] = "zone"
    99  		params[fmt.Sprintf("Filters.%d.Values.0", filter)] = zoneId
   100  		filter++
   101  	}
   102  
   103  	if len(instanceId) > 0 {
   104  		params[fmt.Sprintf("Filters.%d.Name", filter)] = "instance-id"
   105  		params[fmt.Sprintf("Filters.%d.Values.0", filter)] = instanceId
   106  		filter++
   107  	}
   108  
   109  	if len(category) > 0 {
   110  		params[fmt.Sprintf("Filters.%d.Name", filter)] = "disk-type"
   111  		params[fmt.Sprintf("Filters.%d.Values.0", filter)] = category
   112  		filter++
   113  	}
   114  	if diskIds != nil && len(diskIds) > 0 {
   115  		for index, diskId := range diskIds {
   116  			params[fmt.Sprintf("DiskIds.%d", index)] = diskId
   117  		}
   118  	}
   119  
   120  	body, err := self.cbsRequest("DescribeDisks", params)
   121  	if err != nil {
   122  		log.Errorf("GetDisks fail %s", err)
   123  		return nil, 0, err
   124  	}
   125  
   126  	disks := make([]SDisk, 0)
   127  	err = body.Unmarshal(&disks, "DiskSet")
   128  	if err != nil {
   129  		log.Errorf("Unmarshal disk details fail %s", err)
   130  		return nil, 0, err
   131  	}
   132  	total, _ := body.Float("TotalCount")
   133  	sort.Sort(SDiskSet(disks))
   134  	return disks, int(total), nil
   135  }
   136  
   137  func (self *SRegion) GetDisk(diskId string) (*SDisk, error) {
   138  	disks, total, err := self.GetDisks("", "", "", []string{diskId}, 0, 1)
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	if total != 1 {
   143  		return nil, cloudprovider.ErrNotFound
   144  	}
   145  	return &disks[0], nil
   146  }
   147  
   148  func (self *SDisk) GetId() string {
   149  	return self.DiskId
   150  }
   151  
   152  func (self *SRegion) DeleteDisk(diskId string) error {
   153  	params := make(map[string]string)
   154  	params["Region"] = self.Region
   155  	params["DiskIds.0"] = diskId
   156  
   157  	_, err := self.cbsRequest("TerminateDisks", params)
   158  	return err
   159  }
   160  
   161  func (self *SDisk) Delete(ctx context.Context) error {
   162  	return self.storage.zone.region.DeleteDisk(self.DiskId)
   163  }
   164  
   165  func (self *SRegion) ResizeDisk(ctx context.Context, diskId string, sizeGb int64) error {
   166  	params := make(map[string]string)
   167  	params["DiskId"] = diskId
   168  	params["DiskSize"] = fmt.Sprintf("%d", sizeGb)
   169  	startTime := time.Now()
   170  	for {
   171  		_, err := self.cbsRequest("ResizeDisk", params)
   172  		if err != nil {
   173  			if strings.Index(err.Error(), "Code=InvalidDisk.Busy") > 0 {
   174  				log.Infof("The disk is busy, try later ...")
   175  				time.Sleep(10 * time.Second)
   176  				if time.Now().Sub(startTime) > time.Minute*20 {
   177  					return cloudprovider.ErrTimeout
   178  				}
   179  				continue
   180  			}
   181  		}
   182  		return err
   183  	}
   184  }
   185  
   186  func (self *SDisk) Resize(ctx context.Context, sizeMb int64) error {
   187  	return self.storage.zone.region.ResizeDisk(ctx, self.DiskId, sizeMb/1024)
   188  }
   189  
   190  func (self *SDisk) GetName() string {
   191  	if len(self.DiskName) > 0 && self.DiskName != "未命名" {
   192  		return self.DiskName
   193  	}
   194  	return self.DiskId
   195  }
   196  
   197  func (self *SDisk) GetGlobalId() string {
   198  	return self.DiskId
   199  }
   200  
   201  func (self *SDisk) IsEmulated() bool {
   202  	return false
   203  }
   204  
   205  func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) {
   206  	return self.storage, nil
   207  }
   208  
   209  func (self *SDisk) GetStatus() string {
   210  	switch self.DiskState {
   211  	case "ATTACHING", "DETACHING", "EXPANDING", "ROLLBACKING":
   212  		return api.DISK_ALLOCATING
   213  	default:
   214  		return api.DISK_READY
   215  	}
   216  }
   217  
   218  func (self *SDisk) Refresh() error {
   219  	new, err := self.storage.zone.region.GetDisk(self.DiskId)
   220  	if err != nil {
   221  		return err
   222  	}
   223  	return jsonutils.Update(self, new)
   224  }
   225  
   226  func (self *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) {
   227  	snapshotId, err := self.storage.zone.region.CreateSnapshot(self.DiskId, name, desc)
   228  	if err != nil {
   229  		log.Errorf("createSnapshot fail %s", err)
   230  		return nil, err
   231  	}
   232  	snapshots, total, err := self.storage.zone.region.GetSnapshots("", "", "", []string{snapshotId}, 0, 1)
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  	if total == 1 {
   237  		snapshot := &snapshots[0]
   238  		err := cloudprovider.WaitStatus(snapshot, api.SNAPSHOT_READY, 15*time.Second, 3600*time.Second)
   239  		if err != nil {
   240  			return nil, err
   241  		}
   242  		return snapshot, nil
   243  	}
   244  	return nil, nil
   245  }
   246  
   247  func (self *SDisk) GetDiskType() string {
   248  	switch self.DiskUsage {
   249  	case "SYSTEM_DISK":
   250  		return api.DISK_TYPE_SYS
   251  	case "DATA_DISK":
   252  		return api.DISK_TYPE_DATA
   253  	default:
   254  		return api.DISK_TYPE_DATA
   255  	}
   256  }
   257  
   258  func (self *SDisk) GetFsFormat() string {
   259  	return ""
   260  }
   261  
   262  func (self *SDisk) GetIsNonPersistent() bool {
   263  	return false
   264  }
   265  
   266  func (self *SDisk) GetDriver() string {
   267  	return "scsi"
   268  }
   269  
   270  func (self *SDisk) GetCacheMode() string {
   271  	return "none"
   272  }
   273  
   274  func (self *SDisk) GetMountpoint() string {
   275  	return ""
   276  }
   277  
   278  func (self *SDisk) GetBillingType() string {
   279  	switch self.DiskChargeType {
   280  	case "PREPAID":
   281  		return billing_api.BILLING_TYPE_PREPAID
   282  	case "POSTPAID_BY_HOUR":
   283  		return billing_api.BILLING_TYPE_POSTPAID
   284  	default:
   285  		return billing_api.BILLING_TYPE_PREPAID
   286  	}
   287  }
   288  
   289  func (self *SDisk) GetDiskFormat() string {
   290  	return "vhd"
   291  }
   292  
   293  func (self *SDisk) GetDiskSizeMB() int {
   294  	return self.DiskSize * 1024
   295  }
   296  
   297  func (self *SDisk) GetIsAutoDelete() bool {
   298  	return self.DeleteWithInstance
   299  }
   300  
   301  func (self *SDisk) GetCreatedAt() time.Time {
   302  	// 2019-12-25 09:00:43  #非UTC时间
   303  	return self.CreateTime.Add(time.Hour * -8)
   304  }
   305  
   306  func (self *SDisk) GetExpiredAt() time.Time {
   307  	if self.DeadlineTime.IsZero() {
   308  		return time.Time{}
   309  	}
   310  	return self.DeadlineTime.Add(time.Hour * -8)
   311  }
   312  
   313  func (self *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) {
   314  	snapshots, total, err := self.storage.zone.region.GetSnapshots("", "", "", []string{snapshotId}, 0, 1)
   315  	if err != nil {
   316  		return nil, err
   317  	}
   318  	if total == 1 {
   319  		return &snapshots[0], nil
   320  	}
   321  	return nil, cloudprovider.ErrNotFound
   322  }
   323  
   324  func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
   325  	snapshots := make([]SSnapshot, 0)
   326  	for {
   327  		parts, total, err := self.storage.zone.region.GetSnapshots("", self.DiskId, "", []string{}, 0, 20)
   328  		if err != nil {
   329  			log.Errorf("GetDisks fail %s", err)
   330  			return nil, err
   331  		}
   332  		snapshots = append(snapshots, parts...)
   333  		if len(snapshots) >= total {
   334  			break
   335  		}
   336  	}
   337  	isnapshots := make([]cloudprovider.ICloudSnapshot, len(snapshots))
   338  	for i := 0; i < len(snapshots); i++ {
   339  		snapshots[i].region = self.storage.zone.region
   340  		isnapshots[i] = &snapshots[i]
   341  	}
   342  	return isnapshots, nil
   343  }
   344  
   345  func (self *SDisk) GetExtSnapshotPolicyIds() ([]string, error) {
   346  	return self.storage.zone.region.GetSnapshotIdByDiskId(self.GetId())
   347  }
   348  
   349  func (self *SDisk) GetTemplateId() string {
   350  	//return self.ImageId
   351  	return ""
   352  }
   353  
   354  func (self *SRegion) ResetDisk(diskId, snapshotId string) error {
   355  	params := make(map[string]string)
   356  	params["Region"] = self.Region
   357  	params["DiskId"] = diskId
   358  	params["SnapshotId"] = snapshotId
   359  	_, err := self.cbsRequest("ApplySnapshot", params)
   360  	if err != nil {
   361  		log.Errorf("ResetDisk %s to snapshot %s fail %s", diskId, snapshotId, err)
   362  		return err
   363  	}
   364  	return nil
   365  }
   366  
   367  func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) {
   368  	return "", self.storage.zone.region.ResetDisk(self.DiskId, snapshotId)
   369  }
   370  
   371  func (self *SRegion) CreateDisk(zoneId string, category string, name string, sizeGb int, desc string, projectId string) (string, error) {
   372  	params := make(map[string]string)
   373  	params["Region"] = self.Region
   374  	params["DiskType"] = category
   375  	params["DiskChargeType"] = "POSTPAID_BY_HOUR"
   376  	// [TencentCloudSDKError] Code=InvalidParameter, Message=DiskName: vdisk_stress-testvm-qcloud-1_1560117118026502729, length is 48, out of range [0,20] (e11d6c4007e4), RequestId=a8409994-0357-42e9-b028-e11d6c4007e4
   377  	if len(name) > 20 {
   378  		name = name[:20]
   379  	}
   380  	params["DiskName"] = name
   381  	params["Placement.Zone"] = zoneId
   382  	if len(projectId) > 0 {
   383  		params["Placement.ProjectId"] = projectId
   384  	}
   385  	//params["Encrypted"] = "false"
   386  	params["DiskSize"] = fmt.Sprintf("%d", sizeGb)
   387  	params["ClientToken"] = utils.GenRequestId(20)
   388  
   389  	body, err := self.cbsRequest("CreateDisks", params)
   390  	if err != nil {
   391  		return "", err
   392  	}
   393  	diskIDSet := []string{}
   394  	err = body.Unmarshal(&diskIDSet, "DiskIdSet")
   395  	if err != nil {
   396  		return "", err
   397  	}
   398  	if len(diskIDSet) < 1 {
   399  		return "", fmt.Errorf("Create Disk error")
   400  	}
   401  	return diskIDSet[0], nil
   402  }
   403  
   404  func (disk *SDisk) GetAccessPath() string {
   405  	return ""
   406  }
   407  
   408  func (self *SDisk) Rebuild(ctx context.Context) error {
   409  	// TODO
   410  	return cloudprovider.ErrNotSupported
   411  }
   412  
   413  func (self *SDisk) GetProjectId() string {
   414  	return strconv.Itoa(self.Placement.ProjectId)
   415  }