Skip to content

Commit c486546

Browse files
committed
[usage] reduce possibility for races in ResetUsage
1 parent 97a64c9 commit c486546

File tree

3 files changed

+44
-23
lines changed

3 files changed

+44
-23
lines changed

components/gitpod-db/go/cost_center.go

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func (c *CostCenterManager) GetOrCreateCostCenter(ctx context.Context, attributi
104104
// This can happen in the following scenario:
105105
// * User accesses gitpod just after their CostCenter expired, but just before our periodic CostCenter reset kicks in.
106106
if result.BillingStrategy == CostCenter_Other && result.IsExpired() {
107-
cc, err := c.ResetUsage(ctx, result)
107+
cc, err := c.ResetUsage(ctx, result.ID)
108108
if err != nil {
109109
logger.WithError(err).Error("Failed to reset expired usage.")
110110
return CostCenter{}, fmt.Errorf("failed to reset usage for expired cost center ID: %s: %w", result.ID, err)
@@ -262,31 +262,36 @@ func (c *CostCenterManager) ListLatestCostCentersWithBillingTimeBefore(ctx conte
262262
return results, nil
263263
}
264264

265-
func (c *CostCenterManager) ResetUsage(ctx context.Context, cc CostCenter) (CostCenter, error) {
265+
func (c *CostCenterManager) ResetUsage(ctx context.Context, id AttributionID) (CostCenter, error) {
266+
logger := log.WithField("attributionId", id)
267+
now := time.Now().UTC()
268+
cc, err := getCostCenter(ctx, c.conn, id)
269+
if err != nil {
270+
return cc, err
271+
}
266272
if cc.BillingStrategy != CostCenter_Other {
267273
return CostCenter{}, fmt.Errorf("cannot reset usage for Billing Strategy %s for Cost Center ID: %s", cc.BillingStrategy, cc.ID)
268274
}
275+
if !cc.IsExpired() {
276+
logger.Info("Skipping ResetUsage because next billing cycle is in the future.")
277+
return cc, nil
278+
}
269279

270-
now := time.Now().UTC()
271-
280+
logger.Info("Running `ResetUsage`.")
272281
// Default to 1 month from now, if there's no nextBillingTime set on the record.
282+
billingCycleStart := now
273283
nextBillingTime := now.AddDate(0, 1, 0)
274284
if cc.NextBillingTime.IsSet() {
285+
billingCycleStart = cc.NextBillingTime.Time()
275286
nextBillingTime = cc.NextBillingTime.Time().AddDate(0, 1, 0)
276287
}
277288

278-
// Create a synthetic Invoice Usage record, to reset usage
279-
err := c.BalanceOutUsage(ctx, cc.ID)
280-
if err != nil {
281-
return CostCenter{}, fmt.Errorf("failed to compute invocie usage record for AttributonID: %s: %w", cc.ID, err)
282-
}
283-
284289
// All fields on the new cost center remain the same, except for BillingCycleStart, NextBillingTime, and CreationTime
285290
newCostCenter := CostCenter{
286291
ID: cc.ID,
287292
SpendingLimit: cc.SpendingLimit,
288293
BillingStrategy: cc.BillingStrategy,
289-
BillingCycleStart: NewVarCharTime(now),
294+
BillingCycleStart: NewVarCharTime(billingCycleStart),
290295
NextBillingTime: NewVarCharTime(nextBillingTime),
291296
CreationTime: NewVarCharTime(now),
292297
}
@@ -295,5 +300,11 @@ func (c *CostCenterManager) ResetUsage(ctx context.Context, cc CostCenter) (Cost
295300
return CostCenter{}, fmt.Errorf("failed to store new cost center for AttribtuonID: %s: %w", cc.ID, err)
296301
}
297302

303+
// Create a synthetic Invoice Usage record, to reset usage
304+
err = c.BalanceOutUsage(ctx, cc.ID)
305+
if err != nil {
306+
return CostCenter{}, fmt.Errorf("failed to compute invocie usage record for AttributonID: %s: %w", cc.ID, err)
307+
}
308+
298309
return newCostCenter, nil
299310
}

components/gitpod-db/go/cost_center_test.go

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -408,12 +408,16 @@ func TestCostCenterManager_ResetUsage(t *testing.T) {
408408
ForTeams: 0,
409409
ForUsers: 500,
410410
})
411-
_, err := mnr.ResetUsage(context.Background(), db.CostCenter{
411+
cc := &db.CostCenter{
412412
ID: db.NewUserAttributionID(uuid.New().String()),
413413
CreationTime: db.NewVarCharTime(time.Now()),
414414
SpendingLimit: 500,
415415
BillingStrategy: db.CostCenter_Stripe,
416-
})
416+
}
417+
418+
err := conn.Save(cc).Error
419+
require.NoError(t, err)
420+
_, err = mnr.ResetUsage(context.Background(), cc.ID)
417421
require.Error(t, err)
418422
})
419423

@@ -423,23 +427,29 @@ func TestCostCenterManager_ResetUsage(t *testing.T) {
423427
ForTeams: 0,
424428
ForUsers: 500,
425429
})
426-
oldCC := db.CostCenter{
427-
ID: db.NewTeamAttributionID(uuid.New().String()),
428-
CreationTime: db.NewVarCharTime(time.Now()),
429-
SpendingLimit: 0,
430-
BillingStrategy: db.CostCenter_Other,
431-
NextBillingTime: db.NewVarCharTime(ts),
430+
oldCC := &db.CostCenter{
431+
ID: db.NewTeamAttributionID(uuid.New().String()),
432+
CreationTime: db.NewVarCharTime(time.Now()),
433+
SpendingLimit: 0,
434+
BillingStrategy: db.CostCenter_Other,
435+
NextBillingTime: db.NewVarCharTime(ts),
436+
BillingCycleStart: db.NewVarCharTime(ts.AddDate(0, -1, 0)),
432437
}
433-
newCC, err := mnr.ResetUsage(context.Background(), oldCC)
438+
err := conn.Save(oldCC).Error
439+
require.NoError(t, err)
440+
t.Logf("time %s", ts)
441+
newCC, err := mnr.ResetUsage(context.Background(), oldCC.ID)
442+
t.Logf("time %s", time.Now())
434443
require.NoError(t, err)
435444
t.Cleanup(func() {
436-
conn.Model(&db.CostCenter{}).Delete(newCC)
445+
conn.Exec("DELETE from d_b_cost_center where id='%s'", oldCC.ID)
446+
conn.Exec("DELETE from d_b_usage where attributionid='%s'", oldCC.ID)
437447
})
438448

439449
require.Equal(t, oldCC.ID, newCC.ID)
440450
require.EqualValues(t, 0, newCC.SpendingLimit)
441451
require.Equal(t, db.CostCenter_Other, newCC.BillingStrategy)
442-
require.Equal(t, ts.AddDate(0, 1, 0), newCC.NextBillingTime.Time())
452+
require.Equal(t, db.NewVarCharTime(ts.AddDate(0, 1, 0)).Time(), newCC.NextBillingTime.Time())
443453

444454
})
445455

components/usage/pkg/apiv1/usage.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ func (s *UsageService) ResetUsage(ctx context.Context, req *v1.ResetUsageRequest
250250

251251
var errors []error
252252
for _, cc := range costCentersToUpdate {
253-
_, err = s.costCenterManager.ResetUsage(ctx, cc)
253+
_, err = s.costCenterManager.ResetUsage(ctx, cc.ID)
254254
if err != nil {
255255
errors = append(errors, err)
256256
}

0 commit comments

Comments
 (0)