diff --git a/cmd/multi_service_max_instances_test.go b/cmd/multi_service_max_instances_test.go
index 8e52d4bd3..51abe204e 100644
--- a/cmd/multi_service_max_instances_test.go
+++ b/cmd/multi_service_max_instances_test.go
@@ -1,8 +1,15 @@
package main
import (
+ "bytes"
"context"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
"path/filepath"
+ "strings"
+ "sync"
"testing"
"github.com/LeanerCloud/cloud-commitments-go/pkg/common"
@@ -12,6 +19,91 @@ import (
"github.com/stretchr/testify/require"
)
+// maxInstancesAWSStub is the offline AWS endpoint behind the duplicate check
+// (checkDuplicates -> GetExistingCommitments). Every request must match a
+// fixture keyed "host Action"; anything else fails the test, so a request that
+// would have left the machine is reported instead of silently timing out.
+type maxInstancesAWSStub struct {
+ t *testing.T
+ mu sync.Mutex
+ received map[string]int
+ log *bytes.Buffer
+}
+
+// maxInstancesAWSFixtures answer the Query-protocol DescribeReserved* calls
+// with an empty existing-commitment list.
+var maxInstancesAWSFixtures = map[string]string{
+ "rds.us-east-1.amazonaws.com DescribeReservedDBInstances": maxInstancesEmptyRDS,
+ "rds.us-west-2.amazonaws.com DescribeReservedDBInstances": maxInstancesEmptyRDS,
+ "rds.eu-west-1.amazonaws.com DescribeReservedDBInstances": maxInstancesEmptyRDS,
+ "elasticache.us-east-1.amazonaws.com DescribeReservedCacheNodes": maxInstancesEmptyElastiCache,
+ "elasticache.us-west-2.amazonaws.com DescribeReservedCacheNodes": maxInstancesEmptyElastiCache,
+ "elasticache.eu-west-1.amazonaws.com DescribeReservedCacheNodes": maxInstancesEmptyElastiCache,
+}
+
+const (
+ maxInstancesEmptyRDS = `` +
+ `` +
+ `fixture`
+ maxInstancesEmptyElastiCache = `` +
+ `` +
+ `fixture`
+)
+
+func (s *maxInstancesAWSStub) RoundTrip(r *http.Request) (*http.Response, error) {
+ b, _ := io.ReadAll(r.Body)
+ form, _ := url.ParseQuery(string(b))
+ key := r.URL.Host + " " + form.Get("Action")
+ s.mu.Lock()
+ s.received[key]++
+ s.mu.Unlock()
+ body, ok := maxInstancesAWSFixtures[key]
+ status := http.StatusOK
+ if !ok {
+ s.t.Errorf("unexpected AWS request %s %s%s", r.Method, key, r.URL.Path)
+ status, body = http.StatusInternalServerError, "unexpected request"
+ }
+ return &http.Response{
+ StatusCode: status,
+ Header: http.Header{"Content-Type": []string{"text/xml"}},
+ Body: io.NopCloser(strings.NewReader(body)),
+ Request: r,
+ }, nil
+}
+
+// newMaxInstancesAWSConfig returns an aws.Config whose HTTP client is the
+// stub and whose credentials are anonymous, so requests are unsigned and need
+// no environment.
+func newMaxInstancesAWSConfig(t *testing.T) (aws.Config, *maxInstancesAWSStub) {
+ t.Helper()
+ stub := &maxInstancesAWSStub{t: t, received: map[string]int{}, log: captureAppLog(t)}
+ return aws.Config{
+ Region: "us-east-1",
+ HTTPClient: &http.Client{Transport: stub},
+ Credentials: aws.AnonymousCredentials{},
+ Retryer: func() aws.Retryer { return aws.NopRetryer{} },
+ }, stub
+}
+
+// requireDuplicateChecks asserts the duplicate check really ran against the
+// stub: one call per (service, region) pair, no other request, and every
+// response parsed.
+func (s *maxInstancesAWSStub) requireDuplicateChecks(t *testing.T, pairs int) {
+ t.Helper()
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ total := 0
+ for k, n := range s.received {
+ assert.Contains(t, maxInstancesAWSFixtures, k)
+ assert.Equalf(t, 1, n, "duplicate-check requests for %s", k)
+ total += n
+ }
+ // A fixture the SDK cannot parse only logs this warning in dry-run, so the
+ // request count alone would not notice a broken fixture.
+ assert.NotContains(t, s.log.String(), "Could not check for existing RIs")
+ require.Equal(t, pairs, total, fmt.Sprintf("duplicate-check requests received: %v", s.received))
+}
+
// maxInstancesFixtureServices and maxInstancesFixtureRegions describe the
// multi-service, multi-region fan-out the run-wide cap has to survive. A
// single-service or single-region fixture stays green with the per-region bug
@@ -77,7 +169,7 @@ func TestMaxInstancesCapsWholeRunAcrossServicesAndRegions(t *testing.T) {
const maxInstances = 10
ctx := context.Background()
- awsCfg := aws.Config{Region: "us-east-1"}
+ awsCfg, awsStub := newMaxInstancesAWSConfig(t)
origCfg := toolCfg
t.Cleanup(func() { toolCfg = origCfg })
@@ -94,6 +186,7 @@ func TestMaxInstancesCapsWholeRunAcrossServicesAndRegions(t *testing.T) {
accountCache := NewAccountAliasCache(awsCfg)
allRecs, drops, _ := fetchAllRecs(ctx, awsCfg, mockClient, accountCache,
maxInstancesFixtureServices, engineVersionData{}, toolCfg, nil)
+ awsStub.requireDuplicateChecks(t, len(maxInstancesFixtureServices)*len(maxInstancesFixtureRegions))
pairs := len(maxInstancesFixtureServices) * len(maxInstancesFixtureRegions)
naturalTotal := pairs * countPerFixtureRec
@@ -149,7 +242,7 @@ func TestMaxInstancesKeepsHighestSavingsNotFirstFetched(t *testing.T) {
)
ctx := context.Background()
- awsCfg := aws.Config{Region: "us-east-1"}
+ awsCfg, awsStub := newMaxInstancesAWSConfig(t)
origCfg := toolCfg
t.Cleanup(func() { toolCfg = origCfg })
@@ -174,6 +267,7 @@ func TestMaxInstancesKeepsHighestSavingsNotFirstFetched(t *testing.T) {
accountCache := NewAccountAliasCache(awsCfg)
allRecs, drops, _ := fetchAllRecs(ctx, awsCfg, mockClient, accountCache,
maxInstancesFixtureServices, engineVersionData{}, toolCfg, nil)
+ awsStub.requireDuplicateChecks(t, len(maxInstancesFixtureServices)*len(maxInstancesFixtureRegions))
require.Len(t, allRecs, len(maxInstancesFixtureServices)*len(maxInstancesFixtureRegions))
scored := scoreLimitAndDisplay(allRecs, toolCfg, drops)
@@ -223,7 +317,7 @@ func TestMaxInstancesNeverPurchasesBelowMinCount(t *testing.T) {
)
ctx := context.Background()
- awsCfg := aws.Config{Region: "us-east-1"}
+ awsCfg, awsStub := newMaxInstancesAWSConfig(t)
origCfg := toolCfg
t.Cleanup(func() { toolCfg = origCfg })
@@ -243,6 +337,7 @@ func TestMaxInstancesNeverPurchasesBelowMinCount(t *testing.T) {
accountCache := NewAccountAliasCache(awsCfg)
allRecs, drops, _ := fetchAllRecs(ctx, awsCfg, mockClient, accountCache,
maxInstancesFixtureServices, engineVersionData{}, toolCfg, nil)
+ awsStub.requireDuplicateChecks(t, len(maxInstancesFixtureServices)*len(maxInstancesFixtureRegions))
require.Len(t, allRecs, len(maxInstancesFixtureServices)*len(maxInstancesFixtureRegions))
scored := scoreLimitAndDisplay(allRecs, toolCfg, drops)
@@ -312,7 +407,7 @@ func TestDropTruncatedBelowMinCount(t *testing.T) {
// run that never asked for one.
func TestMaxInstancesNotAppliedWhenUnset(t *testing.T) {
ctx := context.Background()
- awsCfg := aws.Config{Region: "us-east-1"}
+ awsCfg, awsStub := newMaxInstancesAWSConfig(t)
origCfg := toolCfg
t.Cleanup(func() { toolCfg = origCfg })
@@ -329,6 +424,7 @@ func TestMaxInstancesNotAppliedWhenUnset(t *testing.T) {
accountCache := NewAccountAliasCache(awsCfg)
allRecs, drops, _ := fetchAllRecs(ctx, awsCfg, mockClient, accountCache,
maxInstancesFixtureServices, engineVersionData{}, toolCfg, nil)
+ awsStub.requireDuplicateChecks(t, len(maxInstancesFixtureServices)*len(maxInstancesFixtureRegions))
scored := scoreLimitAndDisplay(allRecs, toolCfg, drops)