').attr('name', basename);\n insert(dir.children('table').children('tbody'), tr);\n tr.append($('| ').text(basename)).\n append(' | ').\n append(' | ').\n append(' | ');\n entry = tr;\n }\n entry.children('td.rev').text('('+ev.Rev+')');\n entry.children('td.body').text(ev.Body);\n entry.addClass('new');\n\n // Kick off the transition in a bit.\n setTimeout(function() { entry.removeClass('new') }, 550);\n}\n\nfunction time_interval(s) {\n if (s < 120) return Math.ceil(s) + 's';\n if (s < 7200) return Math.round(s/60) + 'm';\n return Math.round(s/3600) + 'h';\n}\n\nfunction countdown() {\n var body = $('body');\n var eta = (deadline - new Date().getTime())/1000;\n if (eta < 0) {\n body.removeClass('waiting');\n open();\n } else {\n $('#retrymsg').text(\"retrying in \" + time_interval(eta));\n body.addClass('waiting');\n ti = setTimeout(countdown, Math.max(100, eta*9));\n }\n}\n\nfunction retry() {\n deadline = ((new Date()).getTime()) + retry_interval * 1000;\n retry_interval += (retry_interval + 5) * (Math.random() + .5);\n countdown();\n}\n\nfunction open() {\n var body = $('body');\n var status = $('#status');\n status.text(\"connecting\");\n var ws = new WebSocket(\"ws://\"+location.host+\"/$events\"+path);\n ws.onmessage = function (ev) {\n var jev = JSON.parse(ev.data);\n apply(jev);\n };\n ws.onopen = function(ev) {\n if (retry_interval > 0) {\n body.addClass('wereback');\n setTimeout(function () { body.removeClass('wereback') }, 8000);\n }\n retry_interval = 0;\n status.text('open')\n body.addClass('open').removeClass('loading closed error');\n $('#root > dl > *, #root > table > tbody > *').remove();\n };\n ws.onclose = function(ev) {\n status.text('closed')\n body.addClass('closed').removeClass('loading open error wereback');\n retry();\n };\n ws.onerror = function(ev) {\n status.text('error ' + ev)\n body.addClass('error').removeClass('loading open closed wereback');\n retry();\n };\n}\n\nfunction dr() {\n $('#trynow').click(function() {\n clearTimeout(ti);\n deadline = 0;\n countdown();\n });\n\n if (\"WebSocket\" in window) {\n open();\n } else {\n $('#status').text(\"your browser does not provide websockets\");\n $('body').addClass('error nows').removeClass('loading open closed wereback');\n }\n}\n\nfunction jerr() {\n const m = 'could not load jquery (is your network link down?)';\n document.getElementById('status').innerText = m;\n document.getElementsByTagName('body')[0].className = 'error';\n}\n"
6 |
--------------------------------------------------------------------------------
/consensus/consensus_test.go:
--------------------------------------------------------------------------------
1 | package consensus
2 |
3 | import (
4 | "errors"
5 | "github.com/bmizerany/assert"
6 | "github.com/ha/doozerd/store"
7 | "net"
8 | "testing"
9 | "time"
10 | )
11 |
12 | func TestConsensusOne(t *testing.T) {
13 | self := "test"
14 | const alpha = 1
15 | st := store.New()
16 |
17 | st.Ops <- store.Op{1, store.MustEncodeSet("/ctl/node/"+self+"/addr", "1.2.3.4:5", 0)}
18 | st.Ops <- store.Op{2, store.MustEncodeSet("/ctl/cal/1", self, 0)}
19 | <-st.Seqns
20 |
21 | in := make(chan Packet)
22 | out := make(chan Packet)
23 | seqns := make(chan int64, alpha)
24 | props := make(chan *Prop)
25 |
26 | m := &Manager{
27 | Self: self,
28 | DefRev: 2,
29 | Alpha: alpha,
30 | In: in,
31 | Out: out,
32 | Ops: st.Ops,
33 | PSeqn: seqns,
34 | Props: props,
35 | TFill: 10e9,
36 | Store: st,
37 | Ticker: time.Tick(10e6),
38 | }
39 | go m.Run()
40 |
41 | go func() {
42 | for o := range out {
43 | in <- o
44 | }
45 | }()
46 |
47 | n := <-seqns
48 | w, err := st.Wait(store.Any, n)
49 | if err != nil {
50 | panic(err)
51 | }
52 | props <- &Prop{n, []byte("foo")}
53 | e := <-w
54 |
55 | exp := store.Event{
56 | Seqn: 3,
57 | Path: "/ctl/err",
58 | Body: "bad mutation",
59 | Rev: 3,
60 | Mut: "foo",
61 | Err: errors.New("bad mutation"),
62 | }
63 |
64 | e.Getter = nil
65 | assert.Equal(t, exp, e)
66 | }
67 |
68 | func TestConsensusTwo(t *testing.T) {
69 | a := "a"
70 | b := "b"
71 | x, _ := net.ResolveUDPAddr("udp", "1.2.3.4:5")
72 | xs := "1.2.3.4:5"
73 | y, _ := net.ResolveUDPAddr("udp", "2.3.4.5:6")
74 | ys := "2.3.4.5:6"
75 | const alpha = 1
76 | st := store.New()
77 |
78 | st.Ops <- store.Op{1, store.Nop}
79 | st.Ops <- store.Op{2, store.MustEncodeSet("/ctl/node/a/addr", xs, 0)}
80 | st.Ops <- store.Op{3, store.MustEncodeSet("/ctl/cal/1", a, 0)}
81 | st.Ops <- store.Op{4, store.MustEncodeSet("/ctl/node/b/addr", ys, 0)}
82 | st.Ops <- store.Op{5, store.MustEncodeSet("/ctl/cal/2", b, 0)}
83 |
84 | ain := make(chan Packet)
85 | aout := make(chan Packet)
86 | aseqns := make(chan int64, alpha)
87 | aprops := make(chan *Prop)
88 | am := &Manager{
89 | Self: a,
90 | DefRev: 5,
91 | Alpha: alpha,
92 | In: ain,
93 | Out: aout,
94 | Ops: st.Ops,
95 | PSeqn: aseqns,
96 | Props: aprops,
97 | TFill: 10e9,
98 | Store: st,
99 | Ticker: time.Tick(10e6),
100 | }
101 | go am.Run()
102 |
103 | bin := make(chan Packet)
104 | bout := make(chan Packet)
105 | bseqns := make(chan int64, alpha)
106 | bprops := make(chan *Prop)
107 | bm := &Manager{
108 | Self: b,
109 | DefRev: 5,
110 | Alpha: alpha,
111 | In: bin,
112 | Out: bout,
113 | Ops: st.Ops,
114 | PSeqn: bseqns,
115 | Props: bprops,
116 | TFill: 10e9,
117 | Store: st,
118 | Ticker: time.Tick(10e6),
119 | }
120 | go bm.Run()
121 |
122 | go func() {
123 | for o := range aout {
124 | if o.Addr.Port == x.Port && o.Addr.IP.Equal(x.IP) {
125 | go func(o Packet) { ain <- o }(o)
126 | } else {
127 | o.Addr = x
128 | go func(o Packet) { bin <- o }(o)
129 | }
130 | }
131 | }()
132 |
133 | go func() {
134 | for o := range bout {
135 | if o.Addr.Port == y.Port && o.Addr.IP.Equal(y.IP) {
136 | go func(o Packet) { bin <- o }(o)
137 | } else {
138 | o.Addr = y
139 | go func(o Packet) { ain <- o }(o)
140 | }
141 | }
142 | }()
143 |
144 | n := <-aseqns
145 | assert.Equal(t, int64(6), n)
146 | w, err := st.Wait(store.Any, n)
147 | if err != nil {
148 | panic(err)
149 | }
150 | aprops <- &Prop{n, []byte("foo")}
151 | e := <-w
152 |
153 | exp := store.Event{
154 | Seqn: 6,
155 | Path: "/ctl/err",
156 | Body: "bad mutation",
157 | Rev: 6,
158 | Mut: "foo",
159 | Err: errors.New("bad mutation"),
160 | }
161 |
162 | e.Getter = nil
163 | assert.Equal(t, exp, e)
164 | }
165 |
--------------------------------------------------------------------------------
/store/node.go:
--------------------------------------------------------------------------------
1 | package store
2 |
3 | import (
4 | "syscall"
5 | )
6 |
7 | var emptyDir = node{V: "", Ds: make(map[string]node), Rev: Dir}
8 |
9 | const ErrorPath = "/ctl/err"
10 |
11 | const Nop = "nop:"
12 |
13 | // This structure should be kept immutable.
14 | type node struct {
15 | V string
16 | Rev int64
17 | Ds map[string]node
18 | }
19 |
20 | func (n node) String() string {
21 | return ""
22 | }
23 |
24 | func (n node) readdir() []string {
25 | names := make([]string, len(n.Ds))
26 | i := 0
27 | for name := range n.Ds {
28 | names[i] = name
29 | i++
30 | }
31 | return names
32 | }
33 |
34 | func (n node) at(parts []string) (node, error) {
35 | switch len(parts) {
36 | case 0:
37 | return n, nil
38 | default:
39 | if n.Ds != nil {
40 | if m, ok := n.Ds[parts[0]]; ok {
41 | return m.at(parts[1:])
42 | }
43 | }
44 | return node{}, syscall.ENOENT
45 | }
46 | panic("unreachable")
47 | }
48 |
49 | func (n node) get(parts []string) ([]string, int64) {
50 | switch m, err := n.at(parts); err {
51 | case syscall.ENOENT:
52 | return []string{""}, Missing
53 | default:
54 | if len(m.Ds) > 0 {
55 | return m.readdir(), m.Rev
56 | } else {
57 | return []string{m.V}, m.Rev
58 | }
59 | }
60 | panic("unreachable")
61 | }
62 |
63 | func (n node) Get(path string) ([]string, int64) {
64 | return n.get(split(path))
65 | }
66 |
67 | func (n node) stat(parts []string) (int32, int64) {
68 | switch m, err := n.at(parts); err {
69 | case syscall.ENOENT:
70 | return 0, Missing
71 | default:
72 | l := len(m.Ds)
73 | if l > 0 {
74 | return int32(l), m.Rev
75 | } else {
76 | return int32(len(m.V)), m.Rev
77 | }
78 | }
79 | panic("unreachable")
80 | }
81 |
82 | func (n node) Stat(path string) (int32, int64) {
83 | if err := checkPath(path); err != nil {
84 | return 0, Missing
85 | }
86 |
87 | return n.stat(split(path))
88 | }
89 |
90 | func copyMap(a map[string]node) map[string]node {
91 | b := make(map[string]node)
92 | for k, v := range a {
93 | b[k] = v
94 | }
95 | return b
96 | }
97 |
98 | // Return value is replacement node
99 | func (n node) set(parts []string, v string, rev int64, keep bool) (node, bool) {
100 | if len(parts) == 0 {
101 | return node{v, rev, n.Ds}, keep
102 | }
103 |
104 | n.Ds = copyMap(n.Ds)
105 | p, ok := n.Ds[parts[0]].set(parts[1:], v, rev, keep)
106 | if ok {
107 | n.Ds[parts[0]] = p
108 | } else {
109 | delete(n.Ds, parts[0])
110 | }
111 | n.Rev = Dir
112 | return n, len(n.Ds) > 0
113 | }
114 |
115 | func (n node) setp(k, v string, rev int64, keep bool) node {
116 | if err := checkPath(k); err != nil {
117 | return n
118 | }
119 |
120 | n, _ = n.set(split(k), v, rev, keep)
121 | return n
122 | }
123 |
124 | func (n node) apply(seqn int64, mut string) (rep node, ev Event) {
125 | ev.Seqn, ev.Rev, ev.Mut = seqn, seqn, mut
126 | if mut == Nop {
127 | ev.Path = "/"
128 | ev.Rev = nop
129 | rep = n
130 | ev.Getter = rep
131 | return
132 | }
133 |
134 | var rev int64
135 | var keep bool
136 | ev.Path, ev.Body, rev, keep, ev.Err = decode(mut)
137 |
138 | if ev.Err == nil && keep {
139 | components := split(ev.Path)
140 | for i := 0; i < len(components)-1; i++ {
141 | _, dirRev := n.get(components[0 : i+1])
142 | if dirRev == Missing {
143 | break
144 | }
145 | if dirRev != Dir {
146 | ev.Err = syscall.ENOTDIR
147 | break
148 | }
149 | }
150 | }
151 |
152 | if ev.Err == nil {
153 | _, curRev := n.Get(ev.Path)
154 | if rev != Clobber && rev < curRev {
155 | ev.Err = ErrRevMismatch
156 | } else if curRev == Dir {
157 | ev.Err = syscall.EISDIR
158 | }
159 | }
160 |
161 | if ev.Err != nil {
162 | ev.Path, ev.Body, rev, keep = ErrorPath, ev.Err.Error(), Clobber, true
163 | }
164 |
165 | if !keep {
166 | ev.Rev = Missing
167 | }
168 |
169 | rep = n.setp(ev.Path, ev.Body, ev.Rev, keep)
170 | ev.Getter = rep
171 | return
172 | }
173 |
--------------------------------------------------------------------------------
/doozerd.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/tls"
5 | _ "expvar"
6 | "flag"
7 | "fmt"
8 | "github.com/ha/doozer"
9 | "github.com/ha/doozerd/peer"
10 | "log"
11 | "net"
12 | "os"
13 | "strconv"
14 | )
15 |
16 | const defWebPort = 8000
17 |
18 | type strings []string
19 |
20 | func (a *strings) Set(s string) error {
21 | *a = append(*a, s)
22 | return nil
23 | }
24 |
25 | func (a *strings) String() string {
26 | return fmt.Sprint(*a)
27 | }
28 |
29 | var (
30 | laddr = flag.String("l", "127.0.0.1:8046", "The address to bind to.")
31 | aaddrs = strings{}
32 | buri = flag.String("b", "", "boot cluster uri (tried after -a)")
33 | waddr = flag.String("w", "", "web listen addr (default: see below)")
34 | name = flag.String("c", "local", "The non-empty cluster name.")
35 | showVersion = flag.Bool("v", false, "print doozerd's version string")
36 | pi = flag.Float64("pulse", 1, "how often (in seconds) to set applied key")
37 | fd = flag.Float64("fill", .1, "delay (in seconds) to fill unowned seqns")
38 | kt = flag.Float64("timeout", 60, "timeout (in seconds) to kick inactive nodes")
39 | hi = flag.Int64("hist", 2000, "length of history/revisions to keep")
40 | certFile = flag.String("tlscert", "", "TLS public certificate")
41 | keyFile = flag.String("tlskey", "", "TLS private key")
42 | )
43 |
44 | var (
45 | rwsk = os.Getenv("DOOZER_RWSECRET")
46 | rosk = os.Getenv("DOOZER_ROSECRET")
47 | )
48 |
49 | func init() {
50 | flag.Var(&aaddrs, "a", "attach address (may be given multiple times)")
51 | }
52 |
53 | func Usage() {
54 | fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS]\n", os.Args[0])
55 | fmt.Fprintf(os.Stderr, "\nOptions:\n")
56 | flag.PrintDefaults()
57 | fmt.Fprintf(os.Stderr, `
58 | The default for -w is to use the addr from -l,
59 | and change the port to 8000. If you give "-w false",
60 | doozerd will not listen for for web connections.
61 | `)
62 | }
63 |
64 | func main() {
65 | *buri = os.Getenv("DOOZER_BOOT_URI")
66 |
67 | flag.Usage = Usage
68 | flag.Parse()
69 |
70 | if *showVersion {
71 | fmt.Println("doozerd", peer.Version)
72 | return
73 | }
74 |
75 | if *laddr == "" {
76 | fmt.Fprintln(os.Stderr, "require a listen address")
77 | flag.Usage()
78 | os.Exit(1)
79 | }
80 |
81 | log.SetPrefix("DOOZER ")
82 | log.SetFlags(log.Ldate | log.Lmicroseconds)
83 |
84 | tsock, err := net.Listen("tcp", *laddr)
85 | if err != nil {
86 | panic(err)
87 | }
88 |
89 | if *certFile != "" || *keyFile != "" {
90 | tsock = tlsWrap(tsock, *certFile, *keyFile)
91 | }
92 |
93 | uaddr, err := net.ResolveUDPAddr("udp", *laddr)
94 | if err != nil {
95 | panic(err)
96 | }
97 |
98 | usock, err := net.ListenUDP("udp", uaddr)
99 | if err != nil {
100 | panic(err)
101 | }
102 |
103 | var wsock net.Listener
104 | if *waddr == "" {
105 | wa, err := net.ResolveTCPAddr("tcp", *laddr)
106 | if err != nil {
107 | panic(err)
108 | }
109 | wa.Port = defWebPort
110 | *waddr = wa.String()
111 | }
112 | if b, err := strconv.ParseBool(*waddr); err != nil && !b {
113 | wsock, err = net.Listen("tcp", *waddr)
114 | if err != nil {
115 | panic(err)
116 | }
117 | }
118 |
119 | id := randId()
120 | var cl *doozer.Conn
121 | switch {
122 | case len(aaddrs) > 0 && *buri != "":
123 | cl = attach(*name, aaddrs)
124 | if cl == nil {
125 | cl = boot(*name, id, *laddr, *buri)
126 | }
127 | case len(aaddrs) > 0:
128 | cl = attach(*name, aaddrs)
129 | if cl == nil {
130 | panic("failed to attach")
131 | }
132 | case *buri != "":
133 | cl = boot(*name, id, *laddr, *buri)
134 | }
135 |
136 | peer.Main(*name, id, *buri, rwsk, rosk, cl, usock, tsock, wsock, ns(*pi), ns(*fd), ns(*kt), *hi)
137 | panic("main exit")
138 | }
139 |
140 | func ns(x float64) int64 {
141 | return int64(x * 1e9)
142 | }
143 |
144 | func tlsWrap(l net.Listener, cfile, kfile string) net.Listener {
145 | if cfile == "" || kfile == "" {
146 | panic("need both cert file and key file")
147 | }
148 |
149 | cert, err := tls.LoadX509KeyPair(cfile, kfile)
150 | if err != nil {
151 | panic(err)
152 | }
153 |
154 | tc := new(tls.Config)
155 | tc.Certificates = append(tc.Certificates, cert)
156 | return tls.NewListener(l, tc)
157 | }
158 |
--------------------------------------------------------------------------------
/web/main.js:
--------------------------------------------------------------------------------
1 | var deadline = 0, retry_interval = 0;
2 | var ti;
3 |
4 | function insert(parent, child) {
5 | var existing = parent.children();
6 | var before = null;
7 | existing.each(function () {
8 | var jq = $(this);
9 | if (jq.attr('name') < child.attr('name')) {
10 | before = jq;
11 | }
12 | });
13 | if (before === null) {
14 | parent.prepend(child);
15 | } else {
16 | before.after(child);
17 | }
18 | }
19 |
20 | function apply(ev) {
21 | var parts = ev.Path.split("/")
22 | if (parts.length < 2) {
23 | return
24 | }
25 | parts = parts.slice(1); // omit leading empty string
26 | var dir_parts = parts.slice(0, parts.length - 1);
27 | var dir = $('#root');
28 | for (var i = 0; i < dir_parts.length; i++) {
29 | var part = dir_parts[i];
30 | var next = dir.find('> dl > div[name="'+part+'"] > dd');
31 | if (next.length < 1) {
32 | var div = $('').attr('name', part);
33 | var dd = $(' ');
34 | div.append($('').text(part+'/')).append(dd);
35 | insert(dir.children('dl'), div);
36 | dd.append('').append('');
37 | next = dd;
38 | }
39 | dir = next;
40 | }
41 |
42 | var basename = parts[parts.length - 1];
43 | var entry = dir.find('tr[name="'+basename+'"]');
44 | if (entry.length < 1) {
45 | var tr = $('').attr('name', basename);
46 | insert(dir.children('table').children('tbody'), tr);
47 | tr.append($('| ').text(basename)).
48 | append(' | ').
49 | append(' | ').
50 | append(' | ');
51 | entry = tr;
52 | }
53 | entry.children('td.rev').text('('+ev.Rev+')');
54 | entry.children('td.body').text(ev.Body);
55 | entry.addClass('new');
56 |
57 | // Kick off the transition in a bit.
58 | setTimeout(function() { entry.removeClass('new') }, 550);
59 | }
60 |
61 | function time_interval(s) {
62 | if (s < 120) return Math.ceil(s) + 's';
63 | if (s < 7200) return Math.round(s/60) + 'm';
64 | return Math.round(s/3600) + 'h';
65 | }
66 |
67 | function countdown() {
68 | var body = $('body');
69 | var eta = (deadline - new Date().getTime())/1000;
70 | if (eta < 0) {
71 | body.removeClass('waiting');
72 | open();
73 | } else {
74 | $('#retrymsg').text("retrying in " + time_interval(eta));
75 | body.addClass('waiting');
76 | ti = setTimeout(countdown, Math.max(100, eta*9));
77 | }
78 | }
79 |
80 | function retry() {
81 | deadline = ((new Date()).getTime()) + retry_interval * 1000;
82 | retry_interval += (retry_interval + 5) * (Math.random() + .5);
83 | countdown();
84 | }
85 |
86 | function open() {
87 | var body = $('body');
88 | var status = $('#status');
89 | status.text("connecting");
90 | var ws = new WebSocket("ws://"+location.host+"/$events"+path);
91 | ws.onmessage = function (ev) {
92 | var jev = JSON.parse(ev.data);
93 | apply(jev);
94 | };
95 | ws.onopen = function(ev) {
96 | if (retry_interval > 0) {
97 | body.addClass('wereback');
98 | setTimeout(function () { body.removeClass('wereback') }, 8000);
99 | }
100 | retry_interval = 0;
101 | status.text('open')
102 | body.addClass('open').removeClass('loading closed error');
103 | $('#root > dl > *, #root > table > tbody > *').remove();
104 | };
105 | ws.onclose = function(ev) {
106 | status.text('closed')
107 | body.addClass('closed').removeClass('loading open error wereback');
108 | retry();
109 | };
110 | ws.onerror = function(ev) {
111 | status.text('error ' + ev)
112 | body.addClass('error').removeClass('loading open closed wereback');
113 | retry();
114 | };
115 | }
116 |
117 | function dr() {
118 | $('#trynow').click(function() {
119 | clearTimeout(ti);
120 | deadline = 0;
121 | countdown();
122 | });
123 |
124 | if ("WebSocket" in window) {
125 | open();
126 | } else {
127 | $('#status').text("your browser does not provide websockets");
128 | $('body').addClass('error nows').removeClass('loading open closed wereback');
129 | }
130 | }
131 |
132 | function jerr() {
133 | const m = 'could not load jquery (is your network link down?)';
134 | document.getElementById('status').innerText = m;
135 | document.getElementsByTagName('body')[0].className = 'error';
136 | }
137 |
--------------------------------------------------------------------------------
/boot.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/rand"
5 | "encoding/base32"
6 | "github.com/ha/doozer"
7 | "time"
8 | )
9 |
10 | const attachTimeout = 1e9
11 |
12 | func boot(name, id, laddr, buri string) *doozer.Conn {
13 | b, err := doozer.DialUri(buri, "")
14 | if err != nil {
15 | panic(err)
16 | }
17 |
18 | err = b.Access(rwsk)
19 | if err != nil {
20 | panic(err)
21 | }
22 |
23 | cl := lookupAndAttach(b, name)
24 | if cl == nil {
25 | return elect(name, id, laddr, b)
26 | }
27 |
28 | return cl
29 | }
30 |
31 | // Elect chooses a seed node, and returns a connection to a cal.
32 | // If this process is the seed, returns nil.
33 | func elect(name, id, laddr string, b *doozer.Conn) *doozer.Conn {
34 | // advertise our presence, since we might become a cal
35 | nspath := "/ctl/ns/" + name + "/" + id
36 | r, err := b.Set(nspath, 0, []byte(laddr))
37 | if err != nil {
38 | panic(err)
39 | }
40 |
41 | // fight to be the seed
42 | _, err = b.Set("/ctl/boot/"+name, 0, []byte(id))
43 | if err, ok := err.(*doozer.Error); ok && err.Err == doozer.ErrOldRev {
44 | // we lost, lookup addresses again
45 | cl := lookupAndAttach(b, name)
46 | if cl == nil {
47 | panic("failed to attach after losing election")
48 | }
49 |
50 | // also delete our entry, since we're not officially a cal yet.
51 | // it gets set again in peer.Main when we become a cal.
52 | err := b.Del(nspath, r)
53 | if err != nil {
54 | panic(err)
55 | }
56 |
57 | return cl
58 | } else if err != nil {
59 | panic(err)
60 | }
61 |
62 | return nil // we are the seed node -- don't attach
63 | }
64 |
65 | func lookupAndAttach(b *doozer.Conn, name string) *doozer.Conn {
66 | as := lookup(b, name)
67 | if len(as) > 0 {
68 | cl := attach(name, as)
69 | if cl != nil {
70 | return cl
71 | }
72 | }
73 | return nil
74 | }
75 |
76 | func attach(name string, addrs []string) *doozer.Conn {
77 | ch := make(chan *doozer.Conn, 1)
78 |
79 | for _, a := range addrs {
80 | go func(a string) {
81 | if c, _ := isCal(name, a); c != nil {
82 | ch <- c
83 | }
84 | }(a)
85 | }
86 |
87 | go func() {
88 | <-time.After(attachTimeout)
89 | ch <- nil
90 | }()
91 |
92 | return <-ch
93 | }
94 |
95 | // IsCal checks if addr is a CAL in the cluster named name.
96 | // Returns a client if so, nil if not.
97 | func isCal(name, addr string) (*doozer.Conn, error) {
98 | c, err := doozer.Dial(addr)
99 | if err != nil {
100 | return nil, err
101 | }
102 |
103 | err = c.Access(rwsk)
104 | if err != nil {
105 | return nil, err
106 | }
107 |
108 | v, _, _ := c.Get("/ctl/name", nil)
109 | if string(v) != name {
110 | return nil, nil
111 | }
112 |
113 | rev, err := c.Rev()
114 | if err != nil {
115 | return nil, err
116 | }
117 |
118 | var cals []string
119 | names, err := c.Getdir("/ctl/cal", rev, 0, -1)
120 | if err != nil {
121 | return nil, err
122 | }
123 | for _, name := range names {
124 | cals = append(cals, name)
125 | }
126 |
127 | for _, cal := range cals {
128 | body, _, err := c.Get("/ctl/cal/"+cal, nil)
129 | if err != nil || len(body) == 0 {
130 | continue
131 | }
132 |
133 | id := string(body)
134 |
135 | v, _, err := c.Get("/ctl/node/"+id+"/addr", nil)
136 | if err != nil {
137 | return nil, err
138 | }
139 | if string(v) == addr {
140 | return c, nil
141 | }
142 | }
143 |
144 | return nil, nil
145 | }
146 |
147 | // Find possible addresses for cluster named name.
148 | func lookup(b *doozer.Conn, name string) (as []string) {
149 | rev, err := b.Rev()
150 | if err != nil {
151 | panic(err)
152 | }
153 |
154 | path := "/ctl/ns/" + name
155 | names, err := b.Getdir(path, rev, 0, -1)
156 | if err == doozer.ErrNoEnt {
157 | return nil
158 | } else if err, ok := err.(*doozer.Error); ok && err.Err == doozer.ErrNoEnt {
159 | return nil
160 | } else if err != nil {
161 | panic(err)
162 | }
163 |
164 | path += "/"
165 | for _, name := range names {
166 | body, _, err := b.Get(path+name, &rev)
167 | if err != nil {
168 | panic(err)
169 | }
170 | as = append(as, string(body))
171 | }
172 | return as
173 | }
174 |
175 | func randId() string {
176 | const bits = 80 // enough for 10**8 ids with p(collision) < 10**-8
177 | rnd := make([]byte, bits/8)
178 |
179 | n, err := rand.Read(rnd)
180 | if err != nil {
181 | panic(err)
182 | }
183 | if n != len(rnd) {
184 | panic("io.ReadFull len mismatch")
185 | }
186 |
187 | enc := make([]byte, base32.StdEncoding.EncodedLen(len(rnd)))
188 | base32.StdEncoding.Encode(enc, rnd)
189 | return string(enc)
190 | }
191 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Doozer
2 |
3 | 
4 |
5 | [](http://travis-ci.org/ha/doozerd)
6 |
7 | ## What Is It?
8 |
9 | Doozer is a highly-available, completely consistent
10 | store for small amounts of extremely important data.
11 | When the data changes, it can notify connected clients
12 | immediately (no polling), making it ideal for
13 | infrequently-updated data for which clients want
14 | real-time updates. Doozer is good for name service,
15 | database master elections, and configuration data shared
16 | between several machines. See *When Should I Use It?*,
17 | below, for details.
18 |
19 | See the [mailing list][mail] to discuss doozer with
20 | other users and developers.
21 |
22 | ## Quick Start
23 |
24 | 1. Download [doozerd](https://github.com/ha/doozerd/downloads)
25 | 2. Unpack the archive and put `doozerd` in your `PATH`
26 | 3. Repeat for [doozer](https://github.com/ha/doozer/downloads)
27 | 4. Start a doozerd with a WebView listening on `:8080`
28 |
29 | $ doozerd -w ":8080"
30 |
31 | 5. Set a key and read it back
32 |
33 | $ echo "hello, world" | doozer add /message
34 | $ doozer get /message
35 | hello, world
36 |
37 | 6. Open and see your message
38 |
39 | 
40 |
41 | ## How Does It Work?
42 |
43 | Doozer is a network service. A handful of machines
44 | (usually three, five, or seven) each run one doozer
45 | server process. These processes communicate with each
46 | other using a standard fully-consistent distributed
47 | consensus algorithm. Clients dial in to one or more of
48 | the doozer servers, issue commands, such as GET, SET,
49 | and WATCH, and receive responses.
50 |
51 | (insert network diagram here)
52 |
53 | Each doozerd process has a complete copy of the
54 | datastore and serves both read and write requests; there
55 | is no distinguished "master" or "leader". Doozer is
56 | designed to store data that fits entirely in memory; it
57 | never writes data to permanent files. A separate tool
58 | provides durable storage for backup and recovery.
59 |
60 | ## When Should I Use It?
61 |
62 | Here are some example scenarios:
63 |
64 | 1. *Name Service*
65 |
66 | You have a set of machines that serve incoming HTTP
67 | requests. Due to hardware failure, occasionally one
68 | of these machines will fail and you replace it with a
69 | new machine at a new network address. A change to DNS
70 | data would take time to reach all clients, because
71 | the TTL of the old DNS record would cause it to
72 | remain in client caches for some time.
73 |
74 | Instead of DNS, you could use Doozer. Clients can
75 | subscribe to the names they are interested in, and
76 | they will get notified when any of those names’
77 | addresses change.
78 |
79 | 2. *Database Master Election*
80 |
81 | You are deploying a MySQL system. You want it to have
82 | high availability, so you add slaves on separate
83 | physical machines. When the master fails, you might
84 | promote one slave to become the new master. At any
85 | given time, clients need to know which machine is the
86 | master, and the slaves must coordinate with each
87 | other during failover.
88 |
89 | You can use doozer to store the address of the
90 | current master and all information necessary to
91 | coordinate failover.
92 |
93 | 3. *Configuration*
94 |
95 | You have processes on several different machines, and
96 | you want them all to use the same config file, which
97 | you must occasionally update. It is important that
98 | they all use the same configuration.
99 |
100 | Store the config file in doozer, and have the
101 | processes read their configuration directly from
102 | doozer.
103 |
104 | ## What can I do with it?
105 |
106 | We have a detailed description of the [data model](doc/data-model.md).
107 |
108 | For ways to manipulate or read the data, see the [protocol spec](doc/proto.md).
109 |
110 | Try out doozer's fault-tolerance with some [fire drills](doc/firedrill.md).
111 |
112 | ## Similar Projects
113 |
114 | Doozer is similar to the following pieces of software:
115 |
116 | * Apache Zookeeper
117 | * Google Chubby
118 |
119 | ## Hacking on Doozer
120 |
121 | * [hacking on doozer](doc/hacking.md)
122 | * [mailing list][mail]
123 |
124 | ## License and Authors
125 |
126 | Doozer is distributed under the terms of the MIT
127 | License. See [LICENSE](LICENSE) for details.
128 |
129 | Doozer was created by Blake Mizerany and Keith Rarick.
130 | Type `git shortlog -s` for a full list of contributors.
131 |
132 | [mail]: https://groups.google.com/group/doozer
133 |
--------------------------------------------------------------------------------
/peer/bench_test.go:
--------------------------------------------------------------------------------
1 | package peer
2 |
3 | import (
4 | "github.com/ha/doozer"
5 | "github.com/ha/doozerd/store"
6 | "testing"
7 | )
8 |
9 | func Benchmark1DoozerClientSet(b *testing.B) {
10 | b.StopTimer()
11 | l := mustListen()
12 | defer l.Close()
13 | a := l.Addr().String()
14 | u := mustListenUDP(a)
15 | defer u.Close()
16 |
17 | go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)
18 |
19 | cl := dial(l.Addr().String())
20 |
21 | b.StartTimer()
22 | for i := 0; i < b.N; i++ {
23 | cl.Set("/test", store.Clobber, nil)
24 | }
25 | }
26 |
27 | func Benchmark1DoozerConClientSet(b *testing.B) {
28 | b.StopTimer()
29 | l := mustListen()
30 | defer l.Close()
31 | a := l.Addr().String()
32 | u := mustListenUDP(a)
33 | defer u.Close()
34 |
35 | go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)
36 |
37 | cl := dial(l.Addr().String())
38 |
39 | c := make(chan bool, b.N)
40 | b.StartTimer()
41 | for i := 0; i < b.N; i++ {
42 | go func() {
43 | cl.Set("/test", store.Clobber, nil)
44 | c <- true
45 | }()
46 | }
47 | for i := 0; i < b.N; i++ {
48 | <-c
49 | }
50 | }
51 |
52 | func Benchmark5DoozerClientSet(b *testing.B) {
53 | b.StopTimer()
54 | l := mustListen()
55 | defer l.Close()
56 | a := l.Addr().String()
57 | u := mustListenUDP(a)
58 | defer u.Close()
59 |
60 | l1 := mustListen()
61 | defer l1.Close()
62 | u1 := mustListenUDP(l1.Addr().String())
63 | defer u1.Close()
64 | l2 := mustListen()
65 | defer l2.Close()
66 | u2 := mustListenUDP(l2.Addr().String())
67 | defer u2.Close()
68 | l3 := mustListen()
69 | defer l3.Close()
70 | u3 := mustListenUDP(l3.Addr().String())
71 | defer u3.Close()
72 | l4 := mustListen()
73 | defer l4.Close()
74 | u4 := mustListenUDP(l4.Addr().String())
75 | defer u4.Close()
76 |
77 | go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 1e8, 3e9, 101)
78 | go Main("a", "Y", "", "", "", dial(a), u1, l1, nil, 1e9, 1e8, 3e9, 101)
79 | go Main("a", "Z", "", "", "", dial(a), u2, l2, nil, 1e9, 1e8, 3e9, 101)
80 | go Main("a", "V", "", "", "", dial(a), u3, l3, nil, 1e9, 1e8, 3e9, 101)
81 | go Main("a", "W", "", "", "", dial(a), u4, l4, nil, 1e9, 1e8, 3e9, 101)
82 |
83 | cl := dial(l.Addr().String())
84 | cl.Set("/ctl/cal/1", store.Missing, nil)
85 | cl.Set("/ctl/cal/2", store.Missing, nil)
86 | cl.Set("/ctl/cal/3", store.Missing, nil)
87 | cl.Set("/ctl/cal/4", store.Missing, nil)
88 |
89 | // make sure all the peers have started up
90 | dial(l1.Addr().String()).Set("/foo", store.Clobber, nil)
91 | dial(l2.Addr().String()).Set("/foo", store.Clobber, nil)
92 | dial(l3.Addr().String()).Set("/foo", store.Clobber, nil)
93 | dial(l4.Addr().String()).Set("/foo", store.Clobber, nil)
94 |
95 | b.StartTimer()
96 | for i := 0; i < b.N; i++ {
97 | cl.Set("/test", store.Clobber, nil)
98 | }
99 | }
100 |
101 | func Benchmark5DoozerConClientSet(b *testing.B) {
102 | if b.N < 5 {
103 | return
104 | }
105 | const C = 20
106 | b.StopTimer()
107 | l := mustListen()
108 | defer l.Close()
109 | a := l.Addr().String()
110 | u := mustListenUDP(a)
111 | defer u.Close()
112 |
113 | l1 := mustListen()
114 | defer l1.Close()
115 | u1 := mustListenUDP(l1.Addr().String())
116 | defer u1.Close()
117 | l2 := mustListen()
118 | defer l2.Close()
119 | u2 := mustListenUDP(l2.Addr().String())
120 | defer u2.Close()
121 | l3 := mustListen()
122 | defer l3.Close()
123 | u3 := mustListenUDP(l3.Addr().String())
124 | defer u3.Close()
125 | l4 := mustListen()
126 | defer l4.Close()
127 | u4 := mustListenUDP(l4.Addr().String())
128 | defer u4.Close()
129 |
130 | go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 1e10, 3e12, 1e9)
131 | go Main("a", "Y", "", "", "", dial(a), u1, l1, nil, 1e9, 1e10, 3e12, 1e9)
132 | go Main("a", "Z", "", "", "", dial(a), u2, l2, nil, 1e9, 1e10, 3e12, 1e9)
133 | go Main("a", "V", "", "", "", dial(a), u3, l3, nil, 1e9, 1e10, 3e12, 1e9)
134 | go Main("a", "W", "", "", "", dial(a), u4, l4, nil, 1e9, 1e10, 3e12, 1e9)
135 |
136 | cl := dial(l.Addr().String())
137 | cl.Set("/ctl/cal/1", store.Missing, nil)
138 | cl.Set("/ctl/cal/2", store.Missing, nil)
139 | cl.Set("/ctl/cal/3", store.Missing, nil)
140 | cl.Set("/ctl/cal/4", store.Missing, nil)
141 |
142 | waitFor(cl, "/ctl/node/X/writable")
143 | waitFor(cl, "/ctl/node/Y/writable")
144 | waitFor(cl, "/ctl/node/Z/writable")
145 | waitFor(cl, "/ctl/node/V/writable")
146 | waitFor(cl, "/ctl/node/W/writable")
147 |
148 | cls := []*doozer.Conn{
149 | cl,
150 | dial(l1.Addr().String()),
151 | dial(l2.Addr().String()),
152 | dial(l3.Addr().String()),
153 | dial(l4.Addr().String()),
154 | }
155 |
156 | done := make(chan bool, C)
157 | f := func(i int, cl *doozer.Conn) {
158 | for ; i < b.N; i += C {
159 | _, err := cl.Set("/test", store.Clobber, nil)
160 | if e, ok := err.(*doozer.Error); ok && e.Err == doozer.ErrReadonly {
161 | } else if err != nil {
162 | panic(err)
163 | }
164 | }
165 | done <- true
166 | }
167 | b.StartTimer()
168 | for i := 0; i < C; i++ {
169 | go f(i, cls[i%len(cls)])
170 | }
171 | for i := 0; i < C; i++ {
172 | <-done
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/doc/doozerd.1.ronn:
--------------------------------------------------------------------------------
1 | doozerd(1) -- A consistent, fault-tolerent, distributed data store.
2 | ===================================================================
3 |
4 | ## SYNOPSIS
5 |
6 | `doozerd` [options]
7 | `doozerd` [-c ] [-l ] [-a | -b ]
8 |
9 | ## DESCRIPTION
10 |
11 | *Doozerd* stores data in a cluster of doozerds consistently. A cluster has one
12 | or more members and zero or more slaves. When there is more than one doozerd in
13 | a cluster, it takes on the property of fault-tolerance. Each doozerd
14 | participating in consensus is called a member. One or more members make up a
15 | *cluster*. A doozerd attached to a cluster, and that is not participating in
16 | consensus, is a *slave*. Slaves watch the `/ctl/cal` directory for an empty
17 | file to appear. If this happens, slaves will attempt to set the contents of
18 | that file to their identity. If the file is written successfully, the slave
19 | will become a member of the cluster. Each doozerd process keeps a complete,
20 | consistent copy of the entire data store by changing their own stores in the
21 | same order as the others. (See [Data
22 | Model](https://github.com/ha/doozerd/blob/master/doc/data-model.md) for more
23 | information.)
24 |
25 | **Consensus**
26 |
27 | For members and slaves to change their stores in the same order, each write
28 | operation given to a member goes through consensus. This guarantees the order
29 | of the writes to all stores. Doozerd employs the
30 | [Paxos](http://en.wikipedia.org/wiki/Paxos_\(computer_science\)) algorithm for
31 | consensus.
32 |
33 | ## OPTIONS
34 |
35 | * `-a`=:
36 | Attach to a member in a cluster at address .
37 |
38 | * `-b`=:
39 | A uri containing the address of a DzNS cluster. If members are found under
40 | `/ctl/ns/`, doozerd will attempt to connect to each until it succeeds.
41 | See [doozer-uri(7)](https://github.com/ha/doozerd/blob/master/doc/uri.md)).
42 |
43 | * `-c`=:
44 | The name of a cluster. This is used for ensuring slaves connect to the
45 | correct cluster and for looking up addresses in DzNS.
46 |
47 | * `-fill`=:
48 | The number of seconds to wait before filling in unknown sequence numbers.
49 |
50 | * `-hist`=:
51 | The length of history/revisions to keep in the store.
52 |
53 | * `-l`=:
54 | The address to bind to. An is formatted as "host:port". It is important
55 | to note that doozerd uses the address given to `-l` as an identifier. It is not
56 | sufficient to use `0.0.0.0`. The must be the address others will connect
57 | to it with.
58 |
59 |
60 | * `-pulse`=:
61 | How often (in seconds) to set applied key. The key is listed in the store under
62 | `/ctl/node//applied`. The contents of the file represents the current
63 | revision of this process's copy of the store at the time of writing.
64 |
65 | * `-timeout`=:
66 | The timeout (in seconds) to kick inactive members.
67 |
68 | * `-tlscert`=:
69 | TLS public certificate. If both a `-tlscert` and `-tlskey` are given, all
70 | client traffic is encrypted with TLS.
71 |
72 | * `-tlskey`=:
73 | TLS private key. If both a `-tlscert` and `-tlskey` are given, all client
74 | traffic is encrypted with TLS.
75 |
76 | * `-v`:
77 | Print doozerd's version string and exit.
78 |
79 | * `-w`=:
80 | The listen address for the web view. The default is to use the addr from `-l`,
81 | and change the port to 8000. If you give `-w false`, doozerd will not listen
82 | for for web connections.
83 |
84 | ## ENVIRONMENT
85 |
86 | * `DOOZER_BOOT_URI`=:
87 | See CLUSTERING > With DzNS.
88 |
89 | ## CLUSTERING
90 |
91 | To start a cluster, you will need to start the initial member for others to
92 | attach to, or use a *Doozer Name Service* (DzNS).
93 |
94 | **Without DzNS**
95 |
96 | Start the initial member:
97 |
98 | $ doozerd -l 127.0.0.1:8046
99 |
100 | We can now slave our initial instance.
101 |
102 | $ doozerd -l 127.0.0.2:8064 -a 127.0.0.1:8046
103 |
104 | Open http://127.0.0.2:8000 to view its web view. Note it sees itself and the
105 | initial member.
106 |
107 | Add the third slave:
108 |
109 | $ doozerd -l 127.0.0.3:8064 -a 127.0.0.1:8046
110 |
111 | NOTE: Once the initial member is booted. Slaves can connect at anytime,
112 | meaning you can launch them in parallel.
113 |
114 | **Adding member slots**
115 |
116 | We need to use a Doozer client to add member slots. Here we will use the
117 | `doozer` command:
118 |
119 | $ export DOOZER_URI="doozer?:ca=127.0.0.1:8046"
120 | $ printf '' | doozer set /ctl/cal/1 0
121 | $ printf '' | doozer set /ctl/cal/2 0
122 |
123 | Open any of the web views and see that each id is the body of one of the three
124 | files under `/ctl/cal`.
125 |
126 | **With DzNS**
127 |
128 | A DzNS cluster is a doozer cluster used by other doozerd processes to discover
129 | members of the cluster they want to join and decide who the initial member will
130 | be when creating new clusters.
131 |
132 | A DzNS is created the same way you create any other doozer cluster.
133 |
134 | To boot a cluster using a DzNS, start a doozerd with the `-b` flag or the
135 | `DOOZER_BOOT_URI` environment variable set. If your cluster name is not the
136 | default name for `-c`, you will also need to set it.
137 |
138 | The newly started doozerd will first connect to a member of the DzNS. Once
139 | connected, it will lookup the address of the doozerd listed under
140 | `/ctl/boot/` in DzNS. If `/ctl/boot/` does not exist, the doozerd
141 | will attempt to create it with its identity as the contents. If succesfull, it
142 | will become the initial member of the cluster and the others will slave it.
143 |
144 |
145 | $ export DOOZER_BOOT_URI="
146 | doozer:?
147 | ca=127.0.0.1:8046&
148 | ca=127.0.0.2:8046&
149 | ca=127.0.0.3:8046
150 | "
151 |
152 | NOTE: All doozerds can be started in parellel when using a DzNS.
153 |
154 | $ doozerd -c example -l 127.0.0.10:8046
155 | $ doozerd -c example -l 127.0.0.20:8046
156 | $ doozerd -c example -l 127.0.0.30:8046
157 | $ doozerd -c example -l 127.0.0.40:8046
158 |
159 |
160 | Now we can create the member slots under `/ctl/cal` in the new doozerd
161 | processes. We will, again, use our DzNS to determine which is the member we
162 | need to write to.
163 |
164 | First, we need to set the cluster name we want to use in the `DOOZER_URI`
165 | environment variable, then we create the empty files. The `DOOZER_BOOT_URI`
166 | will remain unchanged.
167 |
168 | $ export DOOZER_URI="doozer:?cn=example"
169 | $ printf '' | doozer set /ctl/cal/1 0
170 | $ printf '' | doozer set /ctl/cal/2 0
171 |
172 | ## EXIT STATUS
173 |
174 | **doozerd** exits 0 on success, and >0 if an error occurs.
175 |
176 | ## AUTHORS
177 | Keith Rarick , Blake Mizerany
178 |
179 | ## SOURCE
180 |
181 |
--------------------------------------------------------------------------------
/doc/proto.md:
--------------------------------------------------------------------------------
1 | # Client Protocol
2 |
3 | ## Overview
4 |
5 | Doozer is a highly-available, consistent lock service.
6 | It also lets you store small amounts of metadata as
7 | files in a directory tree. See [data model][data] for a complete
8 | description.
9 |
10 | The doozer protocol is used for messages between clients
11 | and servers. A client connects to doozerd by TCP and
12 | transmits *request* messages to a server, which
13 | subsequently returns *response* messages to the client.
14 |
15 | (Note: this protocol is partially based on [9P][],
16 | the Plan 9 file protocol. Parts of this document
17 | are paraphrased from the 9P man pages.)
18 |
19 | Each message consists of a sequence of bytes comprising
20 | two parts. First, a four-byte header field holds an
21 | unsigned integer, *n*, in big-endian order (most
22 | significant byte first). This is followed by *n* bytes
23 | of data; these *n* bytes represent structured data
24 | encoded in [Protocol Buffer][protobuf] format.
25 |
26 | Two Protocol Buffer structures, `Request` and
27 | `Response`, are used for *requests* and *responses*,
28 | respectively. See `src/pkg/server/msg.proto` for their
29 | definitions.
30 |
31 | Each request contains at least a tag, described below,
32 | and a verb, to identify what action is desired.
33 | The other fields may or may not be required; their
34 | meanings depend on the verb, as described below.
35 |
36 | The tag is chosen and used by the client to identify
37 | the message. The reply to the message
38 | will have the same tag. Clients must arrange that no
39 | two outstanding requests on the same connection have
40 | the same tag.
41 |
42 | Each response contains at least a tag.
43 | Other response fields may or may not be present,
44 | depending on the verb of the request.
45 |
46 | A client can send multiple requests without waiting for
47 | the corresponding responses, but all outstanding
48 | requests must specify different tags. The server may
49 | delay the response to a request and respond to later
50 | ones; this is sometimes necessary, for example when the
51 | client has issued a `WAIT` request and the response
52 | is sent after a file is modified in the future.
53 |
54 | ### Data Model
55 |
56 | For a thorough description of Doozer's data model,
57 | see [Data Model][data]. Briefly, doozer's store holds
58 | a tree structure of files identified by paths similar
59 | to paths in Unix, and performs only whole-file reads
60 | and writes, which are atomic. The store also records
61 | the *revision* of each write.
62 | This number can be given to a subsequent write
63 | operation to ensure that no
64 | intervening writes have happened.
65 |
66 | ## Glob Notation
67 |
68 | Some of the requests take a glob pattern that can match
69 | zero or more concrete path names.
70 |
71 | - `?` matches a single char in a single path component
72 | - `*` matches zero or more chars in a single path component
73 | - `**` matches zero or more chars in zero or more components
74 | - any other sequence matches itself
75 |
76 | ## Verbs
77 |
78 | Each verb shows the set of request fields it uses,
79 | followed by the set of response fields it provides.
80 |
81 | * `DEL` *path*, *rev* ⇒ ∅
82 |
83 | Del deletes the file at *path* if *rev* is greater than
84 | or equal to the file's revision.
85 |
86 | * `GET` *path*, *rev* ⇒ *value*, *rev*
87 |
88 | Gets the contents (*value*) and revision (*rev*)
89 | of the file at *path* in the specified revision (*rev*).
90 | If *rev* is not provided, get uses the current revision.
91 |
92 | * `GETDIR` *path*, *rev*, *offset* ⇒ *path*
93 |
94 | Returns the *n*th entry in *path* (a directory) in
95 | the specified revision (*rev*), where *n* is
96 | *offset*. It is an error if *path* is not a
97 | directory.
98 |
99 | * `NOP` (deprecated)
100 |
101 | * `REV` ∅ ⇒ *rev*
102 |
103 | Returns the current revision.
104 |
105 | * `SET` *path*, *rev*, *value* ⇒ *rev*
106 |
107 | Sets the contents of the file at *path* to *value*,
108 | as long as *rev* is greater than or equal to the file's
109 | revision.
110 | Returns the file's new revision.
111 |
112 | * `WAIT` *path*, *rev* ⇒ *path*, *rev*, *value*, *flags*
113 |
114 | Responds with the first change made to any file
115 | matching *path*, a glob pattern, on or after *rev*.
116 | The response *path* is the file that was changed;
117 | the response *rev* is the revision of the change.
118 | *Value* is the new contents of the file.
119 |
120 | *Flags* is a bitwise combination of values with the
121 | following meanings (values 1 and 2 are not used):
122 |
123 | * *set* = 4
124 |
125 | The file was changed or created.
126 |
127 | * *del* = 8
128 |
129 | The file was deleted.
130 |
131 | * `WALK` *path*, *rev*, *offset* ⇒ *path*, *rev*, *value*
132 |
133 | Returns the *n*th file with a name matching *path*
134 | (a glob pattern) in the specified revision (*rev*),
135 | where *n* is *offset*.
136 |
137 | ## Errors
138 |
139 | The server might send a response with the `err_code` field
140 | set. In that case, `err_detail` might also be set, and
141 | the other optional response fields will be unset.
142 |
143 | If `err_detail` is set, it provides extra information as
144 | defined below.
145 |
146 | Error codes are defined with the following meanings:
147 |
148 | * `TAG_IN_USE`
149 |
150 | The server has noticed that the client sent two
151 | or more requests with the same tag. This is a
152 | serious error and always indicates a bug in the
153 | client.
154 |
155 | The server is not guaranteed to send this error.
156 |
157 | * `UNKNOWN_VERB`
158 |
159 | The verb used in the request is not in the list of
160 | verbs defined in the server.
161 |
162 | * `READONLY`
163 |
164 | The Doozer connection is read-only. Clients can attempt a
165 | connection to a new server if writes a needed.
166 |
167 | * `TOO_LATE`
168 |
169 | The rev given in the request is invalid;
170 | it has been garbage collected.
171 |
172 | The current default of history kept is 360,000 revs.
173 |
174 | * `REV_MISMATCH`
175 |
176 | A write operation has failed because the revision given
177 | was less than the revision of the file being set.
178 |
179 | * `BAD_PATH`
180 |
181 | The given path contains invalid characters.
182 |
183 | * `MISSING_ARG`
184 |
185 | The request's verb requires certain fields to be set
186 | and at least one of those fields was not set.
187 |
188 | * `RANGE`
189 |
190 | The `offset` provided is out of range.
191 |
192 | * `NOTDIR`
193 |
194 | The request operates only on a directory, but the
195 | given path is not a directory (either because it is a
196 | file or it is missing).
197 |
198 | * `ISDIR`
199 |
200 | The request operates only on a regular file, but the
201 | given path is a directory.
202 |
203 | * `NOENT`
204 |
205 | Some component of `path` doesn't exist.
206 |
207 | * `OTHER`
208 |
209 | Some other error has occurred. The `err_detail`
210 | string provides a description.
211 |
212 | Error value 0 is reserved.
213 |
214 | [protobuf]: http://code.google.com/p/protobuf/
215 | [9P]: http://plan9.bell-labs.com/magic/man2html/5/intro
216 | [data]: data-model.md
217 |
--------------------------------------------------------------------------------
/server/txn.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "code.google.com/p/goprotobuf/proto"
5 | "github.com/ha/doozerd/consensus"
6 | "github.com/ha/doozerd/store"
7 | "io"
8 | "log"
9 | "sort"
10 | "syscall"
11 | )
12 |
13 | type txn struct {
14 | c *conn
15 | req request
16 | resp response
17 | }
18 |
19 | var ops = map[int32]func(*txn){
20 | int32(request_DEL): (*txn).del,
21 | int32(request_GET): (*txn).get,
22 | int32(request_GETDIR): (*txn).getdir,
23 | int32(request_NOP): (*txn).nop,
24 | int32(request_REV): (*txn).rev,
25 | int32(request_SET): (*txn).set,
26 | int32(request_STAT): (*txn).stat,
27 | int32(request_SELF): (*txn).self,
28 | int32(request_WAIT): (*txn).wait,
29 | int32(request_WALK): (*txn).walk,
30 | int32(request_ACCESS): (*txn).access,
31 | }
32 |
33 | // response flags
34 | const (
35 | _ = 1 << iota
36 | _
37 | set
38 | del
39 | )
40 |
41 | func (t *txn) run() {
42 | verb := int32(t.req.GetVerb())
43 | if f, ok := ops[verb]; ok {
44 | f(t)
45 | } else {
46 | t.respondErrCode(response_UNKNOWN_VERB)
47 | }
48 | }
49 |
50 | func (t *txn) get() {
51 | if !t.c.raccess {
52 | t.respondOsError(syscall.EACCES)
53 | return
54 | }
55 |
56 | if t.req.Path == nil {
57 | t.respondErrCode(response_MISSING_ARG)
58 | return
59 | }
60 |
61 | go func() {
62 | g, err := t.getter()
63 | if err != nil {
64 | t.respondOsError(err)
65 | return
66 | }
67 |
68 | v, rev := g.Get(*t.req.Path)
69 | if rev == store.Dir {
70 | t.respondErrCode(response_ISDIR)
71 | return
72 | }
73 |
74 | t.resp.Rev = &rev
75 | if len(v) == 1 { // not missing
76 | t.resp.Value = []byte(v[0])
77 | }
78 | t.respond()
79 | }()
80 | }
81 |
82 | func (t *txn) set() {
83 | if !t.c.waccess {
84 | t.respondOsError(syscall.EACCES)
85 | return
86 | }
87 |
88 | if !t.c.canWrite {
89 | t.respondErrCode(response_READONLY)
90 | return
91 | }
92 |
93 | if t.req.Path == nil || t.req.Rev == nil {
94 | t.respondErrCode(response_MISSING_ARG)
95 | return
96 | }
97 |
98 | go func() {
99 | ev := consensus.Set(t.c.p, *t.req.Path, t.req.Value, *t.req.Rev)
100 | if ev.Err != nil {
101 | t.respondOsError(ev.Err)
102 | return
103 | }
104 | t.resp.Rev = &ev.Seqn
105 | t.respond()
106 | }()
107 | }
108 |
109 | func (t *txn) del() {
110 | if !t.c.waccess {
111 | t.respondOsError(syscall.EACCES)
112 | return
113 | }
114 |
115 | if !t.c.canWrite {
116 | t.respondErrCode(response_READONLY)
117 | return
118 | }
119 |
120 | if t.req.Path == nil || t.req.Rev == nil {
121 | t.respondErrCode(response_MISSING_ARG)
122 | return
123 | }
124 |
125 | go func() {
126 | ev := consensus.Del(t.c.p, *t.req.Path, *t.req.Rev)
127 | if ev.Err != nil {
128 | t.respondOsError(ev.Err)
129 | return
130 | }
131 | t.respond()
132 | }()
133 | }
134 |
135 | func (t *txn) nop() {
136 | if !t.c.waccess {
137 | t.respondOsError(syscall.EACCES)
138 | return
139 | }
140 |
141 | if !t.c.canWrite {
142 | t.respondErrCode(response_READONLY)
143 | return
144 | }
145 |
146 | go func() {
147 | t.c.p.Propose([]byte(store.Nop))
148 | t.respond()
149 | }()
150 | }
151 |
152 | func (t *txn) rev() {
153 | rev := <-t.c.st.Seqns
154 | t.resp.Rev = &rev
155 | t.respond()
156 | }
157 |
158 | func (t *txn) self() {
159 | t.resp.Value = []byte(t.c.self)
160 | t.respond()
161 | }
162 |
163 | func (t *txn) stat() {
164 | if !t.c.raccess {
165 | t.respondOsError(syscall.EACCES)
166 | return
167 | }
168 |
169 | go func() {
170 | g, err := t.getter()
171 | if err != nil {
172 | t.respondOsError(err)
173 | return
174 | }
175 |
176 | len, rev := g.Stat(t.req.GetPath())
177 | t.resp.Len = &len
178 | t.resp.Rev = &rev
179 | t.respond()
180 | }()
181 | }
182 |
183 | func (t *txn) getdir() {
184 | if !t.c.raccess {
185 | t.respondOsError(syscall.EACCES)
186 | return
187 | }
188 |
189 | if t.req.Path == nil || t.req.Offset == nil {
190 | t.respondErrCode(response_MISSING_ARG)
191 | return
192 | }
193 |
194 | go func() {
195 | g, err := t.getter()
196 | if err != nil {
197 | t.respondOsError(err)
198 | return
199 | }
200 |
201 | ents, rev := g.Get(*t.req.Path)
202 | if rev == store.Missing {
203 | t.respondErrCode(response_NOENT)
204 | return
205 | }
206 | if rev != store.Dir {
207 | t.respondErrCode(response_NOTDIR)
208 | return
209 | }
210 |
211 | sort.Strings(ents)
212 | offset := int(*t.req.Offset)
213 | if offset < 0 || offset >= len(ents) {
214 | t.respondErrCode(response_RANGE)
215 | return
216 | }
217 |
218 | t.resp.Path = &ents[offset]
219 | t.respond()
220 | }()
221 | }
222 |
223 | func (t *txn) wait() {
224 | if !t.c.raccess {
225 | t.respondOsError(syscall.EACCES)
226 | return
227 | }
228 |
229 | if t.req.Path == nil || t.req.Rev == nil {
230 | t.respondErrCode(response_MISSING_ARG)
231 | return
232 | }
233 |
234 | glob, err := store.CompileGlob(*t.req.Path)
235 | if err != nil {
236 | t.respondOsError(err)
237 | return
238 | }
239 |
240 | ch, err := t.c.st.Wait(glob, *t.req.Rev)
241 | if err != nil {
242 | t.respondOsError(err)
243 | return
244 | }
245 |
246 | go func() {
247 | ev := <-ch
248 | t.resp.Path = &ev.Path
249 | t.resp.Value = []byte(ev.Body)
250 | t.resp.Rev = &ev.Seqn
251 | switch {
252 | case ev.IsSet():
253 | t.resp.Flags = proto.Int32(set)
254 | case ev.IsDel():
255 | t.resp.Flags = proto.Int32(del)
256 | default:
257 | t.resp.Flags = proto.Int32(0)
258 | }
259 | t.respond()
260 | }()
261 | }
262 |
263 | func (t *txn) walk() {
264 | if !t.c.raccess {
265 | t.respondOsError(syscall.EACCES)
266 | return
267 | }
268 |
269 | if t.req.Path == nil || t.req.Offset == nil {
270 | t.respondErrCode(response_MISSING_ARG)
271 | return
272 | }
273 |
274 | glob, err := store.CompileGlob(*t.req.Path)
275 | if err != nil {
276 | t.respondOsError(err)
277 | return
278 | }
279 |
280 | offset := *t.req.Offset
281 | if offset < 0 {
282 | t.respondErrCode(response_RANGE)
283 | return
284 | }
285 |
286 | go func() {
287 | g, err := t.getter()
288 | if err != nil {
289 | t.respondOsError(err)
290 | return
291 | }
292 |
293 | f := func(path, body string, rev int64) (stop bool) {
294 | if offset == 0 {
295 | t.resp.Path = &path
296 | t.resp.Value = []byte(body)
297 | t.resp.Rev = &rev
298 | t.resp.Flags = proto.Int32(set)
299 | t.respond()
300 | return true
301 | }
302 | offset--
303 | return false
304 | }
305 | if !store.Walk(g, glob, f) {
306 | t.respondErrCode(response_RANGE)
307 | }
308 | }()
309 | }
310 |
311 | func (t *txn) access() {
312 | if t.c.grant(string(t.req.Value)) {
313 | t.respond()
314 | } else {
315 | t.respondOsError(syscall.EACCES)
316 | }
317 | }
318 |
319 | func (t *txn) respondOsError(err error) {
320 | switch err {
321 | case store.ErrBadPath:
322 | t.respondErrCode(response_BAD_PATH)
323 | case store.ErrRevMismatch:
324 | t.respondErrCode(response_REV_MISMATCH)
325 | case store.ErrTooLate:
326 | t.respondErrCode(response_TOO_LATE)
327 | case syscall.EISDIR:
328 | t.respondErrCode(response_ISDIR)
329 | case syscall.ENOTDIR:
330 | t.respondErrCode(response_NOTDIR)
331 | default:
332 | t.resp.ErrDetail = proto.String(err.Error())
333 | t.respondErrCode(response_OTHER)
334 | }
335 | }
336 |
337 | func (t *txn) respondErrCode(e response_Err) {
338 | t.resp.ErrCode = &e
339 | t.respond()
340 | }
341 |
342 | func (t *txn) respond() {
343 | t.resp.Tag = t.req.Tag
344 | err := t.c.write(&t.resp)
345 | if err != nil && err != io.EOF {
346 | log.Println(err)
347 | }
348 | }
349 |
350 | func (t *txn) getter() (store.Getter, error) {
351 | if t.req.Rev == nil {
352 | _, g := t.c.st.Snap()
353 | return g, nil
354 | }
355 |
356 | ch, err := t.c.st.Wait(store.Any, *t.req.Rev)
357 | if err != nil {
358 | return nil, err
359 | }
360 | return <-ch, nil
361 | }
362 |
--------------------------------------------------------------------------------
/consensus/run_test.go:
--------------------------------------------------------------------------------
1 | package consensus
2 |
3 | import (
4 | "code.google.com/p/goprotobuf/proto"
5 | "github.com/bmizerany/assert"
6 | "github.com/ha/doozerd/store"
7 | "net"
8 | "testing"
9 | )
10 |
11 | const (
12 | node = "/ctl/node"
13 | cal = "/ctl/cal"
14 | )
15 |
16 | func MustResolveUDPAddr(n, addr string) *net.UDPAddr {
17 | udp, err := net.ResolveUDPAddr(n, addr)
18 | if err != nil {
19 | panic(err)
20 | }
21 |
22 | return udp
23 | }
24 |
25 | type msgSlot struct {
26 | *msg
27 | }
28 |
29 | func (ms msgSlot) Put(m *msg) {
30 | *ms.msg = *m
31 | }
32 |
33 | func TestQuorum(t *testing.T) {
34 | assert.Equal(t, 1, (&run{cals: []string{"a"}}).quorum())
35 | assert.Equal(t, 2, (&run{cals: []string{"a", "b"}}).quorum())
36 | assert.Equal(t, 2, (&run{cals: []string{"a", "b", "c"}}).quorum())
37 | assert.Equal(t, 3, (&run{cals: []string{"a", "b", "c", "d"}}).quorum())
38 | assert.Equal(t, 3, (&run{cals: []string{"a", "b", "c", "d", "e"}}).quorum())
39 | assert.Equal(t, 4, (&run{cals: []string{"a", "b", "c", "d", "e", "f"}}).quorum())
40 | assert.Equal(t, 4, (&run{cals: []string{"a", "b", "c", "d", "e", "f", "g"}}).quorum())
41 | assert.Equal(t, 5, (&run{cals: []string{"a", "b", "c", "d", "e", "f", "g", "h"}}).quorum())
42 | }
43 |
44 | func TestRunVoteDelivered(t *testing.T) {
45 | r := run{}
46 | r.out = make(chan Packet, 100)
47 | r.ops = make(chan store.Op, 100)
48 | r.l.init(1, 1)
49 |
50 | p := packet{
51 | msg: msg{
52 | Seqn: proto.Int64(1),
53 | Cmd: vote,
54 | Vrnd: proto.Int64(1),
55 | Value: []byte("foo"),
56 | },
57 | Addr: MustResolveUDPAddr("udp", "1.2.3.4:5"),
58 | }
59 |
60 | r.update(&p, 0, new(triggers))
61 |
62 | assert.Equal(t, true, r.l.done)
63 | assert.Equal(t, "foo", r.l.v)
64 | }
65 |
66 | func TestRunInviteDelivered(t *testing.T) {
67 | var r run
68 | r.out = make(chan Packet, 100)
69 | r.ops = make(chan store.Op, 100)
70 |
71 | r.update(&packet{msg: *newInviteSeqn1(1)}, 0, new(triggers))
72 |
73 | assert.Equal(t, int64(1), r.a.rnd)
74 | }
75 |
76 | func TestRunProposeDelivered(t *testing.T) {
77 | var r run
78 | r.out = make(chan Packet, 100)
79 | r.ops = make(chan store.Op, 100)
80 |
81 | r.update(&packet{msg: msg{Cmd: propose}}, -1, new(triggers))
82 | assert.Equal(t, true, r.c.begun)
83 | }
84 |
85 | func TestRunSendsCoordPacket(t *testing.T) {
86 | c := make(chan Packet, 100)
87 | x := MustResolveUDPAddr("udp", "1.2.3.4:5")
88 | y := MustResolveUDPAddr("udp", "2.3.4.5:6")
89 | var r run
90 | r.c.crnd = 1
91 | r.out = c
92 | r.addr = []*net.UDPAddr{x, y}
93 |
94 | var got msg
95 | exp := msg{
96 | Seqn: proto.Int64(0),
97 | Cmd: invite,
98 | Crnd: proto.Int64(1),
99 | }
100 |
101 | r.update(&packet{msg: *newPropose("foo")}, -1, new(triggers))
102 | <-c
103 | err := proto.Unmarshal((<-c).Data, &got)
104 | assert.Equal(t, nil, err)
105 | assert.Equal(t, exp, got)
106 | assert.Equal(t, 0, len(c))
107 | }
108 |
109 | func TestRunSchedulesTick(t *testing.T) {
110 | var r run
111 | r.seqn = 1
112 | r.bound = 10
113 | r.out = make(chan Packet, 100)
114 | ticks := new(triggers)
115 |
116 | r.update(&packet{msg: *newPropose("foo")}, -1, ticks)
117 |
118 | assert.Equal(t, 1, ticks.Len())
119 | }
120 |
121 | func TestRunSendsAcceptorPacket(t *testing.T) {
122 | c := make(chan Packet, 100)
123 | x := MustResolveUDPAddr("udp", "1.2.3.4:5")
124 | y := MustResolveUDPAddr("udp", "2.3.4.5:6")
125 | var r run
126 | r.out = c
127 | r.addr = []*net.UDPAddr{x, y}
128 |
129 | var got msg
130 | exp := msg{
131 | Seqn: proto.Int64(0),
132 | Cmd: rsvp,
133 | Crnd: proto.Int64(1),
134 | Vrnd: proto.Int64(0),
135 | Value: []byte{},
136 | }
137 |
138 | r.update(&packet{msg: *newInviteSeqn1(1)}, 0, new(triggers))
139 | <-c
140 | err := proto.Unmarshal((<-c).Data, &got)
141 | assert.Equal(t, nil, err)
142 | assert.Equal(t, exp, got)
143 | assert.Equal(t, 0, len(c))
144 | }
145 |
146 | func TestRunSendsLearnerPacket(t *testing.T) {
147 | c := make(chan Packet, 100)
148 | var r run
149 | r.out = c
150 | r.ops = make(chan store.Op, 100)
151 | r.addr = []*net.UDPAddr{nil, nil}
152 | r.l.init(1, 1)
153 |
154 | var got msg
155 | exp := msg{
156 | Seqn: proto.Int64(0),
157 | Cmd: learn,
158 | Value: []byte("foo"),
159 | }
160 |
161 | r.update(&packet{msg: *newVote(1, "foo")}, 0, new(triggers))
162 | assert.Equal(t, 2, len(c))
163 | err := proto.Unmarshal((<-c).Data, &got)
164 | assert.Equal(t, nil, err)
165 | assert.Equal(t, exp, got)
166 | }
167 |
168 | func TestRunAppliesOp(t *testing.T) {
169 | c := make(chan store.Op, 100)
170 | var r run
171 | r.seqn = 1
172 | r.out = make(chan Packet, 100)
173 | r.ops = c
174 | r.l.init(1, 1)
175 |
176 | r.update(&packet{msg: *newVote(1, "foo")}, 0, new(triggers))
177 | assert.Equal(t, store.Op{1, "foo"}, <-c)
178 | }
179 |
180 | func TestRunBroadcastThree(t *testing.T) {
181 | c := make(chan Packet, 100)
182 | var r run
183 | r.seqn = 1
184 | r.out = c
185 | r.addr = []*net.UDPAddr{
186 | MustResolveUDPAddr("udp", "1.2.3.4:5"),
187 | MustResolveUDPAddr("udp", "2.3.4.5:6"),
188 | MustResolveUDPAddr("udp", "3.4.5.6:7"),
189 | }
190 |
191 | r.broadcast(newInvite(1))
192 | c <- Packet{}
193 |
194 | exp := msg{
195 | Seqn: proto.Int64(1),
196 | Cmd: invite,
197 | Crnd: proto.Int64(1),
198 | }
199 |
200 | addr := make([]*net.UDPAddr, len(r.addr))
201 | for i := 0; i < len(r.addr); i++ {
202 | p := <-c
203 | addr[i] = p.Addr
204 | var got msg
205 | err := proto.Unmarshal(p.Data, &got)
206 | assert.Equal(t, nil, err)
207 | assert.Equal(t, exp, got)
208 | }
209 |
210 | assert.Equal(t, Packet{}, <-c)
211 | assert.Equal(t, r.addr, addr)
212 | }
213 |
214 | func TestRunBroadcastFive(t *testing.T) {
215 | c := make(chan Packet, 100)
216 | var r run
217 | r.seqn = 1
218 | r.out = c
219 | r.addr = []*net.UDPAddr{
220 | MustResolveUDPAddr("udp", "1.2.3.4:5"),
221 | MustResolveUDPAddr("udp", "2.3.4.5:6"),
222 | MustResolveUDPAddr("udp", "3.4.5.6:7"),
223 | MustResolveUDPAddr("udp", "4.5.6.7:8"),
224 | MustResolveUDPAddr("udp", "5.6.7.8:9"),
225 | }
226 |
227 | r.broadcast(newInvite(1))
228 | c <- Packet{}
229 |
230 | exp := msg{
231 | Seqn: proto.Int64(1),
232 | Cmd: invite,
233 | Crnd: proto.Int64(1),
234 | }
235 |
236 | addr := make([]*net.UDPAddr, len(r.addr))
237 | for i := 0; i < len(r.addr); i++ {
238 | p := <-c
239 | addr[i] = p.Addr
240 | var got msg
241 | err := proto.Unmarshal(p.Data, &got)
242 | assert.Equal(t, nil, err)
243 | assert.Equal(t, exp, got)
244 | }
245 |
246 | assert.Equal(t, Packet{}, <-c)
247 | assert.Equal(t, r.addr, addr)
248 | }
249 |
250 | func TestRunBroadcastNil(t *testing.T) {
251 | c := make(chan Packet, 100)
252 | var r run
253 | r.out = c
254 | r.addr = []*net.UDPAddr{
255 | MustResolveUDPAddr("udp", "1.2.3.4:5"),
256 | MustResolveUDPAddr("udp", "2.3.4.5:6"),
257 | MustResolveUDPAddr("udp", "3.4.5.6:7"),
258 | }
259 |
260 | r.broadcast(nil)
261 | c <- Packet{}
262 | assert.Equal(t, Packet{}, <-c)
263 | }
264 |
265 | func TestRunIsLeader(t *testing.T) {
266 | r := &run{
267 | cals: []string{"a", "b", "c"}, // len(cals) == 3
268 | seqn: 3, // 3 % 3 == 0
269 | }
270 |
271 | assert.T(t, r.isLeader("a")) // index == 0
272 | assert.T(t, !r.isLeader("b")) // index == 1
273 | assert.T(t, !r.isLeader("c")) // index == 2
274 | assert.T(t, !r.isLeader("x")) // index DNE
275 | }
276 |
277 | func TestRunReturnTrueIfLearned(t *testing.T) {
278 | r := run{}
279 | r.out = make(chan Packet, 100)
280 | r.ops = make(chan store.Op, 100)
281 |
282 | p := packet{msg: msg{
283 | Seqn: proto.Int64(1),
284 | Cmd: learn,
285 | Value: []byte("foo"),
286 | }}
287 |
288 | r.update(&p, 0, new(triggers))
289 | assert.T(t, r.l.done)
290 | }
291 |
292 | func TestRunReturnFalseIfNotLearned(t *testing.T) {
293 | r := run{}
294 | r.out = make(chan Packet, 100)
295 | r.ops = make(chan store.Op, 100)
296 |
297 | p := packet{msg: msg{
298 | Seqn: proto.Int64(1),
299 | Cmd: invite,
300 | Value: []byte("foo"),
301 | }}
302 |
303 | r.update(&p, 0, new(triggers))
304 | assert.T(t, !r.l.done)
305 | }
306 |
307 | func TestRunIndexOfNilAddr(t *testing.T) {
308 | r := run{addr: []*net.UDPAddr{new(net.UDPAddr)}}
309 | assert.Equal(t, -1, r.indexOfAddr(nil))
310 | }
311 |
--------------------------------------------------------------------------------
/peer/peer.go:
--------------------------------------------------------------------------------
1 | package peer
2 |
3 | import (
4 | "github.com/ha/doozer"
5 | "github.com/ha/doozerd/consensus"
6 | "github.com/ha/doozerd/gc"
7 | "github.com/ha/doozerd/member"
8 | "github.com/ha/doozerd/server"
9 | "github.com/ha/doozerd/store"
10 | "github.com/ha/doozerd/web"
11 | "io"
12 | "log"
13 | "net"
14 | "os"
15 | "strings"
16 | "time"
17 | )
18 |
19 | const (
20 | alpha = 50
21 | maxUDPLen = 3000
22 | )
23 |
24 | const calDir = "/ctl/cal"
25 |
26 | var calGlob = store.MustCompileGlob(calDir + "/*")
27 |
28 | type proposer struct {
29 | seqns chan int64
30 | props chan *consensus.Prop
31 | st *store.Store
32 | }
33 |
34 | func (p *proposer) Propose(v []byte) (e store.Event) {
35 | for e.Mut != string(v) {
36 | n := <-p.seqns
37 | w, err := p.st.Wait(store.Any, n)
38 | if err != nil {
39 | panic(err) // can't happen
40 | }
41 | p.props <- &consensus.Prop{n, v}
42 | e = <-w
43 | }
44 | return
45 | }
46 |
47 | func Main(clusterName, self, buri, rwsk, rosk string, cl *doozer.Conn, udpConn *net.UDPConn, listener, webListener net.Listener, pulseInterval, fillDelay, kickTimeout int64, hi int64) {
48 | listenAddr := listener.Addr().String()
49 |
50 | canWrite := make(chan bool, 1)
51 | in := make(chan consensus.Packet, 50)
52 | out := make(chan consensus.Packet, 50)
53 |
54 | st := store.New()
55 | pr := &proposer{
56 | seqns: make(chan int64, alpha),
57 | props: make(chan *consensus.Prop),
58 | st: st,
59 | }
60 |
61 | calSrv := func(start int64) {
62 | go gc.Pulse(self, st.Seqns, pr, pulseInterval)
63 | go gc.Clean(st, hi, time.Tick(1e9))
64 | var m consensus.Manager
65 | m.Self = self
66 | m.DefRev = start
67 | m.Alpha = alpha
68 | m.In = in
69 | m.Out = out
70 | m.Ops = st.Ops
71 | m.PSeqn = pr.seqns
72 | m.Props = pr.props
73 | m.TFill = fillDelay
74 | m.Store = st
75 | m.Ticker = time.Tick(10e6)
76 | go m.Run()
77 | }
78 |
79 | hostname, err := os.Hostname()
80 | if err != nil {
81 | hostname = "unknown"
82 | }
83 |
84 | if cl == nil { // we are the only node in a new cluster
85 | set(st, "/ctl/name", clusterName, store.Missing)
86 | set(st, "/ctl/node/"+self+"/addr", listenAddr, store.Missing)
87 | set(st, "/ctl/node/"+self+"/hostname", hostname, store.Missing)
88 | set(st, "/ctl/node/"+self+"/version", Version, store.Missing)
89 | set(st, "/ctl/cal/0", self, store.Missing)
90 | if buri == "" {
91 | set(st, "/ctl/ns/"+clusterName+"/"+self, listenAddr, store.Missing)
92 | }
93 | calSrv(<-st.Seqns)
94 | // Skip ahead alpha steps so that the registrar can provide a
95 | // meaningful cluster.
96 | for i := 0; i < alpha; i++ {
97 | st.Ops <- store.Op{1 + <-st.Seqns, store.Nop}
98 | }
99 | canWrite <- true
100 | go setReady(pr, self)
101 | } else {
102 | setC(cl, "/ctl/node/"+self+"/addr", listenAddr, store.Clobber)
103 | setC(cl, "/ctl/node/"+self+"/hostname", hostname, store.Clobber)
104 | setC(cl, "/ctl/node/"+self+"/version", Version, store.Clobber)
105 |
106 | rev, err := cl.Rev()
107 | if err != nil {
108 | panic(err)
109 | }
110 |
111 | stop := make(chan bool, 1)
112 | go follow(st, cl, rev+1, stop)
113 |
114 | errs := make(chan error)
115 | go func() {
116 | e, ok := <-errs
117 | if ok {
118 | panic(e)
119 | }
120 | }()
121 | doozer.Walk(cl, rev, "/", cloner{st.Ops, cl, rev}, errs)
122 | close(errs)
123 | st.Flush()
124 |
125 | ch, err := st.Wait(store.Any, rev+1)
126 | if err == nil {
127 | <-ch
128 | }
129 |
130 | go func() {
131 | n := activate(st, self, cl)
132 | calSrv(n)
133 | advanceUntil(cl, st.Seqns, n+alpha)
134 | stop <- true
135 | canWrite <- true
136 | go setReady(pr, self)
137 | if buri != "" {
138 | b, err := doozer.DialUri(buri, "")
139 | if err != nil {
140 | panic(err)
141 | }
142 | setC(
143 | b,
144 | "/ctl/ns/"+clusterName+"/"+self,
145 | listenAddr,
146 | store.Missing,
147 | )
148 | }
149 | }()
150 | }
151 |
152 | shun := make(chan string, 3) // sufficient for a cluster of 7
153 | go member.Clean(shun, st, pr)
154 | go server.ListenAndServe(listener, canWrite, st, pr, rwsk, rosk, self)
155 |
156 | if rwsk == "" && rosk == "" && webListener != nil {
157 | web.Store = st
158 | web.ClusterName = clusterName
159 | go web.Serve(webListener)
160 | }
161 |
162 | go func() {
163 | for p := range out {
164 | n, err := udpConn.WriteTo(p.Data, p.Addr)
165 | if err != nil {
166 | log.Println(err)
167 | continue
168 | }
169 | if n != len(p.Data) {
170 | log.Println("packet len too long:", len(p.Data))
171 | continue
172 | }
173 | }
174 | }()
175 |
176 | selfAddr, ok := udpConn.LocalAddr().(*net.UDPAddr)
177 | if !ok {
178 | panic("no UDP addr")
179 | }
180 | lv := liveness{
181 | timeout: kickTimeout,
182 | ival: kickTimeout / 2,
183 | self: selfAddr,
184 | shun: shun,
185 | }
186 | for {
187 | t := time.Now().UnixNano()
188 |
189 | buf := make([]byte, maxUDPLen)
190 | n, addr, err := udpConn.ReadFromUDP(buf)
191 | if err != nil && strings.Contains(err.Error(), "use of closed network connection") {
192 | log.Printf("<<<< EXITING >>>>")
193 | return
194 | }
195 | if err != nil {
196 | log.Println(err)
197 | continue
198 | }
199 |
200 | buf = buf[:n]
201 |
202 | lv.mark(addr, t)
203 | lv.check(t)
204 |
205 | in <- consensus.Packet{addr, buf}
206 | }
207 | }
208 |
209 | func activate(st *store.Store, self string, c *doozer.Conn) int64 {
210 | rev, _ := st.Snap()
211 |
212 | for _, base := range store.Getdir(st, calDir) {
213 | p := calDir + "/" + base
214 | v, rev := st.Get(p)
215 | if rev != store.Dir && v[0] == "" {
216 | seqn, err := c.Set(p, rev, []byte(self))
217 | if err != nil {
218 | log.Println(err)
219 | continue
220 | }
221 |
222 | return seqn
223 | }
224 | }
225 |
226 | for {
227 | ch, err := st.Wait(calGlob, rev+1)
228 | if err != nil {
229 | panic(err)
230 | }
231 | ev, ok := <-ch
232 | if !ok {
233 | panic(io.EOF)
234 | }
235 | rev = ev.Rev
236 | // TODO ev.IsEmpty()
237 | if ev.IsSet() && ev.Body == "" {
238 | seqn, err := c.Set(ev.Path, ev.Rev, []byte(self))
239 | if err != nil {
240 | log.Println(err)
241 | continue
242 | }
243 | return seqn
244 | } else if ev.IsSet() && ev.Body == self {
245 | return ev.Seqn
246 | }
247 | }
248 |
249 | return 0
250 | }
251 |
252 | func advanceUntil(cl *doozer.Conn, ver <-chan int64, done int64) {
253 | for <-ver < done {
254 | cl.Nop()
255 | }
256 | }
257 |
258 | func set(st *store.Store, path, body string, rev int64) {
259 | mut := store.MustEncodeSet(path, body, rev)
260 | st.Ops <- store.Op{1 + <-st.Seqns, mut}
261 | }
262 |
263 | func setC(cl *doozer.Conn, path, body string, rev int64) {
264 | _, err := cl.Set(path, rev, []byte(body))
265 | if err != nil {
266 | panic(err)
267 | }
268 | }
269 |
270 | func follow(st *store.Store, cl *doozer.Conn, rev int64, stop chan bool) {
271 | for {
272 | ev, err := cl.Wait("/**", rev)
273 | if err != nil {
274 | panic(err)
275 | }
276 |
277 | // store.Clobber is okay here because the event
278 | // has already passed through another store
279 | mut := store.MustEncodeSet(ev.Path, string(ev.Body), store.Clobber)
280 | st.Ops <- store.Op{ev.Rev, mut}
281 | rev = ev.Rev + 1
282 |
283 | select {
284 | case <-stop:
285 | return
286 | default:
287 | }
288 | }
289 | }
290 |
291 | type cloner struct {
292 | ch chan<- store.Op
293 | cl *doozer.Conn
294 | storeRev int64
295 | }
296 |
297 | func (c cloner) VisitDir(path string, f *doozer.FileInfo) bool {
298 | return true
299 | }
300 |
301 | func (c cloner) VisitFile(path string, f *doozer.FileInfo) {
302 | // store.Clobber is okay here because the event
303 | // has already passed through another store
304 | body, _, err := c.cl.Get(path, &c.storeRev)
305 | if err != nil {
306 | panic(err)
307 | }
308 | mut := store.MustEncodeSet(path, string(body), store.Clobber)
309 | c.ch <- store.Op{f.Rev, mut}
310 | }
311 |
312 | func setReady(p consensus.Proposer, self string) {
313 | m, err := store.EncodeSet("/ctl/node/"+self+"/writable", "true", 0)
314 | if err != nil {
315 | log.Println(err)
316 | return
317 | }
318 | p.Propose([]byte(m))
319 | }
320 |
--------------------------------------------------------------------------------
/server/msg.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go.
2 | // source: msg.proto
3 | // DO NOT EDIT!
4 |
5 | package server
6 |
7 | import proto "code.google.com/p/goprotobuf/proto"
8 | import json "encoding/json"
9 | import math "math"
10 |
11 | // Reference proto, json, and math imports to suppress error if they are not otherwise used.
12 | var _ = proto.Marshal
13 | var _ = &json.SyntaxError{}
14 | var _ = math.Inf
15 |
16 | type request_Verb int32
17 |
18 | const (
19 | request_GET request_Verb = 1
20 | request_SET request_Verb = 2
21 | request_DEL request_Verb = 3
22 | request_REV request_Verb = 5
23 | request_WAIT request_Verb = 6
24 | request_NOP request_Verb = 7
25 | request_WALK request_Verb = 9
26 | request_GETDIR request_Verb = 14
27 | request_STAT request_Verb = 16
28 | request_SELF request_Verb = 20
29 | request_ACCESS request_Verb = 99
30 | )
31 |
32 | var request_Verb_name = map[int32]string{
33 | 1: "GET",
34 | 2: "SET",
35 | 3: "DEL",
36 | 5: "REV",
37 | 6: "WAIT",
38 | 7: "NOP",
39 | 9: "WALK",
40 | 14: "GETDIR",
41 | 16: "STAT",
42 | 20: "SELF",
43 | 99: "ACCESS",
44 | }
45 | var request_Verb_value = map[string]int32{
46 | "GET": 1,
47 | "SET": 2,
48 | "DEL": 3,
49 | "REV": 5,
50 | "WAIT": 6,
51 | "NOP": 7,
52 | "WALK": 9,
53 | "GETDIR": 14,
54 | "STAT": 16,
55 | "SELF": 20,
56 | "ACCESS": 99,
57 | }
58 |
59 | func (x request_Verb) Enum() *request_Verb {
60 | p := new(request_Verb)
61 | *p = x
62 | return p
63 | }
64 | func (x request_Verb) String() string {
65 | return proto.EnumName(request_Verb_name, int32(x))
66 | }
67 | func (x request_Verb) MarshalJSON() ([]byte, error) {
68 | return json.Marshal(x.String())
69 | }
70 | func (x *request_Verb) UnmarshalJSON(data []byte) error {
71 | value, err := proto.UnmarshalJSONEnum(request_Verb_value, data, "request_Verb")
72 | if err != nil {
73 | return err
74 | }
75 | *x = request_Verb(value)
76 | return nil
77 | }
78 |
79 | type response_Err int32
80 |
81 | const (
82 | response_OTHER response_Err = 127
83 | response_TAG_IN_USE response_Err = 1
84 | response_UNKNOWN_VERB response_Err = 2
85 | response_READONLY response_Err = 3
86 | response_TOO_LATE response_Err = 4
87 | response_REV_MISMATCH response_Err = 5
88 | response_BAD_PATH response_Err = 6
89 | response_MISSING_ARG response_Err = 7
90 | response_RANGE response_Err = 8
91 | response_NOTDIR response_Err = 20
92 | response_ISDIR response_Err = 21
93 | response_NOENT response_Err = 22
94 | )
95 |
96 | var response_Err_name = map[int32]string{
97 | 127: "OTHER",
98 | 1: "TAG_IN_USE",
99 | 2: "UNKNOWN_VERB",
100 | 3: "READONLY",
101 | 4: "TOO_LATE",
102 | 5: "REV_MISMATCH",
103 | 6: "BAD_PATH",
104 | 7: "MISSING_ARG",
105 | 8: "RANGE",
106 | 20: "NOTDIR",
107 | 21: "ISDIR",
108 | 22: "NOENT",
109 | }
110 | var response_Err_value = map[string]int32{
111 | "OTHER": 127,
112 | "TAG_IN_USE": 1,
113 | "UNKNOWN_VERB": 2,
114 | "READONLY": 3,
115 | "TOO_LATE": 4,
116 | "REV_MISMATCH": 5,
117 | "BAD_PATH": 6,
118 | "MISSING_ARG": 7,
119 | "RANGE": 8,
120 | "NOTDIR": 20,
121 | "ISDIR": 21,
122 | "NOENT": 22,
123 | }
124 |
125 | func (x response_Err) Enum() *response_Err {
126 | p := new(response_Err)
127 | *p = x
128 | return p
129 | }
130 | func (x response_Err) String() string {
131 | return proto.EnumName(response_Err_name, int32(x))
132 | }
133 | func (x response_Err) MarshalJSON() ([]byte, error) {
134 | return json.Marshal(x.String())
135 | }
136 | func (x *response_Err) UnmarshalJSON(data []byte) error {
137 | value, err := proto.UnmarshalJSONEnum(response_Err_value, data, "response_Err")
138 | if err != nil {
139 | return err
140 | }
141 | *x = response_Err(value)
142 | return nil
143 | }
144 |
145 | type request struct {
146 | Tag *int32 `protobuf:"varint,1,opt,name=tag" json:"tag,omitempty"`
147 | Verb *request_Verb `protobuf:"varint,2,opt,name=verb,enum=server.request_Verb" json:"verb,omitempty"`
148 | Path *string `protobuf:"bytes,4,opt,name=path" json:"path,omitempty"`
149 | Value []byte `protobuf:"bytes,5,opt,name=value" json:"value,omitempty"`
150 | OtherTag *int32 `protobuf:"varint,6,opt,name=other_tag" json:"other_tag,omitempty"`
151 | Offset *int32 `protobuf:"varint,7,opt,name=offset" json:"offset,omitempty"`
152 | Rev *int64 `protobuf:"varint,9,opt,name=rev" json:"rev,omitempty"`
153 | XXX_unrecognized []byte `json:"-"`
154 | }
155 |
156 | func (this *request) Reset() { *this = request{} }
157 | func (this *request) String() string { return proto.CompactTextString(this) }
158 | func (*request) ProtoMessage() {}
159 |
160 | func (this *request) GetTag() int32 {
161 | if this != nil && this.Tag != nil {
162 | return *this.Tag
163 | }
164 | return 0
165 | }
166 |
167 | func (this *request) GetVerb() request_Verb {
168 | if this != nil && this.Verb != nil {
169 | return *this.Verb
170 | }
171 | return 0
172 | }
173 |
174 | func (this *request) GetPath() string {
175 | if this != nil && this.Path != nil {
176 | return *this.Path
177 | }
178 | return ""
179 | }
180 |
181 | func (this *request) GetValue() []byte {
182 | if this != nil {
183 | return this.Value
184 | }
185 | return nil
186 | }
187 |
188 | func (this *request) GetOtherTag() int32 {
189 | if this != nil && this.OtherTag != nil {
190 | return *this.OtherTag
191 | }
192 | return 0
193 | }
194 |
195 | func (this *request) GetOffset() int32 {
196 | if this != nil && this.Offset != nil {
197 | return *this.Offset
198 | }
199 | return 0
200 | }
201 |
202 | func (this *request) GetRev() int64 {
203 | if this != nil && this.Rev != nil {
204 | return *this.Rev
205 | }
206 | return 0
207 | }
208 |
209 | type response struct {
210 | Tag *int32 `protobuf:"varint,1,opt,name=tag" json:"tag,omitempty"`
211 | Flags *int32 `protobuf:"varint,2,opt,name=flags" json:"flags,omitempty"`
212 | Rev *int64 `protobuf:"varint,3,opt,name=rev" json:"rev,omitempty"`
213 | Path *string `protobuf:"bytes,5,opt,name=path" json:"path,omitempty"`
214 | Value []byte `protobuf:"bytes,6,opt,name=value" json:"value,omitempty"`
215 | Len *int32 `protobuf:"varint,8,opt,name=len" json:"len,omitempty"`
216 | ErrCode *response_Err `protobuf:"varint,100,opt,name=err_code,enum=server.response_Err" json:"err_code,omitempty"`
217 | ErrDetail *string `protobuf:"bytes,101,opt,name=err_detail" json:"err_detail,omitempty"`
218 | XXX_unrecognized []byte `json:"-"`
219 | }
220 |
221 | func (this *response) Reset() { *this = response{} }
222 | func (this *response) String() string { return proto.CompactTextString(this) }
223 | func (*response) ProtoMessage() {}
224 |
225 | func (this *response) GetTag() int32 {
226 | if this != nil && this.Tag != nil {
227 | return *this.Tag
228 | }
229 | return 0
230 | }
231 |
232 | func (this *response) GetFlags() int32 {
233 | if this != nil && this.Flags != nil {
234 | return *this.Flags
235 | }
236 | return 0
237 | }
238 |
239 | func (this *response) GetRev() int64 {
240 | if this != nil && this.Rev != nil {
241 | return *this.Rev
242 | }
243 | return 0
244 | }
245 |
246 | func (this *response) GetPath() string {
247 | if this != nil && this.Path != nil {
248 | return *this.Path
249 | }
250 | return ""
251 | }
252 |
253 | func (this *response) GetValue() []byte {
254 | if this != nil {
255 | return this.Value
256 | }
257 | return nil
258 | }
259 |
260 | func (this *response) GetLen() int32 {
261 | if this != nil && this.Len != nil {
262 | return *this.Len
263 | }
264 | return 0
265 | }
266 |
267 | func (this *response) GetErrCode() response_Err {
268 | if this != nil && this.ErrCode != nil {
269 | return *this.ErrCode
270 | }
271 | return 0
272 | }
273 |
274 | func (this *response) GetErrDetail() string {
275 | if this != nil && this.ErrDetail != nil {
276 | return *this.ErrDetail
277 | }
278 | return ""
279 | }
280 |
281 | func init() {
282 | proto.RegisterEnum("server.request_Verb", request_Verb_name, request_Verb_value)
283 | proto.RegisterEnum("server.response_Err", response_Err_name, response_Err_value)
284 | }
285 |
--------------------------------------------------------------------------------
/consensus/manager.go:
--------------------------------------------------------------------------------
1 | package consensus
2 |
3 | import (
4 | "code.google.com/p/goprotobuf/proto"
5 | "container/heap"
6 | "github.com/ha/doozerd/store"
7 | "log"
8 | "net"
9 | "sort"
10 | "time"
11 | )
12 |
13 | type packet struct {
14 | Addr *net.UDPAddr
15 | msg
16 | }
17 |
18 | type packets []*packet
19 |
20 | func (p *packets) Len() int {
21 | return len(*p)
22 | }
23 |
24 | func (p *packets) Less(i, j int) bool {
25 | a := *p
26 | return *a[i].Seqn < *a[j].Seqn
27 | }
28 |
29 | func (p *packets) Push(x interface{}) {
30 | *p = append(*p, x.(*packet))
31 | }
32 |
33 | func (p *packets) Pop() (x interface{}) {
34 | a := *p
35 | i := len(a) - 1
36 | *p, x = a[:i], a[i]
37 | return
38 | }
39 |
40 | func (p *packets) Swap(i, j int) {
41 | a := *p
42 | a[i], a[j] = a[j], a[i]
43 | }
44 |
45 | type Packet struct {
46 | Addr *net.UDPAddr
47 | Data []byte
48 | }
49 |
50 | type trigger struct {
51 | t int64 // trigger time
52 | n int64 // seqn
53 | }
54 |
55 | type triggers []trigger
56 |
57 | func (t *triggers) Len() int {
58 | return len(*t)
59 | }
60 |
61 | func (t *triggers) Less(i, j int) bool {
62 | a := *t
63 | if a[i].t == a[j].t {
64 | return a[i].n < a[j].n
65 | }
66 | return a[i].t < a[j].t
67 | }
68 |
69 | func (t *triggers) Push(x interface{}) {
70 | *t = append(*t, x.(trigger))
71 | }
72 |
73 | func (t *triggers) Pop() (x interface{}) {
74 | a := *t
75 | i := len(a) - 1
76 | *t, x = a[:i], a[i]
77 | return nil
78 | }
79 |
80 | func (t *triggers) Swap(i, j int) {
81 | a := *t
82 | a[i], a[j] = a[j], a[i]
83 | }
84 |
85 | type Stats struct {
86 | // Current queue sizes
87 | Runs int
88 | WaitPackets int
89 | WaitTicks int
90 |
91 | // Totals over all time
92 | TotalRuns int64
93 | TotalFills int64
94 | TotalTicks int64
95 | TotalRecv [nmsg]int64
96 | }
97 |
98 | // DefRev is the rev in which this manager was defined;
99 | // it will participate starting at DefRev+Alpha.
100 | type Manager struct {
101 | Self string
102 | DefRev int64
103 | Alpha int64
104 | In <-chan Packet
105 | Out chan<- Packet
106 | Ops chan<- store.Op
107 | PSeqn chan<- int64
108 | Props <-chan *Prop
109 | TFill int64
110 | Store *store.Store
111 | Ticker <-chan time.Time
112 | Stats Stats
113 | run map[int64]*run
114 | next int64 // unused seqn
115 | fill triggers
116 | packet packets
117 | tick triggers
118 | }
119 |
120 | type Prop struct {
121 | Seqn int64
122 | Mut []byte
123 | }
124 |
125 | var tickTemplate = &msg{Cmd: tick}
126 | var fillTemplate = &msg{Cmd: propose, Value: []byte(store.Nop)}
127 |
128 | func (m *Manager) Run() {
129 | m.run = make(map[int64]*run)
130 | runCh, err := m.Store.Wait(store.Any, m.DefRev)
131 | if err != nil {
132 | panic(err) // can't happen
133 | }
134 |
135 | for {
136 | m.Stats.Runs = len(m.run)
137 | m.Stats.WaitPackets = len(m.packet)
138 | m.Stats.WaitTicks = len(m.tick)
139 |
140 | select {
141 | case e, ok := <-runCh:
142 | if !ok {
143 | return
144 | }
145 | log.Println("event", e)
146 |
147 | runCh, err = m.Store.Wait(store.Any, e.Seqn+1)
148 | if err != nil {
149 | panic(err) // can't happen
150 | }
151 |
152 | m.event(e)
153 | m.Stats.TotalRuns++
154 | log.Println("runs:", fmtRuns(m.run))
155 | log.Println("avg tick delay:", avg(m.tick))
156 | log.Println("avg fill delay:", avg(m.fill))
157 | case p := <-m.In:
158 | if p1 := recvPacket(&m.packet, p); p1 != nil {
159 | m.Stats.TotalRecv[*p1.msg.Cmd]++
160 | }
161 | case pr := <-m.Props:
162 | m.propose(&m.packet, pr, time.Now().UnixNano())
163 | case t := <-m.Ticker:
164 | m.doTick(t.UnixNano())
165 | }
166 |
167 | m.pump()
168 | }
169 | }
170 |
171 | func (m *Manager) pump() {
172 | for len(m.packet) > 0 {
173 | p := m.packet[0]
174 | log.Printf("p.seqn=%d m.next=%d", *p.Seqn, m.next)
175 | if *p.Seqn >= m.next {
176 | break
177 | }
178 | heap.Pop(&m.packet)
179 |
180 | r := m.run[*p.Seqn]
181 | if r == nil || r.l.done {
182 | go sendLearn(m.Out, p, m.Store)
183 | } else {
184 | r.update(p, r.indexOfAddr(p.Addr), &m.tick)
185 | }
186 | }
187 | }
188 |
189 | func (m *Manager) doTick(t int64) {
190 | n := applyTriggers(&m.packet, &m.fill, t, fillTemplate)
191 | m.Stats.TotalFills += int64(n)
192 | if n > 0 {
193 | log.Println("applied fills", n)
194 | }
195 |
196 | n = applyTriggers(&m.packet, &m.tick, t, tickTemplate)
197 | m.Stats.TotalTicks += int64(n)
198 | if n > 0 {
199 | log.Println("applied m.tick", n)
200 | }
201 | }
202 |
203 | func (m *Manager) propose(q heap.Interface, pr *Prop, t int64) {
204 | log.Println("prop", pr)
205 | p := new(packet)
206 | p.msg.Seqn = &pr.Seqn
207 | p.msg.Cmd = propose
208 | p.msg.Value = pr.Mut
209 | heap.Push(q, p)
210 | for n := pr.Seqn - 1; ; n-- {
211 | r := m.run[n]
212 | if r == nil || r.isLeader(m.Self) {
213 | break
214 | } else {
215 | schedTrigger(&m.fill, n, t, m.TFill)
216 | }
217 | }
218 | }
219 |
220 | func sendLearn(out chan<- Packet, p *packet, st *store.Store) {
221 | if p.msg.Cmd != nil && *p.msg.Cmd == msg_INVITE {
222 | ch, err := st.Wait(store.Any, *p.Seqn)
223 |
224 | if err == store.ErrTooLate {
225 | log.Println(err)
226 | } else {
227 | e := <-ch
228 | m := msg{
229 | Seqn: &e.Seqn,
230 | Cmd: learn,
231 | Value: []byte(e.Mut),
232 | }
233 | buf, _ := proto.Marshal(&m)
234 | out <- Packet{p.Addr, buf}
235 | }
236 | }
237 | }
238 |
239 | func recvPacket(q heap.Interface, P Packet) (p *packet) {
240 | p = new(packet)
241 | p.Addr = P.Addr
242 |
243 | err := proto.Unmarshal(P.Data, &p.msg)
244 | if err != nil {
245 | log.Println(err)
246 | return nil
247 | }
248 |
249 | if p.msg.Seqn == nil || p.msg.Cmd == nil {
250 | log.Printf("discarding %#v", p)
251 | return nil
252 | }
253 |
254 | heap.Push(q, p)
255 | return p
256 | }
257 |
258 | func avg(v []trigger) (n int64) {
259 | t := time.Now().UnixNano()
260 | if len(v) == 0 {
261 | return -1
262 | }
263 | for _, x := range v {
264 | n += x.t - t
265 | }
266 | return n / int64(len(v))
267 | }
268 |
269 | func schedTrigger(q heap.Interface, n, t, tfill int64) {
270 | heap.Push(q, trigger{n: n, t: t + tfill})
271 | }
272 |
273 | func applyTriggers(ps *packets, ticks *triggers, now int64, tpl *msg) (n int) {
274 | for ticks.Len() > 0 {
275 | tt := (*ticks)[0]
276 | if tt.t > now {
277 | break
278 | }
279 |
280 | heap.Pop(ticks)
281 |
282 | p := new(packet)
283 | p.msg = *tpl
284 | p.msg.Seqn = &tt.n
285 | log.Println("applying", *p.Seqn, msg_Cmd_name[int32(*p.Cmd)])
286 | heap.Push(ps, p)
287 | n++
288 | }
289 | return
290 | }
291 |
292 | func (m *Manager) event(e store.Event) {
293 | delete(m.run, e.Seqn)
294 | log.Printf("del run %d", e.Seqn)
295 | m.addRun(e)
296 | }
297 |
298 | func (m *Manager) addRun(e store.Event) (r *run) {
299 | r = new(run)
300 | r.self = m.Self
301 | r.out = m.Out
302 | r.ops = m.Ops
303 | r.bound = initialWaitBound
304 | r.seqn = e.Seqn + m.Alpha
305 | r.cals = getCals(e)
306 | r.addr = getAddrs(e, r.cals)
307 | if len(r.cals) < 1 {
308 | r.cals = m.run[r.seqn-1].cals
309 | r.addr = m.run[r.seqn-1].addr
310 | }
311 | r.c.size = len(r.cals)
312 | r.c.quor = r.quorum()
313 | r.c.crnd = r.indexOf(r.self) + int64(len(r.cals))
314 | r.l.init(len(r.cals), int64(r.quorum()))
315 | m.run[r.seqn] = r
316 | if r.isLeader(m.Self) {
317 | log.Printf("pseqn %d", r.seqn)
318 | m.PSeqn <- r.seqn
319 | }
320 | log.Printf("add run %d", r.seqn)
321 | m.next = r.seqn + 1
322 | return r
323 | }
324 |
325 | func getCals(g store.Getter) []string {
326 | ents := store.Getdir(g, "/ctl/cal")
327 | cals := make([]string, len(ents))
328 |
329 | i := 0
330 | for _, cal := range ents {
331 | id := store.GetString(g, "/ctl/cal/"+cal)
332 | if id != "" {
333 | cals[i] = id
334 | i++
335 | }
336 | }
337 |
338 | cals = cals[0:i]
339 | sort.Strings(cals)
340 |
341 | return cals
342 | }
343 |
344 | func getAddrs(g store.Getter, cals []string) (a []*net.UDPAddr) {
345 | a = make([]*net.UDPAddr, len(cals))
346 | var i int
347 | var err error
348 | for _, id := range cals {
349 | s := store.GetString(g, "/ctl/node/"+id+"/addr")
350 | a[i], err = net.ResolveUDPAddr("udp", s)
351 | if err != nil {
352 | log.Println(err)
353 | } else {
354 | i++
355 | }
356 | }
357 | return a[:i]
358 | }
359 |
360 | func fmtRuns(rs map[int64]*run) (s string) {
361 | var ns []int
362 | for i := range rs {
363 | ns = append(ns, int(i))
364 | }
365 | sort.Ints(ns)
366 | for _, i := range ns {
367 | r := rs[int64(i)]
368 | if r.l.done {
369 | s += "X"
370 | } else if r.prop {
371 | s += "o"
372 | } else {
373 | s += "."
374 | }
375 | }
376 | return s
377 | }
378 |
--------------------------------------------------------------------------------
/store/store.go:
--------------------------------------------------------------------------------
1 | package store
2 |
3 | import (
4 | "errors"
5 | "math"
6 | "regexp"
7 | "strconv"
8 | "strings"
9 | )
10 |
11 | // Special values for a revision.
12 | const (
13 | Missing = int64(-iota)
14 | Clobber
15 | Dir
16 | nop
17 | )
18 |
19 | // TODO revisit this when package regexp is more complete (e.g. do Unicode)
20 | const charPat = `[a-zA-Z0-9.\-]`
21 |
22 | var pathRe = mustBuildRe(charPat)
23 |
24 | var Any = MustCompileGlob("/**")
25 |
26 | var ErrTooLate = errors.New("too late")
27 |
28 | var (
29 | ErrBadMutation = errors.New("bad mutation")
30 | ErrRevMismatch = errors.New("rev mismatch")
31 | ErrBadPath = errors.New("bad path")
32 | )
33 |
34 | func mustBuildRe(p string) *regexp.Regexp {
35 | return regexp.MustCompile(`^/$|^(/` + p + `+)+$`)
36 | }
37 |
38 | // Applies mutations sent on Ops in sequence according to field Seqn. Any
39 | // errors that occur will be written to ErrorPath. Duplicate operations at a
40 | // given position are sliently ignored.
41 | type Store struct {
42 | Ops chan<- Op
43 | Seqns <-chan int64
44 | Waiting <-chan int
45 | watchCh chan *watch
46 | watches []*watch
47 | todo []Op
48 | state *state
49 | head int64
50 | log map[int64]Event
51 | cleanCh chan int64
52 | flush chan bool
53 | }
54 |
55 | // Represents an operation to apply to the store at position Seqn.
56 | //
57 | // If Mut is Nop, no change will be made, but an event will still be sent.
58 | type Op struct {
59 | Seqn int64
60 | Mut string
61 | }
62 |
63 | type state struct {
64 | ver int64
65 | root node
66 | }
67 |
68 | type watch struct {
69 | glob *Glob
70 | rev int64
71 | c chan<- Event
72 | }
73 |
74 | // Creates a new, empty data store. Mutations will be applied in order,
75 | // starting at number 1 (number 0 can be thought of as the creation of the
76 | // store).
77 | func New() *Store {
78 | ops := make(chan Op)
79 | seqns := make(chan int64)
80 | watches := make(chan int)
81 |
82 | st := &Store{
83 | Ops: ops,
84 | Seqns: seqns,
85 | Waiting: watches,
86 | watchCh: make(chan *watch),
87 | watches: []*watch{},
88 | state: &state{0, emptyDir},
89 | log: map[int64]Event{},
90 | cleanCh: make(chan int64),
91 | flush: make(chan bool),
92 | }
93 |
94 | go st.process(ops, seqns, watches)
95 | return st
96 | }
97 |
98 | func split(path string) []string {
99 | if path == "/" {
100 | return []string{}
101 | }
102 | return strings.Split(path[1:], "/")
103 | }
104 |
105 | func join(parts []string) string {
106 | return "/" + strings.Join(parts, "/")
107 | }
108 |
109 | func checkPath(k string) error {
110 | if !pathRe.MatchString(k) {
111 | return ErrBadPath
112 | }
113 | return nil
114 | }
115 |
116 | // Returns a mutation that can be applied to a `Store`. The mutation will set
117 | // the contents of the file at `path` to `body` iff `rev` is greater than
118 | // of equal to the file's revision at the time of application, with
119 | // one exception: if `rev` is Clobber, the file will be set unconditionally.
120 | func EncodeSet(path, body string, rev int64) (mutation string, err error) {
121 | if err = checkPath(path); err != nil {
122 | return
123 | }
124 | return strconv.FormatInt(rev, 10) + ":" + path + "=" + body, nil
125 | }
126 |
127 | // Returns a mutation that can be applied to a `Store`. The mutation will cause
128 | // the file at `path` to be deleted iff `rev` is greater than
129 | // of equal to the file's revision at the time of application, with
130 | // one exception: if `rev` is Clobber, the file will be deleted
131 | // unconditionally.
132 | func EncodeDel(path string, rev int64) (mutation string, err error) {
133 | if err = checkPath(path); err != nil {
134 | return
135 | }
136 | return strconv.FormatInt(rev, 10) + ":" + path, nil
137 | }
138 |
139 | // MustEncodeSet is like EncodeSet but panics if the mutation cannot be
140 | // encoded. It simplifies safe initialization of global variables holding
141 | // mutations.
142 | func MustEncodeSet(path, body string, rev int64) (mutation string) {
143 | m, err := EncodeSet(path, body, rev)
144 | if err != nil {
145 | panic(err)
146 | }
147 | return m
148 | }
149 |
150 | // MustEncodeDel is like EncodeDel but panics if the mutation cannot be
151 | // encoded. It simplifies safe initialization of global variables holding
152 | // mutations.
153 | func MustEncodeDel(path string, rev int64) (mutation string) {
154 | m, err := EncodeDel(path, rev)
155 | if err != nil {
156 | panic(err)
157 | }
158 | return m
159 | }
160 |
161 | func decode(mutation string) (path, v string, rev int64, keep bool, err error) {
162 | cm := strings.SplitN(mutation, ":", 2)
163 |
164 | if len(cm) != 2 {
165 | err = ErrBadMutation
166 | return
167 | }
168 |
169 | rev, err = strconv.ParseInt(cm[0], 10, 64)
170 | if err != nil {
171 | return
172 | }
173 |
174 | kv := strings.SplitN(cm[1], "=", 2)
175 |
176 | if err = checkPath(kv[0]); err != nil {
177 | return
178 | }
179 |
180 | switch len(kv) {
181 | case 1:
182 | return kv[0], "", rev, false, nil
183 | case 2:
184 | return kv[0], kv[1], rev, true, nil
185 | }
186 | panic("unreachable")
187 | }
188 |
189 | func (st *Store) notify(e Event, ws []*watch) (nws []*watch) {
190 | for _, w := range ws {
191 | if e.Seqn >= w.rev && w.glob.Match(e.Path) {
192 | w.c <- e
193 | } else {
194 | nws = append(nws, w)
195 | }
196 | }
197 |
198 | return nws
199 | }
200 |
201 | func (st *Store) closeWatches() {
202 | for _, w := range st.watches {
203 | close(w.c)
204 | }
205 | }
206 |
207 | func (st *Store) process(ops <-chan Op, seqns chan<- int64, watches chan<- int) {
208 | defer st.closeWatches()
209 |
210 | for {
211 | var flush bool
212 | ver, values := st.state.ver, st.state.root
213 |
214 | // Take any incoming requests and queue them up.
215 | select {
216 | case a, ok := <-ops:
217 | if !ok {
218 | return
219 | }
220 |
221 | if a.Seqn > ver {
222 | st.todo = append(st.todo, a)
223 | }
224 | case w := <-st.watchCh:
225 | n, ws := w.rev, []*watch{w}
226 | for ; len(ws) > 0 && n < st.head; n++ {
227 | ws = []*watch{}
228 | }
229 | for ; len(ws) > 0 && n <= ver; n++ {
230 | ws = st.notify(st.log[n], ws)
231 | }
232 |
233 | st.watches = append(st.watches, ws...)
234 | case seqn := <-st.cleanCh:
235 | for ; st.head <= seqn; st.head++ {
236 | delete(st.log, st.head)
237 | }
238 | case seqns <- ver:
239 | // nothing to do here
240 | case watches <- len(st.watches):
241 | // nothing to do here
242 | case flush = <-st.flush:
243 | // nothing
244 | }
245 |
246 | var ev Event
247 | // If we have any mutations that can be applied, do them.
248 | for len(st.todo) > 0 {
249 | i := firstTodo(st.todo)
250 | t := st.todo[i]
251 | if flush && ver < t.Seqn {
252 | ver = t.Seqn - 1
253 | }
254 | if t.Seqn > ver+1 {
255 | break
256 | }
257 |
258 | st.todo = append(st.todo[:i], st.todo[i+1:]...)
259 | if t.Seqn < ver+1 {
260 | continue
261 | }
262 |
263 | values, ev = values.apply(t.Seqn, t.Mut)
264 | st.state = &state{ev.Seqn, values}
265 | ver = ev.Seqn
266 | if !flush {
267 | st.log[ev.Seqn] = ev
268 | st.watches = st.notify(ev, st.watches)
269 | }
270 | }
271 |
272 | // A flush just gets one final event.
273 | if flush {
274 | st.log[ev.Seqn] = ev
275 | st.watches = st.notify(ev, st.watches)
276 | st.head = ver + 1
277 | }
278 | }
279 | }
280 |
281 | func firstTodo(a []Op) (pos int) {
282 | n := int64(math.MaxInt64)
283 | pos = -1
284 | for i, o := range a {
285 | if o.Seqn < n {
286 | n = o.Seqn
287 | pos = i
288 | }
289 | }
290 | return
291 | }
292 |
293 | // Returns a point-in-time snapshot of the contents of the store.
294 | func (st *Store) Snap() (ver int64, g Getter) {
295 | // WARNING: Be sure to read the pointer value of st.state only once. If you
296 | // need multiple accesses, copy the pointer first.
297 | p := st.state
298 |
299 | return p.ver, p.root
300 | }
301 |
302 | // Gets the value stored at `path`, if any.
303 | //
304 | // If no value is stored at `path`, `rev` will be `Missing` and `value` will be
305 | // nil.
306 | //
307 | // if `path` is a directory, `rev` will be `Dir` and `value` will be a list of
308 | // entries.
309 | //
310 | // Otherwise, `rev` is the revision and `value[0]` is the body.
311 | func (st *Store) Get(path string) (value []string, rev int64) {
312 | _, g := st.Snap()
313 | return g.Get(path)
314 | }
315 |
316 | func (st *Store) Stat(path string) (int32, int64) {
317 | _, g := st.Snap()
318 | return g.Stat(path)
319 | }
320 |
321 | // Apply all operations in the internal queue, even if there are gaps in the
322 | // sequence (gaps will be treated as no-ops). This is only useful for
323 | // bootstrapping a store from a point-in-time snapshot of another store.
324 | func (st *Store) Flush() {
325 | st.flush <- true
326 | }
327 |
328 | // Returns a chan that will receive a single event representing the
329 | // first change made to any file matching glob on or after rev.
330 | //
331 | // If rev is less than any value passed to st.Clean, Wait will return
332 | // ErrTooLate.
333 | func (st *Store) Wait(glob *Glob, rev int64) (<-chan Event, error) {
334 | if rev < 1 {
335 | rev = 1
336 | }
337 |
338 | ch := make(chan Event, 1)
339 | wt := &watch{
340 | glob: glob,
341 | rev: rev,
342 | c: ch,
343 | }
344 | st.watchCh <- wt
345 |
346 | if rev < st.head {
347 | return nil, ErrTooLate
348 | }
349 | return ch, nil
350 | }
351 |
352 | func (st *Store) Clean(seqn int64) {
353 | st.cleanCh <- seqn
354 | }
355 |
--------------------------------------------------------------------------------
/consensus/learner_test.go:
--------------------------------------------------------------------------------
1 | package consensus
2 |
3 | import (
4 | "github.com/bmizerany/assert"
5 | "testing"
6 | )
7 |
8 | func TestLearnsAValueWithAQuorumOfOne(t *testing.T) {
9 | var ln learner
10 | ln.init(1, 1)
11 |
12 | m, v, ok := ln.update(newVoteFrom(0, 1, "foo"))
13 | assert.Equal(t, true, ln.done)
14 | assert.Equal(t, "foo", ln.v)
15 | assert.Equal(t, []byte("foo"), v)
16 | assert.Equal(t, true, ok)
17 | assert.Equal(t, &msg{Cmd: learn, Value: []byte("foo")}, m)
18 | }
19 |
20 | func TestLearnsOkStickyInSameRound(t *testing.T) {
21 | var ln learner
22 | ln.init(1, 1)
23 |
24 | m, v, ok := ln.update(newVoteFrom(0, 1, "foo"))
25 | assert.Equal(t, true, ln.done)
26 | assert.Equal(t, "foo", ln.v)
27 | assert.Equal(t, []byte("foo"), v)
28 | assert.Equal(t, true, ok)
29 | assert.Equal(t, &msg{Cmd: learn, Value: []byte("foo")}, m)
30 |
31 | m, v, ok = ln.update(newVoteFrom(1, 1, "bar"))
32 | assert.Equal(t, true, ln.done)
33 | assert.Equal(t, "foo", ln.v)
34 | assert.Equal(t, []byte(nil), v)
35 | assert.Equal(t, false, ok)
36 | assert.Equal(t, (*msg)(nil), m)
37 | }
38 |
39 | func TestLearnsOkStickyInNewRound(t *testing.T) {
40 | var ln learner
41 | ln.init(1, 1)
42 |
43 | m, v, ok := ln.update(newVoteFrom(0, 1, "foo"))
44 | assert.Equal(t, true, ln.done)
45 | assert.Equal(t, "foo", ln.v)
46 | assert.Equal(t, []byte("foo"), v)
47 | assert.Equal(t, true, ok)
48 | assert.Equal(t, &msg{Cmd: learn, Value: []byte("foo")}, m)
49 |
50 | m, v, ok = ln.update(newVoteFrom(0, 2, "bar"))
51 | assert.Equal(t, true, ln.done)
52 | assert.Equal(t, "foo", ln.v)
53 | assert.Equal(t, []byte(nil), v)
54 | assert.Equal(t, false, ok)
55 | assert.Equal(t, (*msg)(nil), m)
56 | }
57 |
58 | func TestLearnsAValueWithAQuorumOfTwo(t *testing.T) {
59 | var ln learner
60 | ln.init(3, 2)
61 |
62 | m, v, ok := ln.update(newVoteFrom(0, 1, "foo"))
63 | assert.Equal(t, false, ln.done)
64 | assert.Equal(t, []byte(nil), v)
65 | assert.Equal(t, false, ok)
66 | assert.Equal(t, (*msg)(nil), m)
67 |
68 | m, v, ok = ln.update(newVoteFrom(1, 1, "foo"))
69 | assert.Equal(t, true, ln.done)
70 | assert.Equal(t, "foo", ln.v)
71 | assert.Equal(t, []byte("foo"), v)
72 | assert.Equal(t, true, ok)
73 | assert.Equal(t, &msg{Cmd: learn, Value: []byte("foo")}, m)
74 | }
75 |
76 | func TestIgnoresMalformedMessageBadRoundNumber(t *testing.T) {
77 | var ln learner
78 | ln.init(1, 1)
79 |
80 | m, v, ok := ln.update(newVoteFrom(0, 0, "bar"))
81 | assert.Equal(t, false, ln.done)
82 | assert.Equal(t, []byte(nil), v)
83 | assert.Equal(t, false, ok)
84 | assert.Equal(t, (*msg)(nil), m)
85 |
86 | m, v, ok = ln.update(newVoteFrom(0, 1, "foo"))
87 | assert.Equal(t, true, ln.done)
88 | assert.Equal(t, "foo", ln.v)
89 | assert.Equal(t, []byte("foo"), v)
90 | assert.Equal(t, true, ok)
91 | assert.Equal(t, &msg{Cmd: learn, Value: []byte("foo")}, m)
92 | }
93 |
94 | func TestIgnoresMultipleMessagesFromSameSender(t *testing.T) {
95 | var ln learner
96 | ln.init(3, 2)
97 |
98 | m, v, ok := ln.update(newVoteFrom(0, 1, "foo"))
99 | assert.Equal(t, false, ln.done)
100 | assert.Equal(t, []byte(nil), v)
101 | assert.Equal(t, false, ok)
102 | assert.Equal(t, (*msg)(nil), m)
103 |
104 | m, v, ok = ln.update(newVoteFrom(0, 1, "foo"))
105 | assert.Equal(t, false, ln.done)
106 | assert.Equal(t, []byte(nil), v)
107 | assert.Equal(t, false, ok)
108 | assert.Equal(t, (*msg)(nil), m)
109 |
110 | m, v, ok = ln.update(newVoteFrom(1, 1, "foo"))
111 | assert.Equal(t, true, ln.done)
112 | assert.Equal(t, "foo", ln.v)
113 | assert.Equal(t, []byte("foo"), v)
114 | assert.Equal(t, true, ok)
115 | assert.Equal(t, &msg{Cmd: learn, Value: []byte("foo")}, m)
116 | }
117 |
118 | func TestIgnoresSenderInOldRound(t *testing.T) {
119 | var ln learner
120 | ln.init(3, 2)
121 |
122 | m, v, ok := ln.update(newVoteFrom(0, 2, "foo"))
123 | assert.Equal(t, false, ln.done)
124 | assert.Equal(t, []byte(nil), v)
125 | assert.Equal(t, false, ok)
126 | assert.Equal(t, (*msg)(nil), m)
127 |
128 | m, v, ok = ln.update(newVoteFrom(1, 1, "foo"))
129 | assert.Equal(t, false, ln.done)
130 | assert.Equal(t, []byte(nil), v)
131 | assert.Equal(t, false, ok)
132 | assert.Equal(t, (*msg)(nil), m)
133 |
134 | m, v, ok = ln.update(newVoteFrom(1, 2, "foo"))
135 | assert.Equal(t, true, ln.done)
136 | assert.Equal(t, "foo", ln.v)
137 | assert.Equal(t, []byte("foo"), v)
138 | assert.Equal(t, true, ok)
139 | assert.Equal(t, &msg{Cmd: learn, Value: []byte("foo")}, m)
140 | }
141 |
142 | func TestResetsVotedFlags(t *testing.T) {
143 | var ln learner
144 | ln.init(3, 2)
145 |
146 | m, v, ok := ln.update(newVoteFrom(0, 1, "foo"))
147 | assert.Equal(t, false, ln.done)
148 | assert.Equal(t, []byte(nil), v)
149 | assert.Equal(t, false, ok)
150 | assert.Equal(t, (*msg)(nil), m)
151 |
152 | m, v, ok = ln.update(newVoteFrom(0, 2, "foo"))
153 | assert.Equal(t, false, ln.done)
154 | assert.Equal(t, []byte(nil), v)
155 | assert.Equal(t, false, ok)
156 | assert.Equal(t, (*msg)(nil), m)
157 |
158 | m, v, ok = ln.update(newVoteFrom(1, 2, "foo"))
159 | assert.Equal(t, true, ln.done)
160 | assert.Equal(t, "foo", ln.v)
161 | assert.Equal(t, []byte("foo"), v)
162 | assert.Equal(t, true, ok)
163 | assert.Equal(t, &msg{Cmd: learn, Value: []byte("foo")}, m)
164 | }
165 |
166 | func TestResetsVoteCounts(t *testing.T) {
167 | var ln learner
168 | ln.init(5, 3)
169 |
170 | m, v, ok := ln.update(newVoteFrom(0, 1, "foo"))
171 | assert.Equal(t, false, ln.done)
172 | assert.Equal(t, []byte(nil), v)
173 | assert.Equal(t, false, ok)
174 | assert.Equal(t, (*msg)(nil), m)
175 |
176 | m, v, ok = ln.update(newVoteFrom(1, 1, "foo"))
177 | assert.Equal(t, false, ln.done)
178 | assert.Equal(t, []byte(nil), v)
179 | assert.Equal(t, false, ok)
180 | assert.Equal(t, (*msg)(nil), m)
181 |
182 | m, v, ok = ln.update(newVoteFrom(0, 2, "foo"))
183 | assert.Equal(t, false, ln.done)
184 | assert.Equal(t, []byte(nil), v)
185 | assert.Equal(t, false, ok)
186 | assert.Equal(t, (*msg)(nil), m)
187 |
188 | m, v, ok = ln.update(newVoteFrom(1, 2, "foo"))
189 | assert.Equal(t, false, ln.done)
190 | assert.Equal(t, []byte(nil), v)
191 | assert.Equal(t, false, ok)
192 | assert.Equal(t, (*msg)(nil), m)
193 |
194 | m, v, ok = ln.update(newVoteFrom(2, 2, "foo"))
195 | assert.Equal(t, true, ln.done)
196 | assert.Equal(t, "foo", ln.v)
197 | assert.Equal(t, []byte("foo"), v)
198 | assert.Equal(t, true, ok)
199 | assert.Equal(t, &msg{Cmd: learn, Value: []byte("foo")}, m)
200 | }
201 |
202 | func TestLearnsATheBestOfTwoValuesInSameRound(t *testing.T) {
203 | var ln learner
204 | ln.init(3, 2)
205 |
206 | m, v, ok := ln.update(newVoteFrom(0, 1, "foo"))
207 | assert.Equal(t, false, ln.done)
208 | assert.Equal(t, []byte(nil), v)
209 | assert.Equal(t, false, ok)
210 | assert.Equal(t, (*msg)(nil), m)
211 |
212 | m, v, ok = ln.update(newVoteFrom(2, 1, "bar"))
213 | assert.Equal(t, false, ln.done)
214 | assert.Equal(t, []byte(nil), v)
215 | assert.Equal(t, false, ok)
216 | assert.Equal(t, (*msg)(nil), m)
217 |
218 | m, v, ok = ln.update(newVoteFrom(1, 1, "foo"))
219 | assert.Equal(t, true, ln.done)
220 | assert.Equal(t, "foo", ln.v)
221 | }
222 |
223 | func TestBringsOrderOutOfChaos(t *testing.T) {
224 | var ln learner
225 | ln.init(3, 2)
226 |
227 | m, v, ok := ln.update(newVoteFrom(0, 1, "bar")) //valid
228 | assert.Equal(t, false, ln.done)
229 | assert.Equal(t, []byte(nil), v)
230 | assert.Equal(t, false, ok)
231 | assert.Equal(t, (*msg)(nil), m)
232 | m, v, ok = ln.update(newVoteFrom(2, 2, "funk")) //reset
233 | assert.Equal(t, false, ln.done)
234 | assert.Equal(t, []byte(nil), v)
235 | assert.Equal(t, false, ok)
236 | assert.Equal(t, (*msg)(nil), m)
237 | m, v, ok = ln.update(newVoteFrom(1, 1, "bar")) //ignored
238 | assert.Equal(t, false, ln.done)
239 | assert.Equal(t, []byte(nil), v)
240 | assert.Equal(t, false, ok)
241 | assert.Equal(t, (*msg)(nil), m)
242 |
243 | m, v, ok = ln.update(newVoteFrom(2, 1, "foo")) //ignored
244 | assert.Equal(t, false, ln.done)
245 | assert.Equal(t, []byte(nil), v)
246 | assert.Equal(t, false, ok)
247 | assert.Equal(t, (*msg)(nil), m)
248 | m, v, ok = ln.update(newVoteFrom(1, 2, "foo")) //valid
249 | assert.Equal(t, false, ln.done)
250 | assert.Equal(t, []byte(nil), v)
251 | assert.Equal(t, false, ok)
252 | assert.Equal(t, (*msg)(nil), m)
253 | m, v, ok = ln.update(newVoteFrom(0, 2, "foo")) //valid (at quorum)
254 | assert.Equal(t, true, ln.done)
255 | assert.Equal(t, "foo", ln.v)
256 | assert.Equal(t, []byte("foo"), v)
257 | assert.Equal(t, true, ok)
258 | assert.Equal(t, &msg{Cmd: learn, Value: []byte("foo")}, m)
259 | }
260 |
261 | func TestLearnerIgnoresBadMessages(t *testing.T) {
262 | var ln learner
263 |
264 | m, v, ok := ln.update(&packet{msg: msg{Cmd: vote}}, -1) // missing Vrnd
265 | assert.Equal(t, false, ln.done)
266 | assert.Equal(t, []byte(nil), v)
267 | assert.Equal(t, false, ok)
268 | assert.Equal(t, (*msg)(nil), m)
269 | }
270 |
271 | func TestSinkLearnsAValue(t *testing.T) {
272 | var ln learner
273 |
274 | m, v, ok := ln.update(&packet{msg: *newLearn("foo")}, -1)
275 | assert.Equal(t, true, ln.done)
276 | assert.Equal(t, "foo", ln.v)
277 | assert.Equal(t, []byte("foo"), v)
278 | assert.Equal(t, true, ok)
279 | assert.Equal(t, (*msg)(nil), m)
280 | }
281 |
282 | func TestSinkLearnsOkSticky(t *testing.T) {
283 | var ln learner
284 |
285 | m, v, ok := ln.update(&packet{msg: *newLearn("foo")}, -1)
286 | assert.Equal(t, true, ln.done)
287 | assert.Equal(t, "foo", ln.v)
288 | assert.Equal(t, []byte("foo"), v)
289 | assert.Equal(t, true, ok)
290 | assert.Equal(t, (*msg)(nil), m)
291 |
292 | m, v, ok = ln.update(&packet{msg: *newLearn("bar")}, -1)
293 | assert.Equal(t, true, ln.done)
294 | assert.Equal(t, "foo", ln.v)
295 | assert.Equal(t, []byte(nil), v)
296 | assert.Equal(t, false, ok)
297 | assert.Equal(t, (*msg)(nil), m)
298 | }
299 |
--------------------------------------------------------------------------------
/consensus/coordinator_test.go:
--------------------------------------------------------------------------------
1 | package consensus
2 |
3 | import (
4 | "github.com/bmizerany/assert"
5 | "testing"
6 | )
7 |
8 | func TestCoordIgnoreOldMessages(t *testing.T) {
9 | var co coordinator
10 | co.size = 10
11 |
12 | co.update(&packet{msg: *newPropose("foo")}, -1)
13 |
14 | co.update(&packet{msg: *msgTick}, -1) // force the start of a new round
15 |
16 | got, tick := co.update(newRsvpFrom(0, 1, 0, ""))
17 | assert.Equal(t, (*msg)(nil), got)
18 | assert.Equal(t, false, tick)
19 |
20 | got, tick = co.update(newRsvpFrom(1, 1, 0, ""))
21 | assert.Equal(t, (*msg)(nil), got)
22 | assert.Equal(t, false, tick)
23 |
24 | got, tick = co.update(newRsvpFrom(2, 1, 0, ""))
25 | assert.Equal(t, (*msg)(nil), got)
26 | assert.Equal(t, false, tick)
27 |
28 | got, tick = co.update(newRsvpFrom(3, 1, 0, ""))
29 | assert.Equal(t, (*msg)(nil), got)
30 | assert.Equal(t, false, tick)
31 |
32 | got, tick = co.update(newRsvpFrom(4, 1, 0, ""))
33 | assert.Equal(t, (*msg)(nil), got)
34 | assert.Equal(t, false, tick)
35 |
36 | got, tick = co.update(newRsvpFrom(5, 1, 0, ""))
37 | assert.Equal(t, (*msg)(nil), got)
38 | assert.Equal(t, false, tick)
39 | }
40 |
41 | func TestCoordStart(t *testing.T) {
42 | co := coordinator{crnd: 1}
43 |
44 | got, tick := co.update(&packet{msg: *newPropose("foo")}, -1)
45 | assert.Equal(t, newInvite(1), got)
46 | assert.Equal(t, true, tick)
47 | }
48 |
49 | func TestCoordQuorum(t *testing.T) {
50 | co := coordinator{
51 | size: 10,
52 | quor: 6,
53 | crnd: 1,
54 | }
55 |
56 | co.update(&packet{msg: *newPropose("foo")}, -1)
57 |
58 | got, tick := co.update(newRsvpFrom(1, 1, 0, ""))
59 | assert.Equal(t, (*msg)(nil), got)
60 | assert.Equal(t, false, tick)
61 |
62 | got, tick = co.update(newRsvpFrom(2, 1, 0, ""))
63 | assert.Equal(t, (*msg)(nil), got)
64 | assert.Equal(t, false, tick)
65 |
66 | got, tick = co.update(newRsvpFrom(3, 1, 0, ""))
67 | assert.Equal(t, (*msg)(nil), got)
68 | assert.Equal(t, false, tick)
69 |
70 | got, tick = co.update(newRsvpFrom(4, 1, 0, ""))
71 | assert.Equal(t, (*msg)(nil), got)
72 | assert.Equal(t, false, tick)
73 |
74 | got, tick = co.update(newRsvpFrom(5, 1, 0, ""))
75 | assert.Equal(t, (*msg)(nil), got)
76 | assert.Equal(t, false, tick)
77 | }
78 |
79 | func TestCoordDuplicateRsvp(t *testing.T) {
80 | co := coordinator{
81 | size: 10,
82 | quor: 6,
83 | crnd: 1,
84 | }
85 |
86 | co.update(&packet{msg: *newPropose("foo")}, -1)
87 |
88 | got, tick := co.update(newRsvpFrom(1, 1, 0, ""))
89 | assert.Equal(t, (*msg)(nil), got)
90 | assert.Equal(t, false, tick)
91 |
92 | got, tick = co.update(newRsvpFrom(2, 1, 0, ""))
93 | assert.Equal(t, (*msg)(nil), got)
94 | assert.Equal(t, false, tick)
95 |
96 | got, tick = co.update(newRsvpFrom(3, 1, 0, ""))
97 | assert.Equal(t, (*msg)(nil), got)
98 | assert.Equal(t, false, tick)
99 |
100 | got, tick = co.update(newRsvpFrom(4, 1, 0, ""))
101 | assert.Equal(t, (*msg)(nil), got)
102 | assert.Equal(t, false, tick)
103 |
104 | got, tick = co.update(newRsvpFrom(5, 1, 0, "")) // from 5
105 | assert.Equal(t, (*msg)(nil), got)
106 | assert.Equal(t, false, tick)
107 |
108 | got, tick = co.update(newRsvpFrom(5, 1, 0, "")) // from 5
109 | assert.Equal(t, (*msg)(nil), got)
110 | assert.Equal(t, false, tick)
111 | }
112 |
113 | func TestCoordTargetNomination(t *testing.T) {
114 | co := coordinator{crnd: 1, quor: 6, size: 10}
115 |
116 | co.update(&packet{msg: *newPropose("foo")}, -1)
117 |
118 | co.update(newRsvpFrom(1, 1, 0, ""))
119 | co.update(newRsvpFrom(2, 1, 0, ""))
120 | co.update(newRsvpFrom(3, 1, 0, ""))
121 | co.update(newRsvpFrom(4, 1, 0, ""))
122 | co.update(newRsvpFrom(5, 1, 0, ""))
123 |
124 | got, tick := co.update(newRsvpFrom(6, 1, 0, ""))
125 | assert.Equal(t, newNominate(1, "foo"), got)
126 | assert.Equal(t, false, tick)
127 | }
128 |
129 | func TestCoordRetry(t *testing.T) {
130 | co := coordinator{
131 | size: 10,
132 | crnd: 1,
133 | }
134 |
135 | co.update(&packet{msg: *newPropose("foo")}, -1)
136 |
137 | // message from a future round and another proposer
138 | got, tick := co.update(newRsvpFrom(1, 2, 0, ""))
139 | assert.Equal(t, (*msg)(nil), got)
140 | assert.Equal(t, false, tick)
141 |
142 | // second message from a future round and another proposer
143 | got, tick = co.update(newRsvpFrom(1, 2, 0, ""))
144 | assert.Equal(t, (*msg)(nil), got)
145 | assert.Equal(t, false, tick)
146 |
147 | got, tick = co.update(&packet{msg: *msgTick}, -1) // force the start of a new round
148 | assert.Equal(t, newInvite(11), got)
149 | assert.Equal(t, true, tick)
150 | }
151 |
152 | func TestCoordNonTargetNomination(t *testing.T) {
153 | co := coordinator{
154 | quor: 6,
155 | size: 10,
156 | crnd: 1,
157 | }
158 |
159 | co.update(&packet{msg: *newPropose("foo")}, -1)
160 |
161 | co.update(newRsvpFrom(0, 1, 0, ""))
162 | co.update(newRsvpFrom(1, 1, 0, ""))
163 | co.update(newRsvpFrom(2, 1, 0, ""))
164 | co.update(newRsvpFrom(3, 1, 0, ""))
165 | co.update(newRsvpFrom(4, 1, 0, ""))
166 | got, tick := co.update(newRsvpFrom(5, 1, 1, "bar"))
167 | assert.Equal(t, newNominate(1, "bar"), got)
168 | assert.Equal(t, false, tick)
169 | }
170 |
171 | func TestCoordOneNominationPerRound(t *testing.T) {
172 | co := coordinator{
173 | quor: 6,
174 | crnd: 1,
175 | size: 10,
176 | }
177 |
178 | co.update(&packet{msg: *newPropose("foo")}, -1)
179 |
180 | got, tick := co.update(newRsvpFrom(0, 1, 0, ""))
181 | assert.Equal(t, (*msg)(nil), got)
182 | assert.Equal(t, false, tick)
183 |
184 | got, tick = co.update(newRsvpFrom(1, 1, 0, ""))
185 | assert.Equal(t, (*msg)(nil), got)
186 | assert.Equal(t, false, tick)
187 |
188 | got, tick = co.update(newRsvpFrom(2, 1, 0, ""))
189 | assert.Equal(t, (*msg)(nil), got)
190 | assert.Equal(t, false, tick)
191 |
192 | got, tick = co.update(newRsvpFrom(3, 1, 0, ""))
193 | assert.Equal(t, (*msg)(nil), got)
194 | assert.Equal(t, false, tick)
195 |
196 | got, tick = co.update(newRsvpFrom(4, 1, 0, ""))
197 | assert.Equal(t, (*msg)(nil), got)
198 | assert.Equal(t, false, tick)
199 |
200 | got, tick = co.update(newRsvpFrom(5, 1, 0, ""))
201 | assert.Equal(t, newNominate(1, "foo"), got)
202 | assert.Equal(t, false, tick)
203 |
204 | got, tick = co.update(newRsvpFrom(6, 1, 0, ""))
205 | assert.Equal(t, (*msg)(nil), got)
206 | assert.Equal(t, false, tick)
207 | }
208 |
209 | func TestCoordEachRoundResetsCval(t *testing.T) {
210 | co := coordinator{
211 | quor: 6,
212 | size: 10,
213 | crnd: 1,
214 | }
215 |
216 | co.update(&packet{msg: *newPropose("foo")}, -1)
217 |
218 | co.update(newRsvpFrom(0, 1, 0, ""))
219 | co.update(newRsvpFrom(1, 1, 0, ""))
220 | co.update(newRsvpFrom(2, 1, 0, ""))
221 | co.update(newRsvpFrom(3, 1, 0, ""))
222 | co.update(newRsvpFrom(4, 1, 0, ""))
223 | co.update(newRsvpFrom(5, 1, 0, ""))
224 |
225 | co.update(&packet{msg: *msgTick}, -1) // force the start of a new round
226 |
227 | got, tick := co.update(newRsvpFrom(0, 11, 0, ""))
228 | assert.Equal(t, (*msg)(nil), got)
229 | assert.Equal(t, false, tick)
230 |
231 | got, tick = co.update(newRsvpFrom(1, 11, 0, ""))
232 | assert.Equal(t, (*msg)(nil), got)
233 | assert.Equal(t, false, tick)
234 |
235 | got, tick = co.update(newRsvpFrom(2, 11, 0, ""))
236 | assert.Equal(t, (*msg)(nil), got)
237 | assert.Equal(t, false, tick)
238 |
239 | got, tick = co.update(newRsvpFrom(3, 11, 0, ""))
240 | assert.Equal(t, (*msg)(nil), got)
241 | assert.Equal(t, false, tick)
242 |
243 | got, tick = co.update(newRsvpFrom(4, 11, 0, ""))
244 | assert.Equal(t, (*msg)(nil), got)
245 | assert.Equal(t, false, tick)
246 |
247 | got, tick = co.update(newRsvpFrom(5, 11, 0, ""))
248 | assert.Equal(t, newNominate(11, "foo"), got)
249 | assert.Equal(t, false, tick)
250 | }
251 |
252 | func TestCoordStartRsvp(t *testing.T) {
253 | co := coordinator{
254 | quor: 1,
255 | crnd: 1,
256 | }
257 |
258 | got, tick := co.update(newRsvpFrom(0, 1, 0, ""))
259 | assert.Equal(t, (*msg)(nil), got)
260 | assert.Equal(t, false, tick)
261 |
262 | got, tick = co.update(&packet{msg: *newPropose("foo")}, -1)
263 |
264 | // If the RSVPs were ignored, this will be an invite.
265 | // Otherwise, it'll be a nominate.
266 | assert.Equal(t, newInvite(1), got)
267 | assert.Equal(t, true, tick)
268 | }
269 |
270 | func TestCoordDuel(t *testing.T) {
271 | co := coordinator{
272 | quor: 2,
273 | size: 3,
274 | crnd: 1,
275 | }
276 |
277 | co.update(&packet{msg: *newPropose("foo")}, -1)
278 |
279 | got, tick := co.update(newRsvpFrom(1, 1, 0, ""))
280 | assert.Equal(t, (*msg)(nil), got)
281 | assert.Equal(t, false, tick)
282 |
283 | got, tick = co.update(newRsvpFrom(2, 2, 0, ""))
284 | assert.Equal(t, (*msg)(nil), got)
285 | assert.Equal(t, false, tick)
286 |
287 | got, tick = co.update(newRsvpFrom(3, 2, 0, ""))
288 | assert.Equal(t, (*msg)(nil), got)
289 | assert.Equal(t, false, tick)
290 |
291 | got, tick = co.update(newRsvpFrom(4, 2, 0, ""))
292 | assert.Equal(t, (*msg)(nil), got)
293 | assert.Equal(t, false, tick)
294 |
295 | got, tick = co.update(newRsvpFrom(5, 2, 0, ""))
296 | assert.Equal(t, (*msg)(nil), got)
297 | assert.Equal(t, false, tick)
298 |
299 | got, tick = co.update(newRsvpFrom(6, 2, 0, ""))
300 | assert.Equal(t, (*msg)(nil), got)
301 | assert.Equal(t, false, tick)
302 | }
303 |
304 | func TestCoordinatorIgnoresBadMessages(t *testing.T) {
305 | co := coordinator{begun: true}
306 |
307 | // missing Crnd
308 | got, tick := co.update(&packet{msg: msg{Cmd: rsvp, Vrnd: new(int64)}}, -1)
309 | assert.Equal(t, (*msg)(nil), got)
310 | assert.Equal(t, false, tick)
311 | assert.Equal(t, coordinator{begun: true}, co)
312 |
313 | // missing Vrnd
314 | got, tick = co.update(&packet{msg: msg{Cmd: rsvp, Crnd: new(int64)}}, -1)
315 | assert.Equal(t, (*msg)(nil), got)
316 | assert.Equal(t, false, tick)
317 | assert.Equal(t, coordinator{begun: true}, co)
318 | }
319 |
--------------------------------------------------------------------------------
/peer/peer_test.go:
--------------------------------------------------------------------------------
1 | package peer
2 |
3 | import (
4 | "github.com/bmizerany/assert"
5 | "github.com/ha/doozer"
6 | "github.com/ha/doozerd/store"
7 | "os/exec"
8 |
9 | "testing"
10 | "time"
11 | )
12 |
13 | func TestDoozerNop(t *testing.T) {
14 | l := mustListen()
15 | defer l.Close()
16 | u := mustListenUDP(l.Addr().String())
17 | defer u.Close()
18 |
19 | go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)
20 |
21 | cl := dial(l.Addr().String())
22 | err := cl.Nop()
23 | assert.Equal(t, nil, err)
24 | }
25 |
26 | func TestDoozerGet(t *testing.T) {
27 | l := mustListen()
28 | defer l.Close()
29 | u := mustListenUDP(l.Addr().String())
30 | defer u.Close()
31 |
32 | go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)
33 |
34 | cl := dial(l.Addr().String())
35 |
36 | _, err := cl.Set("/x", store.Missing, []byte{'a'})
37 | assert.Equal(t, nil, err)
38 |
39 | ents, rev, err := cl.Get("/x", nil)
40 | assert.Equal(t, nil, err)
41 | assert.NotEqual(t, store.Dir, rev)
42 | assert.Equal(t, []byte{'a'}, ents)
43 |
44 | //cl.Set("/test/a", store.Missing, []byte{'1'})
45 | //cl.Set("/test/b", store.Missing, []byte{'2'})
46 | //cl.Set("/test/c", store.Missing, []byte{'3'})
47 |
48 | //ents, rev, err = cl.Get("/test", 0)
49 | //sort.SortStrings(ents)
50 | //assert.Equal(t, store.Dir, rev)
51 | //assert.Equal(t, nil, err)
52 | //assert.Equal(t, []string{"a", "b", "c"}, ents)
53 | }
54 |
55 | func TestDoozerSet(t *testing.T) {
56 | l := mustListen()
57 | defer l.Close()
58 | u := mustListenUDP(l.Addr().String())
59 | defer u.Close()
60 |
61 | go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)
62 |
63 | cl := dial(l.Addr().String())
64 |
65 | for i := byte(0); i < 10; i++ {
66 | _, err := cl.Set("/x", store.Clobber, []byte{'0' + i})
67 | assert.Equal(t, nil, err)
68 | }
69 |
70 | _, err := cl.Set("/x", 0, []byte{'X'})
71 | assert.Equal(t, &doozer.Error{doozer.ErrOldRev, ""}, err)
72 | }
73 |
74 | func TestDoozerGetWithRev(t *testing.T) {
75 | l := mustListen()
76 | defer l.Close()
77 | u := mustListenUDP(l.Addr().String())
78 | defer u.Close()
79 |
80 | go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)
81 |
82 | cl := dial(l.Addr().String())
83 |
84 | rev1, err := cl.Set("/x", store.Missing, []byte{'a'})
85 | assert.Equal(t, nil, err)
86 |
87 | v, rev, err := cl.Get("/x", &rev1) // Use the snapshot.
88 | assert.Equal(t, nil, err)
89 | assert.Equal(t, rev1, rev)
90 | assert.Equal(t, []byte{'a'}, v)
91 |
92 | rev2, err := cl.Set("/x", rev, []byte{'b'})
93 | assert.Equal(t, nil, err)
94 |
95 | v, rev, err = cl.Get("/x", nil) // Read the new value.
96 | assert.Equal(t, nil, err)
97 | assert.Equal(t, rev2, rev)
98 | assert.Equal(t, []byte{'b'}, v)
99 |
100 | v, rev, err = cl.Get("/x", &rev1) // Read the saved value again.
101 | assert.Equal(t, nil, err)
102 | assert.Equal(t, rev1, rev)
103 | assert.Equal(t, []byte{'a'}, v)
104 | }
105 |
106 | func TestDoozerWaitSimple(t *testing.T) {
107 | l := mustListen()
108 | defer l.Close()
109 | u := mustListenUDP(l.Addr().String())
110 | defer u.Close()
111 |
112 | go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)
113 |
114 | cl := dial(l.Addr().String())
115 | var rev int64 = 1
116 |
117 | cl.Set("/test/foo", store.Clobber, []byte("bar"))
118 | ev, err := cl.Wait("/test/**", rev)
119 | assert.Equal(t, nil, err)
120 | assert.Equal(t, "/test/foo", ev.Path)
121 | assert.Equal(t, []byte("bar"), ev.Body)
122 | assert.T(t, ev.IsSet())
123 | rev = ev.Rev + 1
124 |
125 | cl.Set("/test/fun", store.Clobber, []byte("house"))
126 | ev, err = cl.Wait("/test/**", rev)
127 | assert.Equal(t, nil, err)
128 | assert.Equal(t, "/test/fun", ev.Path)
129 | assert.Equal(t, []byte("house"), ev.Body)
130 | assert.T(t, ev.IsSet())
131 | rev = ev.Rev + 1
132 |
133 | cl.Del("/test/foo", store.Clobber)
134 | ev, err = cl.Wait("/test/**", rev)
135 | assert.Equal(t, nil, err)
136 | assert.Equal(t, "/test/foo", ev.Path)
137 | assert.T(t, ev.IsDel())
138 | }
139 |
140 | func TestDoozerWaitWithRev(t *testing.T) {
141 | l := mustListen()
142 | defer l.Close()
143 | u := mustListenUDP(l.Addr().String())
144 | defer u.Close()
145 |
146 | go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)
147 |
148 | cl := dial(l.Addr().String())
149 |
150 | // Create some history
151 | cl.Set("/test/foo", store.Clobber, []byte("bar"))
152 | cl.Set("/test/fun", store.Clobber, []byte("house"))
153 |
154 | ev, err := cl.Wait("/test/**", 1)
155 | assert.Equal(t, nil, err)
156 | assert.Equal(t, "/test/foo", ev.Path)
157 | assert.Equal(t, []byte("bar"), ev.Body)
158 | assert.T(t, ev.IsSet())
159 | rev := ev.Rev + 1
160 |
161 | ev, err = cl.Wait("/test/**", rev)
162 | assert.Equal(t, nil, err)
163 | assert.Equal(t, "/test/fun", ev.Path)
164 | assert.Equal(t, []byte("house"), ev.Body)
165 | assert.T(t, ev.IsSet())
166 | }
167 |
168 | func TestDoozerStat(t *testing.T) {
169 | l := mustListen()
170 | defer l.Close()
171 | u := mustListenUDP(l.Addr().String())
172 | defer u.Close()
173 |
174 | go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)
175 |
176 | cl := dial(l.Addr().String())
177 |
178 | cl.Set("/test/foo", store.Clobber, []byte("bar"))
179 | setRev, _ := cl.Set("/test/fun", store.Clobber, []byte("house"))
180 |
181 | ln, rev, err := cl.Stat("/test", nil)
182 | assert.Equal(t, nil, err)
183 | assert.Equal(t, store.Dir, rev)
184 | assert.Equal(t, int(2), ln)
185 |
186 | ln, rev, err = cl.Stat("/test/fun", nil)
187 | assert.Equal(t, nil, err)
188 | assert.Equal(t, setRev, rev)
189 | assert.Equal(t, int(5), ln)
190 | }
191 |
192 | func TestDoozerGetdirOnDir(t *testing.T) {
193 | l := mustListen()
194 | defer l.Close()
195 | u := mustListenUDP(l.Addr().String())
196 | defer u.Close()
197 |
198 | go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)
199 |
200 | cl := dial(l.Addr().String())
201 |
202 | cl.Set("/test/a", store.Clobber, []byte("1"))
203 | cl.Set("/test/b", store.Clobber, []byte("2"))
204 | cl.Set("/test/c", store.Clobber, []byte("3"))
205 |
206 | rev, err := cl.Rev()
207 | if err != nil {
208 | panic(err)
209 | }
210 |
211 | got, err := cl.Getdir("/test", rev, 0, -1)
212 | assert.Equal(t, nil, err)
213 | assert.Equal(t, []string{"a", "b", "c"}, got)
214 | }
215 |
216 | func TestDoozerGetdirOnFile(t *testing.T) {
217 | l := mustListen()
218 | defer l.Close()
219 | u := mustListenUDP(l.Addr().String())
220 | defer u.Close()
221 |
222 | go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)
223 |
224 | cl := dial(l.Addr().String())
225 |
226 | cl.Set("/test/a", store.Clobber, []byte("1"))
227 |
228 | rev, err := cl.Rev()
229 | if err != nil {
230 | panic(err)
231 | }
232 |
233 | names, err := cl.Getdir("/test/a", rev, 0, -1)
234 | assert.Equal(t, &doozer.Error{doozer.ErrNotDir, ""}, err)
235 | assert.Equal(t, []string(nil), names)
236 | }
237 |
238 | func TestDoozerGetdirMissing(t *testing.T) {
239 | l := mustListen()
240 | defer l.Close()
241 | u := mustListenUDP(l.Addr().String())
242 | defer u.Close()
243 |
244 | go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)
245 |
246 | cl := dial(l.Addr().String())
247 |
248 | rev, err := cl.Rev()
249 | if err != nil {
250 | panic(err)
251 | }
252 |
253 | names, err := cl.Getdir("/not/here", rev, 0, -1)
254 | assert.Equal(t, &doozer.Error{doozer.ErrNoEnt, ""}, err)
255 | assert.Equal(t, []string(nil), names)
256 | }
257 |
258 | func TestDoozerGetdirOffsetLimit(t *testing.T) {
259 | l := mustListen()
260 | defer l.Close()
261 | u := mustListenUDP(l.Addr().String())
262 | defer u.Close()
263 |
264 | go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)
265 |
266 | cl := dial(l.Addr().String())
267 | cl.Set("/test/a", store.Clobber, []byte("1"))
268 | cl.Set("/test/b", store.Clobber, []byte("2"))
269 | cl.Set("/test/c", store.Clobber, []byte("3"))
270 | cl.Set("/test/d", store.Clobber, []byte("4"))
271 |
272 | rev, err := cl.Rev()
273 | if err != nil {
274 | panic(err)
275 | }
276 |
277 | names, err := cl.Getdir("/test", rev, 1, 2)
278 | assert.Equal(t, nil, err)
279 | assert.Equal(t, []string{"b", "c"}, names)
280 | }
281 |
282 | func TestPeerShun(t *testing.T) {
283 | l0 := mustListen()
284 | defer l0.Close()
285 | a0 := l0.Addr().String()
286 | u0 := mustListenUDP(a0)
287 | defer u0.Close()
288 |
289 | l1 := mustListen()
290 | defer l1.Close()
291 | u1 := mustListenUDP(l1.Addr().String())
292 | defer u1.Close()
293 | l2 := mustListen()
294 | defer l2.Close()
295 | u2 := mustListenUDP(l2.Addr().String())
296 | defer u2.Close()
297 |
298 | go Main("a", "X", "", "", "", nil, u0, l0, nil, 1e8, 1e7, 1e9, 1e9)
299 | go Main("a", "Y", "", "", "", dial(a0), u1, l1, nil, 1e8, 1e7, 1e9, 1e9)
300 | go Main("a", "Z", "", "", "", dial(a0), u2, l2, nil, 1e8, 1e7, 1e9, 1e9)
301 |
302 | cl := dial(l0.Addr().String())
303 | cl.Set("/ctl/cal/1", store.Missing, nil)
304 | cl.Set("/ctl/cal/2", store.Missing, nil)
305 |
306 | waitFor(cl, "/ctl/node/X/writable")
307 | waitFor(cl, "/ctl/node/Y/writable")
308 | waitFor(cl, "/ctl/node/Z/writable")
309 |
310 | rev, err := cl.Set("/test", store.Clobber, nil)
311 | if e, ok := err.(*doozer.Error); ok && e.Err == doozer.ErrReadonly {
312 | } else if err != nil {
313 | panic(err)
314 | }
315 |
316 | u1.Close()
317 | for {
318 | ev, err := cl.Wait("/ctl/cal/*", rev)
319 | if err != nil {
320 | panic(err)
321 | }
322 | if ev.IsSet() && len(ev.Body) == 0 {
323 | break
324 | }
325 | rev = ev.Rev + 1
326 | }
327 | }
328 |
329 | func TestPeerLateJoin(t *testing.T) {
330 | l0 := mustListen()
331 | defer l0.Close()
332 | a0 := l0.Addr().String()
333 | u0 := mustListenUDP(a0)
334 | defer u0.Close()
335 |
336 | l1 := mustListen()
337 | defer l1.Close()
338 | u1 := mustListenUDP(l1.Addr().String())
339 | defer u1.Close()
340 |
341 | go Main("a", "X", "", "", "", nil, u0, l0, nil, 1e8, 1e7, 1e9, 60)
342 |
343 | cl := dial(l0.Addr().String())
344 | waitFor(cl, "/ctl/node/X/writable")
345 |
346 | // TODO: this is set slightly higher than the hardcoded interval
347 | // at which a store is cleaned. Refactor that to be configurable
348 | // so we can drop this down to something reasonable
349 | time.Sleep(1100 * time.Millisecond)
350 |
351 | go Main("a", "Y", "", "", "", dial(a0), u1, l1, nil, 1e8, 1e7, 1e9, 60)
352 | rev, _ := cl.Set("/ctl/cal/1", store.Missing, nil)
353 | for {
354 | ev, err := cl.Wait("/ctl/node/Y/writable", rev)
355 | if err != nil {
356 | panic(err)
357 | }
358 | if ev.IsSet() && len(ev.Body) == 4 {
359 | break
360 | }
361 | rev = ev.Rev + 1
362 | }
363 | }
364 |
365 | func assertDenied(t *testing.T, err error) {
366 | assert.NotEqual(t, nil, err)
367 | assert.Equal(t, doozer.ErrOther, err.(*doozer.Error).Err)
368 | assert.Equal(t, "permission denied", err.(*doozer.Error).Detail)
369 | }
370 |
371 | func runDoozer(a ...string) *exec.Cmd {
372 | path := "/home/kr/src/go/bin/doozerd"
373 | args := append([]string{path}, a...)
374 | c := exec.Command(path, args...)
375 | if err := c.Run(); err != nil {
376 | panic(err)
377 | }
378 | return c
379 | }
380 |
--------------------------------------------------------------------------------
/consensus/manager_test.go:
--------------------------------------------------------------------------------
1 | package consensus
2 |
3 | import (
4 | "code.google.com/p/goprotobuf/proto"
5 | "container/heap"
6 | "github.com/bmizerany/assert"
7 | "github.com/ha/doozerd/store"
8 | "net"
9 | "sort"
10 | "testing"
11 | "time"
12 | )
13 |
14 | // The first element in a protobuf stream is always a varint.
15 | // The high bit of a varint byte indicates continuation;
16 | // This is a continuation bit without a subsequent byte.
17 | // http://code.google.com/apis/protocolbuffers/docs/encoding.html#varints.
18 | var invalidProtobuf = []byte{0x80}
19 |
20 | func mustMarshal(p proto.Message) []byte {
21 | buf, err := proto.Marshal(p)
22 | if err != nil {
23 | panic(err)
24 | }
25 | return buf
26 | }
27 |
28 | func mustWait(s *store.Store, n int64) <-chan store.Event {
29 | c, err := s.Wait(store.Any, n)
30 | if err != nil {
31 | panic(err)
32 | }
33 | return c
34 | }
35 |
36 | func TestManagerPumpDropsOldPackets(t *testing.T) {
37 | st := store.New()
38 | defer close(st.Ops)
39 | x, _ := net.ResolveUDPAddr("udp", "1.2.3.4:5")
40 | st.Ops <- store.Op{1, store.MustEncodeSet(node+"/a/addr", "1.2.3.4:5", 0)}
41 | st.Ops <- store.Op{2, store.MustEncodeSet("/ctl/cal/0", "a", 0)}
42 |
43 | var m Manager
44 | m.run = make(map[int64]*run)
45 | m.event(<-mustWait(st, 2))
46 | m.pump()
47 | recvPacket(&m.packet, Packet{x, mustMarshal(&msg{Seqn: proto.Int64(1)})})
48 | m.pump()
49 | assert.Equal(t, 0, m.Stats.WaitPackets)
50 | }
51 |
52 | func TestRecvPacket(t *testing.T) {
53 | q := new(packets)
54 | x, _ := net.ResolveUDPAddr("udp", "1.2.3.4:5")
55 |
56 | p := recvPacket(q, Packet{x, mustMarshal(&msg{
57 | Seqn: proto.Int64(1),
58 | Cmd: invite,
59 | })})
60 | assert.Equal(t, &packet{x, msg{Seqn: proto.Int64(1), Cmd: invite}}, p)
61 | p = recvPacket(q, Packet{x, mustMarshal(&msg{
62 | Seqn: proto.Int64(2),
63 | Cmd: invite,
64 | })})
65 | assert.Equal(t, &packet{x, msg{Seqn: proto.Int64(2), Cmd: invite}}, p)
66 | p = recvPacket(q, Packet{x, mustMarshal(&msg{
67 | Seqn: proto.Int64(3),
68 | Cmd: invite,
69 | })})
70 | assert.Equal(t, &packet{x, msg{Seqn: proto.Int64(3), Cmd: invite}}, p)
71 | assert.Equal(t, 3, q.Len())
72 | }
73 |
74 | func TestRecvEmptyPacket(t *testing.T) {
75 | q := new(packets)
76 | x, _ := net.ResolveUDPAddr("udp", "1.2.3.4:5")
77 |
78 | p := recvPacket(q, Packet{x, []byte{}})
79 | assert.Equal(t, (*packet)(nil), p)
80 | assert.Equal(t, 0, q.Len())
81 | }
82 |
83 | func TestRecvInvalidPacket(t *testing.T) {
84 | q := new(packets)
85 | x, _ := net.ResolveUDPAddr("udp", "1.2.3.4:5")
86 | p := recvPacket(q, Packet{x, invalidProtobuf})
87 | assert.Equal(t, (*packet)(nil), p)
88 | assert.Equal(t, 0, q.Len())
89 | }
90 |
91 | func TestSchedTrigger(t *testing.T) {
92 | var q triggers
93 | d := int64(15e8)
94 |
95 | t0 := time.Now().UnixNano()
96 | ts := t0 + d
97 | schedTrigger(&q, 1, t0, d)
98 |
99 | assert.Equal(t, 1, q.Len())
100 | f := q[0]
101 | assert.Equal(t, int64(1), f.n)
102 | assert.T(t, f.t == ts)
103 | }
104 |
105 | func TestManagerPacketProcessing(t *testing.T) {
106 | st := store.New()
107 | defer close(st.Ops)
108 | in := make(chan Packet)
109 | out := make(chan Packet, 100)
110 | var m Manager
111 | m.run = make(map[int64]*run)
112 | m.Alpha = 1
113 | m.Store = st
114 | m.In = in
115 | m.Out = out
116 | m.Ops = st.Ops
117 |
118 | st.Ops <- store.Op{1, store.MustEncodeSet(node+"/a/addr", "1.2.3.4:5", 0)}
119 | st.Ops <- store.Op{2, store.MustEncodeSet("/ctl/cal/0", "a", 0)}
120 | m.event(<-mustWait(st, 2))
121 |
122 | addr, _ := net.ResolveUDPAddr("udp", "127.0.0.1:9999")
123 | recvPacket(&m.packet, Packet{
124 | Data: mustMarshal(&msg{Seqn: proto.Int64(2), Cmd: learn, Value: []byte("foo")}),
125 | Addr: addr,
126 | })
127 | m.pump()
128 | assert.Equal(t, 0, m.packet.Len())
129 | }
130 |
131 | func TestManagerTickQueue(t *testing.T) {
132 | st := store.New()
133 | defer close(st.Ops)
134 | st.Ops <- store.Op{1, store.MustEncodeSet(node+"/a/addr", "1.2.3.4:5", 0)}
135 | st.Ops <- store.Op{2, store.MustEncodeSet("/ctl/cal/0", "a", 0)}
136 |
137 | var m Manager
138 | m.run = make(map[int64]*run)
139 | m.Alpha = 1
140 | m.Store = st
141 | m.Out = make(chan Packet, 100)
142 | m.event(<-mustWait(st, 2))
143 |
144 | // get it to tick for seqn 3
145 | recvPacket(&m.packet, Packet{Data: mustMarshal(&msg{Seqn: proto.Int64(3), Cmd: propose})})
146 | m.pump()
147 | assert.Equal(t, 1, m.tick.Len())
148 |
149 | m.doTick(time.Now().UnixNano() + initialWaitBound*2)
150 | assert.Equal(t, int64(1), m.Stats.TotalTicks)
151 | }
152 |
153 | func TestManagerFilterPropSeqn(t *testing.T) {
154 | ps := make(chan int64, 100)
155 | st := store.New()
156 | defer close(st.Ops)
157 |
158 | m := &Manager{
159 | DefRev: 2,
160 | Alpha: 1,
161 | Self: "b",
162 | PSeqn: ps,
163 | Store: st,
164 | }
165 | go m.Run()
166 |
167 | st.Ops <- store.Op{1, store.MustEncodeSet("/ctl/cal/0", "a", 0)}
168 | st.Ops <- store.Op{2, store.MustEncodeSet("/ctl/cal/1", "b", 0)}
169 | st.Ops <- store.Op{3, store.Nop}
170 | st.Ops <- store.Op{4, store.Nop}
171 | assert.Equal(t, int64(3), <-ps)
172 | assert.Equal(t, int64(5), <-ps)
173 |
174 | st.Ops <- store.Op{5, store.Nop}
175 | st.Ops <- store.Op{6, store.Nop}
176 | assert.Equal(t, int64(7), <-ps)
177 | }
178 |
179 | func TestManagerProposalQueue(t *testing.T) {
180 | var m Manager
181 | m.run = make(map[int64]*run)
182 | m.propose(&m.packet, &Prop{Seqn: 1, Mut: []byte("foo")}, time.Now().UnixNano())
183 | assert.Equal(t, 1, m.packet.Len())
184 | }
185 |
186 | func TestManagerProposeFill(t *testing.T) {
187 | q := new(packets)
188 | var m Manager
189 | m.Self = "a"
190 | m.run = map[int64]*run{
191 | 6: &run{seqn: 6, cals: []string{"a", "b", "c"}},
192 | 7: &run{seqn: 7, cals: []string{"a", "b", "c"}},
193 | 8: &run{seqn: 8, cals: []string{"a", "b", "c"}},
194 | }
195 | exp := triggers{
196 | {123, 7},
197 | {123, 8},
198 | }
199 | m.propose(q, &Prop{Seqn: 9, Mut: []byte("foo")}, 123)
200 | assert.Equal(t, exp, m.fill)
201 | }
202 |
203 | func TestApplyTriggers(t *testing.T) {
204 | pkts := new(packets)
205 | tgrs := new(triggers)
206 |
207 | heap.Push(tgrs, trigger{t: 1, n: 1})
208 | heap.Push(tgrs, trigger{t: 2, n: 2})
209 | heap.Push(tgrs, trigger{t: 3, n: 3})
210 | heap.Push(tgrs, trigger{t: 4, n: 4})
211 | heap.Push(tgrs, trigger{t: 5, n: 5})
212 | heap.Push(tgrs, trigger{t: 6, n: 6})
213 | heap.Push(tgrs, trigger{t: 7, n: 7})
214 | heap.Push(tgrs, trigger{t: 8, n: 8})
215 | heap.Push(tgrs, trigger{t: 9, n: 9})
216 |
217 | n := applyTriggers(pkts, tgrs, 5, &msg{Cmd: tick})
218 | assert.Equal(t, 5, n)
219 |
220 | expTriggers := new(triggers)
221 | expPackets := new(packets)
222 | heap.Push(expPackets, &packet{msg: msg{Cmd: tick, Seqn: proto.Int64(1)}})
223 | heap.Push(expPackets, &packet{msg: msg{Cmd: tick, Seqn: proto.Int64(2)}})
224 | heap.Push(expPackets, &packet{msg: msg{Cmd: tick, Seqn: proto.Int64(3)}})
225 | heap.Push(expPackets, &packet{msg: msg{Cmd: tick, Seqn: proto.Int64(4)}})
226 | heap.Push(expPackets, &packet{msg: msg{Cmd: tick, Seqn: proto.Int64(5)}})
227 | heap.Push(expTriggers, trigger{t: 6, n: 6})
228 | heap.Push(expTriggers, trigger{t: 7, n: 7})
229 | heap.Push(expTriggers, trigger{t: 8, n: 8})
230 | heap.Push(expTriggers, trigger{t: 9, n: 9})
231 |
232 | sort.Sort(pkts)
233 | sort.Sort(tgrs)
234 | sort.Sort(expPackets)
235 | sort.Sort(expTriggers)
236 |
237 | assert.Equal(t, expTriggers, tgrs)
238 | assert.Equal(t, expPackets, pkts)
239 | }
240 |
241 | func TestManagerEvent(t *testing.T) {
242 | const alpha = 2
243 | runs := make(map[int64]*run)
244 | st := store.New()
245 | defer close(st.Ops)
246 |
247 | st.Ops <- store.Op{
248 | Seqn: 1,
249 | Mut: store.MustEncodeSet(node+"/a/addr", "1.2.3.4:5", 0),
250 | }
251 |
252 | st.Ops <- store.Op{
253 | Seqn: 2,
254 | Mut: store.MustEncodeSet(cal+"/1", "a", 0),
255 | }
256 |
257 | ch, err := st.Wait(store.Any, 2)
258 | if err != nil {
259 | panic(err)
260 | }
261 |
262 | x, _ := net.ResolveUDPAddr("udp", "1.2.3.4:5")
263 | pseqn := make(chan int64, 1)
264 | m := &Manager{
265 | Alpha: alpha,
266 | Self: "a",
267 | PSeqn: pseqn,
268 | Ops: st.Ops,
269 | Out: make(chan Packet),
270 | run: runs,
271 | }
272 | m.event(<-ch)
273 |
274 | exp := &run{
275 | self: "a",
276 | seqn: 2 + alpha,
277 | cals: []string{"a"},
278 | addr: []*net.UDPAddr{x},
279 | ops: st.Ops,
280 | out: m.Out,
281 | bound: initialWaitBound,
282 | }
283 | exp.c = coordinator{
284 | crnd: 1,
285 | size: 1,
286 | quor: exp.quorum(),
287 | }
288 | exp.l = learner{
289 | round: 1,
290 | size: 1,
291 | quorum: int64(exp.quorum()),
292 | votes: map[string]int64{},
293 | voted: []bool{false},
294 | }
295 |
296 | assert.Equal(t, 1, len(runs))
297 | assert.Equal(t, exp, runs[exp.seqn])
298 | assert.Equal(t, exp.seqn, <-pseqn)
299 | assert.Equal(t, exp.seqn+1, m.next)
300 | }
301 |
302 | func TestManagerRemoveLastCal(t *testing.T) {
303 | const alpha = 2
304 | runs := make(map[int64]*run)
305 | st := store.New()
306 | defer close(st.Ops)
307 |
308 | st.Ops <- store.Op{1, store.MustEncodeSet(node+"/a/addr", "1.2.3.4:5", 0)}
309 | st.Ops <- store.Op{2, store.MustEncodeSet(cal+"/1", "a", 0)}
310 | st.Ops <- store.Op{3, store.MustEncodeSet(cal+"/1", "", -1)}
311 |
312 | x, _ := net.ResolveUDPAddr("udp", "1.2.3.4:5")
313 | pseqn := make(chan int64, 100)
314 | m := &Manager{
315 | Alpha: alpha,
316 | Self: "a",
317 | PSeqn: pseqn,
318 | Ops: st.Ops,
319 | Out: make(chan Packet),
320 | run: runs,
321 | }
322 | m.event(<-mustWait(st, 2))
323 | m.event(<-mustWait(st, 3))
324 |
325 | exp := &run{
326 | self: "a",
327 | seqn: 3 + alpha,
328 | cals: []string{"a"},
329 | addr: []*net.UDPAddr{x},
330 | ops: st.Ops,
331 | out: m.Out,
332 | bound: initialWaitBound,
333 | }
334 | exp.c = coordinator{
335 | crnd: 1,
336 | size: 1,
337 | quor: exp.quorum(),
338 | }
339 | exp.l = learner{
340 | round: 1,
341 | size: 1,
342 | quorum: int64(exp.quorum()),
343 | votes: map[string]int64{},
344 | voted: []bool{false},
345 | }
346 |
347 | assert.Equal(t, 2, len(runs))
348 | assert.Equal(t, exp, runs[exp.seqn])
349 | assert.Equal(t, exp.seqn+1, m.next)
350 | }
351 |
352 | func TestDelRun(t *testing.T) {
353 | const alpha = 2
354 | runs := make(map[int64]*run)
355 | st := store.New()
356 | defer close(st.Ops)
357 |
358 | st.Ops <- store.Op{1, store.MustEncodeSet(node+"/a/addr", "x", 0)}
359 | st.Ops <- store.Op{2, store.MustEncodeSet(cal+"/1", "a", 0)}
360 | st.Ops <- store.Op{3, store.Nop}
361 | st.Ops <- store.Op{4, store.Nop}
362 |
363 | c2, err := st.Wait(store.Any, 2)
364 | if err != nil {
365 | panic(err)
366 | }
367 |
368 | c3, err := st.Wait(store.Any, 3)
369 | if err != nil {
370 | panic(err)
371 | }
372 |
373 | c4, err := st.Wait(store.Any, 4)
374 | if err != nil {
375 | panic(err)
376 | }
377 |
378 | pseqn := make(chan int64, 100)
379 | m := &Manager{
380 | Alpha: alpha,
381 | Self: "a",
382 | PSeqn: pseqn,
383 | Ops: st.Ops,
384 | Out: make(chan Packet),
385 | run: runs,
386 | }
387 | m.event(<-c2)
388 | assert.Equal(t, 1, len(m.run))
389 | m.event(<-c3)
390 | assert.Equal(t, 2, len(m.run))
391 | m.event(<-c4)
392 | assert.Equal(t, 2, len(m.run))
393 | }
394 |
395 | func TestGetCalsFull(t *testing.T) {
396 | st := store.New()
397 | defer close(st.Ops)
398 |
399 | st.Ops <- store.Op{Seqn: 1, Mut: store.MustEncodeSet(cal+"/1", "a", 0)}
400 | st.Ops <- store.Op{Seqn: 2, Mut: store.MustEncodeSet(cal+"/2", "c", 0)}
401 | st.Ops <- store.Op{Seqn: 3, Mut: store.MustEncodeSet(cal+"/3", "b", 0)}
402 | <-st.Seqns
403 |
404 | assert.Equal(t, []string{"a", "b", "c"}, getCals(st))
405 | }
406 |
407 | func TestGetCalsPartial(t *testing.T) {
408 | st := store.New()
409 | defer close(st.Ops)
410 |
411 | st.Ops <- store.Op{Seqn: 1, Mut: store.MustEncodeSet(cal+"/1", "a", 0)}
412 | st.Ops <- store.Op{Seqn: 2, Mut: store.MustEncodeSet(cal+"/2", "", 0)}
413 | st.Ops <- store.Op{Seqn: 3, Mut: store.MustEncodeSet(cal+"/3", "", 0)}
414 | <-st.Seqns
415 |
416 | assert.Equal(t, []string{"a"}, getCals(st))
417 | }
418 |
419 | func TestGetAddrs(t *testing.T) {
420 | st := store.New()
421 | defer close(st.Ops)
422 |
423 | st.Ops <- store.Op{1, store.MustEncodeSet(node+"/1/addr", "1.2.3.4:5", 0)}
424 | st.Ops <- store.Op{2, store.MustEncodeSet(node+"/2/addr", "2.3.4.5:6", 0)}
425 | st.Ops <- store.Op{3, store.MustEncodeSet(node+"/3/addr", "3.4.5.6:7", 0)}
426 | <-st.Seqns
427 |
428 | x, _ := net.ResolveUDPAddr("udp", "1.2.3.4:5")
429 | y, _ := net.ResolveUDPAddr("udp", "2.3.4.5:6")
430 | z, _ := net.ResolveUDPAddr("udp", "3.4.5.6:7")
431 | addrs := getAddrs(st, []string{"1", "2", "3"})
432 | assert.Equal(t, []*net.UDPAddr{x, y, z}, addrs)
433 | }
434 |
--------------------------------------------------------------------------------
| |