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