github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/allwatcher_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 "fmt" 8 "sort" 9 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/names" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/utils" 16 gc "gopkg.in/check.v1" 17 "gopkg.in/juju/charm.v6-unstable" 18 19 "github.com/juju/juju/constraints" 20 "github.com/juju/juju/instance" 21 "github.com/juju/juju/network" 22 "github.com/juju/juju/state/multiwatcher" 23 "github.com/juju/juju/state/watcher" 24 "github.com/juju/juju/status" 25 "github.com/juju/juju/testing" 26 ) 27 28 var ( 29 _ backingEntityDoc = (*backingMachine)(nil) 30 _ backingEntityDoc = (*backingUnit)(nil) 31 _ backingEntityDoc = (*backingService)(nil) 32 _ backingEntityDoc = (*backingRelation)(nil) 33 _ backingEntityDoc = (*backingAnnotation)(nil) 34 _ backingEntityDoc = (*backingStatus)(nil) 35 _ backingEntityDoc = (*backingConstraints)(nil) 36 _ backingEntityDoc = (*backingSettings)(nil) 37 _ backingEntityDoc = (*backingOpenedPorts)(nil) 38 _ backingEntityDoc = (*backingAction)(nil) 39 _ backingEntityDoc = (*backingBlock)(nil) 40 ) 41 42 var dottedConfig = ` 43 options: 44 key.dotted: {default: My Key, description: Desc, type: string} 45 ` 46 47 type allWatcherBaseSuite struct { 48 internalStateSuite 49 envCount int 50 } 51 52 func (s *allWatcherBaseSuite) newState(c *gc.C) *State { 53 s.envCount++ 54 cfg := testing.CustomModelConfig(c, testing.Attrs{ 55 "name": fmt.Sprintf("testenv%d", s.envCount), 56 "uuid": utils.MustNewUUID().String(), 57 }) 58 _, st, err := s.state.NewModel(ModelArgs{Config: cfg, Owner: s.owner}) 59 c.Assert(err, jc.ErrorIsNil) 60 s.AddCleanup(func(*gc.C) { st.Close() }) 61 return st 62 } 63 64 // setUpScenario adds some entities to the state so that 65 // we can check that they all get pulled in by 66 // all(Env)WatcherStateBacking.GetAll. 67 func (s *allWatcherBaseSuite) setUpScenario(c *gc.C, st *State, units int) (entities entityInfoSlice) { 68 modelUUID := st.ModelUUID() 69 add := func(e multiwatcher.EntityInfo) { 70 entities = append(entities, e) 71 } 72 m, err := st.AddMachine("quantal", JobHostUnits) 73 c.Assert(err, jc.ErrorIsNil) 74 c.Assert(m.Tag(), gc.Equals, names.NewMachineTag("0")) 75 err = m.SetHasVote(true) 76 c.Assert(err, jc.ErrorIsNil) 77 // TODO(dfc) instance.Id should take a TAG! 78 err = m.SetProvisioned(instance.Id("i-"+m.Tag().String()), "fake_nonce", nil) 79 c.Assert(err, jc.ErrorIsNil) 80 hc, err := m.HardwareCharacteristics() 81 c.Assert(err, jc.ErrorIsNil) 82 err = m.SetProviderAddresses(network.NewAddress("example.com")) 83 c.Assert(err, jc.ErrorIsNil) 84 add(&multiwatcher.MachineInfo{ 85 ModelUUID: modelUUID, 86 Id: "0", 87 InstanceId: "i-machine-0", 88 JujuStatus: multiwatcher.StatusInfo{ 89 Current: status.StatusPending, 90 Data: map[string]interface{}{}, 91 }, 92 MachineStatus: multiwatcher.StatusInfo{ 93 Current: status.StatusPending, 94 Data: map[string]interface{}{}, 95 }, 96 Life: multiwatcher.Life("alive"), 97 Series: "quantal", 98 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 99 Addresses: m.Addresses(), 100 HardwareCharacteristics: hc, 101 HasVote: true, 102 WantsVote: false, 103 }) 104 105 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 106 err = wordpress.SetExposed() 107 c.Assert(err, jc.ErrorIsNil) 108 err = wordpress.SetMinUnits(units) 109 c.Assert(err, jc.ErrorIsNil) 110 err = wordpress.SetConstraints(constraints.MustParse("mem=100M")) 111 c.Assert(err, jc.ErrorIsNil) 112 setServiceConfigAttr(c, wordpress, "blog-title", "boring") 113 add(&multiwatcher.ServiceInfo{ 114 ModelUUID: modelUUID, 115 Name: "wordpress", 116 Exposed: true, 117 CharmURL: serviceCharmURL(wordpress).String(), 118 OwnerTag: s.owner.String(), 119 Life: multiwatcher.Life("alive"), 120 MinUnits: units, 121 Constraints: constraints.MustParse("mem=100M"), 122 Config: charm.Settings{"blog-title": "boring"}, 123 Subordinate: false, 124 Status: multiwatcher.StatusInfo{ 125 Current: "unknown", 126 Message: "Waiting for agent initialization to finish", 127 Data: map[string]interface{}{}, 128 }, 129 }) 130 pairs := map[string]string{"x": "12", "y": "99"} 131 err = st.SetAnnotations(wordpress, pairs) 132 c.Assert(err, jc.ErrorIsNil) 133 add(&multiwatcher.AnnotationInfo{ 134 ModelUUID: modelUUID, 135 Tag: "service-wordpress", 136 Annotations: pairs, 137 }) 138 139 logging := AddTestingService(c, st, "logging", AddTestingCharm(c, st, "logging"), s.owner) 140 add(&multiwatcher.ServiceInfo{ 141 ModelUUID: modelUUID, 142 Name: "logging", 143 CharmURL: serviceCharmURL(logging).String(), 144 OwnerTag: s.owner.String(), 145 Life: multiwatcher.Life("alive"), 146 Config: charm.Settings{}, 147 Subordinate: true, 148 Status: multiwatcher.StatusInfo{ 149 Current: "unknown", 150 Message: "Waiting for agent initialization to finish", 151 Data: map[string]interface{}{}, 152 }, 153 }) 154 155 eps, err := st.InferEndpoints("logging", "wordpress") 156 c.Assert(err, jc.ErrorIsNil) 157 rel, err := st.AddRelation(eps...) 158 c.Assert(err, jc.ErrorIsNil) 159 add(&multiwatcher.RelationInfo{ 160 ModelUUID: modelUUID, 161 Key: "logging:logging-directory wordpress:logging-dir", 162 Id: rel.Id(), 163 Endpoints: []multiwatcher.Endpoint{ 164 {ServiceName: "logging", Relation: charm.Relation{Name: "logging-directory", Role: "requirer", Interface: "logging", Optional: false, Limit: 1, Scope: "container"}}, 165 {ServiceName: "wordpress", Relation: charm.Relation{Name: "logging-dir", Role: "provider", Interface: "logging", Optional: false, Limit: 0, Scope: "container"}}}, 166 }) 167 168 for i := 0; i < units; i++ { 169 wu, err := wordpress.AddUnit() 170 c.Assert(err, jc.ErrorIsNil) 171 c.Assert(wu.Tag().String(), gc.Equals, fmt.Sprintf("unit-wordpress-%d", i)) 172 173 m, err := st.AddMachine("quantal", JobHostUnits) 174 c.Assert(err, jc.ErrorIsNil) 175 c.Assert(m.Tag().String(), gc.Equals, fmt.Sprintf("machine-%d", i+1)) 176 177 add(&multiwatcher.UnitInfo{ 178 ModelUUID: modelUUID, 179 Name: fmt.Sprintf("wordpress/%d", i), 180 Service: wordpress.Name(), 181 Series: m.Series(), 182 MachineId: m.Id(), 183 Ports: []network.Port{}, 184 Subordinate: false, 185 WorkloadStatus: multiwatcher.StatusInfo{ 186 Current: "unknown", 187 Message: "Waiting for agent initialization to finish", 188 Data: map[string]interface{}{}, 189 }, 190 JujuStatus: multiwatcher.StatusInfo{ 191 Current: "allocating", 192 Message: "", 193 Data: map[string]interface{}{}, 194 }, 195 }) 196 pairs := map[string]string{"name": fmt.Sprintf("bar %d", i)} 197 err = st.SetAnnotations(wu, pairs) 198 c.Assert(err, jc.ErrorIsNil) 199 add(&multiwatcher.AnnotationInfo{ 200 ModelUUID: modelUUID, 201 Tag: fmt.Sprintf("unit-wordpress-%d", i), 202 Annotations: pairs, 203 }) 204 205 err = m.SetProvisioned(instance.Id("i-"+m.Tag().String()), "fake_nonce", nil) 206 c.Assert(err, jc.ErrorIsNil) 207 err = m.SetStatus(status.StatusError, m.Tag().String(), nil) 208 c.Assert(err, jc.ErrorIsNil) 209 hc, err := m.HardwareCharacteristics() 210 c.Assert(err, jc.ErrorIsNil) 211 add(&multiwatcher.MachineInfo{ 212 ModelUUID: modelUUID, 213 Id: fmt.Sprint(i + 1), 214 InstanceId: "i-" + m.Tag().String(), 215 JujuStatus: multiwatcher.StatusInfo{ 216 Current: status.StatusError, 217 Message: m.Tag().String(), 218 Data: map[string]interface{}{}, 219 }, 220 MachineStatus: multiwatcher.StatusInfo{ 221 Current: status.StatusPending, 222 Data: map[string]interface{}{}, 223 }, 224 Life: multiwatcher.Life("alive"), 225 Series: "quantal", 226 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 227 Addresses: []network.Address{}, 228 HardwareCharacteristics: hc, 229 HasVote: false, 230 WantsVote: false, 231 }) 232 err = wu.AssignToMachine(m) 233 c.Assert(err, jc.ErrorIsNil) 234 235 deployer, ok := wu.DeployerTag() 236 c.Assert(ok, jc.IsTrue) 237 c.Assert(deployer, gc.Equals, names.NewMachineTag(fmt.Sprintf("%d", i+1))) 238 239 wru, err := rel.Unit(wu) 240 c.Assert(err, jc.ErrorIsNil) 241 242 // Create the subordinate unit as a side-effect of entering 243 // scope in the principal's relation-unit. 244 err = wru.EnterScope(nil) 245 c.Assert(err, jc.ErrorIsNil) 246 247 lu, err := st.Unit(fmt.Sprintf("logging/%d", i)) 248 c.Assert(err, jc.ErrorIsNil) 249 c.Assert(lu.IsPrincipal(), jc.IsFalse) 250 deployer, ok = lu.DeployerTag() 251 c.Assert(ok, jc.IsTrue) 252 c.Assert(deployer, gc.Equals, names.NewUnitTag(fmt.Sprintf("wordpress/%d", i))) 253 add(&multiwatcher.UnitInfo{ 254 ModelUUID: modelUUID, 255 Name: fmt.Sprintf("logging/%d", i), 256 Service: "logging", 257 Series: "quantal", 258 Ports: []network.Port{}, 259 Subordinate: true, 260 WorkloadStatus: multiwatcher.StatusInfo{ 261 Current: "unknown", 262 Message: "Waiting for agent initialization to finish", 263 Data: map[string]interface{}{}, 264 }, 265 JujuStatus: multiwatcher.StatusInfo{ 266 Current: "allocating", 267 Message: "", 268 Data: map[string]interface{}{}, 269 }, 270 }) 271 } 272 return 273 } 274 275 var _ = gc.Suite(&allWatcherStateSuite{}) 276 277 type allWatcherStateSuite struct { 278 allWatcherBaseSuite 279 } 280 281 func (s *allWatcherStateSuite) Reset(c *gc.C) { 282 s.TearDownTest(c) 283 s.SetUpTest(c) 284 } 285 286 func (s *allWatcherStateSuite) TestGetAll(c *gc.C) { 287 expectEntities := s.setUpScenario(c, s.state, 2) 288 s.checkGetAll(c, expectEntities) 289 } 290 291 func (s *allWatcherStateSuite) TestGetAllMultiEnv(c *gc.C) { 292 // Set up 2 models and ensure that GetAll returns the 293 // entities for the first model with no errors. 294 expectEntities := s.setUpScenario(c, s.state, 2) 295 296 // Use more units in the second env to ensure the number of 297 // entities will mismatch if model filtering isn't in place. 298 s.setUpScenario(c, s.newState(c), 4) 299 300 s.checkGetAll(c, expectEntities) 301 } 302 303 func (s *allWatcherStateSuite) checkGetAll(c *gc.C, expectEntities entityInfoSlice) { 304 b := newAllWatcherStateBacking(s.state) 305 all := newStore() 306 err := b.GetAll(all) 307 c.Assert(err, jc.ErrorIsNil) 308 var gotEntities entityInfoSlice = all.All() 309 sort.Sort(gotEntities) 310 sort.Sort(expectEntities) 311 substNilSinceTimeForEntities(c, gotEntities) 312 assertEntitiesEqual(c, gotEntities, expectEntities) 313 } 314 315 func serviceCharmURL(svc *Service) *charm.URL { 316 url, _ := svc.CharmURL() 317 return url 318 } 319 320 func setServiceConfigAttr(c *gc.C, svc *Service, attr string, val interface{}) { 321 err := svc.UpdateConfigSettings(charm.Settings{attr: val}) 322 c.Assert(err, jc.ErrorIsNil) 323 } 324 325 // changeTestCase encapsulates entities to add, a change, and 326 // the expected contents for a test. 327 type changeTestCase struct { 328 // about describes the test case. 329 about string 330 331 // initialContents contains the infos of the 332 // watcher before signalling the change. 333 initialContents []multiwatcher.EntityInfo 334 335 // change signals the change of the watcher. 336 change watcher.Change 337 338 // expectContents contains the expected infos of 339 // the watcher before signalling the change. 340 expectContents []multiwatcher.EntityInfo 341 } 342 343 func substNilSinceTimeForStatus(c *gc.C, sInfo *multiwatcher.StatusInfo) { 344 if sInfo.Current != "" { 345 c.Assert(sInfo.Since, gc.NotNil) 346 } 347 sInfo.Since = nil 348 } 349 350 func substNilSinceTimeForStatusNoCheck(sInfo *multiwatcher.StatusInfo) { 351 sInfo.Since = nil 352 } 353 354 // substNilSinceTimeForEntities zeros out any updated timestamps for unit 355 // or service status values so we can easily check the results. 356 func substNilSinceTimeForEntities(c *gc.C, entities []multiwatcher.EntityInfo) { 357 // Zero out any updated timestamps for unit or service status values 358 // so we can easily check the results. 359 for i, entity := range entities { 360 if unitInfo, ok := entity.(*multiwatcher.UnitInfo); ok { 361 substNilSinceTimeForStatus(c, &unitInfo.WorkloadStatus) 362 substNilSinceTimeForStatus(c, &unitInfo.JujuStatus) 363 entities[i] = unitInfo 364 } 365 if serviceInfo, ok := entity.(*multiwatcher.ServiceInfo); ok { 366 substNilSinceTimeForStatus(c, &serviceInfo.Status) 367 entities[i] = serviceInfo 368 } 369 if machineInfo, ok := entity.(*multiwatcher.MachineInfo); ok { 370 substNilSinceTimeForStatus(c, &machineInfo.JujuStatus) 371 substNilSinceTimeForStatus(c, &machineInfo.MachineStatus) 372 entities[i] = machineInfo 373 } 374 } 375 } 376 377 func substNilSinceTimeForEntityNoCheck(entity multiwatcher.EntityInfo) multiwatcher.EntityInfo { 378 // Zero out any updated timestamps for unit or service status values 379 // so we can easily check the results. 380 if unitInfo, ok := entity.(*multiwatcher.UnitInfo); ok { 381 substNilSinceTimeForStatusNoCheck(&unitInfo.WorkloadStatus) 382 substNilSinceTimeForStatusNoCheck(&unitInfo.JujuStatus) 383 return unitInfo 384 } 385 if serviceInfo, ok := entity.(*multiwatcher.ServiceInfo); ok { 386 substNilSinceTimeForStatusNoCheck(&serviceInfo.Status) 387 return serviceInfo 388 } 389 if machineInfo, ok := entity.(*multiwatcher.MachineInfo); ok { 390 substNilSinceTimeForStatusNoCheck(&machineInfo.JujuStatus) 391 substNilSinceTimeForStatusNoCheck(&machineInfo.MachineStatus) 392 return machineInfo 393 } 394 return entity 395 } 396 397 // changeTestFunc is a function for the preparation of a test and 398 // the creation of the according case. 399 type changeTestFunc func(c *gc.C, st *State) changeTestCase 400 401 // performChangeTestCases runs a passed number of test cases for changes. 402 func (s *allWatcherStateSuite) performChangeTestCases(c *gc.C, changeTestFuncs []changeTestFunc) { 403 for i, changeTestFunc := range changeTestFuncs { 404 test := changeTestFunc(c, s.state) 405 406 c.Logf("test %d. %s", i, test.about) 407 b := newAllWatcherStateBacking(s.state) 408 all := newStore() 409 for _, info := range test.initialContents { 410 all.Update(info) 411 } 412 err := b.Changed(all, test.change) 413 c.Assert(err, jc.ErrorIsNil) 414 entities := all.All() 415 substNilSinceTimeForEntities(c, entities) 416 assertEntitiesEqual(c, entities, test.expectContents) 417 s.Reset(c) 418 } 419 } 420 421 func (s *allWatcherStateSuite) TestChangeAnnotations(c *gc.C) { 422 testChangeAnnotations(c, s.performChangeTestCases) 423 } 424 425 func (s *allWatcherStateSuite) TestChangeMachines(c *gc.C) { 426 testChangeMachines(c, s.performChangeTestCases) 427 } 428 429 func (s *allWatcherStateSuite) TestChangeRelations(c *gc.C) { 430 testChangeRelations(c, s.owner, s.performChangeTestCases) 431 } 432 433 func (s *allWatcherStateSuite) TestChangeServices(c *gc.C) { 434 testChangeServices(c, s.owner, s.performChangeTestCases) 435 } 436 437 func (s *allWatcherStateSuite) TestChangeServicesConstraints(c *gc.C) { 438 testChangeServicesConstraints(c, s.owner, s.performChangeTestCases) 439 } 440 441 func (s *allWatcherStateSuite) TestChangeUnits(c *gc.C) { 442 testChangeUnits(c, s.owner, s.performChangeTestCases) 443 } 444 445 func (s *allWatcherStateSuite) TestChangeUnitsNonNilPorts(c *gc.C) { 446 testChangeUnitsNonNilPorts(c, s.owner, s.performChangeTestCases) 447 } 448 449 func (s *allWatcherStateSuite) TestChangeActions(c *gc.C) { 450 changeTestFuncs := []changeTestFunc{ 451 func(c *gc.C, st *State) changeTestCase { 452 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 453 u, err := wordpress.AddUnit() 454 c.Assert(err, jc.ErrorIsNil) 455 action, err := st.EnqueueAction(u.Tag(), "vacuumdb", map[string]interface{}{}) 456 c.Assert(err, jc.ErrorIsNil) 457 enqueued := makeActionInfo(action, st) 458 action, err = action.Begin() 459 c.Assert(err, jc.ErrorIsNil) 460 started := makeActionInfo(action, st) 461 return changeTestCase{ 462 about: "action change picks up last change", 463 initialContents: []multiwatcher.EntityInfo{&enqueued, &started}, 464 change: watcher.Change{C: actionsC, Id: st.docID(action.Id())}, 465 expectContents: []multiwatcher.EntityInfo{&started}, 466 } 467 }, 468 } 469 s.performChangeTestCases(c, changeTestFuncs) 470 } 471 472 func (s *allWatcherStateSuite) TestChangeBlocks(c *gc.C) { 473 changeTestFuncs := []changeTestFunc{ 474 func(c *gc.C, st *State) changeTestCase { 475 return changeTestCase{ 476 about: "no blocks in state, no blocks in store -> do nothing", 477 change: watcher.Change{ 478 C: blocksC, 479 Id: "1", 480 }} 481 }, 482 func(c *gc.C, st *State) changeTestCase { 483 blockId := st.docID("0") 484 blockType := DestroyBlock.ToParams() 485 blockMsg := "woot" 486 return changeTestCase{ 487 about: "no change if block is not in backing", 488 initialContents: []multiwatcher.EntityInfo{&multiwatcher.BlockInfo{ 489 ModelUUID: st.ModelUUID(), 490 Id: blockId, 491 Type: blockType, 492 Message: blockMsg, 493 Tag: st.ModelTag().String(), 494 }}, 495 change: watcher.Change{ 496 C: blocksC, 497 Id: st.localID(blockId), 498 }, 499 expectContents: []multiwatcher.EntityInfo{&multiwatcher.BlockInfo{ 500 ModelUUID: st.ModelUUID(), 501 Id: blockId, 502 Type: blockType, 503 Message: blockMsg, 504 Tag: st.ModelTag().String(), 505 }}, 506 } 507 }, 508 func(c *gc.C, st *State) changeTestCase { 509 err := st.SwitchBlockOn(DestroyBlock, "multiwatcher testing") 510 c.Assert(err, jc.ErrorIsNil) 511 b, found, err := st.GetBlockForType(DestroyBlock) 512 c.Assert(err, jc.ErrorIsNil) 513 c.Assert(found, jc.IsTrue) 514 blockId := b.Id() 515 516 return changeTestCase{ 517 about: "block is added if it's in backing but not in Store", 518 change: watcher.Change{ 519 C: blocksC, 520 Id: blockId, 521 }, 522 expectContents: []multiwatcher.EntityInfo{ 523 &multiwatcher.BlockInfo{ 524 ModelUUID: st.ModelUUID(), 525 Id: st.localID(blockId), 526 Type: b.Type().ToParams(), 527 Message: b.Message(), 528 Tag: st.ModelTag().String(), 529 }}} 530 }, 531 func(c *gc.C, st *State) changeTestCase { 532 err := st.SwitchBlockOn(DestroyBlock, "multiwatcher testing") 533 c.Assert(err, jc.ErrorIsNil) 534 b, found, err := st.GetBlockForType(DestroyBlock) 535 c.Assert(err, jc.ErrorIsNil) 536 c.Assert(found, jc.IsTrue) 537 err = st.SwitchBlockOff(DestroyBlock) 538 c.Assert(err, jc.ErrorIsNil) 539 540 return changeTestCase{ 541 about: "block is removed if it's in backing and in multiwatcher.Store", 542 change: watcher.Change{ 543 C: blocksC, 544 Id: b.Id(), 545 }, 546 } 547 }, 548 } 549 s.performChangeTestCases(c, changeTestFuncs) 550 } 551 552 func (s *allWatcherStateSuite) TestClosingPorts(c *gc.C) { 553 defer s.Reset(c) 554 // Init the test model. 555 wordpress := AddTestingService(c, s.state, "wordpress", AddTestingCharm(c, s.state, "wordpress"), s.owner) 556 u, err := wordpress.AddUnit() 557 c.Assert(err, jc.ErrorIsNil) 558 m, err := s.state.AddMachine("quantal", JobHostUnits) 559 c.Assert(err, jc.ErrorIsNil) 560 err = u.AssignToMachine(m) 561 c.Assert(err, jc.ErrorIsNil) 562 publicAddress := network.NewScopedAddress("1.2.3.4", network.ScopePublic) 563 privateAddress := network.NewScopedAddress("4.3.2.1", network.ScopeCloudLocal) 564 err = m.SetProviderAddresses(publicAddress, privateAddress) 565 c.Assert(err, jc.ErrorIsNil) 566 err = u.OpenPorts("tcp", 12345, 12345) 567 c.Assert(err, jc.ErrorIsNil) 568 // Create all watcher state backing. 569 b := newAllWatcherStateBacking(s.state) 570 all := newStore() 571 all.Update(&multiwatcher.MachineInfo{ 572 ModelUUID: s.state.ModelUUID(), 573 Id: "0", 574 }) 575 // Check opened ports. 576 err = b.Changed(all, watcher.Change{ 577 C: "units", 578 Id: s.state.docID("wordpress/0"), 579 }) 580 c.Assert(err, jc.ErrorIsNil) 581 entities := all.All() 582 substNilSinceTimeForEntities(c, entities) 583 assertEntitiesEqual(c, entities, []multiwatcher.EntityInfo{ 584 &multiwatcher.UnitInfo{ 585 ModelUUID: s.state.ModelUUID(), 586 Name: "wordpress/0", 587 Service: "wordpress", 588 Series: "quantal", 589 MachineId: "0", 590 PublicAddress: "1.2.3.4", 591 PrivateAddress: "4.3.2.1", 592 Ports: []network.Port{{"tcp", 12345}}, 593 PortRanges: []network.PortRange{{12345, 12345, "tcp"}}, 594 WorkloadStatus: multiwatcher.StatusInfo{ 595 Current: "unknown", 596 Message: "Waiting for agent initialization to finish", 597 Data: map[string]interface{}{}, 598 }, 599 JujuStatus: multiwatcher.StatusInfo{ 600 Current: "allocating", 601 Data: map[string]interface{}{}, 602 }, 603 }, 604 &multiwatcher.MachineInfo{ 605 ModelUUID: s.state.ModelUUID(), 606 Id: "0", 607 }, 608 }) 609 // Close the ports. 610 err = u.ClosePorts("tcp", 12345, 12345) 611 c.Assert(err, jc.ErrorIsNil) 612 err = b.Changed(all, watcher.Change{ 613 C: openedPortsC, 614 Id: s.state.docID("m#0#0.1.2.0/24"), 615 }) 616 c.Assert(err, jc.ErrorIsNil) 617 entities = all.All() 618 assertEntitiesEqual(c, entities, []multiwatcher.EntityInfo{ 619 &multiwatcher.UnitInfo{ 620 ModelUUID: s.state.ModelUUID(), 621 Name: "wordpress/0", 622 Service: "wordpress", 623 Series: "quantal", 624 MachineId: "0", 625 PublicAddress: "1.2.3.4", 626 PrivateAddress: "4.3.2.1", 627 Ports: []network.Port{}, 628 PortRanges: []network.PortRange{}, 629 WorkloadStatus: multiwatcher.StatusInfo{ 630 Current: "unknown", 631 Message: "Waiting for agent initialization to finish", 632 Data: map[string]interface{}{}, 633 }, 634 JujuStatus: multiwatcher.StatusInfo{ 635 Current: "allocating", 636 Data: map[string]interface{}{}, 637 }, 638 }, 639 &multiwatcher.MachineInfo{ 640 ModelUUID: s.state.ModelUUID(), 641 Id: "0", 642 }, 643 }) 644 } 645 646 func (s *allWatcherStateSuite) TestSettings(c *gc.C) { 647 defer s.Reset(c) 648 // Init the test model. 649 svc := AddTestingService(c, s.state, "dummy-service", AddTestingCharm(c, s.state, "dummy"), s.owner) 650 b := newAllWatcherStateBacking(s.state) 651 all := newStore() 652 // 1st scenario part: set settings and signal change. 653 setServiceConfigAttr(c, svc, "username", "foo") 654 setServiceConfigAttr(c, svc, "outlook", "foo@bar") 655 all.Update(&multiwatcher.ServiceInfo{ 656 ModelUUID: s.state.ModelUUID(), 657 Name: "dummy-service", 658 CharmURL: "local:quantal/quantal-dummy-1", 659 }) 660 err := b.Changed(all, watcher.Change{ 661 C: "settings", 662 Id: s.state.docID("s#dummy-service#local:quantal/quantal-dummy-1"), 663 }) 664 c.Assert(err, jc.ErrorIsNil) 665 entities := all.All() 666 substNilSinceTimeForEntities(c, entities) 667 assertEntitiesEqual(c, entities, []multiwatcher.EntityInfo{ 668 &multiwatcher.ServiceInfo{ 669 ModelUUID: s.state.ModelUUID(), 670 Name: "dummy-service", 671 CharmURL: "local:quantal/quantal-dummy-1", 672 Config: charm.Settings{"outlook": "foo@bar", "username": "foo"}, 673 }, 674 }) 675 // 2nd scenario part: destroy the service and signal change. 676 err = svc.Destroy() 677 c.Assert(err, jc.ErrorIsNil) 678 err = b.Changed(all, watcher.Change{ 679 C: "settings", 680 Id: s.state.docID("s#dummy-service#local:quantal/quantal-dummy-1"), 681 }) 682 c.Assert(err, jc.ErrorIsNil) 683 entities = all.All() 684 assertEntitiesEqual(c, entities, []multiwatcher.EntityInfo{ 685 &multiwatcher.ServiceInfo{ 686 ModelUUID: s.state.ModelUUID(), 687 Name: "dummy-service", 688 CharmURL: "local:quantal/quantal-dummy-1", 689 }, 690 }) 691 } 692 693 // TestStateWatcher tests the integration of the state watcher 694 // with the state-based backing. Most of the logic is tested elsewhere - 695 // this just tests end-to-end. 696 func (s *allWatcherStateSuite) TestStateWatcher(c *gc.C) { 697 m0, err := s.state.AddMachine("trusty", JobManageModel) 698 c.Assert(err, jc.ErrorIsNil) 699 c.Assert(m0.Id(), gc.Equals, "0") 700 701 m1, err := s.state.AddMachine("saucy", JobHostUnits) 702 c.Assert(err, jc.ErrorIsNil) 703 c.Assert(m1.Id(), gc.Equals, "1") 704 705 tw := newTestAllWatcher(s.state, c) 706 defer tw.Stop() 707 708 // Expect to see events for the already created machines first. 709 deltas := tw.All(2) 710 now := time.Now() 711 checkDeltasEqual(c, deltas, []multiwatcher.Delta{{ 712 Entity: &multiwatcher.MachineInfo{ 713 ModelUUID: s.state.ModelUUID(), 714 Id: "0", 715 JujuStatus: multiwatcher.StatusInfo{ 716 Current: status.StatusPending, 717 Data: map[string]interface{}{}, 718 Since: &now, 719 }, 720 MachineStatus: multiwatcher.StatusInfo{ 721 Current: status.StatusPending, 722 Data: map[string]interface{}{}, 723 Since: &now, 724 }, 725 Life: multiwatcher.Life("alive"), 726 Series: "trusty", 727 Jobs: []multiwatcher.MachineJob{JobManageModel.ToParams()}, 728 Addresses: []network.Address{}, 729 HasVote: false, 730 WantsVote: true, 731 }, 732 }, { 733 Entity: &multiwatcher.MachineInfo{ 734 ModelUUID: s.state.ModelUUID(), 735 Id: "1", 736 JujuStatus: multiwatcher.StatusInfo{ 737 Current: status.StatusPending, 738 Data: map[string]interface{}{}, 739 Since: &now, 740 }, 741 MachineStatus: multiwatcher.StatusInfo{ 742 Current: status.StatusPending, 743 Data: map[string]interface{}{}, 744 Since: &now, 745 }, 746 Life: multiwatcher.Life("alive"), 747 Series: "saucy", 748 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 749 Addresses: []network.Address{}, 750 HasVote: false, 751 WantsVote: false, 752 }, 753 }}) 754 755 // Destroy a machine and make sure that's seen. 756 err = m1.Destroy() 757 c.Assert(err, jc.ErrorIsNil) 758 759 deltas = tw.All(1) 760 zeroOutTimestampsForDeltas(c, deltas) 761 checkDeltasEqual(c, deltas, []multiwatcher.Delta{{ 762 Entity: &multiwatcher.MachineInfo{ 763 ModelUUID: s.state.ModelUUID(), 764 Id: "1", 765 JujuStatus: multiwatcher.StatusInfo{ 766 Current: status.StatusPending, 767 Data: map[string]interface{}{}, 768 Since: &now, 769 }, 770 MachineStatus: multiwatcher.StatusInfo{ 771 Current: status.StatusPending, 772 Data: map[string]interface{}{}, 773 Since: &now, 774 }, 775 Life: multiwatcher.Life("dying"), 776 Series: "saucy", 777 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 778 Addresses: []network.Address{}, 779 HasVote: false, 780 WantsVote: false, 781 }, 782 }}) 783 784 err = m1.EnsureDead() 785 c.Assert(err, jc.ErrorIsNil) 786 787 deltas = tw.All(1) 788 zeroOutTimestampsForDeltas(c, deltas) 789 checkDeltasEqual(c, deltas, []multiwatcher.Delta{{ 790 Entity: &multiwatcher.MachineInfo{ 791 ModelUUID: s.state.ModelUUID(), 792 Id: "1", 793 JujuStatus: multiwatcher.StatusInfo{ 794 Current: status.StatusPending, 795 Data: map[string]interface{}{}, 796 Since: &now, 797 }, 798 MachineStatus: multiwatcher.StatusInfo{ 799 Current: status.StatusPending, 800 Data: map[string]interface{}{}, 801 Since: &now, 802 }, 803 Life: multiwatcher.Life("dead"), 804 Series: "saucy", 805 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 806 Addresses: []network.Address{}, 807 HasVote: false, 808 WantsVote: false, 809 }, 810 }}) 811 812 // Make some more changes to the state. 813 arch := "amd64" 814 mem := uint64(4096) 815 hc := &instance.HardwareCharacteristics{ 816 Arch: &arch, 817 Mem: &mem, 818 } 819 err = m0.SetProvisioned("i-0", "bootstrap_nonce", hc) 820 c.Assert(err, jc.ErrorIsNil) 821 822 err = m1.Remove() 823 c.Assert(err, jc.ErrorIsNil) 824 825 m2, err := s.state.AddMachine("quantal", JobHostUnits) 826 c.Assert(err, jc.ErrorIsNil) 827 c.Assert(m2.Id(), gc.Equals, "2") 828 829 wordpress := AddTestingService(c, s.state, "wordpress", AddTestingCharm(c, s.state, "wordpress"), s.owner) 830 wu, err := wordpress.AddUnit() 831 c.Assert(err, jc.ErrorIsNil) 832 err = wu.AssignToMachine(m2) 833 c.Assert(err, jc.ErrorIsNil) 834 835 // Look for the state changes from the allwatcher. 836 deltas = tw.All(5) 837 838 zeroOutTimestampsForDeltas(c, deltas) 839 840 checkDeltasEqual(c, deltas, []multiwatcher.Delta{{ 841 Entity: &multiwatcher.MachineInfo{ 842 ModelUUID: s.state.ModelUUID(), 843 Id: "0", 844 InstanceId: "i-0", 845 JujuStatus: multiwatcher.StatusInfo{ 846 Current: status.StatusPending, 847 Data: map[string]interface{}{}, 848 Since: &now, 849 }, 850 MachineStatus: multiwatcher.StatusInfo{ 851 Current: status.StatusPending, 852 Data: map[string]interface{}{}, 853 Since: &now, 854 }, 855 Life: multiwatcher.Life("alive"), 856 Series: "trusty", 857 Jobs: []multiwatcher.MachineJob{JobManageModel.ToParams()}, 858 Addresses: []network.Address{}, 859 HardwareCharacteristics: hc, 860 HasVote: false, 861 WantsVote: true, 862 }, 863 }, { 864 Removed: true, 865 Entity: &multiwatcher.MachineInfo{ 866 ModelUUID: s.state.ModelUUID(), 867 Id: "1", 868 }, 869 }, { 870 Entity: &multiwatcher.MachineInfo{ 871 ModelUUID: s.state.ModelUUID(), 872 Id: "2", 873 JujuStatus: multiwatcher.StatusInfo{ 874 Current: status.StatusPending, 875 Data: map[string]interface{}{}, 876 Since: &now, 877 }, 878 MachineStatus: multiwatcher.StatusInfo{ 879 Current: status.StatusPending, 880 Data: map[string]interface{}{}, 881 Since: &now, 882 }, 883 Life: multiwatcher.Life("alive"), 884 Series: "quantal", 885 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 886 Addresses: []network.Address{}, 887 HasVote: false, 888 WantsVote: false, 889 }, 890 }, { 891 Entity: &multiwatcher.ServiceInfo{ 892 ModelUUID: s.state.ModelUUID(), 893 Name: "wordpress", 894 CharmURL: "local:quantal/quantal-wordpress-3", 895 OwnerTag: s.owner.String(), 896 Life: "alive", 897 Config: make(map[string]interface{}), 898 Status: multiwatcher.StatusInfo{ 899 Current: "unknown", 900 Message: "Waiting for agent initialization to finish", 901 Data: map[string]interface{}{}, 902 }, 903 }, 904 }, { 905 Entity: &multiwatcher.UnitInfo{ 906 ModelUUID: s.state.ModelUUID(), 907 Name: "wordpress/0", 908 Service: "wordpress", 909 Series: "quantal", 910 MachineId: "2", 911 WorkloadStatus: multiwatcher.StatusInfo{ 912 Current: "unknown", 913 Message: "Waiting for agent initialization to finish", 914 Data: map[string]interface{}{}, 915 }, 916 JujuStatus: multiwatcher.StatusInfo{ 917 Current: "allocating", 918 Message: "", 919 Data: map[string]interface{}{}, 920 }, 921 }, 922 }}) 923 } 924 925 func (s *allWatcherStateSuite) TestStateWatcherTwoModels(c *gc.C) { 926 loggo.GetLogger("juju.state.watcher").SetLogLevel(loggo.TRACE) 927 for i, test := range []struct { 928 about string 929 setUpState func(*State) 930 triggerEvent func(*State) 931 }{ 932 { 933 about: "machines", 934 triggerEvent: func(st *State) { 935 m0, err := st.AddMachine("trusty", JobHostUnits) 936 c.Assert(err, jc.ErrorIsNil) 937 c.Assert(m0.Id(), gc.Equals, "0") 938 }, 939 }, { 940 about: "services", 941 triggerEvent: func(st *State) { 942 AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 943 }, 944 }, { 945 about: "units", 946 setUpState: func(st *State) { 947 AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 948 }, 949 triggerEvent: func(st *State) { 950 svc, err := st.Service("wordpress") 951 c.Assert(err, jc.ErrorIsNil) 952 953 _, err = svc.AddUnit() 954 c.Assert(err, jc.ErrorIsNil) 955 }, 956 }, { 957 about: "relations", 958 setUpState: func(st *State) { 959 AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 960 AddTestingService(c, st, "mysql", AddTestingCharm(c, st, "mysql"), s.owner) 961 }, 962 triggerEvent: func(st *State) { 963 eps, err := st.InferEndpoints("mysql", "wordpress") 964 c.Assert(err, jc.ErrorIsNil) 965 _, err = st.AddRelation(eps...) 966 c.Assert(err, jc.ErrorIsNil) 967 }, 968 }, { 969 about: "annotations", 970 setUpState: func(st *State) { 971 m, err := st.AddMachine("trusty", JobHostUnits) 972 c.Assert(err, jc.ErrorIsNil) 973 c.Assert(m.Id(), gc.Equals, "0") 974 }, 975 triggerEvent: func(st *State) { 976 m, err := st.Machine("0") 977 c.Assert(err, jc.ErrorIsNil) 978 979 err = st.SetAnnotations(m, map[string]string{"foo": "bar"}) 980 c.Assert(err, jc.ErrorIsNil) 981 }, 982 }, { 983 about: "statuses", 984 setUpState: func(st *State) { 985 m, err := st.AddMachine("trusty", JobHostUnits) 986 c.Assert(err, jc.ErrorIsNil) 987 c.Assert(m.Id(), gc.Equals, "0") 988 err = m.SetProvisioned("inst-id", "fake_nonce", nil) 989 c.Assert(err, jc.ErrorIsNil) 990 }, 991 triggerEvent: func(st *State) { 992 m, err := st.Machine("0") 993 c.Assert(err, jc.ErrorIsNil) 994 995 err = m.SetStatus("error", "pete tong", nil) 996 c.Assert(err, jc.ErrorIsNil) 997 }, 998 }, { 999 about: "constraints", 1000 setUpState: func(st *State) { 1001 AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 1002 }, 1003 triggerEvent: func(st *State) { 1004 svc, err := st.Service("wordpress") 1005 c.Assert(err, jc.ErrorIsNil) 1006 1007 cpuCores := uint64(99) 1008 err = svc.SetConstraints(constraints.Value{CpuCores: &cpuCores}) 1009 c.Assert(err, jc.ErrorIsNil) 1010 }, 1011 }, { 1012 about: "settings", 1013 setUpState: func(st *State) { 1014 AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 1015 }, 1016 triggerEvent: func(st *State) { 1017 svc, err := st.Service("wordpress") 1018 c.Assert(err, jc.ErrorIsNil) 1019 1020 err = svc.UpdateConfigSettings(charm.Settings{"blog-title": "boring"}) 1021 c.Assert(err, jc.ErrorIsNil) 1022 }, 1023 }, { 1024 about: "blocks", 1025 triggerEvent: func(st *State) { 1026 m, found, err := st.GetBlockForType(DestroyBlock) 1027 c.Assert(err, jc.ErrorIsNil) 1028 c.Assert(found, jc.IsFalse) 1029 c.Assert(m, gc.IsNil) 1030 1031 err = st.SwitchBlockOn(DestroyBlock, "test block") 1032 c.Assert(err, jc.ErrorIsNil) 1033 }, 1034 }, 1035 } { 1036 c.Logf("Test %d: %s", i, test.about) 1037 func() { 1038 checkIsolationForEnv := func(st *State, w, otherW *testWatcher) { 1039 c.Logf("Making changes to model %s", st.ModelUUID()) 1040 1041 if test.setUpState != nil { 1042 test.setUpState(st) 1043 // Consume events from setup. 1044 w.AssertChanges() 1045 w.AssertNoChange() 1046 otherW.AssertNoChange() 1047 } 1048 1049 test.triggerEvent(st) 1050 // Check event was isolated to the correct watcher. 1051 w.AssertChanges() 1052 w.AssertNoChange() 1053 otherW.AssertNoChange() 1054 } 1055 otherState := s.newState(c) 1056 1057 w1 := newTestAllWatcher(s.state, c) 1058 defer w1.Stop() 1059 w2 := newTestAllWatcher(otherState, c) 1060 defer w2.Stop() 1061 1062 // The first set of deltas is empty, reflecting an empty model. 1063 w1.AssertNoChange() 1064 w2.AssertNoChange() 1065 checkIsolationForEnv(s.state, w1, w2) 1066 checkIsolationForEnv(otherState, w2, w1) 1067 }() 1068 s.Reset(c) 1069 } 1070 } 1071 1072 var _ = gc.Suite(&allModelWatcherStateSuite{}) 1073 1074 type allModelWatcherStateSuite struct { 1075 allWatcherBaseSuite 1076 state1 *State 1077 } 1078 1079 func (s *allModelWatcherStateSuite) SetUpTest(c *gc.C) { 1080 s.allWatcherBaseSuite.SetUpTest(c) 1081 s.state1 = s.newState(c) 1082 } 1083 1084 func (s *allModelWatcherStateSuite) Reset(c *gc.C) { 1085 s.TearDownTest(c) 1086 s.SetUpTest(c) 1087 } 1088 1089 // performChangeTestCases runs a passed number of test cases for changes. 1090 func (s *allModelWatcherStateSuite) performChangeTestCases(c *gc.C, changeTestFuncs []changeTestFunc) { 1091 for i, changeTestFunc := range changeTestFuncs { 1092 func() { // in aid of per-loop defers 1093 defer s.Reset(c) 1094 1095 test0 := changeTestFunc(c, s.state) 1096 1097 c.Logf("test %d. %s", i, test0.about) 1098 b := NewAllModelWatcherStateBacking(s.state) 1099 defer b.Release() 1100 all := newStore() 1101 1102 // Do updates and check for first env. 1103 for _, info := range test0.initialContents { 1104 all.Update(info) 1105 } 1106 err := b.Changed(all, test0.change) 1107 c.Assert(err, jc.ErrorIsNil) 1108 var entities entityInfoSlice = all.All() 1109 substNilSinceTimeForEntities(c, entities) 1110 assertEntitiesEqual(c, entities, test0.expectContents) 1111 1112 // Now do the same updates for a second env. 1113 test1 := changeTestFunc(c, s.state1) 1114 for _, info := range test1.initialContents { 1115 all.Update(info) 1116 } 1117 err = b.Changed(all, test1.change) 1118 c.Assert(err, jc.ErrorIsNil) 1119 1120 entities = all.All() 1121 1122 // substNilSinceTimeForEntities gets upset if it sees non-nil 1123 // times - which the entities for the first env will have - so 1124 // build a list of the entities for the second env. 1125 newEntities := make([]multiwatcher.EntityInfo, 0) 1126 for _, entity := range entities { 1127 if entity.EntityId().ModelUUID == s.state1.ModelUUID() { 1128 newEntities = append(newEntities, entity) 1129 } 1130 } 1131 substNilSinceTimeForEntities(c, newEntities) 1132 1133 // Expected to see entities for both envs. 1134 var expectedEntities entityInfoSlice = append( 1135 test0.expectContents, 1136 test1.expectContents...) 1137 sort.Sort(entities) 1138 sort.Sort(expectedEntities) 1139 assertEntitiesEqual(c, entities, expectedEntities) 1140 }() 1141 } 1142 } 1143 1144 func (s *allModelWatcherStateSuite) TestChangeAnnotations(c *gc.C) { 1145 testChangeAnnotations(c, s.performChangeTestCases) 1146 } 1147 1148 func (s *allModelWatcherStateSuite) TestChangeMachines(c *gc.C) { 1149 testChangeMachines(c, s.performChangeTestCases) 1150 } 1151 1152 func (s *allModelWatcherStateSuite) TestChangeRelations(c *gc.C) { 1153 testChangeRelations(c, s.owner, s.performChangeTestCases) 1154 } 1155 1156 func (s *allModelWatcherStateSuite) TestChangeServices(c *gc.C) { 1157 testChangeServices(c, s.owner, s.performChangeTestCases) 1158 } 1159 1160 func (s *allModelWatcherStateSuite) TestChangeServicesConstraints(c *gc.C) { 1161 testChangeServicesConstraints(c, s.owner, s.performChangeTestCases) 1162 } 1163 1164 func (s *allModelWatcherStateSuite) TestChangeUnits(c *gc.C) { 1165 testChangeUnits(c, s.owner, s.performChangeTestCases) 1166 } 1167 1168 func (s *allModelWatcherStateSuite) TestChangeUnitsNonNilPorts(c *gc.C) { 1169 testChangeUnitsNonNilPorts(c, s.owner, s.performChangeTestCases) 1170 } 1171 1172 func (s *allModelWatcherStateSuite) TestChangeModels(c *gc.C) { 1173 changeTestFuncs := []changeTestFunc{ 1174 func(c *gc.C, st *State) changeTestCase { 1175 return changeTestCase{ 1176 about: "no model in state -> do nothing", 1177 change: watcher.Change{ 1178 C: "models", 1179 Id: "non-existing-uuid", 1180 }} 1181 }, 1182 func(c *gc.C, st *State) changeTestCase { 1183 return changeTestCase{ 1184 about: "model is removed if it's not in backing", 1185 initialContents: []multiwatcher.EntityInfo{&multiwatcher.ModelInfo{ 1186 ModelUUID: "some-uuid", 1187 }}, 1188 change: watcher.Change{ 1189 C: "models", 1190 Id: "some-uuid", 1191 }} 1192 }, 1193 func(c *gc.C, st *State) changeTestCase { 1194 env, err := st.Model() 1195 c.Assert(err, jc.ErrorIsNil) 1196 return changeTestCase{ 1197 about: "model is added if it's in backing but not in Store", 1198 change: watcher.Change{ 1199 C: "models", 1200 Id: st.ModelUUID(), 1201 }, 1202 expectContents: []multiwatcher.EntityInfo{ 1203 &multiwatcher.ModelInfo{ 1204 ModelUUID: env.UUID(), 1205 Name: env.Name(), 1206 Life: multiwatcher.Life("alive"), 1207 Owner: env.Owner().Id(), 1208 ServerUUID: env.ControllerUUID(), 1209 }}} 1210 }, 1211 func(c *gc.C, st *State) changeTestCase { 1212 env, err := st.Model() 1213 c.Assert(err, jc.ErrorIsNil) 1214 return changeTestCase{ 1215 about: "model is updated if it's in backing and in Store", 1216 initialContents: []multiwatcher.EntityInfo{ 1217 &multiwatcher.ModelInfo{ 1218 ModelUUID: env.UUID(), 1219 Name: "", 1220 Life: multiwatcher.Life("alive"), 1221 Owner: env.Owner().Id(), 1222 ServerUUID: env.ControllerUUID(), 1223 }, 1224 }, 1225 change: watcher.Change{ 1226 C: "models", 1227 Id: env.UUID(), 1228 }, 1229 expectContents: []multiwatcher.EntityInfo{ 1230 &multiwatcher.ModelInfo{ 1231 ModelUUID: env.UUID(), 1232 Name: env.Name(), 1233 Life: multiwatcher.Life("alive"), 1234 Owner: env.Owner().Id(), 1235 ServerUUID: env.ControllerUUID(), 1236 }}} 1237 }, 1238 func(c *gc.C, st *State) changeTestCase { 1239 svc := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 1240 err := svc.SetConstraints(constraints.MustParse("mem=4G arch=amd64")) 1241 c.Assert(err, jc.ErrorIsNil) 1242 1243 return changeTestCase{ 1244 about: "status is changed if the service exists in the store", 1245 initialContents: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 1246 ModelUUID: st.ModelUUID(), 1247 Name: "wordpress", 1248 Constraints: constraints.MustParse("mem=99M cpu-cores=2 cpu-power=4"), 1249 }}, 1250 change: watcher.Change{ 1251 C: "constraints", 1252 Id: st.docID("s#wordpress"), 1253 }, 1254 expectContents: []multiwatcher.EntityInfo{ 1255 &multiwatcher.ServiceInfo{ 1256 ModelUUID: st.ModelUUID(), 1257 Name: "wordpress", 1258 Constraints: constraints.MustParse("mem=4G arch=amd64"), 1259 }}} 1260 }, 1261 } 1262 s.performChangeTestCases(c, changeTestFuncs) 1263 } 1264 1265 func (s *allModelWatcherStateSuite) TestChangeForDeadEnv(c *gc.C) { 1266 // Ensure an entity is removed when a change is seen but 1267 // the model the entity belonged to has already died. 1268 1269 b := NewAllModelWatcherStateBacking(s.state) 1270 defer b.Release() 1271 all := newStore() 1272 1273 // Insert a machine for an model that doesn't actually 1274 // exist (mimics env removal). 1275 all.Update(&multiwatcher.MachineInfo{ 1276 ModelUUID: "uuid", 1277 Id: "0", 1278 }) 1279 c.Assert(all.All(), gc.HasLen, 1) 1280 1281 err := b.Changed(all, watcher.Change{ 1282 C: "machines", 1283 Id: ensureModelUUID("uuid", "0"), 1284 }) 1285 c.Assert(err, jc.ErrorIsNil) 1286 1287 // Entity info should be gone now. 1288 c.Assert(all.All(), gc.HasLen, 0) 1289 } 1290 1291 func (s *allModelWatcherStateSuite) TestGetAll(c *gc.C) { 1292 // Set up 2 models and ensure that GetAll returns the 1293 // entities for both of them. 1294 entities0 := s.setUpScenario(c, s.state, 2) 1295 entities1 := s.setUpScenario(c, s.state1, 4) 1296 expectedEntities := append(entities0, entities1...) 1297 1298 // allModelWatcherStateBacking also watches models so add those in. 1299 env, err := s.state.Model() 1300 c.Assert(err, jc.ErrorIsNil) 1301 env1, err := s.state1.Model() 1302 c.Assert(err, jc.ErrorIsNil) 1303 expectedEntities = append(expectedEntities, 1304 &multiwatcher.ModelInfo{ 1305 ModelUUID: env.UUID(), 1306 Name: env.Name(), 1307 Life: multiwatcher.Life("alive"), 1308 Owner: env.Owner().Id(), 1309 ServerUUID: env.ControllerUUID(), 1310 }, 1311 &multiwatcher.ModelInfo{ 1312 ModelUUID: env1.UUID(), 1313 Name: env1.Name(), 1314 Life: multiwatcher.Life("alive"), 1315 Owner: env1.Owner().Id(), 1316 ServerUUID: env1.ControllerUUID(), 1317 }, 1318 ) 1319 1320 b := NewAllModelWatcherStateBacking(s.state) 1321 all := newStore() 1322 err = b.GetAll(all) 1323 c.Assert(err, jc.ErrorIsNil) 1324 var gotEntities entityInfoSlice = all.All() 1325 sort.Sort(gotEntities) 1326 sort.Sort(expectedEntities) 1327 substNilSinceTimeForEntities(c, gotEntities) 1328 assertEntitiesEqual(c, gotEntities, expectedEntities) 1329 } 1330 1331 // TestStateWatcher tests the integration of the state watcher with 1332 // allModelWatcherStateBacking. Most of the logic is comprehensively 1333 // tested elsewhere - this just tests end-to-end. 1334 func (s *allModelWatcherStateSuite) TestStateWatcher(c *gc.C) { 1335 st0 := s.state 1336 env0, err := st0.Model() 1337 c.Assert(err, jc.ErrorIsNil) 1338 1339 st1 := s.state1 1340 env1, err := st1.Model() 1341 c.Assert(err, jc.ErrorIsNil) 1342 1343 // Create some initial machines across 2 models 1344 m00, err := st0.AddMachine("trusty", JobManageModel) 1345 c.Assert(err, jc.ErrorIsNil) 1346 c.Assert(m00.Id(), gc.Equals, "0") 1347 1348 m10, err := st1.AddMachine("saucy", JobHostUnits) 1349 c.Assert(err, jc.ErrorIsNil) 1350 c.Assert(m10.Id(), gc.Equals, "0") 1351 1352 tw := newTestAllModelWatcher(st0, c) 1353 defer tw.Stop() 1354 1355 // Expect to see events for the already created models and 1356 // machines first. 1357 deltas := tw.All(4) 1358 checkDeltasEqual(c, deltas, []multiwatcher.Delta{{ 1359 Entity: &multiwatcher.ModelInfo{ 1360 ModelUUID: env0.UUID(), 1361 Name: env0.Name(), 1362 Life: "alive", 1363 Owner: env0.Owner().Id(), 1364 ServerUUID: env0.ControllerUUID(), 1365 }, 1366 }, { 1367 Entity: &multiwatcher.ModelInfo{ 1368 ModelUUID: env1.UUID(), 1369 Name: env1.Name(), 1370 Life: "alive", 1371 Owner: env1.Owner().Id(), 1372 ServerUUID: env1.ControllerUUID(), 1373 }, 1374 }, { 1375 Entity: &multiwatcher.MachineInfo{ 1376 ModelUUID: st0.ModelUUID(), 1377 Id: "0", 1378 JujuStatus: multiwatcher.StatusInfo{ 1379 Current: status.StatusPending, 1380 Data: map[string]interface{}{}, 1381 }, 1382 MachineStatus: multiwatcher.StatusInfo{ 1383 Current: status.StatusPending, 1384 Data: map[string]interface{}{}, 1385 }, 1386 Life: multiwatcher.Life("alive"), 1387 Series: "trusty", 1388 Jobs: []multiwatcher.MachineJob{JobManageModel.ToParams()}, 1389 Addresses: []network.Address{}, 1390 HasVote: false, 1391 WantsVote: true, 1392 }, 1393 }, { 1394 Entity: &multiwatcher.MachineInfo{ 1395 ModelUUID: st1.ModelUUID(), 1396 Id: "0", 1397 JujuStatus: multiwatcher.StatusInfo{ 1398 Current: status.StatusPending, 1399 Data: map[string]interface{}{}, 1400 }, 1401 MachineStatus: multiwatcher.StatusInfo{ 1402 Current: status.StatusPending, 1403 Data: map[string]interface{}{}, 1404 }, 1405 Life: multiwatcher.Life("alive"), 1406 Series: "saucy", 1407 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 1408 Addresses: []network.Address{}, 1409 HasVote: false, 1410 WantsVote: false, 1411 }, 1412 }}) 1413 1414 // Destroy a machine and make sure that's seen. 1415 err = m10.Destroy() 1416 c.Assert(err, jc.ErrorIsNil) 1417 1418 deltas = tw.All(1) 1419 zeroOutTimestampsForDeltas(c, deltas) 1420 checkDeltasEqual(c, deltas, []multiwatcher.Delta{{ 1421 Entity: &multiwatcher.MachineInfo{ 1422 ModelUUID: st1.ModelUUID(), 1423 Id: "0", 1424 JujuStatus: multiwatcher.StatusInfo{ 1425 Current: status.StatusPending, 1426 Data: map[string]interface{}{}, 1427 }, 1428 MachineStatus: multiwatcher.StatusInfo{ 1429 Current: status.StatusPending, 1430 Data: map[string]interface{}{}, 1431 }, 1432 Life: multiwatcher.Life("dying"), 1433 Series: "saucy", 1434 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 1435 Addresses: []network.Address{}, 1436 HasVote: false, 1437 WantsVote: false, 1438 }, 1439 }}) 1440 1441 err = m10.EnsureDead() 1442 c.Assert(err, jc.ErrorIsNil) 1443 1444 deltas = tw.All(1) 1445 zeroOutTimestampsForDeltas(c, deltas) 1446 checkDeltasEqual(c, deltas, []multiwatcher.Delta{{ 1447 Entity: &multiwatcher.MachineInfo{ 1448 ModelUUID: st1.ModelUUID(), 1449 Id: "0", 1450 JujuStatus: multiwatcher.StatusInfo{ 1451 Current: status.StatusPending, 1452 Data: map[string]interface{}{}, 1453 }, 1454 MachineStatus: multiwatcher.StatusInfo{ 1455 Current: status.StatusPending, 1456 Data: map[string]interface{}{}, 1457 }, 1458 Life: multiwatcher.Life("dead"), 1459 Series: "saucy", 1460 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 1461 Addresses: []network.Address{}, 1462 HasVote: false, 1463 WantsVote: false, 1464 }, 1465 }}) 1466 1467 // Make further changes to the state, including the addition of a 1468 // new model. 1469 err = m00.SetProvisioned("i-0", "bootstrap_nonce", nil) 1470 c.Assert(err, jc.ErrorIsNil) 1471 1472 err = m10.Remove() 1473 c.Assert(err, jc.ErrorIsNil) 1474 1475 m11, err := st1.AddMachine("quantal", JobHostUnits) 1476 c.Assert(err, jc.ErrorIsNil) 1477 c.Assert(m11.Id(), gc.Equals, "1") 1478 1479 wordpress := AddTestingService(c, st1, "wordpress", AddTestingCharm(c, st1, "wordpress"), s.owner) 1480 wu, err := wordpress.AddUnit() 1481 c.Assert(err, jc.ErrorIsNil) 1482 err = wu.AssignToMachine(m11) 1483 c.Assert(err, jc.ErrorIsNil) 1484 1485 st2 := s.newState(c) 1486 env2, err := st2.Model() 1487 c.Assert(err, jc.ErrorIsNil) 1488 1489 m20, err := st2.AddMachine("trusty", JobHostUnits) 1490 c.Assert(err, jc.ErrorIsNil) 1491 c.Assert(m20.Id(), gc.Equals, "0") 1492 1493 // Look for the state changes from the allwatcher. 1494 deltas = tw.All(7) 1495 zeroOutTimestampsForDeltas(c, deltas) 1496 1497 checkDeltasEqual(c, deltas, []multiwatcher.Delta{{ 1498 Entity: &multiwatcher.MachineInfo{ 1499 ModelUUID: st0.ModelUUID(), 1500 Id: "0", 1501 InstanceId: "i-0", 1502 JujuStatus: multiwatcher.StatusInfo{ 1503 Current: status.StatusPending, 1504 Data: map[string]interface{}{}, 1505 }, 1506 MachineStatus: multiwatcher.StatusInfo{ 1507 Current: status.StatusPending, 1508 Data: map[string]interface{}{}, 1509 }, 1510 Life: multiwatcher.Life("alive"), 1511 Series: "trusty", 1512 Jobs: []multiwatcher.MachineJob{JobManageModel.ToParams()}, 1513 Addresses: []network.Address{}, 1514 HardwareCharacteristics: &instance.HardwareCharacteristics{}, 1515 HasVote: false, 1516 WantsVote: true, 1517 }, 1518 }, { 1519 Removed: true, 1520 Entity: &multiwatcher.MachineInfo{ 1521 ModelUUID: st1.ModelUUID(), 1522 Id: "0", 1523 }, 1524 }, { 1525 Entity: &multiwatcher.MachineInfo{ 1526 ModelUUID: st1.ModelUUID(), 1527 Id: "1", 1528 JujuStatus: multiwatcher.StatusInfo{ 1529 Current: status.StatusPending, 1530 Data: map[string]interface{}{}, 1531 }, 1532 MachineStatus: multiwatcher.StatusInfo{ 1533 Current: status.StatusPending, 1534 Data: map[string]interface{}{}, 1535 }, 1536 Life: multiwatcher.Life("alive"), 1537 Series: "quantal", 1538 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 1539 Addresses: []network.Address{}, 1540 HasVote: false, 1541 WantsVote: false, 1542 }, 1543 }, { 1544 Entity: &multiwatcher.ServiceInfo{ 1545 ModelUUID: st1.ModelUUID(), 1546 Name: "wordpress", 1547 CharmURL: "local:quantal/quantal-wordpress-3", 1548 OwnerTag: s.owner.String(), 1549 Life: "alive", 1550 Config: make(map[string]interface{}), 1551 Status: multiwatcher.StatusInfo{ 1552 Current: "unknown", 1553 Message: "Waiting for agent initialization to finish", 1554 Data: map[string]interface{}{}, 1555 }, 1556 }, 1557 }, { 1558 Entity: &multiwatcher.UnitInfo{ 1559 ModelUUID: st1.ModelUUID(), 1560 Name: "wordpress/0", 1561 Service: "wordpress", 1562 Series: "quantal", 1563 MachineId: "1", 1564 WorkloadStatus: multiwatcher.StatusInfo{ 1565 Current: "unknown", 1566 Message: "Waiting for agent initialization to finish", 1567 Data: map[string]interface{}{}, 1568 }, 1569 JujuStatus: multiwatcher.StatusInfo{ 1570 Current: "allocating", 1571 Message: "", 1572 Data: map[string]interface{}{}, 1573 }, 1574 }, 1575 }, { 1576 Entity: &multiwatcher.ModelInfo{ 1577 ModelUUID: env2.UUID(), 1578 Name: env2.Name(), 1579 Life: "alive", 1580 Owner: env2.Owner().Id(), 1581 ServerUUID: env2.ControllerUUID(), 1582 }, 1583 }, { 1584 Entity: &multiwatcher.MachineInfo{ 1585 ModelUUID: st2.ModelUUID(), 1586 Id: "0", 1587 JujuStatus: multiwatcher.StatusInfo{ 1588 Current: status.StatusPending, 1589 Data: map[string]interface{}{}, 1590 }, 1591 MachineStatus: multiwatcher.StatusInfo{ 1592 Current: status.StatusPending, 1593 Data: map[string]interface{}{}, 1594 }, 1595 Life: multiwatcher.Life("alive"), 1596 Series: "trusty", 1597 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 1598 Addresses: []network.Address{}, 1599 HasVote: false, 1600 WantsVote: false, 1601 }, 1602 }}) 1603 } 1604 1605 func zeroOutTimestampsForDeltas(c *gc.C, deltas []multiwatcher.Delta) { 1606 for i, delta := range deltas { 1607 if unitInfo, ok := delta.Entity.(*multiwatcher.UnitInfo); ok { 1608 substNilSinceTimeForStatus(c, &unitInfo.WorkloadStatus) 1609 substNilSinceTimeForStatus(c, &unitInfo.JujuStatus) 1610 delta.Entity = unitInfo 1611 } else if serviceInfo, ok := delta.Entity.(*multiwatcher.ServiceInfo); ok { 1612 substNilSinceTimeForStatus(c, &serviceInfo.Status) 1613 delta.Entity = serviceInfo 1614 } 1615 deltas[i] = delta 1616 } 1617 } 1618 1619 // The testChange* funcs are extracted so the test cases can be used 1620 // to test both the allWatcher and allModelWatcher. 1621 1622 func testChangeAnnotations(c *gc.C, runChangeTests func(*gc.C, []changeTestFunc)) { 1623 changeTestFuncs := []changeTestFunc{ 1624 func(c *gc.C, st *State) changeTestCase { 1625 return changeTestCase{ 1626 about: "no annotation in state, no annotation in store -> do nothing", 1627 change: watcher.Change{ 1628 C: "annotations", 1629 Id: st.docID("m#0"), 1630 }} 1631 }, 1632 func(c *gc.C, st *State) changeTestCase { 1633 return changeTestCase{ 1634 about: "annotation is removed if it's not in backing", 1635 initialContents: []multiwatcher.EntityInfo{&multiwatcher.AnnotationInfo{ 1636 ModelUUID: st.ModelUUID(), 1637 Tag: "machine-0", 1638 }}, 1639 change: watcher.Change{ 1640 C: "annotations", 1641 Id: st.docID("m#0"), 1642 }} 1643 }, 1644 func(c *gc.C, st *State) changeTestCase { 1645 m, err := st.AddMachine("quantal", JobHostUnits) 1646 c.Assert(err, jc.ErrorIsNil) 1647 err = st.SetAnnotations(m, map[string]string{"foo": "bar", "arble": "baz"}) 1648 c.Assert(err, jc.ErrorIsNil) 1649 1650 return changeTestCase{ 1651 about: "annotation is added if it's in backing but not in Store", 1652 change: watcher.Change{ 1653 C: "annotations", 1654 Id: st.docID("m#0"), 1655 }, 1656 expectContents: []multiwatcher.EntityInfo{ 1657 &multiwatcher.AnnotationInfo{ 1658 ModelUUID: st.ModelUUID(), 1659 Tag: "machine-0", 1660 Annotations: map[string]string{"foo": "bar", "arble": "baz"}, 1661 }}} 1662 }, 1663 func(c *gc.C, st *State) changeTestCase { 1664 m, err := st.AddMachine("quantal", JobHostUnits) 1665 c.Assert(err, jc.ErrorIsNil) 1666 err = st.SetAnnotations(m, map[string]string{ 1667 "arble": "khroomph", 1668 "pretty": "", 1669 "new": "attr", 1670 }) 1671 c.Assert(err, jc.ErrorIsNil) 1672 1673 return changeTestCase{ 1674 about: "annotation is updated if it's in backing and in multiwatcher.Store", 1675 initialContents: []multiwatcher.EntityInfo{&multiwatcher.AnnotationInfo{ 1676 ModelUUID: st.ModelUUID(), 1677 Tag: "machine-0", 1678 Annotations: map[string]string{ 1679 "arble": "baz", 1680 "foo": "bar", 1681 "pretty": "polly", 1682 }, 1683 }}, 1684 change: watcher.Change{ 1685 C: "annotations", 1686 Id: st.docID("m#0"), 1687 }, 1688 expectContents: []multiwatcher.EntityInfo{ 1689 &multiwatcher.AnnotationInfo{ 1690 ModelUUID: st.ModelUUID(), 1691 Tag: "machine-0", 1692 Annotations: map[string]string{ 1693 "arble": "khroomph", 1694 "new": "attr", 1695 }}}} 1696 }, 1697 } 1698 runChangeTests(c, changeTestFuncs) 1699 } 1700 1701 func testChangeMachines(c *gc.C, runChangeTests func(*gc.C, []changeTestFunc)) { 1702 now := time.Now() 1703 changeTestFuncs := []changeTestFunc{ 1704 func(c *gc.C, st *State) changeTestCase { 1705 return changeTestCase{ 1706 about: "no machine in state -> do nothing", 1707 change: watcher.Change{ 1708 C: "statuses", 1709 Id: st.docID("m#0"), 1710 }} 1711 }, 1712 func(c *gc.C, st *State) changeTestCase { 1713 return changeTestCase{ 1714 about: "no machine in state, no machine in store -> do nothing", 1715 change: watcher.Change{ 1716 C: "machines", 1717 Id: st.docID("1"), 1718 }} 1719 }, 1720 func(c *gc.C, st *State) changeTestCase { 1721 return changeTestCase{ 1722 about: "machine is removed if it's not in backing", 1723 initialContents: []multiwatcher.EntityInfo{&multiwatcher.MachineInfo{ 1724 ModelUUID: st.ModelUUID(), 1725 Id: "1", 1726 }}, 1727 change: watcher.Change{ 1728 C: "machines", 1729 Id: st.docID("1"), 1730 }} 1731 }, 1732 func(c *gc.C, st *State) changeTestCase { 1733 m, err := st.AddMachine("quantal", JobHostUnits) 1734 c.Assert(err, jc.ErrorIsNil) 1735 err = m.SetStatus(status.StatusError, "failure", nil) 1736 c.Assert(err, jc.ErrorIsNil) 1737 1738 return changeTestCase{ 1739 about: "machine is added if it's in backing but not in Store", 1740 change: watcher.Change{ 1741 C: "machines", 1742 Id: st.docID("0"), 1743 }, 1744 expectContents: []multiwatcher.EntityInfo{ 1745 &multiwatcher.MachineInfo{ 1746 ModelUUID: st.ModelUUID(), 1747 Id: "0", 1748 JujuStatus: multiwatcher.StatusInfo{ 1749 Current: status.StatusError, 1750 Message: "failure", 1751 Data: map[string]interface{}{}, 1752 }, 1753 MachineStatus: multiwatcher.StatusInfo{ 1754 Current: status.StatusPending, 1755 Data: map[string]interface{}{}, 1756 }, 1757 Life: multiwatcher.Life("alive"), 1758 Series: "quantal", 1759 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 1760 Addresses: []network.Address{}, 1761 HasVote: false, 1762 WantsVote: false, 1763 }}} 1764 }, 1765 func(c *gc.C, st *State) changeTestCase { 1766 m, err := st.AddMachine("trusty", JobHostUnits) 1767 c.Assert(err, jc.ErrorIsNil) 1768 err = m.SetProvisioned("i-0", "bootstrap_nonce", nil) 1769 c.Assert(err, jc.ErrorIsNil) 1770 err = m.SetSupportedContainers([]instance.ContainerType{instance.LXC}) 1771 c.Assert(err, jc.ErrorIsNil) 1772 1773 return changeTestCase{ 1774 about: "machine is updated if it's in backing and in Store", 1775 initialContents: []multiwatcher.EntityInfo{ 1776 &multiwatcher.MachineInfo{ 1777 ModelUUID: st.ModelUUID(), 1778 Id: "0", 1779 JujuStatus: multiwatcher.StatusInfo{ 1780 Current: status.StatusError, 1781 Message: "another failure", 1782 Data: map[string]interface{}{}, 1783 Since: &now, 1784 }, 1785 MachineStatus: multiwatcher.StatusInfo{ 1786 Current: status.StatusPending, 1787 Data: map[string]interface{}{}, 1788 Since: &now, 1789 }, 1790 }, 1791 }, 1792 change: watcher.Change{ 1793 C: "machines", 1794 Id: st.docID("0"), 1795 }, 1796 expectContents: []multiwatcher.EntityInfo{ 1797 &multiwatcher.MachineInfo{ 1798 ModelUUID: st.ModelUUID(), 1799 Id: "0", 1800 InstanceId: "i-0", 1801 JujuStatus: multiwatcher.StatusInfo{ 1802 Current: status.StatusError, 1803 Message: "another failure", 1804 Data: map[string]interface{}{}, 1805 }, 1806 MachineStatus: multiwatcher.StatusInfo{ 1807 Current: status.StatusPending, 1808 Data: map[string]interface{}{}, 1809 }, 1810 Life: multiwatcher.Life("alive"), 1811 Series: "trusty", 1812 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 1813 Addresses: []network.Address{}, 1814 HardwareCharacteristics: &instance.HardwareCharacteristics{}, 1815 SupportedContainers: []instance.ContainerType{instance.LXC}, 1816 SupportedContainersKnown: true, 1817 }}} 1818 }, 1819 func(c *gc.C, st *State) changeTestCase { 1820 return changeTestCase{ 1821 about: "no change if status is not in backing", 1822 initialContents: []multiwatcher.EntityInfo{&multiwatcher.MachineInfo{ 1823 ModelUUID: st.ModelUUID(), 1824 Id: "0", 1825 JujuStatus: multiwatcher.StatusInfo{ 1826 Current: status.StatusError, 1827 Message: "failure", 1828 Data: map[string]interface{}{}, 1829 Since: &now, 1830 }, 1831 }}, 1832 change: watcher.Change{ 1833 C: "statuses", 1834 Id: st.docID("m#0"), 1835 }, 1836 expectContents: []multiwatcher.EntityInfo{ 1837 &multiwatcher.MachineInfo{ 1838 ModelUUID: st.ModelUUID(), 1839 Id: "0", 1840 JujuStatus: multiwatcher.StatusInfo{ 1841 Current: status.StatusError, 1842 Message: "failure", 1843 Data: map[string]interface{}{}, 1844 }, 1845 }}} 1846 }, 1847 func(c *gc.C, st *State) changeTestCase { 1848 m, err := st.AddMachine("quantal", JobHostUnits) 1849 c.Assert(err, jc.ErrorIsNil) 1850 err = m.SetStatus(status.StatusStarted, "", nil) 1851 c.Assert(err, jc.ErrorIsNil) 1852 1853 return changeTestCase{ 1854 about: "status is changed if the machine exists in the store", 1855 initialContents: []multiwatcher.EntityInfo{&multiwatcher.MachineInfo{ 1856 ModelUUID: st.ModelUUID(), 1857 Id: "0", 1858 JujuStatus: multiwatcher.StatusInfo{ 1859 Current: status.StatusError, 1860 Message: "failure", 1861 Data: map[string]interface{}{}, 1862 Since: &now, 1863 }, 1864 }}, 1865 change: watcher.Change{ 1866 C: "statuses", 1867 Id: st.docID("m#0"), 1868 }, 1869 expectContents: []multiwatcher.EntityInfo{ 1870 &multiwatcher.MachineInfo{ 1871 ModelUUID: st.ModelUUID(), 1872 Id: "0", 1873 JujuStatus: multiwatcher.StatusInfo{ 1874 Current: status.StatusStarted, 1875 Data: make(map[string]interface{}), 1876 }, 1877 }}} 1878 }, 1879 } 1880 runChangeTests(c, changeTestFuncs) 1881 } 1882 1883 func testChangeRelations(c *gc.C, owner names.UserTag, runChangeTests func(*gc.C, []changeTestFunc)) { 1884 changeTestFuncs := []changeTestFunc{ 1885 func(c *gc.C, st *State) changeTestCase { 1886 return changeTestCase{ 1887 about: "no relation in state, no service in store -> do nothing", 1888 change: watcher.Change{ 1889 C: "relations", 1890 Id: st.docID("logging:logging-directory wordpress:logging-dir"), 1891 }} 1892 }, 1893 func(c *gc.C, st *State) changeTestCase { 1894 return changeTestCase{ 1895 about: "relation is removed if it's not in backing", 1896 initialContents: []multiwatcher.EntityInfo{&multiwatcher.RelationInfo{ 1897 ModelUUID: st.ModelUUID(), 1898 Key: "logging:logging-directory wordpress:logging-dir", 1899 }}, 1900 change: watcher.Change{ 1901 C: "relations", 1902 Id: st.docID("logging:logging-directory wordpress:logging-dir"), 1903 }} 1904 }, 1905 func(c *gc.C, st *State) changeTestCase { 1906 AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), owner) 1907 AddTestingService(c, st, "logging", AddTestingCharm(c, st, "logging"), owner) 1908 eps, err := st.InferEndpoints("logging", "wordpress") 1909 c.Assert(err, jc.ErrorIsNil) 1910 _, err = st.AddRelation(eps...) 1911 c.Assert(err, jc.ErrorIsNil) 1912 1913 return changeTestCase{ 1914 about: "relation is added if it's in backing but not in Store", 1915 change: watcher.Change{ 1916 C: "relations", 1917 Id: st.docID("logging:logging-directory wordpress:logging-dir"), 1918 }, 1919 expectContents: []multiwatcher.EntityInfo{ 1920 &multiwatcher.RelationInfo{ 1921 ModelUUID: st.ModelUUID(), 1922 Key: "logging:logging-directory wordpress:logging-dir", 1923 Endpoints: []multiwatcher.Endpoint{ 1924 {ServiceName: "logging", Relation: charm.Relation{Name: "logging-directory", Role: "requirer", Interface: "logging", Optional: false, Limit: 1, Scope: "container"}}, 1925 {ServiceName: "wordpress", Relation: charm.Relation{Name: "logging-dir", Role: "provider", Interface: "logging", Optional: false, Limit: 0, Scope: "container"}}}, 1926 }}} 1927 }, 1928 } 1929 runChangeTests(c, changeTestFuncs) 1930 } 1931 1932 func testChangeServices(c *gc.C, owner names.UserTag, runChangeTests func(*gc.C, []changeTestFunc)) { 1933 // TODO(wallyworld) - add test for changing service status when that is implemented 1934 changeTestFuncs := []changeTestFunc{ 1935 // Services. 1936 func(c *gc.C, st *State) changeTestCase { 1937 return changeTestCase{ 1938 about: "no service in state, no service in store -> do nothing", 1939 change: watcher.Change{ 1940 C: "services", 1941 Id: st.docID("wordpress"), 1942 }} 1943 }, 1944 func(c *gc.C, st *State) changeTestCase { 1945 return changeTestCase{ 1946 about: "service is removed if it's not in backing", 1947 initialContents: []multiwatcher.EntityInfo{ 1948 &multiwatcher.ServiceInfo{ 1949 ModelUUID: st.ModelUUID(), 1950 Name: "wordpress", 1951 }, 1952 }, 1953 change: watcher.Change{ 1954 C: "services", 1955 Id: st.docID("wordpress"), 1956 }} 1957 }, 1958 func(c *gc.C, st *State) changeTestCase { 1959 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), owner) 1960 err := wordpress.SetExposed() 1961 c.Assert(err, jc.ErrorIsNil) 1962 err = wordpress.SetMinUnits(42) 1963 c.Assert(err, jc.ErrorIsNil) 1964 1965 return changeTestCase{ 1966 about: "service is added if it's in backing but not in Store", 1967 change: watcher.Change{ 1968 C: "services", 1969 Id: st.docID("wordpress"), 1970 }, 1971 expectContents: []multiwatcher.EntityInfo{ 1972 &multiwatcher.ServiceInfo{ 1973 ModelUUID: st.ModelUUID(), 1974 Name: "wordpress", 1975 Exposed: true, 1976 CharmURL: "local:quantal/quantal-wordpress-3", 1977 OwnerTag: owner.String(), 1978 Life: multiwatcher.Life("alive"), 1979 MinUnits: 42, 1980 Config: charm.Settings{}, 1981 Status: multiwatcher.StatusInfo{ 1982 Current: "unknown", 1983 Message: "Waiting for agent initialization to finish", 1984 Data: map[string]interface{}{}, 1985 }, 1986 }}} 1987 }, 1988 func(c *gc.C, st *State) changeTestCase { 1989 svc := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), owner) 1990 setServiceConfigAttr(c, svc, "blog-title", "boring") 1991 1992 return changeTestCase{ 1993 about: "service is updated if it's in backing and in multiwatcher.Store", 1994 initialContents: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 1995 ModelUUID: st.ModelUUID(), 1996 Name: "wordpress", 1997 Exposed: true, 1998 CharmURL: "local:quantal/quantal-wordpress-3", 1999 MinUnits: 47, 2000 Constraints: constraints.MustParse("mem=99M"), 2001 Config: charm.Settings{"blog-title": "boring"}, 2002 }}, 2003 change: watcher.Change{ 2004 C: "services", 2005 Id: st.docID("wordpress"), 2006 }, 2007 expectContents: []multiwatcher.EntityInfo{ 2008 &multiwatcher.ServiceInfo{ 2009 ModelUUID: st.ModelUUID(), 2010 Name: "wordpress", 2011 CharmURL: "local:quantal/quantal-wordpress-3", 2012 OwnerTag: owner.String(), 2013 Life: multiwatcher.Life("alive"), 2014 Constraints: constraints.MustParse("mem=99M"), 2015 Config: charm.Settings{"blog-title": "boring"}, 2016 }}} 2017 }, 2018 func(c *gc.C, st *State) changeTestCase { 2019 svc := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), owner) 2020 setServiceConfigAttr(c, svc, "blog-title", "boring") 2021 2022 return changeTestCase{ 2023 about: "service re-reads config when charm URL changes", 2024 initialContents: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 2025 ModelUUID: st.ModelUUID(), 2026 Name: "wordpress", 2027 // Note: CharmURL has a different revision number from 2028 // the wordpress revision in the testing repo. 2029 CharmURL: "local:quantal/quantal-wordpress-2", 2030 Config: charm.Settings{"foo": "bar"}, 2031 }}, 2032 change: watcher.Change{ 2033 C: "services", 2034 Id: st.docID("wordpress"), 2035 }, 2036 expectContents: []multiwatcher.EntityInfo{ 2037 &multiwatcher.ServiceInfo{ 2038 ModelUUID: st.ModelUUID(), 2039 Name: "wordpress", 2040 CharmURL: "local:quantal/quantal-wordpress-3", 2041 OwnerTag: owner.String(), 2042 Life: multiwatcher.Life("alive"), 2043 Config: charm.Settings{"blog-title": "boring"}, 2044 }}} 2045 }, 2046 // Settings. 2047 func(c *gc.C, st *State) changeTestCase { 2048 return changeTestCase{ 2049 about: "no service in state -> do nothing", 2050 change: watcher.Change{ 2051 C: "settings", 2052 Id: st.docID("s#dummy-service#local:quantal/quantal-dummy-1"), 2053 }} 2054 }, 2055 func(c *gc.C, st *State) changeTestCase { 2056 return changeTestCase{ 2057 about: "no change if service is not in backing", 2058 initialContents: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 2059 ModelUUID: st.ModelUUID(), 2060 Name: "dummy-service", 2061 CharmURL: "local:quantal/quantal-dummy-1", 2062 }}, 2063 change: watcher.Change{ 2064 C: "settings", 2065 Id: st.docID("s#dummy-service#local:quantal/quantal-dummy-1"), 2066 }, 2067 expectContents: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 2068 ModelUUID: st.ModelUUID(), 2069 Name: "dummy-service", 2070 CharmURL: "local:quantal/quantal-dummy-1", 2071 }}} 2072 }, 2073 func(c *gc.C, st *State) changeTestCase { 2074 svc := AddTestingService(c, st, "dummy-service", AddTestingCharm(c, st, "dummy"), owner) 2075 setServiceConfigAttr(c, svc, "username", "foo") 2076 setServiceConfigAttr(c, svc, "outlook", "foo@bar") 2077 2078 return changeTestCase{ 2079 about: "service config is changed if service exists in the store with the same URL", 2080 initialContents: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 2081 ModelUUID: st.ModelUUID(), 2082 Name: "dummy-service", 2083 CharmURL: "local:quantal/quantal-dummy-1", 2084 }}, 2085 change: watcher.Change{ 2086 C: "settings", 2087 Id: st.docID("s#dummy-service#local:quantal/quantal-dummy-1"), 2088 }, 2089 expectContents: []multiwatcher.EntityInfo{ 2090 &multiwatcher.ServiceInfo{ 2091 ModelUUID: st.ModelUUID(), 2092 Name: "dummy-service", 2093 CharmURL: "local:quantal/quantal-dummy-1", 2094 Config: charm.Settings{"username": "foo", "outlook": "foo@bar"}, 2095 }}} 2096 }, 2097 func(c *gc.C, st *State) changeTestCase { 2098 svc := AddTestingService(c, st, "dummy-service", AddTestingCharm(c, st, "dummy"), owner) 2099 setServiceConfigAttr(c, svc, "username", "foo") 2100 setServiceConfigAttr(c, svc, "outlook", "foo@bar") 2101 setServiceConfigAttr(c, svc, "username", nil) 2102 2103 return changeTestCase{ 2104 about: "service config is changed after removing of a setting", 2105 initialContents: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 2106 ModelUUID: st.ModelUUID(), 2107 Name: "dummy-service", 2108 CharmURL: "local:quantal/quantal-dummy-1", 2109 Config: charm.Settings{"username": "foo", "outlook": "foo@bar"}, 2110 }}, 2111 change: watcher.Change{ 2112 C: "settings", 2113 Id: st.docID("s#dummy-service#local:quantal/quantal-dummy-1"), 2114 }, 2115 expectContents: []multiwatcher.EntityInfo{ 2116 &multiwatcher.ServiceInfo{ 2117 ModelUUID: st.ModelUUID(), 2118 Name: "dummy-service", 2119 CharmURL: "local:quantal/quantal-dummy-1", 2120 Config: charm.Settings{"outlook": "foo@bar"}, 2121 }}} 2122 }, 2123 func(c *gc.C, st *State) changeTestCase { 2124 testCharm := AddCustomCharm( 2125 c, st, "dummy", 2126 "config.yaml", dottedConfig, 2127 "quantal", 1) 2128 svc := AddTestingService(c, st, "dummy-service", testCharm, owner) 2129 setServiceConfigAttr(c, svc, "key.dotted", "foo") 2130 2131 return changeTestCase{ 2132 about: "service config is unescaped when reading from the backing store", 2133 initialContents: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 2134 ModelUUID: st.ModelUUID(), 2135 Name: "dummy-service", 2136 CharmURL: "local:quantal/quantal-dummy-1", 2137 Config: charm.Settings{"key.dotted": "bar"}, 2138 }}, 2139 change: watcher.Change{ 2140 C: "settings", 2141 Id: st.docID("s#dummy-service#local:quantal/quantal-dummy-1"), 2142 }, 2143 expectContents: []multiwatcher.EntityInfo{ 2144 &multiwatcher.ServiceInfo{ 2145 ModelUUID: st.ModelUUID(), 2146 Name: "dummy-service", 2147 CharmURL: "local:quantal/quantal-dummy-1", 2148 Config: charm.Settings{"key.dotted": "foo"}, 2149 }}} 2150 }, 2151 func(c *gc.C, st *State) changeTestCase { 2152 svc := AddTestingService(c, st, "dummy-service", AddTestingCharm(c, st, "dummy"), owner) 2153 setServiceConfigAttr(c, svc, "username", "foo") 2154 2155 return changeTestCase{ 2156 about: "service config is unchanged if service exists in the store with a different URL", 2157 initialContents: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 2158 ModelUUID: st.ModelUUID(), 2159 Name: "dummy-service", 2160 CharmURL: "local:quantal/quantal-dummy-2", // Note different revno. 2161 Config: charm.Settings{"username": "bar"}, 2162 }}, 2163 change: watcher.Change{ 2164 C: "settings", 2165 Id: st.docID("s#dummy-service#local:quantal/quantal-dummy-1"), 2166 }, 2167 expectContents: []multiwatcher.EntityInfo{ 2168 &multiwatcher.ServiceInfo{ 2169 ModelUUID: st.ModelUUID(), 2170 Name: "dummy-service", 2171 CharmURL: "local:quantal/quantal-dummy-2", 2172 Config: charm.Settings{"username": "bar"}, 2173 }}} 2174 }, 2175 func(c *gc.C, st *State) changeTestCase { 2176 return changeTestCase{ 2177 about: "non-service config change is ignored", 2178 change: watcher.Change{ 2179 C: "settings", 2180 Id: st.docID("m#0"), 2181 }} 2182 }, 2183 func(c *gc.C, st *State) changeTestCase { 2184 return changeTestCase{ 2185 about: "service config change with no charm url is ignored", 2186 change: watcher.Change{ 2187 C: "settings", 2188 Id: st.docID("s#foo"), 2189 }} 2190 }, 2191 } 2192 runChangeTests(c, changeTestFuncs) 2193 } 2194 2195 func testChangeServicesConstraints(c *gc.C, owner names.UserTag, runChangeTests func(*gc.C, []changeTestFunc)) { 2196 changeTestFuncs := []changeTestFunc{ 2197 func(c *gc.C, st *State) changeTestCase { 2198 return changeTestCase{ 2199 about: "no service in state -> do nothing", 2200 change: watcher.Change{ 2201 C: "constraints", 2202 Id: st.docID("s#wordpress"), 2203 }} 2204 }, 2205 func(c *gc.C, st *State) changeTestCase { 2206 return changeTestCase{ 2207 about: "no change if service is not in backing", 2208 initialContents: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 2209 ModelUUID: st.ModelUUID(), 2210 Name: "wordpress", 2211 Constraints: constraints.MustParse("mem=99M"), 2212 }}, 2213 change: watcher.Change{ 2214 C: "constraints", 2215 Id: st.docID("s#wordpress"), 2216 }, 2217 expectContents: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 2218 ModelUUID: st.ModelUUID(), 2219 Name: "wordpress", 2220 Constraints: constraints.MustParse("mem=99M"), 2221 }}} 2222 }, 2223 func(c *gc.C, st *State) changeTestCase { 2224 svc := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), owner) 2225 err := svc.SetConstraints(constraints.MustParse("mem=4G arch=amd64")) 2226 c.Assert(err, jc.ErrorIsNil) 2227 2228 return changeTestCase{ 2229 about: "status is changed if the service exists in the store", 2230 initialContents: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 2231 ModelUUID: st.ModelUUID(), 2232 Name: "wordpress", 2233 Constraints: constraints.MustParse("mem=99M cpu-cores=2 cpu-power=4"), 2234 }}, 2235 change: watcher.Change{ 2236 C: "constraints", 2237 Id: st.docID("s#wordpress"), 2238 }, 2239 expectContents: []multiwatcher.EntityInfo{ 2240 &multiwatcher.ServiceInfo{ 2241 ModelUUID: st.ModelUUID(), 2242 Name: "wordpress", 2243 Constraints: constraints.MustParse("mem=4G arch=amd64"), 2244 }}} 2245 }, 2246 } 2247 runChangeTests(c, changeTestFuncs) 2248 } 2249 2250 func testChangeUnits(c *gc.C, owner names.UserTag, runChangeTests func(*gc.C, []changeTestFunc)) { 2251 now := time.Now() 2252 changeTestFuncs := []changeTestFunc{ 2253 func(c *gc.C, st *State) changeTestCase { 2254 return changeTestCase{ 2255 about: "no unit in state, no unit in store -> do nothing", 2256 change: watcher.Change{ 2257 C: "units", 2258 Id: st.docID("1"), 2259 }} 2260 }, 2261 func(c *gc.C, st *State) changeTestCase { 2262 return changeTestCase{ 2263 about: "unit is removed if it's not in backing", 2264 initialContents: []multiwatcher.EntityInfo{ 2265 &multiwatcher.UnitInfo{ 2266 ModelUUID: st.ModelUUID(), 2267 Name: "wordpress/1", 2268 }, 2269 }, 2270 change: watcher.Change{ 2271 C: "units", 2272 Id: st.docID("wordpress/1"), 2273 }} 2274 }, 2275 func(c *gc.C, st *State) changeTestCase { 2276 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), owner) 2277 u, err := wordpress.AddUnit() 2278 c.Assert(err, jc.ErrorIsNil) 2279 m, err := st.AddMachine("quantal", JobHostUnits) 2280 c.Assert(err, jc.ErrorIsNil) 2281 err = u.AssignToMachine(m) 2282 c.Assert(err, jc.ErrorIsNil) 2283 err = u.OpenPort("tcp", 12345) 2284 c.Assert(err, jc.ErrorIsNil) 2285 err = u.OpenPort("udp", 54321) 2286 c.Assert(err, jc.ErrorIsNil) 2287 err = u.OpenPorts("tcp", 5555, 5558) 2288 c.Assert(err, jc.ErrorIsNil) 2289 err = u.SetAgentStatus(status.StatusError, "failure", nil) 2290 c.Assert(err, jc.ErrorIsNil) 2291 2292 return changeTestCase{ 2293 about: "unit is added if it's in backing but not in Store", 2294 change: watcher.Change{ 2295 C: "units", 2296 Id: st.docID("wordpress/0"), 2297 }, 2298 expectContents: []multiwatcher.EntityInfo{ 2299 &multiwatcher.UnitInfo{ 2300 ModelUUID: st.ModelUUID(), 2301 Name: "wordpress/0", 2302 Service: "wordpress", 2303 Series: "quantal", 2304 MachineId: "0", 2305 Ports: []network.Port{ 2306 {"tcp", 5555}, 2307 {"tcp", 5556}, 2308 {"tcp", 5557}, 2309 {"tcp", 5558}, 2310 {"tcp", 12345}, 2311 {"udp", 54321}, 2312 }, 2313 PortRanges: []network.PortRange{ 2314 {5555, 5558, "tcp"}, 2315 {12345, 12345, "tcp"}, 2316 {54321, 54321, "udp"}, 2317 }, 2318 JujuStatus: multiwatcher.StatusInfo{ 2319 Current: "idle", 2320 Message: "", 2321 Data: map[string]interface{}{}, 2322 }, 2323 WorkloadStatus: multiwatcher.StatusInfo{ 2324 Current: "error", 2325 Message: "failure", 2326 Data: map[string]interface{}{}, 2327 }, 2328 }}} 2329 }, 2330 func(c *gc.C, st *State) changeTestCase { 2331 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), owner) 2332 u, err := wordpress.AddUnit() 2333 c.Assert(err, jc.ErrorIsNil) 2334 m, err := st.AddMachine("quantal", JobHostUnits) 2335 c.Assert(err, jc.ErrorIsNil) 2336 err = u.AssignToMachine(m) 2337 c.Assert(err, jc.ErrorIsNil) 2338 err = u.OpenPort("udp", 17070) 2339 c.Assert(err, jc.ErrorIsNil) 2340 2341 return changeTestCase{ 2342 about: "unit is updated if it's in backing and in multiwatcher.Store", 2343 initialContents: []multiwatcher.EntityInfo{&multiwatcher.UnitInfo{ 2344 ModelUUID: st.ModelUUID(), 2345 Name: "wordpress/0", 2346 JujuStatus: multiwatcher.StatusInfo{ 2347 Current: "idle", 2348 Message: "", 2349 Data: map[string]interface{}{}, 2350 Since: &now, 2351 }, 2352 WorkloadStatus: multiwatcher.StatusInfo{ 2353 Current: "error", 2354 Message: "another failure", 2355 Data: map[string]interface{}{}, 2356 Since: &now, 2357 }, 2358 Ports: []network.Port{{"udp", 17070}}, 2359 PortRanges: []network.PortRange{{17070, 17070, "udp"}}, 2360 }}, 2361 change: watcher.Change{ 2362 C: "units", 2363 Id: st.docID("wordpress/0"), 2364 }, 2365 expectContents: []multiwatcher.EntityInfo{ 2366 &multiwatcher.UnitInfo{ 2367 ModelUUID: st.ModelUUID(), 2368 Name: "wordpress/0", 2369 Service: "wordpress", 2370 Series: "quantal", 2371 MachineId: "0", 2372 Ports: []network.Port{{"udp", 17070}}, 2373 PortRanges: []network.PortRange{{17070, 17070, "udp"}}, 2374 JujuStatus: multiwatcher.StatusInfo{ 2375 Current: "idle", 2376 Message: "", 2377 Data: map[string]interface{}{}, 2378 }, 2379 WorkloadStatus: multiwatcher.StatusInfo{ 2380 Current: "error", 2381 Message: "another failure", 2382 Data: map[string]interface{}{}, 2383 }, 2384 }}} 2385 }, 2386 func(c *gc.C, st *State) changeTestCase { 2387 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), owner) 2388 u, err := wordpress.AddUnit() 2389 c.Assert(err, jc.ErrorIsNil) 2390 m, err := st.AddMachine("quantal", JobHostUnits) 2391 c.Assert(err, jc.ErrorIsNil) 2392 err = u.AssignToMachine(m) 2393 c.Assert(err, jc.ErrorIsNil) 2394 err = u.OpenPort("tcp", 4242) 2395 c.Assert(err, jc.ErrorIsNil) 2396 2397 return changeTestCase{ 2398 about: "unit info is updated if a port is opened on the machine it is placed in", 2399 initialContents: []multiwatcher.EntityInfo{ 2400 &multiwatcher.UnitInfo{ 2401 ModelUUID: st.ModelUUID(), 2402 Name: "wordpress/0", 2403 }, 2404 &multiwatcher.MachineInfo{ 2405 ModelUUID: st.ModelUUID(), 2406 Id: "0", 2407 }, 2408 }, 2409 change: watcher.Change{ 2410 C: openedPortsC, 2411 Id: st.docID("m#0#"), 2412 }, 2413 expectContents: []multiwatcher.EntityInfo{ 2414 &multiwatcher.UnitInfo{ 2415 ModelUUID: st.ModelUUID(), 2416 Name: "wordpress/0", 2417 Ports: []network.Port{{"tcp", 4242}}, 2418 PortRanges: []network.PortRange{{4242, 4242, "tcp"}}, 2419 }, 2420 &multiwatcher.MachineInfo{ 2421 ModelUUID: st.ModelUUID(), 2422 Id: "0", 2423 }, 2424 }} 2425 }, 2426 func(c *gc.C, st *State) changeTestCase { 2427 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), owner) 2428 u, err := wordpress.AddUnit() 2429 c.Assert(err, jc.ErrorIsNil) 2430 m, err := st.AddMachine("quantal", JobHostUnits) 2431 c.Assert(err, jc.ErrorIsNil) 2432 err = u.AssignToMachine(m) 2433 c.Assert(err, jc.ErrorIsNil) 2434 err = u.OpenPorts("tcp", 21, 22) 2435 c.Assert(err, jc.ErrorIsNil) 2436 2437 return changeTestCase{ 2438 about: "unit is created if a port is opened on the machine it is placed in", 2439 initialContents: []multiwatcher.EntityInfo{ 2440 &multiwatcher.MachineInfo{ 2441 ModelUUID: st.ModelUUID(), 2442 Id: "0", 2443 }, 2444 }, 2445 change: watcher.Change{ 2446 C: "units", 2447 Id: st.docID("wordpress/0"), 2448 }, 2449 expectContents: []multiwatcher.EntityInfo{ 2450 &multiwatcher.UnitInfo{ 2451 ModelUUID: st.ModelUUID(), 2452 Name: "wordpress/0", 2453 Service: "wordpress", 2454 Series: "quantal", 2455 MachineId: "0", 2456 WorkloadStatus: multiwatcher.StatusInfo{ 2457 Current: "unknown", 2458 Message: "Waiting for agent initialization to finish", 2459 Data: map[string]interface{}{}, 2460 }, 2461 JujuStatus: multiwatcher.StatusInfo{ 2462 Current: "allocating", 2463 Data: map[string]interface{}{}, 2464 }, 2465 Ports: []network.Port{{"tcp", 21}, {"tcp", 22}}, 2466 PortRanges: []network.PortRange{{21, 22, "tcp"}}, 2467 }, 2468 &multiwatcher.MachineInfo{ 2469 ModelUUID: st.ModelUUID(), 2470 Id: "0", 2471 }, 2472 }} 2473 }, 2474 func(c *gc.C, st *State) changeTestCase { 2475 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), owner) 2476 u, err := wordpress.AddUnit() 2477 c.Assert(err, jc.ErrorIsNil) 2478 m, err := st.AddMachine("quantal", JobHostUnits) 2479 c.Assert(err, jc.ErrorIsNil) 2480 err = u.AssignToMachine(m) 2481 c.Assert(err, jc.ErrorIsNil) 2482 err = u.OpenPort("tcp", 12345) 2483 c.Assert(err, jc.ErrorIsNil) 2484 publicAddress := network.NewScopedAddress("public", network.ScopePublic) 2485 privateAddress := network.NewScopedAddress("private", network.ScopeCloudLocal) 2486 err = m.SetProviderAddresses(publicAddress, privateAddress) 2487 c.Assert(err, jc.ErrorIsNil) 2488 err = u.SetAgentStatus(status.StatusError, "failure", nil) 2489 c.Assert(err, jc.ErrorIsNil) 2490 2491 return changeTestCase{ 2492 about: "unit addresses are read from the assigned machine for recent Juju releases", 2493 change: watcher.Change{ 2494 C: "units", 2495 Id: st.docID("wordpress/0"), 2496 }, 2497 expectContents: []multiwatcher.EntityInfo{ 2498 &multiwatcher.UnitInfo{ 2499 ModelUUID: st.ModelUUID(), 2500 Name: "wordpress/0", 2501 Service: "wordpress", 2502 Series: "quantal", 2503 PublicAddress: "public", 2504 PrivateAddress: "private", 2505 MachineId: "0", 2506 Ports: []network.Port{{"tcp", 12345}}, 2507 PortRanges: []network.PortRange{{12345, 12345, "tcp"}}, 2508 JujuStatus: multiwatcher.StatusInfo{ 2509 Current: "idle", 2510 Message: "", 2511 Data: map[string]interface{}{}, 2512 }, 2513 WorkloadStatus: multiwatcher.StatusInfo{ 2514 Current: "error", 2515 Message: "failure", 2516 Data: map[string]interface{}{}, 2517 }, 2518 }}} 2519 }, 2520 func(c *gc.C, st *State) changeTestCase { 2521 return changeTestCase{ 2522 about: "no unit in state -> do nothing", 2523 change: watcher.Change{ 2524 C: "statuses", 2525 Id: st.docID("u#wordpress/0"), 2526 }} 2527 }, 2528 func(c *gc.C, st *State) changeTestCase { 2529 return changeTestCase{ 2530 about: "no change if status is not in backing", 2531 initialContents: []multiwatcher.EntityInfo{&multiwatcher.UnitInfo{ 2532 ModelUUID: st.ModelUUID(), 2533 Name: "wordpress/0", 2534 Service: "wordpress", 2535 JujuStatus: multiwatcher.StatusInfo{ 2536 Current: "idle", 2537 Message: "", 2538 Data: map[string]interface{}{}, 2539 Since: &now, 2540 }, 2541 WorkloadStatus: multiwatcher.StatusInfo{ 2542 Current: "error", 2543 Message: "failure", 2544 Data: map[string]interface{}{}, 2545 Since: &now, 2546 }, 2547 }}, 2548 change: watcher.Change{ 2549 C: "statuses", 2550 Id: st.docID("u#wordpress/0"), 2551 }, 2552 expectContents: []multiwatcher.EntityInfo{ 2553 &multiwatcher.UnitInfo{ 2554 ModelUUID: st.ModelUUID(), 2555 Name: "wordpress/0", 2556 Service: "wordpress", 2557 JujuStatus: multiwatcher.StatusInfo{ 2558 Current: "idle", 2559 Message: "", 2560 Data: map[string]interface{}{}, 2561 }, 2562 WorkloadStatus: multiwatcher.StatusInfo{ 2563 Current: "error", 2564 Message: "failure", 2565 Data: map[string]interface{}{}, 2566 }, 2567 }}} 2568 }, 2569 func(c *gc.C, st *State) changeTestCase { 2570 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), owner) 2571 u, err := wordpress.AddUnit() 2572 c.Assert(err, jc.ErrorIsNil) 2573 err = u.SetAgentStatus(status.StatusIdle, "", nil) 2574 c.Assert(err, jc.ErrorIsNil) 2575 2576 return changeTestCase{ 2577 about: "status is changed if the unit exists in the store", 2578 initialContents: []multiwatcher.EntityInfo{&multiwatcher.UnitInfo{ 2579 ModelUUID: st.ModelUUID(), 2580 Name: "wordpress/0", 2581 Service: "wordpress", 2582 JujuStatus: multiwatcher.StatusInfo{ 2583 Current: "idle", 2584 Message: "", 2585 Data: map[string]interface{}{}, 2586 Since: &now, 2587 }, 2588 WorkloadStatus: multiwatcher.StatusInfo{ 2589 Current: "maintenance", 2590 Message: "working", 2591 Data: map[string]interface{}{}, 2592 Since: &now, 2593 }, 2594 }}, 2595 change: watcher.Change{ 2596 C: "statuses", 2597 Id: st.docID("u#wordpress/0"), 2598 }, 2599 expectContents: []multiwatcher.EntityInfo{ 2600 &multiwatcher.UnitInfo{ 2601 ModelUUID: st.ModelUUID(), 2602 Name: "wordpress/0", 2603 Service: "wordpress", 2604 WorkloadStatus: multiwatcher.StatusInfo{ 2605 Current: "maintenance", 2606 Message: "working", 2607 Data: map[string]interface{}{}, 2608 }, 2609 JujuStatus: multiwatcher.StatusInfo{ 2610 Current: "idle", 2611 Message: "", 2612 Data: map[string]interface{}{}, 2613 }, 2614 }}} 2615 }, 2616 func(c *gc.C, st *State) changeTestCase { 2617 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), owner) 2618 u, err := wordpress.AddUnit() 2619 c.Assert(err, jc.ErrorIsNil) 2620 err = u.SetAgentStatus(status.StatusIdle, "", nil) 2621 c.Assert(err, jc.ErrorIsNil) 2622 err = u.SetStatus(status.StatusMaintenance, "doing work", nil) 2623 c.Assert(err, jc.ErrorIsNil) 2624 2625 return changeTestCase{ 2626 about: "unit status is changed if the agent comes off error state", 2627 initialContents: []multiwatcher.EntityInfo{&multiwatcher.UnitInfo{ 2628 ModelUUID: st.ModelUUID(), 2629 Name: "wordpress/0", 2630 Service: "wordpress", 2631 JujuStatus: multiwatcher.StatusInfo{ 2632 Current: "idle", 2633 Message: "", 2634 Data: map[string]interface{}{}, 2635 Since: &now, 2636 }, 2637 WorkloadStatus: multiwatcher.StatusInfo{ 2638 Current: "error", 2639 Message: "failure", 2640 Data: map[string]interface{}{}, 2641 Since: &now, 2642 }, 2643 }}, 2644 change: watcher.Change{ 2645 C: "statuses", 2646 Id: st.docID("u#wordpress/0"), 2647 }, 2648 expectContents: []multiwatcher.EntityInfo{ 2649 &multiwatcher.UnitInfo{ 2650 ModelUUID: st.ModelUUID(), 2651 Name: "wordpress/0", 2652 Service: "wordpress", 2653 WorkloadStatus: multiwatcher.StatusInfo{ 2654 Current: "maintenance", 2655 Message: "doing work", 2656 Data: map[string]interface{}{}, 2657 }, 2658 JujuStatus: multiwatcher.StatusInfo{ 2659 Current: "idle", 2660 Message: "", 2661 Data: map[string]interface{}{}, 2662 }, 2663 }}} 2664 }, 2665 func(c *gc.C, st *State) changeTestCase { 2666 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), owner) 2667 u, err := wordpress.AddUnit() 2668 c.Assert(err, jc.ErrorIsNil) 2669 err = u.SetAgentStatus(status.StatusError, "hook error", map[string]interface{}{ 2670 "1st-key": "one", 2671 "2nd-key": 2, 2672 "3rd-key": true, 2673 }) 2674 c.Assert(err, jc.ErrorIsNil) 2675 2676 return changeTestCase{ 2677 about: "status is changed with additional status data", 2678 initialContents: []multiwatcher.EntityInfo{&multiwatcher.UnitInfo{ 2679 ModelUUID: st.ModelUUID(), 2680 Name: "wordpress/0", 2681 Service: "wordpress", 2682 JujuStatus: multiwatcher.StatusInfo{ 2683 Current: "idle", 2684 Message: "", 2685 Data: map[string]interface{}{}, 2686 Since: &now, 2687 }, 2688 WorkloadStatus: multiwatcher.StatusInfo{ 2689 Current: "active", 2690 Since: &now, 2691 }, 2692 }}, 2693 change: watcher.Change{ 2694 C: "statuses", 2695 Id: st.docID("u#wordpress/0"), 2696 }, 2697 expectContents: []multiwatcher.EntityInfo{ 2698 &multiwatcher.UnitInfo{ 2699 ModelUUID: st.ModelUUID(), 2700 Name: "wordpress/0", 2701 Service: "wordpress", 2702 WorkloadStatus: multiwatcher.StatusInfo{ 2703 Current: "error", 2704 Message: "hook error", 2705 Data: map[string]interface{}{ 2706 "1st-key": "one", 2707 "2nd-key": 2, 2708 "3rd-key": true, 2709 }, 2710 }, 2711 JujuStatus: multiwatcher.StatusInfo{ 2712 Current: "idle", 2713 Message: "", 2714 Data: map[string]interface{}{}, 2715 }, 2716 }}} 2717 }, 2718 func(c *gc.C, st *State) changeTestCase { 2719 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), owner) 2720 u, err := wordpress.AddUnit() 2721 c.Assert(err, jc.ErrorIsNil) 2722 err = u.SetStatus(status.StatusActive, "", nil) 2723 c.Assert(err, jc.ErrorIsNil) 2724 2725 return changeTestCase{ 2726 about: "service status is changed if the unit status changes", 2727 initialContents: []multiwatcher.EntityInfo{ 2728 &multiwatcher.UnitInfo{ 2729 ModelUUID: st.ModelUUID(), 2730 Name: "wordpress/0", 2731 Service: "wordpress", 2732 JujuStatus: multiwatcher.StatusInfo{ 2733 Current: "idle", 2734 Message: "", 2735 Data: map[string]interface{}{}, 2736 Since: &now, 2737 }, 2738 WorkloadStatus: multiwatcher.StatusInfo{ 2739 Current: "error", 2740 Message: "failure", 2741 Data: map[string]interface{}{}, 2742 Since: &now, 2743 }, 2744 }, 2745 &multiwatcher.ServiceInfo{ 2746 ModelUUID: st.ModelUUID(), 2747 Name: "wordpress", 2748 Status: multiwatcher.StatusInfo{ 2749 Current: "error", 2750 Message: "failure", 2751 Data: map[string]interface{}{}, 2752 Since: &now, 2753 }, 2754 }, 2755 }, 2756 change: watcher.Change{ 2757 C: "statuses", 2758 Id: st.docID("u#wordpress/0#charm"), 2759 }, 2760 expectContents: []multiwatcher.EntityInfo{ 2761 &multiwatcher.UnitInfo{ 2762 ModelUUID: st.ModelUUID(), 2763 Name: "wordpress/0", 2764 Service: "wordpress", 2765 WorkloadStatus: multiwatcher.StatusInfo{ 2766 Current: "active", 2767 Message: "", 2768 Data: map[string]interface{}{}, 2769 }, 2770 JujuStatus: multiwatcher.StatusInfo{ 2771 Current: "idle", 2772 Message: "", 2773 Data: map[string]interface{}{}, 2774 }, 2775 }, 2776 &multiwatcher.ServiceInfo{ 2777 ModelUUID: st.ModelUUID(), 2778 Name: "wordpress", 2779 Status: multiwatcher.StatusInfo{ 2780 Current: "active", 2781 Message: "", 2782 Data: map[string]interface{}{}, 2783 }, 2784 }, 2785 }} 2786 }, 2787 } 2788 runChangeTests(c, changeTestFuncs) 2789 } 2790 2791 // initFlag helps to control the different test scenarios. 2792 type initFlag int 2793 2794 const ( 2795 noFlag initFlag = 0 2796 assignUnit initFlag = 1 2797 openPorts initFlag = 2 2798 closePorts initFlag = 4 2799 ) 2800 2801 func testChangeUnitsNonNilPorts(c *gc.C, owner names.UserTag, runChangeTests func(*gc.C, []changeTestFunc)) { 2802 initEnv := func(c *gc.C, st *State, flag initFlag) { 2803 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), owner) 2804 u, err := wordpress.AddUnit() 2805 c.Assert(err, jc.ErrorIsNil) 2806 m, err := st.AddMachine("quantal", JobHostUnits) 2807 c.Assert(err, jc.ErrorIsNil) 2808 if flag&assignUnit != 0 { 2809 // Assign the unit. 2810 err = u.AssignToMachine(m) 2811 c.Assert(err, jc.ErrorIsNil) 2812 } 2813 if flag&openPorts != 0 { 2814 // Add a network to the machine and open a port. 2815 publicAddress := network.NewScopedAddress("1.2.3.4", network.ScopePublic) 2816 privateAddress := network.NewScopedAddress("4.3.2.1", network.ScopeCloudLocal) 2817 err = m.SetProviderAddresses(publicAddress, privateAddress) 2818 c.Assert(err, jc.ErrorIsNil) 2819 err = u.OpenPort("tcp", 12345) 2820 if flag&assignUnit != 0 { 2821 c.Assert(err, jc.ErrorIsNil) 2822 } else { 2823 c.Assert(err, gc.ErrorMatches, `cannot open ports 12345-12345/tcp \("wordpress/0"\) for unit "wordpress/0".*`) 2824 c.Assert(err, jc.Satisfies, errors.IsNotAssigned) 2825 } 2826 } 2827 if flag&closePorts != 0 { 2828 // Close the port again (only if been opened before). 2829 err = u.ClosePort("tcp", 12345) 2830 c.Assert(err, jc.ErrorIsNil) 2831 } 2832 } 2833 changeTestFuncs := []changeTestFunc{ 2834 func(c *gc.C, st *State) changeTestCase { 2835 initEnv(c, st, assignUnit) 2836 2837 return changeTestCase{ 2838 about: "don't open ports on unit", 2839 change: watcher.Change{ 2840 C: "units", 2841 Id: st.docID("wordpress/0"), 2842 }, 2843 expectContents: []multiwatcher.EntityInfo{ 2844 &multiwatcher.UnitInfo{ 2845 ModelUUID: st.ModelUUID(), 2846 Name: "wordpress/0", 2847 Service: "wordpress", 2848 Series: "quantal", 2849 MachineId: "0", 2850 Ports: []network.Port{}, 2851 PortRanges: []network.PortRange{}, 2852 WorkloadStatus: multiwatcher.StatusInfo{ 2853 Current: "unknown", 2854 Message: "Waiting for agent initialization to finish", 2855 Data: map[string]interface{}{}, 2856 }, 2857 JujuStatus: multiwatcher.StatusInfo{ 2858 Current: "allocating", 2859 Message: "", 2860 Data: map[string]interface{}{}, 2861 }, 2862 }}} 2863 }, 2864 func(c *gc.C, st *State) changeTestCase { 2865 initEnv(c, st, assignUnit|openPorts) 2866 2867 return changeTestCase{ 2868 about: "open a port on unit", 2869 change: watcher.Change{ 2870 C: "units", 2871 Id: st.docID("wordpress/0"), 2872 }, 2873 expectContents: []multiwatcher.EntityInfo{ 2874 &multiwatcher.UnitInfo{ 2875 ModelUUID: st.ModelUUID(), 2876 Name: "wordpress/0", 2877 Service: "wordpress", 2878 Series: "quantal", 2879 MachineId: "0", 2880 PublicAddress: "1.2.3.4", 2881 PrivateAddress: "4.3.2.1", 2882 Ports: []network.Port{{"tcp", 12345}}, 2883 PortRanges: []network.PortRange{{12345, 12345, "tcp"}}, 2884 WorkloadStatus: multiwatcher.StatusInfo{ 2885 Current: "unknown", 2886 Message: "Waiting for agent initialization to finish", 2887 Data: map[string]interface{}{}, 2888 }, 2889 JujuStatus: multiwatcher.StatusInfo{ 2890 Current: "allocating", 2891 Message: "", 2892 Data: map[string]interface{}{}, 2893 }, 2894 }}} 2895 }, 2896 func(c *gc.C, st *State) changeTestCase { 2897 initEnv(c, st, assignUnit|openPorts|closePorts) 2898 2899 return changeTestCase{ 2900 about: "open a port on unit and close it again", 2901 change: watcher.Change{ 2902 C: "units", 2903 Id: st.docID("wordpress/0"), 2904 }, 2905 expectContents: []multiwatcher.EntityInfo{ 2906 &multiwatcher.UnitInfo{ 2907 ModelUUID: st.ModelUUID(), 2908 Name: "wordpress/0", 2909 Service: "wordpress", 2910 Series: "quantal", 2911 MachineId: "0", 2912 PublicAddress: "1.2.3.4", 2913 PrivateAddress: "4.3.2.1", 2914 Ports: []network.Port{}, 2915 PortRanges: []network.PortRange{}, 2916 WorkloadStatus: multiwatcher.StatusInfo{ 2917 Current: "unknown", 2918 Message: "Waiting for agent initialization to finish", 2919 Data: map[string]interface{}{}, 2920 }, 2921 JujuStatus: multiwatcher.StatusInfo{ 2922 Current: "allocating", 2923 Message: "", 2924 Data: map[string]interface{}{}, 2925 }, 2926 }}} 2927 }, 2928 func(c *gc.C, st *State) changeTestCase { 2929 initEnv(c, st, openPorts) 2930 2931 return changeTestCase{ 2932 about: "open ports on an unassigned unit", 2933 change: watcher.Change{ 2934 C: "units", 2935 Id: st.docID("wordpress/0"), 2936 }, 2937 expectContents: []multiwatcher.EntityInfo{ 2938 &multiwatcher.UnitInfo{ 2939 ModelUUID: st.ModelUUID(), 2940 Name: "wordpress/0", 2941 Service: "wordpress", 2942 Series: "quantal", 2943 Ports: []network.Port{}, 2944 PortRanges: []network.PortRange{}, 2945 WorkloadStatus: multiwatcher.StatusInfo{ 2946 Current: "unknown", 2947 Message: "Waiting for agent initialization to finish", 2948 Data: map[string]interface{}{}, 2949 }, 2950 JujuStatus: multiwatcher.StatusInfo{ 2951 Current: "allocating", 2952 Message: "", 2953 Data: map[string]interface{}{}, 2954 }, 2955 }}} 2956 }, 2957 } 2958 runChangeTests(c, changeTestFuncs) 2959 } 2960 2961 func newTestAllWatcher(st *State, c *gc.C) *testWatcher { 2962 return newTestWatcher(newAllWatcherStateBacking(st), st, c) 2963 } 2964 2965 func newTestAllModelWatcher(st *State, c *gc.C) *testWatcher { 2966 return newTestWatcher(NewAllModelWatcherStateBacking(st), st, c) 2967 } 2968 2969 type testWatcher struct { 2970 st *State 2971 c *gc.C 2972 b Backing 2973 sm *storeManager 2974 w *Multiwatcher 2975 deltas chan []multiwatcher.Delta 2976 } 2977 2978 func newTestWatcher(b Backing, st *State, c *gc.C) *testWatcher { 2979 sm := newStoreManager(b) 2980 w := NewMultiwatcher(sm) 2981 tw := &testWatcher{ 2982 st: st, 2983 c: c, 2984 b: b, 2985 sm: sm, 2986 w: w, 2987 deltas: make(chan []multiwatcher.Delta), 2988 } 2989 go func() { 2990 for { 2991 deltas, err := tw.w.Next() 2992 if err != nil { 2993 break 2994 } 2995 tw.deltas <- deltas 2996 } 2997 }() 2998 return tw 2999 } 3000 3001 func (tw *testWatcher) Next(timeout time.Duration) []multiwatcher.Delta { 3002 select { 3003 case d := <-tw.deltas: 3004 return d 3005 case <-time.After(timeout): 3006 return nil 3007 } 3008 } 3009 3010 func (tw *testWatcher) NumDeltas() int { 3011 count := 0 3012 tw.st.StartSync() 3013 for { 3014 // TODO(mjs) - this is somewhat fragile. There are no 3015 // guarentees that the watcher will be able to return deltas 3016 // in ShortWait time. 3017 deltas := tw.Next(testing.ShortWait) 3018 if len(deltas) == 0 { 3019 break 3020 } 3021 count += len(deltas) 3022 } 3023 return count 3024 } 3025 3026 func (tw *testWatcher) All(expectedCount int) []multiwatcher.Delta { 3027 var allDeltas []multiwatcher.Delta 3028 tw.st.StartSync() 3029 3030 // Wait up to LongWait for the expected deltas to arrive, unless 3031 // we don't expect any (then just wait for ShortWait). 3032 maxDuration := testing.LongWait 3033 if expectedCount <= 0 { 3034 maxDuration = testing.ShortWait 3035 } 3036 3037 now := time.Now() 3038 maxTime := now.Add(maxDuration) 3039 for { 3040 remaining := maxTime.Sub(now) 3041 if remaining < time.Duration(0) { 3042 break // timed out 3043 } 3044 3045 deltas := tw.Next(remaining) 3046 if len(deltas) > 0 { 3047 allDeltas = append(allDeltas, deltas...) 3048 if len(allDeltas) >= expectedCount { 3049 break 3050 } 3051 } 3052 3053 now = time.Now() 3054 } 3055 return allDeltas 3056 } 3057 3058 func (tw *testWatcher) Stop() { 3059 tw.c.Assert(tw.w.Stop(), jc.ErrorIsNil) 3060 tw.c.Assert(tw.sm.Stop(), jc.ErrorIsNil) 3061 tw.c.Assert(tw.b.Release(), jc.ErrorIsNil) 3062 } 3063 3064 func (tw *testWatcher) AssertNoChange() { 3065 tw.c.Assert(tw.NumDeltas(), gc.Equals, 0) 3066 } 3067 3068 func (tw *testWatcher) AssertChanges() { 3069 tw.c.Assert(tw.NumDeltas(), jc.GreaterThan, 0) 3070 } 3071 3072 type entityInfoSlice []multiwatcher.EntityInfo 3073 3074 func (s entityInfoSlice) Len() int { return len(s) } 3075 func (s entityInfoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 3076 func (s entityInfoSlice) Less(i, j int) bool { 3077 id0, id1 := s[i].EntityId(), s[j].EntityId() 3078 if id0.Kind != id1.Kind { 3079 return id0.Kind < id1.Kind 3080 } 3081 if id0.ModelUUID != id1.ModelUUID { 3082 return id0.ModelUUID < id1.ModelUUID 3083 } 3084 return id0.Id < id1.Id 3085 } 3086 3087 func checkDeltasEqual(c *gc.C, d0, d1 []multiwatcher.Delta) { 3088 // Deltas are returned in arbitrary order, so we compare them as maps. 3089 c.Check(deltaMap(d0), jc.DeepEquals, deltaMap(d1)) 3090 } 3091 3092 func deltaMap(deltas []multiwatcher.Delta) map[interface{}]multiwatcher.EntityInfo { 3093 m := make(map[interface{}]multiwatcher.EntityInfo) 3094 for _, d := range deltas { 3095 id := d.Entity.EntityId() 3096 if d.Removed { 3097 m[id] = nil 3098 } else { 3099 m[id] = substNilSinceTimeForEntityNoCheck(d.Entity) 3100 } 3101 } 3102 return m 3103 } 3104 3105 func makeActionInfo(a Action, st *State) multiwatcher.ActionInfo { 3106 results, message := a.Results() 3107 return multiwatcher.ActionInfo{ 3108 ModelUUID: st.ModelUUID(), 3109 Id: a.Id(), 3110 Receiver: a.Receiver(), 3111 Name: a.Name(), 3112 Parameters: a.Parameters(), 3113 Status: string(a.Status()), 3114 Message: message, 3115 Results: results, 3116 Enqueued: a.Enqueued(), 3117 Started: a.Started(), 3118 Completed: a.Completed(), 3119 } 3120 } 3121 3122 func jcDeepEqualsCheck(c *gc.C, got, want interface{}) bool { 3123 ok, err := jc.DeepEqual(got, want) 3124 if ok { 3125 c.Check(err, jc.ErrorIsNil) 3126 } 3127 return ok 3128 } 3129 3130 // assertEntitiesEqual is a specialised version of the typical 3131 // jc.DeepEquals check that provides more informative output when 3132 // comparing EntityInfo slices. 3133 func assertEntitiesEqual(c *gc.C, got, want []multiwatcher.EntityInfo) { 3134 if jcDeepEqualsCheck(c, got, want) { 3135 return 3136 } 3137 if len(got) != len(want) { 3138 c.Errorf("entity length mismatch; got %d; want %d", len(got), len(want)) 3139 } else { 3140 c.Errorf("entity contents mismatch; same length %d", len(got)) 3141 } 3142 // Lets construct a decent output. 3143 var errorOutput string 3144 errorOutput = "\ngot: \n" 3145 for _, e := range got { 3146 errorOutput += fmt.Sprintf(" %T %#v\n", e, e) 3147 } 3148 errorOutput += "expected: \n" 3149 for _, e := range want { 3150 errorOutput += fmt.Sprintf(" %T %#v\n", e, e) 3151 } 3152 3153 c.Errorf(errorOutput) 3154 3155 var firstDiffError string 3156 if len(got) == len(want) { 3157 for i := 0; i < len(got); i++ { 3158 g := got[i] 3159 w := want[i] 3160 if !jcDeepEqualsCheck(c, g, w) { 3161 firstDiffError += "\n" 3162 firstDiffError += fmt.Sprintf("first difference at position %d\n", i) 3163 firstDiffError += "got:\n" 3164 firstDiffError += fmt.Sprintf(" %T %#v\n", g, g) 3165 firstDiffError += "expected:\n" 3166 firstDiffError += fmt.Sprintf(" %T %#v\n", w, w) 3167 break 3168 } 3169 } 3170 c.Errorf(firstDiffError) 3171 } 3172 c.FailNow() 3173 } 3174 3175 func deepEqual(c *gc.C, got, want interface{}) bool { 3176 same, err := jc.DeepEqual(got, want) 3177 return err == nil && same 3178 }