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