github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/relation/relationer_test.go (about) 1 // Copyright 2012-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package relation_test 5 6 import ( 7 "strconv" 8 "strings" 9 "time" 10 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/hooks" 17 "gopkg.in/juju/names.v2" 18 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" 27 ) 28 29 type RelationerSuite struct { 30 jujutesting.JujuConnSuite 31 hooks chan hook.Info 32 app *state.Application 33 rel *state.Relation 34 dir *relation.StateDir 35 dirPath string 36 37 st api.Connection 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.app = s.AddTestingApplication(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) 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, jc.ErrorIsNil) 57 s.hooks = make(chan hook.Info) 58 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) 67 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) 74 } 75 76 func (s *RelationerSuite) AddRelationUnit(c *gc.C, name string) (*state.RelationUnit, *state.Unit) { 77 u, err := s.app.AddUnit(state.AddUnitParams{}) 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, 86 ) 87 err = machine.SetProviderAddresses(privateAddr) 88 c.Assert(err, jc.ErrorIsNil) 89 ru, err := s.rel.Unit(u) 90 c.Assert(err, jc.ErrorIsNil) 91 return ru, u 92 } 93 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) 99 100 // Join the relation; check the dir was created. 101 err := r.Join() 102 c.Assert(err, jc.ErrorIsNil) 103 ft.Dir{path, 0755}.Check(c, s.dirPath) 104 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) 110 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) 115 } 116 117 func (s *RelationerSuite) TestEnterLeaveScope(c *gc.C) { 118 ru1, _ := s.AddRelationUnit(c, "u/1") 119 r := relation.NewRelationer(s.apiRelUnit, s.dir) 120 121 // u/1 does not consider u/0 to be alive. 122 w := ru1.Watch() 123 defer stop(c, w) 124 s.State.StartSync() 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) 129 130 // u/0 enters scope; u/1 observes it. 131 err := r.Join() 132 c.Assert(err, jc.ErrorIsNil) 133 s.State.StartSync() 134 select { 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") 143 } 144 145 // re-Join is no-op. 146 err = r.Join() 147 c.Assert(err, jc.ErrorIsNil) 148 // TODO(jam): This would be a great to replace with statetesting.NotifyWatcherC 149 s.State.StartSync() 150 select { 151 case ch, ok := <-w.Changes(): 152 c.Fatalf("got unexpected change: %#v, %#v", ch, ok) 153 case <-time.After(coretesting.ShortWait): 154 } 155 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) 160 161 err = r.CommitHook(hi) 162 c.Assert(err, jc.ErrorIsNil) 163 s.State.StartSync() 164 select { 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") 171 } 172 } 173 174 func (s *RelationerSuite) TestPrepareCommitHooks(c *gc.C) { 175 r := relation.NewRelationer(s.apiRelUnit, s.dir) 176 err := r.Join() 177 c.Assert(err, jc.ErrorIsNil) 178 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) 184 } 185 c.Assert(r.ContextInfo().MemberNames, jc.SameContents, expectNames) 186 } 187 assertMembers(map[string]int64{}) 188 189 // Check preparing an invalid hook changes nothing. 190 changed := hook.Info{ 191 Kind: hooks.RelationChanged, 192 RemoteUnit: "u/1", 193 ChangeVersion: 7, 194 } 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{}) 198 199 // Check preparing a valid hook updates neither the context nor persistent 200 // relation state. 201 joined := hook.Info{ 202 Kind: hooks.RelationJoined, 203 RemoteUnit: "u/1", 204 } 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{}) 209 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{}) 214 215 // ...but that committing the previous hook updates the persistent 216 // relation state... 217 err = r.CommitHook(joined) 218 c.Assert(err, jc.ErrorIsNil) 219 assertMembers(map[string]int64{"u/1": 0}) 220 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}) 226 227 // ...and commit it. 228 err = r.CommitHook(changed) 229 c.Assert(err, jc.ErrorIsNil) 230 assertMembers(map[string]int64{"u/1": 7}) 231 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}) 241 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}) 246 } 247 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) 254 err = r.Join() 255 c.Assert(err, jc.ErrorIsNil) 256 257 // Change Life to Dying check the results. 258 err = r.SetDying() 259 c.Assert(err, jc.ErrorIsNil) 260 261 // Check that we cannot rejoin the relation. 262 f := func() { r.Join() } 263 c.Assert(f, gc.PanicMatches, "dying relationer must not join!") 264 265 // Simulate a RelationBroken hook. 266 err = r.CommitHook(hook.Info{Kind: hooks.RelationBroken}) 267 c.Assert(err, jc.ErrorIsNil) 268 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") 272 273 // Check that it left scope, by leaving scope on the other side and destroying 274 // the relation. 275 err = ru1.LeaveScope() 276 c.Assert(err, jc.ErrorIsNil) 277 err = u.Destroy() 278 c.Assert(err, jc.ErrorIsNil) 279 err = u.Refresh() 280 c.Assert(err, jc.Satisfies, errors.IsNotFound) 281 } 282 283 type stopper interface { 284 Stop() error 285 } 286 287 func stop(c *gc.C, s stopper) { 288 c.Assert(s.Stop(), gc.IsNil) 289 } 290 291 type RelationerImplicitSuite struct { 292 jujutesting.JujuConnSuite 293 } 294 295 var _ = gc.Suite(&RelationerImplicitSuite{}) 296 297 func (s *RelationerImplicitSuite) TestImplicitRelationer(c *gc.C) { 298 // Create a relationer for an implicit endpoint (mysql:juju-info). 299 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 300 u, err := mysql.AddUnit(state.AddUnitParams{}) 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.AddTestingApplication(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) 313 relsDir := c.MkDir() 314 dir, err := relation.ReadStateDir(relsDir, rel.Id()) 315 c.Assert(err, jc.ErrorIsNil) 316 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) 325 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) 332 333 r := relation.NewRelationer(apiRelUnit, dir) 334 c.Assert(r, jc.Satisfies, (*relation.Relationer).IsImplicit) 335 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") 341 342 // Set it to Dying; check that the dir is removed immediately. 343 err = r.SetDying() 344 c.Assert(err, jc.ErrorIsNil) 345 path := strconv.Itoa(rel.Id()) 346 ft.Removed{path}.Check(c, relsDir) 347 348 err = rel.Destroy() 349 c.Assert(err, jc.ErrorIsNil) 350 err = rel.Refresh() 351 c.Assert(err, jc.Satisfies, errors.IsNotFound) 352 }