From 0db61e52765ff3f78968a1d3fd04db2851c7f649 Mon Sep 17 00:00:00 2001 From: "maksim.konovalov" Date: Mon, 23 Dec 2024 20:58:10 +0300 Subject: [PATCH] Added viper tarantool 3 configuration provider We have added the ability to read static config by viper for tarantool 3 --- CHANGELOG.md | 17 ++- examples/customer/go-service/main.go | 4 +- providers/static/provider.go | 7 -- providers/static/provider_test.go | 2 +- providers/viper/moonlibs/config.go | 26 +++++ providers/viper/moonlibs/convert.go | 55 +++++++++ providers/viper/provider.go | 106 ++++++------------ providers/viper/provider_test.go | 47 +++++++- providers/viper/tarantool3/config.go | 58 ++++++++++ providers/viper/tarantool3/convert.go | 25 +++++ .../{test => testdata}/config-direct.yaml | 0 .../viper/{test => testdata}/config-sub.yaml | 0 .../viper/testdata/config-tarantool3.yaml | 62 ++++++++++ topology.go | 2 +- 14 files changed, 321 insertions(+), 90 deletions(-) create mode 100644 providers/viper/moonlibs/config.go create mode 100644 providers/viper/moonlibs/convert.go create mode 100644 providers/viper/tarantool3/config.go create mode 100644 providers/viper/tarantool3/convert.go rename providers/viper/{test => testdata}/config-direct.yaml (100%) rename providers/viper/{test => testdata}/config-sub.yaml (100%) create mode 100644 providers/viper/testdata/config-tarantool3.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 4852ff0..fbb56ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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). diff --git a/examples/customer/go-service/main.go b/examples/customer/go-service/main.go index 425cccc..2fab5b9 100644 --- a/examples/customer/go-service/main.go +++ b/examples/customer/go-service/main.go @@ -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), @@ -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 { diff --git a/providers/static/provider.go b/providers/static/provider.go index 308737e..9965829 100644 --- a/providers/static/provider.go +++ b/providers/static/provider.go @@ -4,8 +4,6 @@ import ( "context" "fmt" - "github.com/google/uuid" - vshardrouter "github.com/tarantool/go-vshard-router" ) @@ -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 diff --git a/providers/static/provider_test.go b/providers/static/provider_test.go index 47653f1..1007142 100644 --- a/providers/static/provider_test.go +++ b/providers/static/provider_test.go @@ -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", diff --git a/providers/viper/moonlibs/config.go b/providers/viper/moonlibs/config.go new file mode 100644 index 0000000..8ef2469 --- /dev/null +++ b/providers/viper/moonlibs/config.go @@ -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"` + } +} diff --git a/providers/viper/moonlibs/convert.go b/providers/viper/moonlibs/convert.go new file mode 100644 index 0000000..6426590 --- /dev/null +++ b/providers/viper/moonlibs/convert.go @@ -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 +} diff --git a/providers/viper/provider.go b/providers/viper/provider.go index 2e6b5e4..1cdfeb3 100644 --- a/providers/viper/provider.go +++ b/providers/viper/provider.go @@ -3,80 +3,59 @@ 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 { @@ -84,6 +63,10 @@ func (p *Provider) WatchChanges() *Provider { 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") @@ -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"` -} diff --git a/providers/viper/provider_test.go b/providers/viper/provider_test.go index fac112f..07c87be 100644 --- a/providers/viper/provider_test.go +++ b/providers/viper/provider_test.go @@ -1,6 +1,7 @@ package viper_test import ( + "context" "testing" "github.com/spf13/viper" @@ -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") @@ -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) + } } diff --git a/providers/viper/tarantool3/config.go b/providers/viper/tarantool3/config.go new file mode 100644 index 0000000..ca738b0 --- /dev/null +++ b/providers/viper/tarantool3/config.go @@ -0,0 +1,58 @@ +package tarantool3 + +// ----- Tarantool 3 configuration ----- + +// Config - configuration for all groups +// based on https://www.tarantool.io/en/doc/latest/getting_started/vshard_quick/#step-4-defining-the-cluster-topology. +type Config struct { + Groups Group `yaml:"groups"` +} + +// Group is a structure for each group configuration +type Group struct { + Storages *Storages `yaml:"storages,omitempty"` +} + +// Storages configuration +type Storages struct { + App App `yaml:"app"` + Sharding Sharding `yaml:"sharding"` + Replication Replication `yaml:"replication"` + Replicasets map[string]Replicaset `yaml:"replicasets"` +} + +// App - general information about the module +type App struct { + Module string `yaml:"module"` +} + +// Sharding configuration +type Sharding struct { + Roles []string `yaml:"roles"` +} + +// Replication configuration +type Replication struct { + Failover string `yaml:"failover"` +} + +// Replicaset configuration +type Replicaset struct { + Leader string `yaml:"leader"` + Instances map[string]Instance `yaml:"instances"` +} + +// Instance in the Replicaset +type Instance struct { + IProto IProto `yaml:"iproto"` +} + +// IProto configuration +type IProto struct { + Listen []Listen `yaml:"listen"` +} + +// Listen configuration (URI for connection) +type Listen struct { + URI string `yaml:"uri"` +} diff --git a/providers/viper/tarantool3/convert.go b/providers/viper/tarantool3/convert.go new file mode 100644 index 0000000..1cd2e37 --- /dev/null +++ b/providers/viper/tarantool3/convert.go @@ -0,0 +1,25 @@ +package tarantool3 + +import ( + vshardrouter "github.com/tarantool/go-vshard-router" +) + +func (cfg *Config) Convert() map[vshardrouter.ReplicasetInfo][]vshardrouter.InstanceInfo { + m := make(map[vshardrouter.ReplicasetInfo][]vshardrouter.InstanceInfo) + + for rsName, rs := range cfg.Groups.Storages.Replicasets { + rsInfo := vshardrouter.ReplicasetInfo{Name: rsName} + instances := make([]vshardrouter.InstanceInfo, 0, len(rs.Instances)) + + for instanceName, instance := range rs.Instances { + instances = append(instances, vshardrouter.InstanceInfo{ + Name: instanceName, + Addr: instance.IProto.Listen[0].URI, + }) + } + + m[rsInfo] = instances + } + + return m +} diff --git a/providers/viper/test/config-direct.yaml b/providers/viper/testdata/config-direct.yaml similarity index 100% rename from providers/viper/test/config-direct.yaml rename to providers/viper/testdata/config-direct.yaml diff --git a/providers/viper/test/config-sub.yaml b/providers/viper/testdata/config-sub.yaml similarity index 100% rename from providers/viper/test/config-sub.yaml rename to providers/viper/testdata/config-sub.yaml diff --git a/providers/viper/testdata/config-tarantool3.yaml b/providers/viper/testdata/config-tarantool3.yaml new file mode 100644 index 0000000..ca96608 --- /dev/null +++ b/providers/viper/testdata/config-tarantool3.yaml @@ -0,0 +1,62 @@ +credentials: + users: + replicator: + password: 'topsecret' + roles: [replication] + storage: + password: 'secret' + roles: [sharding] + +iproto: + advertise: + peer: + login: replicator + sharding: + login: storage + +sharding: + bucket_count: 1000 + +groups: + storages: + app: + module: storage + sharding: + roles: [storage] + replication: + failover: manual + replicasets: + storage-a: + leader: storage-a-001 + instances: + storage-a-001: + iproto: + listen: + - uri: '127.0.0.1:3302' + storage-a-002: + iproto: + listen: + - uri: '127.0.0.1:3303' + storage-b: + leader: storage-b-001 + instances: + storage-b-001: + iproto: + listen: + - uri: '127.0.0.1:3304' + storage-b-002: + iproto: + listen: + - uri: '127.0.0.1:3305' + routers: + app: + module: router + sharding: + roles: [router] + replicasets: + router-a: + instances: + router-a-001: + iproto: + listen: + - uri: '127.0.0.1:3301' diff --git a/topology.go b/topology.go index 56fabbb..5ebc097 100644 --- a/topology.go +++ b/topology.go @@ -10,7 +10,7 @@ import ( ) var ( - ErrReplicasetExists = fmt.Errorf("replicaset exists") + ErrReplicasetExists = fmt.Errorf("replicaset already exists") ErrReplicasetNotExists = fmt.Errorf("replicaset not exists") )