github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/state/megawatcher_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 "reflect" 9 "sort" 10 "time" 11 12 "github.com/juju/loggo" 13 "github.com/juju/names" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/utils" 16 gc "gopkg.in/check.v1" 17 "gopkg.in/juju/charm.v4" 18 19 "github.com/juju/juju/constraints" 20 "github.com/juju/juju/instance" 21 "github.com/juju/juju/network" 22 "github.com/juju/juju/state/multiwatcher" 23 "github.com/juju/juju/state/watcher" 24 "github.com/juju/juju/testing" 25 ) 26 27 var ( 28 _ backingEntityDoc = (*backingMachine)(nil) 29 _ backingEntityDoc = (*backingUnit)(nil) 30 _ backingEntityDoc = (*backingService)(nil) 31 _ backingEntityDoc = (*backingRelation)(nil) 32 _ backingEntityDoc = (*backingAnnotation)(nil) 33 _ backingEntityDoc = (*backingStatus)(nil) 34 _ backingEntityDoc = (*backingConstraints)(nil) 35 _ backingEntityDoc = (*backingSettings)(nil) 36 _ backingEntityDoc = (*backingAction)(nil) 37 ) 38 39 var dottedConfig = ` 40 options: 41 key.dotted: {default: My Key, description: Desc, type: string} 42 ` 43 var _ = gc.Suite(&storeManagerStateSuite{}) 44 45 type storeManagerStateSuite struct { 46 internalStateSuite 47 OtherState *State 48 } 49 50 func (s *storeManagerStateSuite) SetUpTest(c *gc.C) { 51 s.internalStateSuite.SetUpTest(c) 52 53 s.OtherState = s.newState(c) 54 s.AddCleanup(func(*gc.C) { s.OtherState.Close() }) 55 } 56 57 func (s *storeManagerStateSuite) newState(c *gc.C) *State { 58 uuid, err := utils.NewUUID() 59 c.Assert(err, jc.ErrorIsNil) 60 cfg := testing.CustomEnvironConfig(c, testing.Attrs{ 61 "name": "testenv", 62 "uuid": uuid.String(), 63 }) 64 _, st, err := s.state.NewEnvironment(cfg, s.owner) 65 c.Assert(err, jc.ErrorIsNil) 66 return st 67 } 68 69 func (s *storeManagerStateSuite) Reset(c *gc.C) { 70 s.TearDownTest(c) 71 s.SetUpTest(c) 72 } 73 74 func assertEntitiesEqual(c *gc.C, got, want []multiwatcher.EntityInfo) { 75 if len(got) == 0 { 76 got = nil 77 } 78 if len(want) == 0 { 79 want = nil 80 } 81 if reflect.DeepEqual(got, want) { 82 return 83 } 84 c.Errorf("entity mismatch; got len %d; want %d", len(got), len(want)) 85 c.Logf("got:") 86 for _, e := range got { 87 c.Logf("\t%T %#v", e, e) 88 } 89 c.Logf("expected:") 90 for _, e := range want { 91 c.Logf("\t%T %#v", e, e) 92 } 93 94 if len(got) == len(want) { 95 for i := 0; i < len(got); i++ { 96 g := got[i] 97 w := want[i] 98 if !reflect.DeepEqual(g, w) { 99 c.Logf("") 100 c.Logf("first difference at position %d", i) 101 c.Logf("got:") 102 c.Logf("\t%T %#v", g, g) 103 c.Logf("expected:") 104 c.Logf("\t%T %#v", w, w) 105 break 106 } 107 } 108 } 109 c.FailNow() 110 } 111 112 func (s *storeManagerStateSuite) TestStateBackingGetAll(c *gc.C) { 113 expectEntities := s.setUpScenario(c, s.state, 2) 114 s.checkGetAll(c, expectEntities) 115 } 116 117 func (s *storeManagerStateSuite) TestStateBackingGetAllMultiEnv(c *gc.C) { 118 // Set up 2 environments and ensure that GetAll returns the 119 // entities for the first environment with no errors. 120 expectEntities := s.setUpScenario(c, s.state, 2) 121 122 // Use more units in the second env to ensure the number of 123 // entities will mismatch if environment filtering isn't in place. 124 s.setUpScenario(c, s.OtherState, 4) 125 126 s.checkGetAll(c, expectEntities) 127 } 128 129 func (s *storeManagerStateSuite) checkGetAll(c *gc.C, expectEntities entityInfoSlice) { 130 b := newAllWatcherStateBacking(s.state) 131 all := newStore() 132 err := b.GetAll(all) 133 c.Assert(err, jc.ErrorIsNil) 134 var gotEntities entityInfoSlice = all.All() 135 sort.Sort(gotEntities) 136 sort.Sort(expectEntities) 137 assertEntitiesEqual(c, gotEntities, expectEntities) 138 } 139 140 // setUpScenario adds some entities to the state so that 141 // we can check that they all get pulled in by 142 // allWatcherStateBacking.GetAll. 143 func (s *storeManagerStateSuite) setUpScenario(c *gc.C, st *State, units int) (entities entityInfoSlice) { 144 add := func(e multiwatcher.EntityInfo) { 145 entities = append(entities, e) 146 } 147 m, err := st.AddMachine("quantal", JobHostUnits) 148 c.Assert(err, jc.ErrorIsNil) 149 c.Assert(m.Tag(), gc.Equals, names.NewMachineTag("0")) 150 err = m.SetHasVote(true) 151 c.Assert(err, jc.ErrorIsNil) 152 // TODO(dfc) instance.Id should take a TAG! 153 err = m.SetProvisioned(instance.Id("i-"+m.Tag().String()), "fake_nonce", nil) 154 c.Assert(err, jc.ErrorIsNil) 155 hc, err := m.HardwareCharacteristics() 156 c.Assert(err, jc.ErrorIsNil) 157 err = m.SetAddresses(network.NewAddress("example.com", network.ScopeUnknown)) 158 c.Assert(err, jc.ErrorIsNil) 159 add(&multiwatcher.MachineInfo{ 160 Id: "0", 161 InstanceId: "i-machine-0", 162 Status: multiwatcher.Status("pending"), 163 Life: multiwatcher.Life("alive"), 164 Series: "quantal", 165 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 166 Addresses: m.Addresses(), 167 HardwareCharacteristics: hc, 168 HasVote: true, 169 WantsVote: false, 170 }) 171 172 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 173 err = wordpress.SetExposed() 174 c.Assert(err, jc.ErrorIsNil) 175 err = wordpress.SetMinUnits(units) 176 c.Assert(err, jc.ErrorIsNil) 177 err = wordpress.SetConstraints(constraints.MustParse("mem=100M")) 178 c.Assert(err, jc.ErrorIsNil) 179 setServiceConfigAttr(c, wordpress, "blog-title", "boring") 180 add(&multiwatcher.ServiceInfo{ 181 Name: "wordpress", 182 Exposed: true, 183 CharmURL: serviceCharmURL(wordpress).String(), 184 OwnerTag: s.owner.String(), 185 Life: multiwatcher.Life("alive"), 186 MinUnits: units, 187 Constraints: constraints.MustParse("mem=100M"), 188 Config: charm.Settings{"blog-title": "boring"}, 189 Subordinate: false, 190 }) 191 pairs := map[string]string{"x": "12", "y": "99"} 192 err = st.SetAnnotations(wordpress, pairs) 193 c.Assert(err, jc.ErrorIsNil) 194 add(&multiwatcher.AnnotationInfo{ 195 Tag: "service-wordpress", 196 Annotations: pairs, 197 }) 198 199 logging := AddTestingService(c, st, "logging", AddTestingCharm(c, st, "logging"), s.owner) 200 add(&multiwatcher.ServiceInfo{ 201 Name: "logging", 202 CharmURL: serviceCharmURL(logging).String(), 203 OwnerTag: s.owner.String(), 204 Life: multiwatcher.Life("alive"), 205 Config: charm.Settings{}, 206 Subordinate: true, 207 }) 208 209 eps, err := st.InferEndpoints("logging", "wordpress") 210 c.Assert(err, jc.ErrorIsNil) 211 rel, err := st.AddRelation(eps...) 212 c.Assert(err, jc.ErrorIsNil) 213 add(&multiwatcher.RelationInfo{ 214 Key: "logging:logging-directory wordpress:logging-dir", 215 Id: rel.Id(), 216 Endpoints: []multiwatcher.Endpoint{ 217 {ServiceName: "logging", Relation: charm.Relation{Name: "logging-directory", Role: "requirer", Interface: "logging", Optional: false, Limit: 1, Scope: "container"}}, 218 {ServiceName: "wordpress", Relation: charm.Relation{Name: "logging-dir", Role: "provider", Interface: "logging", Optional: false, Limit: 0, Scope: "container"}}}, 219 }) 220 221 for i := 0; i < units; i++ { 222 wu, err := wordpress.AddUnit() 223 c.Assert(err, jc.ErrorIsNil) 224 c.Assert(wu.Tag().String(), gc.Equals, fmt.Sprintf("unit-wordpress-%d", i)) 225 226 m, err := st.AddMachine("quantal", JobHostUnits) 227 c.Assert(err, jc.ErrorIsNil) 228 c.Assert(m.Tag().String(), gc.Equals, fmt.Sprintf("machine-%d", i+1)) 229 230 add(&multiwatcher.UnitInfo{ 231 Name: fmt.Sprintf("wordpress/%d", i), 232 Service: wordpress.Name(), 233 Series: m.Series(), 234 MachineId: m.Id(), 235 Ports: []network.Port{}, 236 Status: multiwatcher.Status("allocating"), 237 Subordinate: false, 238 }) 239 pairs := map[string]string{"name": fmt.Sprintf("bar %d", i)} 240 err = st.SetAnnotations(wu, pairs) 241 c.Assert(err, jc.ErrorIsNil) 242 add(&multiwatcher.AnnotationInfo{ 243 Tag: fmt.Sprintf("unit-wordpress-%d", i), 244 Annotations: pairs, 245 }) 246 247 err = m.SetProvisioned(instance.Id("i-"+m.Tag().String()), "fake_nonce", nil) 248 c.Assert(err, jc.ErrorIsNil) 249 err = m.SetStatus(StatusError, m.Tag().String(), nil) 250 c.Assert(err, jc.ErrorIsNil) 251 hc, err := m.HardwareCharacteristics() 252 c.Assert(err, jc.ErrorIsNil) 253 add(&multiwatcher.MachineInfo{ 254 Id: fmt.Sprint(i + 1), 255 InstanceId: "i-" + m.Tag().String(), 256 Status: multiwatcher.Status("error"), 257 StatusInfo: m.Tag().String(), 258 Life: multiwatcher.Life("alive"), 259 Series: "quantal", 260 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 261 Addresses: []network.Address{}, 262 HardwareCharacteristics: hc, 263 HasVote: false, 264 WantsVote: false, 265 }) 266 err = wu.AssignToMachine(m) 267 c.Assert(err, jc.ErrorIsNil) 268 269 deployer, ok := wu.DeployerTag() 270 c.Assert(ok, jc.IsTrue) 271 c.Assert(deployer, gc.Equals, names.NewMachineTag(fmt.Sprintf("%d", i+1))) 272 273 wru, err := rel.Unit(wu) 274 c.Assert(err, jc.ErrorIsNil) 275 276 // Create the subordinate unit as a side-effect of entering 277 // scope in the principal's relation-unit. 278 err = wru.EnterScope(nil) 279 c.Assert(err, jc.ErrorIsNil) 280 281 lu, err := st.Unit(fmt.Sprintf("logging/%d", i)) 282 c.Assert(err, jc.ErrorIsNil) 283 c.Assert(lu.IsPrincipal(), jc.IsFalse) 284 deployer, ok = lu.DeployerTag() 285 c.Assert(ok, jc.IsTrue) 286 c.Assert(deployer, gc.Equals, names.NewUnitTag(fmt.Sprintf("wordpress/%d", i))) 287 add(&multiwatcher.UnitInfo{ 288 Name: fmt.Sprintf("logging/%d", i), 289 Service: "logging", 290 Series: "quantal", 291 Ports: []network.Port{}, 292 Status: multiwatcher.Status("allocating"), 293 Subordinate: true, 294 }) 295 } 296 return 297 } 298 299 func serviceCharmURL(svc *Service) *charm.URL { 300 url, _ := svc.CharmURL() 301 return url 302 } 303 304 func setServiceConfigAttr(c *gc.C, svc *Service, attr string, val interface{}) { 305 err := svc.UpdateConfigSettings(charm.Settings{attr: val}) 306 c.Assert(err, jc.ErrorIsNil) 307 } 308 309 func (s *storeManagerStateSuite) TestChanged(c *gc.C) { 310 type testCase struct { 311 about string 312 add []multiwatcher.EntityInfo 313 change watcher.Change 314 expectContents []multiwatcher.EntityInfo 315 } 316 317 for i, testFunc := range []func(c *gc.C, st *State) testCase{ 318 // Machine changes 319 func(c *gc.C, st *State) testCase { 320 return testCase{ 321 about: "no machine in state, no machine in store -> do nothing", 322 change: watcher.Change{ 323 C: "machines", 324 Id: st.docID("1"), 325 }} 326 }, func(c *gc.C, st *State) testCase { 327 return testCase{ 328 about: "machine is removed if it's not in backing", 329 add: []multiwatcher.EntityInfo{&multiwatcher.MachineInfo{Id: "1"}}, 330 change: watcher.Change{ 331 C: "machines", 332 Id: st.docID("1"), 333 }} 334 }, func(c *gc.C, st *State) testCase { 335 m, err := st.AddMachine("quantal", JobHostUnits) 336 c.Assert(err, jc.ErrorIsNil) 337 err = m.SetStatus(StatusError, "failure", nil) 338 c.Assert(err, jc.ErrorIsNil) 339 340 return testCase{ 341 about: "machine is added if it's in backing but not in Store", 342 change: watcher.Change{ 343 C: "machines", 344 Id: st.docID("0"), 345 }, 346 expectContents: []multiwatcher.EntityInfo{ 347 &multiwatcher.MachineInfo{ 348 Id: "0", 349 Status: multiwatcher.Status("error"), 350 StatusInfo: "failure", 351 Life: multiwatcher.Life("alive"), 352 Series: "quantal", 353 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 354 Addresses: []network.Address{}, 355 HasVote: false, 356 WantsVote: false, 357 }}} 358 }, 359 // Machine status changes 360 func(c *gc.C, st *State) testCase { 361 m, err := st.AddMachine("trusty", JobManageEnviron) 362 c.Assert(err, jc.ErrorIsNil) 363 err = m.SetProvisioned("i-0", "bootstrap_nonce", nil) 364 c.Assert(err, jc.ErrorIsNil) 365 err = m.SetSupportedContainers([]instance.ContainerType{instance.LXC}) 366 c.Assert(err, jc.ErrorIsNil) 367 368 return testCase{ 369 about: "machine is updated if it's in backing and in Store", 370 add: []multiwatcher.EntityInfo{ 371 &multiwatcher.MachineInfo{ 372 Id: "0", 373 Status: multiwatcher.Status("error"), 374 StatusInfo: "another failure", 375 }, 376 }, 377 change: watcher.Change{ 378 C: "machines", 379 Id: st.docID("0"), 380 }, 381 expectContents: []multiwatcher.EntityInfo{ 382 &multiwatcher.MachineInfo{ 383 Id: "0", 384 InstanceId: "i-0", 385 Status: multiwatcher.Status("error"), 386 StatusInfo: "another failure", 387 Life: multiwatcher.Life("alive"), 388 Series: "trusty", 389 Jobs: []multiwatcher.MachineJob{JobManageEnviron.ToParams()}, 390 Addresses: []network.Address{}, 391 HardwareCharacteristics: &instance.HardwareCharacteristics{}, 392 SupportedContainers: []instance.ContainerType{instance.LXC}, 393 SupportedContainersKnown: true, 394 HasVote: false, 395 WantsVote: true, 396 }}} 397 }, 398 // Unit changes 399 func(c *gc.C, st *State) testCase { 400 return testCase{ 401 about: "no unit in state, no unit in store -> do nothing", 402 change: watcher.Change{ 403 C: "units", 404 Id: st.docID("1"), 405 }} 406 }, func(c *gc.C, st *State) testCase { 407 return testCase{ 408 about: "unit is removed if it's not in backing", 409 add: []multiwatcher.EntityInfo{&multiwatcher.UnitInfo{Name: "wordpress/1"}}, 410 change: watcher.Change{ 411 C: "units", 412 Id: st.docID("wordpress/1"), 413 }} 414 }, func(c *gc.C, st *State) testCase { 415 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 416 u, err := wordpress.AddUnit() 417 c.Assert(err, jc.ErrorIsNil) 418 m, err := st.AddMachine("quantal", JobHostUnits) 419 c.Assert(err, jc.ErrorIsNil) 420 err = u.AssignToMachine(m) 421 c.Assert(err, jc.ErrorIsNil) 422 err = u.OpenPort("tcp", 12345) 423 c.Assert(err, jc.ErrorIsNil) 424 err = u.SetStatus(StatusError, "failure", nil) 425 c.Assert(err, jc.ErrorIsNil) 426 427 return testCase{ 428 about: "unit is added if it's in backing but not in Store", 429 change: watcher.Change{ 430 C: "units", 431 Id: st.docID("wordpress/0"), 432 }, 433 expectContents: []multiwatcher.EntityInfo{ 434 &multiwatcher.UnitInfo{ 435 Name: "wordpress/0", 436 Service: "wordpress", 437 Series: "quantal", 438 MachineId: "0", 439 Ports: []network.Port{}, 440 Status: multiwatcher.Status("error"), 441 StatusInfo: "failure", 442 }}} 443 }, func(c *gc.C, st *State) testCase { 444 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 445 u, err := wordpress.AddUnit() 446 c.Assert(err, jc.ErrorIsNil) 447 m, err := st.AddMachine("quantal", JobHostUnits) 448 c.Assert(err, jc.ErrorIsNil) 449 err = u.AssignToMachine(m) 450 c.Assert(err, jc.ErrorIsNil) 451 err = u.OpenPort("udp", 17070) 452 c.Assert(err, jc.ErrorIsNil) 453 454 return testCase{ 455 about: "unit is updated if it's in backing and in multiwatcher.Store", 456 add: []multiwatcher.EntityInfo{&multiwatcher.UnitInfo{ 457 Name: "wordpress/0", 458 Status: multiwatcher.Status("error"), 459 StatusInfo: "another failure", 460 }}, 461 change: watcher.Change{ 462 C: "units", 463 Id: st.docID("wordpress/0"), 464 }, 465 expectContents: []multiwatcher.EntityInfo{ 466 &multiwatcher.UnitInfo{ 467 Name: "wordpress/0", 468 Service: "wordpress", 469 Series: "quantal", 470 MachineId: "0", 471 Ports: []network.Port{}, 472 Status: multiwatcher.Status("error"), 473 StatusInfo: "another failure", 474 }}} 475 }, func(c *gc.C, st *State) testCase { 476 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 477 u, err := wordpress.AddUnit() 478 c.Assert(err, jc.ErrorIsNil) 479 m, err := st.AddMachine("quantal", JobHostUnits) 480 c.Assert(err, jc.ErrorIsNil) 481 err = u.AssignToMachine(m) 482 c.Assert(err, jc.ErrorIsNil) 483 err = u.OpenPort("tcp", 12345) 484 c.Assert(err, jc.ErrorIsNil) 485 publicAddress := network.NewAddress("public", network.ScopePublic) 486 privateAddress := network.NewAddress("private", network.ScopeCloudLocal) 487 err = m.SetAddresses(publicAddress, privateAddress) 488 c.Assert(err, jc.ErrorIsNil) 489 err = u.SetStatus(StatusError, "failure", nil) 490 c.Assert(err, jc.ErrorIsNil) 491 492 return testCase{ 493 about: "unit addresses are read from the assigned machine for recent Juju releases", 494 change: watcher.Change{ 495 C: "units", 496 Id: st.docID("wordpress/0"), 497 }, 498 expectContents: []multiwatcher.EntityInfo{ 499 &multiwatcher.UnitInfo{ 500 Name: "wordpress/0", 501 Service: "wordpress", 502 Series: "quantal", 503 PublicAddress: "public", 504 PrivateAddress: "private", 505 MachineId: "0", 506 Ports: []network.Port{}, 507 Status: multiwatcher.Status("error"), 508 StatusInfo: "failure", 509 }}} 510 }, 511 // Service changes 512 func(c *gc.C, st *State) testCase { 513 return testCase{ 514 about: "no service in state, no service in store -> do nothing", 515 change: watcher.Change{ 516 C: "services", 517 Id: st.docID("wordpress"), 518 }} 519 }, func(c *gc.C, st *State) testCase { 520 return testCase{ 521 about: "service is removed if it's not in backing", 522 add: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{Name: "wordpress"}}, 523 change: watcher.Change{ 524 C: "services", 525 Id: st.docID("wordpress"), 526 }} 527 }, func(c *gc.C, st *State) testCase { 528 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 529 err := wordpress.SetExposed() 530 c.Assert(err, jc.ErrorIsNil) 531 err = wordpress.SetMinUnits(42) 532 c.Assert(err, jc.ErrorIsNil) 533 534 return testCase{ 535 about: "service is added if it's in backing but not in Store", 536 change: watcher.Change{ 537 C: "services", 538 Id: st.docID("wordpress"), 539 }, 540 expectContents: []multiwatcher.EntityInfo{ 541 &multiwatcher.ServiceInfo{ 542 Name: "wordpress", 543 Exposed: true, 544 CharmURL: "local:quantal/quantal-wordpress-3", 545 OwnerTag: s.owner.String(), 546 Life: multiwatcher.Life("alive"), 547 MinUnits: 42, 548 Config: charm.Settings{}, 549 }}} 550 }, func(c *gc.C, st *State) testCase { 551 svc := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 552 setServiceConfigAttr(c, svc, "blog-title", "boring") 553 554 return testCase{ 555 about: "service is updated if it's in backing and in multiwatcher.Store", 556 add: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 557 Name: "wordpress", 558 Exposed: true, 559 CharmURL: "local:quantal/quantal-wordpress-3", 560 MinUnits: 47, 561 Constraints: constraints.MustParse("mem=99M"), 562 Config: charm.Settings{"blog-title": "boring"}, 563 }}, 564 change: watcher.Change{ 565 C: "services", 566 Id: st.docID("wordpress"), 567 }, 568 expectContents: []multiwatcher.EntityInfo{ 569 &multiwatcher.ServiceInfo{ 570 Name: "wordpress", 571 CharmURL: "local:quantal/quantal-wordpress-3", 572 OwnerTag: s.owner.String(), 573 Life: multiwatcher.Life("alive"), 574 Constraints: constraints.MustParse("mem=99M"), 575 Config: charm.Settings{"blog-title": "boring"}, 576 }}} 577 }, func(c *gc.C, st *State) testCase { 578 svc := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 579 setServiceConfigAttr(c, svc, "blog-title", "boring") 580 581 return testCase{ 582 about: "service re-reads config when charm URL changes", 583 add: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 584 Name: "wordpress", 585 // Note: CharmURL has a different revision number from 586 // the wordpress revision in the testing repo. 587 CharmURL: "local:quantal/quantal-wordpress-2", 588 Config: charm.Settings{"foo": "bar"}, 589 }}, 590 change: watcher.Change{ 591 C: "services", 592 Id: st.docID("wordpress"), 593 }, 594 expectContents: []multiwatcher.EntityInfo{ 595 &multiwatcher.ServiceInfo{ 596 Name: "wordpress", 597 CharmURL: "local:quantal/quantal-wordpress-3", 598 OwnerTag: s.owner.String(), 599 Life: multiwatcher.Life("alive"), 600 Config: charm.Settings{"blog-title": "boring"}, 601 }}} 602 }, 603 // Relation changes 604 func(c *gc.C, st *State) testCase { 605 return testCase{ 606 about: "no relation in state, no service in store -> do nothing", 607 change: watcher.Change{ 608 C: "relations", 609 Id: st.docID("logging:logging-directory wordpress:logging-dir"), 610 }} 611 }, func(c *gc.C, st *State) testCase { 612 return testCase{ 613 about: "relation is removed if it's not in backing", 614 add: []multiwatcher.EntityInfo{&multiwatcher.RelationInfo{Key: "logging:logging-directory wordpress:logging-dir"}}, 615 change: watcher.Change{ 616 C: "relations", 617 Id: st.docID("logging:logging-directory wordpress:logging-dir"), 618 }} 619 }, func(c *gc.C, st *State) testCase { 620 AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 621 AddTestingService(c, st, "logging", AddTestingCharm(c, st, "logging"), s.owner) 622 eps, err := st.InferEndpoints("logging", "wordpress") 623 c.Assert(err, jc.ErrorIsNil) 624 _, err = st.AddRelation(eps...) 625 c.Assert(err, jc.ErrorIsNil) 626 627 return testCase{ 628 about: "relation is added if it's in backing but not in Store", 629 change: watcher.Change{ 630 C: "relations", 631 Id: st.docID("logging:logging-directory wordpress:logging-dir"), 632 }, 633 expectContents: []multiwatcher.EntityInfo{ 634 &multiwatcher.RelationInfo{ 635 Key: "logging:logging-directory wordpress:logging-dir", 636 Endpoints: []multiwatcher.Endpoint{ 637 {ServiceName: "logging", Relation: charm.Relation{Name: "logging-directory", Role: "requirer", Interface: "logging", Optional: false, Limit: 1, Scope: "container"}}, 638 {ServiceName: "wordpress", Relation: charm.Relation{Name: "logging-dir", Role: "provider", Interface: "logging", Optional: false, Limit: 0, Scope: "container"}}}, 639 }}} 640 }, 641 // Annotation changes 642 func(c *gc.C, st *State) testCase { 643 return testCase{ 644 about: "no annotation in state, no annotation in store -> do nothing", 645 change: watcher.Change{ 646 C: "annotations", 647 Id: st.docID("m#0"), 648 }} 649 }, func(c *gc.C, st *State) testCase { 650 return testCase{ 651 about: "annotation is removed if it's not in backing", 652 add: []multiwatcher.EntityInfo{&multiwatcher.AnnotationInfo{Tag: "machine-0"}}, 653 change: watcher.Change{ 654 C: "annotations", 655 Id: st.docID("m#0"), 656 }} 657 }, func(c *gc.C, st *State) testCase { 658 m, err := st.AddMachine("quantal", JobHostUnits) 659 c.Assert(err, jc.ErrorIsNil) 660 err = st.SetAnnotations(m, map[string]string{"foo": "bar", "arble": "baz"}) 661 c.Assert(err, jc.ErrorIsNil) 662 663 return testCase{ 664 about: "annotation is added if it's in backing but not in Store", 665 change: watcher.Change{ 666 C: "annotations", 667 Id: st.docID("m#0"), 668 }, 669 expectContents: []multiwatcher.EntityInfo{ 670 &multiwatcher.AnnotationInfo{ 671 Tag: "machine-0", 672 Annotations: map[string]string{"foo": "bar", "arble": "baz"}, 673 }}} 674 }, func(c *gc.C, st *State) testCase { 675 m, err := st.AddMachine("quantal", JobHostUnits) 676 c.Assert(err, jc.ErrorIsNil) 677 err = st.SetAnnotations(m, map[string]string{ 678 "arble": "khroomph", 679 "pretty": "", 680 "new": "attr", 681 }) 682 c.Assert(err, jc.ErrorIsNil) 683 684 return testCase{ 685 about: "annotation is updated if it's in backing and in multiwatcher.Store", 686 add: []multiwatcher.EntityInfo{&multiwatcher.AnnotationInfo{ 687 Tag: "machine-0", 688 Annotations: map[string]string{ 689 "arble": "baz", 690 "foo": "bar", 691 "pretty": "polly", 692 }, 693 }}, 694 change: watcher.Change{ 695 C: "annotations", 696 Id: st.docID("m#0"), 697 }, 698 expectContents: []multiwatcher.EntityInfo{ 699 &multiwatcher.AnnotationInfo{ 700 Tag: "machine-0", 701 Annotations: map[string]string{ 702 "arble": "khroomph", 703 "new": "attr", 704 }}}} 705 }, 706 // Unit status changes 707 func(c *gc.C, st *State) testCase { 708 return testCase{ 709 about: "no unit in state -> do nothing", 710 change: watcher.Change{ 711 C: "statuses", 712 Id: st.docID("u#wordpress/0"), 713 }} 714 }, func(c *gc.C, st *State) testCase { 715 return testCase{ 716 about: "no change if status is not in backing", 717 add: []multiwatcher.EntityInfo{&multiwatcher.UnitInfo{ 718 Name: "wordpress/0", 719 Status: multiwatcher.Status("error"), 720 StatusInfo: "failure", 721 }}, 722 change: watcher.Change{ 723 C: "statuses", 724 Id: st.docID("u#wordpress/0"), 725 }, 726 expectContents: []multiwatcher.EntityInfo{ 727 &multiwatcher.UnitInfo{ 728 Name: "wordpress/0", 729 Status: multiwatcher.Status("error"), 730 StatusInfo: "failure", 731 }}} 732 }, func(c *gc.C, st *State) testCase { 733 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 734 u, err := wordpress.AddUnit() 735 c.Assert(err, jc.ErrorIsNil) 736 err = u.SetStatus(StatusActive, "", nil) 737 c.Assert(err, jc.ErrorIsNil) 738 739 return testCase{ 740 about: "status is changed if the unit exists in the store", 741 add: []multiwatcher.EntityInfo{&multiwatcher.UnitInfo{ 742 Name: "wordpress/0", 743 Status: multiwatcher.Status("error"), 744 StatusInfo: "failure", 745 }}, 746 change: watcher.Change{ 747 C: "statuses", 748 Id: st.docID("u#wordpress/0"), 749 }, 750 expectContents: []multiwatcher.EntityInfo{ 751 &multiwatcher.UnitInfo{ 752 Name: "wordpress/0", 753 Status: multiwatcher.Status("started"), 754 StatusData: make(map[string]interface{}), 755 }}} 756 }, func(c *gc.C, st *State) testCase { 757 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 758 u, err := wordpress.AddUnit() 759 c.Assert(err, jc.ErrorIsNil) 760 err = u.SetStatus(StatusError, "hook error", map[string]interface{}{ 761 "1st-key": "one", 762 "2nd-key": 2, 763 "3rd-key": true, 764 }) 765 c.Assert(err, jc.ErrorIsNil) 766 767 return testCase{ 768 about: "status is changed with additional status data", 769 add: []multiwatcher.EntityInfo{&multiwatcher.UnitInfo{ 770 Name: "wordpress/0", 771 Status: multiwatcher.Status("started"), 772 }}, 773 change: watcher.Change{ 774 C: "statuses", 775 Id: st.docID("u#wordpress/0"), 776 }, 777 expectContents: []multiwatcher.EntityInfo{ 778 &multiwatcher.UnitInfo{ 779 Name: "wordpress/0", 780 Status: multiwatcher.Status("error"), 781 StatusInfo: "hook error", 782 StatusData: map[string]interface{}{ 783 "1st-key": "one", 784 "2nd-key": 2, 785 "3rd-key": true, 786 }}}} 787 }, 788 // Machine status changes 789 func(c *gc.C, st *State) testCase { 790 return testCase{ 791 about: "no machine in state -> do nothing", 792 change: watcher.Change{ 793 C: "statuses", 794 Id: st.docID("m#0"), 795 }} 796 }, func(c *gc.C, st *State) testCase { 797 return testCase{ 798 about: "no change if status is not in backing", 799 add: []multiwatcher.EntityInfo{&multiwatcher.MachineInfo{ 800 Id: "0", 801 Status: multiwatcher.Status("error"), 802 StatusInfo: "failure", 803 }}, 804 change: watcher.Change{ 805 C: "statuses", 806 Id: st.docID("m#0"), 807 }, 808 expectContents: []multiwatcher.EntityInfo{ 809 &multiwatcher.MachineInfo{ 810 Id: "0", 811 Status: multiwatcher.Status("error"), 812 StatusInfo: "failure", 813 }}} 814 }, func(c *gc.C, st *State) testCase { 815 m, err := st.AddMachine("quantal", JobHostUnits) 816 c.Assert(err, jc.ErrorIsNil) 817 err = m.SetStatus(StatusStarted, "", nil) 818 c.Assert(err, jc.ErrorIsNil) 819 820 return testCase{ 821 about: "status is changed if the machine exists in the store", 822 add: []multiwatcher.EntityInfo{&multiwatcher.MachineInfo{ 823 Id: "0", 824 Status: multiwatcher.Status("error"), 825 StatusInfo: "failure", 826 }}, 827 change: watcher.Change{ 828 C: "statuses", 829 Id: st.docID("m#0"), 830 }, 831 expectContents: []multiwatcher.EntityInfo{ 832 &multiwatcher.MachineInfo{ 833 Id: "0", 834 Status: multiwatcher.Status("started"), 835 StatusData: make(map[string]interface{}), 836 }}} 837 }, 838 // Service constraints changes 839 func(c *gc.C, st *State) testCase { 840 return testCase{ 841 about: "no service in state -> do nothing", 842 change: watcher.Change{ 843 C: "constraints", 844 Id: st.docID("s#wordpress"), 845 }} 846 }, func(c *gc.C, st *State) testCase { 847 return testCase{ 848 about: "no change if service is not in backing", 849 add: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 850 Name: "wordpress", 851 Constraints: constraints.MustParse("mem=99M"), 852 }}, 853 change: watcher.Change{ 854 C: "constraints", 855 Id: st.docID("s#wordpress"), 856 }, 857 expectContents: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 858 Name: "wordpress", 859 Constraints: constraints.MustParse("mem=99M"), 860 }}} 861 }, func(c *gc.C, st *State) testCase { 862 svc := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 863 err := svc.SetConstraints(constraints.MustParse("mem=4G cpu-cores= arch=amd64")) 864 c.Assert(err, jc.ErrorIsNil) 865 866 return testCase{ 867 about: "status is changed if the service exists in the store", 868 add: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 869 Name: "wordpress", 870 Constraints: constraints.MustParse("mem=99M cpu-cores=2 cpu-power=4"), 871 }}, 872 change: watcher.Change{ 873 C: "constraints", 874 Id: st.docID("s#wordpress"), 875 }, 876 expectContents: []multiwatcher.EntityInfo{ 877 &multiwatcher.ServiceInfo{ 878 Name: "wordpress", 879 Constraints: constraints.MustParse("mem=4G cpu-cores= arch=amd64"), 880 }}} 881 }, 882 // Service config changes. 883 func(c *gc.C, st *State) testCase { 884 return testCase{ 885 about: "no service in state -> do nothing", 886 change: watcher.Change{ 887 C: "settings", 888 Id: st.docID("s#wordpress#local:quantal/quantal-wordpress-3"), 889 }} 890 }, func(c *gc.C, st *State) testCase { 891 return testCase{ 892 about: "no change if service is not in backing", 893 add: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 894 Name: "wordpress", 895 CharmURL: "local:quantal/quantal-wordpress-3", 896 }}, 897 change: watcher.Change{ 898 C: "settings", 899 Id: st.docID("s#wordpress#local:quantal/quantal-wordpress-3"), 900 }, 901 expectContents: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 902 Name: "wordpress", 903 CharmURL: "local:quantal/quantal-wordpress-3", 904 }}} 905 }, func(c *gc.C, st *State) testCase { 906 svc := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 907 setServiceConfigAttr(c, svc, "blog-title", "foo") 908 909 return testCase{ 910 about: "service config is changed if service exists in the store with the same URL", 911 add: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 912 Name: "wordpress", 913 CharmURL: "local:quantal/quantal-wordpress-3", 914 Config: charm.Settings{"foo": "bar"}, 915 }}, 916 change: watcher.Change{ 917 C: "settings", 918 Id: st.docID("s#wordpress#local:quantal/quantal-wordpress-3"), 919 }, 920 expectContents: []multiwatcher.EntityInfo{ 921 &multiwatcher.ServiceInfo{ 922 Name: "wordpress", 923 CharmURL: "local:quantal/quantal-wordpress-3", 924 Config: charm.Settings{"blog-title": "foo"}, 925 }}} 926 }, func(c *gc.C, st *State) testCase { 927 testCharm := AddCustomCharm( 928 c, st, "wordpress", 929 "config.yaml", dottedConfig, 930 "quantal", 3) 931 svc := AddTestingService(c, st, "wordpress", testCharm, s.owner) 932 setServiceConfigAttr(c, svc, "key.dotted", "foo") 933 934 return testCase{ 935 about: "service config is unescaped when reading from the backing store", 936 add: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 937 Name: "wordpress", 938 CharmURL: "local:quantal/quantal-wordpress-3", 939 Config: charm.Settings{"key.dotted": "bar"}, 940 }}, 941 change: watcher.Change{ 942 C: "settings", 943 Id: st.docID("s#wordpress#local:quantal/quantal-wordpress-3"), 944 }, 945 expectContents: []multiwatcher.EntityInfo{ 946 &multiwatcher.ServiceInfo{ 947 Name: "wordpress", 948 CharmURL: "local:quantal/quantal-wordpress-3", 949 Config: charm.Settings{"key.dotted": "foo"}, 950 }}} 951 }, func(c *gc.C, st *State) testCase { 952 svc := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 953 setServiceConfigAttr(c, svc, "blog-title", "foo") 954 955 return testCase{ 956 about: "service config is unchanged if service exists in the store with a different URL", 957 add: []multiwatcher.EntityInfo{&multiwatcher.ServiceInfo{ 958 Name: "wordpress", 959 CharmURL: "local:quantal/quantal-wordpress-2", // Note different revno. 960 Config: charm.Settings{"foo": "bar"}, 961 }}, 962 change: watcher.Change{ 963 C: "settings", 964 Id: st.docID("s#wordpress#local:quantal/quantal-wordpress-3"), 965 }, 966 expectContents: []multiwatcher.EntityInfo{ 967 &multiwatcher.ServiceInfo{ 968 Name: "wordpress", 969 CharmURL: "local:quantal/quantal-wordpress-2", 970 Config: charm.Settings{"foo": "bar"}, 971 }}} 972 }, func(c *gc.C, st *State) testCase { 973 return testCase{ 974 about: "non-service config change is ignored", 975 change: watcher.Change{ 976 C: "settings", 977 Id: st.docID("m#0"), 978 }} 979 }, func(c *gc.C, st *State) testCase { 980 return testCase{ 981 about: "service config change with no charm url is ignored", 982 change: watcher.Change{ 983 C: "settings", 984 Id: st.docID("s#foo"), 985 }} 986 }, 987 // Action changes 988 func(c *gc.C, st *State) testCase { 989 wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 990 u, err := wordpress.AddUnit() 991 c.Assert(err, jc.ErrorIsNil) 992 action, err := st.EnqueueAction(u.Tag(), "vacuumdb", map[string]interface{}{}) 993 c.Assert(err, jc.ErrorIsNil) 994 enqueued := makeActionInfo(action, st) 995 action, err = action.Begin() 996 c.Assert(err, jc.ErrorIsNil) 997 started := makeActionInfo(action, st) 998 return testCase{ 999 about: "action change picks up last change", 1000 add: []multiwatcher.EntityInfo{&enqueued, &started}, 1001 change: watcher.Change{C: actionsC, Id: st.docID(action.Id())}, 1002 expectContents: []multiwatcher.EntityInfo{&started}, 1003 } 1004 }, 1005 } { 1006 test := testFunc(c, s.state) 1007 1008 c.Logf("test %d. %s", i, test.about) 1009 b := newAllWatcherStateBacking(s.state) 1010 all := newStore() 1011 for _, info := range test.add { 1012 all.Update(info) 1013 } 1014 err := b.Changed(all, test.change) 1015 c.Assert(err, jc.ErrorIsNil) 1016 assertEntitiesEqual(c, all.All(), test.expectContents) 1017 s.Reset(c) 1018 } 1019 } 1020 1021 // TestStateWatcher tests the integration of the state watcher 1022 // with the state-based backing. Most of the logic is tested elsewhere - 1023 // this just tests end-to-end. 1024 func (s *storeManagerStateSuite) TestStateWatcher(c *gc.C) { 1025 m0, err := s.state.AddMachine("trusty", JobManageEnviron) 1026 c.Assert(err, jc.ErrorIsNil) 1027 c.Assert(m0.Id(), gc.Equals, "0") 1028 1029 m1, err := s.state.AddMachine("saucy", JobHostUnits) 1030 c.Assert(err, jc.ErrorIsNil) 1031 c.Assert(m1.Id(), gc.Equals, "1") 1032 1033 tw := newTestWatcher(s.state, c) 1034 defer tw.Stop() 1035 1036 // Expect to see events for the already created machines first. 1037 deltas := tw.All() 1038 checkDeltasEqual(c, deltas, []multiwatcher.Delta{{ 1039 Entity: &multiwatcher.MachineInfo{ 1040 Id: "0", 1041 Status: multiwatcher.Status("pending"), 1042 Life: multiwatcher.Life("alive"), 1043 Series: "trusty", 1044 Jobs: []multiwatcher.MachineJob{JobManageEnviron.ToParams()}, 1045 Addresses: []network.Address{}, 1046 HasVote: false, 1047 WantsVote: true, 1048 }, 1049 }, { 1050 Entity: &multiwatcher.MachineInfo{ 1051 Id: "1", 1052 Status: multiwatcher.Status("pending"), 1053 Life: multiwatcher.Life("alive"), 1054 Series: "saucy", 1055 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 1056 Addresses: []network.Address{}, 1057 HasVote: false, 1058 WantsVote: false, 1059 }, 1060 }}) 1061 1062 // Make some changes to the state. 1063 arch := "amd64" 1064 mem := uint64(4096) 1065 hc := &instance.HardwareCharacteristics{ 1066 Arch: &arch, 1067 Mem: &mem, 1068 } 1069 err = m0.SetProvisioned("i-0", "bootstrap_nonce", hc) 1070 c.Assert(err, jc.ErrorIsNil) 1071 1072 err = m1.Destroy() 1073 c.Assert(err, jc.ErrorIsNil) 1074 err = m1.EnsureDead() 1075 c.Assert(err, jc.ErrorIsNil) 1076 err = m1.Remove() 1077 c.Assert(err, jc.ErrorIsNil) 1078 1079 m2, err := s.state.AddMachine("quantal", JobHostUnits) 1080 c.Assert(err, jc.ErrorIsNil) 1081 c.Assert(m2.Id(), gc.Equals, "2") 1082 1083 wordpress := AddTestingService(c, s.state, "wordpress", AddTestingCharm(c, s.state, "wordpress"), s.owner) 1084 wu, err := wordpress.AddUnit() 1085 c.Assert(err, jc.ErrorIsNil) 1086 err = wu.AssignToMachine(m2) 1087 c.Assert(err, jc.ErrorIsNil) 1088 1089 // Look for the state changes from the allwatcher. 1090 deltas = tw.All() 1091 checkDeltasEqual(c, deltas, []multiwatcher.Delta{{ 1092 Entity: &multiwatcher.MachineInfo{ 1093 Id: "0", 1094 InstanceId: "i-0", 1095 Status: multiwatcher.Status("pending"), 1096 Life: multiwatcher.Life("alive"), 1097 Series: "trusty", 1098 Jobs: []multiwatcher.MachineJob{JobManageEnviron.ToParams()}, 1099 Addresses: []network.Address{}, 1100 HardwareCharacteristics: hc, 1101 HasVote: false, 1102 WantsVote: true, 1103 }, 1104 }, { 1105 Removed: true, 1106 Entity: &multiwatcher.MachineInfo{ 1107 Id: "1", 1108 Status: multiwatcher.Status("pending"), 1109 Life: multiwatcher.Life("alive"), 1110 Series: "saucy", 1111 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 1112 Addresses: []network.Address{}, 1113 }, 1114 }, { 1115 Entity: &multiwatcher.MachineInfo{ 1116 Id: "2", 1117 Status: multiwatcher.Status("pending"), 1118 Life: multiwatcher.Life("alive"), 1119 Series: "quantal", 1120 Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()}, 1121 Addresses: []network.Address{}, 1122 HasVote: false, 1123 WantsVote: false, 1124 }, 1125 }, { 1126 Entity: &multiwatcher.ServiceInfo{ 1127 Name: "wordpress", 1128 CharmURL: "local:quantal/quantal-wordpress-3", 1129 OwnerTag: s.owner.String(), 1130 Life: "alive", 1131 Config: make(map[string]interface{}), 1132 }, 1133 }, { 1134 Entity: &multiwatcher.UnitInfo{ 1135 Name: "wordpress/0", 1136 Service: "wordpress", 1137 Series: "quantal", 1138 MachineId: "2", 1139 Status: "allocating", 1140 }, 1141 }}) 1142 } 1143 1144 func (s *storeManagerStateSuite) TestStateWatcherTwoEnvironments(c *gc.C) { 1145 loggo.GetLogger("juju.state.watcher").SetLogLevel(loggo.TRACE) 1146 for i, test := range []struct { 1147 about string 1148 setUpState func(*State) 1149 triggerEvent func(*State) 1150 }{ 1151 { 1152 about: "machines", 1153 triggerEvent: func(st *State) { 1154 m0, err := st.AddMachine("trusty", JobHostUnits) 1155 c.Assert(err, jc.ErrorIsNil) 1156 c.Assert(m0.Id(), gc.Equals, "0") 1157 }, 1158 }, { 1159 about: "services", 1160 triggerEvent: func(st *State) { 1161 AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 1162 }, 1163 }, { 1164 about: "units", 1165 setUpState: func(st *State) { 1166 AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 1167 }, 1168 triggerEvent: func(st *State) { 1169 svc, err := st.Service("wordpress") 1170 c.Assert(err, jc.ErrorIsNil) 1171 1172 _, err = svc.AddUnit() 1173 c.Assert(err, jc.ErrorIsNil) 1174 }, 1175 }, { 1176 about: "relations", 1177 setUpState: func(st *State) { 1178 AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 1179 AddTestingService(c, st, "mysql", AddTestingCharm(c, st, "mysql"), s.owner) 1180 }, 1181 triggerEvent: func(st *State) { 1182 eps, err := st.InferEndpoints("mysql", "wordpress") 1183 c.Assert(err, jc.ErrorIsNil) 1184 _, err = st.AddRelation(eps...) 1185 c.Assert(err, jc.ErrorIsNil) 1186 }, 1187 }, { 1188 about: "annotations", 1189 setUpState: func(st *State) { 1190 m, err := st.AddMachine("trusty", JobHostUnits) 1191 c.Assert(err, jc.ErrorIsNil) 1192 c.Assert(m.Id(), gc.Equals, "0") 1193 }, 1194 triggerEvent: func(st *State) { 1195 m, err := st.Machine("0") 1196 c.Assert(err, jc.ErrorIsNil) 1197 1198 err = st.SetAnnotations(m, map[string]string{"foo": "bar"}) 1199 c.Assert(err, jc.ErrorIsNil) 1200 }, 1201 }, { 1202 about: "statuses", 1203 setUpState: func(st *State) { 1204 m, err := st.AddMachine("trusty", JobHostUnits) 1205 c.Assert(err, jc.ErrorIsNil) 1206 c.Assert(m.Id(), gc.Equals, "0") 1207 err = m.SetProvisioned("inst-id", "fake_nonce", nil) 1208 c.Assert(err, jc.ErrorIsNil) 1209 }, 1210 triggerEvent: func(st *State) { 1211 m, err := st.Machine("0") 1212 c.Assert(err, jc.ErrorIsNil) 1213 1214 err = m.SetStatus("error", "pete tong", nil) 1215 c.Assert(err, jc.ErrorIsNil) 1216 }, 1217 }, { 1218 about: "constraints", 1219 setUpState: func(st *State) { 1220 AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 1221 }, 1222 triggerEvent: func(st *State) { 1223 svc, err := st.Service("wordpress") 1224 c.Assert(err, jc.ErrorIsNil) 1225 1226 cpuCores := uint64(99) 1227 err = svc.SetConstraints(constraints.Value{CpuCores: &cpuCores}) 1228 c.Assert(err, jc.ErrorIsNil) 1229 }, 1230 }, { 1231 about: "settings", 1232 setUpState: func(st *State) { 1233 AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"), s.owner) 1234 }, 1235 triggerEvent: func(st *State) { 1236 svc, err := st.Service("wordpress") 1237 c.Assert(err, jc.ErrorIsNil) 1238 1239 err = svc.UpdateConfigSettings(charm.Settings{"blog-title": "boring"}) 1240 c.Assert(err, jc.ErrorIsNil) 1241 }, 1242 }, 1243 } { 1244 c.Logf("Test %d: %s", i, test.about) 1245 func() { 1246 checkIsolationForEnv := func(st *State, w, otherW *testWatcher) { 1247 c.Logf("Making changes to environment %s", st.EnvironUUID()) 1248 1249 if test.setUpState != nil { 1250 test.setUpState(st) 1251 // Consume events from setup. 1252 w.AssertChanges() 1253 w.AssertNoChange() 1254 otherW.AssertNoChange() 1255 } 1256 1257 test.triggerEvent(st) 1258 // Check event was isolated to the correct watcher. 1259 w.AssertChanges() 1260 w.AssertNoChange() 1261 otherW.AssertNoChange() 1262 } 1263 1264 w1 := newTestWatcher(s.state, c) 1265 defer w1.Stop() 1266 w2 := newTestWatcher(s.OtherState, c) 1267 defer w2.Stop() 1268 1269 checkIsolationForEnv(s.state, w1, w2) 1270 checkIsolationForEnv(s.OtherState, w2, w1) 1271 }() 1272 s.Reset(c) 1273 } 1274 } 1275 1276 type testWatcher struct { 1277 st *State 1278 c *gc.C 1279 w *Multiwatcher 1280 deltas chan []multiwatcher.Delta 1281 } 1282 1283 func newTestWatcher(st *State, c *gc.C) *testWatcher { 1284 b := newAllWatcherStateBacking(st) 1285 sm := newStoreManager(b) 1286 w := NewMultiwatcher(sm) 1287 tw := &testWatcher{ 1288 st: st, 1289 c: c, 1290 w: w, 1291 deltas: make(chan []multiwatcher.Delta), 1292 } 1293 go func() { 1294 for { 1295 deltas, err := tw.w.Next() 1296 if err != nil { 1297 break 1298 } 1299 tw.deltas <- deltas 1300 } 1301 }() 1302 return tw 1303 } 1304 1305 func (tw *testWatcher) Next(timeout time.Duration) []multiwatcher.Delta { 1306 select { 1307 case d := <-tw.deltas: 1308 return d 1309 case <-time.After(timeout): 1310 return nil 1311 } 1312 } 1313 1314 func (tw *testWatcher) All() []multiwatcher.Delta { 1315 var allDeltas []multiwatcher.Delta 1316 tw.st.StartSync() 1317 for { 1318 deltas := tw.Next(testing.ShortWait) 1319 if len(deltas) == 0 { 1320 break 1321 } 1322 allDeltas = append(allDeltas, deltas...) 1323 } 1324 return allDeltas 1325 } 1326 1327 func (tw *testWatcher) Stop() { 1328 tw.c.Assert(tw.w.Stop(), jc.ErrorIsNil) 1329 } 1330 1331 func (tw *testWatcher) AssertNoChange() { 1332 tw.c.Assert(tw.All(), gc.HasLen, 0) 1333 } 1334 1335 func (tw *testWatcher) AssertChanges() { 1336 tw.c.Assert(len(tw.All()), jc.GreaterThan, 0) 1337 } 1338 1339 type entityInfoSlice []multiwatcher.EntityInfo 1340 1341 func (s entityInfoSlice) Len() int { return len(s) } 1342 func (s entityInfoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 1343 func (s entityInfoSlice) Less(i, j int) bool { 1344 id0, id1 := s[i].EntityId(), s[j].EntityId() 1345 if id0.Kind != id1.Kind { 1346 return id0.Kind < id1.Kind 1347 } 1348 switch id := id0.Id.(type) { 1349 case string: 1350 return id < id1.Id.(string) 1351 default: 1352 } 1353 panic("unexpected entity id type") 1354 } 1355 1356 func checkDeltasEqual(c *gc.C, d0, d1 []multiwatcher.Delta) { 1357 // Deltas are returned in arbitrary order, so we compare them as maps. 1358 c.Check(deltaMap(d0), jc.DeepEquals, deltaMap(d1)) 1359 } 1360 1361 func deltaMap(deltas []multiwatcher.Delta) map[interface{}]multiwatcher.EntityInfo { 1362 m := make(map[interface{}]multiwatcher.EntityInfo) 1363 for _, d := range deltas { 1364 id := d.Entity.EntityId() 1365 if d.Removed { 1366 m[id] = nil 1367 } else { 1368 m[id] = d.Entity 1369 } 1370 } 1371 return m 1372 } 1373 1374 func makeActionInfo(a *Action, st *State) multiwatcher.ActionInfo { 1375 results, message := a.Results() 1376 return multiwatcher.ActionInfo{ 1377 Id: a.Id(), 1378 Receiver: a.Receiver(), 1379 Name: a.Name(), 1380 Parameters: a.Parameters(), 1381 Status: string(a.Status()), 1382 Message: message, 1383 Results: results, 1384 Enqueued: a.Enqueued(), 1385 Started: a.Started(), 1386 Completed: a.Completed(), 1387 } 1388 }