github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"io/ioutil"
     9  	"regexp"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/juju/errors"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/utils"
    16  	gc "gopkg.in/check.v1"
    17  	"gopkg.in/juju/charm.v6"
    18  	charmresource "gopkg.in/juju/charm.v6/resource"
    19  	"gopkg.in/juju/charmrepo.v3"
    20  	"gopkg.in/juju/charmrepo.v3/csclient"
    21  	csparams "gopkg.in/juju/charmrepo.v3/csclient/params"
    22  	"gopkg.in/juju/names.v2"
    23  	"gopkg.in/macaroon.v2-unstable"
    24  	"gopkg.in/mgo.v2"
    25  
    26  	"github.com/juju/juju/apiserver/common"
    27  	commontesting "github.com/juju/juju/apiserver/common/testing"
    28  	"github.com/juju/juju/apiserver/facades/client/application"
    29  	"github.com/juju/juju/apiserver/params"
    30  	apiservertesting "github.com/juju/juju/apiserver/testing"
    31  	"github.com/juju/juju/core/constraints"
    32  	"github.com/juju/juju/core/instance"
    33  	"github.com/juju/juju/core/model"
    34  	"github.com/juju/juju/core/status"
    35  	jujutesting "github.com/juju/juju/juju/testing"
    36  	"github.com/juju/juju/state"
    37  	"github.com/juju/juju/state/stateenvirons"
    38  	statestorage "github.com/juju/juju/state/storage"
    39  	statetesting "github.com/juju/juju/state/testing"
    40  	"github.com/juju/juju/storage"
    41  	"github.com/juju/juju/storage/poolmanager"
    42  	"github.com/juju/juju/testcharms"
    43  	"github.com/juju/juju/testing"
    44  	"github.com/juju/juju/testing/factory"
    45  	jujuversion "github.com/juju/juju/version"
    46  )
    47  
    48  type applicationSuite struct {
    49  	jujutesting.JujuConnSuite
    50  	apiservertesting.CharmStoreSuite
    51  	commontesting.BlockHelper
    52  
    53  	applicationAPI *application.APIv9
    54  	application    *state.Application
    55  	authorizer     *apiservertesting.FakeAuthorizer
    56  }
    57  
    58  var _ = gc.Suite(&applicationSuite{})
    59  
    60  func (s *applicationSuite) SetUpSuite(c *gc.C) {
    61  	s.CharmStoreSuite.SetUpSuite(c)
    62  	s.JujuConnSuite.SetUpSuite(c)
    63  }
    64  
    65  func (s *applicationSuite) TearDownSuite(c *gc.C) {
    66  	s.CharmStoreSuite.TearDownSuite(c)
    67  	s.JujuConnSuite.TearDownSuite(c)
    68  }
    69  
    70  func (s *applicationSuite) SetUpTest(c *gc.C) {
    71  	s.JujuConnSuite.SetUpTest(c)
    72  	s.CharmStoreSuite.Session = s.JujuConnSuite.Session
    73  	s.CharmStoreSuite.SetUpTest(c)
    74  	s.BlockHelper = commontesting.NewBlockHelper(s.APIState)
    75  	s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() })
    76  
    77  	s.application = s.Factory.MakeApplication(c, nil)
    78  
    79  	s.authorizer = &apiservertesting.FakeAuthorizer{
    80  		Tag: s.AdminUserTag(c),
    81  	}
    82  	s.applicationAPI = s.makeAPI(c)
    83  }
    84  
    85  func (s *applicationSuite) TearDownTest(c *gc.C) {
    86  	s.CharmStoreSuite.TearDownTest(c)
    87  	s.JujuConnSuite.TearDownTest(c)
    88  }
    89  
    90  func (s *applicationSuite) makeAPI(c *gc.C) *application.APIv9 {
    91  	resources := common.NewResources()
    92  	c.Assert(resources.RegisterNamed("dataDir", common.StringResource(c.MkDir())), jc.ErrorIsNil)
    93  	storageAccess, err := application.GetStorageState(s.State)
    94  	c.Assert(err, jc.ErrorIsNil)
    95  	model, err := s.State.Model()
    96  	c.Assert(err, jc.ErrorIsNil)
    97  	blockChecker := common.NewBlockChecker(s.State)
    98  	registry := stateenvirons.NewStorageProviderRegistry(s.Environ)
    99  	pm := poolmanager.New(state.NewStateSettings(s.State), registry)
   100  	api, err := application.NewAPIBase(
   101  		application.GetState(s.State),
   102  		storageAccess,
   103  		s.authorizer,
   104  		blockChecker,
   105  		model.ModelTag(),
   106  		model.Type(),
   107  		model.Name(),
   108  		application.CharmToStateCharm,
   109  		application.DeployApplication,
   110  		pm,
   111  		common.NewResources(),
   112  		nil, // CAAS Broker not used in this suite.
   113  	)
   114  	c.Assert(err, jc.ErrorIsNil)
   115  	return &application.APIv9{api}
   116  }
   117  
   118  func (s *applicationSuite) TestCharmConfig(c *gc.C) {
   119  	s.setUpConfigTest(c)
   120  
   121  	// TODO (manadart 2019-02-04): When upstream methods receive a generation,
   122  	// refactor these tests to account for it.
   123  	results, err := s.applicationAPI.CharmConfig(params.ApplicationGetArgs{
   124  		Args: []params.ApplicationGet{
   125  			{ApplicationName: "foo", Generation: model.GenerationCurrent},
   126  			{ApplicationName: "bar", Generation: model.GenerationCurrent},
   127  			{ApplicationName: "wat", Generation: model.GenerationCurrent},
   128  		},
   129  	})
   130  	assertConfigTest(c, results, err, []params.ConfigResult{})
   131  }
   132  
   133  func (s *applicationSuite) TestCharmConfigV8(c *gc.C) {
   134  	s.setUpConfigTest(c)
   135  	api := &application.APIv8{APIv9: s.applicationAPI}
   136  	results, err := api.CharmConfig(params.Entities{
   137  		Entities: []params.Entity{
   138  			{"wat"}, {"machine-0"}, {"user-foo"},
   139  			{"application-foo"}, {"application-bar"}, {"application-wat"},
   140  		},
   141  	})
   142  	assertConfigTest(c, results, err, []params.ConfigResult{
   143  		{Error: &params.Error{Message: `"wat" is not a valid tag`}},
   144  		{Error: &params.Error{Message: `unexpected tag type, expected application, got machine`}},
   145  		{Error: &params.Error{Message: `unexpected tag type, expected application, got user`}},
   146  	})
   147  }
   148  
   149  func (s *applicationSuite) TestGetConfig(c *gc.C) {
   150  	s.setUpConfigTest(c)
   151  	results, err := s.applicationAPI.GetConfig(params.Entities{
   152  		Entities: []params.Entity{
   153  			{"wat"}, {"machine-0"}, {"user-foo"},
   154  			{"application-foo"}, {"application-bar"}, {"application-wat"},
   155  		},
   156  	})
   157  	assertConfigTest(c, results, err, []params.ConfigResult{
   158  		{Error: &params.Error{Message: `"wat" is not a valid tag`}},
   159  		{Error: &params.Error{Message: `unexpected tag type, expected application, got machine`}},
   160  		{Error: &params.Error{Message: `unexpected tag type, expected application, got user`}},
   161  	})
   162  }
   163  
   164  func (s *applicationSuite) setUpConfigTest(c *gc.C) {
   165  	fooConfig := map[string]interface{}{
   166  		"title":       "foo",
   167  		"skill-level": 42,
   168  	}
   169  	dummy := s.Factory.MakeCharm(c, &factory.CharmParams{
   170  		Name: "dummy",
   171  	})
   172  	s.Factory.MakeApplication(c, &factory.ApplicationParams{
   173  		Name:        "foo",
   174  		Charm:       dummy,
   175  		CharmConfig: fooConfig,
   176  	})
   177  	barConfig := map[string]interface{}{
   178  		"title":   "bar",
   179  		"outlook": "fantastic",
   180  	}
   181  	s.Factory.MakeApplication(c, &factory.ApplicationParams{
   182  		Name:        "bar",
   183  		Charm:       dummy,
   184  		CharmConfig: barConfig,
   185  	})
   186  }
   187  
   188  func assertConfigTest(c *gc.C, results params.ApplicationGetConfigResults, err error, resPrefix []params.ConfigResult) {
   189  	c.Assert(err, jc.ErrorIsNil)
   190  	c.Assert(results, jc.DeepEquals, params.ApplicationGetConfigResults{
   191  		Results: append(resPrefix, []params.ConfigResult{
   192  			{
   193  				Config: map[string]interface{}{
   194  					"outlook": map[string]interface{}{
   195  						"description": "No default outlook.",
   196  						"source":      "unset",
   197  						"type":        "string",
   198  					},
   199  					"skill-level": map[string]interface{}{
   200  						"description": "A number indicating skill.",
   201  						"source":      "user",
   202  						"type":        "int",
   203  						"value":       42,
   204  					},
   205  					"title": map[string]interface{}{
   206  						"default":     "My Title",
   207  						"description": "A descriptive title used for the application.",
   208  						"source":      "user",
   209  						"type":        "string",
   210  						"value":       "foo",
   211  					},
   212  					"username": map[string]interface{}{
   213  						"default":     "admin001",
   214  						"description": "The name of the initial account (given admin permissions).",
   215  						"source":      "default",
   216  						"type":        "string",
   217  						"value":       "admin001",
   218  					},
   219  				},
   220  			}, {
   221  				Config: map[string]interface{}{
   222  					"outlook": map[string]interface{}{
   223  						"description": "No default outlook.",
   224  						"source":      "user",
   225  						"type":        "string",
   226  						"value":       "fantastic",
   227  					},
   228  					"skill-level": map[string]interface{}{
   229  						"description": "A number indicating skill.",
   230  						"source":      "unset",
   231  						"type":        "int",
   232  					},
   233  					"title": map[string]interface{}{
   234  						"default":     "My Title",
   235  						"description": "A descriptive title used for the application.",
   236  						"source":      "user",
   237  						"type":        "string",
   238  						"value":       "bar",
   239  					},
   240  					"username": map[string]interface{}{
   241  						"default":     "admin001",
   242  						"description": "The name of the initial account (given admin permissions).",
   243  						"source":      "default",
   244  						"type":        "string",
   245  						"value":       "admin001",
   246  					},
   247  				},
   248  			}, {
   249  				Error: &params.Error{Message: `application "wat" not found`, Code: "not found"},
   250  			},
   251  		}...)})
   252  }
   253  
   254  func (s *applicationSuite) TestSetMetricCredentials(c *gc.C) {
   255  	ch := s.Factory.MakeCharm(c, &factory.CharmParams{Name: "wordpress"})
   256  	wordpress := s.Factory.MakeApplication(c, &factory.ApplicationParams{
   257  		Charm: ch,
   258  	})
   259  	tests := []struct {
   260  		about   string
   261  		args    params.ApplicationMetricCredentials
   262  		results params.ErrorResults
   263  	}{
   264  		{
   265  			"test one argument and it passes",
   266  			params.ApplicationMetricCredentials{Creds: []params.ApplicationMetricCredential{{
   267  				ApplicationName:   s.application.Name(),
   268  				MetricCredentials: []byte("creds 1234"),
   269  			}}},
   270  			params.ErrorResults{Results: []params.ErrorResult{{Error: nil}}},
   271  		},
   272  		{
   273  			"test two arguments and both pass",
   274  			params.ApplicationMetricCredentials{Creds: []params.ApplicationMetricCredential{
   275  				{
   276  					ApplicationName:   s.application.Name(),
   277  					MetricCredentials: []byte("creds 1234"),
   278  				},
   279  				{
   280  					ApplicationName:   wordpress.Name(),
   281  					MetricCredentials: []byte("creds 4567"),
   282  				},
   283  			}},
   284  			params.ErrorResults{Results: []params.ErrorResult{
   285  				{Error: nil},
   286  				{Error: nil},
   287  			}},
   288  		},
   289  		{
   290  			"test two arguments and second one fails",
   291  			params.ApplicationMetricCredentials{Creds: []params.ApplicationMetricCredential{
   292  				{
   293  					ApplicationName:   s.application.Name(),
   294  					MetricCredentials: []byte("creds 1234"),
   295  				},
   296  				{
   297  					ApplicationName:   "not-a-application",
   298  					MetricCredentials: []byte("creds 4567"),
   299  				},
   300  			}},
   301  			params.ErrorResults{Results: []params.ErrorResult{
   302  				{Error: nil},
   303  				{Error: &params.Error{Message: `application "not-a-application" not found`, Code: "not found"}},
   304  			}},
   305  		},
   306  	}
   307  	for i, t := range tests {
   308  		c.Logf("Running test %d %v", i, t.about)
   309  		results, err := s.applicationAPI.SetMetricCredentials(t.args)
   310  		c.Assert(err, jc.ErrorIsNil)
   311  		c.Assert(results.Results, gc.HasLen, len(t.results.Results))
   312  		c.Assert(results, gc.DeepEquals, t.results)
   313  
   314  		for i, a := range t.args.Creds {
   315  			if t.results.Results[i].Error == nil {
   316  				app, err := s.State.Application(a.ApplicationName)
   317  				c.Assert(err, jc.ErrorIsNil)
   318  				creds := app.MetricCredentials()
   319  				c.Assert(creds, gc.DeepEquals, a.MetricCredentials)
   320  			}
   321  		}
   322  	}
   323  }
   324  
   325  func (s *applicationSuite) TestCompatibleSettingsParsing(c *gc.C) {
   326  	// Test the exported settings parsing in a compatible way.
   327  	s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
   328  	app, err := s.State.Application("dummy")
   329  	c.Assert(err, jc.ErrorIsNil)
   330  	ch, _, err := app.Charm()
   331  	c.Assert(err, jc.ErrorIsNil)
   332  	c.Assert(ch.URL().String(), gc.Equals, "local:quantal/dummy-1")
   333  
   334  	// Empty string will be returned as nil.
   335  	options := map[string]string{
   336  		"title":    "foobar",
   337  		"username": "",
   338  	}
   339  	settings, err := application.ParseSettingsCompatible(ch.Config(), options)
   340  	c.Assert(err, jc.ErrorIsNil)
   341  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   342  		"title":    "foobar",
   343  		"username": nil,
   344  	})
   345  
   346  	// Illegal settings lead to an error.
   347  	options = map[string]string{
   348  		"yummy": "didgeridoo",
   349  	}
   350  	_, err = application.ParseSettingsCompatible(ch.Config(), options)
   351  	c.Assert(err, gc.ErrorMatches, `unknown option "yummy"`)
   352  }
   353  
   354  func (s *applicationSuite) TestApplicationDeployWithStorage(c *gc.C) {
   355  	curl, ch := s.UploadCharm(c, "utopic/storage-block-10", "storage-block")
   356  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
   357  		URL: curl.String(),
   358  	})
   359  	c.Assert(err, jc.ErrorIsNil)
   360  	storageConstraints := map[string]storage.Constraints{
   361  		"data": {
   362  			Count: 1,
   363  			Size:  1024,
   364  			Pool:  "modelscoped-block",
   365  		},
   366  	}
   367  
   368  	var cons constraints.Value
   369  	args := params.ApplicationDeploy{
   370  		ApplicationName: "application",
   371  		CharmURL:        curl.String(),
   372  		NumUnits:        1,
   373  		Constraints:     cons,
   374  		Storage:         storageConstraints,
   375  	}
   376  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   377  		Applications: []params.ApplicationDeploy{args}},
   378  	)
   379  	c.Assert(err, jc.ErrorIsNil)
   380  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   381  		Results: []params.ErrorResult{{Error: nil}},
   382  	})
   383  	app := apiservertesting.AssertPrincipalApplicationDeployed(c, s.State, "application", curl, false, ch, cons)
   384  	storageConstraintsOut, err := app.StorageConstraints()
   385  	c.Assert(err, jc.ErrorIsNil)
   386  	c.Assert(storageConstraintsOut, gc.DeepEquals, map[string]state.StorageConstraints{
   387  		"data": {
   388  			Count: 1,
   389  			Size:  1024,
   390  			Pool:  "modelscoped-block",
   391  		},
   392  		"allecto": {
   393  			Count: 0,
   394  			Size:  1024,
   395  			Pool:  "loop",
   396  		},
   397  	})
   398  }
   399  
   400  func (s *applicationSuite) TestMinJujuVersionTooHigh(c *gc.C) {
   401  	curl, _ := s.UploadCharm(c, "quantal/minjujuversion-0", "minjujuversion")
   402  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
   403  		URL: curl.String(),
   404  	})
   405  	match := fmt.Sprintf(`charm's min version (999.999.999) is higher than this juju model's version (%s)`, jujuversion.Current)
   406  	c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(match))
   407  }
   408  
   409  func (s *applicationSuite) TestApplicationDeployWithInvalidStoragePool(c *gc.C) {
   410  	curl, _ := s.UploadCharm(c, "utopic/storage-block-0", "storage-block")
   411  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
   412  		URL: curl.String(),
   413  	})
   414  	c.Assert(err, jc.ErrorIsNil)
   415  	storageConstraints := map[string]storage.Constraints{
   416  		"data": {
   417  			Pool:  "foo",
   418  			Count: 1,
   419  			Size:  1024,
   420  		},
   421  	}
   422  
   423  	var cons constraints.Value
   424  	args := params.ApplicationDeploy{
   425  		ApplicationName: "application",
   426  		CharmURL:        curl.String(),
   427  		NumUnits:        1,
   428  		Constraints:     cons,
   429  		Storage:         storageConstraints,
   430  	}
   431  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   432  		Applications: []params.ApplicationDeploy{args}},
   433  	)
   434  	c.Assert(err, jc.ErrorIsNil)
   435  	c.Assert(results.Results, gc.HasLen, 1)
   436  	c.Assert(results.Results[0].Error, gc.ErrorMatches, `.* pool "foo" not found`)
   437  }
   438  
   439  func (s *applicationSuite) TestApplicationDeployDefaultFilesystemStorage(c *gc.C) {
   440  	curl, ch := s.UploadCharm(c, "trusty/storage-filesystem-1", "storage-filesystem")
   441  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
   442  		URL: curl.String(),
   443  	})
   444  	c.Assert(err, jc.ErrorIsNil)
   445  	var cons constraints.Value
   446  	args := params.ApplicationDeploy{
   447  		ApplicationName: "application",
   448  		CharmURL:        curl.String(),
   449  		NumUnits:        1,
   450  		Constraints:     cons,
   451  	}
   452  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   453  		Applications: []params.ApplicationDeploy{args}},
   454  	)
   455  	c.Assert(err, jc.ErrorIsNil)
   456  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   457  		Results: []params.ErrorResult{{Error: nil}},
   458  	})
   459  	app := apiservertesting.AssertPrincipalApplicationDeployed(c, s.State, "application", curl, false, ch, cons)
   460  	storageConstraintsOut, err := app.StorageConstraints()
   461  	c.Assert(err, jc.ErrorIsNil)
   462  	c.Assert(storageConstraintsOut, gc.DeepEquals, map[string]state.StorageConstraints{
   463  		"data": {
   464  			Count: 1,
   465  			Size:  1024,
   466  			Pool:  "rootfs",
   467  		},
   468  	})
   469  }
   470  
   471  func (s *applicationSuite) TestApplicationDeploy(c *gc.C) {
   472  	curl, ch := s.UploadCharm(c, "precise/dummy-42", "dummy")
   473  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
   474  		URL: curl.String(),
   475  	})
   476  	c.Assert(err, jc.ErrorIsNil)
   477  	var cons constraints.Value
   478  	args := params.ApplicationDeploy{
   479  		ApplicationName: "application",
   480  		CharmURL:        curl.String(),
   481  		NumUnits:        1,
   482  		Constraints:     cons,
   483  		Placement: []*instance.Placement{
   484  			{"deadbeef-0bad-400d-8000-4b1d0d06f00d", "valid"},
   485  		},
   486  	}
   487  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   488  		Applications: []params.ApplicationDeploy{args}},
   489  	)
   490  	c.Assert(err, jc.ErrorIsNil)
   491  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   492  		Results: []params.ErrorResult{{Error: nil}},
   493  	})
   494  	app := apiservertesting.AssertPrincipalApplicationDeployed(c, s.State, "application", curl, false, ch, cons)
   495  	units, err := app.AllUnits()
   496  	c.Assert(err, jc.ErrorIsNil)
   497  	c.Assert(units, gc.HasLen, 1)
   498  
   499  	// Check that the charm cache dir is cleared out.
   500  	files, err := ioutil.ReadDir(charmrepo.CacheDir)
   501  	c.Assert(err, jc.ErrorIsNil)
   502  	c.Assert(files, gc.HasLen, 0)
   503  }
   504  
   505  func (s *applicationSuite) TestApplicationDeployWithInvalidPlacement(c *gc.C) {
   506  	curl, _ := s.UploadCharm(c, "precise/dummy-42", "dummy")
   507  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
   508  		URL: curl.String(),
   509  	})
   510  	c.Assert(err, jc.ErrorIsNil)
   511  	var cons constraints.Value
   512  	args := params.ApplicationDeploy{
   513  		ApplicationName: "application",
   514  		CharmURL:        curl.String(),
   515  		NumUnits:        1,
   516  		Constraints:     cons,
   517  		Placement: []*instance.Placement{
   518  			{"deadbeef-0bad-400d-8000-4b1d0d06f00d", "invalid"},
   519  		},
   520  	}
   521  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   522  		Applications: []params.ApplicationDeploy{args}},
   523  	)
   524  	c.Assert(err, jc.ErrorIsNil)
   525  	c.Assert(results.Results, gc.HasLen, 1)
   526  	c.Assert(results.Results[0].Error, gc.NotNil)
   527  	c.Assert(results.Results[0].Error.Error(), gc.Matches, ".* invalid placement is invalid")
   528  }
   529  
   530  func (s *applicationSuite) TestApplicationDeployWithMachinePlacementLockedError(c *gc.C) {
   531  	s.testApplicationDeployWithPlacementLockedError(c, instance.Placement{"#", "0"}, false)
   532  }
   533  
   534  func (s *applicationSuite) TestApplicationDeployWithMachineContainerPlacementLockedError(c *gc.C) {
   535  	s.testApplicationDeployWithPlacementLockedError(c, instance.Placement{"lxd", "0"}, false)
   536  }
   537  
   538  func (s *applicationSuite) TestApplicationDeployWithExtantMachineContainerLockedParentError(c *gc.C) {
   539  	s.testApplicationDeployWithPlacementLockedError(c, instance.Placement{"#", "0/lxd/0"}, true)
   540  }
   541  
   542  func (s *applicationSuite) testApplicationDeployWithPlacementLockedError(
   543  	c *gc.C, placement instance.Placement, addContainer bool,
   544  ) {
   545  	m, err := s.BackingState.AddMachine("precise", state.JobHostUnits)
   546  	c.Assert(err, jc.ErrorIsNil)
   547  
   548  	if addContainer {
   549  		template := state.MachineTemplate{
   550  			Series: "xenial",
   551  			Jobs:   []state.MachineJob{state.JobHostUnits},
   552  		}
   553  		_, err := s.State.AddMachineInsideMachine(template, m.Id(), "lxd")
   554  		c.Assert(err, jc.ErrorIsNil)
   555  	}
   556  
   557  	c.Assert(m.CreateUpgradeSeriesLock(nil, "trusty"), jc.ErrorIsNil)
   558  
   559  	curl, _ := s.UploadCharm(c, "precise/dummy-42", "dummy")
   560  	err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
   561  		URL: curl.String(),
   562  	})
   563  	c.Assert(err, jc.ErrorIsNil)
   564  	var cons constraints.Value
   565  	args := params.ApplicationDeploy{
   566  		ApplicationName: "application",
   567  		CharmURL:        curl.String(),
   568  		NumUnits:        1,
   569  		Constraints:     cons,
   570  		Placement:       []*instance.Placement{&placement},
   571  	}
   572  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   573  		Applications: []params.ApplicationDeploy{args}},
   574  	)
   575  	c.Assert(err, jc.ErrorIsNil)
   576  	c.Assert(results.Results, gc.HasLen, 1)
   577  	c.Assert(results.Results[0].Error, gc.NotNil)
   578  	c.Assert(results.Results[0].Error.Error(), gc.Matches, ".* machine is locked for series upgrade")
   579  }
   580  
   581  func (s *applicationSuite) TestApplicationDeploymentRemovesPendingResourcesOnFailure(c *gc.C) {
   582  	charm := s.AddTestingCharm(c, "dummy-resource")
   583  	resources, err := s.State.Resources()
   584  	c.Assert(err, jc.ErrorIsNil)
   585  	pendingID, err := resources.AddPendingResource("haha/borken", "user", charmresource.Resource{
   586  		Meta:   charm.Meta().Resources["dummy"],
   587  		Origin: charmresource.OriginUpload,
   588  	})
   589  	c.Assert(err, jc.ErrorIsNil)
   590  
   591  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   592  		Applications: []params.ApplicationDeploy{{
   593  			ApplicationName: "haha/borken",
   594  			NumUnits:        1,
   595  			CharmURL:        charm.URL().String(),
   596  			Resources:       map[string]string{"dummy": pendingID},
   597  		}},
   598  	})
   599  	c.Assert(err, jc.ErrorIsNil)
   600  	c.Assert(results.Results, gc.HasLen, 1)
   601  	c.Assert(results.Results[0].Error, gc.ErrorMatches, `cannot add application "haha/borken": invalid name`)
   602  
   603  	res, err := resources.ListPendingResources("haha/borken")
   604  	c.Assert(err, jc.ErrorIsNil)
   605  	c.Assert(res, gc.HasLen, 0)
   606  }
   607  
   608  func (s *applicationSuite) TestApplicationDeploymentLeavesResourcesOnSuccess(c *gc.C) {
   609  	charm := s.AddTestingCharm(c, "dummy-resource")
   610  	resources, err := s.State.Resources()
   611  	c.Assert(err, jc.ErrorIsNil)
   612  	pendingID, err := resources.AddPendingResource("unborken", "user", charmresource.Resource{
   613  		Meta:   charm.Meta().Resources["dummy"],
   614  		Origin: charmresource.OriginUpload,
   615  	})
   616  	c.Assert(err, jc.ErrorIsNil)
   617  
   618  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   619  		Applications: []params.ApplicationDeploy{{
   620  			ApplicationName: "unborken",
   621  			NumUnits:        1,
   622  			CharmURL:        charm.URL().String(),
   623  			Resources:       map[string]string{"dummy": pendingID},
   624  		}},
   625  	})
   626  	c.Assert(err, jc.ErrorIsNil)
   627  	c.Assert(results.Results, gc.HasLen, 1)
   628  	c.Assert(results.Results[0].Error, gc.IsNil)
   629  
   630  	res, err := resources.ListResources("unborken")
   631  	c.Assert(err, jc.ErrorIsNil)
   632  	c.Assert(res.Resources, gc.HasLen, 1)
   633  }
   634  
   635  func (s *applicationSuite) TestApplicationDeploymentWithTrust(c *gc.C) {
   636  	// This test should fail if the configuration parsing does not
   637  	// understand the "trust" configuration parameter
   638  	curl, ch := s.UploadCharm(c, "precise/dummy-42", "dummy")
   639  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
   640  		URL: curl.String(),
   641  	})
   642  	c.Assert(err, jc.ErrorIsNil)
   643  	var cons constraints.Value
   644  	config := map[string]string{"trust": "true"}
   645  	args := params.ApplicationDeploy{
   646  		ApplicationName: "application",
   647  		CharmURL:        curl.String(),
   648  		NumUnits:        1,
   649  		Config:          config,
   650  		Placement: []*instance.Placement{
   651  			{"deadbeef-0bad-400d-8000-4b1d0d06f00d", "valid"},
   652  		},
   653  	}
   654  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   655  		Applications: []params.ApplicationDeploy{args}},
   656  	)
   657  	c.Assert(err, jc.ErrorIsNil)
   658  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   659  		Results: []params.ErrorResult{{Error: nil}},
   660  	})
   661  
   662  	app := apiservertesting.AssertPrincipalApplicationDeployed(c, s.State, "application", curl, false, ch, cons)
   663  
   664  	appConfig, err := app.ApplicationConfig()
   665  	c.Assert(err, jc.ErrorIsNil)
   666  
   667  	trust := appConfig.GetBool("trust", false)
   668  	c.Assert(trust, jc.IsTrue)
   669  }
   670  
   671  func (s *applicationSuite) TestApplicationDeploymentNoTrust(c *gc.C) {
   672  	// This test should fail if the trust configuration setting defaults to
   673  	// anything other than "false" when no configuration parameter for trust
   674  	// is set at deployment.
   675  	curl, ch := s.UploadCharm(c, "precise/dummy-42", "dummy")
   676  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
   677  		URL: curl.String(),
   678  	})
   679  	c.Assert(err, jc.ErrorIsNil)
   680  	var cons constraints.Value
   681  	args := params.ApplicationDeploy{
   682  		ApplicationName: "application",
   683  		CharmURL:        curl.String(),
   684  		NumUnits:        1,
   685  		Placement: []*instance.Placement{
   686  			{"deadbeef-0bad-400d-8000-4b1d0d06f00d", "valid"},
   687  		},
   688  	}
   689  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   690  		Applications: []params.ApplicationDeploy{args}},
   691  	)
   692  	c.Assert(err, jc.ErrorIsNil)
   693  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   694  		Results: []params.ErrorResult{{Error: nil}},
   695  	})
   696  
   697  	app := apiservertesting.AssertPrincipalApplicationDeployed(c, s.State, "application", curl, false, ch, cons)
   698  	appConfig, err := app.ApplicationConfig()
   699  	trust := appConfig.GetBool(application.TrustConfigOptionName, true)
   700  	c.Assert(trust, jc.IsFalse)
   701  }
   702  
   703  func (s *applicationSuite) testClientApplicationsDeployWithBindings(c *gc.C, endpointBindings, expected map[string]string) {
   704  	curl, _ := s.UploadCharm(c, "utopic/riak-42", "riak")
   705  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
   706  		URL: curl.String(),
   707  	})
   708  	c.Assert(err, jc.ErrorIsNil)
   709  
   710  	var cons constraints.Value
   711  	args := params.ApplicationDeploy{
   712  		ApplicationName:  "application",
   713  		CharmURL:         curl.String(),
   714  		NumUnits:         1,
   715  		Constraints:      cons,
   716  		EndpointBindings: endpointBindings,
   717  	}
   718  
   719  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   720  		Applications: []params.ApplicationDeploy{args}},
   721  	)
   722  	c.Assert(err, jc.ErrorIsNil)
   723  	c.Assert(results.Results, gc.HasLen, 1)
   724  	c.Assert(results.Results[0].Error, gc.IsNil)
   725  
   726  	app, err := s.State.Application(args.ApplicationName)
   727  	c.Assert(err, jc.ErrorIsNil)
   728  
   729  	retrievedBindings, err := app.EndpointBindings()
   730  	c.Assert(err, jc.ErrorIsNil)
   731  	c.Assert(retrievedBindings, jc.DeepEquals, expected)
   732  }
   733  
   734  func (s *applicationSuite) TestClientApplicationsDeployWithBindings(c *gc.C) {
   735  	s.State.AddSpace("a-space", "", nil, true)
   736  	expected := map[string]string{
   737  		"endpoint": "a-space",
   738  		"ring":     "",
   739  		"admin":    "",
   740  	}
   741  	endpointBindings := map[string]string{"endpoint": "a-space"}
   742  	s.testClientApplicationsDeployWithBindings(c, endpointBindings, expected)
   743  }
   744  
   745  func (s *applicationSuite) TestClientApplicationsDeployWithDefaultBindings(c *gc.C) {
   746  	expected := map[string]string{
   747  		"endpoint": "",
   748  		"ring":     "",
   749  		"admin":    "",
   750  	}
   751  	s.testClientApplicationsDeployWithBindings(c, nil, expected)
   752  }
   753  
   754  // TODO(wallyworld) - the following charm tests have been moved from the apiserver/client
   755  // package in order to use the fake charm store testing infrastructure. They are legacy tests
   756  // written to use the api client instead of the apiserver logic. They need to be rewritten and
   757  // feature tests added.
   758  
   759  func (s *applicationSuite) TestAddCharm(c *gc.C) {
   760  	var blobs blobs
   761  	s.PatchValue(application.NewStateStorage, func(uuid string, session *mgo.Session) statestorage.Storage {
   762  		storage := statestorage.NewStorage(uuid, session)
   763  		return &recordingStorage{Storage: storage, blobs: &blobs}
   764  	})
   765  
   766  	client := s.APIState.Client()
   767  	// First test the sanity checks.
   768  	err := client.AddCharm(&charm.URL{Name: "nonsense"}, csparams.StableChannel, false)
   769  	c.Assert(err, gc.ErrorMatches, `cannot parse charm or bundle URL: ":nonsense-0"`)
   770  	err = client.AddCharm(charm.MustParseURL("local:precise/dummy"), csparams.StableChannel, false)
   771  	c.Assert(err, gc.ErrorMatches, "only charm store charm URLs are supported, with cs: schema")
   772  	err = client.AddCharm(charm.MustParseURL("cs:precise/wordpress"), csparams.StableChannel, false)
   773  	c.Assert(err, gc.ErrorMatches, "charm URL must include revision")
   774  
   775  	// Add a charm, without uploading it to storage, to
   776  	// check that AddCharm does not try to do it.
   777  	charmDir := testcharms.Repo.CharmDir("dummy")
   778  	ident := fmt.Sprintf("%s-%d", charmDir.Meta().Name, charmDir.Revision())
   779  	curl := charm.MustParseURL("cs:quantal/" + ident)
   780  	info := state.CharmInfo{
   781  		Charm:       charmDir,
   782  		ID:          curl,
   783  		StoragePath: "",
   784  		SHA256:      ident + "-sha256",
   785  	}
   786  	sch, err := s.State.AddCharm(info)
   787  	c.Assert(err, jc.ErrorIsNil)
   788  
   789  	// AddCharm should see the charm in state and not upload it.
   790  	err = client.AddCharm(sch.URL(), csparams.StableChannel, false)
   791  	c.Assert(err, jc.ErrorIsNil)
   792  
   793  	c.Assert(blobs.m, gc.HasLen, 0)
   794  
   795  	// Now try adding another charm completely.
   796  	curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress")
   797  	err = client.AddCharm(curl, csparams.StableChannel, false)
   798  	c.Assert(err, jc.ErrorIsNil)
   799  
   800  	// Verify it's in state and it got uploaded.
   801  	storage := statestorage.NewStorage(s.State.ModelUUID(), s.State.MongoSession())
   802  	sch, err = s.State.Charm(curl)
   803  	c.Assert(err, jc.ErrorIsNil)
   804  	s.assertUploaded(c, storage, sch.StoragePath(), sch.BundleSha256())
   805  }
   806  
   807  func (s *applicationSuite) TestAddCharmWithAuthorization(c *gc.C) {
   808  	// Upload a new charm to the charm store.
   809  	curl, _ := s.UploadCharm(c, "cs:~restricted/precise/wordpress-3", "wordpress")
   810  
   811  	// Change permissions on the new charm such that only bob
   812  	// can read from it.
   813  	s.DischargeUser = "restricted"
   814  	err := s.Client.Put("/"+curl.Path()+"/meta/perm/read", []string{"bob"})
   815  	c.Assert(err, jc.ErrorIsNil)
   816  
   817  	// Try to add a charm to the model without authorization.
   818  	s.DischargeUser = ""
   819  	err = s.APIState.Client().AddCharm(curl, csparams.StableChannel, false)
   820  	c.Assert(err, gc.ErrorMatches, `cannot retrieve charm "cs:~restricted/precise/wordpress-3": cannot get archive: cannot get discharge from "https://.*": third party refused discharge: cannot discharge: discharge denied \(unauthorized access\)`)
   821  
   822  	tryAs := func(user string) error {
   823  		client := csclient.New(csclient.Params{
   824  			URL: s.Srv.URL,
   825  		})
   826  		s.DischargeUser = user
   827  		var m *macaroon.Macaroon
   828  		err = client.Get("/delegatable-macaroon", &m)
   829  		c.Assert(err, gc.IsNil)
   830  
   831  		return application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
   832  			URL:     curl.String(),
   833  			Channel: string(csparams.StableChannel),
   834  		})
   835  	}
   836  	// Try again with authorization for the wrong user.
   837  	err = tryAs("joe")
   838  	c.Assert(err, gc.ErrorMatches, `cannot retrieve charm "cs:~restricted/precise/wordpress-3": cannot get archive: access denied for user "joe"`)
   839  
   840  	// Try again with the correct authorization this time.
   841  	err = tryAs("bob")
   842  	c.Assert(err, gc.IsNil)
   843  
   844  	// Verify that it has actually been uploaded.
   845  	_, err = s.State.Charm(curl)
   846  	c.Assert(err, gc.IsNil)
   847  }
   848  
   849  func (s *applicationSuite) TestAddCharmConcurrently(c *gc.C) {
   850  	c.Skip("see lp:1596960 -- bad test for bad code")
   851  
   852  	var putBarrier sync.WaitGroup
   853  	var blobs blobs
   854  	s.PatchValue(application.NewStateStorage, func(uuid string, session *mgo.Session) statestorage.Storage {
   855  		storage := statestorage.NewStorage(uuid, session)
   856  		return &recordingStorage{Storage: storage, blobs: &blobs, putBarrier: &putBarrier}
   857  	})
   858  
   859  	client := s.APIState.Client()
   860  	curl, _ := s.UploadCharm(c, "trusty/wordpress-3", "wordpress")
   861  
   862  	// Try adding the same charm concurrently from multiple goroutines
   863  	// to test no "duplicate key errors" are reported (see lp bug
   864  	// #1067979) and also at the end only one charm document is
   865  	// created.
   866  
   867  	var wg sync.WaitGroup
   868  	// We don't add them 1-by-1 because that would allow each goroutine to
   869  	// finish separately without actually synchronizing between them
   870  	putBarrier.Add(10)
   871  	for i := 0; i < 10; i++ {
   872  		wg.Add(1)
   873  		go func(index int) {
   874  			defer wg.Done()
   875  
   876  			c.Assert(client.AddCharm(curl, csparams.StableChannel, false), gc.IsNil, gc.Commentf("goroutine %d", index))
   877  			sch, err := s.State.Charm(curl)
   878  			c.Assert(err, gc.IsNil, gc.Commentf("goroutine %d", index))
   879  			c.Assert(sch.URL(), jc.DeepEquals, curl, gc.Commentf("goroutine %d", index))
   880  		}(i)
   881  	}
   882  	wg.Wait()
   883  
   884  	blobs.Lock()
   885  
   886  	c.Assert(blobs.m, gc.HasLen, 10)
   887  
   888  	// Verify there is only a single uploaded charm remains and it
   889  	// contains the correct data.
   890  	sch, err := s.State.Charm(curl)
   891  	c.Assert(err, jc.ErrorIsNil)
   892  	storagePath := sch.StoragePath()
   893  	c.Assert(blobs.m[storagePath], jc.IsTrue)
   894  	for path, exists := range blobs.m {
   895  		if path != storagePath {
   896  			c.Assert(exists, jc.IsFalse)
   897  		}
   898  	}
   899  
   900  	storage := statestorage.NewStorage(s.State.ModelUUID(), s.State.MongoSession())
   901  	s.assertUploaded(c, storage, sch.StoragePath(), sch.BundleSha256())
   902  }
   903  
   904  func (s *applicationSuite) assertUploaded(c *gc.C, storage statestorage.Storage, storagePath, expectedSHA256 string) {
   905  	reader, _, err := storage.Get(storagePath)
   906  	c.Assert(err, jc.ErrorIsNil)
   907  	defer reader.Close()
   908  	downloadedSHA256, _, err := utils.ReadSHA256(reader)
   909  	c.Assert(err, jc.ErrorIsNil)
   910  	c.Assert(downloadedSHA256, gc.Equals, expectedSHA256)
   911  }
   912  
   913  func (s *applicationSuite) TestAddCharmOverwritesPlaceholders(c *gc.C) {
   914  	client := s.APIState.Client()
   915  	curl, _ := s.UploadCharm(c, "trusty/wordpress-42", "wordpress")
   916  
   917  	// Add a placeholder with the same charm URL.
   918  	err := s.State.AddStoreCharmPlaceholder(curl)
   919  	c.Assert(err, jc.ErrorIsNil)
   920  	_, err = s.State.Charm(curl)
   921  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   922  
   923  	// Now try to add the charm, which will convert the placeholder to
   924  	// a pending charm.
   925  	err = client.AddCharm(curl, csparams.StableChannel, false)
   926  	c.Assert(err, jc.ErrorIsNil)
   927  
   928  	// Make sure the document's flags were reset as expected.
   929  	sch, err := s.State.Charm(curl)
   930  	c.Assert(err, jc.ErrorIsNil)
   931  	c.Assert(sch.URL(), jc.DeepEquals, curl)
   932  	c.Assert(sch.IsPlaceholder(), jc.IsFalse)
   933  	c.Assert(sch.IsUploaded(), jc.IsTrue)
   934  }
   935  
   936  func (s *applicationSuite) TestApplicationGetCharmURL(c *gc.C) {
   937  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   938  	result, err := s.applicationAPI.GetCharmURL(params.ApplicationGet{ApplicationName: "wordpress"})
   939  	c.Assert(err, jc.ErrorIsNil)
   940  	c.Assert(result.Error, gc.IsNil)
   941  	c.Assert(result.Result, gc.Equals, "local:quantal/wordpress-3")
   942  }
   943  
   944  func (s *applicationSuite) TestApplicationSetCharm(c *gc.C) {
   945  	curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy")
   946  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
   947  		URL: curl.String(),
   948  	})
   949  	c.Assert(err, jc.ErrorIsNil)
   950  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   951  		Applications: []params.ApplicationDeploy{{
   952  			CharmURL:        curl.String(),
   953  			ApplicationName: "application",
   954  			NumUnits:        3,
   955  		}}})
   956  	c.Assert(err, jc.ErrorIsNil)
   957  	c.Assert(results.Results, gc.HasLen, 1)
   958  	c.Assert(results.Results[0].Error, gc.IsNil)
   959  	curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress")
   960  	err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
   961  		URL: curl.String(),
   962  	})
   963  	c.Assert(err, jc.ErrorIsNil)
   964  	err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{
   965  		ApplicationName: "application",
   966  		CharmURL:        curl.String(),
   967  	})
   968  	c.Assert(err, jc.ErrorIsNil)
   969  
   970  	// Ensure that the charm is not marked as forced.
   971  	app, err := s.State.Application("application")
   972  	c.Assert(err, jc.ErrorIsNil)
   973  	charm, force, err := app.Charm()
   974  	c.Assert(err, jc.ErrorIsNil)
   975  	c.Assert(charm.URL().String(), gc.Equals, curl.String())
   976  	c.Assert(force, jc.IsFalse)
   977  }
   978  
   979  func (s *applicationSuite) setupApplicationSetCharm(c *gc.C) {
   980  	curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy")
   981  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
   982  		URL: curl.String(),
   983  	})
   984  	c.Assert(err, jc.ErrorIsNil)
   985  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   986  		Applications: []params.ApplicationDeploy{{
   987  			CharmURL:        curl.String(),
   988  			ApplicationName: "application",
   989  			NumUnits:        3,
   990  		}}})
   991  	c.Assert(err, jc.ErrorIsNil)
   992  	c.Assert(results.Results, gc.HasLen, 1)
   993  	c.Assert(results.Results[0].Error, gc.IsNil)
   994  	curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress")
   995  	err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
   996  		URL: curl.String(),
   997  	})
   998  	c.Assert(err, jc.ErrorIsNil)
   999  }
  1000  
  1001  func (s *applicationSuite) assertApplicationSetCharm(c *gc.C, forceUnits bool) {
  1002  	err := s.applicationAPI.SetCharm(params.ApplicationSetCharm{
  1003  		ApplicationName: "application",
  1004  		CharmURL:        "cs:~who/precise/wordpress-3",
  1005  		ForceUnits:      forceUnits,
  1006  	})
  1007  	c.Assert(err, jc.ErrorIsNil)
  1008  	// Ensure that the charm is not marked as forced.
  1009  	app, err := s.State.Application("application")
  1010  	c.Assert(err, jc.ErrorIsNil)
  1011  	charm, _, err := app.Charm()
  1012  	c.Assert(err, jc.ErrorIsNil)
  1013  	c.Assert(charm.URL().String(), gc.Equals, "cs:~who/precise/wordpress-3")
  1014  }
  1015  
  1016  func (s *applicationSuite) assertApplicationSetCharmBlocked(c *gc.C, msg string) {
  1017  	err := s.applicationAPI.SetCharm(params.ApplicationSetCharm{
  1018  		ApplicationName: "application",
  1019  		CharmURL:        "cs:~who/precise/wordpress-3",
  1020  	})
  1021  	s.AssertBlocked(c, err, msg)
  1022  }
  1023  
  1024  func (s *applicationSuite) TestBlockDestroyApplicationSetCharm(c *gc.C) {
  1025  	s.setupApplicationSetCharm(c)
  1026  	s.BlockDestroyModel(c, "TestBlockDestroyApplicationSetCharm")
  1027  	s.assertApplicationSetCharm(c, false)
  1028  }
  1029  
  1030  func (s *applicationSuite) TestBlockRemoveApplicationSetCharm(c *gc.C) {
  1031  	s.setupApplicationSetCharm(c)
  1032  	s.BlockRemoveObject(c, "TestBlockRemoveApplicationSetCharm")
  1033  	s.assertApplicationSetCharm(c, false)
  1034  }
  1035  
  1036  func (s *applicationSuite) TestBlockChangesApplicationSetCharm(c *gc.C) {
  1037  	s.setupApplicationSetCharm(c)
  1038  	s.BlockAllChanges(c, "TestBlockChangesApplicationSetCharm")
  1039  	s.assertApplicationSetCharmBlocked(c, "TestBlockChangesApplicationSetCharm")
  1040  }
  1041  
  1042  func (s *applicationSuite) TestApplicationSetCharmForceUnits(c *gc.C) {
  1043  	curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy")
  1044  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1045  		URL: curl.String(),
  1046  	})
  1047  	c.Assert(err, jc.ErrorIsNil)
  1048  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1049  		Applications: []params.ApplicationDeploy{{
  1050  			CharmURL:        curl.String(),
  1051  			ApplicationName: "application",
  1052  			NumUnits:        3,
  1053  		}}})
  1054  	c.Assert(err, jc.ErrorIsNil)
  1055  	c.Assert(results.Results, gc.HasLen, 1)
  1056  	c.Assert(results.Results[0].Error, gc.IsNil)
  1057  	curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress")
  1058  	err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1059  		URL: curl.String(),
  1060  	})
  1061  	c.Assert(err, jc.ErrorIsNil)
  1062  	err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{
  1063  		ApplicationName: "application",
  1064  		CharmURL:        curl.String(),
  1065  		ForceUnits:      true,
  1066  	})
  1067  	c.Assert(err, jc.ErrorIsNil)
  1068  
  1069  	// Ensure that the charm is marked as forced.
  1070  	app, err := s.State.Application("application")
  1071  	c.Assert(err, jc.ErrorIsNil)
  1072  	charm, force, err := app.Charm()
  1073  	c.Assert(err, jc.ErrorIsNil)
  1074  	c.Assert(charm.URL().String(), gc.Equals, curl.String())
  1075  	c.Assert(force, jc.IsTrue)
  1076  }
  1077  
  1078  func (s *applicationSuite) TestBlockApplicationSetCharmForce(c *gc.C) {
  1079  	s.setupApplicationSetCharm(c)
  1080  
  1081  	// block all changes
  1082  	s.BlockAllChanges(c, "TestBlockApplicationSetCharmForce")
  1083  	s.BlockRemoveObject(c, "TestBlockApplicationSetCharmForce")
  1084  	s.BlockDestroyModel(c, "TestBlockApplicationSetCharmForce")
  1085  
  1086  	s.assertApplicationSetCharm(c, true)
  1087  }
  1088  
  1089  func (s *applicationSuite) TestApplicationSetCharmInvalidApplication(c *gc.C) {
  1090  	err := s.applicationAPI.SetCharm(params.ApplicationSetCharm{
  1091  		ApplicationName: "badapplication",
  1092  		CharmURL:        "cs:precise/wordpress-3",
  1093  		ForceSeries:     true,
  1094  		ForceUnits:      true,
  1095  	})
  1096  	c.Assert(err, gc.ErrorMatches, `application "badapplication" not found`)
  1097  }
  1098  
  1099  func (s *applicationSuite) TestApplicationAddCharmErrors(c *gc.C) {
  1100  	for url, expect := range map[string]string{
  1101  		"wordpress":                   "charm URL must include revision",
  1102  		"cs:wordpress":                "charm URL must include revision",
  1103  		"cs:precise/wordpress":        "charm URL must include revision",
  1104  		"cs:precise/wordpress-999999": `cannot retrieve "cs:precise/wordpress-999999": charm not found`,
  1105  	} {
  1106  		c.Logf("test %s", url)
  1107  		err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1108  			URL: url,
  1109  		})
  1110  		c.Check(err, gc.ErrorMatches, expect)
  1111  	}
  1112  }
  1113  
  1114  func (s *applicationSuite) TestApplicationSetCharmLegacy(c *gc.C) {
  1115  	curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy")
  1116  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1117  		URL: curl.String(),
  1118  	})
  1119  	c.Assert(err, jc.ErrorIsNil)
  1120  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1121  		Applications: []params.ApplicationDeploy{{
  1122  			CharmURL:        curl.String(),
  1123  			ApplicationName: "application",
  1124  		}}})
  1125  	c.Assert(err, jc.ErrorIsNil)
  1126  	c.Assert(results.Results, gc.HasLen, 1)
  1127  	c.Assert(results.Results[0].Error, gc.IsNil)
  1128  	curl, _ = s.UploadCharm(c, "trusty/dummy-1", "dummy")
  1129  	err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1130  		URL: curl.String(),
  1131  	})
  1132  	c.Assert(err, jc.ErrorIsNil)
  1133  
  1134  	// Even with forceSeries = true, we can't change a charm where
  1135  	// the series is specified in the URL.
  1136  	err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{
  1137  		ApplicationName: "application",
  1138  		CharmURL:        curl.String(),
  1139  		ForceSeries:     true,
  1140  	})
  1141  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "cs:~who/trusty/dummy-1": cannot change an application's series`)
  1142  }
  1143  
  1144  func (s *applicationSuite) TestApplicationSetCharmUnsupportedSeries(c *gc.C) {
  1145  	curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series")
  1146  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1147  		URL: curl.String(),
  1148  	})
  1149  	c.Assert(err, jc.ErrorIsNil)
  1150  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1151  		Applications: []params.ApplicationDeploy{{
  1152  			CharmURL:        curl.String(),
  1153  			ApplicationName: "application",
  1154  			Series:          "precise",
  1155  		}}})
  1156  	c.Assert(err, jc.ErrorIsNil)
  1157  	c.Assert(results.Results, gc.HasLen, 1)
  1158  	c.Assert(results.Results[0].Error, gc.IsNil)
  1159  	curl, _ = s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series2")
  1160  	err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1161  		URL: curl.String(),
  1162  	})
  1163  	c.Assert(err, jc.ErrorIsNil)
  1164  
  1165  	err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{
  1166  		ApplicationName: "application",
  1167  		CharmURL:        curl.String(),
  1168  	})
  1169  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "cs:~who/multi-series-1": only these series are supported: trusty, wily`)
  1170  }
  1171  
  1172  func (s *applicationSuite) assertApplicationSetCharmSeries(c *gc.C, upgradeCharm, series string) {
  1173  	curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series")
  1174  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1175  		URL: curl.String(),
  1176  	})
  1177  	c.Assert(err, jc.ErrorIsNil)
  1178  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1179  		Applications: []params.ApplicationDeploy{{
  1180  			CharmURL:        curl.String(),
  1181  			ApplicationName: "application",
  1182  			Series:          "precise",
  1183  		}}})
  1184  	c.Assert(err, jc.ErrorIsNil)
  1185  	c.Assert(results.Results, gc.HasLen, 1)
  1186  	c.Assert(results.Results[0].Error, gc.IsNil)
  1187  
  1188  	url := upgradeCharm
  1189  	if series != "" {
  1190  		url = series + "/" + upgradeCharm
  1191  	}
  1192  	curl, _ = s.UploadCharmMultiSeries(c, "~who/"+url, upgradeCharm)
  1193  	err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1194  		URL: curl.String(),
  1195  	})
  1196  	c.Assert(err, jc.ErrorIsNil)
  1197  
  1198  	err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{
  1199  		ApplicationName: "application",
  1200  		CharmURL:        curl.String(),
  1201  		ForceSeries:     true,
  1202  	})
  1203  	c.Assert(err, jc.ErrorIsNil)
  1204  	app, err := s.State.Application("application")
  1205  	c.Assert(err, jc.ErrorIsNil)
  1206  	ch, _, err := app.Charm()
  1207  	c.Assert(err, jc.ErrorIsNil)
  1208  	c.Assert(ch.URL().String(), gc.Equals, "cs:~who/"+url+"-0")
  1209  }
  1210  
  1211  func (s *applicationSuite) TestApplicationSetCharmUnsupportedSeriesForce(c *gc.C) {
  1212  	s.assertApplicationSetCharmSeries(c, "multi-series2", "")
  1213  }
  1214  
  1215  func (s *applicationSuite) TestApplicationSetCharmNoExplicitSupportedSeries(c *gc.C) {
  1216  	s.assertApplicationSetCharmSeries(c, "dummy", "precise")
  1217  }
  1218  
  1219  func (s *applicationSuite) TestApplicationSetCharmWrongOS(c *gc.C) {
  1220  	curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series")
  1221  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1222  		URL: curl.String(),
  1223  	})
  1224  	c.Assert(err, jc.ErrorIsNil)
  1225  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1226  		Applications: []params.ApplicationDeploy{{
  1227  			CharmURL:        curl.String(),
  1228  			ApplicationName: "application",
  1229  			Series:          "precise",
  1230  		}}})
  1231  	c.Assert(err, jc.ErrorIsNil)
  1232  	c.Assert(results.Results, gc.HasLen, 1)
  1233  	c.Assert(results.Results[0].Error, gc.IsNil)
  1234  	curl, _ = s.UploadCharmMultiSeries(c, "~who/multi-series-windows", "multi-series-windows")
  1235  	err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1236  		URL: curl.String(),
  1237  	})
  1238  	c.Assert(err, jc.ErrorIsNil)
  1239  
  1240  	err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{
  1241  		ApplicationName: "application",
  1242  		CharmURL:        curl.String(),
  1243  		ForceSeries:     true,
  1244  	})
  1245  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "cs:~who/multi-series-windows-0": OS "Ubuntu" not supported by charm`)
  1246  }
  1247  
  1248  type testModeCharmRepo struct {
  1249  	*charmrepo.CharmStore
  1250  	testMode bool
  1251  }
  1252  
  1253  // WithTestMode returns a repository Interface where test mode is enabled.
  1254  func (s *testModeCharmRepo) WithTestMode() charmrepo.Interface {
  1255  	s.testMode = true
  1256  	return s.CharmStore.WithTestMode()
  1257  }
  1258  
  1259  func (s *applicationSuite) TestSpecializeStoreOnDeployApplicationSetCharmAndAddCharm(c *gc.C) {
  1260  	repo := &testModeCharmRepo{}
  1261  	s.PatchValue(&csclient.ServerURL, s.Srv.URL)
  1262  	newCharmStoreRepo := application.NewCharmStoreRepo
  1263  	s.PatchValue(&application.NewCharmStoreRepo, func(c *csclient.Client) charmrepo.Interface {
  1264  		repo.CharmStore = newCharmStoreRepo(c).(*charmrepo.CharmStore)
  1265  		return repo
  1266  	})
  1267  	attrs := map[string]interface{}{"test-mode": true}
  1268  	err := s.Model.UpdateModelConfig(attrs, nil)
  1269  	c.Assert(err, jc.ErrorIsNil)
  1270  
  1271  	// Check that the store's test mode is enabled when calling application Deploy.
  1272  	curl, _ := s.UploadCharm(c, "trusty/dummy-1", "dummy")
  1273  	err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1274  		URL: curl.String(),
  1275  	})
  1276  	c.Assert(err, jc.ErrorIsNil)
  1277  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1278  		Applications: []params.ApplicationDeploy{{
  1279  			CharmURL:        curl.String(),
  1280  			ApplicationName: "application",
  1281  			NumUnits:        3,
  1282  		}}})
  1283  	c.Assert(err, jc.ErrorIsNil)
  1284  	c.Assert(results.Results, gc.HasLen, 1)
  1285  	c.Assert(results.Results[0].Error, gc.IsNil)
  1286  	c.Assert(repo.testMode, jc.IsTrue)
  1287  
  1288  	// Check that the store's test mode is enabled when calling SetCharm.
  1289  	curl, _ = s.UploadCharm(c, "trusty/wordpress-2", "wordpress")
  1290  	err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{
  1291  		ApplicationName: "application",
  1292  		CharmURL:        curl.String(),
  1293  	})
  1294  	c.Assert(repo.testMode, jc.IsTrue)
  1295  
  1296  	// Check that the store's test mode is enabled when calling AddCharm.
  1297  	curl, _ = s.UploadCharm(c, "utopic/riak-42", "riak")
  1298  	err = s.APIState.Client().AddCharm(curl, csparams.StableChannel, false)
  1299  	c.Assert(err, jc.ErrorIsNil)
  1300  	c.Assert(repo.testMode, jc.IsTrue)
  1301  }
  1302  
  1303  func (s *applicationSuite) setupApplicationDeploy(c *gc.C, args string) (*charm.URL, charm.Charm, constraints.Value) {
  1304  	curl, ch := s.UploadCharm(c, "precise/dummy-42", "dummy")
  1305  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1306  		URL: curl.String(),
  1307  	})
  1308  	c.Assert(err, jc.ErrorIsNil)
  1309  	cons := constraints.MustParse(args)
  1310  	return curl, ch, cons
  1311  }
  1312  
  1313  func (s *applicationSuite) assertApplicationDeployPrincipal(c *gc.C, curl *charm.URL, ch charm.Charm, mem4g constraints.Value) {
  1314  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1315  		Applications: []params.ApplicationDeploy{{
  1316  			CharmURL:        curl.String(),
  1317  			ApplicationName: "application",
  1318  			NumUnits:        3,
  1319  			Constraints:     mem4g,
  1320  		}}})
  1321  	c.Assert(err, jc.ErrorIsNil)
  1322  	c.Assert(results.Results, gc.HasLen, 1)
  1323  	c.Assert(results.Results[0].Error, gc.IsNil)
  1324  	apiservertesting.AssertPrincipalApplicationDeployed(c, s.State, "application", curl, false, ch, mem4g)
  1325  }
  1326  
  1327  func (s *applicationSuite) assertApplicationDeployPrincipalBlocked(c *gc.C, msg string, curl *charm.URL, mem4g constraints.Value) {
  1328  	_, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1329  		Applications: []params.ApplicationDeploy{{
  1330  			CharmURL:        curl.String(),
  1331  			ApplicationName: "application",
  1332  			NumUnits:        3,
  1333  			Constraints:     mem4g,
  1334  		}}})
  1335  	s.AssertBlocked(c, err, msg)
  1336  }
  1337  
  1338  func (s *applicationSuite) TestBlockDestroyApplicationDeployPrincipal(c *gc.C) {
  1339  	curl, bundle, cons := s.setupApplicationDeploy(c, "mem=4G")
  1340  	s.BlockDestroyModel(c, "TestBlockDestroyApplicationDeployPrincipal")
  1341  	s.assertApplicationDeployPrincipal(c, curl, bundle, cons)
  1342  }
  1343  
  1344  func (s *applicationSuite) TestBlockRemoveApplicationDeployPrincipal(c *gc.C) {
  1345  	curl, bundle, cons := s.setupApplicationDeploy(c, "mem=4G")
  1346  	s.BlockRemoveObject(c, "TestBlockRemoveApplicationDeployPrincipal")
  1347  	s.assertApplicationDeployPrincipal(c, curl, bundle, cons)
  1348  }
  1349  
  1350  func (s *applicationSuite) TestBlockChangesApplicationDeployPrincipal(c *gc.C) {
  1351  	curl, _, cons := s.setupApplicationDeploy(c, "mem=4G")
  1352  	s.BlockAllChanges(c, "TestBlockChangesApplicationDeployPrincipal")
  1353  	s.assertApplicationDeployPrincipalBlocked(c, "TestBlockChangesApplicationDeployPrincipal", curl, cons)
  1354  }
  1355  
  1356  func (s *applicationSuite) TestApplicationDeploySubordinate(c *gc.C) {
  1357  	curl, ch := s.UploadCharm(c, "utopic/logging-47", "logging")
  1358  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1359  		URL: curl.String(),
  1360  	})
  1361  	c.Assert(err, jc.ErrorIsNil)
  1362  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1363  		Applications: []params.ApplicationDeploy{{
  1364  			CharmURL:        curl.String(),
  1365  			ApplicationName: "application-name",
  1366  		}}})
  1367  	c.Assert(err, jc.ErrorIsNil)
  1368  	c.Assert(results.Results, gc.HasLen, 1)
  1369  	c.Assert(results.Results[0].Error, gc.IsNil)
  1370  
  1371  	app, err := s.State.Application("application-name")
  1372  	c.Assert(err, jc.ErrorIsNil)
  1373  	charm, force, err := app.Charm()
  1374  	c.Assert(err, jc.ErrorIsNil)
  1375  	c.Assert(force, jc.IsFalse)
  1376  	c.Assert(charm.URL(), gc.DeepEquals, curl)
  1377  	c.Assert(charm.Meta(), gc.DeepEquals, ch.Meta())
  1378  	c.Assert(charm.Config(), gc.DeepEquals, ch.Config())
  1379  
  1380  	units, err := app.AllUnits()
  1381  	c.Assert(err, jc.ErrorIsNil)
  1382  	c.Assert(units, gc.HasLen, 0)
  1383  }
  1384  
  1385  func (s *applicationSuite) combinedSettings(ch *state.Charm, inSettings charm.Settings) charm.Settings {
  1386  	result := ch.Config().DefaultSettings()
  1387  	for name, value := range inSettings {
  1388  		result[name] = value
  1389  	}
  1390  	return result
  1391  }
  1392  
  1393  func (s *applicationSuite) TestApplicationDeployConfig(c *gc.C) {
  1394  	curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy")
  1395  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1396  		URL: curl.String(),
  1397  	})
  1398  	c.Assert(err, jc.ErrorIsNil)
  1399  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1400  		Applications: []params.ApplicationDeploy{{
  1401  			CharmURL:        curl.String(),
  1402  			ApplicationName: "application-name",
  1403  			NumUnits:        1,
  1404  			ConfigYAML:      "application-name:\n  username: fred",
  1405  		}}})
  1406  	c.Assert(err, jc.ErrorIsNil)
  1407  	c.Assert(results.Results, gc.HasLen, 1)
  1408  	c.Assert(results.Results[0].Error, gc.IsNil)
  1409  
  1410  	app, err := s.State.Application("application-name")
  1411  	c.Assert(err, jc.ErrorIsNil)
  1412  	settings, err := app.CharmConfig()
  1413  	c.Assert(err, jc.ErrorIsNil)
  1414  	ch, _, err := app.Charm()
  1415  	c.Assert(err, jc.ErrorIsNil)
  1416  	c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{"username": "fred"}))
  1417  }
  1418  
  1419  func (s *applicationSuite) TestApplicationDeployConfigError(c *gc.C) {
  1420  	// TODO(fwereade): test Config/ConfigYAML handling directly on srvClient.
  1421  	// Can't be done cleanly until it's extracted similarly to Machiner.
  1422  	curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy")
  1423  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1424  		URL: curl.String(),
  1425  	})
  1426  	c.Assert(err, jc.ErrorIsNil)
  1427  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1428  		Applications: []params.ApplicationDeploy{{
  1429  			CharmURL:        curl.String(),
  1430  			ApplicationName: "application-name",
  1431  			NumUnits:        1,
  1432  			ConfigYAML:      "application-name:\n  skill-level: fred",
  1433  		}}})
  1434  	c.Assert(err, jc.ErrorIsNil)
  1435  	c.Assert(results.Results, gc.HasLen, 1)
  1436  	c.Assert(results.Results[0].Error, gc.ErrorMatches, `option "skill-level" expected int, got "fred"`)
  1437  	_, err = s.State.Application("application-name")
  1438  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1439  }
  1440  
  1441  func (s *applicationSuite) TestApplicationDeployToMachine(c *gc.C) {
  1442  	curl, ch := s.UploadCharm(c, "precise/dummy-0", "dummy")
  1443  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1444  		URL: curl.String(),
  1445  	})
  1446  	c.Assert(err, jc.ErrorIsNil)
  1447  
  1448  	machine, err := s.State.AddMachine("precise", state.JobHostUnits)
  1449  	c.Assert(err, jc.ErrorIsNil)
  1450  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1451  		Applications: []params.ApplicationDeploy{{
  1452  			CharmURL:        curl.String(),
  1453  			ApplicationName: "application-name",
  1454  			NumUnits:        1,
  1455  			ConfigYAML:      "application-name:\n  username: fred",
  1456  		}}})
  1457  	c.Assert(err, jc.ErrorIsNil)
  1458  	c.Assert(results.Results, gc.HasLen, 1)
  1459  	c.Assert(results.Results[0].Error, gc.IsNil)
  1460  
  1461  	app, err := s.State.Application("application-name")
  1462  	c.Assert(err, jc.ErrorIsNil)
  1463  	charm, force, err := app.Charm()
  1464  	c.Assert(err, jc.ErrorIsNil)
  1465  	c.Assert(force, jc.IsFalse)
  1466  	c.Assert(charm.URL(), gc.DeepEquals, curl)
  1467  	c.Assert(charm.Meta(), gc.DeepEquals, ch.Meta())
  1468  	c.Assert(charm.Config(), gc.DeepEquals, ch.Config())
  1469  
  1470  	errs, err := s.APIState.UnitAssigner().AssignUnits([]names.UnitTag{names.NewUnitTag("application-name/0")})
  1471  	c.Assert(errs, gc.DeepEquals, []error{nil})
  1472  	c.Assert(err, jc.ErrorIsNil)
  1473  
  1474  	units, err := app.AllUnits()
  1475  	c.Assert(err, jc.ErrorIsNil)
  1476  	c.Assert(units, gc.HasLen, 1)
  1477  
  1478  	mid, err := units[0].AssignedMachineId()
  1479  	c.Assert(err, jc.ErrorIsNil)
  1480  	c.Assert(mid, gc.Equals, machine.Id())
  1481  }
  1482  
  1483  func (s *applicationSuite) TestApplicationDeployToMachineWithLXDProfile(c *gc.C) {
  1484  	curl, ch := s.UploadCharm(c, "quantal/lxd-profile-0", "lxd-profile")
  1485  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1486  		URL: curl.String(),
  1487  	})
  1488  	c.Assert(err, jc.ErrorIsNil)
  1489  
  1490  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
  1491  	c.Assert(err, jc.ErrorIsNil)
  1492  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1493  		Applications: []params.ApplicationDeploy{{
  1494  			CharmURL:        curl.String(),
  1495  			ApplicationName: "application-name",
  1496  			NumUnits:        1,
  1497  		}}})
  1498  	c.Assert(err, jc.ErrorIsNil)
  1499  	c.Assert(results.Results, gc.HasLen, 1)
  1500  	c.Assert(results.Results[0].Error, gc.IsNil)
  1501  
  1502  	application, err := s.State.Application("application-name")
  1503  	c.Assert(err, jc.ErrorIsNil)
  1504  	expected, force, err := application.Charm()
  1505  	c.Assert(err, jc.ErrorIsNil)
  1506  	c.Assert(force, jc.IsFalse)
  1507  	c.Assert(expected.URL(), gc.DeepEquals, curl)
  1508  	c.Assert(expected.Meta(), gc.DeepEquals, ch.Meta())
  1509  	c.Assert(expected.Config(), gc.DeepEquals, ch.Config())
  1510  	c.Assert(expected.LXDProfile(), gc.DeepEquals, ch.(charm.LXDProfiler).LXDProfile())
  1511  
  1512  	errs, err := s.APIState.UnitAssigner().AssignUnits([]names.UnitTag{names.NewUnitTag("application-name/0")})
  1513  	c.Assert(errs, gc.DeepEquals, []error{nil})
  1514  	c.Assert(err, jc.ErrorIsNil)
  1515  
  1516  	units, err := application.AllUnits()
  1517  	c.Assert(err, jc.ErrorIsNil)
  1518  	c.Assert(units, gc.HasLen, 1)
  1519  
  1520  	mid, err := units[0].AssignedMachineId()
  1521  	c.Assert(err, jc.ErrorIsNil)
  1522  	c.Assert(mid, gc.Equals, machine.Id())
  1523  }
  1524  
  1525  func (s *applicationSuite) TestApplicationDeployToMachineWithInvalidLXDProfile(c *gc.C) {
  1526  	curl, _ := s.UploadCharm(c, "quantal/lxd-profile-fail-0", "lxd-profile-fail")
  1527  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1528  		URL: curl.String(),
  1529  	})
  1530  	c.Assert(err, gc.ErrorMatches, `.*invalid lxd-profile.yaml: contains device type "unix-disk"`)
  1531  }
  1532  
  1533  func (s *applicationSuite) TestApplicationDeployToMachineWithInvalidLXDProfileAndForceStillSucceeds(c *gc.C) {
  1534  	curl, ch := s.UploadCharm(c, "quantal/lxd-profile-fail-0", "lxd-profile-fail")
  1535  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1536  		URL:   curl.String(),
  1537  		Force: true,
  1538  	})
  1539  	c.Assert(err, jc.ErrorIsNil)
  1540  
  1541  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
  1542  	c.Assert(err, jc.ErrorIsNil)
  1543  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1544  		Applications: []params.ApplicationDeploy{{
  1545  			CharmURL:        curl.String(),
  1546  			ApplicationName: "application-name",
  1547  			NumUnits:        1,
  1548  		}}})
  1549  	c.Assert(err, jc.ErrorIsNil)
  1550  	c.Assert(results.Results, gc.HasLen, 1)
  1551  	c.Assert(results.Results[0].Error, gc.IsNil)
  1552  
  1553  	app, err := s.State.Application("application-name")
  1554  	c.Assert(err, jc.ErrorIsNil)
  1555  	expected, force, err := app.Charm()
  1556  	c.Assert(err, jc.ErrorIsNil)
  1557  	c.Assert(force, jc.IsFalse)
  1558  	c.Assert(expected.URL(), gc.DeepEquals, curl)
  1559  	c.Assert(expected.Meta(), gc.DeepEquals, ch.Meta())
  1560  	c.Assert(expected.Config(), gc.DeepEquals, ch.Config())
  1561  	c.Assert(expected.LXDProfile(), gc.DeepEquals, ch.(charm.LXDProfiler).LXDProfile())
  1562  
  1563  	errs, err := s.APIState.UnitAssigner().AssignUnits([]names.UnitTag{names.NewUnitTag("application-name/0")})
  1564  	c.Assert(errs, gc.DeepEquals, []error{nil})
  1565  	c.Assert(err, jc.ErrorIsNil)
  1566  
  1567  	units, err := app.AllUnits()
  1568  	c.Assert(err, jc.ErrorIsNil)
  1569  	c.Assert(units, gc.HasLen, 1)
  1570  
  1571  	mid, err := units[0].AssignedMachineId()
  1572  	c.Assert(err, jc.ErrorIsNil)
  1573  	c.Assert(mid, gc.Equals, machine.Id())
  1574  }
  1575  
  1576  func (s *applicationSuite) TestApplicationDeployToMachineNotFound(c *gc.C) {
  1577  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1578  		Applications: []params.ApplicationDeploy{{
  1579  			CharmURL:        "cs:precise/application-name-1",
  1580  			ApplicationName: "application-name",
  1581  			NumUnits:        1,
  1582  			Placement:       []*instance.Placement{instance.MustParsePlacement("42")},
  1583  		}}})
  1584  	c.Assert(err, jc.ErrorIsNil)
  1585  	c.Assert(results.Results, gc.HasLen, 1)
  1586  	c.Assert(results.Results[0].Error, gc.ErrorMatches, `cannot deploy "application-name" to machine 42: machine 42 not found`)
  1587  
  1588  	_, err = s.State.Application("application-name")
  1589  	c.Assert(err, gc.ErrorMatches, `application "application-name" not found`)
  1590  }
  1591  
  1592  func (s *applicationSuite) deployApplicationForUpdateTests(c *gc.C) {
  1593  	curl, _ := s.UploadCharm(c, "precise/dummy-1", "dummy")
  1594  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1595  		URL: curl.String(),
  1596  	})
  1597  	c.Assert(err, jc.ErrorIsNil)
  1598  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1599  		Applications: []params.ApplicationDeploy{{
  1600  			CharmURL:        curl.String(),
  1601  			ApplicationName: "application",
  1602  			NumUnits:        1,
  1603  		}}})
  1604  	c.Assert(err, jc.ErrorIsNil)
  1605  	c.Assert(results.Results, gc.HasLen, 1)
  1606  	c.Assert(results.Results[0].Error, gc.IsNil)
  1607  }
  1608  
  1609  func (s *applicationSuite) checkClientApplicationUpdateSetCharm(c *gc.C, forceCharmURL bool) {
  1610  	s.deployApplicationForUpdateTests(c)
  1611  	curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress")
  1612  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1613  		URL: curl.String(),
  1614  	})
  1615  	c.Assert(err, jc.ErrorIsNil)
  1616  
  1617  	// Update the charm for the application.
  1618  	args := params.ApplicationUpdate{
  1619  		ApplicationName: "application",
  1620  		CharmURL:        curl.String(),
  1621  		ForceCharmURL:   forceCharmURL,
  1622  	}
  1623  	err = s.applicationAPI.Update(args)
  1624  	c.Assert(err, jc.ErrorIsNil)
  1625  
  1626  	// Ensure the charm has been updated and and the force flag correctly set.
  1627  	app, err := s.State.Application("application")
  1628  	c.Assert(err, jc.ErrorIsNil)
  1629  	ch, force, err := app.Charm()
  1630  	c.Assert(err, jc.ErrorIsNil)
  1631  	c.Assert(ch.URL().String(), gc.Equals, curl.String())
  1632  	c.Assert(force, gc.Equals, forceCharmURL)
  1633  }
  1634  
  1635  func (s *applicationSuite) TestApplicationUpdateSetCharm(c *gc.C) {
  1636  	s.checkClientApplicationUpdateSetCharm(c, false)
  1637  }
  1638  
  1639  func (s *applicationSuite) TestBlockDestroyApplicationUpdate(c *gc.C) {
  1640  	s.BlockDestroyModel(c, "TestBlockDestroyApplicationUpdate")
  1641  	s.checkClientApplicationUpdateSetCharm(c, false)
  1642  }
  1643  
  1644  func (s *applicationSuite) TestBlockRemoveApplicationUpdate(c *gc.C) {
  1645  	s.BlockRemoveObject(c, "TestBlockRemoveApplicationUpdate")
  1646  	s.checkClientApplicationUpdateSetCharm(c, false)
  1647  }
  1648  
  1649  func (s *applicationSuite) setupApplicationUpdate(c *gc.C) string {
  1650  	s.deployApplicationForUpdateTests(c)
  1651  	curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress")
  1652  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1653  		URL: curl.String(),
  1654  	})
  1655  	c.Assert(err, jc.ErrorIsNil)
  1656  	return curl.String()
  1657  }
  1658  
  1659  func (s *applicationSuite) TestBlockChangeApplicationUpdate(c *gc.C) {
  1660  	curl := s.setupApplicationUpdate(c)
  1661  	s.BlockAllChanges(c, "TestBlockChangeApplicationUpdate")
  1662  	// Update the charm for the application.
  1663  	args := params.ApplicationUpdate{
  1664  		ApplicationName: "application",
  1665  		CharmURL:        curl,
  1666  		ForceCharmURL:   false,
  1667  	}
  1668  	err := s.applicationAPI.Update(args)
  1669  	s.AssertBlocked(c, err, "TestBlockChangeApplicationUpdate")
  1670  }
  1671  
  1672  func (s *applicationSuite) TestApplicationUpdateForceSetCharm(c *gc.C) {
  1673  	s.checkClientApplicationUpdateSetCharm(c, true)
  1674  }
  1675  
  1676  func (s *applicationSuite) TestBlockApplicationUpdateForced(c *gc.C) {
  1677  	curl := s.setupApplicationUpdate(c)
  1678  
  1679  	// block all changes. Force should ignore block :)
  1680  	s.BlockAllChanges(c, "TestBlockApplicationUpdateForced")
  1681  	s.BlockDestroyModel(c, "TestBlockApplicationUpdateForced")
  1682  	s.BlockRemoveObject(c, "TestBlockApplicationUpdateForced")
  1683  
  1684  	// Update the charm for the application.
  1685  	args := params.ApplicationUpdate{
  1686  		ApplicationName: "application",
  1687  		CharmURL:        curl,
  1688  		ForceCharmURL:   true,
  1689  	}
  1690  	err := s.applicationAPI.Update(args)
  1691  	c.Assert(err, jc.ErrorIsNil)
  1692  
  1693  	// Ensure the charm has been updated and and the force flag correctly set.
  1694  	app, err := s.State.Application("application")
  1695  	c.Assert(err, jc.ErrorIsNil)
  1696  	ch, force, err := app.Charm()
  1697  	c.Assert(err, jc.ErrorIsNil)
  1698  	c.Assert(ch.URL().String(), gc.Equals, curl)
  1699  	c.Assert(force, jc.IsTrue)
  1700  }
  1701  
  1702  func (s *applicationSuite) TestApplicationUpdateSetCharmNotFound(c *gc.C) {
  1703  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1704  	args := params.ApplicationUpdate{
  1705  		ApplicationName: "wordpress",
  1706  		CharmURL:        "cs:precise/wordpress-999999",
  1707  	}
  1708  	err := s.applicationAPI.Update(args)
  1709  	c.Check(err, gc.ErrorMatches, `charm "cs:precise/wordpress-999999" not found`)
  1710  }
  1711  
  1712  func (s *applicationSuite) TestApplicationUpdateSetMinUnits(c *gc.C) {
  1713  	app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1714  
  1715  	// Set minimum units for the application.
  1716  	minUnits := 2
  1717  	args := params.ApplicationUpdate{
  1718  		ApplicationName: "dummy",
  1719  		MinUnits:        &minUnits,
  1720  	}
  1721  	err := s.applicationAPI.Update(args)
  1722  	c.Assert(err, jc.ErrorIsNil)
  1723  
  1724  	// Ensure the minimum number of units has been set.
  1725  	c.Assert(app.Refresh(), gc.IsNil)
  1726  	c.Assert(app.MinUnits(), gc.Equals, minUnits)
  1727  }
  1728  
  1729  func (s *applicationSuite) TestApplicationUpdateSetMinUnitsWithLXDProfile(c *gc.C) {
  1730  	app := s.AddTestingApplication(c, "lxd-profile", s.AddTestingCharm(c, "lxd-profile"))
  1731  
  1732  	// Set minimum units for the application.
  1733  	minUnits := 2
  1734  	args := params.ApplicationUpdate{
  1735  		ApplicationName: "lxd-profile",
  1736  		MinUnits:        &minUnits,
  1737  	}
  1738  	err := s.applicationAPI.Update(args)
  1739  	c.Assert(err, jc.ErrorIsNil)
  1740  
  1741  	// Ensure the minimum number of units has been set.
  1742  	c.Assert(app.Refresh(), gc.IsNil)
  1743  	c.Assert(app.MinUnits(), gc.Equals, minUnits)
  1744  }
  1745  
  1746  func (s *applicationSuite) TestApplicationUpdateDoesNotSetMinUnitsWithLXDProfile(c *gc.C) {
  1747  	series := "quantal"
  1748  	repo := testcharms.RepoForSeries(series)
  1749  	ch := repo.CharmDir("lxd-profile-fail")
  1750  	ident := fmt.Sprintf("%s-%d", ch.Meta().Name, ch.Revision())
  1751  	curl := charm.MustParseURL(fmt.Sprintf("local:%s/%s", series, ident))
  1752  	storerepo, err := charmrepo.InferRepository(
  1753  		curl,
  1754  		charmrepo.NewCharmStoreParams{},
  1755  		repo.Path())
  1756  	c.Assert(err, jc.ErrorIsNil)
  1757  	_, err = jujutesting.PutCharm(s.State, curl, storerepo, false, false)
  1758  	c.Assert(err, gc.ErrorMatches, `invalid lxd-profile.yaml: contains device type "unix-disk"`)
  1759  }
  1760  
  1761  func (s *applicationSuite) TestApplicationUpdateSetMinUnitsError(c *gc.C) {
  1762  	app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1763  
  1764  	// Set a negative minimum number of units for the application.
  1765  	minUnits := -1
  1766  	args := params.ApplicationUpdate{
  1767  		ApplicationName: "dummy",
  1768  		MinUnits:        &minUnits,
  1769  	}
  1770  	err := s.applicationAPI.Update(args)
  1771  	c.Assert(err, gc.ErrorMatches,
  1772  		`cannot set minimum units for application "dummy": cannot set a negative minimum number of units`)
  1773  
  1774  	// Ensure the minimum number of units has not been set.
  1775  	c.Assert(app.Refresh(), gc.IsNil)
  1776  	c.Assert(app.MinUnits(), gc.Equals, 0)
  1777  }
  1778  
  1779  func (s *applicationSuite) TestApplicationUpdateSetSettingsStrings(c *gc.C) {
  1780  	ch := s.AddTestingCharm(c, "dummy")
  1781  	app := s.AddTestingApplication(c, "dummy", ch)
  1782  
  1783  	// Update settings for the application.
  1784  	args := params.ApplicationUpdate{
  1785  		ApplicationName: "dummy",
  1786  		SettingsStrings: map[string]string{"title": "s-title", "username": "s-user"},
  1787  	}
  1788  	err := s.applicationAPI.Update(args)
  1789  	c.Assert(err, jc.ErrorIsNil)
  1790  
  1791  	// Ensure the settings have been correctly updated.
  1792  	expected := charm.Settings{"title": "s-title", "username": "s-user"}
  1793  	obtained, err := app.CharmConfig()
  1794  	c.Assert(err, jc.ErrorIsNil)
  1795  	c.Assert(obtained, gc.DeepEquals, s.combinedSettings(ch, expected))
  1796  }
  1797  
  1798  func (s *applicationSuite) TestApplicationUpdateSetSettingsYAML(c *gc.C) {
  1799  	ch := s.AddTestingCharm(c, "dummy")
  1800  	app := s.AddTestingApplication(c, "dummy", ch)
  1801  
  1802  	// Update settings for the application.
  1803  	args := params.ApplicationUpdate{
  1804  		ApplicationName: "dummy",
  1805  		SettingsYAML:    "dummy:\n  title: y-title\n  username: y-user",
  1806  	}
  1807  	err := s.applicationAPI.Update(args)
  1808  	c.Assert(err, jc.ErrorIsNil)
  1809  
  1810  	// Ensure the settings have been correctly updated.
  1811  	expected := charm.Settings{"title": "y-title", "username": "y-user"}
  1812  	obtained, err := app.CharmConfig()
  1813  	c.Assert(err, jc.ErrorIsNil)
  1814  	c.Assert(obtained, gc.DeepEquals, s.combinedSettings(ch, expected))
  1815  }
  1816  
  1817  func (s *applicationSuite) TestClientApplicationUpdateSetSettingsGetYAML(c *gc.C) {
  1818  	ch := s.AddTestingCharm(c, "dummy")
  1819  	app := s.AddTestingApplication(c, "dummy", ch)
  1820  
  1821  	// Update settings for the application.
  1822  	args := params.ApplicationUpdate{
  1823  		ApplicationName: "dummy",
  1824  		SettingsYAML:    "charm: dummy\napplication: dummy\nsettings:\n  title:\n    value: y-title\n    type: string\n  username:\n    value: y-user\n  ignore:\n    blah: true",
  1825  	}
  1826  	err := s.applicationAPI.Update(args)
  1827  	c.Assert(err, jc.ErrorIsNil)
  1828  
  1829  	// Ensure the settings have been correctly updated.
  1830  	expected := charm.Settings{"title": "y-title", "username": "y-user"}
  1831  	obtained, err := app.CharmConfig()
  1832  	c.Assert(err, jc.ErrorIsNil)
  1833  	c.Assert(obtained, gc.DeepEquals, s.combinedSettings(ch, expected))
  1834  }
  1835  
  1836  func (s *applicationSuite) TestApplicationUpdateSetConstraints(c *gc.C) {
  1837  	app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1838  
  1839  	// Update constraints for the application.
  1840  	cons, err := constraints.Parse("mem=4096", "cores=2")
  1841  	c.Assert(err, jc.ErrorIsNil)
  1842  	args := params.ApplicationUpdate{
  1843  		ApplicationName: "dummy",
  1844  		Constraints:     &cons,
  1845  	}
  1846  	err = s.applicationAPI.Update(args)
  1847  	c.Assert(err, jc.ErrorIsNil)
  1848  
  1849  	// Ensure the constraints have been correctly updated.
  1850  	obtained, err := app.Constraints()
  1851  	c.Assert(err, jc.ErrorIsNil)
  1852  	c.Assert(obtained, gc.DeepEquals, cons)
  1853  }
  1854  
  1855  func (s *applicationSuite) TestApplicationUpdateAllParams(c *gc.C) {
  1856  	s.deployApplicationForUpdateTests(c)
  1857  	curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress")
  1858  	err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{
  1859  		URL: curl.String(),
  1860  	})
  1861  	c.Assert(err, jc.ErrorIsNil)
  1862  
  1863  	// Update all the application attributes.
  1864  	minUnits := 3
  1865  	cons, err := constraints.Parse("mem=4096", "cores=2")
  1866  	c.Assert(err, jc.ErrorIsNil)
  1867  	args := params.ApplicationUpdate{
  1868  		ApplicationName: "application",
  1869  		CharmURL:        curl.String(),
  1870  		ForceCharmURL:   true,
  1871  		MinUnits:        &minUnits,
  1872  		SettingsStrings: map[string]string{"blog-title": "string-title"},
  1873  		SettingsYAML:    "application:\n  blog-title: yaml-title\n",
  1874  		Constraints:     &cons,
  1875  	}
  1876  	err = s.applicationAPI.Update(args)
  1877  	c.Assert(err, jc.ErrorIsNil)
  1878  
  1879  	// Ensure the application has been correctly updated.
  1880  	app, err := s.State.Application("application")
  1881  	c.Assert(err, jc.ErrorIsNil)
  1882  
  1883  	// Check the charm.
  1884  	ch, force, err := app.Charm()
  1885  	c.Assert(err, jc.ErrorIsNil)
  1886  	c.Assert(ch.URL().String(), gc.Equals, curl.String())
  1887  	c.Assert(force, jc.IsTrue)
  1888  
  1889  	// Check the minimum number of units.
  1890  	c.Assert(app.MinUnits(), gc.Equals, minUnits)
  1891  
  1892  	// Check the settings: also ensure the YAML settings take precedence
  1893  	// over strings ones.
  1894  	expectedSettings := charm.Settings{"blog-title": "yaml-title"}
  1895  	obtainedSettings, err := app.CharmConfig()
  1896  	c.Assert(err, jc.ErrorIsNil)
  1897  	c.Assert(obtainedSettings, gc.DeepEquals, expectedSettings)
  1898  
  1899  	// Check the constraints.
  1900  	obtainedConstraints, err := app.Constraints()
  1901  	c.Assert(err, jc.ErrorIsNil)
  1902  	c.Assert(obtainedConstraints, gc.DeepEquals, cons)
  1903  }
  1904  
  1905  func (s *applicationSuite) TestApplicationUpdateNoParams(c *gc.C) {
  1906  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1907  
  1908  	// Calling Update with no parameters set is a no-op.
  1909  	args := params.ApplicationUpdate{ApplicationName: "wordpress"}
  1910  	err := s.applicationAPI.Update(args)
  1911  	c.Assert(err, jc.ErrorIsNil)
  1912  }
  1913  
  1914  func (s *applicationSuite) TestApplicationUpdateNoApplication(c *gc.C) {
  1915  	err := s.applicationAPI.Update(params.ApplicationUpdate{})
  1916  	c.Assert(err, gc.ErrorMatches, `"" is not a valid application name`)
  1917  }
  1918  
  1919  func (s *applicationSuite) TestApplicationUpdateInvalidApplication(c *gc.C) {
  1920  	args := params.ApplicationUpdate{ApplicationName: "no-such-application"}
  1921  	err := s.applicationAPI.Update(args)
  1922  	c.Assert(err, gc.ErrorMatches, `application "no-such-application" not found`)
  1923  }
  1924  
  1925  var (
  1926  	validSetTestValue = "a value with spaces\nand newline\nand UTF-8 characters: \U0001F604 / \U0001F44D"
  1927  )
  1928  
  1929  func (s *applicationSuite) TestApplicationSet(c *gc.C) {
  1930  	ch := s.AddTestingCharm(c, "dummy")
  1931  	dummy := s.AddTestingApplication(c, "dummy", ch)
  1932  
  1933  	err := s.applicationAPI.Set(params.ApplicationSet{ApplicationName: "dummy", Options: map[string]string{
  1934  		"title":    "foobar",
  1935  		"username": validSetTestValue,
  1936  	}})
  1937  	c.Assert(err, jc.ErrorIsNil)
  1938  	settings, err := dummy.CharmConfig()
  1939  	c.Assert(err, jc.ErrorIsNil)
  1940  	c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{
  1941  		"title":    "foobar",
  1942  		"username": validSetTestValue,
  1943  	}))
  1944  
  1945  	err = s.applicationAPI.Set(params.ApplicationSet{ApplicationName: "dummy", Options: map[string]string{
  1946  		"title":    "barfoo",
  1947  		"username": "",
  1948  	}})
  1949  	c.Assert(err, jc.ErrorIsNil)
  1950  	settings, err = dummy.CharmConfig()
  1951  	c.Assert(err, jc.ErrorIsNil)
  1952  	c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{
  1953  		"title":    "barfoo",
  1954  		"username": "",
  1955  	}))
  1956  }
  1957  
  1958  func (s *applicationSuite) assertApplicationSetBlocked(c *gc.C, dummy *state.Application, msg string) {
  1959  	err := s.applicationAPI.Set(params.ApplicationSet{
  1960  		ApplicationName: "dummy",
  1961  		Options: map[string]string{
  1962  			"title":    "foobar",
  1963  			"username": validSetTestValue}})
  1964  	s.AssertBlocked(c, err, msg)
  1965  }
  1966  
  1967  func (s *applicationSuite) assertApplicationSet(c *gc.C, dummy *state.Application) {
  1968  	err := s.applicationAPI.Set(params.ApplicationSet{
  1969  		ApplicationName: "dummy",
  1970  		Options: map[string]string{
  1971  			"title":    "foobar",
  1972  			"username": validSetTestValue}})
  1973  	c.Assert(err, jc.ErrorIsNil)
  1974  	settings, err := dummy.CharmConfig()
  1975  	c.Assert(err, jc.ErrorIsNil)
  1976  	ch, _, err := dummy.Charm()
  1977  	c.Assert(err, jc.ErrorIsNil)
  1978  	c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{
  1979  		"title":    "foobar",
  1980  		"username": validSetTestValue,
  1981  	}))
  1982  }
  1983  
  1984  func (s *applicationSuite) TestBlockDestroyApplicationSet(c *gc.C) {
  1985  	dummy := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1986  	s.BlockDestroyModel(c, "TestBlockDestroyApplicationSet")
  1987  	s.assertApplicationSet(c, dummy)
  1988  }
  1989  
  1990  func (s *applicationSuite) TestBlockRemoveApplicationSet(c *gc.C) {
  1991  	dummy := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1992  	s.BlockRemoveObject(c, "TestBlockRemoveApplicationSet")
  1993  	s.assertApplicationSet(c, dummy)
  1994  }
  1995  
  1996  func (s *applicationSuite) TestBlockChangesApplicationSet(c *gc.C) {
  1997  	dummy := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1998  	s.BlockAllChanges(c, "TestBlockChangesApplicationSet")
  1999  	s.assertApplicationSetBlocked(c, dummy, "TestBlockChangesApplicationSet")
  2000  }
  2001  
  2002  func (s *applicationSuite) TestServerUnset(c *gc.C) {
  2003  	ch := s.AddTestingCharm(c, "dummy")
  2004  	dummy := s.AddTestingApplication(c, "dummy", ch)
  2005  
  2006  	err := s.applicationAPI.Set(params.ApplicationSet{ApplicationName: "dummy", Options: map[string]string{
  2007  		"title":    "foobar",
  2008  		"username": "user name",
  2009  	}})
  2010  	c.Assert(err, jc.ErrorIsNil)
  2011  	settings, err := dummy.CharmConfig()
  2012  	c.Assert(err, jc.ErrorIsNil)
  2013  	c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{
  2014  		"title":    "foobar",
  2015  		"username": "user name",
  2016  	}))
  2017  
  2018  	err = s.applicationAPI.Unset(params.ApplicationUnset{ApplicationName: "dummy", Options: []string{"username"}})
  2019  	c.Assert(err, jc.ErrorIsNil)
  2020  	settings, err = dummy.CharmConfig()
  2021  	c.Assert(err, jc.ErrorIsNil)
  2022  	c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{
  2023  		"title": "foobar",
  2024  	}))
  2025  }
  2026  
  2027  func (s *applicationSuite) setupServerUnsetBlocked(c *gc.C) *state.Application {
  2028  	dummy := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  2029  
  2030  	err := s.applicationAPI.Set(params.ApplicationSet{
  2031  		ApplicationName: "dummy",
  2032  		Options: map[string]string{
  2033  			"title":    "foobar",
  2034  			"username": "user name",
  2035  		}})
  2036  	c.Assert(err, jc.ErrorIsNil)
  2037  	settings, err := dummy.CharmConfig()
  2038  	c.Assert(err, jc.ErrorIsNil)
  2039  	ch, _, err := dummy.Charm()
  2040  	c.Assert(err, jc.ErrorIsNil)
  2041  	c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{
  2042  		"title":    "foobar",
  2043  		"username": "user name",
  2044  	}))
  2045  	return dummy
  2046  }
  2047  
  2048  func (s *applicationSuite) assertServerUnset(c *gc.C, dummy *state.Application) {
  2049  	err := s.applicationAPI.Unset(params.ApplicationUnset{
  2050  		ApplicationName: "dummy",
  2051  		Options:         []string{"username"},
  2052  	})
  2053  	c.Assert(err, jc.ErrorIsNil)
  2054  	settings, err := dummy.CharmConfig()
  2055  	c.Assert(err, jc.ErrorIsNil)
  2056  	ch, _, err := dummy.Charm()
  2057  	c.Assert(err, jc.ErrorIsNil)
  2058  	c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{
  2059  		"title": "foobar",
  2060  	}))
  2061  }
  2062  
  2063  func (s *applicationSuite) assertServerUnsetBlocked(c *gc.C, dummy *state.Application, msg string) {
  2064  	err := s.applicationAPI.Unset(params.ApplicationUnset{
  2065  		ApplicationName: "dummy",
  2066  		Options:         []string{"username"},
  2067  	})
  2068  	s.AssertBlocked(c, err, msg)
  2069  }
  2070  
  2071  func (s *applicationSuite) TestBlockDestroyServerUnset(c *gc.C) {
  2072  	dummy := s.setupServerUnsetBlocked(c)
  2073  	s.BlockDestroyModel(c, "TestBlockDestroyServerUnset")
  2074  	s.assertServerUnset(c, dummy)
  2075  }
  2076  
  2077  func (s *applicationSuite) TestBlockRemoveServerUnset(c *gc.C) {
  2078  	dummy := s.setupServerUnsetBlocked(c)
  2079  	s.BlockRemoveObject(c, "TestBlockRemoveServerUnset")
  2080  	s.assertServerUnset(c, dummy)
  2081  }
  2082  
  2083  func (s *applicationSuite) TestBlockChangesServerUnset(c *gc.C) {
  2084  	dummy := s.setupServerUnsetBlocked(c)
  2085  	s.BlockAllChanges(c, "TestBlockChangesServerUnset")
  2086  	s.assertServerUnsetBlocked(c, dummy, "TestBlockChangesServerUnset")
  2087  }
  2088  
  2089  var clientAddApplicationUnitsTests = []struct {
  2090  	about       string
  2091  	application string // if not set, defaults to 'dummy'
  2092  	numUnits    int
  2093  	expected    []string
  2094  	to          string
  2095  	err         string
  2096  }{
  2097  	{
  2098  		about:    "returns unit names",
  2099  		numUnits: 3,
  2100  		expected: []string{"dummy/0", "dummy/1", "dummy/2"},
  2101  	},
  2102  	{
  2103  		about: "fails trying to add zero units",
  2104  		err:   "must add at least one unit",
  2105  	},
  2106  	{
  2107  		// Note: chained-state, we add 1 unit here, but the 3 units
  2108  		// from the first condition still exist
  2109  		about:    "force the unit onto bootstrap machine",
  2110  		numUnits: 1,
  2111  		expected: []string{"dummy/3"},
  2112  		to:       "0",
  2113  	},
  2114  	{
  2115  		about:       "unknown application name",
  2116  		application: "unknown-application",
  2117  		numUnits:    1,
  2118  		err:         `application "unknown-application" not found`,
  2119  	},
  2120  }
  2121  
  2122  func (s *applicationSuite) TestClientAddApplicationUnits(c *gc.C) {
  2123  	s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  2124  	for i, t := range clientAddApplicationUnitsTests {
  2125  		c.Logf("test %d. %s", i, t.about)
  2126  		applicationName := t.application
  2127  		if applicationName == "" {
  2128  			applicationName = "dummy"
  2129  		}
  2130  		args := params.AddApplicationUnits{
  2131  			ApplicationName: applicationName,
  2132  			NumUnits:        t.numUnits,
  2133  		}
  2134  		if t.to != "" {
  2135  			args.Placement = []*instance.Placement{instance.MustParsePlacement(t.to)}
  2136  		}
  2137  		result, err := s.applicationAPI.AddUnits(args)
  2138  		if t.err != "" {
  2139  			c.Assert(err, gc.ErrorMatches, t.err)
  2140  			continue
  2141  		}
  2142  		c.Assert(err, jc.ErrorIsNil)
  2143  		c.Assert(result.Units, gc.DeepEquals, t.expected)
  2144  	}
  2145  	// Test that we actually assigned the unit to machine 0
  2146  	forcedUnit, err := s.BackingState.Unit("dummy/3")
  2147  	c.Assert(err, jc.ErrorIsNil)
  2148  	assignedMachine, err := forcedUnit.AssignedMachineId()
  2149  	c.Assert(err, jc.ErrorIsNil)
  2150  	c.Assert(assignedMachine, gc.Equals, "0")
  2151  }
  2152  
  2153  func (s *applicationSuite) TestAddApplicationUnitsToNewContainer(c *gc.C) {
  2154  	app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  2155  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
  2156  	c.Assert(err, jc.ErrorIsNil)
  2157  
  2158  	_, err = s.applicationAPI.AddUnits(params.AddApplicationUnits{
  2159  		ApplicationName: "dummy",
  2160  		NumUnits:        1,
  2161  		Placement:       []*instance.Placement{instance.MustParsePlacement("lxd:" + machine.Id())},
  2162  	})
  2163  	c.Assert(err, jc.ErrorIsNil)
  2164  
  2165  	units, err := app.AllUnits()
  2166  	c.Assert(err, jc.ErrorIsNil)
  2167  	mid, err := units[0].AssignedMachineId()
  2168  	c.Assert(err, jc.ErrorIsNil)
  2169  	c.Assert(mid, gc.Equals, machine.Id()+"/lxd/0")
  2170  }
  2171  
  2172  var addApplicationUnitTests = []struct {
  2173  	about       string
  2174  	application string // if not set, defaults to 'dummy'
  2175  	expected    []string
  2176  	machineIds  []string
  2177  	placement   []*instance.Placement
  2178  	err         string
  2179  }{
  2180  	{
  2181  		about:      "valid placement directives",
  2182  		expected:   []string{"dummy/0"},
  2183  		placement:  []*instance.Placement{{"deadbeef-0bad-400d-8000-4b1d0d06f00d", "valid"}},
  2184  		machineIds: []string{"1"},
  2185  	}, {
  2186  		about:      "direct machine assignment placement directive",
  2187  		expected:   []string{"dummy/1", "dummy/2"},
  2188  		placement:  []*instance.Placement{{"#", "1"}, {"lxd", "1"}},
  2189  		machineIds: []string{"1", "1/lxd/0"},
  2190  	}, {
  2191  		about:     "invalid placement directive",
  2192  		err:       ".* invalid placement is invalid",
  2193  		expected:  []string{"dummy/3"},
  2194  		placement: []*instance.Placement{{"deadbeef-0bad-400d-8000-4b1d0d06f00d", "invalid"}},
  2195  	},
  2196  }
  2197  
  2198  func (s *applicationSuite) TestAddApplicationUnits(c *gc.C) {
  2199  	s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  2200  	// Add a machine for the units to be placed on.
  2201  	_, err := s.State.AddMachine("quantal", state.JobHostUnits)
  2202  	c.Assert(err, jc.ErrorIsNil)
  2203  	for i, t := range addApplicationUnitTests {
  2204  		c.Logf("test %d. %s", i, t.about)
  2205  		applicationName := t.application
  2206  		if applicationName == "" {
  2207  			applicationName = "dummy"
  2208  		}
  2209  		result, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{
  2210  			ApplicationName: applicationName,
  2211  			NumUnits:        len(t.expected),
  2212  			Placement:       t.placement,
  2213  		})
  2214  		if t.err != "" {
  2215  			c.Assert(err, gc.ErrorMatches, t.err)
  2216  			continue
  2217  		}
  2218  		c.Assert(err, jc.ErrorIsNil)
  2219  		c.Assert(result.Units, gc.DeepEquals, t.expected)
  2220  		for i, unitName := range result.Units {
  2221  			u, err := s.BackingState.Unit(unitName)
  2222  			c.Assert(err, jc.ErrorIsNil)
  2223  			assignedMachine, err := u.AssignedMachineId()
  2224  			c.Assert(err, jc.ErrorIsNil)
  2225  			c.Assert(assignedMachine, gc.Equals, t.machineIds[i])
  2226  		}
  2227  	}
  2228  }
  2229  
  2230  func (s *applicationSuite) assertAddApplicationUnits(c *gc.C) {
  2231  	result, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{
  2232  		ApplicationName: "dummy",
  2233  		NumUnits:        3,
  2234  	})
  2235  	c.Assert(err, jc.ErrorIsNil)
  2236  	c.Assert(result.Units, gc.DeepEquals, []string{"dummy/0", "dummy/1", "dummy/2"})
  2237  
  2238  	// Test that we actually assigned the unit to machine 0
  2239  	forcedUnit, err := s.BackingState.Unit("dummy/0")
  2240  	c.Assert(err, jc.ErrorIsNil)
  2241  	assignedMachine, err := forcedUnit.AssignedMachineId()
  2242  	c.Assert(err, jc.ErrorIsNil)
  2243  	c.Assert(assignedMachine, gc.Equals, "0")
  2244  }
  2245  
  2246  func (s *applicationSuite) TestApplicationCharmRelations(c *gc.C) {
  2247  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2248  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
  2249  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  2250  	c.Assert(err, jc.ErrorIsNil)
  2251  	_, err = s.State.AddRelation(eps...)
  2252  	c.Assert(err, jc.ErrorIsNil)
  2253  
  2254  	_, err = s.applicationAPI.CharmRelations(params.ApplicationCharmRelations{"blah"})
  2255  	c.Assert(err, gc.ErrorMatches, `application "blah" not found`)
  2256  
  2257  	result, err := s.applicationAPI.CharmRelations(params.ApplicationCharmRelations{"wordpress"})
  2258  	c.Assert(err, jc.ErrorIsNil)
  2259  	c.Assert(result.CharmRelations, gc.DeepEquals, []string{
  2260  		"cache", "db", "juju-info", "logging-dir", "monitoring-port", "url",
  2261  	})
  2262  }
  2263  
  2264  func (s *applicationSuite) assertAddApplicationUnitsBlocked(c *gc.C, msg string) {
  2265  	_, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{
  2266  		ApplicationName: "dummy",
  2267  		NumUnits:        3,
  2268  	})
  2269  	s.AssertBlocked(c, err, msg)
  2270  }
  2271  
  2272  func (s *applicationSuite) TestBlockDestroyAddApplicationUnits(c *gc.C) {
  2273  	s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  2274  	s.BlockDestroyModel(c, "TestBlockDestroyAddApplicationUnits")
  2275  	s.assertAddApplicationUnits(c)
  2276  }
  2277  
  2278  func (s *applicationSuite) TestBlockRemoveAddApplicationUnits(c *gc.C) {
  2279  	s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  2280  	s.BlockRemoveObject(c, "TestBlockRemoveAddApplicationUnits")
  2281  	s.assertAddApplicationUnits(c)
  2282  }
  2283  
  2284  func (s *applicationSuite) TestBlockChangeAddApplicationUnits(c *gc.C) {
  2285  	s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  2286  	s.BlockAllChanges(c, "TestBlockChangeAddApplicationUnits")
  2287  	s.assertAddApplicationUnitsBlocked(c, "TestBlockChangeAddApplicationUnits")
  2288  }
  2289  
  2290  func (s *applicationSuite) TestAddUnitToMachineNotFound(c *gc.C) {
  2291  	s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  2292  	_, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{
  2293  		ApplicationName: "dummy",
  2294  		NumUnits:        3,
  2295  		Placement:       []*instance.Placement{instance.MustParsePlacement("42")},
  2296  	})
  2297  	c.Assert(err, gc.ErrorMatches, `acquiring machine to host unit "dummy/0": machine 42 not found`)
  2298  }
  2299  
  2300  func (s *applicationSuite) TestApplicationExpose(c *gc.C) {
  2301  	charm := s.AddTestingCharm(c, "dummy")
  2302  	applicationNames := []string{"dummy-application", "exposed-application"}
  2303  	apps := make([]*state.Application, len(applicationNames))
  2304  	var err error
  2305  	for i, name := range applicationNames {
  2306  		apps[i] = s.AddTestingApplication(c, name, charm)
  2307  		c.Assert(apps[i].IsExposed(), jc.IsFalse)
  2308  	}
  2309  	err = apps[1].SetExposed()
  2310  	c.Assert(err, jc.ErrorIsNil)
  2311  	c.Assert(apps[1].IsExposed(), jc.IsTrue)
  2312  	for i, t := range applicationExposeTests {
  2313  		c.Logf("test %d. %s", i, t.about)
  2314  		err = s.applicationAPI.Expose(params.ApplicationExpose{t.application})
  2315  		if t.err != "" {
  2316  			c.Assert(err, gc.ErrorMatches, t.err)
  2317  		} else {
  2318  			c.Assert(err, jc.ErrorIsNil)
  2319  			app, err := s.State.Application(t.application)
  2320  			c.Assert(err, jc.ErrorIsNil)
  2321  			c.Assert(app.IsExposed(), gc.Equals, t.exposed)
  2322  		}
  2323  	}
  2324  }
  2325  
  2326  func (s *applicationSuite) setupApplicationExpose(c *gc.C) {
  2327  	charm := s.AddTestingCharm(c, "dummy")
  2328  	applicationNames := []string{"dummy-application", "exposed-application"}
  2329  	apps := make([]*state.Application, len(applicationNames))
  2330  	var err error
  2331  	for i, name := range applicationNames {
  2332  		apps[i] = s.AddTestingApplication(c, name, charm)
  2333  		c.Assert(apps[i].IsExposed(), jc.IsFalse)
  2334  	}
  2335  	err = apps[1].SetExposed()
  2336  	c.Assert(err, jc.ErrorIsNil)
  2337  	c.Assert(apps[1].IsExposed(), jc.IsTrue)
  2338  }
  2339  
  2340  var applicationExposeTests = []struct {
  2341  	about       string
  2342  	application string
  2343  	err         string
  2344  	exposed     bool
  2345  }{
  2346  	{
  2347  		about:       "unknown application name",
  2348  		application: "unknown-application",
  2349  		err:         `application "unknown-application" not found`,
  2350  	},
  2351  	{
  2352  		about:       "expose a application",
  2353  		application: "dummy-application",
  2354  		exposed:     true,
  2355  	},
  2356  	{
  2357  		about:       "expose an already exposed application",
  2358  		application: "exposed-application",
  2359  		exposed:     true,
  2360  	},
  2361  }
  2362  
  2363  func (s *applicationSuite) assertApplicationExpose(c *gc.C) {
  2364  	for i, t := range applicationExposeTests {
  2365  		c.Logf("test %d. %s", i, t.about)
  2366  		err := s.applicationAPI.Expose(params.ApplicationExpose{t.application})
  2367  		if t.err != "" {
  2368  			c.Assert(err, gc.ErrorMatches, t.err)
  2369  		} else {
  2370  			c.Assert(err, jc.ErrorIsNil)
  2371  			application, err := s.State.Application(t.application)
  2372  			c.Assert(err, jc.ErrorIsNil)
  2373  			c.Assert(application.IsExposed(), gc.Equals, t.exposed)
  2374  		}
  2375  	}
  2376  }
  2377  
  2378  func (s *applicationSuite) assertApplicationExposeBlocked(c *gc.C, msg string) {
  2379  	for i, t := range applicationExposeTests {
  2380  		c.Logf("test %d. %s", i, t.about)
  2381  		err := s.applicationAPI.Expose(params.ApplicationExpose{t.application})
  2382  		s.AssertBlocked(c, err, msg)
  2383  	}
  2384  }
  2385  
  2386  func (s *applicationSuite) TestBlockDestroyApplicationExpose(c *gc.C) {
  2387  	s.setupApplicationExpose(c)
  2388  	s.BlockDestroyModel(c, "TestBlockDestroyApplicationExpose")
  2389  	s.assertApplicationExpose(c)
  2390  }
  2391  
  2392  func (s *applicationSuite) TestBlockRemoveApplicationExpose(c *gc.C) {
  2393  	s.setupApplicationExpose(c)
  2394  	s.BlockRemoveObject(c, "TestBlockRemoveApplicationExpose")
  2395  	s.assertApplicationExpose(c)
  2396  }
  2397  
  2398  func (s *applicationSuite) TestBlockChangesApplicationExpose(c *gc.C) {
  2399  	s.setupApplicationExpose(c)
  2400  	s.BlockAllChanges(c, "TestBlockChangesApplicationExpose")
  2401  	s.assertApplicationExposeBlocked(c, "TestBlockChangesApplicationExpose")
  2402  }
  2403  
  2404  var applicationUnexposeTests = []struct {
  2405  	about       string
  2406  	application string
  2407  	err         string
  2408  	initial     bool
  2409  	expected    bool
  2410  }{
  2411  	{
  2412  		about:       "unknown application name",
  2413  		application: "unknown-application",
  2414  		err:         `application "unknown-application" not found`,
  2415  	},
  2416  	{
  2417  		about:       "unexpose a application",
  2418  		application: "dummy-application",
  2419  		initial:     true,
  2420  		expected:    false,
  2421  	},
  2422  	{
  2423  		about:       "unexpose an already unexposed application",
  2424  		application: "dummy-application",
  2425  		initial:     false,
  2426  		expected:    false,
  2427  	},
  2428  }
  2429  
  2430  func (s *applicationSuite) TestApplicationUnexpose(c *gc.C) {
  2431  	charm := s.AddTestingCharm(c, "dummy")
  2432  	for i, t := range applicationUnexposeTests {
  2433  		c.Logf("test %d. %s", i, t.about)
  2434  		app := s.AddTestingApplication(c, "dummy-application", charm)
  2435  		if t.initial {
  2436  			app.SetExposed()
  2437  		}
  2438  		c.Assert(app.IsExposed(), gc.Equals, t.initial)
  2439  		err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{t.application})
  2440  		if t.err == "" {
  2441  			c.Assert(err, jc.ErrorIsNil)
  2442  			app.Refresh()
  2443  			c.Assert(app.IsExposed(), gc.Equals, t.expected)
  2444  		} else {
  2445  			c.Assert(err, gc.ErrorMatches, t.err)
  2446  		}
  2447  		err = app.Destroy()
  2448  		c.Assert(err, jc.ErrorIsNil)
  2449  	}
  2450  }
  2451  
  2452  func (s *applicationSuite) setupApplicationUnexpose(c *gc.C) *state.Application {
  2453  	charm := s.AddTestingCharm(c, "dummy")
  2454  	app := s.AddTestingApplication(c, "dummy-application", charm)
  2455  	app.SetExposed()
  2456  	c.Assert(app.IsExposed(), gc.Equals, true)
  2457  	return app
  2458  }
  2459  
  2460  func (s *applicationSuite) assertApplicationUnexpose(c *gc.C, app *state.Application) {
  2461  	err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{"dummy-application"})
  2462  	c.Assert(err, jc.ErrorIsNil)
  2463  	app.Refresh()
  2464  	c.Assert(app.IsExposed(), gc.Equals, false)
  2465  	err = app.Destroy()
  2466  	c.Assert(err, jc.ErrorIsNil)
  2467  }
  2468  
  2469  func (s *applicationSuite) assertApplicationUnexposeBlocked(c *gc.C, app *state.Application, msg string) {
  2470  	err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{"dummy-application"})
  2471  	s.AssertBlocked(c, err, msg)
  2472  	err = app.Destroy()
  2473  	c.Assert(err, jc.ErrorIsNil)
  2474  }
  2475  
  2476  func (s *applicationSuite) TestBlockDestroyApplicationUnexpose(c *gc.C) {
  2477  	app := s.setupApplicationUnexpose(c)
  2478  	s.BlockDestroyModel(c, "TestBlockDestroyApplicationUnexpose")
  2479  	s.assertApplicationUnexpose(c, app)
  2480  }
  2481  
  2482  func (s *applicationSuite) TestBlockRemoveApplicationUnexpose(c *gc.C) {
  2483  	app := s.setupApplicationUnexpose(c)
  2484  	s.BlockRemoveObject(c, "TestBlockRemoveApplicationUnexpose")
  2485  	s.assertApplicationUnexpose(c, app)
  2486  }
  2487  
  2488  func (s *applicationSuite) TestBlockChangesApplicationUnexpose(c *gc.C) {
  2489  	app := s.setupApplicationUnexpose(c)
  2490  	s.BlockAllChanges(c, "TestBlockChangesApplicationUnexpose")
  2491  	s.assertApplicationUnexposeBlocked(c, app, "TestBlockChangesApplicationUnexpose")
  2492  }
  2493  
  2494  var applicationDestroyTests = []struct {
  2495  	about       string
  2496  	application string
  2497  	err         string
  2498  }{
  2499  	{
  2500  		about:       "unknown application name",
  2501  		application: "unknown-application",
  2502  		err:         `application "unknown-application" not found`,
  2503  	},
  2504  	{
  2505  		about:       "destroy an application",
  2506  		application: "dummy-application",
  2507  	},
  2508  	{
  2509  		about:       "destroy an already destroyed application",
  2510  		application: "dummy-application",
  2511  		err:         `application "dummy-application" not found`,
  2512  	},
  2513  }
  2514  
  2515  func (s *applicationSuite) TestApplicationDestroy(c *gc.C) {
  2516  	s.AddTestingApplication(c, "dummy-application", s.AddTestingCharm(c, "dummy"))
  2517  	_, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{
  2518  		Name:        "remote-application",
  2519  		SourceModel: s.Model.ModelTag(),
  2520  		Token:       "t0",
  2521  	})
  2522  	c.Assert(err, jc.ErrorIsNil)
  2523  
  2524  	for i, t := range applicationDestroyTests {
  2525  		c.Logf("test %d. %s", i, t.about)
  2526  		err := s.applicationAPI.Destroy(params.ApplicationDestroy{t.application})
  2527  		if t.err != "" {
  2528  			c.Assert(err, gc.ErrorMatches, t.err)
  2529  		} else {
  2530  			c.Assert(err, jc.ErrorIsNil)
  2531  		}
  2532  	}
  2533  
  2534  	// Now do Destroy on an application with units. Destroy will
  2535  	// cause the application to be not-Alive, but will not remove its
  2536  	// document.
  2537  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2538  	applicationName := "wordpress"
  2539  	app, err := s.State.Application(applicationName)
  2540  	c.Assert(err, jc.ErrorIsNil)
  2541  	err = s.applicationAPI.Destroy(params.ApplicationDestroy{applicationName})
  2542  	c.Assert(err, jc.ErrorIsNil)
  2543  	err = app.Refresh()
  2544  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  2545  }
  2546  
  2547  func assertLife(c *gc.C, entity state.Living, life state.Life) {
  2548  	err := entity.Refresh()
  2549  	c.Assert(err, jc.ErrorIsNil)
  2550  	c.Assert(entity.Life(), gc.Equals, life)
  2551  }
  2552  
  2553  func (s *applicationSuite) TestBlockApplicationDestroy(c *gc.C) {
  2554  	s.AddTestingApplication(c, "dummy-application", s.AddTestingCharm(c, "dummy"))
  2555  
  2556  	// block remove-objects
  2557  	s.BlockRemoveObject(c, "TestBlockApplicationDestroy")
  2558  	err := s.applicationAPI.Destroy(params.ApplicationDestroy{"dummy-application"})
  2559  	s.AssertBlocked(c, err, "TestBlockApplicationDestroy")
  2560  	// Tests may have invalid application names.
  2561  	app, err := s.State.Application("dummy-application")
  2562  	if err == nil {
  2563  		// For valid application names, check that application is alive :-)
  2564  		assertLife(c, app, state.Alive)
  2565  	}
  2566  }
  2567  
  2568  func (s *applicationSuite) TestDestroyPrincipalUnits(c *gc.C) {
  2569  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2570  	units := make([]*state.Unit, 5)
  2571  	for i := range units {
  2572  		unit, err := wordpress.AddUnit(state.AddUnitParams{})
  2573  		c.Assert(err, jc.ErrorIsNil)
  2574  		unit.AssignToNewMachine()
  2575  		c.Assert(err, jc.ErrorIsNil)
  2576  		now := time.Now()
  2577  		sInfo := status.StatusInfo{
  2578  			Status:  status.Idle,
  2579  			Message: "",
  2580  			Since:   &now,
  2581  		}
  2582  		err = unit.SetAgentStatus(sInfo)
  2583  		c.Assert(err, jc.ErrorIsNil)
  2584  		units[i] = unit
  2585  	}
  2586  	s.assertDestroyPrincipalUnits(c, units)
  2587  }
  2588  
  2589  func (s *applicationSuite) TestDestroySubordinateUnits(c *gc.C) {
  2590  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2591  	wordpress0, err := wordpress.AddUnit(state.AddUnitParams{})
  2592  	c.Assert(err, jc.ErrorIsNil)
  2593  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
  2594  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  2595  	c.Assert(err, jc.ErrorIsNil)
  2596  	rel, err := s.State.AddRelation(eps...)
  2597  	c.Assert(err, jc.ErrorIsNil)
  2598  	ru, err := rel.Unit(wordpress0)
  2599  	c.Assert(err, jc.ErrorIsNil)
  2600  	err = ru.EnterScope(nil)
  2601  	c.Assert(err, jc.ErrorIsNil)
  2602  	logging0, err := s.State.Unit("logging/0")
  2603  	c.Assert(err, jc.ErrorIsNil)
  2604  
  2605  	// Try to destroy the subordinate alone; check it fails.
  2606  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2607  		UnitNames: []string{"logging/0"},
  2608  	})
  2609  	c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`)
  2610  	assertLife(c, logging0, state.Alive)
  2611  
  2612  	s.assertDestroySubordinateUnits(c, wordpress0, logging0)
  2613  }
  2614  
  2615  func (s *applicationSuite) assertDestroyPrincipalUnits(c *gc.C, units []*state.Unit) {
  2616  	// Destroy 2 of them; check they become Dying.
  2617  	err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2618  		UnitNames: []string{"wordpress/0", "wordpress/1"},
  2619  	})
  2620  	c.Assert(err, jc.ErrorIsNil)
  2621  	assertLife(c, units[0], state.Dying)
  2622  	assertLife(c, units[1], state.Dying)
  2623  
  2624  	// Try to destroy an Alive one and a Dying one; check
  2625  	// it destroys the Alive one and ignores the Dying one.
  2626  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2627  		UnitNames: []string{"wordpress/2", "wordpress/0"},
  2628  	})
  2629  	c.Assert(err, jc.ErrorIsNil)
  2630  	assertLife(c, units[2], state.Dying)
  2631  
  2632  	// Try to destroy an Alive one along with a nonexistent one; check that
  2633  	// the valid instruction is followed but the invalid one is warned about.
  2634  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2635  		UnitNames: []string{"boojum/123", "wordpress/3"},
  2636  	})
  2637  	c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`)
  2638  	assertLife(c, units[3], state.Dying)
  2639  
  2640  	// Make one Dead, and destroy an Alive one alongside it; check no errors.
  2641  	wp0, err := s.State.Unit("wordpress/0")
  2642  	c.Assert(err, jc.ErrorIsNil)
  2643  	err = wp0.EnsureDead()
  2644  	c.Assert(err, jc.ErrorIsNil)
  2645  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2646  		UnitNames: []string{"wordpress/0", "wordpress/4"},
  2647  	})
  2648  	c.Assert(err, jc.ErrorIsNil)
  2649  	assertLife(c, units[0], state.Dead)
  2650  	assertLife(c, units[4], state.Dying)
  2651  }
  2652  
  2653  func (s *applicationSuite) setupDestroyPrincipalUnits(c *gc.C) []*state.Unit {
  2654  	units := make([]*state.Unit, 5)
  2655  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2656  	for i := range units {
  2657  		unit, err := wordpress.AddUnit(state.AddUnitParams{})
  2658  		c.Assert(err, jc.ErrorIsNil)
  2659  		err = unit.AssignToNewMachine()
  2660  		c.Assert(err, jc.ErrorIsNil)
  2661  		now := time.Now()
  2662  		sInfo := status.StatusInfo{
  2663  			Status:  status.Idle,
  2664  			Message: "",
  2665  			Since:   &now,
  2666  		}
  2667  		err = unit.SetAgentStatus(sInfo)
  2668  		c.Assert(err, jc.ErrorIsNil)
  2669  		units[i] = unit
  2670  	}
  2671  	return units
  2672  }
  2673  
  2674  func (s *applicationSuite) assertBlockedErrorAndLiveliness(
  2675  	c *gc.C,
  2676  	err error,
  2677  	msg string,
  2678  	living1 state.Living,
  2679  	living2 state.Living,
  2680  	living3 state.Living,
  2681  	living4 state.Living,
  2682  ) {
  2683  	s.AssertBlocked(c, err, msg)
  2684  	assertLife(c, living1, state.Alive)
  2685  	assertLife(c, living2, state.Alive)
  2686  	assertLife(c, living3, state.Alive)
  2687  	assertLife(c, living4, state.Alive)
  2688  }
  2689  
  2690  func (s *applicationSuite) TestBlockChangesDestroyPrincipalUnits(c *gc.C) {
  2691  	units := s.setupDestroyPrincipalUnits(c)
  2692  	s.BlockAllChanges(c, "TestBlockChangesDestroyPrincipalUnits")
  2693  	err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2694  		UnitNames: []string{"wordpress/0", "wordpress/1"},
  2695  	})
  2696  	s.assertBlockedErrorAndLiveliness(c, err, "TestBlockChangesDestroyPrincipalUnits", units[0], units[1], units[2], units[3])
  2697  }
  2698  
  2699  func (s *applicationSuite) TestBlockRemoveDestroyPrincipalUnits(c *gc.C) {
  2700  	units := s.setupDestroyPrincipalUnits(c)
  2701  	s.BlockRemoveObject(c, "TestBlockRemoveDestroyPrincipalUnits")
  2702  	err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2703  		UnitNames: []string{"wordpress/0", "wordpress/1"},
  2704  	})
  2705  	s.assertBlockedErrorAndLiveliness(c, err, "TestBlockRemoveDestroyPrincipalUnits", units[0], units[1], units[2], units[3])
  2706  }
  2707  
  2708  func (s *applicationSuite) TestBlockDestroyDestroyPrincipalUnits(c *gc.C) {
  2709  	units := s.setupDestroyPrincipalUnits(c)
  2710  	s.BlockDestroyModel(c, "TestBlockDestroyDestroyPrincipalUnits")
  2711  	err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2712  		UnitNames: []string{"wordpress/0", "wordpress/1"},
  2713  	})
  2714  	c.Assert(err, jc.ErrorIsNil)
  2715  	assertLife(c, units[0], state.Dying)
  2716  	assertLife(c, units[1], state.Dying)
  2717  }
  2718  
  2719  func (s *applicationSuite) assertDestroySubordinateUnits(c *gc.C, wordpress0, logging0 *state.Unit) {
  2720  	// Try to destroy the principal and the subordinate together; check it warns
  2721  	// about the subordinate, but destroys the one it can. (The principal unit
  2722  	// agent will be responsible for destroying the subordinate.)
  2723  	err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2724  		UnitNames: []string{"wordpress/0", "logging/0"},
  2725  	})
  2726  	c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate`)
  2727  	assertLife(c, wordpress0, state.Dying)
  2728  	assertLife(c, logging0, state.Alive)
  2729  }
  2730  
  2731  func (s *applicationSuite) TestBlockRemoveDestroySubordinateUnits(c *gc.C) {
  2732  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2733  	wordpress0, err := wordpress.AddUnit(state.AddUnitParams{})
  2734  	c.Assert(err, jc.ErrorIsNil)
  2735  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
  2736  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  2737  	c.Assert(err, jc.ErrorIsNil)
  2738  	rel, err := s.State.AddRelation(eps...)
  2739  	c.Assert(err, jc.ErrorIsNil)
  2740  	ru, err := rel.Unit(wordpress0)
  2741  	c.Assert(err, jc.ErrorIsNil)
  2742  	err = ru.EnterScope(nil)
  2743  	c.Assert(err, jc.ErrorIsNil)
  2744  	logging0, err := s.State.Unit("logging/0")
  2745  	c.Assert(err, jc.ErrorIsNil)
  2746  
  2747  	s.BlockRemoveObject(c, "TestBlockRemoveDestroySubordinateUnits")
  2748  	// Try to destroy the subordinate alone; check it fails.
  2749  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2750  		UnitNames: []string{"logging/0"},
  2751  	})
  2752  	s.AssertBlocked(c, err, "TestBlockRemoveDestroySubordinateUnits")
  2753  	assertLife(c, rel, state.Alive)
  2754  	assertLife(c, wordpress0, state.Alive)
  2755  	assertLife(c, logging0, state.Alive)
  2756  
  2757  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2758  		UnitNames: []string{"wordpress/0", "logging/0"},
  2759  	})
  2760  	s.AssertBlocked(c, err, "TestBlockRemoveDestroySubordinateUnits")
  2761  	assertLife(c, wordpress0, state.Alive)
  2762  	assertLife(c, logging0, state.Alive)
  2763  	assertLife(c, rel, state.Alive)
  2764  }
  2765  
  2766  func (s *applicationSuite) TestBlockChangesDestroySubordinateUnits(c *gc.C) {
  2767  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2768  	wordpress0, err := wordpress.AddUnit(state.AddUnitParams{})
  2769  	c.Assert(err, jc.ErrorIsNil)
  2770  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
  2771  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  2772  	c.Assert(err, jc.ErrorIsNil)
  2773  	rel, err := s.State.AddRelation(eps...)
  2774  	c.Assert(err, jc.ErrorIsNil)
  2775  	ru, err := rel.Unit(wordpress0)
  2776  	c.Assert(err, jc.ErrorIsNil)
  2777  	err = ru.EnterScope(nil)
  2778  	c.Assert(err, jc.ErrorIsNil)
  2779  	logging0, err := s.State.Unit("logging/0")
  2780  	c.Assert(err, jc.ErrorIsNil)
  2781  
  2782  	s.BlockAllChanges(c, "TestBlockChangesDestroySubordinateUnits")
  2783  	// Try to destroy the subordinate alone; check it fails.
  2784  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2785  		UnitNames: []string{"logging/0"},
  2786  	})
  2787  	s.AssertBlocked(c, err, "TestBlockChangesDestroySubordinateUnits")
  2788  	assertLife(c, rel, state.Alive)
  2789  	assertLife(c, wordpress0, state.Alive)
  2790  	assertLife(c, logging0, state.Alive)
  2791  
  2792  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2793  		UnitNames: []string{"wordpress/0", "logging/0"},
  2794  	})
  2795  	s.AssertBlocked(c, err, "TestBlockChangesDestroySubordinateUnits")
  2796  	assertLife(c, wordpress0, state.Alive)
  2797  	assertLife(c, logging0, state.Alive)
  2798  	assertLife(c, rel, state.Alive)
  2799  }
  2800  
  2801  func (s *applicationSuite) TestBlockDestroyDestroySubordinateUnits(c *gc.C) {
  2802  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2803  	wordpress0, err := wordpress.AddUnit(state.AddUnitParams{})
  2804  	c.Assert(err, jc.ErrorIsNil)
  2805  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
  2806  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  2807  	c.Assert(err, jc.ErrorIsNil)
  2808  	rel, err := s.State.AddRelation(eps...)
  2809  	c.Assert(err, jc.ErrorIsNil)
  2810  	ru, err := rel.Unit(wordpress0)
  2811  	c.Assert(err, jc.ErrorIsNil)
  2812  	err = ru.EnterScope(nil)
  2813  	c.Assert(err, jc.ErrorIsNil)
  2814  	logging0, err := s.State.Unit("logging/0")
  2815  	c.Assert(err, jc.ErrorIsNil)
  2816  
  2817  	s.BlockDestroyModel(c, "TestBlockDestroyDestroySubordinateUnits")
  2818  	// Try to destroy the subordinate alone; check it fails.
  2819  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2820  		UnitNames: []string{"logging/0"},
  2821  	})
  2822  	c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`)
  2823  	assertLife(c, logging0, state.Alive)
  2824  
  2825  	s.assertDestroySubordinateUnits(c, wordpress0, logging0)
  2826  }
  2827  
  2828  func (s *applicationSuite) TestClientSetApplicationConstraints(c *gc.C) {
  2829  	app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  2830  
  2831  	// Update constraints for the application.
  2832  	cons, err := constraints.Parse("mem=4096", "cores=2")
  2833  	c.Assert(err, jc.ErrorIsNil)
  2834  	err = s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons})
  2835  	c.Assert(err, jc.ErrorIsNil)
  2836  
  2837  	// Ensure the constraints have been correctly updated.
  2838  	obtained, err := app.Constraints()
  2839  	c.Assert(err, jc.ErrorIsNil)
  2840  	c.Assert(obtained, gc.DeepEquals, cons)
  2841  }
  2842  
  2843  func (s *applicationSuite) setupSetApplicationConstraints(c *gc.C) (*state.Application, constraints.Value) {
  2844  	app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy"))
  2845  	// Update constraints for the application.
  2846  	cons, err := constraints.Parse("mem=4096", "cores=2")
  2847  	c.Assert(err, jc.ErrorIsNil)
  2848  	return app, cons
  2849  }
  2850  
  2851  func (s *applicationSuite) assertSetApplicationConstraints(c *gc.C, application *state.Application, cons constraints.Value) {
  2852  	err := s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons})
  2853  	c.Assert(err, jc.ErrorIsNil)
  2854  	// Ensure the constraints have been correctly updated.
  2855  	obtained, err := application.Constraints()
  2856  	c.Assert(err, jc.ErrorIsNil)
  2857  	c.Assert(obtained, gc.DeepEquals, cons)
  2858  }
  2859  
  2860  func (s *applicationSuite) assertSetApplicationConstraintsBlocked(c *gc.C, msg string, application *state.Application, cons constraints.Value) {
  2861  	err := s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons})
  2862  	s.AssertBlocked(c, err, msg)
  2863  }
  2864  
  2865  func (s *applicationSuite) TestBlockDestroySetApplicationConstraints(c *gc.C) {
  2866  	app, cons := s.setupSetApplicationConstraints(c)
  2867  	s.BlockDestroyModel(c, "TestBlockDestroySetApplicationConstraints")
  2868  	s.assertSetApplicationConstraints(c, app, cons)
  2869  }
  2870  
  2871  func (s *applicationSuite) TestBlockRemoveSetApplicationConstraints(c *gc.C) {
  2872  	app, cons := s.setupSetApplicationConstraints(c)
  2873  	s.BlockRemoveObject(c, "TestBlockRemoveSetApplicationConstraints")
  2874  	s.assertSetApplicationConstraints(c, app, cons)
  2875  }
  2876  
  2877  func (s *applicationSuite) TestBlockChangesSetApplicationConstraints(c *gc.C) {
  2878  	app, cons := s.setupSetApplicationConstraints(c)
  2879  	s.BlockAllChanges(c, "TestBlockChangesSetApplicationConstraints")
  2880  	s.assertSetApplicationConstraintsBlocked(c, "TestBlockChangesSetApplicationConstraints", app, cons)
  2881  }
  2882  
  2883  func (s *applicationSuite) TestClientGetApplicationConstraints(c *gc.C) {
  2884  	fooConstraints := constraints.MustParse("mem=4G")
  2885  	s.Factory.MakeApplication(c, &factory.ApplicationParams{
  2886  		Name:        "foo",
  2887  		Constraints: fooConstraints,
  2888  	})
  2889  	barConstraints := constraints.MustParse("mem=128G", "cores=64")
  2890  	s.Factory.MakeApplication(c, &factory.ApplicationParams{
  2891  		Name:        "bar",
  2892  		Constraints: barConstraints,
  2893  	})
  2894  
  2895  	results, err := s.applicationAPI.GetConstraints(params.Entities{
  2896  		Entities: []params.Entity{
  2897  			{"wat"}, {"machine-0"}, {"user-foo"},
  2898  			{"application-foo"}, {"application-bar"}, {"application-wat"},
  2899  		},
  2900  	})
  2901  	c.Assert(err, jc.ErrorIsNil)
  2902  	c.Assert(results, jc.DeepEquals, params.ApplicationGetConstraintsResults{
  2903  		Results: []params.ApplicationConstraint{
  2904  			{
  2905  				Error: &params.Error{Message: `"wat" is not a valid tag`},
  2906  			}, {
  2907  				Error: &params.Error{Message: `unexpected tag type, expected application, got machine`},
  2908  			}, {
  2909  				Error: &params.Error{Message: `unexpected tag type, expected application, got user`},
  2910  			}, {
  2911  				Constraints: fooConstraints,
  2912  			}, {
  2913  				Constraints: barConstraints,
  2914  			}, {
  2915  				Error: &params.Error{Message: `application "wat" not found`, Code: "not found"},
  2916  			},
  2917  		}})
  2918  }
  2919  
  2920  func (s *applicationSuite) checkEndpoints(c *gc.C, mysqlAppName string, endpoints map[string]params.CharmRelation) {
  2921  	c.Assert(endpoints["wordpress"], gc.DeepEquals, params.CharmRelation{
  2922  		Name:      "db",
  2923  		Role:      "requirer",
  2924  		Interface: "mysql",
  2925  		Optional:  false,
  2926  		Limit:     1,
  2927  		Scope:     "global",
  2928  	})
  2929  	ep := params.CharmRelation{
  2930  		Name:      "server",
  2931  		Role:      "provider",
  2932  		Interface: "mysql",
  2933  		Scope:     "global",
  2934  	}
  2935  	// Remote applications don't use scope.
  2936  	if mysqlAppName == "hosted-mysql" {
  2937  		ep.Scope = ""
  2938  	}
  2939  	c.Assert(endpoints[mysqlAppName], gc.DeepEquals, ep)
  2940  }
  2941  
  2942  func (s *applicationSuite) setupRelationScenario(c *gc.C) {
  2943  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2944  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
  2945  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  2946  	c.Assert(err, jc.ErrorIsNil)
  2947  	_, err = s.State.AddRelation(eps...)
  2948  	c.Assert(err, jc.ErrorIsNil)
  2949  }
  2950  
  2951  func (s *applicationSuite) assertAddRelation(c *gc.C, endpoints, viaCIDRs []string) {
  2952  	s.setupRelationScenario(c)
  2953  
  2954  	res, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints, ViaCIDRs: viaCIDRs})
  2955  	c.Assert(err, jc.ErrorIsNil)
  2956  	// Show that the relation was added.
  2957  	wpApp, err := s.State.Application("wordpress")
  2958  	c.Assert(err, jc.ErrorIsNil)
  2959  	rels, err := wpApp.Relations()
  2960  	// There are 2 relations - the logging-wordpress one set up in the
  2961  	// scenario and the one created in this test.
  2962  	c.Assert(len(rels), gc.Equals, 2)
  2963  
  2964  	// We may be related to a local application or a remote offer
  2965  	// or an application in another model.
  2966  	var mySqlApplication state.ApplicationEntity
  2967  	mySqlApplication, err = s.State.RemoteApplication("hosted-mysql")
  2968  	if errors.IsNotFound(err) {
  2969  		mySqlApplication, err = s.State.RemoteApplication("othermysql")
  2970  		if errors.IsNotFound(err) {
  2971  			mySqlApplication, err = s.State.Application("mysql")
  2972  			c.Assert(err, jc.ErrorIsNil)
  2973  			s.checkEndpoints(c, "mysql", res.Endpoints)
  2974  		} else {
  2975  			c.Assert(err, jc.ErrorIsNil)
  2976  			s.checkEndpoints(c, "othermysql", res.Endpoints)
  2977  		}
  2978  	} else {
  2979  		c.Assert(err, jc.ErrorIsNil)
  2980  		s.checkEndpoints(c, "hosted-mysql", res.Endpoints)
  2981  	}
  2982  	c.Assert(err, jc.ErrorIsNil)
  2983  	rels, err = mySqlApplication.Relations()
  2984  	c.Assert(err, jc.ErrorIsNil)
  2985  	c.Assert(len(rels), gc.Equals, 1)
  2986  }
  2987  
  2988  func (s *applicationSuite) TestSuccessfullyAddRelation(c *gc.C) {
  2989  	endpoints := []string{"wordpress", "mysql"}
  2990  	s.assertAddRelation(c, endpoints, nil)
  2991  }
  2992  
  2993  func (s *applicationSuite) TestBlockDestroyAddRelation(c *gc.C) {
  2994  	s.BlockDestroyModel(c, "TestBlockDestroyAddRelation")
  2995  	s.assertAddRelation(c, []string{"wordpress", "mysql"}, nil)
  2996  }
  2997  func (s *applicationSuite) TestBlockRemoveAddRelation(c *gc.C) {
  2998  	s.BlockRemoveObject(c, "TestBlockRemoveAddRelation")
  2999  	s.assertAddRelation(c, []string{"wordpress", "mysql"}, nil)
  3000  }
  3001  
  3002  func (s *applicationSuite) TestBlockChangesAddRelation(c *gc.C) {
  3003  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  3004  	s.BlockAllChanges(c, "TestBlockChangesAddRelation")
  3005  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: []string{"wordpress", "mysql"}})
  3006  	s.AssertBlocked(c, err, "TestBlockChangesAddRelation")
  3007  }
  3008  
  3009  func (s *applicationSuite) TestSuccessfullyAddRelationSwapped(c *gc.C) {
  3010  	// Show that the order of the applications listed in the AddRelation call
  3011  	// does not matter.  This is a repeat of the previous test with the application
  3012  	// names swapped.
  3013  	endpoints := []string{"mysql", "wordpress"}
  3014  	s.assertAddRelation(c, endpoints, nil)
  3015  }
  3016  
  3017  func (s *applicationSuite) TestCallWithOnlyOneEndpoint(c *gc.C) {
  3018  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  3019  	endpoints := []string{"wordpress"}
  3020  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  3021  	c.Assert(err, gc.ErrorMatches, "no relations found")
  3022  }
  3023  
  3024  func (s *applicationSuite) TestCallWithOneEndpointTooMany(c *gc.C) {
  3025  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  3026  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
  3027  	endpoints := []string{"wordpress", "mysql", "logging"}
  3028  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  3029  	c.Assert(err, gc.ErrorMatches, "cannot relate 3 endpoints")
  3030  }
  3031  
  3032  func (s *applicationSuite) TestAddAlreadyAddedRelation(c *gc.C) {
  3033  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  3034  	// Add a relation between wordpress and mysql.
  3035  	endpoints := []string{"wordpress", "mysql"}
  3036  	eps, err := s.State.InferEndpoints(endpoints...)
  3037  	c.Assert(err, jc.ErrorIsNil)
  3038  	_, err = s.State.AddRelation(eps...)
  3039  	c.Assert(err, jc.ErrorIsNil)
  3040  	// And try to add it again.
  3041  	_, err = s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  3042  	c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": relation wordpress:db mysql:server already exists`)
  3043  }
  3044  
  3045  func (s *applicationSuite) setupRemoteApplication(c *gc.C) {
  3046  	results, err := s.applicationAPI.Consume(params.ConsumeApplicationArgs{
  3047  		Args: []params.ConsumeApplicationArg{
  3048  			{ApplicationOfferDetails: params.ApplicationOfferDetails{
  3049  				SourceModelTag:         testing.ModelTag.String(),
  3050  				OfferName:              "hosted-mysql",
  3051  				OfferUUID:              "hosted-mysql-uuid",
  3052  				ApplicationDescription: "A pretty popular database",
  3053  				Endpoints: []params.RemoteEndpoint{
  3054  					{Name: "server", Interface: "mysql", Role: "provider"},
  3055  				},
  3056  			}},
  3057  		},
  3058  	})
  3059  	c.Assert(err, jc.ErrorIsNil)
  3060  	c.Assert(results.OneError(), gc.IsNil)
  3061  }
  3062  
  3063  func (s *applicationSuite) TestAddRemoteRelation(c *gc.C) {
  3064  	s.setupRemoteApplication(c)
  3065  	// There's already a wordpress in the scenario this assertion sets up.
  3066  	s.assertAddRelation(c, []string{"wordpress", "hosted-mysql"}, nil)
  3067  }
  3068  
  3069  func (s *applicationSuite) TestAddRemoteRelationWithRelName(c *gc.C) {
  3070  	s.setupRemoteApplication(c)
  3071  	s.assertAddRelation(c, []string{"wordpress", "hosted-mysql:server"}, nil)
  3072  }
  3073  
  3074  func (s *applicationSuite) TestAddRemoteRelationVia(c *gc.C) {
  3075  	s.setupRemoteApplication(c)
  3076  	s.assertAddRelation(c, []string{"wordpress", "hosted-mysql:server"}, []string{"192.168.0.0/16"})
  3077  
  3078  	rel, err := s.State.KeyRelation("wordpress:db hosted-mysql:server")
  3079  	c.Assert(err, jc.ErrorIsNil)
  3080  	w := rel.WatchRelationEgressNetworks()
  3081  	defer statetesting.AssertStop(c, w)
  3082  	wc := statetesting.NewStringsWatcherC(c, s.State, w)
  3083  	wc.AssertChange("192.168.0.0/16")
  3084  	wc.AssertNoChange()
  3085  }
  3086  
  3087  func (s *applicationSuite) TestAddRemoteRelationOnlyOneEndpoint(c *gc.C) {
  3088  	s.setupRemoteApplication(c)
  3089  	endpoints := []string{"hosted-mysql"}
  3090  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  3091  	c.Assert(err, gc.ErrorMatches, "no relations found")
  3092  }
  3093  
  3094  func (s *applicationSuite) TestAlreadyAddedRemoteRelation(c *gc.C) {
  3095  	s.setupRemoteApplication(c)
  3096  	endpoints := []string{"wordpress", "hosted-mysql"}
  3097  	s.assertAddRelation(c, endpoints, nil)
  3098  
  3099  	// And try to add it again.
  3100  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  3101  	c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`cannot add relation "wordpress:db hosted-mysql:server": relation wordpress:db hosted-mysql:server already exists`))
  3102  }
  3103  
  3104  func (s *applicationSuite) TestRemoteRelationInvalidEndpoint(c *gc.C) {
  3105  	s.setupRemoteApplication(c)
  3106  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  3107  
  3108  	endpoints := []string{"wordpress", "hosted-mysql:nope"}
  3109  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  3110  	c.Assert(err, gc.ErrorMatches, `remote application "hosted-mysql" has no "nope" relation`)
  3111  }
  3112  
  3113  func (s *applicationSuite) TestRemoteRelationNoMatchingEndpoint(c *gc.C) {
  3114  	results, err := s.applicationAPI.Consume(params.ConsumeApplicationArgs{
  3115  		Args: []params.ConsumeApplicationArg{
  3116  			{ApplicationOfferDetails: params.ApplicationOfferDetails{
  3117  				SourceModelTag: testing.ModelTag.String(),
  3118  				OfferName:      "hosted-db2",
  3119  				OfferUUID:      "hosted-db2-uuid",
  3120  				Endpoints: []params.RemoteEndpoint{
  3121  					{Name: "database", Interface: "db2", Role: "provider"},
  3122  				},
  3123  			}},
  3124  		},
  3125  	})
  3126  	c.Assert(err, jc.ErrorIsNil)
  3127  	c.Assert(results.OneError(), gc.IsNil)
  3128  
  3129  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  3130  	endpoints := []string{"wordpress", "hosted-db2"}
  3131  	_, err = s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  3132  	c.Assert(err, gc.ErrorMatches, "no relations found")
  3133  }
  3134  
  3135  func (s *applicationSuite) TestRemoteRelationApplicationNotFound(c *gc.C) {
  3136  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  3137  	endpoints := []string{"wordpress", "unknown"}
  3138  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  3139  	c.Assert(err, gc.ErrorMatches, `application "unknown" not found`)
  3140  }