github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/allwatcher_internal_test.go (about)

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