Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@

### 3.0 Cleanup

* [CHANGE] **BREAKING CHANGE** Disable legacy (flat, unscoped) overrides by default. Tempo will refuse to start if legacy overrides are detected. Set `enable_legacy_overrides: true` or `-config.enable-legacy-overrides=true` to opt back in temporarily. Legacy overrides will be removed in a future release. [#6741](https://github.com/grafana/tempo/pull/6741) (@electron0zero)
* [CHANGE] **BREAKING CHANGE** Remove remaining app ingester config [#6667](https://github.com/grafana/tempo/pull/6667) (@javiermolinar)
* [CHANGE] **BREAKING CHANGE** Remove span-metrics leftovers and lazy-init generator clients [#6618](https://github.com/grafana/tempo/pull/6618) (@javiermolinar)
* [CHANGE] **BREAKING CHANGE** Decommission livestore MetricsGenerator query service [#6615](https://github.com/grafana/tempo/pull/6615) (@javiermolinar)
Expand Down
5 changes: 5 additions & 0 deletions cmd/tempo-cli/cmd-migrate-overrides-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
return fmt.Errorf("failed to parse configFile %s: %w", cmd.ConfigFile, err)
}

// The migration command needs to read legacy overrides to convert them,
// so force-enable legacy overrides regardless of the config setting.
cfg.Overrides.EnableLegacyOverrides = true
Comment thread
electron0zero marked this conversation as resolved.

Check notice on line 45 in cmd/tempo-cli/cmd-migrate-overrides-config.go

View workflow job for this annotation

GitHub Actions / Coverage Annotations

Uncovered lines

Lines 44-45 are not covered by tests
o, err := overrides.NewOverrides(cfg.Overrides, nil, prometheus.DefaultRegisterer)
if err != nil {
return fmt.Errorf("failed to load overrides module: %w", err)
Expand All @@ -62,6 +66,7 @@
}

cfg.Overrides.Defaults = runtimeConfig.Defaults
cfg.Overrides.EnableLegacyOverrides = false // reset - migrated config doesn't need this

Check notice on line 69 in cmd/tempo-cli/cmd-migrate-overrides-config.go

View workflow job for this annotation

GitHub Actions / Coverage Annotations

Uncovered line

Line 69 is not covered by tests
configBytes, err := yaml.Marshal(cfg)
if err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
Expand Down
3 changes: 2 additions & 1 deletion cmd/tempo/app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,8 @@ var (
Message: "Local backend will not correctly retrieve traces with a distributed deployment unless all components have access to the same disk. You should probably be using object storage as a backend.",
}
warnLegacyOverridesConfig = ConfigWarning{
Message: "Inline, unscoped overrides are deprecated. Please use the new overrides config format.",
Message: "DEPRECATED: Legacy (flat, unscoped) overrides format is in use and will be removed in a future release.",
Explain: "Migrate your overrides config to the new scoped format, or set -config.enable-legacy-overrides=true (or enable_legacy_overrides: true in YAML) to continue using legacy overrides temporarily.",
}

warnTracesAndUserConfigurableOverridesStorageConflict = ConfigWarning{
Expand Down
4 changes: 4 additions & 0 deletions docs/sources/tempo/configuration/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2115,6 +2115,10 @@ overrides:
# How frequent tenant-specific overrides are read from the configuration file.
[per_tenant_override_period: <duration> | default = 10s]

# Enable the deprecated legacy overrides format.
# NOTE: This is disabled by default and will be removed in a future release.
[enable_legacy_overrides: <bool> | default = false]

# User-configurable overrides configuration
user_configurable_overrides:

Expand Down
1 change: 1 addition & 0 deletions docs/sources/tempo/configuration/manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,7 @@ overrides:
hedge_requests_up_to: 2
api:
check_for_conflicting_runtime_overrides: false
enable_legacy_overrides: false
memberlist:
node_name: ""
randomize_node_name: true
Expand Down
10 changes: 10 additions & 0 deletions modules/overrides/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ type Config struct {

UserConfigurableOverridesConfig UserConfigurableOverridesConfig `yaml:"user_configurable_overrides" json:"user_configurable_overrides"`

// EnableLegacyOverrides allows using the deprecated legacy (flat, unscoped) overrides format.
// Legacy overrides are disabled by default. Set this to true to opt back in while migrating
// to the new format. This option will be removed in a future release.
EnableLegacyOverrides bool `yaml:"enable_legacy_overrides" json:"enable_legacy_overrides"`

ConfigType ConfigType `yaml:"-" json:"-"`
ExpandEnv bool `yaml:"-" json:"-"`
}
Expand All @@ -243,12 +248,15 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
PerTenantOverridePeriod model.Duration `yaml:"per_tenant_override_period"`

UserConfigurableOverridesConfig UserConfigurableOverridesConfig `yaml:"user_configurable_overrides"`

EnableLegacyOverrides bool `yaml:"enable_legacy_overrides"`
}
var legacyCfg legacyConfig
legacyCfg.DefaultOverrides = c.Defaults.toLegacy()
legacyCfg.PerTenantOverrideConfig = c.PerTenantOverrideConfig
legacyCfg.PerTenantOverridePeriod = c.PerTenantOverridePeriod
legacyCfg.UserConfigurableOverridesConfig = c.UserConfigurableOverridesConfig
legacyCfg.EnableLegacyOverrides = c.EnableLegacyOverrides

if legacyErr := unmarshal(&legacyCfg); legacyErr != nil {
return fmt.Errorf("failed to unmarshal config: %w; also failed in legacy format: %w", err, legacyErr)
Expand All @@ -258,6 +266,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
c.PerTenantOverrideConfig = legacyCfg.PerTenantOverrideConfig
c.PerTenantOverridePeriod = legacyCfg.PerTenantOverridePeriod
c.UserConfigurableOverridesConfig = legacyCfg.UserConfigurableOverridesConfig
c.EnableLegacyOverrides = legacyCfg.EnableLegacyOverrides
c.ConfigType = ConfigTypeLegacy
return nil
}
Expand Down Expand Up @@ -291,6 +300,7 @@ func (c *Config) RegisterFlagsAndApplyDefaults(f *flag.FlagSet) {
f.StringVar(&c.PerTenantOverrideConfig, "config.per-user-override-config", "", "File name of per-user Overrides.")
_ = c.PerTenantOverridePeriod.Set("10s")
f.Var(&c.PerTenantOverridePeriod, "config.per-user-override-period", "Period with this to reload the Overrides.")
f.BoolVar(&c.EnableLegacyOverrides, "config.enable-legacy-overrides", false, "Enable the deprecated legacy overrides format. This is disabled by default and will be removed in a future release.")

c.UserConfigurableOverridesConfig.RegisterFlagsAndApplyDefaults(f)
}
Expand Down
20 changes: 20 additions & 0 deletions modules/overrides/overrides.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package overrides

import (
"fmt"

"github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel"

"github.com/grafana/tempo/pkg/util/log"
)

const wildcardTenant = "*"
Expand All @@ -21,6 +26,21 @@ var metricOverridesLimitsDesc = prometheus.NewDesc(
// are defaulted to those values. As such, the last call to NewOverrides will
// become the new global defaults.
func NewOverrides(cfg Config, validator Validator, registerer prometheus.Registerer) (Service, error) {
if cfg.ConfigType == ConfigTypeLegacy {
// always log a warning for ConfigTypeLegacy
level.Warn(log.Logger).Log(
"msg", "DEPRECATED: legacy overrides config format is in use. Legacy overrides are deprecated and will be removed in a future release. "+
"Please migrate your overrides config to the new overrides format.",
)

if !cfg.EnableLegacyOverrides {
return nil, fmt.Errorf(
"DEPRECATED: legacy overrides config format detected but legacy overrides are disabled by default. Legacy overrides will be removed in a future release. " +
"Migrate your overrides config to the new scoped format, or set -config.enable-legacy-overrides=true (or enable_legacy_overrides: true in YAML) to continue using legacy overrides temporarily")
}
Comment thread
electron0zero marked this conversation as resolved.

}

o, err := newRuntimeConfigOverrides(cfg, validator, registerer)
if err != nil {
return nil, err
Expand Down
95 changes: 95 additions & 0 deletions modules/overrides/overrides_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package overrides

import (
"bytes"
"context"
"testing"

"github.com/grafana/dskit/services"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
"go.yaml.in/yaml/v2"
Comment thread
electron0zero marked this conversation as resolved.
)

func TestLegacyOverridesDisabledByDefault(t *testing.T) {
tests := []struct {
name string
configType ConfigType
enableLegacyOverrides bool
expectErr string
}{
{
name: "rejects legacy when EnableLegacyOverrides is false",
configType: ConfigTypeLegacy,
expectErr: "DEPRECATED: legacy overrides config format detected but legacy overrides are disabled by default",
},
{
name: "accepts legacy when EnableLegacyOverrides is true",
configType: ConfigTypeLegacy,
enableLegacyOverrides: true,
},
{
name: "accepts new config regardless of EnableLegacyOverrides",
configType: ConfigTypeNew,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
cfg := Config{
ConfigType: tc.configType,
EnableLegacyOverrides: tc.enableLegacyOverrides,
}

o, err := NewOverrides(cfg, nil, prometheus.NewRegistry())
if tc.expectErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectErr)
return
}

require.NoError(t, err)
require.NoError(t, services.StartAndAwaitRunning(context.TODO(), o))
require.NoError(t, services.StopAndAwaitTerminated(context.TODO(), o))
})
}
}

func TestPerTenantLegacyOverridesDisabledByDefault(t *testing.T) {
legacyOverrides := &perTenantLegacyOverrides{
TenantLimits: map[string]*LegacyOverrides{
"tenant1": {MaxBytesPerTrace: 1000},
},
}
buff, err := yaml.Marshal(legacyOverrides)
require.NoError(t, err)

tests := []struct {
name string
enableLegacy bool
expectErr string
}{
{
name: "rejects legacy per-tenant overrides when enableLegacy is false",
expectErr: "DEPRECATED: legacy overrides config format is in use. per-tenant overrides file uses the legacy format but legacy overrides are disabled by default",
},
{
name: "accepts legacy per-tenant overrides when enableLegacy is true",
enableLegacy: true,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
loader := loadPerTenantOverrides(&mockValidator{}, ConfigTypeNew, false, tc.enableLegacy)
result, err := loader(bytes.NewReader(buff))
if tc.expectErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectErr)
} else {
require.NoError(t, err)
require.NotNil(t, result)
}
})
}
}
24 changes: 22 additions & 2 deletions modules/overrides/runtime_config_overrides.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ func (o *perTenantOverrides) forUser(userID string) *Overrides {
}

