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

Verified Access: Limit acceptance test concurrency #35769

Merged
merged 14 commits into from
Feb 12, 2024
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
1 change: 1 addition & 0 deletions docs/acc-test-environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Environment variables (beyond standard AWS Go SDK ones) used by acceptance testi
| `AWS_DETECTIVE_MEMBER_EMAIL` | Email address for Detective Member testing. A valid email address associated with an AWS root account is required for tests to pass. |
| `AWS_EC2_CLIENT_VPN_LIMIT` | Concurrency limit for Client VPN acceptance tests. [Default is 5](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/limits.html) if not specified. |
| `AWS_EC2_EIP_PUBLIC_IPV4_POOL` | Identifier for EC2 Public IPv4 Pool for EC2 EIP testing. |
| `AWS_EC2_VERIFIED_ACCESS_INSTANCE_LIMIT` | Concurrency limit for Verified Access acceptance tests. [Default is 5](https://docs.aws.amazon.com/verified-access/latest/ug/verified-access-quotas.html) if not specified. |
| `AWS_GUARDDUTY_MEMBER_ACCOUNT_ID` | Identifier of AWS Account for GuardDuty Member testing. **DEPRECATED:** Should be replaced with standard alternate account handling for tests. |
| `AWS_GUARDDUTY_MEMBER_EMAIL` | Email address for GuardDuty Member testing. **DEPRECATED:** It may be possible to use a placeholder email address instead. |
| `AWS_LAMBDA_IMAGE_LATEST_ID` | ECR repository image URI (tagged as `latest`) for Lambda container image acceptance tests.
Expand Down
25 changes: 23 additions & 2 deletions internal/acctest/acctest.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import (
"github.com/hashicorp/terraform-provider-aws/internal/envvar"
"github.com/hashicorp/terraform-provider-aws/internal/errs"
"github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag"
tfsync "github.com/hashicorp/terraform-provider-aws/internal/experimental/sync"
"github.com/hashicorp/terraform-provider-aws/internal/provider"
tfaccount "github.com/hashicorp/terraform-provider-aws/internal/service/account"
tfacmpca "github.com/hashicorp/terraform-provider-aws/internal/service/acmpca"
Expand Down Expand Up @@ -2478,7 +2479,7 @@ func SkipIfEnvVarNotSet(t *testing.T, key string) string {
}

// RunSerialTests1Level runs test cases in parallel, optionally sleeping between each.
func RunSerialTests1Level(t *testing.T, testCases map[string]func(t *testing.T), d time.Duration) {
func RunSerialTests1Level(t *testing.T, testCases map[string]func(*testing.T), d time.Duration) {
t.Helper()

for name, tc := range testCases {
Expand All @@ -2491,7 +2492,7 @@ func RunSerialTests1Level(t *testing.T, testCases map[string]func(t *testing.T),
}

// RunSerialTests2Levels runs test cases in parallel, optionally sleeping between each.
func RunSerialTests2Levels(t *testing.T, testCases map[string]map[string]func(t *testing.T), d time.Duration) {
func RunSerialTests2Levels(t *testing.T, testCases map[string]map[string]func(*testing.T), d time.Duration) {
t.Helper()

for group, m := range testCases {
Expand All @@ -2502,6 +2503,26 @@ func RunSerialTests2Levels(t *testing.T, testCases map[string]map[string]func(t
}
}

// RunLimitedConcurrencyTests2Levels runs test cases with concurrency limited via `semaphore`.
func RunLimitedConcurrencyTests2Levels(t *testing.T, semaphore tfsync.Semaphore, testCases map[string]map[string]func(*testing.T, tfsync.Semaphore)) {
t.Helper()

for group, m := range testCases {
m := m
for name, tc := range m {
tc := tc
t.Run(fmt.Sprintf("%s_%s", group, name), func(t *testing.T) {
t.Cleanup(func() {
if os.Getenv(resource.EnvTfAcc) != "" {
semaphore.Notify()
}
})
tc(t, semaphore)
})
}
}
}

// TestNoMatchResourceAttr ensures a value matching a regular expression is
// NOT stored in state for the given name and key combination. Same as resource.TestMatchResourceAttr()
// except negative.
Expand Down
2 changes: 1 addition & 1 deletion internal/envvar/envvar.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"fmt"
"os"

"github.com/mitchellh/go-testing-interface"
testing "github.com/mitchellh/go-testing-interface"
)

// Standard AWS environment variables used in the Terraform AWS Provider testing.
Expand Down
45 changes: 31 additions & 14 deletions internal/experimental/sync/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,46 @@
package sync

import (
"fmt"
"log"
"os"
"strconv"
"testing"
"sync"

testing "github.com/mitchellh/go-testing-interface"
)

// Semaphore can be used to limit concurrent executions. This can be used to work with resources with low quotas
// Semaphore can be used to limit concurrent executions.
// This can be used to work with resources with low quotas.
type Semaphore chan struct{}

// InitializeSemaphore initializes a semaphore with a default capacity or overrides it using an environment variable
var semaphoreKV = &struct {
lock sync.Locker
store map[string]Semaphore
}{
lock: &sync.Mutex{},
store: make(map[string]Semaphore),
}

// GetSemaphore returns a named semaphore with a default capacity or overrides it using an environment variable
// NOTE: this is currently an experimental feature and is likely to change. DO NOT USE.
func InitializeSemaphore(envvar string, defaultLimit int) Semaphore {
limit := defaultLimit
x := os.Getenv(envvar)
if x != "" {
var err error
limit, err = strconv.Atoi(x)
if err != nil {
panic(fmt.Errorf("could not parse %q: expected integer, got %q", envvar, x))
func GetSemaphore(key, envvar string, defaultLimit int) Semaphore {
semaphoreKV.lock.Lock()
defer semaphoreKV.lock.Unlock()

semaphore, ok := semaphoreKV.store[key]
if !ok {
limit := defaultLimit
if v := os.Getenv(envvar); v != "" {
if v, err := strconv.Atoi(v); err == nil {
limit = v
}
}

semaphore = make(Semaphore, limit)
semaphoreKV.store[key] = semaphore
}
return make(Semaphore, limit)

return semaphore
}

// Wait waits for a semaphore before continuing
Expand All @@ -48,7 +65,7 @@ func (s Semaphore) Notify() {

// TestAccPreCheckSyncronized waits for a semaphore and skips the test if there is no capacity
// NOTE: this is currently an experimental feature and is likely to change. DO NOT USE.
func TestAccPreCheckSyncronize(t *testing.T, semaphore Semaphore, resource string) {
func TestAccPreCheckSyncronize(t testing.T, semaphore Semaphore, resource string) {
if cap(semaphore) == 0 {
t.Skipf("concurrency for %s testing set to 0", resource)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/service/ec2/find.go
Original file line number Diff line number Diff line change
Expand Up @@ -7246,7 +7246,7 @@ func FindVerifiedAccessInstanceTrustProviderAttachmentExists(ctx context.Context
}

return &retry.NotFoundError{
LastError: fmt.Errorf("Verified Access Instance (%s) Trust Provider (%s) Association not found", vaiID, vatpID),
LastError: fmt.Errorf("Verified Access Instance (%s) Trust Provider (%s) Attachment not found", vaiID, vatpID),
}
}

Expand Down
Loading
Loading