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

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	jc "github.com/juju/testing/checkers"
    13  	jujutxn "github.com/juju/txn"
    14  	"github.com/juju/utils/set"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/juju/charm.v6-unstable"
    17  	"gopkg.in/mgo.v2/bson"
    18  	"gopkg.in/mgo.v2/txn"
    19  
    20  	"github.com/juju/juju/constraints"
    21  	"github.com/juju/juju/state"
    22  	"github.com/juju/juju/state/testing"
    23  	"github.com/juju/juju/status"
    24  	coretesting "github.com/juju/juju/testing"
    25  	"github.com/juju/juju/testing/factory"
    26  )
    27  
    28  type ApplicationSuite struct {
    29  	ConnSuite
    30  	charm *state.Charm
    31  	mysql *state.Application
    32  }
    33  
    34  var _ = gc.Suite(&ApplicationSuite{})
    35  
    36  func (s *ApplicationSuite) SetUpTest(c *gc.C) {
    37  	s.ConnSuite.SetUpTest(c)
    38  	s.policy.GetConstraintsValidator = func() (constraints.Validator, error) {
    39  		validator := constraints.NewValidator()
    40  		validator.RegisterConflicts([]string{constraints.InstanceType}, []string{constraints.Mem})
    41  		validator.RegisterUnsupported([]string{constraints.CpuPower})
    42  		return validator, nil
    43  	}
    44  	s.charm = s.AddTestingCharm(c, "mysql")
    45  	s.mysql = s.AddTestingService(c, "mysql", s.charm)
    46  }
    47  
    48  func (s *ApplicationSuite) TestSetCharm(c *gc.C) {
    49  	ch, force, err := s.mysql.Charm()
    50  	c.Assert(err, jc.ErrorIsNil)
    51  	c.Assert(ch.URL(), gc.DeepEquals, s.charm.URL())
    52  	c.Assert(force, jc.IsFalse)
    53  	url, force := s.mysql.CharmURL()
    54  	c.Assert(url, gc.DeepEquals, s.charm.URL())
    55  	c.Assert(force, jc.IsFalse)
    56  
    57  	// Add a compatible charm and force it.
    58  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2)
    59  
    60  	cfg := state.SetCharmConfig{
    61  		Charm:      sch,
    62  		ForceUnits: true,
    63  	}
    64  	err = s.mysql.SetCharm(cfg)
    65  	c.Assert(err, jc.ErrorIsNil)
    66  	ch, force, err = s.mysql.Charm()
    67  	c.Assert(err, jc.ErrorIsNil)
    68  	c.Assert(ch.URL(), gc.DeepEquals, sch.URL())
    69  	c.Assert(force, jc.IsTrue)
    70  	url, force = s.mysql.CharmURL()
    71  	c.Assert(url, gc.DeepEquals, sch.URL())
    72  	c.Assert(force, jc.IsTrue)
    73  }
    74  
    75  func (s *ApplicationSuite) TestSetCharmCharmSettings(c *gc.C) {
    76  	newCh := s.AddConfigCharm(c, "mysql", stringConfig, 2)
    77  	err := s.mysql.SetCharm(state.SetCharmConfig{
    78  		Charm:          newCh,
    79  		ConfigSettings: charm.Settings{"key": "value"},
    80  	})
    81  	c.Assert(err, jc.ErrorIsNil)
    82  
    83  	settings, err := s.mysql.ConfigSettings()
    84  	c.Assert(err, jc.ErrorIsNil)
    85  	c.Assert(settings, jc.DeepEquals, charm.Settings{"key": "value"})
    86  
    87  	newCh = s.AddConfigCharm(c, "mysql", newStringConfig, 3)
    88  	err = s.mysql.SetCharm(state.SetCharmConfig{
    89  		Charm:          newCh,
    90  		ConfigSettings: charm.Settings{"other": "one"},
    91  	})
    92  	c.Assert(err, jc.ErrorIsNil)
    93  
    94  	settings, err = s.mysql.ConfigSettings()
    95  	c.Assert(err, jc.ErrorIsNil)
    96  	c.Assert(settings, jc.DeepEquals, charm.Settings{
    97  		"key":   "value",
    98  		"other": "one",
    99  	})
   100  }
   101  
   102  func (s *ApplicationSuite) TestSetCharmCharmSettingsInvalid(c *gc.C) {
   103  	newCh := s.AddConfigCharm(c, "mysql", stringConfig, 2)
   104  	err := s.mysql.SetCharm(state.SetCharmConfig{
   105  		Charm:          newCh,
   106  		ConfigSettings: charm.Settings{"key": 123.45},
   107  	})
   108  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "mysql" to charm "local:quantal/quantal-mysql-2": validating config settings: option "key" expected string, got 123.45`)
   109  }
   110  
   111  func (s *ApplicationSuite) TestSetCharmLegacy(c *gc.C) {
   112  	chDifferentSeries := state.AddTestingCharmForSeries(c, s.State, "precise", "mysql")
   113  
   114  	cfg := state.SetCharmConfig{
   115  		Charm:       chDifferentSeries,
   116  		ForceSeries: true,
   117  	}
   118  	err := s.mysql.SetCharm(cfg)
   119  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "mysql" to charm "local:precise/precise-mysql-1": cannot change an application's series`)
   120  }
   121  
   122  func (s *ApplicationSuite) TestClientServiceSetCharmUnsupportedSeries(c *gc.C) {
   123  	ch := state.AddTestingCharmMultiSeries(c, s.State, "multi-series")
   124  	svc := state.AddTestingServiceForSeries(c, s.State, "precise", "application", ch)
   125  
   126  	chDifferentSeries := state.AddTestingCharmMultiSeries(c, s.State, "multi-series2")
   127  	cfg := state.SetCharmConfig{
   128  		Charm: chDifferentSeries,
   129  	}
   130  	err := svc.SetCharm(cfg)
   131  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "cs:multi-series2-8": only these series are supported: trusty, wily`)
   132  }
   133  
   134  func (s *ApplicationSuite) TestClientServiceSetCharmUnsupportedSeriesForce(c *gc.C) {
   135  	ch := state.AddTestingCharmMultiSeries(c, s.State, "multi-series")
   136  	svc := state.AddTestingServiceForSeries(c, s.State, "precise", "application", ch)
   137  
   138  	chDifferentSeries := state.AddTestingCharmMultiSeries(c, s.State, "multi-series2")
   139  	cfg := state.SetCharmConfig{
   140  		Charm:       chDifferentSeries,
   141  		ForceSeries: true,
   142  	}
   143  	err := svc.SetCharm(cfg)
   144  	c.Assert(err, jc.ErrorIsNil)
   145  	svc, err = s.State.Application("application")
   146  	c.Assert(err, jc.ErrorIsNil)
   147  	ch, _, err = svc.Charm()
   148  	c.Assert(err, jc.ErrorIsNil)
   149  	c.Assert(ch.URL().String(), gc.Equals, "cs:multi-series2-8")
   150  }
   151  
   152  func (s *ApplicationSuite) TestClientServiceSetCharmWrongOS(c *gc.C) {
   153  	ch := state.AddTestingCharmMultiSeries(c, s.State, "multi-series")
   154  	svc := state.AddTestingServiceForSeries(c, s.State, "precise", "application", ch)
   155  
   156  	chDifferentSeries := state.AddTestingCharmMultiSeries(c, s.State, "multi-series-windows")
   157  	cfg := state.SetCharmConfig{
   158  		Charm:       chDifferentSeries,
   159  		ForceSeries: true,
   160  	}
   161  	err := svc.SetCharm(cfg)
   162  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "cs:multi-series-windows-1": OS "Ubuntu" not supported by charm`)
   163  }
   164  
   165  func (s *ApplicationSuite) TestSetCharmPreconditions(c *gc.C) {
   166  	logging := s.AddTestingCharm(c, "logging")
   167  	cfg := state.SetCharmConfig{Charm: logging}
   168  	err := s.mysql.SetCharm(cfg)
   169  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "mysql" to charm "local:quantal/quantal-logging-1": cannot change an application's subordinacy`)
   170  
   171  	othermysql := s.AddSeriesCharm(c, "mysql", "otherseries")
   172  	cfg2 := state.SetCharmConfig{Charm: othermysql}
   173  	err = s.mysql.SetCharm(cfg2)
   174  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "mysql" to charm "local:otherseries/otherseries-mysql-1": cannot change an application's series`)
   175  }
   176  
   177  func (s *ApplicationSuite) TestSetCharmUpdatesBindings(c *gc.C) {
   178  	_, err := s.State.AddSpace("db", "", nil, false)
   179  	c.Assert(err, jc.ErrorIsNil)
   180  	_, err = s.State.AddSpace("client", "", nil, true)
   181  	c.Assert(err, jc.ErrorIsNil)
   182  	oldCharm := s.AddMetaCharm(c, "mysql", metaBase, 44)
   183  
   184  	service, err := s.State.AddApplication(state.AddApplicationArgs{
   185  		Name:  "yoursql",
   186  		Charm: oldCharm,
   187  		EndpointBindings: map[string]string{
   188  			"server": "db",
   189  			"client": "client",
   190  		}})
   191  	c.Assert(err, jc.ErrorIsNil)
   192  
   193  	newCharm := s.AddMetaCharm(c, "mysql", metaExtraEndpoints, 43)
   194  	cfg := state.SetCharmConfig{Charm: newCharm}
   195  	err = service.SetCharm(cfg)
   196  	c.Assert(err, jc.ErrorIsNil)
   197  	updatedBindings, err := service.EndpointBindings()
   198  	c.Assert(err, jc.ErrorIsNil)
   199  	c.Assert(updatedBindings, jc.DeepEquals, map[string]string{
   200  		// Existing bindings are preserved.
   201  		"server":  "db",
   202  		"client":  "client",
   203  		"cluster": "", // inherited from defaults in AddService.
   204  		// New endpoints use empty defaults.
   205  		"foo":  "",
   206  		"baz":  "",
   207  		"just": "",
   208  	})
   209  }
   210  
   211  func (s *ApplicationSuite) TestSetCharmWithWeirdlyNamedEndpoints(c *gc.C) {
   212  	// This test ensures if special characters appear in endpoint names of the
   213  	// charm metadata, they are properly escaped before saving to mongo, and
   214  	// unescaped when read back.
   215  	_, err := s.State.AddSpace("client", "", nil, true)
   216  	c.Assert(err, jc.ErrorIsNil)
   217  	_, err = s.State.AddSpace("db", "", nil, false)
   218  	c.Assert(err, jc.ErrorIsNil)
   219  
   220  	initialBindings := map[string]string{
   221  		"$pull":     "db",
   222  		"$set.foo":  "",
   223  		"cli ent .": "client",
   224  		".":         "db",
   225  	}
   226  	weirdOldCharm := s.AddMetaCharm(c, "mysql", `
   227  name: mysql
   228  summary: "Fake MySQL Database engine"
   229  description: "Complete with nonsense relations"
   230  provides:
   231    $pull: mysql
   232    $set.foo: bar
   233  requires:
   234    foo: something
   235    "cli ent .": mysql
   236  peers:
   237    ".": bad
   238    "$": mysql
   239  `, 42)
   240  	weirdNewCharm := s.AddMetaCharm(c, "mysql", `
   241  name: mysql
   242  summary: "Fake MySQL Database engine"
   243  description: "Complete with nonsense relations"
   244  provides:
   245    ser$ver2: mysql
   246    $pull: mysql
   247    $set.foo: bar
   248  requires:
   249    "cli ent 2": mysql
   250  peers:
   251    "$": mysql
   252    ".": bad
   253  `, 43)
   254  
   255  	weirdService := s.AddTestingServiceWithBindings(c, "weird", weirdOldCharm, initialBindings)
   256  	readBindings, err := weirdService.EndpointBindings()
   257  	c.Assert(err, jc.ErrorIsNil)
   258  	expectedBindings := map[string]string{
   259  		"cli ent .": "client",
   260  		"foo":       "",
   261  		"$":         "",
   262  		".":         "db",
   263  		"$set.foo":  "",
   264  		"$pull":     "db",
   265  	}
   266  	c.Check(readBindings, jc.DeepEquals, expectedBindings)
   267  
   268  	cfg := state.SetCharmConfig{Charm: weirdNewCharm}
   269  	err = weirdService.SetCharm(cfg)
   270  	c.Assert(err, jc.ErrorIsNil)
   271  	readBindings, err = weirdService.EndpointBindings()
   272  	c.Assert(err, jc.ErrorIsNil)
   273  
   274  	expectedBindings = map[string]string{
   275  		"ser$ver2":  "",
   276  		"cli ent 2": "",
   277  		"$":         "",
   278  		".":         "db",
   279  		"$set.foo":  "",
   280  		"$pull":     "db",
   281  	}
   282  	c.Check(readBindings, jc.DeepEquals, expectedBindings)
   283  }
   284  
   285  var metaBase = `
   286  name: mysql
   287  summary: "Fake MySQL Database engine"
   288  description: "Complete with nonsense relations"
   289  provides:
   290    server: mysql
   291  requires:
   292    client: mysql
   293  peers:
   294    cluster: mysql
   295  `
   296  var metaDifferentProvider = `
   297  name: mysql
   298  description: none
   299  summary: none
   300  provides:
   301    kludge: mysql
   302  requires:
   303    client: mysql
   304  peers:
   305    cluster: mysql
   306  `
   307  var metaDifferentRequirer = `
   308  name: mysql
   309  description: none
   310  summary: none
   311  provides:
   312    server: mysql
   313  requires:
   314    kludge: mysql
   315  peers:
   316    cluster: mysql
   317  `
   318  var metaDifferentPeer = `
   319  name: mysql
   320  description: none
   321  summary: none
   322  provides:
   323    server: mysql
   324  requires:
   325    client: mysql
   326  peers:
   327    kludge: mysql
   328  `
   329  var metaExtraEndpoints = `
   330  name: mysql
   331  description: none
   332  summary: none
   333  provides:
   334    server: mysql
   335    foo: bar
   336  requires:
   337    client: mysql
   338    baz: woot
   339  peers:
   340    cluster: mysql
   341    just: me
   342  `
   343  
   344  var setCharmEndpointsTests = []struct {
   345  	summary string
   346  	meta    string
   347  	err     string
   348  }{{
   349  	summary: "different provider (but no relation yet)",
   350  	meta:    metaDifferentProvider,
   351  }, {
   352  	summary: "different requirer (but no relation yet)",
   353  	meta:    metaDifferentRequirer,
   354  }, {
   355  	summary: "different peer",
   356  	meta:    metaDifferentPeer,
   357  	err:     `cannot upgrade application "fakemysql" to charm "local:quantal/quantal-mysql-5": would break relation "fakemysql:cluster"`,
   358  }, {
   359  	summary: "same relations ok",
   360  	meta:    metaBase,
   361  }, {
   362  	summary: "extra endpoints ok",
   363  	meta:    metaExtraEndpoints,
   364  }}
   365  
   366  func (s *ApplicationSuite) TestSetCharmChecksEndpointsWithoutRelations(c *gc.C) {
   367  	revno := 2
   368  	ms := s.AddMetaCharm(c, "mysql", metaBase, revno)
   369  	svc := s.AddTestingService(c, "fakemysql", ms)
   370  	cfg := state.SetCharmConfig{Charm: ms}
   371  	err := svc.SetCharm(cfg)
   372  	c.Assert(err, jc.ErrorIsNil)
   373  
   374  	for i, t := range setCharmEndpointsTests {
   375  		c.Logf("test %d: %s", i, t.summary)
   376  
   377  		newCh := s.AddMetaCharm(c, "mysql", t.meta, revno+i+1)
   378  		cfg := state.SetCharmConfig{Charm: newCh}
   379  		err = svc.SetCharm(cfg)
   380  		if t.err != "" {
   381  			c.Assert(err, gc.ErrorMatches, t.err)
   382  		} else {
   383  			c.Assert(err, jc.ErrorIsNil)
   384  		}
   385  	}
   386  
   387  	err = svc.Destroy()
   388  	c.Assert(err, jc.ErrorIsNil)
   389  }
   390  
   391  func (s *ApplicationSuite) TestSetCharmChecksEndpointsWithRelations(c *gc.C) {
   392  	revno := 2
   393  	providerCharm := s.AddMetaCharm(c, "mysql", metaDifferentProvider, revno)
   394  	providerSvc := s.AddTestingService(c, "myprovider", providerCharm)
   395  
   396  	cfg := state.SetCharmConfig{Charm: providerCharm}
   397  	err := providerSvc.SetCharm(cfg)
   398  	c.Assert(err, jc.ErrorIsNil)
   399  
   400  	revno++
   401  	requirerCharm := s.AddMetaCharm(c, "mysql", metaDifferentRequirer, revno)
   402  	requirerSvc := s.AddTestingService(c, "myrequirer", requirerCharm)
   403  	cfg = state.SetCharmConfig{Charm: requirerCharm}
   404  	err = requirerSvc.SetCharm(cfg)
   405  	c.Assert(err, jc.ErrorIsNil)
   406  
   407  	eps, err := s.State.InferEndpoints("myprovider:kludge", "myrequirer:kludge")
   408  	c.Assert(err, jc.ErrorIsNil)
   409  	_, err = s.State.AddRelation(eps...)
   410  	c.Assert(err, jc.ErrorIsNil)
   411  
   412  	revno++
   413  	baseCharm := s.AddMetaCharm(c, "mysql", metaBase, revno)
   414  	cfg = state.SetCharmConfig{Charm: baseCharm}
   415  	err = providerSvc.SetCharm(cfg)
   416  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "myprovider" to charm "local:quantal/quantal-mysql-4": would break relation "myrequirer:kludge myprovider:kludge"`)
   417  	err = requirerSvc.SetCharm(cfg)
   418  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "myrequirer" to charm "local:quantal/quantal-mysql-4": would break relation "myrequirer:kludge myprovider:kludge"`)
   419  }
   420  
   421  var stringConfig = `
   422  options:
   423    key: {default: My Key, description: Desc, type: string}
   424  `
   425  var emptyConfig = `
   426  options: {}
   427  `
   428  var floatConfig = `
   429  options:
   430    key: {default: 0.42, description: Float key, type: float}
   431  `
   432  var newStringConfig = `
   433  options:
   434    key: {default: My Key, description: Desc, type: string}
   435    other: {default: None, description: My Other, type: string}
   436  `
   437  
   438  var setCharmConfigTests = []struct {
   439  	summary     string
   440  	startconfig string
   441  	startvalues charm.Settings
   442  	endconfig   string
   443  	endvalues   charm.Settings
   444  	err         string
   445  }{{
   446  	summary:     "add float key to empty config",
   447  	startconfig: emptyConfig,
   448  	endconfig:   floatConfig,
   449  }, {
   450  	summary:     "add string key to empty config",
   451  	startconfig: emptyConfig,
   452  	endconfig:   stringConfig,
   453  }, {
   454  	summary:     "add string key and preserve existing values",
   455  	startconfig: stringConfig,
   456  	startvalues: charm.Settings{"key": "foo"},
   457  	endconfig:   newStringConfig,
   458  	endvalues:   charm.Settings{"key": "foo"},
   459  }, {
   460  	summary:     "remove string key",
   461  	startconfig: stringConfig,
   462  	startvalues: charm.Settings{"key": "value"},
   463  	endconfig:   emptyConfig,
   464  }, {
   465  	summary:     "remove float key",
   466  	startconfig: floatConfig,
   467  	startvalues: charm.Settings{"key": 123.45},
   468  	endconfig:   emptyConfig,
   469  }, {
   470  	summary:     "change key type without values",
   471  	startconfig: stringConfig,
   472  	endconfig:   floatConfig,
   473  }, {
   474  	summary:     "change key type with values",
   475  	startconfig: stringConfig,
   476  	startvalues: charm.Settings{"key": "value"},
   477  	endconfig:   floatConfig,
   478  }}
   479  
   480  func (s *ApplicationSuite) TestSetCharmConfig(c *gc.C) {
   481  	charms := map[string]*state.Charm{
   482  		stringConfig:    s.AddConfigCharm(c, "wordpress", stringConfig, 1),
   483  		emptyConfig:     s.AddConfigCharm(c, "wordpress", emptyConfig, 2),
   484  		floatConfig:     s.AddConfigCharm(c, "wordpress", floatConfig, 3),
   485  		newStringConfig: s.AddConfigCharm(c, "wordpress", newStringConfig, 4),
   486  	}
   487  
   488  	for i, t := range setCharmConfigTests {
   489  		c.Logf("test %d: %s", i, t.summary)
   490  
   491  		origCh := charms[t.startconfig]
   492  		svc := s.AddTestingService(c, "wordpress", origCh)
   493  		err := svc.UpdateConfigSettings(t.startvalues)
   494  		c.Assert(err, jc.ErrorIsNil)
   495  
   496  		newCh := charms[t.endconfig]
   497  		cfg := state.SetCharmConfig{Charm: newCh}
   498  		err = svc.SetCharm(cfg)
   499  		var expectVals charm.Settings
   500  		var expectCh *state.Charm
   501  		if t.err != "" {
   502  			c.Assert(err, gc.ErrorMatches, t.err)
   503  			expectCh = origCh
   504  			expectVals = t.startvalues
   505  		} else {
   506  			c.Assert(err, jc.ErrorIsNil)
   507  			expectCh = newCh
   508  			expectVals = t.endvalues
   509  		}
   510  
   511  		sch, _, err := svc.Charm()
   512  		c.Assert(err, jc.ErrorIsNil)
   513  		c.Assert(sch.URL(), gc.DeepEquals, expectCh.URL())
   514  		settings, err := svc.ConfigSettings()
   515  		c.Assert(err, jc.ErrorIsNil)
   516  		if len(expectVals) == 0 {
   517  			c.Assert(settings, gc.HasLen, 0)
   518  		} else {
   519  			c.Assert(settings, gc.DeepEquals, expectVals)
   520  		}
   521  
   522  		err = svc.Destroy()
   523  		c.Assert(err, jc.ErrorIsNil)
   524  	}
   525  }
   526  
   527  func (s *ApplicationSuite) TestSetCharmWithDyingService(c *gc.C) {
   528  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2)
   529  
   530  	_, err := s.mysql.AddUnit()
   531  	c.Assert(err, jc.ErrorIsNil)
   532  	err = s.mysql.Destroy()
   533  	c.Assert(err, jc.ErrorIsNil)
   534  	assertLife(c, s.mysql, state.Dying)
   535  	cfg := state.SetCharmConfig{
   536  		Charm:      sch,
   537  		ForceUnits: true,
   538  	}
   539  	err = s.mysql.SetCharm(cfg)
   540  	c.Assert(err, jc.ErrorIsNil)
   541  }
   542  
   543  func (s *ApplicationSuite) TestSequenceUnitIdsAfterDestroy(c *gc.C) {
   544  	unit, err := s.mysql.AddUnit()
   545  	c.Assert(err, jc.ErrorIsNil)
   546  	c.Assert(unit.Name(), gc.Equals, "mysql/0")
   547  	err = unit.Destroy()
   548  	c.Assert(err, jc.ErrorIsNil)
   549  	err = s.mysql.Destroy()
   550  	c.Assert(err, jc.ErrorIsNil)
   551  	s.mysql = s.AddTestingService(c, "mysql", s.charm)
   552  	unit, err = s.mysql.AddUnit()
   553  	c.Assert(err, jc.ErrorIsNil)
   554  	c.Assert(unit.Name(), gc.Equals, "mysql/1")
   555  }
   556  
   557  func (s *ApplicationSuite) TestSequenceUnitIds(c *gc.C) {
   558  	unit, err := s.mysql.AddUnit()
   559  	c.Assert(err, jc.ErrorIsNil)
   560  	c.Assert(unit.Name(), gc.Equals, "mysql/0")
   561  	unit, err = s.mysql.AddUnit()
   562  	c.Assert(err, jc.ErrorIsNil)
   563  	c.Assert(unit.Name(), gc.Equals, "mysql/1")
   564  }
   565  
   566  func (s *ApplicationSuite) TestSetCharmWhenDead(c *gc.C) {
   567  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2)
   568  
   569  	defer state.SetBeforeHooks(c, s.State, func() {
   570  		_, err := s.mysql.AddUnit()
   571  		err = s.mysql.Destroy()
   572  		c.Assert(err, jc.ErrorIsNil)
   573  		assertLife(c, s.mysql, state.Dying)
   574  
   575  		// Change the service life to Dead manually, as there's no
   576  		// direct way of doing that otherwise.
   577  		ops := []txn.Op{{
   578  			C:      state.ApplicationsC,
   579  			Id:     state.DocID(s.State, s.mysql.Name()),
   580  			Update: bson.D{{"$set", bson.D{{"life", state.Dead}}}},
   581  		}}
   582  
   583  		err = state.RunTransaction(s.State, ops)
   584  		c.Assert(err, jc.ErrorIsNil)
   585  		assertLife(c, s.mysql, state.Dead)
   586  	}).Check()
   587  
   588  	cfg := state.SetCharmConfig{
   589  		Charm:      sch,
   590  		ForceUnits: true,
   591  	}
   592  	err := s.mysql.SetCharm(cfg)
   593  	c.Assert(errors.Cause(err), gc.Equals, state.ErrDead)
   594  }
   595  
   596  func (s *ApplicationSuite) TestSetCharmWithRemovedService(c *gc.C) {
   597  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2)
   598  
   599  	err := s.mysql.Destroy()
   600  	c.Assert(err, jc.ErrorIsNil)
   601  	assertRemoved(c, s.mysql)
   602  
   603  	cfg := state.SetCharmConfig{
   604  		Charm:      sch,
   605  		ForceUnits: true,
   606  	}
   607  
   608  	err = s.mysql.SetCharm(cfg)
   609  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   610  }
   611  
   612  func (s *ApplicationSuite) TestSetCharmWhenRemoved(c *gc.C) {
   613  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2)
   614  
   615  	defer state.SetBeforeHooks(c, s.State, func() {
   616  		err := s.mysql.Destroy()
   617  		c.Assert(err, jc.ErrorIsNil)
   618  		assertRemoved(c, s.mysql)
   619  	}).Check()
   620  
   621  	cfg := state.SetCharmConfig{
   622  		Charm:      sch,
   623  		ForceUnits: true,
   624  	}
   625  	err := s.mysql.SetCharm(cfg)
   626  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   627  }
   628  
   629  func (s *ApplicationSuite) TestSetCharmWhenDyingIsOK(c *gc.C) {
   630  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2)
   631  
   632  	defer state.SetBeforeHooks(c, s.State, func() {
   633  		_, err := s.mysql.AddUnit()
   634  		c.Assert(err, jc.ErrorIsNil)
   635  		err = s.mysql.Destroy()
   636  		c.Assert(err, jc.ErrorIsNil)
   637  		assertLife(c, s.mysql, state.Dying)
   638  	}).Check()
   639  
   640  	cfg := state.SetCharmConfig{
   641  		Charm:      sch,
   642  		ForceUnits: true,
   643  	}
   644  	err := s.mysql.SetCharm(cfg)
   645  	c.Assert(err, jc.ErrorIsNil)
   646  	assertLife(c, s.mysql, state.Dying)
   647  }
   648  
   649  func (s *ApplicationSuite) TestSetCharmRetriesWithSameCharmURL(c *gc.C) {
   650  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2)
   651  
   652  	defer state.SetTestHooks(c, s.State,
   653  		jujutxn.TestHook{
   654  			Before: func() {
   655  				currentCh, force, err := s.mysql.Charm()
   656  				c.Assert(err, jc.ErrorIsNil)
   657  				c.Assert(force, jc.IsFalse)
   658  				c.Assert(currentCh.URL(), jc.DeepEquals, s.charm.URL())
   659  
   660  				cfg := state.SetCharmConfig{Charm: sch}
   661  				err = s.mysql.SetCharm(cfg)
   662  				c.Assert(err, jc.ErrorIsNil)
   663  			},
   664  			After: func() {
   665  				// Verify the before hook worked.
   666  				currentCh, force, err := s.mysql.Charm()
   667  				c.Assert(err, jc.ErrorIsNil)
   668  				c.Assert(force, jc.IsFalse)
   669  				c.Assert(currentCh.URL(), jc.DeepEquals, sch.URL())
   670  			},
   671  		},
   672  		jujutxn.TestHook{
   673  			Before: nil, // Ensure there will be a retry.
   674  			After: func() {
   675  				// Verify it worked after the retry.
   676  				err := s.mysql.Refresh()
   677  				c.Assert(err, jc.ErrorIsNil)
   678  				currentCh, force, err := s.mysql.Charm()
   679  				c.Assert(err, jc.ErrorIsNil)
   680  				c.Assert(force, jc.IsTrue)
   681  				c.Assert(currentCh.URL(), jc.DeepEquals, sch.URL())
   682  			},
   683  		},
   684  	).Check()
   685  
   686  	cfg := state.SetCharmConfig{
   687  		Charm:      sch,
   688  		ForceUnits: true,
   689  	}
   690  	err := s.mysql.SetCharm(cfg)
   691  	c.Assert(err, jc.ErrorIsNil)
   692  }
   693  
   694  func (s *ApplicationSuite) TestSetCharmRetriesWhenOldSettingsChanged(c *gc.C) {
   695  	revno := 2 // revno 1 is used by SetUpSuite
   696  	oldCh := s.AddConfigCharm(c, "mysql", stringConfig, revno)
   697  	newCh := s.AddConfigCharm(c, "mysql", stringConfig, revno+1)
   698  	cfg := state.SetCharmConfig{Charm: oldCh}
   699  	err := s.mysql.SetCharm(cfg)
   700  	c.Assert(err, jc.ErrorIsNil)
   701  
   702  	defer state.SetBeforeHooks(c, s.State,
   703  		func() {
   704  			err := s.mysql.UpdateConfigSettings(charm.Settings{"key": "value"})
   705  			c.Assert(err, jc.ErrorIsNil)
   706  		},
   707  		nil, // Ensure there will be a retry.
   708  	).Check()
   709  
   710  	cfg = state.SetCharmConfig{
   711  		Charm:      newCh,
   712  		ForceUnits: true,
   713  	}
   714  	err = s.mysql.SetCharm(cfg)
   715  	c.Assert(err, jc.ErrorIsNil)
   716  }
   717  
   718  func (s *ApplicationSuite) TestSetCharmRetriesWhenBothOldAndNewSettingsChanged(c *gc.C) {
   719  	revno := 2 // revno 1 is used by SetUpSuite
   720  	oldCh := s.AddConfigCharm(c, "mysql", stringConfig, revno)
   721  	newCh := s.AddConfigCharm(c, "mysql", stringConfig, revno+1)
   722  
   723  	defer state.SetTestHooks(c, s.State,
   724  		jujutxn.TestHook{
   725  			Before: func() {
   726  				// Add two units, which will keep the refcount of oldCh
   727  				// and newCh settings greater than 0, while the service's
   728  				// charm URLs change between oldCh and newCh. Ensure
   729  				// refcounts change as expected.
   730  				unit1, err := s.mysql.AddUnit()
   731  				c.Assert(err, jc.ErrorIsNil)
   732  				unit2, err := s.mysql.AddUnit()
   733  				c.Assert(err, jc.ErrorIsNil)
   734  				cfg := state.SetCharmConfig{Charm: newCh}
   735  				err = s.mysql.SetCharm(cfg)
   736  				c.Assert(err, jc.ErrorIsNil)
   737  				assertSettingsRef(c, s.State, "mysql", newCh, 1)
   738  				assertNoSettingsRef(c, s.State, "mysql", oldCh)
   739  				err = unit1.SetCharmURL(newCh.URL())
   740  				c.Assert(err, jc.ErrorIsNil)
   741  				assertSettingsRef(c, s.State, "mysql", newCh, 2)
   742  				assertNoSettingsRef(c, s.State, "mysql", oldCh)
   743  				// Update newCh settings, switch to oldCh and update its
   744  				// settings as well.
   745  				err = s.mysql.UpdateConfigSettings(charm.Settings{"key": "value1"})
   746  				c.Assert(err, jc.ErrorIsNil)
   747  				cfg = state.SetCharmConfig{Charm: oldCh}
   748  
   749  				err = s.mysql.SetCharm(cfg)
   750  				c.Assert(err, jc.ErrorIsNil)
   751  				assertSettingsRef(c, s.State, "mysql", newCh, 1)
   752  				assertSettingsRef(c, s.State, "mysql", oldCh, 1)
   753  				err = unit2.SetCharmURL(oldCh.URL())
   754  				c.Assert(err, jc.ErrorIsNil)
   755  				assertSettingsRef(c, s.State, "mysql", newCh, 1)
   756  				assertSettingsRef(c, s.State, "mysql", oldCh, 2)
   757  				err = s.mysql.UpdateConfigSettings(charm.Settings{"key": "value2"})
   758  				c.Assert(err, jc.ErrorIsNil)
   759  			},
   760  			After: func() {
   761  				// Verify the charm and refcounts after the second attempt.
   762  				err := s.mysql.Refresh()
   763  				c.Assert(err, jc.ErrorIsNil)
   764  				currentCh, force, err := s.mysql.Charm()
   765  				c.Assert(err, jc.ErrorIsNil)
   766  				c.Assert(force, jc.IsFalse)
   767  				c.Assert(currentCh.URL(), jc.DeepEquals, oldCh.URL())
   768  				assertSettingsRef(c, s.State, "mysql", newCh, 1)
   769  				assertSettingsRef(c, s.State, "mysql", oldCh, 2)
   770  			},
   771  		},
   772  		jujutxn.TestHook{
   773  			Before: func() {
   774  				// SetCharm has refreshed its cached settings for oldCh
   775  				// and newCh. Change them again to trigger another
   776  				// attempt.
   777  				cfg := state.SetCharmConfig{Charm: newCh}
   778  
   779  				err := s.mysql.SetCharm(cfg)
   780  				c.Assert(err, jc.ErrorIsNil)
   781  				assertSettingsRef(c, s.State, "mysql", newCh, 2)
   782  				assertSettingsRef(c, s.State, "mysql", oldCh, 1)
   783  				err = s.mysql.UpdateConfigSettings(charm.Settings{"key": "value3"})
   784  				c.Assert(err, jc.ErrorIsNil)
   785  
   786  				cfg = state.SetCharmConfig{Charm: oldCh}
   787  				err = s.mysql.SetCharm(cfg)
   788  				c.Assert(err, jc.ErrorIsNil)
   789  				assertSettingsRef(c, s.State, "mysql", newCh, 1)
   790  				assertSettingsRef(c, s.State, "mysql", oldCh, 2)
   791  				err = s.mysql.UpdateConfigSettings(charm.Settings{"key": "value4"})
   792  				c.Assert(err, jc.ErrorIsNil)
   793  			},
   794  			After: func() {
   795  				// Verify the charm and refcounts after the third attempt.
   796  				err := s.mysql.Refresh()
   797  				c.Assert(err, jc.ErrorIsNil)
   798  				currentCh, force, err := s.mysql.Charm()
   799  				c.Assert(err, jc.ErrorIsNil)
   800  				c.Assert(force, jc.IsFalse)
   801  				c.Assert(currentCh.URL(), jc.DeepEquals, oldCh.URL())
   802  				assertSettingsRef(c, s.State, "mysql", newCh, 1)
   803  				assertSettingsRef(c, s.State, "mysql", oldCh, 2)
   804  			},
   805  		},
   806  		jujutxn.TestHook{
   807  			Before: nil, // Ensure there will be a (final) retry.
   808  			After: func() {
   809  				// Verify the charm and refcounts after the final third attempt.
   810  				err := s.mysql.Refresh()
   811  				c.Assert(err, jc.ErrorIsNil)
   812  				currentCh, force, err := s.mysql.Charm()
   813  				c.Assert(err, jc.ErrorIsNil)
   814  				c.Assert(force, jc.IsTrue)
   815  				c.Assert(currentCh.URL(), jc.DeepEquals, newCh.URL())
   816  				assertSettingsRef(c, s.State, "mysql", newCh, 2)
   817  				assertSettingsRef(c, s.State, "mysql", oldCh, 1)
   818  			},
   819  		},
   820  	).Check()
   821  
   822  	cfg := state.SetCharmConfig{
   823  		Charm:      newCh,
   824  		ForceUnits: true,
   825  	}
   826  	err := s.mysql.SetCharm(cfg)
   827  	c.Assert(err, jc.ErrorIsNil)
   828  }
   829  
   830  func (s *ApplicationSuite) TestSetCharmRetriesWhenOldBindingsChanged(c *gc.C) {
   831  	revno := 2 // revno 1 is used by SetUpSuite
   832  	mysqlKey := state.ApplicationGlobalKey(s.mysql.Name())
   833  	oldCharm := s.AddMetaCharm(c, "mysql", metaDifferentRequirer, revno)
   834  	newCharm := s.AddMetaCharm(c, "mysql", metaExtraEndpoints, revno+1)
   835  
   836  	cfg := state.SetCharmConfig{Charm: oldCharm}
   837  	err := s.mysql.SetCharm(cfg)
   838  	c.Assert(err, jc.ErrorIsNil)
   839  
   840  	oldBindings, err := s.mysql.EndpointBindings()
   841  	c.Assert(err, jc.ErrorIsNil)
   842  	c.Assert(oldBindings, jc.DeepEquals, map[string]string{
   843  		"server":  "",
   844  		"kludge":  "",
   845  		"cluster": "",
   846  	})
   847  	_, err = s.State.AddSpace("db", "", nil, true)
   848  	c.Assert(err, jc.ErrorIsNil)
   849  	_, err = s.State.AddSpace("admin", "", nil, false)
   850  	c.Assert(err, jc.ErrorIsNil)
   851  
   852  	updateBindings := func(updatesMap bson.M) {
   853  		ops := []txn.Op{{
   854  			C:      state.EndpointBindingsC,
   855  			Id:     mysqlKey,
   856  			Update: bson.D{{"$set", updatesMap}},
   857  		}}
   858  		err := state.RunTransaction(s.State, ops)
   859  		c.Assert(err, jc.ErrorIsNil)
   860  	}
   861  
   862  	defer state.SetTestHooks(c, s.State,
   863  		jujutxn.TestHook{
   864  			Before: func() {
   865  				// First change.
   866  				updateBindings(bson.M{
   867  					"bindings.server": "db",
   868  					"bindings.kludge": "admin", // will be removed before newCharm is set.
   869  				})
   870  			},
   871  			After: func() {
   872  				// Second change.
   873  				updateBindings(bson.M{
   874  					"bindings.cluster": "admin",
   875  				})
   876  			},
   877  		},
   878  		jujutxn.TestHook{
   879  			Before: nil, // Ensure there will be a (final) retry.
   880  			After: func() {
   881  				// Verify final bindings.
   882  				newBindings, err := s.mysql.EndpointBindings()
   883  				c.Assert(err, jc.ErrorIsNil)
   884  				c.Assert(newBindings, jc.DeepEquals, map[string]string{
   885  					"server":  "db", // from the first change.
   886  					"foo":     "",
   887  					"client":  "",
   888  					"baz":     "",
   889  					"cluster": "admin", // from the second change.
   890  					"just":    "",
   891  				})
   892  			},
   893  		},
   894  	).Check()
   895  
   896  	cfg = state.SetCharmConfig{
   897  		Charm:      newCharm,
   898  		ForceUnits: true,
   899  	}
   900  	err = s.mysql.SetCharm(cfg)
   901  	c.Assert(err, jc.ErrorIsNil)
   902  }
   903  
   904  var serviceUpdateConfigSettingsTests = []struct {
   905  	about   string
   906  	initial charm.Settings
   907  	update  charm.Settings
   908  	expect  charm.Settings
   909  	err     string
   910  }{{
   911  	about:  "unknown option",
   912  	update: charm.Settings{"foo": "bar"},
   913  	err:    `unknown option "foo"`,
   914  }, {
   915  	about:  "bad type",
   916  	update: charm.Settings{"skill-level": "profound"},
   917  	err:    `option "skill-level" expected int, got "profound"`,
   918  }, {
   919  	about:  "set string",
   920  	update: charm.Settings{"outlook": "positive"},
   921  	expect: charm.Settings{"outlook": "positive"},
   922  }, {
   923  	about:   "unset string and set another",
   924  	initial: charm.Settings{"outlook": "positive"},
   925  	update:  charm.Settings{"outlook": nil, "title": "sir"},
   926  	expect:  charm.Settings{"title": "sir"},
   927  }, {
   928  	about:  "unset missing string",
   929  	update: charm.Settings{"outlook": nil},
   930  }, {
   931  	about:   `empty strings are valid`,
   932  	initial: charm.Settings{"outlook": "positive"},
   933  	update:  charm.Settings{"outlook": "", "title": ""},
   934  	expect:  charm.Settings{"outlook": "", "title": ""},
   935  }, {
   936  	about:   "preserve existing value",
   937  	initial: charm.Settings{"title": "sir"},
   938  	update:  charm.Settings{"username": "admin001"},
   939  	expect:  charm.Settings{"username": "admin001", "title": "sir"},
   940  }, {
   941  	about:   "unset a default value, set a different default",
   942  	initial: charm.Settings{"username": "admin001", "title": "sir"},
   943  	update:  charm.Settings{"username": nil, "title": "My Title"},
   944  	expect:  charm.Settings{"title": "My Title"},
   945  }, {
   946  	about:  "non-string type",
   947  	update: charm.Settings{"skill-level": 303},
   948  	expect: charm.Settings{"skill-level": int64(303)},
   949  }, {
   950  	about:   "unset non-string type",
   951  	initial: charm.Settings{"skill-level": 303},
   952  	update:  charm.Settings{"skill-level": nil},
   953  }}
   954  
   955  func (s *ApplicationSuite) TestUpdateConfigSettings(c *gc.C) {
   956  	sch := s.AddTestingCharm(c, "dummy")
   957  	for i, t := range serviceUpdateConfigSettingsTests {
   958  		c.Logf("test %d. %s", i, t.about)
   959  		svc := s.AddTestingService(c, "dummy-application", sch)
   960  		if t.initial != nil {
   961  			err := svc.UpdateConfigSettings(t.initial)
   962  			c.Assert(err, jc.ErrorIsNil)
   963  		}
   964  		err := svc.UpdateConfigSettings(t.update)
   965  		if t.err != "" {
   966  			c.Assert(err, gc.ErrorMatches, t.err)
   967  		} else {
   968  			c.Assert(err, jc.ErrorIsNil)
   969  			settings, err := svc.ConfigSettings()
   970  			c.Assert(err, jc.ErrorIsNil)
   971  			expect := t.expect
   972  			if expect == nil {
   973  				expect = charm.Settings{}
   974  			}
   975  			c.Assert(settings, gc.DeepEquals, expect)
   976  		}
   977  		err = svc.Destroy()
   978  		c.Assert(err, jc.ErrorIsNil)
   979  	}
   980  }
   981  
   982  func assertNoSettingsRef(c *gc.C, st *state.State, svcName string, sch *state.Charm) {
   983  	_, err := state.ServiceSettingsRefCount(st, svcName, sch.URL())
   984  	c.Assert(errors.Cause(err), jc.Satisfies, errors.IsNotFound)
   985  }
   986  
   987  func assertSettingsRef(c *gc.C, st *state.State, svcName string, sch *state.Charm, refcount int) {
   988  	rc, err := state.ServiceSettingsRefCount(st, svcName, sch.URL())
   989  	c.Assert(err, jc.ErrorIsNil)
   990  	c.Assert(rc, gc.Equals, refcount)
   991  }
   992  
   993  func (s *ApplicationSuite) TestSettingsRefCountWorks(c *gc.C) {
   994  	// This test ensures the service settings per charm URL are
   995  	// properly reference counted.
   996  	oldCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 1)
   997  	newCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 2)
   998  	svcName := "mywp"
   999  
  1000  	// Both refcounts are zero initially.
  1001  	assertNoSettingsRef(c, s.State, svcName, oldCh)
  1002  	assertNoSettingsRef(c, s.State, svcName, newCh)
  1003  
  1004  	// svc is using oldCh, so its settings refcount is incremented.
  1005  	svc := s.AddTestingService(c, svcName, oldCh)
  1006  	assertSettingsRef(c, s.State, svcName, oldCh, 1)
  1007  	assertNoSettingsRef(c, s.State, svcName, newCh)
  1008  
  1009  	// Changing to the same charm does not change the refcount.
  1010  	cfg := state.SetCharmConfig{Charm: oldCh}
  1011  	err := svc.SetCharm(cfg)
  1012  	c.Assert(err, jc.ErrorIsNil)
  1013  	assertSettingsRef(c, s.State, svcName, oldCh, 1)
  1014  	assertNoSettingsRef(c, s.State, svcName, newCh)
  1015  
  1016  	// Changing from oldCh to newCh causes the refcount of oldCh's
  1017  	// settings to be decremented, while newCh's settings is
  1018  	// incremented. Consequently, because oldCh's refcount is 0, the
  1019  	// settings doc will be removed.
  1020  	cfg = state.SetCharmConfig{Charm: newCh}
  1021  	err = svc.SetCharm(cfg)
  1022  	c.Assert(err, jc.ErrorIsNil)
  1023  	assertNoSettingsRef(c, s.State, svcName, oldCh)
  1024  	assertSettingsRef(c, s.State, svcName, newCh, 1)
  1025  
  1026  	// The same but newCh swapped with oldCh.
  1027  	cfg = state.SetCharmConfig{Charm: oldCh}
  1028  	err = svc.SetCharm(cfg)
  1029  	c.Assert(err, jc.ErrorIsNil)
  1030  	assertSettingsRef(c, s.State, svcName, oldCh, 1)
  1031  	assertNoSettingsRef(c, s.State, svcName, newCh)
  1032  
  1033  	// Adding a unit without a charm URL set does not affect the
  1034  	// refcount.
  1035  	u, err := svc.AddUnit()
  1036  	c.Assert(err, jc.ErrorIsNil)
  1037  	curl, ok := u.CharmURL()
  1038  	c.Assert(ok, jc.IsFalse)
  1039  	assertSettingsRef(c, s.State, svcName, oldCh, 1)
  1040  	assertNoSettingsRef(c, s.State, svcName, newCh)
  1041  
  1042  	// Setting oldCh as the units charm URL increments oldCh, which is
  1043  	// used by svc as well, hence 2.
  1044  	err = u.SetCharmURL(oldCh.URL())
  1045  	c.Assert(err, jc.ErrorIsNil)
  1046  	curl, ok = u.CharmURL()
  1047  	c.Assert(ok, jc.IsTrue)
  1048  	c.Assert(curl, gc.DeepEquals, oldCh.URL())
  1049  	assertSettingsRef(c, s.State, svcName, oldCh, 2)
  1050  	assertNoSettingsRef(c, s.State, svcName, newCh)
  1051  
  1052  	// A dead unit does not decrement the refcount.
  1053  	err = u.EnsureDead()
  1054  	c.Assert(err, jc.ErrorIsNil)
  1055  	assertSettingsRef(c, s.State, svcName, oldCh, 2)
  1056  	assertNoSettingsRef(c, s.State, svcName, newCh)
  1057  
  1058  	// Once the unit is removed, refcount is decremented.
  1059  	err = u.Remove()
  1060  	c.Assert(err, jc.ErrorIsNil)
  1061  	assertSettingsRef(c, s.State, svcName, oldCh, 1)
  1062  	assertNoSettingsRef(c, s.State, svcName, newCh)
  1063  
  1064  	// Finally, after the service is destroyed and removed (since the
  1065  	// last unit's gone), the refcount is again decremented.
  1066  	err = svc.Destroy()
  1067  	c.Assert(err, jc.ErrorIsNil)
  1068  	assertNoSettingsRef(c, s.State, svcName, oldCh)
  1069  	assertNoSettingsRef(c, s.State, svcName, newCh)
  1070  
  1071  	// Having studiously avoided triggering cleanups throughout,
  1072  	// invoke them now and check that the charms are cleaned up
  1073  	// correctly -- and that a storm of cleanups for the same
  1074  	// charm are not a problem.
  1075  	err = s.State.Cleanup()
  1076  	c.Assert(err, jc.ErrorIsNil)
  1077  	err = oldCh.Refresh()
  1078  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1079  	err = newCh.Refresh()
  1080  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1081  }
  1082  
  1083  func (s *ApplicationSuite) TestSettingsRefCreateRace(c *gc.C) {
  1084  	oldCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 1)
  1085  	newCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 2)
  1086  	appName := "mywp"
  1087  
  1088  	app := s.AddTestingService(c, appName, oldCh)
  1089  	unit, err := app.AddUnit()
  1090  	c.Assert(err, jc.ErrorIsNil)
  1091  
  1092  	// just before setting the unit charm url, switch the service
  1093  	// away from the original charm, causing the attempt to fail
  1094  	// (because the settings have gone away; it's the unit's job to
  1095  	// fail out and handle the new charm when it comes back up
  1096  	// again).
  1097  	dropSettings := func() {
  1098  		cfg := state.SetCharmConfig{Charm: newCh}
  1099  		err = app.SetCharm(cfg)
  1100  		c.Assert(err, jc.ErrorIsNil)
  1101  	}
  1102  	defer state.SetBeforeHooks(c, s.State, dropSettings).Check()
  1103  
  1104  	err = unit.SetCharmURL(oldCh.URL())
  1105  	c.Check(err, gc.ErrorMatches, "settings reference: does not exist")
  1106  }
  1107  
  1108  func (s *ApplicationSuite) TestSettingsRefRemoveRace(c *gc.C) {
  1109  	oldCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 1)
  1110  	newCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 2)
  1111  	appName := "mywp"
  1112  
  1113  	app := s.AddTestingService(c, appName, oldCh)
  1114  	unit, err := app.AddUnit()
  1115  	c.Assert(err, jc.ErrorIsNil)
  1116  
  1117  	// just before updating the app charm url, set that charm url on
  1118  	// a unit to block the removal.
  1119  	grabReference := func() {
  1120  		err := unit.SetCharmURL(oldCh.URL())
  1121  		c.Assert(err, jc.ErrorIsNil)
  1122  	}
  1123  	defer state.SetBeforeHooks(c, s.State, grabReference).Check()
  1124  
  1125  	cfg := state.SetCharmConfig{Charm: newCh}
  1126  	err = app.SetCharm(cfg)
  1127  	c.Assert(err, jc.ErrorIsNil)
  1128  
  1129  	// check refs to both settings exist
  1130  	assertSettingsRef(c, s.State, appName, oldCh, 1)
  1131  	assertSettingsRef(c, s.State, appName, newCh, 1)
  1132  }
  1133  
  1134  const mysqlBaseMeta = `
  1135  name: mysql
  1136  summary: "Database engine"
  1137  description: "A pretty popular database"
  1138  provides:
  1139    server: mysql
  1140  `
  1141  const onePeerMeta = `
  1142  peers:
  1143    cluster: mysql
  1144  `
  1145  const twoPeersMeta = `
  1146  peers:
  1147    cluster: mysql
  1148    loadbalancer: phony
  1149  `
  1150  
  1151  func (s *ApplicationSuite) assertApplicationRelations(c *gc.C, svc *state.Application, expectedKeys ...string) []*state.Relation {
  1152  	rels, err := svc.Relations()
  1153  	c.Assert(err, jc.ErrorIsNil)
  1154  	if len(rels) == 0 {
  1155  		return nil
  1156  	}
  1157  	relKeys := make([]string, len(expectedKeys))
  1158  	for i, rel := range rels {
  1159  		relKeys[i] = rel.String()
  1160  	}
  1161  	sort.Strings(relKeys)
  1162  	c.Assert(relKeys, gc.DeepEquals, expectedKeys)
  1163  	return rels
  1164  }
  1165  
  1166  func (s *ApplicationSuite) TestNewPeerRelationsAddedOnUpgrade(c *gc.C) {
  1167  	// Original mysql charm has no peer relations.
  1168  	oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+onePeerMeta, 2)
  1169  	newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+twoPeersMeta, 3)
  1170  
  1171  	// No relations joined yet.
  1172  	s.assertApplicationRelations(c, s.mysql)
  1173  
  1174  	cfg := state.SetCharmConfig{Charm: oldCh}
  1175  	err := s.mysql.SetCharm(cfg)
  1176  	c.Assert(err, jc.ErrorIsNil)
  1177  	s.assertApplicationRelations(c, s.mysql, "mysql:cluster")
  1178  
  1179  	cfg = state.SetCharmConfig{Charm: newCh}
  1180  	err = s.mysql.SetCharm(cfg)
  1181  	c.Assert(err, jc.ErrorIsNil)
  1182  	rels := s.assertApplicationRelations(c, s.mysql, "mysql:cluster", "mysql:loadbalancer")
  1183  
  1184  	// Check state consistency by attempting to destroy the service.
  1185  	err = s.mysql.Destroy()
  1186  	c.Assert(err, jc.ErrorIsNil)
  1187  
  1188  	// Check the peer relations got destroyed as well.
  1189  	for _, rel := range rels {
  1190  		err = rel.Refresh()
  1191  		c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1192  	}
  1193  }
  1194  
  1195  func jujuInfoEp(applicationname string) state.Endpoint {
  1196  	return state.Endpoint{
  1197  		ApplicationName: applicationname,
  1198  		Relation: charm.Relation{
  1199  			Interface: "juju-info",
  1200  			Name:      "juju-info",
  1201  			Role:      charm.RoleProvider,
  1202  			Scope:     charm.ScopeGlobal,
  1203  		},
  1204  	}
  1205  }
  1206  
  1207  func (s *ApplicationSuite) TestTag(c *gc.C) {
  1208  	c.Assert(s.mysql.Tag().String(), gc.Equals, "application-mysql")
  1209  }
  1210  
  1211  func (s *ApplicationSuite) TestMysqlEndpoints(c *gc.C) {
  1212  	_, err := s.mysql.Endpoint("mysql")
  1213  	c.Assert(err, gc.ErrorMatches, `application "mysql" has no "mysql" relation`)
  1214  
  1215  	jiEP, err := s.mysql.Endpoint("juju-info")
  1216  	c.Assert(err, jc.ErrorIsNil)
  1217  	c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("mysql"))
  1218  
  1219  	serverEP, err := s.mysql.Endpoint("server")
  1220  	c.Assert(err, jc.ErrorIsNil)
  1221  	c.Assert(serverEP, gc.DeepEquals, state.Endpoint{
  1222  		ApplicationName: "mysql",
  1223  		Relation: charm.Relation{
  1224  			Interface: "mysql",
  1225  			Name:      "server",
  1226  			Role:      charm.RoleProvider,
  1227  			Scope:     charm.ScopeGlobal,
  1228  		},
  1229  	})
  1230  
  1231  	eps, err := s.mysql.Endpoints()
  1232  	c.Assert(err, jc.ErrorIsNil)
  1233  	c.Assert(eps, gc.DeepEquals, []state.Endpoint{jiEP, serverEP})
  1234  }
  1235  
  1236  func (s *ApplicationSuite) TestRiakEndpoints(c *gc.C) {
  1237  	riak := s.AddTestingService(c, "myriak", s.AddTestingCharm(c, "riak"))
  1238  
  1239  	_, err := riak.Endpoint("garble")
  1240  	c.Assert(err, gc.ErrorMatches, `application "myriak" has no "garble" relation`)
  1241  
  1242  	jiEP, err := riak.Endpoint("juju-info")
  1243  	c.Assert(err, jc.ErrorIsNil)
  1244  	c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("myriak"))
  1245  
  1246  	ringEP, err := riak.Endpoint("ring")
  1247  	c.Assert(err, jc.ErrorIsNil)
  1248  	c.Assert(ringEP, gc.DeepEquals, state.Endpoint{
  1249  		ApplicationName: "myriak",
  1250  		Relation: charm.Relation{
  1251  			Interface: "riak",
  1252  			Name:      "ring",
  1253  			Role:      charm.RolePeer,
  1254  			Scope:     charm.ScopeGlobal,
  1255  			Limit:     1,
  1256  		},
  1257  	})
  1258  
  1259  	adminEP, err := riak.Endpoint("admin")
  1260  	c.Assert(err, jc.ErrorIsNil)
  1261  	c.Assert(adminEP, gc.DeepEquals, state.Endpoint{
  1262  		ApplicationName: "myriak",
  1263  		Relation: charm.Relation{
  1264  			Interface: "http",
  1265  			Name:      "admin",
  1266  			Role:      charm.RoleProvider,
  1267  			Scope:     charm.ScopeGlobal,
  1268  		},
  1269  	})
  1270  
  1271  	endpointEP, err := riak.Endpoint("endpoint")
  1272  	c.Assert(err, jc.ErrorIsNil)
  1273  	c.Assert(endpointEP, gc.DeepEquals, state.Endpoint{
  1274  		ApplicationName: "myriak",
  1275  		Relation: charm.Relation{
  1276  			Interface: "http",
  1277  			Name:      "endpoint",
  1278  			Role:      charm.RoleProvider,
  1279  			Scope:     charm.ScopeGlobal,
  1280  		},
  1281  	})
  1282  
  1283  	eps, err := riak.Endpoints()
  1284  	c.Assert(err, jc.ErrorIsNil)
  1285  	c.Assert(eps, gc.DeepEquals, []state.Endpoint{adminEP, endpointEP, jiEP, ringEP})
  1286  }
  1287  
  1288  func (s *ApplicationSuite) TestWordpressEndpoints(c *gc.C) {
  1289  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1290  
  1291  	_, err := wordpress.Endpoint("nonsense")
  1292  	c.Assert(err, gc.ErrorMatches, `application "wordpress" has no "nonsense" relation`)
  1293  
  1294  	jiEP, err := wordpress.Endpoint("juju-info")
  1295  	c.Assert(err, jc.ErrorIsNil)
  1296  	c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("wordpress"))
  1297  
  1298  	urlEP, err := wordpress.Endpoint("url")
  1299  	c.Assert(err, jc.ErrorIsNil)
  1300  	c.Assert(urlEP, gc.DeepEquals, state.Endpoint{
  1301  		ApplicationName: "wordpress",
  1302  		Relation: charm.Relation{
  1303  			Interface: "http",
  1304  			Name:      "url",
  1305  			Role:      charm.RoleProvider,
  1306  			Scope:     charm.ScopeGlobal,
  1307  		},
  1308  	})
  1309  
  1310  	ldEP, err := wordpress.Endpoint("logging-dir")
  1311  	c.Assert(err, jc.ErrorIsNil)
  1312  	c.Assert(ldEP, gc.DeepEquals, state.Endpoint{
  1313  		ApplicationName: "wordpress",
  1314  		Relation: charm.Relation{
  1315  			Interface: "logging",
  1316  			Name:      "logging-dir",
  1317  			Role:      charm.RoleProvider,
  1318  			Scope:     charm.ScopeContainer,
  1319  		},
  1320  	})
  1321  
  1322  	mpEP, err := wordpress.Endpoint("monitoring-port")
  1323  	c.Assert(err, jc.ErrorIsNil)
  1324  	c.Assert(mpEP, gc.DeepEquals, state.Endpoint{
  1325  		ApplicationName: "wordpress",
  1326  		Relation: charm.Relation{
  1327  			Interface: "monitoring",
  1328  			Name:      "monitoring-port",
  1329  			Role:      charm.RoleProvider,
  1330  			Scope:     charm.ScopeContainer,
  1331  		},
  1332  	})
  1333  
  1334  	dbEP, err := wordpress.Endpoint("db")
  1335  	c.Assert(err, jc.ErrorIsNil)
  1336  	c.Assert(dbEP, gc.DeepEquals, state.Endpoint{
  1337  		ApplicationName: "wordpress",
  1338  		Relation: charm.Relation{
  1339  			Interface: "mysql",
  1340  			Name:      "db",
  1341  			Role:      charm.RoleRequirer,
  1342  			Scope:     charm.ScopeGlobal,
  1343  			Limit:     1,
  1344  		},
  1345  	})
  1346  
  1347  	cacheEP, err := wordpress.Endpoint("cache")
  1348  	c.Assert(err, jc.ErrorIsNil)
  1349  	c.Assert(cacheEP, gc.DeepEquals, state.Endpoint{
  1350  		ApplicationName: "wordpress",
  1351  		Relation: charm.Relation{
  1352  			Interface: "varnish",
  1353  			Name:      "cache",
  1354  			Role:      charm.RoleRequirer,
  1355  			Scope:     charm.ScopeGlobal,
  1356  			Limit:     2,
  1357  			Optional:  true,
  1358  		},
  1359  	})
  1360  
  1361  	eps, err := wordpress.Endpoints()
  1362  	c.Assert(err, jc.ErrorIsNil)
  1363  	c.Assert(eps, gc.DeepEquals, []state.Endpoint{cacheEP, dbEP, jiEP, ldEP, mpEP, urlEP})
  1364  }
  1365  
  1366  func (s *ApplicationSuite) TestServiceRefresh(c *gc.C) {
  1367  	s1, err := s.State.Application(s.mysql.Name())
  1368  	c.Assert(err, jc.ErrorIsNil)
  1369  
  1370  	cfg := state.SetCharmConfig{
  1371  		Charm:      s.charm,
  1372  		ForceUnits: true,
  1373  	}
  1374  
  1375  	err = s.mysql.SetCharm(cfg)
  1376  	c.Assert(err, jc.ErrorIsNil)
  1377  
  1378  	testch, force, err := s1.Charm()
  1379  	c.Assert(err, jc.ErrorIsNil)
  1380  	c.Assert(force, jc.IsFalse)
  1381  	c.Assert(testch.URL(), gc.DeepEquals, s.charm.URL())
  1382  
  1383  	err = s1.Refresh()
  1384  	c.Assert(err, jc.ErrorIsNil)
  1385  	testch, force, err = s1.Charm()
  1386  	c.Assert(err, jc.ErrorIsNil)
  1387  	c.Assert(force, jc.IsTrue)
  1388  	c.Assert(testch.URL(), gc.DeepEquals, s.charm.URL())
  1389  
  1390  	err = s.mysql.Destroy()
  1391  	c.Assert(err, jc.ErrorIsNil)
  1392  	err = s.mysql.Refresh()
  1393  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1394  }
  1395  
  1396  func (s *ApplicationSuite) TestServiceExposed(c *gc.C) {
  1397  	// Check that querying for the exposed flag works correctly.
  1398  	c.Assert(s.mysql.IsExposed(), jc.IsFalse)
  1399  
  1400  	// Check that setting and clearing the exposed flag works correctly.
  1401  	err := s.mysql.SetExposed()
  1402  	c.Assert(err, jc.ErrorIsNil)
  1403  	c.Assert(s.mysql.IsExposed(), jc.IsTrue)
  1404  	err = s.mysql.ClearExposed()
  1405  	c.Assert(err, jc.ErrorIsNil)
  1406  	c.Assert(s.mysql.IsExposed(), jc.IsFalse)
  1407  
  1408  	// Check that setting and clearing the exposed flag repeatedly does not fail.
  1409  	err = s.mysql.SetExposed()
  1410  	c.Assert(err, jc.ErrorIsNil)
  1411  	err = s.mysql.SetExposed()
  1412  	c.Assert(err, jc.ErrorIsNil)
  1413  	err = s.mysql.ClearExposed()
  1414  	c.Assert(err, jc.ErrorIsNil)
  1415  	err = s.mysql.ClearExposed()
  1416  	c.Assert(err, jc.ErrorIsNil)
  1417  	err = s.mysql.SetExposed()
  1418  	c.Assert(err, jc.ErrorIsNil)
  1419  	c.Assert(s.mysql.IsExposed(), jc.IsTrue)
  1420  
  1421  	// Make the service Dying and check that ClearExposed and SetExposed fail.
  1422  	// TODO(fwereade): maybe service destruction should always unexpose?
  1423  	u, err := s.mysql.AddUnit()
  1424  	c.Assert(err, jc.ErrorIsNil)
  1425  	err = s.mysql.Destroy()
  1426  	c.Assert(err, jc.ErrorIsNil)
  1427  	err = s.mysql.ClearExposed()
  1428  	c.Assert(err, gc.ErrorMatches, notAliveErr)
  1429  	err = s.mysql.SetExposed()
  1430  	c.Assert(err, gc.ErrorMatches, notAliveErr)
  1431  
  1432  	// Remove the service and check that both fail.
  1433  	err = u.EnsureDead()
  1434  	c.Assert(err, jc.ErrorIsNil)
  1435  	err = u.Remove()
  1436  	c.Assert(err, jc.ErrorIsNil)
  1437  	err = s.mysql.SetExposed()
  1438  	c.Assert(err, gc.ErrorMatches, notAliveErr)
  1439  	err = s.mysql.ClearExposed()
  1440  	c.Assert(err, gc.ErrorMatches, notAliveErr)
  1441  }
  1442  
  1443  func (s *ApplicationSuite) TestAddUnit(c *gc.C) {
  1444  	// Check that principal units can be added on their own.
  1445  	unitZero, err := s.mysql.AddUnit()
  1446  	c.Assert(err, jc.ErrorIsNil)
  1447  	c.Assert(unitZero.Name(), gc.Equals, "mysql/0")
  1448  	c.Assert(unitZero.IsPrincipal(), jc.IsTrue)
  1449  	c.Assert(unitZero.SubordinateNames(), gc.HasLen, 0)
  1450  	c.Assert(state.GetUnitModelUUID(unitZero), gc.Equals, s.State.ModelUUID())
  1451  
  1452  	unitOne, err := s.mysql.AddUnit()
  1453  	c.Assert(err, jc.ErrorIsNil)
  1454  	c.Assert(unitOne.Name(), gc.Equals, "mysql/1")
  1455  	c.Assert(unitOne.IsPrincipal(), jc.IsTrue)
  1456  	c.Assert(unitOne.SubordinateNames(), gc.HasLen, 0)
  1457  
  1458  	// Assign the principal unit to a machine.
  1459  	m, err := s.State.AddMachine("quantal", state.JobHostUnits)
  1460  	c.Assert(err, jc.ErrorIsNil)
  1461  	err = unitZero.AssignToMachine(m)
  1462  	c.Assert(err, jc.ErrorIsNil)
  1463  
  1464  	// Add a subordinate service and check that units cannot be added directly.
  1465  	// to add a subordinate unit.
  1466  	subCharm := s.AddTestingCharm(c, "logging")
  1467  	logging := s.AddTestingService(c, "logging", subCharm)
  1468  	_, err = logging.AddUnit()
  1469  	c.Assert(err, gc.ErrorMatches, `cannot add unit to application "logging": application is a subordinate`)
  1470  
  1471  	// Indirectly create a subordinate unit by adding a relation and entering
  1472  	// scope as a principal.
  1473  	eps, err := s.State.InferEndpoints("logging", "mysql")
  1474  	c.Assert(err, jc.ErrorIsNil)
  1475  	rel, err := s.State.AddRelation(eps...)
  1476  	c.Assert(err, jc.ErrorIsNil)
  1477  	ru, err := rel.Unit(unitZero)
  1478  	c.Assert(err, jc.ErrorIsNil)
  1479  	err = ru.EnterScope(nil)
  1480  	c.Assert(err, jc.ErrorIsNil)
  1481  	subZero, err := s.State.Unit("logging/0")
  1482  	c.Assert(err, jc.ErrorIsNil)
  1483  
  1484  	// Check that once it's refreshed unitZero has subordinates.
  1485  	err = unitZero.Refresh()
  1486  	c.Assert(err, jc.ErrorIsNil)
  1487  	c.Assert(unitZero.SubordinateNames(), gc.DeepEquals, []string{"logging/0"})
  1488  
  1489  	// Check the subordinate unit has been assigned its principal's machine.
  1490  	id, err := subZero.AssignedMachineId()
  1491  	c.Assert(err, jc.ErrorIsNil)
  1492  	c.Assert(id, gc.Equals, m.Id())
  1493  }
  1494  
  1495  func (s *ApplicationSuite) TestAddUnitWhenNotAlive(c *gc.C) {
  1496  	u, err := s.mysql.AddUnit()
  1497  	c.Assert(err, jc.ErrorIsNil)
  1498  	err = s.mysql.Destroy()
  1499  	c.Assert(err, jc.ErrorIsNil)
  1500  	_, err = s.mysql.AddUnit()
  1501  	c.Assert(err, gc.ErrorMatches, `cannot add unit to application "mysql": application is not alive`)
  1502  	err = u.EnsureDead()
  1503  	c.Assert(err, jc.ErrorIsNil)
  1504  	err = u.Remove()
  1505  	c.Assert(err, jc.ErrorIsNil)
  1506  	_, err = s.mysql.AddUnit()
  1507  	c.Assert(err, gc.ErrorMatches, `cannot add unit to application "mysql": application "mysql" not found`)
  1508  }
  1509  
  1510  func (s *ApplicationSuite) TestReadUnit(c *gc.C) {
  1511  	_, err := s.mysql.AddUnit()
  1512  	c.Assert(err, jc.ErrorIsNil)
  1513  	_, err = s.mysql.AddUnit()
  1514  	c.Assert(err, jc.ErrorIsNil)
  1515  
  1516  	// Check that retrieving a unit from state works correctly.
  1517  	unit, err := s.State.Unit("mysql/0")
  1518  	c.Assert(err, jc.ErrorIsNil)
  1519  	c.Assert(unit.Name(), gc.Equals, "mysql/0")
  1520  
  1521  	// Check that retrieving a non-existent or an invalidly
  1522  	// named unit fail nicely.
  1523  	unit, err = s.State.Unit("mysql")
  1524  	c.Assert(err, gc.ErrorMatches, `"mysql" is not a valid unit name`)
  1525  	unit, err = s.State.Unit("mysql/0/0")
  1526  	c.Assert(err, gc.ErrorMatches, `"mysql/0/0" is not a valid unit name`)
  1527  	unit, err = s.State.Unit("pressword/0")
  1528  	c.Assert(err, gc.ErrorMatches, `unit "pressword/0" not found`)
  1529  
  1530  	units, err := s.mysql.AllUnits()
  1531  	c.Assert(err, jc.ErrorIsNil)
  1532  	c.Assert(sortedUnitNames(units), gc.DeepEquals, []string{"mysql/0", "mysql/1"})
  1533  }
  1534  
  1535  func (s *ApplicationSuite) TestReadUnitWhenDying(c *gc.C) {
  1536  	// Test that we can still read units when the service is Dying...
  1537  	unit, err := s.mysql.AddUnit()
  1538  	c.Assert(err, jc.ErrorIsNil)
  1539  	preventUnitDestroyRemove(c, unit)
  1540  	err = s.mysql.Destroy()
  1541  	c.Assert(err, jc.ErrorIsNil)
  1542  	_, err = s.mysql.AllUnits()
  1543  	c.Assert(err, jc.ErrorIsNil)
  1544  	_, err = s.State.Unit("mysql/0")
  1545  	c.Assert(err, jc.ErrorIsNil)
  1546  
  1547  	// ...and when those units are Dying or Dead...
  1548  	testWhenDying(c, unit, noErr, noErr, func() error {
  1549  		_, err := s.mysql.AllUnits()
  1550  		return err
  1551  	}, func() error {
  1552  		_, err := s.State.Unit("mysql/0")
  1553  		return err
  1554  	})
  1555  
  1556  	// ...and even, in a very limited way, when the service itself is removed.
  1557  	removeAllUnits(c, s.mysql)
  1558  	_, err = s.mysql.AllUnits()
  1559  	c.Assert(err, jc.ErrorIsNil)
  1560  }
  1561  
  1562  func (s *ApplicationSuite) TestDestroySimple(c *gc.C) {
  1563  	err := s.mysql.Destroy()
  1564  	c.Assert(err, jc.ErrorIsNil)
  1565  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
  1566  	err = s.mysql.Refresh()
  1567  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1568  }
  1569  
  1570  func (s *ApplicationSuite) TestDestroyStillHasUnits(c *gc.C) {
  1571  	unit, err := s.mysql.AddUnit()
  1572  	c.Assert(err, jc.ErrorIsNil)
  1573  	err = s.mysql.Destroy()
  1574  	c.Assert(err, jc.ErrorIsNil)
  1575  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
  1576  
  1577  	err = unit.EnsureDead()
  1578  	c.Assert(err, jc.ErrorIsNil)
  1579  	err = s.mysql.Refresh()
  1580  	c.Assert(err, jc.ErrorIsNil)
  1581  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
  1582  
  1583  	err = unit.Remove()
  1584  	c.Assert(err, jc.ErrorIsNil)
  1585  	err = s.mysql.Refresh()
  1586  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1587  }
  1588  
  1589  func (s *ApplicationSuite) TestDestroyOnceHadUnits(c *gc.C) {
  1590  	unit, err := s.mysql.AddUnit()
  1591  	c.Assert(err, jc.ErrorIsNil)
  1592  	err = unit.EnsureDead()
  1593  	c.Assert(err, jc.ErrorIsNil)
  1594  	err = unit.Remove()
  1595  	c.Assert(err, jc.ErrorIsNil)
  1596  
  1597  	err = s.mysql.Destroy()
  1598  	c.Assert(err, jc.ErrorIsNil)
  1599  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
  1600  	err = s.mysql.Refresh()
  1601  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1602  }
  1603  
  1604  func (s *ApplicationSuite) TestDestroyStaleNonZeroUnitCount(c *gc.C) {
  1605  	unit, err := s.mysql.AddUnit()
  1606  	c.Assert(err, jc.ErrorIsNil)
  1607  	err = s.mysql.Refresh()
  1608  	c.Assert(err, jc.ErrorIsNil)
  1609  	err = unit.EnsureDead()
  1610  	c.Assert(err, jc.ErrorIsNil)
  1611  	err = unit.Remove()
  1612  	c.Assert(err, jc.ErrorIsNil)
  1613  
  1614  	err = s.mysql.Destroy()
  1615  	c.Assert(err, jc.ErrorIsNil)
  1616  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
  1617  	err = s.mysql.Refresh()
  1618  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1619  }
  1620  
  1621  func (s *ApplicationSuite) TestDestroyStaleZeroUnitCount(c *gc.C) {
  1622  	unit, err := s.mysql.AddUnit()
  1623  	c.Assert(err, jc.ErrorIsNil)
  1624  
  1625  	err = s.mysql.Destroy()
  1626  	c.Assert(err, jc.ErrorIsNil)
  1627  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
  1628  
  1629  	err = s.mysql.Refresh()
  1630  	c.Assert(err, jc.ErrorIsNil)
  1631  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
  1632  
  1633  	err = unit.EnsureDead()
  1634  	c.Assert(err, jc.ErrorIsNil)
  1635  	err = s.mysql.Refresh()
  1636  	c.Assert(err, jc.ErrorIsNil)
  1637  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
  1638  
  1639  	err = unit.Remove()
  1640  	c.Assert(err, jc.ErrorIsNil)
  1641  	err = s.mysql.Refresh()
  1642  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1643  }
  1644  
  1645  func (s *ApplicationSuite) TestDestroyWithRemovableRelation(c *gc.C) {
  1646  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1647  	eps, err := s.State.InferEndpoints("wordpress", "mysql")
  1648  	c.Assert(err, jc.ErrorIsNil)
  1649  	rel, err := s.State.AddRelation(eps...)
  1650  	c.Assert(err, jc.ErrorIsNil)
  1651  
  1652  	// Destroy a service with no units in relation scope; check service and
  1653  	// unit removed.
  1654  	err = wordpress.Destroy()
  1655  	c.Assert(err, jc.ErrorIsNil)
  1656  	err = wordpress.Refresh()
  1657  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1658  	err = rel.Refresh()
  1659  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1660  }
  1661  
  1662  func (s *ApplicationSuite) TestDestroyWithReferencedRelation(c *gc.C) {
  1663  	s.assertDestroyWithReferencedRelation(c, true)
  1664  }
  1665  
  1666  func (s *ApplicationSuite) TestDestroyWithReferencedRelationStaleCount(c *gc.C) {
  1667  	s.assertDestroyWithReferencedRelation(c, false)
  1668  }
  1669  
  1670  func (s *ApplicationSuite) assertDestroyWithReferencedRelation(c *gc.C, refresh bool) {
  1671  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1672  	eps, err := s.State.InferEndpoints("wordpress", "mysql")
  1673  	c.Assert(err, jc.ErrorIsNil)
  1674  	rel0, err := s.State.AddRelation(eps...)
  1675  	c.Assert(err, jc.ErrorIsNil)
  1676  
  1677  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
  1678  	eps, err = s.State.InferEndpoints("logging", "mysql")
  1679  	c.Assert(err, jc.ErrorIsNil)
  1680  	rel1, err := s.State.AddRelation(eps...)
  1681  	c.Assert(err, jc.ErrorIsNil)
  1682  
  1683  	// Add a separate reference to the first relation.
  1684  	unit, err := wordpress.AddUnit()
  1685  	c.Assert(err, jc.ErrorIsNil)
  1686  	ru, err := rel0.Unit(unit)
  1687  	c.Assert(err, jc.ErrorIsNil)
  1688  	err = ru.EnterScope(nil)
  1689  	c.Assert(err, jc.ErrorIsNil)
  1690  
  1691  	// Optionally update the service document to get correct relation counts.
  1692  	if refresh {
  1693  		err = s.mysql.Destroy()
  1694  		c.Assert(err, jc.ErrorIsNil)
  1695  	}
  1696  
  1697  	// Destroy, and check that the first relation becomes Dying...
  1698  	err = s.mysql.Destroy()
  1699  	c.Assert(err, jc.ErrorIsNil)
  1700  	err = rel0.Refresh()
  1701  	c.Assert(err, jc.ErrorIsNil)
  1702  	c.Assert(rel0.Life(), gc.Equals, state.Dying)
  1703  
  1704  	// ...while the second is removed directly.
  1705  	err = rel1.Refresh()
  1706  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1707  
  1708  	// Drop the last reference to the first relation; check the relation and
  1709  	// the service are are both removed.
  1710  	err = ru.LeaveScope()
  1711  	c.Assert(err, jc.ErrorIsNil)
  1712  	err = s.mysql.Refresh()
  1713  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1714  	err = rel0.Refresh()
  1715  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1716  }
  1717  
  1718  func (s *ApplicationSuite) TestDestroyQueuesUnitCleanup(c *gc.C) {
  1719  	// Add 5 units; block quick-remove of mysql/1 and mysql/3
  1720  	units := make([]*state.Unit, 5)
  1721  	for i := range units {
  1722  		unit, err := s.mysql.AddUnit()
  1723  		c.Assert(err, jc.ErrorIsNil)
  1724  		units[i] = unit
  1725  		if i%2 != 0 {
  1726  			preventUnitDestroyRemove(c, unit)
  1727  		}
  1728  	}
  1729  
  1730  	// Check state is clean.
  1731  	dirty, err := s.State.NeedsCleanup()
  1732  	c.Assert(err, jc.ErrorIsNil)
  1733  	c.Assert(dirty, jc.IsFalse)
  1734  
  1735  	// Destroy mysql, and check units are not touched.
  1736  	err = s.mysql.Destroy()
  1737  	c.Assert(err, jc.ErrorIsNil)
  1738  	for _, unit := range units {
  1739  		assertLife(c, unit, state.Alive)
  1740  	}
  1741  
  1742  	// Check a cleanup doc was added.
  1743  	dirty, err = s.State.NeedsCleanup()
  1744  	c.Assert(err, jc.ErrorIsNil)
  1745  	c.Assert(dirty, jc.IsTrue)
  1746  
  1747  	// Run the cleanup and check the units.
  1748  	err = s.State.Cleanup()
  1749  	c.Assert(err, jc.ErrorIsNil)
  1750  	for i, unit := range units {
  1751  		if i%2 != 0 {
  1752  			assertLife(c, unit, state.Dying)
  1753  		} else {
  1754  			assertRemoved(c, unit)
  1755  		}
  1756  	}
  1757  
  1758  	// Check for queued unit cleanups, and run them.
  1759  	dirty, err = s.State.NeedsCleanup()
  1760  	c.Assert(err, jc.ErrorIsNil)
  1761  	c.Assert(dirty, jc.IsTrue)
  1762  	err = s.State.Cleanup()
  1763  	c.Assert(err, jc.ErrorIsNil)
  1764  
  1765  	// Check we're now clean.
  1766  	dirty, err = s.State.NeedsCleanup()
  1767  	c.Assert(err, jc.ErrorIsNil)
  1768  	c.Assert(dirty, jc.IsFalse)
  1769  }
  1770  
  1771  func (s *ApplicationSuite) TestRemoveServiceMachine(c *gc.C) {
  1772  	unit, err := s.mysql.AddUnit()
  1773  	c.Assert(err, jc.ErrorIsNil)
  1774  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
  1775  	c.Assert(err, jc.ErrorIsNil)
  1776  	c.Assert(unit.AssignToMachine(machine), gc.IsNil)
  1777  
  1778  	c.Assert(s.mysql.Destroy(), gc.IsNil)
  1779  	assertLife(c, s.mysql, state.Dying)
  1780  
  1781  	// Service.Destroy adds units to cleanup, make it happen now.
  1782  	c.Assert(s.State.Cleanup(), gc.IsNil)
  1783  
  1784  	c.Assert(unit.Refresh(), jc.Satisfies, errors.IsNotFound)
  1785  	assertLife(c, machine, state.Dying)
  1786  }
  1787  
  1788  func (s *ApplicationSuite) TestRemoveQueuesLocalCharmCleanup(c *gc.C) {
  1789  	// Check state is clean.
  1790  	dirty, err := s.State.NeedsCleanup()
  1791  	c.Assert(err, jc.ErrorIsNil)
  1792  	c.Assert(dirty, jc.IsFalse)
  1793  
  1794  	err = s.mysql.Destroy()
  1795  
  1796  	// Check a cleanup doc was added.
  1797  	dirty, err = s.State.NeedsCleanup()
  1798  	c.Assert(err, jc.ErrorIsNil)
  1799  	c.Assert(dirty, jc.IsTrue)
  1800  
  1801  	// Run the cleanup
  1802  	err = s.State.Cleanup()
  1803  	c.Assert(err, jc.ErrorIsNil)
  1804  
  1805  	// Check charm removed
  1806  	err = s.charm.Refresh()
  1807  	c.Check(err, jc.Satisfies, errors.IsNotFound)
  1808  
  1809  	// Check we're now clean.
  1810  	dirty, err = s.State.NeedsCleanup()
  1811  	c.Assert(err, jc.ErrorIsNil)
  1812  	c.Assert(dirty, jc.IsFalse)
  1813  }
  1814  
  1815  func (s *ApplicationSuite) TestReadUnitWithChangingState(c *gc.C) {
  1816  	// Check that reading a unit after removing the service
  1817  	// fails nicely.
  1818  	err := s.mysql.Destroy()
  1819  	c.Assert(err, jc.ErrorIsNil)
  1820  	err = s.mysql.Refresh()
  1821  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1822  	_, err = s.State.Unit("mysql/0")
  1823  	c.Assert(err, gc.ErrorMatches, `unit "mysql/0" not found`)
  1824  }
  1825  
  1826  func uint64p(val uint64) *uint64 {
  1827  	return &val
  1828  }
  1829  
  1830  func (s *ApplicationSuite) TestConstraints(c *gc.C) {
  1831  	// Constraints are initially empty (for now).
  1832  	cons, err := s.mysql.Constraints()
  1833  	c.Assert(err, jc.ErrorIsNil)
  1834  	c.Assert(&cons, jc.Satisfies, constraints.IsEmpty)
  1835  
  1836  	// Constraints can be set.
  1837  	cons2 := constraints.Value{Mem: uint64p(4096)}
  1838  	err = s.mysql.SetConstraints(cons2)
  1839  	cons3, err := s.mysql.Constraints()
  1840  	c.Assert(err, jc.ErrorIsNil)
  1841  	c.Assert(cons3, gc.DeepEquals, cons2)
  1842  
  1843  	// Constraints are completely overwritten when re-set.
  1844  	cons4 := constraints.Value{CpuPower: uint64p(750)}
  1845  	err = s.mysql.SetConstraints(cons4)
  1846  	c.Assert(err, jc.ErrorIsNil)
  1847  	cons5, err := s.mysql.Constraints()
  1848  	c.Assert(err, jc.ErrorIsNil)
  1849  	c.Assert(cons5, gc.DeepEquals, cons4)
  1850  
  1851  	// Destroy the existing service; there's no way to directly assert
  1852  	// that the constraints are deleted...
  1853  	err = s.mysql.Destroy()
  1854  	c.Assert(err, jc.ErrorIsNil)
  1855  	err = s.mysql.Refresh()
  1856  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1857  
  1858  	// ...but we can check that old constraints do not affect new services
  1859  	// with matching names.
  1860  	ch, _, err := s.mysql.Charm()
  1861  	c.Assert(err, jc.ErrorIsNil)
  1862  	mysql := s.AddTestingService(c, s.mysql.Name(), ch)
  1863  	cons6, err := mysql.Constraints()
  1864  	c.Assert(err, jc.ErrorIsNil)
  1865  	c.Assert(&cons6, jc.Satisfies, constraints.IsEmpty)
  1866  }
  1867  
  1868  func (s *ApplicationSuite) TestSetInvalidConstraints(c *gc.C) {
  1869  	cons := constraints.MustParse("mem=4G instance-type=foo")
  1870  	err := s.mysql.SetConstraints(cons)
  1871  	c.Assert(err, gc.ErrorMatches, `ambiguous constraints: "instance-type" overlaps with "mem"`)
  1872  }
  1873  
  1874  func (s *ApplicationSuite) TestSetUnsupportedConstraintsWarning(c *gc.C) {
  1875  	defer loggo.ResetWriters()
  1876  	logger := loggo.GetLogger("test")
  1877  	logger.SetLogLevel(loggo.DEBUG)
  1878  	var tw loggo.TestWriter
  1879  	c.Assert(loggo.RegisterWriter("constraints-tester", &tw), gc.IsNil)
  1880  
  1881  	cons := constraints.MustParse("mem=4G cpu-power=10")
  1882  	err := s.mysql.SetConstraints(cons)
  1883  	c.Assert(err, jc.ErrorIsNil)
  1884  	c.Assert(tw.Log(), jc.LogMatches, jc.SimpleMessages{{
  1885  		loggo.WARNING,
  1886  		`setting constraints on application "mysql": unsupported constraints: cpu-power`},
  1887  	})
  1888  	scons, err := s.mysql.Constraints()
  1889  	c.Assert(err, jc.ErrorIsNil)
  1890  	c.Assert(scons, gc.DeepEquals, cons)
  1891  }
  1892  
  1893  func (s *ApplicationSuite) TestConstraintsLifecycle(c *gc.C) {
  1894  	// Dying.
  1895  	unit, err := s.mysql.AddUnit()
  1896  	c.Assert(err, jc.ErrorIsNil)
  1897  	err = s.mysql.Destroy()
  1898  	c.Assert(err, jc.ErrorIsNil)
  1899  	cons1 := constraints.MustParse("mem=1G")
  1900  	err = s.mysql.SetConstraints(cons1)
  1901  	c.Assert(err, gc.ErrorMatches, `cannot set constraints: not found or not alive`)
  1902  	scons, err := s.mysql.Constraints()
  1903  	c.Assert(err, jc.ErrorIsNil)
  1904  	c.Assert(&scons, jc.Satisfies, constraints.IsEmpty)
  1905  
  1906  	// Removed (== Dead, for a service).
  1907  	err = unit.EnsureDead()
  1908  	c.Assert(err, jc.ErrorIsNil)
  1909  	err = unit.Remove()
  1910  	c.Assert(err, jc.ErrorIsNil)
  1911  	err = s.mysql.SetConstraints(cons1)
  1912  	c.Assert(err, gc.ErrorMatches, `cannot set constraints: not found or not alive`)
  1913  	_, err = s.mysql.Constraints()
  1914  	c.Assert(err, gc.ErrorMatches, `constraints not found`)
  1915  }
  1916  
  1917  func (s *ApplicationSuite) TestSubordinateConstraints(c *gc.C) {
  1918  	loggingCh := s.AddTestingCharm(c, "logging")
  1919  	logging := s.AddTestingService(c, "logging", loggingCh)
  1920  
  1921  	_, err := logging.Constraints()
  1922  	c.Assert(err, gc.Equals, state.ErrSubordinateConstraints)
  1923  
  1924  	err = logging.SetConstraints(constraints.Value{})
  1925  	c.Assert(err, gc.Equals, state.ErrSubordinateConstraints)
  1926  }
  1927  
  1928  func (s *ApplicationSuite) TestWatchUnitsBulkEvents(c *gc.C) {
  1929  	// Alive unit...
  1930  	alive, err := s.mysql.AddUnit()
  1931  	c.Assert(err, jc.ErrorIsNil)
  1932  
  1933  	// Dying unit...
  1934  	dying, err := s.mysql.AddUnit()
  1935  	c.Assert(err, jc.ErrorIsNil)
  1936  	preventUnitDestroyRemove(c, dying)
  1937  	err = dying.Destroy()
  1938  	c.Assert(err, jc.ErrorIsNil)
  1939  
  1940  	// Dead unit...
  1941  	dead, err := s.mysql.AddUnit()
  1942  	c.Assert(err, jc.ErrorIsNil)
  1943  	preventUnitDestroyRemove(c, dead)
  1944  	err = dead.Destroy()
  1945  	c.Assert(err, jc.ErrorIsNil)
  1946  	err = dead.EnsureDead()
  1947  	c.Assert(err, jc.ErrorIsNil)
  1948  
  1949  	// Gone unit.
  1950  	gone, err := s.mysql.AddUnit()
  1951  	c.Assert(err, jc.ErrorIsNil)
  1952  	err = gone.Destroy()
  1953  	c.Assert(err, jc.ErrorIsNil)
  1954  
  1955  	// All except gone unit are reported in initial event.
  1956  	w := s.mysql.WatchUnits()
  1957  	defer testing.AssertStop(c, w)
  1958  	wc := testing.NewStringsWatcherC(c, s.State, w)
  1959  	wc.AssertChange(alive.Name(), dying.Name(), dead.Name())
  1960  	wc.AssertNoChange()
  1961  
  1962  	// Remove them all; alive/dying changes reported; dead never mentioned again.
  1963  	err = alive.Destroy()
  1964  	c.Assert(err, jc.ErrorIsNil)
  1965  	err = dying.EnsureDead()
  1966  	c.Assert(err, jc.ErrorIsNil)
  1967  	err = dying.Remove()
  1968  	c.Assert(err, jc.ErrorIsNil)
  1969  	err = dead.Remove()
  1970  	c.Assert(err, jc.ErrorIsNil)
  1971  	wc.AssertChange(alive.Name(), dying.Name())
  1972  	wc.AssertNoChange()
  1973  }
  1974  
  1975  func (s *ApplicationSuite) TestWatchUnitsLifecycle(c *gc.C) {
  1976  	// Empty initial event when no units.
  1977  	w := s.mysql.WatchUnits()
  1978  	defer testing.AssertStop(c, w)
  1979  	wc := testing.NewStringsWatcherC(c, s.State, w)
  1980  	wc.AssertChange()
  1981  	wc.AssertNoChange()
  1982  
  1983  	// Create one unit, check one change.
  1984  	quick, err := s.mysql.AddUnit()
  1985  	c.Assert(err, jc.ErrorIsNil)
  1986  	wc.AssertChange(quick.Name())
  1987  	wc.AssertNoChange()
  1988  
  1989  	// Destroy that unit (short-circuited to removal), check one change.
  1990  	err = quick.Destroy()
  1991  	c.Assert(err, jc.ErrorIsNil)
  1992  	wc.AssertChange(quick.Name())
  1993  	wc.AssertNoChange()
  1994  
  1995  	// Create another, check one change.
  1996  	slow, err := s.mysql.AddUnit()
  1997  	c.Assert(err, jc.ErrorIsNil)
  1998  	wc.AssertChange(slow.Name())
  1999  	wc.AssertNoChange()
  2000  
  2001  	// Change unit itself, no change.
  2002  	preventUnitDestroyRemove(c, slow)
  2003  	wc.AssertNoChange()
  2004  
  2005  	// Make unit Dying, change detected.
  2006  	err = slow.Destroy()
  2007  	c.Assert(err, jc.ErrorIsNil)
  2008  	wc.AssertChange(slow.Name())
  2009  	wc.AssertNoChange()
  2010  
  2011  	// Make unit Dead, change detected.
  2012  	err = slow.EnsureDead()
  2013  	c.Assert(err, jc.ErrorIsNil)
  2014  	wc.AssertChange(slow.Name())
  2015  	wc.AssertNoChange()
  2016  
  2017  	// Remove unit, final change not detected.
  2018  	err = slow.Remove()
  2019  	c.Assert(err, jc.ErrorIsNil)
  2020  	wc.AssertNoChange()
  2021  }
  2022  
  2023  func (s *ApplicationSuite) TestWatchRelations(c *gc.C) {
  2024  	// TODO(fwereade) split this test up a bit.
  2025  	w := s.mysql.WatchRelations()
  2026  	defer testing.AssertStop(c, w)
  2027  	wc := testing.NewStringsWatcherC(c, s.State, w)
  2028  	wc.AssertChange()
  2029  	wc.AssertNoChange()
  2030  
  2031  	// Add a relation; check change.
  2032  	mysqlep, err := s.mysql.Endpoint("server")
  2033  	c.Assert(err, jc.ErrorIsNil)
  2034  	wpch := s.AddTestingCharm(c, "wordpress")
  2035  	wpi := 0
  2036  	addRelation := func() *state.Relation {
  2037  		name := fmt.Sprintf("wp%d", wpi)
  2038  		wpi++
  2039  		wp := s.AddTestingService(c, name, wpch)
  2040  		wpep, err := wp.Endpoint("db")
  2041  		c.Assert(err, jc.ErrorIsNil)
  2042  		rel, err := s.State.AddRelation(mysqlep, wpep)
  2043  		c.Assert(err, jc.ErrorIsNil)
  2044  		return rel
  2045  	}
  2046  	rel0 := addRelation()
  2047  	wc.AssertChange(rel0.String())
  2048  	wc.AssertNoChange()
  2049  
  2050  	// Add another relation; check change.
  2051  	rel1 := addRelation()
  2052  	wc.AssertChange(rel1.String())
  2053  	wc.AssertNoChange()
  2054  
  2055  	// Destroy a relation; check change.
  2056  	err = rel0.Destroy()
  2057  	c.Assert(err, jc.ErrorIsNil)
  2058  	wc.AssertChange(rel0.String())
  2059  	wc.AssertNoChange()
  2060  
  2061  	// Stop watcher; check change chan is closed.
  2062  	testing.AssertStop(c, w)
  2063  	wc.AssertClosed()
  2064  
  2065  	// Add a new relation; start a new watcher; check initial event.
  2066  	rel2 := addRelation()
  2067  	w = s.mysql.WatchRelations()
  2068  	defer testing.AssertStop(c, w)
  2069  	wc = testing.NewStringsWatcherC(c, s.State, w)
  2070  	wc.AssertChange(rel1.String(), rel2.String())
  2071  	wc.AssertNoChange()
  2072  
  2073  	// Add a unit to the new relation; check no change.
  2074  	unit, err := s.mysql.AddUnit()
  2075  	c.Assert(err, jc.ErrorIsNil)
  2076  	ru2, err := rel2.Unit(unit)
  2077  	c.Assert(err, jc.ErrorIsNil)
  2078  	err = ru2.EnterScope(nil)
  2079  	c.Assert(err, jc.ErrorIsNil)
  2080  	wc.AssertNoChange()
  2081  
  2082  	// Destroy the relation with the unit in scope, and add another; check
  2083  	// changes.
  2084  	err = rel2.Destroy()
  2085  	c.Assert(err, jc.ErrorIsNil)
  2086  	rel3 := addRelation()
  2087  	wc.AssertChange(rel2.String(), rel3.String())
  2088  	wc.AssertNoChange()
  2089  
  2090  	// Leave scope, destroying the relation, and check that change as well.
  2091  	err = ru2.LeaveScope()
  2092  	c.Assert(err, jc.ErrorIsNil)
  2093  	wc.AssertChange(rel2.String())
  2094  	wc.AssertNoChange()
  2095  
  2096  	// Watch relations on the requirer service too (exercises a
  2097  	// different path of the WatchRelations filter function)
  2098  	wpx := s.AddTestingService(c, "wpx", wpch)
  2099  	wpxWatcher := wpx.WatchRelations()
  2100  	defer testing.AssertStop(c, wpxWatcher)
  2101  	wpxWatcherC := testing.NewStringsWatcherC(c, s.State, wpxWatcher)
  2102  	wpxWatcherC.AssertChange()
  2103  	wpxWatcherC.AssertNoChange()
  2104  
  2105  	wpxep, err := wpx.Endpoint("db")
  2106  	c.Assert(err, jc.ErrorIsNil)
  2107  	relx, err := s.State.AddRelation(mysqlep, wpxep)
  2108  	c.Assert(err, jc.ErrorIsNil)
  2109  	wpxWatcherC.AssertChange(relx.String())
  2110  	wpxWatcherC.AssertNoChange()
  2111  }
  2112  
  2113  func removeAllUnits(c *gc.C, s *state.Application) {
  2114  	us, err := s.AllUnits()
  2115  	c.Assert(err, jc.ErrorIsNil)
  2116  	for _, u := range us {
  2117  		err = u.EnsureDead()
  2118  		c.Assert(err, jc.ErrorIsNil)
  2119  		err = u.Remove()
  2120  		c.Assert(err, jc.ErrorIsNil)
  2121  	}
  2122  }
  2123  
  2124  func (s *ApplicationSuite) TestWatchService(c *gc.C) {
  2125  	w := s.mysql.Watch()
  2126  	defer testing.AssertStop(c, w)
  2127  
  2128  	// Initial event.
  2129  	wc := testing.NewNotifyWatcherC(c, s.State, w)
  2130  	wc.AssertOneChange()
  2131  
  2132  	// Make one change (to a separate instance), check one event.
  2133  	service, err := s.State.Application(s.mysql.Name())
  2134  	c.Assert(err, jc.ErrorIsNil)
  2135  	err = service.SetExposed()
  2136  	c.Assert(err, jc.ErrorIsNil)
  2137  	wc.AssertOneChange()
  2138  
  2139  	// Make two changes, check one event.
  2140  	err = service.ClearExposed()
  2141  	c.Assert(err, jc.ErrorIsNil)
  2142  
  2143  	cfg := state.SetCharmConfig{
  2144  		Charm:      s.charm,
  2145  		ForceUnits: true,
  2146  	}
  2147  	err = service.SetCharm(cfg)
  2148  	c.Assert(err, jc.ErrorIsNil)
  2149  	wc.AssertOneChange()
  2150  
  2151  	// Stop, check closed.
  2152  	testing.AssertStop(c, w)
  2153  	wc.AssertClosed()
  2154  
  2155  	// Remove service, start new watch, check single event.
  2156  	err = service.Destroy()
  2157  	c.Assert(err, jc.ErrorIsNil)
  2158  	w = s.mysql.Watch()
  2159  	defer testing.AssertStop(c, w)
  2160  	testing.NewNotifyWatcherC(c, s.State, w).AssertOneChange()
  2161  }
  2162  
  2163  func (s *ApplicationSuite) TestMetricCredentials(c *gc.C) {
  2164  	err := s.mysql.SetMetricCredentials([]byte("hello there"))
  2165  	c.Assert(err, jc.ErrorIsNil)
  2166  	c.Assert(s.mysql.MetricCredentials(), gc.DeepEquals, []byte("hello there"))
  2167  
  2168  	service, err := s.State.Application(s.mysql.Name())
  2169  	c.Assert(service.MetricCredentials(), gc.DeepEquals, []byte("hello there"))
  2170  }
  2171  
  2172  func (s *ApplicationSuite) TestMetricCredentialsOnDying(c *gc.C) {
  2173  	_, err := s.mysql.AddUnit()
  2174  	c.Assert(err, jc.ErrorIsNil)
  2175  	err = s.mysql.SetMetricCredentials([]byte("set before dying"))
  2176  	c.Assert(err, jc.ErrorIsNil)
  2177  	err = s.mysql.Destroy()
  2178  	c.Assert(err, jc.ErrorIsNil)
  2179  	assertLife(c, s.mysql, state.Dying)
  2180  	err = s.mysql.SetMetricCredentials([]byte("set after dying"))
  2181  	c.Assert(err, gc.ErrorMatches, "cannot update metric credentials: application not found or not alive")
  2182  }
  2183  
  2184  func (s *ApplicationSuite) testStatus(c *gc.C, status1, status2, expected status.Status) {
  2185  	u1, err := s.mysql.AddUnit()
  2186  	c.Assert(err, jc.ErrorIsNil)
  2187  	now := coretesting.ZeroTime()
  2188  	sInfo := status.StatusInfo{
  2189  		Status:  status1,
  2190  		Message: "status 1",
  2191  		Since:   &now,
  2192  	}
  2193  	err = u1.SetStatus(sInfo)
  2194  	c.Assert(err, jc.ErrorIsNil)
  2195  
  2196  	u2, err := s.mysql.AddUnit()
  2197  	c.Assert(err, jc.ErrorIsNil)
  2198  	sInfo = status.StatusInfo{
  2199  		Status:  status2,
  2200  		Message: "status 2",
  2201  		Since:   &now,
  2202  	}
  2203  	if status2 == status.Error {
  2204  		err = u2.SetAgentStatus(sInfo)
  2205  	} else {
  2206  		err = u2.SetStatus(sInfo)
  2207  	}
  2208  	c.Assert(err, jc.ErrorIsNil)
  2209  
  2210  	statusInfo, err := s.mysql.Status()
  2211  	c.Assert(err, jc.ErrorIsNil)
  2212  	c.Assert(statusInfo.Since, gc.NotNil)
  2213  	statusInfo.Since = nil
  2214  	c.Assert(statusInfo, jc.DeepEquals, status.StatusInfo{
  2215  		Status:  expected,
  2216  		Message: "status 2",
  2217  		Data:    map[string]interface{}{},
  2218  	})
  2219  }
  2220  
  2221  func (s *ApplicationSuite) TestStatus(c *gc.C) {
  2222  	for _, t := range []struct{ status1, status2, expected status.Status }{
  2223  		{status.Active, status.Waiting, status.Waiting},
  2224  		{status.Maintenance, status.Waiting, status.Waiting},
  2225  		{status.Active, status.Blocked, status.Blocked},
  2226  		{status.Waiting, status.Blocked, status.Blocked},
  2227  		{status.Maintenance, status.Blocked, status.Blocked},
  2228  		{status.Maintenance, status.Error, status.Error},
  2229  		{status.Blocked, status.Error, status.Error},
  2230  		{status.Waiting, status.Error, status.Error},
  2231  		{status.Active, status.Error, status.Error},
  2232  	} {
  2233  		s.testStatus(c, t.status1, t.status2, t.expected)
  2234  	}
  2235  }
  2236  
  2237  const oneRequiredStorageMeta = `
  2238  storage:
  2239    data0:
  2240      type: block
  2241  `
  2242  
  2243  const oneOptionalStorageMeta = `
  2244  storage:
  2245    data0:
  2246      type: block
  2247      multiple:
  2248        range: 0-
  2249  `
  2250  
  2251  const oneRequiredOneOptionalStorageMeta = `
  2252  storage:
  2253    data0:
  2254      type: block
  2255    data1:
  2256      type: block
  2257      multiple:
  2258        range: 0-
  2259  `
  2260  
  2261  const twoRequiredStorageMeta = `
  2262  storage:
  2263    data0:
  2264      type: block
  2265    data1:
  2266      type: block
  2267  `
  2268  
  2269  const twoOptionalStorageMeta = `
  2270  storage:
  2271    data0:
  2272      type: block
  2273      multiple:
  2274        range: 0-
  2275    data1:
  2276      type: block
  2277      multiple:
  2278        range: 0-
  2279  `
  2280  
  2281  const oneRequiredFilesystemStorageMeta = `
  2282  storage:
  2283    data0:
  2284      type: filesystem
  2285  `
  2286  
  2287  const oneOptionalSharedStorageMeta = `
  2288  storage:
  2289    data0:
  2290      type: block
  2291      shared: true
  2292      multiple:
  2293        range: 0-
  2294  `
  2295  
  2296  const oneRequiredReadOnlyStorageMeta = `
  2297  storage:
  2298    data0:
  2299      type: block
  2300      read-only: true
  2301  `
  2302  
  2303  const oneRequiredLocationStorageMeta = `
  2304  storage:
  2305    data0:
  2306      type: filesystem
  2307      location: /srv
  2308  `
  2309  
  2310  const oneMultipleLocationStorageMeta = `
  2311  storage:
  2312    data0:
  2313      type: filesystem
  2314      location: /srv
  2315      multiple:
  2316        range: 1-
  2317  `
  2318  
  2319  func storageRange(min, max int) string {
  2320  	var minStr, maxStr string
  2321  	if min > 0 {
  2322  		minStr = fmt.Sprint(min)
  2323  	}
  2324  	if max > 0 {
  2325  		maxStr = fmt.Sprint(max)
  2326  	}
  2327  	return fmt.Sprintf(`
  2328      multiple:
  2329        range: %s-%s
  2330  `[1:], minStr, maxStr)
  2331  }
  2332  
  2333  func (s *ApplicationSuite) setCharmFromMeta(c *gc.C, oldMeta, newMeta string) error {
  2334  	oldCh := s.AddMetaCharm(c, "mysql", oldMeta, 2)
  2335  	newCh := s.AddMetaCharm(c, "mysql", newMeta, 3)
  2336  	svc := s.AddTestingService(c, "test", oldCh)
  2337  
  2338  	cfg := state.SetCharmConfig{Charm: newCh}
  2339  	return svc.SetCharm(cfg)
  2340  }
  2341  
  2342  func (s *ApplicationSuite) TestSetCharmOptionalUnusedStorageRemoved(c *gc.C) {
  2343  	err := s.setCharmFromMeta(c,
  2344  		mysqlBaseMeta+oneRequiredOneOptionalStorageMeta,
  2345  		mysqlBaseMeta+oneRequiredStorageMeta,
  2346  	)
  2347  	c.Assert(err, jc.ErrorIsNil)
  2348  	// It's valid to remove optional storage so long
  2349  	// as it is not in use.
  2350  }
  2351  
  2352  func (s *ApplicationSuite) TestSetCharmOptionalUsedStorageRemoved(c *gc.C) {
  2353  	oldMeta := mysqlBaseMeta + oneRequiredOneOptionalStorageMeta
  2354  	newMeta := mysqlBaseMeta + oneRequiredStorageMeta
  2355  	oldCh := s.AddMetaCharm(c, "mysql", oldMeta, 2)
  2356  	newCh := s.AddMetaCharm(c, "mysql", newMeta, 3)
  2357  	svc := s.Factory.MakeApplication(c, &factory.ApplicationParams{
  2358  		Name:  "test",
  2359  		Charm: oldCh,
  2360  		Storage: map[string]state.StorageConstraints{
  2361  			"data0": {Count: 1},
  2362  			"data1": {Count: 1},
  2363  		},
  2364  	})
  2365  	defer state.SetBeforeHooks(c, s.State, func() {
  2366  		// Adding a unit will cause the storage to be in-use.
  2367  		_, err := svc.AddUnit()
  2368  		c.Assert(err, jc.ErrorIsNil)
  2369  	}).Check()
  2370  	cfg := state.SetCharmConfig{Charm: newCh}
  2371  	err := svc.SetCharm(cfg)
  2372  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": in-use storage "data1" removed`)
  2373  }
  2374  
  2375  func (s *ApplicationSuite) TestSetCharmRequiredStorageRemoved(c *gc.C) {
  2376  	err := s.setCharmFromMeta(c,
  2377  		mysqlBaseMeta+oneRequiredStorageMeta,
  2378  		mysqlBaseMeta,
  2379  	)
  2380  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": required storage "data0" removed`)
  2381  }
  2382  
  2383  func (s *ApplicationSuite) TestSetCharmRequiredStorageAddedDefaultConstraints(c *gc.C) {
  2384  	oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+oneRequiredStorageMeta, 2)
  2385  	newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+twoRequiredStorageMeta, 3)
  2386  	svc := s.AddTestingService(c, "test", oldCh)
  2387  	u, err := svc.AddUnit()
  2388  	c.Assert(err, jc.ErrorIsNil)
  2389  
  2390  	cfg := state.SetCharmConfig{Charm: newCh}
  2391  	err = svc.SetCharm(cfg)
  2392  	c.Assert(err, jc.ErrorIsNil)
  2393  
  2394  	// Check that the new required storage was added for the unit.
  2395  	attachments, err := s.State.UnitStorageAttachments(u.UnitTag())
  2396  	c.Assert(err, jc.ErrorIsNil)
  2397  	c.Assert(attachments, gc.HasLen, 2)
  2398  }
  2399  
  2400  func (s *ApplicationSuite) TestSetCharmStorageAddedUserSpecifiedConstraints(c *gc.C) {
  2401  	oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+oneRequiredStorageMeta, 2)
  2402  	newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+twoOptionalStorageMeta, 3)
  2403  	svc := s.AddTestingService(c, "test", oldCh)
  2404  	u, err := svc.AddUnit()
  2405  	c.Assert(err, jc.ErrorIsNil)
  2406  
  2407  	cfg := state.SetCharmConfig{
  2408  		Charm: newCh,
  2409  		StorageConstraints: map[string]state.StorageConstraints{
  2410  			"data1": {Count: 3},
  2411  		},
  2412  	}
  2413  	err = svc.SetCharm(cfg)
  2414  	c.Assert(err, jc.ErrorIsNil)
  2415  
  2416  	// Check that new storage was added for the unit, based on the
  2417  	// constraints specified in SetCharmConfig.
  2418  	attachments, err := s.State.UnitStorageAttachments(u.UnitTag())
  2419  	c.Assert(err, jc.ErrorIsNil)
  2420  	c.Assert(attachments, gc.HasLen, 4)
  2421  }
  2422  
  2423  func (s *ApplicationSuite) TestSetCharmOptionalStorageAdded(c *gc.C) {
  2424  	err := s.setCharmFromMeta(c,
  2425  		mysqlBaseMeta+oneRequiredStorageMeta,
  2426  		mysqlBaseMeta+twoOptionalStorageMeta,
  2427  	)
  2428  	c.Assert(err, jc.ErrorIsNil)
  2429  }
  2430  
  2431  func (s *ApplicationSuite) TestSetCharmStorageCountMinDecreased(c *gc.C) {
  2432  	err := s.setCharmFromMeta(c,
  2433  		mysqlBaseMeta+oneRequiredStorageMeta+storageRange(2, 3),
  2434  		mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 3),
  2435  	)
  2436  	c.Assert(err, jc.ErrorIsNil)
  2437  }
  2438  
  2439  func (s *ApplicationSuite) TestSetCharmStorageCountMinIncreased(c *gc.C) {
  2440  	err := s.setCharmFromMeta(c,
  2441  		mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 3),
  2442  		mysqlBaseMeta+oneRequiredStorageMeta+storageRange(2, 3),
  2443  	)
  2444  	// User must increase the storage constraints from 1 to 2.
  2445  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": validating storage constraints: charm "mysql" store "data0": 2 instances required, 1 specified`)
  2446  }
  2447  
  2448  func (s *ApplicationSuite) TestSetCharmStorageCountMaxDecreased(c *gc.C) {
  2449  	err := s.setCharmFromMeta(c,
  2450  		mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 2),
  2451  		mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 1),
  2452  	)
  2453  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" range contracted: max decreased from 2 to 1`)
  2454  }
  2455  
  2456  func (s *ApplicationSuite) TestSetCharmStorageCountMaxUnboundedToBounded(c *gc.C) {
  2457  	err := s.setCharmFromMeta(c,
  2458  		mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, -1),
  2459  		mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 999),
  2460  	)
  2461  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" range contracted: max decreased from \<unbounded\> to 999`)
  2462  }
  2463  
  2464  func (s *ApplicationSuite) TestSetCharmStorageTypeChanged(c *gc.C) {
  2465  	err := s.setCharmFromMeta(c,
  2466  		mysqlBaseMeta+oneRequiredStorageMeta,
  2467  		mysqlBaseMeta+oneRequiredFilesystemStorageMeta,
  2468  	)
  2469  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" type changed from "block" to "filesystem"`)
  2470  }
  2471  
  2472  func (s *ApplicationSuite) TestSetCharmStorageSharedChanged(c *gc.C) {
  2473  	err := s.setCharmFromMeta(c,
  2474  		mysqlBaseMeta+oneOptionalStorageMeta,
  2475  		mysqlBaseMeta+oneOptionalSharedStorageMeta,
  2476  	)
  2477  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" shared changed from false to true`)
  2478  }
  2479  
  2480  func (s *ApplicationSuite) TestSetCharmStorageReadOnlyChanged(c *gc.C) {
  2481  	err := s.setCharmFromMeta(c,
  2482  		mysqlBaseMeta+oneRequiredStorageMeta,
  2483  		mysqlBaseMeta+oneRequiredReadOnlyStorageMeta,
  2484  	)
  2485  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" read-only changed from false to true`)
  2486  }
  2487  
  2488  func (s *ApplicationSuite) TestSetCharmStorageLocationChanged(c *gc.C) {
  2489  	err := s.setCharmFromMeta(c,
  2490  		mysqlBaseMeta+oneRequiredFilesystemStorageMeta,
  2491  		mysqlBaseMeta+oneRequiredLocationStorageMeta,
  2492  	)
  2493  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" location changed from "" to "/srv"`)
  2494  }
  2495  
  2496  func (s *ApplicationSuite) TestSetCharmStorageWithLocationSingletonToMultipleAdded(c *gc.C) {
  2497  	err := s.setCharmFromMeta(c,
  2498  		mysqlBaseMeta+oneRequiredLocationStorageMeta,
  2499  		mysqlBaseMeta+oneMultipleLocationStorageMeta,
  2500  	)
  2501  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" with location changed from single to multiple`)
  2502  }
  2503  
  2504  func (s *ApplicationSuite) assertApplicationRemovedWithItsBindings(c *gc.C, service *state.Application) {
  2505  	// Removing the service removes the bindings with it.
  2506  	err := service.Destroy()
  2507  	c.Assert(err, jc.ErrorIsNil)
  2508  	err = service.Refresh()
  2509  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  2510  	state.AssertEndpointBindingsNotFoundForService(c, service)
  2511  }
  2512  
  2513  func (s *ApplicationSuite) TestEndpointBindingsReturnsDefaultsWhenNotFound(c *gc.C) {
  2514  	ch := s.AddMetaCharm(c, "mysql", metaBase, 42)
  2515  	service := s.AddTestingServiceWithBindings(c, "yoursql", ch, nil)
  2516  	state.RemoveEndpointBindingsForService(c, service)
  2517  
  2518  	s.assertApplicationHasOnlyDefaultEndpointBindings(c, service)
  2519  }
  2520  
  2521  func (s *ApplicationSuite) assertApplicationHasOnlyDefaultEndpointBindings(c *gc.C, service *state.Application) {
  2522  	charm, _, err := service.Charm()
  2523  	c.Assert(err, jc.ErrorIsNil)
  2524  
  2525  	knownEndpoints := set.NewStrings()
  2526  	allBindings := state.DefaultEndpointBindingsForCharm(charm.Meta())
  2527  	for endpoint, _ := range allBindings {
  2528  		knownEndpoints.Add(endpoint)
  2529  	}
  2530  
  2531  	setBindings, err := service.EndpointBindings()
  2532  	c.Assert(err, jc.ErrorIsNil)
  2533  	c.Assert(setBindings, gc.NotNil)
  2534  
  2535  	for endpoint, space := range setBindings {
  2536  		c.Check(endpoint, gc.Not(gc.Equals), "")
  2537  		c.Check(knownEndpoints.Contains(endpoint), jc.IsTrue)
  2538  		c.Check(space, gc.Equals, "", gc.Commentf("expected empty space for endpoint %q, got %q", endpoint, space))
  2539  	}
  2540  }
  2541  
  2542  func (s *ApplicationSuite) TestEndpointBindingsJustDefaults(c *gc.C) {
  2543  	// With unspecified bindings, all endpoints are explicitly bound to the
  2544  	// default space when saved in state.
  2545  	ch := s.AddMetaCharm(c, "mysql", metaBase, 42)
  2546  	service := s.AddTestingServiceWithBindings(c, "yoursql", ch, nil)
  2547  
  2548  	s.assertApplicationHasOnlyDefaultEndpointBindings(c, service)
  2549  	s.assertApplicationRemovedWithItsBindings(c, service)
  2550  }
  2551  
  2552  func (s *ApplicationSuite) TestEndpointBindingsWithExplictOverrides(c *gc.C) {
  2553  	_, err := s.State.AddSpace("db", "", nil, true)
  2554  	c.Assert(err, jc.ErrorIsNil)
  2555  	_, err = s.State.AddSpace("ha", "", nil, false)
  2556  	c.Assert(err, jc.ErrorIsNil)
  2557  
  2558  	bindings := map[string]string{
  2559  		"server":  "db",
  2560  		"cluster": "ha",
  2561  	}
  2562  	ch := s.AddMetaCharm(c, "mysql", metaBase, 42)
  2563  	service := s.AddTestingServiceWithBindings(c, "yoursql", ch, bindings)
  2564  
  2565  	setBindings, err := service.EndpointBindings()
  2566  	c.Assert(err, jc.ErrorIsNil)
  2567  	c.Assert(setBindings, jc.DeepEquals, map[string]string{
  2568  		"server":  "db",
  2569  		"client":  "",
  2570  		"cluster": "ha",
  2571  	})
  2572  
  2573  	s.assertApplicationRemovedWithItsBindings(c, service)
  2574  }
  2575  
  2576  func (s *ApplicationSuite) TestSetCharmExtraBindingsUseDefaults(c *gc.C) {
  2577  	_, err := s.State.AddSpace("db", "", nil, true)
  2578  	c.Assert(err, jc.ErrorIsNil)
  2579  
  2580  	oldCharm := s.AddMetaCharm(c, "mysql", metaDifferentProvider, 42)
  2581  	oldBindings := map[string]string{
  2582  		"kludge": "db",
  2583  		"client": "db",
  2584  	}
  2585  	service := s.AddTestingServiceWithBindings(c, "yoursql", oldCharm, oldBindings)
  2586  	setBindings, err := service.EndpointBindings()
  2587  	c.Assert(err, jc.ErrorIsNil)
  2588  	effectiveOld := map[string]string{
  2589  		"kludge":  "db",
  2590  		"client":  "db",
  2591  		"cluster": "",
  2592  	}
  2593  	c.Assert(setBindings, jc.DeepEquals, effectiveOld)
  2594  
  2595  	newCharm := s.AddMetaCharm(c, "mysql", metaExtraEndpoints, 43)
  2596  
  2597  	cfg := state.SetCharmConfig{Charm: newCharm}
  2598  	err = service.SetCharm(cfg)
  2599  	c.Assert(err, jc.ErrorIsNil)
  2600  	setBindings, err = service.EndpointBindings()
  2601  	c.Assert(err, jc.ErrorIsNil)
  2602  	effectiveNew := map[string]string{
  2603  		// These two should be preserved from oldCharm.
  2604  		"client":  "db",
  2605  		"cluster": "",
  2606  		// "kludge" is missing in newMeta, "server" is new and gets the default.
  2607  		"server": "",
  2608  		// All the remaining are new and use the empty default.
  2609  		"foo":  "",
  2610  		"baz":  "",
  2611  		"just": "",
  2612  	}
  2613  	c.Assert(setBindings, jc.DeepEquals, effectiveNew)
  2614  
  2615  	s.assertApplicationRemovedWithItsBindings(c, service)
  2616  }
  2617  
  2618  func (s *ApplicationSuite) TestSetCharmHandlesMissingBindingsAsDefaults(c *gc.C) {
  2619  	oldCharm := s.AddMetaCharm(c, "mysql", metaDifferentProvider, 69)
  2620  	service := s.AddTestingServiceWithBindings(c, "theirsql", oldCharm, nil)
  2621  	state.RemoveEndpointBindingsForService(c, service)
  2622  
  2623  	newCharm := s.AddMetaCharm(c, "mysql", metaExtraEndpoints, 70)
  2624  
  2625  	cfg := state.SetCharmConfig{Charm: newCharm}
  2626  	err := service.SetCharm(cfg)
  2627  	c.Assert(err, jc.ErrorIsNil)
  2628  	setBindings, err := service.EndpointBindings()
  2629  	c.Assert(err, jc.ErrorIsNil)
  2630  	effectiveNew := map[string]string{
  2631  		// The following two exist for both oldCharm and newCharm.
  2632  		"client":  "",
  2633  		"cluster": "",
  2634  		// "kludge" is missing in newMeta, "server" is new and gets the default.
  2635  		"server": "",
  2636  		// All the remaining are new and use the empty default.
  2637  		"foo":  "",
  2638  		"baz":  "",
  2639  		"just": "",
  2640  	}
  2641  	c.Assert(setBindings, jc.DeepEquals, effectiveNew)
  2642  
  2643  	s.assertApplicationRemovedWithItsBindings(c, service)
  2644  }