Skip to content

Commit

Permalink
fix: sort redis configuration (#107)
Browse files Browse the repository at this point in the history
* fix: sort redis configuration

* fix: yaml serialization

* fix: add comment

* fix: add comment

* fix: doc

* fix: doc
  • Loading branch information
Reasno committed Mar 24, 2021
1 parent 3b58718 commit d810b38
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 45 deletions.
55 changes: 55 additions & 0 deletions config/time.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package config

import (
"encoding/json"
"errors"
"time"

"gopkg.in/yaml.v3"
)

// Duration is a type that describe a time duration. It is suitable for use in
// configurations as it implements a variety of serialization methods.
type Duration struct {
time.Duration
}

// MarshalYAML implements Marshaller
func (d Duration) MarshalYAML() (interface{}, error) {
return d.String(), nil
}

// UnmarshalYAML implements Unmarshaller
func (d *Duration) UnmarshalYAML(value *yaml.Node) error {
if value.Tag == "!!float" {
return d.UnmarshalJSON([]byte(value.Value))
}
return d.UnmarshalJSON([]byte("\"" + value.Value + "\""))
}

// MarshalJSON implements JSONMarshaller
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}

// UnmarshalJSON implements JSONUnmarsheller
func (d *Duration) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case float64:
d.Duration = time.Duration(value)
return nil
case string:
var err error
d.Duration, err = time.ParseDuration(value)
if err != nil {
return err
}
return nil
default:
return errors.New("invalid duration")
}
}
82 changes: 82 additions & 0 deletions config/time_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package config

import (
"encoding/json"
"testing"
"time"

"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)

func TestDuration_UnmarshalJSON(t *testing.T) {
var cases = []struct {
name string
value string
expected Duration
}{
{
"simple",
`"5s"`,
Duration{5 * time.Second},
},
{
"float",
`65000000000.0`,
Duration{5*time.Second + time.Minute},
},
}

for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
v1 := Duration{}
yaml.Unmarshal([]byte(c.value), &v1)
assert.Equal(t, c.expected, v1)
v2 := Duration{}
json.Unmarshal([]byte(c.value), &v2)
assert.Equal(t, c.expected, v2)
})
}
}

