github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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.v4/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.State 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.NewAddress( 86 strings.Replace(name, "/", "-", 1)+".testing.invalid", network.ScopeCloudLocal) 87 err = machine.SetAddresses(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 := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks) 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 := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks) 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(worstCase): 170 c.Fatalf("timed out waiting for absence detection") 171 } 172 } 173 174 func (s *RelationerSuite) TestStartStopHooks(c *gc.C) { 175 ru1, _ := s.AddRelationUnit(c, "u/1") 176 ru2, _ := s.AddRelationUnit(c, "u/2") 177 r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks) 178 c.Assert(r.IsImplicit(), jc.IsFalse) 179 err := r.Join() 180 c.Assert(err, jc.ErrorIsNil) 181 182 // Check no hooks are being sent. 183 s.assertNoHook(c) 184 185 // Start hooks, and check that still no changes are sent. 186 r.StartHooks() 187 defer stopHooks(c, r) 188 s.assertNoHook(c) 189 190 // Check we can't start hooks again. 191 f := func() { r.StartHooks() } 192 c.Assert(f, gc.PanicMatches, "hooks already started!") 193 194 // Join u/1 to the relation, and check that we receive the expected hooks. 195 settings := map[string]interface{}{"unit": "settings"} 196 err = ru1.EnterScope(settings) 197 c.Assert(err, jc.ErrorIsNil) 198 s.assertHook(c, hook.Info{ 199 Kind: hooks.RelationJoined, 200 RemoteUnit: "u/1", 201 }) 202 s.assertHook(c, hook.Info{ 203 Kind: hooks.RelationChanged, 204 RemoteUnit: "u/1", 205 }) 206 s.assertNoHook(c) 207 208 // Stop hooks, make more changes, check no events. 209 err = r.StopHooks() 210 c.Assert(err, jc.ErrorIsNil) 211 err = ru1.LeaveScope() 212 c.Assert(err, jc.ErrorIsNil) 213 err = ru2.EnterScope(nil) 214 c.Assert(err, jc.ErrorIsNil) 215 node, err := ru2.Settings() 216 c.Assert(err, jc.ErrorIsNil) 217 node.Set("private-address", "roehampton") 218 _, err = node.Write() 219 c.Assert(err, jc.ErrorIsNil) 220 s.assertNoHook(c) 221 222 // Stop hooks again to verify safety. 223 err = r.StopHooks() 224 c.Assert(err, jc.ErrorIsNil) 225 s.assertNoHook(c) 226 227 // Start them again, and check we get the expected events sent. 228 r.StartHooks() 229 defer stopHooks(c, r) 230 s.assertHook(c, hook.Info{ 231 Kind: hooks.RelationDeparted, 232 RemoteUnit: "u/1", 233 }) 234 s.assertHook(c, hook.Info{ 235 Kind: hooks.RelationJoined, 236 ChangeVersion: 1, 237 RemoteUnit: "u/2", 238 }) 239 s.assertHook(c, hook.Info{ 240 Kind: hooks.RelationChanged, 241 ChangeVersion: 1, 242 RemoteUnit: "u/2", 243 }) 244 s.assertNoHook(c) 245 246 // Stop them again, just to be sure. 247 err = r.StopHooks() 248 c.Assert(err, jc.ErrorIsNil) 249 s.assertNoHook(c) 250 } 251 252 func (s *RelationerSuite) TestPrepareCommitHooks(c *gc.C) { 253 r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks) 254 err := r.Join() 255 c.Assert(err, jc.ErrorIsNil) 256 257 assertMembers := func(expect map[string]int64) { 258 c.Assert(s.dir.State().Members, jc.DeepEquals, expect) 259 expectNames := make([]string, 0, len(expect)) 260 for name := range expect { 261 expectNames = append(expectNames, name) 262 } 263 c.Assert(r.ContextInfo().MemberNames, jc.SameContents, expectNames) 264 } 265 assertMembers(map[string]int64{}) 266 267 // Check preparing an invalid hook changes nothing. 268 changed := hook.Info{ 269 Kind: hooks.RelationChanged, 270 RemoteUnit: "u/1", 271 ChangeVersion: 7, 272 } 273 _, err = r.PrepareHook(changed) 274 c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`) 275 assertMembers(map[string]int64{}) 276 277 // Check preparing a valid hook updates neither the context nor persistent 278 // relation state. 279 joined := hook.Info{ 280 Kind: hooks.RelationJoined, 281 RemoteUnit: "u/1", 282 } 283 name, err := r.PrepareHook(joined) 284 c.Assert(err, jc.ErrorIsNil) 285 c.Assert(name, gc.Equals, "ring-relation-joined") 286 assertMembers(map[string]int64{}) 287 288 // Check that preparing the following hook fails as before... 289 _, err = r.PrepareHook(changed) 290 c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`) 291 assertMembers(map[string]int64{}) 292 293 // ...but that committing the previous hook updates the persistent 294 // relation state... 295 err = r.CommitHook(joined) 296 c.Assert(err, jc.ErrorIsNil) 297 assertMembers(map[string]int64{"u/1": 0}) 298 299 // ...and allows us to prepare the next hook... 300 name, err = r.PrepareHook(changed) 301 c.Assert(err, jc.ErrorIsNil) 302 c.Assert(name, gc.Equals, "ring-relation-changed") 303 assertMembers(map[string]int64{"u/1": 0}) 304 305 // ...and commit it. 306 err = r.CommitHook(changed) 307 c.Assert(err, jc.ErrorIsNil) 308 assertMembers(map[string]int64{"u/1": 7}) 309 310 // To verify implied behaviour above, prepare a new joined hook with 311 // missing membership information, and check relation context 312 // membership is stil not updated... 313 joined.RemoteUnit = "u/2" 314 joined.ChangeVersion = 3 315 name, err = r.PrepareHook(joined) 316 c.Assert(err, jc.ErrorIsNil) 317 c.Assert(name, gc.Equals, "ring-relation-joined") 318 assertMembers(map[string]int64{"u/1": 7}) 319 320 // ...until commit, at which point so is relation state. 321 err = r.CommitHook(joined) 322 c.Assert(err, jc.ErrorIsNil) 323 assertMembers(map[string]int64{"u/1": 7, "u/2": 3}) 324 } 325 326 func (s *RelationerSuite) TestSetDying(c *gc.C) { 327 ru1, _ := s.AddRelationUnit(c, "u/1") 328 settings := map[string]interface{}{"unit": "settings"} 329 err := ru1.EnterScope(settings) 330 c.Assert(err, jc.ErrorIsNil) 331 r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks) 332 err = r.Join() 333 c.Assert(err, jc.ErrorIsNil) 334 r.StartHooks() 335 defer stopHooks(c, r) 336 s.assertHook(c, hook.Info{ 337 Kind: hooks.RelationJoined, 338 RemoteUnit: "u/1", 339 }) 340 341 // While a changed hook is still pending, the relation (or possibly the unit, 342 // pending lifecycle work), changes Life to Dying, and the relationer is 343 // informed. 344 err = r.SetDying() 345 c.Assert(err, jc.ErrorIsNil) 346 347 // Check that we cannot rejoin the relation. 348 f := func() { r.Join() } 349 c.Assert(f, gc.PanicMatches, "dying relationer must not join!") 350 351 // ...but the hook stream continues, sending the required changed hook for 352 // u/1 before moving on to a departed, despite the fact that its pinger is 353 // still running, and closing with a broken. 354 s.assertHook(c, hook.Info{Kind: hooks.RelationChanged, RemoteUnit: "u/1"}) 355 s.assertHook(c, hook.Info{Kind: hooks.RelationDeparted, RemoteUnit: "u/1"}) 356 s.assertHook(c, hook.Info{Kind: hooks.RelationBroken}) 357 358 // Check that the relation state has been broken. 359 err = s.dir.State().Validate(hook.Info{Kind: hooks.RelationBroken}) 360 c.Assert(err, gc.ErrorMatches, ".*: relation is broken and cannot be changed further") 361 } 362 363 func (s *RelationerSuite) assertNoHook(c *gc.C) { 364 s.BackingState.StartSync() 365 select { 366 case hi, ok := <-s.hooks: 367 c.Fatalf("got unexpected hook info %#v (%t)", hi, ok) 368 case <-time.After(coretesting.ShortWait): 369 } 370 } 371 372 func (s *RelationerSuite) assertHook(c *gc.C, expect hook.Info) { 373 s.BackingState.StartSync() 374 // We must ensure the local state dir exists first. 375 c.Assert(s.dir.Ensure(), gc.IsNil) 376 select { 377 case hi, ok := <-s.hooks: 378 c.Assert(ok, jc.IsTrue) 379 expect.ChangeVersion = hi.ChangeVersion 380 c.Assert(hi, gc.DeepEquals, expect) 381 c.Assert(s.dir.Write(hi), gc.Equals, nil) 382 case <-time.After(coretesting.LongWait): 383 c.Fatalf("timed out waiting for %#v", expect) 384 } 385 } 386 387 type stopper interface { 388 Stop() error 389 } 390 391 func stop(c *gc.C, s stopper) { 392 c.Assert(s.Stop(), gc.IsNil) 393 } 394 395 func stopHooks(c *gc.C, r *uniter.Relationer) { 396 c.Assert(r.StopHooks(), gc.IsNil) 397 } 398 399 type RelationerImplicitSuite struct { 400 jujutesting.JujuConnSuite 401 } 402 403 var _ = gc.Suite(&RelationerImplicitSuite{}) 404 405 func (s *RelationerImplicitSuite) TestImplicitRelationer(c *gc.C) { 406 // Create a relationer for an implicit endpoint (mysql:juju-info). 407 mysql := s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql")) 408 u, err := mysql.AddUnit() 409 c.Assert(err, jc.ErrorIsNil) 410 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 411 c.Assert(err, jc.ErrorIsNil) 412 err = u.AssignToMachine(machine) 413 c.Assert(err, jc.ErrorIsNil) 414 err = machine.SetAddresses(network.NewAddress("blah", network.ScopeCloudLocal)) 415 c.Assert(err, jc.ErrorIsNil) 416 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 417 eps, err := s.State.InferEndpoints("logging", "mysql") 418 c.Assert(err, jc.ErrorIsNil) 419 rel, err := s.State.AddRelation(eps...) 420 c.Assert(err, jc.ErrorIsNil) 421 relsDir := c.MkDir() 422 dir, err := relation.ReadStateDir(relsDir, rel.Id()) 423 c.Assert(err, jc.ErrorIsNil) 424 hooks := make(chan hook.Info) 425 426 password, err := utils.RandomPassword() 427 c.Assert(err, jc.ErrorIsNil) 428 err = u.SetPassword(password) 429 c.Assert(err, jc.ErrorIsNil) 430 st := s.OpenAPIAs(c, u.Tag(), password) 431 uniterState, err := st.Uniter() 432 c.Assert(err, jc.ErrorIsNil) 433 c.Assert(uniterState, gc.NotNil) 434 435 apiUnit, err := uniterState.Unit(u.Tag().(names.UnitTag)) 436 c.Assert(err, jc.ErrorIsNil) 437 apiRel, err := uniterState.Relation(rel.Tag().(names.RelationTag)) 438 c.Assert(err, jc.ErrorIsNil) 439 apiRelUnit, err := apiRel.Unit(apiUnit) 440 c.Assert(err, jc.ErrorIsNil) 441 442 r := uniter.NewRelationer(apiRelUnit, dir, hooks) 443 c.Assert(r, jc.Satisfies, (*uniter.Relationer).IsImplicit) 444 445 // Join the relation. 446 err = r.Join() 447 c.Assert(err, jc.ErrorIsNil) 448 sub, err := s.State.Unit("logging/0") 449 c.Assert(err, jc.ErrorIsNil) 450 451 // Join the other side; check no hooks are sent. 452 r.StartHooks() 453 defer func() { c.Assert(r.StopHooks(), gc.IsNil) }() 454 subru, err := rel.Unit(sub) 455 c.Assert(err, jc.ErrorIsNil) 456 err = subru.EnterScope(map[string]interface{}{"some": "data"}) 457 c.Assert(err, jc.ErrorIsNil) 458 s.State.StartSync() 459 select { 460 case <-time.After(coretesting.ShortWait): 461 case <-hooks: 462 c.Fatalf("unexpected hook generated") 463 } 464 465 // Set it to Dying; check that the dir is removed immediately. 466 err = r.SetDying() 467 c.Assert(err, jc.ErrorIsNil) 468 path := strconv.Itoa(rel.Id()) 469 ft.Removed{path}.Check(c, relsDir) 470 471 // Check that it left scope, by leaving scope on the other side and destroying 472 // the relation. 473 err = subru.LeaveScope() 474 c.Assert(err, jc.ErrorIsNil) 475 err = rel.Destroy() 476 c.Assert(err, jc.ErrorIsNil) 477 err = rel.Refresh() 478 c.Assert(err, jc.Satisfies, errors.IsNotFound) 479 480 // Verify that no other hooks were sent at any stage. 481 select { 482 case <-hooks: 483 c.Fatalf("unexpected hook generated") 484 default: 485 } 486 }