etcdserver: apply config change on cluster store
This commit is contained in:
@ -16,6 +16,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ClusterStore interface {
|
type ClusterStore interface {
|
||||||
|
Create(m Member)
|
||||||
Get() Cluster
|
Get() Cluster
|
||||||
Delete(id int64)
|
Delete(id int64)
|
||||||
}
|
}
|
||||||
@ -27,14 +28,14 @@ type clusterStore struct {
|
|||||||
func NewClusterStore(st store.Store, c Cluster) ClusterStore {
|
func NewClusterStore(st store.Store, c Cluster) ClusterStore {
|
||||||
cls := &clusterStore{Store: st}
|
cls := &clusterStore{Store: st}
|
||||||
for _, m := range c {
|
for _, m := range c {
|
||||||
cls.add(*m)
|
cls.Create(*m)
|
||||||
}
|
}
|
||||||
return cls
|
return cls
|
||||||
}
|
}
|
||||||
|
|
||||||
// add puts a new Member into the store.
|
// Create puts a new Member into the store.
|
||||||
// A Member with a matching id must not exist.
|
// A Member with a matching id must not exist.
|
||||||
func (s *clusterStore) add(m Member) {
|
func (s *clusterStore) Create(m Member) {
|
||||||
b, err := json.Marshal(m)
|
b, err := json.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("marshal peer info error: %v", err)
|
log.Panicf("marshal peer info error: %v", err)
|
||||||
|
@ -9,6 +9,28 @@ import (
|
|||||||
"github.com/coreos/etcd/store"
|
"github.com/coreos/etcd/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestClusterStoreCreate(t *testing.T) {
|
||||||
|
st := &storeRecorder{}
|
||||||
|
ps := &clusterStore{Store: st}
|
||||||
|
ps.Create(Member{Name: "node", ID: 1})
|
||||||
|
|
||||||
|
wactions := []action{
|
||||||
|
{
|
||||||
|
name: "Create",
|
||||||
|
params: []interface{}{
|
||||||
|
machineKVPrefix + "1",
|
||||||
|
false,
|
||||||
|
`{"ID":1,"Name":"node","PeerURLs":null,"ClientURLs":null}`,
|
||||||
|
false,
|
||||||
|
store.Permanent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if g := st.Action(); !reflect.DeepEqual(g, wactions) {
|
||||||
|
t.Error("actions = %v, want %v", g, wactions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestClusterStoreGet(t *testing.T) {
|
func TestClusterStoreGet(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
mems []Member
|
mems []Member
|
||||||
|
@ -1238,6 +1238,8 @@ type fakeCluster struct {
|
|||||||
members []etcdserver.Member
|
members []etcdserver.Member
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *fakeCluster) Create(m etcdserver.Member) { return }
|
||||||
|
|
||||||
func (c *fakeCluster) Get() etcdserver.Cluster {
|
func (c *fakeCluster) Get() etcdserver.Cluster {
|
||||||
cl := &etcdserver.Cluster{}
|
cl := &etcdserver.Cluster{}
|
||||||
cl.AddSlice(c.members)
|
cl.AddSlice(c.members)
|
||||||
|
@ -250,13 +250,13 @@ func (s *EtcdServer) run() {
|
|||||||
if err := r.Unmarshal(e.Data); err != nil {
|
if err := r.Unmarshal(e.Data); err != nil {
|
||||||
panic("TODO: this is bad, what do we do about it?")
|
panic("TODO: this is bad, what do we do about it?")
|
||||||
}
|
}
|
||||||
s.w.Trigger(r.ID, s.apply(r))
|
s.w.Trigger(r.ID, s.applyRequest(r))
|
||||||
case raftpb.EntryConfChange:
|
case raftpb.EntryConfChange:
|
||||||
var cc raftpb.ConfChange
|
var cc raftpb.ConfChange
|
||||||
if err := cc.Unmarshal(e.Data); err != nil {
|
if err := cc.Unmarshal(e.Data); err != nil {
|
||||||
panic("TODO: this is bad, what do we do about it?")
|
panic("TODO: this is bad, what do we do about it?")
|
||||||
}
|
}
|
||||||
s.node.ApplyConfChange(cc)
|
s.applyConfChange(cc)
|
||||||
s.w.Trigger(cc.ID, nil)
|
s.w.Trigger(cc.ID, nil)
|
||||||
default:
|
default:
|
||||||
panic("unexpected entry type")
|
panic("unexpected entry type")
|
||||||
@ -360,17 +360,21 @@ func (s *EtcdServer) Do(ctx context.Context, r pb.Request) (Response, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *EtcdServer) AddNode(ctx context.Context, id int64, context []byte) error {
|
func (s *EtcdServer) AddMember(ctx context.Context, memb Member) error {
|
||||||
|
b, err := json.Marshal(memb)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
cc := raftpb.ConfChange{
|
cc := raftpb.ConfChange{
|
||||||
ID: GenID(),
|
ID: GenID(),
|
||||||
Type: raftpb.ConfChangeAddNode,
|
Type: raftpb.ConfChangeAddNode,
|
||||||
NodeID: id,
|
NodeID: memb.ID,
|
||||||
Context: context,
|
Context: b,
|
||||||
}
|
}
|
||||||
return s.configure(ctx, cc)
|
return s.configure(ctx, cc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *EtcdServer) RemoveNode(ctx context.Context, id int64) error {
|
func (s *EtcdServer) RemoveMember(ctx context.Context, id int64) error {
|
||||||
cc := raftpb.ConfChange{
|
cc := raftpb.ConfChange{
|
||||||
ID: GenID(),
|
ID: GenID(),
|
||||||
Type: raftpb.ConfChangeRemoveNode,
|
Type: raftpb.ConfChangeRemoveNode,
|
||||||
@ -477,9 +481,9 @@ func getExpirationTime(r *pb.Request) time.Time {
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply interprets r as a call to store.X and returns a Response interpreted
|
// applyRequest interprets r as a call to store.X and returns a Response interpreted
|
||||||
// from store.Event
|
// from store.Event
|
||||||
func (s *EtcdServer) apply(r pb.Request) Response {
|
func (s *EtcdServer) applyRequest(r pb.Request) Response {
|
||||||
f := func(ev *store.Event, err error) Response {
|
f := func(ev *store.Event, err error) Response {
|
||||||
return Response{Event: ev, err: err}
|
return Response{Event: ev, err: err}
|
||||||
}
|
}
|
||||||
@ -518,6 +522,22 @@ func (s *EtcdServer) apply(r pb.Request) Response {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *EtcdServer) applyConfChange(cc raftpb.ConfChange) {
|
||||||
|
s.node.ApplyConfChange(cc)
|
||||||
|
switch cc.Type {
|
||||||
|
case raftpb.ConfChangeAddNode:
|
||||||
|
var m Member
|
||||||
|
if err := json.Unmarshal(cc.Context, &m); err != nil {
|
||||||
|
panic("unexpected unmarshal error")
|
||||||
|
}
|
||||||
|
s.ClusterStore.Create(m)
|
||||||
|
case raftpb.ConfChangeRemoveNode:
|
||||||
|
s.ClusterStore.Delete(cc.NodeID)
|
||||||
|
default:
|
||||||
|
panic("unexpected ConfChange type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: non-blocking snapshot
|
// TODO: non-blocking snapshot
|
||||||
func (s *EtcdServer) snapshot(snapi int64, snapnodes []int64) {
|
func (s *EtcdServer) snapshot(snapi int64, snapnodes []int64) {
|
||||||
d, err := s.store.Save()
|
d, err := s.store.Save()
|
||||||
|
@ -128,7 +128,7 @@ func TestDoBadLocalAction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApply(t *testing.T) {
|
func TestApplyRequest(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
req pb.Request
|
req pb.Request
|
||||||
|
|
||||||
@ -356,7 +356,7 @@ func TestApply(t *testing.T) {
|
|||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
st := &storeRecorder{}
|
st := &storeRecorder{}
|
||||||
srv := &EtcdServer{store: st}
|
srv := &EtcdServer{store: st}
|
||||||
resp := srv.apply(tt.req)
|
resp := srv.applyRequest(tt.req)
|
||||||
|
|
||||||
if !reflect.DeepEqual(resp, tt.wresp) {
|
if !reflect.DeepEqual(resp, tt.wresp) {
|
||||||
t.Errorf("#%d: resp = %+v, want %+v", i, resp, tt.wresp)
|
t.Errorf("#%d: resp = %+v, want %+v", i, resp, tt.wresp)
|
||||||
@ -786,17 +786,20 @@ func TestRecvSlowSnapshot(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestAddNode tests AddNode can propose and perform node addition.
|
// TestAddMember tests AddMember can propose and perform node addition.
|
||||||
func TestAddNode(t *testing.T) {
|
func TestAddMember(t *testing.T) {
|
||||||
n := newNodeConfChangeCommitterRecorder()
|
n := newNodeConfChangeCommitterRecorder()
|
||||||
|
cs := &clusterStoreRecorder{}
|
||||||
s := &EtcdServer{
|
s := &EtcdServer{
|
||||||
node: n,
|
node: n,
|
||||||
store: &storeRecorder{},
|
store: &storeRecorder{},
|
||||||
send: func(_ []raftpb.Message) {},
|
send: func(_ []raftpb.Message) {},
|
||||||
storage: &storageRecorder{},
|
storage: &storageRecorder{},
|
||||||
|
ClusterStore: cs,
|
||||||
}
|
}
|
||||||
s.start()
|
s.start()
|
||||||
s.AddNode(context.TODO(), 1, []byte("foo"))
|
m := Member{ID: 1, PeerURLs: []string{"foo"}}
|
||||||
|
s.AddMember(context.TODO(), m)
|
||||||
gaction := n.Action()
|
gaction := n.Action()
|
||||||
s.Stop()
|
s.Stop()
|
||||||
|
|
||||||
@ -804,19 +807,26 @@ func TestAddNode(t *testing.T) {
|
|||||||
if !reflect.DeepEqual(gaction, wactions) {
|
if !reflect.DeepEqual(gaction, wactions) {
|
||||||
t.Errorf("action = %v, want %v", gaction, wactions)
|
t.Errorf("action = %v, want %v", gaction, wactions)
|
||||||
}
|
}
|
||||||
|
wcsactions := []action{{name: "Create", params: []interface{}{m}}}
|
||||||
|
if g := cs.Action(); !reflect.DeepEqual(g, wcsactions) {
|
||||||
|
t.Errorf("csaction = %v, want %v", g, wcsactions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRemoveNode tests RemoveNode can propose and perform node removal.
|
// TestRemoveMember tests RemoveMember can propose and perform node removal.
|
||||||
func TestRemoveNode(t *testing.T) {
|
func TestRemoveMember(t *testing.T) {
|
||||||
n := newNodeConfChangeCommitterRecorder()
|
n := newNodeConfChangeCommitterRecorder()
|
||||||
|
cs := &clusterStoreRecorder{}
|
||||||
s := &EtcdServer{
|
s := &EtcdServer{
|
||||||
node: n,
|
node: n,
|
||||||
store: &storeRecorder{},
|
store: &storeRecorder{},
|
||||||
send: func(_ []raftpb.Message) {},
|
send: func(_ []raftpb.Message) {},
|
||||||
storage: &storageRecorder{},
|
storage: &storageRecorder{},
|
||||||
|
ClusterStore: cs,
|
||||||
}
|
}
|
||||||
s.start()
|
s.start()
|
||||||
s.RemoveNode(context.TODO(), 1)
|
id := int64(1)
|
||||||
|
s.RemoveMember(context.TODO(), id)
|
||||||
gaction := n.Action()
|
gaction := n.Action()
|
||||||
s.Stop()
|
s.Stop()
|
||||||
|
|
||||||
@ -824,6 +834,10 @@ func TestRemoveNode(t *testing.T) {
|
|||||||
if !reflect.DeepEqual(gaction, wactions) {
|
if !reflect.DeepEqual(gaction, wactions) {
|
||||||
t.Errorf("action = %v, want %v", gaction, wactions)
|
t.Errorf("action = %v, want %v", gaction, wactions)
|
||||||
}
|
}
|
||||||
|
wcsactions := []action{{name: "Delete", params: []interface{}{id}}}
|
||||||
|
if g := cs.Action(); !reflect.DeepEqual(g, wcsactions) {
|
||||||
|
t.Errorf("csaction = %v, want %v", g, wcsactions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestServerStopItself tests that if node sends out Ready with ShouldStop,
|
// TestServerStopItself tests that if node sends out Ready with ShouldStop,
|
||||||
@ -1230,6 +1244,21 @@ func (w *waitWithResponse) Register(id int64) <-chan interface{} {
|
|||||||
}
|
}
|
||||||
func (w *waitWithResponse) Trigger(id int64, x interface{}) {}
|
func (w *waitWithResponse) Trigger(id int64, x interface{}) {}
|
||||||
|
|
||||||
|
type clusterStoreRecorder struct {
|
||||||
|
recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *clusterStoreRecorder) Create(m Member) {
|
||||||
|
cs.record(action{name: "Create", params: []interface{}{m}})
|
||||||
|
}
|
||||||
|
func (cs *clusterStoreRecorder) Get() Cluster {
|
||||||
|
cs.record(action{name: "Get"})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (cs *clusterStoreRecorder) Delete(id int64) {
|
||||||
|
cs.record(action{name: "Delete", params: []interface{}{id}})
|
||||||
|
}
|
||||||
|
|
||||||
func mustClusterStore(t *testing.T, membs []Member) ClusterStore {
|
func mustClusterStore(t *testing.T, membs []Member) ClusterStore {
|
||||||
c := Cluster{}
|
c := Cluster{}
|
||||||
if err := c.AddSlice(membs); err != nil {
|
if err := c.AddSlice(membs); err != nil {
|
||||||
|
Reference in New Issue
Block a user