yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/ctyun/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 ctyun
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"strconv"
    21  	"time"
    22  
    23  	"yunion.io/x/jsonutils"
    24  	"yunion.io/x/log"
    25  	"yunion.io/x/pkg/errors"
    26  	"yunion.io/x/pkg/utils"
    27  
    28  	billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
    29  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    30  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    31  	"yunion.io/x/cloudmux/pkg/multicloud"
    32  )
    33  
    34  // http://ctyun-api-url/apiproxy/v3/ondemand/queryVolumes
    35  type SDisk struct {
    36  	storage *SStorage
    37  	multicloud.SDisk
    38  	CtyunTags
    39  	multicloud.SBillingBase
    40  
    41  	diskDetails *DiskDetails
    42  
    43  	ID                  string       `json:"id"`
    44  	Status              string       `json:"status"`
    45  	Name                string       `json:"name"`
    46  	CreatedAt           int64        `json:"created_at"`
    47  	UpdatedAt           string       `json:"updated_at"`
    48  	Multiattach         bool         `json:"multiattach"`
    49  	ReplicationStatus   string       `json:"replication_status"`
    50  	SizeGB              int64        `json:"size"`
    51  	Metadata            Metadata     `json:"metadata"`
    52  	VolumeType          string       `json:"volume_type"`
    53  	UserID              string       `json:"user_id"`
    54  	Shareable           bool         `json:"shareable"`
    55  	Encrypted           bool         `json:"encrypted"`
    56  	Bootable            string       `json:"bootable"`
    57  	AvailabilityZone    string       `json:"availability_zone"`
    58  	Attachments         []Attachment `json:"attachments"`
    59  	MasterOrderID       string       `json:"masterOrderId"`
    60  	WorkOrderResourceID string       `json:"workOrderResourceId"`
    61  	ExpireTime          int64        `json:"expireTime"`
    62  	IsFreeze            int64        `json:"isFreeze"`
    63  }
    64  
    65  type Attachment struct {
    66  	VolumeID     string `json:"volume_id"`
    67  	AttachmentID string `json:"attachment_id"`
    68  	AttachedAt   string `json:"attached_at"`
    69  	ServerID     string `json:"server_id"`
    70  	Device       string `json:"device"`
    71  	ID           string `json:"id"`
    72  }
    73  
    74  type Metadata struct {
    75  	OrderID          string `json:"orderID"`
    76  	AttachedMode     string `json:"attached_mode"`
    77  	ResourceSpecCode string `json:"resourceSpecCode"`
    78  	ProductID        string `json:"productID"`
    79  	Readonly         string `json:"readonly"`
    80  }
    81  
    82  type DiskDetails struct {
    83  	ID                  string `json:"id"`
    84  	ResEbsID            string `json:"resEbsId"`
    85  	Size                int64  `json:"size"`
    86  	Name                string `json:"name"`
    87  	RegionID            string `json:"regionId"`
    88  	AccountID           string `json:"accountId"`
    89  	UserID              string `json:"userId"`
    90  	HostID              string `json:"hostId"`
    91  	OrderID             string `json:"orderId"`
    92  	Status              int64  `json:"status"`
    93  	Type                string `json:"type"`
    94  	VolumeStatus        int64  `json:"volumeStatus"`
    95  	CreateDate          int64  `json:"createDate"`
    96  	DueDate             int64  `json:"dueDate"`
    97  	ZoneID              string `json:"zoneId"`
    98  	ZoneName            string `json:"zoneName"`
    99  	IsSysVolume         int64  `json:"isSysVolume"`
   100  	IsPackaged          int64  `json:"isPackaged"`
   101  	WorkOrderResourceID string `json:"workOrderResourceId"`
   102  	IsFreeze            int64  `json:"isFreeze"`
   103  }
   104  
   105  func (self *SDisk) GetBillingType() string {
   106  	if self.ExpireTime > 0 {
   107  		return billing_api.BILLING_TYPE_PREPAID
   108  	}
   109  
   110  	return billing_api.BILLING_TYPE_POSTPAID
   111  }
   112  
   113  func (self *SDisk) GetCreatedAt() time.Time {
   114  	return time.Unix(self.CreatedAt/1000, 0)
   115  }
   116  
   117  func (self *SDisk) GetExpiredAt() time.Time {
   118  	if self.ExpireTime == 0 {
   119  		return time.Time{}
   120  	}
   121  
   122  	return time.Unix(self.ExpireTime/1000, 0)
   123  }
   124  
   125  func (self *SDisk) GetId() string {
   126  	return self.ID
   127  }
   128  
   129  func (self *SDisk) GetName() string {
   130  	if len(self.Name) > 0 {
   131  		return self.Name
   132  	}
   133  
   134  	return self.ID
   135  }
   136  
   137  func (self *SDisk) GetGlobalId() string {
   138  	return self.GetId()
   139  }
   140  
   141  func (self *SDisk) GetStatus() string {
   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  	new, err := self.storage.zone.region.GetDisk(self.GetId())
   180  	if err != nil {
   181  		return err
   182  	}
   183  	return jsonutils.Update(self, new)
   184  }
   185  
   186  func (self *SDisk) IsEmulated() bool {
   187  	return false
   188  }
   189  
   190  func (self *SDisk) GetSysTags() map[string]string {
   191  	data := map[string]string{}
   192  	data["hypervisor"] = api.HYPERVISOR_CTYUN
   193  	return data
   194  }
   195  
   196  func (self *SDisk) GetProjectId() string {
   197  	return ""
   198  }
   199  
   200  func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) {
   201  	return self.storage, nil
   202  }
   203  
   204  func (self *SDisk) GetIStorageId() string {
   205  	return self.storage.GetId()
   206  }
   207  
   208  func (self *SDisk) GetDiskFormat() string {
   209  	return "vhd"
   210  }
   211  
   212  func (self *SDisk) GetDiskSizeMB() int {
   213  	return int(self.SizeGB * 1024)
   214  }
   215  
   216  func (self *SDisk) GetIsAutoDelete() bool {
   217  	if len(self.Attachments) == 0 {
   218  		return false
   219  	}
   220  
   221  	if self.Bootable == "true" {
   222  		return true
   223  	}
   224  
   225  	return false
   226  }
   227  
   228  func (self *SDisk) GetTemplateId() string {
   229  	if len(self.Attachments) > 0 && len(self.Attachments[0].ServerID) > 0 {
   230  		server, err := self.storage.zone.region.GetVMById(self.Attachments[0].ServerID)
   231  		if err != nil {
   232  			log.Errorf("SDisk.GetTemplateId %s", err)
   233  			return ""
   234  		}
   235  
   236  		image, err := server.GetImage()
   237  		if err != nil {
   238  			log.Errorf("SDisk.GetImage %s", err)
   239  			return ""
   240  		}
   241  
   242  		return image.GetId()
   243  	}
   244  
   245  	return ""
   246  }
   247  
   248  func (self *SDisk) GetDiskType() string {
   249  	if self.Bootable == "true" {
   250  		return api.DISK_TYPE_SYS
   251  	} else {
   252  		return api.DISK_TYPE_DATA
   253  	}
   254  }
   255  
   256  func (self *SDisk) GetFsFormat() string {
   257  	return ""
   258  }
   259  
   260  func (self *SDisk) GetIsNonPersistent() bool {
   261  	return false
   262  }
   263  
   264  func (self *SDisk) GetDriver() string {
   265  	return "scsi"
   266  }
   267  
   268  func (self *SDisk) GetCacheMode() string {
   269  	return "none"
   270  }
   271  
   272  func (self *SDisk) GetMountpoint() string {
   273  	if len(self.Attachments) > 0 {
   274  		return self.Attachments[0].Device
   275  	}
   276  
   277  	return ""
   278  }
   279  
   280  func (self *SDisk) GetAccessPath() string {
   281  	return ""
   282  }
   283  
   284  func (self *SDisk) Delete(ctx context.Context) error {
   285  	_, err := self.storage.zone.region.DeleteDisk(self.GetId())
   286  	if err != nil {
   287  		return errors.Wrap(err, "SDisk.Delete")
   288  	}
   289  
   290  	return nil
   291  }
   292  
   293  func (self *SDisk) CreateISnapshot(ctx context.Context, name string, desc string) (cloudprovider.ICloudSnapshot, error) {
   294  	return nil, cloudprovider.ErrNotSupported
   295  }
   296  
   297  // POST http://ctyun-api-url/apiproxy/v3/ondemand/createVBS
   298  func (self *SDisk) GetISnapshot(idStr string) (cloudprovider.ICloudSnapshot, error) {
   299  	return nil, cloudprovider.ErrNotFound
   300  }
   301  
   302  // no snapshot api opened
   303  func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
   304  	return []cloudprovider.ICloudSnapshot{}, nil
   305  }
   306  
   307  // POST http://ctyun-api-url/apiproxy/v3/ondemand/updateDiskBackupPolicy
   308  func (self *SDisk) GetExtSnapshotPolicyIds() ([]string, error) {
   309  	return []string{}, nil
   310  }
   311  
   312  func (self *SDisk) Resize(ctx context.Context, newSizeMB int64) error {
   313  	jobId, err := self.storage.zone.region.ResizeDisk(self.GetId(), strconv.Itoa(int(newSizeMB/1024)))
   314  	if err != nil {
   315  		return errors.Wrap(err, "Disk.Resize")
   316  	}
   317  
   318  	err = cloudprovider.Wait(10*time.Second, 1800*time.Second, func() (b bool, err error) {
   319  		statusJson, err := self.storage.zone.region.GetVolumeJob(jobId)
   320  		// ctyun 偶尔会报客户端错误,其实job已经到后台执行了
   321  		if err != nil {
   322  			log.Debugf("Ctyun.SDisk.Resize.GetVolumeJob %s", err)
   323  			return false, nil
   324  		}
   325  
   326  		if status, _ := statusJson.GetString("status"); status == "SUCCESS" {
   327  			return true, nil
   328  		} else if status == "FAILED" {
   329  			return false, fmt.Errorf("Resize job %s failed", jobId)
   330  		} else {
   331  			return false, nil
   332  		}
   333  	})
   334  	if err != nil {
   335  		return errors.Wrap(err, "Disk.Resize.Wait")
   336  	}
   337  
   338  	return nil
   339  }
   340  
   341  func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) {
   342  	return "", cloudprovider.ErrNotSupported
   343  }
   344  
   345  func (self *SDisk) Rebuild(ctx context.Context) error {
   346  	return cloudprovider.ErrNotSupported
   347  }
   348  
   349  func (self *SDisk) GetDiskDetails() (*DiskDetails, error) {
   350  	if self.diskDetails != nil {
   351  		return self.diskDetails, nil
   352  	}
   353  
   354  	details, err := self.storage.zone.region.GetDiskDetailByDiskId(self.GetId())
   355  	if err != nil {
   356  		return nil, errors.Wrap(err, "SDisk.GetDiskDetails.GetDiskDetailByDiskId")
   357  	}
   358  
   359  	self.diskDetails = details
   360  	return self.diskDetails, nil
   361  }
   362  
   363  func (self *SRegion) GetDisks() ([]SDisk, error) {
   364  	params := map[string]string{
   365  		"regionId": self.GetId(),
   366  	}
   367  
   368  	resp, err := self.client.DoGet("/apiproxy/v3/ondemand/queryVolumes", params)
   369  	if err != nil {
   370  		return nil, errors.Wrap(err, "Region.GetDisks.DoGet")
   371  	}
   372  
   373  	disks := make([]SDisk, 0)
   374  	err = resp.Unmarshal(&disks, "returnObj", "volumes")
   375  	if err != nil {
   376  		return nil, errors.Wrap(err, "Region.GetDisks.Unmarshal")
   377  	}
   378  
   379  	for i := range disks {
   380  		izone, err := self.GetIZoneById(getZoneGlobalId(self, disks[i].AvailabilityZone))
   381  		if err != nil {
   382  			return nil, errors.Wrap(err, "SRegion.GetDisk.GetIZoneById")
   383  		}
   384  
   385  		disks[i].storage = &SStorage{
   386  			zone:        izone.(*SZone),
   387  			storageType: disks[i].VolumeType,
   388  		}
   389  	}
   390  
   391  	return disks, nil
   392  }
   393  
   394  func (self *SRegion) GetDisk(diskId string) (*SDisk, error) {
   395  	params := map[string]string{
   396  		"regionId": self.GetId(),
   397  		"volumeId": diskId,
   398  	}
   399  
   400  	resp, err := self.client.DoGet("/apiproxy/v3/ondemand/queryVolumes", params)
   401  	if err != nil {
   402  		return nil, errors.Wrap(err, "Region.GetDisk.DoGet")
   403  	}
   404  
   405  	disks := make([]SDisk, 0)
   406  	err = resp.Unmarshal(&disks, "returnObj", "volumes")
   407  	if err != nil {
   408  		return nil, errors.Wrap(err, "Region.GetDisks.Unmarshal")
   409  	}
   410  
   411  	if len(disks) == 0 {
   412  		return nil, errors.Wrap(cloudprovider.ErrNotFound, "SRegion.GetDisk")
   413  	} else if len(disks) == 1 {
   414  		izone, err := self.GetIZoneById(getZoneGlobalId(self, disks[0].AvailabilityZone))
   415  		if err != nil {
   416  			return nil, errors.Wrap(err, "SRegion.GetDisk.GetIZoneById")
   417  		}
   418  
   419  		disks[0].storage = &SStorage{
   420  			zone:        izone.(*SZone),
   421  			storageType: disks[0].VolumeType,
   422  		}
   423  
   424  		return &disks[0], nil
   425  	} else {
   426  		return nil, errors.Wrap(cloudprovider.ErrDuplicateId, "SRegion.GetDisk")
   427  	}
   428  }
   429  
   430  func (self *SRegion) GetDiskDetailByDiskId(diskId string) (*DiskDetails, error) {
   431  	params := map[string]string{
   432  		"volumeId": diskId,
   433  		"regionId": self.GetId(),
   434  	}
   435  
   436  	resp, err := self.client.DoGet("/apiproxy/v3/queryDataDiskDetail", params)
   437  	if err != nil {
   438  		return nil, errors.Wrap(err, "Region.GetDiskDetailByDiskId.DoGet")
   439  	}
   440  
   441  	disk := &DiskDetails{}
   442  	err = resp.Unmarshal(disk, "returnObj")
   443  	if err != nil {
   444  		return nil, errors.Wrap(err, "Region.GetDiskDetailByDiskId.Unmarshal")
   445  	}
   446  
   447  	return disk, nil
   448  }
   449  
   450  func (self *SRegion) CreateDisk(zoneId, name, diskType, size string) (*SDisk, error) {
   451  	diskParams := jsonutils.NewDict()
   452  	diskParams.Set("regionId", jsonutils.NewString(self.GetId()))
   453  	diskParams.Set("zoneId", jsonutils.NewString(zoneId))
   454  	diskParams.Set("name", jsonutils.NewString(name))
   455  	diskParams.Set("type", jsonutils.NewString(diskType))
   456  	diskParams.Set("size", jsonutils.NewString(size))
   457  	diskParams.Set("count", jsonutils.NewString("1"))
   458  
   459  	params := map[string]jsonutils.JSONObject{
   460  		"createVolumeInfo": diskParams,
   461  	}
   462  
   463  	disks, err := self.GetDisks()
   464  	if err != nil {
   465  		return nil, errors.Wrap(err, "SRegion.CreateDisk.GetDisks")
   466  	}
   467  
   468  	diskIds := []string{}
   469  	for i := range disks {
   470  		diskIds = append(diskIds, disks[i].GetId())
   471  	}
   472  
   473  	_, err = self.client.DoPost("/apiproxy/v3/ondemand/createVolume", params)
   474  	if err != nil {
   475  		return nil, errors.Wrap(err, "Region.CreateDisk.DoPost")
   476  	}
   477  
   478  	// 查询job结果一直报错,目前先用替代办法查找新硬盘ID,可能不准确。后续需要替换其它方法
   479  	diskId := ""
   480  	cloudprovider.Wait(3*time.Second, 300*time.Second, func() (b bool, err error) {
   481  		disks, err := self.GetDisks()
   482  		if err != nil {
   483  			return false, err
   484  		}
   485  
   486  		for i := range disks {
   487  			if !utils.IsInStringArray(disks[i].GetId(), diskIds) {
   488  				diskId = disks[i].GetId()
   489  				return true, nil
   490  			}
   491  		}
   492  
   493  		return false, nil
   494  	})
   495  
   496  	return self.GetDisk(diskId)
   497  }
   498  
   499  func (self *SRegion) CreateDiskBackup(name, volumeId, desc string) (string, error) {
   500  	params := map[string]jsonutils.JSONObject{
   501  		"regionId":    jsonutils.NewString(self.GetId()),
   502  		"volumeId":    jsonutils.NewString(volumeId),
   503  		"name":        jsonutils.NewString(name),
   504  		"description": jsonutils.NewString(desc),
   505  	}
   506  
   507  	resp, err := self.client.DoPost("/apiproxy/v3/ondemand/createVBS", params)
   508  	if err != nil {
   509  		return "", errors.Wrap(err, "Region.CreateDiskBackup.DoPost")
   510  	}
   511  
   512  	var jobId string
   513  	err = resp.Unmarshal(&jobId, "returnObj", "data")
   514  	if err != nil {
   515  		return "", errors.Wrap(err, "Region.CreateDiskBackup.Unmarshal")
   516  	}
   517  
   518  	return jobId, nil
   519  }
   520  
   521  func (self *SRegion) DeleteDisk(volumeId string) (string, error) {
   522  	params := map[string]jsonutils.JSONObject{
   523  		"regionId": jsonutils.NewString(self.GetId()),
   524  		"volumeId": jsonutils.NewString(volumeId),
   525  	}
   526  
   527  	resp, err := self.client.DoPost("/apiproxy/v3/ondemand/deleteVolume", params)
   528  	if err != nil {
   529  		msg, _ := resp.GetString("message")
   530  		return "", errors.Wrap(fmt.Errorf(msg), "SRegion.DeleteDisk.DoPost")
   531  	}
   532  
   533  	var jobId string
   534  	err = resp.Unmarshal(&jobId, "returnObj", "data")
   535  	if err != nil {
   536  		return "", errors.Wrap(err, "SRegion.DeleteDisk.Unmarshal")
   537  	}
   538  
   539  	return jobId, nil
   540  }
   541  
   542  func (self *SRegion) ResizeDisk(volumeId string, newSizeGB string) (string, error) {
   543  	params := map[string]jsonutils.JSONObject{
   544  		"regionId": jsonutils.NewString(self.GetId()),
   545  		"volumeId": jsonutils.NewString(volumeId),
   546  		"newSize":  jsonutils.NewString(newSizeGB),
   547  	}
   548  
   549  	resp, err := self.client.DoPost("/apiproxy/v3/ondemand/expandVolumeSize", params)
   550  	if err != nil {
   551  		return "", errors.Wrap(err, "SRegion.ResizeDisk.DoPost")
   552  	}
   553  
   554  	var ok bool
   555  	err = resp.Unmarshal(&ok, "returnObj", "status")
   556  	if !ok {
   557  		msg, _ := resp.GetString("message")
   558  		return "", errors.Wrap(fmt.Errorf(msg), "SRegion.ResizeDisk.JobFailed")
   559  	}
   560  
   561  	var jobId string
   562  	err = resp.Unmarshal(&jobId, "returnObj", "data")
   563  	if err != nil {
   564  		return "", errors.Wrap(err, "SRegion.ResizeDisk.Unmarshal")
   565  	}
   566  
   567  	return jobId, nil
   568  }
   569  
   570  func (self *SRegion) RestoreDisk(volumeId, backupId string) (string, error) {
   571  	diskBackupParams := jsonutils.NewDict()
   572  	diskBackupParams.Set("regionId", jsonutils.NewString(self.GetId()))
   573  	diskBackupParams.Set("backupId", jsonutils.NewString(backupId))
   574  	diskBackupParams.Set("volumeId", jsonutils.NewString(volumeId))
   575  
   576  	params := map[string]jsonutils.JSONObject{
   577  		"diskBackup": diskBackupParams,
   578  	}
   579  
   580  	resp, err := self.client.DoPost("/apiproxy/v3/restoreDiskBackup", params)
   581  	if err != nil {
   582  		return "", errors.Wrap(err, "SRegion.RestoreDisk.DoPost")
   583  	}
   584  
   585  	var ok bool
   586  	err = resp.Unmarshal(&ok, "returnObj", "status")
   587  	if !ok {
   588  		msg, _ := resp.GetString("message")
   589  		return "", errors.Wrap(fmt.Errorf(msg), "SRegion.RestoreDisk.JobFailed")
   590  	}
   591  
   592  	var jobId string
   593  	err = resp.Unmarshal(&jobId, "returnObj", "data")
   594  	if err != nil {
   595  		return "", errors.Wrap(err, "SRegion.RestoreDisk.Unmarshal")
   596  	}
   597  
   598  	return jobId, nil
   599  }