1
// Copyright 2012-2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
11
"github.com/juju/errors"
12
jc "github.com/juju/testing/checkers"
13
ft "github.com/juju/testing/filetesting"
14
"github.com/juju/utils"
15
gc "gopkg.in/check.v1"
16
"gopkg.in/juju/charm.v6-unstable/hooks"
17
"gopkg.in/juju/names.v2"
19
"github.com/juju/juju/api"
20
apiuniter "github.com/juju/juju/api/uniter"
21
jujutesting "github.com/juju/juju/juju/testing"
22
"github.com/juju/juju/network"
23
"github.com/juju/juju/state"
24
coretesting "github.com/juju/juju/testing"
25
"github.com/juju/juju/worker/uniter/hook"
26
"github.com/juju/juju/worker/uniter/relation"
29
type RelationerSuite struct {
30
jujutesting.JujuConnSuite
32
app *state.Application
34
dir *relation.StateDir
38
uniter *apiuniter.State
39
apiRelUnit *apiuniter.RelationUnit
42
var _ = gc.Suite(&RelationerSuite{})
44
func (s *RelationerSuite) SetUpTest(c *gc.C) {
45
s.JujuConnSuite.SetUpTest(c)
47
s.app = s.AddTestingService(c, "u", s.AddTestingCharm(c, "riak"))
48
c.Assert(err, jc.ErrorIsNil)
49
rels, err := s.app.Relations()
50
c.Assert(err, jc.ErrorIsNil)
51
c.Assert(rels, gc.HasLen, 1)
53
_, unit := s.AddRelationUnit(c, "u/0")
55
s.dir, err = relation.ReadStateDir(s.dirPath, s.rel.Id())
56
c.Assert(err, jc.ErrorIsNil)
57
s.hooks = make(chan hook.Info)
59
password, err := utils.RandomPassword()
60
c.Assert(err, jc.ErrorIsNil)
61
err = unit.SetPassword(password)
62
c.Assert(err, jc.ErrorIsNil)
63
s.st = s.OpenAPIAs(c, unit.Tag(), password)
64
s.uniter, err = s.st.Uniter()
65
c.Assert(err, jc.ErrorIsNil)
66
c.Assert(s.uniter, gc.NotNil)
68
apiUnit, err := s.uniter.Unit(unit.Tag().(names.UnitTag))
69
c.Assert(err, jc.ErrorIsNil)
70
apiRel, err := s.uniter.Relation(s.rel.Tag().(names.RelationTag))
71
c.Assert(err, jc.ErrorIsNil)
72
s.apiRelUnit, err = apiRel.Unit(apiUnit)
73
c.Assert(err, jc.ErrorIsNil)
76
func (s *RelationerSuite) AddRelationUnit(c *gc.C, name string) (*state.RelationUnit, *state.Unit) {
77
u, err := s.app.AddUnit()
78
c.Assert(err, jc.ErrorIsNil)
79
c.Assert(u.Name(), gc.Equals, name)
80
machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
81
c.Assert(err, jc.ErrorIsNil)
82
err = u.AssignToMachine(machine)
83
c.Assert(err, jc.ErrorIsNil)
84
privateAddr := network.NewScopedAddress(
85
strings.Replace(name, "/", "-", 1)+".testing.invalid", network.ScopeCloudLocal,
87
err = machine.SetProviderAddresses(privateAddr)
88
c.Assert(err, jc.ErrorIsNil)
89
ru, err := s.rel.Unit(u)
90
c.Assert(err, jc.ErrorIsNil)
94
func (s *RelationerSuite) TestStateDir(c *gc.C) {
95
// Create the relationer; check its state dir is not created.
96
r := relation.NewRelationer(s.apiRelUnit, s.dir)
97
path := strconv.Itoa(s.rel.Id())
98
ft.Removed{path}.Check(c, s.dirPath)
100
// Join the relation; check the dir was created.
102
c.Assert(err, jc.ErrorIsNil)
103
ft.Dir{path, 0755}.Check(c, s.dirPath)
105
// Prepare to depart the relation; check the dir is still there.
106
hi := hook.Info{Kind: hooks.RelationBroken}
107
_, err = r.PrepareHook(hi)
108
c.Assert(err, jc.ErrorIsNil)
109
ft.Dir{path, 0755}.Check(c, s.dirPath)
111
// Actually depart it; check the dir is removed.
112
err = r.CommitHook(hi)
113
c.Assert(err, jc.ErrorIsNil)
114
ft.Removed{path}.Check(c, s.dirPath)
117
func (s *RelationerSuite) TestEnterLeaveScope(c *gc.C) {
118
ru1, _ := s.AddRelationUnit(c, "u/1")
119
r := relation.NewRelationer(s.apiRelUnit, s.dir)
121
// u/1 does not consider u/0 to be alive.
125
ch, ok := <-w.Changes()
126
c.Assert(ok, jc.IsTrue)
127
c.Assert(ch.Changed, gc.HasLen, 0)
128
c.Assert(ch.Departed, gc.HasLen, 0)
130
// u/0 enters scope; u/1 observes it.
132
c.Assert(err, jc.ErrorIsNil)
135
case ch, ok := <-w.Changes():
136
c.Assert(ok, jc.IsTrue)
137
c.Assert(ch.Changed, gc.HasLen, 1)
138
_, found := ch.Changed["u/0"]
139
c.Assert(found, jc.IsTrue)
140
c.Assert(ch.Departed, gc.HasLen, 0)
141
case <-time.After(coretesting.LongWait):
142
c.Fatalf("timed out waiting for presence detection")
147
c.Assert(err, jc.ErrorIsNil)
148
// TODO(jam): This would be a great to replace with statetesting.NotifyWatcherC
151
case ch, ok := <-w.Changes():
152
c.Fatalf("got unexpected change: %#v, %#v", ch, ok)
153
case <-time.After(coretesting.ShortWait):
156
// u/0 leaves scope; u/1 observes it.
157
hi := hook.Info{Kind: hooks.RelationBroken}
158
_, err = r.PrepareHook(hi)
159
c.Assert(err, jc.ErrorIsNil)
161
err = r.CommitHook(hi)
162
c.Assert(err, jc.ErrorIsNil)
165
case ch, ok := <-w.Changes():
166
c.Assert(ok, jc.IsTrue)
167
c.Assert(ch.Changed, gc.HasLen, 0)
168
c.Assert(ch.Departed, gc.DeepEquals, []string{"u/0"})
169
case <-time.After(coretesting.LongWait):
170
c.Fatalf("timed out waiting for absence detection")
174
func (s *RelationerSuite) TestPrepareCommitHooks(c *gc.C) {
175
r := relation.NewRelationer(s.apiRelUnit, s.dir)
177
c.Assert(err, jc.ErrorIsNil)
179
assertMembers := func(expect map[string]int64) {
180
c.Assert(s.dir.State().Members, jc.DeepEquals, expect)
181
expectNames := make([]string, 0, len(expect))
182
for name := range expect {
183
expectNames = append(expectNames, name)
185
c.Assert(r.ContextInfo().MemberNames, jc.SameContents, expectNames)
187
assertMembers(map[string]int64{})
189
// Check preparing an invalid hook changes nothing.
190
changed := hook.Info{
191
Kind: hooks.RelationChanged,
195
_, err = r.PrepareHook(changed)
196
c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`)
197
assertMembers(map[string]int64{})
199
// Check preparing a valid hook updates neither the context nor persistent
202
Kind: hooks.RelationJoined,
205
name, err := r.PrepareHook(joined)
206
c.Assert(err, jc.ErrorIsNil)
207
c.Assert(name, gc.Equals, "ring-relation-joined")
208
assertMembers(map[string]int64{})
210
// Check that preparing the following hook fails as before...
211
_, err = r.PrepareHook(changed)
212
c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`)
213
assertMembers(map[string]int64{})
215
// ...but that committing the previous hook updates the persistent
217
err = r.CommitHook(joined)
218
c.Assert(err, jc.ErrorIsNil)
219
assertMembers(map[string]int64{"u/1": 0})
221
// ...and allows us to prepare the next hook...
222
name, err = r.PrepareHook(changed)
223
c.Assert(err, jc.ErrorIsNil)
224
c.Assert(name, gc.Equals, "ring-relation-changed")
225
assertMembers(map[string]int64{"u/1": 0})
228
err = r.CommitHook(changed)
229
c.Assert(err, jc.ErrorIsNil)
230
assertMembers(map[string]int64{"u/1": 7})
232
// To verify implied behaviour above, prepare a new joined hook with
233
// missing membership information, and check relation context
234
// membership is stil not updated...
235
joined.RemoteUnit = "u/2"
236
joined.ChangeVersion = 3
237
name, err = r.PrepareHook(joined)
238
c.Assert(err, jc.ErrorIsNil)
239
c.Assert(name, gc.Equals, "ring-relation-joined")
240
assertMembers(map[string]int64{"u/1": 7})
242
// ...until commit, at which point so is relation state.
243
err = r.CommitHook(joined)
244
c.Assert(err, jc.ErrorIsNil)
245
assertMembers(map[string]int64{"u/1": 7, "u/2": 3})
248
func (s *RelationerSuite) TestSetDying(c *gc.C) {
249
ru1, u := s.AddRelationUnit(c, "u/1")
250
settings := map[string]interface{}{"unit": "settings"}
251
err := ru1.EnterScope(settings)
252
c.Assert(err, jc.ErrorIsNil)
253
r := relation.NewRelationer(s.apiRelUnit, s.dir)
255
c.Assert(err, jc.ErrorIsNil)
257
// Change Life to Dying check the results.
259
c.Assert(err, jc.ErrorIsNil)
261
// Check that we cannot rejoin the relation.
262
f := func() { r.Join() }
263
c.Assert(f, gc.PanicMatches, "dying relationer must not join!")
265
// Simulate a RelationBroken hook.
266
err = r.CommitHook(hook.Info{Kind: hooks.RelationBroken})
267
c.Assert(err, jc.ErrorIsNil)
269
// Check that the relation state has been broken.
270
err = s.dir.State().Validate(hook.Info{Kind: hooks.RelationBroken})
271
c.Assert(err, gc.ErrorMatches, ".*: relation is broken and cannot be changed further")
273
// Check that it left scope, by leaving scope on the other side and destroying
275
err = ru1.LeaveScope()
276
c.Assert(err, jc.ErrorIsNil)
278
c.Assert(err, jc.ErrorIsNil)
280
c.Assert(err, jc.Satisfies, errors.IsNotFound)
283
type stopper interface {
287
func stop(c *gc.C, s stopper) {
288
c.Assert(s.Stop(), gc.IsNil)
291
type RelationerImplicitSuite struct {
292
jujutesting.JujuConnSuite
295
var _ = gc.Suite(&RelationerImplicitSuite{})
297
func (s *RelationerImplicitSuite) TestImplicitRelationer(c *gc.C) {
298
// Create a relationer for an implicit endpoint (mysql:juju-info).
299
mysql := s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql"))
300
u, err := mysql.AddUnit()
301
c.Assert(err, jc.ErrorIsNil)
302
machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
303
c.Assert(err, jc.ErrorIsNil)
304
err = u.AssignToMachine(machine)
305
c.Assert(err, jc.ErrorIsNil)
306
err = machine.SetProviderAddresses(network.NewScopedAddress("blah", network.ScopeCloudLocal))
307
c.Assert(err, jc.ErrorIsNil)
308
s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
309
eps, err := s.State.InferEndpoints("logging", "mysql")
310
c.Assert(err, jc.ErrorIsNil)
311
rel, err := s.State.AddRelation(eps...)
312
c.Assert(err, jc.ErrorIsNil)
314
dir, err := relation.ReadStateDir(relsDir, rel.Id())
315
c.Assert(err, jc.ErrorIsNil)
317
password, err := utils.RandomPassword()
318
c.Assert(err, jc.ErrorIsNil)
319
err = u.SetPassword(password)
320
c.Assert(err, jc.ErrorIsNil)
321
st := s.OpenAPIAs(c, u.Tag(), password)
322
uniterState, err := st.Uniter()
323
c.Assert(err, jc.ErrorIsNil)
324
c.Assert(uniterState, gc.NotNil)
326
apiUnit, err := uniterState.Unit(u.Tag().(names.UnitTag))
327
c.Assert(err, jc.ErrorIsNil)
328
apiRel, err := uniterState.Relation(rel.Tag().(names.RelationTag))
329
c.Assert(err, jc.ErrorIsNil)
330
apiRelUnit, err := apiRel.Unit(apiUnit)
331
c.Assert(err, jc.ErrorIsNil)
333
r := relation.NewRelationer(apiRelUnit, dir)
334
c.Assert(r, jc.Satisfies, (*relation.Relationer).IsImplicit)
336
// Hooks are not allowed.
337
f := func() { r.PrepareHook(hook.Info{}) }
338
c.Assert(f, gc.PanicMatches, "implicit relations must not run hooks")
339
f = func() { r.CommitHook(hook.Info{}) }
340
c.Assert(f, gc.PanicMatches, "implicit relations must not run hooks")
342
// Set it to Dying; check that the dir is removed immediately.
344
c.Assert(err, jc.ErrorIsNil)
345
path := strconv.Itoa(rel.Id())
346
ft.Removed{path}.Check(c, relsDir)
349
c.Assert(err, jc.ErrorIsNil)
351
c.Assert(err, jc.Satisfies, errors.IsNotFound)