github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/client/application/application_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package application_test
     5  
     6  import (
     7  	"fmt"
     8  	"regexp"
     9  	"time"
    10  
    11  	"github.com/juju/charm/v12"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/names/v5"
    14  	jc "github.com/juju/testing/checkers"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	unitassignerapi "github.com/juju/juju/api/agent/unitassigner"
    18  	"github.com/juju/juju/apiserver/common"
    19  	commontesting "github.com/juju/juju/apiserver/common/testing"
    20  	"github.com/juju/juju/apiserver/facades/client/application"
    21  	apiservertesting "github.com/juju/juju/apiserver/testing"
    22  	"github.com/juju/juju/core/arch"
    23  	"github.com/juju/juju/core/constraints"
    24  	"github.com/juju/juju/core/instance"
    25  	"github.com/juju/juju/core/model"
    26  	"github.com/juju/juju/core/network"
    27  	"github.com/juju/juju/core/network/firewall"
    28  	"github.com/juju/juju/core/status"
    29  	jujutesting "github.com/juju/juju/juju/testing"
    30  	"github.com/juju/juju/rpc/params"
    31  	"github.com/juju/juju/state"
    32  	"github.com/juju/juju/state/stateenvirons"
    33  	statetesting "github.com/juju/juju/state/testing"
    34  	"github.com/juju/juju/storage/poolmanager"
    35  	"github.com/juju/juju/testcharms"
    36  	"github.com/juju/juju/testing"
    37  	"github.com/juju/juju/testing/factory"
    38  )
    39  
    40  type applicationSuite struct {
    41  	jujutesting.JujuConnSuite
    42  	commontesting.BlockHelper
    43  
    44  	applicationAPI *application.APIBase
    45  	application    *state.Application
    46  	authorizer     *apiservertesting.FakeAuthorizer
    47  	lastKnownRev   map[string]int
    48  }
    49  
    50  var _ = gc.Suite(&applicationSuite{})
    51  
    52  func (s *applicationSuite) SetUpTest(c *gc.C) {
    53  	s.JujuConnSuite.SetUpTest(c)
    54  	s.BlockHelper = commontesting.NewBlockHelper(s.APIState)
    55  	s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() })
    56  
    57  	s.application = s.Factory.MakeApplication(c, nil)
    58  
    59  	s.authorizer = &apiservertesting.FakeAuthorizer{
    60  		Tag: s.AdminUserTag(c),
    61  	}
    62  	s.applicationAPI = s.makeAPI(c)
    63  	s.lastKnownRev = make(map[string]int)
    64  }
    65  
    66  func (s *applicationSuite) makeAPI(c *gc.C) *application.APIBase {
    67  	resources := common.NewResources()
    68  	c.Assert(resources.RegisterNamed("dataDir", common.StringResource(c.MkDir())), jc.ErrorIsNil)
    69  	storageAccess, err := application.GetStorageState(s.State)
    70  	c.Assert(err, jc.ErrorIsNil)
    71  	model, err := s.State.Model()
    72  	c.Assert(err, jc.ErrorIsNil)
    73  	blockChecker := common.NewBlockChecker(s.State)
    74  	registry := stateenvirons.NewStorageProviderRegistry(s.Environ)
    75  	pm := poolmanager.New(state.NewStateSettings(s.State), registry)
    76  	api, err := application.NewAPIBase(
    77  		application.GetState(s.State),
    78  		storageAccess,
    79  		s.authorizer,
    80  		nil,
    81  		nil,
    82  		blockChecker,
    83  		application.GetModel(model),
    84  		nil, // leadership not used in these tests.
    85  		application.CharmToStateCharm,
    86  		application.DeployApplication,
    87  		pm,
    88  		registry,
    89  		common.NewResources(),
    90  		nil, // CAAS Broker not used in this suite.
    91  	)
    92  	c.Assert(err, jc.ErrorIsNil)
    93  	return api
    94  }
    95  
    96  func (s *applicationSuite) setupApplicationDeploy(c *gc.C, args string) (string, charm.Charm, constraints.Value) {
    97  	curl, ch := s.addCharmToState(c, "ch:jammy/dummy-42", "dummy")
    98  	cons := constraints.MustParse(args)
    99  	return curl, ch, cons
   100  }
   101  
   102  func (s *applicationSuite) assertApplicationDeployPrincipal(c *gc.C, curl string, ch charm.Charm, mem4g constraints.Value) {
   103  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   104  		Applications: []params.ApplicationDeploy{{
   105  			CharmURL:        curl,
   106  			CharmOrigin:     createCharmOriginFromURL(curl),
   107  			ApplicationName: "application",
   108  			NumUnits:        3,
   109  			Constraints:     mem4g,
   110  		}}})
   111  	c.Assert(err, jc.ErrorIsNil)
   112  	c.Assert(results.Results, gc.HasLen, 1)
   113  	c.Assert(results.Results[0].Error, gc.IsNil)
   114  	apiservertesting.AssertPrincipalApplicationDeployed(c, s.State, "application", curl, false, ch, mem4g)
   115  }
   116  
   117  func (s *applicationSuite) assertApplicationDeployPrincipalBlocked(c *gc.C, msg string, curl string, mem4g constraints.Value) {
   118  	_, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   119  		Applications: []params.ApplicationDeploy{{
   120  			CharmURL:        curl,
   121  			CharmOrigin:     createCharmOriginFromURL(curl),
   122  			ApplicationName: "application",
   123  			NumUnits:        3,
   124  			Constraints:     mem4g,
   125  		}}})
   126  	s.AssertBlocked(c, err, msg)
   127  }
   128  
   129  func (s *applicationSuite) TestBlockDestroyApplicationDeployPrincipal(c *gc.C) {
   130  	curl, bundle, cons := s.setupApplicationDeploy(c, "arch=amd64 mem=4G")
   131  	s.BlockDestroyModel(c, "TestBlockDestroyApplicationDeployPrincipal")
   132  	s.assertApplicationDeployPrincipal(c, curl, bundle, cons)
   133  }
   134  
   135  func (s *applicationSuite) TestBlockRemoveApplicationDeployPrincipal(c *gc.C) {
   136  	curl, bundle, cons := s.setupApplicationDeploy(c, "arch=amd64 mem=4G")
   137  	s.BlockRemoveObject(c, "TestBlockRemoveApplicationDeployPrincipal")
   138  	s.assertApplicationDeployPrincipal(c, curl, bundle, cons)
   139  }
   140  
   141  func (s *applicationSuite) TestBlockChangesApplicationDeployPrincipal(c *gc.C) {
   142  	curl, _, cons := s.setupApplicationDeploy(c, "mem=4G")
   143  	s.BlockAllChanges(c, "TestBlockChangesApplicationDeployPrincipal")
   144  	s.assertApplicationDeployPrincipalBlocked(c, "TestBlockChangesApplicationDeployPrincipal", curl, cons)
   145  }
   146  
   147  func (s *applicationSuite) TestApplicationDeploySubordinate(c *gc.C) {
   148  	curl, ch := s.addCharmToState(c, "ch:utopic/logging-47", "logging")
   149  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   150  		Applications: []params.ApplicationDeploy{{
   151  			CharmURL:        curl,
   152  			CharmOrigin:     createCharmOriginFromURL(curl),
   153  			ApplicationName: "application-name",
   154  		}}})
   155  	c.Assert(err, jc.ErrorIsNil)
   156  	c.Assert(results.Results, gc.HasLen, 1)
   157  	c.Assert(results.Results[0].Error, gc.IsNil)
   158  
   159  	app, err := s.State.Application("application-name")
   160  	c.Assert(err, jc.ErrorIsNil)
   161  	charm, force, err := app.Charm()
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	c.Assert(force, jc.IsFalse)
   164  	c.Assert(charm.URL(), gc.DeepEquals, curl)
   165  	c.Assert(charm.Meta(), gc.DeepEquals, ch.Meta())
   166  	c.Assert(charm.Config(), gc.DeepEquals, ch.Config())
   167  
   168  	units, err := app.AllUnits()
   169  	c.Assert(err, jc.ErrorIsNil)
   170  	c.Assert(units, gc.HasLen, 0)
   171  }
   172  
   173  func (s *applicationSuite) combinedSettings(ch *state.Charm, inSettings charm.Settings) charm.Settings {
   174  	result := ch.Config().DefaultSettings()
   175  	for name, value := range inSettings {
   176  		result[name] = value
   177  	}
   178  	return result
   179  }
   180  
   181  func (s *applicationSuite) TestApplicationDeployConfig(c *gc.C) {
   182  	curl, _ := s.addCharmToState(c, "ch:jammy/dummy-0", "dummy")
   183  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   184  		Applications: []params.ApplicationDeploy{{
   185  			CharmURL:        curl,
   186  			CharmOrigin:     createCharmOriginFromURL(curl),
   187  			ApplicationName: "application-name",
   188  			NumUnits:        1,
   189  			ConfigYAML:      "application-name:\n  username: fred",
   190  		}}})
   191  	c.Assert(err, jc.ErrorIsNil)
   192  	c.Assert(results.Results, gc.HasLen, 1)
   193  	c.Assert(results.Results[0].Error, gc.IsNil)
   194  
   195  	app, err := s.State.Application("application-name")
   196  	c.Assert(err, jc.ErrorIsNil)
   197  	settings, err := app.CharmConfig(model.GenerationMaster)
   198  	c.Assert(err, jc.ErrorIsNil)
   199  	ch, _, err := app.Charm()
   200  	c.Assert(err, jc.ErrorIsNil)
   201  	c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{"username": "fred"}))
   202  }
   203  
   204  func (s *applicationSuite) TestApplicationDeployConfigError(c *gc.C) {
   205  	// TODO(fwereade): test Config/ConfigYAML handling directly on srvClient.
   206  	// Can't be done cleanly until it's extracted similarly to Machiner.
   207  	curl, _ := s.addCharmToState(c, "ch:jammy/dummy-0", "dummy")
   208  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   209  		Applications: []params.ApplicationDeploy{{
   210  			CharmURL:        curl,
   211  			CharmOrigin:     createCharmOriginFromURL(curl),
   212  			ApplicationName: "application-name",
   213  			NumUnits:        1,
   214  			ConfigYAML:      "application-name:\n  skill-level: fred",
   215  		}}})
   216  	c.Assert(err, jc.ErrorIsNil)
   217  	c.Assert(results.Results, gc.HasLen, 1)
   218  	c.Assert(results.Results[0].Error, gc.ErrorMatches, `option "skill-level" expected int, got "fred"`)
   219  	_, err = s.State.Application("application-name")
   220  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   221  }
   222  
   223  func (s *applicationSuite) TestApplicationDeployToMachine(c *gc.C) {
   224  	curl, ch := s.addCharmToState(c, "ch:jammy/dummy-0", "dummy")
   225  
   226  	machine, err := s.State.AddMachine(state.UbuntuBase("22.04"), state.JobHostUnits)
   227  	c.Assert(err, jc.ErrorIsNil)
   228  
   229  	arch := arch.DefaultArchitecture
   230  	hwChar := &instance.HardwareCharacteristics{
   231  		Arch: &arch,
   232  	}
   233  	instId := instance.Id("i-host-machine")
   234  	err = machine.SetProvisioned(instId, "", "fake-nonce", hwChar)
   235  	c.Assert(err, jc.ErrorIsNil)
   236  
   237  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   238  		Applications: []params.ApplicationDeploy{{
   239  			CharmURL:        curl,
   240  			CharmOrigin:     createCharmOriginFromURL(curl),
   241  			ApplicationName: "application-name",
   242  			NumUnits:        1,
   243  			ConfigYAML:      "application-name:\n  username: fred",
   244  		}}})
   245  	c.Assert(err, jc.ErrorIsNil)
   246  	c.Assert(results.Results, gc.HasLen, 1)
   247  	c.Assert(results.Results[0].Error, gc.IsNil)
   248  
   249  	app, err := s.State.Application("application-name")
   250  	c.Assert(err, jc.ErrorIsNil)
   251  	charm, force, err := app.Charm()
   252  	c.Assert(err, jc.ErrorIsNil)
   253  	c.Assert(force, jc.IsFalse)
   254  	c.Assert(charm.URL(), gc.DeepEquals, curl)
   255  	c.Assert(charm.Meta(), gc.DeepEquals, ch.Meta())
   256  	c.Assert(charm.Config(), gc.DeepEquals, ch.Config())
   257  
   258  	errs, err := unitassignerapi.New(s.APIState).AssignUnits([]names.UnitTag{names.NewUnitTag("application-name/0")})
   259  	c.Assert(errs, gc.DeepEquals, []error{nil})
   260  	c.Assert(err, jc.ErrorIsNil)
   261  
   262  	units, err := app.AllUnits()
   263  	c.Assert(err, jc.ErrorIsNil)
   264  	c.Assert(units, gc.HasLen, 1)
   265  
   266  	mid, err := units[0].AssignedMachineId()
   267  	c.Assert(err, jc.ErrorIsNil)
   268  	c.Assert(mid, gc.Equals, machine.Id())
   269  }
   270  
   271  func (s *applicationSuite) TestApplicationDeployToMachineWithLXDProfile(c *gc.C) {
   272  	curl, ch := s.addCharmToState(c, "ch:jammy/lxd-profile-0", "lxd-profile")
   273  
   274  	machine, err := s.State.AddMachine(state.UbuntuBase("22.04"), state.JobHostUnits)
   275  	c.Assert(err, jc.ErrorIsNil)
   276  
   277  	arch := arch.DefaultArchitecture
   278  	hwChar := &instance.HardwareCharacteristics{
   279  		Arch: &arch,
   280  	}
   281  	instId := instance.Id("i-host-machine")
   282  	err = machine.SetProvisioned(instId, "", "fake-nonce", hwChar)
   283  	c.Assert(err, jc.ErrorIsNil)
   284  
   285  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   286  		Applications: []params.ApplicationDeploy{{
   287  			CharmURL:        curl,
   288  			CharmOrigin:     createCharmOriginFromURL(curl),
   289  			ApplicationName: "application-name",
   290  			NumUnits:        1,
   291  		}}})
   292  	c.Assert(err, jc.ErrorIsNil)
   293  	c.Assert(results.Results, gc.HasLen, 1)
   294  	c.Assert(results.Results[0].Error, gc.IsNil)
   295  
   296  	application, err := s.State.Application("application-name")
   297  	c.Assert(err, jc.ErrorIsNil)
   298  	expected, force, err := application.Charm()
   299  	c.Assert(err, jc.ErrorIsNil)
   300  	c.Assert(force, jc.IsFalse)
   301  	c.Assert(expected.URL(), gc.DeepEquals, curl)
   302  	c.Assert(expected.Meta(), gc.DeepEquals, ch.Meta())
   303  	c.Assert(expected.Config(), gc.DeepEquals, ch.Config())
   304  
   305  	expectedProfile := ch.(charm.LXDProfiler).LXDProfile()
   306  	c.Assert(expected.LXDProfile(), gc.DeepEquals, &state.LXDProfile{
   307  		Description: expectedProfile.Description,
   308  		Config:      expectedProfile.Config,
   309  		Devices:     expectedProfile.Devices,
   310  	})
   311  
   312  	errs, err := unitassignerapi.New(s.APIState).AssignUnits([]names.UnitTag{names.NewUnitTag("application-name/0")})
   313  	c.Assert(errs, gc.DeepEquals, []error{nil})
   314  	c.Assert(err, jc.ErrorIsNil)
   315  
   316  	units, err := application.AllUnits()
   317  	c.Assert(err, jc.ErrorIsNil)
   318  	c.Assert(units, gc.HasLen, 1)
   319  
   320  	mid, err := units[0].AssignedMachineId()
   321  	c.Assert(err, jc.ErrorIsNil)
   322  	c.Assert(mid, gc.Equals, machine.Id())
   323  }
   324  
   325  func (s *applicationSuite) TestApplicationDeployToMachineWithInvalidLXDProfileAndForceStillSucceeds(c *gc.C) {
   326  	curl, ch := s.addCharmToState(c, "ch:jammy/lxd-profile-fail-0", "lxd-profile-fail")
   327  
   328  	machine, err := s.State.AddMachine(state.UbuntuBase("22.04"), state.JobHostUnits)
   329  	c.Assert(err, jc.ErrorIsNil)
   330  
   331  	arch := arch.DefaultArchitecture
   332  	hwChar := &instance.HardwareCharacteristics{
   333  		Arch: &arch,
   334  	}
   335  	instId := instance.Id("i-host-machine")
   336  	err = machine.SetProvisioned(instId, "", "fake-nonce", hwChar)
   337  	c.Assert(err, jc.ErrorIsNil)
   338  
   339  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   340  		Applications: []params.ApplicationDeploy{{
   341  			CharmURL:        curl,
   342  			CharmOrigin:     createCharmOriginFromURL(curl),
   343  			ApplicationName: "application-name",
   344  			NumUnits:        1,
   345  		}}})
   346  	c.Assert(err, jc.ErrorIsNil)
   347  	c.Assert(results.Results, gc.HasLen, 1)
   348  	c.Assert(results.Results[0].Error, gc.IsNil)
   349  
   350  	app, err := s.State.Application("application-name")
   351  	c.Assert(err, jc.ErrorIsNil)
   352  	expected, force, err := app.Charm()
   353  	c.Assert(err, jc.ErrorIsNil)
   354  	c.Assert(force, jc.IsFalse)
   355  	c.Assert(expected.URL(), gc.DeepEquals, curl)
   356  	c.Assert(expected.Meta(), gc.DeepEquals, ch.Meta())
   357  	c.Assert(expected.Config(), gc.DeepEquals, ch.Config())
   358  
   359  	expectedProfile := ch.(charm.LXDProfiler).LXDProfile()
   360  	c.Assert(expected.LXDProfile(), gc.DeepEquals, &state.LXDProfile{
   361  		Description: expectedProfile.Description,
   362  		Config:      expectedProfile.Config,
   363  		Devices:     expectedProfile.Devices,
   364  	})
   365  
   366  	errs, err := unitassignerapi.New(s.APIState).AssignUnits([]names.UnitTag{names.NewUnitTag("application-name/0")})
   367  	c.Assert(errs, gc.DeepEquals, []error{nil})
   368  	c.Assert(err, jc.ErrorIsNil)
   369  
   370  	units, err := app.AllUnits()
   371  	c.Assert(err, jc.ErrorIsNil)
   372  	c.Assert(units, gc.HasLen, 1)
   373  
   374  	mid, err := units[0].AssignedMachineId()
   375  	c.Assert(err, jc.ErrorIsNil)
   376  	c.Assert(mid, gc.Equals, machine.Id())
   377  }
   378  
   379  func (s *applicationSuite) TestApplicationDeployToMachineNotFound(c *gc.C) {
   380  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   381  		Applications: []params.ApplicationDeploy{{
   382  			CharmURL:        "ch:jammy/application-name-1",
   383  			CharmOrigin:     &params.CharmOrigin{Source: "charm-hub", Base: params.Base{Name: "ubuntu", Channel: "22.04/stable"}},
   384  			ApplicationName: "application-name",
   385  			NumUnits:        1,
   386  			Placement:       []*instance.Placement{instance.MustParsePlacement("42")},
   387  		}}})
   388  	c.Assert(err, jc.ErrorIsNil)
   389  	c.Assert(results.Results, gc.HasLen, 1)
   390  	c.Assert(results.Results[0].Error, gc.ErrorMatches, `cannot deploy "application-name" to machine 42: machine 42 not found`)
   391  
   392  	_, err = s.State.Application("application-name")
   393  	c.Assert(err, gc.ErrorMatches, `application "application-name" not found`)
   394  }
   395  
   396  func (s *applicationSuite) TestApplicationUpdateDoesNotSetMinUnitsWithLXDProfile(c *gc.C) {
   397  	series := "quantal"
   398  	repo := testcharms.RepoForSeries(series)
   399  	ch := repo.CharmDir("lxd-profile-fail")
   400  	ident := fmt.Sprintf("%s-%d", ch.Meta().Name, ch.Revision())
   401  	curl := charm.MustParseURL(fmt.Sprintf("local:%s/%s", series, ident))
   402  	_, err := jujutesting.PutCharm(s.State, curl, ch)
   403  	c.Assert(err, gc.ErrorMatches, `invalid lxd-profile.yaml: contains device type "unix-disk"`)
   404  }
   405  
   406  var clientAddApplicationUnitsTests = []struct {
   407  	about       string
   408  	application string // if not set, defaults to 'dummy'
   409  	numUnits    int
   410  	expected    []string
   411  	to          string
   412  	err         string
   413  }{
   414  	{
   415  		about:    "returns unit names",
   416  		numUnits: 3,
   417  		expected: []string{"dummy/0", "dummy/1", "dummy/2"},
   418  	},
   419  	{
   420  		about: "fails trying to add zero units",
   421  		err:   "must add at least one unit",
   422  	},
   423  	{
   424  		// Note: chained-state, we add 1 unit here, but the 3 units
   425  		// from the first condition still exist
   426  		about:    "force the unit onto bootstrap machine",
   427  		numUnits: 1,
   428  		expected: []string{"dummy/3"},
   429  		to:       "0",
   430  	},
   431  	{
   432  		about:       "unknown application name",
   433  		application: "unknown-application",
   434  		numUnits:    1,
   435  		err:         `application "unknown-application" not found`,
   436  	},
   437  }
   438  
   439  func (s *applicationSuite) TestClientAddApplicationUnits(c *gc.C) {
   440  	s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
   441  	for i, t := range clientAddApplicationUnitsTests {
   442  		c.Logf("test %d. %s", i, t.about)
   443  		applicationName := t.application
   444  		if applicationName == "" {
   445  			applicationName = "dummy"
   446  		}
   447  		args := params.AddApplicationUnits{
   448  			ApplicationName: applicationName,
   449  			NumUnits:        t.numUnits,
   450  		}
   451  		if t.to != "" {
   452  			args.Placement = []*instance.Placement{instance.MustParsePlacement(t.to)}
   453  		}
   454  		result, err := s.applicationAPI.AddUnits(args)
   455  		if t.err != "" {
   456  			c.Assert(err, gc.ErrorMatches, t.err)
   457  			continue
   458  		}
   459  		c.Assert(err, jc.ErrorIsNil)
   460  		c.Assert(result.Units, gc.DeepEquals, t.expected)
   461  	}
   462  	// Test that we actually assigned the unit to machine 0
   463  	forcedUnit, err := s.BackingState.Unit("dummy/3")
   464  	c.Assert(err, jc.ErrorIsNil)
   465  	assignedMachine, err := forcedUnit.AssignedMachineId()
   466  	c.Assert(err, jc.ErrorIsNil)
   467  	c.Assert(assignedMachine, gc.Equals, "0")
   468  }
   469  
   470  func (s *applicationSuite) TestAddApplicationUnitsToNewContainer(c *gc.C) {
   471  	app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
   472  	machine, err := s.State.AddMachine(state.UbuntuBase("22.04"), state.JobHostUnits)
   473  	c.Assert(err, jc.ErrorIsNil)
   474  
   475  	_, err = s.applicationAPI.AddUnits(params.AddApplicationUnits{
   476  		ApplicationName: "dummy",
   477  		NumUnits:        1,
   478  		Placement:       []*instance.Placement{instance.MustParsePlacement("lxd:" + machine.Id())},
   479  	})
   480  	c.Assert(err, jc.ErrorIsNil)
   481  
   482  	units, err := app.AllUnits()
   483  	c.Assert(err, jc.ErrorIsNil)
   484  	mid, err := units[0].AssignedMachineId()
   485  	c.Assert(err, jc.ErrorIsNil)
   486  	c.Assert(mid, gc.Equals, machine.Id()+"/lxd/0")
   487  }
   488  
   489  var addApplicationUnitTests = []struct {
   490  	about       string
   491  	application string // if not set, defaults to 'dummy'
   492  	expected    []string
   493  	machineIds  []string
   494  	placement   []*instance.Placement
   495  	err         string
   496  }{
   497  	{
   498  		about:      "valid placement directives",
   499  		expected:   []string{"dummy/0"},
   500  		placement:  []*instance.Placement{{Scope: "deadbeef-0bad-400d-8000-4b1d0d06f00d", Directive: "valid"}},
   501  		machineIds: []string{"1"},
   502  	}, {
   503  		about:      "direct machine assignment placement directive",
   504  		expected:   []string{"dummy/1", "dummy/2"},
   505  		placement:  []*instance.Placement{{Scope: "#", Directive: "1"}, {Scope: "lxd", Directive: "1"}},
   506  		machineIds: []string{"1", "1/lxd/0"},
   507  	}, {
   508  		about:     "invalid placement directive",
   509  		err:       ".* invalid placement is invalid",
   510  		expected:  []string{"dummy/3"},
   511  		placement: []*instance.Placement{{Scope: "deadbeef-0bad-400d-8000-4b1d0d06f00d", Directive: "invalid"}},
   512  	},
   513  }
   514  
   515  func (s *applicationSuite) TestAddApplicationUnits(c *gc.C) {
   516  	s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
   517  	// Add a machine for the units to be placed on.
   518  	_, err := s.State.AddMachine(state.UbuntuBase("22.04"), state.JobHostUnits)
   519  	c.Assert(err, jc.ErrorIsNil)
   520  	for i, t := range addApplicationUnitTests {
   521  		c.Logf("test %d. %s", i, t.about)
   522  		applicationName := t.application
   523  		if applicationName == "" {
   524  			applicationName = "dummy"
   525  		}
   526  		result, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{
   527  			ApplicationName: applicationName,
   528  			NumUnits:        len(t.expected),
   529  			Placement:       t.placement,
   530  		})
   531  		if t.err != "" {
   532  			c.Assert(err, gc.ErrorMatches, t.err)
   533  			continue
   534  		}
   535  		c.Assert(err, jc.ErrorIsNil)
   536  		c.Assert(result.Units, gc.DeepEquals, t.expected)
   537  		for i, unitName := range result.Units {
   538  			u, err := s.BackingState.Unit(unitName)
   539  			c.Assert(err, jc.ErrorIsNil)
   540  			assignedMachine, err := u.AssignedMachineId()
   541  			c.Assert(err, jc.ErrorIsNil)
   542  			c.Assert(assignedMachine, gc.Equals, t.machineIds[i])
   543  		}
   544  	}
   545  }
   546  
   547  func (s *applicationSuite) assertAddApplicationUnits(c *gc.C) {
   548  	result, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{
   549  		ApplicationName: "dummy",
   550  		NumUnits:        3,
   551  	})
   552  	c.Assert(err, jc.ErrorIsNil)
   553  	c.Assert(result.Units, gc.DeepEquals, []string{"dummy/0", "dummy/1", "dummy/2"})
   554  
   555  	// Test that we actually assigned the unit to machine 0
   556  	forcedUnit, err := s.BackingState.Unit("dummy/0")
   557  	c.Assert(err, jc.ErrorIsNil)
   558  	assignedMachine, err := forcedUnit.AssignedMachineId()
   559  	c.Assert(err, jc.ErrorIsNil)
   560  	c.Assert(assignedMachine, gc.Equals, "0")
   561  }
   562  
   563  func (s *applicationSuite) TestApplicationCharmRelations(c *gc.C) {
   564  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   565  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
   566  	eps, err := s.State.InferEndpoints("logging", "wordpress")
   567  	c.Assert(err, jc.ErrorIsNil)
   568  	_, err = s.State.AddRelation(eps...)
   569  	c.Assert(err, jc.ErrorIsNil)
   570  
   571  	_, err = s.applicationAPI.CharmRelations(params.ApplicationCharmRelations{ApplicationName: "blah"})
   572  	c.Assert(err, gc.ErrorMatches, `application "blah" not found`)
   573  
   574  	result, err := s.applicationAPI.CharmRelations(params.ApplicationCharmRelations{ApplicationName: "wordpress"})
   575  	c.Assert(err, jc.ErrorIsNil)
   576  	c.Assert(result.CharmRelations, gc.DeepEquals, []string{
   577  		"cache", "db", "juju-info", "logging-dir", "monitoring-port", "url",
   578  	})
   579  }
   580  
   581  func (s *applicationSuite) assertAddApplicationUnitsBlocked(c *gc.C, msg string) {
   582  	_, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{
   583  		ApplicationName: "dummy",
   584  		NumUnits:        3,
   585  	})
   586  	s.AssertBlocked(c, err, msg)
   587  }
   588  
   589  func (s *applicationSuite) TestBlockDestroyAddApplicationUnits(c *gc.C) {
   590  	s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
   591  	s.BlockDestroyModel(c, "TestBlockDestroyAddApplicationUnits")
   592  	s.assertAddApplicationUnits(c)
   593  }
   594  
   595  func (s *applicationSuite) TestBlockRemoveAddApplicationUnits(c *gc.C) {
   596  	s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
   597  	s.BlockRemoveObject(c, "TestBlockRemoveAddApplicationUnits")
   598  	s.assertAddApplicationUnits(c)
   599  }
   600  
   601  func (s *applicationSuite) TestBlockChangeAddApplicationUnits(c *gc.C) {
   602  	s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
   603  	s.BlockAllChanges(c, "TestBlockChangeAddApplicationUnits")
   604  	s.assertAddApplicationUnitsBlocked(c, "TestBlockChangeAddApplicationUnits")
   605  }
   606  
   607  func (s *applicationSuite) TestAddUnitToMachineNotFound(c *gc.C) {
   608  	s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
   609  	_, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{
   610  		ApplicationName: "dummy",
   611  		NumUnits:        3,
   612  		Placement:       []*instance.Placement{instance.MustParsePlacement("42")},
   613  	})
   614  	c.Assert(err, gc.ErrorMatches, `acquiring machine to host unit "dummy/0": machine 42 not found`)
   615  }
   616  
   617  func (s *applicationSuite) TestApplicationExpose(c *gc.C) {
   618  	charm := s.AddTestingCharm(c, "dummy")
   619  	applicationNames := []string{"dummy-application", "exposed-application"}
   620  	apps := make([]*state.Application, len(applicationNames))
   621  	var err error
   622  	for i, name := range applicationNames {
   623  		apps[i] = s.AddTestingApplication(c, name, charm)
   624  		c.Assert(apps[i].IsExposed(), jc.IsFalse)
   625  	}
   626  	err = apps[1].MergeExposeSettings(nil)
   627  	c.Assert(err, jc.ErrorIsNil)
   628  	c.Assert(apps[1].IsExposed(), jc.IsTrue)
   629  
   630  	s.assertApplicationExpose(c)
   631  }
   632  
   633  func (s *applicationSuite) TestApplicationExposeEndpoints(c *gc.C) {
   634  	charm := s.AddTestingCharm(c, "wordpress")
   635  	app := s.AddTestingApplication(c, "wordpress", charm)
   636  	c.Assert(app.IsExposed(), jc.IsFalse)
   637  
   638  	err := s.applicationAPI.Expose(params.ApplicationExpose{
   639  		ApplicationName: app.Name(),
   640  		ExposedEndpoints: map[string]params.ExposedEndpoint{
   641  			// Exposing an endpoint with no expose options implies
   642  			// expose to 0.0.0.0/0 and ::/0.
   643  			"monitoring-port": {},
   644  		},
   645  	})
   646  	c.Assert(err, jc.ErrorIsNil)
   647  
   648  	got, err := s.State.Application(app.Name())
   649  	c.Assert(err, jc.ErrorIsNil)
   650  	c.Assert(got.IsExposed(), gc.Equals, true)
   651  	c.Assert(got.ExposedEndpoints(), gc.DeepEquals, map[string]state.ExposedEndpoint{
   652  		"monitoring-port": {
   653  			ExposeToCIDRs: []string{firewall.AllNetworksIPV4CIDR, firewall.AllNetworksIPV6CIDR},
   654  		},
   655  	})
   656  }
   657  
   658  func (s *applicationSuite) TestApplicationExposeEndpointsWithPre29Client(c *gc.C) {
   659  	charm := s.AddTestingCharm(c, "wordpress")
   660  	app := s.AddTestingApplication(c, "wordpress", charm)
   661  	c.Assert(app.IsExposed(), jc.IsFalse)
   662  
   663  	err := s.applicationAPI.Expose(params.ApplicationExpose{
   664  		ApplicationName: app.Name(),
   665  		// If no endpoint-specific expose params are provided, the call
   666  		// will emulate the behavior of a pre 2.9 controller where all
   667  		// ports are exposed to 0.0.0.0/0 and ::/0.
   668  	})
   669  	c.Assert(err, jc.ErrorIsNil)
   670  
   671  	got, err := s.State.Application(app.Name())
   672  	c.Assert(err, jc.ErrorIsNil)
   673  	c.Assert(got.IsExposed(), gc.Equals, true)
   674  	c.Assert(got.ExposedEndpoints(), gc.DeepEquals, map[string]state.ExposedEndpoint{
   675  		"": {
   676  			ExposeToCIDRs: []string{firewall.AllNetworksIPV4CIDR, firewall.AllNetworksIPV6CIDR},
   677  		},
   678  	})
   679  }
   680  
   681  func (s *applicationSuite) setupApplicationExpose(c *gc.C) {
   682  	charm := s.AddTestingCharm(c, "dummy")
   683  	applicationNames := []string{"dummy-application", "exposed-application"}
   684  	apps := make([]*state.Application, len(applicationNames))
   685  	var err error
   686  	for i, name := range applicationNames {
   687  		apps[i] = s.AddTestingApplication(c, name, charm)
   688  		c.Assert(apps[i].IsExposed(), jc.IsFalse)
   689  	}
   690  	err = apps[1].MergeExposeSettings(nil)
   691  	c.Assert(err, jc.ErrorIsNil)
   692  	c.Assert(apps[1].IsExposed(), jc.IsTrue)
   693  }
   694  
   695  var applicationExposeTests = []struct {
   696  	about                 string
   697  	application           string
   698  	exposedEndpointParams map[string]params.ExposedEndpoint
   699  	//
   700  	expExposed          bool
   701  	expExposedEndpoints map[string]state.ExposedEndpoint
   702  	expErr              string
   703  }{
   704  	{
   705  		about:       "unknown application name",
   706  		application: "unknown-application",
   707  		expErr:      `application "unknown-application" not found`,
   708  	},
   709  	{
   710  		about:       "expose all endpoints of an application ",
   711  		application: "dummy-application",
   712  		expExposed:  true,
   713  		expExposedEndpoints: map[string]state.ExposedEndpoint{
   714  			"": {
   715  				ExposeToCIDRs: []string{"0.0.0.0/0", "::/0"},
   716  			},
   717  		},
   718  	},
   719  	{
   720  		about:       "expose an already exposed application",
   721  		application: "exposed-application",
   722  		expExposed:  true,
   723  		expExposedEndpoints: map[string]state.ExposedEndpoint{
   724  			"": {
   725  				ExposeToCIDRs: []string{"0.0.0.0/0", "::/0"},
   726  			},
   727  		},
   728  	},
   729  	{
   730  		about:       "unknown endpoint name in expose parameters",
   731  		application: "dummy-application",
   732  		exposedEndpointParams: map[string]params.ExposedEndpoint{
   733  			"bogus": {},
   734  		},
   735  		expErr: `endpoint "bogus" not found`,
   736  	},
   737  	{
   738  		about:       "unknown space name in expose parameters",
   739  		application: "dummy-application",
   740  		exposedEndpointParams: map[string]params.ExposedEndpoint{
   741  			"": {
   742  				ExposeToSpaces: []string{"invaders"},
   743  			},
   744  		},
   745  		expErr: `space "invaders" not found`,
   746  	},
   747  	{
   748  		about:       "expose an application and provide expose parameters",
   749  		application: "exposed-application",
   750  		exposedEndpointParams: map[string]params.ExposedEndpoint{
   751  			"": {
   752  				ExposeToSpaces: []string{network.AlphaSpaceName},
   753  				ExposeToCIDRs:  []string{"13.37.0.0/16"},
   754  			},
   755  		},
   756  		expExposed: true,
   757  		expExposedEndpoints: map[string]state.ExposedEndpoint{
   758  			"": {
   759  				ExposeToSpaceIDs: []string{network.AlphaSpaceId},
   760  				ExposeToCIDRs:    []string{"13.37.0.0/16"},
   761  			},
   762  		},
   763  	},
   764  }
   765  
   766  func (s *applicationSuite) assertApplicationExpose(c *gc.C) {
   767  	for i, t := range applicationExposeTests {
   768  		c.Logf("test %d. %s", i, t.about)
   769  		err := s.applicationAPI.Expose(params.ApplicationExpose{
   770  			ApplicationName:  t.application,
   771  			ExposedEndpoints: t.exposedEndpointParams,
   772  		})
   773  		if t.expErr != "" {
   774  			c.Assert(err, gc.ErrorMatches, t.expErr)
   775  		} else {
   776  			c.Assert(err, jc.ErrorIsNil)
   777  			app, err := s.State.Application(t.application)
   778  			c.Assert(err, jc.ErrorIsNil)
   779  			c.Assert(app.IsExposed(), gc.Equals, t.expExposed)
   780  			c.Assert(app.ExposedEndpoints(), gc.DeepEquals, t.expExposedEndpoints)
   781  		}
   782  	}
   783  }
   784  
   785  func (s *applicationSuite) assertApplicationExposeBlocked(c *gc.C, msg string) {
   786  	for i, t := range applicationExposeTests {
   787  		c.Logf("test %d. %s", i, t.about)
   788  		err := s.applicationAPI.Expose(params.ApplicationExpose{
   789  			ApplicationName:  t.application,
   790  			ExposedEndpoints: t.exposedEndpointParams,
   791  		})
   792  		s.AssertBlocked(c, err, msg)
   793  	}
   794  }
   795  
   796  func (s *applicationSuite) TestBlockDestroyApplicationExpose(c *gc.C) {
   797  	s.setupApplicationExpose(c)
   798  	s.BlockDestroyModel(c, "TestBlockDestroyApplicationExpose")
   799  	s.assertApplicationExpose(c)
   800  }
   801  
   802  func (s *applicationSuite) TestBlockRemoveApplicationExpose(c *gc.C) {
   803  	s.setupApplicationExpose(c)
   804  	s.BlockRemoveObject(c, "TestBlockRemoveApplicationExpose")
   805  	s.assertApplicationExpose(c)
   806  }
   807  
   808  func (s *applicationSuite) TestBlockChangesApplicationExpose(c *gc.C) {
   809  	s.setupApplicationExpose(c)
   810  	s.BlockAllChanges(c, "TestBlockChangesApplicationExpose")
   811  	s.assertApplicationExposeBlocked(c, "TestBlockChangesApplicationExpose")
   812  }
   813  
   814  var applicationUnexposeTests = []struct {
   815  	about               string
   816  	application         string
   817  	err                 string
   818  	initial             map[string]state.ExposedEndpoint
   819  	unexposeEndpoints   []string
   820  	expExposed          bool
   821  	expExposedEndpoints map[string]state.ExposedEndpoint
   822  }{
   823  	{
   824  		about:       "unknown application name",
   825  		application: "unknown-application",
   826  		err:         `application "unknown-application" not found`,
   827  	},
   828  	{
   829  		about:       "unexpose a application without specifying any endpoints",
   830  		application: "dummy-application",
   831  		initial: map[string]state.ExposedEndpoint{
   832  			"": {},
   833  		},
   834  		expExposed: false,
   835  	},
   836  	{
   837  		about:       "unexpose specific application endpoint",
   838  		application: "dummy-application",
   839  		initial: map[string]state.ExposedEndpoint{
   840  			"server":       {},
   841  			"server-admin": {},
   842  		},
   843  		unexposeEndpoints: []string{"server"},
   844  		// The server-admin (and hence the app) should remain exposed
   845  		expExposed: true,
   846  		expExposedEndpoints: map[string]state.ExposedEndpoint{
   847  			"server-admin": {ExposeToCIDRs: []string{"0.0.0.0/0", "::/0"}},
   848  		},
   849  	},
   850  	{
   851  		about:       "unexpose all currently exposed application endpoints",
   852  		application: "dummy-application",
   853  		initial: map[string]state.ExposedEndpoint{
   854  			"server":       {},
   855  			"server-admin": {},
   856  		},
   857  		unexposeEndpoints: []string{"server", "server-admin"},
   858  		// Application should now be unexposed as all its endpoints have
   859  		// been unexposed.
   860  		expExposed: false,
   861  	},
   862  	{
   863  		about:       "unexpose an already unexposed application",
   864  		application: "dummy-application",
   865  		initial:     nil,
   866  		expExposed:  false,
   867  	},
   868  }
   869  
   870  func (s *applicationSuite) TestApplicationUnexpose(c *gc.C) {
   871  	charm := s.AddTestingCharm(c, "mysql")
   872  	for i, t := range applicationUnexposeTests {
   873  		c.Logf("test %d. %s", i, t.about)
   874  		app := s.AddTestingApplication(c, "dummy-application", charm)
   875  		if len(t.initial) != 0 {
   876  			err := app.MergeExposeSettings(t.initial)
   877  			c.Assert(err, jc.ErrorIsNil)
   878  		}
   879  		c.Assert(app.IsExposed(), gc.Equals, len(t.initial) != 0)
   880  		err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{
   881  			ApplicationName:  t.application,
   882  			ExposedEndpoints: t.unexposeEndpoints,
   883  		})
   884  		if t.err == "" {
   885  			c.Assert(err, jc.ErrorIsNil)
   886  			app.Refresh()
   887  			c.Assert(app.IsExposed(), gc.Equals, t.expExposed)
   888  			c.Assert(app.ExposedEndpoints(), gc.DeepEquals, t.expExposedEndpoints)
   889  		} else {
   890  			c.Assert(err, gc.ErrorMatches, t.err)
   891  		}
   892  		err = app.Destroy()
   893  		c.Assert(err, jc.ErrorIsNil)
   894  	}
   895  }
   896  
   897  func (s *applicationSuite) setupApplicationUnexpose(c *gc.C) *state.Application {
   898  	charm := s.AddTestingCharm(c, "dummy")
   899  	app := s.AddTestingApplication(c, "dummy-application", charm)
   900  	app.MergeExposeSettings(nil)
   901  	c.Assert(app.IsExposed(), gc.Equals, true)
   902  	return app
   903  }
   904  
   905  func (s *applicationSuite) assertApplicationUnexpose(c *gc.C, app *state.Application) {
   906  	err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{ApplicationName: "dummy-application"})
   907  	c.Assert(err, jc.ErrorIsNil)
   908  	app.Refresh()
   909  	c.Assert(app.IsExposed(), gc.Equals, false)
   910  	err = app.Destroy()
   911  	c.Assert(err, jc.ErrorIsNil)
   912  }
   913  
   914  func (s *applicationSuite) assertApplicationUnexposeBlocked(c *gc.C, app *state.Application, msg string) {
   915  	err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{ApplicationName: "dummy-application"})
   916  	s.AssertBlocked(c, err, msg)
   917  	err = app.Destroy()
   918  	c.Assert(err, jc.ErrorIsNil)
   919  }
   920  
   921  func (s *applicationSuite) TestBlockDestroyApplicationUnexpose(c *gc.C) {
   922  	app := s.setupApplicationUnexpose(c)
   923  	s.BlockDestroyModel(c, "TestBlockDestroyApplicationUnexpose")
   924  	s.assertApplicationUnexpose(c, app)
   925  }
   926  
   927  func (s *applicationSuite) TestBlockRemoveApplicationUnexpose(c *gc.C) {
   928  	app := s.setupApplicationUnexpose(c)
   929  	s.BlockRemoveObject(c, "TestBlockRemoveApplicationUnexpose")
   930  	s.assertApplicationUnexpose(c, app)
   931  }
   932  
   933  func (s *applicationSuite) TestBlockChangesApplicationUnexpose(c *gc.C) {
   934  	app := s.setupApplicationUnexpose(c)
   935  	s.BlockAllChanges(c, "TestBlockChangesApplicationUnexpose")
   936  	s.assertApplicationUnexposeBlocked(c, app, "TestBlockChangesApplicationUnexpose")
   937  }
   938  
   939  var applicationDestroyTests = []struct {
   940  	about       string
   941  	application string
   942  	err         string
   943  }{
   944  	{
   945  		about:       "unknown application name",
   946  		application: "unknown-application",
   947  		err:         `application "unknown-application" not found`,
   948  	},
   949  	{
   950  		about:       "destroy an application",
   951  		application: "dummy-application",
   952  	},
   953  	{
   954  		about:       "destroy an already destroyed application",
   955  		application: "dummy-application",
   956  		err:         `application "dummy-application" not found`,
   957  	},
   958  }
   959  
   960  func (s *applicationSuite) apiv16() *application.APIv16 {
   961  	return &application.APIv16{
   962  		APIv17: &application.APIv17{
   963  			APIv18: &application.APIv18{
   964  				APIv19: &application.APIv19{
   965  					APIv20: &application.APIv20{
   966  						APIBase: s.applicationAPI,
   967  					},
   968  				},
   969  			},
   970  		},
   971  	}
   972  }
   973  
   974  func (s *applicationSuite) TestApplicationDestroy(c *gc.C) {
   975  	apiv16 := s.apiv16()
   976  	s.AddTestingApplication(c, "dummy-application", s.AddTestingCharm(c, "dummy"))
   977  	_, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{
   978  		Name:        "remote-application",
   979  		SourceModel: s.Model.ModelTag(),
   980  		Token:       "t0",
   981  	})
   982  	c.Assert(err, jc.ErrorIsNil)
   983  
   984  	for i, t := range applicationDestroyTests {
   985  		c.Logf("test %d. %s", i, t.about)
   986  		err := apiv16.Destroy(params.ApplicationDestroy{ApplicationName: t.application})
   987  		if t.err != "" {
   988  			c.Assert(err, gc.ErrorMatches, t.err)
   989  		} else {
   990  			c.Assert(err, jc.ErrorIsNil)
   991  		}
   992  	}
   993  
   994  	// Now do Destroy on an application with units. Destroy will
   995  	// cause the application to be not-Alive, but will not remove its
   996  	// document.
   997  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   998  	applicationName := "wordpress"
   999  	app, err := s.State.Application(applicationName)
  1000  	c.Assert(err, jc.ErrorIsNil)
  1001  	err = apiv16.Destroy(params.ApplicationDestroy{ApplicationName: applicationName})
  1002  	c.Assert(err, jc.ErrorIsNil)
  1003  	err = app.Refresh()
  1004  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1005  }
  1006  
  1007  func assertLife(c *gc.C, entity state.Living, life state.Life) {
  1008  	err := entity.Refresh()
  1009  	c.Assert(err, jc.ErrorIsNil)
  1010  	c.Assert(entity.Life(), gc.Equals, life)
  1011  }
  1012  
  1013  func (s *applicationSuite) TestBlockApplicationDestroy(c *gc.C) {
  1014  	apiv16 := s.apiv16()
  1015  	s.AddTestingApplication(c, "dummy-application", s.AddTestingCharm(c, "dummy"))
  1016  
  1017  	// block remove-objects
  1018  	s.BlockRemoveObject(c, "TestBlockApplicationDestroy")
  1019  	err := apiv16.Destroy(params.ApplicationDestroy{ApplicationName: "dummy-application"})
  1020  	s.AssertBlocked(c, err, "TestBlockApplicationDestroy")
  1021  	// Tests may have invalid application names.
  1022  	app, err := s.State.Application("dummy-application")
  1023  	if err == nil {
  1024  		// For valid application names, check that application is alive :-)
  1025  		assertLife(c, app, state.Alive)
  1026  	}
  1027  }
  1028  
  1029  func (s *applicationSuite) TestDestroyControllerApplicationNotAllowed(c *gc.C) {
  1030  	apiv16 := s.apiv16()
  1031  	s.AddTestingApplication(c, "controller-application", s.AddTestingCharm(c, "juju-controller"))
  1032  
  1033  	err := apiv16.Destroy(params.ApplicationDestroy{"controller-application"})
  1034  	c.Assert(err, gc.ErrorMatches, "removing the controller application not supported")
  1035  }
  1036  
  1037  func (s *applicationSuite) TestDestroyPrincipalUnits(c *gc.C) {
  1038  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1039  	units := make([]*state.Unit, 5)
  1040  	for i := range units {
  1041  		unit, err := wordpress.AddUnit(state.AddUnitParams{})
  1042  		c.Assert(err, jc.ErrorIsNil)
  1043  		unit.AssignToNewMachine()
  1044  		c.Assert(err, jc.ErrorIsNil)
  1045  		now := time.Now()
  1046  		sInfo := status.StatusInfo{
  1047  			Status:  status.Idle,
  1048  			Message: "",
  1049  			Since:   &now,
  1050  		}
  1051  		err = unit.SetAgentStatus(sInfo)
  1052  		c.Assert(err, jc.ErrorIsNil)
  1053  		units[i] = unit
  1054  	}
  1055  	s.assertDestroyPrincipalUnits(c, units)
  1056  }
  1057  
  1058  func (s *applicationSuite) TestDestroySubordinateUnits(c *gc.C) {
  1059  	apiv16 := s.apiv16()
  1060  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1061  	wordpress0, err := wordpress.AddUnit(state.AddUnitParams{})
  1062  	c.Assert(err, jc.ErrorIsNil)
  1063  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
  1064  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  1065  	c.Assert(err, jc.ErrorIsNil)
  1066  	rel, err := s.State.AddRelation(eps...)
  1067  	c.Assert(err, jc.ErrorIsNil)
  1068  	ru, err := rel.Unit(wordpress0)
  1069  	c.Assert(err, jc.ErrorIsNil)
  1070  	err = ru.EnterScope(nil)
  1071  	c.Assert(err, jc.ErrorIsNil)
  1072  	logging0, err := s.State.Unit("logging/0")
  1073  	c.Assert(err, jc.ErrorIsNil)
  1074  
  1075  	// Try to destroy the subordinate alone; check it fails.
  1076  	err = apiv16.DestroyUnits(params.DestroyApplicationUnits{
  1077  		UnitNames: []string{"logging/0"},
  1078  	})
  1079  	c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate, .*`)
  1080  	assertLife(c, logging0, state.Alive)
  1081  
  1082  	s.assertDestroySubordinateUnits(c, wordpress0, logging0)
  1083  }
  1084  
  1085  func (s *applicationSuite) assertDestroyPrincipalUnits(c *gc.C, units []*state.Unit) {
  1086  	apiv16 := s.apiv16()
  1087  	// Destroy 2 of them; check they become Dying.
  1088  	err := apiv16.DestroyUnits(params.DestroyApplicationUnits{
  1089  		UnitNames: []string{"wordpress/0", "wordpress/1"},
  1090  	})
  1091  	c.Assert(err, jc.ErrorIsNil)
  1092  	assertLife(c, units[0], state.Dying)
  1093  	assertLife(c, units[1], state.Dying)
  1094  
  1095  	// Try to destroy an Alive one and a Dying one; check
  1096  	// it destroys the Alive one and ignores the Dying one.
  1097  	err = apiv16.DestroyUnits(params.DestroyApplicationUnits{
  1098  		UnitNames: []string{"wordpress/2", "wordpress/0"},
  1099  	})
  1100  	c.Assert(err, jc.ErrorIsNil)
  1101  	assertLife(c, units[2], state.Dying)
  1102  
  1103  	// Try to destroy an Alive one along with a nonexistent one; check that
  1104  	// the valid instruction is followed but the invalid one is warned about.
  1105  	err = apiv16.DestroyUnits(params.DestroyApplicationUnits{
  1106  		UnitNames: []string{"boojum/123", "wordpress/3"},
  1107  	})
  1108  	c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`)
  1109  	assertLife(c, units[3], state.Dying)
  1110  
  1111  	// Make one Dead, and destroy an Alive one alongside it; check no errors.
  1112  	wp0, err := s.State.Unit("wordpress/0")
  1113  	c.Assert(err, jc.ErrorIsNil)
  1114  	err = wp0.EnsureDead()
  1115  	c.Assert(err, jc.ErrorIsNil)
  1116  	err = apiv16.DestroyUnits(params.DestroyApplicationUnits{
  1117  		UnitNames: []string{"wordpress/0", "wordpress/4"},
  1118  	})
  1119  	c.Assert(err, jc.ErrorIsNil)
  1120  	assertLife(c, units[0], state.Dead)
  1121  	assertLife(c, units[4], state.Dying)
  1122  }
  1123  
  1124  func (s *applicationSuite) setupDestroyPrincipalUnits(c *gc.C) []*state.Unit {
  1125  	units := make([]*state.Unit, 5)
  1126  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1127  	for i := range units {
  1128  		unit, err := wordpress.AddUnit(state.AddUnitParams{})
  1129  		c.Assert(err, jc.ErrorIsNil)
  1130  		err = unit.AssignToNewMachine()
  1131  		c.Assert(err, jc.ErrorIsNil)
  1132  		now := time.Now()
  1133  		sInfo := status.StatusInfo{
  1134  			Status:  status.Idle,
  1135  			Message: "",
  1136  			Since:   &now,
  1137  		}
  1138  		err = unit.SetAgentStatus(sInfo)
  1139  		c.Assert(err, jc.ErrorIsNil)
  1140  		units[i] = unit
  1141  	}
  1142  	return units
  1143  }
  1144  
  1145  func (s *applicationSuite) assertBlockedErrorAndLiveliness(
  1146  	c *gc.C,
  1147  	err error,
  1148  	msg string,
  1149  	living1 state.Living,
  1150  	living2 state.Living,
  1151  	living3 state.Living,
  1152  	living4 state.Living,
  1153  ) {
  1154  	s.AssertBlocked(c, err, msg)
  1155  	assertLife(c, living1, state.Alive)
  1156  	assertLife(c, living2, state.Alive)
  1157  	assertLife(c, living3, state.Alive)
  1158  	assertLife(c, living4, state.Alive)
  1159  }
  1160  
  1161  func (s *applicationSuite) TestBlockChangesDestroyPrincipalUnits(c *gc.C) {
  1162  	apiv16 := s.apiv16()
  1163  	units := s.setupDestroyPrincipalUnits(c)
  1164  	s.BlockAllChanges(c, "TestBlockChangesDestroyPrincipalUnits")
  1165  	err := apiv16.DestroyUnits(params.DestroyApplicationUnits{
  1166  		UnitNames: []string{"wordpress/0", "wordpress/1"},
  1167  	})
  1168  	s.assertBlockedErrorAndLiveliness(c, err, "TestBlockChangesDestroyPrincipalUnits", units[0], units[1], units[2], units[3])
  1169  }
  1170  
  1171  func (s *applicationSuite) TestBlockRemoveDestroyPrincipalUnits(c *gc.C) {
  1172  	apiv16 := s.apiv16()
  1173  	units := s.setupDestroyPrincipalUnits(c)
  1174  	s.BlockRemoveObject(c, "TestBlockRemoveDestroyPrincipalUnits")
  1175  	err := apiv16.DestroyUnits(params.DestroyApplicationUnits{
  1176  		UnitNames: []string{"wordpress/0", "wordpress/1"},
  1177  	})
  1178  	s.assertBlockedErrorAndLiveliness(c, err, "TestBlockRemoveDestroyPrincipalUnits", units[0], units[1], units[2], units[3])
  1179  }
  1180  
  1181  func (s *applicationSuite) TestBlockDestroyDestroyPrincipalUnits(c *gc.C) {
  1182  	apiv16 := s.apiv16()
  1183  	units := s.setupDestroyPrincipalUnits(c)
  1184  	s.BlockDestroyModel(c, "TestBlockDestroyDestroyPrincipalUnits")
  1185  	err := apiv16.DestroyUnits(params.DestroyApplicationUnits{
  1186  		UnitNames: []string{"wordpress/0", "wordpress/1"},
  1187  	})
  1188  	c.Assert(err, jc.ErrorIsNil)
  1189  	assertLife(c, units[0], state.Dying)
  1190  	assertLife(c, units[1], state.Dying)
  1191  }
  1192  
  1193  func (s *applicationSuite) assertDestroySubordinateUnits(c *gc.C, wordpress0, logging0 *state.Unit) {
  1194  	apiv16 := s.apiv16()
  1195  	// Try to destroy the principal and the subordinate together; check it warns
  1196  	// about the subordinate, but destroys the one it can. (The principal unit
  1197  	// agent will be responsible for destroying the subordinate.)
  1198  	err := apiv16.DestroyUnits(params.DestroyApplicationUnits{
  1199  		UnitNames: []string{"wordpress/0", "logging/0"},
  1200  	})
  1201  	c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate, .*`)
  1202  	assertLife(c, wordpress0, state.Dying)
  1203  	assertLife(c, logging0, state.Alive)
  1204  }
  1205  
  1206  func (s *applicationSuite) TestBlockRemoveDestroySubordinateUnits(c *gc.C) {
  1207  	apiv16 := s.apiv16()
  1208  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1209  	wordpress0, err := wordpress.AddUnit(state.AddUnitParams{})
  1210  	c.Assert(err, jc.ErrorIsNil)
  1211  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
  1212  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  1213  	c.Assert(err, jc.ErrorIsNil)
  1214  	rel, err := s.State.AddRelation(eps...)
  1215  	c.Assert(err, jc.ErrorIsNil)
  1216  	ru, err := rel.Unit(wordpress0)
  1217  	c.Assert(err, jc.ErrorIsNil)
  1218  	err = ru.EnterScope(nil)
  1219  	c.Assert(err, jc.ErrorIsNil)
  1220  	logging0, err := s.State.Unit("logging/0")
  1221  	c.Assert(err, jc.ErrorIsNil)
  1222  
  1223  	s.BlockRemoveObject(c, "TestBlockRemoveDestroySubordinateUnits")
  1224  	// Try to destroy the subordinate alone; check it fails.
  1225  	err = apiv16.DestroyUnits(params.DestroyApplicationUnits{
  1226  		UnitNames: []string{"logging/0"},
  1227  	})
  1228  	s.AssertBlocked(c, err, "TestBlockRemoveDestroySubordinateUnits")
  1229  	assertLife(c, rel, state.Alive)
  1230  	assertLife(c, wordpress0, state.Alive)
  1231  	assertLife(c, logging0, state.Alive)
  1232  
  1233  	err = apiv16.DestroyUnits(params.DestroyApplicationUnits{
  1234  		UnitNames: []string{"wordpress/0", "logging/0"},
  1235  	})
  1236  	s.AssertBlocked(c, err, "TestBlockRemoveDestroySubordinateUnits")
  1237  	assertLife(c, wordpress0, state.Alive)
  1238  	assertLife(c, logging0, state.Alive)
  1239  	assertLife(c, rel, state.Alive)
  1240  }
  1241  
  1242  func (s *applicationSuite) TestBlockChangesDestroySubordinateUnits(c *gc.C) {
  1243  	apiv16 := s.apiv16()
  1244  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1245  	wordpress0, err := wordpress.AddUnit(state.AddUnitParams{})
  1246  	c.Assert(err, jc.ErrorIsNil)
  1247  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
  1248  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  1249  	c.Assert(err, jc.ErrorIsNil)
  1250  	rel, err := s.State.AddRelation(eps...)
  1251  	c.Assert(err, jc.ErrorIsNil)
  1252  	ru, err := rel.Unit(wordpress0)
  1253  	c.Assert(err, jc.ErrorIsNil)
  1254  	err = ru.EnterScope(nil)
  1255  	c.Assert(err, jc.ErrorIsNil)
  1256  	logging0, err := s.State.Unit("logging/0")
  1257  	c.Assert(err, jc.ErrorIsNil)
  1258  
  1259  	s.BlockAllChanges(c, "TestBlockChangesDestroySubordinateUnits")
  1260  	// Try to destroy the subordinate alone; check it fails.
  1261  	err = apiv16.DestroyUnits(params.DestroyApplicationUnits{
  1262  		UnitNames: []string{"logging/0"},
  1263  	})
  1264  	s.AssertBlocked(c, err, "TestBlockChangesDestroySubordinateUnits")
  1265  	assertLife(c, rel, state.Alive)
  1266  	assertLife(c, wordpress0, state.Alive)
  1267  	assertLife(c, logging0, state.Alive)
  1268  
  1269  	err = apiv16.DestroyUnits(params.DestroyApplicationUnits{
  1270  		UnitNames: []string{"wordpress/0", "logging/0"},
  1271  	})
  1272  	s.AssertBlocked(c, err, "TestBlockChangesDestroySubordinateUnits")
  1273  	assertLife(c, wordpress0, state.Alive)
  1274  	assertLife(c, logging0, state.Alive)
  1275  	assertLife(c, rel, state.Alive)
  1276  }
  1277  
  1278  func (s *applicationSuite) TestBlockDestroyDestroySubordinateUnits(c *gc.C) {
  1279  	apiv16 := s.apiv16()
  1280  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1281  	wordpress0, err := wordpress.AddUnit(state.AddUnitParams{})
  1282  	c.Assert(err, jc.ErrorIsNil)
  1283  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
  1284  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  1285  	c.Assert(err, jc.ErrorIsNil)
  1286  	rel, err := s.State.AddRelation(eps...)
  1287  	c.Assert(err, jc.ErrorIsNil)
  1288  	ru, err := rel.Unit(wordpress0)
  1289  	c.Assert(err, jc.ErrorIsNil)
  1290  	err = ru.EnterScope(nil)
  1291  	c.Assert(err, jc.ErrorIsNil)
  1292  	logging0, err := s.State.Unit("logging/0")
  1293  	c.Assert(err, jc.ErrorIsNil)
  1294  
  1295  	s.BlockDestroyModel(c, "TestBlockDestroyDestroySubordinateUnits")
  1296  	// Try to destroy the subordinate alone; check it fails.
  1297  	err = apiv16.DestroyUnits(params.DestroyApplicationUnits{
  1298  		UnitNames: []string{"logging/0"},
  1299  	})
  1300  	c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate, .*`)
  1301  	assertLife(c, logging0, state.Alive)
  1302  
  1303  	s.assertDestroySubordinateUnits(c, wordpress0, logging0)
  1304  }
  1305  
  1306  func (s *applicationSuite) TestClientSetApplicationConstraints(c *gc.C) {
  1307  	app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1308  
  1309  	// Update constraints for the application.
  1310  	cons, err := constraints.Parse("mem=4096", "cores=2")
  1311  	c.Assert(err, jc.ErrorIsNil)
  1312  	err = s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons})
  1313  	c.Assert(err, jc.ErrorIsNil)
  1314  
  1315  	// Ensure the constraints have been correctly updated.
  1316  	obtained, err := app.Constraints()
  1317  	c.Assert(err, jc.ErrorIsNil)
  1318  	c.Assert(obtained, gc.DeepEquals, cons)
  1319  }
  1320  
  1321  func (s *applicationSuite) setupSetApplicationConstraints(c *gc.C) (*state.Application, constraints.Value) {
  1322  	app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1323  	// Update constraints for the application.
  1324  	cons, err := constraints.Parse("mem=4096", "cores=2")
  1325  	c.Assert(err, jc.ErrorIsNil)
  1326  	return app, cons
  1327  }
  1328  
  1329  func (s *applicationSuite) assertSetApplicationConstraints(c *gc.C, application *state.Application, cons constraints.Value) {
  1330  	err := s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons})
  1331  	c.Assert(err, jc.ErrorIsNil)
  1332  	// Ensure the constraints have been correctly updated.
  1333  	obtained, err := application.Constraints()
  1334  	c.Assert(err, jc.ErrorIsNil)
  1335  	c.Assert(obtained, gc.DeepEquals, cons)
  1336  }
  1337  
  1338  func (s *applicationSuite) assertSetApplicationConstraintsBlocked(c *gc.C, msg string, application *state.Application, cons constraints.Value) {
  1339  	err := s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons})
  1340  	s.AssertBlocked(c, err, msg)
  1341  }
  1342  
  1343  func (s *applicationSuite) TestBlockDestroySetApplicationConstraints(c *gc.C) {
  1344  	app, cons := s.setupSetApplicationConstraints(c)
  1345  	s.BlockDestroyModel(c, "TestBlockDestroySetApplicationConstraints")
  1346  	s.assertSetApplicationConstraints(c, app, cons)
  1347  }
  1348  
  1349  func (s *applicationSuite) TestBlockRemoveSetApplicationConstraints(c *gc.C) {
  1350  	app, cons := s.setupSetApplicationConstraints(c)
  1351  	s.BlockRemoveObject(c, "TestBlockRemoveSetApplicationConstraints")
  1352  	s.assertSetApplicationConstraints(c, app, cons)
  1353  }
  1354  
  1355  func (s *applicationSuite) TestBlockChangesSetApplicationConstraints(c *gc.C) {
  1356  	app, cons := s.setupSetApplicationConstraints(c)
  1357  	s.BlockAllChanges(c, "TestBlockChangesSetApplicationConstraints")
  1358  	s.assertSetApplicationConstraintsBlocked(c, "TestBlockChangesSetApplicationConstraints", app, cons)
  1359  }
  1360  
  1361  func (s *applicationSuite) TestClientGetApplicationConstraints(c *gc.C) {
  1362  	fooConstraints := constraints.MustParse("arch=amd64", "mem=4G")
  1363  	s.Factory.MakeApplication(c, &factory.ApplicationParams{
  1364  		Name:        "foo",
  1365  		Constraints: fooConstraints,
  1366  	})
  1367  	barConstraints := constraints.MustParse("arch=amd64", "mem=128G", "cores=64")
  1368  	s.Factory.MakeApplication(c, &factory.ApplicationParams{
  1369  		Name:        "bar",
  1370  		Constraints: barConstraints,
  1371  	})
  1372  
  1373  	results, err := s.applicationAPI.GetConstraints(params.Entities{
  1374  		Entities: []params.Entity{
  1375  			{Tag: "wat"}, {Tag: "machine-0"}, {Tag: "user-foo"},
  1376  			{Tag: "application-foo"}, {Tag: "application-bar"}, {Tag: "application-wat"},
  1377  		},
  1378  	})
  1379  	c.Assert(err, jc.ErrorIsNil)
  1380  	c.Assert(results, jc.DeepEquals, params.ApplicationGetConstraintsResults{
  1381  		Results: []params.ApplicationConstraint{
  1382  			{
  1383  				Error: &params.Error{Message: `"wat" is not a valid tag`},
  1384  			}, {
  1385  				Error: &params.Error{Message: `unexpected tag type, expected application, got machine`},
  1386  			}, {
  1387  				Error: &params.Error{Message: `unexpected tag type, expected application, got user`},
  1388  			}, {
  1389  				Constraints: fooConstraints,
  1390  			}, {
  1391  				Constraints: barConstraints,
  1392  			}, {
  1393  				Error: &params.Error{Message: `application "wat" not found`, Code: "not found"},
  1394  			},
  1395  		}})
  1396  }
  1397  
  1398  func (s *applicationSuite) checkEndpoints(c *gc.C, mysqlAppName string, endpoints map[string]params.CharmRelation) {
  1399  	c.Assert(endpoints["wordpress"], gc.DeepEquals, params.CharmRelation{
  1400  		Name:      "db",
  1401  		Role:      "requirer",
  1402  		Interface: "mysql",
  1403  		Optional:  false,
  1404  		Limit:     1,
  1405  		Scope:     "global",
  1406  	})
  1407  	ep := params.CharmRelation{
  1408  		Name:      "server",
  1409  		Role:      "provider",
  1410  		Interface: "mysql",
  1411  		Scope:     "global",
  1412  	}
  1413  	// Remote applications don't use scope.
  1414  	if mysqlAppName == "hosted-mysql" {
  1415  		ep.Scope = ""
  1416  	}
  1417  	c.Assert(endpoints[mysqlAppName], gc.DeepEquals, ep)
  1418  }
  1419  
  1420  func (s *applicationSuite) setupRelationScenario(c *gc.C) {
  1421  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1422  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
  1423  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  1424  	c.Assert(err, jc.ErrorIsNil)
  1425  	_, err = s.State.AddRelation(eps...)
  1426  	c.Assert(err, jc.ErrorIsNil)
  1427  }
  1428  
  1429  func (s *applicationSuite) assertAddRelation(c *gc.C, endpoints, viaCIDRs []string) {
  1430  	s.setupRelationScenario(c)
  1431  
  1432  	res, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints, ViaCIDRs: viaCIDRs})
  1433  	c.Assert(err, jc.ErrorIsNil)
  1434  	// Show that the relation was added.
  1435  	wpApp, err := s.State.Application("wordpress")
  1436  	c.Assert(err, jc.ErrorIsNil)
  1437  	rels, err := wpApp.Relations()
  1438  	c.Assert(err, jc.ErrorIsNil)
  1439  	// There are 2 relations - the logging-wordpress one set up in the
  1440  	// scenario and the one created in this test.
  1441  	c.Assert(len(rels), gc.Equals, 2)
  1442  
  1443  	// We may be related to a local application or a remote offer
  1444  	// or an application in another model.
  1445  	var mySqlApplication state.ApplicationEntity
  1446  	mySqlApplication, err = s.State.RemoteApplication("hosted-mysql")
  1447  	if errors.IsNotFound(err) {
  1448  		mySqlApplication, err = s.State.RemoteApplication("othermysql")
  1449  		if errors.IsNotFound(err) {
  1450  			mySqlApplication, err = s.State.Application("mysql")
  1451  			c.Assert(err, jc.ErrorIsNil)
  1452  			s.checkEndpoints(c, "mysql", res.Endpoints)
  1453  		} else {
  1454  			c.Assert(err, jc.ErrorIsNil)
  1455  			s.checkEndpoints(c, "othermysql", res.Endpoints)
  1456  		}
  1457  	} else {
  1458  		c.Assert(err, jc.ErrorIsNil)
  1459  		s.checkEndpoints(c, "hosted-mysql", res.Endpoints)
  1460  	}
  1461  	c.Assert(err, jc.ErrorIsNil)
  1462  	rels, err = mySqlApplication.Relations()
  1463  	c.Assert(err, jc.ErrorIsNil)
  1464  	c.Assert(len(rels), gc.Equals, 1)
  1465  }
  1466  
  1467  func (s *applicationSuite) TestSuccessfullyAddRelation(c *gc.C) {
  1468  	endpoints := []string{"wordpress", "mysql"}
  1469  	s.assertAddRelation(c, endpoints, nil)
  1470  }
  1471  
  1472  func (s *applicationSuite) TestBlockDestroyAddRelation(c *gc.C) {
  1473  	s.BlockDestroyModel(c, "TestBlockDestroyAddRelation")
  1474  	s.assertAddRelation(c, []string{"wordpress", "mysql"}, nil)
  1475  }
  1476  func (s *applicationSuite) TestBlockRemoveAddRelation(c *gc.C) {
  1477  	s.BlockRemoveObject(c, "TestBlockRemoveAddRelation")
  1478  	s.assertAddRelation(c, []string{"wordpress", "mysql"}, nil)
  1479  }
  1480  
  1481  func (s *applicationSuite) TestBlockChangesAddRelation(c *gc.C) {
  1482  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1483  	s.BlockAllChanges(c, "TestBlockChangesAddRelation")
  1484  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: []string{"wordpress", "mysql"}})
  1485  	s.AssertBlocked(c, err, "TestBlockChangesAddRelation")
  1486  }
  1487  
  1488  func (s *applicationSuite) TestSuccessfullyAddRelationSwapped(c *gc.C) {
  1489  	// Show that the order of the applications listed in the AddRelation call
  1490  	// does not matter.  This is a repeat of the previous test with the application
  1491  	// names swapped.
  1492  	endpoints := []string{"mysql", "wordpress"}
  1493  	s.assertAddRelation(c, endpoints, nil)
  1494  }
  1495  
  1496  func (s *applicationSuite) TestCallWithOnlyOneEndpoint(c *gc.C) {
  1497  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1498  	endpoints := []string{"wordpress"}
  1499  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  1500  	c.Assert(err, gc.ErrorMatches, "no relations found")
  1501  }
  1502  
  1503  func (s *applicationSuite) TestCallWithOneEndpointTooMany(c *gc.C) {
  1504  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1505  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
  1506  	endpoints := []string{"wordpress", "mysql", "logging"}
  1507  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  1508  	c.Assert(err, gc.ErrorMatches, "cannot relate 3 endpoints")
  1509  }
  1510  
  1511  func (s *applicationSuite) TestAddAlreadyAddedRelation(c *gc.C) {
  1512  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1513  	// Add a relation between wordpress and mysql.
  1514  	endpoints := []string{"wordpress", "mysql"}
  1515  	eps, err := s.State.InferEndpoints(endpoints...)
  1516  	c.Assert(err, jc.ErrorIsNil)
  1517  	_, err = s.State.AddRelation(eps...)
  1518  	c.Assert(err, jc.ErrorIsNil)
  1519  	// And try to add it again.
  1520  	_, err = s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  1521  	c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": relation wordpress:db mysql:server`)
  1522  }
  1523  
  1524  func (s *applicationSuite) setupRemoteApplication(c *gc.C) {
  1525  	results, err := s.applicationAPI.Consume(params.ConsumeApplicationArgsV5{
  1526  		Args: []params.ConsumeApplicationArgV5{
  1527  			{ApplicationOfferDetailsV5: params.ApplicationOfferDetailsV5{
  1528  				SourceModelTag:         testing.ModelTag.String(),
  1529  				OfferName:              "hosted-mysql",
  1530  				OfferUUID:              "hosted-mysql-uuid",
  1531  				ApplicationDescription: "A pretty popular database",
  1532  				Endpoints: []params.RemoteEndpoint{
  1533  					{Name: "server", Interface: "mysql", Role: "provider"},
  1534  				},
  1535  			}},
  1536  		},
  1537  	})
  1538  	c.Assert(err, jc.ErrorIsNil)
  1539  	c.Assert(results.OneError(), gc.IsNil)
  1540  }
  1541  
  1542  func (s *applicationSuite) TestAddRemoteRelation(c *gc.C) {
  1543  	s.setupRemoteApplication(c)
  1544  	// There's already a wordpress in the scenario this assertion sets up.
  1545  	s.assertAddRelation(c, []string{"wordpress", "hosted-mysql"}, nil)
  1546  }
  1547  
  1548  func (s *applicationSuite) TestAddRemoteRelationWithRelName(c *gc.C) {
  1549  	s.setupRemoteApplication(c)
  1550  	s.assertAddRelation(c, []string{"wordpress", "hosted-mysql:server"}, nil)
  1551  }
  1552  
  1553  func (s *applicationSuite) TestAddRemoteRelationVia(c *gc.C) {
  1554  	s.setupRemoteApplication(c)
  1555  	s.assertAddRelation(c, []string{"wordpress", "hosted-mysql:server"}, []string{"192.168.0.0/16"})
  1556  
  1557  	rel, err := s.State.KeyRelation("wordpress:db hosted-mysql:server")
  1558  	c.Assert(err, jc.ErrorIsNil)
  1559  	w := rel.WatchRelationEgressNetworks()
  1560  	defer statetesting.AssertStop(c, w)
  1561  	wc := statetesting.NewStringsWatcherC(c, w)
  1562  	wc.AssertChange("192.168.0.0/16")
  1563  	wc.AssertNoChange()
  1564  }
  1565  
  1566  func (s *applicationSuite) TestAddRemoteRelationOnlyOneEndpoint(c *gc.C) {
  1567  	s.setupRemoteApplication(c)
  1568  	endpoints := []string{"hosted-mysql"}
  1569  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  1570  	c.Assert(err, gc.ErrorMatches, "no relations found")
  1571  }
  1572  
  1573  func (s *applicationSuite) TestAlreadyAddedRemoteRelation(c *gc.C) {
  1574  	s.setupRemoteApplication(c)
  1575  	endpoints := []string{"wordpress", "hosted-mysql"}
  1576  	s.assertAddRelation(c, endpoints, nil)
  1577  
  1578  	// And try to add it again.
  1579  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  1580  	c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`cannot add relation "wordpress:db hosted-mysql:server": relation wordpress:db hosted-mysql:server`))
  1581  }
  1582  
  1583  func (s *applicationSuite) TestRemoteRelationInvalidEndpoint(c *gc.C) {
  1584  	s.setupRemoteApplication(c)
  1585  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1586  
  1587  	endpoints := []string{"wordpress", "hosted-mysql:nope"}
  1588  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  1589  	c.Assert(err, gc.ErrorMatches, `saas application "hosted-mysql" has no "nope" relation`)
  1590  }
  1591  
  1592  func (s *applicationSuite) TestRemoteRelationNoMatchingEndpoint(c *gc.C) {
  1593  	results, err := s.applicationAPI.Consume(params.ConsumeApplicationArgsV5{
  1594  		Args: []params.ConsumeApplicationArgV5{
  1595  			{ApplicationOfferDetailsV5: params.ApplicationOfferDetailsV5{
  1596  				SourceModelTag: testing.ModelTag.String(),
  1597  				OfferName:      "hosted-db2",
  1598  				OfferUUID:      "hosted-db2-uuid",
  1599  				Endpoints: []params.RemoteEndpoint{
  1600  					{Name: "database", Interface: "db2", Role: "provider"},
  1601  				},
  1602  			}},
  1603  		},
  1604  	})
  1605  	c.Assert(err, jc.ErrorIsNil)
  1606  	c.Assert(results.OneError(), gc.IsNil)
  1607  
  1608  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1609  	endpoints := []string{"wordpress", "hosted-db2"}
  1610  	_, err = s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  1611  	c.Assert(err, gc.ErrorMatches, "no relations found")
  1612  }
  1613  
  1614  func (s *applicationSuite) TestRemoteRelationApplicationNotFound(c *gc.C) {
  1615  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1616  	endpoints := []string{"wordpress", "unknown"}
  1617  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  1618  	c.Assert(err, gc.ErrorMatches, `application "unknown" not found`)
  1619  }
  1620  
  1621  // addCharmToState emulates the side-effects of an AddCharm call so that the
  1622  // deploy tests in the suite can still work even though the AddCharmX calls
  1623  // have been updated to return NotSupported errors for Juju 3.
  1624  func (s *applicationSuite) addCharmToState(c *gc.C, charmURL string, name string) (string, charm.Charm) {
  1625  	curl := charm.MustParseURL(charmURL)
  1626  
  1627  	if curl.Revision < 0 {
  1628  		base := curl.String()
  1629  
  1630  		if rev, found := s.lastKnownRev[base]; found {
  1631  			curl.Revision = rev + 1
  1632  		} else {
  1633  			curl.Revision = 0
  1634  		}
  1635  
  1636  		s.lastKnownRev[base] = curl.Revision
  1637  	}
  1638  
  1639  	_, err := s.State.PrepareCharmUpload(charmURL)
  1640  	c.Assert(err, jc.ErrorIsNil)
  1641  
  1642  	ch, err := charm.ReadCharmArchive(
  1643  		testcharms.RepoWithSeries("quantal").CharmArchivePath(c.MkDir(), name))
  1644  	c.Assert(err, jc.ErrorIsNil)
  1645  
  1646  	_, err = s.State.UpdateUploadedCharm(state.CharmInfo{
  1647  		Charm:       ch,
  1648  		ID:          charmURL,
  1649  		StoragePath: fmt.Sprintf("charms/%s", name),
  1650  		SHA256:      "1234",
  1651  		Version:     ch.Version(),
  1652  	})
  1653  	c.Assert(err, jc.ErrorIsNil)
  1654  
  1655  	return charmURL, ch
  1656  }
  1657  
  1658  func (s *applicationSuite) TestValidateSecretConfig(c *gc.C) {
  1659  	chCfg := &charm.Config{
  1660  		Options: map[string]charm.Option{
  1661  			"foo": {Type: "secret"},
  1662  		},
  1663  	}
  1664  	cfg := charm.Settings{
  1665  		"foo": "bar",
  1666  	}
  1667  	err := application.ValidateSecretConfig(chCfg, cfg)
  1668  	c.Assert(err, gc.ErrorMatches, `invalid secret URI for option "foo": secret URI "bar" not valid`)
  1669  
  1670  	cfg = charm.Settings{"foo": "secret:cj4v5vm78ohs79o84r4g"}
  1671  	err = application.ValidateSecretConfig(chCfg, cfg)
  1672  	c.Assert(err, jc.ErrorIsNil)
  1673  }