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