Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamically generate available options for -target commandline arg #2752

Merged
merged 26 commits into from Jun 30, 2020
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5dbd3db
Add ModuleOption to ModuleManager
alvinlin123 Jun 19, 2020
79fd050
add support for -modules option
alvinlin123 Jun 19, 2020
84a899d
refactor so the -modules flag is defied in cortex.go instead of main.go
alvinlin123 Jun 19, 2020
fa062f9
Error out if given invalid target name
alvinlin123 Jun 19, 2020
1bb5010
Fix tests by defaulting moudules to public
alvinlin123 Jun 20, 2020
016e180
Update changelog
alvinlin123 Jun 20, 2020
b8f8021
Update changelog
alvinlin123 Jun 20, 2020
e92692f
Implemented functional options for RegisterModule
alvinlin123 Jun 23, 2020
726f292
Remove RegisterModuleWithOption method
alvinlin123 Jun 23, 2020
648ada6
Handled majority of PR comments
alvinlin123 Jun 23, 2020
954ad5e
More PR review comments handling
alvinlin123 Jun 23, 2020
0e6f54c
put functional options for modules inside module package
alvinlin123 Jun 24, 2020
a853d26
Update pkg/util/modules/modules.go
alvinlin123 Jun 24, 2020
614f04e
Update pkg/util/modules/modules.go
alvinlin123 Jun 24, 2020
7c8f400
Address more PR comments
alvinlin123 Jun 24, 2020
ede9b7c
Remove unneeded asserts
alvinlin123 Jun 24, 2020
c06598b
merge from upstream/mesger
alvinlin123 Jun 24, 2020
6bc6e27
Update CHANGELOG.md
alvinlin123 Jun 25, 2020
83b2f37
Minimize diff with upstream/master
alvinlin123 Jun 25, 2020
3890d9c
Make code more readible
alvinlin123 Jun 25, 2020
a791200
avoid creating new type for simple concept
alvinlin123 Jun 25, 2020
3cf8f2a
Update pkg/cortex/cortex.go
alvinlin123 Jun 25, 2020
b4e1d65
remove unused import
alvinlin123 Jun 25, 2020
0466ce8
resolve conflict with upstream/master
alvinlin123 Jun 27, 2020
9823de4
Resolve more conflict
alvinlin123 Jun 27, 2020
c90068b
resolve conflict
alvinlin123 Jun 29, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -56,6 +56,7 @@
* [FEATURE] TLS config options added for GRPC clients in Querier (Query-frontend client & Ingester client), Ruler, Store Gateway, as well as HTTP client in Config store client. #2502
* [FEATURE] The flag `-frontend.max-cache-freshness` is now supported within the limits overrides, to specify per-tenant max cache freshness values. The corresponding YAML config parameter has been changed from `results_cache.max_freshness` to `limits_config.max_cache_freshness`. The legacy YAML config parameter (`results_cache.max_freshness`) will continue to be supported till Cortex release `v1.4.0`. #2609
* [FEATURE] Experimental gRPC Store: Added support to 3rd parties index and chunk stores using gRPC client/server plugin mechanism. #2220
* [ENHANCEMENT] Add `-modules` command line flag to list possible values for `-target`. Also, log warning if given target is internal component. #2752
* [ENHANCEMENT] Propagate GOPROXY value when building `build-image`. This is to help the builders building the code in a Network where default Go proxy is not accessible (e.g. when behind some corporate VPN). #2741
* [ENHANCEMENT] Querier: Added metric `cortex_querier_request_duration_seconds` for all requests to the querier. #2708
* [ENHANCEMENT] Experimental TSDB: added the following metrics to the ingester: #2580 #2583 #2589 #2654
Expand Down
30 changes: 24 additions & 6 deletions cmd/cortex/main.go
Expand Up @@ -94,7 +94,9 @@ func main() {
}
}

