diff --git a/common/config/environment.go b/common/config/environment.go index ce9c333635fc533e39bb32ae3fd00e0ddbb35f70..d92c000a3f43f0498689e3b2c14afe49a80b0b6a 100644 --- a/common/config/environment.go +++ b/common/config/environment.go @@ -1,8 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package config -import "sync" +import ( + "container/list" + "sync" +) +// There is dubbo.properties file and application level config center configuration which higner than normal config center in java. So in java the +// configuration sequence will be config center > application level config center > dubbo.properties > spring bean configuration. +// But in go, neither the dubbo.properties file or application level config center configuration will not support for the time being. +// We just have config center configuration which can override configuration in consumer.yaml & provider.yaml. +// But for add these features in future ,I finish the environment struct following Environment class in java. type Environment struct { + configCenterFirst bool + externalConfigs sync.Map + externalConfigMap sync.Map } var instance *Environment @@ -11,7 +39,50 @@ var once sync.Once func GetEnvInstance() *Environment { once.Do(func() { - instance = &Environment{} + instance = &Environment{configCenterFirst: true} }) return instance } + +//func (env *Environment) SetConfigCenterFirst() { +// env.configCenterFirst = true +//} + +//func (env *Environment) ConfigCenterFirst() bool { +// return env.configCenterFirst +//} + +func (env *Environment) UpdateExternalConfigMap(externalMap map[string]string) { + for k, v := range externalMap { + env.externalConfigMap.Store(k, v) + } +} + +func (env *Environment) Configuration(prefix string) *list.List { + list := list.New() + memConf := newInmemoryConfiguration(prefix) + memConf.setProperties(env.externalConfigMap) + list.PushBack(memConf) + return list +} + +type InmemoryConfiguration struct { + prefix string + store sync.Map +} + +func newInmemoryConfiguration(prefix string) *InmemoryConfiguration { + return &InmemoryConfiguration{prefix: prefix} +} +func (conf *InmemoryConfiguration) setProperties(p sync.Map) { + conf.store = p +} + +func (conf *InmemoryConfiguration) GetProperty(key string) string { + v, ok := conf.store.Load(conf.prefix + "." + key) + if ok { + return v.(string) + } else { + return "" + } +} diff --git a/common/constant/default.go b/common/constant/default.go index 85f61f30f115493d3d520f2a68f36921735055d7..75869d552bedec37f0a10cceb23445c5f21f4e04 100644 --- a/common/constant/default.go +++ b/common/constant/default.go @@ -17,6 +17,7 @@ package constant +const DUBBO = "dubbo" const ( DEFAULT_WEIGHT = 100 // DEFAULT_WARMUP = 10 * 60 // in java here is 10*60*1000 because of System.currentTimeMillis() is measured in milliseconds & in go time.Unix() is second diff --git a/config/base_config.go b/config/base_config.go index edecb85b058382feb3aa8a3397a455790c5bfc54..dc7a2f19a765a1f71ac172dfbe343bf423cb9161 100644 --- a/config/base_config.go +++ b/config/base_config.go @@ -18,29 +18,37 @@ package config import ( "context" + "github.com/apache/dubbo-go/common/constant" + "reflect" + "strconv" + "strings" ) import ( - "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/logger" perrors "github.com/pkg/errors" ) import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/config" "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/config_center" ) type baseConfig struct { - // application - ApplicationConfig ApplicationConfig `yaml:"application_config" json:"application_config,omitempty"` ConfigCenterConfig ConfigCenterConfig configCenterUrl *common.URL + prefix string + fatherConfig interface{} } -func (c *baseConfig) startConfigCenter(ctx context.Context) { +func (c *baseConfig) startConfigCenter(ctx context.Context) error { var err error *c.configCenterUrl, err = common.NewURL(ctx, c.ConfigCenterConfig.Address) - c.prepareEnvironment() - + if c.prepareEnvironment() != nil { + return perrors.WithMessagef(err, "start config center error!") + } + c.fresh() + return err } func (c *baseConfig) prepareEnvironment() error { @@ -48,12 +56,92 @@ func (c *baseConfig) prepareEnvironment() error { factory := extension.GetConfigCenterFactory(c.ConfigCenterConfig.Protocol) dynamicConfig, err := factory.GetDynamicConfiguration(c.configCenterUrl) if err != nil { - logger.Errorf("get dynamic configuration error , error message is %v", err) - return err + logger.Errorf("Get dynamic configuration error , error message is %v", err) + return perrors.WithStack(err) } content, err := dynamicConfig.GetConfig(c.ConfigCenterConfig.ConfigFile, config_center.WithGroup(c.ConfigCenterConfig.Group)) if err != nil { logger.Errorf("Get config content in dynamic configuration error , error message is %v", err) - return err + return perrors.WithStack(err) + } + mapContent, err := dynamicConfig.Parser().Parse(content) + if err != nil { + return perrors.WithStack(err) } + config.GetEnvInstance().UpdateExternalConfigMap(mapContent) + return nil +} + +func (c *baseConfig) fresh() { + configList := config.GetEnvInstance().Configuration(c.Prefix()) + config := configList.Front().Value.(*config.InmemoryConfiguration) + val := reflect.Indirect(reflect.ValueOf(c.fatherConfig)) + for i := 0; i < val.NumField(); i++ { + if key := val.Type().Field(i).Tag.Get("property"); key != "-" && key != "" { + f := val.Field(i) + if f.IsValid() { + value := config.GetProperty(key) + setValue := func(f reflect.Value) { + if f.Kind() == reflect.Int { + x, err := strconv.Atoi(value) + if err != nil { + logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}", + val.Type().Name(), val.Type().Field(i).Name, err) + } else { + if !f.OverflowInt(int64(x)) { + f.SetInt(int64(x)) + } else { + logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}", + val.Type().Name(), val.Type().Field(i).Name, perrors.Errorf("the int64 value {%v} from config center is overflow", int64(x))) + } + } + + } + if f.Kind() == reflect.String { + f.SetString(value) + } + if f.Kind() == reflect.Bool { + x, err := strconv.ParseBool(value) + if err != nil { + logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}", + val.Type().Name(), val.Type().Field(i).Name, err) + } + f.SetBool(x) + } + if f.Kind() == reflect.Float64 { + x, err := strconv.ParseFloat(value, 64) + if err != nil { + logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}", + val.Type().Name(), val.Type().Field(i).Name, err) + } else { + if !f.OverflowFloat(x) { + f.SetFloat(x) + } else { + logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}", + val.Type().Name(), val.Type().Field(i).Name, perrors.Errorf("the float64 value {%v} from config center is overflow", x)) + } + } + } + } + setValue(f) + if f.Kind() == reflect.Ptr { + setValue(f.Elem()) + } + } + } + } +} + +func (c *baseConfig) SetPrefix(prefix string) { + c.prefix = prefix +} +func (c *baseConfig) Prefix() string { + if c.prefix == "" { + return constant.DUBBO + "." + strings.ToLower(strings.Replace(reflect.Indirect(reflect.ValueOf(c.fatherConfig)).Type().Name(), "Config", "", -1)) + } else { + return c.prefix + } +} +func (c *baseConfig) SetFatherConfig(fatherConfig interface{}) { + c.fatherConfig = fatherConfig } diff --git a/config/base_config_test.go b/config/base_config_test.go new file mode 100644 index 0000000000000000000000000000000000000000..efdcb2b297217fd89017bebc0ff673a1d059974e --- /dev/null +++ b/config/base_config_test.go @@ -0,0 +1,60 @@ +package config + +import ( + "github.com/apache/dubbo-go/common/config" + "github.com/stretchr/testify/assert" + "testing" +) + +func Test_refresh(t *testing.T) { + //Id string `required:"true" yaml:"id" json:"id,omitempty"` + //Type string `required:"true" yaml:"type" json:"type,omitempty"` + //TimeoutStr string `yaml:"timeout" default:"5s" json:"timeout,omitempty"` // unit: second + //Group string `yaml:"group" json:"group,omitempty"` + ////for registry + //Address string `yaml:"address" json:"address,omitempty"` + //Username string `yaml:"username" json:"address,omitempty"` + //Password string `yaml:"password" json:"address,omitempty"` + + c := &baseConfig{} + mockMap := map[string]string{} + mockMap["dubbo.registry.type"] = "zookeeper" + mockMap["dubbo.registry.timeout"] = "3s" + mockMap["dubbo.registry.group"] = "hangzhou" + mockMap["dubbo.registry.address"] = "zookeeper://172.0.0.1:2181" + mockMap["dubbo.registry.username"] = "admin" + mockMap["dubbo.registry.password"] = "admin" + config.GetEnvInstance().UpdateExternalConfigMap(mockMap) + father := &RegistryConfig{Type: "1111"} + c.SetFatherConfig(father) + c.fresh() + assert.Equal(t, "zookeeper", father.Type) + assert.Equal(t, "zookeeper", father.Type) +} + +func Test_refreshWithPrefix(t *testing.T) { + //Id string `required:"true" yaml:"id" json:"id,omitempty"` + //Type string `required:"true" yaml:"type" json:"type,omitempty"` + //TimeoutStr string `yaml:"timeout" default:"5s" json:"timeout,omitempty"` // unit: second + //Group string `yaml:"group" json:"group,omitempty"` + ////for registry + //Address string `yaml:"address" json:"address,omitempty"` + //Username string `yaml:"username" json:"address,omitempty"` + //Password string `yaml:"password" json:"address,omitempty"` + + c := &baseConfig{} + mockMap := map[string]string{} + mockMap["dubbo.customRegistry.type"] = "zookeeper" + mockMap["dubbo.customRegistry.timeout"] = "3s" + mockMap["dubbo.customRegistry.group"] = "hangzhou" + mockMap["dubbo.customRegistry.address"] = "zookeeper://172.0.0.1:2181" + mockMap["dubbo.customRegistry.username"] = "admin" + mockMap["dubbo.customRegistry.password"] = "admin" + config.GetEnvInstance().UpdateExternalConfigMap(mockMap) + father := &RegistryConfig{Type: "1111"} + c.SetPrefix("dubbo.customRegistry") + c.SetFatherConfig(father) + c.fresh() + assert.Equal(t, "zookeeper", father.Type) + assert.Equal(t, "zookeeper", father.Type) +} diff --git a/config/config_loader.go b/config/config_loader.go index af586af943607e70fad5d3f942efc1cb7105e010..7ee88024f445e411b1dc8451cd4989853a0c4c57 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -125,7 +125,8 @@ func providerInit(confProFile string) error { type ConsumerConfig struct { baseConfig Filter string `yaml:"filter" json:"filter,omitempty"` - + // application + ApplicationConfig ApplicationConfig `yaml:"application_config" json:"application_config,omitempty"` // client Connect_Timeout string `default:"100ms" yaml:"connect_timeout" json:"connect_timeout,omitempty"` ConnectTimeout time.Duration diff --git a/config/registry_config.go b/config/registry_config.go index 6dcf5bde757057df1eeab39c36cf54b142af1f32..a4bc275518709311d52f8a65ccaad931766fc037 100644 --- a/config/registry_config.go +++ b/config/registry_config.go @@ -31,13 +31,13 @@ import ( type RegistryConfig struct { Id string `required:"true" yaml:"id" json:"id,omitempty"` - Type string `required:"true" yaml:"type" json:"type,omitempty"` - TimeoutStr string `yaml:"timeout" default:"5s" json:"timeout,omitempty"` // unit: second - Group string `yaml:"group" json:"group,omitempty"` + Type string `required:"true" yaml:"type" json:"type,omitempty" property:"type"` + TimeoutStr string `yaml:"timeout" default:"5s" json:"timeout,omitempty" property:"timeout"` // unit: second + Group string `yaml:"group" json:"group,omitempty" property:"group"` //for registry - Address string `yaml:"address" json:"address,omitempty"` - Username string `yaml:"username" json:"address,omitempty"` - Password string `yaml:"password" json:"address,omitempty"` + Address string `yaml:"address" json:"address,omitempty" property:"address"` + Username string `yaml:"username" json:"address,omitempty" property:"username"` + Password string `yaml:"password" json:"address,omitempty" property:"password"` } func loadRegistries(registriesIds []ConfigRegistry, registries []RegistryConfig, roleType common.RoleType) []*common.URL { diff --git a/config_center/configuration_parser.go b/config_center/configuration_parser.go new file mode 100644 index 0000000000000000000000000000000000000000..8c5498512c23032eff779af755176bbbbf24324e --- /dev/null +++ b/config_center/configuration_parser.go @@ -0,0 +1,25 @@ +package config_center + +import ( + "github.com/magiconair/properties" +) +import ( + "github.com/apache/dubbo-go/common/logger" +) + +type ConfigurationParser interface { + Parse(string) (map[string]string, error) +} + +//for support properties file in config center +type DefaultConfigurationParser struct { +} + +func (parser *DefaultConfigurationParser) Parse(content string) (map[string]string, error) { + properties, err := properties.LoadString(content) + if err != nil { + logger.Errorf("Parse the content {%v} in DefaultConfigurationParser error ,error message is {%v}", content, err) + return nil, err + } + return properties.Map(), nil +} diff --git a/config_center/configuration_parser_test.go b/config_center/configuration_parser_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b97bc7e8d5b9e94c1c7e18f6bcc99f7f578f35b3 --- /dev/null +++ b/config_center/configuration_parser_test.go @@ -0,0 +1,14 @@ +package config_center + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestDefaultConfigurationParser_Parser(t *testing.T) { + parser := &DefaultConfigurationParser{} + m, err := parser.Parse("dubbo.registry.address=172.0.0.1\ndubbo.registry.name=test") + assert.NoError(t, err) + assert.Equal(t, 2, len(m)) + assert.Equal(t, "172.0.0.1", m["dubbo.registry.address"]) +} diff --git a/config_center/dynamic_configuration.go b/config_center/dynamic_configuration.go index e497ce728441aef4ea90eddcf7d335d92e6f0f9e..3b829507b1cfbcb687b194ac971c9b827c1c3e8b 100644 --- a/config_center/dynamic_configuration.go +++ b/config_center/dynamic_configuration.go @@ -31,6 +31,8 @@ const DEFAULT_GROUP = "dubbo" const DEFAULT_CONFIG_TIMEOUT = "10s" type DynamicConfiguration interface { + Parser() ConfigurationParser + SetParser(ConfigurationParser) AddListener(string, remoting.ConfigurationListener, ...Option) RemoveListener(string, remoting.ConfigurationListener, ...Option) GetConfig(string, ...Option) (string, error) diff --git a/config_center/zookeeper/factory.go b/config_center/zookeeper/factory.go index 3862db55bd001d64e66c7352a011ac0c32deefae..29eb3a34662c18a320434990e8cf00384e75a74d 100644 --- a/config_center/zookeeper/factory.go +++ b/config_center/zookeeper/factory.go @@ -40,6 +40,7 @@ func (f *zookeeperDynamicConfigurationFactory) GetDynamicConfiguration(url *comm var err error once.Do(func() { dynamicConfiguration, err = newZookeeperDynamicConfiguration(url) + dynamicConfiguration.SetParser(&config_center.DefaultConfigurationParser{}) }) return dynamicConfiguration, err diff --git a/config_center/zookeeper/impl.go b/config_center/zookeeper/impl.go index dd46a7606fa17ec540da83c9cb778c58add45b9e..a45d3f6b090e0ae2ee41bfdd9670392e27dd1ca7 100644 --- a/config_center/zookeeper/impl.go +++ b/config_center/zookeeper/impl.go @@ -46,6 +46,7 @@ type zookeeperDynamicConfiguration struct { listenerLock sync.Mutex listener *zookeeper.ZkEventListener cacheListener *CacheListener + parser config_center.ConfigurationParser } func newZookeeperDynamicConfiguration(url *common.URL) (*zookeeperDynamicConfiguration, error) { @@ -108,6 +109,13 @@ func (c *zookeeperDynamicConfiguration) GetConfigs(key string, opts ...config_ce return c.GetConfig(key, opts...) } +func (c *zookeeperDynamicConfiguration) Parser() config_center.ConfigurationParser { + return c.parser +} +func (c *zookeeperDynamicConfiguration) SetParser(p config_center.ConfigurationParser) { + c.parser = p +} + func (r *zookeeperDynamicConfiguration) ZkClient() *zookeeper.ZookeeperClient { return r.client } diff --git a/go.mod b/go.mod index 4c2276429ae315a44c282a8af9fb7e6d7ee1be8e..d00f8e9f35500b98b2f02f4eb993f23fe162e900 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/apache/dubbo-go require ( github.com/dubbogo/getty v1.0.7 github.com/dubbogo/hessian2 v1.0.1 + github.com/magiconair/properties v1.8.1 github.com/pkg/errors v0.8.1 github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec github.com/stretchr/testify v1.3.0 diff --git a/go.sum b/go.sum index 973f80fc375fee47d51b6680fbebc5a3566dbe88..f00aec7e0823e2a0eb7b771f913565c73d4a3d53 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=