Skip to content
2 changes: 2 additions & 0 deletions internal/purchase/errnotes.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ var serverNotePatterns = []string{
regexp.QuoteMeta(redshiftNoJournalRefusal),
regexp.QuoteMeta(redshiftRecentBuyRefusal),
regexp.QuoteMeta(redshiftSettleRefusal),
regexp.QuoteMeta(gcpDuplicateCheckFailedNote),
regexp.QuoteMeta(gcpRecentCommitmentNote),
`EC2 recommendation for ` + resourceTypePattern + ` has zonal scope but no AvailabilityZone: refusing to buy a zonal RI in an arbitrary AZ; re-fetch the recommendation`,
`zonal RI offering needs an availability zone; re-fetch the recommendation`,
`email notifier not configured for this deployment`,
Expand Down
4 changes: 3 additions & 1 deletion internal/purchase/errnotes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ func TestSanitizeStoredError_AllowlistedNotesPassThroughVerbatim(t *testing.T) {
redshiftNoJournalRefusal,
redshiftRecentBuyRefusal,
redshiftSettleRefusal,
gcpDuplicateCheckFailedNote,
gcpRecentCommitmentNote,
"EC2 recommendation for m5.large has zonal scope but no AvailabilityZone: refusing to buy a zonal RI in an arbitrary AZ; re-fetch the recommendation",
"zonal RI offering needs an availability zone; re-fetch the recommendation",
"email notifier not configured for this deployment",
Expand Down Expand Up @@ -213,7 +215,7 @@ func TestSanitizeStoredError_ResolveNoteExchangeIDIsClosed(t *testing.T) {
// The Redshift gate texts are fixed: an appended execution id or raw text must
// not pass verbatim.
func TestSanitizeStoredError_RedshiftGateRefusalsAreClosed(t *testing.T) {
for _, text := range []string{redshiftConflictRefusal, redshiftHorizonRefusal, redshiftNoStampRefusal, redshiftFailedConflictRefusal, redshiftNoJournalRefusal, redshiftRecentBuyRefusal, redshiftSettleRefusal} {
for _, text := range []string{redshiftConflictRefusal, redshiftHorizonRefusal, redshiftNoStampRefusal, redshiftFailedConflictRefusal, redshiftNoJournalRefusal, redshiftRecentBuyRefusal, redshiftSettleRefusal, gcpDuplicateCheckFailedNote, gcpRecentCommitmentNote} {
assert.Equal(t, text, SanitizeStoredError(text))
for _, variant := range []string{
text + " (execution 00000000-0000-0000-0000-000000000000, running)",
Expand Down
32 changes: 26 additions & 6 deletions internal/purchase/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,7 @@ func (m *Manager) processPurchaseRecommendations(ctx context.Context, exec *conf
// Build the list of selected indices once so the fan-out closure only
// has to look up rec[i] (no second pass over the full slice).
selected := selectedIndices(exec.Recommendations)
ownGCPNames := gcpOwnCommitmentNames(exec, selected)
if len(selected) == 0 {
logging.Infof("purchase[%s]: no selected recommendations, nothing to execute (account=%s plan=%q)",
exec.ExecutionID, accountID, plan.Name)
Expand Down Expand Up @@ -833,7 +834,7 @@ func (m *Manager) processPurchaseRecommendations(ctx context.Context, exec *conf
recOpts := opts
recOpts.IdempotencyToken = common.DeriveIdempotencyToken(idempotencyLineageKey(exec), i)
recOpts.AttemptJournal = m.attemptJournal
purchaseResult, err := m.executeSinglePurchase(ctx, rec, provCfg, recOpts)
purchaseResult, err := m.executeSinglePurchase(ctx, rec, provCfg, recOpts, ownGCPNames)
return recPurchaseOutcome{index: i, purchase: purchaseResult, err: err}, nil
}, getMaxAccountParallelism())

Expand Down Expand Up @@ -1247,7 +1248,7 @@ func logRecCtxErr(executionID, recTuple string, elapsed time.Duration, recCtxErr
// provCfg carries optional per-account credentials; pass nil to use ambient credentials.
// opts carries execution-level metadata (the source surface) that providers stamp
// onto the commitment they create.
func (m *Manager) executeSinglePurchase(ctx context.Context, rec config.RecommendationRecord, provCfg *provider.Config, opts common.PurchaseOptions) (common.PurchaseResult, error) {
func (m *Manager) executeSinglePurchase(ctx context.Context, rec config.RecommendationRecord, provCfg *provider.Config, opts common.PurchaseOptions, ownGCPNames map[string]bool) (common.PurchaseResult, error) {
// Per-purchase Info logs tagged with the owning execution ID so a
// CloudWatch filter on the execution UUID surfaces every step of the
// purchase attempt -- provider construction, service-client lookup,
Expand Down Expand Up @@ -1345,13 +1346,11 @@ func (m *Manager) executeSinglePurchase(ctx context.Context, rec config.Recommen
// CloudWatch shows whether a failure came from the SDK call itself
// (which the AWS SDK retries up to 3× × 30s by default) versus
// something earlier in the flow — issue #667.
tCall := time.Now()
// go's Redshift dedupe bounds its DescribeReservedNodes match window by RequestedAt, which must
// be the lineage's first dispatch time, never this call's.
opts, err = withDispatchTime(rec, opts)
opts, err = preSubmitChecks(recCtx, serviceClient, rec, recommendation, opts, ownGCPNames)
if err != nil {
return common.PurchaseResult{}, err
}
tCall := time.Now()
result, err := serviceClient.PurchaseCommitment(recCtx, recommendation, opts)
elapsed := time.Since(tCall)
if err != nil {
Expand Down Expand Up @@ -1381,6 +1380,27 @@ func (m *Manager) executeSinglePurchase(ctx context.Context, rec config.Recommen
return result, nil
}

// preSubmitChecks runs what must hold before PurchaseCommitment: the GCP
// recent-commitment refusal (platform#682, a beforeSubmission error) and go's
// Redshift dispatch time, which bounds its DescribeReservedNodes match window
// by RequestedAt and must be the lineage's first dispatch time, never this
// call's.
func preSubmitChecks(ctx context.Context, client provider.ServiceClient, rec config.RecommendationRecord, recommendation common.Recommendation, opts common.PurchaseOptions, ownGCPNames map[string]bool) (common.PurchaseOptions, error) {
if err := gcpRecentCommitmentRefusal(ctx, client, recommendation, ownGCPNames, ownCommitmentName(opts)); err != nil {
return opts, err
}
return withDispatchTime(rec, opts)
}

// ownCommitmentName is the GCP commitment name this rec's purchase call would
// create, or "" when the call carries no idempotency token.
func ownCommitmentName(opts common.PurchaseOptions) string {
if opts.IdempotencyToken == "" {
return ""
}
return gcpCommitmentName(opts.IdempotencyToken)
}

// purchaseCallError wraps a failed PurchaseCommitment. When the run context
// ended mid-call the request may have landed, so the error also carries
// ErrOutcomeUnknown: the row records an unknown outcome, never a plain failure.
Expand Down
145 changes: 145 additions & 0 deletions internal/purchase/gcp_recent_check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package purchase

import (
"context"
"errors"
"fmt"
"slices"

"github.com/LeanerCloud/cloud-commitments-go/pkg/common"
"github.com/LeanerCloud/cloud-commitments-go/pkg/logging"
"github.com/LeanerCloud/cloud-commitments-go/pkg/provider"
"github.com/LeanerCloud/cloud-commitments-go/pkg/recfilter"

"github.com/LeanerCloud/cloud-commitments-platform/internal/config"
)

// Fixed refusal notes (platform#682). Both are stored verbatim and are on the
// stored-error allowlist (errnotes.go), so neither may carry provider text.
const (
gcpDuplicateCheckFailedNote = "GCP duplicate check could not list existing commitments; no purchase was submitted"
gcpRecentCommitmentNote = "a GCP commitment for this project, region and machine family started in the last 24 hours; no purchase was submitted, to avoid a duplicate"
)

// gcpCommitmentNameTokenLen mirrors computeengine idempotentNameTokenLen
// (providers/gcp/services/computeengine/client.go:1411).
const gcpCommitmentNameTokenLen = 32

// gcpCommitmentName mirrors computeengine idempotentCommitmentName
// (client.go:1423-1434) for a lowercase hex token: "cud-" + token[:32]. The
// parity test drives the real client, so a rule change there fails it.
func gcpCommitmentName(token string) string {
if len(token) > gcpCommitmentNameTokenLen {
token = token[:gcpCommitmentNameTokenLen]
}
return "cud-" + token
}

func isGCPCompute(providerName string, service common.ServiceType) bool {
return common.ProviderType(providerName) == common.ProviderGCP && service == common.ServiceCompute
}

// gcpOwnCommitmentNames returns the commitment names this execution's selected
// GCP compute recs will create, built from the same per-rec tokens the purchase
// path passes to the provider. A listed commitment with one of these names was
// created by this lineage (a lost-response re-drive, or an earlier rec of the
// same run), so it is not a duplicate of the rec being bought.
func gcpOwnCommitmentNames(exec *config.PurchaseExecution, selected []int) map[string]bool {
names := make(map[string]bool)
for _, i := range selected {
rec := exec.Recommendations[i]
service, _ := mapServiceSlug(rec.Service)
if !isGCPCompute(rec.Provider, service) {
continue
}
names[gcpCommitmentName(common.DeriveIdempotencyToken(idempotencyLineageKey(exec), i))] = true
}
return names
}

// recentCommitmentFilter is the optional GCP filter recfilter asserts on the
// client it lists through (pkg/recfilter/dedupe.go:16-18, :71).
type recentCommitmentFilter interface {
FilterRecommendationsForRecentCommitments(recs []common.Recommendation, existing []common.Commitment) (passed, filtered []common.Recommendation, err error)
}

// gcpRecentDedupeClient lists without this execution's own commitments and
// forwards the GCP filter. Embedding the interface does not promote
// FilterRecommendationsForRecentCommitments, and without it recfilter would
// fall back to its generic map path, which does not understand CUDs.
type gcpRecentDedupeClient struct {
provider.ServiceClient
own map[string]bool
// self is this rec's own commitment name; sawSelf records that the listing
// held it, so the purchase call can adopt it instead of being refused.
self string
sawSelf *bool
}

func (c gcpRecentDedupeClient) GetExistingCommitments(ctx context.Context) ([]common.Commitment, error) {
all, err := c.ServiceClient.GetExistingCommitments(ctx)
if err != nil {
return nil, err
}
for i := range all {
if c.self != "" && all[i].CommitmentID == c.self {
*c.sawSelf = true
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
return slices.DeleteFunc(slices.Clone(all), func(cm common.Commitment) bool { return c.own[cm.CommitmentID] }), nil
}

// FilterRecommendationsForRecentCommitments stamps the project onto the recs
// from the listed commitments (each carries Account = the client's project) and
// delegates. The platform does not know the project in ADC mode, so the rec
// cannot carry it itself.
func (c gcpRecentDedupeClient) FilterRecommendationsForRecentCommitments(recs []common.Recommendation, existing []common.Commitment) (passed, filtered []common.Recommendation, err error) {
inner, ok := c.ServiceClient.(recentCommitmentFilter)
if !ok {
return nil, nil, errors.New("GCP service client has no recent-commitment filter")
}
if len(existing) == 0 {
return nil, nil, errors.New("no recent commitments to filter against")
}
project := existing[0].Account
for i := range existing {
if existing[i].Account != project {
return nil, nil, fmt.Errorf("recent commitments span projects %q and %q", project, existing[i].Account)
}
}
stamped := slices.Clone(recs)
for i := range stamped {
stamped[i].Account = project
}
return inner.FilterRecommendationsForRecentCommitments(stamped, existing)
}

// gcpRecentCommitmentRefusal refuses a GCP compute rec when the project already
// has a commitment of the rec's region and machine family that started in the
// last 24 hours, and fails closed when the commitments cannot be listed. It
// returns nil for every other rec, and when this rec's own commitment (self) is
// already listed: a lost-response retry must reach the purchase call, which
// adopts it, rather than be refused by another lineage's commitment. A refusal
// is a beforeSubmission error: the purchase call is never made.
func gcpRecentCommitmentRefusal(ctx context.Context, client provider.ServiceClient, rec common.Recommendation, ownNames map[string]bool, self string) error {
if !isGCPCompute(string(rec.Provider), rec.Service) {
return nil
}
rec.CommitmentType = common.CommitmentCUD
sawSelf := false
passed, filtered, err := recfilter.NewDuplicateChecker(0).AdjustRecommendationsForExisting(
ctx, []common.Recommendation{rec}, gcpRecentDedupeClient{ServiceClient: client, own: ownNames, self: self, sawSelf: &sawSelf})
switch {
case sawSelf:
return nil
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
case err != nil:
logging.Errorf("GCP duplicate check for %s in %s failed: %v", rec.ResourceType, rec.Region, err)
return beforeSubmission(errors.New(gcpDuplicateCheckFailedNote))
case len(filtered) > 0:
return beforeSubmission(errors.New(gcpRecentCommitmentNote))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
case len(passed) != 1:
logging.Errorf("GCP duplicate check for %s in %s returned %d passed recs, want 1", rec.ResourceType, rec.Region, len(passed))
return beforeSubmission(errors.New(gcpDuplicateCheckFailedNote))
}
return nil
}
Loading
Loading