github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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{ModelUUID: "uuid", Id: "0"}) 97 all.Update(&multiwatcher.MachineInfo{ModelUUID: "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{ModelUUID: "uuid", Id: "1"}, 106 }, { 107 creationRevno: 1, 108 revno: 3, 109 refCount: 1, 110 removed: true, 111 info: &multiwatcher.MachineInfo{ModelUUID: "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{ModelUUID: "uuid", Id: "0"}) 122 all.Update(&multiwatcher.MachineInfo{ModelUUID: "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 ModelUUID: "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{ModelUUID: "uuid", Id: "0"}, 139 }, { 140 creationRevno: 2, 141 revno: 4, 142 info: &multiwatcher.MachineInfo{ 143 ModelUUID: "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{ModelUUID: "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{ModelUUID: "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 ModelUUID: "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 ModelUUID: "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{ModelUUID: "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{ModelUUID: "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{ModelUUID: "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{ModelUUID: "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{ModelUUID: "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{ModelUUID: "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{ModelUUID: "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{ModelUUID: "uuid", Id: "0"}) 416 }, 417 func(all *multiwatcherStore) { 418 all.Update(&multiwatcher.MachineInfo{ModelUUID: "uuid", Id: "1"}) 419 }, 420 func(all *multiwatcherStore) { 421 all.Update(&multiwatcher.MachineInfo{ModelUUID: "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 ModelUUID: "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 ModelUUID: "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{ModelUUID: "uuid", Id: "0"}, 627 &multiwatcher.ServiceInfo{ModelUUID: "uuid", Name: "logging"}, 628 &multiwatcher.ServiceInfo{ModelUUID: "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{ModelUUID: "uuid", Id: "0"}}, 637 {Entity: &multiwatcher.ServiceInfo{ModelUUID: "uuid", Name: "logging"}}, 638 {Entity: &multiwatcher.ServiceInfo{ModelUUID: "uuid", Name: "wordpress"}}, 639 }, "") 640 b.updateEntity(&multiwatcher.MachineInfo{ModelUUID: "uuid", Id: "0", InstanceId: "i-0"}) 641 checkNext(c, w, []multiwatcher.Delta{ 642 {Entity: &multiwatcher.MachineInfo{ModelUUID: "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{ModelUUID: "uuid", Id: "0"}}, 647 }, "") 648 } 649 650 func (*storeManagerSuite) TestEmptyModel(c *gc.C) { 651 b := newTestBacking(nil) 652 sm := newStoreManager(b) 653 defer func() { 654 c.Check(sm.Stop(), gc.IsNil) 655 }() 656 w := &Multiwatcher{all: sm} 657 checkNext(c, w, nil, "") 658 } 659 660 func (*storeManagerSuite) TestMultipleEnvironments(c *gc.C) { 661 b := newTestBacking([]multiwatcher.EntityInfo{ 662 &multiwatcher.MachineInfo{ModelUUID: "uuid0", Id: "0"}, 663 &multiwatcher.ServiceInfo{ModelUUID: "uuid0", Name: "logging"}, 664 &multiwatcher.ServiceInfo{ModelUUID: "uuid0", Name: "wordpress"}, 665 &multiwatcher.MachineInfo{ModelUUID: "uuid1", Id: "0"}, 666 &multiwatcher.ServiceInfo{ModelUUID: "uuid1", Name: "logging"}, 667 &multiwatcher.ServiceInfo{ModelUUID: "uuid1", Name: "wordpress"}, 668 &multiwatcher.MachineInfo{ModelUUID: "uuid2", Id: "0"}, 669 }) 670 sm := newStoreManager(b) 671 defer func() { 672 c.Check(sm.Stop(), gc.IsNil) 673 }() 674 w := &Multiwatcher{all: sm} 675 checkNext(c, w, []multiwatcher.Delta{ 676 {Entity: &multiwatcher.MachineInfo{ModelUUID: "uuid0", Id: "0"}}, 677 {Entity: &multiwatcher.ServiceInfo{ModelUUID: "uuid0", Name: "logging"}}, 678 {Entity: &multiwatcher.ServiceInfo{ModelUUID: "uuid0", Name: "wordpress"}}, 679 {Entity: &multiwatcher.MachineInfo{ModelUUID: "uuid1", Id: "0"}}, 680 {Entity: &multiwatcher.ServiceInfo{ModelUUID: "uuid1", Name: "logging"}}, 681 {Entity: &multiwatcher.ServiceInfo{ModelUUID: "uuid1", Name: "wordpress"}}, 682 {Entity: &multiwatcher.MachineInfo{ModelUUID: "uuid2", Id: "0"}}, 683 }, "") 684 b.updateEntity(&multiwatcher.MachineInfo{ModelUUID: "uuid1", Id: "0", InstanceId: "i-0"}) 685 checkNext(c, w, []multiwatcher.Delta{ 686 {Entity: &multiwatcher.MachineInfo{ModelUUID: "uuid1", Id: "0", InstanceId: "i-0"}}, 687 }, "") 688 b.deleteEntity(multiwatcher.EntityId{"machine", "uuid2", "0"}) 689 checkNext(c, w, []multiwatcher.Delta{ 690 {Removed: true, Entity: &multiwatcher.MachineInfo{ModelUUID: "uuid2", Id: "0"}}, 691 }, "") 692 b.updateEntity(&multiwatcher.ServiceInfo{ModelUUID: "uuid0", Name: "logging", Exposed: true}) 693 checkNext(c, w, []multiwatcher.Delta{ 694 {Entity: &multiwatcher.ServiceInfo{ModelUUID: "uuid0", Name: "logging", Exposed: true}}, 695 }, "") 696 } 697 698 func (*storeManagerSuite) TestMultiwatcherStop(c *gc.C) { 699 sm := newStoreManager(newTestBacking(nil)) 700 defer func() { 701 c.Check(sm.Stop(), gc.IsNil) 702 }() 703 w := &Multiwatcher{all: sm} 704 done := make(chan struct{}) 705 go func() { 706 checkNext(c, w, nil, ErrStopped.Error()) 707 done <- struct{}{} 708 }() 709 err := w.Stop() 710 c.Assert(err, jc.ErrorIsNil) 711 <-done 712 } 713 714 func (*storeManagerSuite) TestMultiwatcherStopBecauseStoreManagerError(c *gc.C) { 715 b := newTestBacking([]multiwatcher.EntityInfo{&multiwatcher.MachineInfo{Id: "0"}}) 716 sm := newStoreManager(b) 717 defer func() { 718 c.Check(sm.Stop(), gc.ErrorMatches, "some error") 719 }() 720 w := &Multiwatcher{all: sm} 721 // Receive one delta to make sure that the storeManager 722 // has seen the initial state. 723 checkNext(c, w, []multiwatcher.Delta{{Entity: &multiwatcher.MachineInfo{Id: "0"}}}, "") 724 c.Logf("setting fetch error") 725 b.setFetchError(errors.New("some error")) 726 c.Logf("updating entity") 727 b.updateEntity(&multiwatcher.MachineInfo{Id: "1"}) 728 checkNext(c, w, nil, `shared state watcher was stopped`) 729 } 730 731 func StoreIncRef(a *multiwatcherStore, id interface{}) { 732 entry := a.entities[id].Value.(*entityEntry) 733 entry.refCount++ 734 } 735 736 func assertStoreContents(c *gc.C, a *multiwatcherStore, latestRevno int64, entries []entityEntry) { 737 var gotEntries []entityEntry 738 var gotElems []*list.Element 739 c.Check(a.list.Len(), gc.Equals, len(entries)) 740 for e := a.list.Back(); e != nil; e = e.Prev() { 741 gotEntries = append(gotEntries, *e.Value.(*entityEntry)) 742 gotElems = append(gotElems, e) 743 } 744 c.Assert(gotEntries, gc.DeepEquals, entries) 745 for i, ent := range entries { 746 c.Assert(a.entities[ent.info.EntityId()], gc.Equals, gotElems[i]) 747 } 748 c.Assert(a.entities, gc.HasLen, len(entries)) 749 c.Assert(a.latestRevno, gc.Equals, latestRevno) 750 } 751 752 // watcherState represents a Multiwatcher client's 753 // current view of the state. It holds the last delta that a given 754 // state watcher has seen for each entity. 755 type watcherState map[interface{}]multiwatcher.Delta 756 757 func (s watcherState) update(changes []multiwatcher.Delta) { 758 for _, d := range changes { 759 id := d.Entity.EntityId() 760 if d.Removed { 761 if _, ok := s[id]; !ok { 762 panic(fmt.Errorf("entity id %v removed when it wasn't there", id)) 763 } 764 delete(s, id) 765 } else { 766 s[id] = d 767 } 768 } 769 } 770 771 // check checks that the watcher state matches that 772 // held in current. 773 func (s watcherState) check(c *gc.C, current *multiwatcherStore) { 774 currentEntities := make(watcherState) 775 for id, elem := range current.entities { 776 entry := elem.Value.(*entityEntry) 777 if !entry.removed { 778 currentEntities[id] = multiwatcher.Delta{Entity: entry.info} 779 } 780 } 781 c.Assert(s, gc.DeepEquals, currentEntities) 782 } 783 784 func assertNotReplied(c *gc.C, req *request) { 785 select { 786 case v := <-req.reply: 787 c.Fatalf("request was unexpectedly replied to (got %v)", v) 788 default: 789 } 790 } 791 792 func assertReplied(c *gc.C, val bool, req *request) { 793 select { 794 case v := <-req.reply: 795 c.Assert(v, gc.Equals, val) 796 default: 797 c.Fatalf("request was not replied to") 798 } 799 } 800 801 func assertWaitingRequests(c *gc.C, sm *storeManager, waiting map[*Multiwatcher][]*request) { 802 c.Assert(sm.waiting, gc.HasLen, len(waiting)) 803 for w, reqs := range waiting { 804 i := 0 805 for req := sm.waiting[w]; ; req = req.next { 806 if i >= len(reqs) { 807 c.Assert(req, gc.IsNil) 808 break 809 } 810 c.Assert(req, gc.Equals, reqs[i]) 811 assertNotReplied(c, req) 812 i++ 813 } 814 } 815 } 816 817 type storeManagerTestBacking struct { 818 mu sync.Mutex 819 fetchErr error 820 entities map[multiwatcher.EntityId]multiwatcher.EntityInfo 821 watchc chan<- watcher.Change 822 txnRevno int64 823 } 824 825 func newTestBacking(initial []multiwatcher.EntityInfo) *storeManagerTestBacking { 826 b := &storeManagerTestBacking{ 827 entities: make(map[multiwatcher.EntityId]multiwatcher.EntityInfo), 828 } 829 for _, info := range initial { 830 b.entities[info.EntityId()] = info 831 } 832 return b 833 } 834 835 func (b *storeManagerTestBacking) Changed(all *multiwatcherStore, change watcher.Change) error { 836 modelUUID, changeId, ok := splitDocID(change.Id.(string)) 837 if !ok { 838 return errors.Errorf("unexpected id format: %v", change.Id) 839 } 840 id := multiwatcher.EntityId{ 841 Kind: change.C, 842 ModelUUID: modelUUID, 843 Id: changeId, 844 } 845 info, err := b.fetch(id) 846 if err == mgo.ErrNotFound { 847 all.Remove(id) 848 return nil 849 } 850 if err != nil { 851 return err 852 } 853 all.Update(info) 854 return nil 855 } 856 857 func (b *storeManagerTestBacking) fetch(id multiwatcher.EntityId) (multiwatcher.EntityInfo, error) { 858 b.mu.Lock() 859 defer b.mu.Unlock() 860 if b.fetchErr != nil { 861 return nil, b.fetchErr 862 } 863 if info, ok := b.entities[id]; ok { 864 return info, nil 865 } 866 return nil, mgo.ErrNotFound 867 } 868 869 func (b *storeManagerTestBacking) Watch(c chan<- watcher.Change) { 870 b.mu.Lock() 871 defer b.mu.Unlock() 872 if b.watchc != nil { 873 panic("test backing can only watch once") 874 } 875 b.watchc = c 876 } 877 878 func (b *storeManagerTestBacking) Unwatch(c chan<- watcher.Change) { 879 b.mu.Lock() 880 defer b.mu.Unlock() 881 if c != b.watchc { 882 panic("unwatching wrong channel") 883 } 884 b.watchc = nil 885 } 886 887 func (b *storeManagerTestBacking) GetAll(all *multiwatcherStore) error { 888 b.mu.Lock() 889 defer b.mu.Unlock() 890 for _, info := range b.entities { 891 all.Update(info) 892 } 893 return nil 894 } 895 896 func (b *storeManagerTestBacking) Release() error { 897 return nil 898 } 899 900 func (b *storeManagerTestBacking) updateEntity(info multiwatcher.EntityInfo) { 901 b.mu.Lock() 902 defer b.mu.Unlock() 903 id := info.EntityId() 904 b.entities[id] = info 905 b.txnRevno++ 906 if b.watchc != nil { 907 b.watchc <- watcher.Change{ 908 C: id.Kind, 909 Id: ensureModelUUID(id.ModelUUID, id.Id), 910 Revno: b.txnRevno, // This is actually ignored, but fill it in anyway. 911 } 912 } 913 } 914 915 func (b *storeManagerTestBacking) setFetchError(err error) { 916 b.mu.Lock() 917 defer b.mu.Unlock() 918 b.fetchErr = err 919 } 920 921 func (b *storeManagerTestBacking) deleteEntity(id multiwatcher.EntityId) { 922 b.mu.Lock() 923 defer b.mu.Unlock() 924 delete(b.entities, id) 925 b.txnRevno++ 926 if b.watchc != nil { 927 b.watchc <- watcher.Change{ 928 C: id.Kind, 929 Id: ensureModelUUID(id.ModelUUID, id.Id), 930 Revno: -1, 931 } 932 } 933 } 934 935 var errTimeout = errors.New("no change received in sufficient time") 936 937 func getNext(c *gc.C, w *Multiwatcher, timeout time.Duration) ([]multiwatcher.Delta, error) { 938 var deltas []multiwatcher.Delta 939 var err error 940 ch := make(chan struct{}, 1) 941 go func() { 942 deltas, err = w.Next() 943 ch <- struct{}{} 944 }() 945 select { 946 case <-ch: 947 return deltas, err 948 case <-time.After(timeout): 949 } 950 return nil, errTimeout 951 } 952 953 func checkNext(c *gc.C, w *Multiwatcher, deltas []multiwatcher.Delta, expectErr string) { 954 d, err := getNext(c, w, 1*time.Second) 955 if expectErr != "" { 956 c.Check(err, gc.ErrorMatches, expectErr) 957 return 958 } 959 c.Assert(err, jc.ErrorIsNil) 960 checkDeltasEqual(c, d, deltas) 961 }