ee720040c9
Metainfo needs to know rate and burst limit to be able to limit users requests. We made cache for per project limiter but to make single instance we need to know about limits. So far we were doing direct DB call to get rate/burst limit for project but it's generating lots of DB requests and can be easily cached as we even have project limit cache. This change extends project limit cache with rate/burst limit and starts using this change while creating project limiter instance for metainfo. Because data size kept in project limit cache is quite small this change also bumps a bit default capacity of the cache. Fixes https://github.com/storj/storj/issues/5663 Change-Id: Icb42ec1632bfa0c9f74857b559083dcbd054d071
238 lines
9.4 KiB
Go
238 lines
9.4 KiB
Go
// Copyright (C) 2020 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package accounting_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"storj.io/common/testcontext"
|
|
"storj.io/common/testrand"
|
|
"storj.io/common/uuid"
|
|
"storj.io/storj/private/testplanet"
|
|
"storj.io/storj/satellite"
|
|
"storj.io/storj/satellite/accounting"
|
|
"storj.io/storj/satellite/console"
|
|
"storj.io/storj/satellite/satellitedb/satellitedbtest"
|
|
)
|
|
|
|
type mockDB struct {
|
|
callCount int
|
|
}
|
|
|
|
func (mdb *mockDB) GetProjectLimits(ctx context.Context, projectID uuid.UUID) (accounting.ProjectLimits, error) {
|
|
mdb.callCount++
|
|
return accounting.ProjectLimits{}, nil
|
|
}
|
|
func TestProjectLimitCacheCallCount(t *testing.T) {
|
|
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
|
|
mdb := mockDB{}
|
|
projectLimitCache := accounting.NewProjectLimitCache(&mdb, 0, 0, 0, accounting.ProjectLimitConfig{CacheCapacity: 100})
|
|
|
|
testProject, err := db.Console().Projects().Insert(ctx, &console.Project{Name: "test", OwnerID: testrand.UUID()})
|
|
require.NoError(t, err)
|
|
|
|
const expectedCallCount = 1
|
|
|
|
_, err = projectLimitCache.GetBandwidthLimit(ctx, testProject.ID)
|
|
require.NoError(t, err)
|
|
// if the data isn't in the cache we call into the database to get it
|
|
require.Equal(t, expectedCallCount, mdb.callCount)
|
|
|
|
_, err = projectLimitCache.GetBandwidthLimit(ctx, testProject.ID)
|
|
require.NoError(t, err)
|
|
// call count should still be 1 since the data is in the cache and we don't need
|
|
// to get it from the db
|
|
require.Equal(t, expectedCallCount, mdb.callCount)
|
|
})
|
|
}
|
|
|
|
func TestProjectLimitCache(t *testing.T) {
|
|
testplanet.Run(t, testplanet.Config{
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
saPeer := planet.Satellites[0]
|
|
projectUsageSvc := saPeer.Accounting.ProjectUsage
|
|
projects := saPeer.DB.Console().Projects()
|
|
accountingDB := saPeer.DB.ProjectAccounting()
|
|
projectLimitCache := saPeer.ProjectLimits.Cache
|
|
defaultUsageLimit := saPeer.Config.Console.UsageLimits.Storage.Free.Int64()
|
|
defaultBandwidthLimit := saPeer.Config.Console.UsageLimits.Bandwidth.Free.Int64()
|
|
defaultSegmentLimit := int64(1000000)
|
|
dbDefaultLimits := accounting.ProjectLimits{
|
|
Usage: &defaultUsageLimit,
|
|
Bandwidth: &defaultBandwidthLimit,
|
|
Segments: &defaultSegmentLimit,
|
|
RateLimit: nil,
|
|
BurstLimit: nil,
|
|
}
|
|
|
|
testProject, err := saPeer.DB.Console().Projects().Insert(ctx, &console.Project{Name: "test", OwnerID: testrand.UUID()})
|
|
require.NoError(t, err)
|
|
|
|
secondTestProject, err := saPeer.DB.Console().Projects().Insert(ctx, &console.Project{Name: "second project", OwnerID: testrand.UUID()})
|
|
require.NoError(t, err)
|
|
|
|
const (
|
|
errorLimit = 0
|
|
expectedUsageLimit = 1
|
|
expectedBandwidthLimit = 2
|
|
expectedSegmentLimit = 3
|
|
expectedRateLimit = 4
|
|
expectedBurstLimit = 5
|
|
)
|
|
|
|
t.Run("project ID doesn't exist", func(t *testing.T) {
|
|
projectID := testrand.UUID()
|
|
actualStorageLimitFromDB, err := accountingDB.GetProjectStorageLimit(ctx, projectID)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, actualStorageLimitFromDB)
|
|
|
|
actualLimitsFromDB, err := accountingDB.GetProjectLimits(ctx, projectID)
|
|
assert.Error(t, err)
|
|
assert.Equal(t, accounting.ProjectLimits{}, actualLimitsFromDB)
|
|
|
|
// storage
|
|
_, err = projectLimitCache.GetLimits(ctx, projectID)
|
|
assert.Error(t, err)
|
|
|
|
actualStorageLimitFromSvc, err := projectUsageSvc.GetProjectStorageLimit(ctx, projectID)
|
|
assert.Error(t, err)
|
|
assert.EqualValues(t, errorLimit, actualStorageLimitFromSvc)
|
|
|
|
// bandwidth
|
|
actualBandwidthLimitFromCache, err := projectLimitCache.GetBandwidthLimit(ctx, projectID)
|
|
assert.Error(t, err)
|
|
assert.EqualValues(t, errorLimit, actualBandwidthLimitFromCache)
|
|
|
|
actualBandwidthLimitFromSvc, err := projectUsageSvc.GetProjectBandwidthLimit(ctx, projectID)
|
|
assert.Error(t, err)
|
|
assert.EqualValues(t, errorLimit, actualBandwidthLimitFromSvc)
|
|
})
|
|
|
|
t.Run("default limits", func(t *testing.T) {
|
|
actualLimitsFromDB, err := accountingDB.GetProjectLimits(ctx, testProject.ID)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, accounting.ProjectLimits{
|
|
Segments: &defaultSegmentLimit,
|
|
}, actualLimitsFromDB)
|
|
|
|
actualLimitsFromCache, err := projectLimitCache.GetLimits(ctx, testProject.ID)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, dbDefaultLimits, actualLimitsFromCache)
|
|
|
|
actualStorageLimitFromDB, err := accountingDB.GetProjectStorageLimit(ctx, testProject.ID)
|
|
assert.NoError(t, err)
|
|
assert.Nil(t, actualStorageLimitFromDB)
|
|
|
|
actualLimitFromCache, err := projectLimitCache.GetLimits(ctx, testProject.ID)
|
|
assert.NoError(t, err)
|
|
assert.EqualValues(t, *dbDefaultLimits.Usage, *actualLimitFromCache.Usage)
|
|
|
|
actualStorageLimitFromSvc, err := projectUsageSvc.GetProjectStorageLimit(ctx, testProject.ID)
|
|
assert.NoError(t, err)
|
|
assert.EqualValues(t, *dbDefaultLimits.Usage, actualStorageLimitFromSvc)
|
|
|
|
actualBandwidthLimitFromDB, err := accountingDB.GetProjectBandwidthLimit(ctx, testProject.ID)
|
|
assert.NoError(t, err)
|
|
assert.Nil(t, actualBandwidthLimitFromDB)
|
|
|
|
actualBandwidthLimitFromCache, err := projectLimitCache.GetBandwidthLimit(ctx, testProject.ID)
|
|
assert.NoError(t, err)
|
|
assert.EqualValues(t, *dbDefaultLimits.Bandwidth, actualBandwidthLimitFromCache)
|
|
|
|
actualBandwidthLimitFromSvc, err := projectUsageSvc.GetProjectBandwidthLimit(ctx, testProject.ID)
|
|
assert.NoError(t, err)
|
|
assert.EqualValues(t, *dbDefaultLimits.Bandwidth, actualBandwidthLimitFromSvc)
|
|
})
|
|
|
|
t.Run("update limits in the database", func(t *testing.T) {
|
|
err = accountingDB.UpdateProjectUsageLimit(ctx, testProject.ID, expectedUsageLimit)
|
|
require.NoError(t, err)
|
|
err = accountingDB.UpdateProjectBandwidthLimit(ctx, testProject.ID, expectedBandwidthLimit)
|
|
require.NoError(t, err)
|
|
err = accountingDB.UpdateProjectSegmentLimit(ctx, testProject.ID, expectedSegmentLimit)
|
|
require.NoError(t, err)
|
|
|
|
actualStorageLimitFromDB, err := accountingDB.GetProjectStorageLimit(ctx, testProject.ID)
|
|
assert.NoError(t, err)
|
|
require.EqualValues(t, expectedUsageLimit, *actualStorageLimitFromDB)
|
|
|
|
actualLimitsFromDB, err := accountingDB.GetProjectLimits(ctx, testProject.ID)
|
|
assert.NoError(t, err)
|
|
usageLimits := int64(expectedUsageLimit)
|
|
bwLimits := int64(expectedBandwidthLimit)
|
|
segmentsLimits := int64(expectedSegmentLimit)
|
|
assert.Equal(t, accounting.ProjectLimits{Usage: &usageLimits, Bandwidth: &bwLimits, Segments: &segmentsLimits}, actualLimitsFromDB)
|
|
|
|
// storage
|
|
actualLimitFromCache, err := projectLimitCache.GetLimits(ctx, testProject.ID)
|
|
assert.NoError(t, err)
|
|
require.EqualValues(t, expectedUsageLimit, *actualLimitFromCache.Usage)
|
|
require.EqualValues(t, expectedSegmentLimit, *actualLimitFromCache.Segments)
|
|
|
|
actualStorageLimitFromSvc, err := projectUsageSvc.GetProjectStorageLimit(ctx, testProject.ID)
|
|
assert.NoError(t, err)
|
|
require.EqualValues(t, expectedUsageLimit, actualStorageLimitFromSvc)
|
|
|
|
// bandwidth
|
|
actualBandwidthLimitFromDB, err := accountingDB.GetProjectBandwidthLimit(ctx, testProject.ID)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, expectedBandwidthLimit, *actualBandwidthLimitFromDB)
|
|
|
|
actualBandwidthLimitFromCache, err := projectLimitCache.GetBandwidthLimit(ctx, testProject.ID)
|
|
assert.NoError(t, err)
|
|
require.EqualValues(t, expectedBandwidthLimit, actualBandwidthLimitFromCache)
|
|
|
|
actualBandwidthLimitFromSvc, err := projectUsageSvc.GetProjectBandwidthLimit(ctx, testProject.ID)
|
|
assert.NoError(t, err)
|
|
require.EqualValues(t, expectedBandwidthLimit, actualBandwidthLimitFromSvc)
|
|
|
|
// segments
|
|
actualSegmentLimitFromDB, err := accountingDB.GetProjectSegmentLimit(ctx, testProject.ID)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, expectedSegmentLimit, *actualSegmentLimitFromDB)
|
|
|
|
// rate and burst limit
|
|
require.NoError(t, projects.UpdateRateLimit(ctx, secondTestProject.ID, expectedRateLimit))
|
|
require.NoError(t, projects.UpdateBurstLimit(ctx, secondTestProject.ID, expectedBurstLimit))
|
|
|
|
limits, err := projectLimitCache.GetLimits(ctx, secondTestProject.ID)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, expectedRateLimit, *limits.RateLimit)
|
|
require.EqualValues(t, expectedBurstLimit, *limits.BurstLimit)
|
|
})
|
|
|
|
t.Run("cache is used", func(t *testing.T) {
|
|
require.NoError(t, accountingDB.UpdateProjectUsageLimit(ctx, testProject.ID, 1))
|
|
require.NoError(t, accountingDB.UpdateProjectBandwidthLimit(ctx, testProject.ID, 2))
|
|
require.NoError(t, accountingDB.UpdateProjectSegmentLimit(ctx, testProject.ID, 3))
|
|
|
|
projectLimitCache := accounting.NewProjectLimitCache(accountingDB, 0, 0, 0, accounting.ProjectLimitConfig{
|
|
CacheCapacity: 10,
|
|
CacheExpiration: 60 * time.Second,
|
|
})
|
|
|
|
// fill cache with values from DB
|
|
beforeCachedLimits, err := projectLimitCache.GetLimits(ctx, testProject.ID)
|
|
require.NoError(t, err)
|
|
|
|
// update limits in DB but not in cache
|
|
require.NoError(t, accountingDB.UpdateProjectUsageLimit(ctx, testProject.ID, 4))
|
|
require.NoError(t, accountingDB.UpdateProjectBandwidthLimit(ctx, testProject.ID, 5))
|
|
require.NoError(t, accountingDB.UpdateProjectSegmentLimit(ctx, testProject.ID, 6))
|
|
|
|
// verify that old values are still cached because expiration time was not reached yet
|
|
afterCachedLimits, err := projectLimitCache.GetLimits(ctx, testProject.ID)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, beforeCachedLimits, afterCachedLimits)
|
|
})
|
|
})
|
|
}
|