Merge pull request #1157 from jonboulle/1019_etcdserver

etcdserver: extend storeRecorder to test parameters
release-2.0
Jonathan Boulle 2014-09-24 16:58:57 -07:00
commit 20ac7d6732
2 changed files with 327 additions and 132 deletions

View File

@ -300,7 +300,8 @@ func getExpirationTime(r *pb.Request) time.Time {
return t
}
// apply interprets r as a call to store.X and returns an Response interpreted from store.Event
// apply interprets r as a call to store.X and returns a Response interpreted
// from store.Event
func (s *EtcdServer) apply(r pb.Request) Response {
f := func(ev *store.Event, err error) Response {
return Response{Event: ev, err: err}
@ -315,9 +316,8 @@ func (s *EtcdServer) apply(r pb.Request) Response {
case existsSet:
if exists {
return f(s.Store.Update(r.Path, r.Val, expr))
} else {
return f(s.Store.Create(r.Path, r.Dir, r.Val, false, expr))
}
return f(s.Store.Create(r.Path, r.Dir, r.Val, false, expr))
case r.PrevIndex > 0 || r.PrevValue != "":
return f(s.Store.CompareAndSwap(r.Path, r.PrevValue, r.PrevIndex, r.Val, expr))
default:

View File

@ -49,21 +49,27 @@ func TestDoLocalAction(t *testing.T) {
tests := []struct {
req pb.Request
wresp Response
werr error
waction []string
wresp Response
werr error
wactions []action
}{
{
pb.Request{Method: "GET", Id: 1, Wait: true},
Response{Watcher: &stubWatcher{}}, nil, []string{"Watch"},
Response{Watcher: &stubWatcher{}}, nil, []action{action{name: "Watch"}},
},
{
pb.Request{Method: "GET", Id: 1},
Response{Event: &store.Event{}}, nil, []string{"Get"},
Response{Event: &store.Event{}}, nil,
[]action{
action{
name: "Get",
params: []interface{}{"", false, false},
},
},
},
{
pb.Request{Method: "BADMETHOD", Id: 1},
Response{}, ErrUnknownMethod, []string{},
Response{}, ErrUnknownMethod, []action{},
},
}
for i, tt := range tests {
@ -77,9 +83,9 @@ func TestDoLocalAction(t *testing.T) {
if !reflect.DeepEqual(resp, tt.wresp) {
t.Errorf("#%d: resp = %+v, want %+v", i, resp, tt.wresp)
}
action := st.Action()
if !reflect.DeepEqual(action, tt.waction) {
t.Errorf("#%d: action = %+v, want %+v", i, action, tt.waction)
gaction := st.Action()
if !reflect.DeepEqual(gaction, tt.wactions) {
t.Errorf("#%d: action = %+v, want %+v", i, gaction, tt.wactions)
}
}
}
@ -91,15 +97,15 @@ func TestDoBadLocalAction(t *testing.T) {
tests := []struct {
req pb.Request
waction []string
wactions []action
}{
{
pb.Request{Method: "GET", Id: 1, Wait: true},
[]string{"Watch"},
[]action{action{name: "Watch"}},
},
{
pb.Request{Method: "GET", Id: 1},
[]string{"Get"},
[]action{action{name: "Get"}},
},
}
for i, tt := range tests {
@ -113,9 +119,9 @@ func TestDoBadLocalAction(t *testing.T) {
if !reflect.DeepEqual(resp, Response{}) {
t.Errorf("#%d: resp = %+v, want %+v", i, resp, Response{})
}
action := st.Action()
if !reflect.DeepEqual(action, tt.waction) {
t.Errorf("#%d: action = %+v, want %+v", i, action, tt.waction)
gaction := st.Action()
if !reflect.DeepEqual(gaction, tt.wactions) {
t.Errorf("#%d: action = %+v, want %+v", i, gaction, tt.wactions)
}
}
}
@ -124,64 +130,224 @@ func TestApply(t *testing.T) {
tests := []struct {
req pb.Request
wresp Response
waction []string
wresp Response
wactions []action
}{
// POST ==> Create
{
pb.Request{Method: "POST", Id: 1},
Response{Event: &store.Event{}}, []string{"Create"},
Response{Event: &store.Event{}},
[]action{
action{
name: "Create",
params: []interface{}{"", false, "", true, time.Time{}},
},
},
},
// POST ==> Create, with expiration
{
pb.Request{Method: "PUT", Id: 1, PrevExist: boolp(true), PrevIndex: 1},
Response{Event: &store.Event{}}, []string{"Update"},
pb.Request{Method: "POST", Id: 1, Expiration: 1337},
Response{Event: &store.Event{}},
[]action{
action{
name: "Create",
params: []interface{}{"", false, "", true, time.Unix(0, 1337)},
},
},
},
// POST ==> Create, with dir
{
pb.Request{Method: "PUT", Id: 1, PrevExist: boolp(false), PrevIndex: 1},
Response{Event: &store.Event{}}, []string{"Create"},
},
{
pb.Request{Method: "PUT", Id: 1, PrevExist: boolp(true)},
Response{Event: &store.Event{}}, []string{"Update"},
},
{
pb.Request{Method: "PUT", Id: 1, PrevExist: boolp(false)},
Response{Event: &store.Event{}}, []string{"Create"},
},
{
pb.Request{Method: "PUT", Id: 1, PrevIndex: 1},
Response{Event: &store.Event{}}, []string{"CompareAndSwap"},
},
{
pb.Request{Method: "PUT", Id: 1, PrevValue: "bar"},
Response{Event: &store.Event{}}, []string{"CompareAndSwap"},
pb.Request{Method: "POST", Id: 1, Dir: true},
Response{Event: &store.Event{}},
[]action{
action{
name: "Create",
params: []interface{}{"", true, "", true, time.Time{}},
},
},
},
// PUT ==> Set
{
pb.Request{Method: "PUT", Id: 1},
Response{Event: &store.Event{}}, []string{"Set"},
Response{Event: &store.Event{}},
[]action{
action{
name: "Set",
params: []interface{}{"", false, "", time.Time{}},
},
},
},
// PUT ==> Set, with dir
{
pb.Request{Method: "DELETE", Id: 1, PrevIndex: 1},
Response{Event: &store.Event{}}, []string{"CompareAndDelete"},
pb.Request{Method: "PUT", Id: 1, Dir: true},
Response{Event: &store.Event{}},
[]action{
action{
name: "Set",
params: []interface{}{"", true, "", time.Time{}},
},
},
},
// PUT with PrevExist=true ==> Update
{
pb.Request{Method: "DELETE", Id: 1, PrevValue: "bar"},
Response{Event: &store.Event{}}, []string{"CompareAndDelete"},
pb.Request{Method: "PUT", Id: 1, PrevExist: boolp(true)},
Response{Event: &store.Event{}},
[]action{
action{
name: "Update",
params: []interface{}{"", "", time.Time{}},
},
},
},
// PUT with PrevExist=false ==> Create
{
pb.Request{Method: "PUT", Id: 1, PrevExist: boolp(false)},
Response{Event: &store.Event{}},
[]action{
action{
name: "Create",
params: []interface{}{"", false, "", false, time.Time{}},
},
},
},
// PUT with PrevExist=true *and* PrevIndex set ==> Update
// TODO(jonboulle): is this expected?!
{
pb.Request{Method: "PUT", Id: 1, PrevExist: boolp(true), PrevIndex: 1},
Response{Event: &store.Event{}},
[]action{
action{
name: "Update",
params: []interface{}{"", "", time.Time{}},
},
},
},
// PUT with PrevExist=false *and* PrevIndex set ==> Create
// TODO(jonboulle): is this expected?!
{
pb.Request{Method: "PUT", Id: 1, PrevExist: boolp(false), PrevIndex: 1},
Response{Event: &store.Event{}},
[]action{
action{
name: "Create",
params: []interface{}{"", false, "", false, time.Time{}},
},
},
},
// PUT with PrevIndex set ==> CompareAndSwap
{
pb.Request{Method: "PUT", Id: 1, PrevIndex: 1},
Response{Event: &store.Event{}},
[]action{
action{
name: "CompareAndSwap",
params: []interface{}{"", "", uint64(1), "", time.Time{}},
},
},
},
// PUT with PrevValue set ==> CompareAndSwap
{
pb.Request{Method: "PUT", Id: 1, PrevValue: "bar"},
Response{Event: &store.Event{}},
[]action{
action{
name: "CompareAndSwap",
params: []interface{}{"", "bar", uint64(0), "", time.Time{}},
},
},
},
// PUT with PrevIndex and PrevValue set ==> CompareAndSwap
{
pb.Request{Method: "PUT", Id: 1, PrevIndex: 1, PrevValue: "bar"},
Response{Event: &store.Event{}},
[]action{
action{
name: "CompareAndSwap",
params: []interface{}{"", "bar", uint64(1), "", time.Time{}},
},
},
},
// DELETE ==> Delete
{
pb.Request{Method: "DELETE", Id: 1},
Response{Event: &store.Event{}}, []string{"Delete"},
Response{Event: &store.Event{}},
[]action{
action{
name: "Delete",
params: []interface{}{"", false, false},
},
},
},
// DELETE with PrevIndex set ==> CompareAndDelete
{
pb.Request{Method: "DELETE", Id: 1, PrevIndex: 1},
Response{Event: &store.Event{}},
[]action{
action{
name: "CompareAndDelete",
params: []interface{}{"", "", uint64(1)},
},
},
},
// DELETE with PrevValue set ==> CompareAndDelete
{
pb.Request{Method: "DELETE", Id: 1, PrevValue: "bar"},
Response{Event: &store.Event{}},
[]action{
action{
name: "CompareAndDelete",
params: []interface{}{"", "bar", uint64(0)},
},
},
},
// DELETE with PrevIndex *and* PrevValue set ==> CompareAndDelete
{
pb.Request{Method: "DELETE", Id: 1, PrevIndex: 5, PrevValue: "bar"},
Response{Event: &store.Event{}},
[]action{
action{
name: "CompareAndDelete",
params: []interface{}{"", "bar", uint64(5)},
},
},
},
// QGET ==> Get
{
pb.Request{Method: "QGET", Id: 1},
Response{Event: &store.Event{}}, []string{"Get"},
Response{Event: &store.Event{}},
[]action{
action{
name: "Get",
params: []interface{}{"", false, false},
},
},
},
// SYNC ==> DeleteExpiredKeys
{
pb.Request{Method: "SYNC", Id: 1},
Response{}, []string{"DeleteExpiredKeys"},
Response{},
[]action{
action{
name: "DeleteExpiredKeys",
params: []interface{}{time.Unix(0, 0)},
},
},
},
{
pb.Request{Method: "SYNC", Id: 1, Time: 12345},
Response{},
[]action{
action{
name: "DeleteExpiredKeys",
params: []interface{}{time.Unix(0, 12345)},
},
},
},
// Unknown method - error
{
pb.Request{Method: "BADMETHOD", Id: 1},
Response{err: ErrUnknownMethod}, []string{},
Response{err: ErrUnknownMethod},
[]action{},
},
}
@ -193,9 +359,9 @@ func TestApply(t *testing.T) {
if !reflect.DeepEqual(resp, tt.wresp) {
t.Errorf("#%d: resp = %+v, want %+v", i, resp, tt.wresp)
}
action := st.Action()
if !reflect.DeepEqual(action, tt.waction) {
t.Errorf("#%d: action = %+v, want %+v", i, action, tt.waction)
gaction := st.Action()
if !reflect.DeepEqual(gaction, tt.wactions) {
t.Errorf("#%d: action = %#v, want %#v", i, gaction, tt.wactions)
}
}
}
@ -342,14 +508,14 @@ func TestDoProposalCancelled(t *testing.T) {
cancel()
<-done
action := st.Action()
if len(action) != 0 {
t.Errorf("len(action) = %v, want 0", len(action))
gaction := st.Action()
if len(gaction) != 0 {
t.Errorf("len(action) = %v, want 0", len(gaction))
}
if err != context.Canceled {
t.Fatalf("err = %v, want %v", err, context.Canceled)
}
w := []string{"Register1", "Trigger1"}
w := []action{action{name: "Register1"}, action{name: "Trigger1"}}
if !reflect.DeepEqual(wait.action, w) {
t.Errorf("wait.action = %+v, want %+v", wait.action, w)
}
@ -438,7 +604,7 @@ func TestSyncTimeout(t *testing.T) {
// give time for goroutine in sync to cancel
// TODO: use fake clock
testutil.ForceGosched()
w := []string{"Propose blocked"}
w := []action{action{name: "Propose blocked"}}
if g := n.Action(); !reflect.DeepEqual(g, w) {
t.Errorf("action = %v, want %v", g, w)
}
@ -511,20 +677,20 @@ func TestSnapshot(t *testing.T) {
}
s.snapshot()
action := st.Action()
if len(action) != 1 {
t.Fatalf("len(action) = %d, want 1", len(action))
gaction := st.Action()
if len(gaction) != 1 {
t.Fatalf("len(action) = %d, want 1", len(gaction))
}
if action[0] != "Save" {
t.Errorf("action = %s, want Save", action[0])
if !reflect.DeepEqual(gaction[0], action{name: "Save"}) {
t.Errorf("action = %s, want Save", gaction[0])
}
action = p.Action()
if len(action) != 1 {
t.Fatalf("len(action) = %d, want 1", len(action))
gaction = p.Action()
if len(gaction) != 1 {
t.Fatalf("len(action) = %d, want 1", len(gaction))
}
if action[0] != "Cut" {
t.Errorf("action = %s, want Cut", action[0])
if !reflect.DeepEqual(gaction[0], action{name: "Cut"}) {
t.Errorf("action = %s, want Cut", gaction[0])
}
}
@ -550,14 +716,14 @@ func TestTriggerSnap(t *testing.T) {
time.Sleep(time.Millisecond)
s.Stop()
action := p.Action()
gaction := p.Action()
// each operation is recorded as a Save
// Nop + SnapCount * Puts + Cut + SaveSnap = Save + SnapCount * Save + Cut + SaveSnap
if len(action) != 3+int(s.SnapCount) {
t.Fatalf("len(action) = %d, want %d", len(action), 3+int(s.SnapCount))
if len(gaction) != 3+int(s.SnapCount) {
t.Fatalf("len(action) = %d, want %d", len(gaction), 3+int(s.SnapCount))
}
if action[12] != "SaveSnap" {
t.Errorf("action = %s, want SaveSnap", action[12])
if !reflect.DeepEqual(gaction[12], action{name: "SaveSnap"}) {
t.Errorf("action = %s, want SaveSnap", gaction[12])
}
}
@ -580,13 +746,13 @@ func TestRecvSnapshot(t *testing.T) {
testutil.ForceGosched()
s.Stop()
waction := []string{"Recovery"}
if g := st.Action(); !reflect.DeepEqual(g, waction) {
t.Errorf("store action = %v, want %v", g, waction)
wactions := []action{action{name: "Recovery"}}
if g := st.Action(); !reflect.DeepEqual(g, wactions) {
t.Errorf("store action = %v, want %v", g, wactions)
}
waction = []string{"Save", "SaveSnap"}
if g := p.Action(); !reflect.DeepEqual(g, waction) {
t.Errorf("storage action = %v, want %v", g, waction)
wactions = []action{action{name: "Save"}, action{name: "SaveSnap"}}
if g := p.Action(); !reflect.DeepEqual(g, wactions) {
t.Errorf("storage action = %v, want %v", g, wactions)
}
}
@ -629,12 +795,12 @@ func TestAddNode(t *testing.T) {
}
s.Start()
s.AddNode(context.TODO(), 1, []byte("foo"))
action := n.Action()
gaction := n.Action()
s.Stop()
waction := []string{"ProposeConfChange:ConfChangeAddNode", "ApplyConfChange:ConfChangeAddNode"}
if !reflect.DeepEqual(action, waction) {
t.Errorf("action = %v, want %v", action, waction)
wactions := []action{action{name: "ProposeConfChange:ConfChangeAddNode"}, action{name: "ApplyConfChange:ConfChangeAddNode"}}
if !reflect.DeepEqual(gaction, wactions) {
t.Errorf("action = %v, want %v", gaction, wactions)
}
}
@ -649,12 +815,12 @@ func TestRemoveNode(t *testing.T) {
}
s.Start()
s.RemoveNode(context.TODO(), 1)
action := n.Action()
gaction := n.Action()
s.Stop()
waction := []string{"ProposeConfChange:ConfChangeRemoveNode", "ApplyConfChange:ConfChangeRemoveNode"}
if !reflect.DeepEqual(action, waction) {
t.Errorf("action = %v, want %v", action, waction)
wactions := []action{action{name: "ProposeConfChange:ConfChangeRemoveNode"}, action{name: "ApplyConfChange:ConfChangeRemoveNode"}}
if !reflect.DeepEqual(gaction, wactions) {
t.Errorf("action = %v, want %v", gaction, wactions)
}
}
@ -694,20 +860,25 @@ func TestGenID(t *testing.T) {
}
}
type recorder struct {
sync.Mutex
action []string
type action struct {
name string
params []interface{}
}
func (r *recorder) record(action string) {
type recorder struct {
sync.Mutex
actions []action
}
func (r *recorder) record(a action) {
r.Lock()
r.action = append(r.action, action)
r.actions = append(r.actions, a)
r.Unlock()
}
func (r *recorder) Action() []string {
func (r *recorder) Action() []action {
r.Lock()
cpy := make([]string, len(r.action))
copy(cpy, r.action)
cpy := make([]action, len(r.actions))
copy(cpy, r.actions)
r.Unlock()
return cpy
}
@ -718,50 +889,74 @@ type storeRecorder struct {
func (s *storeRecorder) Version() int { return 0 }
func (s *storeRecorder) Index() uint64 { return 0 }
func (s *storeRecorder) Get(_ string, _, _ bool) (*store.Event, error) {
s.record("Get")
func (s *storeRecorder) Get(path string, recursive, sorted bool) (*store.Event, error) {
s.record(action{
name: "Get",
params: []interface{}{path, recursive, sorted},
})
return &store.Event{}, nil
}
func (s *storeRecorder) Set(_ string, _ bool, _ string, _ time.Time) (*store.Event, error) {
s.record("Set")
func (s *storeRecorder) Set(path string, dir bool, val string, expr time.Time) (*store.Event, error) {
s.record(action{
name: "Set",
params: []interface{}{path, dir, val, expr},
})
return &store.Event{}, nil
}
func (s *storeRecorder) Update(_, _ string, _ time.Time) (*store.Event, error) {
s.record("Update")
func (s *storeRecorder) Update(path, val string, expr time.Time) (*store.Event, error) {
s.record(action{
name: "Update",
params: []interface{}{path, val, expr},
})
return &store.Event{}, nil
}
func (s *storeRecorder) Create(_ string, _ bool, _ string, _ bool, _ time.Time) (*store.Event, error) {
s.record("Create")
func (s *storeRecorder) Create(path string, dir bool, val string, uniq bool, exp time.Time) (*store.Event, error) {
s.record(action{
name: "Create",
params: []interface{}{path, dir, val, uniq, exp},
})
return &store.Event{}, nil
}
func (s *storeRecorder) CompareAndSwap(_, _ string, _ uint64, _ string, _ time.Time) (*store.Event, error) {
s.record("CompareAndSwap")
func (s *storeRecorder) CompareAndSwap(path, prevVal string, prevIdx uint64, val string, expr time.Time) (*store.Event, error) {
s.record(action{
name: "CompareAndSwap",
params: []interface{}{path, prevVal, prevIdx, val, expr},
})
return &store.Event{}, nil
}
func (s *storeRecorder) Delete(_ string, _, _ bool) (*store.Event, error) {
s.record("Delete")
func (s *storeRecorder) Delete(path string, dir, recursive bool) (*store.Event, error) {
s.record(action{
name: "Delete",
params: []interface{}{path, dir, recursive},
})
return &store.Event{}, nil
}
func (s *storeRecorder) CompareAndDelete(_, _ string, _ uint64) (*store.Event, error) {
s.record("CompareAndDelete")
func (s *storeRecorder) CompareAndDelete(path, prevVal string, prevIdx uint64) (*store.Event, error) {
s.record(action{
name: "CompareAndDelete",
params: []interface{}{path, prevVal, prevIdx},
})
return &store.Event{}, nil
}
func (s *storeRecorder) Watch(_ string, _, _ bool, _ uint64) (store.Watcher, error) {
s.record("Watch")
s.record(action{name: "Watch"})
return &stubWatcher{}, nil
}
func (s *storeRecorder) Save() ([]byte, error) {
s.record("Save")
s.record(action{name: "Save"})
return nil, nil
}
func (s *storeRecorder) Recovery(b []byte) error {
s.record("Recovery")
s.record(action{name: "Recovery"})
return nil
}
func (s *storeRecorder) TotalTransactions() uint64 { return 0 }
func (s *storeRecorder) JsonStats() []byte { return nil }
func (s *storeRecorder) DeleteExpiredKeys(cutoff time.Time) {
s.record("DeleteExpiredKeys")
s.record(action{
name: "DeleteExpiredKeys",
params: []interface{}{cutoff},
})
}
type stubWatcher struct{}
@ -776,24 +971,24 @@ type errStoreRecorder struct {
}
func (s *errStoreRecorder) Get(_ string, _, _ bool) (*store.Event, error) {
s.record("Get")
s.record(action{name: "Get"})
return nil, s.err
}
func (s *errStoreRecorder) Watch(_ string, _, _ bool, _ uint64) (store.Watcher, error) {
s.record("Watch")
s.record(action{name: "Watch"})
return nil, s.err
}
type waitRecorder struct {
action []string
action []action
}
func (w *waitRecorder) Register(id int64) <-chan interface{} {
w.action = append(w.action, fmt.Sprint("Register", id))
w.action = append(w.action, action{name: fmt.Sprint("Register", id)})
return nil
}
func (w *waitRecorder) Trigger(id int64, x interface{}) {
w.action = append(w.action, fmt.Sprint("Trigger", id))
w.action = append(w.action, action{name: fmt.Sprint("Trigger", id)})
}
func boolp(b bool) *bool { return &b }
@ -805,17 +1000,17 @@ type storageRecorder struct {
}
func (p *storageRecorder) Save(st raftpb.HardState, ents []raftpb.Entry) {
p.record("Save")
p.record(action{name: "Save"})
}
func (p *storageRecorder) Cut() error {
p.record("Cut")
p.record(action{name: "Cut"})
return nil
}
func (p *storageRecorder) SaveSnap(st raftpb.Snapshot) {
if raft.IsEmptySnap(st) {
return
}
p.record("SaveSnap")
p.record(action{name: "SaveSnap"})
}
type readyNode struct {
@ -843,33 +1038,33 @@ type nodeRecorder struct {
}
func (n *nodeRecorder) Tick() {
n.record("Tick")
n.record(action{name: "Tick"})
}
func (n *nodeRecorder) Campaign(ctx context.Context) error {
n.record("Campaign")
n.record(action{name: "Campaign"})
return nil
}
func (n *nodeRecorder) Propose(ctx context.Context, data []byte) error {
n.record("Propose")
n.record(action{name: "Propose"})
return nil
}
func (n *nodeRecorder) ProposeConfChange(ctx context.Context, conf raftpb.ConfChange) error {
n.record("ProposeConfChange")
n.record(action{name: "ProposeConfChange"})
return nil
}
func (n *nodeRecorder) Step(ctx context.Context, msg raftpb.Message) error {
n.record("Step")
n.record(action{name: "Step"})
return nil
}
func (n *nodeRecorder) Ready() <-chan raft.Ready { return nil }
func (n *nodeRecorder) ApplyConfChange(conf raftpb.ConfChange) {
n.record("ApplyConfChange")
n.record(action{name: "ApplyConfChange"})
}
func (n *nodeRecorder) Stop() {
n.record("Stop")
n.record(action{name: "Stop"})
}
func (n *nodeRecorder) Compact(d []byte) {
n.record("Compact")
n.record(action{name: "Compact"})
}
type nodeProposeDataRecorder struct {
@ -898,7 +1093,7 @@ type nodeProposalBlockerRecorder struct {
func (n *nodeProposalBlockerRecorder) Propose(ctx context.Context, data []byte) error {
<-ctx.Done()
n.record("Propose blocked")
n.record(action{name: "Propose blocked"})
return nil
}
@ -918,12 +1113,12 @@ func (n *nodeConfChangeCommitterRecorder) ProposeConfChange(ctx context.Context,
return err
}
n.readyc <- raft.Ready{CommittedEntries: []raftpb.Entry{{Type: raftpb.EntryConfChange, Data: data}}}
n.record("ProposeConfChange:" + conf.Type.String())
n.record(action{name: "ProposeConfChange:" + conf.Type.String()})
return nil
}
func (n *nodeConfChangeCommitterRecorder) Ready() <-chan raft.Ready {
return n.readyc
}
func (n *nodeConfChangeCommitterRecorder) ApplyConfChange(conf raftpb.ConfChange) {
n.record("ApplyConfChange:" + conf.Type.String())
n.record(action{name: "ApplyConfChange:" + conf.Type.String()})
}