satellite/console: Implement paid tier
When a user adds a credit card, switch them to the paid tier and update their projects with new bandwidth/storage limits. New projects for the paid tier user will also have the updated limits. The new limits are: * storage per project - 50 GB free/25 TB paid * bandwidth per project - 50 GB free/100 TB paid Change-Id: I7d6467d077e8bb2bbe4bcf88ab8d75490f83165e
This commit is contained in:
parent
3b0b0ba3c4
commit
e36001b7cf
@ -60,8 +60,8 @@ func TestProjectLimitCache(t *testing.T) {
|
||||
projectUsageSvc := saPeer.Accounting.ProjectUsage
|
||||
accountingDB := saPeer.DB.ProjectAccounting()
|
||||
projectLimitCache := saPeer.ProjectLimits.Cache
|
||||
defaultUsageLimit := saPeer.Config.Console.UsageLimits.DefaultStorageLimit.Int64()
|
||||
defaultBandwidthLimit := saPeer.Config.Console.UsageLimits.DefaultBandwidthLimit.Int64()
|
||||
defaultUsageLimit := saPeer.Config.Console.UsageLimits.Storage.Free.Int64()
|
||||
defaultBandwidthLimit := saPeer.Config.Console.UsageLimits.Bandwidth.Free.Int64()
|
||||
dbDefaultLimits := accounting.ProjectLimits{Usage: &defaultUsageLimit, Bandwidth: &defaultBandwidthLimit}
|
||||
|
||||
testProject, err := saPeer.DB.Console().Projects().Insert(ctx, &console.Project{Name: "test", OwnerID: testrand.UUID()})
|
||||
|
@ -38,8 +38,8 @@ func TestProjectUsageStorage(t *testing.T) {
|
||||
SatelliteCount: 1, StorageNodeCount: 4, UplinkCount: 1,
|
||||
Reconfigure: testplanet.Reconfigure{
|
||||
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
||||
config.Console.UsageLimits.DefaultStorageLimit = 1 * memory.MB
|
||||
config.Console.UsageLimits.DefaultBandwidthLimit = 1 * memory.MB
|
||||
config.Console.UsageLimits.Storage.Free = 1 * memory.MB
|
||||
config.Console.UsageLimits.Bandwidth.Free = 1 * memory.MB
|
||||
},
|
||||
},
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
@ -632,8 +632,8 @@ func TestProjectUsageBandwidthResetAfter3days(t *testing.T) {
|
||||
SatelliteCount: 1, StorageNodeCount: 4, UplinkCount: 1,
|
||||
Reconfigure: testplanet.Reconfigure{
|
||||
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
||||
config.Console.UsageLimits.DefaultStorageLimit = 1 * memory.MB
|
||||
config.Console.UsageLimits.DefaultBandwidthLimit = 1 * memory.MB
|
||||
config.Console.UsageLimits.Storage.Free = 1 * memory.MB
|
||||
config.Console.UsageLimits.Bandwidth.Free = 1 * memory.MB
|
||||
config.LiveAccounting.AsOfSystemInterval = -time.Millisecond
|
||||
},
|
||||
},
|
||||
|
@ -292,8 +292,8 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
|
||||
{ // setup project limits
|
||||
peer.ProjectLimits.Cache = accounting.NewProjectLimitCache(peer.DB.ProjectAccounting(),
|
||||
config.Console.Config.UsageLimits.DefaultStorageLimit,
|
||||
config.Console.Config.UsageLimits.DefaultBandwidthLimit,
|
||||
config.Console.Config.UsageLimits.Storage.Free,
|
||||
config.Console.Config.UsageLimits.Bandwidth.Free,
|
||||
config.ProjectLimit,
|
||||
)
|
||||
}
|
||||
|
@ -48,8 +48,20 @@ type Projects interface {
|
||||
|
||||
// UsageLimitsConfig is a configuration struct for default per-project usage limits.
|
||||
type UsageLimitsConfig struct {
|
||||
DefaultStorageLimit memory.Size `help:"the default storage usage limit" default:"50.00GB" testDefault:"25.00 GB"`
|
||||
DefaultBandwidthLimit memory.Size `help:"the default bandwidth usage limit" default:"50.00GB" testDefault:"25.00 GB"`
|
||||
Storage StorageLimitConfig
|
||||
Bandwidth BandwidthLimitConfig
|
||||
}
|
||||
|
||||
// StorageLimitConfig is a configuration struct for default storage per-project usage limits.
|
||||
type StorageLimitConfig struct {
|
||||
Free memory.Size `help:"the default free-tier storage usage limit" default:"50.00GB" testDefault:"25.00 GB"`
|
||||
Paid memory.Size `help:"the default paid-tier storage usage limit" default:"25.00TB" testDefault:"25.00 GB"`
|
||||
}
|
||||
|
||||
// BandwidthLimitConfig is a configuration struct for default bandwidth per-project usage limits.
|
||||
type BandwidthLimitConfig struct {
|
||||
Free memory.Size `help:"the default free-tier bandwidth usage limit" default:"50.00GB" testDefault:"25.00 GB"`
|
||||
Paid memory.Size `help:"the default paid-tier bandwidth usage limit" default:"100.00TB" testDefault:"25.00 GB"`
|
||||
}
|
||||
|
||||
// Project is a database object that describes Project entity.
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"storj.io/common/macaroon"
|
||||
"storj.io/common/memory"
|
||||
"storj.io/common/storj"
|
||||
"storj.io/common/uuid"
|
||||
"storj.io/private/cfgstruct"
|
||||
@ -244,6 +245,33 @@ func (paymentService PaymentsService) AddCreditCard(ctx context.Context, creditC
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
if !auth.User.PaidTier {
|
||||
// put this user into the paid tier and convert projects to upgraded limits.
|
||||
err = paymentService.service.store.Users().UpdatePaidTier(ctx, auth.User.ID, true)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
projects, err := paymentService.service.store.Projects().GetOwn(ctx, auth.User.ID)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
for _, project := range projects {
|
||||
if project.StorageLimit == nil || *project.StorageLimit < paymentService.service.config.UsageLimits.Storage.Paid {
|
||||
project.StorageLimit = new(memory.Size)
|
||||
*project.StorageLimit = paymentService.service.config.UsageLimits.Storage.Paid
|
||||
}
|
||||
if project.BandwidthLimit == nil || *project.BandwidthLimit < paymentService.service.config.UsageLimits.Bandwidth.Paid {
|
||||
project.BandwidthLimit = new(memory.Size)
|
||||
*project.BandwidthLimit = paymentService.service.config.UsageLimits.Bandwidth.Paid
|
||||
}
|
||||
err = paymentService.service.store.Projects().Update(ctx, &project)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -979,14 +1007,20 @@ func (s *Service) CreateProject(ctx context.Context, projectInfo ProjectInfo) (p
|
||||
|
||||
var projectID uuid.UUID
|
||||
err = s.store.WithTx(ctx, func(ctx context.Context, tx DBTx) error {
|
||||
storageLimit := s.config.UsageLimits.Storage.Free
|
||||
bandwidthLimit := s.config.UsageLimits.Bandwidth.Free
|
||||
if auth.User.PaidTier {
|
||||
storageLimit = s.config.UsageLimits.Storage.Paid
|
||||
bandwidthLimit = s.config.UsageLimits.Bandwidth.Paid
|
||||
}
|
||||
p, err = tx.Projects().Insert(ctx,
|
||||
&Project{
|
||||
Description: projectInfo.Description,
|
||||
Name: projectInfo.Name,
|
||||
OwnerID: auth.User.ID,
|
||||
PartnerID: auth.User.PartnerID,
|
||||
StorageLimit: &s.config.UsageLimits.DefaultStorageLimit,
|
||||
BandwidthLimit: &s.config.UsageLimits.DefaultBandwidthLimit,
|
||||
StorageLimit: &storageLimit,
|
||||
BandwidthLimit: &bandwidthLimit,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -7,13 +7,16 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"storj.io/common/macaroon"
|
||||
"storj.io/common/memory"
|
||||
"storj.io/common/storj"
|
||||
"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/console"
|
||||
)
|
||||
|
||||
@ -211,3 +214,67 @@ func TestService(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestPaidTier(t *testing.T) {
|
||||
usageConfig := console.UsageLimitsConfig{
|
||||
Storage: console.StorageLimitConfig{
|
||||
Free: memory.GB,
|
||||
Paid: memory.TB,
|
||||
},
|
||||
Bandwidth: console.BandwidthLimitConfig{
|
||||
Free: 2 * memory.GB,
|
||||
Paid: 2 * memory.TB,
|
||||
},
|
||||
}
|
||||
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1,
|
||||
Reconfigure: testplanet.Reconfigure{
|
||||
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
||||
config.Console.UsageLimits = usageConfig
|
||||
},
|
||||
},
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
sat := planet.Satellites[0]
|
||||
service := sat.API.Console.Service
|
||||
|
||||
// project should have free tier usage limits
|
||||
proj1, err := sat.API.DB.Console().Projects().Get(ctx, planet.Uplinks[0].Projects[0].ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, usageConfig.Storage.Free, *proj1.StorageLimit)
|
||||
require.Equal(t, usageConfig.Bandwidth.Free, *proj1.BandwidthLimit)
|
||||
|
||||
// user should be in free tier
|
||||
user, err := service.GetUser(ctx, proj1.OwnerID)
|
||||
require.NoError(t, err)
|
||||
require.False(t, user.PaidTier)
|
||||
|
||||
authCtx, err := sat.AuthenticatedContext(ctx, user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// add a credit card to the user
|
||||
err = service.Payments().AddCreditCard(authCtx, "test-cc-token")
|
||||
require.NoError(t, err)
|
||||
|
||||
// expect user to be in paid tier
|
||||
user, err = service.GetUser(ctx, user.ID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, user.PaidTier)
|
||||
|
||||
// update auth ctx
|
||||
authCtx, err = sat.AuthenticatedContext(ctx, user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// expect project to be migrated to paid tier usage limits
|
||||
proj1, err = service.GetProject(authCtx, proj1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, usageConfig.Storage.Paid, *proj1.StorageLimit)
|
||||
require.Equal(t, usageConfig.Bandwidth.Paid, *proj1.BandwidthLimit)
|
||||
|
||||
// expect new project to be created with paid tier usage limits
|
||||
proj2, err := service.CreateProject(authCtx, console.ProjectInfo{Name: "Project 2"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, usageConfig.Storage.Paid, *proj2.StorageLimit)
|
||||
require.Equal(t, usageConfig.Bandwidth.Paid, *proj2.BandwidthLimit)
|
||||
})
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ type Users interface {
|
||||
Delete(ctx context.Context, id uuid.UUID) error
|
||||
// Update is a method for updating user entity.
|
||||
Update(ctx context.Context, user *User) error
|
||||
// UpdatePaidTier sets whether the user is in the paid tier.
|
||||
UpdatePaidTier(ctx context.Context, id uuid.UUID, paidTier bool) error
|
||||
// GetProjectLimit is a method to get the users project limit
|
||||
GetProjectLimit(ctx context.Context, id uuid.UUID) (limit int, err error)
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"storj.io/common/testcontext"
|
||||
"storj.io/common/testrand"
|
||||
@ -116,6 +117,47 @@ func TestUserEmailCase(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserUpdatePaidTier(t *testing.T) {
|
||||
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
|
||||
email := "testemail@mail.test"
|
||||
fullName := "first name last name"
|
||||
shortName := "short name"
|
||||
password := "password"
|
||||
newUser := &console.User{
|
||||
ID: testrand.UUID(),
|
||||
FullName: fullName,
|
||||
ShortName: shortName,
|
||||
Email: email,
|
||||
Status: console.Active,
|
||||
PasswordHash: []byte(password),
|
||||
}
|
||||
|
||||
createdUser, err := db.Console().Users().Insert(ctx, newUser)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, email, createdUser.Email)
|
||||
require.Equal(t, fullName, createdUser.FullName)
|
||||
require.Equal(t, shortName, createdUser.ShortName)
|
||||
require.False(t, createdUser.PaidTier)
|
||||
|
||||
err = db.Console().Users().UpdatePaidTier(ctx, createdUser.ID, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
retrievedUser, err := db.Console().Users().Get(ctx, createdUser.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, email, retrievedUser.Email)
|
||||
require.Equal(t, fullName, retrievedUser.FullName)
|
||||
require.Equal(t, shortName, retrievedUser.ShortName)
|
||||
require.True(t, retrievedUser.PaidTier)
|
||||
|
||||
err = db.Console().Users().UpdatePaidTier(ctx, createdUser.ID, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
retrievedUser, err = db.Console().Users().Get(ctx, createdUser.ID)
|
||||
require.NoError(t, err)
|
||||
require.False(t, retrievedUser.PaidTier)
|
||||
})
|
||||
}
|
||||
|
||||
func testUsers(ctx context.Context, t *testing.T, repository console.Users, user *console.User) {
|
||||
|
||||
t.Run("User insertion success", func(t *testing.T) {
|
||||
|
@ -139,6 +139,12 @@ func (projects *projects) Update(ctx context.Context, project *console.Project)
|
||||
Description: dbx.Project_Description(project.Description),
|
||||
RateLimit: dbx.Project_RateLimit_Raw(project.RateLimit),
|
||||
}
|
||||
if project.StorageLimit != nil {
|
||||
updateFields.UsageLimit = dbx.Project_UsageLimit(project.StorageLimit.Int64())
|
||||
}
|
||||
if project.BandwidthLimit != nil {
|
||||
updateFields.BandwidthLimit = dbx.Project_BandwidthLimit(project.BandwidthLimit.Int64())
|
||||
}
|
||||
|
||||
_, err = projects.db.Update_Project_By_Id(ctx,
|
||||
dbx.Project_Id(project.ID[:]),
|
||||
|
@ -108,6 +108,21 @@ func (users *users) Update(ctx context.Context, user *console.User) (err error)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdatePaidTier sets whether the user is in the paid tier.
|
||||
func (users *users) UpdatePaidTier(ctx context.Context, id uuid.UUID, paidTier bool) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
_, err = users.db.Update_User_By_Id(
|
||||
ctx,
|
||||
dbx.User_Id(id[:]),
|
||||
dbx.User_Update_Fields{
|
||||
PaidTier: dbx.User_PaidTier(paidTier),
|
||||
},
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetProjectLimit is a method to get the users project limit.
|
||||
func (users *users) GetProjectLimit(ctx context.Context, id uuid.UUID) (limit int, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
14
scripts/testdata/satellite-config.yaml.lock
vendored
14
scripts/testdata/satellite-config.yaml.lock
vendored
@ -163,11 +163,17 @@ compensation.withheld-percents: 75,75,75,50,50,50,25,25,25,0,0,0,0,0,0
|
||||
# url link to terms and conditions page
|
||||
# console.terms-and-conditions-url: https://storj.io/storage-sla/
|
||||
|
||||
# the default bandwidth usage limit
|
||||
# console.usage-limits.default-bandwidth-limit: 50.00 GB
|
||||
# the default free-tier bandwidth usage limit
|
||||
# console.usage-limits.bandwidth.free: 50.00 GB
|
||||
|
||||
# the default storage usage limit
|
||||
# console.usage-limits.default-storage-limit: 50.00 GB
|
||||
# the default paid-tier bandwidth usage limit
|
||||
# console.usage-limits.bandwidth.paid: 100.00 TB
|
||||
|
||||
# the default free-tier storage usage limit
|
||||
# console.usage-limits.storage.free: 50.00 GB
|
||||
|
||||
# the default paid-tier storage usage limit
|
||||
# console.usage-limits.storage.paid: 25.00 TB
|
||||
|
||||
# the public address of the node, useful for nodes behind NAT
|
||||
contact.external-address: ""
|
||||
|
Loading…
Reference in New Issue
Block a user