1
// Copyright 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
11
"github.com/juju/errors"
12
"github.com/juju/loggo"
13
jc "github.com/juju/testing/checkers"
14
"github.com/juju/utils"
15
gc "gopkg.in/check.v1"
16
"gopkg.in/juju/charm.v6-unstable"
17
"gopkg.in/juju/names.v2"
19
"github.com/juju/juju/constraints"
20
"github.com/juju/juju/instance"
21
"github.com/juju/juju/network"
22
"github.com/juju/juju/state/multiwatcher"
23
"github.com/juju/juju/state/watcher"
24
"github.com/juju/juju/status"
25
"github.com/juju/juju/storage"
26
"github.com/juju/juju/testing"
30
_ backingEntityDoc = (*backingMachine)(nil)
31
_ backingEntityDoc = (*backingUnit)(nil)
32
_ backingEntityDoc = (*backingApplication)(nil)
33
_ backingEntityDoc = (*backingRelation)(nil)
34
_ backingEntityDoc = (*backingAnnotation)(nil)
35
_ backingEntityDoc = (*backingStatus)(nil)
36
_ backingEntityDoc = (*backingConstraints)(nil)
37
_ backingEntityDoc = (*backingSettings)(nil)
38
_ backingEntityDoc = (*backingOpenedPorts)(nil)
39
_ backingEntityDoc = (*backingAction)(nil)
40
_ backingEntityDoc = (*backingBlock)(nil)
45
key.dotted: {default: My Key, description: Desc, type: string}
48
type allWatcherBaseSuite struct {
53
func (s *allWatcherBaseSuite) newState(c *gc.C) *State {
55
cfg := testing.CustomModelConfig(c, testing.Attrs{
56
"name": fmt.Sprintf("testenv%d", s.envCount),
57
"uuid": utils.MustNewUUID().String(),
59
_, st, err := s.state.NewModel(ModelArgs{
60
CloudName: "dummy", Config: cfg, Owner: s.owner,
61
StorageProviderRegistry: storage.StaticProviderRegistry{},
63
c.Assert(err, jc.ErrorIsNil)
64
s.AddCleanup(func(*gc.C) { st.Close() })
68
// setUpScenario adds some entities to the state so that
69
// we can check that they all get pulled in by
70
// all(Env)WatcherStateBacking.GetAll.
71
func (s *allWatcherBaseSuite) setUpScenario(c *gc.C, st *State, units int) (entities entityInfoSlice) {
72
modelUUID := st.ModelUUID()
73
add := func(e multiwatcher.EntityInfo) {
74
entities = append(entities, e)
76
m, err := st.AddMachine("quantal", JobHostUnits)
77
c.Assert(err, jc.ErrorIsNil)
78
c.Assert(m.Tag(), gc.Equals, names.NewMachineTag("0"))
79
err = m.SetHasVote(true)
80
c.Assert(err, jc.ErrorIsNil)
81
// TODO(dfc) instance.Id should take a TAG!
82
err = m.SetProvisioned(instance.Id("i-"+m.Tag().String()), "fake_nonce", nil)
83
c.Assert(err, jc.ErrorIsNil)
84
hc, err := m.HardwareCharacteristics()
85
c.Assert(err, jc.ErrorIsNil)
86
err = m.SetProviderAddresses(network.NewAddress("example.com"))
87
c.Assert(err, jc.ErrorIsNil)
88
var addresses []multiwatcher.Address
89
for _, addr := range m.Addresses() {
90
addresses = append(addresses, multiwatcher.Address{
92
Type: string(addr.Type),
93
Scope: string(addr.Scope),
94
SpaceName: string(addr.SpaceName),
95
SpaceProviderId: string(addr.SpaceProviderId),
98
add(&multiwatcher.MachineInfo{
101
InstanceId: "i-machine-0",
102
AgentStatus: multiwatcher.StatusInfo{
103
Current: status.StatusPending,
104
Data: map[string]interface{}{},
106
InstanceStatus: multiwatcher.StatusInfo{
107
Current: status.StatusPending,
108
Data: map[string]interface{}{},
110
Life: multiwatcher.Life("alive"),
112
Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()},
113
Addresses: addresses,
114
HardwareCharacteristics: hc,
119
wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
120
err = wordpress.SetExposed()
121
c.Assert(err, jc.ErrorIsNil)
122
err = wordpress.SetMinUnits(units)
123
c.Assert(err, jc.ErrorIsNil)
124
err = wordpress.SetConstraints(constraints.MustParse("mem=100M"))
125
c.Assert(err, jc.ErrorIsNil)
126
setServiceConfigAttr(c, wordpress, "blog-title", "boring")
127
add(&multiwatcher.ApplicationInfo{
128
ModelUUID: modelUUID,
131
CharmURL: serviceCharmURL(wordpress).String(),
132
Life: multiwatcher.Life("alive"),
134
Constraints: constraints.MustParse("mem=100M"),
135
Config: charm.Settings{"blog-title": "boring"},
137
Status: multiwatcher.StatusInfo{
139
Message: "Waiting for agent initialization to finish",
140
Data: map[string]interface{}{},
143
pairs := map[string]string{"x": "12", "y": "99"}
144
err = st.SetAnnotations(wordpress, pairs)
145
c.Assert(err, jc.ErrorIsNil)
146
add(&multiwatcher.AnnotationInfo{
147
ModelUUID: modelUUID,
148
Tag: "application-wordpress",
152
logging := AddTestingService(c, st, "logging", AddTestingCharm(c, st, "logging"))
153
add(&multiwatcher.ApplicationInfo{
154
ModelUUID: modelUUID,
156
CharmURL: serviceCharmURL(logging).String(),
157
Life: multiwatcher.Life("alive"),
158
Config: charm.Settings{},
160
Status: multiwatcher.StatusInfo{
162
Message: "Waiting for agent initialization to finish",
163
Data: map[string]interface{}{},
167
eps, err := st.InferEndpoints("logging", "wordpress")
168
c.Assert(err, jc.ErrorIsNil)
169
rel, err := st.AddRelation(eps...)
170
c.Assert(err, jc.ErrorIsNil)
171
add(&multiwatcher.RelationInfo{
172
ModelUUID: modelUUID,
173
Key: "logging:logging-directory wordpress:logging-dir",
175
Endpoints: []multiwatcher.Endpoint{
176
{ApplicationName: "logging", Relation: multiwatcher.CharmRelation{Name: "logging-directory", Role: "requirer", Interface: "logging", Optional: false, Limit: 1, Scope: "container"}},
177
{ApplicationName: "wordpress", Relation: multiwatcher.CharmRelation{Name: "logging-dir", Role: "provider", Interface: "logging", Optional: false, Limit: 0, Scope: "container"}}},
180
for i := 0; i < units; i++ {
181
wu, err := wordpress.AddUnit()
182
c.Assert(err, jc.ErrorIsNil)
183
c.Assert(wu.Tag().String(), gc.Equals, fmt.Sprintf("unit-wordpress-%d", i))
185
m, err := st.AddMachine("quantal", JobHostUnits)
186
c.Assert(err, jc.ErrorIsNil)
187
c.Assert(m.Tag().String(), gc.Equals, fmt.Sprintf("machine-%d", i+1))
189
add(&multiwatcher.UnitInfo{
190
ModelUUID: modelUUID,
191
Name: fmt.Sprintf("wordpress/%d", i),
192
Application: wordpress.Name(),
195
Ports: []multiwatcher.Port{},
197
WorkloadStatus: multiwatcher.StatusInfo{
199
Message: "Waiting for agent initialization to finish",
200
Data: map[string]interface{}{},
202
AgentStatus: multiwatcher.StatusInfo{
203
Current: "allocating",
205
Data: map[string]interface{}{},
208
pairs := map[string]string{"name": fmt.Sprintf("bar %d", i)}
209
err = st.SetAnnotations(wu, pairs)
210
c.Assert(err, jc.ErrorIsNil)
211
add(&multiwatcher.AnnotationInfo{
212
ModelUUID: modelUUID,
213
Tag: fmt.Sprintf("unit-wordpress-%d", i),
217
err = m.SetProvisioned(instance.Id("i-"+m.Tag().String()), "fake_nonce", nil)
218
c.Assert(err, jc.ErrorIsNil)
220
sInfo := status.StatusInfo{
221
Status: status.StatusError,
222
Message: m.Tag().String(),
225
err = m.SetStatus(sInfo)
226
c.Assert(err, jc.ErrorIsNil)
227
hc, err := m.HardwareCharacteristics()
228
c.Assert(err, jc.ErrorIsNil)
229
add(&multiwatcher.MachineInfo{
230
ModelUUID: modelUUID,
231
Id: fmt.Sprint(i + 1),
232
InstanceId: "i-" + m.Tag().String(),
233
AgentStatus: multiwatcher.StatusInfo{
234
Current: status.StatusError,
235
Message: m.Tag().String(),
236
Data: map[string]interface{}{},
238
InstanceStatus: multiwatcher.StatusInfo{
239
Current: status.StatusPending,
240
Data: map[string]interface{}{},
242
Life: multiwatcher.Life("alive"),
244
Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()},
245
Addresses: []multiwatcher.Address{},
246
HardwareCharacteristics: hc,
250
err = wu.AssignToMachine(m)
251
c.Assert(err, jc.ErrorIsNil)
253
deployer, ok := wu.DeployerTag()
254
c.Assert(ok, jc.IsTrue)
255
c.Assert(deployer, gc.Equals, names.NewMachineTag(fmt.Sprintf("%d", i+1)))
257
wru, err := rel.Unit(wu)
258
c.Assert(err, jc.ErrorIsNil)
260
// Create the subordinate unit as a side-effect of entering
261
// scope in the principal's relation-unit.
262
err = wru.EnterScope(nil)
263
c.Assert(err, jc.ErrorIsNil)
265
lu, err := st.Unit(fmt.Sprintf("logging/%d", i))
266
c.Assert(err, jc.ErrorIsNil)
267
c.Assert(lu.IsPrincipal(), jc.IsFalse)
268
deployer, ok = lu.DeployerTag()
269
c.Assert(ok, jc.IsTrue)
270
c.Assert(deployer, gc.Equals, names.NewUnitTag(fmt.Sprintf("wordpress/%d", i)))
271
add(&multiwatcher.UnitInfo{
272
ModelUUID: modelUUID,
273
Name: fmt.Sprintf("logging/%d", i),
274
Application: "logging",
276
Ports: []multiwatcher.Port{},
278
WorkloadStatus: multiwatcher.StatusInfo{
280
Message: "Waiting for agent initialization to finish",
281
Data: map[string]interface{}{},
283
AgentStatus: multiwatcher.StatusInfo{
284
Current: "allocating",
286
Data: map[string]interface{}{},
293
var _ = gc.Suite(&allWatcherStateSuite{})
295
type allWatcherStateSuite struct {
299
func (s *allWatcherStateSuite) reset(c *gc.C) {
304
func (s *allWatcherStateSuite) TestGetAll(c *gc.C) {
305
expectEntities := s.setUpScenario(c, s.state, 2)
306
s.checkGetAll(c, expectEntities)
309
func (s *allWatcherStateSuite) TestGetAllMultiEnv(c *gc.C) {
310
// Set up 2 models and ensure that GetAll returns the
311
// entities for the first model with no errors.
312
expectEntities := s.setUpScenario(c, s.state, 2)
314
// Use more units in the second env to ensure the number of
315
// entities will mismatch if model filtering isn't in place.
316
s.setUpScenario(c, s.newState(c), 4)
318
s.checkGetAll(c, expectEntities)
321
func (s *allWatcherStateSuite) checkGetAll(c *gc.C, expectEntities entityInfoSlice) {
322
b := newAllWatcherStateBacking(s.state)
325
c.Assert(err, jc.ErrorIsNil)
326
var gotEntities entityInfoSlice = all.All()
327
sort.Sort(gotEntities)
328
sort.Sort(expectEntities)
329
substNilSinceTimeForEntities(c, gotEntities)
330
assertEntitiesEqual(c, gotEntities, expectEntities)
333
func serviceCharmURL(svc *Application) *charm.URL {
334
url, _ := svc.CharmURL()
338
func setServiceConfigAttr(c *gc.C, svc *Application, attr string, val interface{}) {
339
err := svc.UpdateConfigSettings(charm.Settings{attr: val})
340
c.Assert(err, jc.ErrorIsNil)
343
// changeTestCase encapsulates entities to add, a change, and
344
// the expected contents for a test.
345
type changeTestCase struct {
346
// about describes the test case.
349
// initialContents contains the infos of the
350
// watcher before signalling the change.
351
initialContents []multiwatcher.EntityInfo
353
// change signals the change of the watcher.
354
change watcher.Change
356
// expectContents contains the expected infos of
357
// the watcher before signalling the change.
358
expectContents []multiwatcher.EntityInfo
361
func substNilSinceTimeForStatus(c *gc.C, sInfo *multiwatcher.StatusInfo) {
362
if sInfo.Current != "" {
363
c.Assert(sInfo.Since, gc.NotNil) // TODO(dfc) WTF does this check do ? separation of concerns much
368
// substNilSinceTimeForEntities zeros out any updated timestamps for unit
369
// or service status values so we can easily check the results.
370
func substNilSinceTimeForEntities(c *gc.C, entities []multiwatcher.EntityInfo) {
371
// Zero out any updated timestamps for unit or service status values
372
// so we can easily check the results.
373
for i := range entities {
374
switch e := entities[i].(type) {
375
case *multiwatcher.UnitInfo:
376
unitInfo := *e // must copy because this entity came out of the multiwatcher cache.
377
substNilSinceTimeForStatus(c, &unitInfo.WorkloadStatus)
378
substNilSinceTimeForStatus(c, &unitInfo.AgentStatus)
379
entities[i] = &unitInfo
380
case *multiwatcher.ApplicationInfo:
381
applicationInfo := *e // must copy because this entity came out of the multiwatcher cache.
382
substNilSinceTimeForStatus(c, &applicationInfo.Status)
383
entities[i] = &applicationInfo
384
case *multiwatcher.MachineInfo:
385
machineInfo := *e // must copy because this entity came out of the multiwatcher cache.
386
substNilSinceTimeForStatus(c, &machineInfo.AgentStatus)
387
substNilSinceTimeForStatus(c, &machineInfo.InstanceStatus)
388
entities[i] = &machineInfo
393
func substNilSinceTimeForEntityNoCheck(entity multiwatcher.EntityInfo) multiwatcher.EntityInfo {
394
// Zero out any updated timestamps for unit or service status values
395
// so we can easily check the results.
396
switch e := entity.(type) {
397
case *multiwatcher.UnitInfo:
398
unitInfo := *e // must copy because this entity came out of the multiwatcher cache.
399
unitInfo.WorkloadStatus.Since = nil
400
unitInfo.AgentStatus.Since = nil
402
case *multiwatcher.ApplicationInfo:
403
applicationInfo := *e // must copy because this entity came out of the multiwatcher cache.
404
applicationInfo.Status.Since = nil
405
return &applicationInfo
406
case *multiwatcher.MachineInfo:
407
machineInfo := *e // must copy because we this entity came out of the multiwatcher cache.
408
machineInfo.AgentStatus.Since = nil
409
machineInfo.InstanceStatus.Since = nil
416
// changeTestFunc is a function for the preparation of a test and
417
// the creation of the according case.
418
type changeTestFunc func(c *gc.C, st *State) changeTestCase
420
// performChangeTestCases runs a passed number of test cases for changes.
421
func (s *allWatcherStateSuite) performChangeTestCases(c *gc.C, changeTestFuncs []changeTestFunc) {
422
for i, changeTestFunc := range changeTestFuncs {
423
test := changeTestFunc(c, s.state)
425
c.Logf("test %d. %s", i, test.about)
426
b := newAllWatcherStateBacking(s.state)
428
for _, info := range test.initialContents {
431
err := b.Changed(all, test.change)
432
c.Assert(err, jc.ErrorIsNil)
433
entities := all.All()
434
substNilSinceTimeForEntities(c, entities)
435
assertEntitiesEqual(c, entities, test.expectContents)
440
func (s *allWatcherStateSuite) TestChangeAnnotations(c *gc.C) {
441
testChangeAnnotations(c, s.performChangeTestCases)
444
func (s *allWatcherStateSuite) TestChangeMachines(c *gc.C) {
445
testChangeMachines(c, s.performChangeTestCases)
448
func (s *allWatcherStateSuite) TestChangeRelations(c *gc.C) {
449
testChangeRelations(c, s.owner, s.performChangeTestCases)
452
func (s *allWatcherStateSuite) TestChangeServices(c *gc.C) {
453
testChangeServices(c, s.owner, s.performChangeTestCases)
456
func (s *allWatcherStateSuite) TestChangeServicesConstraints(c *gc.C) {
457
testChangeServicesConstraints(c, s.owner, s.performChangeTestCases)
460
func (s *allWatcherStateSuite) TestChangeUnits(c *gc.C) {
461
testChangeUnits(c, s.owner, s.performChangeTestCases)
464
func (s *allWatcherStateSuite) TestChangeUnitsNonNilPorts(c *gc.C) {
465
testChangeUnitsNonNilPorts(c, s.owner, s.performChangeTestCases)
468
func (s *allWatcherStateSuite) TestChangeActions(c *gc.C) {
469
changeTestFuncs := []changeTestFunc{
470
func(c *gc.C, st *State) changeTestCase {
471
wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
472
u, err := wordpress.AddUnit()
473
c.Assert(err, jc.ErrorIsNil)
474
action, err := st.EnqueueAction(u.Tag(), "vacuumdb", map[string]interface{}{})
475
c.Assert(err, jc.ErrorIsNil)
476
enqueued := makeActionInfo(action, st)
477
action, err = action.Begin()
478
c.Assert(err, jc.ErrorIsNil)
479
started := makeActionInfo(action, st)
480
return changeTestCase{
481
about: "action change picks up last change",
482
initialContents: []multiwatcher.EntityInfo{&enqueued, &started},
483
change: watcher.Change{C: actionsC, Id: st.docID(action.Id())},
484
expectContents: []multiwatcher.EntityInfo{&started},
488
s.performChangeTestCases(c, changeTestFuncs)
491
func (s *allWatcherStateSuite) TestChangeBlocks(c *gc.C) {
492
changeTestFuncs := []changeTestFunc{
493
func(c *gc.C, st *State) changeTestCase {
494
return changeTestCase{
495
about: "no blocks in state, no blocks in store -> do nothing",
496
change: watcher.Change{
501
func(c *gc.C, st *State) changeTestCase {
502
blockId := st.docID("0")
503
blockType := DestroyBlock.ToParams()
505
return changeTestCase{
506
about: "no change if block is not in backing",
507
initialContents: []multiwatcher.EntityInfo{&multiwatcher.BlockInfo{
508
ModelUUID: st.ModelUUID(),
512
Tag: st.ModelTag().String(),
514
change: watcher.Change{
516
Id: st.localID(blockId),
518
expectContents: []multiwatcher.EntityInfo{&multiwatcher.BlockInfo{
519
ModelUUID: st.ModelUUID(),
523
Tag: st.ModelTag().String(),
527
func(c *gc.C, st *State) changeTestCase {
528
err := st.SwitchBlockOn(DestroyBlock, "multiwatcher testing")
529
c.Assert(err, jc.ErrorIsNil)
530
b, found, err := st.GetBlockForType(DestroyBlock)
531
c.Assert(err, jc.ErrorIsNil)
532
c.Assert(found, jc.IsTrue)
535
return changeTestCase{
536
about: "block is added if it's in backing but not in Store",
537
change: watcher.Change{
541
expectContents: []multiwatcher.EntityInfo{
542
&multiwatcher.BlockInfo{
543
ModelUUID: st.ModelUUID(),
544
Id: st.localID(blockId),
545
Type: b.Type().ToParams(),
546
Message: b.Message(),
547
Tag: st.ModelTag().String(),
550
func(c *gc.C, st *State) changeTestCase {
551
err := st.SwitchBlockOn(DestroyBlock, "multiwatcher testing")
552
c.Assert(err, jc.ErrorIsNil)
553
b, found, err := st.GetBlockForType(DestroyBlock)
554
c.Assert(err, jc.ErrorIsNil)
555
c.Assert(found, jc.IsTrue)
556
err = st.SwitchBlockOff(DestroyBlock)
557
c.Assert(err, jc.ErrorIsNil)
559
return changeTestCase{
560
about: "block is removed if it's in backing and in multiwatcher.Store",
561
change: watcher.Change{
568
s.performChangeTestCases(c, changeTestFuncs)
571
func (s *allWatcherStateSuite) TestClosingPorts(c *gc.C) {
572
// Init the test model.
573
wordpress := AddTestingService(c, s.state, "wordpress", AddTestingCharm(c, s.state, "wordpress"))
574
u, err := wordpress.AddUnit()
575
c.Assert(err, jc.ErrorIsNil)
576
m, err := s.state.AddMachine("quantal", JobHostUnits)
577
c.Assert(err, jc.ErrorIsNil)
578
err = u.AssignToMachine(m)
579
c.Assert(err, jc.ErrorIsNil)
580
publicAddress := network.NewScopedAddress("1.2.3.4", network.ScopePublic)
581
privateAddress := network.NewScopedAddress("4.3.2.1", network.ScopeCloudLocal)
582
err = m.SetProviderAddresses(publicAddress, privateAddress)
583
c.Assert(err, jc.ErrorIsNil)
584
err = u.OpenPorts("tcp", 12345, 12345)
585
c.Assert(err, jc.ErrorIsNil)
586
// Create all watcher state backing.
587
b := newAllWatcherStateBacking(s.state)
589
all.Update(&multiwatcher.MachineInfo{
590
ModelUUID: s.state.ModelUUID(),
593
// Check opened ports.
594
err = b.Changed(all, watcher.Change{
596
Id: s.state.docID("wordpress/0"),
598
c.Assert(err, jc.ErrorIsNil)
599
entities := all.All()
600
substNilSinceTimeForEntities(c, entities)
601
assertEntitiesEqual(c, entities, []multiwatcher.EntityInfo{
602
&multiwatcher.UnitInfo{
603
ModelUUID: s.state.ModelUUID(),
605
Application: "wordpress",
608
PublicAddress: "1.2.3.4",
609
PrivateAddress: "4.3.2.1",
610
Ports: []multiwatcher.Port{{"tcp", 12345}},
611
PortRanges: []multiwatcher.PortRange{{12345, 12345, "tcp"}},
612
WorkloadStatus: multiwatcher.StatusInfo{
614
Message: "Waiting for agent initialization to finish",
615
Data: map[string]interface{}{},
617
AgentStatus: multiwatcher.StatusInfo{
618
Current: "allocating",
619
Data: map[string]interface{}{},
622
&multiwatcher.MachineInfo{
623
ModelUUID: s.state.ModelUUID(),
628
err = u.ClosePorts("tcp", 12345, 12345)
629
c.Assert(err, jc.ErrorIsNil)
630
err = b.Changed(all, watcher.Change{
632
Id: s.state.docID("m#0#0.1.2.0/24"),
634
c.Assert(err, jc.ErrorIsNil)
636
substNilSinceTimeForEntities(c, entities)
637
assertEntitiesEqual(c, entities, []multiwatcher.EntityInfo{
638
&multiwatcher.UnitInfo{
639
ModelUUID: s.state.ModelUUID(),
641
Application: "wordpress",
644
PublicAddress: "1.2.3.4",
645
PrivateAddress: "4.3.2.1",
646
Ports: []multiwatcher.Port{},
647
PortRanges: []multiwatcher.PortRange{},
648
WorkloadStatus: multiwatcher.StatusInfo{
650
Message: "Waiting for agent initialization to finish",
651
Data: map[string]interface{}{},
653
AgentStatus: multiwatcher.StatusInfo{
654
Current: "allocating",
655
Data: map[string]interface{}{},
658
&multiwatcher.MachineInfo{
659
ModelUUID: s.state.ModelUUID(),
665
func (s *allWatcherStateSuite) TestSettings(c *gc.C) {
666
// Init the test model.
667
svc := AddTestingService(c, s.state, "dummy-application", AddTestingCharm(c, s.state, "dummy"))
668
b := newAllWatcherStateBacking(s.state)
670
// 1st scenario part: set settings and signal change.
671
setServiceConfigAttr(c, svc, "username", "foo")
672
setServiceConfigAttr(c, svc, "outlook", "foo@bar")
673
all.Update(&multiwatcher.ApplicationInfo{
674
ModelUUID: s.state.ModelUUID(),
675
Name: "dummy-application",
676
CharmURL: "local:quantal/quantal-dummy-1",
678
err := b.Changed(all, watcher.Change{
680
Id: s.state.docID("a#dummy-application#local:quantal/quantal-dummy-1"),
682
c.Assert(err, jc.ErrorIsNil)
683
entities := all.All()
684
substNilSinceTimeForEntities(c, entities)
685
assertEntitiesEqual(c, entities, []multiwatcher.EntityInfo{
686
&multiwatcher.ApplicationInfo{
687
ModelUUID: s.state.ModelUUID(),
688
Name: "dummy-application",
689
CharmURL: "local:quantal/quantal-dummy-1",
690
Config: charm.Settings{"outlook": "foo@bar", "username": "foo"},
693
// 2nd scenario part: destroy the service and signal change.
695
c.Assert(err, jc.ErrorIsNil)
696
err = b.Changed(all, watcher.Change{
698
Id: s.state.docID("a#dummy-application#local:quantal/quantal-dummy-1"),
700
c.Assert(err, jc.ErrorIsNil)
702
assertEntitiesEqual(c, entities, []multiwatcher.EntityInfo{
703
&multiwatcher.ApplicationInfo{
704
ModelUUID: s.state.ModelUUID(),
705
Name: "dummy-application",
706
CharmURL: "local:quantal/quantal-dummy-1",
711
// TestStateWatcher tests the integration of the state watcher
712
// with the state-based backing. Most of the logic is tested elsewhere -
713
// this just tests end-to-end.
714
func (s *allWatcherStateSuite) TestStateWatcher(c *gc.C) {
715
m0, err := s.state.AddMachine("trusty", JobManageModel)
716
c.Assert(err, jc.ErrorIsNil)
717
c.Assert(m0.Id(), gc.Equals, "0")
719
m1, err := s.state.AddMachine("saucy", JobHostUnits)
720
c.Assert(err, jc.ErrorIsNil)
721
c.Assert(m1.Id(), gc.Equals, "1")
723
tw := newTestAllWatcher(s.state, c)
726
// Expect to see events for the already created machines first.
729
checkDeltasEqual(c, deltas, []multiwatcher.Delta{{
730
Entity: &multiwatcher.MachineInfo{
731
ModelUUID: s.state.ModelUUID(),
733
AgentStatus: multiwatcher.StatusInfo{
734
Current: status.StatusPending,
735
Data: map[string]interface{}{},
738
InstanceStatus: multiwatcher.StatusInfo{
739
Current: status.StatusPending,
740
Data: map[string]interface{}{},
743
Life: multiwatcher.Life("alive"),
745
Jobs: []multiwatcher.MachineJob{JobManageModel.ToParams()},
746
Addresses: []multiwatcher.Address{},
751
Entity: &multiwatcher.MachineInfo{
752
ModelUUID: s.state.ModelUUID(),
754
AgentStatus: multiwatcher.StatusInfo{
755
Current: status.StatusPending,
756
Data: map[string]interface{}{},
759
InstanceStatus: multiwatcher.StatusInfo{
760
Current: status.StatusPending,
761
Data: map[string]interface{}{},
764
Life: multiwatcher.Life("alive"),
766
Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()},
767
Addresses: []multiwatcher.Address{},
773
// Destroy a machine and make sure that's seen.
775
c.Assert(err, jc.ErrorIsNil)
778
zeroOutTimestampsForDeltas(c, deltas)
779
checkDeltasEqual(c, deltas, []multiwatcher.Delta{{
780
Entity: &multiwatcher.MachineInfo{
781
ModelUUID: s.state.ModelUUID(),
783
AgentStatus: multiwatcher.StatusInfo{
784
Current: status.StatusPending,
785
Data: map[string]interface{}{},
788
InstanceStatus: multiwatcher.StatusInfo{
789
Current: status.StatusPending,
790
Data: map[string]interface{}{},
793
Life: multiwatcher.Life("dying"),
795
Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()},
796
Addresses: []multiwatcher.Address{},
802
err = m1.EnsureDead()
803
c.Assert(err, jc.ErrorIsNil)
806
zeroOutTimestampsForDeltas(c, deltas)
807
checkDeltasEqual(c, deltas, []multiwatcher.Delta{{
808
Entity: &multiwatcher.MachineInfo{
809
ModelUUID: s.state.ModelUUID(),
811
AgentStatus: multiwatcher.StatusInfo{
812
Current: status.StatusPending,
813
Data: map[string]interface{}{},
816
InstanceStatus: multiwatcher.StatusInfo{
817
Current: status.StatusPending,
818
Data: map[string]interface{}{},
821
Life: multiwatcher.Life("dead"),
823
Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()},
824
Addresses: []multiwatcher.Address{},
830
// Make some more changes to the state.
833
hc := &instance.HardwareCharacteristics{
837
err = m0.SetProvisioned("i-0", "bootstrap_nonce", hc)
838
c.Assert(err, jc.ErrorIsNil)
841
c.Assert(err, jc.ErrorIsNil)
843
m2, err := s.state.AddMachine("quantal", JobHostUnits)
844
c.Assert(err, jc.ErrorIsNil)
845
c.Assert(m2.Id(), gc.Equals, "2")
847
wordpress := AddTestingService(c, s.state, "wordpress", AddTestingCharm(c, s.state, "wordpress"))
848
wu, err := wordpress.AddUnit()
849
c.Assert(err, jc.ErrorIsNil)
850
err = wu.AssignToMachine(m2)
851
c.Assert(err, jc.ErrorIsNil)
853
// Look for the state changes from the allwatcher.
856
zeroOutTimestampsForDeltas(c, deltas)
858
checkDeltasEqual(c, deltas, []multiwatcher.Delta{{
859
Entity: &multiwatcher.MachineInfo{
860
ModelUUID: s.state.ModelUUID(),
863
AgentStatus: multiwatcher.StatusInfo{
864
Current: status.StatusPending,
865
Data: map[string]interface{}{},
868
InstanceStatus: multiwatcher.StatusInfo{
869
Current: status.StatusPending,
870
Data: map[string]interface{}{},
873
Life: multiwatcher.Life("alive"),
875
Jobs: []multiwatcher.MachineJob{JobManageModel.ToParams()},
876
Addresses: []multiwatcher.Address{},
877
HardwareCharacteristics: hc,
883
Entity: &multiwatcher.MachineInfo{
884
ModelUUID: s.state.ModelUUID(),
888
Entity: &multiwatcher.MachineInfo{
889
ModelUUID: s.state.ModelUUID(),
891
AgentStatus: multiwatcher.StatusInfo{
892
Current: status.StatusPending,
893
Data: map[string]interface{}{},
896
InstanceStatus: multiwatcher.StatusInfo{
897
Current: status.StatusPending,
898
Data: map[string]interface{}{},
901
Life: multiwatcher.Life("alive"),
903
Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()},
904
Addresses: []multiwatcher.Address{},
909
Entity: &multiwatcher.ApplicationInfo{
910
ModelUUID: s.state.ModelUUID(),
912
CharmURL: "local:quantal/quantal-wordpress-3",
914
Config: make(map[string]interface{}),
915
Status: multiwatcher.StatusInfo{
917
Message: "Waiting for agent initialization to finish",
918
Data: map[string]interface{}{},
922
Entity: &multiwatcher.UnitInfo{
923
ModelUUID: s.state.ModelUUID(),
925
Application: "wordpress",
928
WorkloadStatus: multiwatcher.StatusInfo{
930
Message: "Waiting for agent initialization to finish",
931
Data: map[string]interface{}{},
933
AgentStatus: multiwatcher.StatusInfo{
934
Current: "allocating",
936
Data: map[string]interface{}{},
942
func (s *allWatcherStateSuite) TestStateWatcherTwoModels(c *gc.C) {
943
loggo.GetLogger("juju.state.watcher").SetLogLevel(loggo.TRACE)
944
// The return values for the setup and trigger functions are the
945
// number of changes to expect.
946
for i, test := range []struct {
948
setUpState func(*State) int
949
triggerEvent func(*State) int
953
triggerEvent: func(st *State) int {
954
m0, err := st.AddMachine("trusty", JobHostUnits)
955
c.Assert(err, jc.ErrorIsNil)
956
c.Assert(m0.Id(), gc.Equals, "0")
960
about: "applications",
961
triggerEvent: func(st *State) int {
962
AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
967
setUpState: func(st *State) int {
968
AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
971
triggerEvent: func(st *State) int {
972
svc, err := st.Application("wordpress")
973
c.Assert(err, jc.ErrorIsNil)
975
_, err = svc.AddUnit()
976
c.Assert(err, jc.ErrorIsNil)
981
setUpState: func(st *State) int {
982
AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
983
AddTestingService(c, st, "mysql", AddTestingCharm(c, st, "mysql"))
986
triggerEvent: func(st *State) int {
987
eps, err := st.InferEndpoints("mysql", "wordpress")
988
c.Assert(err, jc.ErrorIsNil)
989
_, err = st.AddRelation(eps...)
990
c.Assert(err, jc.ErrorIsNil)
994
about: "annotations",
995
setUpState: func(st *State) int {
996
m, err := st.AddMachine("trusty", JobHostUnits)
997
c.Assert(err, jc.ErrorIsNil)
998
c.Assert(m.Id(), gc.Equals, "0")
1001
triggerEvent: func(st *State) int {
1002
m, err := st.Machine("0")
1003
c.Assert(err, jc.ErrorIsNil)
1005
err = st.SetAnnotations(m, map[string]string{"foo": "bar"})
1006
c.Assert(err, jc.ErrorIsNil)
1011
setUpState: func(st *State) int {
1012
m, err := st.AddMachine("trusty", JobHostUnits)
1013
c.Assert(err, jc.ErrorIsNil)
1014
c.Assert(m.Id(), gc.Equals, "0")
1015
err = m.SetProvisioned("inst-id", "fake_nonce", nil)
1016
c.Assert(err, jc.ErrorIsNil)
1019
triggerEvent: func(st *State) int {
1020
m, err := st.Machine("0")
1021
c.Assert(err, jc.ErrorIsNil)
1024
sInfo := status.StatusInfo{
1025
Status: status.StatusError,
1026
Message: "pete tong",
1029
err = m.SetStatus(sInfo)
1030
c.Assert(err, jc.ErrorIsNil)
1034
about: "constraints",
1035
setUpState: func(st *State) int {
1036
AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
1039
triggerEvent: func(st *State) int {
1040
svc, err := st.Application("wordpress")
1041
c.Assert(err, jc.ErrorIsNil)
1043
cpuCores := uint64(99)
1044
err = svc.SetConstraints(constraints.Value{CpuCores: &cpuCores})
1045
c.Assert(err, jc.ErrorIsNil)
1050
setUpState: func(st *State) int {
1051
AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
1054
triggerEvent: func(st *State) int {
1055
svc, err := st.Application("wordpress")
1056
c.Assert(err, jc.ErrorIsNil)
1058
err = svc.UpdateConfigSettings(charm.Settings{"blog-title": "boring"})
1059
c.Assert(err, jc.ErrorIsNil)
1064
triggerEvent: func(st *State) int {
1065
m, found, err := st.GetBlockForType(DestroyBlock)
1066
c.Assert(err, jc.ErrorIsNil)
1067
c.Assert(found, jc.IsFalse)
1068
c.Assert(m, gc.IsNil)
1070
err = st.SwitchBlockOn(DestroyBlock, "test block")
1071
c.Assert(err, jc.ErrorIsNil)
1076
c.Logf("Test %d: %s", i, test.about)
1078
checkIsolationForEnv := func(st *State, w, otherW *testWatcher) {
1079
c.Logf("Making changes to model %s", st.ModelUUID())
1081
if test.setUpState != nil {
1082
expected := test.setUpState(st)
1083
// Consume events from setup.
1084
w.AssertChanges(c, expected)
1085
otherW.AssertNoChange(c)
1088
expected := test.triggerEvent(st)
1089
// Check event was isolated to the correct watcher.
1090
w.AssertChanges(c, expected)
1091
otherW.AssertNoChange(c)
1093
otherState := s.newState(c)
1095
w1 := newTestAllWatcher(s.state, c)
1097
w2 := newTestAllWatcher(otherState, c)
1100
// The first set of deltas is empty, reflecting an empty model.
1101
w1.AssertNoChange(c)
1102
w2.AssertNoChange(c)
1103
checkIsolationForEnv(s.state, w1, w2)
1104
checkIsolationForEnv(otherState, w2, w1)
1110
var _ = gc.Suite(&allModelWatcherStateSuite{})
1112
type allModelWatcherStateSuite struct {
1117
func (s *allModelWatcherStateSuite) SetUpTest(c *gc.C) {
1118
s.allWatcherBaseSuite.SetUpTest(c)
1119
s.state1 = s.newState(c)
1122
func (s *allModelWatcherStateSuite) Reset(c *gc.C) {
1127
// performChangeTestCases runs a passed number of test cases for changes.
1128
func (s *allModelWatcherStateSuite) performChangeTestCases(c *gc.C, changeTestFuncs []changeTestFunc) {
1129
for i, changeTestFunc := range changeTestFuncs {
1130
func() { // in aid of per-loop defers
1133
test0 := changeTestFunc(c, s.state)
1135
c.Logf("test %d. %s", i, test0.about)
1136
b := NewAllModelWatcherStateBacking(s.state)
1140
// Do updates and check for first env.
1141
for _, info := range test0.initialContents {
1144
err := b.Changed(all, test0.change)
1145
c.Assert(err, jc.ErrorIsNil)
1146
var entities entityInfoSlice = all.All()
1147
substNilSinceTimeForEntities(c, entities)
1148
assertEntitiesEqual(c, entities, test0.expectContents)
1150
// Now do the same updates for a second env.
1151
test1 := changeTestFunc(c, s.state1)
1152
for _, info := range test1.initialContents {
1155
err = b.Changed(all, test1.change)
1156
c.Assert(err, jc.ErrorIsNil)
1158
entities = all.All()
1160
// Expected to see entities for both envs.
1161
var expectedEntities entityInfoSlice = append(
1162
test0.expectContents,
1163
test1.expectContents...)
1165
sort.Sort(expectedEntities)
1167
// for some reason substNilSinceTimeForStatus cares if the Current is not blank
1168
// and will abort if it is. Apparently this happens and it's totally fine. So we
1169
// must use the NoCheck variant, rather than substNilSinceTimeForEntities(c, entities)
1170
for i := range entities {
1171
entities[i] = substNilSinceTimeForEntityNoCheck(entities[i])
1173
assertEntitiesEqual(c, entities, expectedEntities)
1178
func (s *allModelWatcherStateSuite) TestChangeAnnotations(c *gc.C) {
1179
testChangeAnnotations(c, s.performChangeTestCases)
1182
func (s *allModelWatcherStateSuite) TestChangeMachines(c *gc.C) {
1183
testChangeMachines(c, s.performChangeTestCases)
1186
func (s *allModelWatcherStateSuite) TestChangeRelations(c *gc.C) {
1187
testChangeRelations(c, s.owner, s.performChangeTestCases)
1190
func (s *allModelWatcherStateSuite) TestChangeServices(c *gc.C) {
1191
testChangeServices(c, s.owner, s.performChangeTestCases)
1194
func (s *allModelWatcherStateSuite) TestChangeServicesConstraints(c *gc.C) {
1195
testChangeServicesConstraints(c, s.owner, s.performChangeTestCases)
1198
func (s *allModelWatcherStateSuite) TestChangeUnits(c *gc.C) {
1199
testChangeUnits(c, s.owner, s.performChangeTestCases)
1202
func (s *allModelWatcherStateSuite) TestChangeUnitsNonNilPorts(c *gc.C) {
1203
testChangeUnitsNonNilPorts(c, s.owner, s.performChangeTestCases)
1206
func (s *allModelWatcherStateSuite) TestChangeModels(c *gc.C) {
1207
changeTestFuncs := []changeTestFunc{
1208
func(c *gc.C, st *State) changeTestCase {
1209
return changeTestCase{
1210
about: "no model in state -> do nothing",
1211
change: watcher.Change{
1213
Id: "non-existing-uuid",
1216
func(c *gc.C, st *State) changeTestCase {
1217
return changeTestCase{
1218
about: "model is removed if it's not in backing",
1219
initialContents: []multiwatcher.EntityInfo{&multiwatcher.ModelInfo{
1220
ModelUUID: "some-uuid",
1222
change: watcher.Change{
1227
func(c *gc.C, st *State) changeTestCase {
1228
model, err := st.Model()
1229
c.Assert(err, jc.ErrorIsNil)
1230
return changeTestCase{
1231
about: "model is added if it's in backing but not in Store",
1232
change: watcher.Change{
1236
expectContents: []multiwatcher.EntityInfo{
1237
&multiwatcher.ModelInfo{
1238
ModelUUID: model.UUID(),
1240
Life: multiwatcher.Life("alive"),
1241
Owner: model.Owner().Id(),
1242
ControllerUUID: model.ControllerUUID(),
1245
func(c *gc.C, st *State) changeTestCase {
1246
model, err := st.Model()
1247
c.Assert(err, jc.ErrorIsNil)
1248
return changeTestCase{
1249
about: "model is updated if it's in backing and in Store",
1250
initialContents: []multiwatcher.EntityInfo{
1251
&multiwatcher.ModelInfo{
1252
ModelUUID: model.UUID(),
1254
Life: multiwatcher.Life("alive"),
1255
Owner: model.Owner().Id(),
1256
ControllerUUID: model.ControllerUUID(),
1259
change: watcher.Change{
1263
expectContents: []multiwatcher.EntityInfo{
1264
&multiwatcher.ModelInfo{
1265
ModelUUID: model.UUID(),
1267
Life: multiwatcher.Life("alive"),
1268
Owner: model.Owner().Id(),
1269
ControllerUUID: model.ControllerUUID(),
1272
func(c *gc.C, st *State) changeTestCase {
1273
svc := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
1274
err := svc.SetConstraints(constraints.MustParse("mem=4G arch=amd64"))
1275
c.Assert(err, jc.ErrorIsNil)
1277
return changeTestCase{
1278
about: "status is changed if the service exists in the store",
1279
initialContents: []multiwatcher.EntityInfo{&multiwatcher.ApplicationInfo{
1280
ModelUUID: st.ModelUUID(),
1282
Constraints: constraints.MustParse("mem=99M cpu-cores=2 cpu-power=4"),
1284
change: watcher.Change{
1286
Id: st.docID("a#wordpress"),
1288
expectContents: []multiwatcher.EntityInfo{
1289
&multiwatcher.ApplicationInfo{
1290
ModelUUID: st.ModelUUID(),
1292
Constraints: constraints.MustParse("mem=4G arch=amd64"),
1296
s.performChangeTestCases(c, changeTestFuncs)
1299
func (s *allModelWatcherStateSuite) TestChangeForDeadEnv(c *gc.C) {
1300
// Ensure an entity is removed when a change is seen but
1301
// the model the entity belonged to has already died.
1303
b := NewAllModelWatcherStateBacking(s.state)
1307
// Insert a machine for an model that doesn't actually
1308
// exist (mimics env removal).
1309
all.Update(&multiwatcher.MachineInfo{
1313
c.Assert(all.All(), gc.HasLen, 1)
1315
err := b.Changed(all, watcher.Change{
1317
Id: ensureModelUUID("uuid", "0"),
1319
c.Assert(err, jc.ErrorIsNil)
1321
// Entity info should be gone now.
1322
c.Assert(all.All(), gc.HasLen, 0)
1325
func (s *allModelWatcherStateSuite) TestGetAll(c *gc.C) {
1326
// Set up 2 models and ensure that GetAll returns the
1327
// entities for both of them.
1328
entities0 := s.setUpScenario(c, s.state, 2)
1329
entities1 := s.setUpScenario(c, s.state1, 4)
1330
expectedEntities := append(entities0, entities1...)
1332
// allModelWatcherStateBacking also watches models so add those in.
1333
env, err := s.state.Model()
1334
c.Assert(err, jc.ErrorIsNil)
1335
env1, err := s.state1.Model()
1336
c.Assert(err, jc.ErrorIsNil)
1337
expectedEntities = append(expectedEntities,
1338
&multiwatcher.ModelInfo{
1339
ModelUUID: env.UUID(),
1341
Life: multiwatcher.Life("alive"),
1342
Owner: env.Owner().Id(),
1343
ControllerUUID: env.ControllerUUID(),
1345
&multiwatcher.ModelInfo{
1346
ModelUUID: env1.UUID(),
1348
Life: multiwatcher.Life("alive"),
1349
Owner: env1.Owner().Id(),
1350
ControllerUUID: env1.ControllerUUID(),
1354
b := NewAllModelWatcherStateBacking(s.state)
1357
c.Assert(err, jc.ErrorIsNil)
1358
var gotEntities entityInfoSlice = all.All()
1359
sort.Sort(gotEntities)
1360
sort.Sort(expectedEntities)
1361
substNilSinceTimeForEntities(c, gotEntities)
1362
assertEntitiesEqual(c, gotEntities, expectedEntities)
1365
// TestStateWatcher tests the integration of the state watcher with
1366
// allModelWatcherStateBacking. Most of the logic is comprehensively
1367
// tested elsewhere - this just tests end-to-end.
1368
func (s *allModelWatcherStateSuite) TestStateWatcher(c *gc.C) {
1370
env0, err := st0.Model()
1371
c.Assert(err, jc.ErrorIsNil)
1374
env1, err := st1.Model()
1375
c.Assert(err, jc.ErrorIsNil)
1377
// Create some initial machines across 2 models
1378
m00, err := st0.AddMachine("trusty", JobManageModel)
1379
c.Assert(err, jc.ErrorIsNil)
1380
c.Assert(m00.Id(), gc.Equals, "0")
1382
m10, err := st1.AddMachine("saucy", JobHostUnits)
1383
c.Assert(err, jc.ErrorIsNil)
1384
c.Assert(m10.Id(), gc.Equals, "0")
1386
tw := newTestAllModelWatcher(st0, c)
1389
// Expect to see events for the already created models and
1392
checkDeltasEqual(c, deltas, []multiwatcher.Delta{{
1393
Entity: &multiwatcher.ModelInfo{
1394
ModelUUID: env0.UUID(),
1397
Owner: env0.Owner().Id(),
1398
ControllerUUID: env0.ControllerUUID(),
1401
Entity: &multiwatcher.ModelInfo{
1402
ModelUUID: env1.UUID(),
1405
Owner: env1.Owner().Id(),
1406
ControllerUUID: env1.ControllerUUID(),
1409
Entity: &multiwatcher.MachineInfo{
1410
ModelUUID: st0.ModelUUID(),
1412
AgentStatus: multiwatcher.StatusInfo{
1413
Current: status.StatusPending,
1414
Data: map[string]interface{}{},
1416
InstanceStatus: multiwatcher.StatusInfo{
1417
Current: status.StatusPending,
1418
Data: map[string]interface{}{},
1420
Life: multiwatcher.Life("alive"),
1422
Jobs: []multiwatcher.MachineJob{JobManageModel.ToParams()},
1423
Addresses: []multiwatcher.Address{},
1428
Entity: &multiwatcher.MachineInfo{
1429
ModelUUID: st1.ModelUUID(),
1431
AgentStatus: multiwatcher.StatusInfo{
1432
Current: status.StatusPending,
1433
Data: map[string]interface{}{},
1435
InstanceStatus: multiwatcher.StatusInfo{
1436
Current: status.StatusPending,
1437
Data: map[string]interface{}{},
1439
Life: multiwatcher.Life("alive"),
1441
Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()},
1442
Addresses: []multiwatcher.Address{},
1448
// Destroy a machine and make sure that's seen.
1450
c.Assert(err, jc.ErrorIsNil)
1453
zeroOutTimestampsForDeltas(c, deltas)
1454
checkDeltasEqual(c, deltas, []multiwatcher.Delta{{
1455
Entity: &multiwatcher.MachineInfo{
1456
ModelUUID: st1.ModelUUID(),
1458
AgentStatus: multiwatcher.StatusInfo{
1459
Current: status.StatusPending,
1460
Data: map[string]interface{}{},
1462
InstanceStatus: multiwatcher.StatusInfo{
1463
Current: status.StatusPending,
1464
Data: map[string]interface{}{},
1466
Life: multiwatcher.Life("dying"),
1468
Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()},
1469
Addresses: []multiwatcher.Address{},
1475
err = m10.EnsureDead()
1476
c.Assert(err, jc.ErrorIsNil)
1479
zeroOutTimestampsForDeltas(c, deltas)
1480
checkDeltasEqual(c, deltas, []multiwatcher.Delta{{
1481
Entity: &multiwatcher.MachineInfo{
1482
ModelUUID: st1.ModelUUID(),
1484
AgentStatus: multiwatcher.StatusInfo{
1485
Current: status.StatusPending,
1486
Data: map[string]interface{}{},
1488
InstanceStatus: multiwatcher.StatusInfo{
1489
Current: status.StatusPending,
1490
Data: map[string]interface{}{},
1492
Life: multiwatcher.Life("dead"),
1494
Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()},
1495
Addresses: []multiwatcher.Address{},
1501
// Make further changes to the state, including the addition of a
1503
err = m00.SetProvisioned("i-0", "bootstrap_nonce", nil)
1504
c.Assert(err, jc.ErrorIsNil)
1507
c.Assert(err, jc.ErrorIsNil)
1509
m11, err := st1.AddMachine("quantal", JobHostUnits)
1510
c.Assert(err, jc.ErrorIsNil)
1511
c.Assert(m11.Id(), gc.Equals, "1")
1513
wordpress := AddTestingService(c, st1, "wordpress", AddTestingCharm(c, st1, "wordpress"))
1514
wu, err := wordpress.AddUnit()
1515
c.Assert(err, jc.ErrorIsNil)
1516
err = wu.AssignToMachine(m11)
1517
c.Assert(err, jc.ErrorIsNil)
1519
st2 := s.newState(c)
1520
env2, err := st2.Model()
1521
c.Assert(err, jc.ErrorIsNil)
1523
m20, err := st2.AddMachine("trusty", JobHostUnits)
1524
c.Assert(err, jc.ErrorIsNil)
1525
c.Assert(m20.Id(), gc.Equals, "0")
1527
// Look for the state changes from the allwatcher.
1529
zeroOutTimestampsForDeltas(c, deltas)
1531
checkDeltasEqual(c, deltas, []multiwatcher.Delta{{
1532
Entity: &multiwatcher.MachineInfo{
1533
ModelUUID: st0.ModelUUID(),
1536
AgentStatus: multiwatcher.StatusInfo{
1537
Current: status.StatusPending,
1538
Data: map[string]interface{}{},
1540
InstanceStatus: multiwatcher.StatusInfo{
1541
Current: status.StatusPending,
1542
Data: map[string]interface{}{},
1544
Life: multiwatcher.Life("alive"),
1546
Jobs: []multiwatcher.MachineJob{JobManageModel.ToParams()},
1547
Addresses: []multiwatcher.Address{},
1548
HardwareCharacteristics: &instance.HardwareCharacteristics{},
1554
Entity: &multiwatcher.MachineInfo{
1555
ModelUUID: st1.ModelUUID(),
1559
Entity: &multiwatcher.MachineInfo{
1560
ModelUUID: st1.ModelUUID(),
1562
AgentStatus: multiwatcher.StatusInfo{
1563
Current: status.StatusPending,
1564
Data: map[string]interface{}{},
1566
InstanceStatus: multiwatcher.StatusInfo{
1567
Current: status.StatusPending,
1568
Data: map[string]interface{}{},
1570
Life: multiwatcher.Life("alive"),
1572
Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()},
1573
Addresses: []multiwatcher.Address{},
1578
Entity: &multiwatcher.ApplicationInfo{
1579
ModelUUID: st1.ModelUUID(),
1581
CharmURL: "local:quantal/quantal-wordpress-3",
1583
Config: make(map[string]interface{}),
1584
Status: multiwatcher.StatusInfo{
1586
Message: "Waiting for agent initialization to finish",
1587
Data: map[string]interface{}{},
1591
Entity: &multiwatcher.UnitInfo{
1592
ModelUUID: st1.ModelUUID(),
1593
Name: "wordpress/0",
1594
Application: "wordpress",
1597
WorkloadStatus: multiwatcher.StatusInfo{
1599
Message: "Waiting for agent initialization to finish",
1600
Data: map[string]interface{}{},
1602
AgentStatus: multiwatcher.StatusInfo{
1603
Current: "allocating",
1605
Data: map[string]interface{}{},
1609
Entity: &multiwatcher.ModelInfo{
1610
ModelUUID: env2.UUID(),
1613
Owner: env2.Owner().Id(),
1614
ControllerUUID: env2.ControllerUUID(),
1617
Entity: &multiwatcher.MachineInfo{
1618
ModelUUID: st2.ModelUUID(),
1620
AgentStatus: multiwatcher.StatusInfo{
1621
Current: status.StatusPending,
1622
Data: map[string]interface{}{},
1624
InstanceStatus: multiwatcher.StatusInfo{
1625
Current: status.StatusPending,
1626
Data: map[string]interface{}{},
1628
Life: multiwatcher.Life("alive"),
1630
Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()},
1631
Addresses: []multiwatcher.Address{},
1638
func zeroOutTimestampsForDeltas(c *gc.C, deltas []multiwatcher.Delta) {
1639
for i, delta := range deltas {
1640
switch e := delta.Entity.(type) {
1641
case *multiwatcher.UnitInfo:
1642
unitInfo := *e // must copy, we may not own this reference
1643
substNilSinceTimeForStatus(c, &unitInfo.WorkloadStatus)
1644
substNilSinceTimeForStatus(c, &unitInfo.AgentStatus)
1645
delta.Entity = &unitInfo
1646
case *multiwatcher.ApplicationInfo:
1647
applicationInfo := *e // must copy, we may not own this reference
1648
substNilSinceTimeForStatus(c, &applicationInfo.Status)
1649
delta.Entity = &applicationInfo
1655
// The testChange* funcs are extracted so the test cases can be used
1656
// to test both the allWatcher and allModelWatcher.
1658
func testChangeAnnotations(c *gc.C, runChangeTests func(*gc.C, []changeTestFunc)) {
1659
changeTestFuncs := []changeTestFunc{
1660
func(c *gc.C, st *State) changeTestCase {
1661
return changeTestCase{
1662
about: "no annotation in state, no annotation in store -> do nothing",
1663
change: watcher.Change{
1665
Id: st.docID("m#0"),
1668
func(c *gc.C, st *State) changeTestCase {
1669
return changeTestCase{
1670
about: "annotation is removed if it's not in backing",
1671
initialContents: []multiwatcher.EntityInfo{&multiwatcher.AnnotationInfo{
1672
ModelUUID: st.ModelUUID(),
1675
change: watcher.Change{
1677
Id: st.docID("m#0"),
1680
func(c *gc.C, st *State) changeTestCase {
1681
m, err := st.AddMachine("quantal", JobHostUnits)
1682
c.Assert(err, jc.ErrorIsNil)
1683
err = st.SetAnnotations(m, map[string]string{"foo": "bar", "arble": "baz"})
1684
c.Assert(err, jc.ErrorIsNil)
1686
return changeTestCase{
1687
about: "annotation is added if it's in backing but not in Store",
1688
change: watcher.Change{
1690
Id: st.docID("m#0"),
1692
expectContents: []multiwatcher.EntityInfo{
1693
&multiwatcher.AnnotationInfo{
1694
ModelUUID: st.ModelUUID(),
1696
Annotations: map[string]string{"foo": "bar", "arble": "baz"},
1699
func(c *gc.C, st *State) changeTestCase {
1700
m, err := st.AddMachine("quantal", JobHostUnits)
1701
c.Assert(err, jc.ErrorIsNil)
1702
err = st.SetAnnotations(m, map[string]string{
1703
"arble": "khroomph",
1707
c.Assert(err, jc.ErrorIsNil)
1709
return changeTestCase{
1710
about: "annotation is updated if it's in backing and in multiwatcher.Store",
1711
initialContents: []multiwatcher.EntityInfo{&multiwatcher.AnnotationInfo{
1712
ModelUUID: st.ModelUUID(),
1714
Annotations: map[string]string{
1720
change: watcher.Change{
1722
Id: st.docID("m#0"),
1724
expectContents: []multiwatcher.EntityInfo{
1725
&multiwatcher.AnnotationInfo{
1726
ModelUUID: st.ModelUUID(),
1728
Annotations: map[string]string{
1729
"arble": "khroomph",
1734
runChangeTests(c, changeTestFuncs)
1737
func testChangeMachines(c *gc.C, runChangeTests func(*gc.C, []changeTestFunc)) {
1739
changeTestFuncs := []changeTestFunc{
1740
func(c *gc.C, st *State) changeTestCase {
1741
return changeTestCase{
1742
about: "no machine in state -> do nothing",
1743
change: watcher.Change{
1745
Id: st.docID("m#0"),
1748
func(c *gc.C, st *State) changeTestCase {
1749
return changeTestCase{
1750
about: "no machine in state, no machine in store -> do nothing",
1751
change: watcher.Change{
1756
func(c *gc.C, st *State) changeTestCase {
1757
return changeTestCase{
1758
about: "machine is removed if it's not in backing",
1759
initialContents: []multiwatcher.EntityInfo{&multiwatcher.MachineInfo{
1760
ModelUUID: st.ModelUUID(),
1763
change: watcher.Change{
1768
func(c *gc.C, st *State) changeTestCase {
1769
m, err := st.AddMachine("quantal", JobHostUnits)
1770
c.Assert(err, jc.ErrorIsNil)
1772
sInfo := status.StatusInfo{
1773
Status: status.StatusError,
1777
err = m.SetStatus(sInfo)
1778
c.Assert(err, jc.ErrorIsNil)
1780
return changeTestCase{
1781
about: "machine is added if it's in backing but not in Store",
1782
change: watcher.Change{
1786
expectContents: []multiwatcher.EntityInfo{
1787
&multiwatcher.MachineInfo{
1788
ModelUUID: st.ModelUUID(),
1790
AgentStatus: multiwatcher.StatusInfo{
1791
Current: status.StatusError,
1793
Data: map[string]interface{}{},
1795
InstanceStatus: multiwatcher.StatusInfo{
1796
Current: status.StatusPending,
1797
Data: map[string]interface{}{},
1799
Life: multiwatcher.Life("alive"),
1801
Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()},
1802
Addresses: []multiwatcher.Address{},
1807
func(c *gc.C, st *State) changeTestCase {
1808
m, err := st.AddMachine("trusty", JobHostUnits)
1809
c.Assert(err, jc.ErrorIsNil)
1810
err = m.SetProvisioned("i-0", "bootstrap_nonce", nil)
1811
c.Assert(err, jc.ErrorIsNil)
1812
err = m.SetSupportedContainers([]instance.ContainerType{instance.LXD})
1813
c.Assert(err, jc.ErrorIsNil)
1815
return changeTestCase{
1816
about: "machine is updated if it's in backing and in Store",
1817
initialContents: []multiwatcher.EntityInfo{
1818
&multiwatcher.MachineInfo{
1819
ModelUUID: st.ModelUUID(),
1821
AgentStatus: multiwatcher.StatusInfo{
1822
Current: status.StatusError,
1823
Message: "another failure",
1824
Data: map[string]interface{}{},
1827
InstanceStatus: multiwatcher.StatusInfo{
1828
Current: status.StatusPending,
1829
Data: map[string]interface{}{},
1834
change: watcher.Change{
1838
expectContents: []multiwatcher.EntityInfo{
1839
&multiwatcher.MachineInfo{
1840
ModelUUID: st.ModelUUID(),
1843
AgentStatus: multiwatcher.StatusInfo{
1844
Current: status.StatusError,
1845
Message: "another failure",
1846
Data: map[string]interface{}{},
1848
InstanceStatus: multiwatcher.StatusInfo{
1849
Current: status.StatusPending,
1850
Data: map[string]interface{}{},
1852
Life: multiwatcher.Life("alive"),
1854
Jobs: []multiwatcher.MachineJob{JobHostUnits.ToParams()},
1855
Addresses: []multiwatcher.Address{},
1856
HardwareCharacteristics: &instance.HardwareCharacteristics{},
1857
SupportedContainers: []instance.ContainerType{instance.LXD},
1858
SupportedContainersKnown: true,
1861
func(c *gc.C, st *State) changeTestCase {
1862
return changeTestCase{
1863
about: "no change if status is not in backing",
1864
initialContents: []multiwatcher.EntityInfo{&multiwatcher.MachineInfo{
1865
ModelUUID: st.ModelUUID(),
1867
AgentStatus: multiwatcher.StatusInfo{
1868
Current: status.StatusError,
1870
Data: map[string]interface{}{},
1874
change: watcher.Change{
1876
Id: st.docID("m#0"),
1878
expectContents: []multiwatcher.EntityInfo{
1879
&multiwatcher.MachineInfo{
1880
ModelUUID: st.ModelUUID(),
1882
AgentStatus: multiwatcher.StatusInfo{
1883
Current: status.StatusError,
1885
Data: map[string]interface{}{},
1889
func(c *gc.C, st *State) changeTestCase {
1890
m, err := st.AddMachine("quantal", JobHostUnits)
1891
c.Assert(err, jc.ErrorIsNil)
1893
sInfo := status.StatusInfo{
1894
Status: status.StatusStarted,
1898
err = m.SetStatus(sInfo)
1899
c.Assert(err, jc.ErrorIsNil)
1901
return changeTestCase{
1902
about: "status is changed if the machine exists in the store",
1903
initialContents: []multiwatcher.EntityInfo{&multiwatcher.MachineInfo{
1904
ModelUUID: st.ModelUUID(),
1906
AgentStatus: multiwatcher.StatusInfo{
1907
Current: status.StatusError,
1909
Data: map[string]interface{}{},
1913
change: watcher.Change{
1915
Id: st.docID("m#0"),
1917
expectContents: []multiwatcher.EntityInfo{
1918
&multiwatcher.MachineInfo{
1919
ModelUUID: st.ModelUUID(),
1921
AgentStatus: multiwatcher.StatusInfo{
1922
Current: status.StatusStarted,
1923
Data: make(map[string]interface{}),
1928
runChangeTests(c, changeTestFuncs)
1931
func testChangeRelations(c *gc.C, owner names.UserTag, runChangeTests func(*gc.C, []changeTestFunc)) {
1932
changeTestFuncs := []changeTestFunc{
1933
func(c *gc.C, st *State) changeTestCase {
1934
return changeTestCase{
1935
about: "no relation in state, no service in store -> do nothing",
1936
change: watcher.Change{
1938
Id: st.docID("logging:logging-directory wordpress:logging-dir"),
1941
func(c *gc.C, st *State) changeTestCase {
1942
return changeTestCase{
1943
about: "relation is removed if it's not in backing",
1944
initialContents: []multiwatcher.EntityInfo{&multiwatcher.RelationInfo{
1945
ModelUUID: st.ModelUUID(),
1946
Key: "logging:logging-directory wordpress:logging-dir",
1948
change: watcher.Change{
1950
Id: st.docID("logging:logging-directory wordpress:logging-dir"),
1953
func(c *gc.C, st *State) changeTestCase {
1954
AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
1955
AddTestingService(c, st, "logging", AddTestingCharm(c, st, "logging"))
1956
eps, err := st.InferEndpoints("logging", "wordpress")
1957
c.Assert(err, jc.ErrorIsNil)
1958
_, err = st.AddRelation(eps...)
1959
c.Assert(err, jc.ErrorIsNil)
1961
return changeTestCase{
1962
about: "relation is added if it's in backing but not in Store",
1963
change: watcher.Change{
1965
Id: st.docID("logging:logging-directory wordpress:logging-dir"),
1967
expectContents: []multiwatcher.EntityInfo{
1968
&multiwatcher.RelationInfo{
1969
ModelUUID: st.ModelUUID(),
1970
Key: "logging:logging-directory wordpress:logging-dir",
1971
Endpoints: []multiwatcher.Endpoint{
1972
{ApplicationName: "logging", Relation: multiwatcher.CharmRelation{Name: "logging-directory", Role: "requirer", Interface: "logging", Optional: false, Limit: 1, Scope: "container"}},
1973
{ApplicationName: "wordpress", Relation: multiwatcher.CharmRelation{Name: "logging-dir", Role: "provider", Interface: "logging", Optional: false, Limit: 0, Scope: "container"}}},
1977
runChangeTests(c, changeTestFuncs)
1980
func testChangeServices(c *gc.C, owner names.UserTag, runChangeTests func(*gc.C, []changeTestFunc)) {
1981
// TODO(wallyworld) - add test for changing service status when that is implemented
1982
changeTestFuncs := []changeTestFunc{
1984
func(c *gc.C, st *State) changeTestCase {
1985
return changeTestCase{
1986
about: "no service in state, no service in store -> do nothing",
1987
change: watcher.Change{
1989
Id: st.docID("wordpress"),
1992
func(c *gc.C, st *State) changeTestCase {
1993
return changeTestCase{
1994
about: "service is removed if it's not in backing",
1995
initialContents: []multiwatcher.EntityInfo{
1996
&multiwatcher.ApplicationInfo{
1997
ModelUUID: st.ModelUUID(),
2001
change: watcher.Change{
2003
Id: st.docID("wordpress"),
2006
func(c *gc.C, st *State) changeTestCase {
2007
wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
2008
err := wordpress.SetExposed()
2009
c.Assert(err, jc.ErrorIsNil)
2010
err = wordpress.SetMinUnits(42)
2011
c.Assert(err, jc.ErrorIsNil)
2013
return changeTestCase{
2014
about: "service is added if it's in backing but not in Store",
2015
change: watcher.Change{
2017
Id: st.docID("wordpress"),
2019
expectContents: []multiwatcher.EntityInfo{
2020
&multiwatcher.ApplicationInfo{
2021
ModelUUID: st.ModelUUID(),
2024
CharmURL: "local:quantal/quantal-wordpress-3",
2025
Life: multiwatcher.Life("alive"),
2027
Config: charm.Settings{},
2028
Status: multiwatcher.StatusInfo{
2030
Message: "Waiting for agent initialization to finish",
2031
Data: map[string]interface{}{},
2035
func(c *gc.C, st *State) changeTestCase {
2036
svc := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
2037
setServiceConfigAttr(c, svc, "blog-title", "boring")
2039
return changeTestCase{
2040
about: "service is updated if it's in backing and in multiwatcher.Store",
2041
initialContents: []multiwatcher.EntityInfo{&multiwatcher.ApplicationInfo{
2042
ModelUUID: st.ModelUUID(),
2045
CharmURL: "local:quantal/quantal-wordpress-3",
2047
Constraints: constraints.MustParse("mem=99M"),
2048
Config: charm.Settings{"blog-title": "boring"},
2050
change: watcher.Change{
2052
Id: st.docID("wordpress"),
2054
expectContents: []multiwatcher.EntityInfo{
2055
&multiwatcher.ApplicationInfo{
2056
ModelUUID: st.ModelUUID(),
2058
CharmURL: "local:quantal/quantal-wordpress-3",
2059
Life: multiwatcher.Life("alive"),
2060
Constraints: constraints.MustParse("mem=99M"),
2061
Config: charm.Settings{"blog-title": "boring"},
2064
func(c *gc.C, st *State) changeTestCase {
2065
svc := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
2066
setServiceConfigAttr(c, svc, "blog-title", "boring")
2068
return changeTestCase{
2069
about: "service re-reads config when charm URL changes",
2070
initialContents: []multiwatcher.EntityInfo{&multiwatcher.ApplicationInfo{
2071
ModelUUID: st.ModelUUID(),
2073
// Note: CharmURL has a different revision number from
2074
// the wordpress revision in the testing repo.
2075
CharmURL: "local:quantal/quantal-wordpress-2",
2076
Config: charm.Settings{"foo": "bar"},
2078
change: watcher.Change{
2080
Id: st.docID("wordpress"),
2082
expectContents: []multiwatcher.EntityInfo{
2083
&multiwatcher.ApplicationInfo{
2084
ModelUUID: st.ModelUUID(),
2086
CharmURL: "local:quantal/quantal-wordpress-3",
2087
Life: multiwatcher.Life("alive"),
2088
Config: charm.Settings{"blog-title": "boring"},
2092
func(c *gc.C, st *State) changeTestCase {
2093
return changeTestCase{
2094
about: "no service in state -> do nothing",
2095
change: watcher.Change{
2097
Id: st.docID("a#dummy-application#local:quantal/quantal-dummy-1"),
2100
func(c *gc.C, st *State) changeTestCase {
2101
return changeTestCase{
2102
about: "no change if service is not in backing",
2103
initialContents: []multiwatcher.EntityInfo{&multiwatcher.ApplicationInfo{
2104
ModelUUID: st.ModelUUID(),
2105
Name: "dummy-application",
2106
CharmURL: "local:quantal/quantal-dummy-1",
2108
change: watcher.Change{
2110
Id: st.docID("a#dummy-application#local:quantal/quantal-dummy-1"),
2112
expectContents: []multiwatcher.EntityInfo{&multiwatcher.ApplicationInfo{
2113
ModelUUID: st.ModelUUID(),
2114
Name: "dummy-application",
2115
CharmURL: "local:quantal/quantal-dummy-1",
2118
func(c *gc.C, st *State) changeTestCase {
2119
svc := AddTestingService(c, st, "dummy-application", AddTestingCharm(c, st, "dummy"))
2120
setServiceConfigAttr(c, svc, "username", "foo")
2121
setServiceConfigAttr(c, svc, "outlook", "foo@bar")
2123
return changeTestCase{
2124
about: "service config is changed if service exists in the store with the same URL",
2125
initialContents: []multiwatcher.EntityInfo{&multiwatcher.ApplicationInfo{
2126
ModelUUID: st.ModelUUID(),
2127
Name: "dummy-application",
2128
CharmURL: "local:quantal/quantal-dummy-1",
2130
change: watcher.Change{
2132
Id: st.docID("a#dummy-application#local:quantal/quantal-dummy-1"),
2134
expectContents: []multiwatcher.EntityInfo{
2135
&multiwatcher.ApplicationInfo{
2136
ModelUUID: st.ModelUUID(),
2137
Name: "dummy-application",
2138
CharmURL: "local:quantal/quantal-dummy-1",
2139
Config: charm.Settings{"username": "foo", "outlook": "foo@bar"},
2142
func(c *gc.C, st *State) changeTestCase {
2143
svc := AddTestingService(c, st, "dummy-application", AddTestingCharm(c, st, "dummy"))
2144
setServiceConfigAttr(c, svc, "username", "foo")
2145
setServiceConfigAttr(c, svc, "outlook", "foo@bar")
2146
setServiceConfigAttr(c, svc, "username", nil)
2148
return changeTestCase{
2149
about: "service config is changed after removing of a setting",
2150
initialContents: []multiwatcher.EntityInfo{&multiwatcher.ApplicationInfo{
2151
ModelUUID: st.ModelUUID(),
2152
Name: "dummy-application",
2153
CharmURL: "local:quantal/quantal-dummy-1",
2154
Config: charm.Settings{"username": "foo", "outlook": "foo@bar"},
2156
change: watcher.Change{
2158
Id: st.docID("a#dummy-application#local:quantal/quantal-dummy-1"),
2160
expectContents: []multiwatcher.EntityInfo{
2161
&multiwatcher.ApplicationInfo{
2162
ModelUUID: st.ModelUUID(),
2163
Name: "dummy-application",
2164
CharmURL: "local:quantal/quantal-dummy-1",
2165
Config: charm.Settings{"outlook": "foo@bar"},
2168
func(c *gc.C, st *State) changeTestCase {
2169
testCharm := AddCustomCharm(
2171
"config.yaml", dottedConfig,
2173
svc := AddTestingService(c, st, "dummy-application", testCharm)
2174
setServiceConfigAttr(c, svc, "key.dotted", "foo")
2176
return changeTestCase{
2177
about: "service config is unescaped when reading from the backing store",
2178
initialContents: []multiwatcher.EntityInfo{&multiwatcher.ApplicationInfo{
2179
ModelUUID: st.ModelUUID(),
2180
Name: "dummy-application",
2181
CharmURL: "local:quantal/quantal-dummy-1",
2182
Config: charm.Settings{"key.dotted": "bar"},
2184
change: watcher.Change{
2186
Id: st.docID("a#dummy-application#local:quantal/quantal-dummy-1"),
2188
expectContents: []multiwatcher.EntityInfo{
2189
&multiwatcher.ApplicationInfo{
2190
ModelUUID: st.ModelUUID(),
2191
Name: "dummy-application",
2192
CharmURL: "local:quantal/quantal-dummy-1",
2193
Config: charm.Settings{"key.dotted": "foo"},
2196
func(c *gc.C, st *State) changeTestCase {
2197
svc := AddTestingService(c, st, "dummy-application", AddTestingCharm(c, st, "dummy"))
2198
setServiceConfigAttr(c, svc, "username", "foo")
2200
return changeTestCase{
2201
about: "service config is unchanged if service exists in the store with a different URL",
2202
initialContents: []multiwatcher.EntityInfo{&multiwatcher.ApplicationInfo{
2203
ModelUUID: st.ModelUUID(),
2204
Name: "dummy-application",
2205
CharmURL: "local:quantal/quantal-dummy-2", // Note different revno.
2206
Config: charm.Settings{"username": "bar"},
2208
change: watcher.Change{
2210
Id: st.docID("a#dummy-application#local:quantal/quantal-dummy-1"),
2212
expectContents: []multiwatcher.EntityInfo{
2213
&multiwatcher.ApplicationInfo{
2214
ModelUUID: st.ModelUUID(),
2215
Name: "dummy-application",
2216
CharmURL: "local:quantal/quantal-dummy-2",
2217
Config: charm.Settings{"username": "bar"},
2220
func(c *gc.C, st *State) changeTestCase {
2221
return changeTestCase{
2222
about: "non-service config change is ignored",
2223
change: watcher.Change{
2225
Id: st.docID("m#0"),
2228
func(c *gc.C, st *State) changeTestCase {
2229
return changeTestCase{
2230
about: "service config change with no charm url is ignored",
2231
change: watcher.Change{
2233
Id: st.docID("a#foo"),
2237
runChangeTests(c, changeTestFuncs)
2240
func testChangeServicesConstraints(c *gc.C, owner names.UserTag, runChangeTests func(*gc.C, []changeTestFunc)) {
2241
changeTestFuncs := []changeTestFunc{
2242
func(c *gc.C, st *State) changeTestCase {
2243
return changeTestCase{
2244
about: "no service in state -> do nothing",
2245
change: watcher.Change{
2247
Id: st.docID("a#wordpress"),
2250
func(c *gc.C, st *State) changeTestCase {
2251
return changeTestCase{
2252
about: "no change if service is not in backing",
2253
initialContents: []multiwatcher.EntityInfo{&multiwatcher.ApplicationInfo{
2254
ModelUUID: st.ModelUUID(),
2256
Constraints: constraints.MustParse("mem=99M"),
2258
change: watcher.Change{
2260
Id: st.docID("a#wordpress"),
2262
expectContents: []multiwatcher.EntityInfo{&multiwatcher.ApplicationInfo{
2263
ModelUUID: st.ModelUUID(),
2265
Constraints: constraints.MustParse("mem=99M"),
2268
func(c *gc.C, st *State) changeTestCase {
2269
svc := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
2270
err := svc.SetConstraints(constraints.MustParse("mem=4G arch=amd64"))
2271
c.Assert(err, jc.ErrorIsNil)
2273
return changeTestCase{
2274
about: "status is changed if the service exists in the store",
2275
initialContents: []multiwatcher.EntityInfo{&multiwatcher.ApplicationInfo{
2276
ModelUUID: st.ModelUUID(),
2278
Constraints: constraints.MustParse("mem=99M cpu-cores=2 cpu-power=4"),
2280
change: watcher.Change{
2282
Id: st.docID("a#wordpress"),
2284
expectContents: []multiwatcher.EntityInfo{
2285
&multiwatcher.ApplicationInfo{
2286
ModelUUID: st.ModelUUID(),
2288
Constraints: constraints.MustParse("mem=4G arch=amd64"),
2292
runChangeTests(c, changeTestFuncs)
2295
func testChangeUnits(c *gc.C, owner names.UserTag, runChangeTests func(*gc.C, []changeTestFunc)) {
2297
changeTestFuncs := []changeTestFunc{
2298
func(c *gc.C, st *State) changeTestCase {
2299
return changeTestCase{
2300
about: "no unit in state, no unit in store -> do nothing",
2301
change: watcher.Change{
2306
func(c *gc.C, st *State) changeTestCase {
2307
return changeTestCase{
2308
about: "unit is removed if it's not in backing",
2309
initialContents: []multiwatcher.EntityInfo{
2310
&multiwatcher.UnitInfo{
2311
ModelUUID: st.ModelUUID(),
2312
Name: "wordpress/1",
2315
change: watcher.Change{
2317
Id: st.docID("wordpress/1"),
2320
func(c *gc.C, st *State) changeTestCase {
2321
wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
2322
u, err := wordpress.AddUnit()
2323
c.Assert(err, jc.ErrorIsNil)
2324
m, err := st.AddMachine("quantal", JobHostUnits)
2325
c.Assert(err, jc.ErrorIsNil)
2326
err = u.AssignToMachine(m)
2327
c.Assert(err, jc.ErrorIsNil)
2328
err = u.OpenPort("tcp", 12345)
2329
c.Assert(err, jc.ErrorIsNil)
2330
err = u.OpenPort("udp", 54321)
2331
c.Assert(err, jc.ErrorIsNil)
2332
err = u.OpenPorts("tcp", 5555, 5558)
2333
c.Assert(err, jc.ErrorIsNil)
2335
sInfo := status.StatusInfo{
2336
Status: status.StatusError,
2340
err = u.SetAgentStatus(sInfo)
2341
c.Assert(err, jc.ErrorIsNil)
2343
return changeTestCase{
2344
about: "unit is added if it's in backing but not in Store",
2345
change: watcher.Change{
2347
Id: st.docID("wordpress/0"),
2349
expectContents: []multiwatcher.EntityInfo{
2350
&multiwatcher.UnitInfo{
2351
ModelUUID: st.ModelUUID(),
2352
Name: "wordpress/0",
2353
Application: "wordpress",
2356
Ports: []multiwatcher.Port{
2364
PortRanges: []multiwatcher.PortRange{
2365
{5555, 5558, "tcp"},
2366
{12345, 12345, "tcp"},
2367
{54321, 54321, "udp"},
2369
AgentStatus: multiwatcher.StatusInfo{
2372
Data: map[string]interface{}{},
2374
WorkloadStatus: multiwatcher.StatusInfo{
2377
Data: map[string]interface{}{},
2381
func(c *gc.C, st *State) changeTestCase {
2382
wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
2383
u, err := wordpress.AddUnit()
2384
c.Assert(err, jc.ErrorIsNil)
2385
m, err := st.AddMachine("quantal", JobHostUnits)
2386
c.Assert(err, jc.ErrorIsNil)
2387
err = u.AssignToMachine(m)
2388
c.Assert(err, jc.ErrorIsNil)
2389
err = u.OpenPort("udp", 17070)
2390
c.Assert(err, jc.ErrorIsNil)
2392
return changeTestCase{
2393
about: "unit is updated if it's in backing and in multiwatcher.Store",
2394
initialContents: []multiwatcher.EntityInfo{&multiwatcher.UnitInfo{
2395
ModelUUID: st.ModelUUID(),
2396
Name: "wordpress/0",
2397
AgentStatus: multiwatcher.StatusInfo{
2400
Data: map[string]interface{}{},
2403
WorkloadStatus: multiwatcher.StatusInfo{
2405
Message: "another failure",
2406
Data: map[string]interface{}{},
2409
Ports: []multiwatcher.Port{{"udp", 17070}},
2410
PortRanges: []multiwatcher.PortRange{{17070, 17070, "udp"}},
2412
change: watcher.Change{
2414
Id: st.docID("wordpress/0"),
2416
expectContents: []multiwatcher.EntityInfo{
2417
&multiwatcher.UnitInfo{
2418
ModelUUID: st.ModelUUID(),
2419
Name: "wordpress/0",
2420
Application: "wordpress",
2423
Ports: []multiwatcher.Port{{"udp", 17070}},
2424
PortRanges: []multiwatcher.PortRange{{17070, 17070, "udp"}},
2425
AgentStatus: multiwatcher.StatusInfo{
2428
Data: map[string]interface{}{},
2430
WorkloadStatus: multiwatcher.StatusInfo{
2432
Message: "another failure",
2433
Data: map[string]interface{}{},
2437
func(c *gc.C, st *State) changeTestCase {
2438
wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
2439
u, err := wordpress.AddUnit()
2440
c.Assert(err, jc.ErrorIsNil)
2441
m, err := st.AddMachine("quantal", JobHostUnits)
2442
c.Assert(err, jc.ErrorIsNil)
2443
err = u.AssignToMachine(m)
2444
c.Assert(err, jc.ErrorIsNil)
2445
err = u.OpenPort("tcp", 4242)
2446
c.Assert(err, jc.ErrorIsNil)
2448
return changeTestCase{
2449
about: "unit info is updated if a port is opened on the machine it is placed in",
2450
initialContents: []multiwatcher.EntityInfo{
2451
&multiwatcher.UnitInfo{
2452
ModelUUID: st.ModelUUID(),
2453
Name: "wordpress/0",
2455
&multiwatcher.MachineInfo{
2456
ModelUUID: st.ModelUUID(),
2460
change: watcher.Change{
2462
Id: st.docID("m#0#"),
2464
expectContents: []multiwatcher.EntityInfo{
2465
&multiwatcher.UnitInfo{
2466
ModelUUID: st.ModelUUID(),
2467
Name: "wordpress/0",
2468
Ports: []multiwatcher.Port{{"tcp", 4242}},
2469
PortRanges: []multiwatcher.PortRange{{4242, 4242, "tcp"}},
2471
&multiwatcher.MachineInfo{
2472
ModelUUID: st.ModelUUID(),
2477
func(c *gc.C, st *State) changeTestCase {
2478
wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
2479
u, err := wordpress.AddUnit()
2480
c.Assert(err, jc.ErrorIsNil)
2481
m, err := st.AddMachine("quantal", JobHostUnits)
2482
c.Assert(err, jc.ErrorIsNil)
2483
err = u.AssignToMachine(m)
2484
c.Assert(err, jc.ErrorIsNil)
2485
err = u.OpenPorts("tcp", 21, 22)
2486
c.Assert(err, jc.ErrorIsNil)
2488
return changeTestCase{
2489
about: "unit is created if a port is opened on the machine it is placed in",
2490
initialContents: []multiwatcher.EntityInfo{
2491
&multiwatcher.MachineInfo{
2492
ModelUUID: st.ModelUUID(),
2496
change: watcher.Change{
2498
Id: st.docID("wordpress/0"),
2500
expectContents: []multiwatcher.EntityInfo{
2501
&multiwatcher.UnitInfo{
2502
ModelUUID: st.ModelUUID(),
2503
Name: "wordpress/0",
2504
Application: "wordpress",
2507
WorkloadStatus: multiwatcher.StatusInfo{
2509
Message: "Waiting for agent initialization to finish",
2510
Data: map[string]interface{}{},
2512
AgentStatus: multiwatcher.StatusInfo{
2513
Current: "allocating",
2514
Data: map[string]interface{}{},
2516
Ports: []multiwatcher.Port{{"tcp", 21}, {"tcp", 22}},
2517
PortRanges: []multiwatcher.PortRange{{21, 22, "tcp"}},
2519
&multiwatcher.MachineInfo{
2520
ModelUUID: st.ModelUUID(),
2525
func(c *gc.C, st *State) changeTestCase {
2526
wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
2527
u, err := wordpress.AddUnit()
2528
c.Assert(err, jc.ErrorIsNil)
2529
m, err := st.AddMachine("quantal", JobHostUnits)
2530
c.Assert(err, jc.ErrorIsNil)
2531
err = u.AssignToMachine(m)
2532
c.Assert(err, jc.ErrorIsNil)
2533
err = u.OpenPort("tcp", 12345)
2534
c.Assert(err, jc.ErrorIsNil)
2535
publicAddress := network.NewScopedAddress("public", network.ScopePublic)
2536
privateAddress := network.NewScopedAddress("private", network.ScopeCloudLocal)
2537
err = m.SetProviderAddresses(publicAddress, privateAddress)
2538
c.Assert(err, jc.ErrorIsNil)
2540
sInfo := status.StatusInfo{
2541
Status: status.StatusError,
2545
err = u.SetAgentStatus(sInfo)
2546
c.Assert(err, jc.ErrorIsNil)
2548
return changeTestCase{
2549
about: "unit addresses are read from the assigned machine for recent Juju releases",
2550
change: watcher.Change{
2552
Id: st.docID("wordpress/0"),
2554
expectContents: []multiwatcher.EntityInfo{
2555
&multiwatcher.UnitInfo{
2556
ModelUUID: st.ModelUUID(),
2557
Name: "wordpress/0",
2558
Application: "wordpress",
2560
PublicAddress: "public",
2561
PrivateAddress: "private",
2563
Ports: []multiwatcher.Port{{"tcp", 12345}},
2564
PortRanges: []multiwatcher.PortRange{{12345, 12345, "tcp"}},
2565
AgentStatus: multiwatcher.StatusInfo{
2568
Data: map[string]interface{}{},
2570
WorkloadStatus: multiwatcher.StatusInfo{
2573
Data: map[string]interface{}{},
2577
func(c *gc.C, st *State) changeTestCase {
2578
return changeTestCase{
2579
about: "no unit in state -> do nothing",
2580
change: watcher.Change{
2582
Id: st.docID("u#wordpress/0"),
2585
func(c *gc.C, st *State) changeTestCase {
2586
return changeTestCase{
2587
about: "no change if status is not in backing",
2588
initialContents: []multiwatcher.EntityInfo{&multiwatcher.UnitInfo{
2589
ModelUUID: st.ModelUUID(),
2590
Name: "wordpress/0",
2591
Application: "wordpress",
2592
AgentStatus: multiwatcher.StatusInfo{
2595
Data: map[string]interface{}{},
2598
WorkloadStatus: multiwatcher.StatusInfo{
2601
Data: map[string]interface{}{},
2605
change: watcher.Change{
2607
Id: st.docID("u#wordpress/0"),
2609
expectContents: []multiwatcher.EntityInfo{
2610
&multiwatcher.UnitInfo{
2611
ModelUUID: st.ModelUUID(),
2612
Name: "wordpress/0",
2613
Application: "wordpress",
2614
AgentStatus: multiwatcher.StatusInfo{
2617
Data: map[string]interface{}{},
2619
WorkloadStatus: multiwatcher.StatusInfo{
2622
Data: map[string]interface{}{},
2626
func(c *gc.C, st *State) changeTestCase {
2627
wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
2628
u, err := wordpress.AddUnit()
2629
c.Assert(err, jc.ErrorIsNil)
2631
sInfo := status.StatusInfo{
2632
Status: status.StatusIdle,
2636
err = u.SetAgentStatus(sInfo)
2637
c.Assert(err, jc.ErrorIsNil)
2639
return changeTestCase{
2640
about: "status is changed if the unit exists in the store",
2641
initialContents: []multiwatcher.EntityInfo{&multiwatcher.UnitInfo{
2642
ModelUUID: st.ModelUUID(),
2643
Name: "wordpress/0",
2644
Application: "wordpress",
2645
AgentStatus: multiwatcher.StatusInfo{
2648
Data: map[string]interface{}{},
2651
WorkloadStatus: multiwatcher.StatusInfo{
2652
Current: "maintenance",
2654
Data: map[string]interface{}{},
2658
change: watcher.Change{
2660
Id: st.docID("u#wordpress/0"),
2662
expectContents: []multiwatcher.EntityInfo{
2663
&multiwatcher.UnitInfo{
2664
ModelUUID: st.ModelUUID(),
2665
Name: "wordpress/0",
2666
Application: "wordpress",
2667
WorkloadStatus: multiwatcher.StatusInfo{
2668
Current: "maintenance",
2670
Data: map[string]interface{}{},
2672
AgentStatus: multiwatcher.StatusInfo{
2675
Data: map[string]interface{}{},
2679
func(c *gc.C, st *State) changeTestCase {
2680
wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
2681
u, err := wordpress.AddUnit()
2682
c.Assert(err, jc.ErrorIsNil)
2684
sInfo := status.StatusInfo{
2685
Status: status.StatusIdle,
2689
err = u.SetAgentStatus(sInfo)
2690
c.Assert(err, jc.ErrorIsNil)
2691
sInfo = status.StatusInfo{
2692
Status: status.StatusMaintenance,
2693
Message: "doing work",
2696
err = u.SetStatus(sInfo)
2697
c.Assert(err, jc.ErrorIsNil)
2699
return changeTestCase{
2700
about: "unit status is changed if the agent comes off error state",
2701
initialContents: []multiwatcher.EntityInfo{&multiwatcher.UnitInfo{
2702
ModelUUID: st.ModelUUID(),
2703
Name: "wordpress/0",
2704
Application: "wordpress",
2705
AgentStatus: multiwatcher.StatusInfo{
2708
Data: map[string]interface{}{},
2711
WorkloadStatus: multiwatcher.StatusInfo{
2714
Data: map[string]interface{}{},
2718
change: watcher.Change{
2720
Id: st.docID("u#wordpress/0"),
2722
expectContents: []multiwatcher.EntityInfo{
2723
&multiwatcher.UnitInfo{
2724
ModelUUID: st.ModelUUID(),
2725
Name: "wordpress/0",
2726
Application: "wordpress",
2727
WorkloadStatus: multiwatcher.StatusInfo{
2728
Current: "maintenance",
2729
Message: "doing work",
2730
Data: map[string]interface{}{},
2732
AgentStatus: multiwatcher.StatusInfo{
2735
Data: map[string]interface{}{},
2739
func(c *gc.C, st *State) changeTestCase {
2740
wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
2741
u, err := wordpress.AddUnit()
2742
c.Assert(err, jc.ErrorIsNil)
2744
sInfo := status.StatusInfo{
2745
Status: status.StatusError,
2746
Message: "hook error",
2747
Data: map[string]interface{}{
2754
err = u.SetAgentStatus(sInfo)
2755
c.Assert(err, jc.ErrorIsNil)
2757
return changeTestCase{
2758
about: "status is changed with additional status data",
2759
initialContents: []multiwatcher.EntityInfo{&multiwatcher.UnitInfo{
2760
ModelUUID: st.ModelUUID(),
2761
Name: "wordpress/0",
2762
Application: "wordpress",
2763
AgentStatus: multiwatcher.StatusInfo{
2766
Data: map[string]interface{}{},
2769
WorkloadStatus: multiwatcher.StatusInfo{
2774
change: watcher.Change{
2776
Id: st.docID("u#wordpress/0"),
2778
expectContents: []multiwatcher.EntityInfo{
2779
&multiwatcher.UnitInfo{
2780
ModelUUID: st.ModelUUID(),
2781
Name: "wordpress/0",
2782
Application: "wordpress",
2783
WorkloadStatus: multiwatcher.StatusInfo{
2785
Message: "hook error",
2786
Data: map[string]interface{}{
2792
AgentStatus: multiwatcher.StatusInfo{
2795
Data: map[string]interface{}{},
2799
func(c *gc.C, st *State) changeTestCase {
2800
wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
2801
u, err := wordpress.AddUnit()
2802
c.Assert(err, jc.ErrorIsNil)
2804
sInfo := status.StatusInfo{
2805
Status: status.StatusActive,
2809
err = u.SetStatus(sInfo)
2810
c.Assert(err, jc.ErrorIsNil)
2812
return changeTestCase{
2813
about: "service status is changed if the unit status changes",
2814
initialContents: []multiwatcher.EntityInfo{
2815
&multiwatcher.UnitInfo{
2816
ModelUUID: st.ModelUUID(),
2817
Name: "wordpress/0",
2818
Application: "wordpress",
2819
AgentStatus: multiwatcher.StatusInfo{
2822
Data: map[string]interface{}{},
2825
WorkloadStatus: multiwatcher.StatusInfo{
2828
Data: map[string]interface{}{},
2832
&multiwatcher.ApplicationInfo{
2833
ModelUUID: st.ModelUUID(),
2835
Status: multiwatcher.StatusInfo{
2838
Data: map[string]interface{}{},
2843
change: watcher.Change{
2845
Id: st.docID("u#wordpress/0#charm"),
2847
expectContents: []multiwatcher.EntityInfo{
2848
&multiwatcher.UnitInfo{
2849
ModelUUID: st.ModelUUID(),
2850
Name: "wordpress/0",
2851
Application: "wordpress",
2852
WorkloadStatus: multiwatcher.StatusInfo{
2855
Data: map[string]interface{}{},
2857
AgentStatus: multiwatcher.StatusInfo{
2860
Data: map[string]interface{}{},
2863
&multiwatcher.ApplicationInfo{
2864
ModelUUID: st.ModelUUID(),
2866
Status: multiwatcher.StatusInfo{
2869
Data: map[string]interface{}{},
2875
runChangeTests(c, changeTestFuncs)
2878
// initFlag helps to control the different test scenarios.
2883
assignUnit initFlag = 1
2884
openPorts initFlag = 2
2885
closePorts initFlag = 4
2888
func testChangeUnitsNonNilPorts(c *gc.C, owner names.UserTag, runChangeTests func(*gc.C, []changeTestFunc)) {
2889
initEnv := func(c *gc.C, st *State, flag initFlag) {
2890
wordpress := AddTestingService(c, st, "wordpress", AddTestingCharm(c, st, "wordpress"))
2891
u, err := wordpress.AddUnit()
2892
c.Assert(err, jc.ErrorIsNil)
2893
m, err := st.AddMachine("quantal", JobHostUnits)
2894
c.Assert(err, jc.ErrorIsNil)
2895
if flag&assignUnit != 0 {
2897
err = u.AssignToMachine(m)
2898
c.Assert(err, jc.ErrorIsNil)
2900
if flag&openPorts != 0 {
2901
// Add a network to the machine and open a port.
2902
publicAddress := network.NewScopedAddress("1.2.3.4", network.ScopePublic)
2903
privateAddress := network.NewScopedAddress("4.3.2.1", network.ScopeCloudLocal)
2904
err = m.SetProviderAddresses(publicAddress, privateAddress)
2905
c.Assert(err, jc.ErrorIsNil)
2906
err = u.OpenPort("tcp", 12345)
2907
if flag&assignUnit != 0 {
2908
c.Assert(err, jc.ErrorIsNil)
2910
c.Assert(err, gc.ErrorMatches, `cannot open ports 12345-12345/tcp \("wordpress/0"\) for unit "wordpress/0".*`)
2911
c.Assert(err, jc.Satisfies, errors.IsNotAssigned)
2914
if flag&closePorts != 0 {
2915
// Close the port again (only if been opened before).
2916
err = u.ClosePort("tcp", 12345)
2917
c.Assert(err, jc.ErrorIsNil)
2920
changeTestFuncs := []changeTestFunc{
2921
func(c *gc.C, st *State) changeTestCase {
2922
initEnv(c, st, assignUnit)
2924
return changeTestCase{
2925
about: "don't open ports on unit",
2926
change: watcher.Change{
2928
Id: st.docID("wordpress/0"),
2930
expectContents: []multiwatcher.EntityInfo{
2931
&multiwatcher.UnitInfo{
2932
ModelUUID: st.ModelUUID(),
2933
Name: "wordpress/0",
2934
Application: "wordpress",
2937
Ports: []multiwatcher.Port{},
2938
PortRanges: []multiwatcher.PortRange{},
2939
WorkloadStatus: multiwatcher.StatusInfo{
2941
Message: "Waiting for agent initialization to finish",
2942
Data: map[string]interface{}{},
2944
AgentStatus: multiwatcher.StatusInfo{
2945
Current: "allocating",
2947
Data: map[string]interface{}{},
2951
func(c *gc.C, st *State) changeTestCase {
2952
initEnv(c, st, assignUnit|openPorts)
2954
return changeTestCase{
2955
about: "open a port on unit",
2956
change: watcher.Change{
2958
Id: st.docID("wordpress/0"),
2960
expectContents: []multiwatcher.EntityInfo{
2961
&multiwatcher.UnitInfo{
2962
ModelUUID: st.ModelUUID(),
2963
Name: "wordpress/0",
2964
Application: "wordpress",
2967
PublicAddress: "1.2.3.4",
2968
PrivateAddress: "4.3.2.1",
2969
Ports: []multiwatcher.Port{{"tcp", 12345}},
2970
PortRanges: []multiwatcher.PortRange{{12345, 12345, "tcp"}},
2971
WorkloadStatus: multiwatcher.StatusInfo{
2973
Message: "Waiting for agent initialization to finish",
2974
Data: map[string]interface{}{},
2976
AgentStatus: multiwatcher.StatusInfo{
2977
Current: "allocating",
2979
Data: map[string]interface{}{},
2983
func(c *gc.C, st *State) changeTestCase {
2984
initEnv(c, st, assignUnit|openPorts|closePorts)
2986
return changeTestCase{
2987
about: "open a port on unit and close it again",
2988
change: watcher.Change{
2990
Id: st.docID("wordpress/0"),
2992
expectContents: []multiwatcher.EntityInfo{
2993
&multiwatcher.UnitInfo{
2994
ModelUUID: st.ModelUUID(),
2995
Name: "wordpress/0",
2996
Application: "wordpress",
2999
PublicAddress: "1.2.3.4",
3000
PrivateAddress: "4.3.2.1",
3001
Ports: []multiwatcher.Port{},
3002
PortRanges: []multiwatcher.PortRange{},
3003
WorkloadStatus: multiwatcher.StatusInfo{
3005
Message: "Waiting for agent initialization to finish",
3006
Data: map[string]interface{}{},
3008
AgentStatus: multiwatcher.StatusInfo{
3009
Current: "allocating",
3011
Data: map[string]interface{}{},
3015
func(c *gc.C, st *State) changeTestCase {
3016
initEnv(c, st, openPorts)
3018
return changeTestCase{
3019
about: "open ports on an unassigned unit",
3020
change: watcher.Change{
3022
Id: st.docID("wordpress/0"),
3024
expectContents: []multiwatcher.EntityInfo{
3025
&multiwatcher.UnitInfo{
3026
ModelUUID: st.ModelUUID(),
3027
Name: "wordpress/0",
3028
Application: "wordpress",
3030
Ports: []multiwatcher.Port{},
3031
PortRanges: []multiwatcher.PortRange{},
3032
WorkloadStatus: multiwatcher.StatusInfo{
3034
Message: "Waiting for agent initialization to finish",
3035
Data: map[string]interface{}{},
3037
AgentStatus: multiwatcher.StatusInfo{
3038
Current: "allocating",
3040
Data: map[string]interface{}{},
3045
runChangeTests(c, changeTestFuncs)
3048
func newTestAllWatcher(st *State, c *gc.C) *testWatcher {
3049
return newTestWatcher(newAllWatcherStateBacking(st), st, c)
3052
func newTestAllModelWatcher(st *State, c *gc.C) *testWatcher {
3053
return newTestWatcher(NewAllModelWatcherStateBacking(st), st, c)
3056
type testWatcher struct {
3062
deltas chan []multiwatcher.Delta
3065
func newTestWatcher(b Backing, st *State, c *gc.C) *testWatcher {
3066
sm := newStoreManager(b)
3067
w := NewMultiwatcher(sm)
3074
deltas: make(chan []multiwatcher.Delta),
3077
defer close(tw.deltas)
3079
deltas, err := tw.w.Next()
3089
func (tw *testWatcher) All(expectedCount int) []multiwatcher.Delta {
3090
var allDeltas []multiwatcher.Delta
3093
// Wait up to LongWait for the expected deltas to arrive, unless
3094
// we don't expect any (then just wait for ShortWait).
3095
maxDuration := testing.LongWait
3096
if expectedCount <= 0 {
3097
maxDuration = testing.ShortWait
3103
case deltas := <-tw.deltas:
3104
if len(deltas) > 0 {
3105
allDeltas = append(allDeltas, deltas...)
3106
if len(allDeltas) >= expectedCount {
3110
case <-time.After(maxDuration):
3118
func (tw *testWatcher) Stop() {
3119
tw.c.Assert(tw.w.Stop(), jc.ErrorIsNil)
3120
tw.c.Assert(tw.sm.Stop(), jc.ErrorIsNil)
3121
tw.c.Assert(tw.b.Release(), jc.ErrorIsNil)
3124
func (tw *testWatcher) AssertNoChange(c *gc.C) {
3127
case d := <-tw.deltas:
3129
c.Error("change detected")
3131
case <-time.After(testing.ShortWait):
3136
func (tw *testWatcher) AssertChanges(c *gc.C, expected int) {
3139
maxWait := time.After(testing.LongWait)
3143
case d := <-tw.deltas:
3145
if count == expected {
3149
// insufficient changes seen
3153
// ensure there are no more than we expect
3154
tw.AssertNoChange(c)
3155
c.Assert(count, gc.Equals, expected)
3158
type entityInfoSlice []multiwatcher.EntityInfo
3160
func (s entityInfoSlice) Len() int { return len(s) }
3161
func (s entityInfoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
3162
func (s entityInfoSlice) Less(i, j int) bool {
3163
id0, id1 := s[i].EntityId(), s[j].EntityId()
3164
if id0.Kind != id1.Kind {
3165
return id0.Kind < id1.Kind
3167
if id0.ModelUUID != id1.ModelUUID {
3168
return id0.ModelUUID < id1.ModelUUID
3170
return id0.Id < id1.Id
3173
func checkDeltasEqual(c *gc.C, d0, d1 []multiwatcher.Delta) {
3174
// Deltas are returned in arbitrary order, so we compare them as maps.
3175
c.Check(deltaMap(d0), jc.DeepEquals, deltaMap(d1))
3178
func deltaMap(deltas []multiwatcher.Delta) map[interface{}]multiwatcher.EntityInfo {
3179
m := make(map[interface{}]multiwatcher.EntityInfo)
3180
for _, d := range deltas {
3181
id := d.Entity.EntityId()
3185
m[id] = substNilSinceTimeForEntityNoCheck(d.Entity)
3191
func makeActionInfo(a Action, st *State) multiwatcher.ActionInfo {
3192
results, message := a.Results()
3193
return multiwatcher.ActionInfo{
3194
ModelUUID: st.ModelUUID(),
3196
Receiver: a.Receiver(),
3198
Parameters: a.Parameters(),
3199
Status: string(a.Status()),
3202
Enqueued: a.Enqueued(),
3203
Started: a.Started(),
3204
Completed: a.Completed(),
3208
func jcDeepEqualsCheck(c *gc.C, got, want interface{}) bool {
3209
ok, err := jc.DeepEqual(got, want)
3211
c.Check(err, jc.ErrorIsNil)
3216
// assertEntitiesEqual is a specialised version of the typical
3217
// jc.DeepEquals check that provides more informative output when
3218
// comparing EntityInfo slices.
3219
func assertEntitiesEqual(c *gc.C, got, want []multiwatcher.EntityInfo) {
3220
if jcDeepEqualsCheck(c, got, want) {
3223
if len(got) != len(want) {
3224
c.Errorf("entity length mismatch; got %d; want %d", len(got), len(want))
3226
c.Errorf("entity contents mismatch; same length %d", len(got))
3228
// Lets construct a decent output.
3229
var errorOutput string
3230
errorOutput = "\ngot: \n"
3231
for _, e := range got {
3232
errorOutput += fmt.Sprintf(" %T %#v\n", e, e)
3234
errorOutput += "expected: \n"
3235
for _, e := range want {
3236
errorOutput += fmt.Sprintf(" %T %#v\n", e, e)
3239
c.Errorf(errorOutput)
3241
var firstDiffError string
3242
if len(got) == len(want) {
3243
for i := 0; i < len(got); i++ {
3246
if !jcDeepEqualsCheck(c, g, w) {
3247
firstDiffError += fmt.Sprintf("first difference at position %d\n", i)
3248
firstDiffError += "got:\n"
3249
firstDiffError += fmt.Sprintf(" %T %#v\n", g, g)
3250
firstDiffError += "expected:\n"
3251
firstDiffError += fmt.Sprintf(" %T %#v\n", w, w)
3255
c.Errorf(firstDiffError)