github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/state/megawatcher_internal_test.go (about)

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