ZooKeeper Publisher improved unit tests and passthrough of zk.Event chan
Bas van Beek
8 years ago
13 | 13 | // DefaultACL is the default ACL to use for creating znodes. |
14 | 14 | var ( |
15 | 15 | DefaultACL = zk.WorldACL(zk.PermAll) |
16 | ErrInvalidPath = errors.New("path must start with / character") | |
17 | 16 | ErrInvalidCredentials = errors.New("invalid credentials provided") |
18 | 17 | ErrClientClosed = errors.New("client service closed") |
19 | 18 | ) |
27 | 26 | DefaultSessionTimeout = 5 * time.Second |
28 | 27 | ) |
29 | 28 | |
30 | // Event is a data container for znode watch events returned by GetEntries. It | |
31 | // provides a consistent trigger for watch events to Publishers, while still | |
32 | // allowing the underlying ZooKeeper client library watch payload to be | |
33 | // inspected. | |
34 | type Event struct { | |
35 | payload interface{} | |
36 | } | |
37 | ||
38 | 29 | // Client is a wrapper around a lower level ZooKeeper client implementation. |
39 | 30 | type Client interface { |
40 | 31 | // GetEntries should query the provided path in ZooKeeper, place a watch on |
41 | 32 | // it and retrieve data from its current child nodes. |
42 | GetEntries(path string) ([]string, <-chan Event, error) | |
33 | GetEntries(path string) ([]string, <-chan zk.Event, error) | |
43 | 34 | // CreateParentNodes should try to create the path in case it does not exist |
44 | 35 | // yet on ZooKeeper. |
45 | 36 | CreateParentNodes(path string) error |
189 | 180 | return ErrClientClosed |
190 | 181 | } |
191 | 182 | if path[0] != '/' { |
192 | return ErrInvalidPath | |
183 | return zk.ErrInvalidPath | |
193 | 184 | } |
194 | 185 | payload := []byte("") |
195 | 186 | pathString := "" |
213 | 204 | } |
214 | 205 | |
215 | 206 | // GetEntries implements the ZooKeeper Client interface. |
216 | func (c *client) GetEntries(path string) ([]string, <-chan Event, error) { | |
217 | eventc := make(chan Event, 1) | |
218 | if !c.active { | |
219 | close(eventc) | |
220 | return nil, eventc, ErrClientClosed | |
221 | } | |
207 | func (c *client) GetEntries(path string) ([]string, <-chan zk.Event, error) { | |
222 | 208 | // retrieve list of child nodes for given path and add watch to path |
223 | znodes, _, updateReceived, err := c.ChildrenW(path) | |
224 | go func() { | |
225 | // wait for update and forward over Event channel | |
226 | payload := <-updateReceived | |
227 | eventc <- Event{payload} | |
228 | }() | |
209 | znodes, _, eventc, err := c.ChildrenW(path) | |
229 | 210 | |
230 | 211 | if err != nil { |
231 | 212 | return nil, eventc, err |
46 | 46 | t.Errorf("retrieved incorrect Client implementation") |
47 | 47 | } |
48 | 48 | if want, have := acl, clientImpl.acl; want[0] != have[0] { |
49 | t.Errorf("want %q, have %q", want, have) | |
49 | t.Errorf("want %+v, have %+v", want, have) | |
50 | 50 | } |
51 | 51 | if want, have := connectTimeout, clientImpl.connectTimeout; want != have { |
52 | t.Errorf("want %q, have %q", want, have) | |
52 | t.Errorf("want %d, have %d", want, have) | |
53 | 53 | } |
54 | 54 | if want, have := sessionTimeout, clientImpl.sessionTimeout; want != have { |
55 | t.Errorf("want %q, have %q", want, have) | |
55 | t.Errorf("want %d, have %d", want, have) | |
56 | 56 | } |
57 | 57 | if want, have := payload, clientImpl.rootNodePayload; bytes.Compare(want[0], have[0]) != 0 || bytes.Compare(want[1], have[1]) != 0 { |
58 | t.Errorf("want %q, have %q", want, have) | |
58 | t.Errorf("want %s, have %s", want, have) | |
59 | 59 | } |
60 | 60 | // Allow EventHandler to be called |
61 | 61 | time.Sleep(1 * time.Millisecond) |
68 | 68 | func TestOptions(t *testing.T) { |
69 | 69 | _, err := NewClient([]string{"localhost"}, logger, Credentials("valid", "credentials")) |
70 | 70 | if err != nil && err != stdzk.ErrNoServer { |
71 | t.Errorf("unexpected error: %q", err) | |
71 | t.Errorf("unexpected error: %v", err) | |
72 | 72 | } |
73 | 73 | |
74 | 74 | _, err = NewClient([]string{"localhost"}, logger, Credentials("nopass", "")) |
75 | 75 | if want, have := err, ErrInvalidCredentials; want != have { |
76 | t.Errorf("want %q, have %q", want, have) | |
76 | t.Errorf("want %v, have %v", want, have) | |
77 | 77 | } |
78 | 78 | |
79 | 79 | _, err = NewClient([]string{"localhost"}, logger, ConnectTimeout(0)) |
86 | 86 | t.Errorf("expected connect timeout error") |
87 | 87 | } |
88 | 88 | } |
89 | ||
90 | func TestCreateParentNodes(t *testing.T) { | |
91 | payload := [][]byte{[]byte("Payload"), []byte("Test")} | |
92 | ||
93 | c, err := NewClient([]string{"localhost:65500"}, logger) | |
94 | if err != nil { | |
95 | t.Errorf("unexpected error: %v", err) | |
96 | } | |
97 | if c == nil { | |
98 | t.Fatalf("expected new Client, got nil") | |
99 | } | |
100 | p, err := NewPublisher(c, "/validpath", newFactory(""), logger) | |
101 | if err != stdzk.ErrNoServer { | |
102 | t.Errorf("unexpected error: %v", err) | |
103 | } | |
104 | if p != nil { | |
105 | t.Errorf("expected failed new Publisher") | |
106 | } | |
107 | p, err = NewPublisher(c, "invalidpath", newFactory(""), logger) | |
108 | if err != stdzk.ErrInvalidPath { | |
109 | t.Errorf("unexpected error: %v", err) | |
110 | } | |
111 | _, _, err = c.GetEntries("/validpath") | |
112 | if err != stdzk.ErrNoServer { | |
113 | t.Errorf("unexpected error: %v", err) | |
114 | } | |
115 | // stopping Client | |
116 | c.Stop() | |
117 | err = c.CreateParentNodes("/validpath") | |
118 | if err != ErrClientClosed { | |
119 | t.Errorf("unexpected error: %v", err) | |
120 | } | |
121 | p, err = NewPublisher(c, "/validpath", newFactory(""), logger) | |
122 | if err != ErrClientClosed { | |
123 | t.Errorf("unexpected error: %v", err) | |
124 | } | |
125 | if p != nil { | |
126 | t.Errorf("expected failed new Publisher") | |
127 | } | |
128 | c, err = NewClient([]string{"localhost:65500"}, logger, Payload(payload)) | |
129 | if err != nil { | |
130 | t.Errorf("unexpected error: %v", err) | |
131 | } | |
132 | if c == nil { | |
133 | t.Fatalf("expected new Client, got nil") | |
134 | } | |
135 | p, err = NewPublisher(c, "/validpath", newFactory(""), logger) | |
136 | if err != stdzk.ErrNoServer { | |
137 | t.Errorf("unexpected error: %v", err) | |
138 | } | |
139 | if p != nil { | |
140 | t.Errorf("expected failed new Publisher") | |
141 | } | |
142 | } |
18 | 18 | func TestMain(m *testing.M) { |
19 | 19 | fmt.Println("ZooKeeper Integration Test Initializing. Starting ZooKeeper Server...") |
20 | 20 | ts, err := stdzk.StartTestCluster(1, nil, nil) |
21 | host = []string{fmt.Sprintf("localhost:%d", ts.Servers[0].Port)} | |
22 | 21 | if err != nil { |
23 | fmt.Printf("Unable to start ZooKeeper Server: %s", err) | |
22 | fmt.Printf("Unable to start ZooKeeper Server: %v\n", err) | |
24 | 23 | os.Exit(-1) |
25 | 24 | } |
25 | defer ts.Stop() | |
26 | host = []string{fmt.Sprintf("localhost:%d", ts.Servers[0].Port)} | |
26 | 27 | code := m.Run() |
27 | ts.Stop() | |
28 | 28 | os.Exit(code) |
29 | 29 | } |
30 | 30 | |
32 | 32 | payload := [][]byte{[]byte("Payload"), []byte("Test")} |
33 | 33 | c1, err := NewClient(host, logger, Payload(payload)) |
34 | 34 | if err != nil { |
35 | t.Fatal(err) | |
35 | t.Fatalf("Connect returned error: %v", err) | |
36 | 36 | } |
37 | 37 | if c1 == nil { |
38 | t.Error("expected pointer to client, got nil") | |
38 | t.Fatal("Expected pointer to client, got nil") | |
39 | 39 | } |
40 | 40 | defer c1.Stop() |
41 | 41 | |
42 | p, err := NewPublisher(c1, path, NewFactory(""), logger) | |
42 | p, err := NewPublisher(c1, path, newFactory(""), logger) | |
43 | 43 | if err != nil { |
44 | t.Fatal(err) | |
44 | t.Fatalf("Unable to create Publisher: %v", err) | |
45 | 45 | } |
46 | 46 | defer p.Stop() |
47 | 47 | |
50 | 50 | t.Fatal(err) |
51 | 51 | } |
52 | 52 | if want, have := 0, len(endpoints); want != have { |
53 | t.Errorf("want %q, have %q", want, have) | |
53 | t.Errorf("want %d, have %d", want, have) | |
54 | 54 | } |
55 | 55 | |
56 | 56 | c2, err := NewClient(host, logger) |
57 | 57 | if err != nil { |
58 | t.Fatalf("Connect returned error: %+v", err) | |
58 | t.Fatalf("Connect returned error: %v", err) | |
59 | 59 | } |
60 | 60 | defer c2.Stop() |
61 | c2impl, _ := c2.(*client) | |
62 | data, _, err := c2impl.Get(path) | |
61 | data, _, err := c2.(*client).Get(path) | |
63 | 62 | if err != nil { |
64 | 63 | t.Fatal(err) |
65 | 64 | } |
66 | 65 | // test Client implementation of CreateParentNodes. It should have created |
67 | 66 | // our payload |
68 | 67 | if bytes.Compare(data, payload[1]) != 0 { |
69 | t.Errorf("want %q, have %q", payload[1], data) | |
68 | t.Errorf("want %s, have %s", payload[1], data) | |
70 | 69 | } |
71 | 70 | |
72 | 71 | } |
75 | 74 | c, _ := NewClient(host, logger) |
76 | 75 | defer c.Stop() |
77 | 76 | |
78 | _, err := NewPublisher(c, "invalid/path", NewFactory(""), logger) | |
77 | _, err := NewPublisher(c, "invalid/path", newFactory(""), logger) | |
79 | 78 | |
80 | if want, have := ErrInvalidPath, err; want != have { | |
81 | t.Errorf("want %q, have %q", want, have) | |
79 | if want, have := stdzk.ErrInvalidPath, err; want != have { | |
80 | t.Errorf("want %v, have %v", want, have) | |
82 | 81 | } |
83 | 82 | } |
84 | 83 | |
87 | 86 | c, _ := NewClient(host, logger, ACL(acl), Credentials("user", "secret")) |
88 | 87 | defer c.Stop() |
89 | 88 | |
90 | _, err := NewPublisher(c, "/acl-issue-test", NewFactory(""), logger) | |
89 | _, err := NewPublisher(c, "/acl-issue-test", newFactory(""), logger) | |
91 | 90 | |
92 | 91 | if err != nil { |
93 | 92 | t.Fatal(err) |
99 | 98 | c, _ := NewClient(host, logger, ACL(acl)) |
100 | 99 | defer c.Stop() |
101 | 100 | |
102 | _, err := NewPublisher(c, "/acl-issue-test", NewFactory(""), logger) | |
101 | _, err := NewPublisher(c, "/acl-issue-test", newFactory(""), logger) | |
103 | 102 | |
104 | 103 | if err != stdzk.ErrNoAuth { |
105 | t.Errorf("want %q, have %q", stdzk.ErrNoAuth, err) | |
104 | t.Errorf("want %v, have %v", stdzk.ErrNoAuth, err) | |
106 | 105 | } |
107 | 106 | } |
108 | 107 | |
110 | 109 | c, _ := NewClient(host, logger) |
111 | 110 | c.Stop() |
112 | 111 | |
113 | _, err := NewPublisher(c, "/acl-issue-test", NewFactory(""), logger) | |
112 | _, err := NewPublisher(c, "/acl-issue-test", newFactory(""), logger) | |
114 | 113 | |
115 | 114 | if err != ErrClientClosed { |
116 | t.Errorf("want %q, have %q", stdzk.ErrNoAuth, err) | |
115 | t.Errorf("want %v, have %v", ErrClientClosed, err) | |
117 | 116 | } |
118 | 117 | } |
119 | 118 | |
122 | 121 | |
123 | 122 | c1, err := NewClient(host, logger) |
124 | 123 | if err != nil { |
125 | t.Fatalf("Connect returned error: %+v", err) | |
124 | t.Fatalf("Connect returned error: %v", err) | |
126 | 125 | } |
127 | 126 | |
128 | 127 | defer c1.Stop() |
129 | 128 | |
130 | 129 | c2, err := NewClient(host, logger) |
131 | p, err := NewPublisher(c2, path, NewFactory(""), logger) | |
130 | p, err := NewPublisher(c2, path, newFactory(""), logger) | |
132 | 131 | if err != nil { |
133 | 132 | t.Fatal(err) |
134 | 133 | } |
142 | 141 | stdzk.WorldACL(stdzk.PermAll), |
143 | 142 | ) |
144 | 143 | if err != nil { |
145 | t.Fatalf("Unable to create test ephemeral znode 1: %+v", err) | |
144 | t.Fatalf("Unable to create test ephemeral znode 1: %v", err) | |
146 | 145 | } |
147 | 146 | _, err = c2impl.Create( |
148 | 147 | path+"/instance2", |
151 | 150 | stdzk.WorldACL(stdzk.PermAll), |
152 | 151 | ) |
153 | 152 | if err != nil { |
154 | t.Fatalf("Unable to create test ephemeral znode 2: %+v", err) | |
153 | t.Fatalf("Unable to create test ephemeral znode 2: %v", err) | |
155 | 154 | } |
156 | 155 | |
157 | time.Sleep(1 * time.Millisecond) | |
156 | time.Sleep(50 * time.Millisecond) | |
158 | 157 | |
159 | 158 | endpoints, err := p.Endpoints() |
160 | 159 | if err != nil { |
161 | 160 | t.Fatal(err) |
162 | 161 | } |
163 | 162 | if want, have := 2, len(endpoints); want != have { |
164 | t.Errorf("want %q, have %q", want, have) | |
163 | t.Errorf("want %d, have %d", want, have) | |
165 | 164 | } |
166 | 165 | } |
167 | 166 | |
168 | 167 | func TestGetEntriesPayloadOnServer(t *testing.T) { |
169 | 168 | c, err := NewClient(host, logger) |
170 | 169 | if err != nil { |
171 | t.Fatal(err) | |
170 | t.Fatalf("Connect returned error: %v", err) | |
172 | 171 | } |
173 | 172 | _, eventc, err := c.GetEntries(path) |
174 | 173 | if err != nil { |
175 | 174 | t.Fatal(err) |
176 | 175 | } |
177 | cimpl, _ := c.(*client) | |
178 | _, err = cimpl.Create( | |
176 | _, err = c.(*client).Create( | |
179 | 177 | path+"/instance3", |
180 | 178 | []byte("just some payload"), |
181 | 179 | stdzk.FlagEphemeral|stdzk.FlagSequence, |
182 | 180 | stdzk.WorldACL(stdzk.PermAll), |
183 | 181 | ) |
182 | if err != nil { | |
183 | t.Fatalf("Unable to create test ephemeral znode: %v", err) | |
184 | } | |
184 | 185 | select { |
185 | 186 | case event := <-eventc: |
186 | payload, ok := event.payload.(stdzk.Event) | |
187 | if !ok { | |
188 | t.Errorf("expected payload to be of type %s", "zk.Event") | |
189 | } | |
190 | if want, have := stdzk.EventNodeChildrenChanged, payload.Type; want != have { | |
191 | t.Errorf("want %q, have %q", want, have) | |
187 | if want, have := stdzk.EventNodeChildrenChanged.String(), event.Type.String(); want != have { | |
188 | t.Errorf("want %s, have %s", want, have) | |
192 | 189 | } |
193 | 190 | case <-time.After(20 * time.Millisecond): |
194 | 191 | t.Errorf("expected incoming watch event, timeout occurred") |
3 | 3 | "github.com/go-kit/kit/endpoint" |
4 | 4 | "github.com/go-kit/kit/loadbalancer" |
5 | 5 | "github.com/go-kit/kit/log" |
6 | "github.com/samuel/go-zookeeper/zk" | |
6 | 7 | ) |
7 | 8 | |
8 | 9 | // Publisher yield endpoints stored in a certain ZooKeeper path. Any kind of |
46 | 47 | return p, nil |
47 | 48 | } |
48 | 49 | |
49 | func (p *Publisher) loop(eventc <-chan Event) { | |
50 | func (p *Publisher) loop(eventc <-chan zk.Event) { | |
50 | 51 | var ( |
51 | 52 | instances []string |
52 | 53 | err error |
53 | 54 | ) |
54 | 55 | for { |
55 | 56 | select { |
56 | case _, more := <-eventc: | |
57 | if !more { | |
58 | // channel was closed by client... end this loop | |
59 | return | |
60 | } | |
57 | case <-eventc: | |
61 | 58 | // we received a path update notification, call GetEntries to |
62 | 59 | // retrieve child node data and set new watch as zk watches are one |
63 | 60 | // time triggers |
10 | 10 | "github.com/go-kit/kit/endpoint" |
11 | 11 | "github.com/go-kit/kit/loadbalancer" |
12 | 12 | "github.com/go-kit/kit/log" |
13 | "github.com/samuel/go-zookeeper/zk" | |
13 | 14 | ) |
14 | 15 | |
15 | 16 | var ( |
21 | 22 | func TestPublisher(t *testing.T) { |
22 | 23 | client := newFakeClient() |
23 | 24 | |
24 | p, err := NewPublisher(client, path, NewFactory(""), logger) | |
25 | p, err := NewPublisher(client, path, newFactory(""), logger) | |
25 | 26 | if err != nil { |
26 | 27 | t.Fatalf("failed to create new publisher: %v", err) |
27 | 28 | } |
35 | 36 | func TestBadFactory(t *testing.T) { |
36 | 37 | client := newFakeClient() |
37 | 38 | |
38 | p, err := NewPublisher(client, path, NewFactory("kaboom"), logger) | |
39 | p, err := NewPublisher(client, path, newFactory("kaboom"), logger) | |
39 | 40 | if err != nil { |
40 | 41 | t.Fatalf("failed to create new publisher: %v", err) |
41 | 42 | } |
45 | 46 | if err != nil { |
46 | 47 | t.Fatal(err) |
47 | 48 | } |
48 | ||
49 | if want, have := 0, len(endpoints); want != have { | |
50 | t.Errorf("want %q, have %q", want, have) | |
51 | } | |
52 | } | |
53 | ||
54 | func TestServiceUpdate(t *testing.T) { | |
55 | client := newFakeClient() | |
56 | ||
57 | p, err := NewPublisher(client, path, NewFactory(""), logger) | |
58 | if err != nil { | |
59 | t.Fatalf("failed to create new publisher: %v", err) | |
60 | } | |
61 | defer p.Stop() | |
62 | ||
63 | endpoints, err := p.Endpoints() | |
64 | if err != nil { | |
65 | t.Fatal(err) | |
66 | } | |
67 | if want, have := 0, len(endpoints); want != have { | |
68 | t.Errorf("want %q, have %q", want, have) | |
69 | } | |
70 | ||
71 | 49 | // instance1 came online |
72 | 50 | client.AddService(path+"/instance1", "zookeeper_node_data") |
73 | 51 | |
52 | if want, have := 0, len(endpoints); want != have { | |
53 | t.Errorf("want %d, have %d", want, have) | |
54 | } | |
55 | } | |
56 | ||
57 | func TestServiceUpdate(t *testing.T) { | |
58 | client := newFakeClient() | |
59 | ||
60 | p, err := NewPublisher(client, path, newFactory(""), logger) | |
61 | if err != nil { | |
62 | t.Fatalf("failed to create new publisher: %v", err) | |
63 | } | |
64 | defer p.Stop() | |
65 | ||
66 | endpoints, err := p.Endpoints() | |
67 | if err != nil { | |
68 | t.Fatal(err) | |
69 | } | |
70 | if want, have := 0, len(endpoints); want != have { | |
71 | t.Errorf("want %d, have %d", want, have) | |
72 | } | |
73 | ||
74 | // instance1 came online | |
75 | client.AddService(path+"/instance1", "zookeeper_node_data") | |
76 | ||
74 | 77 | // test if we received the instance |
75 | 78 | endpoints, err = p.Endpoints() |
76 | 79 | if err != nil { |
77 | 80 | t.Fatal(err) |
78 | 81 | } |
79 | 82 | if want, have := 1, len(endpoints); want != have { |
80 | t.Errorf("want %q, have %q", want, have) | |
83 | t.Errorf("want %d, have %d", want, have) | |
81 | 84 | } |
82 | 85 | |
83 | 86 | // instance2 came online |
89 | 92 | t.Fatal(err) |
90 | 93 | } |
91 | 94 | if want, have := 2, len(endpoints); want != have { |
92 | t.Errorf("want %q, have %q", want, have) | |
95 | t.Errorf("want %d, have %d", want, have) | |
93 | 96 | } |
94 | 97 | |
95 | 98 | // watch triggers an error... |
101 | 104 | t.Fatal(err) |
102 | 105 | } |
103 | 106 | if want, have := 2, len(endpoints); want != have { |
104 | t.Errorf("want %q, have %q", want, have) | |
107 | t.Errorf("want %d, have %d", want, have) | |
105 | 108 | } |
106 | 109 | |
107 | 110 | // instances go offline |
113 | 116 | t.Fatal(err) |
114 | 117 | } |
115 | 118 | if want, have := 0, len(endpoints); want != have { |
116 | t.Errorf("want %q, have %q", want, have) | |
119 | t.Errorf("want %d, have %d", want, have) | |
120 | } | |
121 | } | |
122 | ||
123 | func TestBadPublisherCreate(t *testing.T) { | |
124 | client := newFakeClient() | |
125 | client.SendErrorOnWatch() | |
126 | p, err := NewPublisher(client, path, newFactory(""), logger) | |
127 | if err == nil { | |
128 | t.Errorf("expected error on new publisher") | |
129 | } | |
130 | if p != nil { | |
131 | t.Errorf("expected publisher not to be created") | |
132 | } | |
133 | p, err = NewPublisher(client, "BadPath", newFactory(""), logger) | |
134 | if err == nil { | |
135 | t.Errorf("expected error on new publisher") | |
136 | } | |
137 | if p != nil { | |
138 | t.Errorf("expected publisher not to be created") | |
117 | 139 | } |
118 | 140 | } |
119 | 141 | |
120 | 142 | type fakeClient struct { |
121 | ch chan Event | |
143 | ch chan zk.Event | |
122 | 144 | responses map[string]string |
123 | 145 | result bool |
124 | 146 | } |
125 | 147 | |
126 | 148 | func newFakeClient() *fakeClient { |
127 | 149 | return &fakeClient{ |
128 | make(chan Event, 1), | |
150 | make(chan zk.Event, 1), | |
129 | 151 | make(map[string]string), |
130 | 152 | true, |
131 | 153 | } |
132 | 154 | } |
133 | 155 | |
134 | 156 | func (c *fakeClient) CreateParentNodes(path string) error { |
157 | if path == "BadPath" { | |
158 | return errors.New("Dummy Error") | |
159 | } | |
135 | 160 | return nil |
136 | 161 | } |
137 | 162 | |
138 | func (c *fakeClient) GetEntries(path string) ([]string, <-chan Event, error) { | |
163 | func (c *fakeClient) GetEntries(path string) ([]string, <-chan zk.Event, error) { | |
139 | 164 | responses := []string{} |
140 | 165 | if c.result == false { |
141 | 166 | c.result = true |
164 | 189 | |
165 | 190 | func (c *fakeClient) Stop() {} |
166 | 191 | |
167 | func NewFactory(fakeError string) loadbalancer.Factory { | |
192 | func newFactory(fakeError string) loadbalancer.Factory { | |
168 | 193 | return func(string) (endpoint.Endpoint, io.Closer, error) { |
169 | 194 | if fakeError == "" { |
170 | 195 | return e, nil, nil |
174 | 199 | } |
175 | 200 | |
176 | 201 | func (c *fakeClient) triggerWatch() { |
177 | c.ch <- Event{true} | |
202 | c.ch <- zk.Event{} | |
178 | 203 | // watches on ZooKeeper Nodes trigger once, most ZooKeeper libraries also |
179 | 204 | // implement "fire once" channels for these watches |
180 | 205 | close(c.ch) |
181 | c.ch = make(chan Event, 1) | |
206 | c.ch = make(chan zk.Event, 1) | |
182 | 207 | |
183 | 208 | // make sure we allow the Publisher to handle this update |
184 | 209 | time.Sleep(1 * time.Millisecond) |