mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-11-04 23:13:43 +08:00
600 lines
16 KiB
Go
600 lines
16 KiB
Go
// 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
|
|
}
|