yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/zstack/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 zstack
    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  
    27  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    28  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    29  	"yunion.io/x/cloudmux/pkg/multicloud"
    30  )
    31  
    32  type SDisk struct {
    33  	multicloud.SDisk
    34  	ZStackTags
    35  
    36  	localStorage *SLocalStorage
    37  	storage      *SStorage
    38  	region       *SRegion
    39  
    40  	ZStackBasic
    41  	PrimaryStorageUUID string  `json:"primaryStorageUuid"`
    42  	VMInstanceUUID     string  `json:"vmInstanceUuid"`
    43  	DiskOfferingUUID   string  `json:"diskOfferingUuid"`
    44  	RootImageUUID      string  `json:"rootImageUuid"`
    45  	InstallPath        string  `json:"installPath"`
    46  	Type               string  `json:"type"`
    47  	Format             string  `json:"format"`
    48  	Size               int     `json:"size"`
    49  	ActualSize         int     `json:"actualSize"`
    50  	DeviceID           float32 `json:"deviceId"`
    51  	State              string  `json:"state"`
    52  	Status             string  `json:"status"`
    53  
    54  	ZStackTime
    55  }
    56  
    57  func (region *SRegion) GetDisk(diskId string) (*SDisk, error) {
    58  	disk := &SDisk{region: region}
    59  	err := region.client.getResource("volumes", diskId, disk)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	if disk.Status == "NotInstantiated" || disk.Status == "Deleted" {
    64  		return nil, cloudprovider.ErrNotFound
    65  	}
    66  	return disk, nil
    67  }
    68  
    69  func (region *SRegion) GetDiskWithStorage(diskId string) (*SDisk, error) {
    70  	disk, err := region.GetDisk(diskId)
    71  	if err != nil {
    72  		log.Errorf("Get Disk %s error: %v", diskId, err)
    73  		return nil, err
    74  	}
    75  	storage, err := region.GetStorage(disk.PrimaryStorageUUID)
    76  	if err != nil {
    77  		log.Errorf("Get primary storage %s error: %v", disk.PrimaryStorageUUID, err)
    78  		return nil, err
    79  	}
    80  	switch storage.Type {
    81  	case StorageTypeLocal:
    82  		if len(disk.VMInstanceUUID) > 0 {
    83  			instance, err := region.GetInstance(disk.VMInstanceUUID)
    84  			if err != nil {
    85  				return nil, err
    86  			}
    87  			hostId := instance.LastHostUUID
    88  			if len(hostId) == 0 {
    89  				hostId = instance.HostUUID
    90  			}
    91  			disk.localStorage = &SLocalStorage{region: region, primaryStorageID: storage.UUID, HostUUID: hostId}
    92  			return disk, nil
    93  		}
    94  		tags, err := region.GetResourceSysTags("", "VolumeVO", disk.UUID, "")
    95  		if err != nil {
    96  			log.Errorf("get disk tag error: %v", err)
    97  			return nil, err
    98  		}
    99  		for i := 0; i < len(tags); i++ {
   100  			if strings.HasPrefix(tags[i].Tag, "localStorage::hostUuid::") {
   101  				hostInfo := strings.Split(tags[i].Tag, "localStorage::hostUuid::")
   102  				if len(hostInfo) == 2 {
   103  					localStorage, err := region.GetLocalStorage(storage.UUID, hostInfo[1])
   104  					if err != nil {
   105  						return nil, err
   106  					}
   107  					disk.localStorage = localStorage
   108  					return disk, nil
   109  				}
   110  				return nil, fmt.Errorf("invalid host info %s from disk %s", tags[i].Tag, disk.Name)
   111  			}
   112  		}
   113  		return nil, cloudprovider.ErrNotFound
   114  	case StorageTypeCeph:
   115  		disk.storage = storage
   116  		return disk, nil
   117  	default:
   118  		return nil, fmt.Errorf("Unsupport StorageType %s", storage.Type)
   119  	}
   120  }
   121  
   122  func (region *SRegion) GetDisks(storageId string, diskIds []string, diskType string) ([]SDisk, error) {
   123  	disks := []SDisk{}
   124  	params := url.Values{}
   125  	params.Add("q", "status!=Deleted")
   126  	params.Add("q", "status!=NotInstantiated")
   127  	if len(storageId) > 0 {
   128  		params.Add("q", "primaryStorageUuid="+storageId)
   129  	}
   130  	if len(diskIds) > 0 {
   131  		params.Add("q", "uuid?="+strings.Join(diskIds, ","))
   132  	}
   133  	if len(diskType) > 0 {
   134  		params.Add("q", "type="+diskType)
   135  	}
   136  	return disks, region.client.listAll("volumes", params, &disks)
   137  }
   138  
   139  func (disk *SDisk) GetSysTags() map[string]string {
   140  	data := map[string]string{}
   141  	data["hypervisor"] = api.HYPERVISOR_ZSTACK
   142  	return data
   143  }
   144  
   145  func (disk *SDisk) GetId() string {
   146  	return disk.UUID
   147  }
   148  
   149  func (disk *SDisk) Delete(ctx context.Context) error {
   150  	if disk.Status == "Deleted" {
   151  		return disk.region.ExpungeDisk(disk.UUID)
   152  	}
   153  	return disk.region.DeleteDisk(disk.UUID)
   154  }
   155  
   156  func (disk *SDisk) Resize(ctx context.Context, sizeMb int64) error {
   157  	return disk.region.ResizeDisk(disk.UUID, sizeMb)
   158  }
   159  
   160  func (disk *SDisk) GetName() string {
   161  	return disk.Name
   162  }
   163  
   164  func (disk *SDisk) GetGlobalId() string {
   165  	return disk.UUID
   166  }
   167  
   168  func (disk *SDisk) IsEmulated() bool {
   169  	return false
   170  }
   171  
   172  func (disk *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) {
   173  	if disk.localStorage != nil {
   174  		return disk.localStorage, nil
   175  	}
   176  	if disk.storage != nil {
   177  		return disk.storage, nil
   178  	}
   179  	return nil, cloudprovider.ErrNotFound
   180  }
   181  
   182  func (disk *SDisk) GetIStorageId() string {
   183  	storage, err := disk.region.GetStorage(disk.PrimaryStorageUUID)
   184  	if err != nil {
   185  		return disk.PrimaryStorageUUID
   186  	} else if storage.Type == StorageTypeLocal && len(disk.VMInstanceUUID) > 0 {
   187  		instnace, err := disk.region.GetInstance(disk.VMInstanceUUID)
   188  		if err != nil {
   189  			log.Warningf("failed to get instance %s for disk %s(%s) error: %v", disk.VMInstanceUUID, disk.Name, disk.UUID, err)
   190  			return ""
   191  		}
   192  		return fmt.Sprintf("%s/%s", disk.PrimaryStorageUUID, instnace.LastHostUUID)
   193  	}
   194  	return disk.PrimaryStorageUUID
   195  }
   196  
   197  func (disk *SDisk) GetStatus() string {
   198  	switch disk.Status {
   199  	case "Ready":
   200  		return api.DISK_READY
   201  	case "NotInstantiated":
   202  		//数据云盘特有的状态。在这个连接状态中,数据云盘只存在于数据库的表记录中。NotInstantiated状态的数据云盘可以挂载到任何类型虚拟机管理程序管理的云主机上;当挂载到云主机上后,数据云盘的hypervisorType域会存储云主机对应的虚拟机管理程序类型,在主存储上被实例化为虚拟机管理程序类型的实际二进制文件,同时连接状态会改为就绪(Ready);在这之后,这些数据云盘就只能被重新挂载到相同类型虚拟机管理程序管理的云主机上了。
   203  		return api.DISK_INIT
   204  	case "Creating":
   205  		return api.DISK_ALLOCATING
   206  	case "Deleted":
   207  		return api.DISK_DEALLOC
   208  	default:
   209  		log.Errorf("Unknown disk %s(%s) status %s", disk.Name, disk.UUID, disk.Status)
   210  		return api.DISK_UNKNOWN
   211  	}
   212  }
   213  
   214  func (disk *SDisk) Refresh() error {
   215  	new, err := disk.region.GetDisk(disk.UUID)
   216  	if err != nil {
   217  		return err
   218  	}
   219  	return jsonutils.Update(disk, new)
   220  }
   221  
   222  func (disk *SDisk) GetDiskFormat() string {
   223  	return disk.Format
   224  }
   225  
   226  func (disk *SDisk) GetDiskSizeMB() int {
   227  	return disk.Size / 1024 / 1024
   228  }
   229  
   230  func (disk *SDisk) GetIsAutoDelete() bool {
   231  	return disk.GetDiskType() == api.DISK_TYPE_SYS || disk.localStorage != nil
   232  }
   233  
   234  func (disk *SDisk) GetTemplateId() string {
   235  	return disk.RootImageUUID
   236  }
   237  
   238  func (disk *SDisk) GetDiskType() string {
   239  	switch disk.Type {
   240  	case "Root":
   241  		return api.DISK_TYPE_SYS
   242  	default:
   243  		return api.DISK_TYPE_DATA
   244  	}
   245  }
   246  
   247  func (disk *SDisk) GetFsFormat() string {
   248  	return ""
   249  }
   250  
   251  func (disk *SDisk) GetIsNonPersistent() bool {
   252  	return false
   253  }
   254  
   255  func (disk *SDisk) GetDriver() string {
   256  	return "scsi"
   257  }
   258  
   259  func (disk *SDisk) GetCacheMode() string {
   260  	return "none"
   261  }
   262  
   263  func (disk *SDisk) GetMountpoint() string {
   264  	return ""
   265  }
   266  
   267  func (region *SRegion) CreateDisk(name string, storageId string, hostId string, poolName string, sizeGb int, desc string) (*SDisk, error) {
   268  	offerings, err := region.GetDiskOfferings(sizeGb)
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  	diskOfferingUuid := ""
   273  	if len(offerings) > 0 {
   274  		diskOfferingUuid = offerings[0].UUID
   275  	} else {
   276  		offering, err := region.CreateDiskOffering(sizeGb)
   277  		if err != nil {
   278  			return nil, err
   279  		}
   280  		diskOfferingUuid = offering.UUID
   281  		defer region.DeleteDiskOffering(diskOfferingUuid)
   282  	}
   283  	params := map[string]interface{}{
   284  		"params": map[string]string{
   285  			"name":               name,
   286  			"description":        desc,
   287  			"diskOfferingUuid":   diskOfferingUuid,
   288  			"primaryStorageUuid": storageId,
   289  		},
   290  	}
   291  	if len(hostId) > 0 {
   292  		params["systemTags"] = []string{"localStorage::hostUuid::" + hostId}
   293  	}
   294  	if len(poolName) > 0 {
   295  		params["systemTags"] = []string{"ceph::pool::" + poolName}
   296  	}
   297  	resp, err := region.client.post("volumes/data", jsonutils.Marshal(params))
   298  	if err != nil {
   299  		return nil, err
   300  	}
   301  	disk := &SDisk{region: region}
   302  	return disk, resp.Unmarshal(disk, "inventory")
   303  }
   304  
   305  func (region *SRegion) ExpungeDisk(diskId string) error {
   306  	params := map[string]interface{}{
   307  		"expungeDataVolume": jsonutils.NewDict(),
   308  	}
   309  	_, err := region.client.put("volumes", diskId, jsonutils.Marshal(params))
   310  	return err
   311  }
   312  
   313  func (region *SRegion) DeleteDisk(diskId string) error {
   314  	err := region.client.delete("volumes", diskId, "Enforcing")
   315  	if err != nil {
   316  		return err
   317  	}
   318  	return region.ExpungeDisk(diskId)
   319  }
   320  
   321  func (region *SRegion) ResizeDisk(diskId string, sizeMb int64) error {
   322  	disk, err := region.GetDisk(diskId)
   323  	if err != nil {
   324  		return err
   325  	}
   326  
   327  	if disk.GetDiskSizeMB() == int(sizeMb) {
   328  		return nil
   329  	}
   330  
   331  	params := jsonutils.Marshal(map[string]interface{}{
   332  		fmt.Sprintf("resize%sVolume", disk.Type): map[string]int64{
   333  			"size": sizeMb * 1024 * 1024,
   334  		},
   335  	})
   336  	resource := "volumes/resize"
   337  	if disk.Type == "Data" {
   338  		resource = "volumes/data/resize"
   339  	}
   340  	_, err = region.client.put(resource, diskId, params)
   341  	return err
   342  }
   343  
   344  func (disk *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) {
   345  	return disk.region.CreateSnapshot(name, disk.UUID, desc)
   346  }
   347  
   348  func (disk *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) {
   349  	snapshots, err := disk.region.GetSnapshots(snapshotId, disk.UUID)
   350  	if err != nil {
   351  		return nil, err
   352  	}
   353  	if len(snapshots) == 1 {
   354  		if snapshots[0].UUID == snapshotId {
   355  			return &snapshots[0], nil
   356  		}
   357  		return nil, cloudprovider.ErrNotFound
   358  	}
   359  	if len(snapshots) > 1 {
   360  		return nil, cloudprovider.ErrDuplicateId
   361  	}
   362  	return nil, cloudprovider.ErrNotFound
   363  }
   364  
   365  func (disk *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
   366  	snapshots, err := disk.region.GetSnapshots("", disk.UUID)
   367  	if err != nil {
   368  		return nil, err
   369  	}
   370  	isnapshots := []cloudprovider.ICloudSnapshot{}
   371  	for i := 0; i < len(snapshots); i++ {
   372  		isnapshots = append(isnapshots, &snapshots[i])
   373  	}
   374  	return isnapshots, nil
   375  }
   376  
   377  func (disk *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) {
   378  	_, err := disk.region.ResetDisks(snapshotId)
   379  	return disk.UUID, err
   380  }
   381  
   382  func (region *SRegion) ResetDisks(snapshotId string) (jsonutils.JSONObject, error) {
   383  	params := map[string]interface{}{
   384  		"revertVolumeFromSnapshot": jsonutils.NewDict(),
   385  	}
   386  	return region.client.put("volume-snapshots", snapshotId, jsonutils.Marshal(params))
   387  }
   388  
   389  func (disk *SDisk) GetBillingType() string {
   390  	return ""
   391  }
   392  
   393  func (disk *SDisk) GetExpiredAt() time.Time {
   394  	return time.Time{}
   395  }
   396  
   397  func (disk *SDisk) GetCreatedAt() time.Time {
   398  	return disk.CreateDate
   399  }
   400  
   401  func (disk *SDisk) GetAccessPath() string {
   402  	return disk.InstallPath
   403  }
   404  
   405  func (disk *SDisk) Rebuild(ctx context.Context) error {
   406  	return disk.region.RebuildDisk(disk.UUID)
   407  }
   408  
   409  func (region *SRegion) RebuildDisk(diskId string) error {
   410  	params := map[string]interface{}{
   411  		"recoverDataVolume": jsonutils.NewDict(),
   412  	}
   413  	_, err := region.client.put("volumes", diskId, jsonutils.Marshal(params))
   414  	return err
   415  }
   416  
   417  func (disk *SDisk) GetProjectId() string {
   418  	return ""
   419  }