Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

r/aws_elasticache_user_group_association: Handle concurrent updates (#28688) #28689

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/28689.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
resource/aws_elasticache_user_group_association: Retry on `InvalidUserGroupState` errors to handle concurrent updates
```
13 changes: 9 additions & 4 deletions internal/service/elasticache/user_group_association.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ func resourceUserGroupAssociationCreate(d *schema.ResourceData, meta interface{}

id := userGroupAssociationID(d.Get("user_group_id").(string), d.Get("user_id").(string))

_, err := tfresource.RetryWhenNotFound(30*time.Second, func() (interface{}, error) {
return conn.ModifyUserGroup(input)
})
_, err := tfresource.RetryWhenAWSErrCodeEquals(10*time.Minute, func() (interface{}, error) {
return tfresource.RetryWhenNotFound(30*time.Second, func() (interface{}, error) {
return conn.ModifyUserGroup(input)
})
}, elasticache.ErrCodeInvalidUserGroupStateFault)

if err != nil {
return fmt.Errorf("creating ElastiCache User Group Association (%q): %w", id, err)
Expand Down Expand Up @@ -129,7 +131,10 @@ func resourceUserGroupAssociationDelete(d *schema.ResourceData, meta interface{}
UserIdsToRemove: aws.StringSlice([]string{d.Get("user_id").(string)}),
}

_, err := conn.ModifyUserGroup(input)
_, err := tfresource.RetryWhenAWSErrCodeEquals(10*time.Minute, func() (interface{}, error) {
return conn.ModifyUserGroup(input)
}, elasticache.ErrCodeInvalidUserGroupStateFault)

if err != nil && !tfawserr.ErrMessageContains(err, elasticache.ErrCodeInvalidParameterValueException, "not a member") {
return fmt.Errorf("deleting ElastiCache User Group Association (%q): %w", d.Id(), err)
}
Expand Down
67 changes: 67 additions & 0 deletions internal/service/elasticache/user_group_association_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,35 @@ func TestAccElastiCacheUserGroupAssociation_disappears(t *testing.T) {
})
}

func TestAccElastiCacheUserGroupAssociation_multiple(t *testing.T) {
if testing.Short() {
t.Skip("skipping long-running test in short mode")
}

rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName1 := "aws_elasticache_user_group_association.test1"
resourceName2 := "aws_elasticache_user_group_association.test2"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ErrorCheck: acctest.ErrorCheck(t, elasticache.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckUserGroupAssociationDestroy,
Steps: []resource.TestStep{
{
Config: testAccUserGroupAssociationConfig_preMultiple(rName),
},
{
Config: testAccUserGroupAssociationConfig_multiple(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckUserGroupAssociationExists(resourceName1),
testAccCheckUserGroupAssociationExists(resourceName2),
),
},
},
})
}

func testAccCheckUserGroupAssociationDestroy(s *terraform.State) error {
return testAccCheckUserGroupAssociationDestroyWithProvider(s, acctest.Provider)
}
Expand Down Expand Up @@ -268,3 +297,41 @@ resource "aws_elasticache_user_group_association" "test" {
}
`, rName))
}

func testAccUserGroupAssociationConfig_preMultiple(rName string) string {
return acctest.ConfigCompose(
testAccUserGroupAssociationBaseConfig(rName),
fmt.Sprintf(`
resource "aws_elasticache_user" "test3" {
user_id = "%[1]s-3"
user_name = "username2"
access_string = "on ~app::* -@all +@read +@hash +@bitmap +@geo -setbit -bitfield -hset -hsetnx -hmset -hincrby -hincrbyfloat -hdel -bitop -geoadd -georadius -georadiusbymember"
engine = "REDIS"
passwords = ["password123456789"]
}
`, rName))
}

func testAccUserGroupAssociationConfig_multiple(rName string) string {
return acctest.ConfigCompose(
testAccUserGroupAssociationBaseConfig(rName),
fmt.Sprintf(`
resource "aws_elasticache_user" "test3" {
user_id = "%[1]s-3"
user_name = "username2"
access_string = "on ~app::* -@all +@read +@hash +@bitmap +@geo -setbit -bitfield -hset -hsetnx -hmset -hincrby -hincrbyfloat -hdel -bitop -geoadd -georadius -georadiusbymember"
engine = "REDIS"
passwords = ["password123456789"]
}

resource "aws_elasticache_user_group_association" "test1" {
user_group_id = aws_elasticache_user_group.test.user_group_id
user_id = aws_elasticache_user.test2.user_id
}

resource "aws_elasticache_user_group_association" "test2" {
user_group_id = aws_elasticache_user_group.test.user_group_id
user_id = aws_elasticache_user.test3.user_id
}
`, rName))
}