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