diff --git a/docs/resources/disk.md b/docs/resources/disk.md index 2e3ccbd..51ab6ab 100644 --- a/docs/resources/disk.md +++ b/docs/resources/disk.md @@ -25,12 +25,15 @@ resource "hypercore_disk" "disk_cloned" { vm_uuid = data.hypercore_vms.diskvm.vms.0.uuid type = "VIRTIO_DISK" size = 47.2 + # flash_priority will be fetched from the HC3 API and it will + # be set to that unless specifically specified otherwise } resource "hypercore_disk" "disk_newly_created" { - vm_uuid = data.hypercore_vms.diskvm.vms.0.uuid - type = "IDE_DISK" - size = 3.0 + vm_uuid = data.hypercore_vms.diskvm.vms.0.uuid + type = "IDE_DISK" + size = 3.0 + flash_priority = 5 # defaults to 4 if not provided } output "diskvm_uuid" { @@ -55,10 +58,11 @@ import { ### Optional -- `iso_uuid` (String) ISO UUID we want to attach to the disk, only available with disk type IDE_CDROM. +- `flash_priority` (Number) SSD tiering priority factor for block placement. If not provided, it will default to `4`, unless imported, in which case the disk's current flash priority will be taken into account and can then be modified. This can be any **positive** value between (including) `0` and `11`. +- `iso_uuid` (String) ISO UUID we want to attach to the disk, only available with disk type `IDE_CDROM`. - `size` (Number) Disk size in `GB`. Must be larger than the current size of the disk if specified. - `source_virtual_disk_id` (String) UUID of the virtual disk to use to clone and attach to the VM. -- `type` (String) Disk type. Can be: `IDE_DISK`, `SCSI_DISK`, `VIRTIO_DISK`, `IDE_FLOPPY`, `NVRAM`, `VTPM` +- `type` (String) Disk type. Can be: `IDE_DISK`, `IDE_CDROM`, `SCSI_DISK`, `VIRTIO_DISK`, `IDE_FLOPPY`, `NVRAM`, `VTPM` ### Read-Only diff --git a/examples/resources/hypercore_disk/resource.tf b/examples/resources/hypercore_disk/resource.tf index e0a445a..761ad5d 100644 --- a/examples/resources/hypercore_disk/resource.tf +++ b/examples/resources/hypercore_disk/resource.tf @@ -10,12 +10,15 @@ resource "hypercore_disk" "disk_cloned" { vm_uuid = data.hypercore_vms.diskvm.vms.0.uuid type = "VIRTIO_DISK" size = 47.2 + # flash_priority will be fetched from the HC3 API and it will + # be set to that unless specifically specified otherwise } resource "hypercore_disk" "disk_newly_created" { - vm_uuid = data.hypercore_vms.diskvm.vms.0.uuid - type = "IDE_DISK" - size = 3.0 + vm_uuid = data.hypercore_vms.diskvm.vms.0.uuid + type = "IDE_DISK" + size = 3.0 + flash_priority = 5 # defaults to 4 if not provided } output "diskvm_uuid" { diff --git a/internal/provider/hypercore_disk_resource.go b/internal/provider/hypercore_disk_resource.go index b9fabe1..e669856 100644 --- a/internal/provider/hypercore_disk_resource.go +++ b/internal/provider/hypercore_disk_resource.go @@ -11,6 +11,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" @@ -36,6 +38,7 @@ type HypercoreDiskResourceModel struct { Id types.String `tfsdk:"id"` VmUUID types.String `tfsdk:"vm_uuid"` Slot types.Int64 `tfsdk:"slot"` + FlashPriority types.Int64 `tfsdk:"flash_priority"` Type types.String `tfsdk:"type"` Size types.Float64 `tfsdk:"size"` SourceVirtualDiskID types.String `tfsdk:"source_virtual_disk_id"` @@ -70,8 +73,17 @@ func (r *HypercoreDiskResource) Schema(ctx context.Context, req resource.SchemaR MarkdownDescription: "Disk slot number. Will not do anything if the disk already exists, since HC3 doesn't change disk slots to existing disks.", Computed: true, }, + "flash_priority": schema.Int64Attribute{ + MarkdownDescription: "SSD tiering priority factor for block placement. If not provided, it will default to `4`, unless imported, in which case the disk's current flash priority will be taken into account and can then be modified. This can be any **positive** value between (including) `0` and `11`.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(4), + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, "type": schema.StringAttribute{ - MarkdownDescription: "Disk type. Can be: `IDE_DISK`, `SCSI_DISK`, `VIRTIO_DISK`, `IDE_FLOPPY`, `NVRAM`, `VTPM`", + MarkdownDescription: "Disk type. Can be: `IDE_DISK`, `IDE_CDROM`, `SCSI_DISK`, `VIRTIO_DISK`, `IDE_FLOPPY`, `NVRAM`, `VTPM`", Optional: true, }, "size": schema.Float64Attribute{ @@ -83,7 +95,7 @@ func (r *HypercoreDiskResource) Schema(ctx context.Context, req resource.SchemaR Optional: true, }, "iso_uuid": schema.StringAttribute{ - MarkdownDescription: "ISO UUID we want to attach to the disk, only available with disk type IDE_CDROM.", + MarkdownDescription: "ISO UUID we want to attach to the disk, only available with disk type `IDE_CDROM`.", Optional: true, }, }, @@ -151,11 +163,17 @@ func (r *HypercoreDiskResource) Create(ctx context.Context, req resource.CreateR resp.Diagnostics.AddError(diagISOAttach.Summary(), diagISOAttach.Detail()) return } + diagFlashPriority := utils.ValidateDiskFlashPriority(data.FlashPriority.ValueInt64()) + if diagFlashPriority != nil { + resp.Diagnostics.AddError(diagFlashPriority.Summary(), diagFlashPriority.Detail()) + return + } createPayload := map[string]any{ - "virDomainUUID": data.VmUUID.ValueString(), - "type": data.Type.ValueString(), - "capacity": data.Size.ValueFloat64() * 1000 * 1000 * 1000, // GB to B + "virDomainUUID": data.VmUUID.ValueString(), + "type": data.Type.ValueString(), + "capacity": data.Size.ValueFloat64() * 1000 * 1000 * 1000, // GB to B + "tieringPriorityFactor": utils.FROM_HUMAN_PRIORITY_FACTOR[data.FlashPriority.ValueInt64()], } if isAttachingISO { createPayload["path"] = (*iso)["path"] @@ -177,9 +195,10 @@ func (r *HypercoreDiskResource) Create(ctx context.Context, req resource.CreateR "readOnly": false, }, "template": map[string]any{ - "virDomainUUID": data.VmUUID.ValueString(), - "type": data.Type.ValueString(), - "capacity": originalVDSizeBytes, + "virDomainUUID": data.VmUUID.ValueString(), + "type": data.Type.ValueString(), + "capacity": originalVDSizeBytes, + "tieringPriorityFactor": utils.FROM_HUMAN_PRIORITY_FACTOR[data.FlashPriority.ValueInt64()], }, } @@ -258,6 +277,10 @@ func (r *HypercoreDiskResource) Read(ctx context.Context, req resource.ReadReque data.Slot = types.Int64Value(utils.AnyToInteger64(disk["slot"])) data.Size = types.Float64Value(utils.AnyToFloat64(disk["capacity"]) / 1000 / 1000 / 1000) + hc3PriorityFactor := utils.AnyToInteger64(disk["tieringPriorityFactor"]) + tflog.Info(ctx, fmt.Sprintf("TTRT HypercoreDiskResource: hc3PriorityFactor = %v\n", hc3PriorityFactor)) + data.FlashPriority = types.Int64Value(utils.TO_HUMAN_PRIORITY_FACTOR[hc3PriorityFactor]) + // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -312,7 +335,7 @@ func (r *HypercoreDiskResource) Update(ctx context.Context, req resource.UpdateR } // Validate the type - diagDiskType := utils.ValidateDiskType(data.Type.ValueString(), data.IsoUUID.String()) + diagDiskType := utils.ValidateDiskType(data.Type.ValueString(), data.IsoUUID.ValueString()) if diagDiskType != nil { resp.Diagnostics.AddError(diagDiskType.Summary(), diagDiskType.Detail()) return @@ -322,13 +345,19 @@ func (r *HypercoreDiskResource) Update(ctx context.Context, req resource.UpdateR resp.Diagnostics.AddError(diagISOAttach.Summary(), diagISOAttach.Detail()) return } + diagFlashPriority := utils.ValidateDiskFlashPriority(data.FlashPriority.ValueInt64()) + if diagFlashPriority != nil { + resp.Diagnostics.AddError(diagFlashPriority.Summary(), diagFlashPriority.Detail()) + return + } isDetachingISO := oldHc3Disk["path"] != "" && data.IsoUUID.ValueString() == "" && data.Type.ValueString() == "IDE_CDROM" updatePayload := map[string]any{ - "virDomainUUID": vmUUID, - "type": data.Type.ValueString(), - "capacity": data.Size.ValueFloat64() * 1000 * 1000 * 1000, // GB to B + "virDomainUUID": vmUUID, + "type": data.Type.ValueString(), + "capacity": data.Size.ValueFloat64() * 1000 * 1000 * 1000, // GB to B + "tieringPriorityFactor": utils.FROM_HUMAN_PRIORITY_FACTOR[data.FlashPriority.ValueInt64()], } if isAttachingISO { updatePayload["path"] = (*iso)["path"] @@ -410,11 +439,14 @@ func (r *HypercoreDiskResource) ImportState(ctx context.Context, req resource.Im var diskUUID string var size float64 + var flashPriority int64 for _, disk := range hc3Disks { if utils.AnyToInteger64(disk["slot"]) == slot && utils.AnyToString(disk["type"]) == diskType { diskUUID = utils.AnyToString(disk["uuid"]) size = utils.AnyToFloat64(disk["capacity"]) / 1000 / 1000 / 1000 // hc3 has B, so convert to GB + flashPriority = utils.AnyToInteger64(disk["tieringPriorityFactor"]) + tflog.Debug(ctx, fmt.Sprintf("TTRT HUMAN FLASH PRIORITY = %v", flashPriority)) break } } @@ -428,5 +460,6 @@ func (r *HypercoreDiskResource) ImportState(ctx context.Context, req resource.Im resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("vm_uuid"), vmUUID)...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("type"), diskType)...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("slot"), slot)...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("flash_priority"), flashPriority)...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("size"), size)...) } diff --git a/internal/provider/tests/acceptance/hypercore_disk_resource_acc_test.go b/internal/provider/tests/acceptance/hypercore_disk_resource_acc_test.go index 917d1f4..d03e450 100644 --- a/internal/provider/tests/acceptance/hypercore_disk_resource_acc_test.go +++ b/internal/provider/tests/acceptance/hypercore_disk_resource_acc_test.go @@ -20,6 +20,7 @@ func TestAccHypercoreDiskResource(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("hypercore_disk.test", "size", "3"), resource.TestCheckResourceAttr("hypercore_disk.test", "type", "IDE_DISK"), + resource.TestCheckResourceAttr("hypercore_disk.test", "flash_priority", "4"), // should default to 4 if not specified in resource config ), }, }, diff --git a/internal/provider/tests/acceptance/hypercore_virtual_disk_resource_acc_test.go b/internal/provider/tests/acceptance/hypercore_virtual_disk_resource_acc_test.go index 4cf4da7..0dcb4d4 100644 --- a/internal/provider/tests/acceptance/hypercore_virtual_disk_resource_acc_test.go +++ b/internal/provider/tests/acceptance/hypercore_virtual_disk_resource_acc_test.go @@ -20,6 +20,7 @@ func TestAccHypercoreVirtualDiskResource(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("hypercore_disk.attach_vd_test", "size", "3.4"), resource.TestCheckResourceAttr("hypercore_disk.attach_vd_test", "type", "VIRTIO_DISK"), + resource.TestCheckResourceAttr("hypercore_disk.attach_vd_test", "flash_priority", "4"), ), }, }, diff --git a/internal/utils/vm_disk.go b/internal/utils/vm_disk.go index f8d49ad..502f08e 100644 --- a/internal/utils/vm_disk.go +++ b/internal/utils/vm_disk.go @@ -21,6 +21,36 @@ var ALLOWED_DISK_TYPES = map[string]bool{ "IDE_CDROM": true, } +var FROM_HUMAN_PRIORITY_FACTOR = map[int64]int64{ + 0: 0, + 1: 1, + 2: 2, + 3: 4, + 4: 8, + 5: 16, + 6: 32, + 7: 64, + 8: 128, + 9: 256, + 10: 1024, + 11: 10240, +} + +var TO_HUMAN_PRIORITY_FACTOR = map[int64]int64{ + 0: 0, + 1: 1, + 2: 2, + 4: 3, + 8: 4, + 16: 5, + 32: 6, + 64: 7, + 128: 8, + 256: 9, + 1024: 10, + 10240: 11, +} + type VMDisk struct { Label string UUID string // known after creation @@ -311,11 +341,22 @@ func CreateDisk( return diskUUID, *disk } +func ValidateDiskFlashPriority(diskFlashPriority int64) diag.Diagnostic { + if diskFlashPriority < 0 || diskFlashPriority > 11 { + return diag.NewErrorDiagnostic( + "Invalid disk flash priority", + fmt.Sprintf("Disk flash priority '%v' is invalid. Flash priority must be a positive number between (including) 0 and 11.", diskFlashPriority), + ) + } + + return nil +} + func ValidateDiskType(diskType string, isoUUID string) diag.Diagnostic { if !ALLOWED_DISK_TYPES[diskType] { return diag.NewErrorDiagnostic( "Invalid disk type", - fmt.Sprintf("Disk type '%s' not allowed. Allowed types are: IDE_DISK, SCSI_DISK, VIRTIO_DISK, IDE_FLOPPY, NVRAM, VTPM", diskType), + fmt.Sprintf("Disk type '%s' not allowed. Allowed types are: IDE_DISK, IDE_CDROM, SCSI_DISK, VIRTIO_DISK, IDE_FLOPPY, NVRAM, VTPM", diskType), ) } if isoUUID != "" && diskType != "IDE_CDROM" { diff --git a/local/main.tf b/local/main.tf index b84ebd2..ccbabe0 100644 --- a/local/main.tf +++ b/local/main.tf @@ -10,26 +10,31 @@ terraform { } locals { - vm_name = "testtf-ana-tags-2" - src_vm_name = "ana" + vm_name = "testtf-ana-tags" } provider "hypercore" {} -data "hypercore_vms" "src_empty" { - name = local.src_vm_name +data "hypercore_vms" "testtf-ana-tags" { + name = "testtf-ana-tags" } -resource "hypercore_vm" "vm_on" { - tags = ["ana-tftag2"] - name = local.vm_name - description = "VM created from scratch" - vcpu = 1 - memory = 1234 # MiB - - clone = { - source_vm_uuid = data.hypercore_vms.src_empty.vms.0.uuid - meta_data = "" - user_data = "" - } +resource "hypercore_disk" "disk_newly_created" { + vm_uuid = data.hypercore_vms.testtf-ana-tags.vms.0.uuid + type = "IDE_DISK" + size = 3.0 + flash_priority = 3 +} + +resource "hypercore_disk" "disk_cloned" { + vm_uuid = data.hypercore_vms.testtf-ana-tags.vms.0.uuid + type = "IDE_DISK" + size = 3.0 + + depends_on = [hypercore_disk.disk_newly_created] +} + +import { + to = hypercore_disk.disk_cloned + id = format("%s:%s:%d", data.hypercore_vms.testtf-ana-tags.vms.0.uuid, "IDE_DISK", 0) }