diff --git a/cloudstack/data_source_cloudstack_autoscale_policy.go b/cloudstack/data_source_cloudstack_autoscale_policy.go new file mode 100644 index 00000000..c8bea3c9 --- /dev/null +++ b/cloudstack/data_source_cloudstack_autoscale_policy.go @@ -0,0 +1,152 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceCloudstackAutoscalePolicy() *schema.Resource { + return &schema.Resource{ + Read: dataSourceCloudstackAutoscalePolicyRead, + + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "action": { + Type: schema.TypeString, + Computed: true, + }, + + "duration": { + Type: schema.TypeInt, + Computed: true, + }, + + "quiet_time": { + Type: schema.TypeInt, + Computed: true, + }, + + "condition_ids": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "account_name": { + Type: schema.TypeString, + Computed: true, + }, + + "domain_id": { + Type: schema.TypeString, + Computed: true, + }, + + "project_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceCloudstackAutoscalePolicyRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + id, idOk := d.GetOk("id") + name, nameOk := d.GetOk("name") + + if !idOk && !nameOk { + return fmt.Errorf("either 'id' or 'name' must be specified") + } + + var policy *cloudstack.AutoScalePolicy + + if idOk { + p := cs.AutoScale.NewListAutoScalePoliciesParams() + p.SetId(id.(string)) + + resp, err := cs.AutoScale.ListAutoScalePolicies(p) + if err != nil { + return fmt.Errorf("failed to list autoscale policies: %s", err) + } + + if resp.Count == 0 { + return fmt.Errorf("autoscale policy with ID %s not found", id.(string)) + } + + policy = resp.AutoScalePolicies[0] + } else { + p := cs.AutoScale.NewListAutoScalePoliciesParams() + + resp, err := cs.AutoScale.ListAutoScalePolicies(p) + if err != nil { + return fmt.Errorf("failed to list autoscale policies: %s", err) + } + + for _, pol := range resp.AutoScalePolicies { + if pol.Name == name.(string) { + policy = pol + break + } + } + + if policy == nil { + return fmt.Errorf("autoscale policy with name %s not found", name.(string)) + } + } + + log.Printf("[DEBUG] Found autoscale policy: %s", policy.Name) + + d.SetId(policy.Id) + d.Set("name", policy.Name) + d.Set("action", policy.Action) + d.Set("duration", policy.Duration) + d.Set("quiet_time", policy.Quiettime) + d.Set("account_name", policy.Account) + d.Set("domain_id", policy.Domainid) + if policy.Projectid != "" { + d.Set("project_id", policy.Projectid) + } + + conditionIds := make([]string, len(policy.Conditions)) + for i, condition := range policy.Conditions { + conditionIds[i] = condition.Id + } + d.Set("condition_ids", conditionIds) + + return nil +} diff --git a/cloudstack/data_source_cloudstack_autoscale_vm_group.go b/cloudstack/data_source_cloudstack_autoscale_vm_group.go new file mode 100644 index 00000000..135cd4c3 --- /dev/null +++ b/cloudstack/data_source_cloudstack_autoscale_vm_group.go @@ -0,0 +1,188 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceCloudstackAutoscaleVMGroup() *schema.Resource { + return &schema.Resource{ + Read: dataSourceCloudstackAutoscaleVMGroupRead, + + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "lbrule_id": { + Type: schema.TypeString, + Computed: true, + }, + + "min_members": { + Type: schema.TypeInt, + Computed: true, + }, + + "max_members": { + Type: schema.TypeInt, + Computed: true, + }, + + "vm_profile_id": { + Type: schema.TypeString, + Computed: true, + }, + + "scaleup_policy_ids": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "scaledown_policy_ids": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "state": { + Type: schema.TypeString, + Computed: true, + }, + + "interval": { + Type: schema.TypeInt, + Computed: true, + }, + + "available_virtual_machine_count": { + Type: schema.TypeInt, + Computed: true, + }, + + "account_name": { + Type: schema.TypeString, + Computed: true, + }, + + "domain_id": { + Type: schema.TypeString, + Computed: true, + }, + + "project_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceCloudstackAutoscaleVMGroupRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + id, idOk := d.GetOk("id") + name, nameOk := d.GetOk("name") + + if !idOk && !nameOk { + return fmt.Errorf("either 'id' or 'name' must be specified") + } + + var group *cloudstack.AutoScaleVmGroup + + if idOk { + p := cs.AutoScale.NewListAutoScaleVmGroupsParams() + p.SetId(id.(string)) + + resp, err := cs.AutoScale.ListAutoScaleVmGroups(p) + if err != nil { + return fmt.Errorf("failed to list autoscale VM groups: %s", err) + } + + if resp.Count == 0 { + return fmt.Errorf("autoscale VM group with ID %s not found", id.(string)) + } + + group = resp.AutoScaleVmGroups[0] + } else { + p := cs.AutoScale.NewListAutoScaleVmGroupsParams() + + resp, err := cs.AutoScale.ListAutoScaleVmGroups(p) + if err != nil { + return fmt.Errorf("failed to list autoscale VM groups: %s", err) + } + + for _, grp := range resp.AutoScaleVmGroups { + if grp.Name == name.(string) { + group = grp + break + } + } + + if group == nil { + return fmt.Errorf("autoscale VM group with name %s not found", name.(string)) + } + } + + log.Printf("[DEBUG] Found autoscale VM group: %s", group.Name) + + d.SetId(group.Id) + d.Set("name", group.Name) + d.Set("lbrule_id", group.Lbruleid) + d.Set("min_members", group.Minmembers) + d.Set("max_members", group.Maxmembers) + d.Set("vm_profile_id", group.Vmprofileid) + d.Set("state", group.State) + d.Set("interval", group.Interval) + d.Set("available_virtual_machine_count", group.Availablevirtualmachinecount) + d.Set("account_name", group.Account) + d.Set("domain_id", group.Domainid) + if group.Projectid != "" { + d.Set("project_id", group.Projectid) + } + + scaleupPolicyIds := make([]string, len(group.Scaleuppolicies)) + for i, policy := range group.Scaleuppolicies { + scaleupPolicyIds[i] = policy.Id + } + d.Set("scaleup_policy_ids", scaleupPolicyIds) + + scaledownPolicyIds := make([]string, len(group.Scaledownpolicies)) + for i, policy := range group.Scaledownpolicies { + scaledownPolicyIds[i] = policy.Id + } + d.Set("scaledown_policy_ids", scaledownPolicyIds) + + return nil +} diff --git a/cloudstack/data_source_cloudstack_autoscale_vm_profile.go b/cloudstack/data_source_cloudstack_autoscale_vm_profile.go new file mode 100644 index 00000000..47d38c03 --- /dev/null +++ b/cloudstack/data_source_cloudstack_autoscale_vm_profile.go @@ -0,0 +1,148 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceCloudstackAutoscaleVMProfile() *schema.Resource { + return &schema.Resource{ + Read: dataSourceCloudstackAutoscaleVMProfileRead, + + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "service_offering": { + Type: schema.TypeString, + Computed: true, + }, + + "template": { + Type: schema.TypeString, + Computed: true, + }, + + "zone": { + Type: schema.TypeString, + Computed: true, + }, + + "destroy_vm_grace_period": { + Type: schema.TypeString, + Computed: true, + }, + + "counter_param_list": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "user_data": { + Type: schema.TypeString, + Computed: true, + }, + + "user_data_details": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "account_name": { + Type: schema.TypeString, + Computed: true, + }, + + "domain_id": { + Type: schema.TypeString, + Computed: true, + }, + + "project_id": { + Type: schema.TypeString, + Computed: true, + }, + + "display": { + Type: schema.TypeBool, + Computed: true, + }, + + "other_deploy_params": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceCloudstackAutoscaleVMProfileRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + id, idOk := d.GetOk("id") + + if !idOk { + return fmt.Errorf("'id' must be specified") + } + + p := cs.AutoScale.NewListAutoScaleVmProfilesParams() + p.SetId(id.(string)) + + resp, err := cs.AutoScale.ListAutoScaleVmProfiles(p) + if err != nil { + return fmt.Errorf("failed to list autoscale VM profiles: %s", err) + } + + if resp.Count == 0 { + return fmt.Errorf("autoscale VM profile with ID %s not found", id.(string)) + } + + profile := resp.AutoScaleVmProfiles[0] + + log.Printf("[DEBUG] Found autoscale VM profile: %s", profile.Id) + + d.SetId(profile.Id) + d.Set("service_offering", profile.Serviceofferingid) + d.Set("template", profile.Templateid) + d.Set("zone", profile.Zoneid) + d.Set("account_name", profile.Account) + d.Set("domain_id", profile.Domainid) + if profile.Projectid != "" { + d.Set("project_id", profile.Projectid) + } + d.Set("display", profile.Fordisplay) + + if profile.Userdata != "" { + d.Set("user_data", profile.Userdata) + } + + return nil +} diff --git a/cloudstack/data_source_cloudstack_condition.go b/cloudstack/data_source_cloudstack_condition.go new file mode 100644 index 00000000..d901f286 --- /dev/null +++ b/cloudstack/data_source_cloudstack_condition.go @@ -0,0 +1,110 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceCloudstackCondition() *schema.Resource { + return &schema.Resource{ + Read: dataSourceCloudstackConditionRead, + + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "counter_id": { + Type: schema.TypeString, + Computed: true, + }, + + "relational_operator": { + Type: schema.TypeString, + Computed: true, + }, + + "threshold": { + Type: schema.TypeFloat, + Computed: true, + }, + + "account_name": { + Type: schema.TypeString, + Computed: true, + }, + + "domain_id": { + Type: schema.TypeString, + Computed: true, + }, + + "project_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceCloudstackConditionRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + id, idOk := d.GetOk("id") + + if !idOk { + return fmt.Errorf("'id' must be specified") + } + + p := cs.AutoScale.NewListConditionsParams() + p.SetId(id.(string)) + + resp, err := cs.AutoScale.ListConditions(p) + if err != nil { + return fmt.Errorf("failed to list conditions: %s", err) + } + + if resp.Count == 0 { + return fmt.Errorf("condition with ID %s not found", id.(string)) + } + + condition := resp.Conditions[0] + + log.Printf("[DEBUG] Found condition: %s", condition.Id) + + d.SetId(condition.Id) + d.Set("counter_id", condition.Counterid) + d.Set("relational_operator", condition.Relationaloperator) + d.Set("threshold", condition.Threshold) + d.Set("account_name", condition.Account) + d.Set("domain_id", condition.Domainid) + if condition.Projectid != "" { + d.Set("project_id", condition.Projectid) + } + + return nil +} diff --git a/cloudstack/data_source_cloudstack_counter.go b/cloudstack/data_source_cloudstack_counter.go new file mode 100644 index 00000000..dab1e44a --- /dev/null +++ b/cloudstack/data_source_cloudstack_counter.go @@ -0,0 +1,122 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceCloudstackCounter() *schema.Resource { + return &schema.Resource{ + Read: dataSourceCloudstackCounterRead, + + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "source": { + Type: schema.TypeString, + Computed: true, + }, + + "value": { + Type: schema.TypeString, + Computed: true, + }, + + "counter_provider": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceCloudstackCounterRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + id, idOk := d.GetOk("id") + name, nameOk := d.GetOk("name") + + if !idOk && !nameOk { + return fmt.Errorf("either 'id' or 'name' must be specified") + } + + var counter *cloudstack.Counter + + if idOk { + // Get counter by ID + p := cs.AutoScale.NewListCountersParams() + p.SetId(id.(string)) + + resp, err := cs.AutoScale.ListCounters(p) + if err != nil { + return fmt.Errorf("failed to list counters: %s", err) + } + + if resp.Count == 0 { + return fmt.Errorf("counter with ID %s not found", id.(string)) + } + + counter = resp.Counters[0] + } else { + // Get counter by name + p := cs.AutoScale.NewListCountersParams() + + resp, err := cs.AutoScale.ListCounters(p) + if err != nil { + return fmt.Errorf("failed to list counters: %s", err) + } + + for _, c := range resp.Counters { + if c.Name == name.(string) { + counter = c + break + } + } + + if counter == nil { + return fmt.Errorf("counter with name %s not found", name.(string)) + } + } + + log.Printf("[DEBUG] Found counter: %s", counter.Name) + + d.SetId(counter.Id) + d.Set("name", counter.Name) + d.Set("source", counter.Source) + d.Set("value", counter.Value) + d.Set("counter_provider", counter.Provider) + + return nil +} diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 3b8c4415..950345f4 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -78,31 +78,40 @@ func Provider() *schema.Provider { }, DataSourcesMap: map[string]*schema.Resource{ - "cloudstack_template": dataSourceCloudstackTemplate(), - "cloudstack_ssh_keypair": dataSourceCloudstackSSHKeyPair(), - "cloudstack_instance": dataSourceCloudstackInstance(), - "cloudstack_network_offering": dataSourceCloudstackNetworkOffering(), - "cloudstack_zone": dataSourceCloudStackZone(), - "cloudstack_service_offering": dataSourceCloudstackServiceOffering(), - "cloudstack_volume": dataSourceCloudstackVolume(), - "cloudstack_vpc": dataSourceCloudstackVPC(), - "cloudstack_ipaddress": dataSourceCloudstackIPAddress(), - "cloudstack_user": dataSourceCloudstackUser(), - "cloudstack_vpn_connection": dataSourceCloudstackVPNConnection(), - "cloudstack_pod": dataSourceCloudstackPod(), - "cloudstack_domain": dataSourceCloudstackDomain(), - "cloudstack_project": dataSourceCloudstackProject(), - "cloudstack_physical_network": dataSourceCloudStackPhysicalNetwork(), - "cloudstack_role": dataSourceCloudstackRole(), - "cloudstack_cluster": dataSourceCloudstackCluster(), - "cloudstack_limits": dataSourceCloudStackLimits(), + "cloudstack_autoscale_policy": dataSourceCloudstackAutoscalePolicy(), + "cloudstack_autoscale_vm_group": dataSourceCloudstackAutoscaleVMGroup(), + "cloudstack_autoscale_vm_profile": dataSourceCloudstackAutoscaleVMProfile(), + "cloudstack_condition": dataSourceCloudstackCondition(), + "cloudstack_counter": dataSourceCloudstackCounter(), + "cloudstack_template": dataSourceCloudstackTemplate(), + "cloudstack_ssh_keypair": dataSourceCloudstackSSHKeyPair(), + "cloudstack_instance": dataSourceCloudstackInstance(), + "cloudstack_network_offering": dataSourceCloudstackNetworkOffering(), + "cloudstack_zone": dataSourceCloudStackZone(), + "cloudstack_service_offering": dataSourceCloudstackServiceOffering(), + "cloudstack_volume": dataSourceCloudstackVolume(), + "cloudstack_vpc": dataSourceCloudstackVPC(), + "cloudstack_ipaddress": dataSourceCloudstackIPAddress(), + "cloudstack_user": dataSourceCloudstackUser(), + "cloudstack_vpn_connection": dataSourceCloudstackVPNConnection(), + "cloudstack_pod": dataSourceCloudstackPod(), + "cloudstack_domain": dataSourceCloudstackDomain(), + "cloudstack_project": dataSourceCloudstackProject(), + "cloudstack_physical_network": dataSourceCloudStackPhysicalNetwork(), + "cloudstack_role": dataSourceCloudstackRole(), + "cloudstack_cluster": dataSourceCloudstackCluster(), + "cloudstack_limits": dataSourceCloudStackLimits(), }, ResourcesMap: map[string]*schema.Resource{ "cloudstack_affinity_group": resourceCloudStackAffinityGroup(), "cloudstack_attach_volume": resourceCloudStackAttachVolume(), + "cloudstack_autoscale_policy": resourceCloudStackAutoScalePolicy(), + "cloudstack_autoscale_vm_group": resourceCloudStackAutoScaleVMGroup(), "cloudstack_autoscale_vm_profile": resourceCloudStackAutoScaleVMProfile(), + "cloudstack_condition": resourceCloudStackCondition(), "cloudstack_configuration": resourceCloudStackConfiguration(), + "cloudstack_counter": resourceCloudStackCounter(), "cloudstack_cluster": resourceCloudStackCluster(), "cloudstack_disk": resourceCloudStackDisk(), "cloudstack_egress_firewall": resourceCloudStackEgressFirewall(), diff --git a/cloudstack/resource_cloudstack_autoscale_policy.go b/cloudstack/resource_cloudstack_autoscale_policy.go new file mode 100644 index 00000000..fe390b5e --- /dev/null +++ b/cloudstack/resource_cloudstack_autoscale_policy.go @@ -0,0 +1,226 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + "strings" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceCloudStackAutoScalePolicy() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudStackAutoScalePolicyCreate, + Read: resourceCloudStackAutoScalePolicyRead, + Update: resourceCloudStackAutoScalePolicyUpdate, + Delete: resourceCloudStackAutoScalePolicyDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + Description: "the name of the autoscale policy", + }, + "action": { + Type: schema.TypeString, + Required: true, + Description: "the action to be executed if all the conditions evaluate to true for the specified duration (case insensitive: scaleup/SCALEUP or scaledown/SCALEDOWN)", + ForceNew: true, + StateFunc: func(val interface{}) string { + return strings.ToUpper(val.(string)) + }, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return strings.ToUpper(old) == strings.ToUpper(new) + }, + }, + "duration": { + Type: schema.TypeInt, + Required: true, + Description: "the duration in which the conditions have to be true before action is taken", + }, + "quiet_time": { + Type: schema.TypeInt, + Optional: true, + Description: "the cool down period in which the policy should not be evaluated after the action has been taken", + }, + "condition_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Required: true, + Description: "the list of IDs of the conditions that are being evaluated on every interval", + }, + }, + } +} + +func resourceCloudStackAutoScalePolicyCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + action := d.Get("action").(string) + duration := d.Get("duration").(int) + + conditionIds := []string{} + if v, ok := d.GetOk("condition_ids"); ok { + conditionSet := v.(*schema.Set) + for _, id := range conditionSet.List() { + conditionIds = append(conditionIds, id.(string)) + } + } + + p := cs.AutoScale.NewCreateAutoScalePolicyParams(action, conditionIds, duration) + + if v, ok := d.GetOk("name"); ok { + p.SetName(v.(string)) + } + if v, ok := d.GetOk("quiet_time"); ok { + p.SetQuiettime(v.(int)) + } + + log.Printf("[DEBUG] Creating autoscale policy") + resp, err := cs.AutoScale.CreateAutoScalePolicy(p) + if err != nil { + return fmt.Errorf("Error creating autoscale policy: %s", err) + } + + d.SetId(resp.Id) + log.Printf("[DEBUG] Autoscale policy created with ID: %s", resp.Id) + + conditionSet := schema.NewSet(schema.HashString, []interface{}{}) + for _, id := range conditionIds { + conditionSet.Add(id) + } + d.Set("condition_ids", conditionSet) + d.Set("name", d.Get("name").(string)) + d.Set("action", action) + d.Set("duration", duration) + if v, ok := d.GetOk("quiet_time"); ok { + d.Set("quiet_time", v.(int)) + } + + return nil +} + +func resourceCloudStackAutoScalePolicyRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + p := cs.AutoScale.NewListAutoScalePoliciesParams() + p.SetId(d.Id()) + + resp, err := cs.AutoScale.ListAutoScalePolicies(p) + if err != nil { + return fmt.Errorf("Error retrieving autoscale policy: %s", err) + } + + if resp.Count == 0 { + log.Printf("[DEBUG] Autoscale policy %s no longer exists", d.Id()) + d.SetId("") + return nil + } + + policy := resp.AutoScalePolicies[0] + d.Set("name", policy.Name) + // CloudStack always returns uppercase actions (SCALEUP/SCALEDOWN) + // Our StateFunc normalizes user input to uppercase, so this should match + d.Set("action", policy.Action) + d.Set("duration", policy.Duration) + d.Set("quiet_time", policy.Quiettime) + + conditionIds := schema.NewSet(schema.HashString, []interface{}{}) + for _, condition := range policy.Conditions { + // Try to extract the ID from the condition object + if condition == nil { + continue + } + + // The condition is a *cloudstack.Condition struct, so access the Id field directly + if condition.Id != "" { + conditionIds.Add(condition.Id) + } else { + log.Printf("[WARN] Condition has empty ID: %+v", condition) + } + } + d.Set("condition_ids", conditionIds) + + return nil +} + +func resourceCloudStackAutoScalePolicyUpdate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + if d.HasChange("name") || d.HasChange("condition_ids") || d.HasChange("duration") || d.HasChange("quiet_time") { + log.Printf("[DEBUG] Updating autoscale policy: %s", d.Id()) + + p := cs.AutoScale.NewUpdateAutoScalePolicyParams(d.Id()) + + if d.HasChange("name") { + if v, ok := d.GetOk("name"); ok { + p.SetName(v.(string)) + } + } + + if d.HasChange("duration") { + duration := d.Get("duration").(int) + p.SetDuration(duration) + } + + if d.HasChange("quiet_time") { + if v, ok := d.GetOk("quiet_time"); ok { + p.SetQuiettime(v.(int)) + } + } + + if d.HasChange("condition_ids") { + conditionIds := []string{} + if v, ok := d.GetOk("condition_ids"); ok { + conditionSet := v.(*schema.Set) + for _, id := range conditionSet.List() { + conditionIds = append(conditionIds, id.(string)) + } + } + p.SetConditionids(conditionIds) + } + + _, err := cs.AutoScale.UpdateAutoScalePolicy(p) + if err != nil { + return fmt.Errorf("Error updating autoscale policy: %s", err) + } + + log.Printf("[DEBUG] Autoscale policy updated successfully: %s", d.Id()) + } + + return resourceCloudStackAutoScalePolicyRead(d, meta) +} + +func resourceCloudStackAutoScalePolicyDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + p := cs.AutoScale.NewDeleteAutoScalePolicyParams(d.Id()) + + log.Printf("[DEBUG] Deleting autoscale policy: %s", d.Id()) + _, err := cs.AutoScale.DeleteAutoScalePolicy(p) + if err != nil { + return fmt.Errorf("Error deleting autoscale policy: %s", err) + } + + return nil +} diff --git a/cloudstack/resource_cloudstack_autoscale_vm_group.go b/cloudstack/resource_cloudstack_autoscale_vm_group.go new file mode 100644 index 00000000..8d0eb7e6 --- /dev/null +++ b/cloudstack/resource_cloudstack_autoscale_vm_group.go @@ -0,0 +1,452 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceCloudStackAutoScaleVMGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudStackAutoScaleVMGroupCreate, + Read: resourceCloudStackAutoScaleVMGroupRead, + Update: resourceCloudStackAutoScaleVMGroupUpdate, + Delete: resourceCloudStackAutoScaleVMGroupDelete, + + Schema: map[string]*schema.Schema{ + "lbrule_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "the ID of the load balancer rule", + }, + + "name": { + Type: schema.TypeString, + Optional: true, + Description: "the name of the autoscale vmgroup", + }, + + "min_members": { + Type: schema.TypeInt, + Required: true, + Description: "the minimum number of members in the vmgroup, the number of instances in the vm group will be equal to or more than this number", + }, + + "max_members": { + Type: schema.TypeInt, + Required: true, + Description: "the maximum number of members in the vmgroup, The number of instances in the vm group will be equal to or less than this number", + }, + + "interval": { + Type: schema.TypeInt, + Optional: true, + Description: "the frequency in which the performance counters to be collected", + }, + + "scaleup_policy_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Required: true, + Description: "list of scaleup autoscale policies", + }, + + "scaledown_policy_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Required: true, + Description: "list of scaledown autoscale policies", + }, + + "vm_profile_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "the autoscale profile that contains information about the vms in the vm group", + }, + + "display": { + Type: schema.TypeBool, + Optional: true, + Description: "an optional field, whether to the display the group to the end user or not", + }, + + "state": { + Type: schema.TypeString, + Optional: true, + Default: "enable", + Description: "the state of the autoscale vm group (enable or disable)", + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "enable" && value != "disable" { + errors = append(errors, fmt.Errorf("state must be either 'enable' or 'disable'")) + } + return + }, + }, + + "cleanup": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "true if all the members of autoscale vm group has to be cleaned up, false otherwise", + }, + }, + } +} + +func resourceCloudStackAutoScaleVMGroupCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + lbruleid := d.Get("lbrule_id").(string) + minmembers := d.Get("min_members").(int) + maxmembers := d.Get("max_members").(int) + vmprofileid := d.Get("vm_profile_id").(string) + + scaleUpPolicyIds := []string{} + if v, ok := d.GetOk("scaleup_policy_ids"); ok { + scaleUpSet := v.(*schema.Set) + for _, id := range scaleUpSet.List() { + scaleUpPolicyIds = append(scaleUpPolicyIds, id.(string)) + } + } + + scaleDownPolicyIds := []string{} + if v, ok := d.GetOk("scaledown_policy_ids"); ok { + scaleDownSet := v.(*schema.Set) + for _, id := range scaleDownSet.List() { + scaleDownPolicyIds = append(scaleDownPolicyIds, id.(string)) + } + } + + p := cs.AutoScale.NewCreateAutoScaleVmGroupParams(lbruleid, maxmembers, minmembers, scaleDownPolicyIds, scaleUpPolicyIds, vmprofileid) + + if v, ok := d.GetOk("name"); ok { + p.SetName(v.(string)) + } + + if v, ok := d.GetOk("interval"); ok { + p.SetInterval(v.(int)) + } + + if v, ok := d.GetOk("display"); ok { + p.SetFordisplay(v.(bool)) + } + + log.Printf("[DEBUG] Creating autoscale VM group") + resp, err := cs.AutoScale.CreateAutoScaleVmGroup(p) + if err != nil { + return fmt.Errorf("Error creating autoscale VM group: %s", err) + } + + d.SetId(resp.Id) + log.Printf("[DEBUG] Autoscale VM group created with ID: %s", resp.Id) + + requestedState := d.Get("state").(string) + if requestedState == "disable" { + log.Printf("[DEBUG] Disabling autoscale VM group as requested: %s", resp.Id) + disableParams := cs.AutoScale.NewDisableAutoScaleVmGroupParams(resp.Id) + _, err = cs.AutoScale.DisableAutoScaleVmGroup(disableParams) + if err != nil { + return fmt.Errorf("Error disabling autoscale VM group after creation: %s", err) + } + } + + if v, ok := d.GetOk("name"); ok { + d.Set("name", v.(string)) + } + d.Set("lbrule_id", lbruleid) + d.Set("min_members", minmembers) + d.Set("max_members", maxmembers) + d.Set("vm_profile_id", vmprofileid) + if v, ok := d.GetOk("interval"); ok { + d.Set("interval", v.(int)) + } + if v, ok := d.GetOk("display"); ok { + d.Set("display", v.(bool)) + } + + scaleUpSet := schema.NewSet(schema.HashString, []interface{}{}) + for _, id := range scaleUpPolicyIds { + scaleUpSet.Add(id) + } + d.Set("scaleup_policy_ids", scaleUpSet) + + scaleDownSet := schema.NewSet(schema.HashString, []interface{}{}) + for _, id := range scaleDownPolicyIds { + scaleDownSet.Add(id) + } + d.Set("scaledown_policy_ids", scaleDownSet) + + d.Set("state", requestedState) + + return nil +} + +func resourceCloudStackAutoScaleVMGroupRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + p := cs.AutoScale.NewListAutoScaleVmGroupsParams() + p.SetId(d.Id()) + + resp, err := cs.AutoScale.ListAutoScaleVmGroups(p) + if err != nil { + return fmt.Errorf("Error retrieving autoscale VM group: %s", err) + } + + if resp.Count == 0 { + log.Printf("[DEBUG] Autoscale VM group %s no longer exists", d.Id()) + d.SetId("") + return nil + } + + group := resp.AutoScaleVmGroups[0] + d.Set("name", group.Name) + d.Set("lbrule_id", group.Lbruleid) + d.Set("min_members", group.Minmembers) + d.Set("max_members", group.Maxmembers) + d.Set("interval", group.Interval) + d.Set("vm_profile_id", group.Vmprofileid) + d.Set("display", group.Fordisplay) + + terraformState := "enable" + if strings.ToLower(group.State) == "disabled" { + terraformState = "disable" + } + + currentConfigState := d.Get("state").(string) + log.Printf("[DEBUG] CloudStack API state: %s, mapped to Terraform state: %s", group.State, terraformState) + log.Printf("[DEBUG] Current config state: %s, will set state to: %s", currentConfigState, terraformState) + + d.Set("state", terraformState) + + scaleUpPolicyIds := schema.NewSet(schema.HashString, []interface{}{}) + if group.Scaleuppolicies != nil { + for _, policy := range group.Scaleuppolicies { + // Extract the ID from the AutoScalePolicy object + if policy != nil { + scaleUpPolicyIds.Add(policy.Id) + } + } + } + d.Set("scaleup_policy_ids", scaleUpPolicyIds) + + scaleDownPolicyIds := schema.NewSet(schema.HashString, []interface{}{}) + if group.Scaledownpolicies != nil { + for _, policy := range group.Scaledownpolicies { + // Extract the ID from the AutoScalePolicy object + if policy != nil { + scaleDownPolicyIds.Add(policy.Id) + } + } + } + d.Set("scaledown_policy_ids", scaleDownPolicyIds) + + return nil +} + +func resourceCloudStackAutoScaleVMGroupUpdate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + oldState, newState := d.GetChange("state") + log.Printf("[DEBUG] State in terraform config: %s", d.Get("state").(string)) + log.Printf("[DEBUG] Old state: %s, New state: %s", oldState.(string), newState.(string)) + + changes := []string{} + if d.HasChange("name") { + changes = append(changes, "name") + } + if d.HasChange("min_members") { + changes = append(changes, "min_members") + } + if d.HasChange("max_members") { + changes = append(changes, "max_members") + } + if d.HasChange("interval") { + changes = append(changes, "interval") + } + if d.HasChange("scaleup_policy_ids") { + changes = append(changes, "scaleup_policy_ids") + } + if d.HasChange("scaledown_policy_ids") { + changes = append(changes, "scaledown_policy_ids") + } + if d.HasChange("display") { + changes = append(changes, "display") + } + if d.HasChange("state") { + changes = append(changes, "state") + } + log.Printf("[DEBUG] Detected changes in autoscale VM group: %v", changes) + + if d.HasChange("name") || d.HasChange("min_members") || d.HasChange("max_members") || + d.HasChange("interval") || d.HasChange("scaleup_policy_ids") || + d.HasChange("scaledown_policy_ids") || d.HasChange("display") || d.HasChange("state") { + + log.Printf("[DEBUG] Updating autoscale VM group: %s", d.Id()) + + // Check current state to determine operation order + currentState := "enable" + if oldState, newState := d.GetChange("state"); d.HasChange("state") { + currentState = oldState.(string) + log.Printf("[DEBUG] State change detected: %s -> %s", currentState, newState.(string)) + } + + if d.HasChange("state") { + newState := d.Get("state").(string) + if newState == "disable" && currentState == "enable" { + log.Printf("[DEBUG] Disabling autoscale VM group before other updates: %s", d.Id()) + disableParams := cs.AutoScale.NewDisableAutoScaleVmGroupParams(d.Id()) + _, err := cs.AutoScale.DisableAutoScaleVmGroup(disableParams) + if err != nil { + return fmt.Errorf("Error disabling autoscale VM group: %s", err) + } + // Wait a moment for disable to take effect + time.Sleep(1 * time.Second) + } + } + + if d.HasChange("name") || d.HasChange("min_members") || d.HasChange("max_members") || + d.HasChange("interval") || d.HasChange("scaleup_policy_ids") || + d.HasChange("scaledown_policy_ids") || d.HasChange("display") { + + p := cs.AutoScale.NewUpdateAutoScaleVmGroupParams(d.Id()) + + if d.HasChange("name") { + if v, ok := d.GetOk("name"); ok { + p.SetName(v.(string)) + } + } + + if d.HasChange("min_members") { + minmembers := d.Get("min_members").(int) + p.SetMinmembers(minmembers) + } + + if d.HasChange("max_members") { + maxmembers := d.Get("max_members").(int) + p.SetMaxmembers(maxmembers) + } + + if d.HasChange("interval") { + if v, ok := d.GetOk("interval"); ok { + p.SetInterval(v.(int)) + } + } + + if d.HasChange("scaleup_policy_ids") { + scaleUpPolicyIds := []string{} + if v, ok := d.GetOk("scaleup_policy_ids"); ok { + scaleUpSet := v.(*schema.Set) + for _, id := range scaleUpSet.List() { + scaleUpPolicyIds = append(scaleUpPolicyIds, id.(string)) + } + } + p.SetScaleuppolicyids(scaleUpPolicyIds) + } + + if d.HasChange("scaledown_policy_ids") { + scaleDownPolicyIds := []string{} + if v, ok := d.GetOk("scaledown_policy_ids"); ok { + scaleDownSet := v.(*schema.Set) + for _, id := range scaleDownSet.List() { + scaleDownPolicyIds = append(scaleDownPolicyIds, id.(string)) + } + } + p.SetScaledownpolicyids(scaleDownPolicyIds) + } + + if d.HasChange("display") { + if v, ok := d.GetOk("display"); ok { + p.SetFordisplay(v.(bool)) + } + } + + log.Printf("[DEBUG] Applying parameter updates to autoscale VM group: %s", d.Id()) + _, err := cs.AutoScale.UpdateAutoScaleVmGroup(p) + if err != nil { + return fmt.Errorf("Error updating autoscale VM group parameters: %s", err) + } + } + + if d.HasChange("state") { + newState := d.Get("state").(string) + if newState == "enable" { + log.Printf("[DEBUG] Enabling autoscale VM group after updates: %s", d.Id()) + enableParams := cs.AutoScale.NewEnableAutoScaleVmGroupParams(d.Id()) + _, err := cs.AutoScale.EnableAutoScaleVmGroup(enableParams) + if err != nil { + return fmt.Errorf("Error enabling autoscale VM group: %s", err) + } + } + } + + log.Printf("[DEBUG] Autoscale VM group updated successfully: %s", d.Id()) + } + + return resourceCloudStackAutoScaleVMGroupRead(d, meta) +} + +func resourceCloudStackAutoScaleVMGroupDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + p := cs.AutoScale.NewDeleteAutoScaleVmGroupParams(d.Id()) + + cleanup := d.Get("cleanup").(bool) + p.SetCleanup(cleanup) + log.Printf("[DEBUG] Deleting autoscale VM group with cleanup=%t", cleanup) + log.Printf("[DEBUG] This deletion was triggered by Terraform - check if this should be an update instead") + + log.Printf("[DEBUG] Disabling autoscale VM group before deletion: %s", d.Id()) + disableParams := cs.AutoScale.NewDisableAutoScaleVmGroupParams(d.Id()) + _, err := cs.AutoScale.DisableAutoScaleVmGroup(disableParams) + if err != nil { + if !strings.Contains(err.Error(), "Invalid parameter id value") && + !strings.Contains(err.Error(), "entity does not exist") && + !strings.Contains(err.Error(), "already disabled") { + return fmt.Errorf("Error disabling autoscale VM group: %s", err) + } + } + + time.Sleep(2 * time.Second) + log.Printf("[DEBUG] Autoscale VM group disabled, proceeding with deletion: %s", d.Id()) + + log.Printf("[DEBUG] Deleting autoscale VM group: %s", d.Id()) + _, err = cs.AutoScale.DeleteAutoScaleVmGroup(p) + if err != nil { + // This is a very poor way to be told the ID does no longer exist :( + if strings.Contains(err.Error(), fmt.Sprintf( + "Invalid parameter id value=%s due to incorrect long value format, "+ + "or entity does not exist", d.Id())) { + return nil + } + + return fmt.Errorf("Error deleting autoscale VM group: %s", err) + } + + return nil +} diff --git a/cloudstack/resource_cloudstack_autoscale_vm_profile.go b/cloudstack/resource_cloudstack_autoscale_vm_profile.go index 04fc5d5f..0032b92a 100644 --- a/cloudstack/resource_cloudstack_autoscale_vm_profile.go +++ b/cloudstack/resource_cloudstack_autoscale_vm_profile.go @@ -38,33 +38,95 @@ func resourceCloudStackAutoScaleVMProfile() *schema.Resource { Schema: map[string]*schema.Schema{ "service_offering": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "the service offering of the auto deployed virtual machine", }, "template": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + Description: "the template of the auto deployed virtual machine", }, "zone": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "availability zone for the auto deployed virtual machine", }, "destroy_vm_grace_period": { - Type: schema.TypeString, - Optional: true, - Computed: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "the time allowed for existing connections to get closed before a vm is expunged", }, "other_deploy_params": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - ForceNew: true, + Type: schema.TypeMap, + Optional: true, + Computed: true, + ForceNew: true, + Description: "parameters other than zoneId/serviceOfferringId/templateId of the auto deployed virtual machine", + }, + + "counter_param_list": { + Type: schema.TypeMap, + Optional: true, + Description: "counterparam list. Example: counterparam[0].name=snmpcommunity&counterparam[0].value=public&counterparam[1].name=snmpport&counterparam[1].value=161", + }, + + "user_data": { + Type: schema.TypeString, + Optional: true, + Description: "an optional binary data that can be sent to the virtual machine upon a successful deployment. This binary data must be base64 encoded before adding it to the request.", + }, + + "user_data_id": { + Type: schema.TypeString, + Optional: true, + Description: "the ID of the Userdata", + }, + + "user_data_details": { + Type: schema.TypeMap, + Optional: true, + Description: "used to specify the parameters values for the variables in userdata", + }, + + "autoscale_user_id": { + Type: schema.TypeString, + Optional: true, + Description: "the ID of the user used to launch and destroy the VMs", + }, + + "display": { + Type: schema.TypeBool, + Optional: true, + Description: "an optional field, whether to the display the profile to the end user or not", + }, + + "account_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "account that will own the autoscale VM profile", + }, + + "project_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "an optional project for the autoscale VM profile", + }, + + "domain_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "domain ID of the account owning a autoscale VM profile", }, "metadata": metadataSchema(), @@ -75,19 +137,16 @@ func resourceCloudStackAutoScaleVMProfile() *schema.Resource { func resourceCloudStackAutoScaleVMProfileCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - // Retrieve the service_offering ID serviceofferingid, e := retrieveID(cs, "service_offering", d.Get("service_offering").(string)) if e != nil { return e.Error() } - // Retrieve the zone ID zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string)) if e != nil { return e.Error() } - // Retrieve the template ID templateid, e := retrieveTemplateID(cs, zoneid, d.Get("template").(string)) if e != nil { return e.Error() @@ -111,7 +170,50 @@ func resourceCloudStackAutoScaleVMProfileCreate(d *schema.ResourceData, meta int p.SetOtherdeployparams(nv) } - // Create the new vm profile + 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 v, ok := d.GetOk("user_data"); ok { + p.SetUserdata(v.(string)) + } + + if v, ok := d.GetOk("user_data_id"); ok { + p.SetUserdataid(v.(string)) + } + + 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 v, ok := d.GetOk("autoscale_user_id"); ok { + p.SetAutoscaleuserid(v.(string)) + } + + if v, ok := d.GetOk("display"); ok { + p.SetFordisplay(v.(bool)) + } + + if v, ok := d.GetOk("account_name"); ok { + p.SetAccount(v.(string)) + } + + if v, ok := d.GetOk("project_id"); ok { + p.SetProjectid(v.(string)) + } + + if v, ok := d.GetOk("domain_id"); ok { + p.SetDomainid(v.(string)) + } + r, err := cs.AutoScale.CreateAutoScaleVmProfile(p) if err != nil { return fmt.Errorf("Error creating AutoScaleVmProfile %s: %s", d.Id(), err) @@ -119,12 +221,11 @@ func resourceCloudStackAutoScaleVMProfileCreate(d *schema.ResourceData, meta int d.SetId(r.Id) - // Set metadata if necessary if err = setMetadata(cs, d, "AutoScaleVmProfile"); err != nil { return fmt.Errorf("Error setting metadata on the AutoScaleVmProfile %s: %s", d.Id(), err) } - return nil + return resourceCloudStackAutoScaleVMProfileRead(d, meta) } func resourceCloudStackAutoScaleVMProfileRead(d *schema.ResourceData, meta interface{}) error { @@ -168,6 +269,38 @@ func resourceCloudStackAutoScaleVMProfileRead(d *schema.ResourceData, meta inter d.Set("other_deploy_params", p.Otherdeployparams) } + if p.Userdata != "" { + d.Set("user_data", p.Userdata) + } + + if p.Userdataid != "" { + d.Set("user_data_id", p.Userdataid) + } + + if p.Userdatadetails != "" { + if _, ok := d.GetOk("user_data_details"); !ok { + d.Set("user_data_details", map[string]interface{}{}) + } + } + + if p.Autoscaleuserid != "" { + d.Set("autoscale_user_id", p.Autoscaleuserid) + } + + d.Set("display", p.Fordisplay) + + if p.Account != "" { + d.Set("account_name", p.Account) + } + + if p.Projectid != "" { + d.Set("project_id", p.Projectid) + } + + if p.Domainid != "" { + d.Set("domain_id", p.Domainid) + } + metadata, err := getMetadata(cs, d, "AutoScaleVmProfile") if err != nil { return err @@ -177,40 +310,185 @@ func resourceCloudStackAutoScaleVMProfileRead(d *schema.ResourceData, meta inter 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) + 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) + } + } - // Create a new parameter struct - p := cs.AutoScale.NewUpdateAutoScaleVmProfileParams(d.Id()) + // 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) + } - if d.HasChange("template") { - zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string)) - if e != nil { - return e.Error() + 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() + } + templateid, e := retrieveTemplateID(cs, zoneid, d.Get("template").(string)) + if e != nil { + return e.Error() + } + p.SetTemplateid(templateid) } - templateid, e := retrieveTemplateID(cs, zoneid, d.Get("template").(string)) - if e != nil { - return e.Error() + + 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.SetTemplateid(templateid) - } - if d.HasChange("destroy_vm_grace_period") { - duration, err := time.ParseDuration(d.Get("destroy_vm_grace_period").(string)) - if err != nil { - return err + 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) + } } - p.SetExpungevmgraceperiod(int(duration.Seconds())) - } - _, err := cs.AutoScale.UpdateAutoScaleVmProfile(p) - if err != nil { - return fmt.Errorf("Error updating AutoScaleVmProfile %s: %s", d.Id(), err) + 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) + } + 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) } } @@ -220,10 +498,8 @@ func resourceCloudStackAutoScaleVMProfileUpdate(d *schema.ResourceData, meta int func resourceCloudStackAutoScaleVMProfileDelete(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - // Create a new parameter struct p := cs.AutoScale.NewDeleteAutoScaleVmProfileParams(d.Id()) - // Delete the template log.Printf("[INFO] Deleting AutoScaleVmProfile: %s", d.Id()) _, err := cs.AutoScale.DeleteAutoScaleVmProfile(p) if err != nil { diff --git a/cloudstack/resource_cloudstack_condition.go b/cloudstack/resource_cloudstack_condition.go new file mode 100644 index 00000000..cd74e5cb --- /dev/null +++ b/cloudstack/resource_cloudstack_condition.go @@ -0,0 +1,182 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceCloudStackCondition() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudStackConditionCreate, + Read: resourceCloudStackConditionRead, + Update: resourceCloudStackConditionUpdate, + Delete: resourceCloudStackConditionDelete, + + Schema: map[string]*schema.Schema{ + "counter_id": { + Type: schema.TypeString, + Required: true, + Description: "The ID of the counter to be used in the condition.", + ForceNew: true, + }, + "relational_operator": { + Type: schema.TypeString, + Required: true, + Description: "Relational Operator to be used with threshold. Valid values are EQ, GT, LT, GE, LE.", + }, + "threshold": { + Type: schema.TypeFloat, + Required: true, + Description: "Value for which the Counter will be evaluated with the Operator selected.", + }, + "account_name": { + Type: schema.TypeString, + Required: true, + Description: "the account of the condition. Must be used with the domainId parameter.", + ForceNew: true, + }, + "domain_id": { + Type: schema.TypeString, + Required: true, + Description: "the domain ID of the account.", + ForceNew: true, + }, + "project_id": { + Type: schema.TypeString, + Optional: true, + Description: "optional project for the condition", + ForceNew: true, + }, + }, + } +} + +func resourceCloudStackConditionRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + p := cs.AutoScale.NewListConditionsParams() + p.SetId(d.Id()) + + resp, err := cs.AutoScale.ListConditions(p) + if err != nil { + return fmt.Errorf("Error retrieving condition: %s", err) + } + + if resp.Count == 0 { + log.Printf("[DEBUG] Condition %s no longer exists", d.Id()) + d.SetId("") + return nil + } + + condition := resp.Conditions[0] + d.Set("counter_id", condition.Counterid) + d.Set("relational_operator", condition.Relationaloperator) + d.Set("threshold", condition.Threshold) + d.Set("account_name", condition.Account) + d.Set("domain_id", condition.Domainid) + if condition.Projectid != "" { + d.Set("project_id", condition.Projectid) + } + + return nil +} + +func resourceCloudStackConditionUpdate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + if d.HasChange("relational_operator") || d.HasChange("threshold") { + log.Printf("[DEBUG] Updating condition: %s", d.Id()) + + relationaloperator := d.Get("relational_operator").(string) + threshold := d.Get("threshold").(float64) + + p := cs.AutoScale.NewUpdateConditionParams(d.Id(), relationaloperator, int64(threshold)) + + _, err := cs.AutoScale.UpdateCondition(p) + if err != nil { + return fmt.Errorf("Error updating condition: %s", err) + } + + log.Printf("[DEBUG] Condition updated successfully: %s", d.Id()) + } + + return resourceCloudStackConditionRead(d, meta) +} + +func resourceCloudStackConditionDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + p := cs.AutoScale.NewDeleteConditionParams(d.Id()) + + log.Printf("[DEBUG] Deleting condition: %s", d.Id()) + _, err := cs.AutoScale.DeleteCondition(p) + if err != nil { + return fmt.Errorf("Error deleting condition: %s", err) + } + + return nil +} +func resourceCloudStackConditionCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + counterid := d.Get("counter_id") + relationaloperator := d.Get("relational_operator").(string) + threshold := d.Get("threshold").(float64) + + account, accountOk := d.GetOk("account_name") + domainid, domainOk := d.GetOk("domain_id") + + if !accountOk || !domainOk { + return fmt.Errorf("account_name and domain_id are required fields") + } + + p := cs.AutoScale.NewCreateConditionParams(counterid.(string), relationaloperator, int64(threshold)) + p.SetAccount(account.(string)) + p.SetDomainid(domainid.(string)) + + if v, ok := d.GetOk("project_id"); ok { + p.SetProjectid(v.(string)) + } + + log.Printf("[DEBUG] Creating condition") + resp, err := cs.AutoScale.CreateCondition(p) + if err != nil { + return fmt.Errorf("Error creating condition: %s", err) + } + + d.SetId(resp.Id) + log.Printf("[DEBUG] Condition created with ID: %s", resp.Id) + + // Set the values directly instead of calling read to avoid JSON unmarshaling issues + d.Set("counter_id", counterid.(string)) + d.Set("relational_operator", relationaloperator) + d.Set("threshold", threshold) + d.Set("account_name", account.(string)) + d.Set("domain_id", domainid.(string)) + if v, ok := d.GetOk("project_id"); ok { + d.Set("project_id", v.(string)) + } + + return nil +} diff --git a/cloudstack/resource_cloudstack_counter.go b/cloudstack/resource_cloudstack_counter.go new file mode 100644 index 00000000..ddd7e9e4 --- /dev/null +++ b/cloudstack/resource_cloudstack_counter.go @@ -0,0 +1,126 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceCloudStackCounter() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudStackCounterCreate, + Read: resourceCloudStackCounterRead, + Delete: resourceCloudStackCounterDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the counter", + ForceNew: true, + }, + + "source": { + Type: schema.TypeString, + Required: true, + Description: "Source of the counter", + ForceNew: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + Description: "Value of the counter e.g. oid in case of snmp", + ForceNew: true, + }, + "counter_provider": { + Type: schema.TypeString, + Required: true, + Description: "Provider of the counter", + ForceNew: true, + }, + }, + } +} + +func resourceCloudStackCounterCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + name := d.Get("name").(string) + source := d.Get("source").(string) + value := d.Get("value").(string) + provider := d.Get("counter_provider").(string) + + p := cs.AutoScale.NewCreateCounterParams(name, provider, source, value) + + log.Printf("[DEBUG] Creating counter: %s", name) + resp, err := cs.AutoScale.CreateCounter(p) + if err != nil { + return fmt.Errorf("Error creating counter: %s", err) + } + + d.SetId(resp.Id) + log.Printf("[DEBUG] Counter created with ID: %s", resp.Id) + + return resourceCloudStackCounterRead(d, meta) +} + +func resourceCloudStackCounterRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + p := cs.AutoScale.NewListCountersParams() + p.SetId(d.Id()) + + resp, err := cs.AutoScale.ListCounters(p) + if err != nil { + return fmt.Errorf("Error retrieving counter: %s", err) + } + + if resp.Count == 0 { + log.Printf("[DEBUG] Counter %s no longer exists", d.Id()) + d.SetId("") + return nil + } + + counter := resp.Counters[0] + d.Set("name", counter.Name) + d.Set("source", counter.Source) + d.Set("value", counter.Value) + d.Set("counter_provider", counter.Provider) + + return nil +} + +func resourceCloudStackCounterDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + p := cs.AutoScale.NewDeleteCounterParams(d.Id()) + + log.Printf("[DEBUG] Deleting counter: %s", d.Id()) + _, err := cs.AutoScale.DeleteCounter(p) + if err != nil { + return fmt.Errorf("Error deleting counter: %s", err) + } + + return nil +} diff --git a/cloudstack/resource_cloudstack_loadbalancer_rule.go b/cloudstack/resource_cloudstack_loadbalancer_rule.go index 5c5de866..c31c8617 100644 --- a/cloudstack/resource_cloudstack_loadbalancer_rule.go +++ b/cloudstack/resource_cloudstack_loadbalancer_rule.go @@ -25,6 +25,7 @@ import ( "regexp" "strconv" "strings" + "time" "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -258,11 +259,73 @@ func resourceCloudStackLoadBalancerRuleRead(d *schema.ResourceData, meta interfa 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 @@ func resourceCloudStackLoadBalancerRuleUpdate(d *schema.ResourceData, meta inter } 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 + } + + 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(membersToRemove) > 0 { - p := cs.LoadBalancer.NewRemoveFromLoadBalancerRuleParams(d.Id()) - p.SetVirtualmachineids(membersToRemove) - if _, err := cs.LoadBalancer.RemoveFromLoadBalancerRule(p); err != nil { - return err + 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") } } } diff --git a/go.mod b/go.mod index de1b2059..339e856f 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ module github.com/terraform-providers/terraform-provider-cloudstack require ( - github.com/apache/cloudstack-go/v2 v2.17.1 + github.com/apache/cloudstack-go/v2 v2.17.2 github.com/go-ini/ini v1.67.0 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/terraform-plugin-framework v1.12.0 @@ -35,7 +35,6 @@ require ( github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect github.com/fatih/color v1.16.0 // indirect - github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect @@ -85,5 +84,3 @@ require ( ) go 1.23.0 - -toolchain go1.22.4 diff --git a/go.sum b/go.sum index d71638ed..4b49b663 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/ProtonMail/go-crypto v1.1.0-alpha.0 h1:nHGfwXmFvJrSR9xu8qL7BkO4DqTHXE github.com/ProtonMail/go-crypto v1.1.0-alpha.0/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/apache/cloudstack-go/v2 v2.17.1 h1:XD0bGDOv+MCavXJfc/qxILgJh+cHJbudpqQ1FzA2sDI= -github.com/apache/cloudstack-go/v2 v2.17.1/go.mod h1:p/YBUwIEkQN6CQxFhw8Ff0wzf1MY0qRRRuGYNbcb1F8= +github.com/apache/cloudstack-go/v2 v2.17.2 h1:fMqLUTpHZeJoSVQiD2hDBSoqOvGJ9QMoOaWABW8lc/0= +github.com/apache/cloudstack-go/v2 v2.17.2/go.mod h1:p/YBUwIEkQN6CQxFhw8Ff0wzf1MY0qRRRuGYNbcb1F8= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= @@ -168,7 +168,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= @@ -231,4 +230,4 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= \ No newline at end of file +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/website/docs/d/autoscale_policy.html.markdown b/website/docs/d/autoscale_policy.html.markdown new file mode 100644 index 00000000..be2e72be --- /dev/null +++ b/website/docs/d/autoscale_policy.html.markdown @@ -0,0 +1,69 @@ +--- +layout: "cloudstack" +page_title: "CloudStack: cloudstack_autoscale_policy" +sidebar_current: "docs-cloudstack-data-source-autoscale-policy" +description: |- + Gets information about a CloudStack autoscale policy. +--- + +# cloudstack_autoscale_policy + +Use this data source to get information about a CloudStack autoscale policy. + +## Example Usage + +```hcl +# Get policy by ID +data "cloudstack_autoscale_policy" "existing_policy" { + id = "6a8dc025-d7c9-4676-8a7d-e2d9b55e7e60" +} + +# Get policy by name +data "cloudstack_autoscale_policy" "scale_up_policy" { + filter { + name = "name" + value = "scale-up-policy" + } +} + +# Use in an autoscale VM group +resource "cloudstack_autoscale_vm_group" "vm_group" { + name = "web-autoscale" + lbrule_id = cloudstack_loadbalancer_rule.lb.id + min_members = 1 + max_members = 5 + vm_profile_id = cloudstack_autoscale_vm_profile.profile.id + + scaleup_policy_ids = [ + data.cloudstack_autoscale_policy.existing_policy.id + ] + + scaledown_policy_ids = [ + cloudstack_autoscale_policy.scale_down.id + ] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `id` - (Optional) The ID of the autoscale policy. + +* `filter` - (Optional) One or more name/value pairs to filter off of. You can apply filters on any exported attributes. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The autoscale policy ID. + +* `name` - The name of the policy. + +* `action` - The action (SCALEUP or SCALEDOWN). + +* `duration` - The duration in seconds. + +* `quiet_time` - The quiet time in seconds. + +* `condition_ids` - The list of condition IDs used by this policy. diff --git a/website/docs/d/autoscale_vm_group.html.markdown b/website/docs/d/autoscale_vm_group.html.markdown new file mode 100644 index 00000000..e8b8ba8c --- /dev/null +++ b/website/docs/d/autoscale_vm_group.html.markdown @@ -0,0 +1,71 @@ +--- +layout: "cloudstack" +page_title: "CloudStack: cloudstack_autoscale_vm_group" +sidebar_current: "docs-cloudstack-data-source-autoscale-vm-group" +description: |- + Gets information about a CloudStack autoscale VM group. +--- + +# cloudstack_autoscale_vm_group + +Use this data source to get information about a CloudStack autoscale VM group. + +## Example Usage + +```hcl +# Get autoscale VM group by ID +data "cloudstack_autoscale_vm_group" "existing_group" { + id = "156a819a-dec1-4166-aab3-657c271fa4a3" +} + +# Get autoscale VM group by name +data "cloudstack_autoscale_vm_group" "web_group" { + filter { + name = "name" + value = "web-server-autoscale" + } +} + +# Output information about the group +output "autoscale_group_state" { + value = data.cloudstack_autoscale_vm_group.existing_group.state +} + +output "current_members" { + value = "Min: ${data.cloudstack_autoscale_vm_group.existing_group.min_members}, Max: ${data.cloudstack_autoscale_vm_group.existing_group.max_members}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `id` - (Optional) The ID of the autoscale VM group. + +* `filter` - (Optional) One or more name/value pairs to filter off of. You can apply filters on any exported attributes. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The autoscale VM group ID. + +* `name` - The name of the autoscale VM group. + +* `lbrule_id` - The load balancer rule ID. + +* `min_members` - The minimum number of members. + +* `max_members` - The maximum number of members. + +* `vm_profile_id` - The VM profile ID. + +* `interval` - The monitoring interval in seconds. + +* `display` - Whether the group is displayed to end users. + +* `state` - The current state of the group (enable or disable). + +* `scaleup_policy_ids` - The list of scale-up policy IDs. + +* `scaledown_policy_ids` - The list of scale-down policy IDs. diff --git a/website/docs/d/autoscale_vm_profile.html.markdown b/website/docs/d/autoscale_vm_profile.html.markdown new file mode 100644 index 00000000..a66375a0 --- /dev/null +++ b/website/docs/d/autoscale_vm_profile.html.markdown @@ -0,0 +1,76 @@ +--- +layout: "cloudstack" +page_title: "CloudStack: cloudstack_autoscale_vm_profile" +sidebar_current: "docs-cloudstack-data-source-autoscale-vm-profile" +description: |- + Gets information about a CloudStack autoscale VM profile. +--- + +# cloudstack_autoscale_vm_profile + +Use this data source to get information about a CloudStack autoscale VM profile. + +## Example Usage + +```hcl +# Get VM profile by ID +data "cloudstack_autoscale_vm_profile" "existing_profile" { + id = "a596f7a2-95b8-4f0e-9f15-88f4091f18fe" +} + +# Get VM profile by filter +data "cloudstack_autoscale_vm_profile" "web_profile" { + filter { + name = "service_offering" + value = "Small Instance" + } +} + +# Use in an autoscale VM group +resource "cloudstack_autoscale_vm_group" "vm_group" { + name = "web-autoscale" + lbrule_id = cloudstack_loadbalancer_rule.lb.id + min_members = 1 + max_members = 5 + vm_profile_id = data.cloudstack_autoscale_vm_profile.existing_profile.id + + scaleup_policy_ids = [cloudstack_autoscale_policy.scale_up.id] + scaledown_policy_ids = [cloudstack_autoscale_policy.scale_down.id] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `id` - (Optional) The ID of the autoscale VM profile. + +* `filter` - (Optional) One or more name/value pairs to filter off of. You can apply filters on any exported attributes. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The autoscale VM profile ID. + +* `service_offering` - The service offering name or ID. + +* `template` - The template name or ID. + +* `zone` - The zone name or ID. + +* `destroy_vm_grace_period` - The grace period for VM destruction. + +* `counter_param_list` - Counter parameters for monitoring. + +* `user_data` - User data for VM initialization. + +* `user_data_details` - Additional user data details. + +* `account_name` - The account name that owns the profile. + +* `domain_id` - The domain ID where the profile exists. + +* `display` - Whether the profile is displayed to end users. + +* `other_deploy_params` - Additional deployment parameters. diff --git a/website/docs/d/condition.html.markdown b/website/docs/d/condition.html.markdown new file mode 100644 index 00000000..adcfe715 --- /dev/null +++ b/website/docs/d/condition.html.markdown @@ -0,0 +1,61 @@ +--- +layout: "cloudstack" +page_title: "CloudStack: cloudstack_condition" +sidebar_current: "docs-cloudstack-data-source-condition" +description: |- + Gets information about a CloudStack autoscale condition. +--- + +# cloudstack_condition + +Use this data source to get information about a CloudStack autoscale condition. + +## Example Usage + +```hcl +# Get condition by ID +data "cloudstack_condition" "existing_condition" { + id = "c2f0591b-ce9b-499a-81f2-8fc6318b0c72" +} + +# Get condition by filter +data "cloudstack_condition" "cpu_condition" { + filter { + name = "threshold" + value = "80" + } +} + +# Use in a policy +resource "cloudstack_autoscale_policy" "scale_policy" { + name = "scale-up-policy" + action = "scaleup" + duration = 300 + quiet_time = 300 + condition_ids = [data.cloudstack_condition.existing_condition.id] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `id` - (Optional) The ID of the condition. + +* `filter` - (Optional) One or more name/value pairs to filter off of. You can apply filters on any exported attributes. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The condition ID. + +* `counter_id` - The counter ID being monitored. + +* `relational_operator` - The relational operator for the condition. + +* `threshold` - The threshold value. + +* `account_name` - The account name that owns the condition. + +* `domain_id` - The domain ID where the condition exists. diff --git a/website/docs/d/counter.html.markdown b/website/docs/d/counter.html.markdown new file mode 100644 index 00000000..e207ee85 --- /dev/null +++ b/website/docs/d/counter.html.markdown @@ -0,0 +1,57 @@ +--- +layout: "cloudstack" +page_title: "CloudStack: cloudstack_counter" +sidebar_current: "docs-cloudstack-data-source-counter" +description: |- + Gets information about a CloudStack counter. +--- + +# cloudstack_counter + +Use this data source to get information about a CloudStack counter for use in autoscale conditions. + +## Example Usage + +```hcl +# Get counter by ID +data "cloudstack_counter" "cpu_counter" { + id = "959e11c0-8416-11f0-9a72-1e001b000238" +} + +# Get counter by name +data "cloudstack_counter" "memory_counter" { + filter { + name = "name" + value = "VM CPU - average percentage" + } +} + +# Use in a condition +resource "cloudstack_condition" "scale_up" { + counter_id = data.cloudstack_counter.cpu_counter.id + relational_operator = "GT" + threshold = 80.0 + account_name = "admin" + domain_id = "1" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `id` - (Optional) The ID of the counter. + +* `filter` - (Optional) One or more name/value pairs to filter off of. You can apply filters on any exported attributes. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The counter ID. + +* `name` - The name of the counter. + +* `source` - The source of the counter. + +* `value` - The metric value monitored by the counter. diff --git a/website/docs/r/autoscale_policy.html.markdown b/website/docs/r/autoscale_policy.html.markdown new file mode 100644 index 00000000..98da7426 --- /dev/null +++ b/website/docs/r/autoscale_policy.html.markdown @@ -0,0 +1,63 @@ +--- +layout: "cloudstack" +page_title: "CloudStack: cloudstack_autoscale_policy" +sidebar_current: "docs-cloudstack-autoscale-policy" +description: |- + Creates an autoscale policy. +--- + +# cloudstack_autoscale_policy + +Creates an autoscale policy that defines when and how to scale virtual machines based on conditions. + +## Example Usage + +```hcl +resource "cloudstack_condition" "scale_up_condition" { + counter_id = data.cloudstack_counter.cpu_counter.id + relational_operator = "GT" + threshold = 80.0 + account_name = "admin" + domain_id = "67bc8dbe-8416-11f0-9a72-1e001b000238" +} + +resource "cloudstack_autoscale_policy" "scale_up_policy" { + name = "scale-up-policy" + action = "scaleup" # Case insensitive: scaleup/SCALEUP + duration = 300 # 5 minutes + quiet_time = 300 # 5 minutes + condition_ids = [cloudstack_condition.scale_up_condition.id] +} + +resource "cloudstack_autoscale_policy" "scale_down_policy" { + name = "scale-down-policy" + action = "scaledown" # Case insensitive: scaledown/SCALEDOWN + duration = 300 + quiet_time = 600 # 10 minutes quiet time + condition_ids = [cloudstack_condition.scale_down_condition.id] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Optional) The name of the autoscale policy. + +* `action` - (Required) The action to be executed when conditions are met. Valid values are: + * `"scaleup"` or `"SCALEUP"` - Scale up (add instances) + * `"scaledown"` or `"SCALEDOWN"` - Scale down (remove instances) + + **Note**: The action field is case-insensitive. + +* `duration` - (Required) The duration in seconds for which the conditions must be true before the action is taken. + +* `quiet_time` - (Optional) The cool down period in seconds during which the policy should not be evaluated after the action has been taken. + +* `condition_ids` - (Required) A list of condition IDs that must all evaluate to true for the policy to trigger. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The autoscale policy ID. diff --git a/website/docs/r/autoscale_vm_group.html.markdown b/website/docs/r/autoscale_vm_group.html.markdown new file mode 100644 index 00000000..fa31dca7 --- /dev/null +++ b/website/docs/r/autoscale_vm_group.html.markdown @@ -0,0 +1,111 @@ +--- +layout: "cloudstack" +page_title: "CloudStack: cloudstack_autoscale_vm_group" +sidebar_current: "docs-cloudstack-autoscale-vm-group" +description: |- + Creates an autoscale VM group. +--- + +# cloudstack_autoscale_vm_group + +Creates an autoscale VM group that automatically scales virtual machines based on policies and load balancer rules. + +## Example Usage + +```hcl +# Basic autoscale VM group +resource "cloudstack_autoscale_vm_group" "vm_group" { + name = "web-server-autoscale" + lbrule_id = cloudstack_loadbalancer_rule.lb.id + min_members = 1 + max_members = 5 + vm_profile_id = cloudstack_autoscale_vm_profile.profile.id + state = "enable" # or "disable" + cleanup = true # or false + + scaleup_policy_ids = [ + cloudstack_autoscale_policy.scale_up_policy.id + ] + + scaledown_policy_ids = [ + cloudstack_autoscale_policy.scale_down_policy.id + ] +} + +# Autoscale VM group with optional parameters +resource "cloudstack_autoscale_vm_group" "advanced_vm_group" { + name = "advanced-autoscale-group" + lbrule_id = cloudstack_loadbalancer_rule.lb.id + min_members = 2 + max_members = 10 + vm_profile_id = cloudstack_autoscale_vm_profile.profile.id + interval = 30 # Monitor every 30 seconds + display = true + state = "enable" + cleanup = false # Keep VMs when deleting group + + scaleup_policy_ids = [ + cloudstack_autoscale_policy.cpu_scale_up.id, + cloudstack_autoscale_policy.memory_scale_up.id + ] + + scaledown_policy_ids = [ + cloudstack_autoscale_policy.scale_down.id + ] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `lbrule_id` - (Required) The ID of the load balancer rule. Changing this forces a new resource to be created. + +* `min_members` - (Required) The minimum number of members in the VM group. The number of instances will be equal to or more than this number. + +* `max_members` - (Required) The maximum number of members in the VM group. The number of instances will be equal to or less than this number. + +* `vm_profile_id` - (Required) The ID of the autoscale VM profile that contains information about the VMs in the group. Changing this forces a new resource to be created. + +* `scaleup_policy_ids` - (Required) A list of scale-up autoscale policy IDs. + +* `scaledown_policy_ids` - (Required) A list of scale-down autoscale policy IDs. + +* `name` - (Optional) The name of the autoscale VM group. + +* `interval` - (Optional) The frequency in seconds at which performance counters are collected. Defaults to CloudStack's default interval. + +* `display` - (Optional) Whether to display the group to the end user. Defaults to `true`. + +* `state` - (Optional) The state of the autoscale VM group. Valid values are: + * `"enable"` - Enable the autoscale group (default) + * `"disable"` - Disable the autoscale group + + **Note**: When set to `"disable"`, the autoscale group stops monitoring and scaling, but existing VMs remain running. + +* `cleanup` - (Optional) Whether all members of the autoscale VM group should be cleaned up when the group is deleted. Defaults to `false`. + * `true` - Destroy all VMs when deleting the autoscale group + * `false` - Leave VMs running when deleting the autoscale group + +## Attributes Reference + +The following attributes are exported: + +* `id` - The autoscale VM group ID. + +## State Management + +The `state` parameter allows you to enable or disable the autoscale group without destroying it: + +* **Enabling**: Changes from `"disable"` to `"enable"` will activate autoscale monitoring and allow automatic scaling. +* **Disabling**: Changes from `"enable"` to `"disable"` will stop autoscale monitoring but keep all existing VMs running. + +This is useful for temporarily pausing autoscale behavior during maintenance or testing. + +## Import + +Autoscale VM groups can be imported using the `id`, e.g. + +``` +$ terraform import cloudstack_autoscale_vm_group.vm_group eb22f91-7454-4107-89f4-36afcdf33021 +``` diff --git a/website/docs/r/condition.html.markdown b/website/docs/r/condition.html.markdown new file mode 100644 index 00000000..68f80d65 --- /dev/null +++ b/website/docs/r/condition.html.markdown @@ -0,0 +1,61 @@ +--- +layout: "cloudstack" +page_title: "CloudStack: cloudstack_condition" +sidebar_current: "docs-cloudstack-condition" +description: |- + Creates a condition for autoscale policies. +--- + +# cloudstack_condition + +Creates a condition that evaluates performance metrics against thresholds for autoscale policies. + +## Example Usage + +```hcl +# Reference an existing counter +data "cloudstack_counter" "cpu_counter" { + id = "959e11c0-8416-11f0-9a72-1e001b000238" +} + +resource "cloudstack_condition" "scale_up_condition" { + counter_id = data.cloudstack_counter.cpu_counter.id + relational_operator = "GT" + threshold = 80.0 + account_name = "admin" + domain_id = "1" +} + +resource "cloudstack_condition" "scale_down_condition" { + counter_id = data.cloudstack_counter.cpu_counter.id + relational_operator = "LT" + threshold = 20.0 + account_name = "admin" + domain_id = "1" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `counter_id` - (Required) The ID of the counter to monitor. + +* `relational_operator` - (Required) The relational operator for the condition. Valid values are: + * `"GT"` - Greater than + * `"LT"` - Less than + * `"EQ"` - Equal to + * `"GE"` - Greater than or equal to + * `"LE"` - Less than or equal to + +* `threshold` - (Required) The threshold value to compare against. + +* `account_name` - (Required) The account name that owns this condition. + +* `domain_id` - (Required) The domain ID where the condition will be created. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The condition ID. diff --git a/website/docs/r/counter.html.markdown b/website/docs/r/counter.html.markdown new file mode 100644 index 00000000..9699df96 --- /dev/null +++ b/website/docs/r/counter.html.markdown @@ -0,0 +1,43 @@ +--- +layout: "cloudstack" +page_title: "CloudStack: cloudstack_counter" +sidebar_current: "docs-cloudstack-counter" +description: |- + Creates a counter for autoscale policies. +--- + +# cloudstack_counter + +Creates a counter that can be used in autoscale conditions to monitor performance metrics. + +## Example Usage + +```hcl +resource "cloudstack_counter" "cpu_counter" { + name = "cpu-counter" + source = "cpu" + value = "cpuused" +} + +resource "cloudstack_counter" "memory_counter" { + name = "memory-counter" + source = "memory" + value = "memoryused" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the counter. + +* `source` - (Required) The source of the counter (e.g., "cpu", "memory", "network"). + +* `value` - (Required) The specific metric value to monitor (e.g., "cpuused", "memoryused"). + +## Attributes Reference + +The following attributes are exported: + +* `id` - The counter ID.