func TestDuration_MarshalJSON(t *testing.T) {
var cases = []struct {
name string
value interface{}
expectedJSON string
expectedYaml string
}{
{
"simple",
Duration{5 * time.Second},
`"5s"`,
"5s\n",
},
{
"complex",
Duration{5*time.Second + time.Minute},
`"1m5s"`,
"1m5s\n",
},
{
"wrapped",
struct{ D Duration }{Duration{5*time.Second + time.Minute}},
`{"D":"1m5s"}`,
"d: 1m5s\n",
},
}

for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
data, err := json.Marshal(c.value)
assert.NoError(t, err)
assert.Equal(t, c.expectedJSON, string(data))
data, err = yaml.Marshal(c.value)
assert.Equal(t, c.expectedYaml, string(data))
})
}
}
33 changes: 26 additions & 7 deletions otredis/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,33 @@ package otredis works with redis cluster, redis sentinel and single redis instan
Integration
package otredis exports the configuration in the following format:
redis:
default:
addrs:
- 127.0.0.1:6379
db: 0
To see all available configurations, use the exportConfig command.
default:
addrs:
- 127.0.0.1:6379
db: 0
username: ""
password: ""
sentinelPassword: ""
maxRetries: 0
minRetryBackoff: 0s
maxRetryBackoff: 0s
dialTimeout: 0s
readTimeout: 0s
writeTimeout: 0s
poolSize: 0
minIdleConns: 0
maxConnAge: 0s
poolTimeout: 0s
idleTimeout: 0s
idleCheckFrequency: 0s
maxRedirects: 0
readOnly: false
routeByLatency: false
routeRandomly: false
masterName: ""
To see all available configurations, use the config init command.
Add the redis dependency to core:
Expand Down
70 changes: 37 additions & 33 deletions otredis/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,33 +79,59 @@ type out struct {
// dependency for package core.
func provideRedisFactory(p in) (out, func()) {
var err error
var dbConfs map[string]redis.UniversalOptions
var dbConfs map[string]RedisUniversalOptions
err = p.Conf.Unmarshal("redis", &dbConfs)
if err != nil {
level.Warn(p.Logger).Log("err", err)
}
factory := di.NewFactory(func(name string) (di.Pair, error) {
var (
ok bool
conf redis.UniversalOptions
base RedisUniversalOptions
full redis.UniversalOptions
)
if conf, ok = dbConfs[name]; !ok {
if base, ok = dbConfs[name]; !ok {
if name != "default" {
return di.Pair{}, fmt.Errorf("redis configuration %s not valid", name)
}
conf = redis.UniversalOptions{}
base = RedisUniversalOptions{}
}
full = redis.UniversalOptions{
Addrs: base.Addrs,
DB: base.DB,
Username: base.Username,
Password: base.Password,
SentinelPassword: base.SentinelPassword,
MaxRetries: base.MaxRetries,
MinRetryBackoff: base.MinRetryBackoff.Duration,
MaxRetryBackoff: base.MaxRetryBackoff.Duration,
DialTimeout: base.DialTimeout.Duration,
ReadTimeout: base.ReadTimeout.Duration,
WriteTimeout: base.WriteTimeout.Duration,
PoolSize: base.PoolSize,
MinIdleConns: base.MinIdleConns,
MaxConnAge: base.MaxConnAge.Duration,
PoolTimeout: base.PoolTimeout.Duration,
IdleTimeout: base.IdleTimeout.Duration,
IdleCheckFrequency: base.IdleCheckFrequency.Duration,
TLSConfig: nil,
MaxRedirects: base.MaxRetries,
ReadOnly: base.ReadOnly,
RouteByLatency: base.RouteByLatency,
RouteRandomly: base.RouteRandomly,
MasterName: base.MasterName,
}
if p.Interceptor != nil {
p.Interceptor(name, &conf)
p.Interceptor(name, &full)
}
redis.SetLogger(&RedisLogAdapter{level.Debug(p.Logger)})

client := redis.NewUniversalClient(&conf)
client := redis.NewUniversalClient(&full)
if p.Tracer != nil {
client.AddHook(
hook{
addrs: conf.Addrs,
database: conf.DB,
addrs: full.Addrs,
database: full.DB,
tracer: p.Tracer,
},
)
Expand Down Expand Up @@ -141,33 +167,11 @@ func provideConfig() configOut {
{
Owner: "otredis",
Data: map[string]interface{}{
"redis": map[string]map[string]interface{}{
"redis": map[string]RedisUniversalOptions{
"default": {
"addrs": []string{"127.0.0.1:6379"},
"db": 0,
"username": "",
"password": "",
"sentinelPassword": "",
"maxRetries": 0,
"minRetryBackoff": 0,
"maxRetryBackoff": 0,
"dialTimeout": 0,
"readTimeout": 0,
"writeTimeout": 0,
"poolSize": 0,
"minIdleConns": 0,
"maxConnAge": 0,
"poolTimeout": 0,
"idleTimeout": 0,
"idleCheckFrequency": 0,
"maxRedirects": 0,
"readOnly": false,
"routeByLatency": false,
"routeRandomly": false,
"masterName": "",
Addrs: []string{"127.0.0.1:6379"},
},
},
},
}},
Comment: "The configuration of redis clients",
},
}
Expand Down
8 changes: 3 additions & 5 deletions otredis/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (

func TestNewRedisFactory(t *testing.T) {
redisOut, cleanup := provideRedisFactory(in{
Conf: config.MapAdapter{"redis": map[string]redis.UniversalOptions{
Conf: config.MapAdapter{"redis": map[string]RedisUniversalOptions{
"default": {},
"alternative": {},
}},
Expand All @@ -36,12 +36,10 @@ func TestProvideConfigs(t *testing.T) {
var r redis.UniversalOptions
c := provideConfig()
assert.NotEmpty(t, c.Config)
c.Config[0].Data["redis"].(map[string]map[string]interface{})["default"]["db"] = 1
c.Config[0].Data["redis"].(map[string]map[string]interface{})["default"]["addrs"] = []string{"0.0.0.0:6379"}
bytes, _ := yaml2.Marshal(c.Config[0].Data)
k := koanf.New(".")
k.Load(rawbytes.Provider(bytes), yaml.Parser())
k.Unmarshal("redis.default", &r)
assert.Equal(t, 1, r.DB)
assert.Equal(t, []string{"0.0.0.0:6379"}, r.Addrs)
assert.Equal(t, 0, r.DB)
assert.Equal(t, []string{"127.0.0.1:6379"}, r.Addrs)
}
48 changes: 48 additions & 0 deletions otredis/redis_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package otredis

import (
"github.com/DoNewsCode/core/config"
)

// RedisUniversalOptions is the configuration options for redis.
type RedisUniversalOptions struct {
// Either a single address or a seed list of host:port addresses
// of cluster/sentinel nodes.
Addrs []string `json:"addrs" yaml:"addrs"`

// Database to be selected after connecting to the server.
// Only single-node and failover clients.
DB int `json:"db" yaml:"db"`

// Common options.

Username string `json:"username" yaml:"username"`
Password string `json:"password" yaml:"password"`
SentinelPassword string `json:"sentinelPassword" yaml:"sentinelPassword"`

MaxRetries int `json:"maxRetries" yaml:"maxRetries"`
MinRetryBackoff config.Duration `json:"minRetryBackoff" yaml:"minRetryBackoff"`
MaxRetryBackoff config.Duration `json:"maxRetryBackoff" yaml:"maxRetryBackoff"`

DialTimeout config.Duration `json:"dialTimeout" yaml:"dialTimeout"`
ReadTimeout config.Duration `json:"readTimeout" yaml:"readTimeout"`
WriteTimeout config.Duration `json:"writeTimeout" yaml:"writeTimeout"`

PoolSize int `json:"poolSize" yaml:"poolSize"`
MinIdleConns int `json:"minIdleConns" yaml:"minIdleConns"`
MaxConnAge config.Duration `json:"maxConnAge" yaml:"maxConnAge"`
PoolTimeout config.Duration `json:"poolTimeout" yaml:"poolTimeout"`
IdleTimeout config.Duration `json:"idleTimeout" yaml:"idleTimeout"`
IdleCheckFrequency config.Duration `json:"idleCheckFrequency" yaml:"idleCheckFrequency"`

// Only cluster clients.

MaxRedirects int `json:"maxRedirects" yaml:"maxRedirects"`
ReadOnly bool `json:"readOnly" yaml:"readOnly"`
RouteByLatency bool `json:"routeByLatency" yaml:"routeByLatency"`
RouteRandomly bool `json:"routeRandomly" yaml:"routeRandomly"`

// The sentinel master name.
// Only failover clients.
MasterName string `json:"masterName" yaml:"masterName"`
}

0 comments on commit d810b38

Please sign in to comment.