blob: b3bc4ddf4a794dd00668719bafc8e5e01cc5f5ac [file] [log] [blame]
David Why Use Two When One Will Do Presotto7034d3b2014-05-30 17:08:46 -07001package config
2
3import (
4 "errors"
5 "fmt"
6 "io/ioutil"
David Why Use Two When One Will Do Presottobe00cfb2014-11-20 10:06:33 -08007 "math/rand"
David Why Use Two When One Will Do Presotto7034d3b2014-05-30 17:08:46 -07008 "os"
9 "testing"
10 "time"
11)
12
13// A config file to be written to disk.
14var configFileA = `
15the:rain
16 in:spain
17falls: mainly
18 on : the
19# something ugly this way comes
20plain: today
21version : 1
22`
23
24// A map referring to the config in configFileA.
25var referenceA = map[string]string{
26 "the": "rain",
27 "in": "spain",
28 "falls": "mainly",
29 "on": "the",
30 "plain": "today",
31 "version": "1",
32}
33
34// Another config file to be written to disk.
35var configFileB = `
36the:rain
37 in:spain
38falls: mainly
39 on : my foot
40# something ugly this way comes
41version : 1.1
42`
43
44// A map referring to the config in configFileB.
45var referenceB = map[string]string{
46 "the": "rain",
47 "in": "spain",
48 "falls": "mainly",
49 "on": "my foot",
50 "version": "1.1",
51}
52
53func compare(actual map[string]string, reference map[string]string) error {
54 for k, rv := range reference {
55 av, ok := actual[k]
56 if !ok {
57 return fmt.Errorf("missing entry for key %q", k)
58 }
59 if av != rv {
60 return fmt.Errorf("bad value for key %q: expected %q, got %q", k, rv, av)
61 }
62 }
63 for k, av := range actual {
64 if _, ok := reference[k]; !ok {
65 return fmt.Errorf("unexpected pair %q : %q", k, av)
66 }
67 }
68 return nil
69}
70
71func compareConfigToReference(cs ConfigService, ref map[string]string) error {
72 actual, err := cs.GetAll()
73 if err != nil {
74 return err
75 }
76 return compare(actual, ref)
77}
78
79func compareFileToReference(file string, ref map[string]string) error {
80 c, err := readFile(file)
81 if err != nil {
82 return err
83 }
84 return compare(c.pairs, ref)
85}
86
87func waitForConsistency(cs ConfigService, ref map[string]string) error {
88 finalerr := errors.New("inconsistent")
David Why Use Two When One Will Do Presottoc54f8782014-09-29 11:48:26 -070089 for loops := 0; loops < 30; loops++ {
David Why Use Two When One Will Do Presotto7034d3b2014-05-30 17:08:46 -070090 actual, err := cs.GetAll()
91 if err == nil {
92 err := compare(actual, ref)
93 if err == nil {
94 return nil
95 }
96 finalerr = err
97 }
98 time.Sleep(500 * time.Millisecond)
99 }
David Why Use Two When One Will Do Presottoc54f8782014-09-29 11:48:26 -0700100 return errors.New("timeout waiting for consistency: " + finalerr.Error())
David Why Use Two When One Will Do Presotto7034d3b2014-05-30 17:08:46 -0700101}
102
David Why Use Two When One Will Do Presottobe00cfb2014-11-20 10:06:33 -0800103func pickRandomPort() uint16 {
104 rand.Seed(time.Now().UnixNano())
105 return uint16(rand.Intn(1000)) + 5354
106}
107
David Why Use Two When One Will Do Presotto7034d3b2014-05-30 17:08:46 -0700108func testConfig(fileA, fileB, fileD string) error {
David Why Use Two When One Will Do Presottobe00cfb2014-11-20 10:06:33 -0800109 port := pickRandomPort()
110
David Why Use Two When One Will Do Presotto7034d3b2014-05-30 17:08:46 -0700111 // Write a config to fileA.
112 if err := ioutil.WriteFile(fileA, []byte(configFileA), 0644); err != nil {
113 return fmt.Errorf("writing initial %s: %s", fileA, err)
114 }
115
116 // Start a config service with a file and compare the parsed file to the reference.
David Why Use Two When One Will Do Presottobe00cfb2014-11-20 10:06:33 -0800117 csa, err := MDNSConfigService("foo", fileA, true, port)
David Why Use Two When One Will Do Presotto7034d3b2014-05-30 17:08:46 -0700118 if err != nil {
119 return fmt.Errorf("creating service %s", err)
120 }
121 defer csa.Stop()
122 if err := compareConfigToReference(csa, referenceA); err != nil {
123 return fmt.Errorf("confA ~ refA: %s", err)
124 }
125
126 // Create a second instance with a non existant file.
David Why Use Two When One Will Do Presottobe00cfb2014-11-20 10:06:33 -0800127 csb, err := MDNSConfigService("foo", fileB, true, port)
David Why Use Two When One Will Do Presotto7034d3b2014-05-30 17:08:46 -0700128 if err != nil {
129 return fmt.Errorf("creating service %s", err)
130 }
131 defer csb.Stop()
132
133 // Create a third passive instance with no file.
David Why Use Two When One Will Do Presottobe00cfb2014-11-20 10:06:33 -0800134 csc, err := MDNSConfigService("foo", "", true, port)
David Why Use Two When One Will Do Presotto7034d3b2014-05-30 17:08:46 -0700135 if err != nil {
136 return fmt.Errorf("creating service %s", err)
137 }
138 defer csc.Stop()
139
140 // Loop till the second instance gets a config or we time out.
141 if err := waitForConsistency(csb, referenceA); err != nil {
142 return fmt.Errorf("confB ~ refA: %s", err)
143 }
144
145 // Make sure that the new instance updated its file.
146 if err := compareFileToReference(fileB, referenceA); err != nil {
147 return fmt.Errorf("fileB ~ refA: %s", err)
148 }
149
150 // Rewrite/Reread the second instance's file, make sure the it rereads it correctly.
151 if err := ioutil.WriteFile(fileB, []byte(configFileB), 0644); err != nil {
152 return fmt.Errorf("writing fileB: %s", err)
153 }
154 if err := csb.Reread(); err != nil {
155 return fmt.Errorf("Rereading fileB: %s", err)
156 }
157 if err := compareConfigToReference(csb, referenceB); err != nil {
158 return fmt.Errorf("confB ~ refB: %s", err)
159 }
160 // Loop till the first instance changes its config or we time out.
161 if err := waitForConsistency(csa, referenceB); err != nil {
162 return fmt.Errorf("confA ~ refB: %s", err)
163 }
164
165 // Make sure that the first instance updated its file.
166 if err := compareFileToReference(fileA, referenceB); err != nil {
167 return fmt.Errorf("fileA ~ refB: %s", err)
168 }
169
170 // Loop till the passive instance changes its config or we time out.
171 if err := waitForConsistency(csc, referenceB); err != nil {
172 return fmt.Errorf("confC ~ refB: %s", err)
173 }
174
175 // Create an instance with an lower numbered version. Make sure it doesn't
176 // overcome the higher numbered one.
177 if err := ioutil.WriteFile(fileD, []byte(configFileA), 0644); err != nil {
178 return fmt.Errorf("writing initial %s: %s", fileD, err)
179 }
David Why Use Two When One Will Do Presottobe00cfb2014-11-20 10:06:33 -0800180 csd, err := MDNSConfigService("foo", fileD, true, port)
David Why Use Two When One Will Do Presotto7034d3b2014-05-30 17:08:46 -0700181 if err != nil {
182 return fmt.Errorf("creating service %s", err)
183 }
184 defer csd.Stop()
185 if err := waitForConsistency(csd, referenceB); err != nil {
186 return fmt.Errorf("confD ~ refB: %s", err)
187 }
188 if err := waitForConsistency(csc, referenceA); err == nil {
189 return errors.New("eventual consistency picked wrong version for C")
190 }
191
192 return nil
193}
194
195func createTempFile(t *testing.T, base string) string {
196 f, err := ioutil.TempFile("", base)
197 if err != nil {
198 t.Fatal("creating temp file: %s", err)
199 }
200 f.Close()
201 return f.Name()
202}
203
204func TestConfig(t *testing.T) {
205 // Create temporary files.
206 fileA := createTempFile(t, "testconfigA")
207 defer os.Remove(fileA)
208 fileB := createTempFile(t, "testconfigB")
209 defer os.Remove(fileB)
210 fileD := createTempFile(t, "testconfigD")
211 defer os.Remove(fileD)
212
213 if err := testConfig(fileA, fileB, fileD); err != nil {
214 t.Fatal(err)
215 }
216}
217
218func TestConfigStream(t *testing.T) {
219 // Create temporary file.
220 fileA := createTempFile(t, "testconfigA")
221 defer os.Remove(fileA)
222
223 // Start a cs service.
David Why Use Two When One Will Do Presottobe00cfb2014-11-20 10:06:33 -0800224 cs, err := MDNSConfigService("foo", fileA, true, pickRandomPort())
David Why Use Two When One Will Do Presotto7034d3b2014-05-30 17:08:46 -0700225 if err != nil {
226 t.Fatal("creating service %s", err)
227 }
228
229 // Watch a single key and all keys.
230 ws := cs.Watch("version")
231 wa := cs.WatchAll()
232
233 // Since there's no config yet, we should just get a
234 // deleted entry for the single key.
235 select {
236 case p := <-ws:
237 if !p.Nonexistant {
238 t.Fatal("expected Nonexistant: got %v", p)
239 }
240 case <-time.After(2 * time.Second):
241 t.Fatal("timeout waiting for single config var")
242 }
243
244 // Write a config to the file and read it.
245 if err := ioutil.WriteFile(fileA, []byte(configFileA), 0644); err != nil {
246 t.Fatal("writing initial %s: %s", fileA, err)
247 }
248 if err := cs.Reread(); err != nil {
249 t.Fatal("rereading config: %s", err)
250 }
251
252 // We should now have a version number.
253 select {
254 case p := <-ws:
255 if p.Nonexistant || p.Key != "version" || p.Value != "1" {
256 t.Fatal("expected [version, 1, false]: got %v", p)
257 }
258
259 case <-time.After(2 * time.Second):
260 t.Fatal("timeout waiting for single config var")
261 }
262
263 // And we should be streamed the whole version.
264 cmap := make(map[string]string, 0)
265L:
266 for {
267 select {
268 case p := <-wa:
269 if p.Nonexistant {
270 t.Fatal("unexpected %v", p)
271 }
272 cmap[p.Key] = p.Value
273 err := compare(referenceA, cmap)
274 if err == nil {
275 break L
276 }
277 case <-time.After(2 * time.Second):
278 t.Fatal("timeout waiting for all config vars")
279 }
280 }
281
282 // Change and reread the config file.
283 if err := ioutil.WriteFile(fileA, []byte(configFileB), 0644); err != nil {
284 t.Fatal("writing initial %s: %s", fileA, err)
285 }
286 if err := cs.Reread(); err != nil {
287 t.Fatal("rereading config: %s", err)
288 }
289
290 // The version should have changed.
291 select {
292 case p := <-ws:
293 if p.Nonexistant || p.Key != "version" || p.Value != "1.1" {
294 t.Fatal("expected [version, 1.1, false]: got %v", p)
295 }
296
297 case <-time.After(2 * time.Second):
298 t.Fatal("timeout waiting for single config var")
299 }
300
301 // And we should now be consistent with referenceB
302L2:
303 for {
304 select {
305 case p := <-wa:
306 if p.Nonexistant {
307 delete(cmap, p.Key)
308 break
309 }
310 cmap[p.Key] = p.Value
311 err := compare(referenceB, cmap)
312 if err == nil {
313 break L2
314 }
315 case <-time.After(2 * time.Second):
316 t.Fatal("timeout waiting for all config vars")
317 }
318 }
319}