package cluster import ( gogob "encoding/gob" "fmt" "io" "time" "github.com/hashicorp/raft" ) const ( retainSnapshotCount = 2 raftTimeout = 10 * time.Second ) type Op uint8 const ( SET Op = iota DELETE ) type raftCmd struct { Op Op K string V []byte } type fsm KV func (f *fsm) Apply(l *raft.Log) interface{} { var c raftCmd if err := gob.Unmarshal(l.Data, &c); err != nil { panic(fmt.Sprintf("failed to unmarshal command: %s", err.Error())) } switch c.Op { case SET: f.applySet(c.K, c.V) case DELETE: f.applyDelete(c.K) default: panic(fmt.Sprintf("unrecognized command op: %s", c.Op)) } return nil } func (f *fsm) Snapshot() (raft.FSMSnapshot, error) { f.mu.Lock() defer f.mu.Unlock() m := make(kv) for k, v := range f.m { m[k] = v } return &fsmSnapshot{m: m}, nil } func (f *fsm) Restore(rc io.ReadCloser) error { o := make(kv) if err := gogob.NewDecoder(rc).Decode(&o); err != nil { return err } f.m = o return nil } func (f *fsm) applySet(k string, v []byte) { f.mu.Lock() f.m[k] = v f.mu.Unlock() } func (f *fsm) applyDelete(k string) { f.mu.Lock() delete(f.m, k) f.mu.Unlock() } type fsmSnapshot struct { m kv } func (f *fsmSnapshot) Persist(sink raft.SnapshotSink) error { err := func() error { err := gogob.NewEncoder(sink).Encode(f.m) if err != nil { return err } return sink.Close() }() if err != nil { sink.Cancel() } return err } func (f *fsmSnapshot) Release() {}