github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/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"
     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-unstable"
    18  	"gopkg.in/juju/charmrepo.v2-unstable"
    19  	"gopkg.in/juju/charmrepo.v2-unstable/csclient"
    20  	csparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params"
    21  	"gopkg.in/juju/names.v2"
    22  	"gopkg.in/macaroon.v1"
    23  	"gopkg.in/mgo.v2"
    24  
    25  	"github.com/juju/juju/apiserver/application"
    26  	"github.com/juju/juju/apiserver/common"
    27  	commontesting "github.com/juju/juju/apiserver/common/testing"
    28  	"github.com/juju/juju/apiserver/params"
    29  	apiservertesting "github.com/juju/juju/apiserver/testing"
    30  	"github.com/juju/juju/constraints"
    31  	"github.com/juju/juju/instance"
    32  	jujutesting "github.com/juju/juju/juju/testing"
    33  	"github.com/juju/juju/state"
    34  	statestorage "github.com/juju/juju/state/storage"
    35  	"github.com/juju/juju/status"
    36  	"github.com/juju/juju/storage"
    37  	"github.com/juju/juju/testcharms"
    38  	"github.com/juju/juju/testing/factory"
    39  	jujuversion "github.com/juju/juju/version"
    40  )
    41  
    42  type serviceSuite struct {
    43  	jujutesting.JujuConnSuite
    44  	apiservertesting.CharmStoreSuite
    45  	commontesting.BlockHelper
    46  
    47  	applicationAPI *application.API
    48  	application    *state.Application
    49  	authorizer     apiservertesting.FakeAuthorizer
    50  }
    51  
    52  var _ = gc.Suite(&serviceSuite{})
    53  
    54  func (s *serviceSuite) SetUpSuite(c *gc.C) {
    55  	s.CharmStoreSuite.SetUpSuite(c)
    56  	s.JujuConnSuite.SetUpSuite(c)
    57  }
    58  
    59  func (s *serviceSuite) TearDownSuite(c *gc.C) {
    60  	s.CharmStoreSuite.TearDownSuite(c)
    61  	s.JujuConnSuite.TearDownSuite(c)
    62  }
    63  
    64  func (s *serviceSuite) SetUpTest(c *gc.C) {
    65  	s.JujuConnSuite.SetUpTest(c)
    66  	s.CharmStoreSuite.Session = s.JujuConnSuite.Session
    67  	s.CharmStoreSuite.SetUpTest(c)
    68  	s.BlockHelper = commontesting.NewBlockHelper(s.APIState)
    69  	s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() })
    70  
    71  	s.application = s.Factory.MakeApplication(c, nil)
    72  
    73  	s.authorizer = apiservertesting.FakeAuthorizer{
    74  		Tag: s.AdminUserTag(c),
    75  	}
    76  	var err error
    77  	backend := application.NewStateBackend(s.State)
    78  	blockChecker := common.NewBlockChecker(s.State)
    79  	s.applicationAPI, err = application.NewAPI(
    80  		backend, s.authorizer, blockChecker,
    81  		application.CharmToStateCharm,
    82  	)
    83  	c.Assert(err, jc.ErrorIsNil)
    84  }
    85  
    86  func (s *serviceSuite) TearDownTest(c *gc.C) {
    87  	s.CharmStoreSuite.TearDownTest(c)
    88  	s.JujuConnSuite.TearDownTest(c)
    89  }
    90  
    91  func (s *serviceSuite) TestSetMetricCredentials(c *gc.C) {
    92  	charm := s.Factory.MakeCharm(c, &factory.CharmParams{Name: "wordpress"})
    93  	wordpress := s.Factory.MakeApplication(c, &factory.ApplicationParams{
    94  		Charm: charm,
    95  	})
    96  	tests := []struct {
    97  		about   string
    98  		args    params.ApplicationMetricCredentials
    99  		results params.ErrorResults
   100  	}{
   101  		{
   102  			"test one argument and it passes",
   103  			params.ApplicationMetricCredentials{[]params.ApplicationMetricCredential{{
   104  				s.application.Name(),
   105  				[]byte("creds 1234"),
   106  			}}},
   107  			params.ErrorResults{[]params.ErrorResult{{Error: nil}}},
   108  		},
   109  		{
   110  			"test two arguments and both pass",
   111  			params.ApplicationMetricCredentials{[]params.ApplicationMetricCredential{
   112  				{
   113  					s.application.Name(),
   114  					[]byte("creds 1234"),
   115  				},
   116  				{
   117  					wordpress.Name(),
   118  					[]byte("creds 4567"),
   119  				},
   120  			}},
   121  			params.ErrorResults{[]params.ErrorResult{
   122  				{Error: nil},
   123  				{Error: nil},
   124  			}},
   125  		},
   126  		{
   127  			"test two arguments and second one fails",
   128  			params.ApplicationMetricCredentials{[]params.ApplicationMetricCredential{
   129  				{
   130  					s.application.Name(),
   131  					[]byte("creds 1234"),
   132  				},
   133  				{
   134  					"not-a-application",
   135  					[]byte("creds 4567"),
   136  				},
   137  			}},
   138  			params.ErrorResults{[]params.ErrorResult{
   139  				{Error: nil},
   140  				{Error: &params.Error{Message: `application "not-a-application" not found`, Code: "not found"}},
   141  			}},
   142  		},
   143  	}
   144  	for i, t := range tests {
   145  		c.Logf("Running test %d %v", i, t.about)
   146  		results, err := s.applicationAPI.SetMetricCredentials(t.args)
   147  		c.Assert(err, jc.ErrorIsNil)
   148  		c.Assert(results.Results, gc.HasLen, len(t.results.Results))
   149  		c.Assert(results, gc.DeepEquals, t.results)
   150  
   151  		for i, a := range t.args.Creds {
   152  			if t.results.Results[i].Error == nil {
   153  				svc, err := s.State.Application(a.ApplicationName)
   154  				c.Assert(err, jc.ErrorIsNil)
   155  				creds := svc.MetricCredentials()
   156  				c.Assert(creds, gc.DeepEquals, a.MetricCredentials)
   157  			}
   158  		}
   159  	}
   160  }
   161  
   162  func (s *serviceSuite) TestCompatibleSettingsParsing(c *gc.C) {
   163  	// Test the exported settings parsing in a compatible way.
   164  	s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   165  	svc, err := s.State.Application("dummy")
   166  	c.Assert(err, jc.ErrorIsNil)
   167  	ch, _, err := svc.Charm()
   168  	c.Assert(err, jc.ErrorIsNil)
   169  	c.Assert(ch.URL().String(), gc.Equals, "local:quantal/dummy-1")
   170  
   171  	// Empty string will be returned as nil.
   172  	options := map[string]string{
   173  		"title":    "foobar",
   174  		"username": "",
   175  	}
   176  	settings, err := application.ParseSettingsCompatible(ch.Config(), options)
   177  	c.Assert(err, jc.ErrorIsNil)
   178  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   179  		"title":    "foobar",
   180  		"username": nil,
   181  	})
   182  
   183  	// Illegal settings lead to an error.
   184  	options = map[string]string{
   185  		"yummy": "didgeridoo",
   186  	}
   187  	_, err = application.ParseSettingsCompatible(ch.Config(), options)
   188  	c.Assert(err, gc.ErrorMatches, `unknown option "yummy"`)
   189  }
   190  
   191  func (s *serviceSuite) TestServiceDeployWithStorage(c *gc.C) {
   192  	curl, ch := s.UploadCharm(c, "utopic/storage-block-10", "storage-block")
   193  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   194  		URL: curl.String(),
   195  	})
   196  	c.Assert(err, jc.ErrorIsNil)
   197  	storageConstraints := map[string]storage.Constraints{
   198  		"data": {
   199  			Count: 1,
   200  			Size:  1024,
   201  			Pool:  "environscoped-block",
   202  		},
   203  	}
   204  
   205  	var cons constraints.Value
   206  	args := params.ApplicationDeploy{
   207  		ApplicationName: "application",
   208  		CharmURL:        curl.String(),
   209  		NumUnits:        1,
   210  		Constraints:     cons,
   211  		Storage:         storageConstraints,
   212  	}
   213  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   214  		Applications: []params.ApplicationDeploy{args}},
   215  	)
   216  	c.Assert(err, jc.ErrorIsNil)
   217  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   218  		Results: []params.ErrorResult{{Error: nil}},
   219  	})
   220  	svc := apiservertesting.AssertPrincipalServiceDeployed(c, s.State, "application", curl, false, ch, cons)
   221  	storageConstraintsOut, err := svc.StorageConstraints()
   222  	c.Assert(err, jc.ErrorIsNil)
   223  	c.Assert(storageConstraintsOut, gc.DeepEquals, map[string]state.StorageConstraints{
   224  		"data": {
   225  			Count: 1,
   226  			Size:  1024,
   227  			Pool:  "environscoped-block",
   228  		},
   229  		"allecto": {
   230  			Count: 0,
   231  			Size:  1024,
   232  			Pool:  "loop",
   233  		},
   234  	})
   235  }
   236  
   237  func (s *serviceSuite) TestMinJujuVersionTooHigh(c *gc.C) {
   238  	curl, _ := s.UploadCharm(c, "quantal/minjujuversion-0", "minjujuversion")
   239  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   240  		URL: curl.String(),
   241  	})
   242  	match := fmt.Sprintf(`charm's min version (999.999.999) is higher than this juju environment's version (%s)`, jujuversion.Current)
   243  	c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(match))
   244  }
   245  
   246  func (s *serviceSuite) TestServiceDeployWithInvalidStoragePool(c *gc.C) {
   247  	curl, _ := s.UploadCharm(c, "utopic/storage-block-0", "storage-block")
   248  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   249  		URL: curl.String(),
   250  	})
   251  	c.Assert(err, jc.ErrorIsNil)
   252  	storageConstraints := map[string]storage.Constraints{
   253  		"data": storage.Constraints{
   254  			Pool:  "foo",
   255  			Count: 1,
   256  			Size:  1024,
   257  		},
   258  	}
   259  
   260  	var cons constraints.Value
   261  	args := params.ApplicationDeploy{
   262  		ApplicationName: "application",
   263  		CharmURL:        curl.String(),
   264  		NumUnits:        1,
   265  		Constraints:     cons,
   266  		Storage:         storageConstraints,
   267  	}
   268  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   269  		Applications: []params.ApplicationDeploy{args}},
   270  	)
   271  	c.Assert(err, jc.ErrorIsNil)
   272  	c.Assert(results.Results, gc.HasLen, 1)
   273  	c.Assert(results.Results[0].Error, gc.ErrorMatches, `.* pool "foo" not found`)
   274  }
   275  
   276  func (s *serviceSuite) TestServiceDeployDefaultFilesystemStorage(c *gc.C) {
   277  	curl, ch := s.UploadCharm(c, "trusty/storage-filesystem-1", "storage-filesystem")
   278  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   279  		URL: curl.String(),
   280  	})
   281  	c.Assert(err, jc.ErrorIsNil)
   282  	var cons constraints.Value
   283  	args := params.ApplicationDeploy{
   284  		ApplicationName: "application",
   285  		CharmURL:        curl.String(),
   286  		NumUnits:        1,
   287  		Constraints:     cons,
   288  	}
   289  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   290  		Applications: []params.ApplicationDeploy{args}},
   291  	)
   292  	c.Assert(err, jc.ErrorIsNil)
   293  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   294  		Results: []params.ErrorResult{{Error: nil}},
   295  	})
   296  	svc := apiservertesting.AssertPrincipalServiceDeployed(c, s.State, "application", curl, false, ch, cons)
   297  	storageConstraintsOut, err := svc.StorageConstraints()
   298  	c.Assert(err, jc.ErrorIsNil)
   299  	c.Assert(storageConstraintsOut, gc.DeepEquals, map[string]state.StorageConstraints{
   300  		"data": {
   301  			Count: 1,
   302  			Size:  1024,
   303  			Pool:  "rootfs",
   304  		},
   305  	})
   306  }
   307  
   308  func (s *serviceSuite) TestServiceDeploy(c *gc.C) {
   309  	curl, ch := s.UploadCharm(c, "precise/dummy-42", "dummy")
   310  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   311  		URL: curl.String(),
   312  	})
   313  	c.Assert(err, jc.ErrorIsNil)
   314  	var cons constraints.Value
   315  	args := params.ApplicationDeploy{
   316  		ApplicationName: "application",
   317  		CharmURL:        curl.String(),
   318  		NumUnits:        1,
   319  		Constraints:     cons,
   320  		Placement: []*instance.Placement{
   321  			{"deadbeef-0bad-400d-8000-4b1d0d06f00d", "valid"},
   322  		},
   323  	}
   324  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   325  		Applications: []params.ApplicationDeploy{args}},
   326  	)
   327  	c.Assert(err, jc.ErrorIsNil)
   328  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   329  		Results: []params.ErrorResult{{Error: nil}},
   330  	})
   331  	svc := apiservertesting.AssertPrincipalServiceDeployed(c, s.State, "application", curl, false, ch, cons)
   332  	units, err := svc.AllUnits()
   333  	c.Assert(err, jc.ErrorIsNil)
   334  	c.Assert(units, gc.HasLen, 1)
   335  }
   336  
   337  func (s *serviceSuite) TestServiceDeployWithInvalidPlacement(c *gc.C) {
   338  	curl, _ := s.UploadCharm(c, "precise/dummy-42", "dummy")
   339  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   340  		URL: curl.String(),
   341  	})
   342  	c.Assert(err, jc.ErrorIsNil)
   343  	var cons constraints.Value
   344  	args := params.ApplicationDeploy{
   345  		ApplicationName: "application",
   346  		CharmURL:        curl.String(),
   347  		NumUnits:        1,
   348  		Constraints:     cons,
   349  		Placement: []*instance.Placement{
   350  			{"deadbeef-0bad-400d-8000-4b1d0d06f00d", "invalid"},
   351  		},
   352  	}
   353  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   354  		Applications: []params.ApplicationDeploy{args}},
   355  	)
   356  	c.Assert(err, jc.ErrorIsNil)
   357  	c.Assert(results.Results, gc.HasLen, 1)
   358  	c.Assert(results.Results[0].Error, gc.NotNil)
   359  	c.Assert(results.Results[0].Error.Error(), gc.Matches, ".* invalid placement is invalid")
   360  }
   361  
   362  func (s *serviceSuite) testClientServicesDeployWithBindings(c *gc.C, endpointBindings, expected map[string]string) {
   363  	curl, _ := s.UploadCharm(c, "utopic/riak-42", "riak")
   364  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   365  		URL: curl.String(),
   366  	})
   367  	c.Assert(err, jc.ErrorIsNil)
   368  
   369  	var cons constraints.Value
   370  	args := params.ApplicationDeploy{
   371  		ApplicationName:  "application",
   372  		CharmURL:         curl.String(),
   373  		NumUnits:         1,
   374  		Constraints:      cons,
   375  		EndpointBindings: endpointBindings,
   376  	}
   377  
   378  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   379  		Applications: []params.ApplicationDeploy{args}},
   380  	)
   381  	c.Assert(err, jc.ErrorIsNil)
   382  	c.Assert(results.Results, gc.HasLen, 1)
   383  	c.Assert(results.Results[0].Error, gc.IsNil)
   384  
   385  	application, err := s.State.Application(args.ApplicationName)
   386  	c.Assert(err, jc.ErrorIsNil)
   387  
   388  	retrievedBindings, err := application.EndpointBindings()
   389  	c.Assert(err, jc.ErrorIsNil)
   390  	c.Assert(retrievedBindings, jc.DeepEquals, expected)
   391  }
   392  
   393  func (s *serviceSuite) TestClientServicesDeployWithBindings(c *gc.C) {
   394  	s.State.AddSpace("a-space", "", nil, true)
   395  	expected := map[string]string{
   396  		"endpoint": "a-space",
   397  		"ring":     "",
   398  		"admin":    "",
   399  	}
   400  	endpointBindings := map[string]string{"endpoint": "a-space"}
   401  	s.testClientServicesDeployWithBindings(c, endpointBindings, expected)
   402  }
   403  
   404  func (s *serviceSuite) TestClientServicesDeployWithDefaultBindings(c *gc.C) {
   405  	expected := map[string]string{
   406  		"endpoint": "",
   407  		"ring":     "",
   408  		"admin":    "",
   409  	}
   410  	s.testClientServicesDeployWithBindings(c, nil, expected)
   411  }
   412  
   413  // TODO(wallyworld) - the following charm tests have been moved from the apiserver/client
   414  // package in order to use the fake charm store testing infrastructure. They are legacy tests
   415  // written to use the api client instead of the apiserver logic. They need to be rewritten and
   416  // feature tests added.
   417  
   418  func (s *serviceSuite) TestAddCharm(c *gc.C) {
   419  	var blobs blobs
   420  	s.PatchValue(application.NewStateStorage, func(uuid string, session *mgo.Session) statestorage.Storage {
   421  		storage := statestorage.NewStorage(uuid, session)
   422  		return &recordingStorage{Storage: storage, blobs: &blobs}
   423  	})
   424  
   425  	client := s.APIState.Client()
   426  	// First test the sanity checks.
   427  	err := client.AddCharm(&charm.URL{Name: "nonsense"}, csparams.StableChannel)
   428  	c.Assert(err, gc.ErrorMatches, `cannot parse charm or bundle URL: ":nonsense-0"`)
   429  	err = client.AddCharm(charm.MustParseURL("local:precise/dummy"), csparams.StableChannel)
   430  	c.Assert(err, gc.ErrorMatches, "only charm store charm URLs are supported, with cs: schema")
   431  	err = client.AddCharm(charm.MustParseURL("cs:precise/wordpress"), csparams.StableChannel)
   432  	c.Assert(err, gc.ErrorMatches, "charm URL must include revision")
   433  
   434  	// Add a charm, without uploading it to storage, to
   435  	// check that AddCharm does not try to do it.
   436  	charmDir := testcharms.Repo.CharmDir("dummy")
   437  	ident := fmt.Sprintf("%s-%d", charmDir.Meta().Name, charmDir.Revision())
   438  	curl := charm.MustParseURL("cs:quantal/" + ident)
   439  	info := state.CharmInfo{
   440  		Charm:       charmDir,
   441  		ID:          curl,
   442  		StoragePath: "",
   443  		SHA256:      ident + "-sha256",
   444  	}
   445  	sch, err := s.State.AddCharm(info)
   446  	c.Assert(err, jc.ErrorIsNil)
   447  
   448  	// AddCharm should see the charm in state and not upload it.
   449  	err = client.AddCharm(sch.URL(), csparams.StableChannel)
   450  	c.Assert(err, jc.ErrorIsNil)
   451  
   452  	c.Assert(blobs.m, gc.HasLen, 0)
   453  
   454  	// Now try adding another charm completely.
   455  	curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress")
   456  	err = client.AddCharm(curl, csparams.StableChannel)
   457  	c.Assert(err, jc.ErrorIsNil)
   458  
   459  	// Verify it's in state and it got uploaded.
   460  	storage := statestorage.NewStorage(s.State.ModelUUID(), s.State.MongoSession())
   461  	sch, err = s.State.Charm(curl)
   462  	c.Assert(err, jc.ErrorIsNil)
   463  	s.assertUploaded(c, storage, sch.StoragePath(), sch.BundleSha256())
   464  }
   465  
   466  func (s *serviceSuite) TestAddCharmWithAuthorization(c *gc.C) {
   467  	// Upload a new charm to the charm store.
   468  	curl, _ := s.UploadCharm(c, "cs:~restricted/precise/wordpress-3", "wordpress")
   469  
   470  	// Change permissions on the new charm such that only bob
   471  	// can read from it.
   472  	s.DischargeUser = "restricted"
   473  	err := s.Client.Put("/"+curl.Path()+"/meta/perm/read", []string{"bob"})
   474  	c.Assert(err, jc.ErrorIsNil)
   475  
   476  	// Try to add a charm to the environment without authorization.
   477  	s.DischargeUser = ""
   478  	err = s.APIState.Client().AddCharm(curl, csparams.StableChannel)
   479  	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\)`)
   480  
   481  	tryAs := func(user string) error {
   482  		client := csclient.New(csclient.Params{
   483  			URL: s.Srv.URL,
   484  		})
   485  		s.DischargeUser = user
   486  		var m *macaroon.Macaroon
   487  		err = client.Get("/delegatable-macaroon", &m)
   488  		c.Assert(err, gc.IsNil)
   489  
   490  		return application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   491  			URL:     curl.String(),
   492  			Channel: string(csparams.StableChannel),
   493  		})
   494  	}
   495  	// Try again with authorization for the wrong user.
   496  	err = tryAs("joe")
   497  	c.Assert(err, gc.ErrorMatches, `cannot retrieve charm "cs:~restricted/precise/wordpress-3": cannot get archive: unauthorized: access denied for user "joe"`)
   498  
   499  	// Try again with the correct authorization this time.
   500  	err = tryAs("bob")
   501  	c.Assert(err, gc.IsNil)
   502  
   503  	// Verify that it has actually been uploaded.
   504  	_, err = s.State.Charm(curl)
   505  	c.Assert(err, gc.IsNil)
   506  }
   507  
   508  func (s *serviceSuite) TestAddCharmConcurrently(c *gc.C) {
   509  	c.Skip("see lp:1596960 -- bad test for bad code")
   510  
   511  	var putBarrier sync.WaitGroup
   512  	var blobs blobs
   513  	s.PatchValue(application.NewStateStorage, func(uuid string, session *mgo.Session) statestorage.Storage {
   514  		storage := statestorage.NewStorage(uuid, session)
   515  		return &recordingStorage{Storage: storage, blobs: &blobs, putBarrier: &putBarrier}
   516  	})
   517  
   518  	client := s.APIState.Client()
   519  	curl, _ := s.UploadCharm(c, "trusty/wordpress-3", "wordpress")
   520  
   521  	// Try adding the same charm concurrently from multiple goroutines
   522  	// to test no "duplicate key errors" are reported (see lp bug
   523  	// #1067979) and also at the end only one charm document is
   524  	// created.
   525  
   526  	var wg sync.WaitGroup
   527  	// We don't add them 1-by-1 because that would allow each goroutine to
   528  	// finish separately without actually synchronizing between them
   529  	putBarrier.Add(10)
   530  	for i := 0; i < 10; i++ {
   531  		wg.Add(1)
   532  		go func(index int) {
   533  			defer wg.Done()
   534  
   535  			c.Assert(client.AddCharm(curl, csparams.StableChannel), gc.IsNil, gc.Commentf("goroutine %d", index))
   536  			sch, err := s.State.Charm(curl)
   537  			c.Assert(err, gc.IsNil, gc.Commentf("goroutine %d", index))
   538  			c.Assert(sch.URL(), jc.DeepEquals, curl, gc.Commentf("goroutine %d", index))
   539  		}(i)
   540  	}
   541  	wg.Wait()
   542  
   543  	blobs.Lock()
   544  
   545  	c.Assert(blobs.m, gc.HasLen, 10)
   546  
   547  	// Verify there is only a single uploaded charm remains and it
   548  	// contains the correct data.
   549  	sch, err := s.State.Charm(curl)
   550  	c.Assert(err, jc.ErrorIsNil)
   551  	storagePath := sch.StoragePath()
   552  	c.Assert(blobs.m[storagePath], jc.IsTrue)
   553  	for path, exists := range blobs.m {
   554  		if path != storagePath {
   555  			c.Assert(exists, jc.IsFalse)
   556  		}
   557  	}
   558  
   559  	storage := statestorage.NewStorage(s.State.ModelUUID(), s.State.MongoSession())
   560  	s.assertUploaded(c, storage, sch.StoragePath(), sch.BundleSha256())
   561  }
   562  
   563  func (s *serviceSuite) assertUploaded(c *gc.C, storage statestorage.Storage, storagePath, expectedSHA256 string) {
   564  	reader, _, err := storage.Get(storagePath)
   565  	c.Assert(err, jc.ErrorIsNil)
   566  	defer reader.Close()
   567  	downloadedSHA256, _, err := utils.ReadSHA256(reader)
   568  	c.Assert(err, jc.ErrorIsNil)
   569  	c.Assert(downloadedSHA256, gc.Equals, expectedSHA256)
   570  }
   571  
   572  func (s *serviceSuite) TestAddCharmOverwritesPlaceholders(c *gc.C) {
   573  	client := s.APIState.Client()
   574  	curl, _ := s.UploadCharm(c, "trusty/wordpress-42", "wordpress")
   575  
   576  	// Add a placeholder with the same charm URL.
   577  	err := s.State.AddStoreCharmPlaceholder(curl)
   578  	c.Assert(err, jc.ErrorIsNil)
   579  	_, err = s.State.Charm(curl)
   580  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   581  
   582  	// Now try to add the charm, which will convert the placeholder to
   583  	// a pending charm.
   584  	err = client.AddCharm(curl, csparams.StableChannel)
   585  	c.Assert(err, jc.ErrorIsNil)
   586  
   587  	// Make sure the document's flags were reset as expected.
   588  	sch, err := s.State.Charm(curl)
   589  	c.Assert(err, jc.ErrorIsNil)
   590  	c.Assert(sch.URL(), jc.DeepEquals, curl)
   591  	c.Assert(sch.IsPlaceholder(), jc.IsFalse)
   592  	c.Assert(sch.IsUploaded(), jc.IsTrue)
   593  }
   594  
   595  func (s *serviceSuite) TestServiceGetCharmURL(c *gc.C) {
   596  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   597  	result, err := s.applicationAPI.GetCharmURL(params.ApplicationGet{"wordpress"})
   598  	c.Assert(err, jc.ErrorIsNil)
   599  	c.Assert(result.Error, gc.IsNil)
   600  	c.Assert(result.Result, gc.Equals, "local:quantal/wordpress-3")
   601  }
   602  
   603  func (s *serviceSuite) TestServiceSetCharm(c *gc.C) {
   604  	curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy")
   605  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   606  		URL: curl.String(),
   607  	})
   608  	c.Assert(err, jc.ErrorIsNil)
   609  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   610  		Applications: []params.ApplicationDeploy{{
   611  			CharmURL:        curl.String(),
   612  			ApplicationName: "application",
   613  			NumUnits:        3,
   614  		}}})
   615  	c.Assert(err, jc.ErrorIsNil)
   616  	c.Assert(results.Results, gc.HasLen, 1)
   617  	c.Assert(results.Results[0].Error, gc.IsNil)
   618  	curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress")
   619  	err = application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   620  		URL: curl.String(),
   621  	})
   622  	c.Assert(err, jc.ErrorIsNil)
   623  	err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{
   624  		ApplicationName: "application",
   625  		CharmURL:        curl.String(),
   626  	})
   627  	c.Assert(err, jc.ErrorIsNil)
   628  
   629  	// Ensure that the charm is not marked as forced.
   630  	application, err := s.State.Application("application")
   631  	c.Assert(err, jc.ErrorIsNil)
   632  	charm, force, err := application.Charm()
   633  	c.Assert(err, jc.ErrorIsNil)
   634  	c.Assert(charm.URL().String(), gc.Equals, curl.String())
   635  	c.Assert(force, jc.IsFalse)
   636  }
   637  
   638  func (s *serviceSuite) setupServiceSetCharm(c *gc.C) {
   639  	curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy")
   640  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   641  		URL: curl.String(),
   642  	})
   643  	c.Assert(err, jc.ErrorIsNil)
   644  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   645  		Applications: []params.ApplicationDeploy{{
   646  			CharmURL:        curl.String(),
   647  			ApplicationName: "application",
   648  			NumUnits:        3,
   649  		}}})
   650  	c.Assert(err, jc.ErrorIsNil)
   651  	c.Assert(results.Results, gc.HasLen, 1)
   652  	c.Assert(results.Results[0].Error, gc.IsNil)
   653  	curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress")
   654  	err = application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   655  		URL: curl.String(),
   656  	})
   657  	c.Assert(err, jc.ErrorIsNil)
   658  }
   659  
   660  func (s *serviceSuite) assertServiceSetCharm(c *gc.C, forceUnits bool) {
   661  	err := s.applicationAPI.SetCharm(params.ApplicationSetCharm{
   662  		ApplicationName: "application",
   663  		CharmURL:        "cs:~who/precise/wordpress-3",
   664  		ForceUnits:      forceUnits,
   665  	})
   666  	c.Assert(err, jc.ErrorIsNil)
   667  	// Ensure that the charm is not marked as forced.
   668  	application, err := s.State.Application("application")
   669  	c.Assert(err, jc.ErrorIsNil)
   670  	charm, _, err := application.Charm()
   671  	c.Assert(err, jc.ErrorIsNil)
   672  	c.Assert(charm.URL().String(), gc.Equals, "cs:~who/precise/wordpress-3")
   673  }
   674  
   675  func (s *serviceSuite) assertServiceSetCharmBlocked(c *gc.C, msg string) {
   676  	err := s.applicationAPI.SetCharm(params.ApplicationSetCharm{
   677  		ApplicationName: "application",
   678  		CharmURL:        "cs:~who/precise/wordpress-3",
   679  	})
   680  	s.AssertBlocked(c, err, msg)
   681  }
   682  
   683  func (s *serviceSuite) TestBlockDestroyServiceSetCharm(c *gc.C) {
   684  	s.setupServiceSetCharm(c)
   685  	s.BlockDestroyModel(c, "TestBlockDestroyServiceSetCharm")
   686  	s.assertServiceSetCharm(c, false)
   687  }
   688  
   689  func (s *serviceSuite) TestBlockRemoveServiceSetCharm(c *gc.C) {
   690  	s.setupServiceSetCharm(c)
   691  	s.BlockRemoveObject(c, "TestBlockRemoveServiceSetCharm")
   692  	s.assertServiceSetCharm(c, false)
   693  }
   694  
   695  func (s *serviceSuite) TestBlockChangesServiceSetCharm(c *gc.C) {
   696  	s.setupServiceSetCharm(c)
   697  	s.BlockAllChanges(c, "TestBlockChangesServiceSetCharm")
   698  	s.assertServiceSetCharmBlocked(c, "TestBlockChangesServiceSetCharm")
   699  }
   700  
   701  func (s *serviceSuite) TestServiceSetCharmForceUnits(c *gc.C) {
   702  	curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy")
   703  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   704  		URL: curl.String(),
   705  	})
   706  	c.Assert(err, jc.ErrorIsNil)
   707  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   708  		Applications: []params.ApplicationDeploy{{
   709  			CharmURL:        curl.String(),
   710  			ApplicationName: "application",
   711  			NumUnits:        3,
   712  		}}})
   713  	c.Assert(err, jc.ErrorIsNil)
   714  	c.Assert(results.Results, gc.HasLen, 1)
   715  	c.Assert(results.Results[0].Error, gc.IsNil)
   716  	curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress")
   717  	err = application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   718  		URL: curl.String(),
   719  	})
   720  	c.Assert(err, jc.ErrorIsNil)
   721  	err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{
   722  		ApplicationName: "application",
   723  		CharmURL:        curl.String(),
   724  		ForceUnits:      true,
   725  	})
   726  	c.Assert(err, jc.ErrorIsNil)
   727  
   728  	// Ensure that the charm is marked as forced.
   729  	application, err := s.State.Application("application")
   730  	c.Assert(err, jc.ErrorIsNil)
   731  	charm, force, err := application.Charm()
   732  	c.Assert(err, jc.ErrorIsNil)
   733  	c.Assert(charm.URL().String(), gc.Equals, curl.String())
   734  	c.Assert(force, jc.IsTrue)
   735  }
   736  
   737  func (s *serviceSuite) TestBlockServiceSetCharmForce(c *gc.C) {
   738  	s.setupServiceSetCharm(c)
   739  
   740  	// block all changes
   741  	s.BlockAllChanges(c, "TestBlockServiceSetCharmForce")
   742  	s.BlockRemoveObject(c, "TestBlockServiceSetCharmForce")
   743  	s.BlockDestroyModel(c, "TestBlockServiceSetCharmForce")
   744  
   745  	s.assertServiceSetCharm(c, true)
   746  }
   747  
   748  func (s *serviceSuite) TestServiceSetCharmInvalidService(c *gc.C) {
   749  	err := s.applicationAPI.SetCharm(params.ApplicationSetCharm{
   750  		ApplicationName: "badservice",
   751  		CharmURL:        "cs:precise/wordpress-3",
   752  		ForceSeries:     true,
   753  		ForceUnits:      true,
   754  	})
   755  	c.Assert(err, gc.ErrorMatches, `application "badservice" not found`)
   756  }
   757  
   758  func (s *serviceSuite) TestServiceAddCharmErrors(c *gc.C) {
   759  	for url, expect := range map[string]string{
   760  		"wordpress":                   "charm URL must include revision",
   761  		"cs:wordpress":                "charm URL must include revision",
   762  		"cs:precise/wordpress":        "charm URL must include revision",
   763  		"cs:precise/wordpress-999999": `cannot retrieve "cs:precise/wordpress-999999": charm not found`,
   764  	} {
   765  		c.Logf("test %s", url)
   766  		err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   767  			URL: url,
   768  		})
   769  		c.Check(err, gc.ErrorMatches, expect)
   770  	}
   771  }
   772  
   773  func (s *serviceSuite) TestServiceSetCharmLegacy(c *gc.C) {
   774  	curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy")
   775  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   776  		URL: curl.String(),
   777  	})
   778  	c.Assert(err, jc.ErrorIsNil)
   779  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   780  		Applications: []params.ApplicationDeploy{{
   781  			CharmURL:        curl.String(),
   782  			ApplicationName: "application",
   783  		}}})
   784  	c.Assert(err, jc.ErrorIsNil)
   785  	c.Assert(results.Results, gc.HasLen, 1)
   786  	c.Assert(results.Results[0].Error, gc.IsNil)
   787  	curl, _ = s.UploadCharm(c, "trusty/dummy-1", "dummy")
   788  	err = application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   789  		URL: curl.String(),
   790  	})
   791  	c.Assert(err, jc.ErrorIsNil)
   792  
   793  	// Even with forceSeries = true, we can't change a charm where
   794  	// the series is sepcified in the URL.
   795  	err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{
   796  		ApplicationName: "application",
   797  		CharmURL:        curl.String(),
   798  		ForceSeries:     true,
   799  	})
   800  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "cs:~who/trusty/dummy-1": cannot change an application's series`)
   801  }
   802  
   803  func (s *serviceSuite) TestServiceSetCharmUnsupportedSeries(c *gc.C) {
   804  	curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series")
   805  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   806  		URL: curl.String(),
   807  	})
   808  	c.Assert(err, jc.ErrorIsNil)
   809  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   810  		Applications: []params.ApplicationDeploy{{
   811  			CharmURL:        curl.String(),
   812  			ApplicationName: "application",
   813  			Series:          "precise",
   814  		}}})
   815  	c.Assert(err, jc.ErrorIsNil)
   816  	c.Assert(results.Results, gc.HasLen, 1)
   817  	c.Assert(results.Results[0].Error, gc.IsNil)
   818  	curl, _ = s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series2")
   819  	err = application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   820  		URL: curl.String(),
   821  	})
   822  	c.Assert(err, jc.ErrorIsNil)
   823  
   824  	err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{
   825  		ApplicationName: "application",
   826  		CharmURL:        curl.String(),
   827  	})
   828  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "cs:~who/multi-series-1": only these series are supported: trusty, wily`)
   829  }
   830  
   831  func (s *serviceSuite) assertServiceSetCharmSeries(c *gc.C, upgradeCharm, series string) {
   832  	curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series")
   833  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   834  		URL: curl.String(),
   835  	})
   836  	c.Assert(err, jc.ErrorIsNil)
   837  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   838  		Applications: []params.ApplicationDeploy{{
   839  			CharmURL:        curl.String(),
   840  			ApplicationName: "application",
   841  			Series:          "precise",
   842  		}}})
   843  	c.Assert(err, jc.ErrorIsNil)
   844  	c.Assert(results.Results, gc.HasLen, 1)
   845  	c.Assert(results.Results[0].Error, gc.IsNil)
   846  
   847  	url := upgradeCharm
   848  	if series != "" {
   849  		url = series + "/" + upgradeCharm
   850  	}
   851  	curl, _ = s.UploadCharmMultiSeries(c, "~who/"+url, upgradeCharm)
   852  	err = application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   853  		URL: curl.String(),
   854  	})
   855  	c.Assert(err, jc.ErrorIsNil)
   856  
   857  	err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{
   858  		ApplicationName: "application",
   859  		CharmURL:        curl.String(),
   860  		ForceSeries:     true,
   861  	})
   862  	c.Assert(err, jc.ErrorIsNil)
   863  	svc, err := s.State.Application("application")
   864  	c.Assert(err, jc.ErrorIsNil)
   865  	ch, _, err := svc.Charm()
   866  	c.Assert(err, jc.ErrorIsNil)
   867  	c.Assert(ch.URL().String(), gc.Equals, "cs:~who/"+url+"-0")
   868  }
   869  
   870  func (s *serviceSuite) TestServiceSetCharmUnsupportedSeriesForce(c *gc.C) {
   871  	s.assertServiceSetCharmSeries(c, "multi-series2", "")
   872  }
   873  
   874  func (s *serviceSuite) TestServiceSetCharmNoExplicitSupportedSeries(c *gc.C) {
   875  	s.assertServiceSetCharmSeries(c, "dummy", "precise")
   876  }
   877  
   878  func (s *serviceSuite) TestServiceSetCharmWrongOS(c *gc.C) {
   879  	curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series")
   880  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   881  		URL: curl.String(),
   882  	})
   883  	c.Assert(err, jc.ErrorIsNil)
   884  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   885  		Applications: []params.ApplicationDeploy{{
   886  			CharmURL:        curl.String(),
   887  			ApplicationName: "application",
   888  			Series:          "precise",
   889  		}}})
   890  	c.Assert(err, jc.ErrorIsNil)
   891  	c.Assert(results.Results, gc.HasLen, 1)
   892  	c.Assert(results.Results[0].Error, gc.IsNil)
   893  	curl, _ = s.UploadCharmMultiSeries(c, "~who/multi-series-windows", "multi-series-windows")
   894  	err = application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   895  		URL: curl.String(),
   896  	})
   897  	c.Assert(err, jc.ErrorIsNil)
   898  
   899  	err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{
   900  		ApplicationName: "application",
   901  		CharmURL:        curl.String(),
   902  		ForceSeries:     true,
   903  	})
   904  	c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "cs:~who/multi-series-windows-0": OS "Ubuntu" not supported by charm`)
   905  }
   906  
   907  type testModeCharmRepo struct {
   908  	*charmrepo.CharmStore
   909  	testMode bool
   910  }
   911  
   912  // WithTestMode returns a repository Interface where test mode is enabled.
   913  func (s *testModeCharmRepo) WithTestMode() charmrepo.Interface {
   914  	s.testMode = true
   915  	return s.CharmStore.WithTestMode()
   916  }
   917  
   918  func (s *serviceSuite) TestSpecializeStoreOnDeployServiceSetCharmAndAddCharm(c *gc.C) {
   919  	repo := &testModeCharmRepo{}
   920  	s.PatchValue(&csclient.ServerURL, s.Srv.URL)
   921  	newCharmStoreRepo := application.NewCharmStoreRepo
   922  	s.PatchValue(&application.NewCharmStoreRepo, func(c *csclient.Client) charmrepo.Interface {
   923  		repo.CharmStore = newCharmStoreRepo(c).(*charmrepo.CharmStore)
   924  		return repo
   925  	})
   926  	attrs := map[string]interface{}{"test-mode": true}
   927  	err := s.State.UpdateModelConfig(attrs, nil, nil)
   928  	c.Assert(err, jc.ErrorIsNil)
   929  
   930  	// Check that the store's test mode is enabled when calling service Deploy.
   931  	curl, _ := s.UploadCharm(c, "trusty/dummy-1", "dummy")
   932  	err = application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   933  		URL: curl.String(),
   934  	})
   935  	c.Assert(err, jc.ErrorIsNil)
   936  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   937  		Applications: []params.ApplicationDeploy{{
   938  			CharmURL:        curl.String(),
   939  			ApplicationName: "application",
   940  			NumUnits:        3,
   941  		}}})
   942  	c.Assert(err, jc.ErrorIsNil)
   943  	c.Assert(results.Results, gc.HasLen, 1)
   944  	c.Assert(results.Results[0].Error, gc.IsNil)
   945  	c.Assert(repo.testMode, jc.IsTrue)
   946  
   947  	// Check that the store's test mode is enabled when calling SetCharm.
   948  	curl, _ = s.UploadCharm(c, "trusty/wordpress-2", "wordpress")
   949  	err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{
   950  		ApplicationName: "application",
   951  		CharmURL:        curl.String(),
   952  	})
   953  	c.Assert(repo.testMode, jc.IsTrue)
   954  
   955  	// Check that the store's test mode is enabled when calling AddCharm.
   956  	curl, _ = s.UploadCharm(c, "utopic/riak-42", "riak")
   957  	err = s.APIState.Client().AddCharm(curl, csparams.StableChannel)
   958  	c.Assert(err, jc.ErrorIsNil)
   959  	c.Assert(repo.testMode, jc.IsTrue)
   960  }
   961  
   962  func (s *serviceSuite) setupServiceDeploy(c *gc.C, args string) (*charm.URL, charm.Charm, constraints.Value) {
   963  	curl, ch := s.UploadCharm(c, "precise/dummy-42", "dummy")
   964  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
   965  		URL: curl.String(),
   966  	})
   967  	c.Assert(err, jc.ErrorIsNil)
   968  	cons := constraints.MustParse(args)
   969  	return curl, ch, cons
   970  }
   971  
   972  func (s *serviceSuite) assertServiceDeployPrincipal(c *gc.C, curl *charm.URL, ch charm.Charm, mem4g constraints.Value) {
   973  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   974  		Applications: []params.ApplicationDeploy{{
   975  			CharmURL:        curl.String(),
   976  			ApplicationName: "application",
   977  			NumUnits:        3,
   978  			Constraints:     mem4g,
   979  		}}})
   980  	c.Assert(err, jc.ErrorIsNil)
   981  	c.Assert(results.Results, gc.HasLen, 1)
   982  	c.Assert(results.Results[0].Error, gc.IsNil)
   983  	apiservertesting.AssertPrincipalServiceDeployed(c, s.State, "application", curl, false, ch, mem4g)
   984  }
   985  
   986  func (s *serviceSuite) assertServiceDeployPrincipalBlocked(c *gc.C, msg string, curl *charm.URL, mem4g constraints.Value) {
   987  	_, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
   988  		Applications: []params.ApplicationDeploy{{
   989  			CharmURL:        curl.String(),
   990  			ApplicationName: "application",
   991  			NumUnits:        3,
   992  			Constraints:     mem4g,
   993  		}}})
   994  	s.AssertBlocked(c, err, msg)
   995  }
   996  
   997  func (s *serviceSuite) TestBlockDestroyServiceDeployPrincipal(c *gc.C) {
   998  	curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G")
   999  	s.BlockDestroyModel(c, "TestBlockDestroyServiceDeployPrincipal")
  1000  	s.assertServiceDeployPrincipal(c, curl, bundle, cons)
  1001  }
  1002  
  1003  func (s *serviceSuite) TestBlockRemoveServiceDeployPrincipal(c *gc.C) {
  1004  	curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G")
  1005  	s.BlockRemoveObject(c, "TestBlockRemoveServiceDeployPrincipal")
  1006  	s.assertServiceDeployPrincipal(c, curl, bundle, cons)
  1007  }
  1008  
  1009  func (s *serviceSuite) TestBlockChangesServiceDeployPrincipal(c *gc.C) {
  1010  	curl, _, cons := s.setupServiceDeploy(c, "mem=4G")
  1011  	s.BlockAllChanges(c, "TestBlockChangesServiceDeployPrincipal")
  1012  	s.assertServiceDeployPrincipalBlocked(c, "TestBlockChangesServiceDeployPrincipal", curl, cons)
  1013  }
  1014  
  1015  func (s *serviceSuite) TestServiceDeploySubordinate(c *gc.C) {
  1016  	curl, ch := s.UploadCharm(c, "utopic/logging-47", "logging")
  1017  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
  1018  		URL: curl.String(),
  1019  	})
  1020  	c.Assert(err, jc.ErrorIsNil)
  1021  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1022  		Applications: []params.ApplicationDeploy{{
  1023  			CharmURL:        curl.String(),
  1024  			ApplicationName: "application-name",
  1025  		}}})
  1026  	c.Assert(err, jc.ErrorIsNil)
  1027  	c.Assert(results.Results, gc.HasLen, 1)
  1028  	c.Assert(results.Results[0].Error, gc.IsNil)
  1029  
  1030  	application, err := s.State.Application("application-name")
  1031  	c.Assert(err, jc.ErrorIsNil)
  1032  	charm, force, err := application.Charm()
  1033  	c.Assert(err, jc.ErrorIsNil)
  1034  	c.Assert(force, jc.IsFalse)
  1035  	c.Assert(charm.URL(), gc.DeepEquals, curl)
  1036  	c.Assert(charm.Meta(), gc.DeepEquals, ch.Meta())
  1037  	c.Assert(charm.Config(), gc.DeepEquals, ch.Config())
  1038  
  1039  	units, err := application.AllUnits()
  1040  	c.Assert(err, jc.ErrorIsNil)
  1041  	c.Assert(units, gc.HasLen, 0)
  1042  }
  1043  
  1044  func (s *serviceSuite) TestServiceDeployConfig(c *gc.C) {
  1045  	curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy")
  1046  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
  1047  		URL: curl.String(),
  1048  	})
  1049  	c.Assert(err, jc.ErrorIsNil)
  1050  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1051  		Applications: []params.ApplicationDeploy{{
  1052  			CharmURL:        curl.String(),
  1053  			ApplicationName: "application-name",
  1054  			NumUnits:        1,
  1055  			ConfigYAML:      "application-name:\n  username: fred",
  1056  		}}})
  1057  	c.Assert(err, jc.ErrorIsNil)
  1058  	c.Assert(results.Results, gc.HasLen, 1)
  1059  	c.Assert(results.Results[0].Error, gc.IsNil)
  1060  
  1061  	application, err := s.State.Application("application-name")
  1062  	c.Assert(err, jc.ErrorIsNil)
  1063  	settings, err := application.ConfigSettings()
  1064  	c.Assert(err, jc.ErrorIsNil)
  1065  	c.Assert(settings, gc.DeepEquals, charm.Settings{"username": "fred"})
  1066  }
  1067  
  1068  func (s *serviceSuite) TestServiceDeployConfigError(c *gc.C) {
  1069  	// TODO(fwereade): test Config/ConfigYAML handling directly on srvClient.
  1070  	// Can't be done cleanly until it's extracted similarly to Machiner.
  1071  	curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy")
  1072  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
  1073  		URL: curl.String(),
  1074  	})
  1075  	c.Assert(err, jc.ErrorIsNil)
  1076  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1077  		Applications: []params.ApplicationDeploy{{
  1078  			CharmURL:        curl.String(),
  1079  			ApplicationName: "application-name",
  1080  			NumUnits:        1,
  1081  			ConfigYAML:      "application-name:\n  skill-level: fred",
  1082  		}}})
  1083  	c.Assert(err, jc.ErrorIsNil)
  1084  	c.Assert(results.Results, gc.HasLen, 1)
  1085  	c.Assert(results.Results[0].Error, gc.ErrorMatches, `option "skill-level" expected int, got "fred"`)
  1086  	_, err = s.State.Application("application-name")
  1087  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1088  }
  1089  
  1090  func (s *serviceSuite) TestServiceDeployToMachine(c *gc.C) {
  1091  	curl, ch := s.UploadCharm(c, "precise/dummy-0", "dummy")
  1092  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
  1093  		URL: curl.String(),
  1094  	})
  1095  	c.Assert(err, jc.ErrorIsNil)
  1096  
  1097  	machine, err := s.State.AddMachine("precise", state.JobHostUnits)
  1098  	c.Assert(err, jc.ErrorIsNil)
  1099  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1100  		Applications: []params.ApplicationDeploy{{
  1101  			CharmURL:        curl.String(),
  1102  			ApplicationName: "application-name",
  1103  			NumUnits:        1,
  1104  			ConfigYAML:      "application-name:\n  username: fred",
  1105  		}}})
  1106  	c.Assert(err, jc.ErrorIsNil)
  1107  	c.Assert(results.Results, gc.HasLen, 1)
  1108  	c.Assert(results.Results[0].Error, gc.IsNil)
  1109  
  1110  	application, err := s.State.Application("application-name")
  1111  	c.Assert(err, jc.ErrorIsNil)
  1112  	charm, force, err := application.Charm()
  1113  	c.Assert(err, jc.ErrorIsNil)
  1114  	c.Assert(force, jc.IsFalse)
  1115  	c.Assert(charm.URL(), gc.DeepEquals, curl)
  1116  	c.Assert(charm.Meta(), gc.DeepEquals, ch.Meta())
  1117  	c.Assert(charm.Config(), gc.DeepEquals, ch.Config())
  1118  
  1119  	errs, err := s.APIState.UnitAssigner().AssignUnits([]names.UnitTag{names.NewUnitTag("application-name/0")})
  1120  	c.Assert(errs, gc.DeepEquals, []error{nil})
  1121  	c.Assert(err, jc.ErrorIsNil)
  1122  
  1123  	units, err := application.AllUnits()
  1124  	c.Assert(err, jc.ErrorIsNil)
  1125  	c.Assert(units, gc.HasLen, 1)
  1126  
  1127  	mid, err := units[0].AssignedMachineId()
  1128  	c.Assert(err, jc.ErrorIsNil)
  1129  	c.Assert(mid, gc.Equals, machine.Id())
  1130  }
  1131  
  1132  func (s *serviceSuite) TestServiceDeployToMachineNotFound(c *gc.C) {
  1133  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1134  		Applications: []params.ApplicationDeploy{{
  1135  			CharmURL:        "cs:precise/application-name-1",
  1136  			ApplicationName: "application-name",
  1137  			NumUnits:        1,
  1138  			Placement:       []*instance.Placement{instance.MustParsePlacement("42")},
  1139  		}}})
  1140  	c.Assert(err, jc.ErrorIsNil)
  1141  	c.Assert(results.Results, gc.HasLen, 1)
  1142  	c.Assert(results.Results[0].Error, gc.ErrorMatches, `cannot deploy "application-name" to machine 42: machine 42 not found`)
  1143  
  1144  	_, err = s.State.Application("application-name")
  1145  	c.Assert(err, gc.ErrorMatches, `application "application-name" not found`)
  1146  }
  1147  
  1148  func (s *serviceSuite) deployServiceForUpdateTests(c *gc.C) {
  1149  	curl, _ := s.UploadCharm(c, "precise/dummy-1", "dummy")
  1150  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
  1151  		URL: curl.String(),
  1152  	})
  1153  	c.Assert(err, jc.ErrorIsNil)
  1154  	results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{
  1155  		Applications: []params.ApplicationDeploy{{
  1156  			CharmURL:        curl.String(),
  1157  			ApplicationName: "application",
  1158  			NumUnits:        1,
  1159  		}}})
  1160  	c.Assert(err, jc.ErrorIsNil)
  1161  	c.Assert(results.Results, gc.HasLen, 1)
  1162  	c.Assert(results.Results[0].Error, gc.IsNil)
  1163  }
  1164  
  1165  func (s *serviceSuite) checkClientServiceUpdateSetCharm(c *gc.C, forceCharmURL bool) {
  1166  	s.deployServiceForUpdateTests(c)
  1167  	curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress")
  1168  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
  1169  		URL: curl.String(),
  1170  	})
  1171  	c.Assert(err, jc.ErrorIsNil)
  1172  
  1173  	// Update the charm for the application.
  1174  	args := params.ApplicationUpdate{
  1175  		ApplicationName: "application",
  1176  		CharmURL:        curl.String(),
  1177  		ForceCharmURL:   forceCharmURL,
  1178  	}
  1179  	err = s.applicationAPI.Update(args)
  1180  	c.Assert(err, jc.ErrorIsNil)
  1181  
  1182  	// Ensure the charm has been updated and and the force flag correctly set.
  1183  	application, err := s.State.Application("application")
  1184  	c.Assert(err, jc.ErrorIsNil)
  1185  	ch, force, err := application.Charm()
  1186  	c.Assert(err, jc.ErrorIsNil)
  1187  	c.Assert(ch.URL().String(), gc.Equals, curl.String())
  1188  	c.Assert(force, gc.Equals, forceCharmURL)
  1189  }
  1190  
  1191  func (s *serviceSuite) TestServiceUpdateSetCharm(c *gc.C) {
  1192  	s.checkClientServiceUpdateSetCharm(c, false)
  1193  }
  1194  
  1195  func (s *serviceSuite) TestBlockDestroyServiceUpdate(c *gc.C) {
  1196  	s.BlockDestroyModel(c, "TestBlockDestroyServiceUpdate")
  1197  	s.checkClientServiceUpdateSetCharm(c, false)
  1198  }
  1199  
  1200  func (s *serviceSuite) TestBlockRemoveServiceUpdate(c *gc.C) {
  1201  	s.BlockRemoveObject(c, "TestBlockRemoveServiceUpdate")
  1202  	s.checkClientServiceUpdateSetCharm(c, false)
  1203  }
  1204  
  1205  func (s *serviceSuite) setupServiceUpdate(c *gc.C) string {
  1206  	s.deployServiceForUpdateTests(c)
  1207  	curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress")
  1208  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
  1209  		URL: curl.String(),
  1210  	})
  1211  	c.Assert(err, jc.ErrorIsNil)
  1212  	return curl.String()
  1213  }
  1214  
  1215  func (s *serviceSuite) TestBlockChangeServiceUpdate(c *gc.C) {
  1216  	curl := s.setupServiceUpdate(c)
  1217  	s.BlockAllChanges(c, "TestBlockChangeServiceUpdate")
  1218  	// Update the charm for the application.
  1219  	args := params.ApplicationUpdate{
  1220  		ApplicationName: "application",
  1221  		CharmURL:        curl,
  1222  		ForceCharmURL:   false,
  1223  	}
  1224  	err := s.applicationAPI.Update(args)
  1225  	s.AssertBlocked(c, err, "TestBlockChangeServiceUpdate")
  1226  }
  1227  
  1228  func (s *serviceSuite) TestServiceUpdateForceSetCharm(c *gc.C) {
  1229  	s.checkClientServiceUpdateSetCharm(c, true)
  1230  }
  1231  
  1232  func (s *serviceSuite) TestBlockServiceUpdateForced(c *gc.C) {
  1233  	curl := s.setupServiceUpdate(c)
  1234  
  1235  	// block all changes. Force should ignore block :)
  1236  	s.BlockAllChanges(c, "TestBlockServiceUpdateForced")
  1237  	s.BlockDestroyModel(c, "TestBlockServiceUpdateForced")
  1238  	s.BlockRemoveObject(c, "TestBlockServiceUpdateForced")
  1239  
  1240  	// Update the charm for the application.
  1241  	args := params.ApplicationUpdate{
  1242  		ApplicationName: "application",
  1243  		CharmURL:        curl,
  1244  		ForceCharmURL:   true,
  1245  	}
  1246  	err := s.applicationAPI.Update(args)
  1247  	c.Assert(err, jc.ErrorIsNil)
  1248  
  1249  	// Ensure the charm has been updated and and the force flag correctly set.
  1250  	application, err := s.State.Application("application")
  1251  	c.Assert(err, jc.ErrorIsNil)
  1252  	ch, force, err := application.Charm()
  1253  	c.Assert(err, jc.ErrorIsNil)
  1254  	c.Assert(ch.URL().String(), gc.Equals, curl)
  1255  	c.Assert(force, jc.IsTrue)
  1256  }
  1257  
  1258  func (s *serviceSuite) TestServiceUpdateSetCharmNotFound(c *gc.C) {
  1259  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1260  	args := params.ApplicationUpdate{
  1261  		ApplicationName: "wordpress",
  1262  		CharmURL:        "cs:precise/wordpress-999999",
  1263  	}
  1264  	err := s.applicationAPI.Update(args)
  1265  	c.Check(err, gc.ErrorMatches, `charm "cs:precise/wordpress-999999" not found`)
  1266  }
  1267  
  1268  func (s *serviceSuite) TestServiceUpdateSetMinUnits(c *gc.C) {
  1269  	application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1270  
  1271  	// Set minimum units for the application.
  1272  	minUnits := 2
  1273  	args := params.ApplicationUpdate{
  1274  		ApplicationName: "dummy",
  1275  		MinUnits:        &minUnits,
  1276  	}
  1277  	err := s.applicationAPI.Update(args)
  1278  	c.Assert(err, jc.ErrorIsNil)
  1279  
  1280  	// Ensure the minimum number of units has been set.
  1281  	c.Assert(application.Refresh(), gc.IsNil)
  1282  	c.Assert(application.MinUnits(), gc.Equals, minUnits)
  1283  }
  1284  
  1285  func (s *serviceSuite) TestServiceUpdateSetMinUnitsError(c *gc.C) {
  1286  	application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1287  
  1288  	// Set a negative minimum number of units for the application.
  1289  	minUnits := -1
  1290  	args := params.ApplicationUpdate{
  1291  		ApplicationName: "dummy",
  1292  		MinUnits:        &minUnits,
  1293  	}
  1294  	err := s.applicationAPI.Update(args)
  1295  	c.Assert(err, gc.ErrorMatches,
  1296  		`cannot set minimum units for application "dummy": cannot set a negative minimum number of units`)
  1297  
  1298  	// Ensure the minimum number of units has not been set.
  1299  	c.Assert(application.Refresh(), gc.IsNil)
  1300  	c.Assert(application.MinUnits(), gc.Equals, 0)
  1301  }
  1302  
  1303  func (s *serviceSuite) TestServiceUpdateSetSettingsStrings(c *gc.C) {
  1304  	application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1305  
  1306  	// Update settings for the application.
  1307  	args := params.ApplicationUpdate{
  1308  		ApplicationName: "dummy",
  1309  		SettingsStrings: map[string]string{"title": "s-title", "username": "s-user"},
  1310  	}
  1311  	err := s.applicationAPI.Update(args)
  1312  	c.Assert(err, jc.ErrorIsNil)
  1313  
  1314  	// Ensure the settings have been correctly updated.
  1315  	expected := charm.Settings{"title": "s-title", "username": "s-user"}
  1316  	obtained, err := application.ConfigSettings()
  1317  	c.Assert(err, jc.ErrorIsNil)
  1318  	c.Assert(obtained, gc.DeepEquals, expected)
  1319  }
  1320  
  1321  func (s *serviceSuite) TestServiceUpdateSetSettingsYAML(c *gc.C) {
  1322  	application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1323  
  1324  	// Update settings for the application.
  1325  	args := params.ApplicationUpdate{
  1326  		ApplicationName: "dummy",
  1327  		SettingsYAML:    "dummy:\n  title: y-title\n  username: y-user",
  1328  	}
  1329  	err := s.applicationAPI.Update(args)
  1330  	c.Assert(err, jc.ErrorIsNil)
  1331  
  1332  	// Ensure the settings have been correctly updated.
  1333  	expected := charm.Settings{"title": "y-title", "username": "y-user"}
  1334  	obtained, err := application.ConfigSettings()
  1335  	c.Assert(err, jc.ErrorIsNil)
  1336  	c.Assert(obtained, gc.DeepEquals, expected)
  1337  }
  1338  
  1339  func (s *serviceSuite) TestClientServiceUpdateSetSettingsGetYAML(c *gc.C) {
  1340  	application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1341  
  1342  	// Update settings for the application.
  1343  	args := params.ApplicationUpdate{
  1344  		ApplicationName: "dummy",
  1345  		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",
  1346  	}
  1347  	err := s.applicationAPI.Update(args)
  1348  	c.Assert(err, jc.ErrorIsNil)
  1349  
  1350  	// Ensure the settings have been correctly updated.
  1351  	expected := charm.Settings{"title": "y-title", "username": "y-user"}
  1352  	obtained, err := application.ConfigSettings()
  1353  	c.Assert(err, jc.ErrorIsNil)
  1354  	c.Assert(obtained, gc.DeepEquals, expected)
  1355  }
  1356  
  1357  func (s *serviceSuite) TestServiceUpdateSetConstraints(c *gc.C) {
  1358  	application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1359  
  1360  	// Update constraints for the application.
  1361  	cons, err := constraints.Parse("mem=4096", "cores=2")
  1362  	c.Assert(err, jc.ErrorIsNil)
  1363  	args := params.ApplicationUpdate{
  1364  		ApplicationName: "dummy",
  1365  		Constraints:     &cons,
  1366  	}
  1367  	err = s.applicationAPI.Update(args)
  1368  	c.Assert(err, jc.ErrorIsNil)
  1369  
  1370  	// Ensure the constraints have been correctly updated.
  1371  	obtained, err := application.Constraints()
  1372  	c.Assert(err, jc.ErrorIsNil)
  1373  	c.Assert(obtained, gc.DeepEquals, cons)
  1374  }
  1375  
  1376  func (s *serviceSuite) TestServiceUpdateAllParams(c *gc.C) {
  1377  	s.deployServiceForUpdateTests(c)
  1378  	curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress")
  1379  	err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{
  1380  		URL: curl.String(),
  1381  	})
  1382  	c.Assert(err, jc.ErrorIsNil)
  1383  
  1384  	// Update all the service attributes.
  1385  	minUnits := 3
  1386  	cons, err := constraints.Parse("mem=4096", "cores=2")
  1387  	c.Assert(err, jc.ErrorIsNil)
  1388  	args := params.ApplicationUpdate{
  1389  		ApplicationName: "application",
  1390  		CharmURL:        curl.String(),
  1391  		ForceCharmURL:   true,
  1392  		MinUnits:        &minUnits,
  1393  		SettingsStrings: map[string]string{"blog-title": "string-title"},
  1394  		SettingsYAML:    "application:\n  blog-title: yaml-title\n",
  1395  		Constraints:     &cons,
  1396  	}
  1397  	err = s.applicationAPI.Update(args)
  1398  	c.Assert(err, jc.ErrorIsNil)
  1399  
  1400  	// Ensure the service has been correctly updated.
  1401  	application, err := s.State.Application("application")
  1402  	c.Assert(err, jc.ErrorIsNil)
  1403  
  1404  	// Check the charm.
  1405  	ch, force, err := application.Charm()
  1406  	c.Assert(err, jc.ErrorIsNil)
  1407  	c.Assert(ch.URL().String(), gc.Equals, curl.String())
  1408  	c.Assert(force, jc.IsTrue)
  1409  
  1410  	// Check the minimum number of units.
  1411  	c.Assert(application.MinUnits(), gc.Equals, minUnits)
  1412  
  1413  	// Check the settings: also ensure the YAML settings take precedence
  1414  	// over strings ones.
  1415  	expectedSettings := charm.Settings{"blog-title": "yaml-title"}
  1416  	obtainedSettings, err := application.ConfigSettings()
  1417  	c.Assert(err, jc.ErrorIsNil)
  1418  	c.Assert(obtainedSettings, gc.DeepEquals, expectedSettings)
  1419  
  1420  	// Check the constraints.
  1421  	obtainedConstraints, err := application.Constraints()
  1422  	c.Assert(err, jc.ErrorIsNil)
  1423  	c.Assert(obtainedConstraints, gc.DeepEquals, cons)
  1424  }
  1425  
  1426  func (s *serviceSuite) TestServiceUpdateNoParams(c *gc.C) {
  1427  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1428  
  1429  	// Calling Update with no parameters set is a no-op.
  1430  	args := params.ApplicationUpdate{ApplicationName: "wordpress"}
  1431  	err := s.applicationAPI.Update(args)
  1432  	c.Assert(err, jc.ErrorIsNil)
  1433  }
  1434  
  1435  func (s *serviceSuite) TestServiceUpdateNoService(c *gc.C) {
  1436  	err := s.applicationAPI.Update(params.ApplicationUpdate{})
  1437  	c.Assert(err, gc.ErrorMatches, `"" is not a valid application name`)
  1438  }
  1439  
  1440  func (s *serviceSuite) TestServiceUpdateInvalidService(c *gc.C) {
  1441  	args := params.ApplicationUpdate{ApplicationName: "no-such-service"}
  1442  	err := s.applicationAPI.Update(args)
  1443  	c.Assert(err, gc.ErrorMatches, `application "no-such-service" not found`)
  1444  }
  1445  
  1446  var (
  1447  	validSetTestValue = "a value with spaces\nand newline\nand UTF-8 characters: \U0001F604 / \U0001F44D"
  1448  )
  1449  
  1450  func (s *serviceSuite) TestServiceSet(c *gc.C) {
  1451  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1452  
  1453  	err := s.applicationAPI.Set(params.ApplicationSet{ApplicationName: "dummy", Options: map[string]string{
  1454  		"title":    "foobar",
  1455  		"username": validSetTestValue,
  1456  	}})
  1457  	c.Assert(err, jc.ErrorIsNil)
  1458  	settings, err := dummy.ConfigSettings()
  1459  	c.Assert(err, jc.ErrorIsNil)
  1460  	c.Assert(settings, gc.DeepEquals, charm.Settings{
  1461  		"title":    "foobar",
  1462  		"username": validSetTestValue,
  1463  	})
  1464  
  1465  	err = s.applicationAPI.Set(params.ApplicationSet{ApplicationName: "dummy", Options: map[string]string{
  1466  		"title":    "barfoo",
  1467  		"username": "",
  1468  	}})
  1469  	c.Assert(err, jc.ErrorIsNil)
  1470  	settings, err = dummy.ConfigSettings()
  1471  	c.Assert(err, jc.ErrorIsNil)
  1472  	c.Assert(settings, gc.DeepEquals, charm.Settings{
  1473  		"title":    "barfoo",
  1474  		"username": "",
  1475  	})
  1476  }
  1477  
  1478  func (s *serviceSuite) assertServiceSetBlocked(c *gc.C, dummy *state.Application, msg string) {
  1479  	err := s.applicationAPI.Set(params.ApplicationSet{
  1480  		ApplicationName: "dummy",
  1481  		Options: map[string]string{
  1482  			"title":    "foobar",
  1483  			"username": validSetTestValue}})
  1484  	s.AssertBlocked(c, err, msg)
  1485  }
  1486  
  1487  func (s *serviceSuite) assertServiceSet(c *gc.C, dummy *state.Application) {
  1488  	err := s.applicationAPI.Set(params.ApplicationSet{
  1489  		ApplicationName: "dummy",
  1490  		Options: map[string]string{
  1491  			"title":    "foobar",
  1492  			"username": validSetTestValue}})
  1493  	c.Assert(err, jc.ErrorIsNil)
  1494  	settings, err := dummy.ConfigSettings()
  1495  	c.Assert(err, jc.ErrorIsNil)
  1496  	c.Assert(settings, gc.DeepEquals, charm.Settings{
  1497  		"title":    "foobar",
  1498  		"username": validSetTestValue,
  1499  	})
  1500  }
  1501  
  1502  func (s *serviceSuite) TestBlockDestroyServiceSet(c *gc.C) {
  1503  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1504  	s.BlockDestroyModel(c, "TestBlockDestroyServiceSet")
  1505  	s.assertServiceSet(c, dummy)
  1506  }
  1507  
  1508  func (s *serviceSuite) TestBlockRemoveServiceSet(c *gc.C) {
  1509  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1510  	s.BlockRemoveObject(c, "TestBlockRemoveServiceSet")
  1511  	s.assertServiceSet(c, dummy)
  1512  }
  1513  
  1514  func (s *serviceSuite) TestBlockChangesServiceSet(c *gc.C) {
  1515  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1516  	s.BlockAllChanges(c, "TestBlockChangesServiceSet")
  1517  	s.assertServiceSetBlocked(c, dummy, "TestBlockChangesServiceSet")
  1518  }
  1519  
  1520  func (s *serviceSuite) TestServerUnset(c *gc.C) {
  1521  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1522  
  1523  	err := s.applicationAPI.Set(params.ApplicationSet{ApplicationName: "dummy", Options: map[string]string{
  1524  		"title":    "foobar",
  1525  		"username": "user name",
  1526  	}})
  1527  	c.Assert(err, jc.ErrorIsNil)
  1528  	settings, err := dummy.ConfigSettings()
  1529  	c.Assert(err, jc.ErrorIsNil)
  1530  	c.Assert(settings, gc.DeepEquals, charm.Settings{
  1531  		"title":    "foobar",
  1532  		"username": "user name",
  1533  	})
  1534  
  1535  	err = s.applicationAPI.Unset(params.ApplicationUnset{ApplicationName: "dummy", Options: []string{"username"}})
  1536  	c.Assert(err, jc.ErrorIsNil)
  1537  	settings, err = dummy.ConfigSettings()
  1538  	c.Assert(err, jc.ErrorIsNil)
  1539  	c.Assert(settings, gc.DeepEquals, charm.Settings{
  1540  		"title": "foobar",
  1541  	})
  1542  }
  1543  
  1544  func (s *serviceSuite) setupServerUnsetBlocked(c *gc.C) *state.Application {
  1545  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1546  
  1547  	err := s.applicationAPI.Set(params.ApplicationSet{
  1548  		ApplicationName: "dummy",
  1549  		Options: map[string]string{
  1550  			"title":    "foobar",
  1551  			"username": "user name",
  1552  		}})
  1553  	c.Assert(err, jc.ErrorIsNil)
  1554  	settings, err := dummy.ConfigSettings()
  1555  	c.Assert(err, jc.ErrorIsNil)
  1556  	c.Assert(settings, gc.DeepEquals, charm.Settings{
  1557  		"title":    "foobar",
  1558  		"username": "user name",
  1559  	})
  1560  	return dummy
  1561  }
  1562  
  1563  func (s *serviceSuite) assertServerUnset(c *gc.C, dummy *state.Application) {
  1564  	err := s.applicationAPI.Unset(params.ApplicationUnset{
  1565  		ApplicationName: "dummy",
  1566  		Options:         []string{"username"},
  1567  	})
  1568  	c.Assert(err, jc.ErrorIsNil)
  1569  	settings, err := dummy.ConfigSettings()
  1570  	c.Assert(err, jc.ErrorIsNil)
  1571  	c.Assert(settings, gc.DeepEquals, charm.Settings{
  1572  		"title": "foobar",
  1573  	})
  1574  }
  1575  
  1576  func (s *serviceSuite) assertServerUnsetBlocked(c *gc.C, dummy *state.Application, msg string) {
  1577  	err := s.applicationAPI.Unset(params.ApplicationUnset{
  1578  		ApplicationName: "dummy",
  1579  		Options:         []string{"username"},
  1580  	})
  1581  	s.AssertBlocked(c, err, msg)
  1582  }
  1583  
  1584  func (s *serviceSuite) TestBlockDestroyServerUnset(c *gc.C) {
  1585  	dummy := s.setupServerUnsetBlocked(c)
  1586  	s.BlockDestroyModel(c, "TestBlockDestroyServerUnset")
  1587  	s.assertServerUnset(c, dummy)
  1588  }
  1589  
  1590  func (s *serviceSuite) TestBlockRemoveServerUnset(c *gc.C) {
  1591  	dummy := s.setupServerUnsetBlocked(c)
  1592  	s.BlockRemoveObject(c, "TestBlockRemoveServerUnset")
  1593  	s.assertServerUnset(c, dummy)
  1594  }
  1595  
  1596  func (s *serviceSuite) TestBlockChangesServerUnset(c *gc.C) {
  1597  	dummy := s.setupServerUnsetBlocked(c)
  1598  	s.BlockAllChanges(c, "TestBlockChangesServerUnset")
  1599  	s.assertServerUnsetBlocked(c, dummy, "TestBlockChangesServerUnset")
  1600  }
  1601  
  1602  var clientAddServiceUnitsTests = []struct {
  1603  	about    string
  1604  	service  string // if not set, defaults to 'dummy'
  1605  	expected []string
  1606  	to       string
  1607  	err      string
  1608  }{
  1609  	{
  1610  		about:    "returns unit names",
  1611  		expected: []string{"dummy/0", "dummy/1", "dummy/2"},
  1612  	},
  1613  	{
  1614  		about: "fails trying to add zero units",
  1615  		err:   "must add at least one unit",
  1616  	},
  1617  	{
  1618  		// Note: chained-state, we add 1 unit here, but the 3 units
  1619  		// from the first condition still exist
  1620  		about:    "force the unit onto bootstrap machine",
  1621  		expected: []string{"dummy/3"},
  1622  		to:       "0",
  1623  	},
  1624  	{
  1625  		about:   "unknown service name",
  1626  		service: "unknown-service",
  1627  		err:     `application "unknown-service" not found`,
  1628  	},
  1629  }
  1630  
  1631  func (s *serviceSuite) TestClientAddServiceUnits(c *gc.C) {
  1632  	s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1633  	for i, t := range clientAddServiceUnitsTests {
  1634  		c.Logf("test %d. %s", i, t.about)
  1635  		serviceName := t.service
  1636  		if serviceName == "" {
  1637  			serviceName = "dummy"
  1638  		}
  1639  		args := params.AddApplicationUnits{
  1640  			ApplicationName: serviceName,
  1641  			NumUnits:        len(t.expected),
  1642  		}
  1643  		if t.to != "" {
  1644  			args.Placement = []*instance.Placement{instance.MustParsePlacement(t.to)}
  1645  		}
  1646  		result, err := s.applicationAPI.AddUnits(args)
  1647  		if t.err != "" {
  1648  			c.Assert(err, gc.ErrorMatches, t.err)
  1649  			continue
  1650  		}
  1651  		c.Assert(err, jc.ErrorIsNil)
  1652  		c.Assert(result.Units, gc.DeepEquals, t.expected)
  1653  	}
  1654  	// Test that we actually assigned the unit to machine 0
  1655  	forcedUnit, err := s.BackingState.Unit("dummy/3")
  1656  	c.Assert(err, jc.ErrorIsNil)
  1657  	assignedMachine, err := forcedUnit.AssignedMachineId()
  1658  	c.Assert(err, jc.ErrorIsNil)
  1659  	c.Assert(assignedMachine, gc.Equals, "0")
  1660  }
  1661  
  1662  func (s *serviceSuite) TestAddServiceUnitsToNewContainer(c *gc.C) {
  1663  	svc := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1664  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
  1665  	c.Assert(err, jc.ErrorIsNil)
  1666  
  1667  	_, err = s.applicationAPI.AddUnits(params.AddApplicationUnits{
  1668  		ApplicationName: "dummy",
  1669  		NumUnits:        1,
  1670  		Placement:       []*instance.Placement{instance.MustParsePlacement("lxd:" + machine.Id())},
  1671  	})
  1672  	c.Assert(err, jc.ErrorIsNil)
  1673  
  1674  	units, err := svc.AllUnits()
  1675  	c.Assert(err, jc.ErrorIsNil)
  1676  	mid, err := units[0].AssignedMachineId()
  1677  	c.Assert(err, jc.ErrorIsNil)
  1678  	c.Assert(mid, gc.Equals, machine.Id()+"/lxd/0")
  1679  }
  1680  
  1681  var addServiceUnitTests = []struct {
  1682  	about      string
  1683  	service    string // if not set, defaults to 'dummy'
  1684  	expected   []string
  1685  	machineIds []string
  1686  	placement  []*instance.Placement
  1687  	err        string
  1688  }{
  1689  	{
  1690  		about:      "valid placement directives",
  1691  		expected:   []string{"dummy/0"},
  1692  		placement:  []*instance.Placement{{"deadbeef-0bad-400d-8000-4b1d0d06f00d", "valid"}},
  1693  		machineIds: []string{"1"},
  1694  	}, {
  1695  		about:      "direct machine assignment placement directive",
  1696  		expected:   []string{"dummy/1", "dummy/2"},
  1697  		placement:  []*instance.Placement{{"#", "1"}, {"lxd", "1"}},
  1698  		machineIds: []string{"1", "1/lxd/0"},
  1699  	}, {
  1700  		about:     "invalid placement directive",
  1701  		err:       ".* invalid placement is invalid",
  1702  		expected:  []string{"dummy/3"},
  1703  		placement: []*instance.Placement{{"deadbeef-0bad-400d-8000-4b1d0d06f00d", "invalid"}},
  1704  	},
  1705  }
  1706  
  1707  func (s *serviceSuite) TestAddServiceUnits(c *gc.C) {
  1708  	s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1709  	// Add a machine for the units to be placed on.
  1710  	_, err := s.State.AddMachine("quantal", state.JobHostUnits)
  1711  	c.Assert(err, jc.ErrorIsNil)
  1712  	for i, t := range addServiceUnitTests {
  1713  		c.Logf("test %d. %s", i, t.about)
  1714  		serviceName := t.service
  1715  		if serviceName == "" {
  1716  			serviceName = "dummy"
  1717  		}
  1718  		result, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{
  1719  			ApplicationName: serviceName,
  1720  			NumUnits:        len(t.expected),
  1721  			Placement:       t.placement,
  1722  		})
  1723  		if t.err != "" {
  1724  			c.Assert(err, gc.ErrorMatches, t.err)
  1725  			continue
  1726  		}
  1727  		c.Assert(err, jc.ErrorIsNil)
  1728  		c.Assert(result.Units, gc.DeepEquals, t.expected)
  1729  		for i, unitName := range result.Units {
  1730  			u, err := s.BackingState.Unit(unitName)
  1731  			c.Assert(err, jc.ErrorIsNil)
  1732  			assignedMachine, err := u.AssignedMachineId()
  1733  			c.Assert(err, jc.ErrorIsNil)
  1734  			c.Assert(assignedMachine, gc.Equals, t.machineIds[i])
  1735  		}
  1736  	}
  1737  }
  1738  
  1739  func (s *serviceSuite) assertAddServiceUnits(c *gc.C) {
  1740  	result, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{
  1741  		ApplicationName: "dummy",
  1742  		NumUnits:        3,
  1743  	})
  1744  	c.Assert(err, jc.ErrorIsNil)
  1745  	c.Assert(result.Units, gc.DeepEquals, []string{"dummy/0", "dummy/1", "dummy/2"})
  1746  
  1747  	// Test that we actually assigned the unit to machine 0
  1748  	forcedUnit, err := s.BackingState.Unit("dummy/0")
  1749  	c.Assert(err, jc.ErrorIsNil)
  1750  	assignedMachine, err := forcedUnit.AssignedMachineId()
  1751  	c.Assert(err, jc.ErrorIsNil)
  1752  	c.Assert(assignedMachine, gc.Equals, "0")
  1753  }
  1754  
  1755  func (s *serviceSuite) TestServiceCharmRelations(c *gc.C) {
  1756  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1757  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
  1758  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  1759  	c.Assert(err, jc.ErrorIsNil)
  1760  	_, err = s.State.AddRelation(eps...)
  1761  	c.Assert(err, jc.ErrorIsNil)
  1762  
  1763  	_, err = s.applicationAPI.CharmRelations(params.ApplicationCharmRelations{"blah"})
  1764  	c.Assert(err, gc.ErrorMatches, `application "blah" not found`)
  1765  
  1766  	result, err := s.applicationAPI.CharmRelations(params.ApplicationCharmRelations{"wordpress"})
  1767  	c.Assert(err, jc.ErrorIsNil)
  1768  	c.Assert(result.CharmRelations, gc.DeepEquals, []string{
  1769  		"cache", "db", "juju-info", "logging-dir", "monitoring-port", "url",
  1770  	})
  1771  }
  1772  
  1773  func (s *serviceSuite) assertAddServiceUnitsBlocked(c *gc.C, msg string) {
  1774  	_, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{
  1775  		ApplicationName: "dummy",
  1776  		NumUnits:        3,
  1777  	})
  1778  	s.AssertBlocked(c, err, msg)
  1779  }
  1780  
  1781  func (s *serviceSuite) TestBlockDestroyAddServiceUnits(c *gc.C) {
  1782  	s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1783  	s.BlockDestroyModel(c, "TestBlockDestroyAddServiceUnits")
  1784  	s.assertAddServiceUnits(c)
  1785  }
  1786  
  1787  func (s *serviceSuite) TestBlockRemoveAddServiceUnits(c *gc.C) {
  1788  	s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1789  	s.BlockRemoveObject(c, "TestBlockRemoveAddServiceUnits")
  1790  	s.assertAddServiceUnits(c)
  1791  }
  1792  
  1793  func (s *serviceSuite) TestBlockChangeAddServiceUnits(c *gc.C) {
  1794  	s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1795  	s.BlockAllChanges(c, "TestBlockChangeAddServiceUnits")
  1796  	s.assertAddServiceUnitsBlocked(c, "TestBlockChangeAddServiceUnits")
  1797  }
  1798  
  1799  func (s *serviceSuite) TestAddUnitToMachineNotFound(c *gc.C) {
  1800  	s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1801  	_, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{
  1802  		ApplicationName: "dummy",
  1803  		NumUnits:        3,
  1804  		Placement:       []*instance.Placement{instance.MustParsePlacement("42")},
  1805  	})
  1806  	c.Assert(err, gc.ErrorMatches, `adding new machine to host unit "dummy/0": machine 42 not found`)
  1807  }
  1808  
  1809  func (s *serviceSuite) TestServiceExpose(c *gc.C) {
  1810  	charm := s.AddTestingCharm(c, "dummy")
  1811  	serviceNames := []string{"dummy-service", "exposed-service"}
  1812  	svcs := make([]*state.Application, len(serviceNames))
  1813  	var err error
  1814  	for i, name := range serviceNames {
  1815  		svcs[i] = s.AddTestingService(c, name, charm)
  1816  		c.Assert(svcs[i].IsExposed(), jc.IsFalse)
  1817  	}
  1818  	err = svcs[1].SetExposed()
  1819  	c.Assert(err, jc.ErrorIsNil)
  1820  	c.Assert(svcs[1].IsExposed(), jc.IsTrue)
  1821  	for i, t := range serviceExposeTests {
  1822  		c.Logf("test %d. %s", i, t.about)
  1823  		err = s.applicationAPI.Expose(params.ApplicationExpose{t.service})
  1824  		if t.err != "" {
  1825  			c.Assert(err, gc.ErrorMatches, t.err)
  1826  		} else {
  1827  			c.Assert(err, jc.ErrorIsNil)
  1828  			application, err := s.State.Application(t.service)
  1829  			c.Assert(err, jc.ErrorIsNil)
  1830  			c.Assert(application.IsExposed(), gc.Equals, t.exposed)
  1831  		}
  1832  	}
  1833  }
  1834  
  1835  func (s *serviceSuite) setupServiceExpose(c *gc.C) {
  1836  	charm := s.AddTestingCharm(c, "dummy")
  1837  	serviceNames := []string{"dummy-service", "exposed-service"}
  1838  	svcs := make([]*state.Application, len(serviceNames))
  1839  	var err error
  1840  	for i, name := range serviceNames {
  1841  		svcs[i] = s.AddTestingService(c, name, charm)
  1842  		c.Assert(svcs[i].IsExposed(), jc.IsFalse)
  1843  	}
  1844  	err = svcs[1].SetExposed()
  1845  	c.Assert(err, jc.ErrorIsNil)
  1846  	c.Assert(svcs[1].IsExposed(), jc.IsTrue)
  1847  }
  1848  
  1849  var serviceExposeTests = []struct {
  1850  	about   string
  1851  	service string
  1852  	err     string
  1853  	exposed bool
  1854  }{
  1855  	{
  1856  		about:   "unknown service name",
  1857  		service: "unknown-service",
  1858  		err:     `application "unknown-service" not found`,
  1859  	},
  1860  	{
  1861  		about:   "expose a service",
  1862  		service: "dummy-service",
  1863  		exposed: true,
  1864  	},
  1865  	{
  1866  		about:   "expose an already exposed service",
  1867  		service: "exposed-service",
  1868  		exposed: true,
  1869  	},
  1870  }
  1871  
  1872  func (s *serviceSuite) assertServiceExpose(c *gc.C) {
  1873  	for i, t := range serviceExposeTests {
  1874  		c.Logf("test %d. %s", i, t.about)
  1875  		err := s.applicationAPI.Expose(params.ApplicationExpose{t.service})
  1876  		if t.err != "" {
  1877  			c.Assert(err, gc.ErrorMatches, t.err)
  1878  		} else {
  1879  			c.Assert(err, jc.ErrorIsNil)
  1880  			application, err := s.State.Application(t.service)
  1881  			c.Assert(err, jc.ErrorIsNil)
  1882  			c.Assert(application.IsExposed(), gc.Equals, t.exposed)
  1883  		}
  1884  	}
  1885  }
  1886  
  1887  func (s *serviceSuite) assertServiceExposeBlocked(c *gc.C, msg string) {
  1888  	for i, t := range serviceExposeTests {
  1889  		c.Logf("test %d. %s", i, t.about)
  1890  		err := s.applicationAPI.Expose(params.ApplicationExpose{t.service})
  1891  		s.AssertBlocked(c, err, msg)
  1892  	}
  1893  }
  1894  
  1895  func (s *serviceSuite) TestBlockDestroyServiceExpose(c *gc.C) {
  1896  	s.setupServiceExpose(c)
  1897  	s.BlockDestroyModel(c, "TestBlockDestroyServiceExpose")
  1898  	s.assertServiceExpose(c)
  1899  }
  1900  
  1901  func (s *serviceSuite) TestBlockRemoveServiceExpose(c *gc.C) {
  1902  	s.setupServiceExpose(c)
  1903  	s.BlockRemoveObject(c, "TestBlockRemoveServiceExpose")
  1904  	s.assertServiceExpose(c)
  1905  }
  1906  
  1907  func (s *serviceSuite) TestBlockChangesServiceExpose(c *gc.C) {
  1908  	s.setupServiceExpose(c)
  1909  	s.BlockAllChanges(c, "TestBlockChangesServiceExpose")
  1910  	s.assertServiceExposeBlocked(c, "TestBlockChangesServiceExpose")
  1911  }
  1912  
  1913  var serviceUnexposeTests = []struct {
  1914  	about    string
  1915  	service  string
  1916  	err      string
  1917  	initial  bool
  1918  	expected bool
  1919  }{
  1920  	{
  1921  		about:   "unknown service name",
  1922  		service: "unknown-service",
  1923  		err:     `application "unknown-service" not found`,
  1924  	},
  1925  	{
  1926  		about:    "unexpose a service",
  1927  		service:  "dummy-service",
  1928  		initial:  true,
  1929  		expected: false,
  1930  	},
  1931  	{
  1932  		about:    "unexpose an already unexposed service",
  1933  		service:  "dummy-service",
  1934  		initial:  false,
  1935  		expected: false,
  1936  	},
  1937  }
  1938  
  1939  func (s *serviceSuite) TestServiceUnexpose(c *gc.C) {
  1940  	charm := s.AddTestingCharm(c, "dummy")
  1941  	for i, t := range serviceUnexposeTests {
  1942  		c.Logf("test %d. %s", i, t.about)
  1943  		svc := s.AddTestingService(c, "dummy-service", charm)
  1944  		if t.initial {
  1945  			svc.SetExposed()
  1946  		}
  1947  		c.Assert(svc.IsExposed(), gc.Equals, t.initial)
  1948  		err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{t.service})
  1949  		if t.err == "" {
  1950  			c.Assert(err, jc.ErrorIsNil)
  1951  			svc.Refresh()
  1952  			c.Assert(svc.IsExposed(), gc.Equals, t.expected)
  1953  		} else {
  1954  			c.Assert(err, gc.ErrorMatches, t.err)
  1955  		}
  1956  		err = svc.Destroy()
  1957  		c.Assert(err, jc.ErrorIsNil)
  1958  	}
  1959  }
  1960  
  1961  func (s *serviceSuite) setupServiceUnexpose(c *gc.C) *state.Application {
  1962  	charm := s.AddTestingCharm(c, "dummy")
  1963  	svc := s.AddTestingService(c, "dummy-service", charm)
  1964  	svc.SetExposed()
  1965  	c.Assert(svc.IsExposed(), gc.Equals, true)
  1966  	return svc
  1967  }
  1968  
  1969  func (s *serviceSuite) assertServiceUnexpose(c *gc.C, svc *state.Application) {
  1970  	err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{"dummy-service"})
  1971  	c.Assert(err, jc.ErrorIsNil)
  1972  	svc.Refresh()
  1973  	c.Assert(svc.IsExposed(), gc.Equals, false)
  1974  	err = svc.Destroy()
  1975  	c.Assert(err, jc.ErrorIsNil)
  1976  }
  1977  
  1978  func (s *serviceSuite) assertServiceUnexposeBlocked(c *gc.C, svc *state.Application, msg string) {
  1979  	err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{"dummy-service"})
  1980  	s.AssertBlocked(c, err, msg)
  1981  	err = svc.Destroy()
  1982  	c.Assert(err, jc.ErrorIsNil)
  1983  }
  1984  
  1985  func (s *serviceSuite) TestBlockDestroyServiceUnexpose(c *gc.C) {
  1986  	svc := s.setupServiceUnexpose(c)
  1987  	s.BlockDestroyModel(c, "TestBlockDestroyServiceUnexpose")
  1988  	s.assertServiceUnexpose(c, svc)
  1989  }
  1990  
  1991  func (s *serviceSuite) TestBlockRemoveServiceUnexpose(c *gc.C) {
  1992  	svc := s.setupServiceUnexpose(c)
  1993  	s.BlockRemoveObject(c, "TestBlockRemoveServiceUnexpose")
  1994  	s.assertServiceUnexpose(c, svc)
  1995  }
  1996  
  1997  func (s *serviceSuite) TestBlockChangesServiceUnexpose(c *gc.C) {
  1998  	svc := s.setupServiceUnexpose(c)
  1999  	s.BlockAllChanges(c, "TestBlockChangesServiceUnexpose")
  2000  	s.assertServiceUnexposeBlocked(c, svc, "TestBlockChangesServiceUnexpose")
  2001  }
  2002  
  2003  var serviceDestroyTests = []struct {
  2004  	about   string
  2005  	service string
  2006  	err     string
  2007  }{
  2008  	{
  2009  		about:   "unknown service name",
  2010  		service: "unknown-service",
  2011  		err:     `application "unknown-service" not found`,
  2012  	},
  2013  	{
  2014  		about:   "destroy a service",
  2015  		service: "dummy-service",
  2016  	},
  2017  	{
  2018  		about:   "destroy an already destroyed service",
  2019  		service: "dummy-service",
  2020  		err:     `application "dummy-service" not found`,
  2021  	},
  2022  }
  2023  
  2024  func (s *serviceSuite) TestServiceDestroy(c *gc.C) {
  2025  	s.AddTestingService(c, "dummy-service", s.AddTestingCharm(c, "dummy"))
  2026  	for i, t := range serviceDestroyTests {
  2027  		c.Logf("test %d. %s", i, t.about)
  2028  		err := s.applicationAPI.Destroy(params.ApplicationDestroy{t.service})
  2029  		if t.err != "" {
  2030  			c.Assert(err, gc.ErrorMatches, t.err)
  2031  		} else {
  2032  			c.Assert(err, jc.ErrorIsNil)
  2033  		}
  2034  	}
  2035  
  2036  	// Now do Destroy on a service with units. Destroy will
  2037  	// cause the service to be not-Alive, but will not remove its
  2038  	// document.
  2039  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2040  	serviceName := "wordpress"
  2041  	application, err := s.State.Application(serviceName)
  2042  	c.Assert(err, jc.ErrorIsNil)
  2043  	err = s.applicationAPI.Destroy(params.ApplicationDestroy{serviceName})
  2044  	c.Assert(err, jc.ErrorIsNil)
  2045  	err = application.Refresh()
  2046  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  2047  }
  2048  
  2049  func assertLife(c *gc.C, entity state.Living, life state.Life) {
  2050  	err := entity.Refresh()
  2051  	c.Assert(err, jc.ErrorIsNil)
  2052  	c.Assert(entity.Life(), gc.Equals, life)
  2053  }
  2054  
  2055  func (s *serviceSuite) TestBlockServiceDestroy(c *gc.C) {
  2056  	s.AddTestingService(c, "dummy-service", s.AddTestingCharm(c, "dummy"))
  2057  
  2058  	// block remove-objects
  2059  	s.BlockRemoveObject(c, "TestBlockServiceDestroy")
  2060  	err := s.applicationAPI.Destroy(params.ApplicationDestroy{"dummy-service"})
  2061  	s.AssertBlocked(c, err, "TestBlockServiceDestroy")
  2062  	// Tests may have invalid service names.
  2063  	application, err := s.State.Application("dummy-service")
  2064  	if err == nil {
  2065  		// For valid service names, check that service is alive :-)
  2066  		assertLife(c, application, state.Alive)
  2067  	}
  2068  }
  2069  
  2070  func (s *serviceSuite) TestDestroyPrincipalUnits(c *gc.C) {
  2071  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2072  	units := make([]*state.Unit, 5)
  2073  	for i := range units {
  2074  		unit, err := wordpress.AddUnit()
  2075  		c.Assert(err, jc.ErrorIsNil)
  2076  		now := time.Now()
  2077  		sInfo := status.StatusInfo{
  2078  			Status:  status.Idle,
  2079  			Message: "",
  2080  			Since:   &now,
  2081  		}
  2082  		err = unit.SetAgentStatus(sInfo)
  2083  		c.Assert(err, jc.ErrorIsNil)
  2084  		units[i] = unit
  2085  	}
  2086  	s.assertDestroyPrincipalUnits(c, units)
  2087  }
  2088  
  2089  func (s *serviceSuite) TestDestroySubordinateUnits(c *gc.C) {
  2090  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2091  	wordpress0, err := wordpress.AddUnit()
  2092  	c.Assert(err, jc.ErrorIsNil)
  2093  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
  2094  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  2095  	c.Assert(err, jc.ErrorIsNil)
  2096  	rel, err := s.State.AddRelation(eps...)
  2097  	c.Assert(err, jc.ErrorIsNil)
  2098  	ru, err := rel.Unit(wordpress0)
  2099  	c.Assert(err, jc.ErrorIsNil)
  2100  	err = ru.EnterScope(nil)
  2101  	c.Assert(err, jc.ErrorIsNil)
  2102  	logging0, err := s.State.Unit("logging/0")
  2103  	c.Assert(err, jc.ErrorIsNil)
  2104  
  2105  	// Try to destroy the subordinate alone; check it fails.
  2106  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2107  		UnitNames: []string{"logging/0"},
  2108  	})
  2109  	c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`)
  2110  	assertLife(c, logging0, state.Alive)
  2111  
  2112  	s.assertDestroySubordinateUnits(c, wordpress0, logging0)
  2113  }
  2114  
  2115  func (s *serviceSuite) assertDestroyPrincipalUnits(c *gc.C, units []*state.Unit) {
  2116  	// Destroy 2 of them; check they become Dying.
  2117  	err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2118  		UnitNames: []string{"wordpress/0", "wordpress/1"},
  2119  	})
  2120  	c.Assert(err, jc.ErrorIsNil)
  2121  	assertLife(c, units[0], state.Dying)
  2122  	assertLife(c, units[1], state.Dying)
  2123  
  2124  	// Try to destroy an Alive one and a Dying one; check
  2125  	// it destroys the Alive one and ignores the Dying one.
  2126  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2127  		UnitNames: []string{"wordpress/2", "wordpress/0"},
  2128  	})
  2129  	c.Assert(err, jc.ErrorIsNil)
  2130  	assertLife(c, units[2], state.Dying)
  2131  
  2132  	// Try to destroy an Alive one along with a nonexistent one; check that
  2133  	// the valid instruction is followed but the invalid one is warned about.
  2134  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2135  		UnitNames: []string{"boojum/123", "wordpress/3"},
  2136  	})
  2137  	c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`)
  2138  	assertLife(c, units[3], state.Dying)
  2139  
  2140  	// Make one Dead, and destroy an Alive one alongside it; check no errors.
  2141  	wp0, err := s.State.Unit("wordpress/0")
  2142  	c.Assert(err, jc.ErrorIsNil)
  2143  	err = wp0.EnsureDead()
  2144  	c.Assert(err, jc.ErrorIsNil)
  2145  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2146  		UnitNames: []string{"wordpress/0", "wordpress/4"},
  2147  	})
  2148  	c.Assert(err, jc.ErrorIsNil)
  2149  	assertLife(c, units[0], state.Dead)
  2150  	assertLife(c, units[4], state.Dying)
  2151  }
  2152  
  2153  func (s *serviceSuite) setupDestroyPrincipalUnits(c *gc.C) []*state.Unit {
  2154  	units := make([]*state.Unit, 5)
  2155  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2156  	for i := range units {
  2157  		unit, err := wordpress.AddUnit()
  2158  		c.Assert(err, jc.ErrorIsNil)
  2159  		now := time.Now()
  2160  		sInfo := status.StatusInfo{
  2161  			Status:  status.Idle,
  2162  			Message: "",
  2163  			Since:   &now,
  2164  		}
  2165  		err = unit.SetAgentStatus(sInfo)
  2166  		c.Assert(err, jc.ErrorIsNil)
  2167  		units[i] = unit
  2168  	}
  2169  	return units
  2170  }
  2171  
  2172  func (s *serviceSuite) assertBlockedErrorAndLiveliness(
  2173  	c *gc.C,
  2174  	err error,
  2175  	msg string,
  2176  	living1 state.Living,
  2177  	living2 state.Living,
  2178  	living3 state.Living,
  2179  	living4 state.Living,
  2180  ) {
  2181  	s.AssertBlocked(c, err, msg)
  2182  	assertLife(c, living1, state.Alive)
  2183  	assertLife(c, living2, state.Alive)
  2184  	assertLife(c, living3, state.Alive)
  2185  	assertLife(c, living4, state.Alive)
  2186  }
  2187  
  2188  func (s *serviceSuite) TestBlockChangesDestroyPrincipalUnits(c *gc.C) {
  2189  	units := s.setupDestroyPrincipalUnits(c)
  2190  	s.BlockAllChanges(c, "TestBlockChangesDestroyPrincipalUnits")
  2191  	err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2192  		UnitNames: []string{"wordpress/0", "wordpress/1"},
  2193  	})
  2194  	s.assertBlockedErrorAndLiveliness(c, err, "TestBlockChangesDestroyPrincipalUnits", units[0], units[1], units[2], units[3])
  2195  }
  2196  
  2197  func (s *serviceSuite) TestBlockRemoveDestroyPrincipalUnits(c *gc.C) {
  2198  	units := s.setupDestroyPrincipalUnits(c)
  2199  	s.BlockRemoveObject(c, "TestBlockRemoveDestroyPrincipalUnits")
  2200  	err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2201  		UnitNames: []string{"wordpress/0", "wordpress/1"},
  2202  	})
  2203  	s.assertBlockedErrorAndLiveliness(c, err, "TestBlockRemoveDestroyPrincipalUnits", units[0], units[1], units[2], units[3])
  2204  }
  2205  
  2206  func (s *serviceSuite) TestBlockDestroyDestroyPrincipalUnits(c *gc.C) {
  2207  	units := s.setupDestroyPrincipalUnits(c)
  2208  	s.BlockDestroyModel(c, "TestBlockDestroyDestroyPrincipalUnits")
  2209  	err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2210  		UnitNames: []string{"wordpress/0", "wordpress/1"},
  2211  	})
  2212  	c.Assert(err, jc.ErrorIsNil)
  2213  	assertLife(c, units[0], state.Dying)
  2214  	assertLife(c, units[1], state.Dying)
  2215  }
  2216  
  2217  func (s *serviceSuite) assertDestroySubordinateUnits(c *gc.C, wordpress0, logging0 *state.Unit) {
  2218  	// Try to destroy the principal and the subordinate together; check it warns
  2219  	// about the subordinate, but destroys the one it can. (The principal unit
  2220  	// agent will be responsible for destroying the subordinate.)
  2221  	err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2222  		UnitNames: []string{"wordpress/0", "logging/0"},
  2223  	})
  2224  	c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate`)
  2225  	assertLife(c, wordpress0, state.Dying)
  2226  	assertLife(c, logging0, state.Alive)
  2227  }
  2228  
  2229  func (s *serviceSuite) TestBlockRemoveDestroySubordinateUnits(c *gc.C) {
  2230  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2231  	wordpress0, err := wordpress.AddUnit()
  2232  	c.Assert(err, jc.ErrorIsNil)
  2233  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
  2234  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  2235  	c.Assert(err, jc.ErrorIsNil)
  2236  	rel, err := s.State.AddRelation(eps...)
  2237  	c.Assert(err, jc.ErrorIsNil)
  2238  	ru, err := rel.Unit(wordpress0)
  2239  	c.Assert(err, jc.ErrorIsNil)
  2240  	err = ru.EnterScope(nil)
  2241  	c.Assert(err, jc.ErrorIsNil)
  2242  	logging0, err := s.State.Unit("logging/0")
  2243  	c.Assert(err, jc.ErrorIsNil)
  2244  
  2245  	s.BlockRemoveObject(c, "TestBlockRemoveDestroySubordinateUnits")
  2246  	// Try to destroy the subordinate alone; check it fails.
  2247  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2248  		UnitNames: []string{"logging/0"},
  2249  	})
  2250  	s.AssertBlocked(c, err, "TestBlockRemoveDestroySubordinateUnits")
  2251  	assertLife(c, rel, state.Alive)
  2252  	assertLife(c, wordpress0, state.Alive)
  2253  	assertLife(c, logging0, state.Alive)
  2254  
  2255  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2256  		UnitNames: []string{"wordpress/0", "logging/0"},
  2257  	})
  2258  	s.AssertBlocked(c, err, "TestBlockRemoveDestroySubordinateUnits")
  2259  	assertLife(c, wordpress0, state.Alive)
  2260  	assertLife(c, logging0, state.Alive)
  2261  	assertLife(c, rel, state.Alive)
  2262  }
  2263  
  2264  func (s *serviceSuite) TestBlockChangesDestroySubordinateUnits(c *gc.C) {
  2265  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2266  	wordpress0, err := wordpress.AddUnit()
  2267  	c.Assert(err, jc.ErrorIsNil)
  2268  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
  2269  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  2270  	c.Assert(err, jc.ErrorIsNil)
  2271  	rel, err := s.State.AddRelation(eps...)
  2272  	c.Assert(err, jc.ErrorIsNil)
  2273  	ru, err := rel.Unit(wordpress0)
  2274  	c.Assert(err, jc.ErrorIsNil)
  2275  	err = ru.EnterScope(nil)
  2276  	c.Assert(err, jc.ErrorIsNil)
  2277  	logging0, err := s.State.Unit("logging/0")
  2278  	c.Assert(err, jc.ErrorIsNil)
  2279  
  2280  	s.BlockAllChanges(c, "TestBlockChangesDestroySubordinateUnits")
  2281  	// Try to destroy the subordinate alone; check it fails.
  2282  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2283  		UnitNames: []string{"logging/0"},
  2284  	})
  2285  	s.AssertBlocked(c, err, "TestBlockChangesDestroySubordinateUnits")
  2286  	assertLife(c, rel, state.Alive)
  2287  	assertLife(c, wordpress0, state.Alive)
  2288  	assertLife(c, logging0, state.Alive)
  2289  
  2290  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2291  		UnitNames: []string{"wordpress/0", "logging/0"},
  2292  	})
  2293  	s.AssertBlocked(c, err, "TestBlockChangesDestroySubordinateUnits")
  2294  	assertLife(c, wordpress0, state.Alive)
  2295  	assertLife(c, logging0, state.Alive)
  2296  	assertLife(c, rel, state.Alive)
  2297  }
  2298  
  2299  func (s *serviceSuite) TestBlockDestroyDestroySubordinateUnits(c *gc.C) {
  2300  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2301  	wordpress0, err := wordpress.AddUnit()
  2302  	c.Assert(err, jc.ErrorIsNil)
  2303  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
  2304  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  2305  	c.Assert(err, jc.ErrorIsNil)
  2306  	rel, err := s.State.AddRelation(eps...)
  2307  	c.Assert(err, jc.ErrorIsNil)
  2308  	ru, err := rel.Unit(wordpress0)
  2309  	c.Assert(err, jc.ErrorIsNil)
  2310  	err = ru.EnterScope(nil)
  2311  	c.Assert(err, jc.ErrorIsNil)
  2312  	logging0, err := s.State.Unit("logging/0")
  2313  	c.Assert(err, jc.ErrorIsNil)
  2314  
  2315  	s.BlockDestroyModel(c, "TestBlockDestroyDestroySubordinateUnits")
  2316  	// Try to destroy the subordinate alone; check it fails.
  2317  	err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{
  2318  		UnitNames: []string{"logging/0"},
  2319  	})
  2320  	c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`)
  2321  	assertLife(c, logging0, state.Alive)
  2322  
  2323  	s.assertDestroySubordinateUnits(c, wordpress0, logging0)
  2324  }
  2325  
  2326  func (s *serviceSuite) TestClientSetServiceConstraints(c *gc.C) {
  2327  	application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  2328  
  2329  	// Update constraints for the application.
  2330  	cons, err := constraints.Parse("mem=4096", "cores=2")
  2331  	c.Assert(err, jc.ErrorIsNil)
  2332  	err = s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons})
  2333  	c.Assert(err, jc.ErrorIsNil)
  2334  
  2335  	// Ensure the constraints have been correctly updated.
  2336  	obtained, err := application.Constraints()
  2337  	c.Assert(err, jc.ErrorIsNil)
  2338  	c.Assert(obtained, gc.DeepEquals, cons)
  2339  }
  2340  
  2341  func (s *serviceSuite) setupSetServiceConstraints(c *gc.C) (*state.Application, constraints.Value) {
  2342  	application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  2343  	// Update constraints for the application.
  2344  	cons, err := constraints.Parse("mem=4096", "cores=2")
  2345  	c.Assert(err, jc.ErrorIsNil)
  2346  	return application, cons
  2347  }
  2348  
  2349  func (s *serviceSuite) assertSetServiceConstraints(c *gc.C, application *state.Application, cons constraints.Value) {
  2350  	err := s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons})
  2351  	c.Assert(err, jc.ErrorIsNil)
  2352  	// Ensure the constraints have been correctly updated.
  2353  	obtained, err := application.Constraints()
  2354  	c.Assert(err, jc.ErrorIsNil)
  2355  	c.Assert(obtained, gc.DeepEquals, cons)
  2356  }
  2357  
  2358  func (s *serviceSuite) assertSetServiceConstraintsBlocked(c *gc.C, msg string, service *state.Application, cons constraints.Value) {
  2359  	err := s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons})
  2360  	s.AssertBlocked(c, err, msg)
  2361  }
  2362  
  2363  func (s *serviceSuite) TestBlockDestroySetServiceConstraints(c *gc.C) {
  2364  	svc, cons := s.setupSetServiceConstraints(c)
  2365  	s.BlockDestroyModel(c, "TestBlockDestroySetServiceConstraints")
  2366  	s.assertSetServiceConstraints(c, svc, cons)
  2367  }
  2368  
  2369  func (s *serviceSuite) TestBlockRemoveSetServiceConstraints(c *gc.C) {
  2370  	svc, cons := s.setupSetServiceConstraints(c)
  2371  	s.BlockRemoveObject(c, "TestBlockRemoveSetServiceConstraints")
  2372  	s.assertSetServiceConstraints(c, svc, cons)
  2373  }
  2374  
  2375  func (s *serviceSuite) TestBlockChangesSetServiceConstraints(c *gc.C) {
  2376  	svc, cons := s.setupSetServiceConstraints(c)
  2377  	s.BlockAllChanges(c, "TestBlockChangesSetServiceConstraints")
  2378  	s.assertSetServiceConstraintsBlocked(c, "TestBlockChangesSetServiceConstraints", svc, cons)
  2379  }
  2380  
  2381  func (s *serviceSuite) TestClientGetServiceConstraints(c *gc.C) {
  2382  	application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  2383  
  2384  	// Set constraints for the application.
  2385  	cons, err := constraints.Parse("mem=4096", "cores=2")
  2386  	c.Assert(err, jc.ErrorIsNil)
  2387  	err = application.SetConstraints(cons)
  2388  	c.Assert(err, jc.ErrorIsNil)
  2389  
  2390  	// Check we can get the constraints.
  2391  	result, err := s.applicationAPI.GetConstraints(params.GetApplicationConstraints{"dummy"})
  2392  	c.Assert(err, jc.ErrorIsNil)
  2393  	c.Assert(result.Constraints, gc.DeepEquals, cons)
  2394  }
  2395  
  2396  func (s *serviceSuite) checkEndpoints(c *gc.C, endpoints map[string]params.CharmRelation) {
  2397  	c.Assert(endpoints["wordpress"], gc.DeepEquals, params.CharmRelation{
  2398  		Name:      "db",
  2399  		Role:      "requirer",
  2400  		Interface: "mysql",
  2401  		Optional:  false,
  2402  		Limit:     1,
  2403  		Scope:     "global",
  2404  	})
  2405  	c.Assert(endpoints["mysql"], gc.DeepEquals, params.CharmRelation{
  2406  		Name:      "server",
  2407  		Role:      "provider",
  2408  		Interface: "mysql",
  2409  		Optional:  false,
  2410  		Limit:     0,
  2411  		Scope:     "global",
  2412  	})
  2413  }
  2414  
  2415  func (s *serviceSuite) setupRelationScenario(c *gc.C) {
  2416  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2417  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
  2418  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  2419  	c.Assert(err, jc.ErrorIsNil)
  2420  	_, err = s.State.AddRelation(eps...)
  2421  	c.Assert(err, jc.ErrorIsNil)
  2422  }
  2423  
  2424  func (s *serviceSuite) assertAddRelation(c *gc.C, endpoints []string) {
  2425  	s.setupRelationScenario(c)
  2426  	res, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  2427  	c.Assert(err, jc.ErrorIsNil)
  2428  	s.checkEndpoints(c, res.Endpoints)
  2429  	// Show that the relation was added.
  2430  	wpSvc, err := s.State.Application("wordpress")
  2431  	c.Assert(err, jc.ErrorIsNil)
  2432  	rels, err := wpSvc.Relations()
  2433  	// There are 2 relations - the logging-wordpress one set up in the
  2434  	// scenario and the one created in this test.
  2435  	c.Assert(len(rels), gc.Equals, 2)
  2436  	mySvc, err := s.State.Application("mysql")
  2437  	c.Assert(err, jc.ErrorIsNil)
  2438  	rels, err = mySvc.Relations()
  2439  	c.Assert(err, jc.ErrorIsNil)
  2440  	c.Assert(len(rels), gc.Equals, 1)
  2441  }
  2442  
  2443  func (s *serviceSuite) TestSuccessfullyAddRelation(c *gc.C) {
  2444  	endpoints := []string{"wordpress", "mysql"}
  2445  	s.assertAddRelation(c, endpoints)
  2446  }
  2447  
  2448  func (s *serviceSuite) TestBlockDestroyAddRelation(c *gc.C) {
  2449  	s.BlockDestroyModel(c, "TestBlockDestroyAddRelation")
  2450  	s.assertAddRelation(c, []string{"wordpress", "mysql"})
  2451  }
  2452  func (s *serviceSuite) TestBlockRemoveAddRelation(c *gc.C) {
  2453  	s.BlockRemoveObject(c, "TestBlockRemoveAddRelation")
  2454  	s.assertAddRelation(c, []string{"wordpress", "mysql"})
  2455  }
  2456  
  2457  func (s *serviceSuite) TestBlockChangesAddRelation(c *gc.C) {
  2458  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2459  	s.BlockAllChanges(c, "TestBlockChangesAddRelation")
  2460  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: []string{"wordpress", "mysql"}})
  2461  	s.AssertBlocked(c, err, "TestBlockChangesAddRelation")
  2462  }
  2463  
  2464  func (s *serviceSuite) TestSuccessfullyAddRelationSwapped(c *gc.C) {
  2465  	// Show that the order of the services listed in the AddRelation call
  2466  	// does not matter.  This is a repeat of the previous test with the service
  2467  	// names swapped.
  2468  	endpoints := []string{"mysql", "wordpress"}
  2469  	s.assertAddRelation(c, endpoints)
  2470  }
  2471  
  2472  func (s *serviceSuite) TestCallWithOnlyOneEndpoint(c *gc.C) {
  2473  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2474  	endpoints := []string{"wordpress"}
  2475  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  2476  	c.Assert(err, gc.ErrorMatches, "no relations found")
  2477  }
  2478  
  2479  func (s *serviceSuite) TestCallWithOneEndpointTooMany(c *gc.C) {
  2480  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2481  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
  2482  	endpoints := []string{"wordpress", "mysql", "logging"}
  2483  	_, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  2484  	c.Assert(err, gc.ErrorMatches, "cannot relate 3 endpoints")
  2485  }
  2486  
  2487  func (s *serviceSuite) TestAddAlreadyAddedRelation(c *gc.C) {
  2488  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2489  	// Add a relation between wordpress and mysql.
  2490  	endpoints := []string{"wordpress", "mysql"}
  2491  	eps, err := s.State.InferEndpoints(endpoints...)
  2492  	c.Assert(err, jc.ErrorIsNil)
  2493  	_, err = s.State.AddRelation(eps...)
  2494  	c.Assert(err, jc.ErrorIsNil)
  2495  	// And try to add it again.
  2496  	_, err = s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints})
  2497  	c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": relation already exists`)
  2498  }
  2499  
  2500  func (s *serviceSuite) setupDestroyRelationScenario(c *gc.C, endpoints []string) *state.Relation {
  2501  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2502  	// Add a relation between the endpoints.
  2503  	eps, err := s.State.InferEndpoints(endpoints...)
  2504  	c.Assert(err, jc.ErrorIsNil)
  2505  	relation, err := s.State.AddRelation(eps...)
  2506  	c.Assert(err, jc.ErrorIsNil)
  2507  	return relation
  2508  }
  2509  
  2510  func (s *serviceSuite) assertDestroyRelation(c *gc.C, endpoints []string) {
  2511  	s.assertDestroyRelationSuccess(
  2512  		c,
  2513  		s.setupDestroyRelationScenario(c, endpoints),
  2514  		endpoints)
  2515  }
  2516  
  2517  func (s *serviceSuite) assertDestroyRelationSuccess(c *gc.C, relation *state.Relation, endpoints []string) {
  2518  	err := s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints})
  2519  	c.Assert(err, jc.ErrorIsNil)
  2520  	// Show that the relation was removed.
  2521  	c.Assert(relation.Refresh(), jc.Satisfies, errors.IsNotFound)
  2522  }
  2523  
  2524  func (s *serviceSuite) TestSuccessfulDestroyRelation(c *gc.C) {
  2525  	endpoints := []string{"wordpress", "mysql"}
  2526  	s.assertDestroyRelation(c, endpoints)
  2527  }
  2528  
  2529  func (s *serviceSuite) TestSuccessfullyDestroyRelationSwapped(c *gc.C) {
  2530  	// Show that the order of the services listed in the DestroyRelation call
  2531  	// does not matter.  This is a repeat of the previous test with the service
  2532  	// names swapped.
  2533  	endpoints := []string{"mysql", "wordpress"}
  2534  	s.assertDestroyRelation(c, endpoints)
  2535  }
  2536  
  2537  func (s *serviceSuite) TestNoRelation(c *gc.C) {
  2538  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2539  	endpoints := []string{"wordpress", "mysql"}
  2540  	err := s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints})
  2541  	c.Assert(err, gc.ErrorMatches, `relation "wordpress:db mysql:server" not found`)
  2542  }
  2543  
  2544  func (s *serviceSuite) TestAttemptDestroyingNonExistentRelation(c *gc.C) {
  2545  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2546  	s.AddTestingService(c, "riak", s.AddTestingCharm(c, "riak"))
  2547  	endpoints := []string{"riak", "wordpress"}
  2548  	err := s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints})
  2549  	c.Assert(err, gc.ErrorMatches, "no relations found")
  2550  }
  2551  
  2552  func (s *serviceSuite) TestAttemptDestroyingWithOnlyOneEndpoint(c *gc.C) {
  2553  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2554  	endpoints := []string{"wordpress"}
  2555  	err := s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints})
  2556  	c.Assert(err, gc.ErrorMatches, "no relations found")
  2557  }
  2558  
  2559  func (s *serviceSuite) TestAttemptDestroyingPeerRelation(c *gc.C) {
  2560  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2561  	s.AddTestingService(c, "riak", s.AddTestingCharm(c, "riak"))
  2562  
  2563  	endpoints := []string{"riak:ring"}
  2564  	err := s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints})
  2565  	c.Assert(err, gc.ErrorMatches, `cannot destroy relation "riak:ring": is a peer relation`)
  2566  }
  2567  
  2568  func (s *serviceSuite) TestAttemptDestroyingAlreadyDestroyedRelation(c *gc.C) {
  2569  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2570  
  2571  	// Add a relation between wordpress and mysql.
  2572  	eps, err := s.State.InferEndpoints("wordpress", "mysql")
  2573  	c.Assert(err, jc.ErrorIsNil)
  2574  	rel, err := s.State.AddRelation(eps...)
  2575  	c.Assert(err, jc.ErrorIsNil)
  2576  
  2577  	endpoints := []string{"wordpress", "mysql"}
  2578  	err = s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints})
  2579  	// Show that the relation was removed.
  2580  	c.Assert(rel.Refresh(), jc.Satisfies, errors.IsNotFound)
  2581  
  2582  	// And try to destroy it again.
  2583  	err = s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints})
  2584  	c.Assert(err, gc.ErrorMatches, `relation "wordpress:db mysql:server" not found`)
  2585  }
  2586  
  2587  func (s *serviceSuite) TestBlockRemoveDestroyRelation(c *gc.C) {
  2588  	endpoints := []string{"wordpress", "mysql"}
  2589  	relation := s.setupDestroyRelationScenario(c, endpoints)
  2590  	// block remove-objects
  2591  	s.BlockRemoveObject(c, "TestBlockRemoveDestroyRelation")
  2592  	err := s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints})
  2593  	s.AssertBlocked(c, err, "TestBlockRemoveDestroyRelation")
  2594  	assertLife(c, relation, state.Alive)
  2595  }
  2596  
  2597  func (s *serviceSuite) TestBlockChangeDestroyRelation(c *gc.C) {
  2598  	endpoints := []string{"wordpress", "mysql"}
  2599  	relation := s.setupDestroyRelationScenario(c, endpoints)
  2600  	s.BlockAllChanges(c, "TestBlockChangeDestroyRelation")
  2601  	err := s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints})
  2602  	s.AssertBlocked(c, err, "TestBlockChangeDestroyRelation")
  2603  	assertLife(c, relation, state.Alive)
  2604  }
  2605  
  2606  func (s *serviceSuite) TestBlockDestroyDestroyRelation(c *gc.C) {
  2607  	s.BlockDestroyModel(c, "TestBlockDestroyDestroyRelation")
  2608  	endpoints := []string{"wordpress", "mysql"}
  2609  	s.assertDestroyRelation(c, endpoints)
  2610  }
  2611  
  2612  type mockStorageProvider struct {
  2613  	storage.Provider
  2614  	kind storage.StorageKind
  2615  }
  2616  
  2617  func (m *mockStorageProvider) Scope() storage.Scope {
  2618  	return storage.ScopeMachine
  2619  }
  2620  
  2621  func (m *mockStorageProvider) Supports(k storage.StorageKind) bool {
  2622  	return k == m.kind
  2623  }
  2624  
  2625  func (m *mockStorageProvider) ValidateConfig(*storage.Config) error {
  2626  	return nil
  2627  }
  2628  
  2629  type blobs struct {
  2630  	sync.Mutex
  2631  	m map[string]bool // maps path to added (true), or deleted (false)
  2632  }
  2633  
  2634  // Add adds a path to the list of known paths.
  2635  func (b *blobs) Add(path string) {
  2636  	b.Lock()
  2637  	defer b.Unlock()
  2638  	b.check()
  2639  	b.m[path] = true
  2640  }
  2641  
  2642  // Remove marks a path as deleted, even if it was not previously Added.
  2643  func (b *blobs) Remove(path string) {
  2644  	b.Lock()
  2645  	defer b.Unlock()
  2646  	b.check()
  2647  	b.m[path] = false
  2648  }
  2649  
  2650  func (b *blobs) check() {
  2651  	if b.m == nil {
  2652  		b.m = make(map[string]bool)
  2653  	}
  2654  }
  2655  
  2656  type recordingStorage struct {
  2657  	statestorage.Storage
  2658  	putBarrier *sync.WaitGroup
  2659  	blobs      *blobs
  2660  }
  2661  
  2662  func (s *recordingStorage) Put(path string, r io.Reader, size int64) error {
  2663  	if s.putBarrier != nil {
  2664  		// This goroutine has gotten to Put() so mark it Done() and
  2665  		// wait for the other goroutines to get to this point.
  2666  		s.putBarrier.Done()
  2667  		s.putBarrier.Wait()
  2668  	}
  2669  	if err := s.Storage.Put(path, r, size); err != nil {
  2670  		return errors.Trace(err)
  2671  	}
  2672  	s.blobs.Add(path)
  2673  	return nil
  2674  }
  2675  
  2676  func (s *recordingStorage) Remove(path string) error {
  2677  	if err := s.Storage.Remove(path); err != nil {
  2678  		return errors.Trace(err)
  2679  	}
  2680  	s.blobs.Remove(path)
  2681  	return nil
  2682  }