github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/state/multiwatcher_internal_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "container/list" 8 "fmt" 9 "sync" 10 "time" 11 12 "github.com/juju/errors" 13 jc "github.com/juju/testing/checkers" 14 gc "gopkg.in/check.v1" 15 "gopkg.in/mgo.v2" 16 17 "github.com/juju/juju/state/multiwatcher" 18 "github.com/juju/juju/state/watcher" 19 "github.com/juju/juju/testing" 20 ) 21 22 var _ = gc.Suite(&storeSuite{}) 23 24 type storeSuite struct { 25 testing.BaseSuite 26 } 27 28 var StoreChangeMethodTests = []struct { 29 about string 30 change func(all *multiwatcherStore) 31 expectRevno int64 32 expectContents []entityEntry 33 }{{ 34 about: "empty at first", 35 change: func(*multiwatcherStore) {}, 36 }, { 37 about: "add single entry", 38 change: func(all *multiwatcherStore) { 39 all.Update(&multiwatcher.MachineInfo{ 40 Id: "0", 41 InstanceId: "i-0", 42 }) 43 }, 44 expectRevno: 1, 45 expectContents: []entityEntry{{ 46 creationRevno: 1, 47 revno: 1, 48 info: &multiwatcher.MachineInfo{ 49 Id: "0", 50 InstanceId: "i-0", 51 }, 52 }}, 53 }, { 54 about: "add two entries", 55 change: func(all *multiwatcherStore) { 56 all.Update(&multiwatcher.MachineInfo{ 57 Id: "0", 58 InstanceId: "i-0", 59 }) 60 all.Update(&multiwatcher.ServiceInfo{ 61 Name: "wordpress", 62 Exposed: true, 63 }) 64 }, 65 expectRevno: 2, 66 expectContents: []entityEntry{{ 67 creationRevno: 1, 68 revno: 1, 69 info: &multiwatcher.MachineInfo{ 70 Id: "0", 71 InstanceId: "i-0", 72 }, 73 }, { 74 creationRevno: 2, 75 revno: 2, 76 info: &multiwatcher.ServiceInfo{ 77 Name: "wordpress", 78 Exposed: true, 79 }, 80 }}, 81 }, { 82 about: "update an entity that's not currently there", 83 change: func(all *multiwatcherStore) { 84 m := &multiwatcher.MachineInfo{Id: "1"} 85 all.Update(m) 86 }, 87 expectRevno: 1, 88 expectContents: []entityEntry{{ 89 creationRevno: 1, 90 revno: 1, 91 info: &multiwatcher.MachineInfo{Id: "1"}, 92 }}, 93 }, { 94 about: "mark removed on existing entry", 95 change: func(all *multiwatcherStore) { 96 all.Update(&multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0"}) 97 all.Update(&multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "1"}) 98 StoreIncRef(all, multiwatcher.EntityId{"machine", "uuid", "0"}) 99 all.Remove(multiwatcher.EntityId{"machine", "uuid", "0"}) 100 }, 101 expectRevno: 3, 102 expectContents: []entityEntry{{ 103 creationRevno: 2, 104 revno: 2, 105 info: &multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "1"}, 106 }, { 107 creationRevno: 1, 108 revno: 3, 109 refCount: 1, 110 removed: true, 111 info: &multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0"}, 112 }}, 113 }, { 114 about: "mark removed on nonexistent entry", 115 change: func(all *multiwatcherStore) { 116 all.Remove(multiwatcher.EntityId{"machine", "uuid", "0"}) 117 }, 118 }, { 119 about: "mark removed on already marked entry", 120 change: func(all *multiwatcherStore) { 121 all.Update(&multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0"}) 122 all.Update(&multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "1"}) 123 StoreIncRef(all, multiwatcher.EntityId{"machine", "uuid", "0"}) 124 all.Remove(multiwatcher.EntityId{"machine", "uuid", "0"}) 125 all.Update(&multiwatcher.MachineInfo{ 126 EnvUUID: "uuid", 127 Id: "1", 128 InstanceId: "i-1", 129 }) 130 all.Remove(multiwatcher.EntityId{"machine", "uuid", "0"}) 131 }, 132 expectRevno: 4, 133 expectContents: []entityEntry{{ 134 creationRevno: 1, 135 revno: 3, 136 refCount: 1, 137 removed: true, 138 info: &multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0"}, 139 }, { 140 creationRevno: 2, 141 revno: 4, 142 info: &multiwatcher.MachineInfo{ 143 EnvUUID: "uuid", 144 Id: "1", 145 InstanceId: "i-1", 146 }, 147 }}, 148 }, { 149 about: "mark removed on entry with zero ref count", 150 change: func(all *multiwatcherStore) { 151 all.Update(&multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0"}) 152 all.Remove(multiwatcher.EntityId{"machine", "uuid", "0"}) 153 }, 154 expectRevno: 2, 155 }, { 156 about: "delete entry", 157 change: func(all *multiwatcherStore) { 158 all.Update(&multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0"}) 159 all.delete(multiwatcher.EntityId{"machine", "uuid", "0"}) 160 }, 161 expectRevno: 1, 162 }, { 163 about: "decref of non-removed entity", 164 change: func(all *multiwatcherStore) { 165 m := &multiwatcher.MachineInfo{Id: "0"} 166 all.Update(m) 167 id := m.EntityId() 168 StoreIncRef(all, id) 169 entry := all.entities[id].Value.(*entityEntry) 170 all.decRef(entry) 171 }, 172 expectRevno: 1, 173 expectContents: []entityEntry{{ 174 creationRevno: 1, 175 revno: 1, 176 refCount: 0, 177 info: &multiwatcher.MachineInfo{Id: "0"}, 178 }}, 179 }, { 180 about: "decref of removed entity", 181 change: func(all *multiwatcherStore) { 182 m := &multiwatcher.MachineInfo{Id: "0"} 183 all.Update(m) 184 id := m.EntityId() 185 entry := all.entities[id].Value.(*entityEntry) 186 entry.refCount++ 187 all.Remove(id) 188 all.decRef(entry) 189 }, 190 expectRevno: 2, 191 }, 192 } 193 194 func (s *storeSuite) TestStoreChangeMethods(c *gc.C) { 195 for i, test := range StoreChangeMethodTests { 196 all := newStore() 197 c.Logf("test %d. %s", i, test.about) 198 test.change(all) 199 assertStoreContents(c, all, test.expectRevno, test.expectContents) 200 } 201 } 202 203 func (s *storeSuite) TestChangesSince(c *gc.C) { 204 a := newStore() 205 // Add three entries. 206 var deltas []multiwatcher.Delta 207 for i := 0; i < 3; i++ { 208 m := &multiwatcher.MachineInfo{ 209 EnvUUID: "uuid", 210 Id: fmt.Sprint(i), 211 } 212 a.Update(m) 213 deltas = append(deltas, multiwatcher.Delta{Entity: m}) 214 } 215 // Check that the deltas from each revno are as expected. 216 for i := 0; i < 3; i++ { 217 c.Logf("test %d", i) 218 c.Assert(a.ChangesSince(int64(i)), gc.DeepEquals, deltas[i:]) 219 } 220 221 // Check boundary cases. 222 c.Assert(a.ChangesSince(-1), gc.DeepEquals, deltas) 223 c.Assert(a.ChangesSince(99), gc.HasLen, 0) 224 225 // Update one machine and check we see the changes. 226 rev := a.latestRevno 227 m1 := &multiwatcher.MachineInfo{ 228 EnvUUID: "uuid", 229 Id: "1", 230 InstanceId: "foo", 231 } 232 a.Update(m1) 233 c.Assert(a.ChangesSince(rev), gc.DeepEquals, []multiwatcher.Delta{{Entity: m1}}) 234 235 // Make sure the machine isn't simply removed from 236 // the list when it's marked as removed. 237 StoreIncRef(a, multiwatcher.EntityId{"machine", "uuid", "0"}) 238 239 // Remove another machine and check we see it's removed. 240 m0 := &multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0"} 241 a.Remove(m0.EntityId()) 242 243 // Check that something that never saw m0 does not get 244 // informed of its removal (even those the removed entity 245 // is still in the list. 246 c.Assert(a.ChangesSince(0), gc.DeepEquals, []multiwatcher.Delta{{ 247 Entity: &multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "2"}, 248 }, { 249 Entity: m1, 250 }}) 251 252 c.Assert(a.ChangesSince(rev), gc.DeepEquals, []multiwatcher.Delta{{ 253 Entity: m1, 254 }, { 255 Removed: true, 256 Entity: m0, 257 }}) 258 259 c.Assert(a.ChangesSince(rev+1), gc.DeepEquals, []multiwatcher.Delta{{ 260 Removed: true, 261 Entity: m0, 262 }}) 263 } 264 265 func (s *storeSuite) TestGet(c *gc.C) { 266 a := newStore() 267 m := &multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0"} 268 a.Update(m) 269 270 c.Assert(a.Get(m.EntityId()), gc.Equals, m) 271 c.Assert(a.Get(multiwatcher.EntityId{"machine", "uuid", "1"}), gc.IsNil) 272 } 273 274 type storeManagerSuite struct { 275 testing.BaseSuite 276 } 277 278 var _ = gc.Suite(&storeManagerSuite{}) 279 280 func (*storeManagerSuite) TestHandle(c *gc.C) { 281 sm := newStoreManagerNoRun(newTestBacking(nil)) 282 283 // Add request from first watcher. 284 w0 := &Multiwatcher{all: sm} 285 req0 := &request{ 286 w: w0, 287 reply: make(chan bool, 1), 288 } 289 sm.handle(req0) 290 assertWaitingRequests(c, sm, map[*Multiwatcher][]*request{ 291 w0: {req0}, 292 }) 293 294 // Add second request from first watcher. 295 req1 := &request{ 296 w: w0, 297 reply: make(chan bool, 1), 298 } 299 sm.handle(req1) 300 assertWaitingRequests(c, sm, map[*Multiwatcher][]*request{ 301 w0: {req1, req0}, 302 }) 303 304 // Add request from second watcher. 305 w1 := &Multiwatcher{all: sm} 306 req2 := &request{ 307 w: w1, 308 reply: make(chan bool, 1), 309 } 310 sm.handle(req2) 311 assertWaitingRequests(c, sm, map[*Multiwatcher][]*request{ 312 w0: {req1, req0}, 313 w1: {req2}, 314 }) 315 316 // Stop first watcher. 317 sm.handle(&request{ 318 w: w0, 319 }) 320 assertWaitingRequests(c, sm, map[*Multiwatcher][]*request{ 321 w1: {req2}, 322 }) 323 assertReplied(c, false, req0) 324 assertReplied(c, false, req1) 325 326 // Stop second watcher. 327 sm.handle(&request{ 328 w: w1, 329 }) 330 assertWaitingRequests(c, sm, nil) 331 assertReplied(c, false, req2) 332 } 333 334 func (s *storeManagerSuite) TestHandleStopNoDecRefIfMoreRecentlyCreated(c *gc.C) { 335 // If the Multiwatcher hasn't seen the item, then we shouldn't 336 // decrement its ref count when it is stopped. 337 sm := newStoreManager(newTestBacking(nil)) 338 mi := &multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0"} 339 sm.all.Update(mi) 340 StoreIncRef(sm.all, multiwatcher.EntityId{"machine", "uuid", "0"}) 341 w := &Multiwatcher{all: sm} 342 343 // Stop the watcher. 344 sm.handle(&request{w: w}) 345 assertStoreContents(c, sm.all, 1, []entityEntry{{ 346 creationRevno: 1, 347 revno: 1, 348 refCount: 1, 349 info: mi, 350 }}) 351 } 352 353 func (s *storeManagerSuite) TestHandleStopNoDecRefIfAlreadySeenRemoved(c *gc.C) { 354 // If the Multiwatcher has already seen the item removed, then 355 // we shouldn't decrement its ref count when it is stopped. 356 357 sm := newStoreManager(newTestBacking(nil)) 358 mi := &multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0"} 359 sm.all.Update(mi) 360 361 id := multiwatcher.EntityId{"machine", "uuid", "0"} 362 StoreIncRef(sm.all, id) 363 sm.all.Remove(id) 364 365 w := &Multiwatcher{all: sm} 366 // Stop the watcher. 367 sm.handle(&request{w: w}) 368 assertStoreContents(c, sm.all, 2, []entityEntry{{ 369 creationRevno: 1, 370 revno: 2, 371 refCount: 1, 372 removed: true, 373 info: mi, 374 }}) 375 } 376 377 func (s *storeManagerSuite) TestHandleStopDecRefIfAlreadySeenAndNotRemoved(c *gc.C) { 378 // If the Multiwatcher has already seen the item removed, then 379 // we should decrement its ref count when it is stopped. 380 sm := newStoreManager(newTestBacking(nil)) 381 mi := &multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0"} 382 sm.all.Update(mi) 383 StoreIncRef(sm.all, multiwatcher.EntityId{"machine", "uuid", "0"}) 384 w := &Multiwatcher{all: sm} 385 w.revno = sm.all.latestRevno 386 // Stop the watcher. 387 sm.handle(&request{w: w}) 388 assertStoreContents(c, sm.all, 1, []entityEntry{{ 389 creationRevno: 1, 390 revno: 1, 391 info: mi, 392 }}) 393 } 394 395 func (s *storeManagerSuite) TestHandleStopNoDecRefIfNotSeen(c *gc.C) { 396 // If the Multiwatcher hasn't seen the item at all, it should 397 // leave the ref count untouched. 398 sm := newStoreManager(newTestBacking(nil)) 399 mi := &multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0"} 400 sm.all.Update(mi) 401 StoreIncRef(sm.all, multiwatcher.EntityId{"machine", "uuid", "0"}) 402 w := &Multiwatcher{all: sm} 403 // Stop the watcher. 404 sm.handle(&request{w: w}) 405 assertStoreContents(c, sm.all, 1, []entityEntry{{ 406 creationRevno: 1, 407 revno: 1, 408 refCount: 1, 409 info: mi, 410 }}) 411 } 412 413 var respondTestChanges = [...]func(all *multiwatcherStore){ 414 func(all *multiwatcherStore) { 415 all.Update(&multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0"}) 416 }, 417 func(all *multiwatcherStore) { 418 all.Update(&multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "1"}) 419 }, 420 func(all *multiwatcherStore) { 421 all.Update(&multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "2"}) 422 }, 423 func(all *multiwatcherStore) { 424 all.Remove(multiwatcher.EntityId{"machine", "uuid", "0"}) 425 }, 426 func(all *multiwatcherStore) { 427 all.Update(&multiwatcher.MachineInfo{ 428 EnvUUID: "uuid", 429 Id: "1", 430 InstanceId: "i-1", 431 }) 432 }, 433 func(all *multiwatcherStore) { 434 all.Remove(multiwatcher.EntityId{"machine", "uuid", "1"}) 435 }, 436 } 437 438 var ( 439 respondTestFinalState = []entityEntry{{ 440 creationRevno: 3, 441 revno: 3, 442 info: &multiwatcher.MachineInfo{ 443 EnvUUID: "uuid", 444 Id: "2", 445 }, 446 }} 447 respondTestFinalRevno = int64(len(respondTestChanges)) 448 ) 449 450 func (s *storeManagerSuite) TestRespondResults(c *gc.C) { 451 // We test the response results for a pair of watchers by 452 // interleaving notional Next requests in all possible 453 // combinations after each change in respondTestChanges and 454 // checking that the view of the world as seen by the watchers 455 // matches the actual current state. 456 457 // We decide whether if we make a request for a given 458 // watcher by inspecting a number n - bit i of n determines whether 459 // a request will be responded to after running respondTestChanges[i]. 460 461 numCombinations := 1 << uint(len(respondTestChanges)) 462 const wcount = 2 463 ns := make([]int, wcount) 464 for ns[0] = 0; ns[0] < numCombinations; ns[0]++ { 465 for ns[1] = 0; ns[1] < numCombinations; ns[1]++ { 466 sm := newStoreManagerNoRun(&storeManagerTestBacking{}) 467 c.Logf("test %0*b", len(respondTestChanges), ns) 468 var ( 469 ws []*Multiwatcher 470 wstates []watcherState 471 reqs []*request 472 ) 473 for i := 0; i < wcount; i++ { 474 ws = append(ws, &Multiwatcher{}) 475 wstates = append(wstates, make(watcherState)) 476 reqs = append(reqs, nil) 477 } 478 // Make each change in turn, and make a request for each 479 // watcher if n and respond 480 for i, change := range respondTestChanges { 481 c.Logf("change %d", i) 482 change(sm.all) 483 needRespond := false 484 for wi, n := range ns { 485 if n&(1<<uint(i)) != 0 { 486 needRespond = true 487 if reqs[wi] == nil { 488 reqs[wi] = &request{ 489 w: ws[wi], 490 reply: make(chan bool, 1), 491 } 492 sm.handle(reqs[wi]) 493 } 494 } 495 } 496 if !needRespond { 497 continue 498 } 499 // Check that the expected requests are pending. 500 expectWaiting := make(map[*Multiwatcher][]*request) 501 for wi, w := range ws { 502 if reqs[wi] != nil { 503 expectWaiting[w] = []*request{reqs[wi]} 504 } 505 } 506 assertWaitingRequests(c, sm, expectWaiting) 507 // Actually respond; then check that each watcher with 508 // an outstanding request now has an up to date view 509 // of the world. 510 sm.respond() 511 for wi, req := range reqs { 512 if req == nil { 513 continue 514 } 515 select { 516 case ok := <-req.reply: 517 c.Assert(ok, jc.IsTrue) 518 c.Assert(len(req.changes) > 0, jc.IsTrue) 519 wstates[wi].update(req.changes) 520 reqs[wi] = nil 521 default: 522 } 523 c.Logf("check %d", wi) 524 wstates[wi].check(c, sm.all) 525 } 526 } 527 // Stop the watcher and check that all ref counts end up at zero 528 // and removed objects are deleted. 529 for wi, w := range ws { 530 sm.handle(&request{w: w}) 531 if reqs[wi] != nil { 532 assertReplied(c, false, reqs[wi]) 533 } 534 } 535 assertStoreContents(c, sm.all, respondTestFinalRevno, respondTestFinalState) 536 } 537 } 538 } 539 540 func (*storeManagerSuite) TestRespondMultiple(c *gc.C) { 541 sm := newStoreManager(newTestBacking(nil)) 542 sm.all.Update(&multiwatcher.MachineInfo{Id: "0"}) 543 544 // Add one request and respond. 545 // It should see the above change. 546 w0 := &Multiwatcher{all: sm} 547 req0 := &request{ 548 w: w0, 549 reply: make(chan bool, 1), 550 } 551 sm.handle(req0) 552 sm.respond() 553 assertReplied(c, true, req0) 554 c.Assert(req0.changes, gc.DeepEquals, []multiwatcher.Delta{{Entity: &multiwatcher.MachineInfo{Id: "0"}}}) 555 assertWaitingRequests(c, sm, nil) 556 557 // Add another request from the same watcher and respond. 558 // It should have no reply because nothing has changed. 559 req0 = &request{ 560 w: w0, 561 reply: make(chan bool, 1), 562 } 563 sm.handle(req0) 564 sm.respond() 565 assertNotReplied(c, req0) 566 567 // Add two requests from another watcher and respond. 568 // The request from the first watcher should still not 569 // be replied to, but the later of the two requests from 570 // the second watcher should get a reply. 571 w1 := &Multiwatcher{all: sm} 572 req1 := &request{ 573 w: w1, 574 reply: make(chan bool, 1), 575 } 576 sm.handle(req1) 577 req2 := &request{ 578 w: w1, 579 reply: make(chan bool, 1), 580 } 581 sm.handle(req2) 582 assertWaitingRequests(c, sm, map[*Multiwatcher][]*request{ 583 w0: {req0}, 584 w1: {req2, req1}, 585 }) 586 sm.respond() 587 assertNotReplied(c, req0) 588 assertNotReplied(c, req1) 589 assertReplied(c, true, req2) 590 c.Assert(req2.changes, gc.DeepEquals, []multiwatcher.Delta{{Entity: &multiwatcher.MachineInfo{Id: "0"}}}) 591 assertWaitingRequests(c, sm, map[*Multiwatcher][]*request{ 592 w0: {req0}, 593 w1: {req1}, 594 }) 595 596 // Check that nothing more gets responded to if we call respond again. 597 sm.respond() 598 assertNotReplied(c, req0) 599 assertNotReplied(c, req1) 600 601 // Now make a change and check that both waiting requests 602 // get serviced. 603 sm.all.Update(&multiwatcher.MachineInfo{Id: "1"}) 604 sm.respond() 605 assertReplied(c, true, req0) 606 assertReplied(c, true, req1) 607 assertWaitingRequests(c, sm, nil) 608 609 deltas := []multiwatcher.Delta{{Entity: &multiwatcher.MachineInfo{Id: "1"}}} 610 c.Assert(req0.changes, gc.DeepEquals, deltas) 611 c.Assert(req1.changes, gc.DeepEquals, deltas) 612 } 613 614 func (*storeManagerSuite) TestRunStop(c *gc.C) { 615 sm := newStoreManager(newTestBacking(nil)) 616 w := &Multiwatcher{all: sm} 617 err := sm.Stop() 618 c.Assert(err, jc.ErrorIsNil) 619 d, err := w.Next() 620 c.Assert(err, gc.ErrorMatches, "shared state watcher was stopped") 621 c.Assert(d, gc.HasLen, 0) 622 } 623 624 func (*storeManagerSuite) TestRun(c *gc.C) { 625 b := newTestBacking([]multiwatcher.EntityInfo{ 626 &multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0"}, 627 &multiwatcher.ServiceInfo{EnvUUID: "uuid", Name: "logging"}, 628 &multiwatcher.ServiceInfo{EnvUUID: "uuid", Name: "wordpress"}, 629 }) 630 sm := newStoreManager(b) 631 defer func() { 632 c.Check(sm.Stop(), gc.IsNil) 633 }() 634 w := &Multiwatcher{all: sm} 635 checkNext(c, w, []multiwatcher.Delta{ 636 {Entity: &multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0"}}, 637 {Entity: &multiwatcher.ServiceInfo{EnvUUID: "uuid", Name: "logging"}}, 638 {Entity: &multiwatcher.ServiceInfo{EnvUUID: "uuid", Name: "wordpress"}}, 639 }, "") 640 b.updateEntity(&multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0", InstanceId: "i-0"}) 641 checkNext(c, w, []multiwatcher.Delta{ 642 {Entity: &multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0", InstanceId: "i-0"}}, 643 }, "") 644 b.deleteEntity(multiwatcher.EntityId{"machine", "uuid", "0"}) 645 checkNext(c, w, []multiwatcher.Delta{ 646 {Removed: true, Entity: &multiwatcher.MachineInfo{EnvUUID: "uuid", Id: "0"}}, 647 }, "") 648 } 649 650 func (*storeManagerSuite) TestMultipleEnvironments(c *gc.C) { 651 b := newTestBacking([]multiwatcher.EntityInfo{ 652 &multiwatcher.MachineInfo{EnvUUID: "uuid0", Id: "0"}, 653 &multiwatcher.ServiceInfo{EnvUUID: "uuid0", Name: "logging"}, 654 &multiwatcher.ServiceInfo{EnvUUID: "uuid0", Name: "wordpress"}, 655 &multiwatcher.MachineInfo{EnvUUID: "uuid1", Id: "0"}, 656 &multiwatcher.ServiceInfo{EnvUUID: "uuid1", Name: "logging"}, 657 &multiwatcher.ServiceInfo{EnvUUID: "uuid1", Name: "wordpress"}, 658 &multiwatcher.MachineInfo{EnvUUID: "uuid2", Id: "0"}, 659 }) 660 sm := newStoreManager(b) 661 defer func() { 662 c.Check(sm.Stop(), gc.IsNil) 663 }() 664 w := &Multiwatcher{all: sm} 665 checkNext(c, w, []multiwatcher.Delta{ 666 {Entity: &multiwatcher.MachineInfo{EnvUUID: "uuid0", Id: "0"}}, 667 {Entity: &multiwatcher.ServiceInfo{EnvUUID: "uuid0", Name: "logging"}}, 668 {Entity: &multiwatcher.ServiceInfo{EnvUUID: "uuid0", Name: "wordpress"}}, 669 {Entity: &multiwatcher.MachineInfo{EnvUUID: "uuid1", Id: "0"}}, 670 {Entity: &multiwatcher.ServiceInfo{EnvUUID: "uuid1", Name: "logging"}}, 671 {Entity: &multiwatcher.ServiceInfo{EnvUUID: "uuid1", Name: "wordpress"}}, 672 {Entity: &multiwatcher.MachineInfo{EnvUUID: "uuid2", Id: "0"}}, 673 }, "") 674 b.updateEntity(&multiwatcher.MachineInfo{EnvUUID: "uuid1", Id: "0", InstanceId: "i-0"}) 675 checkNext(c, w, []multiwatcher.Delta{ 676 {Entity: &multiwatcher.MachineInfo{EnvUUID: "uuid1", Id: "0", InstanceId: "i-0"}}, 677 }, "") 678 b.deleteEntity(multiwatcher.EntityId{"machine", "uuid2", "0"}) 679 checkNext(c, w, []multiwatcher.Delta{ 680 {Removed: true, Entity: &multiwatcher.MachineInfo{EnvUUID: "uuid2", Id: "0"}}, 681 }, "") 682 b.updateEntity(&multiwatcher.ServiceInfo{EnvUUID: "uuid0", Name: "logging", Exposed: true}) 683 checkNext(c, w, []multiwatcher.Delta{ 684 {Entity: &multiwatcher.ServiceInfo{EnvUUID: "uuid0", Name: "logging", Exposed: true}}, 685 }, "") 686 } 687 688 func (*storeManagerSuite) TestMultiwatcherStop(c *gc.C) { 689 sm := newStoreManager(newTestBacking(nil)) 690 defer func() { 691 c.Check(sm.Stop(), gc.IsNil) 692 }() 693 w := &Multiwatcher{all: sm} 694 done := make(chan struct{}) 695 go func() { 696 checkNext(c, w, nil, ErrStopped.Error()) 697 done <- struct{}{} 698 }() 699 err := w.Stop() 700 c.Assert(err, jc.ErrorIsNil) 701 <-done 702 } 703 704 func (*storeManagerSuite) TestMultiwatcherStopBecauseStoreManagerError(c *gc.C) { 705 b := newTestBacking([]multiwatcher.EntityInfo{&multiwatcher.MachineInfo{Id: "0"}}) 706 sm := newStoreManager(b) 707 defer func() { 708 c.Check(sm.Stop(), gc.ErrorMatches, "some error") 709 }() 710 w := &Multiwatcher{all: sm} 711 // Receive one delta to make sure that the storeManager 712 // has seen the initial state. 713 checkNext(c, w, []multiwatcher.Delta{{Entity: &multiwatcher.MachineInfo{Id: "0"}}}, "") 714 c.Logf("setting fetch error") 715 b.setFetchError(errors.New("some error")) 716 c.Logf("updating entity") 717 b.updateEntity(&multiwatcher.MachineInfo{Id: "1"}) 718 checkNext(c, w, nil, "some error") 719 } 720 721 func StoreIncRef(a *multiwatcherStore, id interface{}) { 722 entry := a.entities[id].Value.(*entityEntry) 723 entry.refCount++ 724 } 725 726 func assertStoreContents(c *gc.C, a *multiwatcherStore, latestRevno int64, entries []entityEntry) { 727 var gotEntries []entityEntry 728 var gotElems []*list.Element 729 c.Check(a.list.Len(), gc.Equals, len(entries)) 730 for e := a.list.Back(); e != nil; e = e.Prev() { 731 gotEntries = append(gotEntries, *e.Value.(*entityEntry)) 732 gotElems = append(gotElems, e) 733 } 734 c.Assert(gotEntries, gc.DeepEquals, entries) 735 for i, ent := range entries { 736 c.Assert(a.entities[ent.info.EntityId()], gc.Equals, gotElems[i]) 737 } 738 c.Assert(a.entities, gc.HasLen, len(entries)) 739 c.Assert(a.latestRevno, gc.Equals, latestRevno) 740 } 741 742 // watcherState represents a Multiwatcher client's 743 // current view of the state. It holds the last delta that a given 744 // state watcher has seen for each entity. 745 type watcherState map[interface{}]multiwatcher.Delta 746 747 func (s watcherState) update(changes []multiwatcher.Delta) { 748 for _, d := range changes { 749 id := d.Entity.EntityId() 750 if d.Removed { 751 if _, ok := s[id]; !ok { 752 panic(fmt.Errorf("entity id %v removed when it wasn't there", id)) 753 } 754 delete(s, id) 755 } else { 756 s[id] = d 757 } 758 } 759 } 760 761 // check checks that the watcher state matches that 762 // held in current. 763 func (s watcherState) check(c *gc.C, current *multiwatcherStore) { 764 currentEntities := make(watcherState) 765 for id, elem := range current.entities { 766 entry := elem.Value.(*entityEntry) 767 if !entry.removed { 768 currentEntities[id] = multiwatcher.Delta{Entity: entry.info} 769 } 770 } 771 c.Assert(s, gc.DeepEquals, currentEntities) 772 } 773 774 func assertNotReplied(c *gc.C, req *request) { 775 select { 776 case v := <-req.reply: 777 c.Fatalf("request was unexpectedly replied to (got %v)", v) 778 default: 779 } 780 } 781 782 func assertReplied(c *gc.C, val bool, req *request) { 783 select { 784 case v := <-req.reply: 785 c.Assert(v, gc.Equals, val) 786 default: 787 c.Fatalf("request was not replied to") 788 } 789 } 790 791 func assertWaitingRequests(c *gc.C, sm *storeManager, waiting map[*Multiwatcher][]*request) { 792 c.Assert(sm.waiting, gc.HasLen, len(waiting)) 793 for w, reqs := range waiting { 794 i := 0 795 for req := sm.waiting[w]; ; req = req.next { 796 if i >= len(reqs) { 797 c.Assert(req, gc.IsNil) 798 break 799 } 800 c.Assert(req, gc.Equals, reqs[i]) 801 assertNotReplied(c, req) 802 i++ 803 } 804 } 805 } 806 807 type storeManagerTestBacking struct { 808 mu sync.Mutex 809 fetchErr error 810 entities map[multiwatcher.EntityId]multiwatcher.EntityInfo 811 watchc chan<- watcher.Change 812 txnRevno int64 813 } 814 815 func newTestBacking(initial []multiwatcher.EntityInfo) *storeManagerTestBacking { 816 b := &storeManagerTestBacking{ 817 entities: make(map[multiwatcher.EntityId]multiwatcher.EntityInfo), 818 } 819 for _, info := range initial { 820 b.entities[info.EntityId()] = info 821 } 822 return b 823 } 824 825 func (b *storeManagerTestBacking) Changed(all *multiwatcherStore, change watcher.Change) error { 826 envUUID, changeId, ok := splitDocID(change.Id.(string)) 827 if !ok { 828 return errors.Errorf("unexpected id format: %v", change.Id) 829 } 830 id := multiwatcher.EntityId{ 831 Kind: change.C, 832 EnvUUID: envUUID, 833 Id: changeId, 834 } 835 info, err := b.fetch(id) 836 if err == mgo.ErrNotFound { 837 all.Remove(id) 838 return nil 839 } 840 if err != nil { 841 return err 842 } 843 all.Update(info) 844 return nil 845 } 846 847 func (b *storeManagerTestBacking) fetch(id multiwatcher.EntityId) (multiwatcher.EntityInfo, error) { 848 b.mu.Lock() 849 defer b.mu.Unlock() 850 if b.fetchErr != nil { 851 return nil, b.fetchErr 852 } 853 if info, ok := b.entities[id]; ok { 854 return info, nil 855 } 856 return nil, mgo.ErrNotFound 857 } 858 859 func (b *storeManagerTestBacking) Watch(c chan<- watcher.Change) { 860 b.mu.Lock() 861 defer b.mu.Unlock() 862 if b.watchc != nil { 863 panic("test backing can only watch once") 864 } 865 b.watchc = c 866 } 867 868 func (b *storeManagerTestBacking) Unwatch(c chan<- watcher.Change) { 869 b.mu.Lock() 870 defer b.mu.Unlock() 871 if c != b.watchc { 872 panic("unwatching wrong channel") 873 } 874 b.watchc = nil 875 } 876 877 func (b *storeManagerTestBacking) GetAll(all *multiwatcherStore) error { 878 b.mu.Lock() 879 defer b.mu.Unlock() 880 for _, info := range b.entities { 881 all.Update(info) 882 } 883 return nil 884 } 885 886 func (b *storeManagerTestBacking) Release() error { 887 return nil 888 } 889 890 func (b *storeManagerTestBacking) updateEntity(info multiwatcher.EntityInfo) { 891 b.mu.Lock() 892 defer b.mu.Unlock() 893 id := info.EntityId() 894 b.entities[id] = info 895 b.txnRevno++ 896 if b.watchc != nil { 897 b.watchc <- watcher.Change{ 898 C: id.Kind, 899 Id: ensureEnvUUID(id.EnvUUID, id.Id), 900 Revno: b.txnRevno, // This is actually ignored, but fill it in anyway. 901 } 902 } 903 } 904 905 func (b *storeManagerTestBacking) setFetchError(err error) { 906 b.mu.Lock() 907 defer b.mu.Unlock() 908 b.fetchErr = err 909 } 910 911 func (b *storeManagerTestBacking) deleteEntity(id multiwatcher.EntityId) { 912 b.mu.Lock() 913 defer b.mu.Unlock() 914 delete(b.entities, id) 915 b.txnRevno++ 916 if b.watchc != nil { 917 b.watchc <- watcher.Change{ 918 C: id.Kind, 919 Id: ensureEnvUUID(id.EnvUUID, id.Id), 920 Revno: -1, 921 } 922 } 923 } 924 925 var errTimeout = errors.New("no change received in sufficient time") 926 927 func getNext(c *gc.C, w *Multiwatcher, timeout time.Duration) ([]multiwatcher.Delta, error) { 928 var deltas []multiwatcher.Delta 929 var err error 930 ch := make(chan struct{}, 1) 931 go func() { 932 deltas, err = w.Next() 933 ch <- struct{}{} 934 }() 935 select { 936 case <-ch: 937 return deltas, err 938 case <-time.After(timeout): 939 } 940 return nil, errTimeout 941 } 942 943 func checkNext(c *gc.C, w *Multiwatcher, deltas []multiwatcher.Delta, expectErr string) { 944 d, err := getNext(c, w, 1*time.Second) 945 if expectErr != "" { 946 c.Check(err, gc.ErrorMatches, expectErr) 947 return 948 } 949 c.Assert(err, jc.ErrorIsNil) 950 checkDeltasEqual(c, d, deltas) 951 }