func (s *RelationerSuite) TestEnterLeaveScope(c *gc.C) { ru1, _ := s.AddRelationUnit(c, "u/1") r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks) // u/1 does not consider u/0 to be alive. w := ru1.Watch() defer stop(c, w) s.State.StartSync() ch, ok := <-w.Changes() c.Assert(ok, gc.Equals, true) c.Assert(ch.Changed, gc.HasLen, 0) c.Assert(ch.Departed, gc.HasLen, 0) // u/0 enters scope; u/1 observes it. err := r.Join() c.Assert(err, gc.IsNil) s.State.StartSync() select { case ch, ok := <-w.Changes(): c.Assert(ok, gc.Equals, true) c.Assert(ch.Changed, gc.HasLen, 1) _, found := ch.Changed["u/0"] c.Assert(found, gc.Equals, true) c.Assert(ch.Departed, gc.HasLen, 0) case <-time.After(coretesting.LongWait): c.Fatalf("timed out waiting for presence detection") } // re-Join is no-op. err = r.Join() c.Assert(err, gc.IsNil) // TODO(jam): This would be a great to replace with statetesting.NotifyWatcherC s.State.StartSync() select { case ch, ok := <-w.Changes(): c.Fatalf("got unexpected change: %#v, %#v", ch, ok) case <-time.After(coretesting.ShortWait): } // u/0 leaves scope; u/1 observes it. hi := hook.Info{Kind: hooks.RelationBroken} _, err = r.PrepareHook(hi) c.Assert(err, gc.IsNil) err = r.CommitHook(hi) c.Assert(err, gc.IsNil) s.State.StartSync() select { case ch, ok := <-w.Changes(): c.Assert(ok, gc.Equals, true) c.Assert(ch.Changed, gc.HasLen, 0) c.Assert(ch.Departed, gc.DeepEquals, []string{"u/0"}) case <-time.After(worstCase): c.Fatalf("timed out waiting for absence detection") } }
func (s *RelationerSuite) TestStateDir(c *gc.C) { // Create the relationer; check its state dir is not created. r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks) path := strconv.Itoa(s.rel.Id()) ft.Removed{path}.Check(c, s.dirPath) // Join the relation; check the dir was created. err := r.Join() c.Assert(err, gc.IsNil) ft.Dir{path, 0755}.Check(c, s.dirPath) // Prepare to depart the relation; check the dir is still there. hi := hook.Info{Kind: hooks.RelationBroken} _, err = r.PrepareHook(hi) c.Assert(err, gc.IsNil) ft.Dir{path, 0755}.Check(c, s.dirPath) // Actually depart it; check the dir is removed. err = r.CommitHook(hi) c.Assert(err, gc.IsNil) ft.Removed{path}.Check(c, s.dirPath) }
func (s *RelationerSuite) TestSetDying(c *gc.C) { ru1, _ := s.AddRelationUnit(c, "u/1") settings := map[string]interface{}{"unit": "settings"} err := ru1.EnterScope(settings) c.Assert(err, gc.IsNil) r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks) err = r.Join() c.Assert(err, gc.IsNil) r.StartHooks() defer stopHooks(c, r) s.assertHook(c, hook.Info{ Kind: hooks.RelationJoined, RemoteUnit: "u/1", }) // While a changed hook is still pending, the relation (or possibly the unit, // pending lifecycle work), changes Life to Dying, and the relationer is // informed. err = r.SetDying() c.Assert(err, gc.IsNil) // Check that we cannot rejoin the relation. f := func() { r.Join() } c.Assert(f, gc.PanicMatches, "dying relationer must not join!") // ...but the hook stream continues, sending the required changed hook for // u/1 before moving on to a departed, despite the fact that its pinger is // still running, and closing with a broken. s.assertHook(c, hook.Info{Kind: hooks.RelationChanged, RemoteUnit: "u/1"}) s.assertHook(c, hook.Info{Kind: hooks.RelationDeparted, RemoteUnit: "u/1"}) s.assertHook(c, hook.Info{Kind: hooks.RelationBroken}) // Check that the relation state has been broken. err = s.dir.State().Validate(hook.Info{Kind: hooks.RelationBroken}) c.Assert(err, gc.ErrorMatches, ".*: relation is broken and cannot be changed further") }
func (s *RelationerImplicitSuite) TestImplicitRelationer(c *gc.C) { // Create a relationer for an implicit endpoint (mysql:juju-info). mysql := s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql")) u, err := mysql.AddUnit() c.Assert(err, gc.IsNil) machine, err := s.State.AddMachine("quantal", state.JobHostUnits) c.Assert(err, gc.IsNil) err = u.AssignToMachine(machine) c.Assert(err, gc.IsNil) err = machine.SetAddresses(network.NewAddress("blah", network.ScopeCloudLocal)) c.Assert(err, gc.IsNil) logging := s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) eps, err := s.State.InferEndpoints([]string{"logging", "mysql"}) c.Assert(err, gc.IsNil) rel, err := s.State.AddRelation(eps...) c.Assert(err, gc.IsNil) relsDir := c.MkDir() dir, err := relation.ReadStateDir(relsDir, rel.Id()) c.Assert(err, gc.IsNil) hooks := make(chan hook.Info) password, err := utils.RandomPassword() c.Assert(err, gc.IsNil) err = u.SetPassword(password) c.Assert(err, gc.IsNil) st := s.OpenAPIAs(c, u.Tag(), password) uniterState := st.Uniter() c.Assert(uniterState, gc.NotNil) apiUnit, err := uniterState.Unit(u.Tag()) c.Assert(err, gc.IsNil) apiRel, err := uniterState.Relation(rel.Tag()) c.Assert(err, gc.IsNil) apiRelUnit, err := apiRel.Unit(apiUnit) c.Assert(err, gc.IsNil) r := uniter.NewRelationer(apiRelUnit, dir, hooks) c.Assert(r, jc.Satisfies, (*uniter.Relationer).IsImplicit) // Join the relation. err = r.Join() c.Assert(err, gc.IsNil) sub, err := logging.Unit("logging/0") c.Assert(err, gc.IsNil) // Join the other side; check no hooks are sent. r.StartHooks() defer func() { c.Assert(r.StopHooks(), gc.IsNil) }() subru, err := rel.Unit(sub) c.Assert(err, gc.IsNil) err = subru.EnterScope(map[string]interface{}{"some": "data"}) c.Assert(err, gc.IsNil) s.State.StartSync() select { case <-time.After(coretesting.ShortWait): case <-hooks: c.Fatalf("unexpected hook generated") } // Set it to Dying; check that the dir is removed immediately. err = r.SetDying() c.Assert(err, gc.IsNil) path := strconv.Itoa(rel.Id()) ft.Removed{path}.Check(c, relsDir) // Check that it left scope, by leaving scope on the other side and destroying // the relation. err = subru.LeaveScope() c.Assert(err, gc.IsNil) err = rel.Destroy() c.Assert(err, gc.IsNil) err = rel.Refresh() c.Assert(err, jc.Satisfies, errors.IsNotFound) // Verify that no other hooks were sent at any stage. select { case <-hooks: c.Fatalf("unexpected hook generated") default: } }
func (s *RelationerSuite) TestPrepareCommitHooks(c *gc.C) { r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks) err := r.Join() c.Assert(err, gc.IsNil) ctx := r.Context() c.Assert(ctx.UnitNames(), gc.HasLen, 0) // Check preparing an invalid hook changes nothing. changed := hook.Info{ Kind: hooks.RelationChanged, RemoteUnit: "u/1", ChangeVersion: 7, } _, err = r.PrepareHook(changed) c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`) c.Assert(ctx.UnitNames(), gc.HasLen, 0) c.Assert(s.dir.State().Members, gc.HasLen, 0) // Check preparing a valid hook updates the context, but not persistent // relation state. joined := hook.Info{ Kind: hooks.RelationJoined, RemoteUnit: "u/1", } name, err := r.PrepareHook(joined) c.Assert(err, gc.IsNil) c.Assert(s.dir.State().Members, gc.HasLen, 0) c.Assert(name, gc.Equals, "ring-relation-joined") c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1"}) // Check that preparing the following hook fails as before... _, err = r.PrepareHook(changed) c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`) c.Assert(s.dir.State().Members, gc.HasLen, 0) c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1"}) // ...but that committing the previous hook updates the persistent // relation state... err = r.CommitHook(joined) c.Assert(err, gc.IsNil) c.Assert(s.dir.State().Members, gc.DeepEquals, map[string]int64{"u/1": 0}) c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1"}) // ...and allows us to prepare the next hook... name, err = r.PrepareHook(changed) c.Assert(err, gc.IsNil) c.Assert(name, gc.Equals, "ring-relation-changed") c.Assert(s.dir.State().Members, gc.DeepEquals, map[string]int64{"u/1": 0}) c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1"}) // ...and commit it. err = r.CommitHook(changed) c.Assert(err, gc.IsNil) c.Assert(s.dir.State().Members, gc.DeepEquals, map[string]int64{"u/1": 7}) c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1"}) // To verify implied behaviour above, prepare a new joined hook with // missing membership information, and check relation context // membership is updated appropriately... joined.RemoteUnit = "u/2" joined.ChangeVersion = 3 name, err = r.PrepareHook(joined) c.Assert(err, gc.IsNil) c.Assert(s.dir.State().Members, gc.HasLen, 1) c.Assert(name, gc.Equals, "ring-relation-joined") c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1", "u/2"}) // ...and so is relation state on commit. err = r.CommitHook(joined) c.Assert(err, gc.IsNil) c.Assert(s.dir.State().Members, gc.DeepEquals, map[string]int64{"u/1": 7, "u/2": 3}) c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1", "u/2"}) }
func (s *RelationerSuite) TestStartStopHooks(c *gc.C) { ru1, _ := s.AddRelationUnit(c, "u/1") ru2, _ := s.AddRelationUnit(c, "u/2") r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks) c.Assert(r.IsImplicit(), gc.Equals, false) err := r.Join() c.Assert(err, gc.IsNil) // Check no hooks are being sent. s.assertNoHook(c) // Start hooks, and check that still no changes are sent. r.StartHooks() defer stopHooks(c, r) s.assertNoHook(c) // Check we can't start hooks again. f := func() { r.StartHooks() } c.Assert(f, gc.PanicMatches, "hooks already started!") // Join u/1 to the relation, and check that we receive the expected hooks. settings := map[string]interface{}{"unit": "settings"} err = ru1.EnterScope(settings) c.Assert(err, gc.IsNil) s.assertHook(c, hook.Info{ Kind: hooks.RelationJoined, RemoteUnit: "u/1", }) s.assertHook(c, hook.Info{ Kind: hooks.RelationChanged, RemoteUnit: "u/1", }) s.assertNoHook(c) // Stop hooks, make more changes, check no events. err = r.StopHooks() c.Assert(err, gc.IsNil) err = ru1.LeaveScope() c.Assert(err, gc.IsNil) err = ru2.EnterScope(nil) c.Assert(err, gc.IsNil) node, err := ru2.Settings() c.Assert(err, gc.IsNil) node.Set("private-address", "roehampton") _, err = node.Write() c.Assert(err, gc.IsNil) s.assertNoHook(c) // Stop hooks again to verify safety. err = r.StopHooks() c.Assert(err, gc.IsNil) s.assertNoHook(c) // Start them again, and check we get the expected events sent. r.StartHooks() defer stopHooks(c, r) s.assertHook(c, hook.Info{ Kind: hooks.RelationDeparted, RemoteUnit: "u/1", }) s.assertHook(c, hook.Info{ Kind: hooks.RelationJoined, ChangeVersion: 1, RemoteUnit: "u/2", }) s.assertHook(c, hook.Info{ Kind: hooks.RelationChanged, ChangeVersion: 1, RemoteUnit: "u/2", }) s.assertNoHook(c) // Stop them again, just to be sure. err = r.StopHooks() c.Assert(err, gc.IsNil) s.assertNoHook(c) }
func (s *RelationerSuite) TestPrepareCommitHooks(c *gc.C) { r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks) err := r.Join() c.Assert(err, jc.ErrorIsNil) assertMembers := func(expect map[string]int64) { c.Assert(s.dir.State().Members, jc.DeepEquals, expect) expectNames := make([]string, 0, len(expect)) for name := range expect { expectNames = append(expectNames, name) } c.Assert(r.ContextInfo().MemberNames, jc.SameContents, expectNames) } assertMembers(map[string]int64{}) // Check preparing an invalid hook changes nothing. changed := hook.Info{ Kind: hooks.RelationChanged, RemoteUnit: "u/1", ChangeVersion: 7, } _, err = r.PrepareHook(changed) c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`) assertMembers(map[string]int64{}) // Check preparing a valid hook updates neither the context nor persistent // relation state. joined := hook.Info{ Kind: hooks.RelationJoined, RemoteUnit: "u/1", } name, err := r.PrepareHook(joined) c.Assert(err, jc.ErrorIsNil) c.Assert(name, gc.Equals, "ring-relation-joined") assertMembers(map[string]int64{}) // Check that preparing the following hook fails as before... _, err = r.PrepareHook(changed) c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`) assertMembers(map[string]int64{}) // ...but that committing the previous hook updates the persistent // relation state... err = r.CommitHook(joined) c.Assert(err, jc.ErrorIsNil) assertMembers(map[string]int64{"u/1": 0}) // ...and allows us to prepare the next hook... name, err = r.PrepareHook(changed) c.Assert(err, jc.ErrorIsNil) c.Assert(name, gc.Equals, "ring-relation-changed") assertMembers(map[string]int64{"u/1": 0}) // ...and commit it. err = r.CommitHook(changed) c.Assert(err, jc.ErrorIsNil) assertMembers(map[string]int64{"u/1": 7}) // To verify implied behaviour above, prepare a new joined hook with // missing membership information, and check relation context // membership is stil not updated... joined.RemoteUnit = "u/2" joined.ChangeVersion = 3 name, err = r.PrepareHook(joined) c.Assert(err, jc.ErrorIsNil) c.Assert(name, gc.Equals, "ring-relation-joined") assertMembers(map[string]int64{"u/1": 7}) // ...until commit, at which point so is relation state. err = r.CommitHook(joined) c.Assert(err, jc.ErrorIsNil) assertMembers(map[string]int64{"u/1": 7, "u/2": 3}) }