mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00

The split helps to save them in different key paths, because they have distinct life cycle on update.
213 lines
5.2 KiB
Go
213 lines
5.2 KiB
Go
package etcdserver
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
etcdErr "github.com/coreos/etcd/error"
|
|
"github.com/coreos/etcd/store"
|
|
)
|
|
|
|
func TestClusterStoreAdd(t *testing.T) {
|
|
st := &storeRecorder{}
|
|
ps := &clusterStore{Store: st}
|
|
ps.Add(newTestMember(1, nil, "node1", nil))
|
|
|
|
wactions := []action{
|
|
{
|
|
name: "Create",
|
|
params: []interface{}{
|
|
machineKVPrefix + "1/raftAttributes",
|
|
false,
|
|
`{"PeerURLs":null}`,
|
|
false,
|
|
store.Permanent,
|
|
},
|
|
},
|
|
{
|
|
name: "Create",
|
|
params: []interface{}{
|
|
machineKVPrefix + "1/attributes",
|
|
false,
|
|
`{"Name":"node1","ClientURLs":null}`,
|
|
false,
|
|
store.Permanent,
|
|
},
|
|
},
|
|
}
|
|
if g := st.Action(); !reflect.DeepEqual(g, wactions) {
|
|
t.Errorf("actions = %v, want %v", g, wactions)
|
|
}
|
|
}
|
|
|
|
func TestClusterStoreGet(t *testing.T) {
|
|
tests := []struct {
|
|
mems []Member
|
|
wmems []Member
|
|
}{
|
|
{
|
|
[]Member{newTestMember(1, nil, "node1", nil)},
|
|
[]Member{newTestMember(1, nil, "node1", nil)},
|
|
},
|
|
{
|
|
[]Member{},
|
|
[]Member{},
|
|
},
|
|
{
|
|
[]Member{
|
|
newTestMember(1, nil, "node1", nil),
|
|
newTestMember(2, nil, "node2", nil),
|
|
},
|
|
[]Member{
|
|
newTestMember(1, nil, "node1", nil),
|
|
newTestMember(2, nil, "node2", nil),
|
|
},
|
|
},
|
|
{
|
|
[]Member{
|
|
newTestMember(2, nil, "node2", nil),
|
|
newTestMember(1, nil, "node1", nil),
|
|
},
|
|
[]Member{
|
|
newTestMember(1, nil, "node1", nil),
|
|
newTestMember(2, nil, "node2", nil),
|
|
},
|
|
},
|
|
}
|
|
for i, tt := range tests {
|
|
c := Cluster{}
|
|
err := c.AddSlice(tt.mems)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
cs := NewClusterStore(newGetAllStore(), c)
|
|
|
|
if g := cs.Get(); !reflect.DeepEqual(g, c) {
|
|
t.Errorf("#%d: mems = %v, want %v", i, g, c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestClusterStoreDelete(t *testing.T) {
|
|
st := newStoreGetAllAndDeleteRecorder()
|
|
c := Cluster{}
|
|
c.Add(newTestMember(1, nil, "node1", nil))
|
|
cs := NewClusterStore(st, c)
|
|
cs.Remove(1)
|
|
|
|
wdeletes := []string{machineKVPrefix + "1"}
|
|
if !reflect.DeepEqual(st.deletes, wdeletes) {
|
|
t.Error("deletes = %v, want %v", st.deletes, wdeletes)
|
|
}
|
|
}
|
|
|
|
func TestNodeToMemberFail(t *testing.T) {
|
|
tests := []*store.NodeExtern{
|
|
{Key: "/1234", Nodes: []*store.NodeExtern{
|
|
{Key: "/1234/strange"},
|
|
}},
|
|
{Key: "/1234", Nodes: []*store.NodeExtern{
|
|
{Key: "/1234/dynamic", Value: stringp("garbage")},
|
|
}},
|
|
{Key: "/1234", Nodes: []*store.NodeExtern{
|
|
{Key: "/1234/dynamic", Value: stringp(`{"PeerURLs":null}`)},
|
|
}},
|
|
{Key: "/1234", Nodes: []*store.NodeExtern{
|
|
{Key: "/1234/dynamic", Value: stringp(`{"PeerURLs":null}`)},
|
|
{Key: "/1234/strange"},
|
|
}},
|
|
{Key: "/1234", Nodes: []*store.NodeExtern{
|
|
{Key: "/1234/dynamic", Value: stringp(`{"PeerURLs":null}`)},
|
|
{Key: "/1234/static", Value: stringp("garbage")},
|
|
}},
|
|
{Key: "/1234", Nodes: []*store.NodeExtern{
|
|
{Key: "/1234/dynamic", Value: stringp(`{"PeerURLs":null}`)},
|
|
{Key: "/1234/static", Value: stringp(`{"Name":"node1","ClientURLs":null}`)},
|
|
{Key: "/1234/strange"},
|
|
}},
|
|
}
|
|
for i, tt := range tests {
|
|
if _, err := nodeToMember(tt); err == nil {
|
|
t.Errorf("#%d: unexpected nil error", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNodeToMember(t *testing.T) {
|
|
n := &store.NodeExtern{Key: "/1234", Nodes: []*store.NodeExtern{
|
|
{Key: "/1234/attributes", Value: stringp(`{"Name":"node1","ClientURLs":null}`)},
|
|
{Key: "/1234/raftAttributes", Value: stringp(`{"PeerURLs":null}`)},
|
|
}}
|
|
wm := Member{ID: 0x1234, RaftAttributes: RaftAttributes{}, Attributes: Attributes{Name: "node1"}}
|
|
m, err := nodeToMember(n)
|
|
if err != nil {
|
|
t.Fatalf("unexpected nodeToMember error: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(m, wm) {
|
|
t.Errorf("member = %+v, want %+v", m, wm)
|
|
}
|
|
}
|
|
|
|
// simpleStore implements basic create and get.
|
|
type simpleStore struct {
|
|
storeRecorder
|
|
st map[string]string
|
|
}
|
|
|
|
func (s *simpleStore) Create(key string, _ bool, value string, _ bool, _ time.Time) (*store.Event, error) {
|
|
if s.st == nil {
|
|
s.st = make(map[string]string)
|
|
}
|
|
s.st[key] = value
|
|
return nil, nil
|
|
}
|
|
func (s *simpleStore) Get(key string, _, _ bool) (*store.Event, error) {
|
|
val, ok := s.st[key]
|
|
if !ok {
|
|
return nil, etcdErr.NewError(etcdErr.EcodeKeyNotFound, "", 0)
|
|
}
|
|
ev := &store.Event{Node: &store.NodeExtern{Key: key, Value: stringp(val)}}
|
|
return ev, nil
|
|
}
|
|
|
|
// getAllStore embeds simpleStore, and makes Get return all keys sorted.
|
|
// It uses real store because it uses lots of logic in store and is not easy
|
|
// to mock.
|
|
// TODO: use mock one to do testing
|
|
type getAllStore struct {
|
|
store.Store
|
|
}
|
|
|
|
func newGetAllStore() *getAllStore {
|
|
return &getAllStore{store.New()}
|
|
}
|
|
|
|
type storeGetAllAndDeleteRecorder struct {
|
|
*getAllStore
|
|
deletes []string
|
|
}
|
|
|
|
func newStoreGetAllAndDeleteRecorder() *storeGetAllAndDeleteRecorder {
|
|
return &storeGetAllAndDeleteRecorder{getAllStore: newGetAllStore()}
|
|
}
|
|
|
|
func (s *storeGetAllAndDeleteRecorder) Delete(key string, _, _ bool) (*store.Event, error) {
|
|
s.deletes = append(s.deletes, key)
|
|
return nil, nil
|
|
}
|
|
|
|
func newTestMember(id uint64, peerURLs []string, name string, clientURLs []string) Member {
|
|
return Member{
|
|
ID: id,
|
|
RaftAttributes: RaftAttributes{PeerURLs: peerURLs},
|
|
Attributes: Attributes{Name: name, ClientURLs: clientURLs},
|
|
}
|
|
}
|
|
|
|
func newTestMemberp(id uint64, peerURLs []string, name string, clientURLs []string) *Member {
|
|
m := newTestMember(id, peerURLs, name, clientURLs)
|
|
return &m
|
|
}
|