github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/state/apiserver/client/client_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package client_test
     5  
     6  import (
     7  	"fmt"
     8  	"net/url"
     9  	"strconv"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/juju/charm"
    15  	charmtesting "github.com/juju/charm/testing"
    16  	"github.com/juju/errors"
    17  	jc "github.com/juju/testing/checkers"
    18  	"github.com/juju/utils"
    19  	gc "launchpad.net/gocheck"
    20  
    21  	"github.com/juju/juju/constraints"
    22  	"github.com/juju/juju/environs"
    23  	"github.com/juju/juju/environs/config"
    24  	"github.com/juju/juju/environs/manual"
    25  	envstorage "github.com/juju/juju/environs/storage"
    26  	toolstesting "github.com/juju/juju/environs/tools/testing"
    27  	"github.com/juju/juju/instance"
    28  	"github.com/juju/juju/network"
    29  	"github.com/juju/juju/provider/dummy"
    30  	"github.com/juju/juju/state"
    31  	"github.com/juju/juju/state/api"
    32  	"github.com/juju/juju/state/api/params"
    33  	"github.com/juju/juju/state/apiserver/client"
    34  	"github.com/juju/juju/state/presence"
    35  	coretesting "github.com/juju/juju/testing"
    36  	"github.com/juju/juju/testing/factory"
    37  	"github.com/juju/juju/version"
    38  )
    39  
    40  type clientSuite struct {
    41  	baseSuite
    42  }
    43  
    44  var _ = gc.Suite(&clientSuite{})
    45  
    46  func (s *clientSuite) TestClientStatus(c *gc.C) {
    47  	s.setUpScenario(c)
    48  	status, err := s.APIState.Client().Status(nil)
    49  	c.Assert(err, gc.IsNil)
    50  	c.Assert(status, jc.DeepEquals, scenarioStatus)
    51  }
    52  
    53  func (s *clientSuite) TestCompatibleSettingsParsing(c *gc.C) {
    54  	// Test the exported settings parsing in a compatible way.
    55  	s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
    56  	service, err := s.State.Service("dummy")
    57  	c.Assert(err, gc.IsNil)
    58  	ch, _, err := service.Charm()
    59  	c.Assert(err, gc.IsNil)
    60  	c.Assert(ch.URL().String(), gc.Equals, "local:quantal/dummy-1")
    61  
    62  	// Empty string will be returned as nil.
    63  	options := map[string]string{
    64  		"title":    "foobar",
    65  		"username": "",
    66  	}
    67  	settings, err := client.ParseSettingsCompatible(ch, options)
    68  	c.Assert(err, gc.IsNil)
    69  	c.Assert(settings, gc.DeepEquals, charm.Settings{
    70  		"title":    "foobar",
    71  		"username": nil,
    72  	})
    73  
    74  	// Illegal settings lead to an error.
    75  	options = map[string]string{
    76  		"yummy": "didgeridoo",
    77  	}
    78  	settings, err = client.ParseSettingsCompatible(ch, options)
    79  	c.Assert(err, gc.ErrorMatches, `unknown option "yummy"`)
    80  }
    81  
    82  func (s *clientSuite) TestClientServiceSet(c *gc.C) {
    83  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
    84  
    85  	err := s.APIState.Client().ServiceSet("dummy", map[string]string{
    86  		"title":    "foobar",
    87  		"username": "user name",
    88  	})
    89  	c.Assert(err, gc.IsNil)
    90  	settings, err := dummy.ConfigSettings()
    91  	c.Assert(err, gc.IsNil)
    92  	c.Assert(settings, gc.DeepEquals, charm.Settings{
    93  		"title":    "foobar",
    94  		"username": "user name",
    95  	})
    96  
    97  	err = s.APIState.Client().ServiceSet("dummy", map[string]string{
    98  		"title":    "barfoo",
    99  		"username": "",
   100  	})
   101  	c.Assert(err, gc.IsNil)
   102  	settings, err = dummy.ConfigSettings()
   103  	c.Assert(err, gc.IsNil)
   104  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   105  		"title":    "barfoo",
   106  		"username": "",
   107  	})
   108  }
   109  
   110  func (s *clientSuite) TestClientServerUnset(c *gc.C) {
   111  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   112  
   113  	err := s.APIState.Client().ServiceSet("dummy", map[string]string{
   114  		"title":    "foobar",
   115  		"username": "user name",
   116  	})
   117  	c.Assert(err, gc.IsNil)
   118  	settings, err := dummy.ConfigSettings()
   119  	c.Assert(err, gc.IsNil)
   120  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   121  		"title":    "foobar",
   122  		"username": "user name",
   123  	})
   124  
   125  	err = s.APIState.Client().ServiceUnset("dummy", []string{"username"})
   126  	c.Assert(err, gc.IsNil)
   127  	settings, err = dummy.ConfigSettings()
   128  	c.Assert(err, gc.IsNil)
   129  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   130  		"title": "foobar",
   131  	})
   132  }
   133  
   134  func (s *clientSuite) TestClientServiceSetYAML(c *gc.C) {
   135  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   136  
   137  	err := s.APIState.Client().ServiceSetYAML("dummy", "dummy:\n  title: foobar\n  username: user name\n")
   138  	c.Assert(err, gc.IsNil)
   139  	settings, err := dummy.ConfigSettings()
   140  	c.Assert(err, gc.IsNil)
   141  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   142  		"title":    "foobar",
   143  		"username": "user name",
   144  	})
   145  
   146  	err = s.APIState.Client().ServiceSetYAML("dummy", "dummy:\n  title: barfoo\n  username: \n")
   147  	c.Assert(err, gc.IsNil)
   148  	settings, err = dummy.ConfigSettings()
   149  	c.Assert(err, gc.IsNil)
   150  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   151  		"title": "barfoo",
   152  	})
   153  }
   154  
   155  var clientAddServiceUnitsTests = []struct {
   156  	about    string
   157  	service  string // if not set, defaults to 'dummy'
   158  	expected []string
   159  	to       string
   160  	err      string
   161  }{
   162  	{
   163  		about:    "returns unit names",
   164  		expected: []string{"dummy/0", "dummy/1", "dummy/2"},
   165  	},
   166  	{
   167  		about: "fails trying to add zero units",
   168  		err:   "must add at least one unit",
   169  	},
   170  	{
   171  		about:    "cannot mix to when adding multiple units",
   172  		err:      "cannot use NumUnits with ToMachineSpec",
   173  		expected: []string{"dummy/0", "dummy/1"},
   174  		to:       "0",
   175  	},
   176  	{
   177  		// Note: chained-state, we add 1 unit here, but the 3 units
   178  		// from the first condition still exist
   179  		about:    "force the unit onto bootstrap machine",
   180  		expected: []string{"dummy/3"},
   181  		to:       "0",
   182  	},
   183  	{
   184  		about:   "unknown service name",
   185  		service: "unknown-service",
   186  		err:     `service "unknown-service" not found`,
   187  	},
   188  }
   189  
   190  func (s *clientSuite) TestClientAddServiceUnits(c *gc.C) {
   191  	s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   192  	for i, t := range clientAddServiceUnitsTests {
   193  		c.Logf("test %d. %s", i, t.about)
   194  		serviceName := t.service
   195  		if serviceName == "" {
   196  			serviceName = "dummy"
   197  		}
   198  		units, err := s.APIState.Client().AddServiceUnits(serviceName, len(t.expected), t.to)
   199  		if t.err != "" {
   200  			c.Assert(err, gc.ErrorMatches, t.err)
   201  			continue
   202  		}
   203  		c.Assert(err, gc.IsNil)
   204  		c.Assert(units, gc.DeepEquals, t.expected)
   205  	}
   206  	// Test that we actually assigned the unit to machine 0
   207  	forcedUnit, err := s.BackingState.Unit("dummy/3")
   208  	c.Assert(err, gc.IsNil)
   209  	assignedMachine, err := forcedUnit.AssignedMachineId()
   210  	c.Assert(err, gc.IsNil)
   211  	c.Assert(assignedMachine, gc.Equals, "0")
   212  }
   213  
   214  var clientCharmInfoTests = []struct {
   215  	about string
   216  	url   string
   217  	err   string
   218  }{
   219  	{
   220  		about: "retrieves charm info",
   221  		url:   "local:quantal/wordpress-3",
   222  	},
   223  	{
   224  		about: "invalid URL",
   225  		url:   "not-valid",
   226  		err:   "charm url series is not resolved",
   227  	},
   228  	{
   229  		about: "invalid schema",
   230  		url:   "not-valid:your-arguments",
   231  		err:   `charm URL has invalid schema: "not-valid:your-arguments"`,
   232  	},
   233  	{
   234  		about: "unknown charm",
   235  		url:   "cs:missing/one-1",
   236  		err:   `charm "cs:missing/one-1" not found`,
   237  	},
   238  }
   239  
   240  func (s *clientSuite) TestClientCharmInfo(c *gc.C) {
   241  	// Use wordpress for tests so that we can compare Provides and Requires.
   242  	charm := s.AddTestingCharm(c, "wordpress")
   243  	for i, t := range clientCharmInfoTests {
   244  		c.Logf("test %d. %s", i, t.about)
   245  		info, err := s.APIState.Client().CharmInfo(t.url)
   246  		if t.err != "" {
   247  			c.Assert(err, gc.ErrorMatches, t.err)
   248  			continue
   249  		}
   250  		c.Assert(err, gc.IsNil)
   251  		expected := &api.CharmInfo{
   252  			Revision: charm.Revision(),
   253  			URL:      charm.URL().String(),
   254  			Config:   charm.Config(),
   255  			Meta:     charm.Meta(),
   256  		}
   257  		c.Assert(info, gc.DeepEquals, expected)
   258  	}
   259  }
   260  
   261  func (s *clientSuite) TestClientEnvironmentInfo(c *gc.C) {
   262  	conf, _ := s.State.EnvironConfig()
   263  	info, err := s.APIState.Client().EnvironmentInfo()
   264  	c.Assert(err, gc.IsNil)
   265  	env, err := s.State.Environment()
   266  	c.Assert(err, gc.IsNil)
   267  	c.Assert(info.DefaultSeries, gc.Equals, config.PreferredSeries(conf))
   268  	c.Assert(info.ProviderType, gc.Equals, conf.Type())
   269  	c.Assert(info.Name, gc.Equals, conf.Name())
   270  	c.Assert(info.UUID, gc.Equals, env.UUID())
   271  }
   272  
   273  var clientAnnotationsTests = []struct {
   274  	about    string
   275  	initial  map[string]string
   276  	input    map[string]string
   277  	expected map[string]string
   278  	err      string
   279  }{
   280  	{
   281  		about:    "test setting an annotation",
   282  		input:    map[string]string{"mykey": "myvalue"},
   283  		expected: map[string]string{"mykey": "myvalue"},
   284  	},
   285  	{
   286  		about:    "test setting multiple annotations",
   287  		input:    map[string]string{"key1": "value1", "key2": "value2"},
   288  		expected: map[string]string{"key1": "value1", "key2": "value2"},
   289  	},
   290  	{
   291  		about:    "test overriding annotations",
   292  		initial:  map[string]string{"mykey": "myvalue"},
   293  		input:    map[string]string{"mykey": "another-value"},
   294  		expected: map[string]string{"mykey": "another-value"},
   295  	},
   296  	{
   297  		about: "test setting an invalid annotation",
   298  		input: map[string]string{"invalid.key": "myvalue"},
   299  		err:   `cannot update annotations on .*: invalid key "invalid.key"`,
   300  	},
   301  }
   302  
   303  func (s *clientSuite) TestClientAnnotations(c *gc.C) {
   304  	// Set up entities.
   305  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   306  	unit, err := service.AddUnit()
   307  	c.Assert(err, gc.IsNil)
   308  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
   309  	c.Assert(err, gc.IsNil)
   310  	environment, err := s.State.Environment()
   311  	c.Assert(err, gc.IsNil)
   312  	type taggedAnnotator interface {
   313  		state.Annotator
   314  		state.Entity
   315  	}
   316  	entities := []taggedAnnotator{service, unit, machine, environment}
   317  	for i, t := range clientAnnotationsTests {
   318  		for _, entity := range entities {
   319  			id := entity.Tag()
   320  			c.Logf("test %d. %s. entity %s", i, t.about, id)
   321  			// Set initial entity annotations.
   322  			err := entity.SetAnnotations(t.initial)
   323  			c.Assert(err, gc.IsNil)
   324  			// Add annotations using the API call.
   325  			err = s.APIState.Client().SetAnnotations(id, t.input)
   326  			if t.err != "" {
   327  				c.Assert(err, gc.ErrorMatches, t.err)
   328  				continue
   329  			}
   330  			// Check annotations are correctly set.
   331  			dbann, err := entity.Annotations()
   332  			c.Assert(err, gc.IsNil)
   333  			c.Assert(dbann, gc.DeepEquals, t.expected)
   334  			// Retrieve annotations using the API call.
   335  			ann, err := s.APIState.Client().GetAnnotations(id)
   336  			c.Assert(err, gc.IsNil)
   337  			// Check annotations are correctly returned.
   338  			c.Assert(ann, gc.DeepEquals, dbann)
   339  			// Clean up annotations on the current entity.
   340  			cleanup := make(map[string]string)
   341  			for key := range dbann {
   342  				cleanup[key] = ""
   343  			}
   344  			err = entity.SetAnnotations(cleanup)
   345  			c.Assert(err, gc.IsNil)
   346  		}
   347  	}
   348  }
   349  
   350  func (s *clientSuite) TestClientAnnotationsBadEntity(c *gc.C) {
   351  	bad := []string{"", "machine", "-foo", "foo-", "---", "machine-jim", "unit-123", "unit-foo", "service-", "service-foo/bar"}
   352  	expected := `".*" is not a valid( [a-z]+)? tag`
   353  	for _, id := range bad {
   354  		err := s.APIState.Client().SetAnnotations(id, map[string]string{"mykey": "myvalue"})
   355  		c.Assert(err, gc.ErrorMatches, expected)
   356  		_, err = s.APIState.Client().GetAnnotations(id)
   357  		c.Assert(err, gc.ErrorMatches, expected)
   358  	}
   359  }
   360  
   361  var serviceExposeTests = []struct {
   362  	about   string
   363  	service string
   364  	err     string
   365  	exposed bool
   366  }{
   367  	{
   368  		about:   "unknown service name",
   369  		service: "unknown-service",
   370  		err:     `service "unknown-service" not found`,
   371  	},
   372  	{
   373  		about:   "expose a service",
   374  		service: "dummy-service",
   375  		exposed: true,
   376  	},
   377  	{
   378  		about:   "expose an already exposed service",
   379  		service: "exposed-service",
   380  		exposed: true,
   381  	},
   382  }
   383  
   384  func (s *clientSuite) TestClientServiceExpose(c *gc.C) {
   385  	charm := s.AddTestingCharm(c, "dummy")
   386  	serviceNames := []string{"dummy-service", "exposed-service"}
   387  	svcs := make([]*state.Service, len(serviceNames))
   388  	var err error
   389  	for i, name := range serviceNames {
   390  		svcs[i] = s.AddTestingService(c, name, charm)
   391  		c.Assert(svcs[i].IsExposed(), gc.Equals, false)
   392  	}
   393  	err = svcs[1].SetExposed()
   394  	c.Assert(err, gc.IsNil)
   395  	c.Assert(svcs[1].IsExposed(), gc.Equals, true)
   396  	for i, t := range serviceExposeTests {
   397  		c.Logf("test %d. %s", i, t.about)
   398  		err = s.APIState.Client().ServiceExpose(t.service)
   399  		if t.err != "" {
   400  			c.Assert(err, gc.ErrorMatches, t.err)
   401  		} else {
   402  			c.Assert(err, gc.IsNil)
   403  			service, err := s.State.Service(t.service)
   404  			c.Assert(err, gc.IsNil)
   405  			c.Assert(service.IsExposed(), gc.Equals, t.exposed)
   406  		}
   407  	}
   408  }
   409  
   410  var serviceUnexposeTests = []struct {
   411  	about    string
   412  	service  string
   413  	err      string
   414  	initial  bool
   415  	expected bool
   416  }{
   417  	{
   418  		about:   "unknown service name",
   419  		service: "unknown-service",
   420  		err:     `service "unknown-service" not found`,
   421  	},
   422  	{
   423  		about:    "unexpose a service",
   424  		service:  "dummy-service",
   425  		initial:  true,
   426  		expected: false,
   427  	},
   428  	{
   429  		about:    "unexpose an already unexposed service",
   430  		service:  "dummy-service",
   431  		initial:  false,
   432  		expected: false,
   433  	},
   434  }
   435  
   436  func (s *clientSuite) TestClientServiceUnexpose(c *gc.C) {
   437  	charm := s.AddTestingCharm(c, "dummy")
   438  	for i, t := range serviceUnexposeTests {
   439  		c.Logf("test %d. %s", i, t.about)
   440  		svc := s.AddTestingService(c, "dummy-service", charm)
   441  		if t.initial {
   442  			svc.SetExposed()
   443  		}
   444  		c.Assert(svc.IsExposed(), gc.Equals, t.initial)
   445  		err := s.APIState.Client().ServiceUnexpose(t.service)
   446  		if t.err == "" {
   447  			c.Assert(err, gc.IsNil)
   448  			svc.Refresh()
   449  			c.Assert(svc.IsExposed(), gc.Equals, t.expected)
   450  		} else {
   451  			c.Assert(err, gc.ErrorMatches, t.err)
   452  		}
   453  		err = svc.Destroy()
   454  		c.Assert(err, gc.IsNil)
   455  	}
   456  }
   457  
   458  var serviceDestroyTests = []struct {
   459  	about   string
   460  	service string
   461  	err     string
   462  }{
   463  	{
   464  		about:   "unknown service name",
   465  		service: "unknown-service",
   466  		err:     `service "unknown-service" not found`,
   467  	},
   468  	{
   469  		about:   "destroy a service",
   470  		service: "dummy-service",
   471  	},
   472  	{
   473  		about:   "destroy an already destroyed service",
   474  		service: "dummy-service",
   475  		err:     `service "dummy-service" not found`,
   476  	},
   477  }
   478  
   479  func (s *clientSuite) TestClientServiceDestroy(c *gc.C) {
   480  	s.AddTestingService(c, "dummy-service", s.AddTestingCharm(c, "dummy"))
   481  	for i, t := range serviceDestroyTests {
   482  		c.Logf("test %d. %s", i, t.about)
   483  		err := s.APIState.Client().ServiceDestroy(t.service)
   484  		if t.err != "" {
   485  			c.Assert(err, gc.ErrorMatches, t.err)
   486  		} else {
   487  			c.Assert(err, gc.IsNil)
   488  		}
   489  	}
   490  
   491  	// Now do ServiceDestroy on a service with units. Destroy will
   492  	// cause the service to be not-Alive, but will not remove its
   493  	// document.
   494  	s.setUpScenario(c)
   495  	serviceName := "wordpress"
   496  	service, err := s.State.Service(serviceName)
   497  	c.Assert(err, gc.IsNil)
   498  	err = s.APIState.Client().ServiceDestroy(serviceName)
   499  	c.Assert(err, gc.IsNil)
   500  	err = service.Refresh()
   501  	c.Assert(err, gc.IsNil)
   502  	c.Assert(service.Life(), gc.Not(gc.Equals), state.Alive)
   503  }
   504  
   505  func assertLife(c *gc.C, entity state.Living, life state.Life) {
   506  	err := entity.Refresh()
   507  	c.Assert(err, gc.IsNil)
   508  	c.Assert(entity.Life(), gc.Equals, life)
   509  }
   510  
   511  func assertRemoved(c *gc.C, entity state.Living) {
   512  	err := entity.Refresh()
   513  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   514  }
   515  
   516  func (s *clientSuite) setupDestroyMachinesTest(c *gc.C) (*state.Machine, *state.Machine, *state.Machine, *state.Unit) {
   517  	m0, err := s.State.AddMachine("quantal", state.JobManageEnviron)
   518  	c.Assert(err, gc.IsNil)
   519  	m1, err := s.State.AddMachine("quantal", state.JobHostUnits)
   520  	c.Assert(err, gc.IsNil)
   521  	m2, err := s.State.AddMachine("quantal", state.JobHostUnits)
   522  	c.Assert(err, gc.IsNil)
   523  
   524  	sch := s.AddTestingCharm(c, "wordpress")
   525  	wordpress := s.AddTestingService(c, "wordpress", sch)
   526  	u, err := wordpress.AddUnit()
   527  	c.Assert(err, gc.IsNil)
   528  	err = u.AssignToMachine(m1)
   529  	c.Assert(err, gc.IsNil)
   530  
   531  	return m0, m1, m2, u
   532  }
   533  
   534  func (s *clientSuite) TestDestroyMachines(c *gc.C) {
   535  	m0, m1, m2, u := s.setupDestroyMachinesTest(c)
   536  
   537  	err := s.APIState.Client().DestroyMachines("0", "1", "2")
   538  	c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 is required by the environment; machine 1 has unit "wordpress/0" assigned`)
   539  	assertLife(c, m0, state.Alive)
   540  	assertLife(c, m1, state.Alive)
   541  	assertLife(c, m2, state.Dying)
   542  
   543  	err = u.UnassignFromMachine()
   544  	c.Assert(err, gc.IsNil)
   545  	err = s.APIState.Client().DestroyMachines("0", "1", "2")
   546  	c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 is required by the environment`)
   547  	assertLife(c, m0, state.Alive)
   548  	assertLife(c, m1, state.Dying)
   549  	assertLife(c, m2, state.Dying)
   550  }
   551  
   552  func (s *clientSuite) TestForceDestroyMachines(c *gc.C) {
   553  	m0, m1, m2, u := s.setupDestroyMachinesTest(c)
   554  
   555  	err := s.APIState.Client().ForceDestroyMachines("0", "1", "2")
   556  	c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 is required by the environment`)
   557  	assertLife(c, m0, state.Alive)
   558  	assertLife(c, m1, state.Alive)
   559  	assertLife(c, m2, state.Alive)
   560  	assertLife(c, u, state.Alive)
   561  
   562  	err = s.State.Cleanup()
   563  	c.Assert(err, gc.IsNil)
   564  	assertLife(c, m0, state.Alive)
   565  	assertLife(c, m1, state.Dead)
   566  	assertLife(c, m2, state.Dead)
   567  	assertRemoved(c, u)
   568  }
   569  
   570  func (s *clientSuite) TestDestroyPrincipalUnits(c *gc.C) {
   571  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   572  	units := make([]*state.Unit, 5)
   573  	for i := range units {
   574  		unit, err := wordpress.AddUnit()
   575  		c.Assert(err, gc.IsNil)
   576  		err = unit.SetStatus(params.StatusStarted, "", nil)
   577  		c.Assert(err, gc.IsNil)
   578  		units[i] = unit
   579  	}
   580  
   581  	// Destroy 2 of them; check they become Dying.
   582  	err := s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/1")
   583  	c.Assert(err, gc.IsNil)
   584  	assertLife(c, units[0], state.Dying)
   585  	assertLife(c, units[1], state.Dying)
   586  
   587  	// Try to destroy an Alive one and a Dying one; check
   588  	// it destroys the Alive one and ignores the Dying one.
   589  	err = s.APIState.Client().DestroyServiceUnits("wordpress/2", "wordpress/0")
   590  	c.Assert(err, gc.IsNil)
   591  	assertLife(c, units[2], state.Dying)
   592  
   593  	// Try to destroy an Alive one along with a nonexistent one; check that
   594  	// the valid instruction is followed but the invalid one is warned about.
   595  	err = s.APIState.Client().DestroyServiceUnits("boojum/123", "wordpress/3")
   596  	c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`)
   597  	assertLife(c, units[3], state.Dying)
   598  
   599  	// Make one Dead, and destroy an Alive one alongside it; check no errors.
   600  	wp0, err := s.State.Unit("wordpress/0")
   601  	c.Assert(err, gc.IsNil)
   602  	err = wp0.EnsureDead()
   603  	c.Assert(err, gc.IsNil)
   604  	err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/4")
   605  	c.Assert(err, gc.IsNil)
   606  	assertLife(c, units[0], state.Dead)
   607  	assertLife(c, units[4], state.Dying)
   608  }
   609  
   610  func (s *clientSuite) TestDestroySubordinateUnits(c *gc.C) {
   611  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   612  	wordpress0, err := wordpress.AddUnit()
   613  	c.Assert(err, gc.IsNil)
   614  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
   615  	eps, err := s.State.InferEndpoints([]string{"logging", "wordpress"})
   616  	c.Assert(err, gc.IsNil)
   617  	rel, err := s.State.AddRelation(eps...)
   618  	c.Assert(err, gc.IsNil)
   619  	ru, err := rel.Unit(wordpress0)
   620  	c.Assert(err, gc.IsNil)
   621  	err = ru.EnterScope(nil)
   622  	c.Assert(err, gc.IsNil)
   623  	logging0, err := s.State.Unit("logging/0")
   624  	c.Assert(err, gc.IsNil)
   625  
   626  	// Try to destroy the subordinate alone; check it fails.
   627  	err = s.APIState.Client().DestroyServiceUnits("logging/0")
   628  	c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`)
   629  	assertLife(c, logging0, state.Alive)
   630  
   631  	// Try to destroy the principal and the subordinate together; check it warns
   632  	// about the subordinate, but destroys the one it can. (The principal unit
   633  	// agent will be resposible for destroying the subordinate.)
   634  	err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "logging/0")
   635  	c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate`)
   636  	assertLife(c, wordpress0, state.Dying)
   637  	assertLife(c, logging0, state.Alive)
   638  }
   639  
   640  func (s *clientSuite) testClientUnitResolved(c *gc.C, retry bool, expectedResolvedMode state.ResolvedMode) {
   641  	// Setup:
   642  	s.setUpScenario(c)
   643  	u, err := s.State.Unit("wordpress/0")
   644  	c.Assert(err, gc.IsNil)
   645  	err = u.SetStatus(params.StatusError, "gaaah", nil)
   646  	c.Assert(err, gc.IsNil)
   647  	// Code under test:
   648  	err = s.APIState.Client().Resolved("wordpress/0", retry)
   649  	c.Assert(err, gc.IsNil)
   650  	// Freshen the unit's state.
   651  	err = u.Refresh()
   652  	c.Assert(err, gc.IsNil)
   653  	// And now the actual test assertions: we set the unit as resolved via
   654  	// the API so it should have a resolved mode set.
   655  	mode := u.Resolved()
   656  	c.Assert(mode, gc.Equals, expectedResolvedMode)
   657  }
   658  
   659  func (s *clientSuite) TestClientUnitResolved(c *gc.C) {
   660  	s.testClientUnitResolved(c, false, state.ResolvedNoHooks)
   661  }
   662  
   663  func (s *clientSuite) TestClientUnitResolvedRetry(c *gc.C) {
   664  	s.testClientUnitResolved(c, true, state.ResolvedRetryHooks)
   665  }
   666  
   667  func (s *clientSuite) TestClientServiceDeployCharmErrors(c *gc.C) {
   668  	_, restore := makeMockCharmStore()
   669  	defer restore()
   670  	for url, expect := range map[string]string{
   671  		"wordpress":                   "charm url series is not resolved",
   672  		"cs:wordpress":                "charm url series is not resolved",
   673  		"cs:precise/wordpress":        "charm url must include revision",
   674  		"cs:precise/wordpress-999999": `cannot download charm ".*": charm not found in mock store: cs:precise/wordpress-999999`,
   675  	} {
   676  		c.Logf("test %s", url)
   677  		err := s.APIState.Client().ServiceDeploy(
   678  			url, "service", 1, "", constraints.Value{}, "",
   679  		)
   680  		c.Check(err, gc.ErrorMatches, expect)
   681  		_, err = s.State.Service("service")
   682  		c.Assert(err, jc.Satisfies, errors.IsNotFound)
   683  	}
   684  }
   685  
   686  func (s *clientSuite) TestClientServiceDeployWithNetworks(c *gc.C) {
   687  	store, restore := makeMockCharmStore()
   688  	defer restore()
   689  	curl, bundle := addCharm(c, store, "dummy")
   690  	cons := constraints.MustParse("mem=4G networks=^net3")
   691  
   692  	// Check for invalid network tags handling.
   693  	err := s.APIState.Client().ServiceDeployWithNetworks(
   694  		curl.String(), "service", 3, "", cons, "",
   695  		[]string{"net1", "net2"},
   696  	)
   697  	c.Assert(err, gc.ErrorMatches, `"net1" is not a valid network tag`)
   698  
   699  	err = s.APIState.Client().ServiceDeployWithNetworks(
   700  		curl.String(), "service", 3, "", cons, "",
   701  		[]string{"network-net1", "network-net2"},
   702  	)
   703  	c.Assert(err, gc.IsNil)
   704  	service := s.assertPrincipalDeployed(c, "service", curl, false, bundle, cons)
   705  
   706  	networks, err := service.Networks()
   707  	c.Assert(err, gc.IsNil)
   708  	c.Assert(networks, gc.DeepEquals, []string{"net1", "net2"})
   709  	serviceCons, err := service.Constraints()
   710  	c.Assert(err, gc.IsNil)
   711  	c.Assert(serviceCons, gc.DeepEquals, cons)
   712  }
   713  
   714  func (s *clientSuite) assertPrincipalDeployed(c *gc.C, serviceName string, curl *charm.URL, forced bool, bundle charm.Charm, cons constraints.Value) *state.Service {
   715  	service, err := s.State.Service(serviceName)
   716  	c.Assert(err, gc.IsNil)
   717  	charm, force, err := service.Charm()
   718  	c.Assert(err, gc.IsNil)
   719  	c.Assert(force, gc.Equals, forced)
   720  	c.Assert(charm.URL(), gc.DeepEquals, curl)
   721  	c.Assert(charm.Meta(), gc.DeepEquals, bundle.Meta())
   722  	c.Assert(charm.Config(), gc.DeepEquals, bundle.Config())
   723  
   724  	serviceCons, err := service.Constraints()
   725  	c.Assert(err, gc.IsNil)
   726  	c.Assert(serviceCons, gc.DeepEquals, cons)
   727  	units, err := service.AllUnits()
   728  	c.Assert(err, gc.IsNil)
   729  	for _, unit := range units {
   730  		mid, err := unit.AssignedMachineId()
   731  		c.Assert(err, gc.IsNil)
   732  		machine, err := s.State.Machine(mid)
   733  		c.Assert(err, gc.IsNil)
   734  		machineCons, err := machine.Constraints()
   735  		c.Assert(err, gc.IsNil)
   736  		c.Assert(machineCons, gc.DeepEquals, cons)
   737  	}
   738  	return service
   739  }
   740  
   741  func (s *clientSuite) TestClientServiceDeployPrincipal(c *gc.C) {
   742  	// TODO(fwereade): test ToMachineSpec directly on srvClient, when we
   743  	// manage to extract it as a package and can thus do it conveniently.
   744  	store, restore := makeMockCharmStore()
   745  	defer restore()
   746  	curl, bundle := addCharm(c, store, "dummy")
   747  	mem4g := constraints.MustParse("mem=4G")
   748  	err := s.APIState.Client().ServiceDeploy(
   749  		curl.String(), "service", 3, "", mem4g, "",
   750  	)
   751  	c.Assert(err, gc.IsNil)
   752  	s.assertPrincipalDeployed(c, "service", curl, false, bundle, mem4g)
   753  }
   754  
   755  func (s *clientSuite) TestClientServiceDeploySubordinate(c *gc.C) {
   756  	store, restore := makeMockCharmStore()
   757  	defer restore()
   758  	curl, bundle := addCharm(c, store, "logging")
   759  	err := s.APIState.Client().ServiceDeploy(
   760  		curl.String(), "service-name", 0, "", constraints.Value{}, "",
   761  	)
   762  	service, err := s.State.Service("service-name")
   763  	c.Assert(err, gc.IsNil)
   764  	charm, force, err := service.Charm()
   765  	c.Assert(err, gc.IsNil)
   766  	c.Assert(force, gc.Equals, false)
   767  	c.Assert(charm.URL(), gc.DeepEquals, curl)
   768  	c.Assert(charm.Meta(), gc.DeepEquals, bundle.Meta())
   769  	c.Assert(charm.Config(), gc.DeepEquals, bundle.Config())
   770  
   771  	units, err := service.AllUnits()
   772  	c.Assert(err, gc.IsNil)
   773  	c.Assert(units, gc.HasLen, 0)
   774  }
   775  
   776  func (s *clientSuite) TestClientServiceDeployConfig(c *gc.C) {
   777  	// TODO(fwereade): test Config/ConfigYAML handling directly on srvClient.
   778  	// Can't be done cleanly until it's extracted similarly to Machiner.
   779  	store, restore := makeMockCharmStore()
   780  	defer restore()
   781  	curl, _ := addCharm(c, store, "dummy")
   782  	err := s.APIState.Client().ServiceDeploy(
   783  		curl.String(), "service-name", 1, "service-name:\n  username: fred", constraints.Value{}, "",
   784  	)
   785  	c.Assert(err, gc.IsNil)
   786  	service, err := s.State.Service("service-name")
   787  	c.Assert(err, gc.IsNil)
   788  	settings, err := service.ConfigSettings()
   789  	c.Assert(err, gc.IsNil)
   790  	c.Assert(settings, gc.DeepEquals, charm.Settings{"username": "fred"})
   791  }
   792  
   793  func (s *clientSuite) TestClientServiceDeployConfigError(c *gc.C) {
   794  	// TODO(fwereade): test Config/ConfigYAML handling directly on srvClient.
   795  	// Can't be done cleanly until it's extracted similarly to Machiner.
   796  	store, restore := makeMockCharmStore()
   797  	defer restore()
   798  	curl, _ := addCharm(c, store, "dummy")
   799  	err := s.APIState.Client().ServiceDeploy(
   800  		curl.String(), "service-name", 1, "service-name:\n  skill-level: fred", constraints.Value{}, "",
   801  	)
   802  	c.Assert(err, gc.ErrorMatches, `option "skill-level" expected int, got "fred"`)
   803  	_, err = s.State.Service("service-name")
   804  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   805  }
   806  
   807  func (s *clientSuite) TestClientServiceDeployToMachine(c *gc.C) {
   808  	store, restore := makeMockCharmStore()
   809  	defer restore()
   810  	curl, bundle := addCharm(c, store, "dummy")
   811  
   812  	machine, err := s.State.AddMachine("precise", state.JobHostUnits)
   813  	c.Assert(err, gc.IsNil)
   814  	err = s.APIState.Client().ServiceDeploy(
   815  		curl.String(), "service-name", 1, "service-name:\n  username: fred", constraints.Value{}, machine.Id(),
   816  	)
   817  	c.Assert(err, gc.IsNil)
   818  
   819  	service, err := s.State.Service("service-name")
   820  	c.Assert(err, gc.IsNil)
   821  	charm, force, err := service.Charm()
   822  	c.Assert(err, gc.IsNil)
   823  	c.Assert(force, gc.Equals, false)
   824  	c.Assert(charm.URL(), gc.DeepEquals, curl)
   825  	c.Assert(charm.Meta(), gc.DeepEquals, bundle.Meta())
   826  	c.Assert(charm.Config(), gc.DeepEquals, bundle.Config())
   827  
   828  	units, err := service.AllUnits()
   829  	c.Assert(err, gc.IsNil)
   830  	c.Assert(units, gc.HasLen, 1)
   831  	mid, err := units[0].AssignedMachineId()
   832  	c.Assert(err, gc.IsNil)
   833  	c.Assert(mid, gc.Equals, machine.Id())
   834  }
   835  
   836  func (s *clientSuite) TestClientServiceDeployServiceOwner(c *gc.C) {
   837  	store, restore := makeMockCharmStore()
   838  	defer restore()
   839  	curl, _ := addCharm(c, store, "dummy")
   840  
   841  	user := s.Factory.MakeUser(factory.UserParams{Password: "password"})
   842  	s.APIState = s.OpenAPIAs(c, user.Tag(), "password")
   843  
   844  	err := s.APIState.Client().ServiceDeploy(
   845  		curl.String(), "service", 3, "", constraints.Value{}, "",
   846  	)
   847  	c.Assert(err, gc.IsNil)
   848  
   849  	service, err := s.State.Service("service")
   850  	c.Assert(err, gc.IsNil)
   851  	c.Assert(service.GetOwnerTag(), gc.Equals, user.Tag())
   852  }
   853  
   854  func (s *clientSuite) deployServiceForTests(c *gc.C, store *charmtesting.MockCharmStore) {
   855  	curl, _ := addCharm(c, store, "dummy")
   856  	err := s.APIState.Client().ServiceDeploy(curl.String(),
   857  		"service", 1, "", constraints.Value{}, "",
   858  	)
   859  	c.Assert(err, gc.IsNil)
   860  }
   861  
   862  func (s *clientSuite) checkClientServiceUpdateSetCharm(c *gc.C, forceCharmUrl bool) {
   863  	store, restore := makeMockCharmStore()
   864  	defer restore()
   865  	s.deployServiceForTests(c, store)
   866  	addCharm(c, store, "wordpress")
   867  
   868  	// Update the charm for the service.
   869  	args := params.ServiceUpdate{
   870  		ServiceName:   "service",
   871  		CharmUrl:      "cs:precise/wordpress-3",
   872  		ForceCharmUrl: forceCharmUrl,
   873  	}
   874  	err := s.APIState.Client().ServiceUpdate(args)
   875  	c.Assert(err, gc.IsNil)
   876  
   877  	// Ensure the charm has been updated and and the force flag correctly set.
   878  	service, err := s.State.Service("service")
   879  	c.Assert(err, gc.IsNil)
   880  	ch, force, err := service.Charm()
   881  	c.Assert(err, gc.IsNil)
   882  	c.Assert(ch.URL().String(), gc.Equals, "cs:precise/wordpress-3")
   883  	c.Assert(force, gc.Equals, forceCharmUrl)
   884  }
   885  
   886  func (s *clientSuite) TestClientServiceUpdateSetCharm(c *gc.C) {
   887  	s.checkClientServiceUpdateSetCharm(c, false)
   888  }
   889  
   890  func (s *clientSuite) TestClientServiceUpdateForceSetCharm(c *gc.C) {
   891  	s.checkClientServiceUpdateSetCharm(c, true)
   892  }
   893  
   894  func (s *clientSuite) TestClientServiceUpdateSetCharmErrors(c *gc.C) {
   895  	_, restore := makeMockCharmStore()
   896  	defer restore()
   897  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   898  	for charmUrl, expect := range map[string]string{
   899  		"wordpress":                   "charm url series is not resolved",
   900  		"cs:wordpress":                "charm url series is not resolved",
   901  		"cs:precise/wordpress":        "charm url must include revision",
   902  		"cs:precise/wordpress-999999": `cannot download charm ".*": charm not found in mock store: cs:precise/wordpress-999999`,
   903  	} {
   904  		c.Logf("test %s", charmUrl)
   905  		args := params.ServiceUpdate{
   906  			ServiceName: "wordpress",
   907  			CharmUrl:    charmUrl,
   908  		}
   909  		err := s.APIState.Client().ServiceUpdate(args)
   910  		c.Check(err, gc.ErrorMatches, expect)
   911  	}
   912  }
   913  
   914  func (s *clientSuite) TestClientServiceUpdateSetMinUnits(c *gc.C) {
   915  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   916  
   917  	// Set minimum units for the service.
   918  	minUnits := 2
   919  	args := params.ServiceUpdate{
   920  		ServiceName: "dummy",
   921  		MinUnits:    &minUnits,
   922  	}
   923  	err := s.APIState.Client().ServiceUpdate(args)
   924  	c.Assert(err, gc.IsNil)
   925  
   926  	// Ensure the minimum number of units has been set.
   927  	c.Assert(service.Refresh(), gc.IsNil)
   928  	c.Assert(service.MinUnits(), gc.Equals, minUnits)
   929  }
   930  
   931  func (s *clientSuite) TestClientServiceUpdateSetMinUnitsError(c *gc.C) {
   932  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   933  
   934  	// Set a negative minimum number of units for the service.
   935  	minUnits := -1
   936  	args := params.ServiceUpdate{
   937  		ServiceName: "dummy",
   938  		MinUnits:    &minUnits,
   939  	}
   940  	err := s.APIState.Client().ServiceUpdate(args)
   941  	c.Assert(err, gc.ErrorMatches,
   942  		`cannot set minimum units for service "dummy": cannot set a negative minimum number of units`)
   943  
   944  	// Ensure the minimum number of units has not been set.
   945  	c.Assert(service.Refresh(), gc.IsNil)
   946  	c.Assert(service.MinUnits(), gc.Equals, 0)
   947  }
   948  
   949  func (s *clientSuite) TestClientServiceUpdateSetSettingsStrings(c *gc.C) {
   950  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   951  
   952  	// Update settings for the service.
   953  	args := params.ServiceUpdate{
   954  		ServiceName:     "dummy",
   955  		SettingsStrings: map[string]string{"title": "s-title", "username": "s-user"},
   956  	}
   957  	err := s.APIState.Client().ServiceUpdate(args)
   958  	c.Assert(err, gc.IsNil)
   959  
   960  	// Ensure the settings have been correctly updated.
   961  	expected := charm.Settings{"title": "s-title", "username": "s-user"}
   962  	obtained, err := service.ConfigSettings()
   963  	c.Assert(err, gc.IsNil)
   964  	c.Assert(obtained, gc.DeepEquals, expected)
   965  }
   966  
   967  func (s *clientSuite) TestClientServiceUpdateSetSettingsYAML(c *gc.C) {
   968  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   969  
   970  	// Update settings for the service.
   971  	args := params.ServiceUpdate{
   972  		ServiceName:  "dummy",
   973  		SettingsYAML: "dummy:\n  title: y-title\n  username: y-user",
   974  	}
   975  	err := s.APIState.Client().ServiceUpdate(args)
   976  	c.Assert(err, gc.IsNil)
   977  
   978  	// Ensure the settings have been correctly updated.
   979  	expected := charm.Settings{"title": "y-title", "username": "y-user"}
   980  	obtained, err := service.ConfigSettings()
   981  	c.Assert(err, gc.IsNil)
   982  	c.Assert(obtained, gc.DeepEquals, expected)
   983  }
   984  
   985  func (s *clientSuite) TestClientServiceUpdateSetConstraints(c *gc.C) {
   986  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   987  
   988  	// Update constraints for the service.
   989  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
   990  	c.Assert(err, gc.IsNil)
   991  	args := params.ServiceUpdate{
   992  		ServiceName: "dummy",
   993  		Constraints: &cons,
   994  	}
   995  	err = s.APIState.Client().ServiceUpdate(args)
   996  	c.Assert(err, gc.IsNil)
   997  
   998  	// Ensure the constraints have been correctly updated.
   999  	obtained, err := service.Constraints()
  1000  	c.Assert(err, gc.IsNil)
  1001  	c.Assert(obtained, gc.DeepEquals, cons)
  1002  }
  1003  
  1004  func (s *clientSuite) TestClientServiceUpdateAllParams(c *gc.C) {
  1005  	store, restore := makeMockCharmStore()
  1006  	defer restore()
  1007  	s.deployServiceForTests(c, store)
  1008  	addCharm(c, store, "wordpress")
  1009  
  1010  	// Update all the service attributes.
  1011  	minUnits := 3
  1012  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
  1013  	c.Assert(err, gc.IsNil)
  1014  	args := params.ServiceUpdate{
  1015  		ServiceName:     "service",
  1016  		CharmUrl:        "cs:precise/wordpress-3",
  1017  		ForceCharmUrl:   true,
  1018  		MinUnits:        &minUnits,
  1019  		SettingsStrings: map[string]string{"blog-title": "string-title"},
  1020  		SettingsYAML:    "service:\n  blog-title: yaml-title\n",
  1021  		Constraints:     &cons,
  1022  	}
  1023  	err = s.APIState.Client().ServiceUpdate(args)
  1024  	c.Assert(err, gc.IsNil)
  1025  
  1026  	// Ensure the service has been correctly updated.
  1027  	service, err := s.State.Service("service")
  1028  	c.Assert(err, gc.IsNil)
  1029  
  1030  	// Check the charm.
  1031  	ch, force, err := service.Charm()
  1032  	c.Assert(err, gc.IsNil)
  1033  	c.Assert(ch.URL().String(), gc.Equals, "cs:precise/wordpress-3")
  1034  	c.Assert(force, gc.Equals, true)
  1035  
  1036  	// Check the minimum number of units.
  1037  	c.Assert(service.MinUnits(), gc.Equals, minUnits)
  1038  
  1039  	// Check the settings: also ensure the YAML settings take precedence
  1040  	// over strings ones.
  1041  	expectedSettings := charm.Settings{"blog-title": "yaml-title"}
  1042  	obtainedSettings, err := service.ConfigSettings()
  1043  	c.Assert(err, gc.IsNil)
  1044  	c.Assert(obtainedSettings, gc.DeepEquals, expectedSettings)
  1045  
  1046  	// Check the constraints.
  1047  	obtainedConstraints, err := service.Constraints()
  1048  	c.Assert(err, gc.IsNil)
  1049  	c.Assert(obtainedConstraints, gc.DeepEquals, cons)
  1050  }
  1051  
  1052  func (s *clientSuite) TestClientServiceUpdateNoParams(c *gc.C) {
  1053  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1054  
  1055  	// Calling ServiceUpdate with no parameters set is a no-op.
  1056  	args := params.ServiceUpdate{ServiceName: "wordpress"}
  1057  	err := s.APIState.Client().ServiceUpdate(args)
  1058  	c.Assert(err, gc.IsNil)
  1059  }
  1060  
  1061  func (s *clientSuite) TestClientServiceUpdateNoService(c *gc.C) {
  1062  	err := s.APIState.Client().ServiceUpdate(params.ServiceUpdate{})
  1063  	c.Assert(err, gc.ErrorMatches, `"" is not a valid service name`)
  1064  }
  1065  
  1066  func (s *clientSuite) TestClientServiceUpdateInvalidService(c *gc.C) {
  1067  	args := params.ServiceUpdate{ServiceName: "no-such-service"}
  1068  	err := s.APIState.Client().ServiceUpdate(args)
  1069  	c.Assert(err, gc.ErrorMatches, `service "no-such-service" not found`)
  1070  }
  1071  
  1072  func (s *clientSuite) TestClientServiceSetCharm(c *gc.C) {
  1073  	store, restore := makeMockCharmStore()
  1074  	defer restore()
  1075  	curl, _ := addCharm(c, store, "dummy")
  1076  	err := s.APIState.Client().ServiceDeploy(
  1077  		curl.String(), "service", 3, "", constraints.Value{}, "",
  1078  	)
  1079  	c.Assert(err, gc.IsNil)
  1080  	addCharm(c, store, "wordpress")
  1081  	err = s.APIState.Client().ServiceSetCharm(
  1082  		"service", "cs:precise/wordpress-3", false,
  1083  	)
  1084  	c.Assert(err, gc.IsNil)
  1085  
  1086  	// Ensure that the charm is not marked as forced.
  1087  	service, err := s.State.Service("service")
  1088  	c.Assert(err, gc.IsNil)
  1089  	charm, force, err := service.Charm()
  1090  	c.Assert(err, gc.IsNil)
  1091  	c.Assert(charm.URL().String(), gc.Equals, "cs:precise/wordpress-3")
  1092  	c.Assert(force, gc.Equals, false)
  1093  }
  1094  
  1095  func (s *clientSuite) TestClientServiceSetCharmForce(c *gc.C) {
  1096  	store, restore := makeMockCharmStore()
  1097  	defer restore()
  1098  	curl, _ := addCharm(c, store, "dummy")
  1099  	err := s.APIState.Client().ServiceDeploy(
  1100  		curl.String(), "service", 3, "", constraints.Value{}, "",
  1101  	)
  1102  	c.Assert(err, gc.IsNil)
  1103  	addCharm(c, store, "wordpress")
  1104  	err = s.APIState.Client().ServiceSetCharm(
  1105  		"service", "cs:precise/wordpress-3", true,
  1106  	)
  1107  	c.Assert(err, gc.IsNil)
  1108  
  1109  	// Ensure that the charm is marked as forced.
  1110  	service, err := s.State.Service("service")
  1111  	c.Assert(err, gc.IsNil)
  1112  	charm, force, err := service.Charm()
  1113  	c.Assert(err, gc.IsNil)
  1114  	c.Assert(charm.URL().String(), gc.Equals, "cs:precise/wordpress-3")
  1115  	c.Assert(force, gc.Equals, true)
  1116  }
  1117  
  1118  func (s *clientSuite) TestClientServiceSetCharmInvalidService(c *gc.C) {
  1119  	_, restore := makeMockCharmStore()
  1120  	defer restore()
  1121  	err := s.APIState.Client().ServiceSetCharm(
  1122  		"badservice", "cs:precise/wordpress-3", true,
  1123  	)
  1124  	c.Assert(err, gc.ErrorMatches, `service "badservice" not found`)
  1125  }
  1126  
  1127  func (s *clientSuite) TestClientServiceSetCharmErrors(c *gc.C) {
  1128  	_, restore := makeMockCharmStore()
  1129  	defer restore()
  1130  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1131  	for url, expect := range map[string]string{
  1132  		// TODO(fwereade,Makyo) make these errors consistent one day.
  1133  		"wordpress":                   "charm url series is not resolved",
  1134  		"cs:wordpress":                "charm url series is not resolved",
  1135  		"cs:precise/wordpress":        "charm url must include revision",
  1136  		"cs:precise/wordpress-999999": `cannot download charm ".*": charm not found in mock store: cs:precise/wordpress-999999`,
  1137  	} {
  1138  		c.Logf("test %s", url)
  1139  		err := s.APIState.Client().ServiceSetCharm(
  1140  			"wordpress", url, false,
  1141  		)
  1142  		c.Check(err, gc.ErrorMatches, expect)
  1143  	}
  1144  }
  1145  
  1146  func makeMockCharmStore() (store *charmtesting.MockCharmStore, restore func()) {
  1147  	mockStore := charmtesting.NewMockCharmStore()
  1148  	origStore := client.CharmStore
  1149  	client.CharmStore = mockStore
  1150  	return mockStore, func() { client.CharmStore = origStore }
  1151  }
  1152  
  1153  func addCharm(c *gc.C, store *charmtesting.MockCharmStore, name string) (*charm.URL, charm.Charm) {
  1154  	return addSeriesCharm(c, store, "precise", name)
  1155  }
  1156  
  1157  func addSeriesCharm(c *gc.C, store *charmtesting.MockCharmStore, series, name string) (*charm.URL, charm.Charm) {
  1158  	bundle := charmtesting.Charms.Bundle(c.MkDir(), name)
  1159  	scurl := fmt.Sprintf("cs:%s/%s-%d", series, name, bundle.Revision())
  1160  	curl := charm.MustParseURL(scurl)
  1161  	err := store.SetCharm(curl, bundle)
  1162  	c.Assert(err, gc.IsNil)
  1163  	return curl, bundle
  1164  }
  1165  
  1166  func (s *clientSuite) checkEndpoints(c *gc.C, endpoints map[string]charm.Relation) {
  1167  	c.Assert(endpoints["wordpress"], gc.DeepEquals, charm.Relation{
  1168  		Name:      "db",
  1169  		Role:      charm.RelationRole("requirer"),
  1170  		Interface: "mysql",
  1171  		Optional:  false,
  1172  		Limit:     1,
  1173  		Scope:     charm.RelationScope("global"),
  1174  	})
  1175  	c.Assert(endpoints["mysql"], gc.DeepEquals, charm.Relation{
  1176  		Name:      "server",
  1177  		Role:      charm.RelationRole("provider"),
  1178  		Interface: "mysql",
  1179  		Optional:  false,
  1180  		Limit:     0,
  1181  		Scope:     charm.RelationScope("global"),
  1182  	})
  1183  }
  1184  
  1185  func (s *clientSuite) assertAddRelation(c *gc.C, endpoints []string) {
  1186  	s.setUpScenario(c)
  1187  	res, err := s.APIState.Client().AddRelation(endpoints...)
  1188  	c.Assert(err, gc.IsNil)
  1189  	s.checkEndpoints(c, res.Endpoints)
  1190  	// Show that the relation was added.
  1191  	wpSvc, err := s.State.Service("wordpress")
  1192  	c.Assert(err, gc.IsNil)
  1193  	rels, err := wpSvc.Relations()
  1194  	// There are 2 relations - the logging-wordpress one set up in the
  1195  	// scenario and the one created in this test.
  1196  	c.Assert(len(rels), gc.Equals, 2)
  1197  	mySvc, err := s.State.Service("mysql")
  1198  	c.Assert(err, gc.IsNil)
  1199  	rels, err = mySvc.Relations()
  1200  	c.Assert(len(rels), gc.Equals, 1)
  1201  }
  1202  
  1203  func (s *clientSuite) TestSuccessfullyAddRelation(c *gc.C) {
  1204  	endpoints := []string{"wordpress", "mysql"}
  1205  	s.assertAddRelation(c, endpoints)
  1206  }
  1207  
  1208  func (s *clientSuite) TestSuccessfullyAddRelationSwapped(c *gc.C) {
  1209  	// Show that the order of the services listed in the AddRelation call
  1210  	// does not matter.  This is a repeat of the previous test with the service
  1211  	// names swapped.
  1212  	endpoints := []string{"mysql", "wordpress"}
  1213  	s.assertAddRelation(c, endpoints)
  1214  }
  1215  
  1216  func (s *clientSuite) TestCallWithOnlyOneEndpoint(c *gc.C) {
  1217  	s.setUpScenario(c)
  1218  	endpoints := []string{"wordpress"}
  1219  	_, err := s.APIState.Client().AddRelation(endpoints...)
  1220  	c.Assert(err, gc.ErrorMatches, "no relations found")
  1221  }
  1222  
  1223  func (s *clientSuite) TestCallWithOneEndpointTooMany(c *gc.C) {
  1224  	s.setUpScenario(c)
  1225  	endpoints := []string{"wordpress", "mysql", "logging"}
  1226  	_, err := s.APIState.Client().AddRelation(endpoints...)
  1227  	c.Assert(err, gc.ErrorMatches, "cannot relate 3 endpoints")
  1228  }
  1229  
  1230  func (s *clientSuite) TestAddAlreadyAddedRelation(c *gc.C) {
  1231  	s.setUpScenario(c)
  1232  	// Add a relation between wordpress and mysql.
  1233  	endpoints := []string{"wordpress", "mysql"}
  1234  	eps, err := s.State.InferEndpoints(endpoints)
  1235  	c.Assert(err, gc.IsNil)
  1236  	_, err = s.State.AddRelation(eps...)
  1237  	c.Assert(err, gc.IsNil)
  1238  	// And try to add it again.
  1239  	_, err = s.APIState.Client().AddRelation(endpoints...)
  1240  	c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": relation already exists`)
  1241  }
  1242  
  1243  func (s *clientSuite) assertDestroyRelation(c *gc.C, endpoints []string) {
  1244  	s.setUpScenario(c)
  1245  	// Add a relation between the endpoints.
  1246  	eps, err := s.State.InferEndpoints(endpoints)
  1247  	c.Assert(err, gc.IsNil)
  1248  	relation, err := s.State.AddRelation(eps...)
  1249  	c.Assert(err, gc.IsNil)
  1250  
  1251  	err = s.APIState.Client().DestroyRelation(endpoints...)
  1252  	c.Assert(err, gc.IsNil)
  1253  	// Show that the relation was removed.
  1254  	c.Assert(relation.Refresh(), jc.Satisfies, errors.IsNotFound)
  1255  }
  1256  
  1257  func (s *clientSuite) TestSuccessfulDestroyRelation(c *gc.C) {
  1258  	endpoints := []string{"wordpress", "mysql"}
  1259  	s.assertDestroyRelation(c, endpoints)
  1260  }
  1261  
  1262  func (s *clientSuite) TestSuccessfullyDestroyRelationSwapped(c *gc.C) {
  1263  	// Show that the order of the services listed in the DestroyRelation call
  1264  	// does not matter.  This is a repeat of the previous test with the service
  1265  	// names swapped.
  1266  	endpoints := []string{"mysql", "wordpress"}
  1267  	s.assertDestroyRelation(c, endpoints)
  1268  }
  1269  
  1270  func (s *clientSuite) TestNoRelation(c *gc.C) {
  1271  	s.setUpScenario(c)
  1272  	endpoints := []string{"wordpress", "mysql"}
  1273  	err := s.APIState.Client().DestroyRelation(endpoints...)
  1274  	c.Assert(err, gc.ErrorMatches, `relation "wordpress:db mysql:server" not found`)
  1275  }
  1276  
  1277  func (s *clientSuite) TestAttemptDestroyingNonExistentRelation(c *gc.C) {
  1278  	s.setUpScenario(c)
  1279  	s.AddTestingService(c, "riak", s.AddTestingCharm(c, "riak"))
  1280  	endpoints := []string{"riak", "wordpress"}
  1281  	err := s.APIState.Client().DestroyRelation(endpoints...)
  1282  	c.Assert(err, gc.ErrorMatches, "no relations found")
  1283  }
  1284  
  1285  func (s *clientSuite) TestAttemptDestroyingWithOnlyOneEndpoint(c *gc.C) {
  1286  	s.setUpScenario(c)
  1287  	endpoints := []string{"wordpress"}
  1288  	err := s.APIState.Client().DestroyRelation(endpoints...)
  1289  	c.Assert(err, gc.ErrorMatches, "no relations found")
  1290  }
  1291  
  1292  func (s *clientSuite) TestAttemptDestroyingPeerRelation(c *gc.C) {
  1293  	s.setUpScenario(c)
  1294  	s.AddTestingService(c, "riak", s.AddTestingCharm(c, "riak"))
  1295  
  1296  	endpoints := []string{"riak:ring"}
  1297  	err := s.APIState.Client().DestroyRelation(endpoints...)
  1298  	c.Assert(err, gc.ErrorMatches, `cannot destroy relation "riak:ring": is a peer relation`)
  1299  }
  1300  
  1301  func (s *clientSuite) TestAttemptDestroyingAlreadyDestroyedRelation(c *gc.C) {
  1302  	s.setUpScenario(c)
  1303  
  1304  	// Add a relation between wordpress and mysql.
  1305  	eps, err := s.State.InferEndpoints([]string{"wordpress", "mysql"})
  1306  	c.Assert(err, gc.IsNil)
  1307  	rel, err := s.State.AddRelation(eps...)
  1308  	c.Assert(err, gc.IsNil)
  1309  
  1310  	endpoints := []string{"wordpress", "mysql"}
  1311  	err = s.APIState.Client().DestroyRelation(endpoints...)
  1312  	// Show that the relation was removed.
  1313  	c.Assert(rel.Refresh(), jc.Satisfies, errors.IsNotFound)
  1314  
  1315  	// And try to destroy it again.
  1316  	err = s.APIState.Client().DestroyRelation(endpoints...)
  1317  	c.Assert(err, gc.ErrorMatches, `relation "wordpress:db mysql:server" not found`)
  1318  }
  1319  
  1320  func (s *clientSuite) TestClientWatchAll(c *gc.C) {
  1321  	// A very simple end-to-end test, because
  1322  	// all the logic is tested elsewhere.
  1323  	m, err := s.State.AddMachine("quantal", state.JobManageEnviron)
  1324  	c.Assert(err, gc.IsNil)
  1325  	err = m.SetProvisioned("i-0", state.BootstrapNonce, nil)
  1326  	c.Assert(err, gc.IsNil)
  1327  	watcher, err := s.APIState.Client().WatchAll()
  1328  	c.Assert(err, gc.IsNil)
  1329  	defer func() {
  1330  		err := watcher.Stop()
  1331  		c.Assert(err, gc.IsNil)
  1332  	}()
  1333  	deltas, err := watcher.Next()
  1334  	c.Assert(err, gc.IsNil)
  1335  	if !c.Check(deltas, gc.DeepEquals, []params.Delta{{
  1336  		Entity: &params.MachineInfo{
  1337  			Id:                      m.Id(),
  1338  			InstanceId:              "i-0",
  1339  			Status:                  params.StatusPending,
  1340  			Life:                    params.Alive,
  1341  			Series:                  "quantal",
  1342  			Jobs:                    []params.MachineJob{state.JobManageEnviron.ToParams()},
  1343  			Addresses:               []network.Address{},
  1344  			HardwareCharacteristics: &instance.HardwareCharacteristics{},
  1345  		},
  1346  	}}) {
  1347  		c.Logf("got:")
  1348  		for _, d := range deltas {
  1349  			c.Logf("%#v\n", d.Entity)
  1350  		}
  1351  	}
  1352  }
  1353  
  1354  func (s *clientSuite) TestClientSetServiceConstraints(c *gc.C) {
  1355  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1356  
  1357  	// Update constraints for the service.
  1358  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
  1359  	c.Assert(err, gc.IsNil)
  1360  	err = s.APIState.Client().SetServiceConstraints("dummy", cons)
  1361  	c.Assert(err, gc.IsNil)
  1362  
  1363  	// Ensure the constraints have been correctly updated.
  1364  	obtained, err := service.Constraints()
  1365  	c.Assert(err, gc.IsNil)
  1366  	c.Assert(obtained, gc.DeepEquals, cons)
  1367  }
  1368  
  1369  func (s *clientSuite) TestClientGetServiceConstraints(c *gc.C) {
  1370  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1371  
  1372  	// Set constraints for the service.
  1373  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
  1374  	c.Assert(err, gc.IsNil)
  1375  	err = service.SetConstraints(cons)
  1376  	c.Assert(err, gc.IsNil)
  1377  
  1378  	// Check we can get the constraints.
  1379  	obtained, err := s.APIState.Client().GetServiceConstraints("dummy")
  1380  	c.Assert(err, gc.IsNil)
  1381  	c.Assert(obtained, gc.DeepEquals, cons)
  1382  }
  1383  
  1384  func (s *clientSuite) TestClientSetEnvironmentConstraints(c *gc.C) {
  1385  	// Set constraints for the environment.
  1386  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
  1387  	c.Assert(err, gc.IsNil)
  1388  	err = s.APIState.Client().SetEnvironmentConstraints(cons)
  1389  	c.Assert(err, gc.IsNil)
  1390  
  1391  	// Ensure the constraints have been correctly updated.
  1392  	obtained, err := s.State.EnvironConstraints()
  1393  	c.Assert(err, gc.IsNil)
  1394  	c.Assert(obtained, gc.DeepEquals, cons)
  1395  }
  1396  
  1397  func (s *clientSuite) TestClientGetEnvironmentConstraints(c *gc.C) {
  1398  	// Set constraints for the environment.
  1399  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
  1400  	c.Assert(err, gc.IsNil)
  1401  	err = s.State.SetEnvironConstraints(cons)
  1402  	c.Assert(err, gc.IsNil)
  1403  
  1404  	// Check we can get the constraints.
  1405  	obtained, err := s.APIState.Client().GetEnvironmentConstraints()
  1406  	c.Assert(err, gc.IsNil)
  1407  	c.Assert(obtained, gc.DeepEquals, cons)
  1408  }
  1409  
  1410  func (s *clientSuite) TestClientServiceCharmRelations(c *gc.C) {
  1411  	s.setUpScenario(c)
  1412  	_, err := s.APIState.Client().ServiceCharmRelations("blah")
  1413  	c.Assert(err, gc.ErrorMatches, `service "blah" not found`)
  1414  
  1415  	relations, err := s.APIState.Client().ServiceCharmRelations("wordpress")
  1416  	c.Assert(err, gc.IsNil)
  1417  	c.Assert(relations, gc.DeepEquals, []string{
  1418  		"cache", "db", "juju-info", "logging-dir", "monitoring-port", "url",
  1419  	})
  1420  }
  1421  
  1422  func (s *clientSuite) TestClientPublicAddressErrors(c *gc.C) {
  1423  	s.setUpScenario(c)
  1424  	_, err := s.APIState.Client().PublicAddress("wordpress")
  1425  	c.Assert(err, gc.ErrorMatches, `unknown unit or machine "wordpress"`)
  1426  	_, err = s.APIState.Client().PublicAddress("0")
  1427  	c.Assert(err, gc.ErrorMatches, `machine "0" has no public address`)
  1428  	_, err = s.APIState.Client().PublicAddress("wordpress/0")
  1429  	c.Assert(err, gc.ErrorMatches, `unit "wordpress/0" has no public address`)
  1430  }
  1431  
  1432  func (s *clientSuite) TestClientPublicAddressMachine(c *gc.C) {
  1433  	s.setUpScenario(c)
  1434  
  1435  	// Internally, network.SelectPublicAddress is used; the "most public"
  1436  	// address is returned.
  1437  	m1, err := s.State.Machine("1")
  1438  	c.Assert(err, gc.IsNil)
  1439  	cloudLocalAddress := network.NewAddress("cloudlocal", network.ScopeCloudLocal)
  1440  	publicAddress := network.NewAddress("public", network.ScopePublic)
  1441  	err = m1.SetAddresses(cloudLocalAddress)
  1442  	c.Assert(err, gc.IsNil)
  1443  	addr, err := s.APIState.Client().PublicAddress("1")
  1444  	c.Assert(err, gc.IsNil)
  1445  	c.Assert(addr, gc.Equals, "cloudlocal")
  1446  	err = m1.SetAddresses(cloudLocalAddress, publicAddress)
  1447  	addr, err = s.APIState.Client().PublicAddress("1")
  1448  	c.Assert(err, gc.IsNil)
  1449  	c.Assert(addr, gc.Equals, "public")
  1450  }
  1451  
  1452  func (s *clientSuite) TestClientPublicAddressUnit(c *gc.C) {
  1453  	s.setUpScenario(c)
  1454  
  1455  	m1, err := s.State.Machine("1")
  1456  	publicAddress := network.NewAddress("public", network.ScopePublic)
  1457  	err = m1.SetAddresses(publicAddress)
  1458  	c.Assert(err, gc.IsNil)
  1459  	addr, err := s.APIState.Client().PublicAddress("wordpress/0")
  1460  	c.Assert(err, gc.IsNil)
  1461  	c.Assert(addr, gc.Equals, "public")
  1462  }
  1463  
  1464  func (s *clientSuite) TestClientPrivateAddressErrors(c *gc.C) {
  1465  	s.setUpScenario(c)
  1466  	_, err := s.APIState.Client().PrivateAddress("wordpress")
  1467  	c.Assert(err, gc.ErrorMatches, `unknown unit or machine "wordpress"`)
  1468  	_, err = s.APIState.Client().PrivateAddress("0")
  1469  	c.Assert(err, gc.ErrorMatches, `machine "0" has no internal address`)
  1470  	_, err = s.APIState.Client().PrivateAddress("wordpress/0")
  1471  	c.Assert(err, gc.ErrorMatches, `unit "wordpress/0" has no internal address`)
  1472  }
  1473  
  1474  func (s *clientSuite) TestClientPrivateAddress(c *gc.C) {
  1475  	s.setUpScenario(c)
  1476  
  1477  	// Internally, network.SelectInternalAddress is used; the public
  1478  	// address if no cloud-local one is available.
  1479  	m1, err := s.State.Machine("1")
  1480  	c.Assert(err, gc.IsNil)
  1481  	cloudLocalAddress := network.NewAddress("cloudlocal", network.ScopeCloudLocal)
  1482  	publicAddress := network.NewAddress("public", network.ScopePublic)
  1483  	err = m1.SetAddresses(publicAddress)
  1484  	c.Assert(err, gc.IsNil)
  1485  	addr, err := s.APIState.Client().PrivateAddress("1")
  1486  	c.Assert(err, gc.IsNil)
  1487  	c.Assert(addr, gc.Equals, "public")
  1488  	err = m1.SetAddresses(cloudLocalAddress, publicAddress)
  1489  	addr, err = s.APIState.Client().PrivateAddress("1")
  1490  	c.Assert(err, gc.IsNil)
  1491  	c.Assert(addr, gc.Equals, "cloudlocal")
  1492  }
  1493  
  1494  func (s *clientSuite) TestClientPrivateAddressUnit(c *gc.C) {
  1495  	s.setUpScenario(c)
  1496  
  1497  	m1, err := s.State.Machine("1")
  1498  	privateAddress := network.NewAddress("private", network.ScopeCloudLocal)
  1499  	err = m1.SetAddresses(privateAddress)
  1500  	c.Assert(err, gc.IsNil)
  1501  	addr, err := s.APIState.Client().PrivateAddress("wordpress/0")
  1502  	c.Assert(err, gc.IsNil)
  1503  	c.Assert(addr, gc.Equals, "private")
  1504  }
  1505  
  1506  func (s *clientSuite) TestClientEnvironmentGet(c *gc.C) {
  1507  	envConfig, err := s.State.EnvironConfig()
  1508  	c.Assert(err, gc.IsNil)
  1509  	attrs, err := s.APIState.Client().EnvironmentGet()
  1510  	c.Assert(err, gc.IsNil)
  1511  	allAttrs := envConfig.AllAttrs()
  1512  	// We cannot simply use DeepEquals, because after the
  1513  	// map[string]interface{} result of EnvironmentGet is
  1514  	// serialized to JSON, integers are converted to floats.
  1515  	for key, apiValue := range attrs {
  1516  		envValue, found := allAttrs[key]
  1517  		c.Check(found, jc.IsTrue)
  1518  		switch apiValue.(type) {
  1519  		case float64, float32:
  1520  			c.Check(fmt.Sprintf("%v", envValue), gc.Equals, fmt.Sprintf("%v", apiValue))
  1521  		default:
  1522  			c.Check(envValue, gc.Equals, apiValue)
  1523  		}
  1524  	}
  1525  }
  1526  
  1527  func (s *clientSuite) TestClientEnvironmentSet(c *gc.C) {
  1528  	envConfig, err := s.State.EnvironConfig()
  1529  	c.Assert(err, gc.IsNil)
  1530  	_, found := envConfig.AllAttrs()["some-key"]
  1531  	c.Assert(found, jc.IsFalse)
  1532  
  1533  	args := map[string]interface{}{"some-key": "value"}
  1534  	err = s.APIState.Client().EnvironmentSet(args)
  1535  	c.Assert(err, gc.IsNil)
  1536  
  1537  	envConfig, err = s.State.EnvironConfig()
  1538  	c.Assert(err, gc.IsNil)
  1539  	value, found := envConfig.AllAttrs()["some-key"]
  1540  	c.Assert(found, jc.IsTrue)
  1541  	c.Assert(value, gc.Equals, "value")
  1542  }
  1543  
  1544  func (s *clientSuite) TestClientSetEnvironAgentVersion(c *gc.C) {
  1545  	err := s.APIState.Client().SetEnvironAgentVersion(version.MustParse("9.8.7"))
  1546  	c.Assert(err, gc.IsNil)
  1547  
  1548  	envConfig, err := s.State.EnvironConfig()
  1549  	c.Assert(err, gc.IsNil)
  1550  	agentVersion, found := envConfig.AllAttrs()["agent-version"]
  1551  	c.Assert(found, jc.IsTrue)
  1552  	c.Assert(agentVersion, gc.Equals, "9.8.7")
  1553  }
  1554  
  1555  func (s *clientSuite) TestClientEnvironmentSetCannotChangeAgentVersion(c *gc.C) {
  1556  	args := map[string]interface{}{"agent-version": "9.9.9"}
  1557  	err := s.APIState.Client().EnvironmentSet(args)
  1558  	c.Assert(err, gc.ErrorMatches, "agent-version cannot be changed")
  1559  	// It's okay to pass env back with the same agent-version.
  1560  	cfg, err := s.APIState.Client().EnvironmentGet()
  1561  	c.Assert(err, gc.IsNil)
  1562  	c.Assert(cfg["agent-version"], gc.NotNil)
  1563  	err = s.APIState.Client().EnvironmentSet(cfg)
  1564  	c.Assert(err, gc.IsNil)
  1565  }
  1566  
  1567  func (s *clientSuite) TestClientEnvironmentUnset(c *gc.C) {
  1568  	err := s.State.UpdateEnvironConfig(map[string]interface{}{"abc": 123}, nil, nil)
  1569  	c.Assert(err, gc.IsNil)
  1570  	envConfig, err := s.State.EnvironConfig()
  1571  	c.Assert(err, gc.IsNil)
  1572  	_, found := envConfig.AllAttrs()["abc"]
  1573  	c.Assert(found, jc.IsTrue)
  1574  
  1575  	err = s.APIState.Client().EnvironmentUnset("abc")
  1576  	c.Assert(err, gc.IsNil)
  1577  	envConfig, err = s.State.EnvironConfig()
  1578  	c.Assert(err, gc.IsNil)
  1579  	_, found = envConfig.AllAttrs()["abc"]
  1580  	c.Assert(found, jc.IsFalse)
  1581  }
  1582  
  1583  func (s *clientSuite) TestClientEnvironmentUnsetMissing(c *gc.C) {
  1584  	// It's okay to unset a non-existent attribute.
  1585  	err := s.APIState.Client().EnvironmentUnset("not_there")
  1586  	c.Assert(err, gc.IsNil)
  1587  }
  1588  
  1589  func (s *clientSuite) TestClientEnvironmentUnsetError(c *gc.C) {
  1590  	err := s.State.UpdateEnvironConfig(map[string]interface{}{"abc": 123}, nil, nil)
  1591  	c.Assert(err, gc.IsNil)
  1592  	envConfig, err := s.State.EnvironConfig()
  1593  	c.Assert(err, gc.IsNil)
  1594  	_, found := envConfig.AllAttrs()["abc"]
  1595  	c.Assert(found, jc.IsTrue)
  1596  
  1597  	// "type" may not be removed, and this will cause an error.
  1598  	// If any one attribute's removal causes an error, there
  1599  	// should be no change.
  1600  	err = s.APIState.Client().EnvironmentUnset("abc", "type")
  1601  	c.Assert(err, gc.ErrorMatches, "type: expected string, got nothing")
  1602  	envConfig, err = s.State.EnvironConfig()
  1603  	c.Assert(err, gc.IsNil)
  1604  	_, found = envConfig.AllAttrs()["abc"]
  1605  	c.Assert(found, jc.IsTrue)
  1606  }
  1607  
  1608  func (s *clientSuite) TestClientFindTools(c *gc.C) {
  1609  	result, err := s.APIState.Client().FindTools(2, -1, "", "")
  1610  	c.Assert(err, gc.IsNil)
  1611  	c.Assert(result.Error, jc.Satisfies, params.IsCodeNotFound)
  1612  	toolstesting.UploadToStorage(c, s.Conn.Environ.Storage(), version.MustParseBinary("2.12.0-precise-amd64"))
  1613  	result, err = s.APIState.Client().FindTools(2, 12, "precise", "amd64")
  1614  	c.Assert(err, gc.IsNil)
  1615  	c.Assert(result.Error, gc.IsNil)
  1616  	c.Assert(result.List, gc.HasLen, 1)
  1617  	c.Assert(result.List[0].Version, gc.Equals, version.MustParseBinary("2.12.0-precise-amd64"))
  1618  }
  1619  
  1620  func (s *clientSuite) checkMachine(c *gc.C, id, series, cons string) {
  1621  	// Ensure the machine was actually created.
  1622  	machine, err := s.BackingState.Machine(id)
  1623  	c.Assert(err, gc.IsNil)
  1624  	c.Assert(machine.Series(), gc.Equals, series)
  1625  	c.Assert(machine.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobHostUnits})
  1626  	machineConstraints, err := machine.Constraints()
  1627  	c.Assert(err, gc.IsNil)
  1628  	c.Assert(machineConstraints.String(), gc.Equals, cons)
  1629  }
  1630  
  1631  func (s *clientSuite) TestClientAddMachinesDefaultSeries(c *gc.C) {
  1632  	apiParams := make([]params.AddMachineParams, 3)
  1633  	for i := 0; i < 3; i++ {
  1634  		apiParams[i] = params.AddMachineParams{
  1635  			Jobs: []params.MachineJob{params.JobHostUnits},
  1636  		}
  1637  	}
  1638  	machines, err := s.APIState.Client().AddMachines(apiParams)
  1639  	c.Assert(err, gc.IsNil)
  1640  	c.Assert(len(machines), gc.Equals, 3)
  1641  	for i, machineResult := range machines {
  1642  		c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
  1643  		s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String())
  1644  	}
  1645  }
  1646  
  1647  func (s *clientSuite) TestClientAddMachinesWithSeries(c *gc.C) {
  1648  	apiParams := make([]params.AddMachineParams, 3)
  1649  	for i := 0; i < 3; i++ {
  1650  		apiParams[i] = params.AddMachineParams{
  1651  			Series: "quantal",
  1652  			Jobs:   []params.MachineJob{params.JobHostUnits},
  1653  		}
  1654  	}
  1655  	machines, err := s.APIState.Client().AddMachines(apiParams)
  1656  	c.Assert(err, gc.IsNil)
  1657  	c.Assert(len(machines), gc.Equals, 3)
  1658  	for i, machineResult := range machines {
  1659  		c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
  1660  		s.checkMachine(c, machineResult.Machine, "quantal", apiParams[i].Constraints.String())
  1661  	}
  1662  }
  1663  
  1664  func (s *clientSuite) TestClientAddMachineInsideMachine(c *gc.C) {
  1665  	_, err := s.State.AddMachine("quantal", state.JobHostUnits)
  1666  	c.Assert(err, gc.IsNil)
  1667  
  1668  	machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{{
  1669  		Jobs:          []params.MachineJob{params.JobHostUnits},
  1670  		ContainerType: instance.LXC,
  1671  		ParentId:      "0",
  1672  		Series:        "quantal",
  1673  	}})
  1674  	c.Assert(err, gc.IsNil)
  1675  	c.Assert(machines, gc.HasLen, 1)
  1676  	c.Assert(machines[0].Machine, gc.Equals, "0/lxc/0")
  1677  }
  1678  
  1679  func (s *clientSuite) TestClientAddMachinesWithConstraints(c *gc.C) {
  1680  	apiParams := make([]params.AddMachineParams, 3)
  1681  	for i := 0; i < 3; i++ {
  1682  		apiParams[i] = params.AddMachineParams{
  1683  			Jobs: []params.MachineJob{params.JobHostUnits},
  1684  		}
  1685  	}
  1686  	// The last machine has some constraints.
  1687  	apiParams[2].Constraints = constraints.MustParse("mem=4G")
  1688  	machines, err := s.APIState.Client().AddMachines(apiParams)
  1689  	c.Assert(err, gc.IsNil)
  1690  	c.Assert(len(machines), gc.Equals, 3)
  1691  	for i, machineResult := range machines {
  1692  		c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
  1693  		s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String())
  1694  	}
  1695  }
  1696  
  1697  func (s *clientSuite) TestClientAddMachinesWithPlacement(c *gc.C) {
  1698  	apiParams := make([]params.AddMachineParams, 4)
  1699  	for i := range apiParams {
  1700  		apiParams[i] = params.AddMachineParams{
  1701  			Jobs: []params.MachineJob{params.JobHostUnits},
  1702  		}
  1703  	}
  1704  	apiParams[0].Placement = instance.MustParsePlacement("lxc")
  1705  	apiParams[1].Placement = instance.MustParsePlacement("lxc:0")
  1706  	apiParams[1].ContainerType = instance.LXC
  1707  	apiParams[2].Placement = instance.MustParsePlacement("dummyenv:invalid")
  1708  	apiParams[3].Placement = instance.MustParsePlacement("dummyenv:valid")
  1709  	machines, err := s.APIState.Client().AddMachines(apiParams)
  1710  	c.Assert(err, gc.IsNil)
  1711  	c.Assert(len(machines), gc.Equals, 4)
  1712  	c.Assert(machines[0].Machine, gc.Equals, "0/lxc/0")
  1713  	c.Assert(machines[1].Error, gc.ErrorMatches, "container type and placement are mutually exclusive")
  1714  	c.Assert(machines[2].Error, gc.ErrorMatches, "cannot add a new machine: invalid placement is invalid")
  1715  	c.Assert(machines[3].Machine, gc.Equals, "1")
  1716  
  1717  	m, err := s.BackingState.Machine(machines[3].Machine)
  1718  	c.Assert(err, gc.IsNil)
  1719  	c.Assert(m.Placement(), gc.DeepEquals, apiParams[3].Placement.Directive)
  1720  }
  1721  
  1722  func (s *clientSuite) TestClientAddMachines1dot18(c *gc.C) {
  1723  	apiParams := make([]params.AddMachineParams, 2)
  1724  	for i := range apiParams {
  1725  		apiParams[i] = params.AddMachineParams{
  1726  			Jobs: []params.MachineJob{params.JobHostUnits},
  1727  		}
  1728  	}
  1729  	apiParams[1].ContainerType = instance.LXC
  1730  	apiParams[1].ParentId = "0"
  1731  	machines, err := s.APIState.Client().AddMachines1dot18(apiParams)
  1732  	c.Assert(err, gc.IsNil)
  1733  	c.Assert(len(machines), gc.Equals, 2)
  1734  	c.Assert(machines[0].Machine, gc.Equals, "0")
  1735  	c.Assert(machines[1].Machine, gc.Equals, "0/lxc/0")
  1736  }
  1737  
  1738  func (s *clientSuite) TestClientAddMachines1dot18SomeErrors(c *gc.C) {
  1739  	apiParams := []params.AddMachineParams{{
  1740  		Jobs:     []params.MachineJob{params.JobHostUnits},
  1741  		ParentId: "123",
  1742  	}}
  1743  	machines, err := s.APIState.Client().AddMachines1dot18(apiParams)
  1744  	c.Assert(err, gc.IsNil)
  1745  	c.Assert(len(machines), gc.Equals, 1)
  1746  	c.Check(machines[0].Error, gc.ErrorMatches, "parent machine specified without container type")
  1747  }
  1748  
  1749  func (s *clientSuite) TestClientAddMachinesSomeErrors(c *gc.C) {
  1750  	// Here we check that adding a number of containers correctly handles the
  1751  	// case that some adds succeed and others fail and report the errors
  1752  	// accordingly.
  1753  	// We will set up params to the AddMachines API to attempt to create 3 machines.
  1754  	// Machines 0 and 1 will be added successfully.
  1755  	// Remaining machines will fail due to different reasons.
  1756  
  1757  	// Create a machine to host the requested containers.
  1758  	host, err := s.State.AddMachine("quantal", state.JobHostUnits)
  1759  	c.Assert(err, gc.IsNil)
  1760  	// The host only supports lxc containers.
  1761  	err = host.SetSupportedContainers([]instance.ContainerType{instance.LXC})
  1762  	c.Assert(err, gc.IsNil)
  1763  
  1764  	// Set up params for adding 3 containers.
  1765  	apiParams := make([]params.AddMachineParams, 3)
  1766  	for i := range apiParams {
  1767  		apiParams[i] = params.AddMachineParams{
  1768  			Jobs: []params.MachineJob{params.JobHostUnits},
  1769  		}
  1770  	}
  1771  	// This will cause a machine add to fail due to an unsupported container.
  1772  	apiParams[2].ContainerType = instance.KVM
  1773  	apiParams[2].ParentId = host.Id()
  1774  	machines, err := s.APIState.Client().AddMachines(apiParams)
  1775  	c.Assert(err, gc.IsNil)
  1776  	c.Assert(len(machines), gc.Equals, 3)
  1777  
  1778  	// Check the results - machines 2 and 3 will have errors.
  1779  	c.Check(machines[0].Machine, gc.Equals, "1")
  1780  	c.Check(machines[0].Error, gc.IsNil)
  1781  	c.Check(machines[1].Machine, gc.Equals, "2")
  1782  	c.Check(machines[1].Error, gc.IsNil)
  1783  	c.Check(machines[2].Error, gc.ErrorMatches, "cannot add a new machine: machine 0 cannot host kvm containers")
  1784  }
  1785  
  1786  func (s *clientSuite) TestClientAddMachinesWithInstanceIdSomeErrors(c *gc.C) {
  1787  	apiParams := make([]params.AddMachineParams, 3)
  1788  	addrs := []network.Address{network.NewAddress("1.2.3.4", network.ScopeUnknown)}
  1789  	hc := instance.MustParseHardware("mem=4G")
  1790  	for i := 0; i < 3; i++ {
  1791  		apiParams[i] = params.AddMachineParams{
  1792  			Jobs:       []params.MachineJob{params.JobHostUnits},
  1793  			InstanceId: instance.Id(fmt.Sprintf("1234-%d", i)),
  1794  			Nonce:      "foo",
  1795  			HardwareCharacteristics: hc,
  1796  			Addrs: addrs,
  1797  		}
  1798  	}
  1799  	// This will cause the last machine add to fail.
  1800  	apiParams[2].Nonce = ""
  1801  	machines, err := s.APIState.Client().AddMachines(apiParams)
  1802  	c.Assert(err, gc.IsNil)
  1803  	c.Assert(len(machines), gc.Equals, 3)
  1804  	for i, machineResult := range machines {
  1805  		if i == 2 {
  1806  			c.Assert(machineResult.Error, gc.NotNil)
  1807  			c.Assert(machineResult.Error, gc.ErrorMatches, "cannot add a new machine: cannot add a machine with an instance id and no nonce")
  1808  		} else {
  1809  			c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
  1810  			s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String())
  1811  			instanceId := fmt.Sprintf("1234-%d", i)
  1812  			s.checkInstance(c, machineResult.Machine, instanceId, "foo", hc, addrs)
  1813  		}
  1814  	}
  1815  }
  1816  
  1817  func (s *clientSuite) checkInstance(c *gc.C, id, instanceId, nonce string,
  1818  	hc instance.HardwareCharacteristics, addr []network.Address) {
  1819  
  1820  	machine, err := s.BackingState.Machine(id)
  1821  	c.Assert(err, gc.IsNil)
  1822  	machineInstanceId, err := machine.InstanceId()
  1823  	c.Assert(err, gc.IsNil)
  1824  	c.Assert(machine.CheckProvisioned(nonce), jc.IsTrue)
  1825  	c.Assert(machineInstanceId, gc.Equals, instance.Id(instanceId))
  1826  	machineHardware, err := machine.HardwareCharacteristics()
  1827  	c.Assert(err, gc.IsNil)
  1828  	c.Assert(machineHardware.String(), gc.Equals, hc.String())
  1829  	c.Assert(machine.Addresses(), gc.DeepEquals, addr)
  1830  }
  1831  
  1832  func (s *clientSuite) TestInjectMachinesStillExists(c *gc.C) {
  1833  	results := new(params.AddMachinesResults)
  1834  	// We need to use Call directly because the client interface
  1835  	// no longer refers to InjectMachine.
  1836  	args := params.AddMachines{
  1837  		MachineParams: []params.AddMachineParams{{
  1838  			Jobs:       []params.MachineJob{params.JobHostUnits},
  1839  			InstanceId: "i-foo",
  1840  			Nonce:      "nonce",
  1841  		}},
  1842  	}
  1843  	err := s.APIState.Call("Client", "", "AddMachines", args, &results)
  1844  	c.Assert(err, gc.IsNil)
  1845  	c.Assert(results.Machines, gc.HasLen, 1)
  1846  }
  1847  
  1848  func (s *clientSuite) TestProvisioningScript(c *gc.C) {
  1849  	// Inject a machine and then call the ProvisioningScript API.
  1850  	// The result should be the same as when calling MachineConfig,
  1851  	// converting it to a cloudinit.MachineConfig, and disabling
  1852  	// apt_upgrade.
  1853  	apiParams := params.AddMachineParams{
  1854  		Jobs:       []params.MachineJob{params.JobHostUnits},
  1855  		InstanceId: instance.Id("1234"),
  1856  		Nonce:      "foo",
  1857  		HardwareCharacteristics: instance.MustParseHardware("arch=amd64"),
  1858  	}
  1859  	machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{apiParams})
  1860  	c.Assert(err, gc.IsNil)
  1861  	c.Assert(len(machines), gc.Equals, 1)
  1862  	machineId := machines[0].Machine
  1863  	// Call ProvisioningScript. Normally ProvisioningScript and
  1864  	// MachineConfig are mutually exclusive; both of them will
  1865  	// allocate a state/api password for the machine agent.
  1866  	script, err := s.APIState.Client().ProvisioningScript(params.ProvisioningScriptParams{
  1867  		MachineId: machineId,
  1868  		Nonce:     apiParams.Nonce,
  1869  	})
  1870  	c.Assert(err, gc.IsNil)
  1871  	mcfg, err := client.MachineConfig(s.State, machineId, apiParams.Nonce, "")
  1872  	c.Assert(err, gc.IsNil)
  1873  	sshinitScript, err := manual.ProvisioningScript(mcfg)
  1874  	c.Assert(err, gc.IsNil)
  1875  	// ProvisioningScript internally calls MachineConfig,
  1876  	// which allocates a new, random password. Everything
  1877  	// about the scripts should be the same other than
  1878  	// the line containing "oldpassword" from agent.conf.
  1879  	scriptLines := strings.Split(script, "\n")
  1880  	sshinitScriptLines := strings.Split(sshinitScript, "\n")
  1881  	c.Assert(scriptLines, gc.HasLen, len(sshinitScriptLines))
  1882  	for i, line := range scriptLines {
  1883  		if strings.Contains(line, "oldpassword") {
  1884  			continue
  1885  		}
  1886  		c.Assert(line, gc.Equals, sshinitScriptLines[i])
  1887  	}
  1888  }
  1889  
  1890  func (s *clientSuite) TestProvisioningScriptDisablePackageCommands(c *gc.C) {
  1891  	apiParams := params.AddMachineParams{
  1892  		Jobs:       []params.MachineJob{params.JobHostUnits},
  1893  		InstanceId: instance.Id("1234"),
  1894  		Nonce:      "foo",
  1895  		HardwareCharacteristics: instance.MustParseHardware("arch=amd64"),
  1896  	}
  1897  	machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{apiParams})
  1898  	c.Assert(err, gc.IsNil)
  1899  	c.Assert(len(machines), gc.Equals, 1)
  1900  	machineId := machines[0].Machine
  1901  	for _, disable := range []bool{false, true} {
  1902  		script, err := s.APIState.Client().ProvisioningScript(params.ProvisioningScriptParams{
  1903  			MachineId: machineId,
  1904  			Nonce:     apiParams.Nonce,
  1905  			DisablePackageCommands: disable,
  1906  		})
  1907  		c.Assert(err, gc.IsNil)
  1908  		var checker gc.Checker = jc.Contains
  1909  		if disable {
  1910  			// We disabled package commands: there should be no "apt" commands in the script.
  1911  			checker = gc.Not(checker)
  1912  		}
  1913  		c.Assert(script, checker, "apt-get")
  1914  	}
  1915  }
  1916  
  1917  func (s *clientSuite) TestClientSpecializeStoreOnDeployServiceSetCharmAndAddCharm(c *gc.C) {
  1918  	store, restore := makeMockCharmStore()
  1919  	defer restore()
  1920  
  1921  	attrs := map[string]interface{}{"charm-store-auth": "token=value",
  1922  		"test-mode": true}
  1923  	err := s.State.UpdateEnvironConfig(attrs, nil, nil)
  1924  	c.Assert(err, gc.IsNil)
  1925  
  1926  	curl, _ := addCharm(c, store, "dummy")
  1927  	err = s.APIState.Client().ServiceDeploy(
  1928  		curl.String(), "service", 3, "", constraints.Value{}, "",
  1929  	)
  1930  	c.Assert(err, gc.IsNil)
  1931  
  1932  	// check that the store's auth attributes were set
  1933  	c.Assert(store.AuthAttrs, gc.Equals, "token=value")
  1934  	c.Assert(store.TestMode, gc.Equals, true)
  1935  
  1936  	store.AuthAttrs = ""
  1937  
  1938  	curl, _ = addCharm(c, store, "wordpress")
  1939  	err = s.APIState.Client().ServiceSetCharm(
  1940  		"service", curl.String(), false,
  1941  	)
  1942  
  1943  	// check that the store's auth attributes were set
  1944  	c.Assert(store.AuthAttrs, gc.Equals, "token=value")
  1945  
  1946  	curl, _ = addCharm(c, store, "riak")
  1947  	err = s.APIState.Client().AddCharm(curl)
  1948  
  1949  	// check that the store's auth attributes were set
  1950  	c.Assert(store.AuthAttrs, gc.Equals, "token=value")
  1951  }
  1952  
  1953  func (s *clientSuite) TestAddCharm(c *gc.C) {
  1954  	store, restore := makeMockCharmStore()
  1955  	defer restore()
  1956  
  1957  	client := s.APIState.Client()
  1958  	// First test the sanity checks.
  1959  	err := client.AddCharm(&charm.URL{Reference: charm.Reference{Name: "nonsense"}})
  1960  	c.Assert(err, gc.ErrorMatches, `charm URL has invalid schema: ":nonsense-0"`)
  1961  	err = client.AddCharm(charm.MustParseURL("local:precise/dummy"))
  1962  	c.Assert(err, gc.ErrorMatches, "only charm store charm URLs are supported, with cs: schema")
  1963  	err = client.AddCharm(charm.MustParseURL("cs:precise/wordpress"))
  1964  	c.Assert(err, gc.ErrorMatches, "charm URL must include revision")
  1965  
  1966  	// Add a charm, without uploading it to storage, to
  1967  	// check that AddCharm does not try to do it.
  1968  	charmDir := charmtesting.Charms.Dir("dummy")
  1969  	ident := fmt.Sprintf("%s-%d", charmDir.Meta().Name, charmDir.Revision())
  1970  	curl := charm.MustParseURL("cs:quantal/" + ident)
  1971  	bundleURL, err := url.Parse("http://bundles.testing.invalid/" + ident)
  1972  	c.Assert(err, gc.IsNil)
  1973  	sch, err := s.State.AddCharm(charmDir, curl, bundleURL, ident+"-sha256")
  1974  	c.Assert(err, gc.IsNil)
  1975  
  1976  	name := charm.Quote(sch.URL().String())
  1977  	storage := s.Conn.Environ.Storage()
  1978  	_, err = storage.Get(name)
  1979  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1980  
  1981  	// AddCharm should see the charm in state and not upload it.
  1982  	err = client.AddCharm(sch.URL())
  1983  	c.Assert(err, gc.IsNil)
  1984  	_, err = storage.Get(name)
  1985  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1986  
  1987  	// Now try adding another charm completely.
  1988  	curl, _ = addCharm(c, store, "wordpress")
  1989  	err = client.AddCharm(curl)
  1990  	c.Assert(err, gc.IsNil)
  1991  
  1992  	// Verify it's in state and it got uploaded.
  1993  	sch, err = s.State.Charm(curl)
  1994  	c.Assert(err, gc.IsNil)
  1995  	s.assertUploaded(c, storage, sch.BundleURL(), sch.BundleSha256())
  1996  }
  1997  
  1998  var resolveCharmCases = []struct {
  1999  	schema, defaultSeries, charmName string
  2000  	parseErr                         string
  2001  	resolveErr                       string
  2002  }{
  2003  	{"cs", "precise", "wordpress", "", ""},
  2004  	{"cs", "trusty", "wordpress", "", ""},
  2005  	{"cs", "", "wordpress", "", `missing default series, cannot resolve charm url: "cs:wordpress"`},
  2006  	{"cs", "trusty", "", `charm URL has invalid charm name: "cs:"`, ""},
  2007  	{"local", "trusty", "wordpress", "", `only charm store charm references are supported, with cs: schema`},
  2008  	{"cs", "precise", "hl3", "", ""},
  2009  	{"cs", "trusty", "hl3", "", ""},
  2010  	{"cs", "", "hl3", "", `missing default series, cannot resolve charm url: \"cs:hl3\"`},
  2011  }
  2012  
  2013  func (s *clientSuite) TestResolveCharm(c *gc.C) {
  2014  	store, restore := makeMockCharmStore()
  2015  	defer restore()
  2016  
  2017  	for i, test := range resolveCharmCases {
  2018  		c.Logf("test %d: %#v", i, test)
  2019  		// Mock charm store will use this to resolve a charm reference.
  2020  		store.DefaultSeries = test.defaultSeries
  2021  
  2022  		client := s.APIState.Client()
  2023  		ref, series, err := charm.ParseReference(fmt.Sprintf("%s:%s", test.schema, test.charmName))
  2024  		if test.parseErr == "" {
  2025  			if !c.Check(err, gc.IsNil) {
  2026  				continue
  2027  			}
  2028  		} else {
  2029  			c.Assert(err, gc.NotNil)
  2030  			c.Check(err, gc.ErrorMatches, test.parseErr)
  2031  			continue
  2032  		}
  2033  		c.Check(series, gc.Equals, "")
  2034  		c.Check(ref.String(), gc.Equals, fmt.Sprintf("%s:%s", test.schema, test.charmName))
  2035  
  2036  		curl, err := client.ResolveCharm(ref)
  2037  		if err == nil {
  2038  			c.Assert(curl, gc.NotNil)
  2039  			// Only cs: schema should make it through here
  2040  			c.Check(curl.String(), gc.Equals, fmt.Sprintf("cs:%s/%s", test.defaultSeries, test.charmName))
  2041  			c.Check(test.resolveErr, gc.Equals, "")
  2042  		} else {
  2043  			c.Check(curl, gc.IsNil)
  2044  			c.Check(err, gc.ErrorMatches, test.resolveErr)
  2045  		}
  2046  	}
  2047  }
  2048  
  2049  func (s *clientSuite) TestAddCharmConcurrently(c *gc.C) {
  2050  	store, restore := makeMockCharmStore()
  2051  	defer restore()
  2052  
  2053  	client := s.APIState.Client()
  2054  	curl, _ := addCharm(c, store, "wordpress")
  2055  
  2056  	// Expect storage Put() to be called once for each goroutine
  2057  	// below.
  2058  	ops := make(chan dummy.Operation, 500)
  2059  	dummy.Listen(ops)
  2060  	go s.assertPutCalled(c, ops, 10)
  2061  
  2062  	// Try adding the same charm concurrently from multiple goroutines
  2063  	// to test no "duplicate key errors" are reported (see lp bug
  2064  	// #1067979) and also at the end only one charm document is
  2065  	// created.
  2066  
  2067  	var wg sync.WaitGroup
  2068  	for i := 0; i < 10; i++ {
  2069  		wg.Add(1)
  2070  		go func(index int) {
  2071  			defer wg.Done()
  2072  
  2073  			c.Assert(client.AddCharm(curl), gc.IsNil, gc.Commentf("goroutine %d", index))
  2074  			sch, err := s.State.Charm(curl)
  2075  			c.Assert(err, gc.IsNil, gc.Commentf("goroutine %d", index))
  2076  			c.Assert(sch.URL(), jc.DeepEquals, curl, gc.Commentf("goroutine %d", index))
  2077  			expectedName := fmt.Sprintf("%s-%d-[0-9a-f-]+", curl.Name, curl.Revision)
  2078  			c.Assert(getArchiveName(sch.BundleURL()), gc.Matches, expectedName)
  2079  		}(i)
  2080  	}
  2081  	wg.Wait()
  2082  	close(ops)
  2083  
  2084  	// Verify there is only a single uploaded charm remains and it
  2085  	// contains the correct data.
  2086  	sch, err := s.State.Charm(curl)
  2087  	c.Assert(err, gc.IsNil)
  2088  	storage, err := environs.GetStorage(s.State)
  2089  	c.Assert(err, gc.IsNil)
  2090  	uploads, err := storage.List(fmt.Sprintf("%s-%d-", curl.Name, curl.Revision))
  2091  	c.Assert(err, gc.IsNil)
  2092  	c.Assert(uploads, gc.HasLen, 1)
  2093  	c.Assert(getArchiveName(sch.BundleURL()), gc.Equals, uploads[0])
  2094  	s.assertUploaded(c, storage, sch.BundleURL(), sch.BundleSha256())
  2095  }
  2096  
  2097  func (s *clientSuite) TestAddCharmOverwritesPlaceholders(c *gc.C) {
  2098  	store, restore := makeMockCharmStore()
  2099  	defer restore()
  2100  
  2101  	client := s.APIState.Client()
  2102  	curl, _ := addCharm(c, store, "wordpress")
  2103  
  2104  	// Add a placeholder with the same charm URL.
  2105  	err := s.State.AddStoreCharmPlaceholder(curl)
  2106  	c.Assert(err, gc.IsNil)
  2107  	_, err = s.State.Charm(curl)
  2108  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  2109  
  2110  	// Now try to add the charm, which will convert the placeholder to
  2111  	// a pending charm.
  2112  	err = client.AddCharm(curl)
  2113  	c.Assert(err, gc.IsNil)
  2114  
  2115  	// Make sure the document's flags were reset as expected.
  2116  	sch, err := s.State.Charm(curl)
  2117  	c.Assert(err, gc.IsNil)
  2118  	c.Assert(sch.URL(), jc.DeepEquals, curl)
  2119  	c.Assert(sch.IsPlaceholder(), jc.IsFalse)
  2120  	c.Assert(sch.IsUploaded(), jc.IsTrue)
  2121  }
  2122  
  2123  func (s *clientSuite) TestCharmArchiveName(c *gc.C) {
  2124  	for rev, name := range []string{"Foo", "bar", "wordpress", "mysql"} {
  2125  		archiveFormat := fmt.Sprintf("%s-%d-[0-9a-f-]+", name, rev)
  2126  		archiveName, err := client.CharmArchiveName(name, rev)
  2127  		c.Check(err, gc.IsNil)
  2128  		c.Check(archiveName, gc.Matches, archiveFormat)
  2129  	}
  2130  }
  2131  
  2132  func (s *clientSuite) assertPutCalled(c *gc.C, ops chan dummy.Operation, numCalls int) {
  2133  	calls := 0
  2134  	select {
  2135  	case op, ok := <-ops:
  2136  		if !ok {
  2137  			return
  2138  		}
  2139  		if op, ok := op.(dummy.OpPutFile); ok {
  2140  			calls++
  2141  			if calls > numCalls {
  2142  				c.Fatalf("storage Put() called %d times, expected %d times", calls, numCalls)
  2143  				return
  2144  			}
  2145  			nameFormat := "[0-9a-z-]+-[0-9]+-[0-9a-f-]+"
  2146  			c.Assert(op.FileName, gc.Matches, nameFormat)
  2147  		}
  2148  	case <-time.After(coretesting.LongWait):
  2149  		c.Fatalf("timed out while waiting for a storage Put() calls")
  2150  		return
  2151  	}
  2152  }
  2153  
  2154  func (s *clientSuite) assertUploaded(c *gc.C, storage envstorage.Storage, bundleURL *url.URL, expectedSHA256 string) {
  2155  	archiveName := getArchiveName(bundleURL)
  2156  	reader, err := storage.Get(archiveName)
  2157  	c.Assert(err, gc.IsNil)
  2158  	defer reader.Close()
  2159  	downloadedSHA256, _, err := utils.ReadSHA256(reader)
  2160  	c.Assert(err, gc.IsNil)
  2161  	c.Assert(downloadedSHA256, gc.Equals, expectedSHA256)
  2162  }
  2163  
  2164  func getArchiveName(bundleURL *url.URL) string {
  2165  	return strings.TrimPrefix(bundleURL.RequestURI(), "/dummyenv/private/")
  2166  }
  2167  
  2168  func (s *clientSuite) TestRetryProvisioning(c *gc.C) {
  2169  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
  2170  	c.Assert(err, gc.IsNil)
  2171  	err = machine.SetStatus(params.StatusError, "error", nil)
  2172  	c.Assert(err, gc.IsNil)
  2173  	_, err = s.APIState.Client().RetryProvisioning(machine.Tag())
  2174  	c.Assert(err, gc.IsNil)
  2175  
  2176  	status, info, data, err := machine.Status()
  2177  	c.Assert(err, gc.IsNil)
  2178  	c.Assert(status, gc.Equals, params.StatusError)
  2179  	c.Assert(info, gc.Equals, "error")
  2180  	c.Assert(data["transient"], gc.Equals, true)
  2181  }
  2182  
  2183  func (s *clientSuite) setAgentAlive(c *gc.C, machineId string) *presence.Pinger {
  2184  	m, err := s.BackingState.Machine(machineId)
  2185  	c.Assert(err, gc.IsNil)
  2186  	pinger, err := m.SetAgentAlive()
  2187  	c.Assert(err, gc.IsNil)
  2188  	s.BackingState.StartSync()
  2189  	err = m.WaitAgentAlive(coretesting.LongWait)
  2190  	c.Assert(err, gc.IsNil)
  2191  	return pinger
  2192  }
  2193  
  2194  var (
  2195  	emptyCons     = constraints.Value{}
  2196  	defaultSeries = ""
  2197  )
  2198  
  2199  func (s *clientSuite) TestClientEnsureAvailabilitySeries(c *gc.C) {
  2200  	_, err := s.State.AddMachine("quantal", state.JobManageEnviron)
  2201  	c.Assert(err, gc.IsNil)
  2202  	// We have to ensure the agents are alive, or EnsureAvailability will
  2203  	// create more to replace them.
  2204  	pinger := s.setAgentAlive(c, "0")
  2205  	defer pinger.Kill()
  2206  	machines, err := s.State.AllMachines()
  2207  	c.Assert(err, gc.IsNil)
  2208  	c.Assert(machines, gc.HasLen, 1)
  2209  	c.Assert(machines[0].Series(), gc.Equals, "quantal")
  2210  	err = s.APIState.Client().EnsureAvailability(3, emptyCons, defaultSeries)
  2211  	c.Assert(err, gc.IsNil)
  2212  	machines, err = s.State.AllMachines()
  2213  	c.Assert(err, gc.IsNil)
  2214  	c.Assert(machines, gc.HasLen, 3)
  2215  	c.Assert(machines[0].Series(), gc.Equals, "quantal")
  2216  	c.Assert(machines[1].Series(), gc.Equals, "quantal")
  2217  	c.Assert(machines[2].Series(), gc.Equals, "quantal")
  2218  	defer s.setAgentAlive(c, "1").Kill()
  2219  	defer s.setAgentAlive(c, "2").Kill()
  2220  	err = s.APIState.Client().EnsureAvailability(5, emptyCons, "non-default")
  2221  	c.Assert(err, gc.IsNil)
  2222  	machines, err = s.State.AllMachines()
  2223  	c.Assert(err, gc.IsNil)
  2224  	c.Assert(machines, gc.HasLen, 5)
  2225  	c.Assert(machines[0].Series(), gc.Equals, "quantal")
  2226  	c.Assert(machines[1].Series(), gc.Equals, "quantal")
  2227  	c.Assert(machines[2].Series(), gc.Equals, "quantal")
  2228  	c.Assert(machines[3].Series(), gc.Equals, "non-default")
  2229  	c.Assert(machines[4].Series(), gc.Equals, "non-default")
  2230  }
  2231  
  2232  func (s *clientSuite) TestClientEnsureAvailabilityConstraints(c *gc.C) {
  2233  	_, err := s.State.AddMachine("quantal", state.JobManageEnviron)
  2234  	c.Assert(err, gc.IsNil)
  2235  	pinger := s.setAgentAlive(c, "0")
  2236  	defer pinger.Kill()
  2237  	err = s.APIState.Client().EnsureAvailability(
  2238  		3, constraints.MustParse("mem=4G"), defaultSeries)
  2239  	c.Assert(err, gc.IsNil)
  2240  	machines, err := s.State.AllMachines()
  2241  	c.Assert(err, gc.IsNil)
  2242  	c.Assert(machines, gc.HasLen, 3)
  2243  	expectedCons := []constraints.Value{
  2244  		constraints.Value{},
  2245  		constraints.MustParse("mem=4G"),
  2246  		constraints.MustParse("mem=4G"),
  2247  	}
  2248  	for i, m := range machines {
  2249  		cons, err := m.Constraints()
  2250  		c.Assert(err, gc.IsNil)
  2251  		c.Check(cons, gc.DeepEquals, expectedCons[i])
  2252  	}
  2253  }
  2254  
  2255  func (s *clientSuite) TestClientEnsureAvailability0Preserves(c *gc.C) {
  2256  	_, err := s.State.AddMachine("quantal", state.JobManageEnviron)
  2257  	c.Assert(err, gc.IsNil)
  2258  	pinger := s.setAgentAlive(c, "0")
  2259  	defer pinger.Kill()
  2260  	// A value of 0 says either "if I'm not HA, make me HA" or "preserve my
  2261  	// current HA settings".
  2262  	err = s.APIState.Client().EnsureAvailability(0, emptyCons, defaultSeries)
  2263  	c.Assert(err, gc.IsNil)
  2264  	machines, err := s.State.AllMachines()
  2265  	c.Assert(machines, gc.HasLen, 3)
  2266  	defer s.setAgentAlive(c, "1").Kill()
  2267  	// Now, we keep agent 1 alive, but not agent 2, calling
  2268  	// EnsureAvailability(0) again will cause us to start another machine
  2269  	err = s.APIState.Client().EnsureAvailability(0, emptyCons, defaultSeries)
  2270  	c.Assert(err, gc.IsNil)
  2271  	machines, err = s.State.AllMachines()
  2272  	c.Assert(machines, gc.HasLen, 4)
  2273  }
  2274  
  2275  func (s *clientSuite) TestClientEnsureAvailability0Preserves5(c *gc.C) {
  2276  	_, err := s.State.AddMachine("quantal", state.JobManageEnviron)
  2277  	c.Assert(err, gc.IsNil)
  2278  	pinger := s.setAgentAlive(c, "0")
  2279  	defer pinger.Kill()
  2280  	// Start off with 5 servers
  2281  	err = s.APIState.Client().EnsureAvailability(5, emptyCons, defaultSeries)
  2282  	c.Assert(err, gc.IsNil)
  2283  	machines, err := s.State.AllMachines()
  2284  	c.Assert(machines, gc.HasLen, 5)
  2285  	defer s.setAgentAlive(c, "1").Kill()
  2286  	defer s.setAgentAlive(c, "2").Kill()
  2287  	defer s.setAgentAlive(c, "3").Kill()
  2288  	// Keeping all alive but one, will bring up 1 more server to preserve 5
  2289  	err = s.APIState.Client().EnsureAvailability(0, emptyCons, defaultSeries)
  2290  	c.Assert(err, gc.IsNil)
  2291  	machines, err = s.State.AllMachines()
  2292  	c.Assert(machines, gc.HasLen, 6)
  2293  }
  2294  
  2295  func (s *clientSuite) TestClientEnsureAvailabilityErrors(c *gc.C) {
  2296  	_, err := s.State.AddMachine("quantal", state.JobManageEnviron)
  2297  	c.Assert(err, gc.IsNil)
  2298  	pinger := s.setAgentAlive(c, "0")
  2299  	defer pinger.Kill()
  2300  	err = s.APIState.Client().EnsureAvailability(-1, emptyCons, defaultSeries)
  2301  	c.Assert(err, gc.ErrorMatches, "number of state servers must be odd and non-negative")
  2302  	err = s.APIState.Client().EnsureAvailability(3, emptyCons, defaultSeries)
  2303  	c.Assert(err, gc.IsNil)
  2304  	err = s.APIState.Client().EnsureAvailability(1, emptyCons, defaultSeries)
  2305  	c.Assert(err, gc.ErrorMatches, "cannot reduce state server count")
  2306  }
  2307  
  2308  func (s *clientSuite) TestAPIHostPorts(c *gc.C) {
  2309  	apiHostPorts, err := s.APIState.Client().APIHostPorts()
  2310  	c.Assert(err, gc.IsNil)
  2311  	c.Assert(apiHostPorts, gc.HasLen, 0)
  2312  
  2313  	server1Addresses := []network.Address{{
  2314  		Value: "server-1",
  2315  		Type:  network.HostName,
  2316  		Scope: network.ScopePublic,
  2317  	}, {
  2318  		Value:       "10.0.0.1",
  2319  		Type:        network.IPv4Address,
  2320  		NetworkName: "internal",
  2321  		Scope:       network.ScopeCloudLocal,
  2322  	}}
  2323  	server2Addresses := []network.Address{{
  2324  		Value:       "::1",
  2325  		Type:        network.IPv6Address,
  2326  		NetworkName: "loopback",
  2327  		Scope:       network.ScopeMachineLocal,
  2328  	}}
  2329  	stateAPIHostPorts := [][]network.HostPort{
  2330  		network.AddressesWithPort(server1Addresses, 123),
  2331  		network.AddressesWithPort(server2Addresses, 456),
  2332  	}
  2333  
  2334  	err = s.State.SetAPIHostPorts(stateAPIHostPorts)
  2335  	c.Assert(err, gc.IsNil)
  2336  	apiHostPorts, err = s.APIState.Client().APIHostPorts()
  2337  	c.Assert(err, gc.IsNil)
  2338  	c.Assert(apiHostPorts, gc.DeepEquals, stateAPIHostPorts)
  2339  }
  2340  
  2341  func (s *clientSuite) TestClientAgentVersion(c *gc.C) {
  2342  	current := version.MustParse("1.2.0")
  2343  	s.PatchValue(&version.Current.Number, current)
  2344  	result, err := s.APIState.Client().AgentVersion()
  2345  	c.Assert(err, gc.IsNil)
  2346  	c.Assert(result, gc.Equals, current)
  2347  }