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