mirror of
https://github.com/go-kratos/kratos.git
synced 2025-04-15 11:56:41 +02:00
Merge pull request #340 from Colstuwjx/apollo-config-center
feat: add apollo config client support for paladin.
This commit is contained in:
commit
e5c9307c0a
1
go.mod
1
go.mod
@ -30,6 +30,7 @@ require (
|
|||||||
github.com/montanaflynn/stats v0.5.0
|
github.com/montanaflynn/stats v0.5.0
|
||||||
github.com/openzipkin/zipkin-go v0.2.1
|
github.com/openzipkin/zipkin-go v0.2.1
|
||||||
github.com/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3
|
github.com/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3
|
||||||
|
github.com/philchia/agollo v2.3.1+incompatible
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.8.1
|
||||||
github.com/prometheus/client_golang v1.1.0
|
github.com/prometheus/client_golang v1.1.0
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -171,6 +171,8 @@ github.com/openzipkin/zipkin-go v0.2.1 h1:noL5/5Uf1HpVl3wNsfkZhIKbSWCVi5jgqkONNx
|
|||||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||||
github.com/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3 h1:zjmNboC3QFuMdJSaZJ7Qvi3HUxWXPdj7wb3rc4jH5HI=
|
github.com/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3 h1:zjmNboC3QFuMdJSaZJ7Qvi3HUxWXPdj7wb3rc4jH5HI=
|
||||||
github.com/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3/go.mod h1:pLR8n2aimFxvvDJ6n8JuQWthMGezCYMjuhlaTjPTZf0=
|
github.com/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3/go.mod h1:pLR8n2aimFxvvDJ6n8JuQWthMGezCYMjuhlaTjPTZf0=
|
||||||
|
github.com/philchia/agollo v2.3.1+incompatible h1:C4zDDuOcP1Qynikz2rSJQSMjwexv4GfDpwBHJRinhPc=
|
||||||
|
github.com/philchia/agollo v2.3.1+incompatible/go.mod h1:EXNdWdQkS+QBi0nb/Xm+sBBuQ1PM7/NIPr1JDzOlt8A=
|
||||||
github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
@ -2,17 +2,18 @@
|
|||||||
|
|
||||||
##### 项目简介
|
##### 项目简介
|
||||||
|
|
||||||
paladin 是一个config SDK客户端,包括了file、mock几个抽象功能,方便使用本地文件或者sven配置中心,并且集成了对象自动reload功能。
|
paladin 是一个config SDK客户端,包括了file、mock几个抽象功能,方便使用本地文件或者sven\apollo配置中心,并且集成了对象自动reload功能。
|
||||||
|
|
||||||
|
|
||||||
local files:
|
local files:
|
||||||
```
|
```
|
||||||
demo -conf=/data/conf/app/msm-servie.toml
|
demo -conf=/data/conf/app/msm-servie.toml
|
||||||
// or dir
|
// or dir
|
||||||
demo -conf=/data/conf/app/
|
demo -conf=/data/conf/app/
|
||||||
|
|
||||||
```
|
```
|
||||||
example:
|
|
||||||
|
*注:使用远程配置中心的用户在执行应用,如这里的`demo`时务必**不要**带上`-conf`参数,具体见下文远程配置中心的例子*
|
||||||
|
|
||||||
|
local file example:
|
||||||
```
|
```
|
||||||
type exampleConf struct {
|
type exampleConf struct {
|
||||||
Bool bool
|
Bool bool
|
||||||
@ -65,6 +66,71 @@ func ExampleClient() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
remote config center example:
|
||||||
|
```
|
||||||
|
type exampleConf struct {
|
||||||
|
Bool bool
|
||||||
|
Int int64
|
||||||
|
Float float64
|
||||||
|
String string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *exampleConf) Set(text string) error {
|
||||||
|
var ec exampleConf
|
||||||
|
if err := yaml.Unmarshal([]byte(text), &ec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*e = ec
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleApolloClient() {
|
||||||
|
/*
|
||||||
|
pass flags or set envs that apollo needs, for example:
|
||||||
|
|
||||||
|
```
|
||||||
|
export APOLLO_APP_ID=SampleApp
|
||||||
|
export APOLLO_CLUSTER=default
|
||||||
|
export APOLLO_CACHE_DIR=/tmp
|
||||||
|
export APOLLO_META_ADDR=localhost:8080
|
||||||
|
export APOLLO_NAMESPACES=example.yml
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
|
||||||
|
if err := paladin.Init(apollo.PaladinDriverApollo); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
ec exampleConf
|
||||||
|
eo exampleConf
|
||||||
|
m paladin.Map
|
||||||
|
strs []string
|
||||||
|
)
|
||||||
|
// config unmarshal
|
||||||
|
if err := paladin.Get("example.yml").UnmarshalYAML(&ec); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// config setter
|
||||||
|
if err := paladin.Watch("example.yml", &ec); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// paladin map
|
||||||
|
if err := paladin.Watch("example.yml", &m); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
s, err := m.Value("key").String()
|
||||||
|
b, err := m.Value("key").Bool()
|
||||||
|
i, err := m.Value("key").Int64()
|
||||||
|
f, err := m.Value("key").Float64()
|
||||||
|
// value slice
|
||||||
|
err = m.Value("strings").Slice(&strs)
|
||||||
|
// watch key
|
||||||
|
for event := range paladin.WatchEvent(context.TODO(), "key") {
|
||||||
|
fmt.Println(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
##### 编译环境
|
##### 编译环境
|
||||||
|
|
||||||
- **请只用 Golang v1.12.x 以上版本编译执行**
|
- **请只用 Golang v1.12.x 以上版本编译执行**
|
||||||
|
273
pkg/conf/paladin/apollo/apollo.go
Normal file
273
pkg/conf/paladin/apollo/apollo.go
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
package apollo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/philchia/agollo"
|
||||||
|
|
||||||
|
"github.com/bilibili/kratos/pkg/conf/paladin"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ paladin.Client = &apollo{}
|
||||||
|
defaultValue = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
type apolloWatcher struct {
|
||||||
|
keys []string // in apollo, they're called namespaces
|
||||||
|
C chan paladin.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
func newApolloWatcher(keys []string) *apolloWatcher {
|
||||||
|
return &apolloWatcher{keys: keys, C: make(chan paladin.Event, 5)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aw *apolloWatcher) HasKey(key string) bool {
|
||||||
|
if len(aw.keys) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, k := range aw.keys {
|
||||||
|
if k == key {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aw *apolloWatcher) Handle(event paladin.Event) {
|
||||||
|
select {
|
||||||
|
case aw.C <- event:
|
||||||
|
default:
|
||||||
|
log.Printf("paladin: event channel full discard ns %s update event", event.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// apollo is apollo config client.
|
||||||
|
type apollo struct {
|
||||||
|
client *agollo.Client
|
||||||
|
values *paladin.Map
|
||||||
|
wmu sync.RWMutex
|
||||||
|
watchers map[*apolloWatcher]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is apollo config client config.
|
||||||
|
type Config struct {
|
||||||
|
AppID string `json:"app_id"`
|
||||||
|
Cluster string `json:"cluster"`
|
||||||
|
CacheDir string `json:"cache_dir"`
|
||||||
|
MetaAddr string `json:"meta_addr"`
|
||||||
|
Namespaces []string `json:"namespaces"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type apolloDriver struct{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
confAppID, confCluster, confCacheDir, confMetaAddr, confNamespaces string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
addApolloFlags()
|
||||||
|
paladin.Register(PaladinDriverApollo, &apolloDriver{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func addApolloFlags() {
|
||||||
|
flag.StringVar(&confAppID, "apollo.appid", "", "apollo app id")
|
||||||
|
flag.StringVar(&confCluster, "apollo.cluster", "", "apollo cluster")
|
||||||
|
flag.StringVar(&confCacheDir, "apollo.cachedir", "/tmp", "apollo cache dir")
|
||||||
|
flag.StringVar(&confMetaAddr, "apollo.metaaddr", "", "apollo meta server addr, e.g. localhost:8080")
|
||||||
|
flag.StringVar(&confNamespaces, "apollo.namespaces", "", "subscribed apollo namespaces, comma separated, e.g. app.yml,mysql.yml")
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildConfigForApollo() (c *Config, err error) {
|
||||||
|
if appidFromEnv := os.Getenv("APOLLO_APP_ID"); appidFromEnv != "" {
|
||||||
|
confAppID = appidFromEnv
|
||||||
|
}
|
||||||
|
if confAppID == "" {
|
||||||
|
err = errors.New("invalid apollo appid, pass it via APOLLO_APP_ID=xxx with env or --apollo.appid=xxx with flag")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if clusterFromEnv := os.Getenv("APOLLO_CLUSTER"); clusterFromEnv != "" {
|
||||||
|
confCluster = clusterFromEnv
|
||||||
|
}
|
||||||
|
if confAppID == "" {
|
||||||
|
err = errors.New("invalid apollo cluster, pass it via APOLLO_CLUSTER=xxx with env or --apollo.cluster=xxx with flag")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cacheDirFromEnv := os.Getenv("APOLLO_CACHE_DIR"); cacheDirFromEnv != "" {
|
||||||
|
confCacheDir = cacheDirFromEnv
|
||||||
|
}
|
||||||
|
if metaAddrFromEnv := os.Getenv("APOLLO_META_ADDR"); metaAddrFromEnv != "" {
|
||||||
|
confMetaAddr = metaAddrFromEnv
|
||||||
|
}
|
||||||
|
if confMetaAddr == "" {
|
||||||
|
err = errors.New("invalid apollo meta addr, pass it via APOLLO_META_ADDR=xxx with env or --apollo.metaaddr=xxx with flag")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if namespacesFromEnv := os.Getenv("APOLLO_NAMESPACES"); namespacesFromEnv != "" {
|
||||||
|
confNamespaces = namespacesFromEnv
|
||||||
|
}
|
||||||
|
namespaceNames := strings.Split(confNamespaces, ",")
|
||||||
|
if len(namespaceNames) == 0 {
|
||||||
|
err = errors.New("invalid apollo namespaces, pass it via APOLLO_NAMESPACES=xxx with env or --apollo.namespaces=xxx with flag")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c = &Config{
|
||||||
|
AppID: confAppID,
|
||||||
|
Cluster: confCluster,
|
||||||
|
CacheDir: confCacheDir,
|
||||||
|
MetaAddr: confMetaAddr,
|
||||||
|
Namespaces: namespaceNames,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// New new an apollo config client.
|
||||||
|
// it watches apollo namespaces changes and updates local cache.
|
||||||
|
// BTW, in our context, namespaces in apollo means keys in paladin.
|
||||||
|
func (ad *apolloDriver) New() (paladin.Client, error) {
|
||||||
|
c, err := buildConfigForApollo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ad.new(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ad *apolloDriver) new(conf *Config) (paladin.Client, error) {
|
||||||
|
if conf == nil {
|
||||||
|
err := errors.New("invalid apollo conf")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client := agollo.NewClient(&agollo.Conf{
|
||||||
|
AppID: conf.AppID,
|
||||||
|
Cluster: conf.Cluster,
|
||||||
|
NameSpaceNames: conf.Namespaces, // these namespaces will be subscribed at init
|
||||||
|
CacheDir: conf.CacheDir,
|
||||||
|
IP: conf.MetaAddr,
|
||||||
|
})
|
||||||
|
err := client.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a := &apollo{
|
||||||
|
client: client,
|
||||||
|
values: new(paladin.Map),
|
||||||
|
watchers: make(map[*apolloWatcher]struct{}),
|
||||||
|
}
|
||||||
|
raws, err := a.loadValues(conf.Namespaces)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a.values.Store(raws)
|
||||||
|
// watch namespaces by default.
|
||||||
|
a.WatchEvent(context.TODO(), conf.Namespaces...)
|
||||||
|
go a.watchproc(conf.Namespaces)
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadValues load values from apollo namespaces to values
|
||||||
|
func (a *apollo) loadValues(keys []string) (values map[string]*paladin.Value, err error) {
|
||||||
|
values = make(map[string]*paladin.Value, len(keys))
|
||||||
|
for _, k := range keys {
|
||||||
|
if values[k], err = a.loadValue(k); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadValue load value from apollo namespace content to value
|
||||||
|
func (a *apollo) loadValue(key string) (*paladin.Value, error) {
|
||||||
|
content := a.client.GetNameSpaceContent(key, defaultValue)
|
||||||
|
return paladin.NewValue(content, content), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// reloadValue reload value by key and send event
|
||||||
|
func (a *apollo) reloadValue(key string) (err error) {
|
||||||
|
// NOTE: in some case immediately read content from client after receive event
|
||||||
|
// will get old content due to cache, sleep 100ms make sure get correct content.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
var (
|
||||||
|
value *paladin.Value
|
||||||
|
rawValue string
|
||||||
|
)
|
||||||
|
value, err = a.loadValue(key)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rawValue, err = value.Raw()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
raws := a.values.Load()
|
||||||
|
raws[key] = value
|
||||||
|
a.values.Store(raws)
|
||||||
|
a.wmu.RLock()
|
||||||
|
n := 0
|
||||||
|
for w := range a.watchers {
|
||||||
|
if w.HasKey(key) {
|
||||||
|
n++
|
||||||
|
// FIXME(Colstuwjx): check change event and send detail type like EventAdd\Update\Delete.
|
||||||
|
w.Handle(paladin.Event{Event: paladin.EventUpdate, Key: key, Value: rawValue})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.wmu.RUnlock()
|
||||||
|
log.Printf("paladin: reload config: %s events: %d\n", key, n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// apollo config daemon to watch remote apollo notifications
|
||||||
|
func (a *apollo) watchproc(keys []string) {
|
||||||
|
events := a.client.WatchUpdate()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-events:
|
||||||
|
if err := a.reloadValue(event.Namespace); err != nil {
|
||||||
|
log.Printf("paladin: load key: %s error: %s, skipped", event.Namespace, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get return value by key.
|
||||||
|
func (a *apollo) Get(key string) *paladin.Value {
|
||||||
|
return a.values.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll return value map.
|
||||||
|
func (a *apollo) GetAll() *paladin.Map {
|
||||||
|
return a.values
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchEvent watch with the specified keys.
|
||||||
|
func (a *apollo) WatchEvent(ctx context.Context, keys ...string) <-chan paladin.Event {
|
||||||
|
aw := newApolloWatcher(keys)
|
||||||
|
err := a.client.SubscribeToNamespaces(keys...)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("subscribe namespaces %v failed, %v", keys, err)
|
||||||
|
return aw.C
|
||||||
|
}
|
||||||
|
a.wmu.Lock()
|
||||||
|
a.watchers[aw] = struct{}{}
|
||||||
|
a.wmu.Unlock()
|
||||||
|
return aw.C
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close close watcher.
|
||||||
|
func (a *apollo) Close() (err error) {
|
||||||
|
if err = a.client.Stop(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.wmu.RLock()
|
||||||
|
for w := range a.watchers {
|
||||||
|
close(w.C)
|
||||||
|
}
|
||||||
|
a.wmu.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
73
pkg/conf/paladin/apollo/apollo_test.go
Normal file
73
pkg/conf/paladin/apollo/apollo_test.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package apollo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bilibili/kratos/pkg/conf/paladin/apollo/internal/mockserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
setup()
|
||||||
|
code := m.Run()
|
||||||
|
teardown()
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup() {
|
||||||
|
go func() {
|
||||||
|
if err := mockserver.Run(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// wait for mock server to run
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
func teardown() {
|
||||||
|
mockserver.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApollo(t *testing.T) {
|
||||||
|
var (
|
||||||
|
testAppYAML = "app.yml"
|
||||||
|
testAppYAMLContent1 = "test: test12234\ntest2: test333"
|
||||||
|
testAppYAMLContent2 = "test: 1111"
|
||||||
|
testClientJSON = "client.json"
|
||||||
|
testClientJSONContent = `{"name":"agollo"}`
|
||||||
|
)
|
||||||
|
os.Setenv("APOLLO_APP_ID", "SampleApp")
|
||||||
|
os.Setenv("APOLLO_CLUSTER", "default")
|
||||||
|
os.Setenv("APOLLO_CACHE_DIR", "/tmp")
|
||||||
|
os.Setenv("APOLLO_META_ADDR", "localhost:8080")
|
||||||
|
os.Setenv("APOLLO_NAMESPACES", fmt.Sprintf("%s,%s", testAppYAML, testClientJSON))
|
||||||
|
mockserver.Set(testAppYAML, "content", testAppYAMLContent1)
|
||||||
|
mockserver.Set(testClientJSON, "content", testClientJSONContent)
|
||||||
|
ad := &apolloDriver{}
|
||||||
|
apollo, err := ad.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("new apollo error, %v", err)
|
||||||
|
}
|
||||||
|
value := apollo.Get(testAppYAML)
|
||||||
|
if content, _ := value.String(); content != testAppYAMLContent1 {
|
||||||
|
t.Fatalf("got app.yml unexpected value %s", content)
|
||||||
|
}
|
||||||
|
value = apollo.Get(testClientJSON)
|
||||||
|
if content, _ := value.String(); content != testClientJSONContent {
|
||||||
|
t.Fatalf("got app.yml unexpected value %s", content)
|
||||||
|
}
|
||||||
|
mockserver.Set(testAppYAML, "content", testAppYAMLContent2)
|
||||||
|
updates := apollo.WatchEvent(context.TODO(), testAppYAML)
|
||||||
|
select {
|
||||||
|
case <-updates:
|
||||||
|
case <-time.After(time.Millisecond * 30000):
|
||||||
|
}
|
||||||
|
value = apollo.Get(testAppYAML)
|
||||||
|
if content, _ := value.String(); content != testAppYAMLContent2 {
|
||||||
|
t.Fatalf("got app.yml unexpected updated value %s", content)
|
||||||
|
}
|
||||||
|
}
|
6
pkg/conf/paladin/apollo/const.go
Normal file
6
pkg/conf/paladin/apollo/const.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package apollo
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PaladinDriverApollo ...
|
||||||
|
PaladinDriverApollo = "apollo"
|
||||||
|
)
|
149
pkg/conf/paladin/apollo/internal/mockserver/mockserver.go
Normal file
149
pkg/conf/paladin/apollo/internal/mockserver/mockserver.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package mockserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type notification struct {
|
||||||
|
NamespaceName string `json:"namespaceName,omitempty"`
|
||||||
|
NotificationID int `json:"notificationId,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type result struct {
|
||||||
|
// AppID string `json:"appId"`
|
||||||
|
// Cluster string `json:"cluster"`
|
||||||
|
NamespaceName string `json:"namespaceName"`
|
||||||
|
Configurations map[string]string `json:"configurations"`
|
||||||
|
ReleaseKey string `json:"releaseKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockServer struct {
|
||||||
|
server http.Server
|
||||||
|
|
||||||
|
lock sync.Mutex
|
||||||
|
notifications map[string]int
|
||||||
|
config map[string]map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mockServer) NotificationHandler(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
req.ParseForm()
|
||||||
|
var notifications []notification
|
||||||
|
if err := json.Unmarshal([]byte(req.FormValue("notifications")), ¬ifications); err != nil {
|
||||||
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var changes []notification
|
||||||
|
for _, noti := range notifications {
|
||||||
|
if currentID := s.notifications[noti.NamespaceName]; currentID != noti.NotificationID {
|
||||||
|
changes = append(changes, notification{NamespaceName: noti.NamespaceName, NotificationID: currentID})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(changes) == 0 {
|
||||||
|
rw.WriteHeader(http.StatusNotModified)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bts, err := json.Marshal(&changes)
|
||||||
|
if err != nil {
|
||||||
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rw.Write(bts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mockServer) ConfigHandler(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
req.ParseForm()
|
||||||
|
|
||||||
|
strs := strings.Split(req.RequestURI, "/")
|
||||||
|
var namespace, releaseKey = strings.Split(strs[4], "?")[0], req.FormValue("releaseKey")
|
||||||
|
config := s.Get(namespace)
|
||||||
|
|
||||||
|
var result = result{NamespaceName: namespace, Configurations: config, ReleaseKey: releaseKey}
|
||||||
|
bts, err := json.Marshal(&result)
|
||||||
|
if err != nil {
|
||||||
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rw.Write(bts)
|
||||||
|
}
|
||||||
|
|
||||||
|
var server *mockServer
|
||||||
|
|
||||||
|
func (s *mockServer) Set(namespace, key, value string) {
|
||||||
|
server.lock.Lock()
|
||||||
|
defer server.lock.Unlock()
|
||||||
|
|
||||||
|
notificationID := s.notifications[namespace]
|
||||||
|
notificationID++
|
||||||
|
s.notifications[namespace] = notificationID
|
||||||
|
|
||||||
|
if kv, ok := s.config[namespace]; ok {
|
||||||
|
kv[key] = value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kv := map[string]string{key: value}
|
||||||
|
s.config[namespace] = kv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mockServer) Get(namespace string) map[string]string {
|
||||||
|
server.lock.Lock()
|
||||||
|
defer server.lock.Unlock()
|
||||||
|
|
||||||
|
return s.config[namespace]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mockServer) Delete(namespace, key string) {
|
||||||
|
server.lock.Lock()
|
||||||
|
defer server.lock.Unlock()
|
||||||
|
|
||||||
|
if kv, ok := s.config[namespace]; ok {
|
||||||
|
delete(kv, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationID := s.notifications[namespace]
|
||||||
|
notificationID++
|
||||||
|
s.notifications[namespace] = notificationID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set namespace's key value
|
||||||
|
func Set(namespace, key, value string) {
|
||||||
|
server.Set(namespace, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete namespace's key
|
||||||
|
func Delete(namespace, key string) {
|
||||||
|
server.Delete(namespace, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run mock server
|
||||||
|
func Run() error {
|
||||||
|
initServer()
|
||||||
|
return server.server.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func initServer() {
|
||||||
|
server = &mockServer{
|
||||||
|
notifications: map[string]int{},
|
||||||
|
config: map[string]map[string]string{},
|
||||||
|
}
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.Handle("/notifications/", http.HandlerFunc(server.NotificationHandler))
|
||||||
|
mux.Handle("/configs/", http.HandlerFunc(server.ConfigHandler))
|
||||||
|
server.server.Handler = mux
|
||||||
|
server.server.Addr = ":8080"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close mock server
|
||||||
|
func Close() error {
|
||||||
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second))
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return server.server.Shutdown(ctx)
|
||||||
|
}
|
@ -2,6 +2,7 @@ package paladin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,12 +17,30 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Init init config client.
|
// Init init config client.
|
||||||
func Init() (err error) {
|
// If confPath is set, it inits file client by default
|
||||||
|
// Otherwise we could pass args to init remote client
|
||||||
|
// args[0]: driver name, string type
|
||||||
|
func Init(args ...interface{}) (err error) {
|
||||||
if confPath != "" {
|
if confPath != "" {
|
||||||
DefaultClient, err = NewFile(confPath)
|
DefaultClient, err = NewFile(confPath)
|
||||||
} else {
|
} else {
|
||||||
// TODO: Get the configuration from the remote service
|
var (
|
||||||
panic("Please specify a file or dir name by -conf flag.")
|
driver Driver
|
||||||
|
)
|
||||||
|
argsLackErr := errors.New("lack of remote config center args")
|
||||||
|
if len(args) == 0 {
|
||||||
|
panic(argsLackErr.Error())
|
||||||
|
}
|
||||||
|
argsInvalidErr := errors.New("invalid remote config center args")
|
||||||
|
driverName, ok := args[0].(string)
|
||||||
|
if !ok {
|
||||||
|
panic(argsInvalidErr.Error())
|
||||||
|
}
|
||||||
|
driver, err = GetDriver(driverName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
DefaultClient, err = driver.New()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
9
pkg/conf/paladin/driver.go
Normal file
9
pkg/conf/paladin/driver.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package paladin
|
||||||
|
|
||||||
|
// Driver defined paladin remote client impl
|
||||||
|
// each remote config center driver must do
|
||||||
|
// 1. implements `New` method
|
||||||
|
// 2. call `Register` to register itself
|
||||||
|
type Driver interface {
|
||||||
|
New() (Client, error)
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/bilibili/kratos/pkg/conf/paladin"
|
"github.com/bilibili/kratos/pkg/conf/paladin"
|
||||||
|
"github.com/bilibili/kratos/pkg/conf/paladin/apollo"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
)
|
)
|
||||||
@ -26,7 +27,7 @@ func (e *exampleConf) Set(text string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExampleClient is a example client usage.
|
// ExampleClient is an example client usage.
|
||||||
// exmaple.toml:
|
// exmaple.toml:
|
||||||
/*
|
/*
|
||||||
bool = true
|
bool = true
|
||||||
@ -56,7 +57,41 @@ func ExampleClient() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExampleMap is a example map usage.
|
// ExampleApolloClient is an example client for apollo driver usage.
|
||||||
|
func ExampleApolloClient() {
|
||||||
|
/*
|
||||||
|
pass flags or set envs that apollo needs, for example:
|
||||||
|
|
||||||
|
```
|
||||||
|
export APOLLO_APP_ID=SampleApp
|
||||||
|
export APOLLO_CLUSTER=default
|
||||||
|
export APOLLO_CACHE_DIR=/tmp
|
||||||
|
export APOLLO_META_ADDR=localhost:8080
|
||||||
|
export APOLLO_NAMESPACES=example.yml
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
|
||||||
|
if err := paladin.Init(apollo.PaladinDriverApollo); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var ec exampleConf
|
||||||
|
// var setter
|
||||||
|
if err := paladin.Watch("example.yml", &ec); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := paladin.Get("example.yml").UnmarshalYAML(&ec); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// use exampleConf
|
||||||
|
// watch event key
|
||||||
|
go func() {
|
||||||
|
for event := range paladin.WatchEvent(context.TODO(), "key") {
|
||||||
|
fmt.Println(event)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExampleMap is an example map usage.
|
||||||
// exmaple.toml:
|
// exmaple.toml:
|
||||||
/*
|
/*
|
||||||
bool = true
|
bool = true
|
||||||
|
@ -31,7 +31,7 @@ func (w *watcher) HasKey(key string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, k := range w.keys {
|
for _, k := range w.keys {
|
||||||
if keyNamed(k) == key {
|
if KeyNamed(k) == key {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,7 +138,7 @@ func (f *file) reloadFile(fpath string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
key := keyNamed(path.Base(fpath))
|
key := KeyNamed(path.Base(fpath))
|
||||||
raws := f.values.Load()
|
raws := f.values.Load()
|
||||||
raws[key] = value
|
raws[key] = value
|
||||||
f.values.Store(raws)
|
f.values.Store(raws)
|
||||||
|
@ -5,8 +5,8 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
// keyNamed key naming to lower case.
|
// KeyNamed key naming to lower case.
|
||||||
func keyNamed(key string) string {
|
func KeyNamed(key string) string {
|
||||||
return strings.ToLower(key)
|
return strings.ToLower(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ type Map struct {
|
|||||||
func (m *Map) Store(values map[string]*Value) {
|
func (m *Map) Store(values map[string]*Value) {
|
||||||
dst := make(map[string]*Value, len(values))
|
dst := make(map[string]*Value, len(values))
|
||||||
for k, v := range values {
|
for k, v := range values {
|
||||||
dst[keyNamed(k)] = v
|
dst[KeyNamed(k)] = v
|
||||||
}
|
}
|
||||||
m.values.Store(dst)
|
m.values.Store(dst)
|
||||||
}
|
}
|
||||||
@ -36,13 +36,13 @@ func (m *Map) Load() map[string]*Value {
|
|||||||
|
|
||||||
// Exist check if values map exist a key.
|
// Exist check if values map exist a key.
|
||||||
func (m *Map) Exist(key string) bool {
|
func (m *Map) Exist(key string) bool {
|
||||||
_, ok := m.Load()[keyNamed(key)]
|
_, ok := m.Load()[KeyNamed(key)]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get return get value by key.
|
// Get return get value by key.
|
||||||
func (m *Map) Get(key string) *Value {
|
func (m *Map) Get(key string) *Value {
|
||||||
v, ok := m.Load()[keyNamed(key)]
|
v, ok := m.Load()[KeyNamed(key)]
|
||||||
if ok {
|
if ok {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
55
pkg/conf/paladin/register.go
Normal file
55
pkg/conf/paladin/register.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package paladin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
driversMu sync.RWMutex
|
||||||
|
drivers = make(map[string]Driver)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register makes a paladin driver available by the provided name.
|
||||||
|
// If Register is called twice with the same name or if driver is nil,
|
||||||
|
// it panics.
|
||||||
|
func Register(name string, driver Driver) {
|
||||||
|
driversMu.Lock()
|
||||||
|
defer driversMu.Unlock()
|
||||||
|
|
||||||
|
if driver == nil {
|
||||||
|
panic("paladin: driver is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, dup := drivers[name]; dup {
|
||||||
|
panic("paladin: Register called twice for driver " + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
drivers[name] = driver
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drivers returns a sorted list of the names of the registered paladin driver.
|
||||||
|
func Drivers() []string {
|
||||||
|
driversMu.RLock()
|
||||||
|
defer driversMu.RUnlock()
|
||||||
|
|
||||||
|
var list []string
|
||||||
|
for name := range drivers {
|
||||||
|
list = append(list, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(list)
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDriver returns a driver implement by name.
|
||||||
|
func GetDriver(name string) (Driver, error) {
|
||||||
|
driversMu.RLock()
|
||||||
|
driveri, ok := drivers[name]
|
||||||
|
driversMu.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("paladin: unknown driver %q (forgotten import?)", name)
|
||||||
|
}
|
||||||
|
return driveri, nil
|
||||||
|
}
|
@ -28,7 +28,7 @@ func (m *TOML) UnmarshalText(text []byte) error {
|
|||||||
}
|
}
|
||||||
values := map[string]*Value{}
|
values := map[string]*Value{}
|
||||||
for k, v := range raws {
|
for k, v := range raws {
|
||||||
k = keyNamed(k)
|
k = KeyNamed(k)
|
||||||
rv := reflect.ValueOf(v)
|
rv := reflect.ValueOf(v)
|
||||||
switch rv.Kind() {
|
switch rv.Kind() {
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
|
@ -25,6 +25,14 @@ type Value struct {
|
|||||||
raw string
|
raw string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewValue new a value
|
||||||
|
func NewValue(val interface{}, raw string) *Value {
|
||||||
|
return &Value{
|
||||||
|
val: val,
|
||||||
|
raw: raw,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Bool return bool value.
|
// Bool return bool value.
|
||||||
func (v *Value) Bool() (bool, error) {
|
func (v *Value) Bool() (bool, error) {
|
||||||
if v.val == nil {
|
if v.val == nil {
|
||||||
@ -112,7 +120,7 @@ func (v *Value) Raw() (string, error) {
|
|||||||
return v.raw, nil
|
return v.raw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slice scan a slcie interface, if slice has element it will be discard.
|
// Slice scan a slice interface, if slice has element it will be discard.
|
||||||
func (v *Value) Slice(dst interface{}) error {
|
func (v *Value) Slice(dst interface{}) error {
|
||||||
// NOTE: val is []interface{}, slice is []type
|
// NOTE: val is []interface{}, slice is []type
|
||||||
if v.val == nil {
|
if v.val == nil {
|
||||||
@ -167,6 +175,7 @@ func (v *Value) UnmarshalJSON(dst interface{}) error {
|
|||||||
return json.Unmarshal([]byte(text), dst)
|
return json.Unmarshal([]byte(text), dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML unmarshal yaml to struct.
|
||||||
func (v *Value) UnmarshalYAML(dst interface{}) error {
|
func (v *Value) UnmarshalYAML(dst interface{}) error {
|
||||||
text, err := v.Raw()
|
text, err := v.Raw()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user