feat: enforce shared application resource quotas across namespaces
This commit is contained in:
79
platform/resources/service.go
Normal file
79
platform/resources/service.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// Package resources 管理当前凭据所属应用的共享额度,应用管理动作需单独授权。
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"git.yuntuo.hk/k2safe/orbit/internal/fault"
|
||||
core "git.yuntuo.hk/k2safe/orbit/model"
|
||||
"git.yuntuo.hk/k2safe/orbit/platform/auth"
|
||||
)
|
||||
|
||||
// View 返回配置与当前用量;运行计数不参与配置版本。
|
||||
type View struct {
|
||||
// ApplicationID 所属应用标识,由凭据归属确定。
|
||||
ApplicationID core.ID `json:"application_id"`
|
||||
// Revision 仅配置变更增加的版本;JSON 使用十进制字符串。
|
||||
Revision uint64 `json:"revision,string"`
|
||||
// MaxUnfinishedTasks 跨命名空间的未终结任务上限,包含待核查任务。
|
||||
MaxUnfinishedTasks uint32 `json:"max_unfinished_tasks"`
|
||||
// UnfinishedTasks 当前未终结任务总数;自动重试不重复占用。
|
||||
UnfinishedTasks uint64 `json:"unfinished_tasks,string"`
|
||||
// MaxConcurrency 跨队列的预占及运行尝试总上限。
|
||||
MaxConcurrency uint32 `json:"max_concurrency"`
|
||||
// ActiveAttempts 当前预占与运行的尝试数。
|
||||
ActiveAttempts uint64 `json:"active_attempts,string"`
|
||||
// PendingStarts 已预占但尚未开始的尝试数,属于 ActiveAttempts。
|
||||
PendingStarts uint64 `json:"pending_starts,string"`
|
||||
// RatePerSecond 每秒固定窗口允许的开始授权次数。
|
||||
RatePerSecond uint32 `json:"rate_per_second"`
|
||||
}
|
||||
|
||||
// ConfigureInput 完整替换三个正整数上限;要求版本和审计原因。
|
||||
type ConfigureInput struct {
|
||||
// ExpectedRevision 管理命令预期配置版本,须保留原值重报。
|
||||
ExpectedRevision uint64 `json:"expected_revision,string"`
|
||||
// Reason 非空审计原因,最多 1024 个 Unicode 字符。
|
||||
Reason string `json:"reason"`
|
||||
// MaxUnfinishedTasks 跨命名空间的未终结任务上限,包含待核查任务。
|
||||
MaxUnfinishedTasks uint32 `json:"max_unfinished_tasks"`
|
||||
// MaxConcurrency 跨队列的预占及运行尝试总上限。
|
||||
MaxConcurrency uint32 `json:"max_concurrency"`
|
||||
// RatePerSecond 每秒固定窗口允许的开始授权次数。
|
||||
RatePerSecond uint32 `json:"rate_per_second"`
|
||||
}
|
||||
type Result struct {
|
||||
// Resources 配置成功时的原用量快照,重报不刷新。
|
||||
Resources View `json:"resources"`
|
||||
// Deduplicated 是否返回先前已提交的同身份回执。
|
||||
Deduplicated bool `json:"deduplicated"`
|
||||
}
|
||||
type Store interface {
|
||||
GetApplicationResources(context.Context, auth.Token) (View, error)
|
||||
ConfigureApplicationResources(context.Context, auth.Token, string, ConfigureInput) (Result, error)
|
||||
}
|
||||
type Service struct{ store Store }
|
||||
|
||||
func New(store Store) *Service { return &Service{store: store} }
|
||||
func (s *Service) Get(ctx context.Context, token auth.Token) (View, error) {
|
||||
return s.store.GetApplicationResources(ctx, token)
|
||||
}
|
||||
func (s *Service) Configure(ctx context.Context, token auth.Token, key string, in ConfigureInput) (Result, error) {
|
||||
if len(key) < 1 || len(key) > 128 {
|
||||
return Result{}, fault.Invalid("invalid Idempotency-Key")
|
||||
}
|
||||
for _, c := range key {
|
||||
if c < 33 || c > 126 {
|
||||
return Result{}, fault.Invalid("invalid Idempotency-Key")
|
||||
}
|
||||
}
|
||||
if in.ExpectedRevision == 0 || strings.TrimSpace(in.Reason) == "" || !utf8.ValidString(in.Reason) || utf8.RuneCountInString(in.Reason) > 1024 {
|
||||
return Result{}, fault.Invalid("reason and nonzero expected_revision are required")
|
||||
}
|
||||
if in.MaxUnfinishedTasks < 1 || in.MaxUnfinishedTasks > 100000000 || in.MaxConcurrency < 1 || in.MaxConcurrency > 1000000 || in.RatePerSecond < 1 || in.RatePerSecond > 10000000 {
|
||||
return Result{}, fault.Invalid("application limits are invalid")
|
||||
}
|
||||
return s.store.ConfigureApplicationResources(ctx, token, key, in)
|
||||
}
|
||||
Reference in New Issue
Block a user