From ac54ba96df134d2b4bce79c574722a106be35df3 Mon Sep 17 00:00:00 2001 From: Mario de Frutos Dieguez Date: Wed, 27 Jan 2021 16:30:38 +0100 Subject: [PATCH] Runtime config endpoint now supports diff parameter (#3700) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Runtime config endpoint now supports diff parameter The runtime config endpoint now supports the diff parameter meaning that using /runtime_config?mode=diff will show only the non-default values for the configuration Signed-off-by: Mario de Frutos * Fix lint errors Signed-off-by: Mario de Frutos * Move runtime handler to the cortext package To avoid having a dependency of the validation package in the api one we have move the runtime configuration handle to the cortex package. This change also led to move some function of the handle to the util package Signed-off-by: Mario de Frutos * Added kv.Multi diff too Now we're doing diff with the kv.Multi component for the runtime config Signed-off-by: Mario de Frutos * Refactor and improvement of the runtime config diff Co-authored-by: Peter Štibraný Signed-off-by: Mario de Frutos Co-authored-by: Peter Štibraný --- CHANGELOG.md | 1 + docs/api/_index.md | 10 +++- docs/configuration/arguments.md | 2 +- pkg/api/api.go | 6 +-- pkg/api/handlers.go | 92 ++------------------------------- pkg/cortex/modules.go | 2 +- pkg/cortex/runtime_config.go | 47 +++++++++++++++++ pkg/util/config.go | 68 ++++++++++++++++++++++++ pkg/util/yaml.go | 19 +++++++ 9 files changed, 152 insertions(+), 95 deletions(-) create mode 100644 pkg/util/config.go create mode 100644 pkg/util/yaml.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 57aed7364c..19963031a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Avoid unnecessary `runtime.GC()` during compactions. * Prevent compaction loop in TSDB on data gap. * [ENHANCEMENT] Return server side performance metrics for query-frontend (using Server-timing header). #3685 +* [ENHANCEMENT] Runtime Config: Add a `mode` query parameter for the runtime config endpoint. `/runtime_config?mode=diff` now shows the YAML runtime configuration with all values that differ from the defaults. #3700 * [BUGFIX] HA Tracker: don't track as error in the `cortex_kv_request_duration_seconds` metric a CAS operation intentionally aborted. #3745 ## 1.7.0 in progress diff --git a/docs/api/_index.md b/docs/api/_index.md index 1cc8673237..da5298ee76 100644 --- a/docs/api/_index.md +++ b/docs/api/_index.md @@ -117,7 +117,7 @@ Displays the configuration currently applied to Cortex (in YAML format), includi GET /config?mode=diff ``` -Displays the configuration currently applied to Cortex (in YAML format) as before, but containing only the values that differ from the default values. +Displays the configuration currently applied to Cortex (in YAML format) as before, but containing only the values that differ from the default values. ``` GET /config?mode=defaults @@ -133,6 +133,14 @@ GET /runtime_config Displays the runtime configuration currently applied to Cortex (in YAML format), including default values. Please be aware that the endpoint will be only available if Cortex is configured with the `-runtime-config.file` option. +#### Different modes + +``` +GET /runtime_config?mode=diff +``` + +Displays the runtime configuration currently applied to Cortex (in YAML format) as before, but containing only the values that differ from the default values. + ### Services status ``` diff --git a/docs/configuration/arguments.md b/docs/configuration/arguments.md index 71af9cb62a..c44f11c08d 100644 --- a/docs/configuration/arguments.md +++ b/docs/configuration/arguments.md @@ -422,7 +422,7 @@ multi_kv_config: When running Cortex on Kubernetes, store this file in a config map and mount it in each services' containers. When changing the values there is no need to restart the services, unless otherwise specified. -The `/runtime_config` endpoint returns the runtime configuration, including the overrides. +The `/runtime_config` endpoint returns the whole runtime configuration, including the overrides. In case you want to get only the non-default values of the configuration you can pass the `mode` parameter with the `diff` value. ## Ingester, Distributor & Querier limits. diff --git a/pkg/api/api.go b/pkg/api/api.go index 90a7a75cb8..12c14d24bc 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -33,7 +33,6 @@ import ( "github.com/cortexproject/cortex/pkg/storegateway" "github.com/cortexproject/cortex/pkg/storegateway/storegatewaypb" "github.com/cortexproject/cortex/pkg/util/push" - "github.com/cortexproject/cortex/pkg/util/runtimeconfig" ) type Config struct { @@ -179,10 +178,11 @@ func (a *API) RegisterAPI(httpPathPrefix string, actualCfg interface{}, defaultC } // RegisterRuntimeConfig registers the endpoints associates with the runtime configuration -func (a *API) RegisterRuntimeConfig(runtimeCfgManager *runtimeconfig.Manager) { +func (a *API) RegisterRuntimeConfig(runtimeConfigHandler http.HandlerFunc) { a.indexPage.AddLink(SectionAdminEndpoints, "/runtime_config", "Current Runtime Config (incl. Overrides)") + a.indexPage.AddLink(SectionAdminEndpoints, "/runtime_config?mode=diff", "Current Runtime Config (show only values that differ from the defaults)") - a.RegisterRoute("/runtime_config", runtimeConfigHandler(runtimeCfgManager), false, "GET") + a.RegisterRoute("/runtime_config", runtimeConfigHandler, false, "GET") } // RegisterDistributor registers the endpoints associated with the distributor. diff --git a/pkg/api/handlers.go b/pkg/api/handlers.go index ff893fdd1e..80a90ae48e 100644 --- a/pkg/api/handlers.go +++ b/pkg/api/handlers.go @@ -2,11 +2,9 @@ package api import ( "context" - "fmt" "html/template" "net/http" "path" - "reflect" "regexp" "sync" @@ -25,14 +23,12 @@ import ( v1 "github.com/prometheus/prometheus/web/api/v1" "github.com/weaveworks/common/instrument" "github.com/weaveworks/common/middleware" - "gopkg.in/yaml.v2" "github.com/cortexproject/cortex/pkg/chunk/purger" "github.com/cortexproject/cortex/pkg/distributor" "github.com/cortexproject/cortex/pkg/querier" "github.com/cortexproject/cortex/pkg/querier/stats" "github.com/cortexproject/cortex/pkg/util" - "github.com/cortexproject/cortex/pkg/util/runtimeconfig" ) const ( @@ -117,95 +113,24 @@ func indexHandler(httpPathPrefix string, content *IndexPageContent) http.Handler } } -func yamlMarshalUnmarshal(in interface{}) (map[interface{}]interface{}, error) { - yamlBytes, err := yaml.Marshal(in) - if err != nil { - return nil, err - } - - object := make(map[interface{}]interface{}) - if err := yaml.Unmarshal(yamlBytes, object); err != nil { - return nil, err - } - - return object, nil -} - -func diffConfig(defaultConfig, actualConfig map[interface{}]interface{}) (map[interface{}]interface{}, error) { - output := make(map[interface{}]interface{}) - - for key, value := range actualConfig { - - defaultValue, ok := defaultConfig[key] - if !ok { - output[key] = value - continue - } - - switch v := value.(type) { - case int: - defaultV, ok := defaultValue.(int) - if !ok || defaultV != v { - output[key] = v - } - case string: - defaultV, ok := defaultValue.(string) - if !ok || defaultV != v { - output[key] = v - } - case bool: - defaultV, ok := defaultValue.(bool) - if !ok || defaultV != v { - output[key] = v - } - case []interface{}: - defaultV, ok := defaultValue.([]interface{}) - if !ok || !reflect.DeepEqual(defaultV, v) { - output[key] = v - } - case float64: - defaultV, ok := defaultValue.(float64) - if !ok || !reflect.DeepEqual(defaultV, v) { - output[key] = v - } - case map[interface{}]interface{}: - defaultV, ok := defaultValue.(map[interface{}]interface{}) - if !ok { - output[key] = value - } - diff, err := diffConfig(defaultV, v) - if err != nil { - return nil, err - } - if len(diff) > 0 { - output[key] = diff - } - default: - return nil, fmt.Errorf("unsupported type %T", v) - } - } - - return output, nil -} - func configHandler(actualCfg interface{}, defaultCfg interface{}) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var output interface{} switch r.URL.Query().Get("mode") { case "diff": - defaultCfgObj, err := yamlMarshalUnmarshal(defaultCfg) + defaultCfgObj, err := util.YAMLMarshalUnmarshal(defaultCfg) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - actualCfgObj, err := yamlMarshalUnmarshal(actualCfg) + actualCfgObj, err := util.YAMLMarshalUnmarshal(actualCfg) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - diff, err := diffConfig(defaultCfgObj, actualCfgObj) + diff, err := util.DiffConfig(defaultCfgObj, actualCfgObj) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -222,17 +147,6 @@ func configHandler(actualCfg interface{}, defaultCfg interface{}) http.HandlerFu } } -func runtimeConfigHandler(runtimeCfgManager *runtimeconfig.Manager) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - runtimeConfig := runtimeCfgManager.GetConfig() - if runtimeConfig == nil { - util.WriteTextResponse(w, "runtime config file doesn't exist") - return - } - util.WriteYAMLResponse(w, runtimeConfig) - } -} - // NewQuerierHandler returns a HTTP handler that can be used by the querier service to // either register with the frontend worker query processor or with the external HTTP // server to fulfill the Prometheus query API. diff --git a/pkg/cortex/modules.go b/pkg/cortex/modules.go index 48b6c7762a..d92d18dbc5 100644 --- a/pkg/cortex/modules.go +++ b/pkg/cortex/modules.go @@ -159,7 +159,7 @@ func (t *Cortex) initRuntimeConfig() (services.Service, error) { serv, err := runtimeconfig.NewRuntimeConfigManager(t.Cfg.RuntimeConfig, prometheus.DefaultRegisterer) t.RuntimeConfig = serv - t.API.RegisterRuntimeConfig(t.RuntimeConfig) + t.API.RegisterRuntimeConfig(runtimeConfigHandler(t.RuntimeConfig, t.Cfg.LimitsConfig)) return serv, err } diff --git a/pkg/cortex/runtime_config.go b/pkg/cortex/runtime_config.go index a5f8deaa3a..eea655b081 100644 --- a/pkg/cortex/runtime_config.go +++ b/pkg/cortex/runtime_config.go @@ -3,10 +3,12 @@ package cortex import ( "errors" "io" + "net/http" "gopkg.in/yaml.v2" "github.com/cortexproject/cortex/pkg/ring/kv" + "github.com/cortexproject/cortex/pkg/util" "github.com/cortexproject/cortex/pkg/util/runtimeconfig" "github.com/cortexproject/cortex/pkg/util/validation" ) @@ -83,3 +85,48 @@ func multiClientRuntimeConfigChannel(manager *runtimeconfig.Manager) func() <-ch return outCh } } +func runtimeConfigHandler(runtimeCfgManager *runtimeconfig.Manager, defaultLimits validation.Limits) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + cfg, ok := runtimeCfgManager.GetConfig().(*runtimeConfigValues) + if !ok || cfg == nil { + util.WriteTextResponse(w, "runtime config file doesn't exist") + return + } + + var output interface{} + 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 := runtimeConfigValues{} + defaultCfg.TenantLimits = map[string]*validation.Limits{} + for k, v := range cfg.TenantLimits { + if v != nil { + defaultCfg.TenantLimits[k] = &defaultLimits + } + } + + cfgYaml, err := util.YAMLMarshalUnmarshal(cfg) + 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) + } +} diff --git a/pkg/util/config.go b/pkg/util/config.go new file mode 100644 index 0000000000..e1032d0f6f --- /dev/null +++ b/pkg/util/config.go @@ -0,0 +1,68 @@ +package util + +import ( + "fmt" + "reflect" +) + +// DiffConfig utility function that returns the diff between two config map objects +func DiffConfig(defaultConfig, actualConfig map[interface{}]interface{}) (map[interface{}]interface{}, error) { + output := make(map[interface{}]interface{}) + + for key, value := range actualConfig { + + defaultValue, ok := defaultConfig[key] + if !ok { + output[key] = value + continue + } + + switch v := value.(type) { + case int: + defaultV, ok := defaultValue.(int) + if !ok || defaultV != v { + output[key] = v + } + case string: + defaultV, ok := defaultValue.(string) + if !ok || defaultV != v { + output[key] = v + } + case bool: + defaultV, ok := defaultValue.(bool) + if !ok || defaultV != v { + output[key] = v + } + case []interface{}: + defaultV, ok := defaultValue.([]interface{}) + if !ok || !reflect.DeepEqual(defaultV, v) { + output[key] = v + } + case float64: + defaultV, ok := defaultValue.(float64) + if !ok || !reflect.DeepEqual(defaultV, v) { + output[key] = v + } + case nil: + if defaultValue != nil { + output[key] = v + } + case map[interface{}]interface{}: + defaultV, ok := defaultValue.(map[interface{}]interface{}) + if !ok { + output[key] = value + } + diff, err := DiffConfig(defaultV, v) + if err != nil { + return nil, err + } + if len(diff) > 0 { + output[key] = diff + } + default: + return nil, fmt.Errorf("unsupported type %T", v) + } + } + + return output, nil +} diff --git a/pkg/util/yaml.go b/pkg/util/yaml.go new file mode 100644 index 0000000000..bb8b4d802a --- /dev/null +++ b/pkg/util/yaml.go @@ -0,0 +1,19 @@ +package util + +import "gopkg.in/yaml.v2" + +// YAMLMarshalUnmarshal utility function that converts a YAML interface in a map +// doing marshal and unmarshal of the parameter +func YAMLMarshalUnmarshal(in interface{}) (map[interface{}]interface{}, error) { + yamlBytes, err := yaml.Marshal(in) + if err != nil { + return nil, err + } + + object := make(map[interface{}]interface{}) + if err := yaml.Unmarshal(yamlBytes, object); err != nil { + return nil, err + } + + return object, nil +}