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