Skip to content

Commit

Permalink
Add runtime config handler (#936)
Browse files Browse the repository at this point in the history
* Add runtime config handler

* Improve code

* Inline overrides to not duplicate the field

* Finish doc comment

* Use runtime config manager instead of limits struct

* Unexport tenantOverrides
  • Loading branch information
mapno authored Sep 13, 2021
1 parent e235c26 commit 6ca8535
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 30 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
* [CHANGE] Renamed CLI flag from `--storage.trace.maintenance-cycle` to `--storage.trace.blocklist_poll`. This is a **breaking change** [#897](https://github.com/grafana/tempo/pull/897) (@mritunjaysharma394)
* [CHANGE] update jsonnet alerts and recording rules to use `job_selectors` and `cluster_selectors` for configurable unique identifier labels [#935](https://github.com/grafana/tempo/pull/935) (@kevinschoonover)
* [CHANGE] Modify generated tag keys in Vulture for easier filtering [#934](https://github.com/grafana/tempo/pull/934) (@zalegrala)
* [FEATURE] Add runtime config handler [#936](https://github.com/grafana/tempo/pull/936) (@mapno)

## v1.1.0 / 2021-08-26
* [CHANGE] Upgrade Cortex from v1.9.0 to v1.9.0-131-ga4bf10354 [#841](https://github.com/grafana/tempo/pull/841) (@aknuds1)
Expand Down
4 changes: 3 additions & 1 deletion cmd/tempo/app/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ func (t *App) initOverrides() (services.Service, error) {
}
t.overrides = overrides

t.Server.HTTP.Handle("/runtime_config", overrides.Handler())

return t.overrides, nil
}

Expand Down Expand Up @@ -282,8 +284,8 @@ func (t *App) setupModuleManager() error {

deps := map[string][]string{
// Server: nil,
// Overrides: nil,
// Store: nil,
Overrides: {Server},
MemberlistKV: {Server},
QueryFrontend: {Server},
Ring: {Server, MemberlistKV},
Expand Down
2 changes: 1 addition & 1 deletion modules/overrides/limits.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type Limits struct {
// Compactor enforced limits.
BlockRetention model.Duration `yaml:"block_retention" json:"block_retention"`

// Config for overrides, convenient if it goes here.
// Configuration for overrides, convenient if it goes here.
PerTenantOverrideConfig string `yaml:"per_tenant_override_config" json:"per_tenant_override_config"`
PerTenantOverridePeriod model.Duration `yaml:"per_tenant_override_period" json:"per_tenant_override_period"`
}
Expand Down
117 changes: 89 additions & 28 deletions modules/overrides/overrides.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,33 @@ import (
"context"
"fmt"
"io"
"net/http"
"time"

"github.com/cortexproject/cortex/pkg/util"
"github.com/cortexproject/cortex/pkg/util/log"
"github.com/grafana/dskit/runtimeconfig"
"github.com/grafana/dskit/services"
"github.com/prometheus/client_golang/prometheus"
"gopkg.in/yaml.v2"
)

// TenantLimits is a function that returns limits for given tenant, or
// nil, if there are no tenant-specific limits.
type TenantLimits func(userID string) *Limits

const wildcardTenant = "*"

// perTenantOverrides represents the overrides config file
type perTenantOverrides struct {
TenantLimits map[string]*Limits `yaml:"overrides"`
}

// forUser returns limits for a given tenant, or nil if there are no tenant-specific limits.
func (o *perTenantOverrides) forUser(userID string) *Limits {
l, ok := o.TenantLimits[userID]
if !ok || l == nil {
return nil
}
return l
}

// loadPerTenantOverrides is of type runtimeconfig.Loader
func loadPerTenantOverrides(r io.Reader) (interface{}, error) {
var overrides = &perTenantOverrides{}
Expand All @@ -37,13 +44,19 @@ func loadPerTenantOverrides(r io.Reader) (interface{}, error) {
return overrides, nil
}

// Config is a struct used to print the complete runtime config (defaults + overrides)
type Config struct {
Defaults *Limits `yaml:"defaults"`
PerTenantOverrides perTenantOverrides `yaml:",inline"`
}

// Overrides periodically fetch a set of per-user overrides, and provides convenience
// functions for fetching the correct value.
type Overrides struct {
services.Service

defaultLimits *Limits
tenantLimits TenantLimits
defaultLimits *Limits
runtimeConfigMgr *runtimeconfig.Manager

// Manager for subservices
subservices *services.Manager
Expand All @@ -55,7 +68,7 @@ type Overrides struct {
// are defaulted to those values. As such, the last call to NewOverrides will
// become the new global defaults.
func NewOverrides(defaults Limits) (*Overrides, error) {
var tenantLimits TenantLimits
var manager *runtimeconfig.Manager
subservices := []services.Service(nil)

if defaults.PerTenantOverrideConfig != "" {
Expand All @@ -68,13 +81,13 @@ func NewOverrides(defaults Limits) (*Overrides, error) {
if err != nil {
return nil, fmt.Errorf("failed to create runtime config manager %w", err)
}
tenantLimits = tenantLimitsFromRuntimeConfig(runtimeCfgMgr)
manager = runtimeCfgMgr
subservices = append(subservices, runtimeCfgMgr)
}

o := &Overrides{
tenantLimits: tenantLimits,
defaultLimits: &defaults,
runtimeConfigMgr: manager,
defaultLimits: &defaults,
}

if len(subservices) > 0 {
Expand Down Expand Up @@ -123,6 +136,65 @@ func (o *Overrides) stopping(_ error) error {
return nil
}

func (o *Overrides) tenantOverrides() *perTenantOverrides {
if o.runtimeConfigMgr == nil {
return nil
}
cfg, ok := o.runtimeConfigMgr.GetConfig().(*perTenantOverrides)
if !ok || cfg == nil {
return nil
}

return cfg
}

func (o *Overrides) Handler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var tenantOverrides perTenantOverrides
if o.tenantOverrides() != nil {
tenantOverrides = *o.tenantOverrides()
}
var output interface{}
cfg := Config{
Defaults: o.defaultLimits,
PerTenantOverrides: tenantOverrides,
}
switch r.URL.Query().Get("mode") {
case "diff":
// Default runtime config is just empty struct, but to make diff work,
// we set defaultLimits for every tenant that exists in runtime config.
defaultCfg := perTenantOverrides{TenantLimits: map[string]*Limits{}}
defaultCfg.TenantLimits = map[string]*Limits{}
for k, v := range tenantOverrides.TenantLimits {
if v != nil {
defaultCfg.TenantLimits[k] = o.defaultLimits
}
}

cfgYaml, err := util.YAMLMarshalUnmarshal(cfg.PerTenantOverrides)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

defaultCfgYaml, err := util.YAMLMarshalUnmarshal(defaultCfg)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

output, err = util.DiffConfig(defaultCfgYaml, cfgYaml)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
default:
output = cfg
}
util.WriteYAMLResponse(w, output)
}
}

// IngestionRateStrategy returns whether the ingestion rate limit should be individually applied
// to each distributor instance (local) or evenly shared across the cluster (global).
func (o *Overrides) IngestionRateStrategy() string {
Expand Down Expand Up @@ -153,45 +225,34 @@ func (o *Overrides) MaxSearchBytesPerTrace(userID string) int {
return o.getOverridesForUser(userID).MaxSearchBytesPerTrace
}

// IngestionRateSpans is the number of spans per second allowed for this tenant
// IngestionRateLimitBytes is the number of spans per second allowed for this tenant
func (o *Overrides) IngestionRateLimitBytes(userID string) float64 {
return float64(o.getOverridesForUser(userID).IngestionRateLimitBytes)
}

// IngestionBurstSize is the burst size in spans allowed for this tenant
// IngestionBurstSizeBytes is the burst size in spans allowed for this tenant
func (o *Overrides) IngestionBurstSizeBytes(userID string) int {
return o.getOverridesForUser(userID).IngestionBurstSizeBytes
}

// BlockRetention is the duration of the block retention for this tenant
func (o *Overrides) BlockRetention(userID string) time.Duration {
return time.Duration(o.getOverridesForUser(userID).BlockRetention)
}

func (o *Overrides) getOverridesForUser(userID string) *Limits {
if o.tenantLimits != nil {
l := o.tenantLimits(userID)
if tenantOverrides := o.tenantOverrides(); tenantOverrides != nil {
l := tenantOverrides.forUser(userID)
if l != nil {
return l
}

l = o.tenantLimits(wildcardTenant)
l = tenantOverrides.forUser(wildcardTenant)
if l != nil {
return l
}
}
return o.defaultLimits
}

func tenantLimitsFromRuntimeConfig(c *runtimeconfig.Manager) TenantLimits {
if c == nil {
return nil
}
return func(userID string) *Limits {
cfg, ok := c.GetConfig().(*perTenantOverrides)
if !ok || cfg == nil {
return nil
}

return cfg.TenantLimits[userID]
}
return o.defaultLimits
}

0 comments on commit 6ca8535

Please sign in to comment.