github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/service/service_test.go (about)

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