func (s *RelationerSuite) SetUpTest(c *gc.C) { s.JujuConnSuite.SetUpTest(c) var err error s.svc = s.AddTestingService(c, "u", s.AddTestingCharm(c, "riak")) c.Assert(err, gc.IsNil) rels, err := s.svc.Relations() c.Assert(err, gc.IsNil) c.Assert(rels, gc.HasLen, 1) s.rel = rels[0] _, unit := s.AddRelationUnit(c, "u/0") s.dirPath = c.MkDir() s.dir, err = relation.ReadStateDir(s.dirPath, s.rel.Id()) c.Assert(err, gc.IsNil) s.hooks = make(chan hook.Info) password, err := utils.RandomPassword() c.Assert(err, gc.IsNil) err = unit.SetPassword(password) c.Assert(err, gc.IsNil) s.st = s.OpenAPIAs(c, unit.Tag(), password) s.uniter = s.st.Uniter() c.Assert(s.uniter, gc.NotNil) apiUnit, err := s.uniter.Unit(unit.Tag()) c.Assert(err, gc.IsNil) apiRel, err := s.uniter.Relation(s.rel.Tag()) c.Assert(err, gc.IsNil) s.apiRelUnit, err = apiRel.Unit(apiUnit) c.Assert(err, gc.IsNil) }
func (s *StateDirSuite) TestWrite(c *gc.C) { for i, t := range writeTests { c.Logf("test %d", i) basedir := c.MkDir() setUpDir(c, basedir, "123", map[string]string{ "foo-1": "change-version: 0\n", "foo-2": "change-version: 0\n", }) dir, err := relation.ReadStateDir(basedir, 123) c.Assert(err, jc.ErrorIsNil) for i, hi := range t.hooks { c.Logf(" hook %d", i) if i == len(t.hooks)-1 && t.err != "" { err = dir.State().Validate(hi) expect := fmt.Sprintf(`inappropriate %q for %q: %s`, hi.Kind, hi.RemoteUnit, t.err) c.Assert(err, gc.ErrorMatches, expect) } else { err = dir.State().Validate(hi) c.Assert(err, jc.ErrorIsNil) err = dir.Write(hi) c.Assert(err, jc.ErrorIsNil) // Check that writing the same change again is OK. err = dir.Write(hi) c.Assert(err, jc.ErrorIsNil) } } members := t.members if members == nil && !t.deleted { members = defaultMembers } assertState(c, dir, basedir, 123, members, t.pending, t.deleted) } }
// restoreRelations reconciles the local relation state dirs with the // remote state of the corresponding relations. func (u *Uniter) restoreRelations() error { joinedRelations, err := u.getJoinedRelations() if err != nil { return err } knownDirs, err := relation.ReadAllStateDirs(u.relationsDir) if err != nil { return err } for id, dir := range knownDirs { if rel, ok := joinedRelations[id]; ok { if err := u.addRelation(rel, dir); err != nil { return err } } else if err := dir.Remove(); err != nil { return err } } for id, rel := range joinedRelations { if _, ok := knownDirs[id]; ok { continue } dir, err := relation.ReadStateDir(u.relationsDir, id) if err != nil { return err } if err := u.addRelation(rel, dir); err != nil { return err } } return nil }
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, jc.ErrorIsNil) machine, err := s.State.AddMachine("quantal", state.JobHostUnits) c.Assert(err, jc.ErrorIsNil) err = u.AssignToMachine(machine) c.Assert(err, jc.ErrorIsNil) err = machine.SetProviderAddresses(network.NewScopedAddress("blah", network.ScopeCloudLocal)) c.Assert(err, jc.ErrorIsNil) s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) eps, err := s.State.InferEndpoints("logging", "mysql") c.Assert(err, jc.ErrorIsNil) rel, err := s.State.AddRelation(eps...) c.Assert(err, jc.ErrorIsNil) relsDir := c.MkDir() dir, err := relation.ReadStateDir(relsDir, rel.Id()) c.Assert(err, jc.ErrorIsNil) password, err := utils.RandomPassword() c.Assert(err, jc.ErrorIsNil) err = u.SetPassword(password) c.Assert(err, jc.ErrorIsNil) st := s.OpenAPIAs(c, u.Tag(), password) uniterState, err := st.Uniter() c.Assert(err, jc.ErrorIsNil) c.Assert(uniterState, gc.NotNil) apiUnit, err := uniterState.Unit(u.Tag().(names.UnitTag)) c.Assert(err, jc.ErrorIsNil) apiRel, err := uniterState.Relation(rel.Tag().(names.RelationTag)) c.Assert(err, jc.ErrorIsNil) apiRelUnit, err := apiRel.Unit(apiUnit) c.Assert(err, jc.ErrorIsNil) r := relation.NewRelationer(apiRelUnit, dir) c.Assert(r, jc.Satisfies, (*relation.Relationer).IsImplicit) // Hooks are not allowed. f := func() { r.PrepareHook(hook.Info{}) } c.Assert(f, gc.PanicMatches, "implicit relations must not run hooks") f = func() { r.CommitHook(hook.Info{}) } c.Assert(f, gc.PanicMatches, "implicit relations must not run hooks") // Set it to Dying; check that the dir is removed immediately. err = r.SetDying() c.Assert(err, jc.ErrorIsNil) path := strconv.Itoa(rel.Id()) ft.Removed{path}.Check(c, relsDir) err = rel.Destroy() c.Assert(err, jc.ErrorIsNil) err = rel.Refresh() c.Assert(err, jc.Satisfies, errors.IsNotFound) }
func (s *StateDirSuite) TestRemove(c *gc.C) { basedir := c.MkDir() dir, err := relation.ReadStateDir(basedir, 1) c.Assert(err, gc.IsNil) err = dir.Ensure() c.Assert(err, gc.IsNil) err = dir.Remove() c.Assert(err, gc.IsNil) err = dir.Remove() c.Assert(err, gc.IsNil) setUpDir(c, basedir, "99", map[string]string{ "foo-1": "change-version: 0\n", }) dir, err = relation.ReadStateDir(basedir, 99) c.Assert(err, gc.IsNil) err = dir.Remove() c.Assert(err, gc.ErrorMatches, ".*: directory not empty") }
func (s *StateDirSuite) TestRemove(c *gc.C) { basedir := c.MkDir() dir, err := relation.ReadStateDir(basedir, 1) c.Assert(err, jc.ErrorIsNil) err = dir.Ensure() c.Assert(err, jc.ErrorIsNil) err = dir.Remove() c.Assert(err, jc.ErrorIsNil) err = dir.Remove() c.Assert(err, jc.ErrorIsNil) setUpDir(c, basedir, "99", map[string]string{ "foo-1": "change-version: 0\n", }) dir, err = relation.ReadStateDir(basedir, 99) c.Assert(err, jc.ErrorIsNil) err = dir.Remove() // Windows message is The directory is not empty // Unix message is directory not empty c.Assert(err, gc.ErrorMatches, ".* directory (is )?not empty.?") }
func (s *StateDirSuite) TestBadRelations(c *gc.C) { for i, t := range badRelationsTests { c.Logf("test %d", i) basedir := c.MkDir() reldir := setUpDir(c, basedir, "123", t.contents) for _, subdir := range t.subdirs { setUpDir(c, reldir, subdir, nil) } _, err := relation.ReadStateDir(basedir, 123) expect := `cannot load relation state from ".*": ` + t.err c.Assert(err, gc.ErrorMatches, expect) } }
func assertState(c *gc.C, dir *relation.StateDir, relsdir string, relationId int, members msi, pending string, deleted bool) { expect := &relation.State{ RelationId: relationId, Members: map[string]int64(members), ChangedPending: pending, } c.Assert(dir.State(), gc.DeepEquals, expect) if deleted { _, err := os.Stat(filepath.Join(relsdir, strconv.Itoa(relationId))) c.Assert(err, jc.Satisfies, os.IsNotExist) } else { fresh, err := relation.ReadStateDir(relsdir, relationId) c.Assert(err, jc.ErrorIsNil) c.Assert(fresh.State(), gc.DeepEquals, expect) } }
func (s *StateDirSuite) TestReadStateDirValid(c *gc.C) { basedir := c.MkDir() reldir := setUpDir(c, basedir, "123", map[string]string{ "foo-bar-1": "change-version: 99\n", "foo-bar-1.preparing": "change-version: 100\n", "baz-qux-7": "change-version: 101\nchanged-pending: true\n", "nonsensical": "blah", "27": "blah", }) setUpDir(c, reldir, "ignored", nil) dir, err := relation.ReadStateDir(basedir, 123) c.Assert(err, jc.ErrorIsNil) state := dir.State() c.Assert(state.RelationId, gc.Equals, 123) c.Assert(msi(state.Members), gc.DeepEquals, msi{"foo-bar/1": 99, "baz-qux/7": 101}) c.Assert(state.ChangedPending, gc.Equals, "baz-qux/7") }
func (s *StateDirSuite) TestReadStateDirEmpty(c *gc.C) { basedir := c.MkDir() reldir := filepath.Join(basedir, "123") dir, err := relation.ReadStateDir(basedir, 123) c.Assert(err, jc.ErrorIsNil) state := dir.State() c.Assert(state.RelationId, gc.Equals, 123) c.Assert(msi(state.Members), gc.DeepEquals, msi{}) c.Assert(state.ChangedPending, gc.Equals, "") _, err = os.Stat(reldir) c.Assert(err, jc.Satisfies, os.IsNotExist) err = dir.Ensure() c.Assert(err, jc.ErrorIsNil) fi, err := os.Stat(reldir) c.Assert(err, jc.ErrorIsNil) c.Assert(fi, jc.Satisfies, os.FileInfo.IsDir) }
// init reconciles the local relation state dirs with the remote state of // the corresponding relations. It's only expected to be called while a // *relations is being created. func (r *relations) init() error { joinedRelationTags, err := r.unit.JoinedRelations() if err != nil { return errors.Trace(err) } joinedRelations := make(map[int]*uniter.Relation) for _, tag := range joinedRelationTags { relation, err := r.st.Relation(tag) if err != nil { return errors.Trace(err) } joinedRelations[relation.Id()] = relation } knownDirs, err := relation.ReadAllStateDirs(r.relationsDir) if err != nil { return errors.Trace(err) } for id, dir := range knownDirs { if rel, ok := joinedRelations[id]; ok { if err := r.add(rel, dir); err != nil { return errors.Trace(err) } } else if err := dir.Remove(); err != nil { return errors.Trace(err) } } for id, rel := range joinedRelations { if _, ok := knownDirs[id]; ok { continue } dir, err := relation.ReadStateDir(r.relationsDir, id) if err != nil { return errors.Trace(err) } if err := r.add(rel, dir); err != nil { return errors.Trace(err) } } return nil }
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: } }
// updateRelations responds to changes in the life states of the relations // with the supplied ids. If any id corresponds to an alive relation not // known to the unit, the uniter will join that relation and return its // relationer in the added list. func (u *Uniter) updateRelations(ids []int) (added []*Relationer, err error) { for _, id := range ids { if r, found := u.relationers[id]; found { rel := r.ru.Relation() if err := rel.Refresh(); err != nil { return nil, fmt.Errorf("cannot update relation %q: %v", rel, err) } if rel.Life() == params.Dying { if err := r.SetDying(); err != nil { return nil, err } else if r.IsImplicit() { delete(u.relationers, id) } } continue } // Relations that are not alive are simply skipped, because they // were not previously known anyway. rel, err := u.st.RelationById(id) if err != nil { if params.IsCodeNotFoundOrCodeUnauthorized(err) { continue } return nil, err } if rel.Life() != params.Alive { continue } // Make sure we ignore relations not implemented by the unit's charm. ch, err := corecharm.ReadDir(u.charmPath) if err != nil { return nil, err } if ep, err := rel.Endpoint(); err != nil { return nil, err } else if !ep.ImplementedBy(ch) { logger.Warningf("skipping relation with unknown endpoint %q", ep.Name) continue } dir, err := relation.ReadStateDir(u.relationsDir, id) if err != nil { return nil, err } err = u.addRelation(rel, dir) if err == nil { added = append(added, u.relationers[id]) continue } e := dir.Remove() if !params.IsCodeCannotEnterScope(err) { return nil, err } if e != nil { return nil, e } } if ok, err := u.unit.IsPrincipal(); err != nil { return nil, err } else if ok { return added, nil } // If no Alive relations remain between a subordinate unit's service // and its principal's service, the subordinate must become Dying. keepAlive := false for _, r := range u.relationers { scope := r.ru.Endpoint().Scope if scope == corecharm.ScopeContainer && !r.dying { keepAlive = true break } } if !keepAlive { if err := u.unit.Destroy(); err != nil { return nil, err } } return added, nil }
// Update is part of the Relations interface. func (r *relations) Update(ids []int) error { for _, id := range ids { if relationer, found := r.relationers[id]; found { rel := relationer.ru.Relation() if err := rel.Refresh(); err != nil { return errors.Annotatef(err, "cannot update relation %q", rel) } if rel.Life() == params.Dying { if err := r.setDying(id); err != nil { return errors.Trace(err) } } continue } // Relations that are not alive are simply skipped, because they // were not previously known anyway. rel, err := r.st.RelationById(id) if err != nil { if params.IsCodeNotFoundOrCodeUnauthorized(err) { continue } return errors.Trace(err) } if rel.Life() != params.Alive { continue } // Make sure we ignore relations not implemented by the unit's charm. ch, err := corecharm.ReadCharmDir(r.charmDir) if err != nil { return errors.Trace(err) } if ep, err := rel.Endpoint(); err != nil { return errors.Trace(err) } else if !ep.ImplementedBy(ch) { logger.Warningf("skipping relation with unknown endpoint %q", ep.Name) continue } dir, err := relation.ReadStateDir(r.relationsDir, id) if err != nil { return errors.Trace(err) } err = r.add(rel, dir) if err == nil { r.relationers[id].StartHooks() continue } e := dir.Remove() if !params.IsCodeCannotEnterScope(err) { return errors.Trace(err) } if e != nil { return errors.Trace(e) } } if ok, err := r.unit.IsPrincipal(); err != nil { return errors.Trace(err) } else if ok { return nil } // If no Alive relations remain between a subordinate unit's service // and its principal's service, the subordinate must become Dying. for _, relationer := range r.relationers { scope := relationer.ru.Endpoint().Scope if scope == corecharm.ScopeContainer && !relationer.dying { return nil } } return r.unit.Destroy() }