Files
cloudpods/pkg/multicloud/ctyun/disk.go
2020-03-27 19:30:06 +08:00

600 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ctyun
import (
"context"
"fmt"
"strconv"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/utils"
billing_api "yunion.io/x/onecloud/pkg/apis/billing"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/multicloud"
)
// http://ctyun-api-url/apiproxy/v3/ondemand/queryVolumes
type SDisk struct {
storage *SStorage
multicloud.SDisk
multicloud.SBillingBase
diskDetails *DiskDetails
ID string `json:"id"`
Status string `json:"status"`
Name string `json:"name"`
CreatedAt int64 `json:"created_at"`
UpdatedAt string `json:"updated_at"`
Multiattach bool `json:"multiattach"`
ReplicationStatus string `json:"replication_status"`
SizeGB int64 `json:"size"`
Metadata Metadata `json:"metadata"`
VolumeType string `json:"volume_type"`
UserID string `json:"user_id"`
Shareable bool `json:"shareable"`
Encrypted bool `json:"encrypted"`
Bootable string `json:"bootable"`
AvailabilityZone string `json:"availability_zone"`
Attachments []Attachment `json:"attachments"`
MasterOrderID string `json:"masterOrderId"`
WorkOrderResourceID string `json:"workOrderResourceId"`
ExpireTime int64 `json:"expireTime"`
IsFreeze int64 `json:"isFreeze"`
}
type Attachment struct {
VolumeID string `json:"volume_id"`
AttachmentID string `json:"attachment_id"`
AttachedAt string `json:"attached_at"`
ServerID string `json:"server_id"`
Device string `json:"device"`
ID string `json:"id"`
}
type Metadata struct {
OrderID string `json:"orderID"`
AttachedMode string `json:"attached_mode"`
ResourceSpecCode string `json:"resourceSpecCode"`
ProductID string `json:"productID"`
Readonly string `json:"readonly"`
}
type DiskDetails struct {
ID string `json:"id"`
ResEbsID string `json:"resEbsId"`
Size int64 `json:"size"`
Name string `json:"name"`
RegionID string `json:"regionId"`
AccountID string `json:"accountId"`
UserID string `json:"userId"`
HostID string `json:"hostId"`
OrderID string `json:"orderId"`
Status int64 `json:"status"`
Type string `json:"type"`
VolumeStatus int64 `json:"volumeStatus"`
CreateDate int64 `json:"createDate"`
DueDate int64 `json:"dueDate"`
ZoneID string `json:"zoneId"`
ZoneName string `json:"zoneName"`
IsSysVolume int64 `json:"isSysVolume"`
IsPackaged int64 `json:"isPackaged"`
WorkOrderResourceID string `json:"workOrderResourceId"`
IsFreeze int64 `json:"isFreeze"`
}
func (self *SDisk) GetBillingType() string {
if self.ExpireTime > 0 {
return billing_api.BILLING_TYPE_PREPAID
}
return billing_api.BILLING_TYPE_POSTPAID
}
func (self *SDisk) GetCreatedAt() time.Time {
return time.Unix(self.CreatedAt/1000, 0)
}
func (self *SDisk) GetExpiredAt() time.Time {
if self.ExpireTime == 0 {
return time.Time{}
}
return time.Unix(self.ExpireTime/1000, 0)
}
func (self *SDisk) GetId() string {
return self.ID
}
func (self *SDisk) GetName() string {
if len(self.Name) > 0 {
return self.Name
}
return self.ID
}
func (self *SDisk) GetGlobalId() string {
return self.GetId()
}
func (self *SDisk) GetStatus() string {
switch self.Status {
case "creating", "downloading":
return api.DISK_ALLOCATING
case "available", "in-use":
return api.DISK_READY
case "error":
return api.DISK_ALLOC_FAILED
case "attaching":
return api.DISK_ATTACHING
case "detaching":
return api.DISK_DETACHING
case "restoring-backup":
return api.DISK_REBUILD
case "backing-up":
return api.DISK_BACKUP_STARTALLOC
case "error_restoring":
return api.DISK_BACKUP_ALLOC_FAILED
case "uploading":
return api.DISK_SAVING
case "extending":
return api.DISK_RESIZING
case "error_extending":
return api.DISK_ALLOC_FAILED
case "deleting":
return api.DISK_DEALLOC
case "error_deleting":
return api.DISK_DEALLOC_FAILED
case "rollbacking":
return api.DISK_REBUILD
case "error_rollbacking":
return api.DISK_UNKNOWN
default:
return api.DISK_UNKNOWN
}
}
func (self *SDisk) Refresh() error {
new, err := self.storage.zone.region.GetDisk(self.GetId())
if err != nil {
return err
}
return jsonutils.Update(self, new)
}
func (self *SDisk) IsEmulated() bool {
return false
}
func (self *SDisk) GetMetadata() *jsonutils.JSONDict {
data := jsonutils.NewDict()
data.Add(jsonutils.NewString(api.HYPERVISOR_CTYUN), "hypervisor")
return data
}
func (self *SDisk) GetProjectId() string {
return ""
}
func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) {
return self.storage, nil
}
func (self *SDisk) GetIStorageId() string {
return self.storage.GetId()
}
func (self *SDisk) GetDiskFormat() string {
return "vhd"
}
func (self *SDisk) GetDiskSizeMB() int {
return int(self.SizeGB * 1024)
}
func (self *SDisk) GetIsAutoDelete() bool {
if len(self.Attachments) == 0 {
return false
}
if self.Bootable == "true" {
return true
}
return false
}
func (self *SDisk) GetTemplateId() string {
if len(self.Attachments) > 0 && len(self.Attachments[0].ServerID) > 0 {
server, err := self.storage.zone.region.GetVMById(self.Attachments[0].ServerID)
if err != nil {
log.Errorf("SDisk.GetTemplateId %s", err)
return ""
}
image, err := server.GetImage()
if err != nil {
log.Errorf("SDisk.GetImage %s", err)
return ""
}
return image.GetId()
}
return ""
}
func (self *SDisk) GetDiskType() string {
if self.Bootable == "true" {
return api.DISK_TYPE_SYS
} else {
return api.DISK_TYPE_DATA
}
}
func (self *SDisk) GetFsFormat() string {
return ""
}
func (self *SDisk) GetIsNonPersistent() bool {
return false
}
func (self *SDisk) GetDriver() string {
return "scsi"
}
func (self *SDisk) GetCacheMode() string {
return "none"
}
func (self *SDisk) GetMountpoint() string {
if len(self.Attachments) > 0 {
return self.Attachments[0].Device
}
return ""
}
func (self *SDisk) GetAccessPath() string {
return ""
}
func (self *SDisk) Delete(ctx context.Context) error {
_, err := self.storage.zone.region.DeleteDisk(self.GetId())
if err != nil {
return errors.Wrap(err, "SDisk.Delete")
}
return nil
}
func (self *SDisk) CreateISnapshot(ctx context.Context, name string, desc string) (cloudprovider.ICloudSnapshot, error) {
return nil, cloudprovider.ErrNotSupported
}
// POST http://ctyun-api-url/apiproxy/v3/ondemand/createVBS
func (self *SDisk) GetISnapshot(idStr string) (cloudprovider.ICloudSnapshot, error) {
return nil, cloudprovider.ErrNotFound
}
// no snapshot api opened
func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
return []cloudprovider.ICloudSnapshot{}, nil
}
// POST http://ctyun-api-url/apiproxy/v3/ondemand/updateDiskBackupPolicy
func (self *SDisk) GetExtSnapshotPolicyIds() ([]string, error) {
return []string{}, nil
}
func (self *SDisk) Resize(ctx context.Context, newSizeMB int64) error {
jobId, err := self.storage.zone.region.ResizeDisk(self.GetId(), strconv.Itoa(int(newSizeMB/1024)))
if err != nil {
return errors.Wrap(err, "Disk.Resize")
}
err = cloudprovider.Wait(10*time.Second, 1800*time.Second, func() (b bool, err error) {
statusJson, err := self.storage.zone.region.GetVolumeJob(jobId)
// ctyun 偶尔会报客户端错误其实job已经到后台执行了
if err != nil {
log.Debugf("Ctyun.SDisk.Resize.GetVolumeJob %s", err)
return false, nil
}
if status, _ := statusJson.GetString("status"); status == "SUCCESS" {
return true, nil
} else if status == "FAILED" {
return false, fmt.Errorf("Resize job %s failed", jobId)
} else {
return false, nil
}
})
if err != nil {
return errors.Wrap(err, "Disk.Resize.Wait")
}
return nil
}
func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) {
return "", cloudprovider.ErrNotSupported
}
func (self *SDisk) Rebuild(ctx context.Context) error {
return cloudprovider.ErrNotSupported
}
func (self *SDisk) GetDiskDetails() (*DiskDetails, error) {
if self.diskDetails != nil {
return self.diskDetails, nil
}
details, err := self.storage.zone.region.GetDiskDetailByDiskId(self.GetId())
if err != nil {
return nil, errors.Wrap(err, "SDisk.GetDiskDetails.GetDiskDetailByDiskId")
}
self.diskDetails = details
return self.diskDetails, nil
}
func (self *SRegion) GetDisks() ([]SDisk, error) {
params := map[string]string{
"regionId": self.GetId(),
}
resp, err := self.client.DoGet("/apiproxy/v3/ondemand/queryVolumes", params)
if err != nil {
return nil, errors.Wrap(err, "Region.GetDisks.DoGet")
}
disks := make([]SDisk, 0)
err = resp.Unmarshal(&disks, "returnObj", "volumes")
if err != nil {
return nil, errors.Wrap(err, "Region.GetDisks.Unmarshal")
}
for i := range disks {
izone, err := self.GetIZoneById(getZoneGlobalId(self, disks[i].AvailabilityZone))
if err != nil {
return nil, errors.Wrap(err, "SRegion.GetDisk.GetIZoneById")
}
disks[i].storage = &SStorage{
zone: izone.(*SZone),
storageType: disks[i].VolumeType,
}
}
return disks, nil
}
func (self *SRegion) GetDisk(diskId string) (*SDisk, error) {
params := map[string]string{
"regionId": self.GetId(),
"volumeId": diskId,
}
resp, err := self.client.DoGet("/apiproxy/v3/ondemand/queryVolumes", params)
if err != nil {
return nil, errors.Wrap(err, "Region.GetDisk.DoGet")
}
disks := make([]SDisk, 0)
err = resp.Unmarshal(&disks, "returnObj", "volumes")
if err != nil {
return nil, errors.Wrap(err, "Region.GetDisks.Unmarshal")
}
if len(disks) == 0 {
return nil, errors.Wrap(cloudprovider.ErrNotFound, "SRegion.GetDisk")
} else if len(disks) == 1 {
izone, err := self.GetIZoneById(getZoneGlobalId(self, disks[0].AvailabilityZone))
if err != nil {
return nil, errors.Wrap(err, "SRegion.GetDisk.GetIZoneById")
}
disks[0].storage = &SStorage{
zone: izone.(*SZone),
storageType: disks[0].VolumeType,
}
return &disks[0], nil
} else {
return nil, errors.Wrap(cloudprovider.ErrDuplicateId, "SRegion.GetDisk")
}
}
func (self *SRegion) GetDiskDetailByDiskId(diskId string) (*DiskDetails, error) {
params := map[string]string{
"volumeId": diskId,
"regionId": self.GetId(),
}
resp, err := self.client.DoGet("/apiproxy/v3/queryDataDiskDetail", params)
if err != nil {
return nil, errors.Wrap(err, "Region.GetDiskDetailByDiskId.DoGet")
}
disk := &DiskDetails{}
err = resp.Unmarshal(disk, "returnObj")
if err != nil {
return nil, errors.Wrap(err, "Region.GetDiskDetailByDiskId.Unmarshal")
}
return disk, nil
}
func (self *SRegion) CreateDisk(zoneId, name, diskType, size string) (*SDisk, error) {
diskParams := jsonutils.NewDict()
diskParams.Set("regionId", jsonutils.NewString(self.GetId()))
diskParams.Set("zoneId", jsonutils.NewString(zoneId))
diskParams.Set("name", jsonutils.NewString(name))
diskParams.Set("type", jsonutils.NewString(diskType))
diskParams.Set("size", jsonutils.NewString(size))
diskParams.Set("count", jsonutils.NewString("1"))
params := map[string]jsonutils.JSONObject{
"createVolumeInfo": diskParams,
}
disks, err := self.GetDisks()
if err != nil {
return nil, errors.Wrap(err, "SRegion.CreateDisk.GetDisks")
}
diskIds := []string{}
for i := range disks {
diskIds = append(diskIds, disks[i].GetId())
}
_, err = self.client.DoPost("/apiproxy/v3/ondemand/createVolume", params)
if err != nil {
return nil, errors.Wrap(err, "Region.CreateDisk.DoPost")
}
// 查询job结果一直报错目前先用替代办法查找新硬盘ID可能不准确。后续需要替换其它方法
diskId := ""
cloudprovider.Wait(3*time.Second, 300*time.Second, func() (b bool, err error) {
disks, err := self.GetDisks()
if err != nil {
return false, err
}
for i := range disks {
if !utils.IsInStringArray(disks[i].GetId(), diskIds) {
diskId = disks[i].GetId()
return true, nil
}
}
return false, nil
})
return self.GetDisk(diskId)
}
func (self *SRegion) CreateDiskBackup(name, volumeId, desc string) (string, error) {
params := map[string]jsonutils.JSONObject{
"regionId": jsonutils.NewString(self.GetId()),
"volumeId": jsonutils.NewString(volumeId),
"name": jsonutils.NewString(name),
"description": jsonutils.NewString(desc),
}
resp, err := self.client.DoPost("/apiproxy/v3/ondemand/createVBS", params)
if err != nil {
return "", errors.Wrap(err, "Region.CreateDiskBackup.DoPost")
}
var jobId string
err = resp.Unmarshal(&jobId, "returnObj", "data")
if err != nil {
return "", errors.Wrap(err, "Region.CreateDiskBackup.Unmarshal")
}
return jobId, nil
}
func (self *SRegion) DeleteDisk(volumeId string) (string, error) {
params := map[string]jsonutils.JSONObject{
"regionId": jsonutils.NewString(self.GetId()),
"volumeId": jsonutils.NewString(volumeId),
}
resp, err := self.client.DoPost("/apiproxy/v3/ondemand/deleteVolume", params)
if err != nil {
msg, _ := resp.GetString("message")
return "", errors.Wrap(fmt.Errorf(msg), "SRegion.DeleteDisk.DoPost")
}
var jobId string
err = resp.Unmarshal(&jobId, "returnObj", "data")
if err != nil {
return "", errors.Wrap(err, "SRegion.DeleteDisk.Unmarshal")
}
return jobId, nil
}
func (self *SRegion) ResizeDisk(volumeId string, newSizeGB string) (string, error) {
params := map[string]jsonutils.JSONObject{
"regionId": jsonutils.NewString(self.GetId()),
"volumeId": jsonutils.NewString(volumeId),
"newSize": jsonutils.NewString(newSizeGB),
}
resp, err := self.client.DoPost("/apiproxy/v3/ondemand/expandVolumeSize", params)
if err != nil {
return "", errors.Wrap(err, "SRegion.ResizeDisk.DoPost")
}
var ok bool
err = resp.Unmarshal(&ok, "returnObj", "status")
if !ok {
msg, _ := resp.GetString("message")
return "", errors.Wrap(fmt.Errorf(msg), "SRegion.ResizeDisk.JobFailed")
}
var jobId string
err = resp.Unmarshal(&jobId, "returnObj", "data")
if err != nil {
return "", errors.Wrap(err, "SRegion.ResizeDisk.Unmarshal")
}
return jobId, nil
}
func (self *SRegion) RestoreDisk(volumeId, backupId string) (string, error) {
diskBackupParams := jsonutils.NewDict()
diskBackupParams.Set("regionId", jsonutils.NewString(self.GetId()))
diskBackupParams.Set("backupId", jsonutils.NewString(backupId))
diskBackupParams.Set("volumeId", jsonutils.NewString(volumeId))
params := map[string]jsonutils.JSONObject{
"diskBackup": diskBackupParams,
}
resp, err := self.client.DoPost("/apiproxy/v3/restoreDiskBackup", params)
if err != nil {
return "", errors.Wrap(err, "SRegion.RestoreDisk.DoPost")
}
var ok bool
err = resp.Unmarshal(&ok, "returnObj", "status")
if !ok {
msg, _ := resp.GetString("message")
return "", errors.Wrap(fmt.Errorf(msg), "SRegion.RestoreDisk.JobFailed")
}
var jobId string
err = resp.Unmarshal(&jobId, "returnObj", "data")
if err != nil {
return "", errors.Wrap(err, "SRegion.RestoreDisk.Unmarshal")
}
return jobId, nil
}