Skip to content

Commit

Permalink
fix(AS): Fix some problems with AS group resource and add new fields (h…
Browse files Browse the repository at this point in the history
…uaweicloud#5025)

* fix(AS): fix some lint error in AS group resource

* fix(AS): add AS group new field and support editing `delete_publicip`
  • Loading branch information
deer-hang authored Jun 18, 2024
1 parent c209935 commit 3284503
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 62 deletions.
17 changes: 17 additions & 0 deletions docs/resources/as_group.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,13 @@ The following arguments are supported:
`yearly/monthly` basis, they will not be released when the ECSs are removed.
+ **false**: The EIPs bound to ECSs will be unbound but will not be released when the ECSs are removed.

* `delete_volume` - (Optional, Bool) Specifies whether to delete the data disks attached to ECSs when the ECSs are removed.
Defaults to **false**.

+ **true**: The data disks attached to ECSs will be released when the ECSs are removed. If the data disks are billed a
`yearly/monthly` basis, they will not be deleted when ECSs are removed.
+ **false**: The data disks attached to ECSs will be detached but will not be released when the ECSs are removed.

* `delete_instances` - (Optional, String) Specifies whether to delete the instances in the AS group when deleting
the AS group. The options are **yes** and **no**.

Expand Down Expand Up @@ -237,6 +244,16 @@ The `lbaas_listeners` block supports:
compared to other backend ECSs added to the same listener. The value of this parameter ranges from **0** to **100**.
Defaults to **1**.

* `protocol_version` - (Optional, String) Specifies the version of instance IP addresses to be associated with the
load balancer. The value can be **ipv4** or **ipv6**. Defaults to **ipv4**.

-> 1. Instances in an AS group do not support IPv4/IPv6 dual-stack on multiple NICs. IPv4/IPv6 dual-stack is only
available for the first NIC that supports both IPv4 and IPv6. The NIC may be a primary NIC or an extension NIC.
<br/>2. Only ECSs with flavors that support IPv6 can use IPv4/IPv6 dual-stack networks. If you want to select IPv6 for
this parameter, make sure that you have selected such ECS flavors in a supported region.
<br/>3. If you add two or more load balancers whose pool_id, protocol_port, and protocol_version settings are totally
same, deduplication will be performed.

## Attribute Reference

In addition to all arguments above, the following attributes are exported:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.18

require (
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
github.com/chnsz/golangsdk v0.0.0-20240614072804-903a1a5d0f98
github.com/chnsz/golangsdk v0.0.0-20240618022207-775fb17e64e9
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-uuid v1.0.3
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chnsz/golangsdk v0.0.0-20240614072804-903a1a5d0f98 h1:XjE/C5YSdQoN2U3l9AwJJtZmftxEjYm/39n03f6DH4E=
github.com/chnsz/golangsdk v0.0.0-20240614072804-903a1a5d0f98/go.mod h1:Erm4hDWxXgAdbkG3+hhJFgRzEL1TvvcroWzw2Gax4uI=
github.com/chnsz/golangsdk v0.0.0-20240618022207-775fb17e64e9 h1:8HfuICiNSdlWJVe+jFFuXXSDMQHYH8cW8NltZKqKVuY=
github.com/chnsz/golangsdk v0.0.0-20240618022207-775fb17e64e9/go.mod h1:Erm4hDWxXgAdbkG3+hhJFgRzEL1TvvcroWzw2Gax4uI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,14 @@ func TestAccASGroup_basic(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "desire_instance_number", "0"),
resource.TestCheckResourceAttr(resourceName, "min_instance_number", "0"),
resource.TestCheckResourceAttr(resourceName, "max_instance_number", "5"),
resource.TestCheckResourceAttr(resourceName, "lbaas_listeners.0.protocol_port", "8080"),
resource.TestCheckResourceAttr(resourceName, "delete_publicip", "false"),
resource.TestCheckResourceAttr(resourceName, "delete_volume", "true"),
resource.TestCheckResourceAttrPair(resourceName, "lbaas_listeners.0.pool_id",
"huaweicloud_lb_pool.pool_1", "id"),
resource.TestCheckResourceAttrPair(resourceName, "lbaas_listeners.0.protocol_port",
"huaweicloud_lb_listener.listener_1", "protocol_port"),
resource.TestCheckResourceAttr(resourceName, "lbaas_listeners.0.weight", "20"),
resource.TestCheckResourceAttr(resourceName, "lbaas_listeners.0.protocol_version", "ipv4"),
resource.TestCheckResourceAttr(resourceName, "networks.0.source_dest_check", "true"),
resource.TestCheckResourceAttr(resourceName, "tags.foo", "bar"),
resource.TestCheckResourceAttr(resourceName, "tags.key", "value"),
Expand All @@ -70,6 +77,14 @@ func TestAccASGroup_basic(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "description", "this is an updated AS group"),
resource.TestCheckResourceAttr(resourceName, "min_instance_number", "0"),
resource.TestCheckResourceAttr(resourceName, "max_instance_number", "5"),
resource.TestCheckResourceAttr(resourceName, "delete_publicip", "true"),
resource.TestCheckResourceAttr(resourceName, "delete_volume", "false"),
resource.TestCheckResourceAttrPair(resourceName, "lbaas_listeners.0.pool_id",
"huaweicloud_lb_pool.pool_1", "id"),
resource.TestCheckResourceAttrPair(resourceName, "lbaas_listeners.0.protocol_port",
"huaweicloud_lb_listener.listener_1", "protocol_port"),
resource.TestCheckResourceAttr(resourceName, "lbaas_listeners.0.weight", "30"),
resource.TestCheckResourceAttr(resourceName, "lbaas_listeners.0.protocol_version", "ipv4"),
resource.TestCheckResourceAttr(resourceName, "tags.foo", "bar"),
resource.TestCheckResourceAttr(resourceName, "tags.owner", "terraform"),
resource.TestCheckResourceAttr(resourceName, "agency_name", "ims_admin"),
Expand All @@ -89,6 +104,7 @@ func TestAccASGroup_basic(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
rc.CheckResourceExists(),
resource.TestCheckResourceAttr(resourceName, "status", "PAUSED"),
resource.TestCheckResourceAttr(resourceName, "lbaas_listeners.0.weight", "1"),
),
},
{
Expand Down Expand Up @@ -132,6 +148,8 @@ func TestAccASGroup_withEpsId(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
rc.CheckResourceExists(),
resource.TestCheckResourceAttr(resourceName, "enterprise_project_id", acceptance.HW_ENTERPRISE_PROJECT_ID_TEST),
resource.TestCheckResourceAttr(resourceName, "lbaas_listeners.0.weight", "1"),
resource.TestCheckResourceAttr(resourceName, "lbaas_listeners.0.protocol_version", "ipv4"),
),
},
},
Expand Down Expand Up @@ -165,6 +183,8 @@ func TestAccASGroup_forceDelete(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "max_instance_number", "5"),
resource.TestCheckResourceAttr(resourceName, "instances.#", "2"),
resource.TestCheckResourceAttr(resourceName, "status", "INSERVICE"),
resource.TestCheckResourceAttr(resourceName, "delete_publicip", "true"),
resource.TestCheckResourceAttr(resourceName, "delete_volume", "true"),
),
},
},
Expand Down Expand Up @@ -255,6 +275,8 @@ resource "huaweicloud_as_group" "acc_as_group"{
vpc_id = huaweicloud_vpc.test.id
max_instance_number = 5
description = "this is a basic AS group"
delete_publicip = false
delete_volume = true
networks {
id = huaweicloud_vpc_subnet.test.id
Expand All @@ -263,8 +285,10 @@ resource "huaweicloud_as_group" "acc_as_group"{
id = huaweicloud_networking_secgroup.test.id
}
lbaas_listeners {
pool_id = huaweicloud_lb_pool.pool_1.id
protocol_port = huaweicloud_lb_listener.listener_1.protocol_port
pool_id = huaweicloud_lb_pool.pool_1.id
protocol_port = huaweicloud_lb_listener.listener_1.protocol_port
weight = 20
protocol_version = "ipv4"
}
tags = {
foo = "bar"
Expand All @@ -287,6 +311,8 @@ resource "huaweicloud_as_group" "acc_as_group"{
max_instance_number = 5
description = "this is an updated AS group"
agency_name = "ims_admin"
delete_publicip = true
delete_volume = false
networks {
id = huaweicloud_vpc_subnet.test.id
Expand All @@ -295,8 +321,10 @@ resource "huaweicloud_as_group" "acc_as_group"{
id = huaweicloud_networking_secgroup.test.id
}
lbaas_listeners {
pool_id = huaweicloud_lb_pool.pool_1.id
protocol_port = huaweicloud_lb_listener.listener_1.protocol_port
pool_id = huaweicloud_lb_pool.pool_1.id
protocol_port = huaweicloud_lb_listener.listener_1.protocol_port
weight = 30
protocol_version = "ipv4"
}
tags = {
foo = "bar"
Expand Down Expand Up @@ -408,6 +436,8 @@ resource "huaweicloud_as_group" "acc_as_group"{
max_instance_number = 5
force_delete = true
vpc_id = huaweicloud_vpc.test.id
delete_publicip = true
delete_volume = true
networks {
id = huaweicloud_vpc_subnet.test.id
Expand Down
88 changes: 40 additions & 48 deletions huaweicloud/services/as/resource_huaweicloud_as_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ func ResourceASGroup() *schema.Resource {
Optional: true,
Default: 1,
},
// This field has a default value and cannot be set to empty.
"protocol_version": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
},
},
Expand Down Expand Up @@ -197,6 +203,11 @@ func ResourceASGroup() *schema.Resource {
Optional: true,
Default: false,
},
"delete_volume": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"delete_instances": {
Description: "Whether to delete instances when they are removed from the AS group.",
Type: schema.TypeString,
Expand Down Expand Up @@ -317,9 +328,10 @@ func buildLBaaSListenersOpts(listeners []interface{}) []groups.LBaaSListenerOpts
for i, v := range listeners {
item := v.(map[string]interface{})
res[i] = groups.LBaaSListenerOpts{
PoolID: item["pool_id"].(string),
ProtocolPort: item["protocol_port"].(int),
Weight: item["weight"].(int),
PoolID: item["pool_id"].(string),
ProtocolPort: item["protocol_port"].(int),
Weight: item["weight"].(int),
ProtocolVersion: item["protocol_version"].(string),
}
}

Expand Down Expand Up @@ -377,29 +389,6 @@ func getInstancesIDs(allIns []instances.Instance) []string {
return allIDs
}

func getInstancesLifeStates(allIns []instances.Instance) []string {
var allStates = make([]string, len(allIns))
for i, ins := range allIns {
allStates[i] = ins.LifeCycleStatus
}

return allStates
}

func refreshGroupState(client *golangsdk.ServiceClient, groupID string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
asGroup, err := groups.Get(client, groupID).Extract()
if err != nil {
var errDefault404 golangsdk.ErrDefault404
if errors.As(parseGroupResponseError(err), &errDefault404) {
return asGroup, "DELETED", nil
}
return nil, "ERROR", err
}
return asGroup, asGroup.Status, nil
}
}

// When the AS group does not exist, the response body example of the details interface is as follows:
// {"error":{"code":"AS.2007","message":"The AS group does not exist."}}
func parseGroupResponseError(err error) error {
Expand Down Expand Up @@ -432,7 +421,7 @@ func isAllInstanceInService(allIns []instances.Instance) bool {
return true
}

func checkASGroupInstancesInService(ctx context.Context, client *golangsdk.ServiceClient, groupID string, insNum int,
func waitingForAllInstancesInService(ctx context.Context, client *golangsdk.ServiceClient, groupID string, insNum int,
timeout time.Duration) error {
stateConf := &resource.StateChangeConf{
Pending: []string{"PENDING"},
Expand All @@ -458,7 +447,7 @@ func checkASGroupInstancesInService(ctx context.Context, client *golangsdk.Servi
return err
}

func checkASGroupInstancesRemoved(ctx context.Context, client *golangsdk.ServiceClient, groupID string,
func waitingForAllInstancesRemoved(ctx context.Context, client *golangsdk.ServiceClient, groupID string,
timeout time.Duration) error {
stateConf := &resource.StateChangeConf{
Pending: []string{"PENDING"},
Expand All @@ -484,7 +473,7 @@ func checkASGroupInstancesRemoved(ctx context.Context, client *golangsdk.Service
return err
}

func checkASGroupRemoved(ctx context.Context, client *golangsdk.ServiceClient, groupID string, timeout time.Duration) error {
func waitingForASGroupDeleted(ctx context.Context, client *golangsdk.ServiceClient, groupID string, timeout time.Duration) error {
stateConf := &resource.StateChangeConf{
Pending: []string{"PENDING"},
Target: []string{"COMPLETED"},
Expand Down Expand Up @@ -548,7 +537,8 @@ func resourceASGroupCreate(ctx context.Context, d *schema.ResourceData, meta int
Description: d.Get("description").(string),
IamAgencyName: d.Get("agency_name").(string),
IsDeletePublicip: d.Get("delete_publicip").(bool),
EnterpriseProjectID: common.GetEnterpriseProjectID(d, conf),
IsDeleteVolume: d.Get("delete_volume").(bool),
EnterpriseProjectID: conf.GetEnterpriseProjectID(d),
}

asgId, err := groups.Create(asClient, createOpts).Extract()
Expand All @@ -575,11 +565,10 @@ func resourceASGroupCreate(ctx context.Context, d *schema.ResourceData, meta int
}
}

// check all instances are inservice
if desireNum > 0 {
err = checkASGroupInstancesInService(ctx, asClient, asgId, desireNum, d.Timeout(schema.TimeoutCreate))
err = waitingForAllInstancesInService(ctx, asClient, asgId, desireNum, d.Timeout(schema.TimeoutCreate))
if err != nil {
return diag.Errorf("error waiting for instances in the AS group %s to become inservice: %s", asgId, err)
return diag.Errorf("error waiting for all instances in the AS group %s to become INSERVICE: %s", asgId, err)
}
}

Expand All @@ -597,12 +586,12 @@ func resourceASGroupRead(_ context.Context, d *schema.ResourceData, meta interfa
groupID := d.Id()
asg, err := groups.Get(asClient, groupID).Extract()
if err != nil {
return common.CheckDeletedDiag(d, parseGroupResponseError(err), "AS group")
return common.CheckDeletedDiag(d, parseGroupResponseError(err), "error retrieving AS group")
}

allIns, err := getInstancesInGroup(asClient, groupID, nil)
if err != nil {
return diag.Errorf("can not get the instances in AS Group %s: %s", groupID, err)
return diag.Errorf("error retrieving the instances in AS Group %s: %s", groupID, err)
}
allIDs := getInstancesIDs(allIns)

Expand All @@ -625,6 +614,7 @@ func resourceASGroupRead(_ context.Context, d *schema.ResourceData, meta interfa
d.Set("health_periodic_audit_grace_period", asg.HealthPeriodicAuditGrace),
d.Set("instance_terminate_policy", asg.InstanceTerminatePolicy),
d.Set("delete_publicip", asg.DeletePublicip),
d.Set("delete_volume", asg.DeleteVolume),
d.Set("enterprise_project_id", asg.EnterpriseProjectID),
d.Set("availability_zones", asg.AvailableZones),
d.Set("multi_az_scaling_policy", asg.MultiAZPriorityPolicy),
Expand All @@ -639,11 +629,11 @@ func resourceASGroupRead(_ context.Context, d *schema.ResourceData, meta interfa

// save group tags
if resourceTags, err := tags.Get(asClient, groupID).Extract(); err == nil {
tagmap := make(map[string]string)
tagMap := make(map[string]string)
for _, val := range resourceTags.Tags {
tagmap[val.Key] = val.Value
tagMap[val.Key] = val.Value
}
mErr = multierror.Append(mErr, d.Set("tags", tagmap))
mErr = multierror.Append(mErr, d.Set("tags", tagMap))
} else {
log.Printf("[WARN] Error fetching tags of AS group (%s): %s", groupID, err)
}
Expand Down Expand Up @@ -682,9 +672,10 @@ func flattenLBaaSListeners(listeners []groups.LBaaSListener) []map[string]interf
res := make([]map[string]interface{}, len(listeners))
for i, item := range listeners {
res[i] = map[string]interface{}{
"pool_id": item.PoolID,
"protocol_port": item.ProtocolPort,
"weight": item.Weight,
"pool_id": item.PoolID,
"protocol_port": item.ProtocolPort,
"weight": item.Weight,
"protocol_version": item.ProtocolVersion,
}
}
return res
Expand Down Expand Up @@ -729,9 +720,10 @@ func resourceASGroupUpdate(ctx context.Context, d *schema.ResourceData, meta int
HealthPeriodicAuditGrace: d.Get("health_periodic_audit_grace_period").(int),
InstanceTerminatePolicy: d.Get("instance_terminate_policy").(string),
MultiAZPriorityPolicy: d.Get("multi_az_scaling_policy").(string),
IsDeletePublicip: d.Get("delete_publicip").(bool),
IsDeletePublicip: utils.Bool(d.Get("delete_publicip").(bool)),
IsDeleteVolume: utils.Bool(d.Get("delete_volume").(bool)),
Description: utils.String(d.Get("description").(string)),
EnterpriseProjectID: common.GetEnterpriseProjectID(d, conf),
EnterpriseProjectID: conf.GetEnterpriseProjectID(d),
}

if d.HasChange("agency_name") {
Expand Down Expand Up @@ -788,8 +780,8 @@ func forceDeleteASGroup(ctx context.Context, asClient *golangsdk.ServiceClient,
return fmt.Errorf("error deleting AS group %s: %s", d.Id(), err)
}

if err := checkASGroupRemoved(ctx, asClient, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil {
return fmt.Errorf("error deleting AS group %s: %s", d.Id(), err)
if err := waitingForASGroupDeleted(ctx, asClient, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil {
return fmt.Errorf("error waiting for AS group deleted %s: %s", d.Id(), err)
}
return nil
}
Expand All @@ -813,12 +805,12 @@ func deleteAllInstancesFromASGroup(ctx context.Context, asClient *golangsdk.Serv
deleteIns := d.Get("delete_instances").(string)
batchResult := instances.BatchDelete(asClient, d.Id(), allIDs, deleteIns)
if batchResult.Err != nil {
return fmt.Errorf("error removing instancess of AS group: %s", batchResult.Err)
return fmt.Errorf("error removing instances of AS group: %s", batchResult.Err)
}

err := checkASGroupInstancesRemoved(ctx, asClient, d.Id(), d.Timeout(schema.TimeoutDelete))
err := waitingForAllInstancesRemoved(ctx, asClient, d.Id(), d.Timeout(schema.TimeoutDelete))
if err != nil {
return fmt.Errorf("error removing instances from AS group %s: %s", d.Id(), err)
return fmt.Errorf("error waiting for removing instances from AS group %s: %s", d.Id(), err)
}
return nil
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 3284503

Please sign in to comment.