Files
grafana/pkg/storage/unified/resource/quotas.go
owensmallwood 6b50e2d730 Unified Storage: Update yaml decoding for quotas to accomodate top-level overrides key (#115049)
* update yaml decoding for quotas to accomodate top-level overrides key

* update test

* fix test indentation
2025-12-09 23:14:22 +00:00

131 lines
3.4 KiB
Go

package resource
import (
"context"
"fmt"
"io"
"os"
"strings"
"time"
"github.com/grafana/dskit/runtimeconfig"
"github.com/grafana/dskit/services"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/trace"
"go.yaml.in/yaml/v3"
)
const DEFAULT_RESOURCE_LIMIT = 1000
type OverridesService struct {
manager *runtimeconfig.Manager
logger log.Logger
tracer trace.Tracer
}
type ReloadOptions struct {
FilePath string
ReloadPeriod time.Duration
}
// ResourceQuota represents quota limits for a specific resource
type ResourceQuota struct {
Limit int `yaml:"limit"`
}
// NamespaceOverrides represents all overrides for a tenant
type NamespaceOverrides struct {
Quotas map[string]ResourceQuota `yaml:"quotas"`
}
// Overrides represents the entire overrides configuration file
type Overrides struct {
Namespaces map[string]NamespaceOverrides
}
/*
This service loads overrides (currently just quotas) from a YAML file with the following yaml structure:
overrides:
"123":
quotas:
dashboard.grafana.app/dashboards:
limit: 1500
folder.grafana.app/folders:
limit: 1500
*/
func NewOverridesService(_ context.Context, logger log.Logger, reg prometheus.Registerer, tracer trace.Tracer, opts ReloadOptions) (*OverridesService, error) {
// shouldn't be empty since we use file path existence to determine if we should enable the service
if opts.FilePath == "" {
return nil, fmt.Errorf("overrides file path is required")
}
if opts.ReloadPeriod == 0 {
opts.ReloadPeriod = time.Second * 30
}
// Check if file exists
if _, err := os.Stat(opts.FilePath); err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("overrides file does not exist: %s", opts.FilePath)
}
return nil, fmt.Errorf("failed to stat overrides file: %w", err)
}
config := runtimeconfig.Config{
ReloadPeriod: opts.ReloadPeriod,
LoadPath: []string{opts.FilePath},
Loader: func(r io.Reader) (interface{}, error) {
var raw struct {
Overrides map[string]NamespaceOverrides `yaml:"overrides"`
}
decoder := yaml.NewDecoder(r)
if err := decoder.Decode(&raw); err != nil {
return nil, err
}
return &Overrides{Namespaces: raw.Overrides}, nil
},
}
manager, err := runtimeconfig.New(config, "tenant-overrides", reg, logger)
if err != nil {
return nil, err
}
return &OverridesService{
manager: manager,
logger: logger,
tracer: tracer,
}, nil
}
func (q *OverridesService) init(ctx context.Context) error {
return services.StartAndAwaitRunning(ctx, q.manager)
}
func (q *OverridesService) stop(ctx context.Context) error {
return services.StopAndAwaitTerminated(ctx, q.manager)
}
func (q *OverridesService) GetQuota(_ context.Context, nsr NamespacedResource) (ResourceQuota, error) {
if nsr.Namespace == "" || nsr.Resource == "" || nsr.Group == "" {
return ResourceQuota{}, fmt.Errorf("invalid namespaced resource: %+v", nsr)
}
overrides, ok := q.manager.GetConfig().(*Overrides)
if !ok {
return ResourceQuota{}, fmt.Errorf("failed to get quota overrides from config manager")
}
tenantId := strings.TrimPrefix(nsr.Namespace, "stacks-")
groupResource := nsr.Group + "/" + nsr.Resource
if tenantOverrides, ok := overrides.Namespaces[tenantId]; ok {
if resourceQuota, ok := tenantOverrides.Quotas[groupResource]; ok {
return resourceQuota, nil
}
}
return ResourceQuota{Limit: DEFAULT_RESOURCE_LIMIT}, nil
}