diff --git a/satellite/accounting/db.go b/satellite/accounting/db.go index d3caf480db6a..85c85c5598c1 100644 --- a/satellite/accounting/db.go +++ b/satellite/accounting/db.go @@ -116,6 +116,9 @@ type BucketUsage struct { ProjectID uuid.UUID `json:"projectID"` BucketName string `json:"bucketName"` + DefaultPlacement storj.PlacementConstraint `json:"defaultPlacement"` + Location string `json:"location"` + Versioning buckets.Versioning `json:"versioning"` Storage float64 `json:"storage"` diff --git a/satellite/accounting/projectusage_test.go b/satellite/accounting/projectusage_test.go index 5f460c17b22d..3e94b19f4ffc 100644 --- a/satellite/accounting/projectusage_test.go +++ b/satellite/accounting/projectusage_test.go @@ -21,8 +21,10 @@ import ( "go.uber.org/zap" "golang.org/x/sync/errgroup" + "storj.io/common/macaroon" "storj.io/common/memory" "storj.io/common/pb" + "storj.io/common/storj" "storj.io/common/sync2" "storj.io/common/testcontext" "storj.io/common/testrand" @@ -839,6 +841,30 @@ func TestUsageRollups(t *testing.T) { require.Equal(t, satbuckets.VersioningSuspended, usage.Versioning) } }) + + t.Run("placement", func(t *testing.T) { + bList, err := db.Buckets().ListBuckets(ctx, planet.Uplinks[0].Projects[0].ID, satbuckets.ListOptions{Direction: satbuckets.DirectionForward}, macaroon.AllowedBuckets{All: true}) + require.NoError(t, err) + for _, b := range bList.Items { + // this is a number. See earlier in test where bucket is created + elems := strings.Split(b.Name, "-") + n, err := strconv.Atoi(elems[1]) + require.NoError(t, err) + b.Placement = storj.PlacementConstraint(n) + _, err = db.Buckets().UpdateBucket(ctx, b) + require.NoError(t, err) + } + page, err := usageRollups.GetBucketTotals(ctx, planet.Uplinks[0].Projects[0].ID, accounting.BucketUsageCursor{Limit: 100, Page: 1}, time.Now()) + require.NoError(t, err) + require.NotNil(t, page) + for _, b := range page.BucketUsages { + // this is a number. See earlier in test where bucket is created + elems := strings.Split(b.BucketName, "-") + n, err := strconv.Atoi(elems[1]) + require.NoError(t, err) + require.True(t, int(b.DefaultPlacement) == n) + } + }) }) } diff --git a/satellite/api.go b/satellite/api.go index 83a046dc208b..29d797c7154e 100644 --- a/satellite/api.go +++ b/satellite/api.go @@ -600,6 +600,7 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB, externalAddress, consoleConfig.SatelliteName, config.Metainfo.ProjectLimits.MaxBuckets, + placement, consoleConfig.Config, ) if err != nil { diff --git a/satellite/console/consoleweb/server.go b/satellite/console/consoleweb/server.go index 05c5443ac77d..1b27dde7fd14 100644 --- a/satellite/console/consoleweb/server.go +++ b/satellite/console/consoleweb/server.go @@ -265,7 +265,6 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc } router.Handle("/api/v0/config", server.withCORS(http.HandlerFunc(server.frontendConfigHandler))) - router.HandleFunc("/registrationToken/", server.createRegistrationTokenHandler) router.HandleFunc("/robots.txt", server.seoHandler) diff --git a/satellite/console/placements.go b/satellite/console/placements.go new file mode 100644 index 000000000000..f1fc7724cbd4 --- /dev/null +++ b/satellite/console/placements.go @@ -0,0 +1,14 @@ +// Copyright (C) 2024 Storj Labs, Inc. +// See LICENSE for copying information. + +package console + +import ( + "storj.io/common/storj" +) + +// Placement contains placement info. +type Placement struct { + ID storj.PlacementConstraint `json:"id"` + Location string `json:"location"` +} diff --git a/satellite/console/service.go b/satellite/console/service.go index f8c34b35ea2d..da4bf6b126d2 100644 --- a/satellite/console/service.go +++ b/satellite/console/service.go @@ -40,6 +40,7 @@ import ( "storj.io/storj/satellite/buckets" "storj.io/storj/satellite/console/consoleauth" "storj.io/storj/satellite/mailservice" + "storj.io/storj/satellite/nodeselection" "storj.io/storj/satellite/payments" "storj.io/storj/satellite/payments/billing" "storj.io/storj/satellite/satellitedb/dbx" @@ -181,6 +182,7 @@ type Service struct { projectAccounting accounting.ProjectAccounting projectUsage *accounting.Service buckets buckets.DB + placements nodeselection.PlacementDefinitions accounts payments.Accounts depositWallets payments.DepositWallets billing billing.TransactionsDB @@ -218,7 +220,7 @@ type Payments struct { } // NewService returns new instance of Service. -func NewService(log *zap.Logger, store DB, restKeys RESTKeys, projectAccounting accounting.ProjectAccounting, projectUsage *accounting.Service, buckets buckets.DB, accounts payments.Accounts, depositWallets payments.DepositWallets, billing billing.TransactionsDB, analytics *analytics.Service, tokens *consoleauth.Service, mailService *mailservice.Service, accountFreezeService *AccountFreezeService, satelliteAddress string, satelliteName string, maxProjectBuckets int, config Config) (*Service, error) { +func NewService(log *zap.Logger, store DB, restKeys RESTKeys, projectAccounting accounting.ProjectAccounting, projectUsage *accounting.Service, buckets buckets.DB, accounts payments.Accounts, depositWallets payments.DepositWallets, billing billing.TransactionsDB, analytics *analytics.Service, tokens *consoleauth.Service, mailService *mailservice.Service, accountFreezeService *AccountFreezeService, satelliteAddress string, satelliteName string, maxProjectBuckets int, placements nodeselection.PlacementDefinitions, config Config) (*Service, error) { if store == nil { return nil, errs.New("store can't be nil") } @@ -254,6 +256,7 @@ func NewService(log *zap.Logger, store DB, restKeys RESTKeys, projectAccounting projectAccounting: projectAccounting, projectUsage: projectUsage, buckets: buckets, + placements: placements, accounts: accounts, depositWallets: depositWallets, billing: billing, @@ -2818,6 +2821,15 @@ func (s *Service) GetBucketTotals(ctx context.Context, projectID uuid.UUID, curs return nil, Error.Wrap(err) } + if usage == nil { + return usage, nil + } + + for i := range usage.BucketUsages { + placementID := usage.BucketUsages[i].DefaultPlacement + usage.BucketUsages[i].Location = s.placements[placementID].Name + } + return usage, nil } diff --git a/satellite/console/service_test.go b/satellite/console/service_test.go index 8e92c3fd9390..0f0ca7b827e3 100644 --- a/satellite/console/service_test.go +++ b/satellite/console/service_test.go @@ -33,9 +33,11 @@ import ( "storj.io/storj/private/post" "storj.io/storj/private/testplanet" "storj.io/storj/satellite" + "storj.io/storj/satellite/accounting" "storj.io/storj/satellite/buckets" "storj.io/storj/satellite/console" "storj.io/storj/satellite/console/consoleweb/consoleapi" + "storj.io/storj/satellite/nodeselection" "storj.io/storj/satellite/payments" "storj.io/storj/satellite/payments/billing" "storj.io/storj/satellite/payments/coinpayments" @@ -45,11 +47,20 @@ import ( ) func TestService(t *testing.T) { + placements := make(map[int]string) + for i := 0; i < 4; i++ { + placements[i] = fmt.Sprintf("loc-%d", i) + } testplanet.Run(t, testplanet.Config{ SatelliteCount: 1, StorageNodeCount: 1, UplinkCount: 3, Reconfigure: testplanet.Reconfigure{ Satellite: func(log *zap.Logger, index int, config *satellite.Config) { config.Payments.StripeCoinPayments.StripeFreeTierCouponID = stripe.MockCouponID1 + var plcStr string + for k, v := range placements { + plcStr += fmt.Sprintf(`%d:annotation("location", "%s"); `, k, v) + } + config.Placement = nodeselection.ConfigurablePlacementRule{PlacementRules: plcStr} }, }, }, @@ -653,6 +664,25 @@ func TestService(t *testing.T) { require.Nil(t, bucketsForUnauthorizedUser) }) + t.Run("GetBucketTotals", func(t *testing.T) { + list, err := sat.DB.Buckets().ListBuckets(ctx, up2Proj.ID, buckets.ListOptions{Direction: buckets.DirectionForward}, macaroon.AllowedBuckets{All: true}) + require.NoError(t, err) + for i, item := range list.Items { + item.Placement = storj.PlacementConstraint(i) + if i > len(placements)-1 { + item.Placement = storj.PlacementConstraint(len(placements) - 1) + } + b, err := sat.DB.Buckets().UpdateBucket(ctx, item) + require.NoError(t, err) + require.Equal(t, i, int(b.Placement)) + } + bt, err := service.GetBucketTotals(userCtx2, up2Proj.ID, accounting.BucketUsageCursor{Limit: 100, Page: 1}, time.Now()) + require.NoError(t, err) + for _, b := range bt.BucketUsages { + require.Equal(t, placements[int(b.DefaultPlacement)], b.Location) + } + }) + t.Run("DeleteAPIKeyByNameAndProjectID", func(t *testing.T) { secret, err := macaroon.NewSecret() require.NoError(t, err) diff --git a/satellite/payments/stripe/accounts_test.go b/satellite/payments/stripe/accounts_test.go index dc3ffbae1f63..5e40740fd859 100644 --- a/satellite/payments/stripe/accounts_test.go +++ b/satellite/payments/stripe/accounts_test.go @@ -19,6 +19,7 @@ import ( "storj.io/storj/satellite/console" "storj.io/storj/satellite/console/consoleauth" "storj.io/storj/satellite/console/restkeys" + "storj.io/storj/satellite/nodeselection" "storj.io/storj/satellite/payments" "storj.io/storj/satellite/payments/paymentsconfig" "storj.io/storj/satellite/payments/stripe" @@ -96,6 +97,7 @@ func TestSignupCouponCodes(t *testing.T) { "", "", sat.Config.Metainfo.ProjectLimits.MaxBuckets, + nodeselection.NewPlacementDefinitions(), console.Config{PasswordCost: console.TestPasswordCost, DefaultProjectLimit: 5}, ) diff --git a/satellite/satellitedb/projectaccounting.go b/satellite/satellitedb/projectaccounting.go index ff2fcea4625c..b2282d10aee7 100644 --- a/satellite/satellitedb/projectaccounting.go +++ b/satellite/satellitedb/projectaccounting.go @@ -20,6 +20,7 @@ import ( "storj.io/common/dbutil/pgxutil" "storj.io/common/memory" "storj.io/common/pb" + "storj.io/common/storj" "storj.io/common/tagsql" "storj.io/common/useragent" "storj.io/common/uuid" @@ -894,7 +895,7 @@ func (db *ProjectAccounting) GetBucketTotals(ctx context.Context, projectID uuid return nil, errs.New("page is out of range") } - bucketsQuery := db.db.Rebind(`SELECT name, versioning, created_at FROM bucket_metainfos + bucketsQuery := db.db.Rebind(`SELECT name, versioning, placement, created_at FROM bucket_metainfos WHERE project_id = ? AND ` + bucketNameRange + `ORDER BY name ASC LIMIT ? OFFSET ?`) args = []interface{}{ @@ -915,6 +916,7 @@ func (db *ProjectAccounting) GetBucketTotals(ctx context.Context, projectID uuid type bucketWithCreationDate struct { name string versioning satbuckets.Versioning + placement storj.PlacementConstraint createdAt time.Time } @@ -923,9 +925,10 @@ func (db *ProjectAccounting) GetBucketTotals(ctx context.Context, projectID uuid var ( bucket string versioning satbuckets.Versioning + placement storj.PlacementConstraint createdAt time.Time ) - err = bucketRows.Scan(&bucket, &versioning, &createdAt) + err = bucketRows.Scan(&bucket, &versioning, &placement, &createdAt) if err != nil { return nil, err } @@ -933,6 +936,7 @@ func (db *ProjectAccounting) GetBucketTotals(ctx context.Context, projectID uuid buckets = append(buckets, bucketWithCreationDate{ name: bucket, versioning: versioning, + placement: placement, createdAt: createdAt, }) } @@ -953,11 +957,12 @@ func (db *ProjectAccounting) GetBucketTotals(ctx context.Context, projectID uuid var bucketUsages []accounting.BucketUsage for _, bucket := range buckets { bucketUsage := accounting.BucketUsage{ - ProjectID: projectID, - BucketName: bucket.name, - Versioning: bucket.versioning, - Since: bucket.createdAt, - Before: before, + ProjectID: projectID, + BucketName: bucket.name, + Versioning: bucket.versioning, + DefaultPlacement: bucket.placement, + Since: bucket.createdAt, + Before: before, } // get bucket_bandwidth_rollups diff --git a/web/satellite/src/store/modules/appStore.ts b/web/satellite/src/store/modules/appStore.ts index ff76e7c70139..9340f86fecef 100644 --- a/web/satellite/src/store/modules/appStore.ts +++ b/web/satellite/src/store/modules/appStore.ts @@ -24,7 +24,7 @@ class ErrorPageState { public statusCode = 0, public fatal = false, public visible = false, - ) {} + ) { } } export const useAppStore = defineStore('app', () => {