github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/worker/uniter/filter_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/charm" 11 "github.com/juju/utils" 12 gc "launchpad.net/gocheck" 13 "launchpad.net/tomb" 14 15 jujutesting "github.com/juju/juju/juju/testing" 16 "github.com/juju/juju/state" 17 "github.com/juju/juju/state/api" 18 "github.com/juju/juju/state/api/params" 19 apiuniter "github.com/juju/juju/state/api/uniter" 20 statetesting "github.com/juju/juju/state/testing" 21 coretesting "github.com/juju/juju/testing" 22 "github.com/juju/juju/worker" 23 ) 24 25 type FilterSuite struct { 26 jujutesting.JujuConnSuite 27 wordpress *state.Service 28 unit *state.Unit 29 mysqlcharm *state.Charm 30 wpcharm *state.Charm 31 32 st *api.State 33 uniter *apiuniter.State 34 } 35 36 var _ = gc.Suite(&FilterSuite{}) 37 38 func (s *FilterSuite) SetUpTest(c *gc.C) { 39 s.JujuConnSuite.SetUpTest(c) 40 s.wpcharm = s.AddTestingCharm(c, "wordpress") 41 s.wordpress = s.AddTestingService(c, "wordpress", s.wpcharm) 42 var err error 43 s.unit, err = s.wordpress.AddUnit() 44 c.Assert(err, gc.IsNil) 45 err = s.unit.AssignToNewMachine() 46 c.Assert(err, gc.IsNil) 47 mid, err := s.unit.AssignedMachineId() 48 c.Assert(err, gc.IsNil) 49 machine, err := s.State.Machine(mid) 50 c.Assert(err, gc.IsNil) 51 err = machine.SetProvisioned("i-exist", "fake_nonce", nil) 52 c.Assert(err, gc.IsNil) 53 s.APILogin(c, s.unit) 54 } 55 56 func (s *FilterSuite) APILogin(c *gc.C, unit *state.Unit) { 57 password, err := utils.RandomPassword() 58 c.Assert(err, gc.IsNil) 59 err = unit.SetPassword(password) 60 c.Assert(err, gc.IsNil) 61 s.st = s.OpenAPIAs(c, unit.Tag(), password) 62 s.uniter = s.st.Uniter() 63 c.Assert(s.uniter, gc.NotNil) 64 } 65 66 func (s *FilterSuite) TestUnitDeath(c *gc.C) { 67 f, err := newFilter(s.uniter, s.unit.Tag()) 68 c.Assert(err, gc.IsNil) 69 defer f.Stop() // no AssertStop, we test for an error below 70 asserter := coretesting.NotifyAsserterC{ 71 Precond: func() { s.BackingState.StartSync() }, 72 C: c, 73 Chan: f.UnitDying(), 74 } 75 asserter.AssertNoReceive() 76 77 // Irrelevant change. 78 err = s.unit.SetResolved(state.ResolvedRetryHooks) 79 c.Assert(err, gc.IsNil) 80 asserter.AssertNoReceive() 81 82 // Set dying. 83 err = s.unit.SetStatus(params.StatusStarted, "", nil) 84 c.Assert(err, gc.IsNil) 85 err = s.unit.Destroy() 86 c.Assert(err, gc.IsNil) 87 asserter.AssertClosed() 88 89 // Another irrelevant change. 90 err = s.unit.ClearResolved() 91 c.Assert(err, gc.IsNil) 92 asserter.AssertClosed() 93 94 // Set dead. 95 err = s.unit.EnsureDead() 96 c.Assert(err, gc.IsNil) 97 s.assertAgentTerminates(c, f) 98 } 99 100 func (s *FilterSuite) TestUnitRemoval(c *gc.C) { 101 f, err := newFilter(s.uniter, s.unit.Tag()) 102 c.Assert(err, gc.IsNil) 103 defer f.Stop() // no AssertStop, we test for an error below 104 105 // short-circuit to remove because no status set. 106 err = s.unit.Destroy() 107 c.Assert(err, gc.IsNil) 108 s.assertAgentTerminates(c, f) 109 } 110 111 // Ensure we get a signal on f.Dead() 112 func (s *FilterSuite) assertFilterDies(c *gc.C, f *filter) { 113 asserter := coretesting.NotifyAsserterC{ 114 Precond: func() { s.BackingState.StartSync() }, 115 C: c, 116 Chan: f.Dead(), 117 } 118 asserter.AssertClosed() 119 } 120 121 func (s *FilterSuite) assertAgentTerminates(c *gc.C, f *filter) { 122 s.assertFilterDies(c, f) 123 c.Assert(f.Wait(), gc.Equals, worker.ErrTerminateAgent) 124 } 125 126 func (s *FilterSuite) TestServiceDeath(c *gc.C) { 127 f, err := newFilter(s.uniter, s.unit.Tag()) 128 c.Assert(err, gc.IsNil) 129 defer statetesting.AssertStop(c, f) 130 dyingAsserter := coretesting.NotifyAsserterC{ 131 C: c, 132 Precond: func() { s.BackingState.StartSync() }, 133 Chan: f.UnitDying(), 134 } 135 dyingAsserter.AssertNoReceive() 136 137 err = s.unit.SetStatus(params.StatusStarted, "", nil) 138 c.Assert(err, gc.IsNil) 139 err = s.wordpress.Destroy() 140 c.Assert(err, gc.IsNil) 141 142 timeout := time.After(coretesting.LongWait) 143 loop: 144 for { 145 select { 146 case <-f.UnitDying(): 147 break loop 148 case <-time.After(coretesting.ShortWait): 149 s.BackingState.StartSync() 150 case <-timeout: 151 c.Fatalf("dead not detected") 152 } 153 } 154 err = s.unit.Refresh() 155 c.Assert(err, gc.IsNil) 156 c.Assert(s.unit.Life(), gc.Equals, state.Dying) 157 158 // Can't set s.wordpress to Dead while it still has units. 159 } 160 161 func (s *FilterSuite) TestResolvedEvents(c *gc.C) { 162 f, err := newFilter(s.uniter, s.unit.Tag()) 163 c.Assert(err, gc.IsNil) 164 defer statetesting.AssertStop(c, f) 165 166 resolvedAsserter := coretesting.ContentAsserterC{ 167 C: c, 168 Precond: func() { s.BackingState.StartSync() }, 169 Chan: f.ResolvedEvents(), 170 } 171 resolvedAsserter.AssertNoReceive() 172 173 // Request an event; no interesting event is available. 174 f.WantResolvedEvent() 175 resolvedAsserter.AssertNoReceive() 176 177 // Change the unit in an irrelevant way; no events. 178 err = s.unit.SetStatus(params.StatusError, "blarg", nil) 179 c.Assert(err, gc.IsNil) 180 resolvedAsserter.AssertNoReceive() 181 182 // Change the unit's resolved to an interesting value; new event received. 183 err = s.unit.SetResolved(state.ResolvedRetryHooks) 184 c.Assert(err, gc.IsNil) 185 assertChange := func(expect params.ResolvedMode) { 186 rm := resolvedAsserter.AssertOneReceive().(params.ResolvedMode) 187 c.Assert(rm, gc.Equals, expect) 188 } 189 assertChange(params.ResolvedRetryHooks) 190 191 // Ask for the event again, and check it's resent. 192 f.WantResolvedEvent() 193 assertChange(params.ResolvedRetryHooks) 194 195 // Clear the resolved status *via the filter*; check not resent... 196 err = f.ClearResolved() 197 c.Assert(err, gc.IsNil) 198 resolvedAsserter.AssertNoReceive() 199 200 // ...even when requested. 201 f.WantResolvedEvent() 202 resolvedAsserter.AssertNoReceive() 203 204 // Induce several events; only latest state is reported. 205 err = s.unit.SetResolved(state.ResolvedRetryHooks) 206 c.Assert(err, gc.IsNil) 207 err = f.ClearResolved() 208 c.Assert(err, gc.IsNil) 209 err = s.unit.SetResolved(state.ResolvedNoHooks) 210 c.Assert(err, gc.IsNil) 211 assertChange(params.ResolvedNoHooks) 212 } 213 214 func (s *FilterSuite) TestCharmUpgradeEvents(c *gc.C) { 215 oldCharm := s.AddTestingCharm(c, "upgrade1") 216 svc := s.AddTestingService(c, "upgradetest", oldCharm) 217 unit, err := svc.AddUnit() 218 c.Assert(err, gc.IsNil) 219 220 s.APILogin(c, unit) 221 222 f, err := newFilter(s.uniter, unit.Tag()) 223 c.Assert(err, gc.IsNil) 224 defer statetesting.AssertStop(c, f) 225 226 // No initial event is sent. 227 assertNoChange := func() { 228 s.BackingState.StartSync() 229 select { 230 case sch := <-f.UpgradeEvents(): 231 c.Fatalf("unexpected %#v", sch) 232 case <-time.After(coretesting.ShortWait): 233 } 234 } 235 assertNoChange() 236 237 // Setting a charm generates no new events if it already matches. 238 err = f.SetCharm(oldCharm.URL()) 239 c.Assert(err, gc.IsNil) 240 assertNoChange() 241 242 // Explicitly request an event relative to the existing state; nothing. 243 f.WantUpgradeEvent(false) 244 assertNoChange() 245 246 // Change the service in an irrelevant way; no events. 247 err = svc.SetExposed() 248 c.Assert(err, gc.IsNil) 249 assertNoChange() 250 251 // Change the service's charm; new event received. 252 newCharm := s.AddTestingCharm(c, "upgrade2") 253 err = svc.SetCharm(newCharm, false) 254 c.Assert(err, gc.IsNil) 255 assertChange := func(url *charm.URL) { 256 s.BackingState.StartSync() 257 select { 258 case upgradeCharm := <-f.UpgradeEvents(): 259 c.Assert(upgradeCharm, gc.DeepEquals, url) 260 case <-time.After(coretesting.LongWait): 261 c.Fatalf("timed out") 262 } 263 } 264 assertChange(newCharm.URL()) 265 assertNoChange() 266 267 // Request a new upgrade *unforced* upgrade event, we should see one. 268 f.WantUpgradeEvent(false) 269 assertChange(newCharm.URL()) 270 assertNoChange() 271 272 // Request only *forced* upgrade events; nothing. 273 f.WantUpgradeEvent(true) 274 assertNoChange() 275 276 // But when we have a forced upgrade to the same URL, no new event. 277 err = svc.SetCharm(oldCharm, true) 278 c.Assert(err, gc.IsNil) 279 assertNoChange() 280 281 // ...but a *forced* change to a different URL should generate an event. 282 err = svc.SetCharm(newCharm, true) 283 assertChange(newCharm.URL()) 284 assertNoChange() 285 } 286 287 func (s *FilterSuite) TestConfigEvents(c *gc.C) { 288 f, err := newFilter(s.uniter, s.unit.Tag()) 289 c.Assert(err, gc.IsNil) 290 defer statetesting.AssertStop(c, f) 291 292 // Test no changes before the charm URL is set. 293 assertNoChange := func() { 294 s.BackingState.StartSync() 295 select { 296 case <-f.ConfigEvents(): 297 c.Fatalf("unexpected config event") 298 case <-time.After(coretesting.ShortWait): 299 } 300 } 301 assertNoChange() 302 303 // Set the charm URL to trigger config events. 304 err = f.SetCharm(s.wpcharm.URL()) 305 c.Assert(err, gc.IsNil) 306 assertChange := func() { 307 s.BackingState.StartSync() 308 select { 309 case _, ok := <-f.ConfigEvents(): 310 c.Assert(ok, gc.Equals, true) 311 case <-time.After(coretesting.LongWait): 312 c.Fatalf("timed out") 313 } 314 assertNoChange() 315 } 316 assertChange() 317 318 // Change the config; new event received. 319 changeConfig := func(title interface{}) { 320 err := s.wordpress.UpdateConfigSettings(charm.Settings{ 321 "blog-title": title, 322 }) 323 c.Assert(err, gc.IsNil) 324 } 325 changeConfig("20,000 leagues in the cloud") 326 assertChange() 327 328 // Change the config a few more times, then reset the events. We sync to 329 // make sure the events have arrived in the watcher -- and then wait a 330 // little longer, to allow for the delay while the events are coalesced 331 // -- before we tell it to discard all received events. This would be 332 // much better tested by controlling a mocked-out watcher directly, but 333 // that's a bit inconvenient for this change. 334 changeConfig(nil) 335 changeConfig("the curious incident of the dog in the cloud") 336 s.BackingState.StartSync() 337 time.Sleep(250 * time.Millisecond) 338 f.DiscardConfigEvent() 339 assertNoChange() 340 341 // Check that a filter's initial event works with DiscardConfigEvent 342 // as expected. 343 f, err = newFilter(s.uniter, s.unit.Tag()) 344 c.Assert(err, gc.IsNil) 345 defer statetesting.AssertStop(c, f) 346 s.BackingState.StartSync() 347 f.DiscardConfigEvent() 348 assertNoChange() 349 350 // Further changes are still collapsed as appropriate. 351 changeConfig("forsooth") 352 changeConfig("imagination failure") 353 assertChange() 354 } 355 356 func (s *FilterSuite) TestCharmErrorEvents(c *gc.C) { 357 f, err := newFilter(s.uniter, s.unit.Tag()) 358 c.Assert(err, gc.IsNil) 359 defer f.Stop() // no AssertStop, we test for an error below 360 361 assertNoChange := func() { 362 s.BackingState.StartSync() 363 select { 364 case <-f.ConfigEvents(): 365 c.Fatalf("unexpected config event") 366 case <-time.After(coretesting.ShortWait): 367 } 368 } 369 370 // Check setting an invalid charm URL does not send events. 371 err = f.SetCharm(charm.MustParseURL("cs:missing/one-1")) 372 c.Assert(err, gc.Equals, tomb.ErrDying) 373 assertNoChange() 374 s.assertFilterDies(c, f) 375 376 // Filter died after the error, so restart it. 377 f, err = newFilter(s.uniter, s.unit.Tag()) 378 c.Assert(err, gc.IsNil) 379 defer f.Stop() // no AssertStop, we test for an error below 380 381 // Check with a nil charm URL, again no changes. 382 err = f.SetCharm(nil) 383 c.Assert(err, gc.Equals, tomb.ErrDying) 384 assertNoChange() 385 s.assertFilterDies(c, f) 386 } 387 388 func (s *FilterSuite) TestRelationsEvents(c *gc.C) { 389 f, err := newFilter(s.uniter, s.unit.Tag()) 390 c.Assert(err, gc.IsNil) 391 defer statetesting.AssertStop(c, f) 392 393 assertNoChange := func() { 394 s.BackingState.StartSync() 395 select { 396 case ids := <-f.RelationsEvents(): 397 c.Fatalf("unexpected relations event %#v", ids) 398 case <-time.After(coretesting.ShortWait): 399 } 400 } 401 assertNoChange() 402 403 // Add a couple of relations; check the event. 404 rel0 := s.addRelation(c) 405 rel1 := s.addRelation(c) 406 assertChange := func(expect []int) { 407 s.BackingState.StartSync() 408 select { 409 case got := <-f.RelationsEvents(): 410 c.Assert(got, gc.DeepEquals, expect) 411 case <-time.After(coretesting.LongWait): 412 c.Fatalf("timed out") 413 } 414 assertNoChange() 415 } 416 assertChange([]int{0, 1}) 417 418 // Add another relation, and change another's Life (by entering scope before 419 // Destroy, thereby setting the relation to Dying); check event. 420 s.addRelation(c) 421 ru0, err := rel0.Unit(s.unit) 422 c.Assert(err, gc.IsNil) 423 err = ru0.EnterScope(nil) 424 c.Assert(err, gc.IsNil) 425 err = rel0.Destroy() 426 c.Assert(err, gc.IsNil) 427 assertChange([]int{0, 2}) 428 429 // Remove a relation completely; check no event, because the relation 430 // could not have been removed if the unit was in scope, and therefore 431 // the uniter never needs to hear about it. 432 err = rel1.Destroy() 433 c.Assert(err, gc.IsNil) 434 assertNoChange() 435 err = f.Stop() 436 c.Assert(err, gc.IsNil) 437 438 // Start a new filter, check initial event. 439 f, err = newFilter(s.uniter, s.unit.Tag()) 440 c.Assert(err, gc.IsNil) 441 defer statetesting.AssertStop(c, f) 442 assertChange([]int{0, 2}) 443 444 // Check setting the charm URL generates all new relation events. 445 err = f.SetCharm(s.wpcharm.URL()) 446 c.Assert(err, gc.IsNil) 447 assertChange([]int{0, 2}) 448 } 449 450 func (s *FilterSuite) addRelation(c *gc.C) *state.Relation { 451 if s.mysqlcharm == nil { 452 s.mysqlcharm = s.AddTestingCharm(c, "mysql") 453 } 454 rels, err := s.wordpress.Relations() 455 c.Assert(err, gc.IsNil) 456 svcName := fmt.Sprintf("mysql%d", len(rels)) 457 s.AddTestingService(c, svcName, s.mysqlcharm) 458 eps, err := s.State.InferEndpoints([]string{svcName, "wordpress"}) 459 c.Assert(err, gc.IsNil) 460 rel, err := s.State.AddRelation(eps...) 461 c.Assert(err, gc.IsNil) 462 return rel 463 }