From a37692c5b8ee771ba4aae281e3b3627af3e167d2 Mon Sep 17 00:00:00 2001 From: Dorian Jolivald Date: Mon, 22 Jul 2024 16:53:40 +0200 Subject: [PATCH 1/4] Allow to recreate VMs if in DONE state --- .../resource_opennebula_virtual_machine.go | 35 +++--- ...esource_opennebula_virtual_machine_test.go | 106 ++++++++++++++++++ 2 files changed, 126 insertions(+), 15 deletions(-) diff --git a/opennebula/resource_opennebula_virtual_machine.go b/opennebula/resource_opennebula_virtual_machine.go index 8efbe5fde..9a2311210 100644 --- a/opennebula/resource_opennebula_virtual_machine.go +++ b/opennebula/resource_opennebula_virtual_machine.go @@ -526,7 +526,7 @@ func resourceOpennebulaVirtualMachineReadCustom(ctx context.Context, d *schema.R // TODO: fix it after 5.10 release // Force the "decrypt" bool to false to keep ONE 5.8 behavior - vm, err := vmc.Info(false) + vmInfo, err := vmc.Info(false) if err != nil { if NoExists(err) { log.Printf("[WARN] Removing virtual machine %s from state because it no longer exists in", d.Get("name")) @@ -540,16 +540,21 @@ func resourceOpennebulaVirtualMachineReadCustom(ctx context.Context, d *schema.R }) return diags } - d.SetId(fmt.Sprintf("%v", vm.ID)) - d.Set("name", vm.Name) - d.Set("uid", vm.UID) - d.Set("gid", vm.GID) - d.Set("uname", vm.UName) - d.Set("gname", vm.GName) - d.Set("state", vm.StateRaw) - d.Set("lcmstate", vm.LCMStateRaw) + d.SetId(fmt.Sprintf("%v", vmInfo.ID)) + d.Set("name", vmInfo.Name) + d.Set("uid", vmInfo.UID) + d.Set("gid", vmInfo.GID) + d.Set("uname", vmInfo.UName) + d.Set("gname", vmInfo.GName) + d.Set("state", vmInfo.StateRaw) + d.Set("lcmstate", vmInfo.LCMStateRaw) + if vm.State(vmInfo.StateRaw) == vm.Done { + log.Printf("[WARN] Replacing virtual machine %s (id: %s) because VM is 'Done'; ", d.Get("name"), d.Id()) + d.SetId("") + return nil + } //TODO fix this: - err = d.Set("permissions", permissionsUnixString(*vm.Permissions)) + err = d.Set("permissions", permissionsUnixString(*vmInfo.Permissions)) if err != nil { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, @@ -560,7 +565,7 @@ func resourceOpennebulaVirtualMachineReadCustom(ctx context.Context, d *schema.R } if customVM != nil { - customDiags := customVM(ctx, d, vm) + customDiags := customVM(ctx, d, vmInfo) if len(customDiags) > 0 { return customDiags } @@ -571,7 +576,7 @@ func resourceOpennebulaVirtualMachineReadCustom(ctx context.Context, d *schema.R if inheritedVectorsIf != nil { inheritedVectors = inheritedVectorsIf.(map[string]interface{}) } - err = flattenTemplate(d, inheritedVectors, &vm.Template) + err = flattenTemplate(d, inheritedVectors, &vmInfo.Template) if err != nil { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, @@ -587,14 +592,14 @@ func resourceOpennebulaVirtualMachineReadCustom(ctx context.Context, d *schema.R inheritedTags = inheritedTagsIf.(map[string]interface{}) } - flattenDiags := flattenVMUserTemplate(d, meta, inheritedTags, &vm.UserTemplate.Template) + flattenDiags := flattenVMUserTemplate(d, meta, inheritedTags, &vmInfo.UserTemplate.Template) for _, diag := range flattenDiags { diag.Detail = fmt.Sprintf("virtual machine (ID: %s): %s", d.Id(), err) diags = append(diags, diag) } - if vm.LockInfos != nil { - d.Set("lock", LockLevelToString(vm.LockInfos.Locked)) + if vmInfo.LockInfos != nil { + d.Set("lock", LockLevelToString(vmInfo.LockInfos.Locked)) } return diags diff --git a/opennebula/resource_opennebula_virtual_machine_test.go b/opennebula/resource_opennebula_virtual_machine_test.go index b2e126c5b..70aef1351 100644 --- a/opennebula/resource_opennebula_virtual_machine_test.go +++ b/opennebula/resource_opennebula_virtual_machine_test.go @@ -2,10 +2,12 @@ package opennebula import ( "fmt" + "github.com/OpenNebula/one/src/oca/go/src/goca/schemas/vm" "os" "reflect" "strconv" "testing" + "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -313,6 +315,36 @@ func TestAccVirtualMachinePending(t *testing.T) { }) } +func TestAccVirtualMachineDoneTriggerRecreation(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVirtualMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVirtualMachineDone, + ExpectNonEmptyPlan: true, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("opennebula_virtual_machine.test", "name", "virtual_machine_done"), + testAccTerminateVM("virtual_machine_done"), + ), + }, + { + RefreshState: true, + ExpectNonEmptyPlan: true, + Check: resource.ComposeTestCheckFunc( + func(state *terraform.State) error { + if !state.Empty() && len(state.RootModule().Resources) != 0 { + return fmt.Errorf("expected state to be empty") + } + return nil + }, + ), + }, + }, + }) +} + func TestAccVirtualMachineResize(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -454,6 +486,54 @@ func testAccSetDSdummy() resource.TestCheckFunc { } } +func testAccTerminateVM(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Configuration) + controller := config.Controller + + id, err := controller.VMs().ByName(name) + if err != nil { + return err + } + + err = controller.VM(id).TerminateHard() + if err != nil { + return err + } + return waitForVMState(id, vm.Done, time.Minute*5) + } +} + +// waitForVMState waits until the VM with vmId has the desiredState. +// returns an error and a boolean to indicate success. +// If the timeout is reached or there is an error, success is false +// success is true if the VM has reached the state within the timeout window +func waitForVMState(vmId int, desiredState vm.State, timeout time.Duration) error { + config := testAccProvider.Meta().(*Configuration) + controller := config.Controller + interval := time.NewTicker(5 * time.Second) + deadline := time.NewTimer(timeout) + + // Ensure ticker and timer are stopped after use + defer interval.Stop() + defer deadline.Stop() + + for { + select { + case <-interval.C: + info, err := controller.VM(vmId).Info(false) + if err != nil { + return err + } + if vm.State(info.StateRaw) == desiredState { + return nil + } + case <-deadline.C: + return fmt.Errorf("timeout waiting for vm id '%d' to reach desired state %s\n", vmId, desiredState) + } + } +} + func testAccCheckVirtualMachinePermissions(expected *shared.Permissions) resource.TestCheckFunc { return func(s *terraform.State) error { config := testAccProvider.Meta().(*Configuration) @@ -861,6 +941,32 @@ resource "opennebula_virtual_machine" "test" { } ` +var testAccVirtualMachineDone = ` +resource "opennebula_virtual_machine" "test" { + name = "virtual_machine_done" + group = "oneadmin" + permissions = "642" + memory = 128 + cpu = 0.1 + + context = { + NETWORK = "YES" + SET_HOSTNAME = "$NAME" + } + + graphics { + type = "VNC" + listen = "0.0.0.0" + keymap = "en-us" + } + + os { + arch = "x86_64" + boot = "" + } +} +` + var testAccVirtualMachineTemplateAddvCPU = ` resource "opennebula_virtual_machine" "test" { name = "test-virtual_machine" From 9d2b652ab627bf5fad3b7d02a166dc2f7eb543f1 Mon Sep 17 00:00:00 2001 From: Dorian Jolivald Date: Mon, 22 Jul 2024 17:01:26 +0200 Subject: [PATCH 2/4] Do not terminateHard and fix comment --- opennebula/resource_opennebula_virtual_machine_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/opennebula/resource_opennebula_virtual_machine_test.go b/opennebula/resource_opennebula_virtual_machine_test.go index 70aef1351..a37035aed 100644 --- a/opennebula/resource_opennebula_virtual_machine_test.go +++ b/opennebula/resource_opennebula_virtual_machine_test.go @@ -496,7 +496,7 @@ func testAccTerminateVM(name string) resource.TestCheckFunc { return err } - err = controller.VM(id).TerminateHard() + err = controller.VM(id).Terminate() if err != nil { return err } @@ -505,9 +505,7 @@ func testAccTerminateVM(name string) resource.TestCheckFunc { } // waitForVMState waits until the VM with vmId has the desiredState. -// returns an error and a boolean to indicate success. -// If the timeout is reached or there is an error, success is false -// success is true if the VM has reached the state within the timeout window +// returns an error if timeout is reached. func waitForVMState(vmId int, desiredState vm.State, timeout time.Duration) error { config := testAccProvider.Meta().(*Configuration) controller := config.Controller From 1a437063122886cd43beb9e0a24c76e6718637be Mon Sep 17 00:00:00 2001 From: Dorian jolivald Date: Mon, 29 Jul 2024 16:58:05 +0200 Subject: [PATCH 3/4] allow to add ips to vrouter nics (#2) Co-authored-by: Dorian Jolivald --- opennebula/helpers_vr.go | 7 ++++++ .../resource_opennebula_virtual_router_nic.go | 12 ++++++++++ ...resource_opennebula_virtual_router_test.go | 24 +++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/opennebula/helpers_vr.go b/opennebula/helpers_vr.go index bea7378d6..f8a1a0a26 100644 --- a/opennebula/helpers_vr.go +++ b/opennebula/helpers_vr.go @@ -101,6 +101,13 @@ func vrNICAttach(ctx context.Context, timeout time.Duration, controller *goca.Co updatedNICsLoop: for i, nic := range updatedNICs { + // For VRouter NICs, floating IPs are set using the "IP" field, but it is represented + // as the "VROUTER_IP" field. + if vrouterIP, err := nic.GetStr("VROUTER_IP"); err == nil { + if _, err = nic.GetStr("IP"); err != nil { + nic.Add("IP", vrouterIP) + } + } for _, pair := range nicTpl.Pairs { value, err := nic.GetStr(pair.Key()) diff --git a/opennebula/resource_opennebula_virtual_router_nic.go b/opennebula/resource_opennebula_virtual_router_nic.go index c00941715..8c7d4d073 100644 --- a/opennebula/resource_opennebula_virtual_router_nic.go +++ b/opennebula/resource_opennebula_virtual_router_nic.go @@ -87,6 +87,12 @@ func resourceOpennebulaVirtualRouterNIC() *schema.Resource { Default: false, ForceNew: true, }, + "ip": { + Type: schema.TypeString, + Optional: true, + Default: "", + ForceNew: true, + }, }, } } @@ -137,6 +143,10 @@ func resourceOpennebulaVirtualRouterNICCreate(ctx context.Context, d *schema.Res } } + if v, ok := d.GetOk("ip"); ok { + nicTpl.Add("IP", v.(string)) + } + // wait before checking NIC nicID, err := vrNICAttach(ctx, d.Timeout(schema.TimeoutCreate), controller, vRouterID, nicTpl) if err != nil { @@ -198,6 +208,7 @@ func resourceOpennebulaVirtualRouterNICRead(ctx context.Context, d *schema.Resou network, _ := nic.Get(shared.Network) model, _ := nic.Get(shared.Model) virtioQueues, _ := nic.GetStr("VIRTIO_QUEUES") + ip, _ := nic.Get("VROUTER_IP") sg := make([]int, 0) securityGroupsArray, _ := nic.Get(shared.SecurityGroups) @@ -219,6 +230,7 @@ func resourceOpennebulaVirtualRouterNICRead(ctx context.Context, d *schema.Resou d.Set("security_groups", sg) d.Set("floating_ip", strings.ToUpper(floatingIP) == "YES") d.Set("floating_only", strings.ToUpper(floatingOnly) == "YES") + d.Set("ip", ip) return nil } diff --git a/opennebula/resource_opennebula_virtual_router_test.go b/opennebula/resource_opennebula_virtual_router_test.go index e59745847..b6f9fe08e 100644 --- a/opennebula/resource_opennebula_virtual_router_test.go +++ b/opennebula/resource_opennebula_virtual_router_test.go @@ -119,10 +119,17 @@ func TestAccVirtualRouter(t *testing.T) { resource.TestCheckResourceAttrSet("opennebula_virtual_router.test", "gname"), resource.TestCheckResourceAttrSet("opennebula_virtual_router_nic.nic1", "network_id"), resource.TestCheckResourceAttrSet("opennebula_virtual_router_nic.nic2", "network_id"), + resource.TestCheckResourceAttrSet("opennebula_virtual_router_nic.nic3", "network_id"), + resource.TestCheckResourceAttrSet("opennebula_virtual_router_nic.nic4", "network_id"), resource.TestCheckResourceAttr("opennebula_virtual_router_nic.nic1", "floating_ip", "true"), resource.TestCheckResourceAttr("opennebula_virtual_router_nic.nic1", "floating_only", "true"), + resource.TestCheckResourceAttr("opennebula_virtual_router_nic.nic1", "ip", ""), resource.TestCheckResourceAttr("opennebula_virtual_router_nic.nic2", "floating_ip", "false"), resource.TestCheckResourceAttr("opennebula_virtual_router_nic.nic2", "floating_only", "false"), + resource.TestCheckResourceAttr("opennebula_virtual_router_nic.nic2", "ip", ""), + resource.TestCheckResourceAttr("opennebula_virtual_router_nic.nic3", "floating_ip", "false"), + resource.TestCheckResourceAttr("opennebula_virtual_router_nic.nic3", "floating_only", "false"), + resource.TestCheckResourceAttr("opennebula_virtual_router_nic.nic3", "ip", "10.0.0.1"), testAccCheckVirtualRouterPermissions(&shared.Permissions{ OwnerU: 1, OwnerM: 1, @@ -148,6 +155,9 @@ func TestAccVirtualRouter(t *testing.T) { resource.TestCheckResourceAttr("opennebula_virtual_router_nic.nic1", "floating_only", "false"), resource.TestCheckResourceAttr("opennebula_virtual_router_nic.nic2", "floating_ip", "false"), resource.TestCheckResourceAttr("opennebula_virtual_router_nic.nic2", "floating_only", "false"), + resource.TestCheckResourceAttr("opennebula_virtual_router_nic.nic3", "floating_ip", "yes"), + resource.TestCheckResourceAttr("opennebula_virtual_router_nic.nic3", "floating_only", "false"), + resource.TestCheckResourceAttr("opennebula_virtual_router_nic.nic3", "ip", "10.0.0.2"), testAccCheckVirtualRouterPermissions(&shared.Permissions{ OwnerU: 1, OwnerM: 1, @@ -473,6 +483,12 @@ resource "opennebula_virtual_router_nic" "nic1" { virtual_router_id = opennebula_virtual_router.test.id network_id = opennebula_virtual_network.network1.id } + +resource "opennebula_virtual_router_nic" "nic3" { + ip = "10.0.0.1" + virtual_router_id = opennebula_virtual_router.test.id + network_id = opennebula_virtual_network.network1.id +} ` var testAccVirtualRouterUpdateNICs = testAccVirtualRouterMachineTemplate + testAccVirtualRouterVNet + ` @@ -519,4 +535,12 @@ resource "opennebula_virtual_router_nic" "nic1" { virtual_router_id = opennebula_virtual_router.test.id network_id = opennebula_virtual_network.network1.id } + + +resource "opennebula_virtual_router_nic" "nic3" { + floating_ip = true + ip = "10.0.0.2" + virtual_router_id = opennebula_virtual_router.test.id + network_id = opennebula_virtual_network.network1.id +} ` From 73245fa0728dfce947363a3d6ca2fc8873480668 Mon Sep 17 00:00:00 2001 From: Dorian jolivald Date: Wed, 31 Jul 2024 16:47:09 +0200 Subject: [PATCH 4/4] Update release.yml to fix breaking gorelease version --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6b8e41bcc..48e30c15d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,7 +40,7 @@ jobs: name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 with: - version: latest + version: 1.26.2 args: release --rm-dist env: GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}