f731267e8c
What: Changes to support custom usage limit for the project. With this implementation by default project usage limit is taken from configuration flag. If project DB field usage_limit will be set to value larger than 0 it will become custom usage limit and we will be used to verify is limit was exceeded. Whats changed: usage_limit (bigint) field added to projects table (with migration) things related to project usage moved from metainfo endpoint to project usage type accounting.ProjectAccounting extended with GetProjectUsageLimits() method Why: We need to have different usage limits per project. https://storjlabs.atlassian.net/browse/V3-1814
137 lines
4.4 KiB
Go
137 lines
4.4 KiB
Go
// Copyright (C) 2019 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package accounting
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/skyrings/skyring-common/tools/uuid"
|
|
"github.com/zeebo/errs"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"storj.io/storj/internal/memory"
|
|
"storj.io/storj/pkg/accounting/live"
|
|
)
|
|
|
|
const (
|
|
// AverageDaysInMonth is how many days in a month
|
|
AverageDaysInMonth = 30
|
|
// ExpansionFactor is the expansion for redundancy, based on the default
|
|
// redundancy scheme for the uplink.
|
|
ExpansionFactor = 3
|
|
)
|
|
|
|
var (
|
|
// ErrProjectUsage general error for project usage
|
|
ErrProjectUsage = errs.Class("project usage error")
|
|
)
|
|
|
|
// ProjectUsage defines project usage
|
|
type ProjectUsage struct {
|
|
projectAccountingDB ProjectAccounting
|
|
liveAccounting live.Service
|
|
maxAlphaUsage memory.Size
|
|
}
|
|
|
|
// NewProjectUsage created new instance of project usage service
|
|
func NewProjectUsage(projectAccountingDB ProjectAccounting, liveAccounting live.Service, maxAlphaUsage memory.Size) *ProjectUsage {
|
|
return &ProjectUsage{
|
|
projectAccountingDB: projectAccountingDB,
|
|
liveAccounting: liveAccounting,
|
|
maxAlphaUsage: maxAlphaUsage,
|
|
}
|
|
}
|
|
|
|
// ExceedsBandwidthUsage returns true if the bandwidth usage limits have been exceeded
|
|
// for a project in the past month (30 days). The usage limit is (e.g 25GB) multiplied by the redundancy
|
|
// expansion factor, so that the uplinks have a raw limit.
|
|
// Ref: https://storjlabs.atlassian.net/browse/V3-1274
|
|
func (usage *ProjectUsage) ExceedsBandwidthUsage(ctx context.Context, projectID uuid.UUID, bucketID []byte) (_ bool, limit memory.Size, err error) {
|
|
var group errgroup.Group
|
|
var bandwidthGetTotal int64
|
|
limit = usage.maxAlphaUsage
|
|
|
|
// TODO(michal): to reduce db load, consider using a cache to retrieve the project.UsageLimit value if needed
|
|
group.Go(func() error {
|
|
projectLimit, err := usage.projectAccountingDB.GetProjectUsageLimits(ctx, projectID)
|
|
if projectLimit > 0 {
|
|
limit = projectLimit
|
|
}
|
|
return err
|
|
})
|
|
group.Go(func() error {
|
|
var err error
|
|
from := time.Now().AddDate(0, 0, -AverageDaysInMonth) // past 30 days
|
|
bandwidthGetTotal, err = usage.projectAccountingDB.GetAllocatedBandwidthTotal(ctx, bucketID, from)
|
|
return err
|
|
})
|
|
|
|
err = group.Wait()
|
|
if err != nil {
|
|
return false, 0, ErrProjectUsage.Wrap(err)
|
|
}
|
|
|
|
maxUsage := limit.Int64() * int64(ExpansionFactor)
|
|
if bandwidthGetTotal >= maxUsage {
|
|
return true, limit, nil
|
|
}
|
|
|
|
return false, limit, nil
|
|
}
|
|
|
|
// ExceedsStorageUsage returns true if the storage usage limits have been exceeded
|
|
// for a project in the past month (30 days). The usage limit is (e.g. 25GB) multiplied by the redundancy
|
|
// expansion factor, so that the uplinks have a raw limit.
|
|
// Ref: https://storjlabs.atlassian.net/browse/V3-1274
|
|
func (usage *ProjectUsage) ExceedsStorageUsage(ctx context.Context, projectID uuid.UUID) (_ bool, limit memory.Size, err error) {
|
|
var group errgroup.Group
|
|
var inlineTotal, remoteTotal int64
|
|
limit = usage.maxAlphaUsage
|
|
|
|
// TODO(michal): to reduce db load, consider using a cache to retrieve the project.UsageLimit value if needed
|
|
group.Go(func() error {
|
|
projectLimit, err := usage.projectAccountingDB.GetProjectUsageLimits(ctx, projectID)
|
|
if projectLimit > 0 {
|
|
limit = projectLimit
|
|
}
|
|
return err
|
|
})
|
|
group.Go(func() error {
|
|
var err error
|
|
inlineTotal, remoteTotal, err = usage.getProjectStorageTotals(ctx, projectID)
|
|
return err
|
|
})
|
|
err = group.Wait()
|
|
if err != nil {
|
|
return false, 0, ErrProjectUsage.Wrap(err)
|
|
}
|
|
|
|
maxUsage := limit.Int64() * int64(ExpansionFactor)
|
|
if inlineTotal+remoteTotal >= maxUsage {
|
|
return true, limit, nil
|
|
}
|
|
|
|
return false, limit, nil
|
|
}
|
|
|
|
func (usage *ProjectUsage) getProjectStorageTotals(ctx context.Context, projectID uuid.UUID) (int64, int64, error) {
|
|
lastCountInline, lastCountRemote, err := usage.projectAccountingDB.GetStorageTotals(ctx, projectID)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
rtInline, rtRemote, err := usage.liveAccounting.GetProjectStorageUsage(ctx, projectID)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
return lastCountInline + rtInline, lastCountRemote + rtRemote, nil
|
|
}
|
|
|
|
// AddProjectStorageUsage lets the live accounting know that the given
|
|
// project has just added inlineSpaceUsed bytes of inline space usage
|
|
// and remoteSpaceUsed bytes of remote space usage.
|
|
func (usage *ProjectUsage) AddProjectStorageUsage(ctx context.Context, projectID uuid.UUID, inlineSpaceUsed, remoteSpaceUsed int64) error {
|
|
return usage.liveAccounting.AddProjectStorageUsage(ctx, projectID, inlineSpaceUsed, remoteSpaceUsed)
|
|
}
|