OLD | NEW |
| (Empty) |
1 // Copyright 2012, 2013 Canonical Ltd. | |
2 // Licensed under the AGPLv3, see LICENCE file for details. | |
3 | |
4 package apiuniter_test | |
5 | |
6 import ( | |
7 "fmt" | |
8 "io/ioutil" | |
9 "os" | |
10 "path/filepath" | |
11 "strings" | |
12 "time" | |
13 | |
14 gc "launchpad.net/gocheck" | |
15 | |
16 "launchpad.net/juju-core/charm" | |
17 "launchpad.net/juju-core/juju/testing" | |
18 "launchpad.net/juju-core/state" | |
19 "launchpad.net/juju-core/state/api" | |
20 "launchpad.net/juju-core/state/api/params" | |
21 "launchpad.net/juju-core/state/api/uniter" | |
22 "launchpad.net/juju-core/utils" | |
23 "launchpad.net/juju-core/worker/apiuniter" | |
24 "launchpad.net/juju-core/worker/apiuniter/jujuc" | |
25 ) | |
26 | |
27 type RunHookSuite struct { | |
28 HookContextSuite | |
29 } | |
30 | |
31 var _ = gc.Suite(&RunHookSuite{}) | |
32 | |
33 type hookSpec struct { | |
34 // name is the name of the hook. | |
35 name string | |
36 // perm is the file permissions of the hook. | |
37 perm os.FileMode | |
38 // code is the exit status of the hook. | |
39 code int | |
40 // stdout holds a string to print to stdout | |
41 stdout string | |
42 // stderr holds a string to print to stderr | |
43 stderr string | |
44 // background holds a string to print in the background after 0.2s. | |
45 background string | |
46 } | |
47 | |
48 // makeCharm constructs a fake charm dir containing a single named hook | |
49 // with permissions perm and exit code code. If output is non-empty, | |
50 // the charm will write it to stdout and stderr, with each one prefixed | |
51 // by name of the stream. It returns the charm directory and the path | |
52 // to which the hook script will write environment variables. | |
53 func makeCharm(c *gc.C, spec hookSpec) (charmDir, outPath string) { | |
54 charmDir = c.MkDir() | |
55 hooksDir := filepath.Join(charmDir, "hooks") | |
56 err := os.Mkdir(hooksDir, 0755) | |
57 c.Assert(err, gc.IsNil) | |
58 c.Logf("openfile perm %v", spec.perm) | |
59 hook, err := os.OpenFile(filepath.Join(hooksDir, spec.name), os.O_CREATE
|os.O_WRONLY, spec.perm) | |
60 c.Assert(err, gc.IsNil) | |
61 defer hook.Close() | |
62 printf := func(f string, a ...interface{}) { | |
63 if _, err := fmt.Fprintf(hook, f+"\n", a...); err != nil { | |
64 panic(err) | |
65 } | |
66 } | |
67 outPath = filepath.Join(c.MkDir(), "hook.out") | |
68 printf("#!/bin/bash") | |
69 printf("env > " + outPath) | |
70 if spec.stdout != "" { | |
71 printf("echo %s", spec.stdout) | |
72 } | |
73 if spec.stderr != "" { | |
74 printf("echo %s >&2", spec.stderr) | |
75 } | |
76 if spec.background != "" { | |
77 // Print something fairly quickly, then sleep for | |
78 // quite a long time - if the hook execution is | |
79 // blocking because of the background process, | |
80 // the hook execution will take much longer than | |
81 // expected. | |
82 printf("(sleep 0.2; echo %s; sleep 10) &", spec.background) | |
83 } | |
84 printf("exit %d", spec.code) | |
85 return charmDir, outPath | |
86 } | |
87 | |
88 func AssertEnvContains(c *gc.C, lines []string, env map[string]string) { | |
89 for k, v := range env { | |
90 sought := k + "=" + v | |
91 found := false | |
92 for _, line := range lines { | |
93 if line == sought { | |
94 found = true | |
95 continue | |
96 } | |
97 } | |
98 comment := gc.Commentf("expected to find %v among %v", sought, l
ines) | |
99 c.Assert(found, gc.Equals, true, comment) | |
100 } | |
101 } | |
102 | |
103 func AssertEnv(c *gc.C, outPath string, charmDir string, env map[string]string,
uuid string) { | |
104 out, err := ioutil.ReadFile(outPath) | |
105 c.Assert(err, gc.IsNil) | |
106 lines := strings.Split(string(out), "\n") | |
107 AssertEnvContains(c, lines, env) | |
108 AssertEnvContains(c, lines, map[string]string{ | |
109 "DEBIAN_FRONTEND": "noninteractive", | |
110 "APT_LISTCHANGES_FRONTEND": "none", | |
111 "CHARM_DIR": charmDir, | |
112 "JUJU_AGENT_SOCKET": "/path/to/socket", | |
113 "JUJU_ENV_UUID": uuid, | |
114 }) | |
115 } | |
116 | |
117 // LineBufferSize matches the constant used when creating | |
118 // the bufio line reader. | |
119 const lineBufferSize = 4096 | |
120 | |
121 var apiAddrs = []string{"a1:123", "a2:123"} | |
122 var expectedApiAddrs = strings.Join(apiAddrs, " ") | |
123 | |
124 var runHookTests = []struct { | |
125 summary string | |
126 relid int | |
127 remote string | |
128 spec hookSpec | |
129 err string | |
130 env map[string]string | |
131 }{ | |
132 { | |
133 summary: "missing hook is not an error", | |
134 relid: -1, | |
135 }, { | |
136 summary: "report failure to execute hook", | |
137 relid: -1, | |
138 spec: hookSpec{perm: 0600}, | |
139 err: `exec: .*something-happened": permission denied`, | |
140 }, { | |
141 summary: "report error indicated by hook's exit status", | |
142 relid: -1, | |
143 spec: hookSpec{ | |
144 perm: 0700, | |
145 code: 99, | |
146 }, | |
147 err: "exit status 99", | |
148 }, { | |
149 summary: "output logging", | |
150 relid: -1, | |
151 spec: hookSpec{ | |
152 perm: 0700, | |
153 stdout: "stdout", | |
154 stderr: "stderr", | |
155 }, | |
156 }, { | |
157 summary: "output logging with background process", | |
158 relid: -1, | |
159 spec: hookSpec{ | |
160 perm: 0700, | |
161 stdout: "stdout", | |
162 background: "not printed", | |
163 }, | |
164 }, { | |
165 summary: "long line split", | |
166 relid: -1, | |
167 spec: hookSpec{ | |
168 perm: 0700, | |
169 stdout: strings.Repeat("a", lineBufferSize+10), | |
170 }, | |
171 }, { | |
172 summary: "check shell environment for non-relation hook context"
, | |
173 relid: -1, | |
174 spec: hookSpec{perm: 0700}, | |
175 env: map[string]string{ | |
176 "JUJU_UNIT_NAME": "u/0", | |
177 "JUJU_API_ADDRESSES": expectedApiAddrs, | |
178 }, | |
179 }, { | |
180 summary: "check shell environment for relation-broken hook conte
xt", | |
181 relid: 1, | |
182 spec: hookSpec{perm: 0700}, | |
183 env: map[string]string{ | |
184 "JUJU_UNIT_NAME": "u/0", | |
185 "JUJU_API_ADDRESSES": expectedApiAddrs, | |
186 "JUJU_RELATION": "db", | |
187 "JUJU_RELATION_ID": "db:1", | |
188 "JUJU_REMOTE_UNIT": "", | |
189 }, | |
190 }, { | |
191 summary: "check shell environment for relation hook context", | |
192 relid: 1, | |
193 remote: "r/1", | |
194 spec: hookSpec{perm: 0700}, | |
195 env: map[string]string{ | |
196 "JUJU_UNIT_NAME": "u/0", | |
197 "JUJU_API_ADDRESSES": expectedApiAddrs, | |
198 "JUJU_RELATION": "db", | |
199 "JUJU_RELATION_ID": "db:1", | |
200 "JUJU_REMOTE_UNIT": "r/1", | |
201 }, | |
202 }, | |
203 } | |
204 | |
205 func (s *RunHookSuite) TestRunHook(c *gc.C) { | |
206 uuid, err := utils.NewUUID() | |
207 c.Assert(err, gc.IsNil) | |
208 for i, t := range runHookTests { | |
209 c.Logf("test %d: %s; perm %v", i, t.summary, t.spec.perm) | |
210 ctx := s.GetHookContext(c, uuid.String(), t.relid, t.remote) | |
211 var charmDir, outPath string | |
212 if t.spec.perm == 0 { | |
213 charmDir = c.MkDir() | |
214 } else { | |
215 spec := t.spec | |
216 spec.name = "something-happened" | |
217 c.Logf("makeCharm %#v", spec) | |
218 charmDir, outPath = makeCharm(c, spec) | |
219 } | |
220 toolsDir := c.MkDir() | |
221 t0 := time.Now() | |
222 err := ctx.RunHook("something-happened", charmDir, toolsDir, "/p
ath/to/socket") | |
223 if t.err == "" { | |
224 c.Assert(err, gc.IsNil) | |
225 } else { | |
226 c.Assert(err, gc.ErrorMatches, t.err) | |
227 } | |
228 if t.env != nil { | |
229 env := map[string]string{"PATH": toolsDir + ":" + os.Get
env("PATH")} | |
230 for k, v := range t.env { | |
231 env[k] = v | |
232 } | |
233 AssertEnv(c, outPath, charmDir, env, uuid.String()) | |
234 } | |
235 if t.spec.background != "" && time.Now().Sub(t0) > 5*time.Second
{ | |
236 c.Errorf("background process holding up hook execution") | |
237 } | |
238 } | |
239 } | |
240 | |
241 // split the line into buffer-sized lengths. | |
242 func splitLine(s string) []string { | |
243 var ss []string | |
244 for len(s) > lineBufferSize { | |
245 ss = append(ss, s[0:lineBufferSize]) | |
246 s = s[lineBufferSize:] | |
247 } | |
248 if len(s) > 0 { | |
249 ss = append(ss, s) | |
250 } | |
251 return ss | |
252 } | |
253 | |
254 func (s *RunHookSuite) TestRunHookRelationFlushing(c *gc.C) { | |
255 // Create a charm with a breaking hook. | |
256 uuid, err := utils.NewUUID() | |
257 c.Assert(err, gc.IsNil) | |
258 ctx := s.GetHookContext(c, uuid.String(), -1, "") | |
259 charmDir, _ := makeCharm(c, hookSpec{ | |
260 name: "something-happened", | |
261 perm: 0700, | |
262 code: 123, | |
263 }) | |
264 | |
265 // Mess with multiple relation settings. | |
266 node0, err := s.relctxs[0].Settings() | |
267 node0.Set("foo", "1") | |
268 node1, err := s.relctxs[1].Settings() | |
269 node1.Set("bar", "2") | |
270 | |
271 // Run the failing hook. | |
272 err = ctx.RunHook("something-happened", charmDir, c.MkDir(), "/path/to/s
ocket") | |
273 c.Assert(err, gc.ErrorMatches, "exit status 123") | |
274 | |
275 // Check that the changes to the local settings nodes have been discarde
d. | |
276 node0, err = s.relctxs[0].Settings() | |
277 c.Assert(err, gc.IsNil) | |
278 c.Assert(node0.Map(), gc.DeepEquals, params.Settings{"relation-name": "d
b0"}) | |
279 node1, err = s.relctxs[1].Settings() | |
280 c.Assert(err, gc.IsNil) | |
281 c.Assert(node1.Map(), gc.DeepEquals, params.Settings{"relation-name": "d
b1"}) | |
282 | |
283 // Check that the changes have been written to state. | |
284 settings0, err := s.relunits[0].ReadSettings("u/0") | |
285 c.Assert(err, gc.IsNil) | |
286 c.Assert(settings0, gc.DeepEquals, map[string]interface{}{"relation-name
": "db0"}) | |
287 settings1, err := s.relunits[1].ReadSettings("u/0") | |
288 c.Assert(err, gc.IsNil) | |
289 c.Assert(settings1, gc.DeepEquals, map[string]interface{}{"relation-name
": "db1"}) | |
290 | |
291 // Create a charm with a working hook, and mess with settings again. | |
292 charmDir, _ = makeCharm(c, hookSpec{ | |
293 name: "something-happened", | |
294 perm: 0700, | |
295 }) | |
296 node0.Set("baz", "3") | |
297 node1.Set("qux", "4") | |
298 | |
299 // Run the hook. | |
300 err = ctx.RunHook("something-happened", charmDir, c.MkDir(), "/path/to/s
ocket") | |
301 c.Assert(err, gc.IsNil) | |
302 | |
303 // Check that the changes to the local settings nodes are still there. | |
304 node0, err = s.relctxs[0].Settings() | |
305 c.Assert(err, gc.IsNil) | |
306 c.Assert(node0.Map(), gc.DeepEquals, params.Settings{ | |
307 "relation-name": "db0", | |
308 "baz": "3", | |
309 }) | |
310 node1, err = s.relctxs[1].Settings() | |
311 c.Assert(err, gc.IsNil) | |
312 c.Assert(node1.Map(), gc.DeepEquals, params.Settings{ | |
313 "relation-name": "db1", | |
314 "qux": "4", | |
315 }) | |
316 | |
317 // Check that the changes have been written to state. | |
318 settings0, err = s.relunits[0].ReadSettings("u/0") | |
319 c.Assert(err, gc.IsNil) | |
320 c.Assert(settings0, gc.DeepEquals, map[string]interface{}{ | |
321 "relation-name": "db0", | |
322 "baz": "3", | |
323 }) | |
324 settings1, err = s.relunits[1].ReadSettings("u/0") | |
325 c.Assert(err, gc.IsNil) | |
326 c.Assert(settings1, gc.DeepEquals, map[string]interface{}{ | |
327 "relation-name": "db1", | |
328 "qux": "4", | |
329 }) | |
330 } | |
331 | |
332 type ContextRelationSuite struct { | |
333 testing.JujuConnSuite | |
334 svc *state.Service | |
335 rel *state.Relation | |
336 ru *state.RelationUnit | |
337 | |
338 st *api.State | |
339 uniter *uniter.State | |
340 apiRelUnit *uniter.RelationUnit | |
341 } | |
342 | |
343 var _ = gc.Suite(&ContextRelationSuite{}) | |
344 | |
345 func (s *ContextRelationSuite) SetUpTest(c *gc.C) { | |
346 s.JujuConnSuite.SetUpTest(c) | |
347 ch := s.AddTestingCharm(c, "riak") | |
348 var err error | |
349 s.svc, err = s.State.AddService("u", ch) | |
350 c.Assert(err, gc.IsNil) | |
351 rels, err := s.svc.Relations() | |
352 c.Assert(err, gc.IsNil) | |
353 c.Assert(rels, gc.HasLen, 1) | |
354 s.rel = rels[0] | |
355 unit, err := s.svc.AddUnit() | |
356 c.Assert(err, gc.IsNil) | |
357 s.ru, err = s.rel.Unit(unit) | |
358 c.Assert(err, gc.IsNil) | |
359 err = s.ru.EnterScope(nil) | |
360 c.Assert(err, gc.IsNil) | |
361 | |
362 err = unit.SetPassword("password") | |
363 c.Assert(err, gc.IsNil) | |
364 s.st = s.OpenAPIAs(c, unit.Tag(), "password") | |
365 c.Assert(s.st, gc.NotNil) | |
366 s.uniter = s.st.Uniter() | |
367 c.Assert(s.uniter, gc.NotNil) | |
368 | |
369 apiRel, err := s.uniter.Relation(s.rel.Tag()) | |
370 c.Assert(err, gc.IsNil) | |
371 apiUnit, err := s.uniter.Unit(unit.Tag()) | |
372 c.Assert(err, gc.IsNil) | |
373 s.apiRelUnit, err = apiRel.Unit(apiUnit) | |
374 c.Assert(err, gc.IsNil) | |
375 } | |
376 | |
377 func (s *ContextRelationSuite) TearDownTest(c *gc.C) { | |
378 if s.st != nil { | |
379 err := s.st.Close() | |
380 c.Assert(err, gc.IsNil) | |
381 } | |
382 s.JujuConnSuite.TearDownTest(c) | |
383 } | |
384 | |
385 func (s *ContextRelationSuite) TestChangeMembers(c *gc.C) { | |
386 ctx := apiuniter.NewContextRelation(s.apiRelUnit, nil) | |
387 c.Assert(ctx.UnitNames(), gc.HasLen, 0) | |
388 | |
389 // Check the units and settings after a simple update. | |
390 ctx.UpdateMembers(apiuniter.SettingsMap{ | |
391 "u/2": {"baz": "2"}, | |
392 "u/4": {"qux": "4"}, | |
393 }) | |
394 c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/2", "u/4"}) | |
395 assertSettings := func(unit string, expect params.Settings) { | |
396 actual, err := ctx.ReadSettings(unit) | |
397 c.Assert(err, gc.IsNil) | |
398 c.Assert(actual, gc.DeepEquals, expect) | |
399 } | |
400 assertSettings("u/2", params.Settings{"baz": "2"}) | |
401 assertSettings("u/4", params.Settings{"qux": "4"}) | |
402 | |
403 // Send a second update; check that members are only added, not removed. | |
404 ctx.UpdateMembers(apiuniter.SettingsMap{ | |
405 "u/1": {"foo": "1"}, | |
406 "u/2": {"abc": "2"}, | |
407 "u/3": {"bar": "3"}, | |
408 }) | |
409 c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1", "u/2", "u/3", "
u/4"}) | |
410 | |
411 // Check that all settings remain cached. | |
412 assertSettings("u/1", params.Settings{"foo": "1"}) | |
413 assertSettings("u/2", params.Settings{"abc": "2"}) | |
414 assertSettings("u/3", params.Settings{"bar": "3"}) | |
415 assertSettings("u/4", params.Settings{"qux": "4"}) | |
416 | |
417 // Delete a member, and check that it is no longer a member... | |
418 ctx.DeleteMember("u/2") | |
419 c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1", "u/3", "u/4"}) | |
420 | |
421 // ...and that its settings are no longer cached. | |
422 _, err := ctx.ReadSettings("u/2") | |
423 c.Assert(err, gc.ErrorMatches, "permission denied") | |
424 } | |
425 | |
426 func (s *ContextRelationSuite) TestMemberCaching(c *gc.C) { | |
427 unit, err := s.svc.AddUnit() | |
428 c.Assert(err, gc.IsNil) | |
429 ru, err := s.rel.Unit(unit) | |
430 c.Assert(err, gc.IsNil) | |
431 err = ru.EnterScope(map[string]interface{}{"blib": "blob"}) | |
432 c.Assert(err, gc.IsNil) | |
433 settings, err := ru.Settings() | |
434 c.Assert(err, gc.IsNil) | |
435 settings.Set("ping", "pong") | |
436 _, err = settings.Write() | |
437 c.Assert(err, gc.IsNil) | |
438 ctx := apiuniter.NewContextRelation(s.apiRelUnit, map[string]int64{"u/1"
: 0}) | |
439 | |
440 // Check that uncached settings are read from state. | |
441 m, err := ctx.ReadSettings("u/1") | |
442 c.Assert(err, gc.IsNil) | |
443 expectMap := settings.Map() | |
444 expectSettings := convertMap(expectMap) | |
445 c.Assert(m, gc.DeepEquals, expectSettings) | |
446 | |
447 // Check that changes to state do not affect the cached settings. | |
448 settings.Set("ping", "pow") | |
449 _, err = settings.Write() | |
450 c.Assert(err, gc.IsNil) | |
451 m, err = ctx.ReadSettings("u/1") | |
452 c.Assert(err, gc.IsNil) | |
453 c.Assert(m, gc.DeepEquals, expectSettings) | |
454 | |
455 // Check that ClearCache spares the members cache. | |
456 ctx.ClearCache() | |
457 m, err = ctx.ReadSettings("u/1") | |
458 c.Assert(err, gc.IsNil) | |
459 c.Assert(m, gc.DeepEquals, expectSettings) | |
460 | |
461 // Check that updating the context overwrites the cached settings, and | |
462 // that the contents of state are ignored. | |
463 ctx.UpdateMembers(apiuniter.SettingsMap{"u/1": {"entirely": "different"}
}) | |
464 m, err = ctx.ReadSettings("u/1") | |
465 c.Assert(err, gc.IsNil) | |
466 c.Assert(m, gc.DeepEquals, params.Settings{"entirely": "different"}) | |
467 } | |
468 | |
469 func (s *ContextRelationSuite) TestNonMemberCaching(c *gc.C) { | |
470 unit, err := s.svc.AddUnit() | |
471 c.Assert(err, gc.IsNil) | |
472 ru, err := s.rel.Unit(unit) | |
473 c.Assert(err, gc.IsNil) | |
474 err = ru.EnterScope(map[string]interface{}{"blib": "blob"}) | |
475 c.Assert(err, gc.IsNil) | |
476 settings, err := ru.Settings() | |
477 c.Assert(err, gc.IsNil) | |
478 settings.Set("ping", "pong") | |
479 _, err = settings.Write() | |
480 c.Assert(err, gc.IsNil) | |
481 ctx := apiuniter.NewContextRelation(s.apiRelUnit, nil) | |
482 | |
483 // Check that settings are read from state. | |
484 m, err := ctx.ReadSettings("u/1") | |
485 c.Assert(err, gc.IsNil) | |
486 expectMap := settings.Map() | |
487 expectSettings := convertMap(expectMap) | |
488 c.Assert(m, gc.DeepEquals, expectSettings) | |
489 | |
490 // Check that changes to state do not affect the obtained settings... | |
491 settings.Set("ping", "pow") | |
492 _, err = settings.Write() | |
493 c.Assert(err, gc.IsNil) | |
494 m, err = ctx.ReadSettings("u/1") | |
495 c.Assert(err, gc.IsNil) | |
496 c.Assert(m, gc.DeepEquals, expectSettings) | |
497 | |
498 // ...until the caches are cleared. | |
499 ctx.ClearCache() | |
500 c.Assert(err, gc.IsNil) | |
501 m, err = ctx.ReadSettings("u/1") | |
502 c.Assert(err, gc.IsNil) | |
503 c.Assert(m["ping"], gc.Equals, "pow") | |
504 } | |
505 | |
506 func (s *ContextRelationSuite) TestSettings(c *gc.C) { | |
507 ctx := apiuniter.NewContextRelation(s.apiRelUnit, nil) | |
508 | |
509 // Change Settings, then clear cache without writing. | |
510 node, err := ctx.Settings() | |
511 c.Assert(err, gc.IsNil) | |
512 expectSettings := node.Map() | |
513 expectMap := convertSettings(expectSettings) | |
514 node.Set("change", "exciting") | |
515 ctx.ClearCache() | |
516 | |
517 // Check that the change is not cached... | |
518 node, err = ctx.Settings() | |
519 c.Assert(err, gc.IsNil) | |
520 c.Assert(node.Map(), gc.DeepEquals, expectSettings) | |
521 | |
522 // ...and not written to state. | |
523 settings, err := s.ru.ReadSettings("u/0") | |
524 c.Assert(err, gc.IsNil) | |
525 c.Assert(settings, gc.DeepEquals, expectMap) | |
526 | |
527 // Change again, write settings, and clear caches. | |
528 node.Set("change", "exciting") | |
529 err = ctx.WriteSettings() | |
530 c.Assert(err, gc.IsNil) | |
531 ctx.ClearCache() | |
532 | |
533 // Check that the change is reflected in Settings... | |
534 expectSettings["change"] = "exciting" | |
535 expectMap["change"] = expectSettings["change"] | |
536 node, err = ctx.Settings() | |
537 c.Assert(err, gc.IsNil) | |
538 c.Assert(node.Map(), gc.DeepEquals, expectSettings) | |
539 | |
540 // ...and was written to state. | |
541 settings, err = s.ru.ReadSettings("u/0") | |
542 c.Assert(err, gc.IsNil) | |
543 c.Assert(settings, gc.DeepEquals, expectMap) | |
544 } | |
545 | |
546 type InterfaceSuite struct { | |
547 HookContextSuite | |
548 } | |
549 | |
550 var _ = gc.Suite(&InterfaceSuite{}) | |
551 | |
552 func (s *InterfaceSuite) GetContext(c *gc.C, relId int, | |
553 remoteName string) jujuc.Context { | |
554 uuid, err := utils.NewUUID() | |
555 c.Assert(err, gc.IsNil) | |
556 return s.HookContextSuite.GetHookContext(c, uuid.String(), relId, remote
Name) | |
557 } | |
558 | |
559 func (s *InterfaceSuite) TestUtils(c *gc.C) { | |
560 ctx := s.GetContext(c, -1, "") | |
561 c.Assert(ctx.UnitName(), gc.Equals, "u/0") | |
562 r, found := ctx.HookRelation() | |
563 c.Assert(found, gc.Equals, false) | |
564 c.Assert(r, gc.IsNil) | |
565 name, found := ctx.RemoteUnitName() | |
566 c.Assert(found, gc.Equals, false) | |
567 c.Assert(name, gc.Equals, "") | |
568 c.Assert(ctx.RelationIds(), gc.HasLen, 2) | |
569 r, found = ctx.Relation(0) | |
570 c.Assert(found, gc.Equals, true) | |
571 c.Assert(r.Name(), gc.Equals, "db") | |
572 c.Assert(r.FakeId(), gc.Equals, "db:0") | |
573 r, found = ctx.Relation(123) | |
574 c.Assert(found, gc.Equals, false) | |
575 c.Assert(r, gc.IsNil) | |
576 | |
577 ctx = s.GetContext(c, 1, "") | |
578 r, found = ctx.HookRelation() | |
579 c.Assert(found, gc.Equals, true) | |
580 c.Assert(r.Name(), gc.Equals, "db") | |
581 c.Assert(r.FakeId(), gc.Equals, "db:1") | |
582 | |
583 ctx = s.GetContext(c, 1, "u/123") | |
584 name, found = ctx.RemoteUnitName() | |
585 c.Assert(found, gc.Equals, true) | |
586 c.Assert(name, gc.Equals, "u/123") | |
587 } | |
588 | |
589 func (s *InterfaceSuite) TestUnitCaching(c *gc.C) { | |
590 ctx := s.GetContext(c, -1, "") | |
591 pr, ok := ctx.PrivateAddress() | |
592 c.Assert(ok, gc.Equals, true) | |
593 c.Assert(pr, gc.Equals, "u-0.testing.invalid") | |
594 _, ok = ctx.PublicAddress() | |
595 c.Assert(ok, gc.Equals, false) | |
596 | |
597 // Change remote state. | |
598 u, err := s.State.Unit("u/0") | |
599 c.Assert(err, gc.IsNil) | |
600 err = u.SetPrivateAddress("") | |
601 c.Assert(err, gc.IsNil) | |
602 err = u.SetPublicAddress("blah.testing.invalid") | |
603 c.Assert(err, gc.IsNil) | |
604 | |
605 // Local view is unchanged. | |
606 pr, ok = ctx.PrivateAddress() | |
607 c.Assert(ok, gc.Equals, true) | |
608 c.Assert(pr, gc.Equals, "u-0.testing.invalid") | |
609 _, ok = ctx.PublicAddress() | |
610 c.Assert(ok, gc.Equals, false) | |
611 } | |
612 | |
613 func (s *InterfaceSuite) TestConfigCaching(c *gc.C) { | |
614 ctx := s.GetContext(c, -1, "") | |
615 settings, err := ctx.ConfigSettings() | |
616 c.Assert(err, gc.IsNil) | |
617 c.Assert(settings, gc.DeepEquals, charm.Settings{"blog-title": "My Title
"}) | |
618 | |
619 // Change remote config. | |
620 err = s.service.UpdateConfigSettings(charm.Settings{ | |
621 "blog-title": "Something Else", | |
622 }) | |
623 c.Assert(err, gc.IsNil) | |
624 | |
625 // Local view is not changed. | |
626 settings, err = ctx.ConfigSettings() | |
627 c.Assert(err, gc.IsNil) | |
628 c.Assert(settings, gc.DeepEquals, charm.Settings{"blog-title": "My Title
"}) | |
629 } | |
630 | |
631 type HookContextSuite struct { | |
632 testing.JujuConnSuite | |
633 service *state.Service | |
634 unit *state.Unit | |
635 relch *state.Charm | |
636 relunits map[int]*state.RelationUnit | |
637 relctxs map[int]*apiuniter.ContextRelation | |
638 | |
639 st *api.State | |
640 uniter *uniter.State | |
641 apiUnit *uniter.Unit | |
642 } | |
643 | |
644 func (s *HookContextSuite) SetUpTest(c *gc.C) { | |
645 s.JujuConnSuite.SetUpTest(c) | |
646 var err error | |
647 sch := s.AddTestingCharm(c, "wordpress") | |
648 s.service, err = s.State.AddService("u", sch) | |
649 c.Assert(err, gc.IsNil) | |
650 s.unit = s.AddUnit(c, s.service) | |
651 | |
652 err = s.unit.SetPassword("password") | |
653 c.Assert(err, gc.IsNil) | |
654 s.st = s.OpenAPIAs(c, s.unit.Tag(), "password") | |
655 c.Assert(s.st, gc.NotNil) | |
656 s.uniter = s.st.Uniter() | |
657 c.Assert(s.uniter, gc.NotNil) | |
658 | |
659 // Note: The unit must always have a charm URL set, because this | |
660 // happens as part of the installation process (that happens | |
661 // before the initial install hook). | |
662 err = s.unit.SetCharmURL(sch.URL()) | |
663 c.Assert(err, gc.IsNil) | |
664 s.relch = s.AddTestingCharm(c, "mysql") | |
665 s.relunits = map[int]*state.RelationUnit{} | |
666 s.relctxs = map[int]*apiuniter.ContextRelation{} | |
667 s.AddContextRelation(c, "db0") | |
668 s.AddContextRelation(c, "db1") | |
669 } | |
670 | |
671 func (s *HookContextSuite) TearDownTest(c *gc.C) { | |
672 if s.st != nil { | |
673 err := s.st.Close() | |
674 c.Assert(err, gc.IsNil) | |
675 } | |
676 s.JujuConnSuite.TearDownTest(c) | |
677 } | |
678 | |
679 func (s *HookContextSuite) AddUnit(c *gc.C, svc *state.Service) *state.Unit { | |
680 unit, err := svc.AddUnit() | |
681 c.Assert(err, gc.IsNil) | |
682 name := strings.Replace(unit.Name(), "/", "-", 1) | |
683 err = unit.SetPrivateAddress(name + ".testing.invalid") | |
684 c.Assert(err, gc.IsNil) | |
685 return unit | |
686 } | |
687 | |
688 func (s *HookContextSuite) AddContextRelation(c *gc.C, name string) { | |
689 _, err := s.State.AddService(name, s.relch) | |
690 c.Assert(err, gc.IsNil) | |
691 eps, err := s.State.InferEndpoints([]string{"u", name}) | |
692 c.Assert(err, gc.IsNil) | |
693 rel, err := s.State.AddRelation(eps...) | |
694 c.Assert(err, gc.IsNil) | |
695 ru, err := rel.Unit(s.unit) | |
696 c.Assert(err, gc.IsNil) | |
697 s.relunits[rel.Id()] = ru | |
698 err = ru.EnterScope(map[string]interface{}{"relation-name": name}) | |
699 c.Assert(err, gc.IsNil) | |
700 s.apiUnit, err = s.uniter.Unit(s.unit.Tag()) | |
701 c.Assert(err, gc.IsNil) | |
702 apiRel, err := s.uniter.Relation(rel.Tag()) | |
703 c.Assert(err, gc.IsNil) | |
704 apiRelUnit, err := apiRel.Unit(s.apiUnit) | |
705 c.Assert(err, gc.IsNil) | |
706 s.relctxs[rel.Id()] = apiuniter.NewContextRelation(apiRelUnit, nil) | |
707 } | |
708 | |
709 func (s *HookContextSuite) GetHookContext(c *gc.C, uuid string, relid int, | |
710 remote string) *apiuniter.HookContext { | |
711 if relid != -1 { | |
712 _, found := s.relctxs[relid] | |
713 c.Assert(found, gc.Equals, true) | |
714 } | |
715 context, err := apiuniter.NewHookContext(s.apiUnit, "TestCtx", uuid, rel
id, remote, | |
716 s.relctxs, apiAddrs) | |
717 c.Assert(err, gc.IsNil) | |
718 return context | |
719 } | |
720 | |
721 func convertSettings(settings params.Settings) map[string]interface{} { | |
722 result := make(map[string]interface{}) | |
723 for k, v := range settings { | |
724 result[k] = v | |
725 } | |
726 return result | |
727 } | |
728 | |
729 func convertMap(settingsMap map[string]interface{}) params.Settings { | |
730 result := make(params.Settings) | |
731 for k, v := range settingsMap { | |
732 result[k] = v.(string) | |
733 } | |
734 return result | |
735 } | |
OLD | NEW |