diff --git a/internal/provider/hypercore_disk_resource.go b/internal/provider/hypercore_disk_resource.go index 1289b13..f28f67f 100644 --- a/internal/provider/hypercore_disk_resource.go +++ b/internal/provider/hypercore_disk_resource.go @@ -330,6 +330,15 @@ func (r *HypercoreDiskResource) Update(ctx context.Context, req resource.UpdateR } oldHc3Disk := *pDisk + // Validate that source VM UUID hasn't changed (task 103 - disk source UUID cannot be changed after creation) + oldVMUUID := utils.AnyToString(oldHc3Disk["virDomainUUID"]) + newVMUUID := data.VmUUID.ValueString() + diagDiskSourceVMUUID := utils.ValidateDiskSourceVMUUIDUnchanged(diskUUID, oldVMUUID, newVMUUID) + if diagDiskSourceVMUUID != nil { + resp.Diagnostics.AddError(diagDiskSourceVMUUID.Summary(), diagDiskSourceVMUUID.Detail()) + return + } + // Validate the size oldDiskSize := utils.AnyToFloat64(oldHc3Disk["capacity"]) / 1000 / 1000 / 1000 // B to GB wantedDiskSize := data.Size.ValueFloat64() @@ -360,7 +369,6 @@ func (r *HypercoreDiskResource) Update(ctx context.Context, req resource.UpdateR 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 "tieringPriorityFactor": utils.FROM_HUMAN_PRIORITY_FACTOR[data.FlashPriority.ValueInt64()], diff --git a/internal/provider/hypercore_nic_resource.go b/internal/provider/hypercore_nic_resource.go index c2687e5..3d24b80 100644 --- a/internal/provider/hypercore_nic_resource.go +++ b/internal/provider/hypercore_nic_resource.go @@ -207,11 +207,28 @@ func (r *HypercoreNicResource) Update(ctx context.Context, req resource.UpdateRe tflog.Debug(ctx, fmt.Sprintf("TTRT HypercoreNicResource Update vm_uuid=%s nic_uuid=%s REQUESTED vlan=%d type=%s", vmUUID, nicUUID, data.Vlan.ValueInt64(), data.Type.String())) tflog.Debug(ctx, fmt.Sprintf("TTRT HypercoreNicResource Update vm_uuid=%s nic_uuid=%s STATE vlan=%d type=%s", vmUUID, nicUUID, data_state.Vlan.ValueInt64(), data_state.Type.String())) + // Get NIC before update + pNic := utils.GetNic(restClient, nicUUID) + if pNic == nil { + msg := fmt.Sprintf("NIC not found - nicUUID=%s, vmUUID=%s.", nicUUID, vmUUID) + resp.Diagnostics.AddError("NIC not found", msg) + return + } + oldHc3Nic := *pNic + + // Validate that source VM UUID hasn't changed (task 103 - NIC source UUID cannot be changed after creation) + oldVMUUID := utils.AnyToString(oldHc3Nic["virDomainUUID"]) + newVMUUID := data.VmUUID.ValueString() + diagNICSourceVMUUID := utils.ValidateNICSourceVMUUIDUnchanged(nicUUID, oldVMUUID, newVMUUID) + if diagNICSourceVMUUID != nil { + resp.Diagnostics.AddError(diagNICSourceVMUUID.Summary(), diagNICSourceVMUUID.Detail()) + return + } + updatePayload := map[string]any{ - "virDomainUUID": vmUUID, - "type": data.Type.ValueString(), - "vlan": data.Vlan.ValueInt64(), - "macAddress": data.MacAddress.ValueString(), + "type": data.Type.ValueString(), + "vlan": data.Vlan.ValueInt64(), + "macAddress": data.MacAddress.ValueString(), } diag := utils.UpdateNic(restClient, nicUUID, updatePayload, ctx) if diag != nil { @@ -220,15 +237,16 @@ func (r *HypercoreNicResource) Update(ctx context.Context, req resource.UpdateRe // TODO: Check if HC3 matches TF // Do not trust UpdateNic made what we asked for. Read new NIC state from HC3. - pNic := utils.GetNic(restClient, nicUUID) + pNic = utils.GetNic(restClient, nicUUID) if pNic == nil { msg := fmt.Sprintf("NIC not found - nicUUID=%s, vmUUID=%s.", nicUUID, vmUUID) resp.Diagnostics.AddError("NIC not found", msg) return } - nic := *pNic + newHc3Nic := *pNic + // - tflog.Info(ctx, fmt.Sprintf("TTRT HypercoreNicResource: vm_uuid=%s, nic_uuid=%s, nic=%v", vmUUID, nicUUID, nic)) + tflog.Info(ctx, fmt.Sprintf("TTRT HypercoreNicResource: vm_uuid=%s, nic_uuid=%s, nic=%v", vmUUID, nicUUID, newHc3Nic)) // TODO MAC, IP address etc diff --git a/internal/provider/hypercore_vm_replication_resource.go b/internal/provider/hypercore_vm_replication_resource.go index fbeff17..e6669a9 100644 --- a/internal/provider/hypercore_vm_replication_resource.go +++ b/internal/provider/hypercore_vm_replication_resource.go @@ -246,7 +246,6 @@ func (r *HypercoreVMReplicationResource) Update(ctx context.Context, req resourc restClient := *r.client replicationUUID := data.Id.ValueString() - vmUUID := data.VmUUID.ValueString() connectionUUID := data.ConnectionUUID.ValueString() label := data.Label.ValueString() @@ -261,22 +260,42 @@ func (r *HypercoreVMReplicationResource) Update(ctx context.Context, req resourc "Missing connection_uuid", "Parameter 'connection_uuid' is required for updating a VM replication", ) + return + } + + // Get replication before update + pReplication := utils.GetVMReplicationByUUID(restClient, replicationUUID) + if pReplication == nil { + msg := fmt.Sprintf("VM replication not found - replicationUUID=%s.", replicationUUID) + resp.Diagnostics.AddError("VM replication not found", msg) + return + } + oldHc3Replication := *pReplication + + // Validate that source VM UUID hasn't changed (task 103 - replication source UUID cannot be changed after creation) + oldVMUUID := utils.AnyToString(oldHc3Replication["sourceDomainUUID"]) + newVMUUID := data.VmUUID.ValueString() + diagReplicationSourceVMUUID := utils.ValidateReplicationSourceVMUUIDUnchanged(replicationUUID, oldVMUUID, newVMUUID) + if diagReplicationSourceVMUUID != nil { + resp.Diagnostics.AddError(diagReplicationSourceVMUUID.Summary(), diagReplicationSourceVMUUID.Detail()) + return } - diag := utils.UpdateVMReplication(restClient, replicationUUID, vmUUID, connectionUUID, label, enable, ctx) + + diag := utils.UpdateVMReplication(restClient, replicationUUID, connectionUUID, label, enable, ctx) if diag != nil { resp.Diagnostics.AddWarning(diag.Summary(), diag.Detail()) } // TODO: Check if HC3 matches TF // Do not trust UpdateVMReplication made what we asked for. Read new power state from HC3. - pHc3Replication := utils.GetVMReplicationByUUID(restClient, replicationUUID) - if pHc3Replication == nil { + pReplication = utils.GetVMReplicationByUUID(restClient, replicationUUID) + if pReplication == nil { msg := fmt.Sprintf("VM replication not found - replicationUUID=%s.", replicationUUID) resp.Diagnostics.AddError("VM replication not found", msg) return } - newHc3Replication := *pHc3Replication + newHc3Replication := *pReplication tflog.Info(ctx, fmt.Sprintf("TTRT HypercoreVMReplicationResource: replication_uuid=%s, replication=%v", replicationUUID, newHc3Replication)) diff --git a/internal/provider/tests/acceptance/hypercore_vms_data_source_acc_test.go b/internal/provider/tests/acceptance/hypercore_vms_data_source_acc_test.go index c2a27f4..73c9eca 100644 --- a/internal/provider/tests/acceptance/hypercore_vms_data_source_acc_test.go +++ b/internal/provider/tests/acceptance/hypercore_vms_data_source_acc_test.go @@ -44,7 +44,7 @@ func TestAccHypercoreVMsDatasource_stopped(t *testing.T) { resource.TestCheckResourceAttr("data.hypercore_vms.test", "vms.0.memory", "4096"), // resource.TestCheckResourceAttr("data.hypercore_vms.test", "vms.0.vcpu", "1"), resource.TestCheckResourceAttr("data.hypercore_vms.test", "vms.0.power_state", "SHUTOFF"), - resource.TestCheckResourceAttr("data.hypercore_vms.test", "vms.0.disks.#", "2"), + resource.TestCheckResourceAttr("data.hypercore_vms.test", "vms.0.disks.#", "4"), resource.TestCheckResourceAttr("data.hypercore_vms.test", "vms.0.disks.0.type", "VIRTIO_DISK"), resource.TestCheckResourceAttr("data.hypercore_vms.test", "vms.0.disks.0.slot", "0"), resource.TestCheckResourceAttr("data.hypercore_vms.test", "vms.0.disks.0.size", "1.2"), diff --git a/internal/utils/nic.go b/internal/utils/nic.go index 836b9d7..8dba9cc 100644 --- a/internal/utils/nic.go +++ b/internal/utils/nic.go @@ -78,153 +78,16 @@ func UpdateNic( return nil } -/* -func (vd *VMDisk) CreateOrUpdate( - vc *VMClone, - restClient RestClient, - ctx context.Context, -) (bool, bool, string, error) { - changed := false - vm := GetByName(vc.VMName, restClient, true) - vmUUID := AnyToString((*vm)["uuid"]) - vmDisks := AnyToListOfMap((*vm)["blockDevs"]) - - if vd.Size != nil { - existingDisk := vd.GetSpecificDisk(vmDisks, ctx) // from HC3 - desiredDisk := vd.BuildDiskPayload(vmUUID) - - tflog.Debug(ctx, fmt.Sprintf("Desired disk: %v\n", desiredDisk)) - tflog.Debug(ctx, fmt.Sprintf("Existing disk: %v\n", existingDisk)) - - if existingDisk != nil { - existingDiskSize := AnyToFloat64((*existingDisk)["capacity"]) / 1000 / 1000 / 1000 - existingDiskSlot := AnyToInteger64((*existingDisk)["slot"]) - existingDiskType := AnyToString((*existingDisk)["type"]) - desiredDiskSize := AnyToFloat64(desiredDisk["capacity"]) / 1000 / 1000 / 1000 - if existingDiskSize > desiredDiskSize { - return false, false, "", fmt.Errorf( - "Disk of type '%s' on slot %d can only be expanded. Use a different slot or use a larger size. %v GB > %v GB\n", - existingDiskType, existingDiskSlot, existingDiskSize, desiredDiskSize, - ) - } - } - - if existingDisk != nil { - if isSuperset(*existingDisk, desiredDisk) { - return false, vc.WasRebooted(), "", nil - } - - tflog.Debug(ctx, "Updating existing disk\n") - vd.UUID = vd.UpdateBlockDevice(vc, vmUUID, restClient, desiredDisk, *existingDisk, ctx) - changed = true - } else { - tflog.Debug(ctx, "Creating new disk\n") - vd.UUID = vd.CreateBlockDevice(restClient, desiredDisk, ctx) - changed = true - } - } - - return changed, vc.WasRebooted(), vd.UUID, nil -} - -func (vd *VMDisk) UpdateBlockDevice( - vc *VMClone, - vmUUID string, - restClient RestClient, - desiredDisk map[string]any, - existingDisk map[string]any, - ctx context.Context, -) string { - vc.DoShutdownSteps(vmUUID, SHUTDOWN_TIMEOUT_SECONDS, restClient, ctx) - - existingDiskUUID := AnyToString(existingDisk["uuid"]) - taskTag := restClient.UpdateRecord( - fmt.Sprintf("/rest/v1/VirDomainBlockDevice/%s", existingDiskUUID), - desiredDisk, - -1, - ctx, - ) - taskTag.WaitTask(restClient, ctx) - - return existingDiskUUID -} - -func (vd *VMDisk) CreateBlockDevice( - restClient RestClient, - desiredDisk map[string]any, - ctx context.Context, -) string { - taskTag, _, _ := restClient.CreateRecord( - "/rest/v1/VirDomainBlockDevice", - desiredDisk, - -1, - ) - taskTag.WaitTask(restClient, ctx) - - return taskTag.CreatedUUID -} - -// This function will be useful when dealing with IDE_CDROM type disks: so for the future -// nolint:unused -func (vd *VMDisk) EnsureAbsend( - vc *VMClone, - changedParams map[string]bool, - restClient RestClient, - ctx context.Context, -) (bool, bool, map[string]any) { - vm := GetByName(vc.VMName, restClient, true) - vmDisks := AnyToListOfMap((*vm)["blockDevs"]) - - if vd.Size != nil { - existingDisk := vd.GetSpecificDisk(vmDisks, ctx) - if existingDisk == nil { - return true, false, map[string]any{} // no disk - absent is already ensured - } - - diskUUID := AnyToString((*existingDisk)["uuid"]) - - // Remove the disk to ensure it's absence - vmUUID := AnyToString((*vm)["uuid"]) - vc.DoShutdownSteps(vmUUID, SHUTDOWN_TIMEOUT_SECONDS, restClient, ctx) - - taskTag := restClient.DeleteRecord( - fmt.Sprintf("/rest/v1/VirDomainBlockDevice/%s", diskUUID), - -1, - ctx, +// Checks that source VM UUID wasn't altered during update. +func ValidateNICSourceVMUUIDUnchanged(nicUUID string, oldVMUUID string, newVMUUID string) diag.Diagnostic { + if oldVMUUID != newVMUUID { + return diag.NewErrorDiagnostic( + "Invalid NIC source virtual machine UUID", + fmt.Sprintf( + " virtual machine and NIC relationship is established at creation and cannot be changed, source UUID: %s, new VM UUID: %s, NIC UUID: %s", + oldVMUUID, newVMUUID, nicUUID, + ), ) - taskTag.WaitTask(restClient, ctx) - - vc.PowerUp(*vm, restClient, ctx) - return true, true, map[string]any{} - } - - return false, false, map[string]any{} -} - -func (vd *VMDisk) BuildDiskPayload(vmUUID string) map[string]any { - return map[string]any{ - "virDomainUUID": vmUUID, - "type": vd.Type, - "slot": vd.Slot, - "capacity": *vd.Size, - } -} - -func (vd *VMDisk) GetSpecificDisk(vmDisks []map[string]any, ctx context.Context) *map[string]any { - for _, vmDisk := range vmDisks { - vmDiskUUID := AnyToString(vmDisk["uuid"]) - vmDiskSlot := AnyToInteger64(vmDisk["slot"]) - vmDiskType := AnyToString(vmDisk["type"]) - if vmDiskUUID == vd.UUID { - tflog.Debug(ctx, fmt.Sprintf("Got disk by UUID: %v", vmDisk)) - return &vmDisk - } - - if vmDiskSlot == vd.Slot && vmDiskType == vd.Type { - tflog.Debug(ctx, fmt.Sprintf("Got disk by slot and type: %v", vmDisk)) - return &vmDisk - } } return nil } -*/ diff --git a/internal/utils/vm_disk.go b/internal/utils/vm_disk.go index 502f08e..77a21b4 100644 --- a/internal/utils/vm_disk.go +++ b/internal/utils/vm_disk.go @@ -114,92 +114,6 @@ func UpdateVMDisk( return vmDisk, nil } -func (vd *VMDisk) CreateOrUpdate( - vc *VM, - restClient RestClient, - ctx context.Context, -) (bool, bool, string, error) { - changed := false - vm := GetVMByName(vc.VMName, restClient, true) - vmUUID := AnyToString((*vm)["uuid"]) - vmDisks := AnyToListOfMap((*vm)["blockDevs"]) - - if vd.Size != nil { - existingDisk := vd.Get(vmDisks, ctx) // from HC3 - desiredDisk := vd.BuildDiskPayload(vmUUID) - - tflog.Debug(ctx, fmt.Sprintf("Desired disk: %v\n", desiredDisk)) - tflog.Debug(ctx, fmt.Sprintf("Existing disk: %v\n", existingDisk)) - - if existingDisk != nil { - existingDiskSize := AnyToFloat64((*existingDisk)["capacity"]) / 1000 / 1000 / 1000 - existingDiskSlot := AnyToInteger64((*existingDisk)["slot"]) - existingDiskType := AnyToString((*existingDisk)["type"]) - desiredDiskSize := AnyToFloat64(desiredDisk["capacity"]) / 1000 / 1000 / 1000 - if existingDiskSize > desiredDiskSize { - return false, false, "", fmt.Errorf( - "disk of type '%s' on slot %d can only be expanded. Use a different slot or use a larger size. %v GB > %v GB", - existingDiskType, existingDiskSlot, existingDiskSize, desiredDiskSize, - ) - } - } - - if existingDisk != nil { - if isSuperset(*existingDisk, desiredDisk) { - return false, vc.WasRebooted(), "", nil - } - - tflog.Debug(ctx, "Updating existing disk\n") - vd.UUID = vd.UpdateBlockDevice(vc, vmUUID, restClient, desiredDisk, *existingDisk, ctx) - changed = true - } else { - tflog.Debug(ctx, "Creating new disk\n") - vd.UUID = vd.CreateBlockDevice(restClient, desiredDisk, ctx) - changed = true - } - } - - return changed, vc.WasRebooted(), vd.UUID, nil -} - -func (vd *VMDisk) UpdateBlockDevice( - vc *VM, - vmUUID string, - restClient RestClient, - desiredDisk map[string]any, - existingDisk map[string]any, - ctx context.Context, -) string { - // TODO: this will be a new resource in the future, for now we act like the VMs are always shut down - // vc.DoShutdownSteps(vmUUID, SHUTDOWN_TIMEOUT_SECONDS, restClient, ctx) - - existingDiskUUID := AnyToString(existingDisk["uuid"]) - taskTag, _ := restClient.UpdateRecord( - fmt.Sprintf("/rest/v1/VirDomainBlockDevice/%s", existingDiskUUID), - desiredDisk, - -1, - ctx, - ) - taskTag.WaitTask(restClient, ctx) - - return existingDiskUUID -} - -func (vd *VMDisk) CreateBlockDevice( - restClient RestClient, - desiredDisk map[string]any, - ctx context.Context, -) string { - taskTag, _, _ := restClient.CreateRecord( - "/rest/v1/VirDomainBlockDevice", - desiredDisk, - -1, - ) - taskTag.WaitTask(restClient, ctx) - - return taskTag.CreatedUUID -} - // TODO: this function might be useful when dealing with IDE_CDROM type disks: so for the future // nolint:unused func (vd *VMDisk) EnsureAbsend( @@ -237,12 +151,10 @@ func (vd *VMDisk) EnsureAbsend( return false, false, map[string]any{} } -func (vd *VMDisk) BuildDiskPayload(vmUUID string) map[string]any { +func (vd *VMDisk) BuildDiskPayload() map[string]any { return map[string]any{ - "virDomainUUID": vmUUID, - "type": vd.Type, - "slot": vd.Slot, - "capacity": *vd.Size, + "type": vd.Type, + "capacity": *vd.Size, } } @@ -289,12 +201,10 @@ func GetDiskByUUID(restClient RestClient, diskUUID string) *map[string]any { return disk } -func BuildDiskPayload(vmUUID string, diskType string, diskSlot int64, diskSizeGB float64) map[string]any { +func BuildDiskPayload(diskType string, diskSizeGB float64) map[string]any { return map[string]any{ - "virDomainUUID": vmUUID, - "type": diskType, - "slot": diskSlot, - "capacity": diskSizeGB * 1000 * 1000 * 1000, // GB to B + "type": diskType, + "capacity": diskSizeGB * 1000 * 1000 * 1000, // GB to B } } @@ -395,3 +305,17 @@ func ValidateDiskSize(diskUUID string, oldSize float64, newSize float64) diag.Di } return nil } + +// Checks that source VM UUID wasn't altered during update. +func ValidateDiskSourceVMUUIDUnchanged(diskUUID string, oldVMUUID string, newVMUUID string) diag.Diagnostic { + if oldVMUUID != newVMUUID { + return diag.NewErrorDiagnostic( + "Invalid disk source virtual machine UUID", + fmt.Sprintf( + " virtual machine and disk relationship is established at creation and cannot be changed, source UUID: %s, new VM UUID: %s, disk UUID: %s", + oldVMUUID, newVMUUID, diskUUID, + ), + ) + } + return nil +} diff --git a/internal/utils/vm_replication.go b/internal/utils/vm_replication.go index d914bd1..36cc074 100644 --- a/internal/utils/vm_replication.go +++ b/internal/utils/vm_replication.go @@ -68,17 +68,15 @@ func CreateVMReplication( func UpdateVMReplication( restClient RestClient, replicationUUID string, - sourceVmUUID string, connectionUUID string, label string, enable bool, ctx context.Context, ) diag.Diagnostic { payload := map[string]any{ - "sourceDomainUUID": sourceVmUUID, - "connectionUUID": connectionUUID, - "label": label, - "enable": enable, + "connectionUUID": connectionUUID, + "label": label, + "enable": enable, } taskTag, err := restClient.UpdateRecord( @@ -100,3 +98,17 @@ func UpdateVMReplication( return nil } + +// Checks that source VM UUID wasn't altered during update. +func ValidateReplicationSourceVMUUIDUnchanged(replicationUUID string, oldVMUUID string, newVMUUID string) diag.Diagnostic { + if oldVMUUID != newVMUUID { + return diag.NewErrorDiagnostic( + "Invalid replication source virtual machine UUID", + fmt.Sprintf( + " virtual machine and replication relationship is established at creation and cannot be changed, source UUID: %s, new VM UUID: %s, replication UUID: %s", + oldVMUUID, newVMUUID, replicationUUID, + ), + ) + } + return nil +}