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

feat: support configure refinery to use redis in cluster mode #1294

Merged
merged 7 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions config/cmdenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type CmdEnv struct {
PeerListenAddr string `long:"peer-listen-address" env:"REFINERY_PEER_LISTEN_ADDRESS" description:"Peer listen address for communication between Refinery instances"`
GRPCListenAddr string `long:"grpc-listen-address" env:"REFINERY_GRPC_LISTEN_ADDRESS" description:"gRPC listen address for OTLP traffic"`
RedisHost string `long:"redis-host" env:"REFINERY_REDIS_HOST" description:"Redis host address"`
RedisClusterHosts []string `long:"redis-cluster-hosts" env:"REFINERY_REDIS_CLUSTER_HOSTS" env-delim:"," description:"Redis cluster host addresses"`
RedisUsername string `long:"redis-username" env:"REFINERY_REDIS_USERNAME" description:"Redis username. Setting this value via a flag may expose credentials - it is recommended to use the env var or a configuration file."`
RedisPassword string `long:"redis-password" env:"REFINERY_REDIS_PASSWORD" description:"Redis password. Setting this value via a flag may expose credentials - it is recommended to use the env var or a configuration file."`
RedisAuthCode string `long:"redis-auth-code" env:"REFINERY_REDIS_AUTH_CODE" description:"Redis AUTH code. Setting this value via a flag may expose credentials - it is recommended to use the env var or a configuration file."`
Expand Down Expand Up @@ -78,6 +79,14 @@ func (c *CmdEnv) GetField(name string) reflect.Value {
return reflect.ValueOf(c).Elem().FieldByName(name)
}

func (c *CmdEnv) GetDelimiter(name string) string {
field, ok := reflect.TypeOf(c).Elem().FieldByName(name)
if !ok {
return ""
}
return field.Tag.Get("env-delim")
}

// ApplyTags uses reflection to apply the values from the CmdEnv struct to the
// given struct. Any field in the struct that wants to be set from the command
// line must have a `cmdenv` tag on it that names one or more fields in the
Expand All @@ -90,6 +99,7 @@ func (c *CmdEnv) ApplyTags(s reflect.Value) error {

type getFielder interface {
GetField(name string) reflect.Value
GetDelimiter(name string) string
}

// applyCmdEnvTags is a helper function that applies the values from the given
Expand Down Expand Up @@ -123,6 +133,30 @@ func applyCmdEnvTags(s reflect.Value, fielder getFielder) error {
return fmt.Errorf("programming error -- types don't match for field: %s (%v and %v)",
fieldType.Name, fieldType.Type, value.Type())
}

if value.Kind() == reflect.Slice {
delimiter := fielder.GetDelimiter(tag)
if delimiter == "" {
return fmt.Errorf("programming error -- missing delimiter for slice field: %s", fieldType.Name)
}

rawValue, ok := value.Index(0).Interface().(string)
if !ok {
return fmt.Errorf("programming error -- slice field must be a string: %s", fieldType.Name)
}

// split the value on the delimiter
values := strings.Split(rawValue, delimiter)
// create a new slice of the same type as the field
slice := reflect.MakeSlice(field.Type(), len(values), len(values))
// iterate over the values and set them
for i, v := range values {
slice.Index(i).SetString(v)
}
// set the field
field.Set(slice)
break
}
// now we can set it
field.Set(value)
// and we're done with this field
Expand Down
36 changes: 23 additions & 13 deletions config/cmdenv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,32 @@ import (
)

type TestFielder struct {
S string
S2 string
I int
F float64
S string
S2 string
I int
F float64
STRS []string `env-delim:","`
}

// implement getFielder
func (t *TestFielder) GetField(name string) reflect.Value {
return reflect.ValueOf(t).Elem().FieldByName(name)
}

func (t *TestFielder) GetDelimiter(name string) string {
field, ok := reflect.TypeOf(t).Elem().FieldByName(name)
if !ok {
return ""
}
return field.Tag.Get("env-delim")
}

type TestConfig struct {
St string `cmdenv:"S"`
It int `cmdenv:"I"`
Fl float64 `cmdenv:"F"`
No string
St string `cmdenv:"S"`
It int `cmdenv:"I"`
Fl float64 `cmdenv:"F"`
No string
Strs []string `cmdenv:"STRS"`
}

type FallbackConfig struct {
Expand All @@ -43,11 +53,11 @@ func TestApplyCmdEnvTags(t *testing.T) {
want any
wantErr bool
}{
{"normal", &TestFielder{"foo", "bar", 1, 2.3}, &TestConfig{}, &TestConfig{"foo", 1, 2.3, ""}, false},
{"bad", &TestFielder{"foo", "bar", 1, 2.3}, &BadTestConfig1{}, &BadTestConfig1{}, true},
{"type mismatch", &TestFielder{"foo", "bar", 1, 2.3}, &BadTestConfig2{17}, &BadTestConfig2{17}, true},
{"fallback1", &TestFielder{"foo", "bar", 1, 2.3}, &FallbackConfig{}, &FallbackConfig{"foo"}, false},
{"fallback2", &TestFielder{"", "bar", 1, 2.3}, &FallbackConfig{}, &FallbackConfig{"bar"}, false},
{"normal", &TestFielder{"foo", "bar", 1, 2.3, []string{"test,test1"}}, &TestConfig{}, &TestConfig{"foo", 1, 2.3, "", []string{"test", "test1"}}, false},
{"bad", &TestFielder{"foo", "bar", 1, 2.3, []string{}}, &BadTestConfig1{}, &BadTestConfig1{}, true},
{"type mismatch", &TestFielder{"foo", "bar", 1, 2.3, []string{}}, &BadTestConfig2{17}, &BadTestConfig2{17}, true},
{"fallback1", &TestFielder{"foo", "bar", 1, 2.3, []string{}}, &FallbackConfig{}, &FallbackConfig{"foo"}, false},
{"fallback2", &TestFielder{"", "bar", 1, 2.3, []string{}}, &FallbackConfig{}, &FallbackConfig{"bar"}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
30 changes: 1 addition & 29 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,35 +62,7 @@ type Config interface {

GetPeerManagementType() string

// GetRedisHost returns the address of a Redis instance to use for peer
// management.
GetRedisHost() string

// GetRedisUsername returns the username of a Redis instance to use for peer
// management.
GetRedisUsername() string

// GetRedisPassword returns the password of a Redis instance to use for peer
// management.
GetRedisPassword() string

// GetRedisAuthCode returns the AUTH string to use for connecting to a Redis
// instance to use for peer management
GetRedisAuthCode() string

// GetRedisPrefix returns the prefix string used in the keys for peer
// management.
GetRedisPrefix() string

// GetRedisDatabase returns the ID of the Redis database to use for peer management.
GetRedisDatabase() int

// GetUseTLS returns true when TLS must be enabled to dial the Redis instance to
// use for peer management.
GetUseTLS() bool

// UseTLSInsecure returns true when certificate checks are disabled
GetUseTLSInsecure() bool
GetRedisPeerManagement() RedisPeerManagementConfig

// GetHoneycombAPI returns the base URL (protocol, hostname, and port) of
// the upstream Honeycomb API server
Expand Down
34 changes: 28 additions & 6 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/honeycombio/refinery/internal/configwatcher"
"github.com/honeycombio/refinery/pubsub"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -90,7 +91,7 @@ func TestRedisHostEnvVar(t *testing.T) {
c, err := getConfig([]string{"--no-validate", "--config", "../config.yaml", "--rules_config", "../rules.yaml"})
assert.NoError(t, err)

if d := c.GetRedisHost(); d != host {
if d := c.GetRedisPeerManagement().Host; d != host {
t.Error("received", d, "expected", host)
}
}
Expand All @@ -103,7 +104,7 @@ func TestRedisUsernameEnvVar(t *testing.T) {
c, err := getConfig([]string{"--no-validate", "--config", "../config.yaml", "--rules_config", "../rules.yaml"})
assert.NoError(t, err)

if d := c.GetRedisUsername(); d != username {
if d := c.GetRedisPeerManagement().Username; d != username {
t.Error("received", d, "expected", username)
}
}
Expand All @@ -116,7 +117,7 @@ func TestRedisPasswordEnvVar(t *testing.T) {
c, err := getConfig([]string{"--no-validate", "--config", "../config.yaml", "--rules_config", "../rules.yaml"})
assert.NoError(t, err)

if d := c.GetRedisPassword(); d != password {
if d := c.GetRedisPeerManagement().Password; d != password {
t.Error("received", d, "expected", password)
}
}
Expand All @@ -129,7 +130,7 @@ func TestRedisAuthCodeEnvVar(t *testing.T) {
c, err := getConfig([]string{"--no-validate", "--config", "../config.yaml", "--rules_config", "../rules.yaml"})
assert.NoError(t, err)

if d := c.GetRedisAuthCode(); d != authCode {
if d := c.GetRedisPeerManagement().AuthCode; d != authCode {
t.Error("received", d, "expected", authCode)
}
}
Expand Down Expand Up @@ -403,11 +404,11 @@ func TestPeerManagementType(t *testing.T) {
t.Error("received", d, "expected", "redis")
}

if s := c.GetRedisPrefix(); s != "testPrefix" {
if s := c.GetRedisPeerManagement().Prefix; s != "testPrefix" {
t.Error("received", s, "expected", "testPrefix")
}

if db := c.GetRedisDatabase(); db != 9 {
if db := c.GetRedisPeerManagement().Database; db != 9 {
t.Error("received", db, "expected", 9)
}
}
Expand Down Expand Up @@ -454,6 +455,27 @@ func TestDryRun(t *testing.T) {
}
}

func TestRedisClusterHosts(t *testing.T) {
clusterHosts := []string{"localhost:7001", "localhost:7002"}
cm := makeYAML(
"General.ConfigurationVersion", 2,
"PeerManagement.Type", "redis",
"RedisPeerManagement.ClusterHosts", clusterHosts,
"RedisPeerManagement.Prefix", "test",
"RedisPeerManagement.Database", 9,
)
rm := makeYAML("ConfigVersion", 2)
config, rules := createTempConfigs(t, cm, rm)
defer os.Remove(rules)
defer os.Remove(config)
c, err := getConfig([]string{"--no-validate", "--config", config, "--rules_config", rules})
assert.NoError(t, err)

d := c.GetRedisPeerManagement().ClusterHosts
require.NotNil(t, d)
require.EqualValues(t, clusterHosts, d)
}

func TestMaxAlloc(t *testing.T) {
cm := makeYAML("General.ConfigurationVersion", 2, "Collection.CacheCapacity", 1000, "Collection.MaxAlloc", 17179869184)
rm := makeYAML("ConfigVersion", 2)
Expand Down
15 changes: 15 additions & 0 deletions config/file_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ type PeerManagementConfig struct {

type RedisPeerManagementConfig struct {
Host string `yaml:"Host" cmdenv:"RedisHost"`
ClusterHosts []string `yaml:"ClusterHosts" cmdenv:"RedisClusterHosts"`
Username string `yaml:"Username" cmdenv:"RedisUsername"`
Password string `yaml:"Password" cmdenv:"RedisPassword"`
AuthCode string `yaml:"AuthCode" cmdenv:"RedisAuthCode"`
Expand Down Expand Up @@ -646,13 +647,27 @@ func (f *fileConfig) GetPeers() []string {
return f.mainConfig.PeerManagement.Peers
}

func (f *fileConfig) GetRedisPeerManagement() RedisPeerManagementConfig {
f.mux.RLock()
defer f.mux.RUnlock()

return f.mainConfig.RedisPeerManagement
}

func (f *fileConfig) GetRedisHost() string {
f.mux.RLock()
defer f.mux.RUnlock()

return f.mainConfig.RedisPeerManagement.Host
}

func (f *fileConfig) GetRedisClusterHosts() []string {
f.mux.RLock()
defer f.mux.RUnlock()

return f.mainConfig.RedisPeerManagement.ClusterHosts
}

func (f *fileConfig) GetRedisUsername() string {
f.mux.RLock()
defer f.mux.RUnlock()
Expand Down
16 changes: 16 additions & 0 deletions config/metadata/configMeta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,21 @@ groups:
description: >
Must be in the form `host:port`.

- name: ClusterHosts
type: stringarray
valuetype: stringarray
example: "- localhost:6379"
firstversion: v2.8
validations:
- type: elementType
arg: hostport
reload: false
summary: is a list of host and port pairs for the instances in a Redis Cluster, used for managing peer cluster membership.
description: >
This configuration enables Refinery to connect to a Redis deployment setup in Cluster Mode.
Each entry in the list should follow the format `host:port`.
If `ClusterHosts` is specified, the `Host` setting will be ignored.

- name: Username
v1group: PeerManagement
v1name: RedisUsername
Expand Down Expand Up @@ -1253,6 +1268,7 @@ groups:
- name: ShutdownDelay
type: duration
valuetype: nondefault
firstversion: v2.8
default: 15s
reload: true
summary: controls the maximum time Refinery can use while draining traces at shutdown.
Expand Down
62 changes: 3 additions & 59 deletions config/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,7 @@ type MockConfig struct {
GetStdoutLoggerConfigVal StdoutLoggerConfig
GetLoggerLevelVal Level
GetPeersVal []string
GetRedisHostVal string
GetRedisUsernameVal string
GetRedisPasswordVal string
GetRedisAuthCodeVal string
GetRedisDatabaseVal int
GetRedisPrefixVal string
GetUseTLSVal bool
GetUseTLSInsecureVal bool
GetRedisPeerManagementVal RedisPeerManagementConfig
GetSamplerTypeName string
GetSamplerTypeVal interface{}
GetMetricsTypeVal string
Expand Down Expand Up @@ -209,60 +202,11 @@ func (m *MockConfig) GetPeers() []string {
return m.GetPeersVal
}

func (m *MockConfig) GetRedisHost() string {
func (m *MockConfig) GetRedisPeerManagement() RedisPeerManagementConfig {
m.Mux.RLock()
defer m.Mux.RUnlock()

return m.GetRedisHostVal
}

func (m *MockConfig) GetRedisUsername() string {
m.Mux.RLock()
defer m.Mux.RUnlock()

return m.GetRedisUsernameVal
}

func (m *MockConfig) GetRedisPassword() string {
m.Mux.RLock()
defer m.Mux.RUnlock()

return m.GetRedisPasswordVal
}

func (m *MockConfig) GetRedisAuthCode() string {
m.Mux.RLock()
defer m.Mux.RUnlock()

return m.GetRedisAuthCodeVal
}

func (m *MockConfig) GetRedisPrefix() string {
m.Mux.RLock()
defer m.Mux.RUnlock()

return m.GetRedisPrefixVal
}

func (m *MockConfig) GetRedisDatabase() int {
m.Mux.RLock()
defer m.Mux.RUnlock()

return m.GetRedisDatabaseVal
}

func (m *MockConfig) GetUseTLS() bool {
m.Mux.RLock()
defer m.Mux.RUnlock()

return m.GetUseTLSVal
}

func (m *MockConfig) GetUseTLSInsecure() bool {
m.Mux.RLock()
defer m.Mux.RUnlock()

return m.GetUseTLSInsecureVal
return m.GetRedisPeerManagementVal
}

func (m *MockConfig) GetGeneralConfig() GeneralConfig {
Expand Down
Loading
Loading