Skip to content

Commit

Permalink
Added viper tarantool 3 configuration provider
Browse files Browse the repository at this point in the history
We have added the ability to read static config by viper for tarantool 3
  • Loading branch information
maksim.konovalov committed Dec 24, 2024
1 parent 249e9ba commit 0db61e5
Show file tree
Hide file tree
Showing 14 changed files with 321 additions and 90 deletions.
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
## Unreleased
## v1.3.1

REFACTOR:

* Change already exists rs error "replicaset exists" to "replicaset already exists".
* Rewrite some comments to en.

FEATURES:

* Added viper tarantool3 topology provider implementation.

EXAMPLES:

* Logger usage in customer example fixed.

## v1.3.0

BUG FIXES:
* Fix decoding fields for StorageCallVShardError (MasterUUID, ReplicasetUUID).
Expand Down
4 changes: 2 additions & 2 deletions examples/customer/go-service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func main() {
cfg := readCfg(os.Args[1])

vshardRouter, err := vshardrouter.NewRouter(ctx, vshardrouter.Config{
Logger: &vshardrouter.StdoutLogger{},
Loggerf: &vshardrouter.StdoutLoggerf{},
DiscoveryTimeout: time.Minute,
DiscoveryMode: vshardrouter.DiscoveryModeOn,
TopologyProvider: static.NewProvider(cfg.Storage.Topology),
Expand Down Expand Up @@ -258,7 +258,7 @@ func readCfg(cfgPath string) Config {
os.Exit(2)
}

// готовим конфиг для vshard-router
// prepare vshard router config
vshardRouterTopology := make(map[vshardrouter.ReplicasetInfo][]vshardrouter.InstanceInfo)

for rsName, rs := range cfg.Storage.SourceTopology.Clusters {
Expand Down
7 changes: 0 additions & 7 deletions providers/static/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import (
"context"
"fmt"

"github.com/google/uuid"

vshardrouter "github.com/tarantool/go-vshard-router"
)

Expand Down Expand Up @@ -34,11 +32,6 @@ func (p *Provider) Validate() error {
if rs.Name == "" {
return fmt.Errorf("one of replicaset name is empty")
}

// check replicaset uuid
if rs.UUID == uuid.Nil {
return fmt.Errorf("one of replicaset uuid is empty")
}
}

return nil
Expand Down
2 changes: 1 addition & 1 deletion providers/static/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func TestProvider_Validate(t *testing.T) {
vshardrouter.InstanceInfo{},
},
},
IsErr: true,
IsErr: false, // uuid is not required. tarantool3 have an only alias support.
},
{
Name: "valid",
Expand Down
26 changes: 26 additions & 0 deletions providers/viper/moonlibs/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package moonlibs

// ----- Moonlibs configuration -----

// Config is a representation of the topology configuration for tarantool version below 3.
// based on https://github.com/moonlibs/config?tab=readme-ov-file#example-of-etcd-configuration-etcdclustermaster.
type Config struct {
Topology SourceTopologyConfig `json:"topology"`
}

type SourceTopologyConfig struct {
Clusters map[string]ClusterInfo `json:"clusters,omitempty" yaml:"clusters" `
Instances map[string]InstanceInfo `json:"instances,omitempty" yaml:"instances"`
}

type ClusterInfo struct {
ReplicasetUUID string `yaml:"replicaset_uuid" mapstructure:"replicaset_uuid"`
}

type InstanceInfo struct {
Cluster string
Box struct {
Listen string `json:"listen,omitempty" yaml:"listen" mapstructure:"listen"`
InstanceUUID string `yaml:"instance_uuid" mapstructure:"instance_uuid" json:"instanceUUID,omitempty"`
}
}
55 changes: 55 additions & 0 deletions providers/viper/moonlibs/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package moonlibs

import (
"log"

"github.com/google/uuid"
vshardrouter "github.com/tarantool/go-vshard-router"
)

func (cfg *Config) Convert() map[vshardrouter.ReplicasetInfo][]vshardrouter.InstanceInfo {
if cfg.Topology.Instances == nil {
panic("instances is nil")
}

if cfg.Topology.Clusters == nil {
panic("clusters is nil")
}

// prepare vshard router config
vshardRouterTopology := make(map[vshardrouter.ReplicasetInfo][]vshardrouter.InstanceInfo)

for rsName, rs := range cfg.Topology.Clusters {
rsUUID, err := uuid.Parse(rs.ReplicasetUUID)
if err != nil {
panic("Can't parse replicaset uuid: %s")
}

rsInstances := make([]vshardrouter.InstanceInfo, 0)

for _, instInfo := range cfg.Topology.Instances {
if instInfo.Cluster != rsName {
continue
}

instUUID, err := uuid.Parse(instInfo.Box.InstanceUUID)
if err != nil {
log.Printf("Can't parse replicaset uuid: %s", err)

panic(err)
}

rsInstances = append(rsInstances, vshardrouter.InstanceInfo{
Addr: instInfo.Box.Listen,
UUID: instUUID,
})
}

vshardRouterTopology[vshardrouter.ReplicasetInfo{
Name: rsName,
UUID: rsUUID,
}] = rsInstances
}

return vshardRouterTopology
}
106 changes: 34 additions & 72 deletions providers/viper/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,87 +3,70 @@ package viper
import (
"context"
"fmt"
"log"
"os"

"github.com/google/uuid"
srcviper "github.com/spf13/viper"

vshardrouter "github.com/tarantool/go-vshard-router"
"github.com/tarantool/go-vshard-router/providers/viper/moonlibs"
"github.com/tarantool/go-vshard-router/providers/viper/tarantool3"
)

// Check that provider implements TopologyProvider interface
var _ vshardrouter.TopologyProvider = (*Provider)(nil)

type Provider struct {
ctx context.Context

v *srcviper.Viper
rs map[vshardrouter.ReplicasetInfo][]vshardrouter.InstanceInfo
}

func NewProvider(v *srcviper.Viper) *Provider {
type ConfigType int

const (
ConfigTypeMoonlibs ConfigType = iota
ConfigTypeTarantool3
)

type Convertable interface {
Convert() map[vshardrouter.ReplicasetInfo][]vshardrouter.InstanceInfo
}

func NewProvider(ctx context.Context, v *srcviper.Viper, cfgType ConfigType) *Provider {
if v == nil {
panic("viper entity is nil")
}

cfg := &TopologyConfig{}
err := v.Unmarshal(cfg)
if err != nil {
panic(err)
}
var cfg Convertable

if cfg.Topology.Instances == nil {
panic("instances is nil")
switch cfgType {
case ConfigTypeMoonlibs:
cfg = &moonlibs.Config{}
case ConfigTypeTarantool3:
cfg = &tarantool3.Config{}
default:
panic("unknown config type")
}

if cfg.Topology.Clusters == nil {
panic("clusters is nil")
err := v.Unmarshal(cfg)
if err != nil {
panic(err)
}

// готовим конфиг для vshard-router`а
vshardRouterTopology := make(map[vshardrouter.ReplicasetInfo][]vshardrouter.InstanceInfo)

for rsName, rs := range cfg.Topology.Clusters {
rsUUID, err := uuid.Parse(rs.ReplicasetUUID)
if err != nil {
log.Printf("Can't parse replicaset uuid: %s", err)

os.Exit(2)
}

rsInstances := make([]vshardrouter.InstanceInfo, 0)
resultMap := cfg.Convert()

for _, instInfo := range cfg.Topology.Instances {
if instInfo.Cluster != rsName {
continue
}

instUUID, err := uuid.Parse(instInfo.Box.InstanceUUID)
if err != nil {
log.Printf("Can't parse replicaset uuid: %s", err)

panic(err)
}

rsInstances = append(rsInstances, vshardrouter.InstanceInfo{
Addr: instInfo.Box.Listen,
UUID: instUUID,
})
}

vshardRouterTopology[vshardrouter.ReplicasetInfo{
Name: rsName,
UUID: rsUUID,
}] = rsInstances
}

return &Provider{v: v, rs: vshardRouterTopology}
return &Provider{ctx: ctx, v: v, rs: resultMap}
}

func (p *Provider) WatchChanges() *Provider {
// todo
return p
}

func (p *Provider) Topology() map[vshardrouter.ReplicasetInfo][]vshardrouter.InstanceInfo {
return p.rs
}

func (p *Provider) Validate() error {
if len(p.rs) < 1 {
return fmt.Errorf("replicasets are empty")
Expand All @@ -105,28 +88,7 @@ func (p *Provider) Validate() error {
}

func (p *Provider) Init(c vshardrouter.TopologyController) error {
return c.AddReplicasets(context.TODO(), p.rs)
return c.AddReplicasets(p.ctx, p.rs)
}

func (p *Provider) Close() {}

type ClusterInfo struct {
ReplicasetUUID string `yaml:"replicaset_uuid" mapstructure:"replicaset_uuid"`
}

type InstanceInfo struct {
Cluster string
Box struct {
Listen string `json:"listen,omitempty" yaml:"listen" mapstructure:"listen"`
InstanceUUID string `yaml:"instance_uuid" mapstructure:"instance_uuid" json:"instanceUUID,omitempty"`
}
}

type TopologyConfig struct {
Topology SourceTopologyConfig `json:"topology"`
}

type SourceTopologyConfig struct {
Clusters map[string]ClusterInfo `json:"clusters,omitempty" yaml:"clusters" `
Instances map[string]InstanceInfo `json:"instances,omitempty" yaml:"instances"`
}
47 changes: 41 additions & 6 deletions providers/viper/provider_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package viper_test

import (
"context"
"testing"

"github.com/spf13/viper"
Expand All @@ -15,29 +16,31 @@ func TestProvider_Close(t *testing.T) {

func TestNewProviderNilPanic(t *testing.T) {
require.Panics(t, func() {
vprovider.NewProvider(nil)
vprovider.NewProvider(context.TODO(), nil, vprovider.ConfigTypeMoonlibs)
})
}

func TestNewProviderDirect(t *testing.T) {
ctx := context.TODO()
v := viper.New()

v.AddConfigPath("test/")
v.AddConfigPath("testdata/")
v.SetConfigName("config-direct")
v.SetConfigType("yaml")

err := v.ReadInConfig()
require.NoError(t, err)

provider := vprovider.NewProvider(v)
provider := vprovider.NewProvider(ctx, v, vprovider.ConfigTypeMoonlibs)

require.NotNil(t, provider)
anyProviderValidation(t, provider)
}

func TestNewProviderSub(t *testing.T) {
ctx := context.TODO()
v := viper.New()

v.AddConfigPath("test/")
v.AddConfigPath("testdata/")
v.SetConfigName("config-sub")
v.SetConfigType("yaml")

Expand All @@ -46,7 +49,39 @@ func TestNewProviderSub(t *testing.T) {

v = v.Sub("supbpath")

provider := vprovider.NewProvider(v)
provider := vprovider.NewProvider(ctx, v, vprovider.ConfigTypeMoonlibs)

anyProviderValidation(t, provider)
}

func TestNewProviderTarantool3(t *testing.T) {
ctx := context.TODO()
v := viper.New()

v.AddConfigPath("testdata/")
v.SetConfigName("config-tarantool3")
v.SetConfigType("yaml")

err := v.ReadInConfig()
require.NoError(t, err)

provider := vprovider.NewProvider(ctx, v, vprovider.ConfigTypeTarantool3)

anyProviderValidation(t, provider)
}

func anyProviderValidation(t testing.TB, provider *vprovider.Provider) {
// not empty
require.NotNil(t, provider)

topology := provider.Topology()
// topology not empty
require.NotNil(t, topology)
// topology more than 0
require.True(t, len(topology) > 0)

// there are no empty replicates
for _, instances := range topology {
require.NotEmpty(t, instances)
}
}
Loading

0 comments on commit 0db61e5

Please sign in to comment.