github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/worker/uniter/relationer_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter_test 5 6 import ( 7 "strconv" 8 "strings" 9 "time" 10 11 "github.com/juju/charm/hooks" 12 "github.com/juju/errors" 13 jc "github.com/juju/testing/checkers" 14 ft "github.com/juju/testing/filetesting" 15 "github.com/juju/utils" 16 gc "launchpad.net/gocheck" 17 18 jujutesting "github.com/juju/juju/juju/testing" 19 "github.com/juju/juju/network" 20 "github.com/juju/juju/state" 21 "github.com/juju/juju/state/api" 22 apiuniter "github.com/juju/juju/state/api/uniter" 23 coretesting "github.com/juju/juju/testing" 24 "github.com/juju/juju/worker/uniter" 25 "github.com/juju/juju/worker/uniter/hook" 26 "github.com/juju/juju/worker/uniter/relation" 27 ) 28 29 type RelationerSuite struct { 30 jujutesting.JujuConnSuite 31 hooks chan hook.Info 32 svc *state.Service 33 rel *state.Relation 34 dir *relation.StateDir 35 dirPath string 36 37 st *api.State 38 uniter *apiuniter.State 39 apiRelUnit *apiuniter.RelationUnit 40 } 41 42 var _ = gc.Suite(&RelationerSuite{}) 43 44 func (s *RelationerSuite) SetUpTest(c *gc.C) { 45 s.JujuConnSuite.SetUpTest(c) 46 var err error 47 s.svc = s.AddTestingService(c, "u", s.AddTestingCharm(c, "riak")) 48 c.Assert(err, gc.IsNil) 49 rels, err := s.svc.Relations() 50 c.Assert(err, gc.IsNil) 51 c.Assert(rels, gc.HasLen, 1) 52 s.rel = rels[0] 53 _, unit := s.AddRelationUnit(c, "u/0") 54 s.dirPath = c.MkDir() 55 s.dir, err = relation.ReadStateDir(s.dirPath, s.rel.Id()) 56 c.Assert(err, gc.IsNil) 57 s.hooks = make(chan hook.Info) 58 59 password, err := utils.RandomPassword() 60 c.Assert(err, gc.IsNil) 61 err = unit.SetPassword(password) 62 c.Assert(err, gc.IsNil) 63 s.st = s.OpenAPIAs(c, unit.Tag(), password) 64 s.uniter = s.st.Uniter() 65 c.Assert(s.uniter, gc.NotNil) 66 67 apiUnit, err := s.uniter.Unit(unit.Tag()) 68 c.Assert(err, gc.IsNil) 69 apiRel, err := s.uniter.Relation(s.rel.Tag()) 70 c.Assert(err, gc.IsNil) 71 s.apiRelUnit, err = apiRel.Unit(apiUnit) 72 c.Assert(err, gc.IsNil) 73 } 74 75 func (s *RelationerSuite) AddRelationUnit(c *gc.C, name string) (*state.RelationUnit, *state.Unit) { 76 u, err := s.svc.AddUnit() 77 c.Assert(err, gc.IsNil) 78 c.Assert(u.Name(), gc.Equals, name) 79 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 80 c.Assert(err, gc.IsNil) 81 err = u.AssignToMachine(machine) 82 c.Assert(err, gc.IsNil) 83 privateAddr := network.NewAddress( 84 strings.Replace(name, "/", "-", 1)+".testing.invalid", network.ScopeCloudLocal) 85 err = machine.SetAddresses(privateAddr) 86 c.Assert(err, gc.IsNil) 87 ru, err := s.rel.Unit(u) 88 c.Assert(err, gc.IsNil) 89 return ru, u 90 } 91 92 func (s *RelationerSuite) TestStateDir(c *gc.C) { 93 // Create the relationer; check its state dir is not created. 94 r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks) 95 path := strconv.Itoa(s.rel.Id()) 96 ft.Removed{path}.Check(c, s.dirPath) 97 98 // Join the relation; check the dir was created. 99 err := r.Join() 100 c.Assert(err, gc.IsNil) 101 ft.Dir{path, 0755}.Check(c, s.dirPath) 102 103 // Prepare to depart the relation; check the dir is still there. 104 hi := hook.Info{Kind: hooks.RelationBroken} 105 _, err = r.PrepareHook(hi) 106 c.Assert(err, gc.IsNil) 107 ft.Dir{path, 0755}.Check(c, s.dirPath) 108 109 // Actually depart it; check the dir is removed. 110 err = r.CommitHook(hi) 111 c.Assert(err, gc.IsNil) 112 ft.Removed{path}.Check(c, s.dirPath) 113 } 114 115 func (s *RelationerSuite) TestEnterLeaveScope(c *gc.C) { 116 ru1, _ := s.AddRelationUnit(c, "u/1") 117 r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks) 118 119 // u/1 does not consider u/0 to be alive. 120 w := ru1.Watch() 121 defer stop(c, w) 122 s.State.StartSync() 123 ch, ok := <-w.Changes() 124 c.Assert(ok, gc.Equals, true) 125 c.Assert(ch.Changed, gc.HasLen, 0) 126 c.Assert(ch.Departed, gc.HasLen, 0) 127 128 // u/0 enters scope; u/1 observes it. 129 err := r.Join() 130 c.Assert(err, gc.IsNil) 131 s.State.StartSync() 132 select { 133 case ch, ok := <-w.Changes(): 134 c.Assert(ok, gc.Equals, true) 135 c.Assert(ch.Changed, gc.HasLen, 1) 136 _, found := ch.Changed["u/0"] 137 c.Assert(found, gc.Equals, true) 138 c.Assert(ch.Departed, gc.HasLen, 0) 139 case <-time.After(coretesting.LongWait): 140 c.Fatalf("timed out waiting for presence detection") 141 } 142 143 // re-Join is no-op. 144 err = r.Join() 145 c.Assert(err, gc.IsNil) 146 // TODO(jam): This would be a great to replace with statetesting.NotifyWatcherC 147 s.State.StartSync() 148 select { 149 case ch, ok := <-w.Changes(): 150 c.Fatalf("got unexpected change: %#v, %#v", ch, ok) 151 case <-time.After(coretesting.ShortWait): 152 } 153 154 // u/0 leaves scope; u/1 observes it. 155 hi := hook.Info{Kind: hooks.RelationBroken} 156 _, err = r.PrepareHook(hi) 157 c.Assert(err, gc.IsNil) 158 159 err = r.CommitHook(hi) 160 c.Assert(err, gc.IsNil) 161 s.State.StartSync() 162 select { 163 case ch, ok := <-w.Changes(): 164 c.Assert(ok, gc.Equals, true) 165 c.Assert(ch.Changed, gc.HasLen, 0) 166 c.Assert(ch.Departed, gc.DeepEquals, []string{"u/0"}) 167 case <-time.After(worstCase): 168 c.Fatalf("timed out waiting for absence detection") 169 } 170 } 171 172 func (s *RelationerSuite) TestStartStopHooks(c *gc.C) { 173 ru1, _ := s.AddRelationUnit(c, "u/1") 174 ru2, _ := s.AddRelationUnit(c, "u/2") 175 r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks) 176 c.Assert(r.IsImplicit(), gc.Equals, false) 177 err := r.Join() 178 c.Assert(err, gc.IsNil) 179 180 // Check no hooks are being sent. 181 s.assertNoHook(c) 182 183 // Start hooks, and check that still no changes are sent. 184 r.StartHooks() 185 defer stopHooks(c, r) 186 s.assertNoHook(c) 187 188 // Check we can't start hooks again. 189 f := func() { r.StartHooks() } 190 c.Assert(f, gc.PanicMatches, "hooks already started!") 191 192 // Join u/1 to the relation, and check that we receive the expected hooks. 193 settings := map[string]interface{}{"unit": "settings"} 194 err = ru1.EnterScope(settings) 195 c.Assert(err, gc.IsNil) 196 s.assertHook(c, hook.Info{ 197 Kind: hooks.RelationJoined, 198 RemoteUnit: "u/1", 199 }) 200 s.assertHook(c, hook.Info{ 201 Kind: hooks.RelationChanged, 202 RemoteUnit: "u/1", 203 }) 204 s.assertNoHook(c) 205 206 // Stop hooks, make more changes, check no events. 207 err = r.StopHooks() 208 c.Assert(err, gc.IsNil) 209 err = ru1.LeaveScope() 210 c.Assert(err, gc.IsNil) 211 err = ru2.EnterScope(nil) 212 c.Assert(err, gc.IsNil) 213 node, err := ru2.Settings() 214 c.Assert(err, gc.IsNil) 215 node.Set("private-address", "roehampton") 216 _, err = node.Write() 217 c.Assert(err, gc.IsNil) 218 s.assertNoHook(c) 219 220 // Stop hooks again to verify safety. 221 err = r.StopHooks() 222 c.Assert(err, gc.IsNil) 223 s.assertNoHook(c) 224 225 // Start them again, and check we get the expected events sent. 226 r.StartHooks() 227 defer stopHooks(c, r) 228 s.assertHook(c, hook.Info{ 229 Kind: hooks.RelationDeparted, 230 RemoteUnit: "u/1", 231 }) 232 s.assertHook(c, hook.Info{ 233 Kind: hooks.RelationJoined, 234 ChangeVersion: 1, 235 RemoteUnit: "u/2", 236 }) 237 s.assertHook(c, hook.Info{ 238 Kind: hooks.RelationChanged, 239 ChangeVersion: 1, 240 RemoteUnit: "u/2", 241 }) 242 s.assertNoHook(c) 243 244 // Stop them again, just to be sure. 245 err = r.StopHooks() 246 c.Assert(err, gc.IsNil) 247 s.assertNoHook(c) 248 } 249 250 func (s *RelationerSuite) TestPrepareCommitHooks(c *gc.C) { 251 r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks) 252 err := r.Join() 253 c.Assert(err, gc.IsNil) 254 ctx := r.Context() 255 c.Assert(ctx.UnitNames(), gc.HasLen, 0) 256 257 // Check preparing an invalid hook changes nothing. 258 changed := hook.Info{ 259 Kind: hooks.RelationChanged, 260 RemoteUnit: "u/1", 261 ChangeVersion: 7, 262 } 263 _, err = r.PrepareHook(changed) 264 c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`) 265 c.Assert(ctx.UnitNames(), gc.HasLen, 0) 266 c.Assert(s.dir.State().Members, gc.HasLen, 0) 267 268 // Check preparing a valid hook updates the context, but not persistent 269 // relation state. 270 joined := hook.Info{ 271 Kind: hooks.RelationJoined, 272 RemoteUnit: "u/1", 273 } 274 name, err := r.PrepareHook(joined) 275 c.Assert(err, gc.IsNil) 276 c.Assert(s.dir.State().Members, gc.HasLen, 0) 277 c.Assert(name, gc.Equals, "ring-relation-joined") 278 c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1"}) 279 280 // Check that preparing the following hook fails as before... 281 _, err = r.PrepareHook(changed) 282 c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`) 283 c.Assert(s.dir.State().Members, gc.HasLen, 0) 284 c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1"}) 285 286 // ...but that committing the previous hook updates the persistent 287 // relation state... 288 err = r.CommitHook(joined) 289 c.Assert(err, gc.IsNil) 290 c.Assert(s.dir.State().Members, gc.DeepEquals, map[string]int64{"u/1": 0}) 291 c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1"}) 292 293 // ...and allows us to prepare the next hook... 294 name, err = r.PrepareHook(changed) 295 c.Assert(err, gc.IsNil) 296 c.Assert(name, gc.Equals, "ring-relation-changed") 297 c.Assert(s.dir.State().Members, gc.DeepEquals, map[string]int64{"u/1": 0}) 298 c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1"}) 299 300 // ...and commit it. 301 err = r.CommitHook(changed) 302 c.Assert(err, gc.IsNil) 303 c.Assert(s.dir.State().Members, gc.DeepEquals, map[string]int64{"u/1": 7}) 304 c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1"}) 305 306 // To verify implied behaviour above, prepare a new joined hook with 307 // missing membership information, and check relation context 308 // membership is updated appropriately... 309 joined.RemoteUnit = "u/2" 310 joined.ChangeVersion = 3 311 name, err = r.PrepareHook(joined) 312 c.Assert(err, gc.IsNil) 313 c.Assert(s.dir.State().Members, gc.HasLen, 1) 314 c.Assert(name, gc.Equals, "ring-relation-joined") 315 c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1", "u/2"}) 316 317 // ...and so is relation state on commit. 318 err = r.CommitHook(joined) 319 c.Assert(err, gc.IsNil) 320 c.Assert(s.dir.State().Members, gc.DeepEquals, map[string]int64{"u/1": 7, "u/2": 3}) 321 c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1", "u/2"}) 322 } 323 324 func (s *RelationerSuite) TestSetDying(c *gc.C) { 325 ru1, _ := s.AddRelationUnit(c, "u/1") 326 settings := map[string]interface{}{"unit": "settings"} 327 err := ru1.EnterScope(settings) 328 c.Assert(err, gc.IsNil) 329 r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks) 330 err = r.Join() 331 c.Assert(err, gc.IsNil) 332 r.StartHooks() 333 defer stopHooks(c, r) 334 s.assertHook(c, hook.Info{ 335 Kind: hooks.RelationJoined, 336 RemoteUnit: "u/1", 337 }) 338 339 // While a changed hook is still pending, the relation (or possibly the unit, 340 // pending lifecycle work), changes Life to Dying, and the relationer is 341 // informed. 342 err = r.SetDying() 343 c.Assert(err, gc.IsNil) 344 345 // Check that we cannot rejoin the relation. 346 f := func() { r.Join() } 347 c.Assert(f, gc.PanicMatches, "dying relationer must not join!") 348 349 // ...but the hook stream continues, sending the required changed hook for 350 // u/1 before moving on to a departed, despite the fact that its pinger is 351 // still running, and closing with a broken. 352 s.assertHook(c, hook.Info{Kind: hooks.RelationChanged, RemoteUnit: "u/1"}) 353 s.assertHook(c, hook.Info{Kind: hooks.RelationDeparted, RemoteUnit: "u/1"}) 354 s.assertHook(c, hook.Info{Kind: hooks.RelationBroken}) 355 356 // Check that the relation state has been broken. 357 err = s.dir.State().Validate(hook.Info{Kind: hooks.RelationBroken}) 358 c.Assert(err, gc.ErrorMatches, ".*: relation is broken and cannot be changed further") 359 } 360 361 func (s *RelationerSuite) assertNoHook(c *gc.C) { 362 s.BackingState.StartSync() 363 select { 364 case hi, ok := <-s.hooks: 365 c.Fatalf("got unexpected hook info %#v (%t)", hi, ok) 366 case <-time.After(coretesting.ShortWait): 367 } 368 } 369 370 func (s *RelationerSuite) assertHook(c *gc.C, expect hook.Info) { 371 s.BackingState.StartSync() 372 // We must ensure the local state dir exists first. 373 c.Assert(s.dir.Ensure(), gc.IsNil) 374 select { 375 case hi, ok := <-s.hooks: 376 c.Assert(ok, gc.Equals, true) 377 expect.ChangeVersion = hi.ChangeVersion 378 c.Assert(hi, gc.DeepEquals, expect) 379 c.Assert(s.dir.Write(hi), gc.Equals, nil) 380 case <-time.After(coretesting.LongWait): 381 c.Fatalf("timed out waiting for %#v", expect) 382 } 383 } 384 385 type stopper interface { 386 Stop() error 387 } 388 389 func stop(c *gc.C, s stopper) { 390 c.Assert(s.Stop(), gc.IsNil) 391 } 392 393 func stopHooks(c *gc.C, r *uniter.Relationer) { 394 c.Assert(r.StopHooks(), gc.IsNil) 395 } 396 397 type RelationerImplicitSuite struct { 398 jujutesting.JujuConnSuite 399 } 400 401 var _ = gc.Suite(&RelationerImplicitSuite{}) 402 403 func (s *RelationerImplicitSuite) TestImplicitRelationer(c *gc.C) { 404 // Create a relationer for an implicit endpoint (mysql:juju-info). 405 mysql := s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql")) 406 u, err := mysql.AddUnit() 407 c.Assert(err, gc.IsNil) 408 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 409 c.Assert(err, gc.IsNil) 410 err = u.AssignToMachine(machine) 411 c.Assert(err, gc.IsNil) 412 err = machine.SetAddresses(network.NewAddress("blah", network.ScopeCloudLocal)) 413 c.Assert(err, gc.IsNil) 414 logging := s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 415 eps, err := s.State.InferEndpoints([]string{"logging", "mysql"}) 416 c.Assert(err, gc.IsNil) 417 rel, err := s.State.AddRelation(eps...) 418 c.Assert(err, gc.IsNil) 419 relsDir := c.MkDir() 420 dir, err := relation.ReadStateDir(relsDir, rel.Id()) 421 c.Assert(err, gc.IsNil) 422 hooks := make(chan hook.Info) 423 424 password, err := utils.RandomPassword() 425 c.Assert(err, gc.IsNil) 426 err = u.SetPassword(password) 427 c.Assert(err, gc.IsNil) 428 st := s.OpenAPIAs(c, u.Tag(), password) 429 uniterState := st.Uniter() 430 c.Assert(uniterState, gc.NotNil) 431 432 apiUnit, err := uniterState.Unit(u.Tag()) 433 c.Assert(err, gc.IsNil) 434 apiRel, err := uniterState.Relation(rel.Tag()) 435 c.Assert(err, gc.IsNil) 436 apiRelUnit, err := apiRel.Unit(apiUnit) 437 c.Assert(err, gc.IsNil) 438 439 r := uniter.NewRelationer(apiRelUnit, dir, hooks) 440 c.Assert(r, jc.Satisfies, (*uniter.Relationer).IsImplicit) 441 442 // Join the relation. 443 err = r.Join() 444 c.Assert(err, gc.IsNil) 445 sub, err := logging.Unit("logging/0") 446 c.Assert(err, gc.IsNil) 447 448 // Join the other side; check no hooks are sent. 449 r.StartHooks() 450 defer func() { c.Assert(r.StopHooks(), gc.IsNil) }() 451 subru, err := rel.Unit(sub) 452 c.Assert(err, gc.IsNil) 453 err = subru.EnterScope(map[string]interface{}{"some": "data"}) 454 c.Assert(err, gc.IsNil) 455 s.State.StartSync() 456 select { 457 case <-time.After(coretesting.ShortWait): 458 case <-hooks: 459 c.Fatalf("unexpected hook generated") 460 } 461 462 // Set it to Dying; check that the dir is removed immediately. 463 err = r.SetDying() 464 c.Assert(err, gc.IsNil) 465 path := strconv.Itoa(rel.Id()) 466 ft.Removed{path}.Check(c, relsDir) 467 468 // Check that it left scope, by leaving scope on the other side and destroying 469 // the relation. 470 err = subru.LeaveScope() 471 c.Assert(err, gc.IsNil) 472 err = rel.Destroy() 473 c.Assert(err, gc.IsNil) 474 err = rel.Refresh() 475 c.Assert(err, jc.Satisfies, errors.IsNotFound) 476 477 // Verify that no other hooks were sent at any stage. 478 select { 479 case <-hooks: 480 c.Fatalf("unexpected hook generated") 481 default: 482 } 483 }