// Copyright (c) 2021 OceanBase // obagent is licensed under Mulan PSL v2. // You can use this software according to the terms and conditions of the Mulan PSL v2. // You may obtain a copy of Mulan PSL v2 at: // // http://license.coscl.org.cn/MulanPSL2 // // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. package config import ( "context" "io/ioutil" "os" "path/filepath" "reflect" "testing" "github.com/pkg/errors" log "github.com/sirupsen/logrus" . "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/assert" "github.com/oceanbase/obagent/lib/crypto" agentlog "github.com/oceanbase/obagent/log" ) var ( fooServer = &FooService{ Foo: &Foo{Foo: "foo_value"}, } ) const ( testFooModule = "foo" testFooModuleType ModuleType = "fooType" ) type Foo struct { Foo string `json:"foo"` Bar Bar `json:"bar"` } type Bar struct { Bar int `json:"bar"` Duration string `json:"duration"` } type FooService struct { Foo *Foo FooNoDefine string `json:"fooNoDefine"` } var ( fooKVYaml = `configVersion: "foo" configs: - key: foo.foo value: foo_value - key: foo.no.defined valueType: string value: foo-no-define - key: foo.bar.duration value: 10s - key: foo.bar.bar value: 3306` fooModuleYaml = `modules: - module: foo moduleType: fooType process: ocp_mgragent config: foo: ${foo.foo} fooNoDefine: ${foo.not.defined} bar: bar: ${foo.bar.bar} duration: ${foo.bar.duration}` ) func _init(t *testing.T) string { SetConfigPropertyMeta( &ConfigProperty{ Key: "foo.foo", DefaultValue: "foo", ValueType: ValueString, Encrypted: true, Fatal: false, Masked: false, NeedRestart: false, Description: "", Unit: "", Valid: nil, }) SetConfigPropertyMeta( &ConfigProperty{ Key: "foo.bar.bar", DefaultValue: 3306, ValueType: ValueInt64, Fatal: false, Masked: true, NeedRestart: true, Description: "", Unit: "", Valid: nil, }) SetConfigPropertyMeta( &ConfigProperty{ Key: "foo.bar.duration", DefaultValue: "100ms", ValueType: ValueString, Fatal: false, Masked: true, NeedRestart: true, Description: "", Unit: "", Valid: nil, }) croptoErr := InitCrypto("", crypto.PLAIN) assert.Nil(t, croptoErr) tempDir := os.TempDir() moduleConfigDir := filepath.Join(tempDir, "module_config") err := os.MkdirAll(moduleConfigDir, 0755) assert.Nil(t, err) err = ioutil.WriteFile(filepath.Join(moduleConfigDir, "foo.yaml"), []byte(fooModuleYaml), 0755) assert.Nil(t, err) configPropertiesDir := filepath.Join(tempDir, "config_properties") err = os.MkdirAll(configPropertiesDir, 0755) assert.Nil(t, err) err = ioutil.WriteFile(filepath.Join(configPropertiesDir, "foo.yaml"), []byte(fooKVYaml), 0755) assert.Nil(t, err) err = InitModuleConfigs(moduleConfigDir) assert.Nil(t, err) err = InitConfigProperties(configPropertiesDir) assert.Nil(t, err) agentlog.InitLogger(agentlog.LoggerConfig{ Output: os.Stdout, Level: "debug", }) registerFooCallback() return tempDir } func cleanup() { mainModuleConfig = nil mainConfigProperties = nil configPropertyMetas = map[string]*ConfigProperty{} callbacks = map[ModuleType]*ConfigCallback{} } func registerFooCallback() { RegisterConfigCallback(testFooModuleType, // create an instance of foo func() interface{} { return new(Foo) }, // init config func(ctx context.Context, conf interface{}) error { log.WithField("module config", conf).Info("init foo config") foo, ok := conf.(*Foo) if !ok { return errors.Errorf("config is not *Foo, but %s", reflect.TypeOf(conf)) } log.WithField("module", foo).Info("init foo config") fooServer.Foo = foo log.Infof("init module %s config sucessfully", testFooModule) return nil }, // notify updated config func(ctx context.Context, conf interface{}) error { log.WithField("module config", conf).Info("update foo config") foo, ok := conf.(*Foo) if !ok { return errors.Errorf("config is not *Foo, but %s", reflect.TypeOf(conf)) } log.WithField("module", foo).Info("update foo config") fooServer.Foo = foo log.Infof("update module %s config sucessfully", testFooModule) return nil }, ) } func TestModuleConfigCallback_Success(t *testing.T) { _init(t) defer cleanup() t.Run("foo callback", func(t *testing.T) { callback, ex := getModuleCallback(testFooModule) Convey("getModuleCallback", t, func() { So(ex, ShouldBeTrue) So(callback, ShouldNotBeNil) }) err := callback.InitConfigCallback(context.Background(), testFooModule) Convey("InitConfigCallback before UpdateConfigCallback", t, func() { So(err, ShouldBeNil) So(fooServer.Foo.Foo, ShouldEqual, "foo_value") So(fooServer.Foo.Bar.Bar, ShouldEqual, 3306) So(fooServer.Foo.Bar.Duration, ShouldEqual, "10s") }) }) } func TestGetFinalModuleConfig(t *testing.T) { _init(t) defer cleanup() Convey("GetFinalModuleConfig with exist module", t, func() { conf, err := GetFinalModuleConfig("foo") So(err, ShouldBeNil) foo, ok := conf.Config.(*Foo) So(ok, ShouldBeTrue) So(foo.Foo, ShouldEqual, "foo_value") So(foo.Bar.Bar, ShouldEqual, 3306) So(foo.Bar.Duration, ShouldEqual, "10s") }) Convey("GetFinalModuleConfig with not exist module", t, func() { _, err := GetFinalModuleConfig("module-not-exist") So(err, ShouldNotBeNil) }) Convey("GetFinalModuleConfig with not exist module callback", t, func() { delete(callbacks, "foo") _, err := GetFinalModuleConfig("foo") So(err, ShouldBeNil) }) } func TestInitModule(t *testing.T) { _init(t) defer cleanup() err := InitModuleTypeConfig(context.Background(), testFooModuleType) Convey("InitModuleTypeConfig", t, func() { So(err, ShouldBeNil) }) err = InitModuleConfig(context.Background(), testFooModule) Convey("InitModuleConfig", t, func() { So(err, ShouldBeNil) }) }