yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/openstack/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 openstack
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net/url"
    21  	"strings"
    22  	"time"
    23  
    24  	"yunion.io/x/jsonutils"
    25  	"yunion.io/x/log"
    26  	"yunion.io/x/pkg/errors"
    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  	"yunion.io/x/onecloud/pkg/util/version"
    33  )
    34  
    35  const (
    36  	DISK_STATUS_CREATING = "creating" // The volume is being created.
    37  
    38  	DISK_STATUS_ATTACHING = "attaching" // The volume is attaching to an instance.
    39  	DISK_STATUS_DETACHING = "detaching" // The volume is detaching from an instance.
    40  	DISK_STATUS_EXTENDING = "extending" // The volume is being extended.
    41  	DISK_STATUS_DELETING  = "deleting"  // The volume is being deleted.
    42  
    43  	DISK_STATUS_RETYPING          = "retyping"          // The volume is changing type to another volume type.
    44  	DISK_STATUS_AVAILABLE         = "available"         // The volume is ready to attach to an instance.
    45  	DISK_STATUS_RESERVED          = "reserved"          // The volume is reserved for attaching or shelved.
    46  	DISK_STATUS_IN_USE            = "in-use"            // The volume is attached to an instance.
    47  	DISK_STATUS_MAINTENANCE       = "maintenance"       // The volume is locked and being migrated.
    48  	DISK_STATUS_AWAITING_TRANSFER = "awaiting-transfer" // The volume is awaiting for transfer.
    49  	DISK_STATUS_BACKING_UP        = "backing-up"        // The volume is being backed up.
    50  	DISK_STATUS_RESTORING_BACKUP  = "restoring-backup"  // A backup is being restored to the volume.
    51  	DISK_STATUS_DOWNLOADING       = "downloading"       // The volume is downloading an image.
    52  	DISK_STATUS_UPLOADING         = "uploading"         // The volume is being uploaded to an image.
    53  
    54  	DISK_STATUS_ERROR            = "error"            // A volume creation error occurred.
    55  	DISK_STATUS_ERROR_DELETING   = "error_deleting"   // A volume deletion error occurred.
    56  	DISK_STATUS_ERROR_BACKING_UP = "error_backing-up" // A backup error occurred.
    57  	DISK_STATUS_ERROR_RESTORING  = "error_restoring"  // A backup restoration error occurred.
    58  	DISK_STATUS_ERROR_EXTENDING  = "error_extending"  // An error occurred while attempting to extend a volume.
    59  
    60  )
    61  
    62  type Attachment struct {
    63  	ServerId     string
    64  	AttachmentId string
    65  	HostName     string
    66  	VolumeId     string
    67  	Device       string
    68  	Id           string
    69  }
    70  
    71  type Link struct {
    72  	Href string
    73  	Rel  string
    74  }
    75  
    76  type VolumeImageMetadata struct {
    77  	Checksum        string
    78  	MinRAM          int
    79  	DiskFormat      string
    80  	ImageName       string
    81  	ImageId         string
    82  	ContainerFormat string
    83  	MinDisk         int
    84  	Size            int
    85  }
    86  
    87  type SDisk struct {
    88  	storage *SStorage
    89  	multicloud.SDisk
    90  	OpenStackTags
    91  
    92  	Id   string
    93  	Name string
    94  
    95  	MigrationStatus string
    96  	Attachments     []Attachment
    97  	Links           []Link
    98  
    99  	AvailabilityZone  string
   100  	Host              string `json:"os-vol-host-attr:host"`
   101  	Encrypted         bool
   102  	ReplicationStatus string
   103  	SnapshotId        string
   104  	Size              int
   105  	UserId            string
   106  	TenantId          string `json:"os-vol-tenant-attr:tenant_id"`
   107  	Migstat           string `json:"os-vol-mig-status-attr:migstat"`
   108  
   109  	Status              string
   110  	Description         string
   111  	Multiattach         string
   112  	SourceVolid         string
   113  	ConsistencygroupId  string
   114  	VolumeImageMetadata VolumeImageMetadata
   115  	NameId              string `json:"os-vol-mig-status-attr:name_id"`
   116  	Bootable            bool
   117  	CreatedAt           time.Time
   118  	VolumeType          string
   119  }
   120  
   121  func (region *SRegion) GetDisks() ([]SDisk, error) {
   122  	disks := []SDisk{}
   123  	resource := "/volumes/detail"
   124  	query := url.Values{}
   125  	query.Set("all_tenants", "true")
   126  	for {
   127  		resp, err := region.bsList(resource, query)
   128  		if err != nil {
   129  			return nil, errors.Wrap(err, "bsList")
   130  		}
   131  		part := struct {
   132  			Volumes      []SDisk
   133  			VolumesLinks SNextLinks
   134  		}{}
   135  		err = resp.Unmarshal(&part)
   136  		if err != nil {
   137  			return nil, errors.Wrap(err, "resp.Unmarshal")
   138  		}
   139  		disks = append(disks, part.Volumes...)
   140  		marker := part.VolumesLinks.GetNextMark()
   141  		if len(marker) == 0 {
   142  			break
   143  		}
   144  		query.Set("marker", marker)
   145  	}
   146  	return disks, nil
   147  }
   148  
   149  func (disk *SDisk) GetId() string {
   150  	return disk.Id
   151  }
   152  
   153  func (disk *SDisk) Delete(ctx context.Context) error {
   154  	err := disk.storage.zone.region.DeleteDisk(disk.Id)
   155  	if err != nil {
   156  		return err
   157  	}
   158  	return cloudprovider.WaitDeleted(disk, 10*time.Second, 8*time.Minute)
   159  }
   160  
   161  func (disk *SDisk) attachInstances(attachments []Attachment) error {
   162  	for _, attachment := range attachments {
   163  		startTime := time.Now()
   164  		for time.Now().Sub(startTime) < 5*time.Minute {
   165  			if err := disk.storage.zone.region.AttachDisk(attachment.ServerId, disk.Id); err != nil {
   166  				if strings.Contains(err.Error(), "status must be available or downloading") {
   167  					time.Sleep(time.Second * 10)
   168  					continue
   169  				}
   170  				log.Errorf("recover attach disk %s => instance %s error: %v", disk.Id, attachment.ServerId, err)
   171  				return err
   172  			} else {
   173  				return nil
   174  			}
   175  		}
   176  	}
   177  	return nil
   178  }
   179  
   180  func (disk *SDisk) Resize(ctx context.Context, sizeMb int64) error {
   181  	maxVersion := ""
   182  	for _, service := range []string{OPENSTACK_SERVICE_VOLUMEV3, OPENSTACK_SERVICE_VOLUMEV2, OPENSTACK_SERVICE_VOLUME} {
   183  		maxVersion, _ = disk.storage.zone.region.GetMaxVersion(service)
   184  		if len(maxVersion) > 0 {
   185  			break
   186  		}
   187  	}
   188  	if version.GE(maxVersion, "3.42") {
   189  		return disk.storage.zone.region.ResizeDisk(disk.Id, sizeMb)
   190  	}
   191  	instanceIds := []string{}
   192  	for _, attachement := range disk.Attachments {
   193  		if err := disk.storage.zone.region.DetachDisk(attachement.ServerId, disk.Id); err != nil {
   194  			return err
   195  		}
   196  		instanceIds = append(instanceIds, attachement.ServerId)
   197  	}
   198  	err := disk.storage.zone.region.ResizeDisk(disk.Id, sizeMb)
   199  	if err != nil {
   200  		disk.attachInstances(disk.Attachments)
   201  		return err
   202  	}
   203  	return disk.attachInstances(disk.Attachments)
   204  }
   205  
   206  func (disk *SDisk) GetName() string {
   207  	if len(disk.Name) > 0 {
   208  		return disk.Name
   209  	}
   210  	return disk.Id
   211  }
   212  
   213  func (disk *SDisk) GetGlobalId() string {
   214  	return disk.Id
   215  }
   216  
   217  func (disk *SDisk) IsEmulated() bool {
   218  	return false
   219  }
   220  
   221  func (disk *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) {
   222  	return disk.storage, nil
   223  }
   224  
   225  func (disk *SDisk) GetStatus() string {
   226  	switch disk.Status {
   227  	case DISK_STATUS_CREATING, DISK_STATUS_DOWNLOADING:
   228  		return api.DISK_ALLOCATING
   229  	case DISK_STATUS_ATTACHING:
   230  		return api.DISK_ATTACHING
   231  	case DISK_STATUS_DETACHING:
   232  		return api.DISK_DETACHING
   233  	case DISK_STATUS_EXTENDING:
   234  		return api.DISK_RESIZING
   235  	case DISK_STATUS_RETYPING, DISK_STATUS_AVAILABLE, DISK_STATUS_RESERVED, DISK_STATUS_IN_USE, DISK_STATUS_MAINTENANCE, DISK_STATUS_AWAITING_TRANSFER, DISK_STATUS_BACKING_UP, DISK_STATUS_RESTORING_BACKUP, DISK_STATUS_UPLOADING:
   236  		return api.DISK_READY
   237  	case DISK_STATUS_DELETING:
   238  		return api.DISK_DEALLOC
   239  	case DISK_STATUS_ERROR:
   240  		return api.DISK_ALLOC_FAILED
   241  	default:
   242  		return api.DISK_UNKNOWN
   243  	}
   244  }
   245  
   246  func (disk *SDisk) Refresh() error {
   247  	_disk, err := disk.storage.zone.region.GetDisk(disk.Id)
   248  	if err != nil {
   249  		return err
   250  	}
   251  	return jsonutils.Update(disk, _disk)
   252  }
   253  
   254  func (disk *SDisk) ResizeDisk(sizeMb int64) error {
   255  	return disk.storage.zone.region.ResizeDisk(disk.Id, sizeMb)
   256  }
   257  
   258  func (disk *SDisk) GetDiskFormat() string {
   259  	return "lvm"
   260  }
   261  
   262  func (disk *SDisk) GetDiskSizeMB() int {
   263  	return disk.Size * 1024
   264  }
   265  
   266  func (disk *SDisk) GetIsAutoDelete() bool {
   267  	return false
   268  }
   269  
   270  func (disk *SDisk) GetTemplateId() string {
   271  	return disk.VolumeImageMetadata.ImageId
   272  }
   273  
   274  func (disk *SDisk) GetDiskType() string {
   275  	if disk.Bootable {
   276  		return api.DISK_TYPE_SYS
   277  	}
   278  	return api.DISK_TYPE_DATA
   279  }
   280  
   281  func (disk *SDisk) GetFsFormat() string {
   282  	return ""
   283  }
   284  
   285  func (disk *SDisk) GetIsNonPersistent() bool {
   286  	return false
   287  }
   288  
   289  func (disk *SDisk) GetDriver() string {
   290  	return "scsi"
   291  }
   292  
   293  func (disk *SDisk) GetCacheMode() string {
   294  	return "none"
   295  }
   296  
   297  func (disk *SDisk) GetMountpoint() string {
   298  	return ""
   299  }
   300  
   301  func (region *SRegion) CreateDisk(imageRef string, volumeType string, name string, sizeGb int, desc string, projectId string) (*SDisk, error) {
   302  	params := map[string]map[string]interface{}{
   303  		"volume": {
   304  			"size":        sizeGb,
   305  			"volume_type": volumeType,
   306  			"name":        name,
   307  			"description": desc,
   308  		},
   309  	}
   310  	if len(imageRef) > 0 {
   311  		params["volume"]["imageRef"] = imageRef
   312  	}
   313  	resp, err := region.bsCreate(projectId, "/volumes", params)
   314  	if err != nil {
   315  		return nil, errors.Wrap(err, "bsCreate")
   316  	}
   317  
   318  	disk := &SDisk{}
   319  	err = resp.Unmarshal(disk, "volume")
   320  	if err != nil {
   321  		return nil, errors.Wrap(err, "resp.Unmarshal")
   322  	}
   323  	//这里由于不好初始化disk的storage就手动循环了,如果是通过镜像创建,有个下载过程,比较慢,等待时间较长
   324  	startTime := time.Now()
   325  	timeout := time.Minute * 10
   326  	//若是通过镜像创建,需要先下载镜像,需要的时间更长
   327  	if len(imageRef) > 0 {
   328  		timeout = time.Minute * 30
   329  	}
   330  	for time.Now().Sub(startTime) < timeout {
   331  		disk, err = region.GetDisk(disk.GetGlobalId())
   332  		if err != nil {
   333  			return nil, errors.Wrapf(err, "GetDisk(%s)", disk.GetGlobalId())
   334  		}
   335  		log.Debugf("disk status %s expect %s", disk.GetStatus(), api.DISK_READY)
   336  		status := disk.GetStatus()
   337  		if status == api.DISK_READY {
   338  			break
   339  		}
   340  		if status == api.DISK_ALLOC_FAILED {
   341  			messages, _ := region.GetMessages(disk.Id)
   342  			if len(messages) > 0 {
   343  				return nil, fmt.Errorf("allocate disk %s failed, status is %s message: %s", disk.Name, disk.Status, messages[0].UserMessage)
   344  			}
   345  			return nil, fmt.Errorf("allocate disk %s failed, status is %s", disk.Name, disk.Status)
   346  		}
   347  		time.Sleep(time.Second * 10)
   348  	}
   349  	if disk.GetStatus() != api.DISK_READY {
   350  		return nil, fmt.Errorf("timeout for waitting disk ready, current status: %s", disk.Status)
   351  	}
   352  	return disk, nil
   353  }
   354  
   355  func (region *SRegion) GetDisk(diskId string) (*SDisk, error) {
   356  	resource := fmt.Sprintf("/volumes/%s", diskId)
   357  	resp, err := region.bsGet(resource)
   358  	if err != nil {
   359  		return nil, errors.Wrap(err, "bsGet")
   360  	}
   361  	disk := &SDisk{}
   362  	err = resp.Unmarshal(disk, "volume")
   363  	if err != nil {
   364  		return nil, errors.Wrap(err, "resp.Unmarshal")
   365  	}
   366  	return disk, nil
   367  }
   368  
   369  func (region *SRegion) DeleteDisk(diskId string) error {
   370  	resource := fmt.Sprintf("/volumes/%s", diskId)
   371  	_, err := region.bsDelete(resource)
   372  	return err
   373  }
   374  
   375  func (region *SRegion) ResizeDisk(diskId string, sizeMb int64) error {
   376  	params := map[string]map[string]interface{}{
   377  		"os-extend": {
   378  			"new_size": sizeMb / 1024,
   379  		},
   380  	}
   381  	resource := fmt.Sprintf("/volumes/%s/action", diskId)
   382  	_, err := region.bsPost(resource, params)
   383  	return err
   384  }
   385  
   386  func (region *SRegion) ResetDisk(diskId, snapshotId string) error {
   387  	params := map[string]map[string]interface{}{
   388  		"revert": {
   389  			"snapshot_id": snapshotId,
   390  		},
   391  	}
   392  	resource := fmt.Sprintf("/volumes/%s/action", diskId)
   393  	_, err := region.bsPost(resource, params)
   394  	return err
   395  }
   396  
   397  func (disk *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) {
   398  	snapshot, err := disk.storage.zone.region.CreateSnapshot(disk.Id, name, desc)
   399  	if err != nil {
   400  		return nil, err
   401  	}
   402  	return snapshot, cloudprovider.WaitStatus(snapshot, api.SNAPSHOT_READY, time.Second*5, time.Minute*5)
   403  }
   404  
   405  func (disk *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) {
   406  	return disk.storage.zone.region.GetISnapshotById(snapshotId)
   407  }
   408  
   409  func (disk *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
   410  	snapshots, err := disk.storage.zone.region.GetSnapshots(disk.Id)
   411  	if err != nil {
   412  		return nil, errors.Wrapf(err, "GetSnapshots(%s)", disk.Id)
   413  	}
   414  	isnapshots := []cloudprovider.ICloudSnapshot{}
   415  	for i := range snapshots {
   416  		snapshots[i].region = disk.storage.zone.region
   417  		isnapshots = append(isnapshots, &snapshots[i])
   418  	}
   419  	return isnapshots, nil
   420  }
   421  
   422  func (disk *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) {
   423  	return disk.Id, disk.storage.zone.region.ResetDisk(disk.Id, snapshotId)
   424  }
   425  
   426  func (disk *SDisk) GetBillingType() string {
   427  	return billing_api.BILLING_TYPE_POSTPAID
   428  }
   429  
   430  func (disk *SDisk) GetCreatedAt() time.Time {
   431  	return disk.CreatedAt
   432  }
   433  
   434  func (disk *SDisk) GetExpiredAt() time.Time {
   435  	return time.Time{}
   436  }
   437  
   438  func (disk *SDisk) GetAccessPath() string {
   439  	return ""
   440  }
   441  
   442  func (disk *SDisk) Rebuild(ctx context.Context) error {
   443  	return cloudprovider.ErrNotSupported
   444  }
   445  
   446  func (disk *SDisk) GetProjectId() string {
   447  	return disk.TenantId
   448  }