// loadPerTenantOverrides is of type runtimeconfig.Loader
func loadPerTenantOverrides(validator Validator, typ ConfigType, expandEnv bool) func(r io.Reader) (interface{}, error) {
func loadPerTenantOverrides(validator Validator, typ ConfigType, expandEnv bool, enableLegacy bool) func(r io.Reader) (interface{}, error) {
// var is outside closure to ensure it's not recreated on each call
var lastLegacyWarn time.Time
return func(r io.Reader) (interface{}, error) {
overrides := &perTenantOverrides{}

Expand All @@ -92,6 +94,24 @@ func loadPerTenantOverrides(validator Validator, typ ConfigType, expandEnv bool)
return nil, err
}

if overrides.ConfigType == ConfigTypeLegacy {
// Log periodically (every 10 mins) and not every reload, so the warning stays visible in recent logs
// without spamming logs on every reload cycle.
if time.Since(lastLegacyWarn) >= 10*time.Minute {
level.Warn(log.Logger).Log(
"msg", "DEPRECATED: legacy overrides config format is in use. per-tenant overrides file uses the legacy format. Legacy overrides are deprecated and will be removed in a future release. "+
"Please migrate your per-tenant overrides to the new scoped format.",
)
lastLegacyWarn = time.Now()
}

if !enableLegacy {
return nil, fmt.Errorf(
"DEPRECATED: legacy overrides config format is in use. per-tenant overrides file uses the legacy format but legacy overrides are disabled by default. " +
"Migrate your per-tenant overrides to the new scoped format, or set -config.enable-legacy-overrides=true (or enable_legacy_overrides: true in YAML) to continue using legacy overrides temporarily")
}
}

if overrides.ConfigType != typ {
// TODO: Return error?
level.Warn(log.Logger).Log(
Expand Down Expand Up @@ -141,7 +161,7 @@ func newRuntimeConfigOverrides(cfg Config, validator Validator, registerer prome
runtimeCfg := runtimeconfig.Config{
LoadPath: []string{cfg.PerTenantOverrideConfig},
ReloadPeriod: time.Duration(cfg.PerTenantOverridePeriod),
Loader: loadPerTenantOverrides(validator, cfg.ConfigType, cfg.ExpandEnv),
Loader: loadPerTenantOverrides(validator, cfg.ConfigType, cfg.ExpandEnv, cfg.EnableLegacyOverrides),
}
runtimeCfgMgr, err := runtimeconfig.New(runtimeCfg, "overrides", prometheus.WrapRegistererWithPrefix("tempo_", registerer), log.Logger)
if err != nil {
Expand Down
5 changes: 3 additions & 2 deletions modules/overrides/runtime_config_overrides_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
func TestRuntimeConfigOverrides_loadPerTenantOverrides(t *testing.T) {
validator := &mockValidator{}

loader := loadPerTenantOverrides(validator, ConfigTypeNew, false)
loader := loadPerTenantOverrides(validator, ConfigTypeNew, false, false)

perTenantOverrides := perTenantOverrides{
TenantLimits: map[string]*Overrides{
Expand Down Expand Up @@ -205,7 +205,8 @@ func TestRuntimeConfigOverrides(t *testing.T) {

t.Run(fmt.Sprintf("%s (legacy)", tt.name), func(t *testing.T) {
cfg := Config{
Defaults: tt.defaultLimits,
Defaults: tt.defaultLimits,
EnableLegacyOverrides: true, // need to enable it to test
}

if tt.perTenantOverrides != nil {
Expand Down
4 changes: 2 additions & 2 deletions operations/jsonnet-compiled/jsonnetfile.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"subdir": "ksonnet-util"
}
},
"version": "f53b1c9f855e11adce73b0aa6d6b08f511b89274",
"version": "2e9c7f4a276f6f6a9bc3f994f2b8804555f31bb9",
"sum": "0y3AFX9LQSpfWTxWKSwoLgbt0Wc9nnCwhMH2szKzHv0="
},
{
Expand All @@ -18,7 +18,7 @@
"subdir": "memcached"
}
},
"version": "f53b1c9f855e11adce73b0aa6d6b08f511b89274",
"version": "2e9c7f4a276f6f6a9bc3f994f2b8804555f31bb9",
"sum": "zNTCDRaCqJUHjQSQRsxGR3jLrMP9tU7YNQb13dbm1bU="
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ data:
overrides.yaml: |
overrides:
super_user:
ingestion_burst_size_bytes: 2e+07
ingestion_rate_limit_bytes: 2e+07
max_bytes_per_trace: 3e+07
max_traces_per_user: 100000
global:
max_bytes_per_trace: 3e+07
ingestion:
burst_size_bytes: 2e+07
max_traces_per_user: 100000
rate_limit_bytes: 2e+07
kind: ConfigMap
metadata:
name: tempo-overrides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ data:
overrides.yaml: |
overrides:
super_user:
ingestion_burst_size_bytes: 2e+07
ingestion_rate_limit_bytes: 2e+07
max_bytes_per_trace: 3e+07
max_traces_per_user: 100000
global:
max_bytes_per_trace: 3e+07
ingestion:
burst_size_bytes: 2e+07
max_traces_per_user: 100000
rate_limit_bytes: 2e+07
kind: ConfigMap
metadata:
name: tempo-overrides
Expand Down
12 changes: 8 additions & 4 deletions operations/jsonnet/microservices/config.libsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,14 @@
overrides_configmap_name: 'tempo-overrides',
overrides+:: {
super_user: {
max_traces_per_user: 100000,
ingestion_rate_limit_bytes: 200e5, // ~20MB per sec
ingestion_burst_size_bytes: 200e5, // ~20MB
max_bytes_per_trace: 300e5, // ~30MB
ingestion: {
max_traces_per_user: 100000,
rate_limit_bytes: 200e5, // ~20MB per sec
burst_size_bytes: 200e5, // ~20MB
},
global: {
max_bytes_per_trace: 300e5, // ~30MB
},
},
},
},
Expand Down
Loading