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