if testMode {
// Continue on if -modules flag is given. Code handling the
// -modules flag will not start cortex.
if testMode && !cfg.ListModules {
DumpYaml(&cfg)
return
}
Expand All @@ -117,11 +119,15 @@ func main() {

util.InitEvents(eventSampleRate)

// Setting the environment variable JAEGER_AGENT_HOST enables tracing
if trace, err := tracing.NewFromEnv("cortex-" + cfg.Target); err != nil {
level.Error(util.Logger).Log("msg", "Failed to setup tracing", "err", err.Error())
} else {
defer trace.Close()
// In testing mode skip JAEGER setup to avoid panic due to
// "duplicate metrics collector registration attempted"
if !testMode {
// Setting the environment variable JAEGER_AGENT_HOST enables tracing.
if trace, err := tracing.NewFromEnv("cortex-" + cfg.Target); err != nil {
level.Error(util.Logger).Log("msg", "Failed to setup tracing", "err", err.Error())
} else {
defer trace.Close()
}
}

// Initialise seed for randomness usage.
Expand All @@ -130,6 +136,18 @@ func main() {
t, err := cortex.New(cfg)
util.CheckFatal("initializing cortex", err)

if t.Cfg.ListModules {
alvinlin123 marked this conversation as resolved.
Show resolved Hide resolved
for _, m := range t.ModuleManager.UserVisibleModuleNames() {
fmt.Fprintln(os.Stdout, m)
}

// in test mode we cannot call os.Exit, it will stop to whole test process.
if testMode {
return
}
os.Exit(2)
}

level.Info(util.Logger).Log("msg", "Starting Cortex", "version", version.Info())

err = t.Run()
Expand Down
12 changes: 12 additions & 0 deletions cmd/cortex/main_test.go
Expand Up @@ -63,6 +63,18 @@ func TestFlagParsing(t *testing.T) {
stdoutMessage: "target: distributor\n",
},

"user visible module listing": {
arguments: []string{"-modules"},
stdoutMessage: "ingester\n",
stderrExcluded: "ingester\n",
},

"user visible module listing flag take precedence over target flag": {
arguments: []string{"-modules", "-target=blah"},
stdoutMessage: "ingester\n",
stderrExcluded: "ingester\n",
},

// we cannot test the happy path, as cortex would then fully start
} {
t.Run(name, func(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions docs/configuration/config-file-reference.md
Expand Up @@ -49,8 +49,8 @@ Where default_value is the value to use if the environment variable is undefined
### Supported contents and default values of the config file

```yaml
# The Cortex service to run. Supported values are: all, distributor, ingester,
# querier, query-frontend, table-manager, ruler, alertmanager, configs.
# The Cortex service to run. Use "-modules" command line flag to get a list of
# available options.
# CLI flag: -target
[target: <string> | default = "all"]

Expand Down
11 changes: 10 additions & 1 deletion pkg/cortex/cortex.go
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"net/http"
"os"
"strings"

"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
Expand Down Expand Up @@ -76,6 +77,7 @@ type Config struct {
AuthEnabled bool `yaml:"auth_enabled"`
PrintConfig bool `yaml:"-"`
HTTPPrefix string `yaml:"http_prefix"`
ListModules bool `yaml:"-"` // No yaml for this, it only works with flags.

API api.Config `yaml:"api"`
Server server.Config `yaml:"server"`
Expand Down Expand Up @@ -110,7 +112,8 @@ type Config struct {
func (c *Config) RegisterFlags(f *flag.FlagSet) {
c.Server.MetricsNamespace = "cortex"
c.Server.ExcludeRequestInLog = true
f.StringVar(&c.Target, "target", All, "The Cortex service to run. Supported values are: all, distributor, ingester, querier, query-frontend, table-manager, ruler, alertmanager, configs.")
f.StringVar(&c.Target, "target", All, "The Cortex service to run. Use \"-modules\" command line flag to get a list of available options.")
f.BoolVar(&c.ListModules, "modules", false, "List available values to be use as target. Cannot be used in YAML config.")
f.BoolVar(&c.AuthEnabled, "auth.enabled", true, "Set to false to disable auth.")
f.BoolVar(&c.PrintConfig, "print.config", false, "Print the config and exit.")
f.StringVar(&c.HTTPPrefix, "http.prefix", "/api/prom", "HTTP path prefix for Cortex API.")
Expand Down Expand Up @@ -291,6 +294,12 @@ func (t *Cortex) setupThanosTracing() {

// Run starts Cortex running, and blocks until a Cortex stops.
func (t *Cortex) Run() error {
if !t.ModuleManager.IsUserVisibleModule(t.Cfg.Target) {
level.Warn(util.Logger).Log(
"msg", fmt.Sprintf("'%v' is not a user visible module, is this intended?", t.Cfg.Target),
"user-visible-modules", strings.Join(t.ModuleManager.UserVisibleModuleNames(), ", "))
alvinlin123 marked this conversation as resolved.
Show resolved Hide resolved
}

serviceMap, err := t.ModuleManager.InitModuleServices(t.Cfg.Target)
if err != nil {
return err
Expand Down
19 changes: 9 additions & 10 deletions pkg/cortex/modules.go
Expand Up @@ -514,19 +514,19 @@ func (t *Cortex) setupModuleManager() error {

// Register all modules here.
// RegisterModule(name string, initFn func()(services.Service, error))
mm.RegisterModule(Server, t.initServer)
mm.RegisterModule(API, t.initAPI)
mm.RegisterModule(RuntimeConfig, t.initRuntimeConfig)
mm.RegisterModule(MemberlistKV, t.initMemberlistKV)
mm.RegisterModule(Ring, t.initRing)
mm.RegisterModule(Overrides, t.initOverrides)
mm.RegisterModule(Server, t.initServer, modules.UserInvisibleModule)
mm.RegisterModule(API, t.initAPI, modules.UserInvisibleModule)
mm.RegisterModule(RuntimeConfig, t.initRuntimeConfig, modules.UserInvisibleModule)
mm.RegisterModule(MemberlistKV, t.initMemberlistKV, modules.UserInvisibleModule)
mm.RegisterModule(Ring, t.initRing, modules.UserInvisibleModule)
mm.RegisterModule(Overrides, t.initOverrides, modules.UserInvisibleModule)
mm.RegisterModule(Distributor, t.initDistributor)
mm.RegisterModule(Store, t.initStore)
mm.RegisterModule(DeleteRequestsStore, t.initDeleteRequestsStore)
mm.RegisterModule(Store, t.initStore, modules.UserInvisibleModule)
mm.RegisterModule(DeleteRequestsStore, t.initDeleteRequestsStore, modules.UserInvisibleModule)
mm.RegisterModule(Ingester, t.initIngester)
mm.RegisterModule(Flusher, t.initFlusher)
mm.RegisterModule(Querier, t.initQuerier)
mm.RegisterModule(StoreQueryable, t.initStoreQueryable)
mm.RegisterModule(StoreQueryable, t.initStoreQueryable, modules.UserInvisibleModule)
mm.RegisterModule(QueryFrontend, t.initQueryFrontend)
mm.RegisterModule(TableManager, t.initTableManager)
mm.RegisterModule(Ruler, t.initRuler)
Expand All @@ -536,7 +536,6 @@ func (t *Cortex) setupModuleManager() error {
mm.RegisterModule(StoreGateway, t.initStoreGateway)
mm.RegisterModule(Purger, t.initPurger)
mm.RegisterModule(All, nil)
mm.RegisterModule(StoreGateway, t.initStoreGateway)

// Add dependencies
deps := map[string][]string{
Expand Down
52 changes: 47 additions & 5 deletions pkg/util/modules/modules.go
Expand Up @@ -2,6 +2,7 @@ package modules

import (
"fmt"
"sort"

"github.com/pkg/errors"

Expand All @@ -15,6 +16,9 @@ type module struct {

// initFn for this module (can return nil)
initFn func() (services.Service, error)

// is this module user visible (i.e intended to be passed to `InitModuleServices`)
userVisible bool
}

// Manager is a component that initialises modules of the application
Expand All @@ -23,19 +27,29 @@ type Manager struct {
modules map[string]*module
}

// UserInvisibleModule is an option for `RegisterModule` that marks module not visible to user. Modules are user visible by default.
func UserInvisibleModule(m *module) {
m.userVisible = false
}

// NewManager creates a new Manager
func NewManager() *Manager {
return &Manager{
modules: make(map[string]*module),
}
}

// RegisterModule registers a new module with name and init function
// name must be unique to avoid overwriting modules
// if initFn is nil, the module will not initialise
func (m *Manager) RegisterModule(name string, initFn func() (services.Service, error)) {
// RegisterModule registers a new module with name, init function, and options. Name must
// be unique to avoid overwriting modules. If initFn is nil, the module will not initialise.
// Modules are user visible by default.
func (m *Manager) RegisterModule(name string, initFn func() (services.Service, error), options ...func(option *module)) {
m.modules[name] = &module{
initFn: initFn,
initFn: initFn,
userVisible: true,
}

for _, o := range options {
o(m.modules[name])
}
}

Expand All @@ -57,6 +71,7 @@ func (m *Manager) InitModuleServices(target string) (map[string]services.Service
if _, ok := m.modules[target]; !ok {
return nil, fmt.Errorf("unrecognised module name: %s", target)
}

servicesMap := map[string]services.Service{}

// initialize all of our dependencies first
Expand Down Expand Up @@ -89,6 +104,33 @@ func (m *Manager) InitModuleServices(target string) (map[string]services.Service
return servicesMap, nil
}

// UserVisibleModuleNames gets list of module names that are
// user visible. Returned list is sorted in increasing order.
func (m *Manager) UserVisibleModuleNames() []string {
var result []string
for key, val := range m.modules {
if val.userVisible {
result = append(result, key)
}
}

sort.Strings(result)

return result
}

// IsUserVisibleModule check if given module is public or not. Returns true
// if and only if the given module is registered and is public.
func (m *Manager) IsUserVisibleModule(mod string) bool {
val, ok := m.modules[mod]

if ok {
return val.userVisible
}

return false
}

// listDeps recursively gets a list of dependencies for a passed moduleName
func (m *Manager) listDeps(mod string) []string {
deps := m.modules[mod].deps
Expand Down
79 changes: 79 additions & 0 deletions pkg/util/modules/modules_test.go
Expand Up @@ -47,3 +47,82 @@ func TestDependencies(t *testing.T) {
assert.Nil(t, svcs)
assert.Error(t, err, fmt.Errorf("unrecognised module name: service_unknown"))
}

func TestRegisterModuleDefaultsToUserVisible(t *testing.T) {
sut := NewManager()
sut.RegisterModule("module1", mockInitFunc)

m := sut.modules["module1"]

assert.NotNil(t, mockInitFunc, m.initFn, "initFn not assigned")
assert.True(t, m.userVisible, "module should be user visible")
}

func TestFunctionalOptAtTheEndWins(t *testing.T) {
userVisibleMod := func(option *module) {
option.userVisible = true
}
sut := NewManager()
sut.RegisterModule("mod1", mockInitFunc, UserInvisibleModule, userVisibleMod, UserInvisibleModule)

m := sut.modules["mod1"]

assert.NotNil(t, mockInitFunc, m.initFn, "initFn not assigned")
assert.False(t, m.userVisible, "module should be internal")
}

func TestGetAllUserVisibleModulesNames(t *testing.T) {
sut := NewManager()
sut.RegisterModule("userVisible3", mockInitFunc)
sut.RegisterModule("userVisible2", mockInitFunc)
sut.RegisterModule("userVisible1", mockInitFunc)
sut.RegisterModule("internal1", mockInitFunc, UserInvisibleModule)
sut.RegisterModule("internal2", mockInitFunc, UserInvisibleModule)

pm := sut.UserVisibleModuleNames()

assert.Equal(t, []string{"userVisible1", "userVisible2", "userVisible3"}, pm, "module list contains wrong element and/or not sorted")
}

func TestGetAllUserVisibleModulesNamesHasNoDupWithDependency(t *testing.T) {
sut := NewManager()
sut.RegisterModule("userVisible1", mockInitFunc)
sut.RegisterModule("userVisible2", mockInitFunc)
sut.RegisterModule("userVisible3", mockInitFunc)

assert.NoError(t, sut.AddDependency("userVisible1", "userVisible2", "userVisible3"))

pm := sut.UserVisibleModuleNames()

// make sure we don't include any module twice because there is a dependency
assert.Equal(t, []string{"userVisible1", "userVisible2", "userVisible3"}, pm, "module list contains wrong elements and/or not sorted")
}

func TestGetEmptyListWhenThereIsNoUserVisibleModule(t *testing.T) {
sut := NewManager()
sut.RegisterModule("internal1", mockInitFunc, UserInvisibleModule)
sut.RegisterModule("internal2", mockInitFunc, UserInvisibleModule)
sut.RegisterModule("internal3", mockInitFunc, UserInvisibleModule)
sut.RegisterModule("internal4", mockInitFunc, UserInvisibleModule)

pm := sut.UserVisibleModuleNames()

assert.Len(t, pm, 0, "wrong result slice size")
}

func TestIsUserVisibleModule(t *testing.T) {
userVisibleModName := "userVisible"
internalModName := "internal"
sut := NewManager()
sut.RegisterModule(userVisibleModName, mockInitFunc)
sut.RegisterModule(internalModName, mockInitFunc, UserInvisibleModule)

var result = sut.IsUserVisibleModule(userVisibleModName)
assert.True(t, result, "module '%v' should be user visible", userVisibleModName)

result = sut.IsUserVisibleModule(internalModName)
assert.False(t, result, "module '%v' should be internal", internalModName)

result = sut.IsUserVisibleModule("ghost")
assert.False(t, result, "expects result be false when module does not exist")
}