Spaces:
Running
Running
// _ _ | |
// __ _____ __ ___ ___ __ _| |_ ___ | |
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ | |
// \ V V / __/ (_| |\ V /| | (_| | || __/ | |
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| | |
// | |
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. | |
// | |
// CONTACT: [email protected] | |
// | |
package config | |
import ( | |
"errors" | |
"os" | |
"testing" | |
"github.com/stretchr/testify/assert" | |
"github.com/stretchr/testify/require" | |
"github.com/weaviate/weaviate/usecases/cluster" | |
) | |
const DefaultGoroutineFactor = 1.5 | |
func TestEnvironmentImportGoroutineFactor(t *testing.T) { | |
factors := []struct { | |
name string | |
goroutineFactor []string | |
expected float64 | |
expectedErr bool | |
}{ | |
{"Valid factor", []string{"1"}, 1, false}, | |
{"Low factor", []string{"0.5"}, 0.5, false}, | |
{"not given", []string{}, DefaultGoroutineFactor, false}, | |
{"High factor", []string{"5"}, 5, false}, | |
{"invalid factor", []string{"-1"}, -1, true}, | |
{"not parsable", []string{"I'm not a number"}, -1, true}, | |
} | |
for _, tt := range factors { | |
t.Run(tt.name, func(t *testing.T) { | |
if len(tt.goroutineFactor) == 1 { | |
t.Setenv("MAX_IMPORT_GOROUTINES_FACTOR", tt.goroutineFactor[0]) | |
} | |
conf := Config{} | |
err := FromEnv(&conf) | |
if tt.expectedErr { | |
require.NotNil(t, err) | |
} else { | |
require.Equal(t, tt.expected, conf.MaxImportGoroutinesFactor) | |
} | |
}) | |
} | |
} | |
func TestEnvironmentSetFlushAfter_BackwardCompatibility(t *testing.T) { | |
factors := []struct { | |
name string | |
flushAfter []string | |
expected int | |
expectedErr bool | |
}{ | |
{"Valid", []string{"1"}, 1, false}, | |
{"not given", []string{}, DefaultPersistenceFlushIdleMemtablesAfter, false}, | |
{"invalid factor", []string{"-1"}, -1, true}, | |
{"zero factor", []string{"0"}, -1, true}, | |
{"not parsable", []string{"I'm not a number"}, -1, true}, | |
} | |
for _, tt := range factors { | |
t.Run(tt.name, func(t *testing.T) { | |
if len(tt.flushAfter) == 1 { | |
t.Setenv("PERSISTENCE_FLUSH_IDLE_MEMTABLES_AFTER", tt.flushAfter[0]) | |
} | |
conf := Config{} | |
err := FromEnv(&conf) | |
if tt.expectedErr { | |
require.NotNil(t, err) | |
} else { | |
require.Equal(t, tt.expected, conf.Persistence.FlushIdleMemtablesAfter) | |
} | |
}) | |
} | |
} | |
func TestEnvironmentSetFlushAfter_NewName(t *testing.T) { | |
factors := []struct { | |
name string | |
flushAfter []string | |
expected int | |
expectedErr bool | |
}{ | |
{"Valid", []string{"1"}, 1, false}, | |
{"not given", []string{}, DefaultPersistenceFlushIdleMemtablesAfter, false}, | |
{"invalid factor", []string{"-1"}, -1, true}, | |
{"zero factor", []string{"0"}, -1, true}, | |
{"not parsable", []string{"I'm not a number"}, -1, true}, | |
} | |
for _, tt := range factors { | |
t.Run(tt.name, func(t *testing.T) { | |
if len(tt.flushAfter) == 1 { | |
t.Setenv("PERSISTENCE_MEMTABLES_FLUSH_IDLE_AFTER_SECONDS", tt.flushAfter[0]) | |
} | |
conf := Config{} | |
err := FromEnv(&conf) | |
if tt.expectedErr { | |
require.NotNil(t, err) | |
} else { | |
require.Equal(t, tt.expected, conf.Persistence.FlushIdleMemtablesAfter) | |
} | |
}) | |
} | |
} | |
func TestEnvironmentFlushConflictingValues(t *testing.T) { | |
// if both the old and new variable names are used the new variable name | |
// should be taken into consideration | |
os.Clearenv() | |
t.Setenv("PERSISTENCE_FLUSH_IDLE_MEMTABLES_AFTER", "16") | |
t.Setenv("PERSISTENCE_MEMTABLES_FLUSH_IDLE_AFTER_SECONDS", "17") | |
conf := Config{} | |
err := FromEnv(&conf) | |
require.Nil(t, err) | |
assert.Equal(t, 17, conf.Persistence.FlushIdleMemtablesAfter) | |
} | |
func TestEnvironmentMemtable_MaxSize(t *testing.T) { | |
factors := []struct { | |
name string | |
value []string | |
expected int | |
expectedErr bool | |
}{ | |
{"Valid", []string{"100"}, 100, false}, | |
{"not given", []string{}, DefaultPersistenceMemtablesMaxSize, false}, | |
{"invalid factor", []string{"-1"}, -1, true}, | |
{"zero factor", []string{"0"}, -1, true}, | |
{"not parsable", []string{"I'm not a number"}, -1, true}, | |
} | |
for _, tt := range factors { | |
t.Run(tt.name, func(t *testing.T) { | |
if len(tt.value) == 1 { | |
t.Setenv("PERSISTENCE_MEMTABLES_MAX_SIZE_MB", tt.value[0]) | |
} | |
conf := Config{} | |
err := FromEnv(&conf) | |
if tt.expectedErr { | |
require.NotNil(t, err) | |
} else { | |
require.Equal(t, tt.expected, conf.Persistence.MemtablesMaxSizeMB) | |
} | |
}) | |
} | |
} | |
func TestEnvironmentMemtable_MinDuration(t *testing.T) { | |
factors := []struct { | |
name string | |
value []string | |
expected int | |
expectedErr bool | |
}{ | |
{"Valid", []string{"100"}, 100, false}, | |
{"not given", []string{}, DefaultPersistenceMemtablesMinDuration, false}, | |
{"invalid factor", []string{"-1"}, -1, true}, | |
{"zero factor", []string{"0"}, -1, true}, | |
{"not parsable", []string{"I'm not a number"}, -1, true}, | |
} | |
for _, tt := range factors { | |
t.Run(tt.name, func(t *testing.T) { | |
if len(tt.value) == 1 { | |
t.Setenv("PERSISTENCE_MEMTABLES_MIN_ACTIVE_DURATION_SECONDS", tt.value[0]) | |
} | |
conf := Config{} | |
err := FromEnv(&conf) | |
if tt.expectedErr { | |
require.NotNil(t, err) | |
} else { | |
require.Equal(t, tt.expected, conf.Persistence.MemtablesMinActiveDurationSeconds) | |
} | |
}) | |
} | |
} | |
func TestEnvironmentMemtable_MaxDuration(t *testing.T) { | |
factors := []struct { | |
name string | |
value []string | |
expected int | |
expectedErr bool | |
}{ | |
{"Valid", []string{"100"}, 100, false}, | |
{"not given", []string{}, DefaultPersistenceMemtablesMaxDuration, false}, | |
{"invalid factor", []string{"-1"}, -1, true}, | |
{"zero factor", []string{"0"}, -1, true}, | |
{"not parsable", []string{"I'm not a number"}, -1, true}, | |
} | |
for _, tt := range factors { | |
t.Run(tt.name, func(t *testing.T) { | |
if len(tt.value) == 1 { | |
t.Setenv("PERSISTENCE_MEMTABLES_MAX_ACTIVE_DURATION_SECONDS", tt.value[0]) | |
} | |
conf := Config{} | |
err := FromEnv(&conf) | |
if tt.expectedErr { | |
require.NotNil(t, err) | |
} else { | |
require.Equal(t, tt.expected, conf.Persistence.MemtablesMaxActiveDurationSeconds) | |
} | |
}) | |
} | |
} | |
func TestEnvironmentParseClusterConfig(t *testing.T) { | |
tests := []struct { | |
name string | |
envVars map[string]string | |
expectedResult cluster.Config | |
expectedErr error | |
}{ | |
{ | |
name: "valid cluster config - both ports provided", | |
envVars: map[string]string{ | |
"CLUSTER_GOSSIP_BIND_PORT": "7100", | |
"CLUSTER_DATA_BIND_PORT": "7101", | |
}, | |
expectedResult: cluster.Config{ | |
GossipBindPort: 7100, | |
DataBindPort: 7101, | |
}, | |
}, | |
{ | |
name: "valid cluster config - no ports provided", | |
expectedResult: cluster.Config{ | |
GossipBindPort: DefaultGossipBindPort, | |
DataBindPort: DefaultGossipBindPort + 1, | |
}, | |
}, | |
{ | |
name: "valid cluster config - only gossip bind port provided", | |
envVars: map[string]string{ | |
"CLUSTER_GOSSIP_BIND_PORT": "7777", | |
}, | |
expectedResult: cluster.Config{ | |
GossipBindPort: 7777, | |
DataBindPort: 7778, | |
}, | |
}, | |
{ | |
name: "invalid cluster config - both ports provided", | |
envVars: map[string]string{ | |
"CLUSTER_GOSSIP_BIND_PORT": "7100", | |
"CLUSTER_DATA_BIND_PORT": "7111", | |
}, | |
expectedErr: errors.New("CLUSTER_DATA_BIND_PORT must be one port " + | |
"number greater than CLUSTER_GOSSIP_BIND_PORT"), | |
}, | |
{ | |
name: "invalid config - only data bind port provided", | |
envVars: map[string]string{ | |
"CLUSTER_DATA_BIND_PORT": "7101", | |
}, | |
expectedErr: errors.New("CLUSTER_DATA_BIND_PORT must be one port " + | |
"number greater than CLUSTER_GOSSIP_BIND_PORT"), | |
}, | |
{ | |
name: "schema sync disabled", | |
envVars: map[string]string{ | |
"CLUSTER_IGNORE_SCHEMA_SYNC": "true", | |
}, | |
expectedResult: cluster.Config{ | |
GossipBindPort: 7946, | |
DataBindPort: 7947, | |
IgnoreStartupSchemaSync: true, | |
}, | |
}, | |
} | |
for _, test := range tests { | |
t.Run(test.name, func(t *testing.T) { | |
for k, v := range test.envVars { | |
t.Setenv(k, v) | |
} | |
cfg, err := parseClusterConfig() | |
if test.expectedErr != nil { | |
assert.EqualError(t, err, test.expectedErr.Error(), | |
"expected err: %v, got: %v", test.expectedErr, err) | |
} else { | |
assert.Nil(t, err, "expected nil, got: %v", err) | |
assert.EqualValues(t, test.expectedResult, cfg) | |
} | |
}) | |
} | |
} | |
func TestEnvironmentSetDefaultVectorDistanceMetric(t *testing.T) { | |
t.Run("DefaultVectorDistanceMetricIsEmpty", func(t *testing.T) { | |
os.Clearenv() | |
conf := Config{} | |
FromEnv(&conf) | |
require.Equal(t, "", conf.DefaultVectorDistanceMetric) | |
}) | |
t.Run("NonEmptyDefaultVectorDistanceMetric", func(t *testing.T) { | |
os.Clearenv() | |
t.Setenv("DEFAULT_VECTOR_DISTANCE_METRIC", "l2-squared") | |
conf := Config{} | |
FromEnv(&conf) | |
require.Equal(t, "l2-squared", conf.DefaultVectorDistanceMetric) | |
}) | |
} | |
func TestEnvironmentMaxConcurrentGetRequests(t *testing.T) { | |
factors := []struct { | |
name string | |
value []string | |
expected int | |
expectedErr bool | |
}{ | |
{"Valid", []string{"100"}, 100, false}, | |
{"not given", []string{}, DefaultMaxConcurrentGetRequests, false}, | |
{"unlimited", []string{"-1"}, -1, false}, | |
{"not parsable", []string{"I'm not a number"}, -1, true}, | |
} | |
for _, tt := range factors { | |
t.Run(tt.name, func(t *testing.T) { | |
if len(tt.value) == 1 { | |
t.Setenv("MAXIMUM_CONCURRENT_GET_REQUESTS", tt.value[0]) | |
} | |
conf := Config{} | |
err := FromEnv(&conf) | |
if tt.expectedErr { | |
require.NotNil(t, err) | |
} else { | |
require.Equal(t, tt.expected, conf.MaximumConcurrentGetRequests) | |
} | |
}) | |
} | |
} | |
func TestEnvironmentCORS_Origin(t *testing.T) { | |
factors := []struct { | |
name string | |
value []string | |
expected string | |
expectedErr bool | |
}{ | |
{"Valid", []string{"http://foo.com"}, "http://foo.com", false}, | |
{"not given", []string{}, DefaultCORSAllowOrigin, false}, | |
} | |
for _, tt := range factors { | |
t.Run(tt.name, func(t *testing.T) { | |
os.Clearenv() | |
if len(tt.value) == 1 { | |
os.Setenv("CORS_ALLOW_ORIGIN", tt.value[0]) | |
} | |
conf := Config{} | |
err := FromEnv(&conf) | |
if tt.expectedErr { | |
require.NotNil(t, err) | |
} else { | |
require.Equal(t, tt.expected, conf.CORS.AllowOrigin) | |
} | |
}) | |
} | |
} | |
func TestEnvironmentGRPCPort(t *testing.T) { | |
factors := []struct { | |
name string | |
value []string | |
expected int | |
expectedErr bool | |
}{ | |
{"Valid", []string{"50052"}, 50052, false}, | |
{"not given", []string{}, DefaultGRPCPort, false}, | |
{"invalid factor", []string{"-1"}, -1, true}, | |
{"zero factor", []string{"0"}, -1, true}, | |
{"not parsable", []string{"I'm not a number"}, -1, true}, | |
} | |
for _, tt := range factors { | |
t.Run(tt.name, func(t *testing.T) { | |
if len(tt.value) == 1 { | |
t.Setenv("GRPC_PORT", tt.value[0]) | |
} | |
conf := Config{} | |
err := FromEnv(&conf) | |
if tt.expectedErr { | |
require.NotNil(t, err) | |
} else { | |
require.Equal(t, tt.expected, conf.GRPC.Port) | |
} | |
}) | |
} | |
} | |
func TestEnvironmentCORS_Methods(t *testing.T) { | |
factors := []struct { | |
name string | |
value []string | |
expected string | |
expectedErr bool | |
}{ | |
{"Valid", []string{"POST"}, "POST", false}, | |
{"not given", []string{}, DefaultCORSAllowMethods, false}, | |
} | |
for _, tt := range factors { | |
t.Run(tt.name, func(t *testing.T) { | |
os.Clearenv() | |
if len(tt.value) == 1 { | |
os.Setenv("CORS_ALLOW_METHODS", tt.value[0]) | |
} | |
conf := Config{} | |
err := FromEnv(&conf) | |
if tt.expectedErr { | |
require.NotNil(t, err) | |
} else { | |
require.Equal(t, tt.expected, conf.CORS.AllowMethods) | |
} | |
}) | |
} | |
} | |
func TestEnvironmentDisableGraphQL(t *testing.T) { | |
factors := []struct { | |
name string | |
value []string | |
expected bool | |
expectedErr bool | |
}{ | |
{"Valid: true", []string{"true"}, true, false}, | |
{"Valid: false", []string{"false"}, false, false}, | |
{"Valid: 1", []string{"1"}, true, false}, | |
{"Valid: 0", []string{"0"}, false, false}, | |
{"Valid: on", []string{"on"}, true, false}, | |
{"Valid: off", []string{"off"}, false, false}, | |
{"not given", []string{}, false, false}, | |
} | |
for _, tt := range factors { | |
t.Run(tt.name, func(t *testing.T) { | |
if len(tt.value) == 1 { | |
t.Setenv("DISABLE_GRAPHQL", tt.value[0]) | |
} | |
conf := Config{} | |
err := FromEnv(&conf) | |
if tt.expectedErr { | |
require.NotNil(t, err) | |
} else { | |
require.Equal(t, tt.expected, conf.DisableGraphQL) | |
} | |
}) | |
} | |
} | |
func TestEnvironmentCORS_Headers(t *testing.T) { | |
factors := []struct { | |
name string | |
value []string | |
expected string | |
expectedErr bool | |
}{ | |
{"Valid", []string{"Authorization"}, "Authorization", false}, | |
{"not given", []string{}, DefaultCORSAllowHeaders, false}, | |
} | |
for _, tt := range factors { | |
t.Run(tt.name, func(t *testing.T) { | |
os.Clearenv() | |
if len(tt.value) == 1 { | |
os.Setenv("CORS_ALLOW_HEADERS", tt.value[0]) | |
} | |
conf := Config{} | |
err := FromEnv(&conf) | |
if tt.expectedErr { | |
require.NotNil(t, err) | |
} else { | |
require.Equal(t, tt.expected, conf.CORS.AllowHeaders) | |
} | |
}) | |
} | |
} | |
func TestEnvironmentPrometheusGroupClasses_OldName(t *testing.T) { | |
factors := []struct { | |
name string | |
value []string | |
expected bool | |
expectedErr bool | |
}{ | |
{"Valid: true", []string{"true"}, true, false}, | |
{"Valid: false", []string{"false"}, false, false}, | |
{"Valid: 1", []string{"1"}, true, false}, | |
{"Valid: 0", []string{"0"}, false, false}, | |
{"Valid: on", []string{"on"}, true, false}, | |
{"Valid: off", []string{"off"}, false, false}, | |
{"not given", []string{}, false, false}, | |
} | |
for _, tt := range factors { | |
t.Run(tt.name, func(t *testing.T) { | |
t.Setenv("PROMETHEUS_MONITORING_ENABLED", "true") | |
if len(tt.value) == 1 { | |
t.Setenv("PROMETHEUS_MONITORING_GROUP_CLASSES", tt.value[0]) | |
} | |
conf := Config{} | |
err := FromEnv(&conf) | |
if tt.expectedErr { | |
require.NotNil(t, err) | |
} else { | |
require.Equal(t, tt.expected, conf.Monitoring.Group) | |
} | |
}) | |
} | |
} | |
func TestEnvironmentPrometheusGroupClasses_NewName(t *testing.T) { | |
factors := []struct { | |
name string | |
value []string | |
expected bool | |
expectedErr bool | |
}{ | |
{"Valid: true", []string{"true"}, true, false}, | |
{"Valid: false", []string{"false"}, false, false}, | |
{"Valid: 1", []string{"1"}, true, false}, | |
{"Valid: 0", []string{"0"}, false, false}, | |
{"Valid: on", []string{"on"}, true, false}, | |
{"Valid: off", []string{"off"}, false, false}, | |
{"not given", []string{}, false, false}, | |
} | |
for _, tt := range factors { | |
t.Run(tt.name, func(t *testing.T) { | |
t.Setenv("PROMETHEUS_MONITORING_ENABLED", "true") | |
if len(tt.value) == 1 { | |
t.Setenv("PROMETHEUS_MONITORING_GROUP", tt.value[0]) | |
} | |
conf := Config{} | |
err := FromEnv(&conf) | |
if tt.expectedErr { | |
require.NotNil(t, err) | |
} else { | |
require.Equal(t, tt.expected, conf.Monitoring.Group) | |
} | |
}) | |
} | |
} | |
func TestEnvironmentMinimumReplicationFactor(t *testing.T) { | |
factors := []struct { | |
name string | |
value []string | |
expected int | |
expectedErr bool | |
}{ | |
{"Valid", []string{"3"}, 3, false}, | |
{"not given", []string{}, DefaultMinimumReplicationFactor, false}, | |
{"invalid factor", []string{"-1"}, -1, true}, | |
{"zero factor", []string{"0"}, -1, true}, | |
{"not parsable", []string{"I'm not a number"}, -1, true}, | |
} | |
for _, tt := range factors { | |
t.Run(tt.name, func(t *testing.T) { | |
if len(tt.value) == 1 { | |
t.Setenv("REPLICATION_MINIMUM_FACTOR", tt.value[0]) | |
} | |
conf := Config{} | |
err := FromEnv(&conf) | |
if tt.expectedErr { | |
require.NotNil(t, err) | |
} else { | |
require.Equal(t, tt.expected, conf.Replication.MinimumFactor) | |
} | |
}) | |
} | |
} | |