wait for asg to move to disabled state before making modifications to lb rule associated to an asg or autoscale vm profile
diff --git a/cloudstack/resource_cloudstack_autoscale_vm_profile.go b/cloudstack/resource_cloudstack_autoscale_vm_profile.go
index 5c2cf4c..0032b92 100644
--- a/cloudstack/resource_cloudstack_autoscale_vm_profile.go
+++ b/cloudstack/resource_cloudstack_autoscale_vm_profile.go
@@ -278,7 +278,9 @@
}
if p.Userdatadetails != "" {
- d.Set("user_data_details", map[string]interface{}{})
+ if _, ok := d.GetOk("user_data_details"); !ok {
+ d.Set("user_data_details", map[string]interface{}{})
+ }
}
if p.Autoscaleuserid != "" {
@@ -308,85 +310,185 @@
return nil
}
+// waitForVMGroupsState waits for the specified VM groups to reach the desired state
+func waitForVMGroupsState(cs *cloudstack.CloudStackClient, groupIDs []string, desiredState string) error {
+ maxRetries := 30 // 30 * 2 seconds = 60 seconds max wait
+ for i := 0; i < maxRetries; i++ {
+ allInDesiredState := true
+
+ for _, groupID := range groupIDs {
+ group, _, err := cs.AutoScale.GetAutoScaleVmGroupByID(groupID)
+ if err != nil {
+ return fmt.Errorf("Error checking state of VM group %s: %s", groupID, err)
+ }
+
+ groupInDesiredState := false
+ if desiredState == "disabled" {
+ groupInDesiredState = (group.State == "disabled")
+ } else if desiredState == "enabled" {
+ groupInDesiredState = (group.State == "enabled")
+ } else {
+ groupInDesiredState = (group.State == desiredState)
+ }
+
+ if !groupInDesiredState {
+ allInDesiredState = false
+ log.Printf("[DEBUG] VM group %s is in state '%s', waiting for '%s'", groupID, group.State, desiredState)
+ break
+ }
+ }
+
+ if allInDesiredState {
+ log.Printf("[INFO] All VM groups have reached desired state: %s", desiredState)
+ return nil
+ }
+
+ if i < maxRetries-1 {
+ log.Printf("[INFO] Waiting for VM groups to reach state '%s' (attempt %d/%d)", desiredState, i+1, maxRetries)
+ time.Sleep(2 * time.Second)
+ }
+ }
+
+ return fmt.Errorf("Timeout waiting for VM groups to reach state '%s' after %d seconds", desiredState, maxRetries*2)
+}
+
+func waitForVMGroupsToBeDisabled(cs *cloudstack.CloudStackClient, profileID string) error {
+ log.Printf("[DEBUG] Waiting for VM groups using profile %s to be disabled", profileID)
+ listParams := cs.AutoScale.NewListAutoScaleVmGroupsParams()
+ listParams.SetVmprofileid(profileID)
+
+ groups, err := cs.AutoScale.ListAutoScaleVmGroups(listParams)
+ if err != nil {
+ log.Printf("[ERROR] Failed to list VM groups for profile %s: %s", profileID, err)
+ return fmt.Errorf("Error listing autoscale VM groups: %s", err)
+ }
+
+ log.Printf("[DEBUG] Found %d VM groups using profile %s", len(groups.AutoScaleVmGroups), profileID)
+
+ var groupIDs []string
+ for _, group := range groups.AutoScaleVmGroups {
+ log.Printf("[DEBUG] VM group %s (%s) current state: %s", group.Name, group.Id, group.State)
+ groupIDs = append(groupIDs, group.Id)
+ }
+
+ if len(groupIDs) == 0 {
+ log.Printf("[DEBUG] No VM groups found using profile %s", profileID)
+ return nil
+ }
+
+ log.Printf("[INFO] Waiting for %d VM groups to be disabled for profile update", len(groupIDs))
+ if err := waitForVMGroupsState(cs, groupIDs, "disabled"); err != nil {
+ return fmt.Errorf("Autoscale VM groups must be disabled before updating profile: %s", err)
+ }
+
+ log.Printf("[DEBUG] All VM groups are now disabled for profile %s", profileID)
+ return nil
+}
+
func resourceCloudStackAutoScaleVMProfileUpdate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
-
- p := cs.AutoScale.NewUpdateAutoScaleVmProfileParams(d.Id())
-
- if d.HasChange("template") {
- zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string))
- if e != nil {
- return e.Error()
+ log.Printf("[DEBUG] Profile update requested for ID: %s", d.Id())
+ for _, key := range []string{"template", "destroy_vm_grace_period", "counter_param_list", "user_data", "user_data_id", "user_data_details", "autoscale_user_id", "display", "metadata"} {
+ if d.HasChange(key) {
+ old, new := d.GetChange(key)
+ log.Printf("[DEBUG] Field '%s' changed from %v to %v", key, old, new)
}
- templateid, e := retrieveTemplateID(cs, zoneid, d.Get("template").(string))
- if e != nil {
- return e.Error()
- }
- p.SetTemplateid(templateid)
}
- if d.HasChange("destroy_vm_grace_period") {
- if v, ok := d.GetOk("destroy_vm_grace_period"); ok {
- duration, err := time.ParseDuration(v.(string))
- if err != nil {
- return err
+ // Check if we only have metadata changes (which don't require CloudStack API update)
+ onlyMetadataChanges := d.HasChange("metadata") &&
+ !d.HasChange("template") &&
+ !d.HasChange("destroy_vm_grace_period") &&
+ !d.HasChange("counter_param_list") &&
+ !d.HasChange("user_data") &&
+ !d.HasChange("user_data_id") &&
+ !d.HasChange("user_data_details") &&
+ !d.HasChange("autoscale_user_id") &&
+ !d.HasChange("display")
+
+ if !onlyMetadataChanges {
+ if err := waitForVMGroupsToBeDisabled(cs, d.Id()); err != nil {
+ return fmt.Errorf("Autoscale VM groups must be disabled before updating profile: %s", err)
+ }
+
+ p := cs.AutoScale.NewUpdateAutoScaleVmProfileParams(d.Id())
+
+ if d.HasChange("template") {
+ zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string))
+ if e != nil {
+ return e.Error()
}
- p.SetExpungevmgraceperiod(int(duration.Seconds()))
- }
- }
-
- if d.HasChange("counter_param_list") {
- if v, ok := d.GetOk("counter_param_list"); ok {
- nv := make(map[string]string)
- for k, v := range v.(map[string]interface{}) {
- nv[k] = v.(string)
+ templateid, e := retrieveTemplateID(cs, zoneid, d.Get("template").(string))
+ if e != nil {
+ return e.Error()
}
- p.SetCounterparam(nv)
+ p.SetTemplateid(templateid)
}
- }
- if d.HasChange("user_data") {
- if v, ok := d.GetOk("user_data"); ok {
- p.SetUserdata(v.(string))
- }
- }
-
- if d.HasChange("user_data_id") {
- if v, ok := d.GetOk("user_data_id"); ok {
- p.SetUserdataid(v.(string))
- }
- }
-
- if d.HasChange("user_data_details") {
- if v, ok := d.GetOk("user_data_details"); ok {
- nv := make(map[string]string)
- for k, v := range v.(map[string]interface{}) {
- nv[k] = v.(string)
+ if d.HasChange("destroy_vm_grace_period") {
+ if v, ok := d.GetOk("destroy_vm_grace_period"); ok {
+ duration, err := time.ParseDuration(v.(string))
+ if err != nil {
+ return err
+ }
+ p.SetExpungevmgraceperiod(int(duration.Seconds()))
}
- p.SetUserdatadetails(nv)
}
- }
- if d.HasChange("autoscale_user_id") {
- if v, ok := d.GetOk("autoscale_user_id"); ok {
- p.SetAutoscaleuserid(v.(string))
+ if d.HasChange("counter_param_list") {
+ if v, ok := d.GetOk("counter_param_list"); ok {
+ nv := make(map[string]string)
+ for k, v := range v.(map[string]interface{}) {
+ nv[k] = v.(string)
+ }
+ p.SetCounterparam(nv)
+ }
}
- }
- if d.HasChange("display") {
- if v, ok := d.GetOk("display"); ok {
- p.SetFordisplay(v.(bool))
+ if d.HasChange("user_data") {
+ if v, ok := d.GetOk("user_data"); ok {
+ p.SetUserdata(v.(string))
+ }
}
- }
- _, err := cs.AutoScale.UpdateAutoScaleVmProfile(p)
- if err != nil {
- return fmt.Errorf("Error updating AutoScaleVmProfile %s: %s", d.Id(), err)
+ if d.HasChange("user_data_id") {
+ if v, ok := d.GetOk("user_data_id"); ok {
+ p.SetUserdataid(v.(string))
+ }
+ }
+
+ if d.HasChange("user_data_details") {
+ if v, ok := d.GetOk("user_data_details"); ok {
+ nv := make(map[string]string)
+ for k, v := range v.(map[string]interface{}) {
+ nv[k] = v.(string)
+ }
+ p.SetUserdatadetails(nv)
+ }
+ }
+
+ if d.HasChange("autoscale_user_id") {
+ if v, ok := d.GetOk("autoscale_user_id"); ok {
+ p.SetAutoscaleuserid(v.(string))
+ }
+ }
+
+ if d.HasChange("display") {
+ if v, ok := d.GetOk("display"); ok {
+ p.SetFordisplay(v.(bool))
+ }
+ }
+
+ log.Printf("[DEBUG] Performing CloudStack API update for profile %s", d.Id())
+ _, updateErr := cs.AutoScale.UpdateAutoScaleVmProfile(p)
+ if updateErr != nil {
+ return fmt.Errorf("Error updating AutoScaleVmProfile %s: %s", d.Id(), updateErr)
+ }
}
if d.HasChange("metadata") {
- if err := updateMetadata(cs, d, "AutoScaleVmProfile"); err != nil {
- return fmt.Errorf("Error updating tags on AutoScaleVmProfile %s: %s", d.Id(), err)
+ if metadataErr := updateMetadata(cs, d, "AutoScaleVmProfile"); metadataErr != nil {
+ return fmt.Errorf("Error updating tags on AutoScaleVmProfile %s: %s", d.Id(), metadataErr)
}
}
diff --git a/cloudstack/resource_cloudstack_loadbalancer_rule.go b/cloudstack/resource_cloudstack_loadbalancer_rule.go
index 5c5de86..c31c861 100644
--- a/cloudstack/resource_cloudstack_loadbalancer_rule.go
+++ b/cloudstack/resource_cloudstack_loadbalancer_rule.go
@@ -25,6 +25,7 @@
"regexp"
"strconv"
"strings"
+ "time"
"github.com/apache/cloudstack-go/v2/cloudstack"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -258,11 +259,73 @@
for _, i := range l.LoadBalancerRuleInstances {
mbs = append(mbs, i.Id)
}
+
+ asgCheckParams := cs.AutoScale.NewListAutoScaleVmGroupsParams()
+ asgCheckParams.SetLbruleid(d.Id())
+
+ asgGroups, err := cs.AutoScale.ListAutoScaleVmGroups(asgCheckParams)
+ if err != nil {
+ log.Printf("[WARN] Failed to check for autoscale VM groups during read: %s", err)
+ }
+
+ if len(asgGroups.AutoScaleVmGroups) > 0 {
+ log.Printf("[DEBUG] Load balancer rule %s is managed by %d autoscale VM group(s), current members: %v",
+ d.Id(), len(asgGroups.AutoScaleVmGroups), mbs)
+
+ if currentMemberIds, ok := d.GetOk("member_ids"); ok {
+ currentSet := currentMemberIds.(*schema.Set)
+ if currentSet.Len() == 0 && len(mbs) > 0 {
+ d.Set("member_ids", []string{})
+ return nil
+ }
+ }
+ }
+
d.Set("member_ids", mbs)
return nil
}
+func waitForASGsToBeDisabled(cs *cloudstack.CloudStackClient, lbRuleID string) error {
+ log.Printf("[DEBUG] Waiting for autoscale VM groups using load balancer rule %s to be disabled", lbRuleID)
+
+ maxRetries := 60 // 60 * 2 seconds = 120 seconds max wait (longer for Terraform-driven changes)
+ for i := 0; i < maxRetries; i++ {
+ listParams := cs.AutoScale.NewListAutoScaleVmGroupsParams()
+ listParams.SetLbruleid(lbRuleID)
+
+ groups, err := cs.AutoScale.ListAutoScaleVmGroups(listParams)
+ if err != nil {
+ log.Printf("[WARN] Failed to list autoscale VM groups: %s", err)
+ time.Sleep(2 * time.Second)
+ continue
+ }
+
+ allDisabled := true
+ var enabledGroups []string
+
+ for _, group := range groups.AutoScaleVmGroups {
+ if group.State != "disabled" && group.State != "disable" {
+ allDisabled = false
+ enabledGroups = append(enabledGroups, fmt.Sprintf("%s(%s:%s)", group.Name, group.Id, group.State))
+ }
+ }
+
+ if allDisabled {
+ log.Printf("[INFO] All autoscale VM groups using load balancer rule %s are now disabled", lbRuleID)
+ return nil
+ }
+
+ if i < maxRetries-1 {
+ log.Printf("[DEBUG] Waiting for autoscale VM groups to be disabled (attempt %d/%d). Groups still enabled: %v",
+ i+1, maxRetries, enabledGroups)
+ time.Sleep(2 * time.Second)
+ }
+ }
+
+ return fmt.Errorf("Timeout waiting for autoscale VM groups to be disabled after %d seconds", maxRetries*2)
+}
+
func resourceCloudStackLoadBalancerRuleUpdate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
@@ -324,35 +387,126 @@
}
if d.HasChange("member_ids") {
- o, n := d.GetChange("member_ids")
- ombs, nmbs := o.(*schema.Set), n.(*schema.Set)
+ log.Printf("[DEBUG] Load balancer rule %s member_ids change detected", d.Id())
- setToStringList := func(s *schema.Set) []string {
- l := make([]string, s.Len())
- for i, v := range s.List() {
- l[i] = v.(string)
- }
- return l
+ asgCheckParams := cs.AutoScale.NewListAutoScaleVmGroupsParams()
+ asgCheckParams.SetLbruleid(d.Id())
+
+ asgGroups, err := cs.AutoScale.ListAutoScaleVmGroups(asgCheckParams)
+ if err != nil {
+ log.Printf("[WARN] Failed to check for autoscale VM groups: %s", err)
}
- membersToAdd := setToStringList(nmbs.Difference(ombs))
- membersToRemove := setToStringList(ombs.Difference(nmbs))
+ if len(asgGroups.AutoScaleVmGroups) > 0 {
+ log.Printf("[INFO] Load balancer rule %s is managed by %d autoscale VM group(s), handling member updates carefully",
+ d.Id(), len(asgGroups.AutoScaleVmGroups))
- log.Printf("[DEBUG] Members to add: %v, remove: %v", membersToAdd, membersToRemove)
+ o, n := d.GetChange("member_ids")
+ ombs, nmbs := o.(*schema.Set), n.(*schema.Set)
- if len(membersToAdd) > 0 {
- p := cs.LoadBalancer.NewAssignToLoadBalancerRuleParams(d.Id())
- p.SetVirtualmachineids(membersToAdd)
- if _, err := cs.LoadBalancer.AssignToLoadBalancerRule(p); err != nil {
- return err
+ setToStringList := func(s *schema.Set) []string {
+ l := make([]string, s.Len())
+ for i, v := range s.List() {
+ l[i] = v.(string)
+ }
+ return l
}
- }
- if len(membersToRemove) > 0 {
- p := cs.LoadBalancer.NewRemoveFromLoadBalancerRuleParams(d.Id())
- p.SetVirtualmachineids(membersToRemove)
- if _, err := cs.LoadBalancer.RemoveFromLoadBalancerRule(p); err != nil {
- return err
+ oldMembers := setToStringList(ombs)
+ newMembers := setToStringList(nmbs)
+
+ log.Printf("[DEBUG] Terraform state - old members: %v, new members: %v", oldMembers, newMembers)
+
+ p := cs.LoadBalancer.NewListLoadBalancerRuleInstancesParams(d.Id())
+ currentInstances, err := cs.LoadBalancer.ListLoadBalancerRuleInstances(p)
+ if err != nil {
+ return fmt.Errorf("Error listing current load balancer members: %s", err)
+ }
+
+ var currentMembers []string
+ for _, i := range currentInstances.LoadBalancerRuleInstances {
+ currentMembers = append(currentMembers, i.Id)
+ }
+
+ log.Printf("[DEBUG] CloudStack actual members: %v", currentMembers)
+
+ // If Terraform state is empty but CloudStack has members, it means autoscale is managing them
+ if len(oldMembers) == 0 && len(currentMembers) > 0 {
+ log.Printf("[INFO] Detected autoscale-managed members in load balancer. Skipping member updates to avoid conflicts.")
+ log.Printf("[INFO] Autoscale VM groups will manage the member lifecycle automatically.")
+
+ d.Set("member_ids", currentMembers)
+ return resourceCloudStackLoadBalancerRuleRead(d, meta)
+ }
+
+ if len(newMembers) > 0 {
+ log.Printf("[WARN] Explicit member_ids specified for autoscale-managed load balancer. This may conflict with autoscale operations.")
+
+ if err := waitForASGsToBeDisabled(cs, d.Id()); err != nil {
+ return fmt.Errorf("Autoscale VM groups must be disabled before modifying load balancer members: %s", err)
+ }
+
+ membersToAdd := setToStringList(nmbs.Difference(ombs))
+ membersToRemove := setToStringList(ombs.Difference(nmbs))
+
+ log.Printf("[DEBUG] Explicit member changes - to add: %v, to remove: %v", membersToAdd, membersToRemove)
+
+ if len(membersToRemove) > 0 {
+ log.Printf("[DEBUG] Removing %d explicit members from load balancer rule %s", len(membersToRemove), d.Id())
+ removeParams := cs.LoadBalancer.NewRemoveFromLoadBalancerRuleParams(d.Id())
+ removeParams.SetVirtualmachineids(membersToRemove)
+ if _, err := cs.LoadBalancer.RemoveFromLoadBalancerRule(removeParams); err != nil {
+ return fmt.Errorf("Error removing explicit members from load balancer rule %s: %s. Members: %v", d.Id(), err, membersToRemove)
+ }
+ }
+
+ if len(membersToAdd) > 0 {
+ log.Printf("[DEBUG] Adding %d explicit members to load balancer rule %s", len(membersToAdd), d.Id())
+ addParams := cs.LoadBalancer.NewAssignToLoadBalancerRuleParams(d.Id())
+ addParams.SetVirtualmachineids(membersToAdd)
+ if _, err := cs.LoadBalancer.AssignToLoadBalancerRule(addParams); err != nil {
+ return fmt.Errorf("Error adding explicit members to load balancer rule %s: %s. Members: %v", d.Id(), err, membersToAdd)
+ }
+ }
+ }
+ } else {
+ // No autoscale groups, proceed with normal member management
+ log.Printf("[DEBUG] No autoscale groups found, proceeding with normal member management")
+
+ o, n := d.GetChange("member_ids")
+ ombs, nmbs := o.(*schema.Set), n.(*schema.Set)
+
+ setToStringList := func(s *schema.Set) []string {
+ l := make([]string, s.Len())
+ for i, v := range s.List() {
+ l[i] = v.(string)
+ }
+ return l
+ }
+
+ membersToAdd := setToStringList(nmbs.Difference(ombs))
+ membersToRemove := setToStringList(ombs.Difference(nmbs))
+
+ log.Printf("[DEBUG] Members to add: %v, remove: %v", membersToAdd, membersToRemove)
+
+ if len(membersToRemove) > 0 {
+ log.Printf("[DEBUG] Removing %d members from load balancer rule %s", len(membersToRemove), d.Id())
+ p := cs.LoadBalancer.NewRemoveFromLoadBalancerRuleParams(d.Id())
+ p.SetVirtualmachineids(membersToRemove)
+ if _, err := cs.LoadBalancer.RemoveFromLoadBalancerRule(p); err != nil {
+ return fmt.Errorf("Error removing members from load balancer rule %s: %s. Members to remove: %v", d.Id(), err, membersToRemove)
+ }
+ log.Printf("[DEBUG] Successfully removed members from load balancer rule")
+ }
+
+ if len(membersToAdd) > 0 {
+ log.Printf("[DEBUG] Adding %d members to load balancer rule %s", len(membersToAdd), d.Id())
+ p := cs.LoadBalancer.NewAssignToLoadBalancerRuleParams(d.Id())
+ p.SetVirtualmachineids(membersToAdd)
+ if _, err := cs.LoadBalancer.AssignToLoadBalancerRule(p); err != nil {
+ return fmt.Errorf("Error adding members to load balancer rule %s: %s. Members to add: %v", d.Id(), err, membersToAdd)
+ }
+ log.Printf("[DEBUG] Successfully added members to load balancer rule")
}
}
}