github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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  	"strings"
    10  	"time"
    11  
    12  	"github.com/juju/charm/v12"
    13  	"github.com/juju/collections/set"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/loggo"
    16  	"github.com/juju/mgo/v3/bson"
    17  	"github.com/juju/mgo/v3/txn"
    18  	"github.com/juju/names/v5"
    19  	jc "github.com/juju/testing/checkers"
    20  	jujutxn "github.com/juju/txn/v3"
    21  	"github.com/juju/version/v2"
    22  	gc "gopkg.in/check.v1"
    23  	"gopkg.in/juju/environschema.v1"
    24  
    25  	"github.com/juju/juju/core/arch"
    26  	corearch "github.com/juju/juju/core/arch"
    27  	corebase "github.com/juju/juju/core/base"
    28  	corecharm "github.com/juju/juju/core/charm"
    29  	"github.com/juju/juju/core/config"
    30  	"github.com/juju/juju/core/constraints"
    31  	"github.com/juju/juju/core/crossmodel"
    32  	"github.com/juju/juju/core/model"
    33  	"github.com/juju/juju/core/network"
    34  	"github.com/juju/juju/core/network/firewall"
    35  	resourcetesting "github.com/juju/juju/core/resources/testing"
    36  	"github.com/juju/juju/core/secrets"
    37  	"github.com/juju/juju/core/status"
    38  	"github.com/juju/juju/state"
    39  	stateerrors "github.com/juju/juju/state/errors"
    40  	"github.com/juju/juju/state/testing"
    41  	statetesting "github.com/juju/juju/state/testing"
    42  	"github.com/juju/juju/storage"
    43  	"github.com/juju/juju/storage/poolmanager"
    44  	"github.com/juju/juju/storage/provider/dummy"
    45  	"github.com/juju/juju/testcharms"
    46  	coretesting "github.com/juju/juju/testing"
    47  	"github.com/juju/juju/testing/factory"
    48  	jujuversion "github.com/juju/juju/version"
    49  )
    50  
    51  type ApplicationSuite struct {
    52  	ConnSuite
    53  
    54  	charm *state.Charm
    55  	mysql *state.Application
    56  }
    57  
    58  var _ = gc.Suite(&ApplicationSuite{})
    59  
    60  func (s *ApplicationSuite) SetUpTest(c *gc.C) {
    61  	s.ConnSuite.SetUpTest(c)
    62  	s.policy.GetConstraintsValidator = func() (constraints.Validator, error) {
    63  		validator := constraints.NewValidator()
    64  		validator.RegisterConflicts([]string{constraints.InstanceType}, []string{constraints.Mem})
    65  		validator.RegisterUnsupported([]string{constraints.CpuPower})
    66  		return validator, nil
    67  	}
    68  	s.charm = s.AddTestingCharm(c, "mysql")
    69  	s.mysql = s.AddTestingApplication(c, "mysql", s.charm)
    70  	// Before we get into the tests, ensure that all the creation events have flowed through the system.
    71  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
    72  }
    73  
    74  func (s *ApplicationSuite) assertNeedsCleanup(c *gc.C) {
    75  	dirty, err := s.State.NeedsCleanup()
    76  	c.Assert(err, jc.ErrorIsNil)
    77  	c.Assert(dirty, jc.IsTrue)
    78  }
    79  
    80  func (s *ApplicationSuite) assertNoCleanup(c *gc.C) {
    81  	dirty, err := s.State.NeedsCleanup()
    82  	c.Assert(err, jc.ErrorIsNil)
    83  	c.Assert(dirty, jc.IsFalse)
    84  }
    85  
    86  func (s *ApplicationSuite) TestSetCharm(c *gc.C) {
    87  	ch, force, err := s.mysql.Charm()
    88  	c.Assert(err, jc.ErrorIsNil)
    89  	c.Assert(ch.URL(), gc.DeepEquals, s.charm.URL())
    90  	c.Assert(force, jc.IsFalse)
    91  	url, force := s.mysql.CharmURL()
    92  	c.Assert(*url, gc.DeepEquals, s.charm.URL())
    93  	c.Assert(force, jc.IsFalse)
    94  
    95  	// Add a compatible charm and force it.
    96  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2)
    97  
    98  	cfg := state.SetCharmConfig{
    99  		Charm:       sch,
   100  		CharmOrigin: defaultCharmOrigin(sch.URL()),
   101  		ForceUnits:  true,
   102  	}
   103  	err = s.mysql.SetCharm(cfg)
   104  	c.Assert(err, jc.ErrorIsNil)
   105  	ch, force, err = s.mysql.Charm()
   106  	c.Assert(err, jc.ErrorIsNil)
   107  	c.Assert(ch.URL(), gc.DeepEquals, sch.URL())
   108  	c.Assert(force, jc.IsTrue)
   109  	url, force = s.mysql.CharmURL()
   110  	c.Assert(*url, gc.DeepEquals, sch.URL())
   111  	c.Assert(force, jc.IsTrue)
   112  }
   113  
   114  func (s *ApplicationSuite) TestSetCharmCharmOrigin(c *gc.C) {
   115  	// Add a compatible charm.
   116  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2)
   117  	rev := sch.Revision()
   118  	origin := &state.CharmOrigin{
   119  		Source:   "charm-hub",
   120  		Revision: &rev,
   121  		Channel:  &state.Channel{Risk: "stable"},
   122  		Platform: &state.Platform{
   123  			OS:      "ubuntu",
   124  			Channel: "22.04/stable",
   125  		},
   126  	}
   127  	cfg := state.SetCharmConfig{
   128  		Charm:       sch,
   129  		CharmOrigin: origin,
   130  	}
   131  	err := s.mysql.SetCharm(cfg)
   132  	c.Assert(err, jc.ErrorIsNil)
   133  	err = s.mysql.Refresh()
   134  	c.Assert(err, jc.ErrorIsNil)
   135  	obtainedOrigin := s.mysql.CharmOrigin()
   136  	c.Assert(obtainedOrigin, gc.DeepEquals, origin)
   137  }
   138  
   139  func (s *ApplicationSuite) TestSetCharmUpdateChannelURLNoChange(c *gc.C) {
   140  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2)
   141  
   142  	origin := defaultCharmOrigin(sch.URL())
   143  	// This is a workaround, AddCharm creates a local
   144  	// charm, which cannot have a channel in the CharmOrigin.
   145  	// However, we need to test changing the channel only.
   146  	origin.Source = "charm-hub"
   147  	origin.Channel = &state.Channel{
   148  		Risk: "stable",
   149  	}
   150  	cfg := state.SetCharmConfig{
   151  		Charm:       sch,
   152  		CharmOrigin: origin,
   153  	}
   154  	err := s.mysql.SetCharm(cfg)
   155  	c.Assert(err, jc.ErrorIsNil)
   156  	mOrigin := s.mysql.CharmOrigin()
   157  	c.Assert(mOrigin.Channel, gc.NotNil)
   158  	c.Assert(mOrigin.Channel.Risk, gc.DeepEquals, "stable")
   159  
   160  	cfg.CharmOrigin.Channel.Risk = "candidate"
   161  	err = s.mysql.SetCharm(cfg)
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	c.Assert(s.mysql.CharmOrigin().Channel.Risk, gc.DeepEquals, "candidate")
   164  }
   165  
   166  func (s *ApplicationSuite) TestLXDProfileSetCharm(c *gc.C) {
   167  	charm := s.AddTestingCharm(c, "lxd-profile")
   168  	app := s.AddTestingApplication(c, "lxd-profile", charm)
   169  
   170  	c.Assert(charm.LXDProfile(), gc.NotNil)
   171  
   172  	ch, force, err := app.Charm()
   173  	c.Assert(err, jc.ErrorIsNil)
   174  	c.Assert(ch.URL(), gc.DeepEquals, charm.URL())
   175  	c.Assert(force, jc.IsFalse)
   176  	c.Assert(charm.LXDProfile(), gc.DeepEquals, ch.LXDProfile())
   177  
   178  	url, force := app.CharmURL()
   179  	c.Assert(*url, gc.DeepEquals, charm.URL())
   180  	c.Assert(force, jc.IsFalse)
   181  
   182  	sch := s.AddMetaCharm(c, "lxd-profile", lxdProfileMetaBase, 2)
   183  
   184  	cfg := state.SetCharmConfig{
   185  		Charm:       sch,
   186  		CharmOrigin: defaultCharmOrigin(ch.URL()),
   187  		ForceUnits:  true,
   188  	}
   189  	err = app.SetCharm(cfg)
   190  	c.Assert(err, jc.ErrorIsNil)
   191  	ch, force, err = app.Charm()
   192  	c.Assert(err, jc.ErrorIsNil)
   193  	c.Assert(ch.URL(), gc.DeepEquals, sch.URL())
   194  	c.Assert(force, jc.IsTrue)
   195  	url, force = app.CharmURL()
   196  	c.Assert(*url, gc.DeepEquals, sch.URL())
   197  	c.Assert(force, jc.IsTrue)
   198  	c.Assert(charm.LXDProfile(), gc.DeepEquals, ch.LXDProfile())
   199  }
   200  
   201  func (s *ApplicationSuite) TestLXDProfileFailSetCharm(c *gc.C) {
   202  	charm := s.AddTestingCharm(c, "lxd-profile-fail")
   203  	app := s.AddTestingApplication(c, "lxd-profile-fail", charm)
   204  
   205  	c.Assert(charm.LXDProfile(), gc.NotNil)
   206  
   207  	ch, force, err := app.Charm()
   208  	c.Assert(err, jc.ErrorIsNil)
   209  	c.Assert(ch.URL(), gc.DeepEquals, charm.URL())
   210  	c.Assert(force, jc.IsFalse)
   211  	c.Assert(charm.LXDProfile(), gc.DeepEquals, ch.LXDProfile())
   212  
   213  	url, force := app.CharmURL()
   214  	c.Assert(*url, gc.DeepEquals, charm.URL())
   215  	c.Assert(force, jc.IsFalse)
   216  
   217  	sch := s.AddMetaCharm(c, "lxd-profile-fail", lxdProfileMetaBase, 2)
   218  
   219  	cfg := state.SetCharmConfig{
   220  		Charm:       sch,
   221  		CharmOrigin: defaultCharmOrigin(ch.URL()),
   222  		ForceUnits:  true,
   223  	}
   224  	err = app.SetCharm(cfg)
   225  	c.Assert(err, gc.ErrorMatches, ".*validating lxd profile: invalid lxd-profile\\.yaml.*")
   226  }
   227  
   228  func (s *ApplicationSuite) TestLXDProfileFailWithForceSetCharm(c *gc.C) {
   229  	charm := s.AddTestingCharm(c, "lxd-profile-fail")
   230  	app := s.AddTestingApplication(c, "lxd-profile-fail", charm)
   231  
   232  	c.Assert(charm.LXDProfile(), gc.NotNil)
   233  
   234  	ch, force, err := app.Charm()
   235  	c.Assert(err, jc.ErrorIsNil)
   236  	c.Assert(ch.URL(), gc.DeepEquals, charm.URL())
   237  	c.Assert(force, jc.IsFalse)
   238  	c.Assert(charm.LXDProfile(), gc.DeepEquals, ch.LXDProfile())
   239  
   240  	url, force := app.CharmURL()
   241  	c.Assert(*url, gc.DeepEquals, charm.URL())
   242  	c.Assert(force, jc.IsFalse)
   243  
   244  	sch := s.AddMetaCharm(c, "lxd-profile-fail", lxdProfileMetaBase, 2)
   245  
   246  	cfg := state.SetCharmConfig{
   247  		Charm:       sch,
   248  		CharmOrigin: defaultCharmOrigin(ch.URL()),
   249  		Force:       true,
   250  		ForceUnits:  true,
   251  	}
   252  	err = app.SetCharm(cfg)
   253  	c.Assert(err, jc.ErrorIsNil)
   254  	ch, force, err = app.Charm()
   255  	c.Assert(err, jc.ErrorIsNil)
   256  	c.Assert(ch.URL(), gc.DeepEquals, sch.URL())
   257  	c.Assert(force, jc.IsTrue)
   258  	url, force = app.CharmURL()
   259  	c.Assert(*url, gc.DeepEquals, sch.URL())
   260  	c.Assert(force, jc.IsTrue)
   261  	c.Assert(charm.LXDProfile(), gc.DeepEquals, ch.LXDProfile())
   262  }
   263  
   264  func (s *ApplicationSuite) TestCAASSetCharm(c *gc.C) {
   265  	st := s.Factory.MakeModel(c, &factory.ModelParams{
   266  		Name: "caas-model",
   267  		Type: state.ModelTypeCAAS,
   268  	})
   269  	defer st.Close()
   270  	f := factory.NewFactory(st, s.StatePool)
   271  	ch := f.MakeCharm(c, &factory.CharmParams{Name: "mysql", Series: "kubernetes"})
   272  	app := f.MakeApplication(c, &factory.ApplicationParams{Name: "mysql", Charm: ch})
   273  
   274  	// Add a compatible charm and force it.
   275  	sch := state.AddCustomCharm(c, st, "mysql", "metadata.yaml", metaBaseCAAS, "kubernetes", 2)
   276  
   277  	cfg := state.SetCharmConfig{
   278  		Charm:       sch,
   279  		CharmOrigin: defaultCharmOrigin(ch.URL()),
   280  		ForceUnits:  true,
   281  	}
   282  	err := app.SetCharm(cfg)
   283  	c.Assert(err, jc.ErrorIsNil)
   284  	ch, force, err := app.Charm()
   285  	c.Assert(err, jc.ErrorIsNil)
   286  	c.Assert(ch.URL(), gc.DeepEquals, sch.URL())
   287  	c.Assert(force, jc.IsTrue)
   288  }
   289  
   290  func (s *ApplicationSuite) TestCAASSetCharmRequireNoUnits(c *gc.C) {
   291  	st := s.Factory.MakeModel(c, &factory.ModelParams{
   292  		Name: "caas-model",
   293  		Type: state.ModelTypeCAAS,
   294  	})
   295  	defer st.Close()
   296  	f := factory.NewFactory(st, s.StatePool)
   297  	ch := f.MakeCharm(c, &factory.CharmParams{Name: "mysql", Series: "kubernetes"})
   298  	app := f.MakeApplication(c, &factory.ApplicationParams{Name: "mysql", Charm: ch, DesiredScale: 1})
   299  
   300  	// Add a compatible charm and force it.
   301  	sch := state.AddCustomCharm(c, st, "mysql", "metadata.yaml", metaBaseCAAS, "kubernetes", 2)
   302  
   303  	cfg := state.SetCharmConfig{
   304  		Charm:          sch,
   305  		CharmOrigin:    defaultCharmOrigin(ch.URL()),
   306  		ForceUnits:     true,
   307  		RequireNoUnits: true,
   308  	}
   309  	err := app.SetCharm(cfg)
   310  	c.Assert(err, gc.ErrorMatches, `.*application should not have units`)
   311  }
   312  
   313  func (s *ApplicationSuite) TestCAASSetCharmNewDeploymentFails(c *gc.C) {
   314  	st := s.Factory.MakeModel(c, &factory.ModelParams{
   315  		Name: "caas-model",
   316  		Type: state.ModelTypeCAAS,
   317  	})
   318  	defer st.Close()
   319  	f := factory.NewFactory(st, s.StatePool)
   320  	ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"})
   321  	app := f.MakeApplication(c, &factory.ApplicationParams{Name: "gitlab", Charm: ch})
   322  
   323  	// Create a charm with new deployment info in metadata.
   324  	metaYaml := `
   325  name: gitlab
   326  summary: test
   327  description: test
   328  provides:
   329    website:
   330      interface: http
   331  requires:
   332    db:
   333      interface: mysql
   334  series:
   335    - kubernetes
   336  deployment:
   337    type: stateful
   338    service: loadbalancer
   339  `[1:]
   340  	newCh := state.AddCustomCharm(c, st, "gitlab", "metadata.yaml", metaYaml, "kubernetes", 2)
   341  	cfg := state.SetCharmConfig{
   342  		Charm:       newCh,
   343  		CharmOrigin: defaultCharmOrigin(newCh.URL()),
   344  		ForceUnits:  true,
   345  	}
   346  	err := app.SetCharm(cfg)
   347  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "gitlab" to charm "local:kubernetes/kubernetes-gitlab-2": cannot change a charm's deployment info`)
   348  }
   349  
   350  func (s *ApplicationSuite) TestCAASSetCharmNewDeploymentTypeFails(c *gc.C) {
   351  	st := s.Factory.MakeModel(c, &factory.ModelParams{
   352  		Name: "caas-model",
   353  		Type: state.ModelTypeCAAS,
   354  	})
   355  	defer st.Close()
   356  	f := factory.NewFactory(st, s.StatePool)
   357  	ch := f.MakeCharm(c, &factory.CharmParams{Name: "elastic-operator", Series: "kubernetes"})
   358  	app := f.MakeApplication(c, &factory.ApplicationParams{Name: "elastic-operator", Charm: ch})
   359  
   360  	// Create a charm with new deployment info in metadata.
   361  	metaYaml := `
   362  name: elastic-operator
   363  summary: test
   364  description: test
   365  provides:
   366    website:
   367      interface: http
   368  requires:
   369    db:
   370      interface: mysql
   371  series:
   372    - kubernetes
   373  deployment:
   374    type: stateful
   375    service: loadbalancer
   376  `[1:]
   377  	newCh := state.AddCustomCharm(c, st, "elastic-operator", "metadata.yaml", metaYaml, "kubernetes", 2)
   378  	cfg := state.SetCharmConfig{
   379  		Charm:       newCh,
   380  		CharmOrigin: defaultCharmOrigin(newCh.URL()),
   381  		ForceUnits:  true,
   382  	}
   383  	err := app.SetCharm(cfg)
   384  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "elastic-operator" to charm "local:kubernetes/kubernetes-elastic-operator-2": cannot change a charm's deployment type`)
   385  }
   386  
   387  func (s *ApplicationSuite) TestCAASSetCharmNewDeploymentModeFails(c *gc.C) {
   388  	st := s.Factory.MakeModel(c, &factory.ModelParams{
   389  		Name: "caas-model",
   390  		Type: state.ModelTypeCAAS,
   391  	})
   392  	defer st.Close()
   393  	f := factory.NewFactory(st, s.StatePool)
   394  	ch := f.MakeCharm(c, &factory.CharmParams{Name: "elastic-operator", Series: "kubernetes"})
   395  	app := f.MakeApplication(c, &factory.ApplicationParams{Name: "elastic-operator", Charm: ch})
   396  
   397  	// Create a charm with new deployment info in metadata.
   398  	metaYaml := `
   399  name: elastic-operator
   400  summary: test
   401  description: test
   402  provides:
   403    website:
   404      interface: http
   405  requires:
   406    db:
   407      interface: mysql
   408  series:
   409    - kubernetes
   410  deployment:
   411    mode: workload
   412  `[1:]
   413  	newCh := state.AddCustomCharm(c, st, "elastic-operator", "metadata.yaml", metaYaml, "kubernetes", 2)
   414  	cfg := state.SetCharmConfig{
   415  		Charm:       newCh,
   416  		CharmOrigin: defaultCharmOrigin(newCh.URL()),
   417  		ForceUnits:  true,
   418  	}
   419  	err := app.SetCharm(cfg)
   420  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "elastic-operator" to charm "local:kubernetes/kubernetes-elastic-operator-2": cannot change a charm's deployment mode`)
   421  }
   422  
   423  func (s *ApplicationSuite) TestSetCharmWithNewBindings(c *gc.C) {
   424  	sp := s.assignUnitOnMachineWithSpaceToApplication(c, s.mysql, "isolated")
   425  	sch := s.AddMetaCharm(c, "mysql", metaBaseWithNewEndpoint, 2)
   426  
   427  	// Assign new charm endpoint to "isolated" space
   428  	cfg := state.SetCharmConfig{
   429  		Charm:       sch,
   430  		CharmOrigin: defaultCharmOrigin(sch.URL()),
   431  		ForceUnits:  true,
   432  		EndpointBindings: map[string]string{
   433  			"events": sp.Name(),
   434  		},
   435  	}
   436  	err := s.mysql.SetCharm(cfg)
   437  	c.Assert(err, jc.ErrorIsNil)
   438  
   439  	expBindings := map[string]string{
   440  		"":        network.AlphaSpaceId,
   441  		"server":  network.AlphaSpaceId,
   442  		"client":  network.AlphaSpaceId,
   443  		"cluster": network.AlphaSpaceId,
   444  		"events":  sp.Id(),
   445  	}
   446  
   447  	updatedBindings, err := s.mysql.EndpointBindings()
   448  	c.Assert(err, jc.ErrorIsNil)
   449  	c.Assert(updatedBindings.Map(), gc.DeepEquals, expBindings)
   450  }
   451  
   452  func (s *ApplicationSuite) TestMergeBindings(c *gc.C) {
   453  	s.assignUnitOnMachineWithSpaceToApplication(c, s.mysql, "isolated")
   454  
   455  	expBindings := map[string]string{
   456  		"":               network.AlphaSpaceName,
   457  		"metrics-client": network.AlphaSpaceName,
   458  		"server":         network.AlphaSpaceName,
   459  		"server-admin":   network.AlphaSpaceName,
   460  	}
   461  	b, err := s.mysql.EndpointBindings()
   462  	c.Assert(err, jc.ErrorIsNil)
   463  
   464  	allSpaceInfosLookup, err := s.State.AllSpaceInfos()
   465  	c.Assert(err, jc.ErrorIsNil)
   466  
   467  	curBindings, err := b.MapWithSpaceNames(allSpaceInfosLookup)
   468  	c.Assert(err, jc.ErrorIsNil)
   469  	c.Assert(curBindings, gc.DeepEquals, expBindings)
   470  
   471  	// Use MergeBindings to bind "server" -> "isolated"
   472  	b, err = state.NewBindings(s.State, map[string]string{
   473  		"server": "isolated",
   474  	})
   475  	c.Assert(err, jc.ErrorIsNil)
   476  
   477  	err = s.mysql.MergeBindings(b, false)
   478  	c.Assert(err, jc.ErrorIsNil)
   479  
   480  	// Check that the bindings have been updated
   481  	expBindings["server"] = "isolated"
   482  	b, err = s.mysql.EndpointBindings()
   483  	c.Assert(err, jc.ErrorIsNil)
   484  	updatedBindings, err := b.MapWithSpaceNames(allSpaceInfosLookup)
   485  	c.Assert(err, jc.ErrorIsNil)
   486  	c.Assert(updatedBindings, gc.DeepEquals, expBindings)
   487  }
   488  
   489  func (s *ApplicationSuite) TestMergeBindingsWithForce(c *gc.C) {
   490  	s.assignUnitOnMachineWithSpaceToApplication(c, s.mysql, "isolated")
   491  
   492  	sn, err := s.State.AddSubnet(network.SubnetInfo{CIDR: "10.99.99.0/24"})
   493  	c.Assert(err, gc.IsNil)
   494  	_, err = s.State.AddSpace("far", "", []string{sn.ID()}, false)
   495  	c.Assert(err, gc.IsNil)
   496  
   497  	expBindings := map[string]string{
   498  		"":               network.AlphaSpaceName,
   499  		"metrics-client": network.AlphaSpaceName,
   500  		"server":         network.AlphaSpaceName,
   501  		"server-admin":   network.AlphaSpaceName,
   502  	}
   503  	b, err := s.mysql.EndpointBindings()
   504  	c.Assert(err, jc.ErrorIsNil)
   505  
   506  	allSpaceInfosLookup, err := s.State.AllSpaceInfos()
   507  	c.Assert(err, jc.ErrorIsNil)
   508  
   509  	curBindings, err := b.MapWithSpaceNames(allSpaceInfosLookup)
   510  	c.Assert(err, jc.ErrorIsNil)
   511  	c.Assert(curBindings, gc.DeepEquals, expBindings)
   512  
   513  	// Use MergeBindings to force-bind "server" -> "far"
   514  	b, err = state.NewBindings(s.State, map[string]string{
   515  		"server": "far",
   516  	})
   517  	c.Assert(err, jc.ErrorIsNil)
   518  
   519  	err = s.mysql.MergeBindings(b, true)
   520  	c.Assert(err, jc.ErrorIsNil)
   521  
   522  	// Check that the bindings have been updated
   523  	expBindings["server"] = "far"
   524  	b, err = s.mysql.EndpointBindings()
   525  	c.Assert(err, jc.ErrorIsNil)
   526  	updatedBindings, err := b.MapWithSpaceNames(allSpaceInfosLookup)
   527  	c.Assert(err, jc.ErrorIsNil)
   528  	c.Assert(updatedBindings, gc.DeepEquals, expBindings)
   529  }
   530  
   531  func (s *ApplicationSuite) TestSetCharmWithNewBindingsAssigneToDefaultSpace(c *gc.C) {
   532  	_ = s.assignUnitOnMachineWithSpaceToApplication(c, s.mysql, "isolated")
   533  	sch := s.AddMetaCharm(c, "mysql", metaBaseWithNewEndpoint, 2)
   534  
   535  	// New charm endpoint should be auto-assigned to default space if not
   536  	// explicitly bound by the operator.
   537  	cfg := state.SetCharmConfig{
   538  		Charm:       sch,
   539  		CharmOrigin: defaultCharmOrigin(sch.URL()),
   540  		ForceUnits:  true,
   541  	}
   542  	err := s.mysql.SetCharm(cfg)
   543  	c.Assert(err, jc.ErrorIsNil)
   544  
   545  	expBindings := map[string]string{
   546  		"":        network.AlphaSpaceId,
   547  		"server":  network.AlphaSpaceId,
   548  		"client":  network.AlphaSpaceId,
   549  		"cluster": network.AlphaSpaceId,
   550  		"events":  network.AlphaSpaceId,
   551  	}
   552  
   553  	updatedBindings, err := s.mysql.EndpointBindings()
   554  	c.Assert(err, jc.ErrorIsNil)
   555  	c.Assert(updatedBindings.Map(), gc.DeepEquals, expBindings)
   556  }
   557  
   558  func (s *ApplicationSuite) assignUnitOnMachineWithSpaceToApplication(c *gc.C, a *state.Application, spaceName string) *state.Space {
   559  	sn1, err := s.State.AddSubnet(network.SubnetInfo{CIDR: "10.0.254.0/24"})
   560  	c.Assert(err, gc.IsNil)
   561  
   562  	sp, err := s.State.AddSpace(spaceName, "", []string{sn1.ID()}, false)
   563  	c.Assert(err, gc.IsNil)
   564  
   565  	m1, err := s.State.AddOneMachine(state.MachineTemplate{
   566  		Base:        state.UbuntuBase("12.10"),
   567  		Jobs:        []state.MachineJob{state.JobHostUnits},
   568  		Constraints: constraints.MustParse("spaces=isolated"),
   569  	})
   570  	c.Assert(err, gc.IsNil)
   571  	err = m1.SetLinkLayerDevices(state.LinkLayerDeviceArgs{
   572  		Name: "enp5s0",
   573  		Type: network.EthernetDevice,
   574  	})
   575  	c.Assert(err, gc.IsNil)
   576  	err = m1.SetDevicesAddresses(state.LinkLayerDeviceAddress{
   577  		DeviceName:   "enp5s0",
   578  		CIDRAddress:  "10.0.254.42/24",
   579  		ConfigMethod: network.ConfigStatic,
   580  	})
   581  	c.Assert(err, gc.IsNil)
   582  
   583  	u1, err := a.AddUnit(state.AddUnitParams{})
   584  	c.Assert(err, gc.IsNil)
   585  	err = u1.AssignToMachine(m1)
   586  	c.Assert(err, gc.IsNil)
   587  
   588  	return sp
   589  }
   590  
   591  func (s *ApplicationSuite) combinedSettings(ch *state.Charm, inSettings charm.Settings) charm.Settings {
   592  	result := ch.Config().DefaultSettings()
   593  	for name, value := range inSettings {
   594  		result[name] = value
   595  	}
   596  	return result
   597  }
   598  
   599  func (s *ApplicationSuite) TestSetCharmCharmSettings(c *gc.C) {
   600  	newCh := s.AddConfigCharm(c, "mysql", stringConfig, 2)
   601  	err := s.mysql.SetCharm(state.SetCharmConfig{
   602  		Charm:          newCh,
   603  		CharmOrigin:    defaultCharmOrigin(newCh.URL()),
   604  		ConfigSettings: charm.Settings{"key": "value"},
   605  	})
   606  	c.Assert(err, jc.ErrorIsNil)
   607  
   608  	cfg, err := s.mysql.CharmConfig(model.GenerationMaster)
   609  	c.Assert(err, jc.ErrorIsNil)
   610  	c.Assert(cfg, jc.DeepEquals, s.combinedSettings(newCh, charm.Settings{"key": "value"}))
   611  
   612  	newCh = s.AddConfigCharm(c, "mysql", newStringConfig, 3)
   613  	err = s.mysql.SetCharm(state.SetCharmConfig{
   614  		Charm:          newCh,
   615  		CharmOrigin:    defaultCharmOrigin(newCh.URL()),
   616  		ConfigSettings: charm.Settings{"other": "one"},
   617  	})
   618  	c.Assert(err, jc.ErrorIsNil)
   619  
   620  	cfg, err = s.mysql.CharmConfig(model.GenerationMaster)
   621  	c.Assert(err, jc.ErrorIsNil)
   622  	c.Assert(cfg, jc.DeepEquals, s.combinedSettings(newCh, charm.Settings{
   623  		"key":   "value",
   624  		"other": "one",
   625  	}))
   626  }
   627  
   628  func (s *ApplicationSuite) TestSetCharmCharmSettingsForBranch(c *gc.C) {
   629  	c.Assert(s.State.AddBranch("new-branch", "branch-user"), jc.ErrorIsNil)
   630  
   631  	newCh := s.AddConfigCharm(c, "mysql", stringConfig, 2)
   632  	err := s.mysql.SetCharm(state.SetCharmConfig{
   633  		Charm:          newCh,
   634  		CharmOrigin:    defaultCharmOrigin(newCh.URL()),
   635  		ConfigSettings: charm.Settings{"key": "value"},
   636  	})
   637  	c.Assert(err, jc.ErrorIsNil)
   638  
   639  	cfg, err := s.mysql.CharmConfig(model.GenerationMaster)
   640  	c.Assert(err, jc.ErrorIsNil)
   641  
   642  	// Update the next generation settings.
   643  	cfg["key"] = "next-gen-value"
   644  	c.Assert(s.mysql.UpdateCharmConfig("new-branch", cfg), jc.ErrorIsNil)
   645  
   646  	// Settings for the next generation reflect the change.
   647  	cfg, err = s.mysql.CharmConfig("new-branch")
   648  	c.Assert(err, jc.ErrorIsNil)
   649  	c.Assert(cfg, jc.DeepEquals, s.combinedSettings(newCh, charm.Settings{
   650  		"key": "next-gen-value",
   651  	}))
   652  
   653  	// Settings for the current generation are as set with charm.
   654  	cfg, err = s.mysql.CharmConfig(model.GenerationMaster)
   655  	c.Assert(err, jc.ErrorIsNil)
   656  	c.Assert(cfg, jc.DeepEquals, s.combinedSettings(newCh, charm.Settings{
   657  		"key": "value",
   658  	}))
   659  }
   660  
   661  func (s *ApplicationSuite) TestSetCharmCharmSettingsInvalid(c *gc.C) {
   662  	newCh := s.AddConfigCharm(c, "mysql", stringConfig, 2)
   663  	err := s.mysql.SetCharm(state.SetCharmConfig{
   664  		Charm:          newCh,
   665  		CharmOrigin:    defaultCharmOrigin(newCh.URL()),
   666  		ConfigSettings: charm.Settings{"key": 123.45},
   667  	})
   668  	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`)
   669  }
   670  
   671  func (s *ApplicationSuite) TestClientApplicationSetCharmUnsupportedSeries(c *gc.C) {
   672  	ch := state.AddTestingCharmMultiSeries(c, s.State, "multi-series")
   673  	app := state.AddTestingApplicationForBase(c, s.State, state.UbuntuBase("12.04"), "application", ch)
   674  
   675  	chDifferentSeries := state.AddTestingCharmMultiSeries(c, s.State, "multi-series2")
   676  	cfg := state.SetCharmConfig{
   677  		Charm:       chDifferentSeries,
   678  		CharmOrigin: defaultCharmOrigin(chDifferentSeries.URL()),
   679  	}
   680  	err := app.SetCharm(cfg)
   681  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "ch:multi-series2-8": base "ubuntu@12.04" not supported by charm, the charm supported bases are: ubuntu@14.04, ubuntu@15.10`)
   682  }
   683  
   684  func (s *ApplicationSuite) TestClientApplicationSetCharmUnsupportedSeriesForce(c *gc.C) {
   685  	ch := state.AddTestingCharmMultiSeries(c, s.State, "multi-series")
   686  	app := state.AddTestingApplicationForBase(c, s.State, state.UbuntuBase("12.04"), "application", ch)
   687  
   688  	chDifferentSeries := state.AddTestingCharmMultiSeries(c, s.State, "multi-series2")
   689  	cfg := state.SetCharmConfig{
   690  		Charm:       chDifferentSeries,
   691  		CharmOrigin: defaultCharmOrigin(chDifferentSeries.URL()),
   692  		ForceBase:   true,
   693  	}
   694  	err := app.SetCharm(cfg)
   695  	c.Assert(err, jc.ErrorIsNil)
   696  	app, err = s.State.Application("application")
   697  	c.Assert(err, jc.ErrorIsNil)
   698  	ch, _, err = app.Charm()
   699  	c.Assert(err, jc.ErrorIsNil)
   700  	c.Assert(ch.URL(), gc.Equals, "ch:multi-series2-8")
   701  }
   702  
   703  func (s *ApplicationSuite) TestClientApplicationSetCharmWrongOS(c *gc.C) {
   704  	ch := state.AddTestingCharmMultiSeries(c, s.State, "multi-series")
   705  	app := state.AddTestingApplicationForBase(c, s.State, state.UbuntuBase("12.04"), "application", ch)
   706  
   707  	chDifferentSeries := state.AddTestingCharmMultiSeries(c, s.State, "multi-series-centos")
   708  	cfg := state.SetCharmConfig{
   709  		Charm:       chDifferentSeries,
   710  		CharmOrigin: defaultCharmOrigin(chDifferentSeries.URL()),
   711  		ForceBase:   true,
   712  	}
   713  	err := app.SetCharm(cfg)
   714  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "ch:multi-series-centos-1": OS "ubuntu" not supported by charm.*`)
   715  }
   716  
   717  func (s *ApplicationSuite) TestSetCharmPreconditions(c *gc.C) {
   718  	logging := s.AddTestingCharm(c, "logging")
   719  	cfg := state.SetCharmConfig{
   720  		Charm:       logging,
   721  		CharmOrigin: defaultCharmOrigin(logging.URL()),
   722  	}
   723  	err := s.mysql.SetCharm(cfg)
   724  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "mysql" to charm "local:quantal/quantal-logging-1": cannot change an application's subordinacy`)
   725  }
   726  
   727  func (s *ApplicationSuite) TestSetCharmUpdatesBindings(c *gc.C) {
   728  	dbSpace, err := s.State.AddSpace("db", "", nil, false)
   729  	c.Assert(err, jc.ErrorIsNil)
   730  	clientSpace, err := s.State.AddSpace("client", "", nil, true)
   731  	c.Assert(err, jc.ErrorIsNil)
   732  	oldCharm := s.AddMetaCharm(c, "mysql", metaBase, 44)
   733  
   734  	application, err := s.State.AddApplication(state.AddApplicationArgs{
   735  		Name:  "yoursql",
   736  		Charm: oldCharm,
   737  		CharmOrigin: &state.CharmOrigin{Platform: &state.Platform{
   738  			OS:      "ubuntu",
   739  			Channel: "12.10/stable",
   740  		}},
   741  		EndpointBindings: map[string]string{
   742  			"":       dbSpace.Id(),
   743  			"server": dbSpace.Id(),
   744  			"client": clientSpace.Id(),
   745  		}})
   746  	c.Assert(err, jc.ErrorIsNil)
   747  
   748  	newCharm := s.AddMetaCharm(c, "mysql", metaExtraEndpoints, 43)
   749  	cfg := state.SetCharmConfig{
   750  		Charm:       newCharm,
   751  		CharmOrigin: defaultCharmOrigin(newCharm.URL()),
   752  	}
   753  	err = application.SetCharm(cfg)
   754  	c.Assert(err, jc.ErrorIsNil)
   755  	updatedBindings, err := application.EndpointBindings()
   756  	c.Assert(err, jc.ErrorIsNil)
   757  	c.Assert(updatedBindings.Map(), jc.DeepEquals, map[string]string{
   758  		// Existing bindings are preserved.
   759  		"":        dbSpace.Id(),
   760  		"server":  dbSpace.Id(),
   761  		"client":  clientSpace.Id(),
   762  		"cluster": dbSpace.Id(), // inherited from defaults in AddApplication.
   763  		// New endpoints use defaults.
   764  		"foo":  dbSpace.Id(),
   765  		"baz":  dbSpace.Id(),
   766  		"just": dbSpace.Id(),
   767  	})
   768  }
   769  
   770  var metaRelationConsumer = `
   771  name: sqlvampire
   772  summary: "connects to an sql server"
   773  description: "lorem ipsum"
   774  requires:
   775    server: mysql
   776  `
   777  var metaBase = `
   778  name: mysql
   779  summary: "Fake MySQL Database engine"
   780  description: "Complete with nonsense relations"
   781  provides:
   782    server: mysql
   783  requires:
   784    client: mysql
   785  peers:
   786    cluster: mysql
   787  `
   788  var metaBaseCAAS = `
   789  name: mysql
   790  summary: "Fake MySQL Database engine"
   791  description: "Complete with nonsense relations"
   792  provides:
   793    server: mysql
   794  requires:
   795    client: mysql
   796  peers:
   797    cluster: mysql
   798  `
   799  var metaBaseWithNewEndpoint = `
   800  name: mysql
   801  summary: "Fake MySQL Database engine"
   802  description: "Complete with nonsense relations"
   803  provides:
   804    server: mysql
   805  requires:
   806    client: mysql
   807  peers:
   808    cluster: mysql
   809  extra-bindings:
   810    events:
   811  `
   812  var metaDifferentProvider = `
   813  name: mysql
   814  description: none
   815  summary: none
   816  provides:
   817    server: mysql
   818    kludge: mysql
   819  requires:
   820    client: mysql
   821  peers:
   822    cluster: mysql
   823  `
   824  var metaDifferentRequirer = `
   825  name: mysql
   826  description: none
   827  summary: none
   828  provides:
   829    server: mysql
   830  requires:
   831    kludge: mysql
   832  peers:
   833    cluster: mysql
   834  `
   835  var metaDifferentPeer = `
   836  name: mysql
   837  description: none
   838  summary: none
   839  provides:
   840    server: mysql
   841  requires:
   842    client: mysql
   843  peers:
   844    kludge: mysql
   845  `
   846  var metaRemoveNonPeerRelation = `
   847  name: mysql
   848  summary: "Fake MySQL Database engine"
   849  description: "Complete with nonsense relations"
   850  requires:
   851    client: mysql
   852  peers:
   853    cluster: mysql
   854  `
   855  var metaExtraEndpoints = `
   856  name: mysql
   857  description: none
   858  summary: none
   859  provides:
   860    server: mysql
   861    foo: bar
   862  requires:
   863    client: mysql
   864    baz: woot
   865  peers:
   866    cluster: mysql
   867    just: me
   868  `
   869  var lxdProfileMetaBase = `
   870  name: lxd-profile
   871  summary: "Fake LXDProfile"
   872  description: "Fake description"
   873  `
   874  
   875  var setCharmEndpointsTests = []struct {
   876  	summary string
   877  	meta    string
   878  	err     string
   879  }{{
   880  	summary: "different provider (but no relation yet)",
   881  	meta:    metaDifferentProvider,
   882  }, {
   883  	summary: "different requirer (but no relation yet)",
   884  	meta:    metaDifferentRequirer,
   885  }, {
   886  	summary: "different peer",
   887  	meta:    metaDifferentPeer,
   888  }, {
   889  	summary: "attempt to break existing non-peer relations",
   890  	meta:    metaRemoveNonPeerRelation,
   891  	err:     `.*would break relation "fakeother:server fakemysql:server"`,
   892  }, {
   893  	summary: "same relations ok",
   894  	meta:    metaBase,
   895  }, {
   896  	summary: "extra endpoints ok",
   897  	meta:    metaExtraEndpoints,
   898  }}
   899  
   900  func (s *ApplicationSuite) TestSetCharmChecksEndpointsWithoutRelations(c *gc.C) {
   901  	revno := 2
   902  	ms := s.AddMetaCharm(c, "mysql", metaBase, revno)
   903  	app := s.AddTestingApplication(c, "fakemysql", ms)
   904  	appServerEP, err := app.Endpoint("server")
   905  	c.Assert(err, jc.ErrorIsNil)
   906  
   907  	otherCharm := s.AddMetaCharm(c, "dummy", metaRelationConsumer, 42)
   908  	otherApp := s.AddTestingApplication(c, "fakeother", otherCharm)
   909  	otherServerEP, err := otherApp.Endpoint("server")
   910  	c.Assert(err, jc.ErrorIsNil)
   911  
   912  	// Add two mysql units so that peer relations get established and we
   913  	// can check that we are allowed to break them when we upgrade.
   914  	_, err = app.AddUnit(state.AddUnitParams{})
   915  	c.Assert(err, jc.ErrorIsNil)
   916  	_, err = app.AddUnit(state.AddUnitParams{})
   917  	c.Assert(err, jc.ErrorIsNil)
   918  
   919  	// Add a unit for the other application and establish a relation.
   920  	_, err = otherApp.AddUnit(state.AddUnitParams{})
   921  	c.Assert(err, jc.ErrorIsNil)
   922  	_, err = s.State.AddRelation(appServerEP, otherServerEP)
   923  	c.Assert(err, jc.ErrorIsNil)
   924  
   925  	cfg := state.SetCharmConfig{
   926  		Charm:       ms,
   927  		CharmOrigin: defaultCharmOrigin(ms.URL()),
   928  	}
   929  	err = app.SetCharm(cfg)
   930  	c.Assert(err, jc.ErrorIsNil)
   931  
   932  	for i, t := range setCharmEndpointsTests {
   933  		c.Logf("test %d: %s", i, t.summary)
   934  
   935  		newCh := s.AddMetaCharm(c, "mysql", t.meta, revno+i+1)
   936  		cfg := state.SetCharmConfig{
   937  			Charm:       newCh,
   938  			CharmOrigin: defaultCharmOrigin(newCh.URL()),
   939  		}
   940  		err = app.SetCharm(cfg)
   941  		if t.err != "" {
   942  			c.Assert(err, gc.ErrorMatches, t.err)
   943  		} else {
   944  			c.Assert(err, jc.ErrorIsNil)
   945  		}
   946  	}
   947  
   948  	err = app.Destroy()
   949  	c.Assert(err, jc.ErrorIsNil)
   950  }
   951  
   952  func (s *ApplicationSuite) TestSetCharmChecksEndpointsWithRelations(c *gc.C) {
   953  	revno := 2
   954  	providerCharm := s.AddMetaCharm(c, "mysql", metaDifferentProvider, revno)
   955  	providerApp := s.AddTestingApplication(c, "myprovider", providerCharm)
   956  
   957  	cfg := state.SetCharmConfig{
   958  		Charm:       providerCharm,
   959  		CharmOrigin: defaultCharmOrigin(providerCharm.URL()),
   960  	}
   961  	err := providerApp.SetCharm(cfg)
   962  	c.Assert(err, jc.ErrorIsNil)
   963  
   964  	revno++
   965  	requirerCharm := s.AddMetaCharm(c, "mysql", metaDifferentRequirer, revno)
   966  	requirerApp := s.AddTestingApplication(c, "myrequirer", requirerCharm)
   967  	cfg = state.SetCharmConfig{Charm: requirerCharm, CharmOrigin: defaultCharmOrigin(requirerCharm.URL())}
   968  	err = requirerApp.SetCharm(cfg)
   969  	c.Assert(err, jc.ErrorIsNil)
   970  
   971  	eps, err := s.State.InferEndpoints("myprovider:kludge", "myrequirer:kludge")
   972  	c.Assert(err, jc.ErrorIsNil)
   973  	_, err = s.State.AddRelation(eps...)
   974  	c.Assert(err, jc.ErrorIsNil)
   975  
   976  	revno++
   977  	baseCharm := s.AddMetaCharm(c, "mysql", metaBase, revno)
   978  	cfg = state.SetCharmConfig{
   979  		Charm:       baseCharm,
   980  		CharmOrigin: defaultCharmOrigin(baseCharm.URL()),
   981  	}
   982  	err = providerApp.SetCharm(cfg)
   983  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "myprovider" to charm "local:quantal/quantal-mysql-4": would break relation "myrequirer:kludge myprovider:kludge"`)
   984  	err = requirerApp.SetCharm(cfg)
   985  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "myrequirer" to charm "local:quantal/quantal-mysql-4": would break relation "myrequirer:kludge myprovider:kludge"`)
   986  }
   987  
   988  var stringConfig = `
   989  options:
   990    key: {default: My Key, description: Desc, type: string}
   991  `
   992  var emptyConfig = `
   993  options: {}
   994  `
   995  var floatConfig = `
   996  options:
   997    key: {default: 0.42, description: Float key, type: float}
   998  `
   999  var newStringConfig = `
  1000  options:
  1001    key: {default: My Key, description: Desc, type: string}
  1002    other: {default: None, description: My Other, type: string}
  1003  `
  1004  
  1005  var sortableConfig = `
  1006  options:
  1007    blog-title: {default: My Title, description: A descriptive title used for the blog., type: string}
  1008    alphabetic:
  1009      type: int
  1010      description: Something early in the alphabet.
  1011    zygomatic:
  1012      type: int
  1013      description: Something late in the alphabet.
  1014  `
  1015  
  1016  var wordpressConfig = `
  1017  options:
  1018    blog-title: {default: My Title, description: A descriptive title used for the blog., type: string}
  1019  `
  1020  
  1021  var setCharmConfigTests = []struct {
  1022  	summary     string
  1023  	startconfig string
  1024  	startvalues charm.Settings
  1025  	endconfig   string
  1026  	endvalues   charm.Settings
  1027  	err         string
  1028  }{{
  1029  	summary:     "add float key to empty config",
  1030  	startconfig: emptyConfig,
  1031  	endconfig:   floatConfig,
  1032  }, {
  1033  	summary:     "add string key to empty config",
  1034  	startconfig: emptyConfig,
  1035  	endconfig:   stringConfig,
  1036  }, {
  1037  	summary:     "add string key and preserve existing values",
  1038  	startconfig: stringConfig,
  1039  	startvalues: charm.Settings{"key": "foo"},
  1040  	endconfig:   newStringConfig,
  1041  	endvalues:   charm.Settings{"key": "foo"},
  1042  }, {
  1043  	summary:     "remove string key",
  1044  	startconfig: stringConfig,
  1045  	startvalues: charm.Settings{"key": "value"},
  1046  	endconfig:   emptyConfig,
  1047  }, {
  1048  	summary:     "remove float key",
  1049  	startconfig: floatConfig,
  1050  	startvalues: charm.Settings{"key": 123.45},
  1051  	endconfig:   emptyConfig,
  1052  }, {
  1053  	summary:     "change key type without values",
  1054  	startconfig: stringConfig,
  1055  	endconfig:   floatConfig,
  1056  }, {
  1057  	summary:     "change key type with values",
  1058  	startconfig: stringConfig,
  1059  	startvalues: charm.Settings{"key": "value"},
  1060  	endconfig:   floatConfig,
  1061  }}
  1062  
  1063  func (s *ApplicationSuite) TestSetCharmConfig(c *gc.C) {
  1064  	charms := map[string]*state.Charm{
  1065  		stringConfig:    s.AddConfigCharm(c, "wordpress", stringConfig, 1),
  1066  		emptyConfig:     s.AddConfigCharm(c, "wordpress", emptyConfig, 2),
  1067  		floatConfig:     s.AddConfigCharm(c, "wordpress", floatConfig, 3),
  1068  		newStringConfig: s.AddConfigCharm(c, "wordpress", newStringConfig, 4),
  1069  	}
  1070  
  1071  	for i, t := range setCharmConfigTests {
  1072  		c.Logf("test %d: %s", i, t.summary)
  1073  
  1074  		origCh := charms[t.startconfig]
  1075  		app := s.AddTestingApplication(c, "wordpress", origCh)
  1076  		err := app.UpdateCharmConfig(model.GenerationMaster, t.startvalues)
  1077  		c.Assert(err, jc.ErrorIsNil)
  1078  
  1079  		newCh := charms[t.endconfig]
  1080  		cfg := state.SetCharmConfig{
  1081  			Charm:       newCh,
  1082  			CharmOrigin: defaultCharmOrigin(newCh.URL()),
  1083  		}
  1084  		err = app.SetCharm(cfg)
  1085  		var expectVals charm.Settings
  1086  		var expectCh *state.Charm
  1087  		if t.err != "" {
  1088  			c.Assert(err, gc.ErrorMatches, t.err)
  1089  			expectCh = origCh
  1090  			expectVals = t.startvalues
  1091  		} else {
  1092  			c.Assert(err, jc.ErrorIsNil)
  1093  			expectCh = newCh
  1094  			expectVals = t.endvalues
  1095  		}
  1096  
  1097  		sch, _, err := app.Charm()
  1098  		c.Assert(err, jc.ErrorIsNil)
  1099  		c.Assert(sch.URL(), gc.DeepEquals, expectCh.URL())
  1100  
  1101  		chConfig, err := app.CharmConfig(model.GenerationMaster)
  1102  		c.Assert(err, jc.ErrorIsNil)
  1103  		expected := s.combinedSettings(sch, expectVals)
  1104  		if len(expected) == 0 {
  1105  			c.Assert(chConfig, gc.HasLen, 0)
  1106  		} else {
  1107  			c.Assert(chConfig, gc.DeepEquals, expected)
  1108  		}
  1109  
  1110  		err = app.Destroy()
  1111  		c.Assert(err, jc.ErrorIsNil)
  1112  	}
  1113  }
  1114  
  1115  func (s *ApplicationSuite) TestSetCharmWithDyingApplication(c *gc.C) {
  1116  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2)
  1117  
  1118  	_, err := s.mysql.AddUnit(state.AddUnitParams{})
  1119  	c.Assert(err, jc.ErrorIsNil)
  1120  	err = s.mysql.Destroy()
  1121  	c.Assert(err, jc.ErrorIsNil)
  1122  	assertLife(c, s.mysql, state.Dying)
  1123  	cfg := state.SetCharmConfig{
  1124  		Charm:       sch,
  1125  		CharmOrigin: defaultCharmOrigin(sch.URL()),
  1126  		ForceUnits:  true,
  1127  	}
  1128  	err = s.mysql.SetCharm(cfg)
  1129  	c.Assert(err, jc.ErrorIsNil)
  1130  }
  1131  
  1132  func (s *ApplicationSuite) TestSequenceUnitIdsAfterDestroy(c *gc.C) {
  1133  	unit, err := s.mysql.AddUnit(state.AddUnitParams{})
  1134  	c.Assert(err, jc.ErrorIsNil)
  1135  	c.Assert(unit.Name(), gc.Equals, "mysql/0")
  1136  	err = unit.Destroy()
  1137  	c.Assert(err, jc.ErrorIsNil)
  1138  	err = s.mysql.Destroy()
  1139  	c.Assert(err, jc.ErrorIsNil)
  1140  	assertRemoved(c, s.mysql)
  1141  	s.mysql = s.AddTestingApplication(c, "mysql", s.charm)
  1142  	unit, err = s.mysql.AddUnit(state.AddUnitParams{})
  1143  	c.Assert(err, jc.ErrorIsNil)
  1144  	c.Assert(unit.Name(), gc.Equals, "mysql/1")
  1145  }
  1146  
  1147  func (s *ApplicationSuite) TestAssignUnitsRemovedAfterAppDestroy(c *gc.C) {
  1148  	mariadb := s.AddTestingApplicationWithNumUnits(c, 1, "mariadb", s.charm)
  1149  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
  1150  
  1151  	units, err := mariadb.AllUnits()
  1152  	c.Assert(err, jc.ErrorIsNil)
  1153  	c.Assert(len(units), gc.Equals, 1)
  1154  	unit := units[0]
  1155  	c.Assert(unit.Name(), gc.Equals, "mariadb/0")
  1156  	unitAssignments, err := s.State.AllUnitAssignments()
  1157  	c.Assert(err, jc.ErrorIsNil)
  1158  	c.Assert(len(unitAssignments), gc.Equals, 1)
  1159  
  1160  	err = unit.Destroy()
  1161  	c.Assert(err, jc.ErrorIsNil)
  1162  	err = mariadb.Destroy()
  1163  	c.Assert(err, jc.ErrorIsNil)
  1164  	assertRemoved(c, mariadb)
  1165  
  1166  	unitAssignments, err = s.State.AllUnitAssignments()
  1167  	c.Assert(err, jc.ErrorIsNil)
  1168  	c.Assert(len(unitAssignments), gc.Equals, 0)
  1169  }
  1170  
  1171  func (s *ApplicationSuite) TestSequenceUnitIdsAfterDestroyForSidecarApplication(c *gc.C) {
  1172  	st := s.Factory.MakeModel(c, &factory.ModelParams{
  1173  		Name: "caas-model",
  1174  		Type: state.ModelTypeCAAS,
  1175  	})
  1176  	s.AddCleanup(func(*gc.C) { _ = st.Close() })
  1177  	f := factory.NewFactory(st, s.StatePool)
  1178  	charmDef := `
  1179  name: cockroachdb
  1180  description: foo
  1181  summary: foo
  1182  containers:
  1183    redis:
  1184      resource: redis-container-resource
  1185  resources:
  1186    redis-container-resource:
  1187      name: redis-container
  1188      type: oci-image
  1189  `
  1190  	ch := state.AddCustomCharmWithManifest(c, st, "cockroach", "metadata.yaml", charmDef, "focal", 1)
  1191  	app := f.MakeApplication(c, &factory.ApplicationParams{Name: "cockroachdb", Charm: ch})
  1192  	unit, err := app.AddUnit(state.AddUnitParams{})
  1193  	c.Assert(err, jc.ErrorIsNil)
  1194  	c.Assert(unit.Name(), gc.Equals, "cockroachdb/0")
  1195  	err = unit.Destroy()
  1196  	c.Assert(err, jc.ErrorIsNil)
  1197  	err = unit.Refresh()
  1198  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1199  	err = app.Destroy()
  1200  	c.Assert(err, jc.ErrorIsNil)
  1201  	err = app.ClearResources()
  1202  	c.Assert(err, jc.ErrorIsNil)
  1203  	s.WaitForModelWatchersIdle(c, st.ModelUUID())
  1204  	assertCleanupCount(c, st, 2)
  1205  	unitAssignments, err := st.AllUnitAssignments()
  1206  	c.Assert(err, jc.ErrorIsNil)
  1207  	c.Assert(len(unitAssignments), gc.Equals, 0)
  1208  
  1209  	ch = state.AddCustomCharmWithManifest(c, st, "cockroach", "metadata.yaml", charmDef, "focal", 1)
  1210  	app = f.MakeApplication(c, &factory.ApplicationParams{Name: "cockroachdb", Charm: ch})
  1211  	unit, err = app.AddUnit(state.AddUnitParams{})
  1212  	c.Assert(err, jc.ErrorIsNil)
  1213  	c.Assert(unit.Name(), gc.Equals, "cockroachdb/0")
  1214  }
  1215  
  1216  func (s *ApplicationSuite) TestSequenceUnitIds(c *gc.C) {
  1217  	unit, err := s.mysql.AddUnit(state.AddUnitParams{})
  1218  	c.Assert(err, jc.ErrorIsNil)
  1219  	c.Assert(unit.Name(), gc.Equals, "mysql/0")
  1220  	unit, err = s.mysql.AddUnit(state.AddUnitParams{})
  1221  	c.Assert(err, jc.ErrorIsNil)
  1222  	c.Assert(unit.Name(), gc.Equals, "mysql/1")
  1223  }
  1224  
  1225  func (s *ApplicationSuite) TestExplicitUnitName(c *gc.C) {
  1226  	name1 := "mysql/100"
  1227  	unit, err := s.mysql.AddUnit(state.AddUnitParams{
  1228  		UnitName: &name1,
  1229  	})
  1230  	c.Assert(err, jc.ErrorIsNil)
  1231  	c.Assert(unit.Name(), gc.Equals, name1)
  1232  	name0 := "mysql/0"
  1233  	unit, err = s.mysql.AddUnit(state.AddUnitParams{
  1234  		UnitName: &name0,
  1235  	})
  1236  	c.Assert(err, jc.ErrorIsNil)
  1237  	c.Assert(unit.Name(), gc.Equals, name0)
  1238  }
  1239  
  1240  func (s *ApplicationSuite) TestSetCharmWhenDead(c *gc.C) {
  1241  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2)
  1242  
  1243  	defer state.SetBeforeHooks(c, s.State, func() {
  1244  		_, err := s.mysql.AddUnit(state.AddUnitParams{})
  1245  		c.Assert(err, jc.ErrorIsNil)
  1246  		err = s.mysql.Destroy()
  1247  		c.Assert(err, jc.ErrorIsNil)
  1248  		assertLife(c, s.mysql, state.Dying)
  1249  
  1250  		// Change the application life to Dead manually, as there's no
  1251  		// direct way of doing that otherwise.
  1252  		ops := []txn.Op{{
  1253  			C:      state.ApplicationsC,
  1254  			Id:     state.DocID(s.State, s.mysql.Name()),
  1255  			Update: bson.D{{"$set", bson.D{{"life", state.Dead}}}},
  1256  		}}
  1257  
  1258  		state.RunTransaction(c, s.State, ops)
  1259  		assertLife(c, s.mysql, state.Dead)
  1260  	}).Check()
  1261  
  1262  	cfg := state.SetCharmConfig{
  1263  		Charm:       sch,
  1264  		CharmOrigin: defaultCharmOrigin(sch.URL()),
  1265  		ForceUnits:  true,
  1266  	}
  1267  	err := s.mysql.SetCharm(cfg)
  1268  	c.Assert(errors.Cause(err), gc.Equals, stateerrors.ErrDead)
  1269  }
  1270  
  1271  func (s *ApplicationSuite) TestSetCharmWithRemovedApplication(c *gc.C) {
  1272  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2)
  1273  
  1274  	err := s.mysql.Destroy()
  1275  	c.Assert(err, jc.ErrorIsNil)
  1276  	assertRemoved(c, s.mysql)
  1277  
  1278  	cfg := state.SetCharmConfig{
  1279  		Charm:       sch,
  1280  		CharmOrigin: defaultCharmOrigin(sch.URL()),
  1281  		ForceUnits:  true,
  1282  	}
  1283  
  1284  	err = s.mysql.SetCharm(cfg)
  1285  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1286  }
  1287  
  1288  func (s *ApplicationSuite) TestSetCharmWhenRemoved(c *gc.C) {
  1289  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2)
  1290  
  1291  	defer state.SetBeforeHooks(c, s.State, func() {
  1292  		err := s.mysql.Destroy()
  1293  		c.Assert(err, jc.ErrorIsNil)
  1294  		assertRemoved(c, s.mysql)
  1295  	}).Check()
  1296  
  1297  	cfg := state.SetCharmConfig{
  1298  		Charm:       sch,
  1299  		CharmOrigin: defaultCharmOrigin(sch.URL()),
  1300  		ForceUnits:  true,
  1301  	}
  1302  	err := s.mysql.SetCharm(cfg)
  1303  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1304  }
  1305  
  1306  func (s *ApplicationSuite) TestSetCharmWhenDyingIsOK(c *gc.C) {
  1307  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2)
  1308  
  1309  	defer state.SetBeforeHooks(c, s.State, func() {
  1310  		_, err := s.mysql.AddUnit(state.AddUnitParams{})
  1311  		c.Assert(err, jc.ErrorIsNil)
  1312  		err = s.mysql.Destroy()
  1313  		c.Assert(err, jc.ErrorIsNil)
  1314  		assertLife(c, s.mysql, state.Dying)
  1315  	}).Check()
  1316  
  1317  	cfg := state.SetCharmConfig{
  1318  		Charm:       sch,
  1319  		CharmOrigin: defaultCharmOrigin(sch.URL()),
  1320  		ForceUnits:  true,
  1321  	}
  1322  	err := s.mysql.SetCharm(cfg)
  1323  	c.Assert(err, jc.ErrorIsNil)
  1324  	assertLife(c, s.mysql, state.Dying)
  1325  }
  1326  
  1327  func (s *ApplicationSuite) TestSetCharmRetriesWithSameCharmURL(c *gc.C) {
  1328  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2)
  1329  
  1330  	defer state.SetTestHooks(c, s.State,
  1331  		jujutxn.TestHook{
  1332  			Before: func() {
  1333  				currentCh, force, err := s.mysql.Charm()
  1334  				c.Assert(err, jc.ErrorIsNil)
  1335  				c.Assert(force, jc.IsFalse)
  1336  				c.Assert(currentCh.URL(), jc.DeepEquals, s.charm.URL())
  1337  
  1338  				cfg := state.SetCharmConfig{
  1339  					Charm:       sch,
  1340  					CharmOrigin: defaultCharmOrigin(sch.URL()),
  1341  				}
  1342  				err = s.mysql.SetCharm(cfg)
  1343  				c.Assert(err, jc.ErrorIsNil)
  1344  			},
  1345  			After: func() {
  1346  				// Verify the before hook worked.
  1347  				currentCh, force, err := s.mysql.Charm()
  1348  				c.Assert(err, jc.ErrorIsNil)
  1349  				c.Assert(force, jc.IsFalse)
  1350  				c.Assert(currentCh.URL(), jc.DeepEquals, sch.URL())
  1351  			},
  1352  		},
  1353  		jujutxn.TestHook{
  1354  			Before: nil, // Ensure there will be a retry.
  1355  			After: func() {
  1356  				// Verify it worked after the retry.
  1357  				err := s.mysql.Refresh()
  1358  				c.Assert(err, jc.ErrorIsNil)
  1359  				currentCh, force, err := s.mysql.Charm()
  1360  				c.Assert(err, jc.ErrorIsNil)
  1361  				c.Assert(force, jc.IsTrue)
  1362  				c.Assert(currentCh.URL(), jc.DeepEquals, sch.URL())
  1363  			},
  1364  		},
  1365  	).Check()
  1366  
  1367  	cfg := state.SetCharmConfig{
  1368  		Charm:       sch,
  1369  		CharmOrigin: defaultCharmOrigin(sch.URL()),
  1370  		ForceUnits:  true,
  1371  	}
  1372  	err := s.mysql.SetCharm(cfg)
  1373  	c.Assert(err, jc.ErrorIsNil)
  1374  }
  1375  
  1376  func (s *ApplicationSuite) TestSetCharmRetriesWhenOldSettingsChanged(c *gc.C) {
  1377  	revno := 2 // revno 1 is used by SetUpSuite
  1378  	oldCh := s.AddConfigCharm(c, "mysql", stringConfig, revno)
  1379  	newCh := s.AddConfigCharm(c, "mysql", stringConfig, revno+1)
  1380  	cfg := state.SetCharmConfig{
  1381  		Charm:       oldCh,
  1382  		CharmOrigin: defaultCharmOrigin(oldCh.URL()),
  1383  	}
  1384  	err := s.mysql.SetCharm(cfg)
  1385  	c.Assert(err, jc.ErrorIsNil)
  1386  
  1387  	defer state.SetBeforeHooks(c, s.State,
  1388  		func() {
  1389  			err := s.mysql.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"key": "value"})
  1390  			c.Assert(err, jc.ErrorIsNil)
  1391  		},
  1392  		nil, // Ensure there will be a retry.
  1393  	).Check()
  1394  
  1395  	cfg = state.SetCharmConfig{
  1396  		Charm:       newCh,
  1397  		CharmOrigin: defaultCharmOrigin(newCh.URL()),
  1398  		ForceUnits:  true,
  1399  	}
  1400  	err = s.mysql.SetCharm(cfg)
  1401  	c.Assert(err, jc.ErrorIsNil)
  1402  }
  1403  
  1404  func (s *ApplicationSuite) TestSetCharmRetriesWhenBothOldAndNewSettingsChanged(c *gc.C) {
  1405  	revno := 2 // revno 1 is used by SetUpSuite
  1406  	oldCh := s.AddConfigCharm(c, "mysql", stringConfig, revno)
  1407  	newCh := s.AddConfigCharm(c, "mysql", stringConfig, revno+1)
  1408  
  1409  	defer state.SetTestHooks(c, s.State,
  1410  		jujutxn.TestHook{
  1411  			Before: func() {
  1412  				// Add two units, which will keep the refcount of oldCh
  1413  				// and newCh settings greater than 0, while the application's
  1414  				// charm URLs change between oldCh and newCh. Ensure
  1415  				// refcounts change as expected.
  1416  				unit1, err := s.mysql.AddUnit(state.AddUnitParams{})
  1417  				c.Assert(err, jc.ErrorIsNil)
  1418  				unit2, err := s.mysql.AddUnit(state.AddUnitParams{})
  1419  				c.Assert(err, jc.ErrorIsNil)
  1420  				cfg := state.SetCharmConfig{
  1421  					Charm:       newCh,
  1422  					CharmOrigin: defaultCharmOrigin(newCh.URL()),
  1423  				}
  1424  				err = s.mysql.SetCharm(cfg)
  1425  				c.Assert(err, jc.ErrorIsNil)
  1426  				assertSettingsRef(c, s.State, "mysql", newCh, 1)
  1427  				assertNoSettingsRef(c, s.State, "mysql", oldCh)
  1428  				err = unit1.SetCharmURL(newCh.URL())
  1429  				c.Assert(err, jc.ErrorIsNil)
  1430  				assertSettingsRef(c, s.State, "mysql", newCh, 2)
  1431  				assertNoSettingsRef(c, s.State, "mysql", oldCh)
  1432  				// Update newCh settings, switch to oldCh and update its
  1433  				// settings as well.
  1434  				err = s.mysql.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"key": "value1"})
  1435  				c.Assert(err, jc.ErrorIsNil)
  1436  				cfg = state.SetCharmConfig{
  1437  					Charm:       oldCh,
  1438  					CharmOrigin: defaultCharmOrigin(oldCh.URL()),
  1439  				}
  1440  
  1441  				err = s.mysql.SetCharm(cfg)
  1442  				c.Assert(err, jc.ErrorIsNil)
  1443  				assertSettingsRef(c, s.State, "mysql", newCh, 1)
  1444  				assertSettingsRef(c, s.State, "mysql", oldCh, 1)
  1445  				err = unit2.SetCharmURL(oldCh.URL())
  1446  				c.Assert(err, jc.ErrorIsNil)
  1447  				assertSettingsRef(c, s.State, "mysql", newCh, 1)
  1448  				assertSettingsRef(c, s.State, "mysql", oldCh, 2)
  1449  				err = s.mysql.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"key": "value2"})
  1450  				c.Assert(err, jc.ErrorIsNil)
  1451  			},
  1452  			After: func() {
  1453  				// Verify the charm and refcounts after the second attempt.
  1454  				err := s.mysql.Refresh()
  1455  				c.Assert(err, jc.ErrorIsNil)
  1456  				currentCh, force, err := s.mysql.Charm()
  1457  				c.Assert(err, jc.ErrorIsNil)
  1458  				c.Assert(force, jc.IsFalse)
  1459  				c.Assert(currentCh.URL(), jc.DeepEquals, oldCh.URL())
  1460  				assertSettingsRef(c, s.State, "mysql", newCh, 1)
  1461  				assertSettingsRef(c, s.State, "mysql", oldCh, 2)
  1462  			},
  1463  		},
  1464  		jujutxn.TestHook{
  1465  			Before: func() {
  1466  				// SetCharm has refreshed its cached settings for oldCh
  1467  				// and newCh. Change them again to trigger another
  1468  				// attempt.
  1469  				cfg := state.SetCharmConfig{
  1470  					Charm:       newCh,
  1471  					CharmOrigin: defaultCharmOrigin(newCh.URL()),
  1472  				}
  1473  
  1474  				err := s.mysql.SetCharm(cfg)
  1475  				c.Assert(err, jc.ErrorIsNil)
  1476  				assertSettingsRef(c, s.State, "mysql", newCh, 2)
  1477  				assertSettingsRef(c, s.State, "mysql", oldCh, 1)
  1478  				err = s.mysql.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"key": "value3"})
  1479  				c.Assert(err, jc.ErrorIsNil)
  1480  
  1481  				cfg = state.SetCharmConfig{
  1482  					Charm:       oldCh,
  1483  					CharmOrigin: defaultCharmOrigin(oldCh.URL()),
  1484  				}
  1485  				err = s.mysql.SetCharm(cfg)
  1486  				c.Assert(err, jc.ErrorIsNil)
  1487  				assertSettingsRef(c, s.State, "mysql", newCh, 1)
  1488  				assertSettingsRef(c, s.State, "mysql", oldCh, 2)
  1489  				err = s.mysql.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"key": "value4"})
  1490  				c.Assert(err, jc.ErrorIsNil)
  1491  			},
  1492  			After: func() {
  1493  				// Verify the charm and refcounts after the third attempt.
  1494  				err := s.mysql.Refresh()
  1495  				c.Assert(err, jc.ErrorIsNil)
  1496  				currentCh, force, err := s.mysql.Charm()
  1497  				c.Assert(err, jc.ErrorIsNil)
  1498  				c.Assert(force, jc.IsFalse)
  1499  				c.Assert(currentCh.URL(), jc.DeepEquals, oldCh.URL())
  1500  				assertSettingsRef(c, s.State, "mysql", newCh, 1)
  1501  				assertSettingsRef(c, s.State, "mysql", oldCh, 2)
  1502  			},
  1503  		},
  1504  		jujutxn.TestHook{
  1505  			Before: nil, // Ensure there will be a (final) retry.
  1506  			After: func() {
  1507  				// Verify the charm and refcounts after the final third attempt.
  1508  				err := s.mysql.Refresh()
  1509  				c.Assert(err, jc.ErrorIsNil)
  1510  				currentCh, force, err := s.mysql.Charm()
  1511  				c.Assert(err, jc.ErrorIsNil)
  1512  				c.Assert(force, jc.IsTrue)
  1513  				c.Assert(currentCh.URL(), jc.DeepEquals, newCh.URL())
  1514  				assertSettingsRef(c, s.State, "mysql", newCh, 2)
  1515  				assertSettingsRef(c, s.State, "mysql", oldCh, 1)
  1516  			},
  1517  		},
  1518  	).Check()
  1519  
  1520  	cfg := state.SetCharmConfig{
  1521  		Charm:       newCh,
  1522  		CharmOrigin: defaultCharmOrigin(newCh.URL()),
  1523  		ForceUnits:  true,
  1524  	}
  1525  	err := s.mysql.SetCharm(cfg)
  1526  	c.Assert(err, jc.ErrorIsNil)
  1527  }
  1528  
  1529  func (s *ApplicationSuite) TestSetCharmRetriesWhenOldBindingsChanged(c *gc.C) {
  1530  	revno := 2 // revno 1 is used by SetUpSuite
  1531  	mysqlKey := state.ApplicationGlobalKey(s.mysql.Name())
  1532  	oldCharm := s.AddMetaCharm(c, "mysql", metaDifferentRequirer, revno)
  1533  	newCharm := s.AddMetaCharm(c, "mysql", metaExtraEndpoints, revno+1)
  1534  
  1535  	cfg := state.SetCharmConfig{
  1536  		Charm:       oldCharm,
  1537  		CharmOrigin: defaultCharmOrigin(oldCharm.URL()),
  1538  	}
  1539  	err := s.mysql.SetCharm(cfg)
  1540  	c.Assert(err, jc.ErrorIsNil)
  1541  
  1542  	oldBindings, err := s.mysql.EndpointBindings()
  1543  	c.Assert(err, jc.ErrorIsNil)
  1544  	c.Assert(oldBindings.Map(), jc.DeepEquals, map[string]string{
  1545  		"":        network.AlphaSpaceId,
  1546  		"server":  network.AlphaSpaceId,
  1547  		"kludge":  network.AlphaSpaceId,
  1548  		"cluster": network.AlphaSpaceId,
  1549  	})
  1550  	dbSpace, err := s.State.AddSpace("db", "", nil, true)
  1551  	c.Assert(err, jc.ErrorIsNil)
  1552  	adminSpace, err := s.State.AddSpace("admin", "", nil, false)
  1553  	c.Assert(err, jc.ErrorIsNil)
  1554  
  1555  	updateBindings := func(updatesMap bson.M) {
  1556  		ops := []txn.Op{{
  1557  			C:      state.EndpointBindingsC,
  1558  			Id:     mysqlKey,
  1559  			Update: bson.D{{"$set", updatesMap}},
  1560  		}}
  1561  		state.RunTransaction(c, s.State, ops)
  1562  	}
  1563  
  1564  	defer state.SetTestHooks(c, s.State,
  1565  		jujutxn.TestHook{
  1566  			Before: func() {
  1567  				// First change.
  1568  				updateBindings(bson.M{
  1569  					"bindings.server": dbSpace.Id(),
  1570  					"bindings.kludge": adminSpace.Id(), // will be removed before newCharm is set.
  1571  				})
  1572  			},
  1573  			After: func() {
  1574  				// Second change.
  1575  				updateBindings(bson.M{
  1576  					"bindings.cluster": adminSpace.Id(),
  1577  				})
  1578  			},
  1579  		},
  1580  		jujutxn.TestHook{
  1581  			Before: nil, // Ensure there will be a (final) retry.
  1582  			After: func() {
  1583  				// Verify final bindings.
  1584  				newBindings, err := s.mysql.EndpointBindings()
  1585  				c.Assert(err, jc.ErrorIsNil)
  1586  				c.Assert(newBindings.Map(), jc.DeepEquals, map[string]string{
  1587  					"":        network.AlphaSpaceId,
  1588  					"server":  dbSpace.Id(), // from the first change.
  1589  					"foo":     network.AlphaSpaceId,
  1590  					"client":  network.AlphaSpaceId,
  1591  					"baz":     network.AlphaSpaceId,
  1592  					"cluster": adminSpace.Id(), // from the second change.
  1593  					"just":    network.AlphaSpaceId,
  1594  				})
  1595  			},
  1596  		},
  1597  	).Check()
  1598  
  1599  	cfg = state.SetCharmConfig{
  1600  		Charm:       newCharm,
  1601  		CharmOrigin: defaultCharmOrigin(newCharm.URL()),
  1602  		ForceUnits:  true,
  1603  	}
  1604  	err = s.mysql.SetCharm(cfg)
  1605  	c.Assert(err, jc.ErrorIsNil)
  1606  }
  1607  
  1608  func (s *ApplicationSuite) TestSetCharmViolatesMaxRelationCount(c *gc.C) {
  1609  	wp0Charm := `
  1610  name: wordpress
  1611  description: foo
  1612  summary: foo
  1613  requires:
  1614    db:
  1615      interface: mysql
  1616      limit: 99
  1617  `
  1618  
  1619  	wp1Charm := `
  1620  name: wordpress
  1621  description: bar
  1622  summary: foo
  1623  requires:
  1624    db:
  1625      interface: mysql
  1626      limit: 1
  1627  `
  1628  
  1629  	// wp0Charm allows up to 99 relations for the db endpoint
  1630  	wpApp := s.AddTestingApplication(c, "wordpress", s.AddMetaCharm(c, "wordpress", wp0Charm, 1))
  1631  
  1632  	// Establish 2 relations (note: mysql is already added by the suite setup code)
  1633  	s.AddTestingApplication(c, "some-mariadb", s.AddTestingCharm(c, "mariadb"))
  1634  	eps, err := s.State.InferEndpoints("wordpress", "mysql")
  1635  	c.Assert(err, jc.ErrorIsNil)
  1636  	_, err = s.State.AddRelation(eps...)
  1637  	c.Assert(err, jc.ErrorIsNil)
  1638  	eps, err = s.State.InferEndpoints("wordpress", "some-mariadb")
  1639  	c.Assert(err, jc.ErrorIsNil)
  1640  	_, err = s.State.AddRelation(eps...)
  1641  	c.Assert(err, jc.ErrorIsNil)
  1642  
  1643  	// Try to update wordpress to a new version with max 1 relation for the db endpoint
  1644  	wpCharmWithRelLimit := s.AddMetaCharm(c, "wordpress", wp1Charm, 2)
  1645  	cfg := state.SetCharmConfig{
  1646  		Charm:       wpCharmWithRelLimit,
  1647  		CharmOrigin: defaultCharmOrigin(wpCharmWithRelLimit.URL()),
  1648  	}
  1649  	err = wpApp.SetCharm(cfg)
  1650  	c.Assert(err, jc.Satisfies, errors.IsQuotaLimitExceeded, gc.Commentf("expected quota limit error due to max relation mismatch"))
  1651  
  1652  	// Try again with --force
  1653  	cfg.Force = true
  1654  	err = wpApp.SetCharm(cfg)
  1655  	c.Assert(err, jc.ErrorIsNil)
  1656  }
  1657  
  1658  func (s *ApplicationSuite) TestSetDownloadedIDAndHash(c *gc.C) {
  1659  	s.setupSetDownloadedIDAndHash(c, &state.CharmOrigin{
  1660  		Source: "charm-hub",
  1661  	})
  1662  	err := s.mysql.SetDownloadedIDAndHash("testing-ID", "testing-hash")
  1663  	c.Assert(err, jc.ErrorIsNil)
  1664  	c.Assert(s.mysql.CharmOrigin().ID, gc.Equals, "testing-ID")
  1665  	c.Assert(s.mysql.CharmOrigin().Hash, gc.Equals, "testing-hash")
  1666  }
  1667  
  1668  func (s *ApplicationSuite) TestSetDownloadedIDAndHashFailEmptyStrings(c *gc.C) {
  1669  	err := s.mysql.SetDownloadedIDAndHash("", "")
  1670  	c.Assert(err, jc.Satisfies, errors.IsBadRequest)
  1671  }
  1672  
  1673  func (s *ApplicationSuite) TestSetDownloadedIDAndHashFailChangeID(c *gc.C) {
  1674  	s.setupSetDownloadedIDAndHash(c, &state.CharmOrigin{
  1675  		Source:   "charm-hub",
  1676  		ID:       "testing-ID",
  1677  		Hash:     "testing-hash",
  1678  		Platform: &state.Platform{},
  1679  	})
  1680  	err := s.mysql.SetDownloadedIDAndHash("change-ID", "testing-hash")
  1681  	c.Assert(err, jc.Satisfies, errors.IsBadRequest)
  1682  }
  1683  
  1684  func (s *ApplicationSuite) TestSetDownloadedIDAndHashReplaceHash(c *gc.C) {
  1685  	s.setupSetDownloadedIDAndHash(c, &state.CharmOrigin{
  1686  		Source: "charm-hub",
  1687  		ID:     "testing-ID",
  1688  		Hash:   "testing-hash",
  1689  	})
  1690  	err := s.mysql.SetDownloadedIDAndHash("", "new-testing-hash")
  1691  	c.Assert(err, jc.ErrorIsNil)
  1692  	c.Assert(s.mysql.CharmOrigin().Hash, gc.Equals, "new-testing-hash")
  1693  }
  1694  
  1695  func (s *ApplicationSuite) setupSetDownloadedIDAndHash(c *gc.C, origin *state.CharmOrigin) {
  1696  	origin.Platform = &state.Platform{}
  1697  	chInfoOne := s.dummyCharm(c, "ch:testing-3")
  1698  	chOne, err := s.State.AddCharm(chInfoOne)
  1699  	c.Assert(err, jc.ErrorIsNil)
  1700  	err = s.mysql.SetCharm(state.SetCharmConfig{
  1701  		Charm:       chOne,
  1702  		CharmOrigin: origin,
  1703  	})
  1704  	c.Assert(err, jc.ErrorIsNil)
  1705  	err = s.mysql.Refresh()
  1706  	c.Assert(err, jc.ErrorIsNil)
  1707  }
  1708  
  1709  var applicationUpdateCharmConfigTests = []struct {
  1710  	about   string
  1711  	initial charm.Settings
  1712  	update  charm.Settings
  1713  	expect  charm.Settings
  1714  	err     string
  1715  }{{
  1716  	about:  "unknown option",
  1717  	update: charm.Settings{"foo": "bar"},
  1718  	err:    `unknown option "foo"`,
  1719  }, {
  1720  	about:  "bad type",
  1721  	update: charm.Settings{"skill-level": "profound"},
  1722  	err:    `option "skill-level" expected int, got "profound"`,
  1723  }, {
  1724  	about:  "set string",
  1725  	update: charm.Settings{"outlook": "positive"},
  1726  	expect: charm.Settings{"outlook": "positive"},
  1727  }, {
  1728  	about:   "unset string and set another",
  1729  	initial: charm.Settings{"outlook": "positive"},
  1730  	update:  charm.Settings{"outlook": nil, "title": "sir"},
  1731  	expect:  charm.Settings{"title": "sir"},
  1732  }, {
  1733  	about:  "unset missing string",
  1734  	update: charm.Settings{"outlook": nil},
  1735  }, {
  1736  	about:   `empty strings are valid`,
  1737  	initial: charm.Settings{"outlook": "positive"},
  1738  	update:  charm.Settings{"outlook": "", "title": ""},
  1739  	expect:  charm.Settings{"outlook": "", "title": ""},
  1740  }, {
  1741  	about:   "preserve existing value",
  1742  	initial: charm.Settings{"title": "sir"},
  1743  	update:  charm.Settings{"username": "admin001"},
  1744  	expect:  charm.Settings{"username": "admin001", "title": "sir"},
  1745  }, {
  1746  	about:   "unset a default value, set a different default",
  1747  	initial: charm.Settings{"username": "admin001", "title": "sir"},
  1748  	update:  charm.Settings{"username": nil, "title": "My Title"},
  1749  	expect:  charm.Settings{"title": "My Title"},
  1750  }, {
  1751  	about:  "non-string type",
  1752  	update: charm.Settings{"skill-level": 303},
  1753  	expect: charm.Settings{"skill-level": int64(303)},
  1754  }, {
  1755  	about:   "unset non-string type",
  1756  	initial: charm.Settings{"skill-level": 303},
  1757  	update:  charm.Settings{"skill-level": nil},
  1758  }}
  1759  
  1760  func (s *ApplicationSuite) TestUpdateCharmConfig(c *gc.C) {
  1761  	sch := s.AddTestingCharm(c, "dummy")
  1762  	for i, t := range applicationUpdateCharmConfigTests {
  1763  		c.Logf("test %d. %s", i, t.about)
  1764  		app := s.AddTestingApplication(c, "dummy-application", sch)
  1765  		if t.initial != nil {
  1766  			err := app.UpdateCharmConfig(model.GenerationMaster, t.initial)
  1767  			c.Assert(err, jc.ErrorIsNil)
  1768  		}
  1769  		err := app.UpdateCharmConfig(model.GenerationMaster, t.update)
  1770  		if t.err != "" {
  1771  			c.Assert(err, gc.ErrorMatches, t.err)
  1772  		} else {
  1773  			c.Assert(err, jc.ErrorIsNil)
  1774  			cfg, err := app.CharmConfig(model.GenerationMaster)
  1775  			c.Assert(err, jc.ErrorIsNil)
  1776  			appConfig := t.expect
  1777  			expected := s.combinedSettings(sch, appConfig)
  1778  			c.Assert(cfg, gc.DeepEquals, expected)
  1779  		}
  1780  		err = app.Destroy()
  1781  		c.Assert(err, jc.ErrorIsNil)
  1782  	}
  1783  }
  1784  
  1785  func (s *ApplicationSuite) setupCharmForTestUpdateApplicationBase(c *gc.C, name string) *state.Application {
  1786  	ch := state.AddTestingCharmMultiSeries(c, s.State, name)
  1787  	app := state.AddTestingApplicationForBase(c, s.State, state.UbuntuBase("20.04"), name, ch)
  1788  
  1789  	rev := ch.Revision()
  1790  	origin := &state.CharmOrigin{
  1791  		Source:   "charm-hub",
  1792  		Revision: &rev,
  1793  		Platform: &state.Platform{
  1794  			OS:      "ubuntu",
  1795  			Channel: "20.04/stable",
  1796  		},
  1797  	}
  1798  	cfg := state.SetCharmConfig{
  1799  		Charm:       ch,
  1800  		CharmOrigin: origin,
  1801  	}
  1802  	err := app.SetCharm(cfg)
  1803  	c.Assert(err, jc.ErrorIsNil)
  1804  	err = app.Refresh()
  1805  	c.Assert(err, jc.ErrorIsNil)
  1806  	return app
  1807  }
  1808  
  1809  func (s *ApplicationSuite) TestUpdateApplicationBase(c *gc.C) {
  1810  	app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series")
  1811  	err := app.UpdateApplicationBase(state.UbuntuBase("22.04"), false)
  1812  	c.Assert(err, jc.ErrorIsNil)
  1813  	assertApplicationBaseUpdate(c, app, state.UbuntuBase("22.04"))
  1814  }
  1815  
  1816  func (s *ApplicationSuite) TestUpdateApplicationSeriesSamesSeriesToStart(c *gc.C) {
  1817  	app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series")
  1818  	err := app.UpdateApplicationBase(state.UbuntuBase("20.04"), false)
  1819  	c.Assert(err, jc.ErrorIsNil)
  1820  	assertApplicationBaseUpdate(c, app, state.UbuntuBase("20.04"))
  1821  }
  1822  
  1823  func (s *ApplicationSuite) TestUpdateApplicationSeriesSamesSeriesAfterStart(c *gc.C) {
  1824  	app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series")
  1825  
  1826  	defer state.SetTestHooks(c, s.State,
  1827  		jujutxn.TestHook{
  1828  			Before: func() {
  1829  				unit, err := app.AddUnit(state.AddUnitParams{})
  1830  				c.Assert(err, jc.ErrorIsNil)
  1831  				err = unit.AssignToNewMachine()
  1832  				c.Assert(err, jc.ErrorIsNil)
  1833  
  1834  				ops := []txn.Op{{
  1835  					C:  state.ApplicationsC,
  1836  					Id: state.DocID(s.State, "multi-series"),
  1837  					Update: bson.D{{"$set", bson.D{{
  1838  						"charm-origin.platform.channel", "22.04/stable"}}}},
  1839  				}}
  1840  				state.RunTransaction(c, s.State, ops)
  1841  			},
  1842  			After: func() {
  1843  				assertApplicationBaseUpdate(c, app, state.UbuntuBase("22.04"))
  1844  			},
  1845  		},
  1846  	).Check()
  1847  
  1848  	err := app.UpdateApplicationBase(state.UbuntuBase("22.04"), false)
  1849  	c.Assert(err, jc.ErrorIsNil)
  1850  	assertApplicationBaseUpdate(c, app, state.UbuntuBase("22.04"))
  1851  }
  1852  
  1853  func (s *ApplicationSuite) TestUpdateApplicationSeriesCharmURLChangedSeriesFail(c *gc.C) {
  1854  	app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series")
  1855  
  1856  	defer state.SetTestHooks(c, s.State,
  1857  		jujutxn.TestHook{
  1858  			Before: func() {
  1859  				v2 := state.AddTestingCharmMultiSeries(c, s.State, "multi-seriesv2")
  1860  				cfg := state.SetCharmConfig{
  1861  					Charm:       v2,
  1862  					CharmOrigin: defaultCharmOrigin(v2.URL()),
  1863  				}
  1864  				err := app.SetCharm(cfg)
  1865  				c.Assert(err, jc.ErrorIsNil)
  1866  			},
  1867  		},
  1868  	).Check()
  1869  
  1870  	// Trusty is listed in only version 1 of the charm.
  1871  	err := app.UpdateApplicationBase(state.UbuntuBase("22.04"), false)
  1872  	c.Assert(err, gc.ErrorMatches,
  1873  		"updating application base: base \"ubuntu@22.04\" not supported by charm, "+
  1874  			"the charm supported bases are: ubuntu@20.04, ubuntu@18.04")
  1875  }
  1876  
  1877  func (s *ApplicationSuite) TestUpdateApplicationSeriesCharmURLChangedSeriesPass(c *gc.C) {
  1878  	app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series")
  1879  
  1880  	defer state.SetTestHooks(c, s.State,
  1881  		jujutxn.TestHook{
  1882  			Before: func() {
  1883  				v2 := state.AddTestingCharmMultiSeries(c, s.State, "multi-seriesv2")
  1884  				origin := defaultCharmOrigin(v2.URL())
  1885  				origin.Platform.OS = "ubuntu"
  1886  				origin.Platform.Channel = "18.04/stable"
  1887  				cfg := state.SetCharmConfig{
  1888  					Charm:       v2,
  1889  					CharmOrigin: origin,
  1890  				}
  1891  				err := app.SetCharm(cfg)
  1892  				c.Assert(err, jc.ErrorIsNil)
  1893  			},
  1894  		},
  1895  	).Check()
  1896  
  1897  	// bionic is listed in both revisions of the charm.
  1898  	err := app.UpdateApplicationBase(state.UbuntuBase("18.04"), false)
  1899  	c.Assert(err, jc.ErrorIsNil)
  1900  	assertApplicationBaseUpdate(c, app, state.UbuntuBase("18.04"))
  1901  }
  1902  
  1903  func (s *ApplicationSuite) setupMultiSeriesUnitSubordinate(c *gc.C, app *state.Application, name string) *state.Application {
  1904  	unit, err := app.AddUnit(state.AddUnitParams{})
  1905  	c.Assert(err, jc.ErrorIsNil)
  1906  	err = unit.AssignToNewMachine()
  1907  	c.Assert(err, jc.ErrorIsNil)
  1908  	return s.setupMultiSeriesUnitSubordinateGivenUnit(c, app, unit, name)
  1909  }
  1910  
  1911  func (s *ApplicationSuite) setupMultiSeriesUnitSubordinateGivenUnit(c *gc.C, app *state.Application, unit *state.Unit, name string) *state.Application {
  1912  	subApp := s.setupCharmForTestUpdateApplicationBase(c, name)
  1913  
  1914  	eps, err := s.State.InferEndpoints(app.Name(), name)
  1915  	c.Assert(err, jc.ErrorIsNil)
  1916  	rel, err := s.State.AddRelation(eps...)
  1917  	c.Assert(err, jc.ErrorIsNil)
  1918  
  1919  	ru, err := rel.Unit(unit)
  1920  	c.Assert(err, jc.ErrorIsNil)
  1921  	err = ru.EnterScope(nil)
  1922  	c.Assert(err, jc.ErrorIsNil)
  1923  
  1924  	err = app.Refresh()
  1925  	c.Assert(err, jc.ErrorIsNil)
  1926  	err = subApp.Refresh()
  1927  	c.Assert(err, jc.ErrorIsNil)
  1928  	err = unit.Refresh()
  1929  	c.Assert(err, jc.ErrorIsNil)
  1930  
  1931  	return subApp
  1932  }
  1933  
  1934  func assertApplicationBaseUpdate(c *gc.C, a *state.Application, base state.Base) {
  1935  	err := a.Refresh()
  1936  	c.Assert(err, jc.ErrorIsNil)
  1937  	stBase, err := corebase.ParseBase(base.OS, base.Channel)
  1938  	c.Assert(err, jc.ErrorIsNil)
  1939  	c.Assert(a.Base().String(), gc.Equals, stBase.String())
  1940  }
  1941  
  1942  func (s *ApplicationSuite) TestUpdateApplicationSeriesWithSubordinate(c *gc.C) {
  1943  	app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series")
  1944  	subApp := s.setupMultiSeriesUnitSubordinate(c, app, "multi-series-subordinate")
  1945  	err := app.UpdateApplicationBase(state.UbuntuBase("22.04"), false)
  1946  	c.Assert(err, jc.ErrorIsNil)
  1947  	assertApplicationBaseUpdate(c, app, state.UbuntuBase("22.04"))
  1948  	assertApplicationBaseUpdate(c, subApp, state.UbuntuBase("22.04"))
  1949  }
  1950  
  1951  func (s *ApplicationSuite) TestUpdateApplicationSeriesWithSubordinateFail(c *gc.C) {
  1952  	app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series")
  1953  	subApp := s.setupMultiSeriesUnitSubordinate(c, app, "multi-series-subordinate")
  1954  	err := app.UpdateApplicationBase(state.UbuntuBase("16.04"), false)
  1955  	c.Assert(err, gc.ErrorMatches, `updating application base: base "ubuntu@16.04" not supported by charm.*`)
  1956  	assertApplicationBaseUpdate(c, app, state.UbuntuBase("20.04"))
  1957  	assertApplicationBaseUpdate(c, subApp, state.UbuntuBase("20.04"))
  1958  }
  1959  
  1960  func (s *ApplicationSuite) TestUpdateApplicationSeriesWithSubordinateForce(c *gc.C) {
  1961  	app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series")
  1962  	subApp := s.setupMultiSeriesUnitSubordinate(c, app, "multi-series-subordinate")
  1963  	err := app.UpdateApplicationBase(state.UbuntuBase("16.04"), true)
  1964  	c.Assert(err, jc.ErrorIsNil)
  1965  	assertApplicationBaseUpdate(c, app, state.UbuntuBase("16.04"))
  1966  	assertApplicationBaseUpdate(c, subApp, state.UbuntuBase("16.04"))
  1967  }
  1968  
  1969  func (s *ApplicationSuite) TestUpdateApplicationSeriesUnitCountChange(c *gc.C) {
  1970  	app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series")
  1971  	units, err := app.AllUnits()
  1972  	c.Assert(err, jc.ErrorIsNil)
  1973  	c.Assert(len(units), gc.Equals, 0)
  1974  
  1975  	defer state.SetTestHooks(c, s.State,
  1976  		jujutxn.TestHook{
  1977  			Before: func() {
  1978  				// Add a subordinate and unit
  1979  				_ = s.setupMultiSeriesUnitSubordinate(c, app, "multi-series-subordinate")
  1980  			},
  1981  		},
  1982  	).Check()
  1983  
  1984  	err = app.UpdateApplicationBase(state.UbuntuBase("22.04"), false)
  1985  	c.Assert(err, jc.ErrorIsNil)
  1986  	assertApplicationBaseUpdate(c, app, state.UbuntuBase("22.04"))
  1987  
  1988  	units, err = app.AllUnits()
  1989  	c.Assert(err, jc.ErrorIsNil)
  1990  	c.Assert(len(units), gc.Equals, 1)
  1991  	subApp, err := s.State.Application("multi-series-subordinate")
  1992  	c.Assert(err, jc.ErrorIsNil)
  1993  	assertApplicationBaseUpdate(c, subApp, state.UbuntuBase("22.04"))
  1994  }
  1995  
  1996  func (s *ApplicationSuite) TestUpdateApplicationSeriesSecondSubordinate(c *gc.C) {
  1997  	app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series")
  1998  	subApp := s.setupMultiSeriesUnitSubordinate(c, app, "multi-series-subordinate")
  1999  	unit, err := s.State.Unit("multi-series/0")
  2000  	c.Assert(err, jc.ErrorIsNil)
  2001  	c.Assert(unit.SubordinateNames(), gc.DeepEquals, []string{"multi-series-subordinate/0"})
  2002  
  2003  	defer state.SetTestHooks(c, s.State,
  2004  		jujutxn.TestHook{
  2005  			Before: func() {
  2006  				// Add 2nd subordinate
  2007  				_ = s.setupMultiSeriesUnitSubordinateGivenUnit(c, app, unit, "multi-series-subordinate2")
  2008  			},
  2009  		},
  2010  	).Check()
  2011  
  2012  	err = app.UpdateApplicationBase(state.UbuntuBase("22.04"), false)
  2013  	c.Assert(err, jc.ErrorIsNil)
  2014  	assertApplicationBaseUpdate(c, app, state.UbuntuBase("22.04"))
  2015  	assertApplicationBaseUpdate(c, subApp, state.UbuntuBase("22.04"))
  2016  
  2017  	subApp2, err := s.State.Application("multi-series-subordinate2")
  2018  	c.Assert(err, jc.ErrorIsNil)
  2019  	assertApplicationBaseUpdate(c, subApp2, state.UbuntuBase("22.04"))
  2020  }
  2021  
  2022  func (s *ApplicationSuite) TestUpdateApplicationSeriesSecondSubordinateIncompatible(c *gc.C) {
  2023  	app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series")
  2024  	subApp := s.setupMultiSeriesUnitSubordinate(c, app, "multi-series-subordinate")
  2025  	unit, err := s.State.Unit("multi-series/0")
  2026  	c.Assert(err, jc.ErrorIsNil)
  2027  	c.Assert(unit.SubordinateNames(), gc.DeepEquals, []string{"multi-series-subordinate/0"})
  2028  
  2029  	defer state.SetTestHooks(c, s.State,
  2030  		jujutxn.TestHook{
  2031  			Before: func() {
  2032  				// Add 2nd subordinate
  2033  				_ = s.setupMultiSeriesUnitSubordinateGivenUnit(c, app, unit, "multi-series-subordinate2")
  2034  			},
  2035  		},
  2036  	).Check()
  2037  
  2038  	err = app.UpdateApplicationBase(state.UbuntuBase("18.04"), false)
  2039  	c.Assert(err, gc.ErrorMatches, `updating application base: base "ubuntu@18.04" not supported by charm.*`)
  2040  	assertApplicationBaseUpdate(c, app, state.UbuntuBase("20.04"))
  2041  	assertApplicationBaseUpdate(c, subApp, state.UbuntuBase("20.04"))
  2042  
  2043  	subApp2, err := s.State.Application("multi-series-subordinate2")
  2044  	c.Assert(err, jc.ErrorIsNil)
  2045  	assertApplicationBaseUpdate(c, subApp2, state.UbuntuBase("20.04"))
  2046  }
  2047  
  2048  func assertNoSettingsRef(c *gc.C, st *state.State, appName string, sch *state.Charm) {
  2049  	cURL := sch.URL()
  2050  	_, err := state.ApplicationSettingsRefCount(st, appName, &cURL)
  2051  	c.Assert(errors.Cause(err), jc.Satisfies, errors.IsNotFound)
  2052  }
  2053  
  2054  func assertSettingsRef(c *gc.C, st *state.State, appName string, sch *state.Charm, refcount int) {
  2055  	cURL := sch.URL()
  2056  	rc, err := state.ApplicationSettingsRefCount(st, appName, &cURL)
  2057  	c.Assert(err, jc.ErrorIsNil)
  2058  	c.Assert(rc, gc.Equals, refcount)
  2059  }
  2060  
  2061  func (s *ApplicationSuite) TestSettingsRefCountWorks(c *gc.C) {
  2062  	// This test ensures the application settings per charm URL are
  2063  	// properly reference counted.
  2064  	oldCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 1)
  2065  	newCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 2)
  2066  	appName := "mywp"
  2067  
  2068  	// Both refcounts are zero initially.
  2069  	assertNoSettingsRef(c, s.State, appName, oldCh)
  2070  	assertNoSettingsRef(c, s.State, appName, newCh)
  2071  
  2072  	// app is using oldCh, so its settings refcount is incremented.
  2073  	app := s.AddTestingApplication(c, appName, oldCh)
  2074  	assertSettingsRef(c, s.State, appName, oldCh, 1)
  2075  	assertNoSettingsRef(c, s.State, appName, newCh)
  2076  
  2077  	// Changing to the same charm does not change the refcount.
  2078  	cfg := state.SetCharmConfig{
  2079  		Charm:       oldCh,
  2080  		CharmOrigin: defaultCharmOrigin(oldCh.URL()),
  2081  	}
  2082  	err := app.SetCharm(cfg)
  2083  	c.Assert(err, jc.ErrorIsNil)
  2084  	assertSettingsRef(c, s.State, appName, oldCh, 1)
  2085  	assertNoSettingsRef(c, s.State, appName, newCh)
  2086  
  2087  	// Changing from oldCh to newCh causes the refcount of oldCh's
  2088  	// settings to be decremented, while newCh's settings is
  2089  	// incremented. Consequently, because oldCh's refcount is 0, the
  2090  	// settings doc will be removed.
  2091  	cfg = state.SetCharmConfig{
  2092  		Charm:       newCh,
  2093  		CharmOrigin: defaultCharmOrigin(newCh.URL()),
  2094  	}
  2095  	err = app.SetCharm(cfg)
  2096  	c.Assert(err, jc.ErrorIsNil)
  2097  	assertNoSettingsRef(c, s.State, appName, oldCh)
  2098  	assertSettingsRef(c, s.State, appName, newCh, 1)
  2099  
  2100  	// The same but newCh swapped with oldCh.
  2101  	cfg = state.SetCharmConfig{
  2102  		Charm:       oldCh,
  2103  		CharmOrigin: defaultCharmOrigin(oldCh.URL()),
  2104  	}
  2105  	err = app.SetCharm(cfg)
  2106  	c.Assert(err, jc.ErrorIsNil)
  2107  	assertSettingsRef(c, s.State, appName, oldCh, 1)
  2108  	assertNoSettingsRef(c, s.State, appName, newCh)
  2109  
  2110  	// Adding a unit without a charm URL set does not affect the
  2111  	// refcount.
  2112  	u, err := app.AddUnit(state.AddUnitParams{})
  2113  	c.Assert(err, jc.ErrorIsNil)
  2114  	charmURL := u.CharmURL()
  2115  	c.Assert(charmURL, gc.IsNil)
  2116  	assertSettingsRef(c, s.State, appName, oldCh, 1)
  2117  	assertNoSettingsRef(c, s.State, appName, newCh)
  2118  
  2119  	// Setting oldCh as the units charm URL increments oldCh, which is
  2120  	// used by app as well, hence 2.
  2121  	err = u.SetCharmURL(oldCh.URL())
  2122  	c.Assert(err, jc.ErrorIsNil)
  2123  	charmURL = u.CharmURL()
  2124  	c.Assert(charmURL, gc.NotNil)
  2125  	c.Assert(*charmURL, gc.Equals, oldCh.URL())
  2126  	assertSettingsRef(c, s.State, appName, oldCh, 2)
  2127  	assertNoSettingsRef(c, s.State, appName, newCh)
  2128  
  2129  	// A dead unit does not decrement the refcount.
  2130  	err = u.EnsureDead()
  2131  	c.Assert(err, jc.ErrorIsNil)
  2132  	assertSettingsRef(c, s.State, appName, oldCh, 2)
  2133  	assertNoSettingsRef(c, s.State, appName, newCh)
  2134  
  2135  	// Once the unit is removed, refcount is decremented.
  2136  	err = u.Remove()
  2137  	c.Assert(err, jc.ErrorIsNil)
  2138  	assertSettingsRef(c, s.State, appName, oldCh, 1)
  2139  	assertNoSettingsRef(c, s.State, appName, newCh)
  2140  
  2141  	// Finally, after the application is destroyed and removed (since the
  2142  	// last unit's gone), the refcount is again decremented.
  2143  	err = app.Destroy()
  2144  	c.Assert(err, jc.ErrorIsNil)
  2145  	assertNoSettingsRef(c, s.State, appName, oldCh)
  2146  	assertNoSettingsRef(c, s.State, appName, newCh)
  2147  
  2148  	// Having studiously avoided triggering cleanups throughout,
  2149  	// invoke them now and check that the charms are cleaned up
  2150  	// correctly -- and that a storm of cleanups for the same
  2151  	// charm are not a problem.
  2152  	err = s.State.Cleanup()
  2153  	c.Assert(err, jc.ErrorIsNil)
  2154  	err = oldCh.Refresh()
  2155  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  2156  	err = newCh.Refresh()
  2157  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  2158  }
  2159  
  2160  func (s *ApplicationSuite) TestSettingsRefCreateRace(c *gc.C) {
  2161  	oldCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 1)
  2162  	newCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 2)
  2163  	appName := "mywp"
  2164  
  2165  	app := s.AddTestingApplication(c, appName, oldCh)
  2166  	unit, err := app.AddUnit(state.AddUnitParams{})
  2167  	c.Assert(err, jc.ErrorIsNil)
  2168  
  2169  	// just before setting the unit charm url, switch the application
  2170  	// away from the original charm, causing the attempt to fail
  2171  	// (because the settings have gone away; it's the unit's job to
  2172  	// fail out and handle the new charm when it comes back up
  2173  	// again).
  2174  	dropSettings := func() {
  2175  		cfg := state.SetCharmConfig{
  2176  			Charm:       newCh,
  2177  			CharmOrigin: defaultCharmOrigin(newCh.URL()),
  2178  		}
  2179  		err = app.SetCharm(cfg)
  2180  		c.Assert(err, jc.ErrorIsNil)
  2181  	}
  2182  	defer state.SetBeforeHooks(c, s.State, dropSettings).Check()
  2183  
  2184  	err = unit.SetCharmURL(oldCh.URL())
  2185  	c.Check(err, gc.ErrorMatches, "settings reference: does not exist")
  2186  }
  2187  
  2188  func (s *ApplicationSuite) TestSettingsRefRemoveRace(c *gc.C) {
  2189  	oldCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 1)
  2190  	newCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 2)
  2191  	appName := "mywp"
  2192  
  2193  	app := s.AddTestingApplication(c, appName, oldCh)
  2194  	unit, err := app.AddUnit(state.AddUnitParams{})
  2195  	c.Assert(err, jc.ErrorIsNil)
  2196  
  2197  	// just before updating the app charm url, set that charm url on
  2198  	// a unit to block the removal.
  2199  	grabReference := func() {
  2200  		err := unit.SetCharmURL(oldCh.URL())
  2201  		c.Assert(err, jc.ErrorIsNil)
  2202  	}
  2203  	defer state.SetBeforeHooks(c, s.State, grabReference).Check()
  2204  
  2205  	cfg := state.SetCharmConfig{
  2206  		Charm:       newCh,
  2207  		CharmOrigin: defaultCharmOrigin(newCh.URL()),
  2208  	}
  2209  	err = app.SetCharm(cfg)
  2210  	c.Assert(err, jc.ErrorIsNil)
  2211  
  2212  	// check refs to both settings exist
  2213  	assertSettingsRef(c, s.State, appName, oldCh, 1)
  2214  	assertSettingsRef(c, s.State, appName, newCh, 1)
  2215  }
  2216  
  2217  func assertNoOffersRef(c *gc.C, st *state.State, appName string) {
  2218  	_, err := state.ApplicationOffersRefCount(st, appName)
  2219  	c.Assert(errors.Cause(err), jc.Satisfies, errors.IsNotFound)
  2220  }
  2221  
  2222  func assertOffersRef(c *gc.C, st *state.State, appName string, refcount int) {
  2223  	rc, err := state.ApplicationOffersRefCount(st, appName)
  2224  	c.Assert(err, jc.ErrorIsNil)
  2225  	c.Assert(rc, gc.Equals, refcount)
  2226  }
  2227  
  2228  func (s *ApplicationSuite) TestOffersRefCountWorks(c *gc.C) {
  2229  	// Refcounts are zero initially.
  2230  	assertNoOffersRef(c, s.State, "mysql")
  2231  
  2232  	ao := state.NewApplicationOffers(s.State)
  2233  	_, err := ao.AddOffer(crossmodel.AddApplicationOfferArgs{
  2234  		OfferName:       "hosted-mysql",
  2235  		ApplicationName: "mysql",
  2236  		Endpoints:       map[string]string{"server": "server"},
  2237  		Owner:           s.Owner.Id(),
  2238  	})
  2239  	c.Assert(err, jc.ErrorIsNil)
  2240  	assertOffersRef(c, s.State, "mysql", 1)
  2241  
  2242  	_, err = ao.AddOffer(crossmodel.AddApplicationOfferArgs{
  2243  		OfferName:       "mysql-offer",
  2244  		ApplicationName: "mysql",
  2245  		Endpoints:       map[string]string{"server": "server"},
  2246  		Owner:           s.Owner.Id(),
  2247  	})
  2248  	c.Assert(err, jc.ErrorIsNil)
  2249  	assertOffersRef(c, s.State, "mysql", 2)
  2250  
  2251  	// Once the offer is removed, refcount is decremented.
  2252  	err = ao.Remove("hosted-mysql", false)
  2253  	c.Assert(err, jc.ErrorIsNil)
  2254  	assertOffersRef(c, s.State, "mysql", 1)
  2255  
  2256  	// Trying to destroy the app while there is an offer
  2257  	// succeeds when that offer has no connections
  2258  	err = s.mysql.Destroy()
  2259  	c.Assert(err, jc.ErrorIsNil)
  2260  	assertRemoved(c, s.mysql)
  2261  	assertNoOffersRef(c, s.State, "mysql")
  2262  }
  2263  
  2264  func (s *ApplicationSuite) TestDestroyApplicationRemoveOffers(c *gc.C) {
  2265  	// Refcounts are zero initially.
  2266  	assertNoOffersRef(c, s.State, "mysql")
  2267  
  2268  	ao := state.NewApplicationOffers(s.State)
  2269  	_, err := ao.AddOffer(crossmodel.AddApplicationOfferArgs{
  2270  		OfferName:       "hosted-mysql",
  2271  		ApplicationName: "mysql",
  2272  		Endpoints:       map[string]string{"server": "server"},
  2273  		Owner:           s.Owner.Id(),
  2274  	})
  2275  	c.Assert(err, jc.ErrorIsNil)
  2276  	assertOffersRef(c, s.State, "mysql", 1)
  2277  
  2278  	_, err = ao.AddOffer(crossmodel.AddApplicationOfferArgs{
  2279  		OfferName:       "mysql-offer",
  2280  		ApplicationName: "mysql",
  2281  		Endpoints:       map[string]string{"server": "server"},
  2282  		Owner:           s.Owner.Id(),
  2283  	})
  2284  	c.Assert(err, jc.ErrorIsNil)
  2285  	assertOffersRef(c, s.State, "mysql", 2)
  2286  
  2287  	op := s.mysql.DestroyOperation()
  2288  	op.RemoveOffers = true
  2289  	err = s.State.ApplyOperation(op)
  2290  	c.Assert(err, jc.ErrorIsNil)
  2291  	assertRemoved(c, s.mysql)
  2292  	assertNoOffersRef(c, s.State, "mysql")
  2293  
  2294  	offers, err := ao.AllApplicationOffers()
  2295  	c.Assert(err, jc.ErrorIsNil)
  2296  	c.Assert(offers, gc.HasLen, 0)
  2297  }
  2298  
  2299  func (s *ApplicationSuite) TestOffersRefRace(c *gc.C) {
  2300  	addOffer := func() {
  2301  		ao := state.NewApplicationOffers(s.State)
  2302  		_, err := ao.AddOffer(crossmodel.AddApplicationOfferArgs{
  2303  			OfferName:       "hosted-mysql",
  2304  			ApplicationName: "mysql",
  2305  			Endpoints:       map[string]string{"server": "server"},
  2306  			Owner:           s.Owner.Id(),
  2307  		})
  2308  		c.Assert(err, jc.ErrorIsNil)
  2309  	}
  2310  	defer state.SetBeforeHooks(c, s.State, addOffer).Check()
  2311  
  2312  	err := s.mysql.Destroy()
  2313  	c.Assert(err, jc.ErrorIsNil)
  2314  	assertRemoved(c, s.mysql)
  2315  	assertNoOffersRef(c, s.State, "mysql")
  2316  }
  2317  
  2318  func (s *ApplicationSuite) TestOffersRefRaceWithForce(c *gc.C) {
  2319  	addOffer := func() {
  2320  		ao := state.NewApplicationOffers(s.State)
  2321  		_, err := ao.AddOffer(crossmodel.AddApplicationOfferArgs{
  2322  			OfferName:       "hosted-mysql",
  2323  			ApplicationName: "mysql",
  2324  			Endpoints:       map[string]string{"server": "server"},
  2325  			Owner:           s.Owner.Id(),
  2326  		})
  2327  		c.Assert(err, jc.ErrorIsNil)
  2328  	}
  2329  	defer state.SetBeforeHooks(c, s.State, addOffer).Check()
  2330  
  2331  	op := s.mysql.DestroyOperation()
  2332  	op.Force = true
  2333  	err := s.State.ApplyOperation(op)
  2334  	c.Assert(err, jc.ErrorIsNil)
  2335  	assertRemoved(c, s.mysql)
  2336  	assertNoOffersRef(c, s.State, "mysql")
  2337  }
  2338  
  2339  const mysqlBaseMeta = `
  2340  name: mysql
  2341  summary: "Database engine"
  2342  description: "A pretty popular database"
  2343  provides:
  2344    server: mysql
  2345  `
  2346  const onePeerMeta = `
  2347  peers:
  2348    cluster: mysql
  2349  `
  2350  const onePeerAltMeta = `
  2351  peers:
  2352    minion: helper
  2353  `
  2354  const twoPeersMeta = `
  2355  peers:
  2356    cluster: mysql
  2357    loadbalancer: phony
  2358  `
  2359  
  2360  func (s *ApplicationSuite) assertApplicationRelations(c *gc.C, app *state.Application, expectedKeys ...string) []*state.Relation {
  2361  	rels, err := app.Relations()
  2362  	c.Assert(err, jc.ErrorIsNil)
  2363  	if len(rels) == 0 {
  2364  		return nil
  2365  	}
  2366  	relKeys := make([]string, len(expectedKeys))
  2367  	for i, rel := range rels {
  2368  		relKeys[i] = rel.String()
  2369  	}
  2370  	sort.Strings(relKeys)
  2371  	c.Assert(relKeys, gc.DeepEquals, expectedKeys)
  2372  	return rels
  2373  }
  2374  
  2375  func (s *ApplicationSuite) TestNewPeerRelationsAddedOnUpgrade(c *gc.C) {
  2376  	// Original mysql charm has no peer relations.
  2377  	oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+onePeerMeta, 2)
  2378  	newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+twoPeersMeta, 3)
  2379  
  2380  	// No relations joined yet.
  2381  	s.assertApplicationRelations(c, s.mysql)
  2382  
  2383  	cfg := state.SetCharmConfig{Charm: oldCh, CharmOrigin: defaultCharmOrigin(oldCh.URL())}
  2384  	err := s.mysql.SetCharm(cfg)
  2385  	c.Assert(err, jc.ErrorIsNil)
  2386  	s.assertApplicationRelations(c, s.mysql, "mysql:cluster")
  2387  
  2388  	cfg = state.SetCharmConfig{Charm: newCh, CharmOrigin: defaultCharmOrigin(newCh.URL())}
  2389  	err = s.mysql.SetCharm(cfg)
  2390  	c.Assert(err, jc.ErrorIsNil)
  2391  	rels := s.assertApplicationRelations(c, s.mysql, "mysql:cluster", "mysql:loadbalancer")
  2392  
  2393  	// Check state consistency by attempting to destroy the application.
  2394  	err = s.mysql.Destroy()
  2395  	c.Assert(err, jc.ErrorIsNil)
  2396  	assertRemoved(c, s.mysql)
  2397  
  2398  	// Check the peer relations got destroyed as well.
  2399  	for _, rel := range rels {
  2400  		err = rel.Refresh()
  2401  		c.Assert(err, jc.Satisfies, errors.IsNotFound)
  2402  	}
  2403  }
  2404  
  2405  func (s *ApplicationSuite) TestStalePeerRelationsRemovedOnUpgrade(c *gc.C) {
  2406  	// Original mysql charm has no peer relations.
  2407  	// oldCh is mysql + the peer relation "mysql:cluster"
  2408  	// newCh is mysql + the peer relation "mysql:minion"
  2409  	oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+onePeerMeta, 2)
  2410  	newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+onePeerAltMeta, 42)
  2411  
  2412  	// No relations joined yet.
  2413  	s.assertApplicationRelations(c, s.mysql)
  2414  
  2415  	cfg := state.SetCharmConfig{Charm: oldCh, CharmOrigin: defaultCharmOrigin(oldCh.URL())}
  2416  	err := s.mysql.SetCharm(cfg)
  2417  	c.Assert(err, jc.ErrorIsNil)
  2418  	s.assertApplicationRelations(c, s.mysql, "mysql:cluster")
  2419  
  2420  	// Since the two charms have different URLs, the following SetCharm call
  2421  	// emulates a "juju refresh --switch" request. We expect that any prior
  2422  	// peer relations that are not referenced by the new charm metadata
  2423  	// to be dropped and any new peer relations to be created.
  2424  	cfg = state.SetCharmConfig{
  2425  		Charm:       newCh,
  2426  		CharmOrigin: defaultCharmOrigin(newCh.URL()),
  2427  		ForceUnits:  true,
  2428  	}
  2429  	err = s.mysql.SetCharm(cfg)
  2430  	c.Assert(err, jc.ErrorIsNil)
  2431  
  2432  	rels := s.assertApplicationRelations(c, s.mysql, "mysql:minion")
  2433  
  2434  	// Check state consistency by attempting to destroy the application.
  2435  	err = s.mysql.Destroy()
  2436  	c.Assert(err, jc.ErrorIsNil)
  2437  	assertRemoved(c, s.mysql)
  2438  
  2439  	// Check the peer relations got destroyed as well.
  2440  	for _, rel := range rels {
  2441  		err = rel.Refresh()
  2442  		c.Assert(err, jc.Satisfies, errors.IsNotFound)
  2443  	}
  2444  }
  2445  
  2446  func jujuInfoEp(applicationname string) state.Endpoint {
  2447  	return state.Endpoint{
  2448  		ApplicationName: applicationname,
  2449  		Relation: charm.Relation{
  2450  			Interface: "juju-info",
  2451  			Name:      "juju-info",
  2452  			Role:      charm.RoleProvider,
  2453  			Scope:     charm.ScopeGlobal,
  2454  		},
  2455  	}
  2456  }
  2457  
  2458  func (s *ApplicationSuite) TestTag(c *gc.C) {
  2459  	c.Assert(s.mysql.Tag().String(), gc.Equals, "application-mysql")
  2460  }
  2461  
  2462  func (s *ApplicationSuite) TestMysqlEndpoints(c *gc.C) {
  2463  	_, err := s.mysql.Endpoint("mysql")
  2464  	c.Assert(err, gc.ErrorMatches, `application "mysql" has no "mysql" relation`)
  2465  
  2466  	jiEP, err := s.mysql.Endpoint("juju-info")
  2467  	c.Assert(err, jc.ErrorIsNil)
  2468  	c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("mysql"))
  2469  
  2470  	serverEP, err := s.mysql.Endpoint("server")
  2471  	c.Assert(err, jc.ErrorIsNil)
  2472  	c.Assert(serverEP, gc.DeepEquals, state.Endpoint{
  2473  		ApplicationName: "mysql",
  2474  		Relation: charm.Relation{
  2475  			Interface: "mysql",
  2476  			Name:      "server",
  2477  			Role:      charm.RoleProvider,
  2478  			Scope:     charm.ScopeGlobal,
  2479  		},
  2480  	})
  2481  	serverAdminEP, err := s.mysql.Endpoint("server-admin")
  2482  	c.Assert(err, jc.ErrorIsNil)
  2483  	c.Assert(serverAdminEP, gc.DeepEquals, state.Endpoint{
  2484  		ApplicationName: "mysql",
  2485  		Relation: charm.Relation{
  2486  			Interface: "mysql-root",
  2487  			Name:      "server-admin",
  2488  			Role:      charm.RoleProvider,
  2489  			Scope:     charm.ScopeGlobal,
  2490  		},
  2491  	})
  2492  	monitoringEP, err := s.mysql.Endpoint("metrics-client")
  2493  	c.Assert(err, jc.ErrorIsNil)
  2494  	c.Assert(monitoringEP, gc.DeepEquals, state.Endpoint{
  2495  		ApplicationName: "mysql",
  2496  		Relation: charm.Relation{
  2497  			Interface: "metrics",
  2498  			Name:      "metrics-client",
  2499  			Role:      charm.RoleRequirer,
  2500  			Scope:     charm.ScopeGlobal,
  2501  		},
  2502  	})
  2503  
  2504  	eps, err := s.mysql.Endpoints()
  2505  	c.Assert(err, jc.ErrorIsNil)
  2506  	c.Assert(eps, jc.SameContents, []state.Endpoint{jiEP, serverEP, serverAdminEP, monitoringEP})
  2507  }
  2508  
  2509  func (s *ApplicationSuite) TestRiakEndpoints(c *gc.C) {
  2510  	riak := s.AddTestingApplication(c, "myriak", s.AddTestingCharm(c, "riak"))
  2511  
  2512  	_, err := riak.Endpoint("garble")
  2513  	c.Assert(err, gc.ErrorMatches, `application "myriak" has no "garble" relation`)
  2514  
  2515  	jiEP, err := riak.Endpoint("juju-info")
  2516  	c.Assert(err, jc.ErrorIsNil)
  2517  	c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("myriak"))
  2518  
  2519  	ringEP, err := riak.Endpoint("ring")
  2520  	c.Assert(err, jc.ErrorIsNil)
  2521  	c.Assert(ringEP, gc.DeepEquals, state.Endpoint{
  2522  		ApplicationName: "myriak",
  2523  		Relation: charm.Relation{
  2524  			Interface: "riak",
  2525  			Name:      "ring",
  2526  			Role:      charm.RolePeer,
  2527  			Scope:     charm.ScopeGlobal,
  2528  		},
  2529  	})
  2530  
  2531  	adminEP, err := riak.Endpoint("admin")
  2532  	c.Assert(err, jc.ErrorIsNil)
  2533  	c.Assert(adminEP, gc.DeepEquals, state.Endpoint{
  2534  		ApplicationName: "myriak",
  2535  		Relation: charm.Relation{
  2536  			Interface: "http",
  2537  			Name:      "admin",
  2538  			Role:      charm.RoleProvider,
  2539  			Scope:     charm.ScopeGlobal,
  2540  		},
  2541  	})
  2542  
  2543  	endpointEP, err := riak.Endpoint("endpoint")
  2544  	c.Assert(err, jc.ErrorIsNil)
  2545  	c.Assert(endpointEP, gc.DeepEquals, state.Endpoint{
  2546  		ApplicationName: "myriak",
  2547  		Relation: charm.Relation{
  2548  			Interface: "http",
  2549  			Name:      "endpoint",
  2550  			Role:      charm.RoleProvider,
  2551  			Scope:     charm.ScopeGlobal,
  2552  		},
  2553  	})
  2554  
  2555  	eps, err := riak.Endpoints()
  2556  	c.Assert(err, jc.ErrorIsNil)
  2557  	c.Assert(eps, gc.DeepEquals, []state.Endpoint{adminEP, endpointEP, jiEP, ringEP})
  2558  }
  2559  
  2560  func (s *ApplicationSuite) TestWordpressEndpoints(c *gc.C) {
  2561  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2562  
  2563  	_, err := wordpress.Endpoint("nonsense")
  2564  	c.Assert(err, gc.ErrorMatches, `application "wordpress" has no "nonsense" relation`)
  2565  
  2566  	jiEP, err := wordpress.Endpoint("juju-info")
  2567  	c.Assert(err, jc.ErrorIsNil)
  2568  	c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("wordpress"))
  2569  
  2570  	urlEP, err := wordpress.Endpoint("url")
  2571  	c.Assert(err, jc.ErrorIsNil)
  2572  	c.Assert(urlEP, gc.DeepEquals, state.Endpoint{
  2573  		ApplicationName: "wordpress",
  2574  		Relation: charm.Relation{
  2575  			Interface: "http",
  2576  			Name:      "url",
  2577  			Role:      charm.RoleProvider,
  2578  			Scope:     charm.ScopeGlobal,
  2579  		},
  2580  	})
  2581  
  2582  	ldEP, err := wordpress.Endpoint("logging-dir")
  2583  	c.Assert(err, jc.ErrorIsNil)
  2584  	c.Assert(ldEP, gc.DeepEquals, state.Endpoint{
  2585  		ApplicationName: "wordpress",
  2586  		Relation: charm.Relation{
  2587  			Interface: "logging",
  2588  			Name:      "logging-dir",
  2589  			Role:      charm.RoleProvider,
  2590  			Scope:     charm.ScopeContainer,
  2591  		},
  2592  	})
  2593  
  2594  	mpEP, err := wordpress.Endpoint("monitoring-port")
  2595  	c.Assert(err, jc.ErrorIsNil)
  2596  	c.Assert(mpEP, gc.DeepEquals, state.Endpoint{
  2597  		ApplicationName: "wordpress",
  2598  		Relation: charm.Relation{
  2599  			Interface: "monitoring",
  2600  			Name:      "monitoring-port",
  2601  			Role:      charm.RoleProvider,
  2602  			Scope:     charm.ScopeContainer,
  2603  		},
  2604  	})
  2605  
  2606  	dbEP, err := wordpress.Endpoint("db")
  2607  	c.Assert(err, jc.ErrorIsNil)
  2608  	c.Assert(dbEP, gc.DeepEquals, state.Endpoint{
  2609  		ApplicationName: "wordpress",
  2610  		Relation: charm.Relation{
  2611  			Interface: "mysql",
  2612  			Name:      "db",
  2613  			Role:      charm.RoleRequirer,
  2614  			Scope:     charm.ScopeGlobal,
  2615  			Limit:     1,
  2616  		},
  2617  	})
  2618  
  2619  	cacheEP, err := wordpress.Endpoint("cache")
  2620  	c.Assert(err, jc.ErrorIsNil)
  2621  	c.Assert(cacheEP, gc.DeepEquals, state.Endpoint{
  2622  		ApplicationName: "wordpress",
  2623  		Relation: charm.Relation{
  2624  			Interface: "varnish",
  2625  			Name:      "cache",
  2626  			Role:      charm.RoleRequirer,
  2627  			Scope:     charm.ScopeGlobal,
  2628  			Limit:     2,
  2629  			Optional:  true,
  2630  		},
  2631  	})
  2632  
  2633  	eps, err := wordpress.Endpoints()
  2634  	c.Assert(err, jc.ErrorIsNil)
  2635  	c.Assert(eps, gc.DeepEquals, []state.Endpoint{cacheEP, dbEP, jiEP, ldEP, mpEP, urlEP})
  2636  }
  2637  
  2638  func (s *ApplicationSuite) TestApplicationRefresh(c *gc.C) {
  2639  	s1, err := s.State.Application(s.mysql.Name())
  2640  	c.Assert(err, jc.ErrorIsNil)
  2641  
  2642  	cfg := state.SetCharmConfig{
  2643  		Charm:       s.charm,
  2644  		CharmOrigin: defaultCharmOrigin(s.charm.URL()),
  2645  		ForceUnits:  true,
  2646  	}
  2647  
  2648  	err = s.mysql.SetCharm(cfg)
  2649  	c.Assert(err, jc.ErrorIsNil)
  2650  
  2651  	testch, force, err := s1.Charm()
  2652  	c.Assert(err, jc.ErrorIsNil)
  2653  	c.Assert(force, jc.IsFalse)
  2654  	c.Assert(testch.URL(), gc.DeepEquals, s.charm.URL())
  2655  
  2656  	err = s1.Refresh()
  2657  	c.Assert(err, jc.ErrorIsNil)
  2658  	testch, force, err = s1.Charm()
  2659  	c.Assert(err, jc.ErrorIsNil)
  2660  	c.Assert(force, jc.IsTrue)
  2661  	c.Assert(testch.URL(), gc.DeepEquals, s.charm.URL())
  2662  
  2663  	err = s.mysql.Destroy()
  2664  	c.Assert(err, jc.ErrorIsNil)
  2665  	assertRemoved(c, s.mysql)
  2666  }
  2667  
  2668  func (s *ApplicationSuite) TestSetPassword(c *gc.C) {
  2669  	testSetPassword(c, func() (state.Authenticator, error) {
  2670  		return s.State.Application(s.mysql.Name())
  2671  	})
  2672  }
  2673  
  2674  func (s *ApplicationSuite) TestApplicationExposed(c *gc.C) {
  2675  	// Check that querying for the exposed flag works correctly.
  2676  	c.Assert(s.mysql.IsExposed(), jc.IsFalse)
  2677  
  2678  	// Check that setting and clearing the exposed flag works correctly.
  2679  	err := s.mysql.MergeExposeSettings(nil)
  2680  	c.Assert(err, jc.ErrorIsNil)
  2681  	c.Assert(s.mysql.IsExposed(), jc.IsTrue)
  2682  	err = s.mysql.ClearExposed()
  2683  	c.Assert(err, jc.ErrorIsNil)
  2684  	c.Assert(s.mysql.IsExposed(), jc.IsFalse)
  2685  
  2686  	// Check that setting and clearing the exposed flag repeatedly does not fail.
  2687  	err = s.mysql.MergeExposeSettings(nil)
  2688  	c.Assert(err, jc.ErrorIsNil)
  2689  	err = s.mysql.MergeExposeSettings(nil)
  2690  	c.Assert(err, jc.ErrorIsNil)
  2691  	err = s.mysql.ClearExposed()
  2692  	c.Assert(err, jc.ErrorIsNil)
  2693  	err = s.mysql.ClearExposed()
  2694  	c.Assert(err, jc.ErrorIsNil)
  2695  	err = s.mysql.MergeExposeSettings(nil)
  2696  	c.Assert(err, jc.ErrorIsNil)
  2697  	c.Assert(s.mysql.IsExposed(), jc.IsTrue)
  2698  
  2699  	// Make the application Dying and check that ClearExposed and MergeExposeSettings fail.
  2700  	// TODO(fwereade): maybe application destruction should always unexpose?
  2701  	u, err := s.mysql.AddUnit(state.AddUnitParams{})
  2702  	c.Assert(err, jc.ErrorIsNil)
  2703  	err = s.mysql.Destroy()
  2704  	c.Assert(err, jc.ErrorIsNil)
  2705  	assertLife(c, s.mysql, state.Dying)
  2706  	err = s.mysql.ClearExposed()
  2707  	c.Assert(err, gc.ErrorMatches, notAliveErr)
  2708  	err = s.mysql.MergeExposeSettings(nil)
  2709  	c.Assert(err, gc.ErrorMatches, notAliveErr)
  2710  
  2711  	// Remove the application and check that both fail.
  2712  	err = u.EnsureDead()
  2713  	c.Assert(err, jc.ErrorIsNil)
  2714  	err = u.Remove()
  2715  	c.Assert(err, jc.ErrorIsNil)
  2716  	err = s.mysql.MergeExposeSettings(nil)
  2717  	c.Assert(err, gc.ErrorMatches, notAliveErr)
  2718  	err = s.mysql.ClearExposed()
  2719  	c.Assert(err, gc.ErrorMatches, notAliveErr)
  2720  }
  2721  
  2722  func (s *ApplicationSuite) TestApplicationExposeEndpoints(c *gc.C) {
  2723  	// Check that querying for the exposed flag works correctly.
  2724  	c.Assert(s.mysql.IsExposed(), jc.IsFalse)
  2725  
  2726  	// Check argument validation
  2727  	err := s.mysql.MergeExposeSettings(map[string]state.ExposedEndpoint{
  2728  		"":               {},
  2729  		"bogus-endpoint": {},
  2730  	})
  2731  	c.Assert(err, gc.ErrorMatches, `.*endpoint "bogus-endpoint" not found`)
  2732  	err = s.mysql.MergeExposeSettings(map[string]state.ExposedEndpoint{
  2733  		"server": {ExposeToSpaceIDs: []string{"bogus-space-id"}},
  2734  	})
  2735  	c.Assert(err, gc.ErrorMatches, `.*space with ID "bogus-space-id" not found`)
  2736  	err = s.mysql.MergeExposeSettings(map[string]state.ExposedEndpoint{
  2737  		"server": {ExposeToCIDRs: []string{"not-a-cidr"}},
  2738  	})
  2739  	c.Assert(err, gc.ErrorMatches, `.*unable to parse "not-a-cidr" as a CIDR.*`)
  2740  
  2741  	// Check that the expose parameters are properly persisted
  2742  	exp := map[string]state.ExposedEndpoint{
  2743  		"server": {
  2744  			ExposeToSpaceIDs: []string{network.AlphaSpaceId},
  2745  			ExposeToCIDRs:    []string{"13.37.0.0/16"},
  2746  		},
  2747  	}
  2748  	err = s.mysql.MergeExposeSettings(exp)
  2749  	c.Assert(err, jc.ErrorIsNil)
  2750  
  2751  	c.Assert(s.mysql.ExposedEndpoints(), gc.DeepEquals, exp)
  2752  
  2753  	// Refresh model and ensure that we get the same parameters
  2754  	err = s.mysql.Refresh()
  2755  	c.Assert(err, jc.ErrorIsNil)
  2756  	c.Assert(s.mysql.ExposedEndpoints(), gc.DeepEquals, exp)
  2757  }
  2758  
  2759  func (s *ApplicationSuite) TestApplicationExposeEndpointMergeLogic(c *gc.C) {
  2760  	// Check that querying for the exposed flag works correctly.
  2761  	c.Assert(s.mysql.IsExposed(), jc.IsFalse)
  2762  
  2763  	// Set initial value
  2764  	initial := map[string]state.ExposedEndpoint{
  2765  		"server": {
  2766  			ExposeToSpaceIDs: []string{network.AlphaSpaceId},
  2767  			ExposeToCIDRs:    []string{"13.37.0.0/16"},
  2768  		},
  2769  	}
  2770  	err := s.mysql.MergeExposeSettings(initial)
  2771  	c.Assert(err, jc.ErrorIsNil)
  2772  	c.Assert(s.mysql.ExposedEndpoints(), gc.DeepEquals, initial)
  2773  
  2774  	// The merge call should overwrite the "server" value and append the
  2775  	// entry for "server-admin"
  2776  	updated := map[string]state.ExposedEndpoint{
  2777  		"server": {
  2778  			ExposeToCIDRs: []string{"0.0.0.0/0"},
  2779  		},
  2780  		"server-admin": {
  2781  			ExposeToSpaceIDs: []string{network.AlphaSpaceId},
  2782  			ExposeToCIDRs:    []string{"13.37.0.0/16"},
  2783  		},
  2784  	}
  2785  	err = s.mysql.MergeExposeSettings(updated)
  2786  	c.Assert(err, jc.ErrorIsNil)
  2787  	c.Assert(s.mysql.ExposedEndpoints(), gc.DeepEquals, updated)
  2788  }
  2789  
  2790  func (s *ApplicationSuite) TestApplicationExposeWithoutSpaceAndCIDR(c *gc.C) {
  2791  	// Check that querying for the exposed flag works correctly.
  2792  	c.Assert(s.mysql.IsExposed(), jc.IsFalse)
  2793  
  2794  	err := s.mysql.MergeExposeSettings(map[string]state.ExposedEndpoint{
  2795  		// If the expose params are empty, an implicit 0.0.0.0/0 will
  2796  		// be assumed (equivalent to: juju expose --endpoints server)
  2797  		"server": {},
  2798  	})
  2799  	c.Assert(err, jc.ErrorIsNil)
  2800  
  2801  	exp := map[string]state.ExposedEndpoint{
  2802  		"server": {
  2803  			ExposeToCIDRs: []string{firewall.AllNetworksIPV4CIDR, firewall.AllNetworksIPV6CIDR},
  2804  		},
  2805  	}
  2806  	c.Assert(s.mysql.ExposedEndpoints(), gc.DeepEquals, exp, gc.Commentf("expected the implicit 0.0.0.0/0 and ::/0 CIDRs to be added when an empty ExposedEndpoint value is provided to MergeExposeSettings"))
  2807  }
  2808  
  2809  func (s *ApplicationSuite) TestApplicationUnsetExposeEndpoints(c *gc.C) {
  2810  	// Check that querying for the exposed flag works correctly.
  2811  	c.Assert(s.mysql.IsExposed(), jc.IsFalse)
  2812  
  2813  	// Set initial value
  2814  	initial := map[string]state.ExposedEndpoint{
  2815  		"": {
  2816  			ExposeToCIDRs: []string{"13.37.0.0/16"},
  2817  		},
  2818  		"server": {
  2819  			ExposeToSpaceIDs: []string{network.AlphaSpaceId},
  2820  			ExposeToCIDRs:    []string{"13.37.0.0/16"},
  2821  		},
  2822  	}
  2823  	err := s.mysql.MergeExposeSettings(initial)
  2824  	c.Assert(err, jc.ErrorIsNil)
  2825  	c.Assert(s.mysql.ExposedEndpoints(), gc.DeepEquals, initial)
  2826  
  2827  	// Check argument validation
  2828  	err = s.mysql.UnsetExposeSettings([]string{"bogus-endpoint"})
  2829  	c.Assert(err, gc.ErrorMatches, `.*endpoint "bogus-endpoint" not found`)
  2830  	err = s.mysql.UnsetExposeSettings([]string{"server-admin"})
  2831  	c.Assert(err, gc.ErrorMatches, `.*endpoint "server-admin" is not exposed`)
  2832  
  2833  	// Check unexpose logic
  2834  	err = s.mysql.UnsetExposeSettings([]string{""})
  2835  	c.Assert(err, jc.ErrorIsNil)
  2836  	c.Assert(s.mysql.ExposedEndpoints(), gc.DeepEquals, map[string]state.ExposedEndpoint{
  2837  		"server": {
  2838  			ExposeToSpaceIDs: []string{network.AlphaSpaceId},
  2839  			ExposeToCIDRs:    []string{"13.37.0.0/16"},
  2840  		},
  2841  	}, gc.Commentf("expected the entry of the wildcard endpoint to be removed"))
  2842  	c.Assert(s.mysql.IsExposed(), jc.IsTrue, gc.Commentf("expected application to remain exposed"))
  2843  
  2844  	err = s.mysql.UnsetExposeSettings([]string{"server"})
  2845  	c.Assert(err, jc.ErrorIsNil)
  2846  	c.Assert(s.mysql.ExposedEndpoints(), gc.HasLen, 0)
  2847  	c.Assert(s.mysql.IsExposed(), jc.IsFalse, gc.Commentf("expected exposed flag to be cleared when last expose setting gets removed"))
  2848  }
  2849  
  2850  func (s *ApplicationSuite) TestAddUnit(c *gc.C) {
  2851  	// Check that principal units can be added on their own.
  2852  	c.Assert(s.mysql.UnitCount(), gc.Equals, 0)
  2853  	unitZero, err := s.mysql.AddUnit(state.AddUnitParams{})
  2854  	c.Assert(err, jc.ErrorIsNil)
  2855  	err = s.mysql.Refresh()
  2856  	c.Assert(err, jc.ErrorIsNil)
  2857  	c.Assert(s.mysql.UnitCount(), gc.Equals, 1)
  2858  	c.Assert(unitZero.Name(), gc.Equals, "mysql/0")
  2859  	c.Assert(unitZero.IsPrincipal(), jc.IsTrue)
  2860  	c.Assert(unitZero.SubordinateNames(), gc.HasLen, 0)
  2861  	c.Assert(state.GetUnitModelUUID(unitZero), gc.Equals, s.State.ModelUUID())
  2862  
  2863  	unitOne, err := s.mysql.AddUnit(state.AddUnitParams{})
  2864  	c.Assert(err, jc.ErrorIsNil)
  2865  	c.Assert(unitOne.Name(), gc.Equals, "mysql/1")
  2866  	c.Assert(unitOne.IsPrincipal(), jc.IsTrue)
  2867  	c.Assert(unitOne.SubordinateNames(), gc.HasLen, 0)
  2868  
  2869  	// Assign the principal unit to a machine.
  2870  	m, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
  2871  	c.Assert(err, jc.ErrorIsNil)
  2872  	err = unitZero.AssignToMachine(m)
  2873  	c.Assert(err, jc.ErrorIsNil)
  2874  
  2875  	// Add a subordinate application and check that units cannot be added directly.
  2876  	// to add a subordinate unit.
  2877  	subCharm := s.AddTestingCharm(c, "logging")
  2878  	logging := s.AddTestingApplication(c, "logging", subCharm)
  2879  	_, err = logging.AddUnit(state.AddUnitParams{})
  2880  	c.Assert(err, gc.ErrorMatches, `cannot add unit to application "logging": application is a subordinate`)
  2881  
  2882  	// Indirectly create a subordinate unit by adding a relation and entering
  2883  	// scope as a principal.
  2884  	eps, err := s.State.InferEndpoints("logging", "mysql")
  2885  	c.Assert(err, jc.ErrorIsNil)
  2886  	rel, err := s.State.AddRelation(eps...)
  2887  	c.Assert(err, jc.ErrorIsNil)
  2888  	ru, err := rel.Unit(unitZero)
  2889  	c.Assert(err, jc.ErrorIsNil)
  2890  	err = ru.EnterScope(nil)
  2891  	c.Assert(err, jc.ErrorIsNil)
  2892  	subZero, err := s.State.Unit("logging/0")
  2893  	c.Assert(err, jc.ErrorIsNil)
  2894  
  2895  	// Check that once it's refreshed unitZero has subordinates.
  2896  	err = unitZero.Refresh()
  2897  	c.Assert(err, jc.ErrorIsNil)
  2898  	c.Assert(unitZero.SubordinateNames(), gc.DeepEquals, []string{"logging/0"})
  2899  
  2900  	// Check the subordinate unit has been assigned its principal's machine.
  2901  	id, err := subZero.AssignedMachineId()
  2902  	c.Assert(err, jc.ErrorIsNil)
  2903  	c.Assert(id, gc.Equals, m.Id())
  2904  }
  2905  
  2906  func (s *ApplicationSuite) TestAddUnitWhenNotAlive(c *gc.C) {
  2907  	u, err := s.mysql.AddUnit(state.AddUnitParams{})
  2908  	c.Assert(err, jc.ErrorIsNil)
  2909  	err = s.mysql.Destroy()
  2910  	c.Assert(err, jc.ErrorIsNil)
  2911  	assertLife(c, s.mysql, state.Dying)
  2912  	_, err = s.mysql.AddUnit(state.AddUnitParams{})
  2913  	c.Assert(err, gc.ErrorMatches, `cannot add unit to application "mysql": application is not found or not alive`)
  2914  	c.Assert(u.EnsureDead(), jc.ErrorIsNil)
  2915  	c.Assert(u.Remove(), jc.ErrorIsNil)
  2916  	c.Assert(s.State.Cleanup(), jc.ErrorIsNil)
  2917  	_, err = s.mysql.AddUnit(state.AddUnitParams{})
  2918  	c.Assert(err, gc.ErrorMatches, `cannot add unit to application "mysql": application "mysql" not found`)
  2919  }
  2920  
  2921  func (s *ApplicationSuite) TestAddCAASUnit(c *gc.C) {
  2922  	st := s.Factory.MakeModel(c, &factory.ModelParams{
  2923  		Name: "caas-model",
  2924  		Type: state.ModelTypeCAAS,
  2925  	})
  2926  	defer st.Close()
  2927  	f := factory.NewFactory(st, s.StatePool)
  2928  	ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"})
  2929  	app := f.MakeApplication(c, &factory.ApplicationParams{Name: "gitlab", Charm: ch})
  2930  
  2931  	unitZero, err := app.AddUnit(state.AddUnitParams{})
  2932  	c.Assert(err, jc.ErrorIsNil)
  2933  	c.Assert(unitZero.Name(), gc.Equals, "gitlab/0")
  2934  	c.Assert(unitZero.IsPrincipal(), jc.IsTrue)
  2935  	c.Assert(unitZero.SubordinateNames(), gc.HasLen, 0)
  2936  	c.Assert(state.GetUnitModelUUID(unitZero), gc.Equals, st.ModelUUID())
  2937  
  2938  	err = unitZero.SetWorkloadVersion("3.combined")
  2939  	c.Assert(err, jc.ErrorIsNil)
  2940  	version, err := unitZero.WorkloadVersion()
  2941  	c.Assert(err, jc.ErrorIsNil)
  2942  	c.Check(version, gc.Equals, "3.combined")
  2943  
  2944  	err = unitZero.SetMeterStatus(state.MeterGreen.String(), "all good")
  2945  	c.Assert(err, jc.ErrorIsNil)
  2946  	ms, err := unitZero.GetMeterStatus()
  2947  	c.Assert(err, jc.ErrorIsNil)
  2948  	c.Assert(ms.Code, gc.Equals, state.MeterGreen)
  2949  	c.Assert(ms.Info, gc.Equals, "all good")
  2950  
  2951  	// But they do have status.
  2952  	us, err := unitZero.Status()
  2953  	c.Assert(err, jc.ErrorIsNil)
  2954  	us.Since = nil
  2955  	c.Assert(us, jc.DeepEquals, status.StatusInfo{
  2956  		Status:  status.Waiting,
  2957  		Message: status.MessageInstallingAgent,
  2958  		Data:    map[string]interface{}{},
  2959  	})
  2960  	as, err := unitZero.AgentStatus()
  2961  	c.Assert(err, jc.ErrorIsNil)
  2962  	c.Assert(as.Since, gc.NotNil)
  2963  	as.Since = nil
  2964  	c.Assert(as, jc.DeepEquals, status.StatusInfo{
  2965  		Status: status.Allocating,
  2966  		Data:   map[string]interface{}{},
  2967  	})
  2968  }
  2969  
  2970  func (s *ApplicationSuite) TestAgentTools(c *gc.C) {
  2971  	st := s.Factory.MakeModel(c, &factory.ModelParams{
  2972  		Name: "caas-model",
  2973  		Type: state.ModelTypeCAAS,
  2974  	})
  2975  	defer st.Close()
  2976  	f := factory.NewFactory(st, s.StatePool)
  2977  	ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"})
  2978  	app := f.MakeApplication(c, &factory.ApplicationParams{Charm: ch})
  2979  	agentTools := version.Binary{
  2980  		Number:  jujuversion.Current,
  2981  		Arch:    arch.HostArch(),
  2982  		Release: "ubuntu",
  2983  	}
  2984  
  2985  	tools, err := app.AgentTools()
  2986  	c.Assert(err, jc.ErrorIsNil)
  2987  	c.Assert(tools.Version, gc.DeepEquals, agentTools)
  2988  }
  2989  
  2990  func (s *ApplicationSuite) TestSetAgentVersion(c *gc.C) {
  2991  	st := s.Factory.MakeCAASModel(c, nil)
  2992  	defer st.Close()
  2993  	f := factory.NewFactory(st, s.StatePool)
  2994  	ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"})
  2995  	app := f.MakeApplication(c, &factory.ApplicationParams{Charm: ch})
  2996  
  2997  	agentVersion := version.MustParseBinary("2.0.1-ubuntu-and64")
  2998  	err := app.SetAgentVersion(agentVersion)
  2999  	c.Assert(err, jc.ErrorIsNil)
  3000  
  3001  	err = app.Refresh()
  3002  	c.Assert(err, jc.ErrorIsNil)
  3003  
  3004  	tools, err := app.AgentTools()
  3005  	c.Assert(err, jc.ErrorIsNil)
  3006  	c.Assert(tools.Version, gc.DeepEquals, agentVersion)
  3007  }
  3008  
  3009  func (s *ApplicationSuite) TestAddUnitWithProviderIdNonCAASModel(c *gc.C) {
  3010  	u, err := s.mysql.AddUnit(state.AddUnitParams{ProviderId: strPtr("provider-id")})
  3011  	c.Assert(err, jc.ErrorIsNil)
  3012  	_, err = u.ContainerInfo()
  3013  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  3014  }
  3015  
  3016  func (s *ApplicationSuite) TestReadUnit(c *gc.C) {
  3017  	_, err := s.mysql.AddUnit(state.AddUnitParams{})
  3018  	c.Assert(err, jc.ErrorIsNil)
  3019  	_, err = s.mysql.AddUnit(state.AddUnitParams{})
  3020  	c.Assert(err, jc.ErrorIsNil)
  3021  
  3022  	// Check that retrieving a unit from state works correctly.
  3023  	unit, err := s.State.Unit("mysql/0")
  3024  	c.Assert(err, jc.ErrorIsNil)
  3025  	c.Assert(unit.Name(), gc.Equals, "mysql/0")
  3026  
  3027  	// Check that retrieving a non-existent or an invalidly
  3028  	// named unit fail nicely.
  3029  	unit, err = s.State.Unit("mysql")
  3030  	c.Assert(err, gc.ErrorMatches, `"mysql" is not a valid unit name`)
  3031  	unit, err = s.State.Unit("mysql/0/0")
  3032  	c.Assert(err, gc.ErrorMatches, `"mysql/0/0" is not a valid unit name`)
  3033  	unit, err = s.State.Unit("pressword/0")
  3034  	c.Assert(err, gc.ErrorMatches, `unit "pressword/0" not found`)
  3035  
  3036  	units, err := s.mysql.AllUnits()
  3037  	c.Assert(err, jc.ErrorIsNil)
  3038  	c.Assert(sortedUnitNames(units), gc.DeepEquals, []string{"mysql/0", "mysql/1"})
  3039  }
  3040  
  3041  func (s *ApplicationSuite) TestReadUnitWhenDying(c *gc.C) {
  3042  	// Test that we can still read units when the application is Dying...
  3043  	unit, err := s.mysql.AddUnit(state.AddUnitParams{})
  3044  	c.Assert(err, jc.ErrorIsNil)
  3045  	preventUnitDestroyRemove(c, unit)
  3046  	err = s.mysql.Destroy()
  3047  	c.Assert(err, jc.ErrorIsNil)
  3048  	assertLife(c, s.mysql, state.Dying)
  3049  	_, err = s.mysql.AllUnits()
  3050  	c.Assert(err, jc.ErrorIsNil)
  3051  	_, err = s.State.Unit("mysql/0")
  3052  	c.Assert(err, jc.ErrorIsNil)
  3053  
  3054  	// ...and when those units are Dying or Dead...
  3055  	testWhenDying(c, unit, noErr, noErr, func() error {
  3056  		_, err := s.mysql.AllUnits()
  3057  		return err
  3058  	}, func() error {
  3059  		_, err := s.State.Unit("mysql/0")
  3060  		return err
  3061  	})
  3062  
  3063  	// ...and even, in a very limited way, when the application itself is removed.
  3064  	removeAllUnits(c, s.mysql)
  3065  	_, err = s.mysql.AllUnits()
  3066  	c.Assert(err, jc.ErrorIsNil)
  3067  }
  3068  
  3069  func (s *ApplicationSuite) TestDestroySimple(c *gc.C) {
  3070  	err := s.mysql.Destroy()
  3071  	c.Assert(err, jc.ErrorIsNil)
  3072  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
  3073  	err = s.mysql.Refresh()
  3074  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  3075  }
  3076  
  3077  func (s *ApplicationSuite) TestDestroyRemovesStatusHistory(c *gc.C) {
  3078  	err := s.mysql.SetStatus(status.StatusInfo{
  3079  		Status: status.Active,
  3080  	})
  3081  	c.Assert(err, jc.ErrorIsNil)
  3082  	filter := status.StatusHistoryFilter{Size: 100}
  3083  	agentInfo, err := s.mysql.StatusHistory(filter)
  3084  	c.Assert(err, jc.ErrorIsNil)
  3085  	c.Assert(len(agentInfo), gc.Equals, 2)
  3086  
  3087  	err = s.mysql.Destroy()
  3088  	c.Assert(err, jc.ErrorIsNil)
  3089  
  3090  	agentInfo, err = s.mysql.StatusHistory(filter)
  3091  	c.Assert(err, jc.ErrorIsNil)
  3092  	c.Assert(agentInfo, gc.HasLen, 0)
  3093  }
  3094  
  3095  func (s *ApplicationSuite) TestDestroyStillHasUnits(c *gc.C) {
  3096  	unit, err := s.mysql.AddUnit(state.AddUnitParams{})
  3097  	c.Assert(err, jc.ErrorIsNil)
  3098  	err = s.mysql.Destroy()
  3099  	c.Assert(err, jc.ErrorIsNil)
  3100  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
  3101  
  3102  	c.Assert(unit.EnsureDead(), jc.ErrorIsNil)
  3103  	c.Assert(s.mysql.Refresh(), jc.ErrorIsNil)
  3104  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
  3105  
  3106  	c.Assert(unit.Remove(), jc.ErrorIsNil)
  3107  	c.Assert(s.State.Cleanup(), jc.ErrorIsNil)
  3108  	err = s.mysql.Refresh()
  3109  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  3110  }
  3111  
  3112  func (s *ApplicationSuite) TestDestroyOnceHadUnits(c *gc.C) {
  3113  	unit, err := s.mysql.AddUnit(state.AddUnitParams{})
  3114  	c.Assert(err, jc.ErrorIsNil)
  3115  	err = unit.EnsureDead()
  3116  	c.Assert(err, jc.ErrorIsNil)
  3117  	err = unit.Remove()
  3118  	c.Assert(err, jc.ErrorIsNil)
  3119  
  3120  	err = s.mysql.Destroy()
  3121  	c.Assert(err, jc.ErrorIsNil)
  3122  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
  3123  	err = s.mysql.Refresh()
  3124  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  3125  }
  3126  
  3127  func (s *ApplicationSuite) TestDestroyStaleNonZeroUnitCount(c *gc.C) {
  3128  	unit, err := s.mysql.AddUnit(state.AddUnitParams{})
  3129  	c.Assert(err, jc.ErrorIsNil)
  3130  	err = s.mysql.Refresh()
  3131  	c.Assert(err, jc.ErrorIsNil)
  3132  	err = unit.EnsureDead()
  3133  	c.Assert(err, jc.ErrorIsNil)
  3134  	err = unit.Remove()
  3135  	c.Assert(err, jc.ErrorIsNil)
  3136  
  3137  	err = s.mysql.Destroy()
  3138  	c.Assert(err, jc.ErrorIsNil)
  3139  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
  3140  	err = s.mysql.Refresh()
  3141  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  3142  }
  3143  
  3144  func (s *ApplicationSuite) TestDestroyStaleZeroUnitCount(c *gc.C) {
  3145  	unit, err := s.mysql.AddUnit(state.AddUnitParams{})
  3146  	c.Assert(err, jc.ErrorIsNil)
  3147  
  3148  	err = s.mysql.Destroy()
  3149  	c.Assert(err, jc.ErrorIsNil)
  3150  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
  3151  
  3152  	err = s.mysql.Refresh()
  3153  	c.Assert(err, jc.ErrorIsNil)
  3154  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
  3155  
  3156  	err = unit.EnsureDead()
  3157  	c.Assert(err, jc.ErrorIsNil)
  3158  	err = s.mysql.Refresh()
  3159  	c.Assert(err, jc.ErrorIsNil)
  3160  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
  3161  
  3162  	c.Assert(unit.Remove(), jc.ErrorIsNil)
  3163  	c.Assert(s.State.Cleanup(), jc.ErrorIsNil)
  3164  	err = s.mysql.Refresh()
  3165  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  3166  }
  3167  
  3168  func (s *ApplicationSuite) TestDestroyWithRemovableRelation(c *gc.C) {
  3169  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  3170  	eps, err := s.State.InferEndpoints("wordpress", "mysql")
  3171  	c.Assert(err, jc.ErrorIsNil)
  3172  	rel, err := s.State.AddRelation(eps...)
  3173  	c.Assert(err, jc.ErrorIsNil)
  3174  
  3175  	// Destroy a application with no units in relation scope; check application and
  3176  	// unit removed.
  3177  	err = wordpress.Destroy()
  3178  	c.Assert(err, jc.ErrorIsNil)
  3179  	err = wordpress.Refresh()
  3180  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  3181  	err = rel.Refresh()
  3182  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  3183  }
  3184  
  3185  func (s *ApplicationSuite) TestDestroyWithRemovableApplicationOpenedPortRanges(c *gc.C) {
  3186  	st, app := s.addCAASSidecarApplication(c)
  3187  	defer st.Close()
  3188  
  3189  	appPortRanges, err := app.OpenedPortRanges()
  3190  	c.Assert(err, jc.ErrorIsNil)
  3191  	c.Assert(appPortRanges.UniquePortRanges(), gc.HasLen, 0)
  3192  
  3193  	unit0, err := app.AddUnit(state.AddUnitParams{})
  3194  	c.Assert(err, jc.ErrorIsNil)
  3195  	portRangesUnit0, err := unit0.OpenedPortRanges()
  3196  	c.Assert(err, jc.ErrorIsNil)
  3197  	portRangesUnit0.Open(allEndpoints, network.MustParsePortRange("3000/tcp"))
  3198  	portRangesUnit0.Open(allEndpoints, network.MustParsePortRange("3001/tcp"))
  3199  	c.Assert(st.ApplyOperation(portRangesUnit0.Changes()), jc.ErrorIsNil)
  3200  
  3201  	portRangesUnit0, err = unit0.OpenedPortRanges()
  3202  	c.Assert(err, jc.ErrorIsNil)
  3203  	c.Assert(portRangesUnit0.UniquePortRanges(), gc.HasLen, 2)
  3204  
  3205  	unit1, err := app.AddUnit(state.AddUnitParams{})
  3206  	c.Assert(err, jc.ErrorIsNil)
  3207  	portRangesUnit1, err := unit1.OpenedPortRanges()
  3208  	c.Assert(err, jc.ErrorIsNil)
  3209  	portRangesUnit1.Open(allEndpoints, network.MustParsePortRange("3001/tcp"))
  3210  	portRangesUnit1.Open(allEndpoints, network.MustParsePortRange("3002/tcp"))
  3211  	c.Assert(st.ApplyOperation(portRangesUnit1.Changes()), jc.ErrorIsNil)
  3212  
  3213  	portRangesUnit1, err = unit1.OpenedPortRanges()
  3214  	c.Assert(err, jc.ErrorIsNil)
  3215  	c.Assert(portRangesUnit1.UniquePortRanges(), gc.HasLen, 2)
  3216  
  3217  	appPortRanges, err = app.OpenedPortRanges()
  3218  	c.Assert(err, jc.ErrorIsNil)
  3219  	c.Assert(appPortRanges.UniquePortRanges(), gc.HasLen, 3)
  3220  
  3221  	portRangesUnit1.Close(allEndpoints, network.MustParsePortRange("3002/tcp"))
  3222  	c.Assert(st.ApplyOperation(portRangesUnit1.Changes()), jc.ErrorIsNil)
  3223  
  3224  	portRangesUnit1, err = unit1.OpenedPortRanges()
  3225  	c.Assert(err, jc.ErrorIsNil)
  3226  	c.Assert(portRangesUnit1.UniquePortRanges(), gc.HasLen, 1)
  3227  
  3228  	appPortRanges, err = app.OpenedPortRanges()
  3229  	c.Assert(err, jc.ErrorIsNil)
  3230  	c.Assert(appPortRanges.UniquePortRanges(), gc.HasLen, 2)
  3231  
  3232  	portRangesUnit1.Open(allEndpoints, network.MustParsePortRange("3003/tcp"))
  3233  	c.Assert(st.ApplyOperation(portRangesUnit1.Changes()), jc.ErrorIsNil)
  3234  
  3235  	appPortRanges, err = app.OpenedPortRanges()
  3236  	c.Assert(err, jc.ErrorIsNil)
  3237  	c.Assert(appPortRanges.UniquePortRanges(), gc.HasLen, 3)
  3238  
  3239  	err = unit1.EnsureDead()
  3240  	c.Assert(err, jc.ErrorIsNil)
  3241  	err = unit1.Remove()
  3242  	c.Assert(err, jc.ErrorIsNil)
  3243  
  3244  	appPortRanges, err = app.OpenedPortRanges()
  3245  	c.Assert(err, jc.ErrorIsNil)
  3246  	c.Assert(appPortRanges.UniquePortRanges(), gc.HasLen, 2)
  3247  
  3248  	// Remove all units, all opened ports should be removed.
  3249  	err = unit0.EnsureDead()
  3250  	c.Assert(err, jc.ErrorIsNil)
  3251  	err = unit0.Remove()
  3252  	c.Assert(err, jc.ErrorIsNil)
  3253  	err = unit1.EnsureDead()
  3254  	c.Assert(err, jc.ErrorIsNil)
  3255  	err = unit1.Remove()
  3256  	c.Assert(err, jc.ErrorIsNil)
  3257  
  3258  	appPortRanges, err = app.OpenedPortRanges()
  3259  	c.Assert(err, jc.ErrorIsNil)
  3260  	c.Assert(appPortRanges.UniquePortRanges(), gc.HasLen, 0)
  3261  
  3262  	err = app.Destroy()
  3263  	c.Assert(err, jc.ErrorIsNil)
  3264  }
  3265  
  3266  func (s *ApplicationSuite) TestOpenedPortRanges(c *gc.C) {
  3267  	st, app := s.addCAASSidecarApplication(c)
  3268  	defer st.Close()
  3269  	unit, err := app.AddUnit(state.AddUnitParams{})
  3270  	c.Assert(err, jc.ErrorIsNil)
  3271  	portRanges, err := unit.OpenedPortRanges()
  3272  	c.Assert(err, jc.ErrorIsNil)
  3273  
  3274  	flush := func(expectedErr string) {
  3275  		if len(expectedErr) == 0 {
  3276  			c.Assert(st.ApplyOperation(portRanges.Changes()), jc.ErrorIsNil)
  3277  		} else {
  3278  			c.Assert(st.ApplyOperation(portRanges.Changes()), gc.ErrorMatches, expectedErr)
  3279  		}
  3280  		portRanges, err = unit.OpenedPortRanges()
  3281  		c.Assert(err, jc.ErrorIsNil)
  3282  	}
  3283  
  3284  	c.Assert(portRanges.UniquePortRanges(), gc.HasLen, 0)
  3285  	portRanges.Open(allEndpoints, network.MustParsePortRange("3000/tcp"))
  3286  	portRanges.Open("data-port", network.MustParsePortRange("2000/udp"))
  3287  	// All good.
  3288  	flush(``)
  3289  	c.Assert(portRanges.UnitName(), jc.DeepEquals, `cockroachdb/0`)
  3290  	c.Assert(portRanges.UniquePortRanges(), jc.DeepEquals, []network.PortRange{
  3291  		network.MustParsePortRange("3000/tcp"),
  3292  		network.MustParsePortRange("2000/udp"),
  3293  	})
  3294  	c.Assert(portRanges.ByEndpoint(), jc.DeepEquals, network.GroupedPortRanges{
  3295  		allEndpoints: []network.PortRange{network.MustParsePortRange("3000/tcp")},
  3296  		"data-port":  []network.PortRange{network.MustParsePortRange("2000/udp")},
  3297  	})
  3298  
  3299  	// Errors for unknown endpoint.
  3300  	portRanges.Open("bad-endpoint", network.MustParsePortRange("2000/udp"))
  3301  	flush(`cannot open/close ports: open port range: endpoint "bad-endpoint" for application "cockroachdb" not found`)
  3302  	c.Assert(portRanges.ByEndpoint(), jc.DeepEquals, network.GroupedPortRanges{
  3303  		allEndpoints: []network.PortRange{network.MustParsePortRange("3000/tcp")},
  3304  		"data-port":  []network.PortRange{network.MustParsePortRange("2000/udp")},
  3305  	})
  3306  
  3307  	// No ops for duplicated Open.
  3308  	portRanges.Open("data-port", network.MustParsePortRange("2000/udp"))
  3309  	flush(``)
  3310  	c.Assert(portRanges.ByEndpoint(), jc.DeepEquals, network.GroupedPortRanges{
  3311  		allEndpoints: []network.PortRange{network.MustParsePortRange("3000/tcp")},
  3312  		"data-port":  []network.PortRange{network.MustParsePortRange("2000/udp")},
  3313  	})
  3314  
  3315  	// Close one port.
  3316  	portRanges.Close("data-port", network.MustParsePortRange("2000/udp"))
  3317  	flush(``)
  3318  	c.Assert(portRanges.ByEndpoint(), jc.DeepEquals, network.GroupedPortRanges{
  3319  		allEndpoints: []network.PortRange{network.MustParsePortRange("3000/tcp")},
  3320  	})
  3321  
  3322  	// No ops for Close non existing port.
  3323  	portRanges.Close("data-port", network.MustParsePortRange("2000/udp"))
  3324  	flush(``)
  3325  	c.Assert(portRanges.ByEndpoint(), jc.DeepEquals, network.GroupedPortRanges{
  3326  		allEndpoints: []network.PortRange{network.MustParsePortRange("3000/tcp")},
  3327  	})
  3328  
  3329  	// Destroy the application; check application and
  3330  	// openedApplicationportRanges removed.
  3331  	err = unit.EnsureDead()
  3332  	c.Assert(err, jc.ErrorIsNil)
  3333  	err = unit.Remove()
  3334  	c.Assert(err, jc.ErrorIsNil)
  3335  
  3336  	appPortRanges, err := app.OpenedPortRanges()
  3337  	c.Assert(err, jc.ErrorIsNil)
  3338  	c.Assert(appPortRanges.UniquePortRanges(), gc.HasLen, 0)
  3339  
  3340  	err = app.Destroy()
  3341  	c.Assert(err, jc.ErrorIsNil)
  3342  }
  3343  
  3344  func (s *ApplicationSuite) TestDestroyWithReferencedRelation(c *gc.C) {
  3345  	s.assertDestroyWithReferencedRelation(c, true)
  3346  }
  3347  
  3348  func (s *ApplicationSuite) TestDestroyWithReferencedRelationStaleCount(c *gc.C) {
  3349  	s.assertDestroyWithReferencedRelation(c, false)
  3350  }
  3351  
  3352  func (s *ApplicationSuite) assertDestroyWithReferencedRelation(c *gc.C, refresh bool) {
  3353  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  3354  	eps, err := s.State.InferEndpoints("wordpress", "mysql")
  3355  	c.Assert(err, jc.ErrorIsNil)
  3356  	rel0, err := s.State.AddRelation(eps...)
  3357  	c.Assert(err, jc.ErrorIsNil)
  3358  
  3359  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
  3360  	eps, err = s.State.InferEndpoints("logging", "mysql")
  3361  	c.Assert(err, jc.ErrorIsNil)
  3362  	rel1, err := s.State.AddRelation(eps...)
  3363  	c.Assert(err, jc.ErrorIsNil)
  3364  
  3365  	// Add a separate reference to the first relation.
  3366  	unit, err := wordpress.AddUnit(state.AddUnitParams{})
  3367  	c.Assert(err, jc.ErrorIsNil)
  3368  	ru, err := rel0.Unit(unit)
  3369  	c.Assert(err, jc.ErrorIsNil)
  3370  	err = ru.EnterScope(nil)
  3371  	c.Assert(err, jc.ErrorIsNil)
  3372  
  3373  	// Optionally update the application document to get correct relation counts.
  3374  	if refresh {
  3375  		err = s.mysql.Destroy()
  3376  		c.Assert(err, jc.ErrorIsNil)
  3377  	}
  3378  
  3379  	// Destroy, and check that the first relation becomes Dying...
  3380  	c.Assert(s.mysql.Destroy(), jc.ErrorIsNil)
  3381  	err = rel0.Refresh()
  3382  	c.Assert(err, jc.ErrorIsNil)
  3383  	c.Assert(rel0.Life(), gc.Equals, state.Dying)
  3384  
  3385  	// ...while the second is removed directly.
  3386  	err = rel1.Refresh()
  3387  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  3388  
  3389  	// Drop the last reference to the first relation; check the relation and
  3390  	// the application are are both removed.
  3391  	c.Assert(ru.LeaveScope(), jc.ErrorIsNil)
  3392  	c.Assert(s.State.Cleanup(), jc.ErrorIsNil)
  3393  	err = s.mysql.Refresh()
  3394  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  3395  	err = rel0.Refresh()
  3396  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  3397  }
  3398  
  3399  func (s *ApplicationSuite) TestDestroyQueuesUnitCleanup(c *gc.C) {
  3400  	// Add 5 units; block quick-remove of mysql/1 and mysql/3
  3401  	units := make([]*state.Unit, 5)
  3402  	for i := range units {
  3403  		unit, err := s.mysql.AddUnit(state.AddUnitParams{})
  3404  		c.Assert(err, jc.ErrorIsNil)
  3405  		units[i] = unit
  3406  		if i%2 != 0 {
  3407  			preventUnitDestroyRemove(c, unit)
  3408  		}
  3409  	}
  3410  
  3411  	s.assertNoCleanup(c)
  3412  
  3413  	// Destroy mysql, and check units are not touched.
  3414  	err := s.mysql.Destroy()
  3415  	c.Assert(err, jc.ErrorIsNil)
  3416  	assertLife(c, s.mysql, state.Dying)
  3417  	for _, unit := range units {
  3418  		assertLife(c, unit, state.Alive)
  3419  	}
  3420  
  3421  	s.assertNeedsCleanup(c)
  3422  
  3423  	// Run the cleanup and check the units.
  3424  	err = s.State.Cleanup()
  3425  	c.Assert(err, jc.ErrorIsNil)
  3426  	for i, unit := range units {
  3427  		if i%2 != 0 {
  3428  			assertLife(c, unit, state.Dying)
  3429  		} else {
  3430  			assertRemoved(c, unit)
  3431  		}
  3432  	}
  3433  
  3434  	// Check for queued unit cleanups, and run them.
  3435  	s.assertNeedsCleanup(c)
  3436  	err = s.State.Cleanup()
  3437  	c.Assert(err, jc.ErrorIsNil)
  3438  
  3439  	// Check we're now clean.
  3440  	s.assertNoCleanup(c)
  3441  }
  3442  
  3443  func (s *ApplicationSuite) TestRemoveApplicationMachine(c *gc.C) {
  3444  	unit, err := s.mysql.AddUnit(state.AddUnitParams{})
  3445  	c.Assert(err, jc.ErrorIsNil)
  3446  	machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
  3447  	c.Assert(err, jc.ErrorIsNil)
  3448  	c.Assert(unit.AssignToMachine(machine), gc.IsNil)
  3449  
  3450  	c.Assert(s.mysql.Destroy(), gc.IsNil)
  3451  	assertLife(c, s.mysql, state.Dying)
  3452  
  3453  	// Application.Destroy adds units to cleanup, make it happen now.
  3454  	c.Assert(s.State.Cleanup(), gc.IsNil)
  3455  
  3456  	c.Assert(unit.Refresh(), jc.Satisfies, errors.IsNotFound)
  3457  	assertLife(c, machine, state.Dying)
  3458  }
  3459  
  3460  func (s *ApplicationSuite) TestDestroyRemoveAlsoDeletesSecretPermissions(c *gc.C) {
  3461  	store := state.NewSecrets(s.State)
  3462  	uri := secrets.NewURI()
  3463  	cp := state.CreateSecretParams{
  3464  		Version: 1,
  3465  		Owner:   s.mysql.Tag(),
  3466  		UpdateSecretParams: state.UpdateSecretParams{
  3467  			LeaderToken: &fakeToken{},
  3468  			Data:        map[string]string{"foo": "bar"},
  3469  		},
  3470  	}
  3471  	_, err := store.CreateSecret(uri, cp)
  3472  	c.Assert(err, jc.ErrorIsNil)
  3473  
  3474  	// Make a relation for the access scope.
  3475  	endpoint1, err := s.mysql.Endpoint("juju-info")
  3476  	c.Assert(err, jc.ErrorIsNil)
  3477  	application2 := s.Factory.MakeApplication(c, &factory.ApplicationParams{
  3478  		Charm: s.Factory.MakeCharm(c, &factory.CharmParams{
  3479  			Name: "logging",
  3480  		}),
  3481  	})
  3482  	endpoint2, err := application2.Endpoint("info")
  3483  	c.Assert(err, jc.ErrorIsNil)
  3484  	rel := s.Factory.MakeRelation(c, &factory.RelationParams{
  3485  		Endpoints: []state.Endpoint{endpoint1, endpoint2},
  3486  	})
  3487  
  3488  	err = s.State.GrantSecretAccess(uri, state.SecretAccessParams{
  3489  		LeaderToken: &fakeToken{},
  3490  		Scope:       rel.Tag(),
  3491  		Subject:     s.mysql.Tag(),
  3492  		Role:        secrets.RoleView,
  3493  	})
  3494  	c.Assert(err, jc.ErrorIsNil)
  3495  	access, err := s.State.SecretAccess(uri, s.mysql.Tag())
  3496  	c.Assert(err, jc.ErrorIsNil)
  3497  	c.Assert(access, gc.Equals, secrets.RoleView)
  3498  
  3499  	err = s.mysql.Destroy()
  3500  	c.Assert(err, jc.ErrorIsNil)
  3501  	_, err = s.State.SecretAccess(uri, s.mysql.Tag())
  3502  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  3503  }
  3504  
  3505  func (s *ApplicationSuite) TestDestroyRemoveAlsoDeletesOwnedSecrets(c *gc.C) {
  3506  	store := state.NewSecrets(s.State)
  3507  	uri := secrets.NewURI()
  3508  	cp := state.CreateSecretParams{
  3509  		Version: 1,
  3510  		Owner:   s.mysql.Tag(),
  3511  		UpdateSecretParams: state.UpdateSecretParams{
  3512  			LeaderToken: &fakeToken{},
  3513  			Label:       ptr("label"),
  3514  			Data:        map[string]string{"foo": "bar"},
  3515  		},
  3516  	}
  3517  	_, err := store.CreateSecret(uri, cp)
  3518  	c.Assert(err, jc.ErrorIsNil)
  3519  
  3520  	err = s.mysql.Destroy()
  3521  	c.Assert(err, jc.ErrorIsNil)
  3522  	_, err = store.GetSecret(uri)
  3523  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  3524  
  3525  	// Create again, no label clash.
  3526  	s.AddTestingApplication(c, "mysql", s.charm)
  3527  	_, err = store.CreateSecret(uri, cp)
  3528  	c.Assert(err, jc.ErrorIsNil)
  3529  }
  3530  
  3531  func (s *ApplicationSuite) TestDestroyNoRemoveKeepsOwnedSecrets(c *gc.C) {
  3532  	// Create a relation so destroy does not remove.
  3533  	_, err := s.mysql.AddUnit(state.AddUnitParams{})
  3534  	c.Assert(err, jc.ErrorIsNil)
  3535  	mysqlep, err := s.mysql.Endpoint("server")
  3536  	c.Assert(err, jc.ErrorIsNil)
  3537  	wpch := s.AddTestingCharm(c, "wordpress")
  3538  	wp := s.AddTestingApplication(c, "wordpress", wpch)
  3539  	wpep, err := wp.Endpoint("db")
  3540  	c.Assert(err, jc.ErrorIsNil)
  3541  	_, err = s.State.AddRelation(mysqlep, wpep)
  3542  	c.Assert(err, jc.ErrorIsNil)
  3543  
  3544  	store := state.NewSecrets(s.State)
  3545  	uri := secrets.NewURI()
  3546  	cp := state.CreateSecretParams{
  3547  		Version: 1,
  3548  		Owner:   s.mysql.Tag(),
  3549  		UpdateSecretParams: state.UpdateSecretParams{
  3550  			LeaderToken: &fakeToken{},
  3551  			Label:       ptr("label"),
  3552  			Data:        map[string]string{"foo": "bar"},
  3553  		},
  3554  	}
  3555  	_, err = store.CreateSecret(uri, cp)
  3556  	c.Assert(err, jc.ErrorIsNil)
  3557  
  3558  	err = s.mysql.Destroy()
  3559  	c.Assert(err, jc.ErrorIsNil)
  3560  	_, err = store.GetSecret(uri)
  3561  	c.Assert(err, jc.ErrorIsNil)
  3562  }
  3563  
  3564  func (s *ApplicationSuite) TestApplicationCleanupRemovesStorageConstraints(c *gc.C) {
  3565  	ch := s.AddTestingCharm(c, "storage-block")
  3566  	storage := map[string]state.StorageConstraints{
  3567  		"data": makeStorageCons("loop", 1024, 1),
  3568  	}
  3569  	app := s.AddTestingApplicationWithStorage(c, "storage-block", ch, storage)
  3570  	u, err := app.AddUnit(state.AddUnitParams{})
  3571  	c.Assert(err, jc.ErrorIsNil)
  3572  	err = u.SetCharmURL(ch.URL())
  3573  	c.Assert(err, jc.ErrorIsNil)
  3574  
  3575  	c.Assert(app.Destroy(), gc.IsNil)
  3576  	assertLife(c, app, state.Dying)
  3577  	assertCleanupCount(c, s.State, 2)
  3578  
  3579  	// These next API calls are normally done by the uniter.
  3580  	c.Assert(u.EnsureDead(), jc.ErrorIsNil)
  3581  	c.Assert(u.Remove(), jc.ErrorIsNil)
  3582  	c.Assert(s.State.Cleanup(), jc.ErrorIsNil)
  3583  
  3584  	// Ensure storage constraints and settings are now gone.
  3585  	_, err = state.AppStorageConstraints(app)
  3586  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  3587  	cfg := state.GetApplicationCharmConfig(s.State, app)
  3588  	err = cfg.Read()
  3589  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  3590  }
  3591  
  3592  func (s *ApplicationSuite) TestApplicationCleanupRemovesAppFromActiveBranches(c *gc.C) {
  3593  	s.assertNoCleanup(c)
  3594  
  3595  	// setup branch, tracking and app with config changes.
  3596  	app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  3597  	c.Assert(s.Model.AddBranch("apple", "testuser"), jc.ErrorIsNil)
  3598  	branch, err := s.Model.Branch("apple")
  3599  	c.Assert(err, jc.ErrorIsNil)
  3600  	c.Assert(branch.AssignApplication(app.Name()), jc.ErrorIsNil)
  3601  	c.Assert(branch.AssignApplication(s.mysql.Name()), jc.ErrorIsNil)
  3602  	newCfg := map[string]interface{}{"outlook": "testing"}
  3603  	c.Assert(app.UpdateCharmConfig(branch.BranchName(), newCfg), jc.ErrorIsNil)
  3604  
  3605  	// verify the branch setup
  3606  	c.Assert(branch.Refresh(), jc.ErrorIsNil)
  3607  	c.Assert(branch.AssignedUnits(), jc.DeepEquals, map[string][]string{
  3608  		app.Name():     {},
  3609  		s.mysql.Name(): {},
  3610  	})
  3611  	branchCfg := branch.Config()
  3612  	_, ok := branchCfg[app.Name()]
  3613  	c.Assert(ok, jc.IsTrue)
  3614  
  3615  	// destroy the app
  3616  	c.Assert(app.Destroy(), gc.IsNil)
  3617  	assertRemoved(c, app)
  3618  
  3619  	// Check the branch
  3620  	c.Assert(branch.Refresh(), jc.ErrorIsNil)
  3621  	c.Assert(branch.AssignedUnits(), jc.DeepEquals, map[string][]string{
  3622  		s.mysql.Name(): {},
  3623  	})
  3624  	c.Assert(branch.Config(), gc.HasLen, 0)
  3625  }
  3626  
  3627  func (s *ApplicationSuite) TestRemoveQueuesLocalCharmCleanup(c *gc.C) {
  3628  	s.assertNoCleanup(c)
  3629  
  3630  	err := s.mysql.Destroy()
  3631  	c.Assert(err, jc.ErrorIsNil)
  3632  	assertRemoved(c, s.mysql)
  3633  
  3634  	// Check a cleanup doc was added.
  3635  	s.assertNeedsCleanup(c)
  3636  
  3637  	// Run the cleanup
  3638  	err = s.State.Cleanup()
  3639  	c.Assert(err, jc.ErrorIsNil)
  3640  
  3641  	// Check charm removed
  3642  	err = s.charm.Refresh()
  3643  	c.Check(err, jc.Satisfies, errors.IsNotFound)
  3644  
  3645  	// Check we're now clean.
  3646  	s.assertNoCleanup(c)
  3647  }
  3648  
  3649  func (s *ApplicationSuite) TestDestroyQueuesResourcesCleanup(c *gc.C) {
  3650  	s.assertNoCleanup(c)
  3651  
  3652  	// Add a resource to the application, ensuring it is stored.
  3653  	rSt := s.State.Resources()
  3654  	const content = "abc"
  3655  	res := resourcetesting.NewCharmResource(c, "blob", content)
  3656  	outRes, err := rSt.SetResource(s.mysql.Name(), "user", res, strings.NewReader(content), state.IncrementCharmModifiedVersion)
  3657  	c.Assert(err, jc.ErrorIsNil)
  3658  	storagePath := state.ResourceStoragePath(c, s.State, outRes.ID)
  3659  	c.Assert(state.IsBlobStored(c, s.State, storagePath), jc.IsTrue)
  3660  
  3661  	// Detroy the application.
  3662  	err = s.mysql.Destroy()
  3663  	c.Assert(err, jc.ErrorIsNil)
  3664  	assertRemoved(c, s.mysql)
  3665  
  3666  	// Cleanup should be registered but not yet run.
  3667  	s.assertNeedsCleanup(c)
  3668  	c.Assert(state.IsBlobStored(c, s.State, storagePath), jc.IsTrue)
  3669  
  3670  	// Run the cleanup.
  3671  	err = s.State.Cleanup()
  3672  	c.Assert(err, jc.ErrorIsNil)
  3673  
  3674  	// Check we're now clean.
  3675  	s.assertNoCleanup(c)
  3676  	c.Assert(state.IsBlobStored(c, s.State, storagePath), jc.IsFalse)
  3677  }
  3678  
  3679  func (s *ApplicationSuite) TestDestroyWithPlaceholderResources(c *gc.C) {
  3680  	s.assertNoCleanup(c)
  3681  
  3682  	// Add a placeholder resource to the application.
  3683  	rSt := s.State.Resources()
  3684  	res := resourcetesting.NewPlaceholderResource(c, "blob", s.mysql.Name())
  3685  	outRes, err := rSt.SetResource(s.mysql.Name(), "user", res.Resource, nil, state.IncrementCharmModifiedVersion)
  3686  	c.Assert(err, jc.ErrorIsNil)
  3687  	c.Assert(outRes.IsPlaceholder(), jc.IsTrue)
  3688  
  3689  	// Detroy the application.
  3690  	err = s.mysql.Destroy()
  3691  	c.Assert(err, jc.ErrorIsNil)
  3692  	assertRemoved(c, s.mysql)
  3693  
  3694  	// No cleanup required for placeholder resources.
  3695  	state.AssertNoCleanupsWithKind(c, s.State, "resourceBlob")
  3696  }
  3697  
  3698  func (s *ApplicationSuite) TestReadUnitWithChangingState(c *gc.C) {
  3699  	// Check that reading a unit after removing the application
  3700  	// fails nicely.
  3701  	err := s.mysql.Destroy()
  3702  	c.Assert(err, jc.ErrorIsNil)
  3703  	assertRemoved(c, s.mysql)
  3704  	_, err = s.State.Unit("mysql/0")
  3705  	c.Assert(err, gc.ErrorMatches, `unit "mysql/0" not found`)
  3706  }
  3707  
  3708  func uint64p(val uint64) *uint64 {
  3709  	return &val
  3710  }
  3711  
  3712  func (s *ApplicationSuite) TestConstraints(c *gc.C) {
  3713  	// Constraints are initially empty (for now).
  3714  	cons, err := s.mysql.Constraints()
  3715  	c.Assert(err, jc.ErrorIsNil)
  3716  	c.Assert(&cons, gc.Not(jc.Satisfies), constraints.IsEmpty)
  3717  	c.Assert(cons, gc.DeepEquals, constraints.MustParse("arch=amd64"))
  3718  
  3719  	// Constraints can be set.
  3720  	cons2 := constraints.Value{Mem: uint64p(4096)}
  3721  	err = s.mysql.SetConstraints(cons2)
  3722  	c.Assert(err, jc.ErrorIsNil)
  3723  	cons3, err := s.mysql.Constraints()
  3724  	c.Assert(err, jc.ErrorIsNil)
  3725  	c.Assert(cons3, gc.DeepEquals, cons2)
  3726  
  3727  	// Constraints are completely overwritten when re-set.
  3728  	cons4 := constraints.Value{CpuPower: uint64p(750)}
  3729  	err = s.mysql.SetConstraints(cons4)
  3730  	c.Assert(err, jc.ErrorIsNil)
  3731  	cons5, err := s.mysql.Constraints()
  3732  	c.Assert(err, jc.ErrorIsNil)
  3733  	c.Assert(cons5, gc.DeepEquals, cons4)
  3734  
  3735  	// Destroy the existing application; there's no way to directly assert
  3736  	// that the constraints are deleted...
  3737  	err = s.mysql.Destroy()
  3738  	c.Assert(err, jc.ErrorIsNil)
  3739  	assertRemoved(c, s.mysql)
  3740  
  3741  	// ...but we can check that old constraints do not affect new applications
  3742  	// with matching names.
  3743  	ch, _, err := s.mysql.Charm()
  3744  	c.Assert(err, jc.ErrorIsNil)
  3745  	mysql := s.AddTestingApplication(c, s.mysql.Name(), ch)
  3746  	cons6, err := mysql.Constraints()
  3747  	c.Assert(err, jc.ErrorIsNil)
  3748  	c.Assert(&cons6, gc.Not(jc.Satisfies), constraints.IsEmpty)
  3749  	c.Assert(cons6, gc.DeepEquals, constraints.MustParse("arch=amd64"))
  3750  }
  3751  
  3752  func (s *ApplicationSuite) TestArchConstraints(c *gc.C) {
  3753  	amdArch := "amd64"
  3754  	armArch := "arm64"
  3755  
  3756  	cons2 := constraints.Value{Arch: &amdArch}
  3757  	err := s.mysql.SetConstraints(cons2)
  3758  	c.Assert(err, jc.ErrorIsNil)
  3759  	cons3, err := s.mysql.Constraints()
  3760  	c.Assert(err, jc.ErrorIsNil)
  3761  	c.Assert(cons3, gc.DeepEquals, cons2)
  3762  
  3763  	// Constraints error out if it's already set.
  3764  	cons4 := constraints.Value{Arch: &armArch}
  3765  	err = s.mysql.SetConstraints(cons4)
  3766  	c.Assert(err, gc.ErrorMatches, "changing architecture \\(amd64\\) not supported")
  3767  
  3768  	// Destroy the existing application; there's no way to directly assert
  3769  	// that the constraints are deleted...
  3770  	err = s.mysql.Destroy()
  3771  	c.Assert(err, jc.ErrorIsNil)
  3772  	assertRemoved(c, s.mysql)
  3773  
  3774  	// ...but we can check that old constraints do not affect new applications
  3775  	// with matching names.
  3776  	ch, _, err := s.mysql.Charm()
  3777  	c.Assert(err, jc.ErrorIsNil)
  3778  	mysql := s.AddTestingApplication(c, s.mysql.Name(), ch)
  3779  	cons6, err := mysql.Constraints()
  3780  	c.Assert(err, jc.ErrorIsNil)
  3781  	c.Assert(constraints.IsEmpty(&cons6), jc.IsFalse)
  3782  	c.Assert(cons6, jc.DeepEquals, cons2)
  3783  }
  3784  
  3785  func (s *ApplicationSuite) TestSetInvalidConstraints(c *gc.C) {
  3786  	cons := constraints.MustParse("mem=4G instance-type=foo")
  3787  	err := s.mysql.SetConstraints(cons)
  3788  	c.Assert(err, gc.ErrorMatches, `ambiguous constraints: "instance-type" overlaps with "mem"`)
  3789  }
  3790  
  3791  func (s *ApplicationSuite) TestSetUnsupportedConstraintsWarning(c *gc.C) {
  3792  	defer loggo.ResetWriters()
  3793  	logger := loggo.GetLogger("test")
  3794  	logger.SetLogLevel(loggo.DEBUG)
  3795  	var tw loggo.TestWriter
  3796  	c.Assert(loggo.RegisterWriter("constraints-tester", &tw), gc.IsNil)
  3797  
  3798  	cons := constraints.MustParse("mem=4G cpu-power=10")
  3799  	err := s.mysql.SetConstraints(cons)
  3800  	c.Assert(err, jc.ErrorIsNil)
  3801  	c.Assert(tw.Log(), jc.LogMatches, jc.SimpleMessages{{
  3802  		loggo.WARNING,
  3803  		`setting constraints on application "mysql": unsupported constraints: cpu-power`},
  3804  	})
  3805  	scons, err := s.mysql.Constraints()
  3806  	c.Assert(err, jc.ErrorIsNil)
  3807  	c.Assert(scons, gc.DeepEquals, cons)
  3808  }
  3809  
  3810  func (s *ApplicationSuite) TestConstraintsLifecycle(c *gc.C) {
  3811  	// Dying.
  3812  	unit, err := s.mysql.AddUnit(state.AddUnitParams{})
  3813  	c.Assert(err, jc.ErrorIsNil)
  3814  	err = s.mysql.Destroy()
  3815  	c.Assert(err, jc.ErrorIsNil)
  3816  	assertLife(c, s.mysql, state.Dying)
  3817  
  3818  	cons1 := constraints.MustParse("mem=1G")
  3819  	err = s.mysql.SetConstraints(cons1)
  3820  	c.Assert(err, gc.ErrorMatches, `cannot set constraints: application is not found or not alive`)
  3821  
  3822  	scons, err := s.mysql.Constraints()
  3823  	c.Assert(err, jc.ErrorIsNil)
  3824  	c.Assert(&scons, gc.Not(jc.Satisfies), constraints.IsEmpty)
  3825  	c.Assert(scons, gc.DeepEquals, constraints.MustParse("arch=amd64"))
  3826  
  3827  	// Removed (== Dead, for a application).
  3828  	c.Assert(unit.EnsureDead(), jc.ErrorIsNil)
  3829  	c.Assert(unit.Remove(), jc.ErrorIsNil)
  3830  	c.Assert(s.State.Cleanup(), jc.ErrorIsNil)
  3831  	err = s.mysql.SetConstraints(cons1)
  3832  	c.Assert(err, gc.ErrorMatches, `cannot set constraints: application is not found or not alive`)
  3833  	_, err = s.mysql.Constraints()
  3834  	c.Assert(err, gc.ErrorMatches, `constraints not found`)
  3835  }
  3836  
  3837  func (s *ApplicationSuite) TestSubordinateConstraints(c *gc.C) {
  3838  	loggingCh := s.AddTestingCharm(c, "logging")
  3839  	logging := s.AddTestingApplication(c, "logging", loggingCh)
  3840  
  3841  	_, err := logging.Constraints()
  3842  	c.Assert(err, gc.Equals, state.ErrSubordinateConstraints)
  3843  
  3844  	err = logging.SetConstraints(constraints.Value{})
  3845  	c.Assert(err, gc.Equals, state.ErrSubordinateConstraints)
  3846  }
  3847  
  3848  func (s *ApplicationSuite) TestWatchUnitsBulkEvents(c *gc.C) {
  3849  	// Alive unit...
  3850  	alive, err := s.mysql.AddUnit(state.AddUnitParams{})
  3851  	c.Assert(err, jc.ErrorIsNil)
  3852  
  3853  	// Dying unit...
  3854  	dying, err := s.mysql.AddUnit(state.AddUnitParams{})
  3855  	c.Assert(err, jc.ErrorIsNil)
  3856  	preventUnitDestroyRemove(c, dying)
  3857  	err = dying.Destroy()
  3858  	c.Assert(err, jc.ErrorIsNil)
  3859  
  3860  	// Dead unit...
  3861  	dead, err := s.mysql.AddUnit(state.AddUnitParams{})
  3862  	c.Assert(err, jc.ErrorIsNil)
  3863  	preventUnitDestroyRemove(c, dead)
  3864  	err = dead.Destroy()
  3865  	c.Assert(err, jc.ErrorIsNil)
  3866  	err = dead.EnsureDead()
  3867  	c.Assert(err, jc.ErrorIsNil)
  3868  
  3869  	// Gone unit.
  3870  	gone, err := s.mysql.AddUnit(state.AddUnitParams{})
  3871  	c.Assert(err, jc.ErrorIsNil)
  3872  	err = gone.Destroy()
  3873  	c.Assert(err, jc.ErrorIsNil)
  3874  
  3875  	// All except gone unit are reported in initial event.
  3876  	w := s.mysql.WatchUnits()
  3877  	defer testing.AssertStop(c, w)
  3878  	wc := testing.NewStringsWatcherC(c, w)
  3879  	wc.AssertChange(alive.Name(), dying.Name(), dead.Name())
  3880  	wc.AssertNoChange()
  3881  
  3882  	// Remove them all; alive/dying changes reported; dead never mentioned again.
  3883  	err = alive.Destroy()
  3884  	c.Assert(err, jc.ErrorIsNil)
  3885  	err = dying.EnsureDead()
  3886  	c.Assert(err, jc.ErrorIsNil)
  3887  	err = dying.Remove()
  3888  	c.Assert(err, jc.ErrorIsNil)
  3889  	err = dead.Remove()
  3890  	c.Assert(err, jc.ErrorIsNil)
  3891  	wc.AssertChange(alive.Name(), dying.Name())
  3892  	wc.AssertNoChange()
  3893  }
  3894  
  3895  func (s *ApplicationSuite) TestWatchUnitsLifecycle(c *gc.C) {
  3896  	// Empty initial event when no units.
  3897  	w := s.mysql.WatchUnits()
  3898  	defer testing.AssertStop(c, w)
  3899  	wc := testing.NewStringsWatcherC(c, w)
  3900  	wc.AssertChange()
  3901  	wc.AssertNoChange()
  3902  
  3903  	// Create one unit, check one change.
  3904  	quick, err := s.mysql.AddUnit(state.AddUnitParams{})
  3905  	c.Assert(err, jc.ErrorIsNil)
  3906  	wc.AssertChange(quick.Name())
  3907  	wc.AssertNoChange()
  3908  
  3909  	// Destroy that unit (short-circuited to removal), check one change.
  3910  	err = quick.Destroy()
  3911  	c.Assert(err, jc.ErrorIsNil)
  3912  	wc.AssertChange(quick.Name())
  3913  	wc.AssertNoChange()
  3914  
  3915  	// Create another, check one change.
  3916  	slow, err := s.mysql.AddUnit(state.AddUnitParams{})
  3917  	c.Assert(err, jc.ErrorIsNil)
  3918  	wc.AssertChange(slow.Name())
  3919  	wc.AssertNoChange()
  3920  
  3921  	// Change unit itself, no change.
  3922  	preventUnitDestroyRemove(c, slow)
  3923  	wc.AssertNoChange()
  3924  
  3925  	// Make unit Dying, change detected.
  3926  	err = slow.Destroy()
  3927  	c.Assert(err, jc.ErrorIsNil)
  3928  	wc.AssertChange(slow.Name())
  3929  	wc.AssertNoChange()
  3930  
  3931  	// Make unit Dead, change detected.
  3932  	err = slow.EnsureDead()
  3933  	c.Assert(err, jc.ErrorIsNil)
  3934  	wc.AssertChange(slow.Name())
  3935  	wc.AssertNoChange()
  3936  
  3937  	// Remove unit, final change not detected.
  3938  	err = slow.Remove()
  3939  	c.Assert(err, jc.ErrorIsNil)
  3940  	wc.AssertNoChange()
  3941  }
  3942  
  3943  func (s *ApplicationSuite) TestWatchRelations(c *gc.C) {
  3944  	// TODO(fwereade) split this test up a bit.
  3945  	w := s.mysql.WatchRelations()
  3946  	defer testing.AssertStop(c, w)
  3947  	wc := testing.NewStringsWatcherC(c, w)
  3948  	wc.AssertChange()
  3949  	wc.AssertNoChange()
  3950  
  3951  	// Add a relation; check change.
  3952  	mysqlep, err := s.mysql.Endpoint("server")
  3953  	c.Assert(err, jc.ErrorIsNil)
  3954  	wpch := s.AddTestingCharm(c, "wordpress")
  3955  	wpi := 0
  3956  	addRelation := func() *state.Relation {
  3957  		name := fmt.Sprintf("wp%d", wpi)
  3958  		wpi++
  3959  		wp := s.AddTestingApplication(c, name, wpch)
  3960  		wpep, err := wp.Endpoint("db")
  3961  		c.Assert(err, jc.ErrorIsNil)
  3962  		rel, err := s.State.AddRelation(mysqlep, wpep)
  3963  		c.Assert(err, jc.ErrorIsNil)
  3964  		return rel
  3965  	}
  3966  	rel0 := addRelation()
  3967  	wc.AssertChange(rel0.String())
  3968  	wc.AssertNoChange()
  3969  
  3970  	// Add another relation; check change.
  3971  	rel1 := addRelation()
  3972  	wc.AssertChange(rel1.String())
  3973  	wc.AssertNoChange()
  3974  
  3975  	// Destroy a relation; check change.
  3976  	err = rel0.Destroy()
  3977  	c.Assert(err, jc.ErrorIsNil)
  3978  	wc.AssertChange(rel0.String())
  3979  	wc.AssertNoChange()
  3980  
  3981  	// Stop watcher; check change chan is closed.
  3982  	testing.AssertStop(c, w)
  3983  	wc.AssertClosed()
  3984  
  3985  	// Add a new relation; start a new watcher; check initial event.
  3986  	rel2 := addRelation()
  3987  	w = s.mysql.WatchRelations()
  3988  	defer testing.AssertStop(c, w)
  3989  	wc = testing.NewStringsWatcherC(c, w)
  3990  	wc.AssertChange(rel1.String(), rel2.String())
  3991  	wc.AssertNoChange()
  3992  
  3993  	// Add a unit to the new relation; check no change.
  3994  	unit, err := s.mysql.AddUnit(state.AddUnitParams{})
  3995  	c.Assert(err, jc.ErrorIsNil)
  3996  	ru2, err := rel2.Unit(unit)
  3997  	c.Assert(err, jc.ErrorIsNil)
  3998  	err = ru2.EnterScope(nil)
  3999  	c.Assert(err, jc.ErrorIsNil)
  4000  	wc.AssertNoChange()
  4001  
  4002  	// Destroy the relation with the unit in scope, and add another; check
  4003  	// changes.
  4004  	err = rel2.Refresh()
  4005  	c.Assert(err, jc.ErrorIsNil)
  4006  	err = rel2.Destroy()
  4007  	c.Assert(err, jc.ErrorIsNil)
  4008  	rel3 := addRelation()
  4009  	wc.AssertChange(rel2.String(), rel3.String())
  4010  	wc.AssertNoChange()
  4011  
  4012  	// Leave scope, destroying the relation, and check that change as well.
  4013  	err = ru2.LeaveScope()
  4014  	c.Assert(err, jc.ErrorIsNil)
  4015  	wc.AssertChange(rel2.String())
  4016  	wc.AssertNoChange()
  4017  
  4018  	// Watch relations on the requirer application too (exercises a
  4019  	// different path of the WatchRelations filter function)
  4020  	wpx := s.AddTestingApplication(c, "wpx", wpch)
  4021  	wpxWatcher := wpx.WatchRelations()
  4022  	defer testing.AssertStop(c, wpxWatcher)
  4023  	wpxWatcherC := testing.NewStringsWatcherC(c, wpxWatcher)
  4024  	wpxWatcherC.AssertChange()
  4025  	wpxWatcherC.AssertNoChange()
  4026  
  4027  	wpxep, err := wpx.Endpoint("db")
  4028  	c.Assert(err, jc.ErrorIsNil)
  4029  	relx, err := s.State.AddRelation(mysqlep, wpxep)
  4030  	c.Assert(err, jc.ErrorIsNil)
  4031  	wpxWatcherC.AssertChange(relx.String())
  4032  	wpxWatcherC.AssertNoChange()
  4033  
  4034  	err = relx.SetSuspended(true, "")
  4035  	c.Assert(err, jc.ErrorIsNil)
  4036  	wpxWatcherC.AssertChange(relx.String())
  4037  	wpxWatcherC.AssertNoChange()
  4038  }
  4039  
  4040  func removeAllUnits(c *gc.C, s *state.Application) {
  4041  	us, err := s.AllUnits()
  4042  	c.Assert(err, jc.ErrorIsNil)
  4043  	for _, u := range us {
  4044  		err = u.EnsureDead()
  4045  		c.Assert(err, jc.ErrorIsNil)
  4046  		err = u.Remove()
  4047  		c.Assert(err, jc.ErrorIsNil)
  4048  	}
  4049  }
  4050  
  4051  func (s *ApplicationSuite) TestWatchApplication(c *gc.C) {
  4052  	w := s.mysql.Watch()
  4053  	defer testing.AssertStop(c, w)
  4054  
  4055  	// Initial event.
  4056  	wc := testing.NewNotifyWatcherC(c, w)
  4057  	wc.AssertOneChange()
  4058  
  4059  	// Make one change (to a separate instance), check one event.
  4060  	application, err := s.State.Application(s.mysql.Name())
  4061  	c.Assert(err, jc.ErrorIsNil)
  4062  	err = application.MergeExposeSettings(nil)
  4063  	c.Assert(err, jc.ErrorIsNil)
  4064  	wc.AssertOneChange()
  4065  
  4066  	// Make two changes, check one event.
  4067  	err = application.ClearExposed()
  4068  	c.Assert(err, jc.ErrorIsNil)
  4069  	// TODO(quiescence): these two changes should be one event.
  4070  	wc.AssertOneChange()
  4071  
  4072  	cfg := state.SetCharmConfig{
  4073  		Charm:       s.charm,
  4074  		CharmOrigin: defaultCharmOrigin(s.charm.URL()),
  4075  		ForceUnits:  true,
  4076  	}
  4077  	err = application.SetCharm(cfg)
  4078  	c.Assert(err, jc.ErrorIsNil)
  4079  	wc.AssertOneChange()
  4080  
  4081  	// Stop, check closed.
  4082  	testing.AssertStop(c, w)
  4083  	wc.AssertClosed()
  4084  
  4085  	// Remove application, start new watch, check single event.
  4086  	err = application.Destroy()
  4087  	c.Assert(err, jc.ErrorIsNil)
  4088  	// The destruction needs to have been processed by the txn watcher before the
  4089  	// watcher in the test is started or the destroy notification may come through
  4090  	// as an additional event.
  4091  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
  4092  	w = s.mysql.Watch()
  4093  	defer testing.AssertStop(c, w)
  4094  	testing.NewNotifyWatcherC(c, w).AssertOneChange()
  4095  }
  4096  
  4097  func (s *ApplicationSuite) TestMetricCredentials(c *gc.C) {
  4098  	err := s.mysql.SetMetricCredentials([]byte("hello there"))
  4099  	c.Assert(err, jc.ErrorIsNil)
  4100  	c.Assert(s.mysql.MetricCredentials(), gc.DeepEquals, []byte("hello there"))
  4101  
  4102  	application, err := s.State.Application(s.mysql.Name())
  4103  	c.Assert(err, jc.ErrorIsNil)
  4104  	c.Assert(application.MetricCredentials(), gc.DeepEquals, []byte("hello there"))
  4105  }
  4106  
  4107  func (s *ApplicationSuite) TestMetricCredentialsOnDying(c *gc.C) {
  4108  	_, err := s.mysql.AddUnit(state.AddUnitParams{})
  4109  	c.Assert(err, jc.ErrorIsNil)
  4110  	err = s.mysql.SetMetricCredentials([]byte("set before dying"))
  4111  	c.Assert(err, jc.ErrorIsNil)
  4112  	err = s.mysql.Destroy()
  4113  	c.Assert(err, jc.ErrorIsNil)
  4114  	assertLife(c, s.mysql, state.Dying)
  4115  	err = s.mysql.SetMetricCredentials([]byte("set after dying"))
  4116  	c.Assert(err, gc.ErrorMatches, "cannot update metric credentials: application is not found or not alive")
  4117  }
  4118  
  4119  const oneRequiredStorageMeta = `
  4120  storage:
  4121    data0:
  4122      type: block
  4123  `
  4124  
  4125  const oneOptionalStorageMeta = `
  4126  storage:
  4127    data0:
  4128      type: block
  4129      multiple:
  4130        range: 0-
  4131  `
  4132  
  4133  const oneRequiredOneOptionalStorageMeta = `
  4134  storage:
  4135    data0:
  4136      type: block
  4137    data1:
  4138      type: block
  4139      multiple:
  4140        range: 0-
  4141  `
  4142  
  4143  const twoRequiredStorageMeta = `
  4144  storage:
  4145    data0:
  4146      type: block
  4147    data1:
  4148      type: block
  4149  `
  4150  
  4151  const twoOptionalStorageMeta = `
  4152  storage:
  4153    data0:
  4154      type: block
  4155      multiple:
  4156        range: 0-
  4157    data1:
  4158      type: block
  4159      multiple:
  4160        range: 0-
  4161  `
  4162  
  4163  const oneRequiredFilesystemStorageMeta = `
  4164  storage:
  4165    data0:
  4166      type: filesystem
  4167  `
  4168  
  4169  const oneOptionalSharedStorageMeta = `
  4170  storage:
  4171    data0:
  4172      type: block
  4173      shared: true
  4174      multiple:
  4175        range: 0-
  4176  `
  4177  
  4178  const oneRequiredReadOnlyStorageMeta = `
  4179  storage:
  4180    data0:
  4181      type: block
  4182      read-only: true
  4183  `
  4184  
  4185  const oneRequiredLocationStorageMeta = `
  4186  storage:
  4187    data0:
  4188      type: filesystem
  4189      location: /srv
  4190  `
  4191  
  4192  const oneMultipleLocationStorageMeta = `
  4193  storage:
  4194    data0:
  4195      type: filesystem
  4196      location: /srv
  4197      multiple:
  4198        range: 1-
  4199  `
  4200  
  4201  func storageRange(min, max int) string {
  4202  	var minStr, maxStr string
  4203  	if min > 0 {
  4204  		minStr = fmt.Sprint(min)
  4205  	}
  4206  	if max > 0 {
  4207  		maxStr = fmt.Sprint(max)
  4208  	}
  4209  	return fmt.Sprintf(`
  4210      multiple:
  4211        range: %s-%s
  4212  `[1:], minStr, maxStr)
  4213  }
  4214  
  4215  func (s *ApplicationSuite) setCharmFromMeta(c *gc.C, oldMeta, newMeta string) error {
  4216  	oldCh := s.AddMetaCharm(c, "mysql", oldMeta, 2)
  4217  	newCh := s.AddMetaCharm(c, "mysql", newMeta, 3)
  4218  	app := s.AddTestingApplication(c, "test", oldCh)
  4219  
  4220  	cfg := state.SetCharmConfig{
  4221  		Charm:       newCh,
  4222  		CharmOrigin: defaultCharmOrigin(newCh.URL()),
  4223  	}
  4224  	return app.SetCharm(cfg)
  4225  }
  4226  
  4227  func (s *ApplicationSuite) TestSetCharmOptionalUnusedStorageRemoved(c *gc.C) {
  4228  	err := s.setCharmFromMeta(c,
  4229  		mysqlBaseMeta+oneRequiredOneOptionalStorageMeta,
  4230  		mysqlBaseMeta+oneRequiredStorageMeta,
  4231  	)
  4232  	c.Assert(err, jc.ErrorIsNil)
  4233  	// It's valid to remove optional storage so long
  4234  	// as it is not in use.
  4235  }
  4236  
  4237  func (s *ApplicationSuite) TestSetCharmOptionalUsedStorageRemoved(c *gc.C) {
  4238  	oldMeta := mysqlBaseMeta + oneRequiredOneOptionalStorageMeta
  4239  	newMeta := mysqlBaseMeta + oneRequiredStorageMeta
  4240  	oldCh := s.AddMetaCharm(c, "mysql", oldMeta, 2)
  4241  	newCh := s.AddMetaCharm(c, "mysql", newMeta, 3)
  4242  	app := s.Factory.MakeApplication(c, &factory.ApplicationParams{
  4243  		Name:  "test",
  4244  		Charm: oldCh,
  4245  		Storage: map[string]state.StorageConstraints{
  4246  			"data0": {Count: 1},
  4247  			"data1": {Count: 1},
  4248  		},
  4249  	})
  4250  	defer state.SetBeforeHooks(c, s.State, func() {
  4251  		// Adding a unit will cause the storage to be in-use.
  4252  		_, err := app.AddUnit(state.AddUnitParams{})
  4253  		c.Assert(err, jc.ErrorIsNil)
  4254  	}).Check()
  4255  	cfg := state.SetCharmConfig{
  4256  		Charm:       newCh,
  4257  		CharmOrigin: defaultCharmOrigin(newCh.URL()),
  4258  	}
  4259  	err := app.SetCharm(cfg)
  4260  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": in-use storage "data1" removed`)
  4261  }
  4262  
  4263  func (s *ApplicationSuite) TestSetCharmRequiredStorageRemoved(c *gc.C) {
  4264  	err := s.setCharmFromMeta(c,
  4265  		mysqlBaseMeta+oneRequiredStorageMeta,
  4266  		mysqlBaseMeta,
  4267  	)
  4268  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": required storage "data0" removed`)
  4269  }
  4270  
  4271  func (s *ApplicationSuite) TestSetCharmRequiredStorageAddedDefaultConstraints(c *gc.C) {
  4272  	oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+oneRequiredStorageMeta, 2)
  4273  	newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+twoRequiredStorageMeta, 3)
  4274  	app := s.AddTestingApplication(c, "test", oldCh)
  4275  	u, err := app.AddUnit(state.AddUnitParams{})
  4276  	c.Assert(err, jc.ErrorIsNil)
  4277  
  4278  	cfg := state.SetCharmConfig{
  4279  		Charm:       newCh,
  4280  		CharmOrigin: defaultCharmOrigin(newCh.URL()),
  4281  	}
  4282  	err = app.SetCharm(cfg)
  4283  	c.Assert(err, jc.ErrorIsNil)
  4284  
  4285  	// Check that the new required storage was added for the unit.
  4286  	sb, err := state.NewStorageBackend(s.State)
  4287  	c.Assert(err, jc.ErrorIsNil)
  4288  	attachments, err := sb.UnitStorageAttachments(u.UnitTag())
  4289  	c.Assert(err, jc.ErrorIsNil)
  4290  	c.Assert(attachments, gc.HasLen, 2)
  4291  }
  4292  
  4293  func (s *ApplicationSuite) TestSetCharmStorageAddedUserSpecifiedConstraints(c *gc.C) {
  4294  	oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+oneRequiredStorageMeta, 2)
  4295  	newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+twoOptionalStorageMeta, 3)
  4296  	app := s.AddTestingApplication(c, "test", oldCh)
  4297  	u, err := app.AddUnit(state.AddUnitParams{})
  4298  	c.Assert(err, jc.ErrorIsNil)
  4299  
  4300  	cfg := state.SetCharmConfig{
  4301  		Charm:       newCh,
  4302  		CharmOrigin: defaultCharmOrigin(newCh.URL()),
  4303  		StorageConstraints: map[string]state.StorageConstraints{
  4304  			"data1": {Count: 3},
  4305  		},
  4306  	}
  4307  	err = app.SetCharm(cfg)
  4308  	c.Assert(err, jc.ErrorIsNil)
  4309  
  4310  	// Check that new storage was added for the unit, based on the
  4311  	// constraints specified in SetCharmConfig.
  4312  	sb, err := state.NewStorageBackend(s.State)
  4313  	c.Assert(err, jc.ErrorIsNil)
  4314  	attachments, err := sb.UnitStorageAttachments(u.UnitTag())
  4315  	c.Assert(err, jc.ErrorIsNil)
  4316  	c.Assert(attachments, gc.HasLen, 4)
  4317  }
  4318  
  4319  func (s *ApplicationSuite) TestSetCharmOptionalStorageAdded(c *gc.C) {
  4320  	err := s.setCharmFromMeta(c,
  4321  		mysqlBaseMeta+oneRequiredStorageMeta,
  4322  		mysqlBaseMeta+twoOptionalStorageMeta,
  4323  	)
  4324  	c.Assert(err, jc.ErrorIsNil)
  4325  }
  4326  
  4327  func (s *ApplicationSuite) TestSetCharmStorageCountMinDecreased(c *gc.C) {
  4328  	err := s.setCharmFromMeta(c,
  4329  		mysqlBaseMeta+oneRequiredStorageMeta+storageRange(2, 3),
  4330  		mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 3),
  4331  	)
  4332  	c.Assert(err, jc.ErrorIsNil)
  4333  }
  4334  
  4335  func (s *ApplicationSuite) TestSetCharmStorageCountMinIncreased(c *gc.C) {
  4336  	err := s.setCharmFromMeta(c,
  4337  		mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 3),
  4338  		mysqlBaseMeta+oneRequiredStorageMeta+storageRange(2, 3),
  4339  	)
  4340  	// User must increase the storage constraints from 1 to 2.
  4341  	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`)
  4342  }
  4343  
  4344  func (s *ApplicationSuite) TestSetCharmStorageCountMaxDecreased(c *gc.C) {
  4345  	err := s.setCharmFromMeta(c,
  4346  		mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 2),
  4347  		mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 1),
  4348  	)
  4349  	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`)
  4350  }
  4351  
  4352  func (s *ApplicationSuite) TestSetCharmStorageCountMaxUnboundedToBounded(c *gc.C) {
  4353  	err := s.setCharmFromMeta(c,
  4354  		mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, -1),
  4355  		mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 999),
  4356  	)
  4357  	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`)
  4358  }
  4359  
  4360  func (s *ApplicationSuite) TestSetCharmStorageTypeChanged(c *gc.C) {
  4361  	err := s.setCharmFromMeta(c,
  4362  		mysqlBaseMeta+oneRequiredStorageMeta,
  4363  		mysqlBaseMeta+oneRequiredFilesystemStorageMeta,
  4364  	)
  4365  	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"`)
  4366  }
  4367  
  4368  func (s *ApplicationSuite) TestSetCharmStorageSharedChanged(c *gc.C) {
  4369  	err := s.setCharmFromMeta(c,
  4370  		mysqlBaseMeta+oneOptionalStorageMeta,
  4371  		mysqlBaseMeta+oneOptionalSharedStorageMeta,
  4372  	)
  4373  	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`)
  4374  }
  4375  
  4376  func (s *ApplicationSuite) TestSetCharmStorageReadOnlyChanged(c *gc.C) {
  4377  	err := s.setCharmFromMeta(c,
  4378  		mysqlBaseMeta+oneRequiredStorageMeta,
  4379  		mysqlBaseMeta+oneRequiredReadOnlyStorageMeta,
  4380  	)
  4381  	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`)
  4382  }
  4383  
  4384  func (s *ApplicationSuite) TestSetCharmStorageLocationChanged(c *gc.C) {
  4385  	err := s.setCharmFromMeta(c,
  4386  		mysqlBaseMeta+oneRequiredFilesystemStorageMeta,
  4387  		mysqlBaseMeta+oneRequiredLocationStorageMeta,
  4388  	)
  4389  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" location changed from "" to "/srv"`)
  4390  }
  4391  
  4392  func (s *ApplicationSuite) TestSetCharmStorageWithLocationSingletonToMultipleAdded(c *gc.C) {
  4393  	err := s.setCharmFromMeta(c,
  4394  		mysqlBaseMeta+oneRequiredLocationStorageMeta,
  4395  		mysqlBaseMeta+oneMultipleLocationStorageMeta,
  4396  	)
  4397  	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`)
  4398  }
  4399  
  4400  func (s *ApplicationSuite) assertApplicationRemovedWithItsBindings(c *gc.C, application *state.Application) {
  4401  	// Removing the application removes the bindings with it.
  4402  	err := application.Destroy()
  4403  	c.Assert(err, jc.ErrorIsNil)
  4404  	err = application.Refresh()
  4405  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  4406  	state.AssertEndpointBindingsNotFoundForApplication(c, application)
  4407  }
  4408  
  4409  func (s *ApplicationSuite) TestEndpointBindingsReturnsDefaultsWhenNotFound(c *gc.C) {
  4410  	ch := s.AddMetaCharm(c, "mysql", metaBase, 42)
  4411  	application := s.AddTestingApplicationWithBindings(c, "yoursql", ch, nil)
  4412  	state.RemoveEndpointBindingsForApplication(c, application)
  4413  
  4414  	s.assertApplicationHasOnlyDefaultEndpointBindings(c, application)
  4415  }
  4416  
  4417  func (s *ApplicationSuite) assertApplicationHasOnlyDefaultEndpointBindings(c *gc.C, application *state.Application) {
  4418  	charm, _, err := application.Charm()
  4419  	c.Assert(err, jc.ErrorIsNil)
  4420  
  4421  	knownEndpoints := set.NewStrings("")
  4422  	allBindings, err := state.DefaultEndpointBindingsForCharm(s.State, charm.Meta())
  4423  	c.Assert(err, jc.ErrorIsNil)
  4424  	for endpoint := range allBindings {
  4425  		knownEndpoints.Add(endpoint)
  4426  	}
  4427  
  4428  	setBindings, err := application.EndpointBindings()
  4429  	c.Assert(err, jc.ErrorIsNil)
  4430  	c.Assert(setBindings.Map(), gc.NotNil)
  4431  
  4432  	for endpoint, space := range setBindings.Map() {
  4433  		c.Check(knownEndpoints.Contains(endpoint), jc.IsTrue)
  4434  		c.Check(space, gc.Equals, network.AlphaSpaceId, gc.Commentf("expected default space for endpoint %q, got %q", endpoint, space))
  4435  	}
  4436  }
  4437  
  4438  func (s *ApplicationSuite) TestEndpointBindingsJustDefaults(c *gc.C) {
  4439  	// With unspecified bindings, all endpoints are explicitly bound to the
  4440  	// default space when saved in state.
  4441  	ch := s.AddMetaCharm(c, "mysql", metaBase, 42)
  4442  	application := s.AddTestingApplicationWithBindings(c, "yoursql", ch, nil)
  4443  
  4444  	s.assertApplicationHasOnlyDefaultEndpointBindings(c, application)
  4445  	s.assertApplicationRemovedWithItsBindings(c, application)
  4446  }
  4447  
  4448  func (s *ApplicationSuite) TestEndpointBindingsWithExplictOverrides(c *gc.C) {
  4449  	dbSpace, err := s.State.AddSpace("db", "", nil, true)
  4450  	c.Assert(err, jc.ErrorIsNil)
  4451  	haSpace, err := s.State.AddSpace("ha", "", nil, false)
  4452  	c.Assert(err, jc.ErrorIsNil)
  4453  
  4454  	bindings := map[string]string{
  4455  		"server":  dbSpace.Id(),
  4456  		"cluster": haSpace.Id(),
  4457  	}
  4458  	ch := s.AddMetaCharm(c, "mysql", metaBase, 42)
  4459  	application := s.AddTestingApplicationWithBindings(c, "yoursql", ch, bindings)
  4460  
  4461  	setBindings, err := application.EndpointBindings()
  4462  	c.Assert(err, jc.ErrorIsNil)
  4463  	c.Assert(setBindings.Map(), jc.DeepEquals, map[string]string{
  4464  		"":        network.AlphaSpaceId,
  4465  		"server":  dbSpace.Id(),
  4466  		"client":  network.AlphaSpaceId,
  4467  		"cluster": haSpace.Id(),
  4468  	})
  4469  
  4470  	s.assertApplicationRemovedWithItsBindings(c, application)
  4471  }
  4472  
  4473  func (s *ApplicationSuite) TestSetCharmExtraBindingsUseDefaults(c *gc.C) {
  4474  	dbSpace, err := s.State.AddSpace("db", "", nil, true)
  4475  	c.Assert(err, jc.ErrorIsNil)
  4476  
  4477  	oldCharm := s.AddMetaCharm(c, "mysql", metaDifferentProvider, 42)
  4478  	oldBindings := map[string]string{
  4479  		"server": dbSpace.Id(),
  4480  		"kludge": dbSpace.Id(),
  4481  		"client": dbSpace.Id(),
  4482  	}
  4483  	application := s.AddTestingApplicationWithBindings(c, "yoursql", oldCharm, oldBindings)
  4484  	setBindings, err := application.EndpointBindings()
  4485  	c.Assert(err, jc.ErrorIsNil)
  4486  	effectiveOld := map[string]string{
  4487  		"":        network.AlphaSpaceId,
  4488  		"server":  dbSpace.Id(),
  4489  		"kludge":  dbSpace.Id(),
  4490  		"client":  dbSpace.Id(),
  4491  		"cluster": network.AlphaSpaceId,
  4492  	}
  4493  	c.Assert(setBindings.Map(), jc.DeepEquals, effectiveOld)
  4494  
  4495  	newCharm := s.AddMetaCharm(c, "mysql", metaExtraEndpoints, 43)
  4496  
  4497  	cfg := state.SetCharmConfig{
  4498  		Charm:       newCharm,
  4499  		CharmOrigin: defaultCharmOrigin(newCharm.URL()),
  4500  	}
  4501  	err = application.SetCharm(cfg)
  4502  	c.Assert(err, jc.ErrorIsNil)
  4503  	setBindings, err = application.EndpointBindings()
  4504  	c.Assert(err, jc.ErrorIsNil)
  4505  	effectiveNew := map[string]string{
  4506  		"": network.AlphaSpaceId,
  4507  		// These three should be preserved from oldCharm.
  4508  		"client":  dbSpace.Id(),
  4509  		"server":  dbSpace.Id(),
  4510  		"cluster": network.AlphaSpaceId,
  4511  		// "kludge" is missing in newMeta
  4512  		// All the remaining are new and use the empty default.
  4513  		"foo":  network.AlphaSpaceId,
  4514  		"baz":  network.AlphaSpaceId,
  4515  		"just": network.AlphaSpaceId,
  4516  	}
  4517  	c.Assert(setBindings.Map(), jc.DeepEquals, effectiveNew)
  4518  
  4519  	s.assertApplicationRemovedWithItsBindings(c, application)
  4520  }
  4521  
  4522  func (s *ApplicationSuite) TestSetCharmHandlesMissingBindingsAsDefaults(c *gc.C) {
  4523  	oldCharm := s.AddMetaCharm(c, "mysql", metaDifferentProvider, 69)
  4524  	app := s.AddTestingApplicationWithBindings(c, "theirsql", oldCharm, nil)
  4525  	state.RemoveEndpointBindingsForApplication(c, app)
  4526  
  4527  	newCharm := s.AddMetaCharm(c, "mysql", metaExtraEndpoints, 70)
  4528  
  4529  	cfg := state.SetCharmConfig{
  4530  		Charm:       newCharm,
  4531  		CharmOrigin: defaultCharmOrigin(newCharm.URL()),
  4532  	}
  4533  	err := app.SetCharm(cfg)
  4534  	c.Assert(err, jc.ErrorIsNil)
  4535  	setBindings, err := app.EndpointBindings()
  4536  	c.Assert(err, jc.ErrorIsNil)
  4537  	effectiveNew := map[string]string{
  4538  		// The following two exist for both oldCharm and newCharm.
  4539  		"client":  network.AlphaSpaceId,
  4540  		"cluster": network.AlphaSpaceId,
  4541  		// "kludge" is missing in newMeta, "server" is new and gets the default.
  4542  		"server": network.AlphaSpaceId,
  4543  		// All the remaining are new and use the empty default.
  4544  		"foo":  network.AlphaSpaceId,
  4545  		"baz":  network.AlphaSpaceId,
  4546  		"just": network.AlphaSpaceId,
  4547  	}
  4548  	c.Assert(setBindings.Map(), jc.DeepEquals, effectiveNew)
  4549  
  4550  	s.assertApplicationRemovedWithItsBindings(c, app)
  4551  }
  4552  
  4553  func (s *ApplicationSuite) setupApplicationWithUnitsForUpgradeCharmScenario(c *gc.C, numOfUnits int) (deployedV int, err error) {
  4554  	originalCharmMeta := mysqlBaseMeta + `
  4555  peers:
  4556    replication:
  4557      interface: pgreplication
  4558  `
  4559  	originalCharm := s.AddMetaCharm(c, "mysql", originalCharmMeta, 2)
  4560  	cfg := state.SetCharmConfig{Charm: originalCharm, CharmOrigin: defaultCharmOrigin(originalCharm.URL())}
  4561  	err = s.mysql.SetCharm(cfg)
  4562  	c.Assert(err, jc.ErrorIsNil)
  4563  	s.assertApplicationRelations(c, s.mysql, "mysql:replication")
  4564  	deployedV = s.mysql.CharmModifiedVersion()
  4565  
  4566  	for i := 0; i < numOfUnits; i++ {
  4567  		_, err = s.mysql.AddUnit(state.AddUnitParams{})
  4568  		c.Assert(err, jc.ErrorIsNil)
  4569  	}
  4570  
  4571  	// New mysql charm renames peer relation.
  4572  	updatedCharmMeta := mysqlBaseMeta + `
  4573  peers:
  4574    replication:
  4575      interface: pgpeer
  4576  `
  4577  	updatedCharm := s.AddMetaCharm(c, "mysql", updatedCharmMeta, 3)
  4578  
  4579  	cfg = state.SetCharmConfig{Charm: updatedCharm, CharmOrigin: defaultCharmOrigin(updatedCharm.URL())}
  4580  	err = s.mysql.SetCharm(cfg)
  4581  	return
  4582  }
  4583  
  4584  func (s *ApplicationSuite) TestRenamePeerRelationOnUpgradeWithOneUnit(c *gc.C) {
  4585  	obtainedV, err := s.setupApplicationWithUnitsForUpgradeCharmScenario(c, 1)
  4586  
  4587  	// ensure upgrade happened
  4588  	c.Assert(err, jc.ErrorIsNil)
  4589  	c.Assert(s.mysql.CharmModifiedVersion() == obtainedV+1, jc.IsTrue)
  4590  }
  4591  
  4592  func (s *ApplicationSuite) TestRenamePeerRelationOnUpgradeWithMoreThanOneUnit(c *gc.C) {
  4593  	obtainedV, err := s.setupApplicationWithUnitsForUpgradeCharmScenario(c, 2)
  4594  
  4595  	// ensure upgrade happened
  4596  	c.Assert(err, jc.ErrorIsNil)
  4597  	c.Assert(s.mysql.CharmModifiedVersion() == obtainedV+1, jc.IsTrue)
  4598  }
  4599  
  4600  func (s *ApplicationSuite) TestWatchCharmConfig(c *gc.C) {
  4601  	oldCharm := s.AddTestingCharm(c, "wordpress")
  4602  	app := s.AddTestingApplication(c, "wordpress", oldCharm)
  4603  	// Add a unit so when we change the application's charm,
  4604  	// the old charm isn't removed (due to a reference).
  4605  	u, err := app.AddUnit(state.AddUnitParams{})
  4606  	c.Assert(err, jc.ErrorIsNil)
  4607  	err = u.SetCharmURL(oldCharm.URL())
  4608  	c.Assert(err, jc.ErrorIsNil)
  4609  
  4610  	w, err := app.WatchCharmConfig()
  4611  	c.Assert(err, jc.ErrorIsNil)
  4612  	defer testing.AssertStop(c, w)
  4613  
  4614  	// Initial event.
  4615  	wc := testing.NewNotifyWatcherC(c, w)
  4616  	wc.AssertOneChange()
  4617  
  4618  	// Update config a couple of times, check a single event.
  4619  	err = app.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"blog-title": "superhero paparazzi"})
  4620  	c.Assert(err, jc.ErrorIsNil)
  4621  	// TODO(quiescence): these two changes should be one event.
  4622  	wc.AssertOneChange()
  4623  	err = app.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"blog-title": "sauceror central"})
  4624  	c.Assert(err, jc.ErrorIsNil)
  4625  	wc.AssertOneChange()
  4626  
  4627  	// Non-change is not reported.
  4628  	err = app.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"blog-title": "sauceror central"})
  4629  	c.Assert(err, jc.ErrorIsNil)
  4630  	wc.AssertNoChange()
  4631  
  4632  	// Change application's charm; nothing detected.
  4633  	newCharm := s.AddConfigCharm(c, "wordpress", stringConfig, 123)
  4634  	err = app.SetCharm(state.SetCharmConfig{Charm: newCharm, CharmOrigin: defaultCharmOrigin(newCharm.URL())})
  4635  	c.Assert(err, jc.ErrorIsNil)
  4636  	wc.AssertNoChange()
  4637  
  4638  	// Change application config for new charm; nothing detected.
  4639  	err = app.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"key": "value"})
  4640  	c.Assert(err, jc.ErrorIsNil)
  4641  	wc.AssertNoChange()
  4642  }
  4643  
  4644  var updateApplicationConfigTests = []struct {
  4645  	about   string
  4646  	initial config.ConfigAttributes
  4647  	update  config.ConfigAttributes
  4648  	expect  config.ConfigAttributes
  4649  	err     string
  4650  }{{
  4651  	about:  "set string",
  4652  	update: config.ConfigAttributes{"outlook": "positive"},
  4653  	expect: config.ConfigAttributes{"outlook": "positive"},
  4654  }, {
  4655  	about:   "unset string and set another",
  4656  	initial: config.ConfigAttributes{"outlook": "positive"},
  4657  	update:  config.ConfigAttributes{"outlook": nil, "title": "sir"},
  4658  	expect:  config.ConfigAttributes{"title": "sir"},
  4659  }, {
  4660  	about:  "unset missing string",
  4661  	update: config.ConfigAttributes{"outlook": nil},
  4662  	expect: config.ConfigAttributes{},
  4663  }, {
  4664  	about:   `empty strings are valid`,
  4665  	initial: config.ConfigAttributes{"outlook": "positive"},
  4666  	update:  config.ConfigAttributes{"outlook": "", "title": ""},
  4667  	expect:  config.ConfigAttributes{"outlook": "", "title": ""},
  4668  }, {
  4669  	about:   "preserve existing value",
  4670  	initial: config.ConfigAttributes{"title": "sir"},
  4671  	update:  config.ConfigAttributes{"username": "admin001"},
  4672  	expect:  config.ConfigAttributes{"username": "admin001", "title": "sir"},
  4673  }, {
  4674  	about:   "unset a default value, set a different default",
  4675  	initial: config.ConfigAttributes{"username": "admin001", "title": "sir"},
  4676  	update:  config.ConfigAttributes{"username": nil, "title": "My Title"},
  4677  	expect:  config.ConfigAttributes{"title": "My Title"},
  4678  }, {
  4679  	about:  "non-string type",
  4680  	update: config.ConfigAttributes{"skill-level": 303},
  4681  	expect: config.ConfigAttributes{"skill-level": 303},
  4682  }, {
  4683  	about:   "unset non-string type",
  4684  	initial: config.ConfigAttributes{"skill-level": 303},
  4685  	update:  config.ConfigAttributes{"skill-level": nil},
  4686  	expect:  config.ConfigAttributes{},
  4687  }}
  4688  
  4689  func (s *ApplicationSuite) TestUpdateApplicationConfig(c *gc.C) {
  4690  	sch := s.AddTestingCharm(c, "dummy")
  4691  	for i, t := range updateApplicationConfigTests {
  4692  		c.Logf("test %d. %s", i, t.about)
  4693  		app := s.AddTestingApplication(c, "dummy-application", sch)
  4694  		if t.initial != nil {
  4695  			err := app.UpdateApplicationConfig(t.initial, nil, sampleApplicationConfigSchema(), nil)
  4696  			c.Assert(err, jc.ErrorIsNil)
  4697  		}
  4698  		updates := make(map[string]interface{})
  4699  		var resets []string
  4700  		for k, v := range t.update {
  4701  			if v == nil {
  4702  				resets = append(resets, k)
  4703  			} else {
  4704  				updates[k] = v
  4705  			}
  4706  		}
  4707  		err := app.UpdateApplicationConfig(updates, resets, sampleApplicationConfigSchema(), nil)
  4708  		if t.err != "" {
  4709  			c.Assert(err, gc.ErrorMatches, t.err)
  4710  		} else {
  4711  			c.Assert(err, jc.ErrorIsNil)
  4712  			cfg, err := app.ApplicationConfig()
  4713  			c.Assert(err, jc.ErrorIsNil)
  4714  			c.Assert(cfg, gc.DeepEquals, t.expect)
  4715  		}
  4716  		err = app.Destroy()
  4717  		c.Assert(err, jc.ErrorIsNil)
  4718  	}
  4719  }
  4720  
  4721  func (s *ApplicationSuite) TestApplicationConfigNotFoundNoError(c *gc.C) {
  4722  	ch := s.AddTestingCharm(c, "dummy")
  4723  	app := s.AddTestingApplication(c, "dummy-application", ch)
  4724  
  4725  	// Delete all the settings. We should get a nil return, but no error.
  4726  	_, _ = s.State.MongoSession().DB("juju").C("settings").RemoveAll(nil)
  4727  
  4728  	cfg, err := app.ApplicationConfig()
  4729  	c.Assert(err, jc.ErrorIsNil)
  4730  	c.Assert(cfg, gc.HasLen, 0)
  4731  }
  4732  
  4733  func (s *ApplicationSuite) TestStatusInitial(c *gc.C) {
  4734  	appStatus, err := s.mysql.Status()
  4735  	c.Check(err, jc.ErrorIsNil)
  4736  	c.Check(appStatus.Status, gc.Equals, status.Unset)
  4737  	c.Check(appStatus.Message, gc.Equals, "")
  4738  	c.Check(appStatus.Data, gc.HasLen, 0)
  4739  }
  4740  
  4741  func (s *ApplicationSuite) TestUnitStatusesNoUnits(c *gc.C) {
  4742  	statuses, err := s.mysql.UnitStatuses()
  4743  	c.Check(err, jc.ErrorIsNil)
  4744  	c.Check(statuses, gc.HasLen, 0)
  4745  }
  4746  
  4747  func (s *ApplicationSuite) TestUnitStatusesWithUnits(c *gc.C) {
  4748  	u1, err := s.mysql.AddUnit(state.AddUnitParams{})
  4749  	c.Assert(err, jc.ErrorIsNil)
  4750  	err = u1.SetStatus(status.StatusInfo{
  4751  		Status: status.Maintenance,
  4752  	})
  4753  	c.Assert(err, jc.ErrorIsNil)
  4754  
  4755  	// If Agent status is in error, we see that.
  4756  	u2, err := s.mysql.AddUnit(state.AddUnitParams{})
  4757  	c.Assert(err, jc.ErrorIsNil)
  4758  	err = u2.Agent().SetStatus(status.StatusInfo{
  4759  		Status:  status.Error,
  4760  		Message: "foo",
  4761  	})
  4762  	c.Assert(err, jc.ErrorIsNil)
  4763  	err = u2.SetStatus(status.StatusInfo{
  4764  		Status: status.Blocked,
  4765  	})
  4766  	c.Assert(err, jc.ErrorIsNil)
  4767  
  4768  	statuses, err := s.mysql.UnitStatuses()
  4769  	c.Check(err, jc.ErrorIsNil)
  4770  
  4771  	check := jc.NewMultiChecker()
  4772  	check.AddExpr(`_[_].Since`, jc.Ignore)
  4773  	check.AddExpr(`_[_].Data`, jc.Ignore)
  4774  	c.Assert(statuses, check, map[string]status.StatusInfo{
  4775  		"mysql/0": {
  4776  			Status: status.Maintenance,
  4777  		},
  4778  		"mysql/1": {
  4779  			Status:  status.Error,
  4780  			Message: "foo",
  4781  		},
  4782  	})
  4783  }
  4784  
  4785  func sampleApplicationConfigSchema() environschema.Fields {
  4786  	schema := environschema.Fields{
  4787  		"title":       environschema.Attr{Type: environschema.Tstring},
  4788  		"outlook":     environschema.Attr{Type: environschema.Tstring},
  4789  		"username":    environschema.Attr{Type: environschema.Tstring},
  4790  		"skill-level": environschema.Attr{Type: environschema.Tint},
  4791  	}
  4792  	return schema
  4793  }
  4794  
  4795  func (s *ApplicationSuite) TestUpdateApplicationConfigWithDyingApplication(c *gc.C) {
  4796  	_, err := s.mysql.AddUnit(state.AddUnitParams{})
  4797  	c.Assert(err, jc.ErrorIsNil)
  4798  	err = s.mysql.Destroy()
  4799  	c.Assert(err, jc.ErrorIsNil)
  4800  	assertLife(c, s.mysql, state.Dying)
  4801  	err = s.mysql.UpdateApplicationConfig(config.ConfigAttributes{"title": "value"}, nil, sampleApplicationConfigSchema(), nil)
  4802  	c.Assert(err, jc.ErrorIsNil)
  4803  }
  4804  
  4805  func (s *ApplicationSuite) TestDestroyApplicationRemovesConfig(c *gc.C) {
  4806  	err := s.mysql.UpdateApplicationConfig(config.ConfigAttributes{"title": "value"}, nil, sampleApplicationConfigSchema(), nil)
  4807  	c.Assert(err, jc.ErrorIsNil)
  4808  	appConfig := state.GetApplicationConfig(s.State, s.mysql)
  4809  	err = appConfig.Read()
  4810  	c.Assert(err, jc.ErrorIsNil)
  4811  	c.Assert(appConfig.Map(), gc.Not(gc.HasLen), 0)
  4812  
  4813  	op := s.mysql.DestroyOperation()
  4814  	op.RemoveOffers = true
  4815  	err = s.State.ApplyOperation(op)
  4816  	c.Assert(err, jc.ErrorIsNil)
  4817  	assertRemoved(c, s.mysql)
  4818  }
  4819  
  4820  type CAASApplicationSuite struct {
  4821  	ConnSuite
  4822  	app    *state.Application
  4823  	caasSt *state.State
  4824  }
  4825  
  4826  var _ = gc.Suite(&CAASApplicationSuite{})
  4827  
  4828  func (s *CAASApplicationSuite) SetUpTest(c *gc.C) {
  4829  	s.ConnSuite.SetUpTest(c)
  4830  	s.caasSt = s.Factory.MakeCAASModel(c, nil)
  4831  	s.AddCleanup(func(_ *gc.C) { _ = s.caasSt.Close() })
  4832  
  4833  	f := factory.NewFactory(s.caasSt, s.StatePool)
  4834  	ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"})
  4835  	s.app = f.MakeApplication(c, &factory.ApplicationParams{Name: "gitlab", Charm: ch})
  4836  	// Consume the initial construction events from the watchers.
  4837  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
  4838  }
  4839  
  4840  func strPtr(s string) *string {
  4841  	return &s
  4842  }
  4843  
  4844  func (s *CAASApplicationSuite) TestUpdateCAASUnits(c *gc.C) {
  4845  	s.assertUpdateCAASUnits(c, true)
  4846  }
  4847  
  4848  func (s *CAASApplicationSuite) TestUpdateCAASUnitsApplicationNotALive(c *gc.C) {
  4849  	s.assertUpdateCAASUnits(c, false)
  4850  }
  4851  
  4852  func (s *CAASApplicationSuite) assertUpdateCAASUnits(c *gc.C, aliveApp bool) {
  4853  	existingUnit, err := s.app.AddUnit(state.AddUnitParams{ProviderId: strPtr("unit-uuid")})
  4854  	c.Assert(err, jc.ErrorIsNil)
  4855  	removedUnit, err := s.app.AddUnit(state.AddUnitParams{ProviderId: strPtr("removed-unit-uuid")})
  4856  	c.Assert(err, jc.ErrorIsNil)
  4857  	noContainerUnit, err := s.app.AddUnit(state.AddUnitParams{ProviderId: strPtr("never-cloud-container")})
  4858  	c.Assert(err, jc.ErrorIsNil)
  4859  	if !aliveApp {
  4860  		err := s.app.Destroy()
  4861  		c.Assert(err, jc.ErrorIsNil)
  4862  	}
  4863  
  4864  	var updateUnits state.UpdateUnitsOperation
  4865  	updateUnits.Deletes = []*state.DestroyUnitOperation{removedUnit.DestroyOperation()}
  4866  	updateUnits.Adds = []*state.AddUnitOperation{
  4867  		s.app.AddOperation(state.UnitUpdateProperties{
  4868  			ProviderId: strPtr("new-unit-uuid"),
  4869  			Address:    strPtr("192.168.1.1"),
  4870  			Ports:      &[]string{"80"},
  4871  			AgentStatus: &status.StatusInfo{
  4872  				Status:  status.Running,
  4873  				Message: "new running",
  4874  			},
  4875  			CloudContainerStatus: &status.StatusInfo{
  4876  				Status:  status.Running,
  4877  				Message: "new container running",
  4878  			},
  4879  		}),
  4880  		s.app.AddOperation(state.UnitUpdateProperties{
  4881  			ProviderId: strPtr("add-never-cloud-container"),
  4882  			AgentStatus: &status.StatusInfo{
  4883  				Status:  status.Running,
  4884  				Message: "new running",
  4885  			},
  4886  			// Status history should not show this as active.
  4887  			UnitStatus: &status.StatusInfo{
  4888  				Status:  status.Active,
  4889  				Message: "unit active",
  4890  			},
  4891  		}),
  4892  	}
  4893  	updateUnits.Updates = []*state.UpdateUnitOperation{
  4894  		noContainerUnit.UpdateOperation(state.UnitUpdateProperties{
  4895  			ProviderId: strPtr("never-cloud-container"),
  4896  			Address:    strPtr("192.168.1.2"),
  4897  			Ports:      &[]string{"443"},
  4898  			UnitStatus: &status.StatusInfo{
  4899  				Status:  status.Active,
  4900  				Message: "unit active",
  4901  			},
  4902  		}),
  4903  		existingUnit.UpdateOperation(state.UnitUpdateProperties{
  4904  			ProviderId: strPtr("unit-uuid"),
  4905  			Address:    strPtr("192.168.1.2"),
  4906  			Ports:      &[]string{"443"},
  4907  			AgentStatus: &status.StatusInfo{
  4908  				Status:  status.Running,
  4909  				Message: "existing running",
  4910  			},
  4911  			CloudContainerStatus: &status.StatusInfo{
  4912  				Status:  status.Running,
  4913  				Message: "existing container running",
  4914  			},
  4915  		})}
  4916  	err = s.app.UpdateUnits(&updateUnits)
  4917  	if !aliveApp {
  4918  		c.Assert(err, jc.Satisfies, state.IsNotAlive)
  4919  		return
  4920  	}
  4921  	c.Assert(err, jc.ErrorIsNil)
  4922  
  4923  	units, err := s.app.AllUnits()
  4924  	c.Assert(err, jc.ErrorIsNil)
  4925  	c.Assert(units, gc.HasLen, 4)
  4926  
  4927  	unitsById := make(map[string]*state.Unit)
  4928  	containerInfoById := make(map[string]state.CloudContainer)
  4929  	for _, u := range units {
  4930  		c.Assert(u.ShouldBeAssigned(), jc.IsFalse)
  4931  		containerInfo, err := u.ContainerInfo()
  4932  		c.Assert(err, jc.ErrorIsNil)
  4933  		c.Assert(containerInfo.Unit(), gc.Equals, u.Name())
  4934  		c.Assert(containerInfo.ProviderId(), gc.Not(gc.Equals), "")
  4935  		unitsById[containerInfo.ProviderId()] = u
  4936  		containerInfoById[containerInfo.ProviderId()] = containerInfo
  4937  	}
  4938  	u, ok := unitsById["unit-uuid"]
  4939  	c.Assert(ok, jc.IsTrue)
  4940  	info, ok := containerInfoById["unit-uuid"]
  4941  	c.Assert(ok, jc.IsTrue)
  4942  	c.Check(u.Name(), gc.Equals, existingUnit.Name())
  4943  	c.Check(info.Address(), gc.NotNil)
  4944  	c.Check(*info.Address(), gc.DeepEquals,
  4945  		network.NewSpaceAddress("192.168.1.2", network.WithScope(network.ScopeMachineLocal)))
  4946  	c.Check(info.Ports(), jc.DeepEquals, []string{"443"})
  4947  	statusInfo, err := u.AgentStatus()
  4948  	c.Assert(err, jc.ErrorIsNil)
  4949  	c.Assert(statusInfo.Status, gc.Equals, status.Running)
  4950  	c.Assert(statusInfo.Message, gc.Equals, "existing running")
  4951  	history, err := u.AgentHistory().StatusHistory(status.StatusHistoryFilter{Size: 10})
  4952  	c.Assert(err, jc.ErrorIsNil)
  4953  	c.Assert(history, gc.HasLen, 2)
  4954  	// Creating a new unit may cause the history entries to be written with
  4955  	// the same timestamp due to the precision used by the db.
  4956  	if history[0].Status == status.Running {
  4957  		c.Assert(history[0].Status, gc.Equals, status.Running)
  4958  		c.Assert(history[1].Status, gc.Equals, status.Allocating)
  4959  	} else {
  4960  		c.Assert(history[1].Status, gc.Equals, status.Running)
  4961  		c.Assert(history[0].Status, gc.Equals, status.Allocating)
  4962  		c.Assert(history[0].Since.Unix(), gc.Equals, history[1].Since.Unix())
  4963  	}
  4964  	statusInfo, err = u.Status()
  4965  	c.Assert(err, jc.ErrorIsNil)
  4966  	c.Assert(statusInfo.Status, gc.Equals, status.Waiting)
  4967  	c.Assert(statusInfo.Message, gc.Equals, "installing agent")
  4968  	statusInfo, err = state.GetCloudContainerStatus(s.caasSt, u.Name())
  4969  	c.Assert(err, jc.ErrorIsNil)
  4970  	c.Assert(statusInfo.Status, gc.Equals, status.Running)
  4971  	c.Assert(statusInfo.Message, gc.Equals, "existing container running")
  4972  	unitHistory, err := u.StatusHistory(status.StatusHistoryFilter{Size: 10})
  4973  	c.Assert(err, jc.ErrorIsNil)
  4974  	c.Assert(unitHistory, gc.HasLen, 2)
  4975  	// Creating a new unit may cause the history entries to be written with
  4976  	// the same timestamp due to the precision used by the db.
  4977  	if unitHistory[0].Status == status.Running {
  4978  		c.Assert(unitHistory[0].Status, gc.Equals, status.Running)
  4979  		c.Assert(unitHistory[0].Message, gc.Equals, "existing container running")
  4980  		c.Assert(unitHistory[1].Status, gc.Equals, status.Waiting)
  4981  	} else {
  4982  		c.Assert(unitHistory[1].Status, gc.Equals, status.Running)
  4983  		c.Assert(unitHistory[1].Message, gc.Equals, "existing container running")
  4984  		c.Assert(unitHistory[0].Status, gc.Equals, status.Waiting)
  4985  		c.Assert(unitHistory[0].Since.Unix(), gc.Equals, history[1].Since.Unix())
  4986  	}
  4987  
  4988  	u, ok = unitsById["never-cloud-container"]
  4989  	c.Assert(ok, jc.IsTrue)
  4990  	info, ok = containerInfoById["never-cloud-container"]
  4991  	c.Assert(ok, jc.IsTrue)
  4992  	unitHistory, err = u.StatusHistory(status.StatusHistoryFilter{Size: 10})
  4993  	c.Assert(err, jc.ErrorIsNil)
  4994  	c.Assert(unitHistory[0].Status, gc.Equals, status.Waiting)
  4995  	c.Assert(unitHistory[0].Message, gc.Equals, status.MessageInstallingAgent)
  4996  
  4997  	u, ok = unitsById["add-never-cloud-container"]
  4998  	c.Assert(ok, jc.IsTrue)
  4999  	info, ok = containerInfoById["add-never-cloud-container"]
  5000  	c.Assert(ok, jc.IsTrue)
  5001  	unitHistory, err = u.StatusHistory(status.StatusHistoryFilter{Size: 10})
  5002  	c.Assert(err, jc.ErrorIsNil)
  5003  	c.Assert(unitHistory[0].Status, gc.Equals, status.Waiting)
  5004  	c.Assert(unitHistory[0].Message, gc.Equals, status.MessageInstallingAgent)
  5005  
  5006  	u, ok = unitsById["new-unit-uuid"]
  5007  	c.Assert(ok, jc.IsTrue)
  5008  	info, ok = containerInfoById["new-unit-uuid"]
  5009  	c.Assert(ok, jc.IsTrue)
  5010  	c.Assert(u.Name(), gc.Equals, "gitlab/3")
  5011  	c.Check(info.Address(), gc.NotNil)
  5012  	c.Check(*info.Address(), gc.DeepEquals,
  5013  		network.NewSpaceAddress("192.168.1.1", network.WithScope(network.ScopeMachineLocal)))
  5014  	c.Assert(info.Ports(), jc.DeepEquals, []string{"80"})
  5015  
  5016  	addr, err := u.PrivateAddress()
  5017  	c.Assert(err, jc.ErrorIsNil)
  5018  	c.Assert(addr, jc.DeepEquals, network.NewSpaceAddress("192.168.1.1", network.WithScope(network.ScopeMachineLocal)))
  5019  
  5020  	statusInfo, err = u.Status()
  5021  	c.Assert(err, jc.ErrorIsNil)
  5022  	c.Assert(statusInfo.Status, gc.Equals, status.Waiting)
  5023  	c.Assert(statusInfo.Message, gc.Equals, status.MessageInstallingAgent)
  5024  	statusInfo, err = state.GetCloudContainerStatus(s.caasSt, u.Name())
  5025  	c.Assert(err, jc.ErrorIsNil)
  5026  	c.Assert(statusInfo.Status, gc.Equals, status.Running)
  5027  	c.Assert(statusInfo.Message, gc.Equals, "new container running")
  5028  	statusInfo, err = u.AgentStatus()
  5029  	c.Assert(err, jc.ErrorIsNil)
  5030  	c.Assert(statusInfo.Status, gc.Equals, status.Running)
  5031  	c.Assert(statusInfo.Message, gc.Equals, "new running")
  5032  	history, err = u.AgentHistory().StatusHistory(status.StatusHistoryFilter{Size: 10})
  5033  	c.Assert(err, jc.ErrorIsNil)
  5034  	c.Assert(history, gc.HasLen, 2)
  5035  	// Creating a new unit may cause the history entries to be written with
  5036  	// the same timestamp due to the precision used by the db.
  5037  	if history[0].Status == status.Running {
  5038  		c.Assert(history[0].Status, gc.Equals, status.Running)
  5039  		c.Assert(history[1].Status, gc.Equals, status.Allocating)
  5040  	} else {
  5041  		c.Assert(history[1].Status, gc.Equals, status.Running)
  5042  		c.Assert(history[0].Status, gc.Equals, status.Allocating)
  5043  		c.Assert(history[0].Since.Unix(), gc.Equals, history[1].Since.Unix())
  5044  	}
  5045  	// container status history must have overridden the unit status.
  5046  	unitHistory, err = u.StatusHistory(status.StatusHistoryFilter{Size: 10})
  5047  	c.Assert(err, jc.ErrorIsNil)
  5048  	c.Assert(unitHistory, gc.HasLen, 2)
  5049  	// Creating a new unit may cause the history entries to be written with
  5050  	// the same timestamp due to the precision used by the db.
  5051  	if unitHistory[0].Status == status.Running {
  5052  		c.Assert(unitHistory[0].Status, gc.Equals, status.Running)
  5053  		c.Assert(unitHistory[0].Message, gc.Equals, "new container running")
  5054  		c.Assert(unitHistory[1].Status, gc.Equals, status.Waiting)
  5055  	} else {
  5056  		c.Assert(unitHistory[1].Status, gc.Equals, status.Running)
  5057  		c.Assert(unitHistory[1].Message, gc.Equals, "new container running")
  5058  		c.Assert(unitHistory[0].Status, gc.Equals, status.Waiting)
  5059  		c.Assert(unitHistory[0].Since.Unix(), gc.Equals, history[1].Since.Unix())
  5060  	}
  5061  
  5062  	// check cloud container status history is stored.
  5063  	containerStatusHistory, err := state.GetCloudContainerStatusHistory(s.caasSt, u.Name(), status.StatusHistoryFilter{Size: 10})
  5064  	c.Assert(err, jc.ErrorIsNil)
  5065  	c.Assert(containerStatusHistory, gc.HasLen, 1)
  5066  	c.Assert(containerStatusHistory[0].Status, gc.Equals, status.Running)
  5067  	c.Assert(containerStatusHistory[0].Message, gc.Equals, "new container running")
  5068  
  5069  	err = removedUnit.Refresh()
  5070  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  5071  }
  5072  
  5073  func (s *CAASApplicationSuite) TestAddUnitWithProviderId(c *gc.C) {
  5074  	u, err := s.app.AddUnit(state.AddUnitParams{ProviderId: strPtr("provider-id")})
  5075  	c.Assert(err, jc.ErrorIsNil)
  5076  	info, err := u.ContainerInfo()
  5077  	c.Assert(err, jc.ErrorIsNil)
  5078  	c.Assert(info.Unit(), gc.Equals, u.Name())
  5079  	c.Assert(info.ProviderId(), gc.Equals, "provider-id")
  5080  }
  5081  
  5082  func (s *CAASApplicationSuite) TestServiceInfo(c *gc.C) {
  5083  	addrs := network.NewSpaceAddresses("10.0.0.1")
  5084  
  5085  	for i := 0; i < 2; i++ {
  5086  		err := s.app.UpdateCloudService("id", addrs)
  5087  		c.Assert(err, jc.ErrorIsNil)
  5088  		app, err := s.caasSt.Application(s.app.Name())
  5089  		c.Assert(err, jc.ErrorIsNil)
  5090  		info, err := app.ServiceInfo()
  5091  		c.Assert(err, jc.ErrorIsNil)
  5092  		c.Assert(info.ProviderId(), gc.Equals, "id")
  5093  		c.Assert(info.Addresses(), jc.DeepEquals, addrs)
  5094  	}
  5095  }
  5096  
  5097  func (s *CAASApplicationSuite) TestServiceInfoEmptyProviderId(c *gc.C) {
  5098  	addrs := network.NewSpaceAddresses("10.0.0.1")
  5099  
  5100  	for i := 0; i < 2; i++ {
  5101  		err := s.app.UpdateCloudService("", addrs)
  5102  		c.Assert(err, jc.ErrorIsNil)
  5103  		app, err := s.caasSt.Application(s.app.Name())
  5104  		c.Assert(err, jc.ErrorIsNil)
  5105  		info, err := app.ServiceInfo()
  5106  		c.Assert(err, jc.ErrorIsNil)
  5107  		c.Assert(info.ProviderId(), gc.Equals, "")
  5108  		c.Assert(info.Addresses(), jc.DeepEquals, addrs)
  5109  	}
  5110  }
  5111  
  5112  func (s *CAASApplicationSuite) TestRemoveApplicationDeletesServiceInfo(c *gc.C) {
  5113  	addrs := network.NewSpaceAddresses("10.0.0.1")
  5114  
  5115  	err := s.app.UpdateCloudService("id", addrs)
  5116  	c.Assert(err, jc.ErrorIsNil)
  5117  	err = s.app.Destroy()
  5118  	c.Assert(err, jc.ErrorIsNil)
  5119  	err = s.app.ClearResources()
  5120  	c.Assert(err, jc.ErrorIsNil)
  5121  	// Until cleanups run, no removal.
  5122  	si, err := s.app.ServiceInfo()
  5123  	c.Assert(err, jc.ErrorIsNil)
  5124  	c.Assert(si, gc.NotNil)
  5125  	assertCleanupCount(c, s.caasSt, 2)
  5126  	_, err = s.app.ServiceInfo()
  5127  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  5128  }
  5129  
  5130  func (s *CAASApplicationSuite) TestInvalidScale(c *gc.C) {
  5131  	err := s.app.SetScale(-1, 0, true)
  5132  	c.Assert(err, gc.ErrorMatches, "application scale -1 not valid")
  5133  
  5134  	// set scale without force for caas workers - a new Generation is required.
  5135  	err = s.app.SetScale(3, 0, false)
  5136  	c.Assert(err, jc.Satisfies, errors.IsForbidden)
  5137  }
  5138  
  5139  func (s *CAASApplicationSuite) TestSetScale(c *gc.C) {
  5140  	// set scale with force for CLI - DesiredScaleProtected set to true.
  5141  	err := s.app.SetScale(5, 0, true)
  5142  	c.Assert(err, jc.ErrorIsNil)
  5143  	err = s.app.Refresh()
  5144  	c.Assert(err, jc.ErrorIsNil)
  5145  	c.Assert(s.app.GetScale(), gc.Equals, 5)
  5146  	svcInfo, err := s.app.ServiceInfo()
  5147  	c.Assert(err, jc.ErrorIsNil)
  5148  	c.Assert(svcInfo.DesiredScaleProtected(), jc.IsTrue)
  5149  
  5150  	// set scale without force for caas workers - a new Generation is required.
  5151  	err = s.app.SetScale(5, 1, false)
  5152  	c.Assert(err, jc.ErrorIsNil)
  5153  	err = s.app.Refresh()
  5154  	c.Assert(err, jc.ErrorIsNil)
  5155  	c.Assert(s.app.GetScale(), gc.Equals, 5)
  5156  	svcInfo, err = s.app.ServiceInfo()
  5157  	c.Assert(err, jc.ErrorIsNil)
  5158  	c.Assert(svcInfo.DesiredScaleProtected(), jc.IsFalse)
  5159  	c.Assert(svcInfo.Generation(), jc.DeepEquals, int64(1))
  5160  }
  5161  
  5162  func (s *CAASApplicationSuite) TestInvalidChangeScale(c *gc.C) {
  5163  	newScale, err := s.app.ChangeScale(-1)
  5164  	c.Assert(err, gc.ErrorMatches, "cannot remove more units than currently exist not valid")
  5165  	c.Assert(newScale, gc.Equals, 0)
  5166  }
  5167  
  5168  func (s *CAASApplicationSuite) TestChangeScale(c *gc.C) {
  5169  	newScale, err := s.app.ChangeScale(5)
  5170  	c.Assert(err, jc.ErrorIsNil)
  5171  	c.Assert(newScale, gc.Equals, 5)
  5172  	err = s.app.Refresh()
  5173  	c.Assert(err, jc.ErrorIsNil)
  5174  	c.Assert(s.app.GetScale(), gc.Equals, 5)
  5175  
  5176  	newScale, err = s.app.ChangeScale(-4)
  5177  	c.Assert(err, jc.ErrorIsNil)
  5178  	c.Assert(newScale, gc.Equals, 1)
  5179  	err = s.app.Refresh()
  5180  	c.Assert(err, jc.ErrorIsNil)
  5181  	c.Assert(s.app.GetScale(), gc.Equals, 1)
  5182  }
  5183  
  5184  func (s *CAASApplicationSuite) TestWatchScale(c *gc.C) {
  5185  	// Empty initial event.
  5186  	w := s.app.WatchScale()
  5187  	defer testing.AssertStop(c, w)
  5188  	wc := testing.NewNotifyWatcherC(c, w)
  5189  	wc.AssertOneChange()
  5190  
  5191  	err := s.app.SetScale(5, 0, true)
  5192  	c.Assert(err, jc.ErrorIsNil)
  5193  	wc.AssertOneChange()
  5194  
  5195  	// Set to same value, no change.
  5196  	err = s.app.SetScale(5, 0, true)
  5197  	c.Assert(err, jc.ErrorIsNil)
  5198  	wc.AssertNoChange()
  5199  
  5200  	err = s.app.SetScale(6, 0, true)
  5201  	c.Assert(err, jc.ErrorIsNil)
  5202  	wc.AssertOneChange()
  5203  
  5204  	// An unrelated update, no change.
  5205  	err = s.app.SetMinUnits(2)
  5206  	c.Assert(err, jc.ErrorIsNil)
  5207  	wc.AssertNoChange()
  5208  
  5209  	err = s.app.Destroy()
  5210  	c.Assert(err, jc.ErrorIsNil)
  5211  	wc.AssertNoChange()
  5212  }
  5213  
  5214  func (s *CAASApplicationSuite) TestWatchCloudService(c *gc.C) {
  5215  	cloudSvc, err := s.State.SaveCloudService(state.SaveCloudServiceArgs{
  5216  		Id: s.app.Name(),
  5217  	})
  5218  	c.Assert(err, jc.ErrorIsNil)
  5219  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
  5220  
  5221  	w := cloudSvc.Watch()
  5222  	defer testing.AssertStop(c, w)
  5223  
  5224  	// Initial event.
  5225  	wc := testing.NewNotifyWatcherC(c, w)
  5226  	wc.AssertOneChange()
  5227  
  5228  	_, err = s.State.SaveCloudService(state.SaveCloudServiceArgs{
  5229  		Id:         s.app.Name(),
  5230  		ProviderId: "123",
  5231  	})
  5232  	c.Assert(err, jc.ErrorIsNil)
  5233  	wc.AssertOneChange()
  5234  
  5235  	// Stop, check closed.
  5236  	testing.AssertStop(c, w)
  5237  	wc.AssertClosed()
  5238  
  5239  	// Remove service by removing app, start new watch, check single event.
  5240  	err = s.app.Destroy()
  5241  	c.Assert(err, jc.ErrorIsNil)
  5242  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
  5243  	w = cloudSvc.Watch()
  5244  	defer testing.AssertStop(c, w)
  5245  	testing.NewNotifyWatcherC(c, w).AssertOneChange()
  5246  }
  5247  
  5248  func (s *CAASApplicationSuite) TestRewriteStatusHistory(c *gc.C) {
  5249  	st := s.Factory.MakeModel(c, &factory.ModelParams{
  5250  		Name: "caas-model",
  5251  		Type: state.ModelTypeCAAS,
  5252  	})
  5253  	defer st.Close()
  5254  	f := factory.NewFactory(st, s.StatePool)
  5255  	ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"})
  5256  	app := f.MakeApplication(c, &factory.ApplicationParams{Name: "gitlab", Charm: ch})
  5257  
  5258  	history, err := app.StatusHistory(status.StatusHistoryFilter{Size: 10})
  5259  	c.Assert(err, jc.ErrorIsNil)
  5260  	c.Assert(history, gc.HasLen, 1)
  5261  	c.Assert(history[0].Status, gc.Equals, status.Unset)
  5262  	c.Assert(history[0].Message, gc.Equals, "")
  5263  
  5264  	// Must overwrite the history
  5265  	err = app.SetOperatorStatus(status.StatusInfo{
  5266  		Status:  status.Allocating,
  5267  		Message: "operator message",
  5268  	})
  5269  	c.Assert(err, jc.ErrorIsNil)
  5270  	history, err = app.StatusHistory(status.StatusHistoryFilter{Size: 10})
  5271  	c.Assert(err, jc.ErrorIsNil)
  5272  	c.Assert(history, gc.HasLen, 2)
  5273  	c.Assert(history[0].Status, gc.Equals, status.Allocating)
  5274  	c.Assert(history[0].Message, gc.Equals, "operator message")
  5275  	c.Assert(history[1].Status, gc.Equals, status.Unset)
  5276  	c.Assert(history[1].Message, gc.Equals, "")
  5277  
  5278  	err = app.SetOperatorStatus(status.StatusInfo{
  5279  		Status:  status.Running,
  5280  		Message: "operator running",
  5281  	})
  5282  	c.Assert(err, jc.ErrorIsNil)
  5283  	err = app.SetStatus(status.StatusInfo{
  5284  		Status:  status.Active,
  5285  		Message: "app active",
  5286  	})
  5287  	c.Assert(err, jc.ErrorIsNil)
  5288  	history, err = app.StatusHistory(status.StatusHistoryFilter{Size: 10})
  5289  	c.Log(history)
  5290  	c.Assert(err, jc.ErrorIsNil)
  5291  	c.Assert(history, gc.HasLen, 3)
  5292  	c.Assert(history[0].Status, gc.Equals, status.Active)
  5293  	c.Assert(history[0].Message, gc.Equals, "app active")
  5294  	c.Assert(history[1].Status, gc.Equals, status.Allocating)
  5295  	c.Assert(history[1].Message, gc.Equals, "operator message")
  5296  	c.Assert(history[2].Status, gc.Equals, status.Unset)
  5297  	c.Assert(history[2].Message, gc.Equals, "")
  5298  }
  5299  
  5300  func (s *CAASApplicationSuite) TestClearResources(c *gc.C) {
  5301  	c.Assert(state.GetApplicationHasResources(s.app), jc.IsTrue)
  5302  	err := s.app.ClearResources()
  5303  	c.Assert(err, gc.ErrorMatches, `application "gitlab" is alive`)
  5304  	err = s.app.Destroy()
  5305  	c.Assert(err, jc.ErrorIsNil)
  5306  	assertCleanupCount(c, s.caasSt, 1)
  5307  
  5308  	// ClearResources should be idempotent.
  5309  	for i := 0; i < 2; i++ {
  5310  		err := s.app.ClearResources()
  5311  		c.Assert(err, jc.ErrorIsNil)
  5312  		c.Assert(state.GetApplicationHasResources(s.app), jc.IsFalse)
  5313  	}
  5314  	// Resetting the app's HasResources the first time schedules a cleanup.
  5315  	assertCleanupCount(c, s.caasSt, 2)
  5316  }
  5317  
  5318  func (s *CAASApplicationSuite) TestDestroySimple(c *gc.C) {
  5319  	err := s.app.Destroy()
  5320  	c.Assert(err, jc.ErrorIsNil)
  5321  	// App not removed since cluster resources not cleaned up yet.
  5322  	c.Assert(s.app.Life(), gc.Equals, state.Dead)
  5323  	err = s.app.Refresh()
  5324  	c.Assert(err, jc.ErrorIsNil)
  5325  	c.Assert(state.GetApplicationHasResources(s.app), jc.IsTrue)
  5326  }
  5327  
  5328  func (s *CAASApplicationSuite) TestForceDestroyQueuesForceCleanup(c *gc.C) {
  5329  	op := s.app.DestroyOperation()
  5330  	op.Force = true
  5331  	err := s.caasSt.ApplyOperation(op)
  5332  	c.Assert(err, jc.ErrorIsNil)
  5333  
  5334  	// Cleanup queued but won't run until scheduled.
  5335  	assertNeedsCleanup(c, s.caasSt)
  5336  	s.Clock.Advance(2 * time.Minute)
  5337  	assertCleanupRuns(c, s.caasSt)
  5338  
  5339  	err = s.app.Refresh()
  5340  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  5341  }
  5342  
  5343  func (s *CAASApplicationSuite) TestDestroyStillHasUnits(c *gc.C) {
  5344  	unit, err := s.app.AddUnit(state.AddUnitParams{})
  5345  	c.Assert(err, jc.ErrorIsNil)
  5346  	err = s.app.Destroy()
  5347  	c.Assert(err, jc.ErrorIsNil)
  5348  	c.Assert(s.app.Life(), gc.Equals, state.Dying)
  5349  
  5350  	c.Assert(unit.EnsureDead(), jc.ErrorIsNil)
  5351  	assertLife(c, s.app, state.Dying)
  5352  
  5353  	c.Assert(unit.Remove(), jc.ErrorIsNil)
  5354  	assertCleanupCount(c, s.caasSt, 1)
  5355  	// App not removed since cluster resources not cleaned up yet.
  5356  	assertLife(c, s.app, state.Dead)
  5357  }
  5358  
  5359  func (s *CAASApplicationSuite) TestDestroyOnceHadUnits(c *gc.C) {
  5360  	unit, err := s.app.AddUnit(state.AddUnitParams{})
  5361  	c.Assert(err, jc.ErrorIsNil)
  5362  	err = unit.EnsureDead()
  5363  	c.Assert(err, jc.ErrorIsNil)
  5364  	err = unit.Remove()
  5365  	c.Assert(err, jc.ErrorIsNil)
  5366  
  5367  	err = s.app.Destroy()
  5368  	c.Assert(err, jc.ErrorIsNil)
  5369  	c.Assert(s.app.Life(), gc.Equals, state.Dead)
  5370  	// App not removed since cluster resources not cleaned up yet.
  5371  	assertLife(c, s.app, state.Dead)
  5372  }
  5373  
  5374  func (s *CAASApplicationSuite) TestDestroyStaleNonZeroUnitCount(c *gc.C) {
  5375  	unit, err := s.app.AddUnit(state.AddUnitParams{})
  5376  	c.Assert(err, jc.ErrorIsNil)
  5377  	err = s.app.Refresh()
  5378  	c.Assert(err, jc.ErrorIsNil)
  5379  	err = unit.EnsureDead()
  5380  	c.Assert(err, jc.ErrorIsNil)
  5381  	err = unit.Remove()
  5382  	c.Assert(err, jc.ErrorIsNil)
  5383  
  5384  	err = s.app.Destroy()
  5385  	c.Assert(err, jc.ErrorIsNil)
  5386  	c.Assert(s.app.Life(), gc.Equals, state.Dead)
  5387  	// App not removed since cluster resources not cleaned up yet.
  5388  	assertLife(c, s.app, state.Dead)
  5389  }
  5390  
  5391  func (s *CAASApplicationSuite) TestDestroyStaleZeroUnitCount(c *gc.C) {
  5392  	unit, err := s.app.AddUnit(state.AddUnitParams{})
  5393  	c.Assert(err, jc.ErrorIsNil)
  5394  
  5395  	err = s.app.Destroy()
  5396  	c.Assert(err, jc.ErrorIsNil)
  5397  	c.Assert(s.app.Life(), gc.Equals, state.Dying)
  5398  	assertLife(c, s.app, state.Dying)
  5399  
  5400  	err = unit.EnsureDead()
  5401  	c.Assert(err, jc.ErrorIsNil)
  5402  	assertLife(c, s.app, state.Dying)
  5403  
  5404  	c.Assert(unit.Remove(), jc.ErrorIsNil)
  5405  	assertCleanupCount(c, s.caasSt, 1)
  5406  	c.Assert(err, jc.ErrorIsNil)
  5407  	// App not removed since cluster resources not cleaned up yet.
  5408  	assertLife(c, s.app, state.Dead)
  5409  }
  5410  
  5411  func (s *CAASApplicationSuite) TestDestroyWithRemovableRelation(c *gc.C) {
  5412  	ch := state.AddTestingCharmForSeries(c, s.caasSt, "kubernetes", "mysql")
  5413  	mysql := state.AddTestingApplicationForBase(c, s.caasSt, state.UbuntuBase("20.04"), "mysql", ch)
  5414  	eps, err := s.caasSt.InferEndpoints("gitlab", "mysql")
  5415  	c.Assert(err, jc.ErrorIsNil)
  5416  	rel, err := s.caasSt.AddRelation(eps...)
  5417  	c.Assert(err, jc.ErrorIsNil)
  5418  
  5419  	// Destroy a application with no units in relation scope; check application and
  5420  	// unit removed.
  5421  	err = mysql.Destroy()
  5422  	c.Assert(err, jc.ErrorIsNil)
  5423  	err = mysql.Refresh()
  5424  	c.Assert(err, jc.ErrorIsNil)
  5425  	// App not removed since cluster resources not cleaned up yet.
  5426  	assertLife(c, mysql, state.Dead)
  5427  
  5428  	err = rel.Refresh()
  5429  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  5430  }
  5431  
  5432  func (s *CAASApplicationSuite) TestDestroyWithReferencedRelation(c *gc.C) {
  5433  	s.assertDestroyWithReferencedRelation(c, true)
  5434  }
  5435  
  5436  func (s *CAASApplicationSuite) TestDestroyWithReferencedRelationStaleCount(c *gc.C) {
  5437  	s.assertDestroyWithReferencedRelation(c, false)
  5438  }
  5439  
  5440  func (s *CAASApplicationSuite) assertDestroyWithReferencedRelation(c *gc.C, refresh bool) {
  5441  	ch := state.AddTestingCharmForSeries(c, s.caasSt, "kubernetes", "mysql")
  5442  	mysql := state.AddTestingApplicationForBase(c, s.caasSt, state.UbuntuBase("20.04"), "mysql", ch)
  5443  	eps, err := s.caasSt.InferEndpoints("gitlab", "mysql")
  5444  	c.Assert(err, jc.ErrorIsNil)
  5445  	rel0, err := s.caasSt.AddRelation(eps...)
  5446  	c.Assert(err, jc.ErrorIsNil)
  5447  
  5448  	ch = state.AddTestingCharmForSeries(c, s.caasSt, "kubernetes", "proxy")
  5449  	state.AddTestingApplicationForBase(c, s.caasSt, state.UbuntuBase("20.04"), "proxy", ch)
  5450  	eps, err = s.caasSt.InferEndpoints("proxy", "gitlab")
  5451  	c.Assert(err, jc.ErrorIsNil)
  5452  	rel1, err := s.caasSt.AddRelation(eps...)
  5453  	c.Assert(err, jc.ErrorIsNil)
  5454  
  5455  	// Add a separate reference to the first relation.
  5456  	unit, err := mysql.AddUnit(state.AddUnitParams{})
  5457  	c.Assert(err, jc.ErrorIsNil)
  5458  	ru, err := rel0.Unit(unit)
  5459  	c.Assert(err, jc.ErrorIsNil)
  5460  	err = ru.EnterScope(nil)
  5461  	c.Assert(err, jc.ErrorIsNil)
  5462  
  5463  	// Optionally update the application document to get correct relation counts.
  5464  	if refresh {
  5465  		err = s.app.Destroy()
  5466  		c.Assert(err, jc.ErrorIsNil)
  5467  	}
  5468  
  5469  	// Destroy, and check that the first relation becomes Dying...
  5470  	c.Assert(s.app.Destroy(), jc.ErrorIsNil)
  5471  	assertLife(c, rel0, state.Dying)
  5472  
  5473  	// ...while the second is removed directly.
  5474  	err = rel1.Refresh()
  5475  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  5476  
  5477  	// Drop the last reference to the first relation; check the relation and
  5478  	// the application are are both removed.
  5479  	c.Assert(ru.LeaveScope(), jc.ErrorIsNil)
  5480  	assertCleanupCount(c, s.caasSt, 1)
  5481  	// App not removed since cluster resources not cleaned up yet.
  5482  	assertLife(c, s.app, state.Dead)
  5483  
  5484  	err = rel0.Refresh()
  5485  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  5486  }
  5487  
  5488  func (s *CAASApplicationSuite) TestDestroyQueuesUnitCleanup(c *gc.C) {
  5489  	// Add 5 units; block quick-remove of gitlab/1 and gitlab/3
  5490  	units := make([]*state.Unit, 5)
  5491  	for i := range units {
  5492  		unit, err := s.app.AddUnit(state.AddUnitParams{})
  5493  		c.Assert(err, jc.ErrorIsNil)
  5494  		units[i] = unit
  5495  		if i%2 != 0 {
  5496  			unitState := state.NewUnitState()
  5497  			unitState.SetUniterState("idle")
  5498  			err := unit.SetState(unitState, state.UnitStateSizeLimits{})
  5499  			c.Assert(err, jc.ErrorIsNil)
  5500  		}
  5501  	}
  5502  
  5503  	assertDoesNotNeedCleanup(c, s.caasSt)
  5504  
  5505  	// Destroy gitlab, and check units are not touched.
  5506  	err := s.app.Destroy()
  5507  	c.Assert(err, jc.ErrorIsNil)
  5508  	assertLife(c, s.app, state.Dying)
  5509  	for _, unit := range units {
  5510  		assertLife(c, unit, state.Alive)
  5511  	}
  5512  
  5513  	dirty, err := s.caasSt.NeedsCleanup()
  5514  	c.Assert(err, jc.ErrorIsNil)
  5515  	c.Assert(dirty, jc.IsTrue)
  5516  	assertCleanupCount(c, s.caasSt, 2)
  5517  
  5518  	for i, unit := range units {
  5519  		if i%2 != 0 {
  5520  			assertLife(c, unit, state.Dying)
  5521  		} else {
  5522  			assertRemoved(c, unit)
  5523  		}
  5524  	}
  5525  
  5526  	// App dying until units are gone.
  5527  	assertLife(c, s.app, state.Dying)
  5528  }
  5529  
  5530  func (s *ApplicationSuite) TestSetOperatorStatusNonCAAS(c *gc.C) {
  5531  	_, err := state.ApplicationOperatorStatus(s.State, s.mysql.Name())
  5532  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  5533  }
  5534  
  5535  func (s *ApplicationSuite) TestSetOperatorStatus(c *gc.C) {
  5536  	st := s.Factory.MakeModel(c, &factory.ModelParams{
  5537  		Name: "caas-model",
  5538  		Type: state.ModelTypeCAAS,
  5539  	})
  5540  	defer st.Close()
  5541  	f := factory.NewFactory(st, s.StatePool)
  5542  	ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"})
  5543  	app := f.MakeApplication(c, &factory.ApplicationParams{Name: "gitlab", Charm: ch})
  5544  
  5545  	now := coretesting.ZeroTime()
  5546  	sInfo := status.StatusInfo{
  5547  		Status:  status.Error,
  5548  		Message: "broken",
  5549  		Since:   &now,
  5550  	}
  5551  	err := app.SetOperatorStatus(sInfo)
  5552  	c.Assert(err, jc.ErrorIsNil)
  5553  
  5554  	appStatus, err := state.ApplicationOperatorStatus(st, app.Name())
  5555  	c.Assert(err, jc.ErrorIsNil)
  5556  	c.Assert(appStatus.Status, gc.DeepEquals, status.Error)
  5557  	c.Assert(appStatus.Message, gc.DeepEquals, "broken")
  5558  }
  5559  
  5560  func (s *ApplicationSuite) TestCharmLegacyOnlySupportsOneSeries(c *gc.C) {
  5561  	ch := state.AddTestingCharmForSeries(c, s.State, "precise", "mysql")
  5562  	app := s.AddTestingApplication(c, "legacy-charm", ch)
  5563  	err := app.VerifySupportedBase(state.UbuntuBase("12.10"))
  5564  	c.Assert(err, jc.ErrorIsNil)
  5565  	err = app.VerifySupportedBase(state.UbuntuBase("16.04"))
  5566  	c.Assert(err, gc.ErrorMatches, "base \"ubuntu@16.04\" not supported by charm, the charm supported bases are: ubuntu@12.10")
  5567  }
  5568  
  5569  func (s *ApplicationSuite) TestCharmLegacyNoOSInvalid(c *gc.C) {
  5570  	ch := state.AddTestingCharmForSeries(c, s.State, "precise", "sample-fail-no-os")
  5571  	_, err := s.State.AddApplication(state.AddApplicationArgs{
  5572  		Name:  "sample-fail-no-os",
  5573  		Charm: ch,
  5574  		CharmOrigin: &state.CharmOrigin{
  5575  			Source: "charm-hub",
  5576  			Platform: &state.Platform{
  5577  				OS:      "ubuntu",
  5578  				Channel: "22.04/stable",
  5579  			},
  5580  		},
  5581  	})
  5582  	c.Assert(err, gc.ErrorMatches, `.*charm does not define any bases`)
  5583  }
  5584  
  5585  func (s *ApplicationSuite) TestDeployedMachines(c *gc.C) {
  5586  	charm := s.Factory.MakeCharm(c, &factory.CharmParams{Name: "riak"})
  5587  	app := s.Factory.MakeApplication(c, &factory.ApplicationParams{Charm: charm})
  5588  	s.Factory.MakeUnit(c, &factory.UnitParams{Application: app})
  5589  	machines, err := app.DeployedMachines()
  5590  
  5591  	c.Assert(err, jc.ErrorIsNil)
  5592  	var ids []string
  5593  	for _, m := range machines {
  5594  		ids = append(ids, m.Id())
  5595  	}
  5596  	c.Assert(ids, jc.SameContents, []string{"0"})
  5597  }
  5598  
  5599  func (s *ApplicationSuite) TestDeployedMachinesNotAssignedUnit(c *gc.C) {
  5600  	charm := s.Factory.MakeCharm(c, &factory.CharmParams{Name: "riak"})
  5601  	app := s.Factory.MakeApplication(c, &factory.ApplicationParams{Charm: charm})
  5602  
  5603  	unit, err := app.AddUnit(state.AddUnitParams{})
  5604  	c.Assert(err, jc.ErrorIsNil)
  5605  	_, err = unit.AssignedMachineId()
  5606  	c.Assert(err, jc.Satisfies, errors.IsNotAssigned)
  5607  
  5608  	machines, err := app.DeployedMachines()
  5609  	c.Assert(err, jc.ErrorIsNil)
  5610  	c.Assert(machines, gc.HasLen, 0)
  5611  }
  5612  
  5613  func (s *ApplicationSuite) TestCAASSidecarCharm(c *gc.C) {
  5614  	st, app := s.addCAASSidecarApplication(c)
  5615  	defer st.Close()
  5616  	unit, err := app.AddUnit(state.AddUnitParams{})
  5617  	c.Assert(err, jc.ErrorIsNil)
  5618  	sidecar, err := unit.IsSidecar()
  5619  	c.Assert(err, jc.ErrorIsNil)
  5620  	c.Assert(sidecar, jc.IsTrue)
  5621  }
  5622  
  5623  func (s *ApplicationSuite) addCAASSidecarApplication(c *gc.C) (*state.State, *state.Application) {
  5624  	st := s.Factory.MakeModel(c, &factory.ModelParams{
  5625  		Name: "caas-model",
  5626  		Type: state.ModelTypeCAAS,
  5627  	})
  5628  	f := factory.NewFactory(st, s.StatePool)
  5629  
  5630  	charmDef := `
  5631  name: cockroachdb
  5632  description: foo
  5633  summary: foo
  5634  containers:
  5635    redis:
  5636      resource: redis-container-resource
  5637  resources:
  5638    redis-container-resource:
  5639      name: redis-container
  5640      type: oci-image
  5641  provides:
  5642    data-port:
  5643      interface: data
  5644      scope: container
  5645  `
  5646  	ch := state.AddCustomCharmWithManifest(c, st, "cockroach", "metadata.yaml", charmDef, "focal", 1)
  5647  	return st, f.MakeApplication(c, &factory.ApplicationParams{Name: "cockroachdb", Charm: ch})
  5648  }
  5649  
  5650  func (s *ApplicationSuite) TestCAASNonSidecarCharm(c *gc.C) {
  5651  	st := s.Factory.MakeModel(c, &factory.ModelParams{
  5652  		Name: "caas-model",
  5653  		Type: state.ModelTypeCAAS,
  5654  	})
  5655  	defer st.Close()
  5656  	f := factory.NewFactory(st, s.StatePool)
  5657  
  5658  	charmDef := `
  5659  name: mysql
  5660  description: foo
  5661  summary: foo
  5662  series:
  5663    - kubernetes
  5664  deployment:
  5665    mode: workload
  5666  `
  5667  	ch := state.AddCustomCharmForSeries(c, st, "mysql", "metadata.yaml", charmDef, "kubernetes", 1)
  5668  	app := f.MakeApplication(c, &factory.ApplicationParams{Name: "mysql", Charm: ch})
  5669  
  5670  	unit, err := app.AddUnit(state.AddUnitParams{})
  5671  	c.Assert(err, jc.ErrorIsNil)
  5672  	sidecar, err := unit.IsSidecar()
  5673  	c.Assert(err, jc.ErrorIsNil)
  5674  	c.Assert(sidecar, jc.IsFalse)
  5675  }
  5676  
  5677  func (s *ApplicationSuite) TestWatchApplicationsWithPendingCharms(c *gc.C) {
  5678  	w := s.State.WatchApplicationsWithPendingCharms()
  5679  	defer func() { _ = w.Stop() }()
  5680  
  5681  	wc := statetesting.NewStringsWatcherC(c, w)
  5682  	wc.AssertChange() // consume initial change set.
  5683  
  5684  	// Add a pending charm with an origin and associate it with the
  5685  	// application. This should trigger a change.
  5686  	dummy2 := s.dummyCharm(c, "ch:dummy-1")
  5687  	dummy2.SHA256 = ""      // indicates that we don't have the data in the blobstore yet.
  5688  	dummy2.StoragePath = "" // indicates that we don't have the data in the blobstore yet.
  5689  	ch2, err := s.State.AddCharmMetadata(dummy2)
  5690  	c.Assert(err, jc.ErrorIsNil)
  5691  	twoOrigin := defaultCharmOrigin(ch2.URL())
  5692  	twoOrigin.Platform.OS = "ubuntu"
  5693  	twoOrigin.Platform.Channel = "22.04/stable"
  5694  	err = s.mysql.SetCharm(state.SetCharmConfig{
  5695  		Charm:       ch2,
  5696  		CharmOrigin: twoOrigin,
  5697  	})
  5698  	c.Assert(err, jc.ErrorIsNil)
  5699  	wc.AssertChange(s.mysql.Name())
  5700  
  5701  	// "Upload" a charm and check that we don't get a notification for it.
  5702  	dummy3 := s.dummyCharm(c, "ch:dummy-2")
  5703  	ch3, err := s.State.AddCharm(dummy3)
  5704  	c.Assert(err, jc.ErrorIsNil)
  5705  	threeOrigin := defaultCharmOrigin(ch3.URL())
  5706  	threeOrigin.Platform.OS = "ubuntu"
  5707  	threeOrigin.Platform.Channel = "22.04/stable"
  5708  	threeOrigin.ID = "charm-hub-id"
  5709  	threeOrigin.Hash = "charm-hub-hash"
  5710  	err = s.mysql.SetCharm(state.SetCharmConfig{
  5711  		Charm:       ch3,
  5712  		CharmOrigin: threeOrigin,
  5713  	})
  5714  	c.Assert(err, jc.ErrorIsNil)
  5715  	wc.AssertNoChange()
  5716  	origin := &state.CharmOrigin{
  5717  		Source: "charm-hub",
  5718  		Platform: &state.Platform{
  5719  			OS:      "ubuntu",
  5720  			Channel: "22.04/stable",
  5721  		},
  5722  	}
  5723  	// Simulate a bundle deploying multiple applications from a single
  5724  	// charm. The watcher needs to notify on the secondary applications.
  5725  	appSameCharm, err := s.State.AddApplication(state.AddApplicationArgs{
  5726  		Name:        "mysql-testing",
  5727  		Charm:       ch3,
  5728  		CharmOrigin: origin,
  5729  	})
  5730  	c.Assert(err, jc.ErrorIsNil)
  5731  	wc.AssertChange(appSameCharm.Name())
  5732  	origin.ID = "charm-hub-id"
  5733  	origin.Hash = "charm-hub-hash"
  5734  	_ = appSameCharm.SetCharm(state.SetCharmConfig{
  5735  		Charm:       ch3,
  5736  		CharmOrigin: origin,
  5737  	})
  5738  	c.Assert(err, jc.ErrorIsNil)
  5739  	wc.AssertNoChange()
  5740  }
  5741  
  5742  func (s *ApplicationSuite) dummyCharm(c *gc.C, curlOverride string) state.CharmInfo {
  5743  	info := state.CharmInfo{
  5744  		Charm:       testcharms.Repo.CharmDir("dummy"),
  5745  		StoragePath: "dummy-1",
  5746  		SHA256:      "dummy-1-sha256",
  5747  		Version:     "dummy-146-g725cfd3-dirty",
  5748  	}
  5749  	if curlOverride != "" {
  5750  		info.ID = curlOverride
  5751  	} else {
  5752  		info.ID = fmt.Sprintf("local:quantal/%s-%d", info.Charm.Meta().Name, info.Charm.Revision())
  5753  	}
  5754  	info.Charm.Meta().Series = []string{"quantal", "jammy"}
  5755  	return info
  5756  }
  5757  
  5758  func (s *ApplicationSuite) TestWatch(c *gc.C) {
  5759  	w := s.mysql.WatchConfigSettingsHash()
  5760  	defer testing.AssertStop(c, w)
  5761  
  5762  	wc := testing.NewStringsWatcherC(c, w)
  5763  	wc.AssertChange("1e11259677ef769e0ec4076b873c76dcc3a54be7bc651b081d0f0e2b87077717")
  5764  
  5765  	schema := environschema.Fields{
  5766  		"username":    environschema.Attr{Type: environschema.Tstring},
  5767  		"alive":       environschema.Attr{Type: environschema.Tbool},
  5768  		"skill-level": environschema.Attr{Type: environschema.Tint},
  5769  		"options":     environschema.Attr{Type: environschema.Tattrs},
  5770  	}
  5771  
  5772  	err := s.mysql.UpdateApplicationConfig(config.ConfigAttributes{
  5773  		"username":    "abbas",
  5774  		"alive":       true,
  5775  		"skill-level": 23,
  5776  		"options": map[string]string{
  5777  			"fortuna": "crescis",
  5778  			"luna":    "velut",
  5779  			"status":  "malus",
  5780  		},
  5781  	}, nil, schema, nil)
  5782  	c.Assert(err, jc.ErrorIsNil)
  5783  
  5784  	wc.AssertChange("e1471e8a7299da0ac2150445ffc6d08d9d801194037d88416c54b01899b8a9b2")
  5785  }
  5786  
  5787  func (s *ApplicationSuite) TestProvisioningState(c *gc.C) {
  5788  	ps := s.mysql.ProvisioningState()
  5789  	c.Assert(ps, gc.IsNil)
  5790  
  5791  	err := s.mysql.SetProvisioningState(state.ApplicationProvisioningState{
  5792  		Scaling:     true,
  5793  		ScaleTarget: 10,
  5794  	})
  5795  	c.Assert(errors.Is(err, stateerrors.ProvisioningStateInconsistent), jc.IsTrue)
  5796  
  5797  	err = s.mysql.SetScale(10, 0, true)
  5798  	c.Assert(err, jc.ErrorIsNil)
  5799  
  5800  	err = s.mysql.SetProvisioningState(state.ApplicationProvisioningState{
  5801  		Scaling:     true,
  5802  		ScaleTarget: 10,
  5803  	})
  5804  	c.Assert(err, jc.ErrorIsNil)
  5805  
  5806  	ps = s.mysql.ProvisioningState()
  5807  	c.Assert(ps, jc.DeepEquals, &state.ApplicationProvisioningState{
  5808  		Scaling:     true,
  5809  		ScaleTarget: 10,
  5810  	})
  5811  }
  5812  
  5813  func (s *CAASApplicationSuite) TestUpsertCAASUnit(c *gc.C) {
  5814  	registry := &storage.StaticProviderRegistry{
  5815  		Providers: map[storage.ProviderType]storage.Provider{
  5816  			"kubernetes": &dummy.StorageProvider{
  5817  				StorageScope: storage.ScopeEnviron,
  5818  				IsDynamic:    true,
  5819  				IsReleasable: true,
  5820  				SupportsFunc: func(k storage.StorageKind) bool {
  5821  					return k == storage.StorageKindBlock
  5822  				},
  5823  			},
  5824  		},
  5825  	}
  5826  
  5827  	st := s.Factory.MakeCAASModel(c, &factory.ModelParams{
  5828  		CloudName: "caascloud",
  5829  	})
  5830  	s.AddCleanup(func(_ *gc.C) { _ = st.Close() })
  5831  
  5832  	pm := poolmanager.New(state.NewStateSettings(st), registry)
  5833  	_, err := pm.Create("kubernetes", "kubernetes", map[string]interface{}{})
  5834  	c.Assert(err, jc.ErrorIsNil)
  5835  	s.policy = testing.MockPolicy{
  5836  		GetStorageProviderRegistry: func() (storage.ProviderRegistry, error) {
  5837  			return registry, nil
  5838  		},
  5839  	}
  5840  
  5841  	sb, err := state.NewStorageBackend(st)
  5842  	c.Assert(err, jc.ErrorIsNil)
  5843  
  5844  	fsInfo := state.FilesystemInfo{
  5845  		Size: 100,
  5846  		Pool: "kubernetes",
  5847  	}
  5848  	volumeInfo := state.VolumeInfo{
  5849  		VolumeId:   "pv-database-0",
  5850  		Size:       100,
  5851  		Pool:       "kubernetes",
  5852  		Persistent: true,
  5853  	}
  5854  	storageTag, err := sb.AddExistingFilesystem(fsInfo, &volumeInfo, "database")
  5855  	c.Assert(err, jc.ErrorIsNil)
  5856  	c.Assert(storageTag.Id(), gc.Equals, "database/0")
  5857  
  5858  	ch := state.AddTestingCharmForSeries(c, st, "quantal", "cockroachdb")
  5859  	cockroachdb := state.AddTestingApplicationWithStorage(c, st, "cockroachdb", ch, map[string]state.StorageConstraints{
  5860  		"database": {
  5861  			Pool:  "kubernetes",
  5862  			Size:  100,
  5863  			Count: 0,
  5864  		},
  5865  	})
  5866  
  5867  	unitName := "cockroachdb/0"
  5868  	providerId := "cockroachdb-0"
  5869  	address := "1.2.3.4"
  5870  	ports := []string{"80", "443"}
  5871  
  5872  	// output of utils.AgentPasswordHash("juju")
  5873  	passwordHash := "v+jK3ht5NEdKeoQBfyxmlYe0"
  5874  
  5875  	p := state.UpsertCAASUnitParams{
  5876  		AddUnitParams: state.AddUnitParams{
  5877  			UnitName:     &unitName,
  5878  			ProviderId:   &providerId,
  5879  			Address:      &address,
  5880  			Ports:        &ports,
  5881  			PasswordHash: &passwordHash,
  5882  		},
  5883  		OrderedScale:              true,
  5884  		OrderedId:                 0,
  5885  		ObservedAttachedVolumeIDs: []string{"pv-database-0"},
  5886  	}
  5887  	unit, err := cockroachdb.UpsertCAASUnit(p)
  5888  	c.Assert(err, gc.ErrorMatches, `unrequired unit cockroachdb/0 is not assigned`)
  5889  	c.Assert(unit, gc.IsNil)
  5890  
  5891  	err = cockroachdb.SetScale(1, 0, true)
  5892  	c.Assert(err, jc.ErrorIsNil)
  5893  
  5894  	unit, err = cockroachdb.UpsertCAASUnit(p)
  5895  	c.Assert(err, jc.ErrorIsNil)
  5896  	c.Assert(unit, gc.NotNil)
  5897  	c.Assert(unit.UnitTag().Id(), gc.Equals, "cockroachdb/0")
  5898  	c.Assert(unit.Life(), gc.Equals, state.Alive)
  5899  	containerInfo, err := unit.ContainerInfo()
  5900  	c.Assert(err, jc.ErrorIsNil)
  5901  	c.Assert(containerInfo.ProviderId(), gc.Equals, "cockroachdb-0")
  5902  	c.Assert(containerInfo.Ports(), jc.SameContents, []string{"80", "443"})
  5903  	c.Assert(containerInfo.Address().Value, gc.Equals, "1.2.3.4")
  5904  
  5905  	err = unit.Destroy()
  5906  	c.Assert(err, jc.ErrorIsNil)
  5907  
  5908  	err = sb.DetachStorage(storageTag, unit.UnitTag(), false, 0)
  5909  	c.Assert(err, jc.ErrorIsNil)
  5910  
  5911  	err = sb.DetachFilesystem(unit.UnitTag(), names.NewFilesystemTag("0"))
  5912  	c.Assert(err, jc.ErrorIsNil)
  5913  	err = sb.RemoveFilesystemAttachment(unit.UnitTag(), names.NewFilesystemTag("0"), false)
  5914  	c.Assert(err, jc.ErrorIsNil)
  5915  
  5916  	err = sb.DetachVolume(unit.Tag(), names.NewVolumeTag("0"), false)
  5917  	c.Assert(err, jc.ErrorIsNil)
  5918  	err = sb.RemoveVolumeAttachment(unit.Tag(), names.NewVolumeTag("0"), false)
  5919  	c.Assert(err, jc.ErrorIsNil)
  5920  
  5921  	err = unit.EnsureDead()
  5922  	c.Assert(err, jc.ErrorIsNil)
  5923  
  5924  	unit2, err := cockroachdb.UpsertCAASUnit(p)
  5925  	c.Assert(err, gc.ErrorMatches, `dead unit "cockroachdb/0" already exists`)
  5926  	c.Assert(unit2, gc.IsNil)
  5927  
  5928  	err = unit.Remove()
  5929  	c.Assert(err, jc.ErrorIsNil)
  5930  
  5931  	err = st.Cleanup()
  5932  	c.Assert(err, jc.ErrorIsNil)
  5933  
  5934  	unit, err = cockroachdb.UpsertCAASUnit(p)
  5935  	c.Assert(err, jc.ErrorIsNil)
  5936  	c.Assert(unit, gc.NotNil)
  5937  	c.Assert(unit.UnitTag().Id(), gc.Equals, "cockroachdb/0")
  5938  	c.Assert(unit.Life(), gc.Equals, state.Alive)
  5939  	containerInfo, err = unit.ContainerInfo()
  5940  	c.Assert(err, jc.ErrorIsNil)
  5941  	c.Assert(containerInfo.ProviderId(), gc.Equals, "cockroachdb-0")
  5942  	c.Assert(containerInfo.Ports(), jc.SameContents, []string{"80", "443"})
  5943  	c.Assert(containerInfo.Address().Value, gc.Equals, "1.2.3.4")
  5944  }
  5945  
  5946  func intPtr(val int) *int {
  5947  	return &val
  5948  }
  5949  
  5950  func defaultCharmOrigin(curlStr string) *state.CharmOrigin {
  5951  	// Use ParseURL here in test until either the charm and/or application
  5952  	// can easily provide the same data.
  5953  	curl, _ := charm.ParseURL(curlStr)
  5954  	var source string
  5955  	var channel *state.Channel
  5956  	if charm.CharmHub.Matches(curl.Schema) {
  5957  		source = corecharm.CharmHub.String()
  5958  		channel = &state.Channel{
  5959  			Risk: "stable",
  5960  		}
  5961  	} else if charm.Local.Matches(curl.Schema) {
  5962  		source = corecharm.Local.String()
  5963  	}
  5964  
  5965  	base, _ := corebase.GetBaseFromSeries(curl.Series)
  5966  
  5967  	platform := &state.Platform{
  5968  		Architecture: corearch.DefaultArchitecture,
  5969  		OS:           base.OS,
  5970  		Channel:      base.Channel.String(),
  5971  	}
  5972  
  5973  	return &state.CharmOrigin{
  5974  		Source:   source,
  5975  		Type:     "charm",
  5976  		Revision: intPtr(curl.Revision),
  5977  		Channel:  channel,
  5978  		Platform: platform,
  5979  	}
  5980  }