github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/allwatcher_internal_test.go (about)

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