func (s *RelationerSuite) TestEnterLeaveScope(c *C) { ru1 := s.AddRelationUnit(c, "u/1") r := uniter.NewRelationer(s.ru, 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, Equals, true) c.Assert(ch.Joined, HasLen, 0) c.Assert(ch.Changed, HasLen, 0) c.Assert(ch.Departed, HasLen, 0) // u/0 enters scope; u/1 observes it. err := r.Join() c.Assert(err, IsNil) s.State.StartSync() select { case ch, ok := <-w.Changes(): c.Assert(ok, Equals, true) c.Assert(ch.Joined, DeepEquals, []string{"u/0"}) c.Assert(ch.Changed, HasLen, 1) _, found := ch.Changed["u/0"] c.Assert(found, Equals, true) c.Assert(ch.Departed, HasLen, 0) case <-time.After(500 * time.Millisecond): c.Fatalf("timed out waiting for presence detection") } // re-Join is no-op. err = r.Join() c.Assert(err, IsNil) s.State.StartSync() select { case ch, ok := <-w.Changes(): c.Fatalf("got unexpected change: %#v, %#v", ch, ok) case <-time.After(50 * time.Millisecond): } // u/0 leaves scope; u/1 observes it. hi := hook.Info{Kind: hook.RelationBroken} _, err = r.PrepareHook(hi) c.Assert(err, IsNil) err = r.CommitHook(hi) c.Assert(err, IsNil) s.State.StartSync() select { case ch, ok := <-w.Changes(): c.Assert(ok, Equals, true) c.Assert(ch.Joined, HasLen, 0) c.Assert(ch.Changed, HasLen, 0) c.Assert(ch.Departed, DeepEquals, []string{"u/0"}) case <-time.After(5 * time.Second): c.Fatalf("timed out waiting for absence detection") } }
func (s *RelationerSuite) TestSetDying(c *C) { ru1 := s.AddRelationUnit(c, "u/1") settings := map[string]interface{}{"unit": "settings"} err := ru1.EnterScope(settings) c.Assert(err, IsNil) r := uniter.NewRelationer(s.ru, s.dir, s.hooks) err = r.Join() c.Assert(err, IsNil) r.StartHooks() defer stopHooks(c, r) s.assertHook(c, hook.Info{ Kind: hook.RelationJoined, RemoteUnit: "u/1", Members: map[string]map[string]interface{}{ "u/1": settings, }, }) // 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, IsNil) // Check that we cannot rejoin the relation. f := func() { r.Join() } c.Assert(f, 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: hook.RelationChanged, RemoteUnit: "u/1"}) s.assertHook(c, hook.Info{Kind: hook.RelationDeparted, RemoteUnit: "u/1"}) s.assertHook(c, hook.Info{Kind: hook.RelationBroken}) // Check that the relation state has been broken. err = s.dir.State().Validate(hook.Info{Kind: hook.RelationBroken}) c.Assert(err, ErrorMatches, ".*: relation is broken and cannot be changed further") }
func (s *RelationerImplicitSuite) TestImplicitRelationer(c *C) { // Create a relationer for an implicit endpoint (mysql:juju-info). mysql, err := s.State.AddService("mysql", s.AddTestingCharm(c, "mysql")) c.Assert(err, IsNil) u, err := mysql.AddUnit() c.Assert(err, IsNil) err = u.SetPrivateAddress("blah") c.Assert(err, IsNil) logging, err := s.State.AddService("logging", s.AddTestingCharm(c, "logging")) c.Assert(err, IsNil) eps, err := s.State.InferEndpoints([]string{"logging", "mysql"}) c.Assert(err, IsNil) rel, err := s.State.AddRelation(eps...) c.Assert(err, IsNil) ru, err := rel.Unit(u) c.Assert(err, IsNil) relsDir := c.MkDir() dir, err := relation.ReadStateDir(relsDir, rel.Id()) c.Assert(err, IsNil) hooks := make(chan hook.Info) r := uniter.NewRelationer(ru, dir, hooks) c.Assert(r.IsImplicit(), Equals, true) // Join the relationer; check the dir was created and scope was entered. err = r.Join() c.Assert(err, IsNil) fi, err := os.Stat(filepath.Join(relsDir, strconv.Itoa(rel.Id()))) c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, true) sub, err := logging.Unit("logging/0") c.Assert(err, IsNil) err = sub.SetPrivateAddress("blah") c.Assert(err, IsNil) // Join the other side; check no hooks are sent. r.StartHooks() defer func() { c.Assert(r.StopHooks(), IsNil) }() subru, err := rel.Unit(sub) c.Assert(err, IsNil) err = subru.EnterScope(map[string]interface{}{"some": "data"}) c.Assert(err, IsNil) s.State.StartSync() select { case <-time.After(50 * time.Millisecond): case <-hooks: c.Fatalf("unexpected hook generated") } // Set it to Dying; check that the dir is removed. err = r.SetDying() c.Assert(err, IsNil) _, err = os.Stat(filepath.Join(relsDir, strconv.Itoa(rel.Id()))) c.Assert(os.IsNotExist(err), Equals, true) // Check that it left scope, by leaving scope on the other side and destroying // the relation. err = subru.LeaveScope() c.Assert(err, IsNil) err = rel.Destroy() c.Assert(err, IsNil) err = rel.Refresh() c.Assert(state.IsNotFound(err), Equals, true) // Verify that no other hooks were sent at any stage. select { case <-hooks: c.Fatalf("unexpected hook generated") default: } }
func (s *RelationerSuite) TestPrepareCommitHooks(c *C) { r := uniter.NewRelationer(s.ru, s.dir, s.hooks) err := r.Join() c.Assert(err, IsNil) ctx := r.Context() c.Assert(ctx.UnitNames(), HasLen, 0) // Check preparing an invalid hook changes nothing. changed := hook.Info{ Kind: hook.RelationChanged, RemoteUnit: "u/1", ChangeVersion: 7, Members: map[string]map[string]interface{}{ "u/1": {"private-address": "glastonbury"}, }, } _, err = r.PrepareHook(changed) c.Assert(err, ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`) c.Assert(ctx.UnitNames(), HasLen, 0) c.Assert(s.dir.State().Members, HasLen, 0) // Check preparing a valid hook updates the context, but not persistent // relation state. joined := hook.Info{ Kind: hook.RelationJoined, RemoteUnit: "u/1", Members: map[string]map[string]interface{}{ "u/1": {"private-address": "u-1.example.com"}, }, } name, err := r.PrepareHook(joined) c.Assert(err, IsNil) c.Assert(s.dir.State().Members, HasLen, 0) c.Assert(name, Equals, "my-relation-relation-joined") c.Assert(ctx.UnitNames(), DeepEquals, []string{"u/1"}) s1, err := ctx.ReadSettings("u/1") c.Assert(err, IsNil) c.Assert(s1, DeepEquals, joined.Members["u/1"]) // Clear the changed hook's Members, as though it had been deserialized. changed.Members = nil // Check that preparing the following hook fails as before... _, err = r.PrepareHook(changed) c.Assert(err, ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`) c.Assert(s.dir.State().Members, HasLen, 0) c.Assert(ctx.UnitNames(), DeepEquals, []string{"u/1"}) s1, err = ctx.ReadSettings("u/1") c.Assert(err, IsNil) c.Assert(s1, DeepEquals, joined.Members["u/1"]) // ...but that committing the previous hook updates the persistent // relation state... err = r.CommitHook(joined) c.Assert(err, IsNil) c.Assert(s.dir.State().Members, DeepEquals, map[string]int64{"u/1": 0}) c.Assert(ctx.UnitNames(), DeepEquals, []string{"u/1"}) s1, err = ctx.ReadSettings("u/1") c.Assert(err, IsNil) c.Assert(s1, DeepEquals, joined.Members["u/1"]) // ...and allows us to prepare the next hook... name, err = r.PrepareHook(changed) c.Assert(err, IsNil) c.Assert(name, Equals, "my-relation-relation-changed") c.Assert(s.dir.State().Members, DeepEquals, map[string]int64{"u/1": 0}) c.Assert(ctx.UnitNames(), DeepEquals, []string{"u/1"}) s1, err = ctx.ReadSettings("u/1") c.Assert(err, IsNil) c.Assert(s1, DeepEquals, map[string]interface{}{"private-address": "u-1.example.com"}) // ...and commit it. err = r.CommitHook(changed) c.Assert(err, IsNil) c.Assert(s.dir.State().Members, DeepEquals, map[string]int64{"u/1": 7}) c.Assert(ctx.UnitNames(), 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 joined.Members = nil name, err = r.PrepareHook(joined) c.Assert(err, IsNil) c.Assert(s.dir.State().Members, HasLen, 1) c.Assert(name, Equals, "my-relation-relation-joined") c.Assert(ctx.UnitNames(), DeepEquals, []string{"u/1", "u/2"}) // ...and so is relation state on commit. err = r.CommitHook(joined) c.Assert(err, IsNil) c.Assert(s.dir.State().Members, DeepEquals, map[string]int64{"u/1": 7, "u/2": 3}) c.Assert(ctx.UnitNames(), DeepEquals, []string{"u/1", "u/2"}) }
func (s *RelationerSuite) TestStartStopHooks(c *C) { ru1 := s.AddRelationUnit(c, "u/1") ru2 := s.AddRelationUnit(c, "u/2") r := uniter.NewRelationer(s.ru, s.dir, s.hooks) c.Assert(r.IsImplicit(), Equals, false) err := r.Join() c.Assert(err, 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, 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, IsNil) s.assertHook(c, hook.Info{ Kind: hook.RelationJoined, RemoteUnit: "u/1", Members: map[string]map[string]interface{}{"u/1": settings}, }) s.assertHook(c, hook.Info{ Kind: hook.RelationChanged, RemoteUnit: "u/1", Members: map[string]map[string]interface{}{"u/1": settings}, }) s.assertNoHook(c) // Stop hooks, make more changes, check no events. err = r.StopHooks() c.Assert(err, IsNil) err = ru1.LeaveScope() c.Assert(err, IsNil) err = ru2.EnterScope(nil) c.Assert(err, IsNil) node, err := ru2.Settings() c.Assert(err, IsNil) node.Set("private-address", "roehampton") _, err = node.Write() c.Assert(err, IsNil) s.assertNoHook(c) // Stop hooks again to verify safety. err = r.StopHooks() c.Assert(err, 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: hook.RelationDeparted, RemoteUnit: "u/1", Members: map[string]map[string]interface{}{}, }) s.assertHook(c, hook.Info{ Kind: hook.RelationJoined, ChangeVersion: 1, RemoteUnit: "u/2", Members: map[string]map[string]interface{}{ "u/2": {"private-address": "roehampton"}, }, }) s.assertHook(c, hook.Info{ Kind: hook.RelationChanged, ChangeVersion: 1, RemoteUnit: "u/2", Members: map[string]map[string]interface{}{ "u/2": {"private-address": "roehampton"}, }, }) s.assertNoHook(c) // Stop them again, just to be sure. err = r.StopHooks() c.Assert(err, IsNil) s.assertNoHook(c) }
func (s *RelationerSuite) TestEnterLeaveScope(c *C) { ru1 := s.AddRelationUnit(c, "u/1") r := uniter.NewRelationer(s.ru, 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, Equals, true) c.Assert(ch.Joined, HasLen, 0) c.Assert(ch.Changed, HasLen, 0) c.Assert(ch.Departed, HasLen, 0) // u/0 enters scope; u/1 observes it. err := r.Join() c.Assert(err, IsNil) s.State.StartSync() select { case ch, ok := <-w.Changes(): c.Assert(ok, Equals, true) c.Assert(ch.Joined, DeepEquals, []string{"u/0"}) c.Assert(ch.Changed, HasLen, 1) _, found := ch.Changed["u/0"] c.Assert(found, Equals, true) c.Assert(ch.Departed, 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, 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, IsNil) // Verify PrepareHook created the dir. fi, err := os.Stat(filepath.Join(s.dirPath, strconv.Itoa(s.rel.Id()))) c.Assert(err, IsNil) c.Assert(fi, checkers.Satisfies, os.FileInfo.IsDir) err = r.CommitHook(hi) c.Assert(err, IsNil) s.State.StartSync() select { case ch, ok := <-w.Changes(): c.Assert(ok, Equals, true) c.Assert(ch.Joined, HasLen, 0) c.Assert(ch.Changed, HasLen, 0) c.Assert(ch.Departed, DeepEquals, []string{"u/0"}) case <-time.After(worstCase): c.Fatalf("timed out waiting for absence detection") } }