Add files

This commit is contained in:
stSolo
2022-07-20 17:14:00 +03:00
parent 2b7f3d45f3
commit 28ceebecf8
125 changed files with 15225 additions and 735 deletions

View File

@@ -31,9 +31,11 @@ Documentation: https://github.com/rudecs/terraform-provider-decort/wiki
package disks
const DisksCreateAPI = "/restmachine/cloudapi/disks/create"
const DisksGetAPI = "/restmachine/cloudapi/disks/get" // Returns single DiskRecord on success
const DisksListAPI = "/restmachine/cloudapi/disks/list" // Returns list of DiskRecord on success
const DisksResizeAPI = "/restmachine/cloudapi/disks/resize2"
const DisksRenameAPI = "/restmachine/cloudapi/disks/rename"
const DisksDeleteAPI = "/restmachine/cloudapi/disks/delete"
const disksCreateAPI = "/restmachine/cloudapi/disks/create"
const disksGetAPI = "/restmachine/cloudapi/disks/get"
const disksListAPI = "/restmachine/cloudapi/disks/list"
const disksResizeAPI = "/restmachine/cloudapi/disks/resize2"
const disksRenameAPI = "/restmachine/cloudapi/disks/rename"
const disksDeleteAPI = "/restmachine/cloudapi/disks/delete"
const disksIOLimitAPI = "/restmachine/cloudapi/disks/limitIO"
const disksRestoreAPI = "/restmachine/cloudapi/disks/restore"

View File

@@ -34,162 +34,340 @@ package disks
import (
"context"
"encoding/json"
"fmt"
// "net/url"
"github.com/google/uuid"
"github.com/rudecs/terraform-provider-decort/internal/constants"
log "github.com/sirupsen/logrus"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func flattenDisk(d *schema.ResourceData, disk_facts string) error {
// This function expects disk_facts string to contain a response from disks/get API
//
// NOTE: this function modifies ResourceData argument - as such it should never be called
// from resourceDiskExists(...) method. Use utilityDiskCheckPresence instead.
log.Debugf("flattenDisk: ready to unmarshal string %s", disk_facts)
model := DiskRecord{}
err := json.Unmarshal([]byte(disk_facts), &model)
func dataSourceDiskRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
disk, err := utilityDiskCheckPresence(ctx, d, m)
if err != nil {
return err
return diag.FromErr(err)
}
log.Debugf("flattenDisk: disk ID %d, disk AccountID %d", model.ID, model.AccountID)
id := uuid.New()
d.SetId(id.String())
d.SetId(fmt.Sprintf("%d", model.ID))
// d.Set("disk_id", model.ID) - we should NOT update disk_id in the schema. If it was set - it is already set, if it wasn't - we shouldn't
d.Set("name", model.Name)
d.Set("account_id", model.AccountID)
d.Set("account_name", model.AccountName)
d.Set("size", model.SizeMax)
// d.Set("sizeUsed", model.SizeUsed)
d.Set("type", model.Type)
d.Set("image_id", model.ImageID)
d.Set("sep_id", model.SepID)
d.Set("sep_type", model.SepType)
d.Set("pool", model.Pool)
// d.Set("compute_id", model.ComputeID)
diskAcl, _ := json.Marshal(disk.Acl)
d.Set("description", model.Desc)
d.Set("account_id", disk.AccountID)
d.Set("account_name", disk.AccountName)
d.Set("acl", string(diskAcl))
d.Set("boot_partition", disk.BootPartition)
d.Set("compute_id", disk.ComputeID)
d.Set("compute_name", disk.ComputeName)
d.Set("created_time", disk.CreatedTime)
d.Set("deleted_time", disk.DeletedTime)
d.Set("desc", disk.Desc)
d.Set("destruction_time", disk.DestructionTime)
d.Set("devicename", disk.DeviceName)
d.Set("disk_path", disk.DiskPath)
d.Set("gid", disk.GridID)
d.Set("guid", disk.GUID)
d.Set("disk_id", disk.ID)
d.Set("image_id", disk.ImageID)
d.Set("images", disk.Images)
d.Set("iotune", flattenIOTune(disk.IOTune))
d.Set("iqn", disk.IQN)
d.Set("login", disk.Login)
d.Set("milestones", disk.Milestones)
d.Set("disk_name", disk.Name)
d.Set("order", disk.Order)
d.Set("params", disk.Params)
d.Set("parent_id", disk.ParentId)
d.Set("passwd", disk.Passwd)
d.Set("pci_slot", disk.PciSlot)
d.Set("pool", disk.Pool)
d.Set("purge_attempts", disk.PurgeAttempts)
d.Set("purge_time", disk.PurgeTime)
d.Set("reality_device_number", disk.RealityDeviceNumber)
d.Set("reference_id", disk.ReferenceId)
d.Set("res_id", disk.ResID)
d.Set("res_name", disk.ResName)
d.Set("role", disk.Role)
d.Set("sep_id", disk.SepID)
d.Set("sep_type", disk.SepType)
d.Set("size_max", disk.SizeMax)
d.Set("size_used", disk.SizeUsed)
d.Set("snapshots", flattendDiskSnapshotList(disk.Snapshots))
d.Set("status", disk.Status)
d.Set("tech_status", disk.TechStatus)
d.Set("type", disk.Type)
d.Set("vmid", disk.VMID)
return nil
}
func dataSourceDiskRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
disk_facts, err := utilityDiskCheckPresence(ctx, d, m)
if disk_facts == "" {
// if empty string is returned from utilityDiskCheckPresence then there is no
// such Disk and err tells so - just return it to the calling party
d.SetId("") // ensure ID is empty
return diag.FromErr(err)
}
return diag.FromErr(flattenDisk(d, disk_facts))
}
func dataSourceDiskSchemaMake() map[string]*schema.Schema {
rets := map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Optional: true,
Description: "Name of this disk. NOTE: disk names are NOT unique within an account. If disk ID is specified, disk name is ignored.",
},
"disk_id": {
Type: schema.TypeInt,
Optional: true,
Description: "ID of the disk to get. If disk ID is specified, then disk name and account ID are ignored.",
Type: schema.TypeInt,
Required: true,
},
"account_id": {
Type: schema.TypeInt,
Optional: true,
Description: "ID of the account this disk belongs to. If disk ID is specified, then account ID is ignored.",
Type: schema.TypeInt,
Computed: true,
},
// The rest of the data source Disk schema are all computed
"sep_id": {
Type: schema.TypeInt,
Computed: true,
Description: "Storage end-point provider serving this disk.",
},
"pool": {
Type: schema.TypeString,
Computed: true,
Description: "Pool where this disk is located.",
},
"size": {
Type: schema.TypeInt,
Computed: true,
Description: "Size of the disk in GB.",
},
"type": {
Type: schema.TypeString,
Computed: true,
Description: "Type of this disk. E.g. D for data disks, B for boot.",
},
"description": {
Type: schema.TypeString,
Computed: true,
Description: "User-defined text description of this disk.",
},
"account_name": {
Type: schema.TypeString,
Computed: true,
Description: "Name of the account this disk belongs to. If account ID is specified, account name is ignored.",
Type: schema.TypeString,
Computed: true,
},
"acl": {
Type: schema.TypeString,
Computed: true,
},
"boot_partition": {
Type: schema.TypeInt,
Computed: true,
},
"compute_id": {
Type: schema.TypeInt,
Computed: true,
},
"compute_name": {
Type: schema.TypeString,
Computed: true,
},
"created_time": {
Type: schema.TypeInt,
Computed: true,
},
"deleted_time": {
Type: schema.TypeInt,
Computed: true,
},
"desc": {
Type: schema.TypeString,
Computed: true,
},
"destruction_time": {
Type: schema.TypeInt,
Computed: true,
},
"devicename": {
Type: schema.TypeString,
Computed: true,
},
"disk_path": {
Type: schema.TypeString,
Computed: true,
},
"gid": {
Type: schema.TypeInt,
Computed: true,
},
"guid": {
Type: schema.TypeInt,
Computed: true,
},
"image_id": {
Type: schema.TypeInt,
Computed: true,
Description: "ID of the image, which this disk was cloned from (valid for disk clones only).",
Type: schema.TypeInt,
Computed: true,
},
"sep_type": {
Type: schema.TypeString,
Computed: true,
Description: "Type of the storage end-point provider serving this disk.",
"images": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
/*
"snapshots": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource {
Schema: snapshotSubresourceSchemaMake(),
"iotune": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"read_bytes_sec": {
Type: schema.TypeInt,
Computed: true,
},
"read_bytes_sec_max": {
Type: schema.TypeInt,
Computed: true,
},
"read_iops_sec": {
Type: schema.TypeInt,
Computed: true,
},
"read_iops_sec_max": {
Type: schema.TypeInt,
Computed: true,
},
"size_iops_sec": {
Type: schema.TypeInt,
Computed: true,
},
"total_bytes_sec": {
Type: schema.TypeInt,
Computed: true,
},
"total_bytes_sec_max": {
Type: schema.TypeInt,
Computed: true,
},
"total_iops_sec": {
Type: schema.TypeInt,
Computed: true,
},
"total_iops_sec_max": {
Type: schema.TypeInt,
Computed: true,
},
"write_bytes_sec": {
Type: schema.TypeInt,
Computed: true,
},
"write_bytes_sec_max": {
Type: schema.TypeInt,
Computed: true,
},
"write_iops_sec": {
Type: schema.TypeInt,
Computed: true,
},
"write_iops_sec_max": {
Type: schema.TypeInt,
Computed: true,
},
Description: "List of user-created snapshots for this disk."
},
"status": {
Type: schema.TypeString,
Computed: true,
Description: "Current model status of this disk.",
},
"tech_status": {
Type: schema.TypeString,
Computed: true,
Description: "Current technical status of this disk.",
},
"iqn": {
Type: schema.TypeString,
Computed: true,
},
"login": {
Type: schema.TypeString,
Computed: true,
},
"milestones": {
Type: schema.TypeInt,
Computed: true,
},
"disk_name": {
Type: schema.TypeString,
Computed: true,
},
"order": {
Type: schema.TypeInt,
Computed: true,
},
"params": {
Type: schema.TypeString,
Computed: true,
},
"parent_id": {
Type: schema.TypeInt,
Computed: true,
},
"passwd": {
Type: schema.TypeString,
Computed: true,
},
"pci_slot": {
Type: schema.TypeInt,
Computed: true,
},
"pool": {
Type: schema.TypeString,
Computed: true,
},
"purge_attempts": {
Type: schema.TypeInt,
Computed: true,
},
"purge_time": {
Type: schema.TypeInt,
Computed: true,
},
"reality_device_number": {
Type: schema.TypeInt,
Computed: true,
},
"reference_id": {
Type: schema.TypeString,
Computed: true,
},
"res_id": {
Type: schema.TypeString,
Computed: true,
},
"res_name": {
Type: schema.TypeString,
Computed: true,
},
"role": {
Type: schema.TypeString,
Computed: true,
},
"sep_id": {
Type: schema.TypeInt,
Computed: true,
},
"sep_type": {
Type: schema.TypeString,
Computed: true,
},
"size_max": {
Type: schema.TypeInt,
Computed: true,
},
"size_used": {
Type: schema.TypeInt,
Computed: true,
},
"snapshots": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"guid": {
Type: schema.TypeString,
Computed: true,
},
"label": {
Type: schema.TypeString,
Computed: true,
},
"res_id": {
Type: schema.TypeString,
Computed: true,
},
"snap_set_guid": {
Type: schema.TypeString,
Computed: true,
},
"snap_set_time": {
Type: schema.TypeInt,
Computed: true,
},
"timestamp": {
Type: schema.TypeInt,
Computed: true,
},
},
},
"compute_id": {
Type: schema.TypeInt,
Computed: true,
Description: "ID of the compute instance where this disk is attached to, or 0 for unattached disk.",
},
*/
},
"status": {
Type: schema.TypeString,
Computed: true,
},
"tech_status": {
Type: schema.TypeString,
Computed: true,
},
"type": {
Type: schema.TypeString,
Computed: true,
},
"vmid": {
Type: schema.TypeInt,
Computed: true,
},
}
return rets

View File

@@ -41,11 +41,32 @@ import (
"github.com/rudecs/terraform-provider-decort/internal/constants"
)
func flattenDiskList(dl DisksListResp) []map[string]interface{} {
func flattenIOTune(iot IOTune) []map[string]interface{} {
res := make([]map[string]interface{}, 0)
temp := map[string]interface{}{
"read_bytes_sec": iot.ReadBytesSec,
"read_bytes_sec_max": iot.ReadBytesSecMax,
"read_iops_sec": iot.ReadIopsSec,
"read_iops_sec_max": iot.ReadIopsSecMax,
"size_iops_sec": iot.SizeIopsSec,
"total_bytes_sec": iot.TotalBytesSec,
"total_bytes_sec_max": iot.TotalBytesSecMax,
"total_iops_sec": iot.TotalIopsSec,
"total_iops_sec_max": iot.TotalIopsSecMax,
"write_bytes_sec": iot.WriteBytesSec,
"write_bytes_sec_max": iot.WriteBytesSecMax,
"write_iops_sec": iot.WriteIopsSec,
"write_iops_sec_max": iot.WriteIopsSecMax,
}
res = append(res, temp)
return res
}
func flattenDiskList(dl DisksList) []map[string]interface{} {
res := make([]map[string]interface{}, 0)
for _, disk := range dl {
diskAcl, _ := json.Marshal(disk.Acl)
diskIotune, _ := json.Marshal(disk.IOTune)
temp := map[string]interface{}{
"account_id": disk.AccountID,
"account_name": disk.AccountName,
@@ -64,13 +85,13 @@ func flattenDiskList(dl DisksListResp) []map[string]interface{} {
"disk_id": disk.ID,
"image_id": disk.ImageID,
"images": disk.Images,
"iotune": string(diskIotune),
"iotune": flattenIOTune(disk.IOTune),
"iqn": disk.IQN,
"login": disk.Login,
"machine_id": disk.MachineId,
"machine_name": disk.MachineName,
"milestones": disk.Milestones,
"name": disk.Name,
"disk_name": disk.Name,
"order": disk.Order,
"params": disk.Params,
"parent_id": disk.ParentId,
@@ -93,7 +114,6 @@ func flattenDiskList(dl DisksListResp) []map[string]interface{} {
"tech_status": disk.TechStatus,
"type": disk.Type,
"vmid": disk.VMID,
"update_by": disk.UpdateBy,
}
res = append(res, temp)
}
@@ -101,7 +121,7 @@ func flattenDiskList(dl DisksListResp) []map[string]interface{} {
}
func flattendDiskSnapshotList(sl SnapshotRecordList) []interface{} {
func flattendDiskSnapshotList(sl SnapshotList) []interface{} {
res := make([]interface{}, 0)
for _, snapshot := range sl {
temp := map[string]interface{}{
@@ -231,8 +251,64 @@ func dataSourceDiskListSchemaMake() map[string]*schema.Schema {
},
},
"iotune": {
Type: schema.TypeString,
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"read_bytes_sec": {
Type: schema.TypeInt,
Computed: true,
},
"read_bytes_sec_max": {
Type: schema.TypeInt,
Computed: true,
},
"read_iops_sec": {
Type: schema.TypeInt,
Computed: true,
},
"read_iops_sec_max": {
Type: schema.TypeInt,
Computed: true,
},
"size_iops_sec": {
Type: schema.TypeInt,
Computed: true,
},
"total_bytes_sec": {
Type: schema.TypeInt,
Computed: true,
},
"total_bytes_sec_max": {
Type: schema.TypeInt,
Computed: true,
},
"total_iops_sec": {
Type: schema.TypeInt,
Computed: true,
},
"total_iops_sec_max": {
Type: schema.TypeInt,
Computed: true,
},
"write_bytes_sec": {
Type: schema.TypeInt,
Computed: true,
},
"write_bytes_sec_max": {
Type: schema.TypeInt,
Computed: true,
},
"write_iops_sec": {
Type: schema.TypeInt,
Computed: true,
},
"write_iops_sec_max": {
Type: schema.TypeInt,
Computed: true,
},
},
},
},
"iqn": {
Type: schema.TypeString,
@@ -254,7 +330,7 @@ func dataSourceDiskListSchemaMake() map[string]*schema.Schema {
Type: schema.TypeInt,
Computed: true,
},
"name": {
"disk_name": {
Type: schema.TypeString,
Computed: true,
},
@@ -374,10 +450,6 @@ func dataSourceDiskListSchemaMake() map[string]*schema.Schema {
Type: schema.TypeInt,
Computed: true,
},
"update_by": {
Type: schema.TypeInt,
Computed: true,
},
},
},
},

View File

@@ -31,7 +31,7 @@ Documentation: https://github.com/rudecs/terraform-provider-decort/wiki
package disks
type DiskRecord struct {
type Disk struct {
Acl map[string]interface{} `json:"acl"`
AccountID int `json:"accountId"`
AccountName string `json:"accountName"`
@@ -49,7 +49,7 @@ type DiskRecord struct {
ID uint `json:"id"`
ImageID int `json:"imageId"`
Images []int `json:"images"`
IOTune map[string]interface{} `json:"iotune"`
IOTune IOTune `json:"iotune"`
IQN string `json:"iqn"`
Login string `json:"login"`
Name string `json:"name"`
@@ -73,7 +73,7 @@ type DiskRecord struct {
SepID int `json:"sepId"` // NOTE: absent from compute/get output
SizeMax int `json:"sizeMax"`
SizeUsed int `json:"sizeUsed"` // sum over all snapshots of this disk to report total consumed space
Snapshots []SnapshotRecord `json:"snapshots"`
Snapshots []Snapshot `json:"snapshots"`
Status string `json:"status"`
TechStatus string `json:"techStatus"`
Type string `json:"type"`
@@ -81,7 +81,7 @@ type DiskRecord struct {
VMID int `json:"vmid"`
}
type SnapshotRecord struct {
type Snapshot struct {
Guid string `json:"guid"`
Label string `json:"label"`
ResId string `json:"resId"`
@@ -90,6 +90,22 @@ type SnapshotRecord struct {
TimeStamp uint64 `json:"timestamp"`
}
type SnapshotRecordList []SnapshotRecord
type SnapshotList []Snapshot
type DisksListResp []DiskRecord
type DisksList []Disk
type IOTune struct {
ReadBytesSec int `json:"read_bytes_sec"`
ReadBytesSecMax int `json:"read_bytes_sec_max"`
ReadIopsSec int `json:"read_iops_sec"`
ReadIopsSecMax int `json:"read_iops_sec_max"`
SizeIopsSec int `json:"size_iops_sec"`
TotalBytesSec int `json:"total_bytes_sec"`
TotalBytesSecMax int `json:"total_bytes_sec_max"`
TotalIopsSec int `json:"total_iops_sec"`
TotalIopsSecMax int `json:"total_iops_sec_max"`
WriteBytesSec int `json:"write_bytes_sec"`
WriteBytesSecMax int `json:"write_bytes_sec_max"`
WriteIopsSec int `json:"write_iops_sec"`
WriteIopsSecMax int `json:"write_iops_sec_max"`
}

View File

@@ -32,15 +32,15 @@ Documentation: https://github.com/rudecs/terraform-provider-decort/wiki
package disks
import (
// "encoding/json"
"context"
"encoding/json"
"fmt"
"net/url"
"strconv"
"strings"
"github.com/rudecs/terraform-provider-decort/internal/constants"
"github.com/rudecs/terraform-provider-decort/internal/controller"
"github.com/rudecs/terraform-provider-decort/internal/location"
log "github.com/sirupsen/logrus"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
@@ -49,16 +49,18 @@ import (
)
func resourceDiskCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
log.Debugf("resourceDiskCreate: called for Disk name %q, Account ID %d", d.Get("name").(string), d.Get("account_id").(int))
c := m.(*controller.ControllerCfg)
urlValues := &url.Values{}
// accountId, gid, name, description, size, type, sep_id, pool
urlValues.Add("accountId", fmt.Sprintf("%d", d.Get("account_id").(int)))
urlValues.Add("gid", fmt.Sprintf("%d", location.DefaultGridID)) // we use default Grid ID, which was obtained along with DECORT Controller init
urlValues.Add("name", d.Get("name").(string))
urlValues.Add("size", fmt.Sprintf("%d", d.Get("size").(int)))
urlValues.Add("type", "D") // NOTE: only disks of Data type are managed via plugin
urlValues.Add("gid", fmt.Sprintf("%d", d.Get("gid").(int)))
urlValues.Add("name", d.Get("disk_name").(string))
urlValues.Add("size", fmt.Sprintf("%d", d.Get("size_max").(int)))
if typeRaw, ok := d.GetOk("type"); ok {
urlValues.Add("type", strings.ToUpper(typeRaw.(string)))
} else {
urlValues.Add("type", "D")
}
if sepId, ok := d.GetOk("sep_id"); ok {
urlValues.Add("sep_id", strconv.Itoa(sepId.(int)))
@@ -68,126 +70,210 @@ func resourceDiskCreate(ctx context.Context, d *schema.ResourceData, m interface
urlValues.Add("pool", poolName.(string))
}
argVal, argSet := d.GetOk("description")
argVal, argSet := d.GetOk("desc")
if argSet {
urlValues.Add("description", argVal.(string))
}
apiResp, err := c.DecortAPICall(ctx, "POST", DisksCreateAPI, urlValues)
diskId, err := c.DecortAPICall(ctx, "POST", disksCreateAPI, urlValues)
if err != nil {
return diag.FromErr(err)
}
d.SetId(apiResp) // update ID of the resource to tell Terraform that the disk resource exists
diskId, _ := strconv.Atoi(apiResp)
urlValues = &url.Values{}
log.Debugf("resourceDiskCreate: new Disk ID / name %d / %s creation sequence complete", diskId, d.Get("name").(string))
d.SetId(diskId) // update ID of the resource to tell Terraform that the disk resource exists
// We may reuse dataSourceDiskRead here as we maintain similarity
// between Disk resource and Disk data source schemas
// Disk resource read function will also update resource ID on success, so that Terraform
// will know the resource exists (however, we already did it a few lines before)
return dataSourceDiskRead(ctx, d, m)
if iotuneRaw, ok := d.GetOk("iotune"); ok {
iot := iotuneRaw.([]interface{})[0]
iotune := iot.(map[string]interface{})
urlValues.Add("diskId", diskId)
urlValues.Add("iops", strconv.Itoa(iotune["total_iops_sec"].(int)))
urlValues.Add("read_bytes_sec", strconv.Itoa(iotune["read_bytes_sec"].(int)))
urlValues.Add("read_bytes_sec_max", strconv.Itoa(iotune["read_bytes_sec_max"].(int)))
urlValues.Add("read_iops_sec", strconv.Itoa(iotune["read_iops_sec"].(int)))
urlValues.Add("read_iops_sec_max", strconv.Itoa(iotune["read_iops_sec_max"].(int)))
urlValues.Add("size_iops_sec", strconv.Itoa(iotune["size_iops_sec"].(int)))
urlValues.Add("total_bytes_sec", strconv.Itoa(iotune["total_bytes_sec"].(int)))
urlValues.Add("total_bytes_sec_max", strconv.Itoa(iotune["total_bytes_sec_max"].(int)))
urlValues.Add("total_iops_sec_max", strconv.Itoa(iotune["total_iops_sec_max"].(int)))
urlValues.Add("write_bytes_sec", strconv.Itoa(iotune["write_bytes_sec"].(int)))
urlValues.Add("write_bytes_sec_max", strconv.Itoa(iotune["write_bytes_sec_max"].(int)))
urlValues.Add("write_iops_sec", strconv.Itoa(iotune["write_iops_sec"].(int)))
urlValues.Add("write_iops_sec_max", strconv.Itoa(iotune["write_iops_sec_max"].(int)))
_, err := c.DecortAPICall(ctx, "POST", disksIOLimitAPI, urlValues)
if err != nil {
return diag.FromErr(err)
}
urlValues = &url.Values{}
}
dgn := resourceDiskRead(ctx, d, m)
if dgn != nil {
return dgn
}
return nil
}
func resourceDiskRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
diskFacts, err := utilityDiskCheckPresence(ctx, d, m)
if diskFacts == "" {
// if empty string is returned from utilityDiskCheckPresence then there is no
// such Disk and err tells so - just return it to the calling party
d.SetId("") // ensure ID is empty
return diag.FromErr(err)
disk, err := utilityDiskCheckPresence(ctx, d, m)
if disk == nil {
d.SetId("")
if err != nil {
return diag.FromErr(err)
}
return nil
}
return diag.FromErr(flattenDisk(d, diskFacts))
diskAcl, _ := json.Marshal(disk.Acl)
d.Set("account_id", disk.AccountID)
d.Set("account_name", disk.AccountName)
d.Set("acl", string(diskAcl))
d.Set("boot_partition", disk.BootPartition)
d.Set("compute_id", disk.ComputeID)
d.Set("compute_name", disk.ComputeName)
d.Set("created_time", disk.CreatedTime)
d.Set("deleted_time", disk.DeletedTime)
d.Set("desc", disk.Desc)
d.Set("destruction_time", disk.DestructionTime)
d.Set("devicename", disk.DeviceName)
d.Set("disk_path", disk.DiskPath)
d.Set("gid", disk.GridID)
d.Set("guid", disk.GUID)
d.Set("disk_id", disk.ID)
d.Set("image_id", disk.ImageID)
d.Set("images", disk.Images)
d.Set("iotune", flattenIOTune(disk.IOTune))
d.Set("iqn", disk.IQN)
d.Set("login", disk.Login)
d.Set("milestones", disk.Milestones)
d.Set("disk_name", disk.Name)
d.Set("order", disk.Order)
d.Set("params", disk.Params)
d.Set("parent_id", disk.ParentId)
d.Set("passwd", disk.Passwd)
d.Set("pci_slot", disk.PciSlot)
d.Set("pool", disk.Pool)
d.Set("purge_attempts", disk.PurgeAttempts)
d.Set("purge_time", disk.PurgeTime)
d.Set("reality_device_number", disk.RealityDeviceNumber)
d.Set("reference_id", disk.ReferenceId)
d.Set("res_id", disk.ResID)
d.Set("res_name", disk.ResName)
d.Set("role", disk.Role)
d.Set("sep_id", disk.SepID)
d.Set("sep_type", disk.SepType)
d.Set("size_max", disk.SizeMax)
d.Set("size_used", disk.SizeUsed)
d.Set("snapshots", flattendDiskSnapshotList(disk.Snapshots))
d.Set("status", disk.Status)
d.Set("tech_status", disk.TechStatus)
d.Set("type", disk.Type)
d.Set("vmid", disk.VMID)
return nil
}
func resourceDiskUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
// Update will only change the following attributes of the disk:
// - Size; to keep data safe, shrinking disk is not allowed.
// - Name
//
// Attempt to change disk type will throw an error and mark disk
// resource as partially updated
log.Debugf("resourceDiskUpdate: called for Disk ID / name %s / %s, Account ID %d",
d.Id(), d.Get("name").(string), d.Get("account_id").(int))
c := m.(*controller.ControllerCfg)
urlValues := &url.Values{}
oldSize, newSize := d.GetChange("size")
if oldSize.(int) < newSize.(int) {
log.Debugf("resourceDiskUpdate: resizing disk ID %s - %d GB -> %d GB",
d.Id(), oldSize.(int), newSize.(int))
sizeParams := &url.Values{}
sizeParams.Add("diskId", d.Id())
sizeParams.Add("size", fmt.Sprintf("%d", newSize.(int)))
_, err := c.DecortAPICall(ctx, "POST", DisksResizeAPI, sizeParams)
if d.HasChange("size_max") {
oldSize, newSize := d.GetChange("size_max")
if oldSize.(int) < newSize.(int) {
log.Debugf("resourceDiskUpdate: resizing disk ID %s - %d GB -> %d GB",
d.Id(), oldSize.(int), newSize.(int))
urlValues.Add("diskId", d.Id())
urlValues.Add("size", fmt.Sprintf("%d", newSize.(int)))
_, err := c.DecortAPICall(ctx, "POST", disksResizeAPI, urlValues)
if err != nil {
return diag.FromErr(err)
}
d.Set("size_max", newSize)
} else if oldSize.(int) > newSize.(int) {
return diag.FromErr(fmt.Errorf("resourceDiskUpdate: Disk ID %s - reducing disk size is not allowed", d.Id()))
}
urlValues = &url.Values{}
}
if d.HasChange("disk_name") {
urlValues.Add("diskId", d.Id())
urlValues.Add("name", d.Get("disk_name").(string))
_, err := c.DecortAPICall(ctx, "POST", disksRenameAPI, urlValues)
if err != nil {
return diag.FromErr(err)
}
d.Set("size", newSize)
} else if oldSize.(int) > newSize.(int) {
return diag.FromErr(fmt.Errorf("resourceDiskUpdate: Disk ID %s - reducing disk size is not allowed", d.Id()))
urlValues = &url.Values{}
}
oldName, newName := d.GetChange("name")
if oldName.(string) != newName.(string) {
log.Debugf("resourceDiskUpdate: renaming disk ID %d - %s -> %s",
d.Get("disk_id").(int), oldName.(string), newName.(string))
renameParams := &url.Values{}
renameParams.Add("diskId", d.Id())
renameParams.Add("name", newName.(string))
_, err := c.DecortAPICall(ctx, "POST", DisksRenameAPI, renameParams)
if d.HasChange("iotune") {
iot := d.Get("iotune").([]interface{})[0]
iotune := iot.(map[string]interface{})
urlValues.Add("diskId", d.Id())
urlValues.Add("iops", strconv.Itoa(iotune["total_iops_sec"].(int)))
urlValues.Add("read_bytes_sec", strconv.Itoa(iotune["read_bytes_sec"].(int)))
urlValues.Add("read_bytes_sec_max", strconv.Itoa(iotune["read_bytes_sec_max"].(int)))
urlValues.Add("read_iops_sec", strconv.Itoa(iotune["read_iops_sec"].(int)))
urlValues.Add("read_iops_sec_max", strconv.Itoa(iotune["read_iops_sec_max"].(int)))
urlValues.Add("size_iops_sec", strconv.Itoa(iotune["size_iops_sec"].(int)))
urlValues.Add("total_bytes_sec", strconv.Itoa(iotune["total_bytes_sec"].(int)))
urlValues.Add("total_bytes_sec_max", strconv.Itoa(iotune["total_bytes_sec_max"].(int)))
urlValues.Add("total_iops_sec_max", strconv.Itoa(iotune["total_iops_sec_max"].(int)))
urlValues.Add("write_bytes_sec", strconv.Itoa(iotune["write_bytes_sec"].(int)))
urlValues.Add("write_bytes_sec_max", strconv.Itoa(iotune["write_bytes_sec_max"].(int)))
urlValues.Add("write_iops_sec", strconv.Itoa(iotune["write_iops_sec"].(int)))
urlValues.Add("write_iops_sec_max", strconv.Itoa(iotune["write_iops_sec_max"].(int)))
_, err := c.DecortAPICall(ctx, "POST", disksIOLimitAPI, urlValues)
if err != nil {
return diag.FromErr(err)
}
urlValues = &url.Values{}
}
/*
NOTE: plugin will manage disks of type "Data" only, and type cannot be changed once disk is created
if d.HasChange("restore") {
if d.Get("restore").(bool) {
urlValues.Add("diskId", d.Id())
urlValues.Add("reason", d.Get("reason").(string))
oldType, newType := d.GetChange("type")
if oldType.(string) != newType.(string) {
return fmt.Errorf("resourceDiskUpdate: Disk ID %s - changing type of existing disk not allowed", d.Id())
_, err := c.DecortAPICall(ctx, "POST", disksRestoreAPI, urlValues)
if err != nil {
return diag.FromErr(err)
}
urlValues = &url.Values{}
}
*/
// we may reuse dataSourceDiskRead here as we maintain similarity
// between Compute resource and Compute data source schemas
return dataSourceDiskRead(ctx, d, m)
}
return resourceDiskRead(ctx, d, m)
}
func resourceDiskDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
// NOTE: this function tries to detach and destroy target Disk "permanently", so
// there is no way to restore it.
// If, however, the disk is attached to a compute, the method will
// fail (by failing the underpinning DECORt API call, which is issued with detach=false)
log.Debugf("resourceDiskDelete: called for Disk ID / name %d / %s, Account ID %d",
d.Get("disk_id").(int), d.Get("name").(string), d.Get("account_id").(int))
diskFacts, err := utilityDiskCheckPresence(ctx, d, m)
if diskFacts == "" {
disk, err := utilityDiskCheckPresence(ctx, d, m)
if disk == nil {
if err != nil {
return diag.FromErr(err)
}
// the specified Disk does not exist - in this case according to Terraform best practice
// we exit from Destroy method without error
return nil
}
params := &url.Values{}
params.Add("diskId", d.Id())
// NOTE: we are not force-detaching disk from a compute (if attached) thus protecting
// data that may be on that disk from destruction.
// However, this may change in the future, as TF state management logic may want
// to delete disk resource BEFORE it is detached from compute instance, and, while
// perfectly OK from data preservation viewpoint, this is breaking expected TF workflow
// in the eyes of an experienced TF user
params.Add("detach", "0")
params.Add("permanently", "1")
params.Add("detach", strconv.FormatBool(d.Get("detach").(bool)))
params.Add("permanently", strconv.FormatBool(d.Get("permanently").(bool)))
params.Add("reason", d.Get("reason").(string))
c := m.(*controller.ControllerCfg)
_, err = c.DecortAPICall(ctx, "POST", DisksDeleteAPI, params)
_, err = c.DecortAPICall(ctx, "POST", disksDeleteAPI, params)
if err != nil {
return diag.FromErr(err)
}
@@ -196,12 +282,9 @@ func resourceDiskDelete(ctx context.Context, d *schema.ResourceData, m interface
}
func resourceDiskExists(ctx context.Context, d *schema.ResourceData, m interface{}) (bool, error) {
// Reminder: according to Terraform rules, this function should not modify its ResourceData argument
log.Debugf("resourceDiskExists: called for Disk ID / name %d / %s, Account ID %d",
d.Get("disk_id").(int), d.Get("name").(string), d.Get("account_id").(int))
diskFacts, err := utilityDiskCheckPresence(ctx, d, m)
if diskFacts == "" {
if diskFacts == nil {
if err != nil {
return false, err
}
@@ -212,101 +295,318 @@ func resourceDiskExists(ctx context.Context, d *schema.ResourceData, m interface
func resourceDiskSchemaMake() map[string]*schema.Schema {
rets := map[string]*schema.Schema{
"name": {
"account_id": {
Type: schema.TypeInt,
Required: true,
},
"disk_name": {
Type: schema.TypeString,
Required: true,
},
"size_max": {
Type: schema.TypeInt,
Required: true,
},
"gid": {
Type: schema.TypeInt,
Required: true,
},
"pool": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"sep_id": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"desc": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"type": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice([]string{"D", "B", "T"}, false),
},
"detach": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "detach disk from machine first",
},
"permanently": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "whether to completely delete the disk, works only with non attached disks",
},
"reason": {
Type: schema.TypeString,
Required: true,
Description: "Name of this disk. NOTE: disk names are NOT unique within an account. If disk ID is specified, disk name is ignored.",
Optional: true,
Default: "",
Description: "reason for an action",
},
"restore": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "restore deleting disk",
},
"disk_id": {
Type: schema.TypeInt,
Optional: true,
Description: "ID of the disk to get. If disk ID is specified, then disk name and account ID are ignored.",
Type: schema.TypeInt,
Computed: true,
},
"account_id": {
Type: schema.TypeInt,
Required: true,
Description: "ID of the account this disk belongs to.",
},
"sep_id": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
ForceNew: true,
Description: "Storage end-point provider serving this disk. Cannot be changed for existing disk.",
},
"pool": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
Description: "Pool where this disk is located. Cannot be changed for existing disk.",
},
"size": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntAtLeast(1),
Description: "Size of the disk in GB. Note, that existing disks can only be grown in size.",
},
/* We moved "type" attribute to computed attributes section, as plugin manages disks of only
one type - "D", e.g. data disks.
"type": {
Type: schema.TypeString,
Optional: true,
Default: "D",
StateFunc: stateFuncToUpper,
ValidateFunc: validation.StringInSlice([]string{"B", "D"}, false),
Description: "Optional type of this disk. Defaults to D, i.e. data disk. Cannot be changed for existing disks.",
},
*/
"description": {
Type: schema.TypeString,
Optional: true,
Default: "Disk resource managed by Terraform",
Description: "Optional user-defined text description of this disk.",
},
// The rest of the attributes are all computed
"account_name": {
Type: schema.TypeString,
Computed: true,
Description: "Name of the account this disk belongs to.",
Type: schema.TypeString,
Computed: true,
},
"acl": {
Type: schema.TypeString,
Computed: true,
},
"boot_partition": {
Type: schema.TypeInt,
Computed: true,
},
"compute_id": {
Type: schema.TypeInt,
Computed: true,
},
"compute_name": {
Type: schema.TypeString,
Computed: true,
},
"created_time": {
Type: schema.TypeInt,
Computed: true,
},
"deleted_time": {
Type: schema.TypeInt,
Computed: true,
},
"destruction_time": {
Type: schema.TypeInt,
Computed: true,
},
"devicename": {
Type: schema.TypeString,
Computed: true,
},
"disk_path": {
Type: schema.TypeString,
Computed: true,
},
"guid": {
Type: schema.TypeInt,
Computed: true,
},
"image_id": {
Type: schema.TypeInt,
Computed: true,
Description: "ID of the image, which this disk was cloned from (if ever cloned).",
Type: schema.TypeInt,
Computed: true,
},
"images": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"iotune": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"read_bytes_sec": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"read_bytes_sec_max": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"read_iops_sec": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"read_iops_sec_max": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"size_iops_sec": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"total_bytes_sec": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"total_bytes_sec_max": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"total_iops_sec": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"total_iops_sec_max": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"write_bytes_sec": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"write_bytes_sec_max": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"write_iops_sec": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"write_iops_sec_max": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
},
},
},
"iqn": {
Type: schema.TypeString,
Computed: true,
},
"login": {
Type: schema.TypeString,
Computed: true,
},
"milestones": {
Type: schema.TypeInt,
Computed: true,
},
"type": {
Type: schema.TypeString,
Computed: true,
Description: "Type of this disk.",
"order": {
Type: schema.TypeInt,
Computed: true,
},
"params": {
Type: schema.TypeString,
Computed: true,
},
"parent_id": {
Type: schema.TypeInt,
Computed: true,
},
"passwd": {
Type: schema.TypeString,
Computed: true,
},
"pci_slot": {
Type: schema.TypeInt,
Computed: true,
},
"purge_attempts": {
Type: schema.TypeInt,
Computed: true,
},
"purge_time": {
Type: schema.TypeInt,
Computed: true,
},
"reality_device_number": {
Type: schema.TypeInt,
Computed: true,
},
"reference_id": {
Type: schema.TypeString,
Computed: true,
},
"res_id": {
Type: schema.TypeString,
Computed: true,
},
"res_name": {
Type: schema.TypeString,
Computed: true,
},
"role": {
Type: schema.TypeString,
Computed: true,
},
"sep_type": {
Type: schema.TypeString,
Computed: true,
Description: "Type of the storage end-point provider serving this disk.",
Type: schema.TypeString,
Computed: true,
},
/*
"snapshots": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource {
Schema: snapshotSubresourceSchemaMake(),
"size_used": {
Type: schema.TypeInt,
Computed: true,
},
"snapshots": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"guid": {
Type: schema.TypeString,
Computed: true,
},
"label": {
Type: schema.TypeString,
Computed: true,
},
"res_id": {
Type: schema.TypeString,
Computed: true,
},
"snap_set_guid": {
Type: schema.TypeString,
Computed: true,
},
"snap_set_time": {
Type: schema.TypeInt,
Computed: true,
},
"timestamp": {
Type: schema.TypeInt,
Computed: true,
},
},
Description: "List of user-created snapshots for this disk."
},
*/
},
"status": {
Type: schema.TypeString,
Computed: true,
},
"tech_status": {
Type: schema.TypeString,
Computed: true,
},
"vmid": {
Type: schema.TypeInt,
Computed: true,
},
}
return rets

View File

@@ -34,7 +34,6 @@ package disks
import (
"context"
"encoding/json"
"fmt"
"net/url"
"strconv"
@@ -44,103 +43,28 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func utilityDiskCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (string, error) {
// This function tries to locate Disk by one of the following algorithms depending on
// the parameters passed:
// - if disk ID is specified -> by disk ID
// - if disk name is specifeid -> by disk name and either account ID or account name
//
// NOTE: disk names are not unique, so the first occurence of this name in the account will
// be returned. There is no such ambiguity when locating disk by its ID.
//
// If succeeded, it returns non empty string that contains JSON formatted facts about the disk
// as returned by disks/get API call.
// Otherwise it returns empty string and meaningful error.
//
// This function does not modify its ResourceData argument, so it is safe to use it as core
// method for resource's Exists method.
//
func utilityDiskCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (*Disk, error) {
c := m.(*controller.ControllerCfg)
urlValues := &url.Values{}
// make it possible to use "read" & "check presence" functions with disk ID set so
// that Import of preexisting Disk resource is possible
idSet := false
theId, err := strconv.Atoi(d.Id())
if err != nil || theId <= 0 {
diskId, argSet := d.GetOk("disk_id")
if argSet {
theId = diskId.(int)
idSet = true
}
disk := &Disk{}
if d.Get("disk_id").(int) == 0 {
urlValues.Add("diskId", d.Id())
} else {
idSet = true
urlValues.Add("diskId", strconv.Itoa(d.Get("disk_id").(int)))
}
if idSet {
// disk ID is specified, try to get disk instance straight by this ID
log.Debugf("utilityDiskCheckPresence: locating disk by its ID %d", theId)
urlValues.Add("diskId", fmt.Sprintf("%d", theId))
diskFacts, err := c.DecortAPICall(ctx, "POST", DisksGetAPI, urlValues)
if err != nil {
return "", err
}
return diskFacts, nil
}
// ID or disk_di was not set in the schema upon entering this function - rely on Disk name
// and Account ID to find the disk
diskName, argSet := d.GetOk("name")
if !argSet {
// no disk ID and no disk name - we cannot locate disk in this case
return "", fmt.Errorf("Cannot locate disk if name is empty and no disk ID specified")
}
// Valid account ID is required to locate disks
// obtain Account ID by account name - it should not be zero on success
urlValues.Add("accountId", fmt.Sprintf("%d", d.Get("account_id").(int)))
diskFacts, err := c.DecortAPICall(ctx, "POST", DisksListAPI, urlValues)
log.Debugf("utilityDiskCheckPresence: load disk")
diskRaw, err := c.DecortAPICall(ctx, "POST", disksGetAPI, urlValues)
if err != nil {
return "", err
return nil, err
}
log.Debugf("utilityDiskCheckPresence: ready to unmarshal string %s", diskFacts)
disksList := DisksListResp{}
err = json.Unmarshal([]byte(diskFacts), &disksList)
err = json.Unmarshal([]byte(diskRaw), disk)
if err != nil {
return "", err
return nil, err
}
// log.Printf("%#v", vm_list)
log.Debugf("utilityDiskCheckPresence: traversing decoded JSON of length %d", len(disksList))
for index, item := range disksList {
// need to match disk by name, return the first match
if item.Name == diskName.(string) && item.Status != "DESTROYED" {
log.Debugf("utilityDiskCheckPresence: index %d, matched disk name %q", index, item.Name)
// we found the disk we need - not get detailed information via API call to disks/get
/*
// TODO: this may not be optimal as it initiates one extra call to the DECORT controller
// in spite of the fact that we already have all required information about the disk in
// item variable
//
get_urlValues := &url.Values{}
get_urlValues.Add("diskId", fmt.Sprintf("%d", item.ID))
diskFacts, err = controller.decortAPICall("POST", DisksGetAPI, get_urlValues)
if err != nil {
return "", err
}
return diskFacts, nil
*/
reencodedItem, err := json.Marshal(item)
if err != nil {
return "", err
}
return string(reencodedItem[:]), nil
}
}
return "", nil // there should be no error if disk does not exist
return disk, nil
}

View File

@@ -44,8 +44,8 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func utilityDiskListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (DisksListResp, error) {
diskList := DisksListResp{}
func utilityDiskListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (DisksList, error) {
diskList := DisksList{}
c := m.(*controller.ControllerCfg)
urlValues := &url.Values{}
@@ -62,8 +62,8 @@ func utilityDiskListCheckPresence(ctx context.Context, d *schema.ResourceData, m
urlValues.Add("accountId", strconv.Itoa(accountId.(int)))
}
log.Debugf("utilityDiskListCheckPresence: load grid list")
diskListRaw, err := c.DecortAPICall(ctx, "POST", DisksListAPI, urlValues)
log.Debugf("utilityDiskListCheckPresence: load disk list")
diskListRaw, err := c.DecortAPICall(ctx, "POST", disksListAPI, urlValues)
if err != nil {
return nil, err
}