github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/apiserver/client/client_test.go (about)

     1  // Copyright 2012-2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package client_test
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/names"
    15  	jc "github.com/juju/testing/checkers"
    16  	"github.com/juju/utils/series"
    17  	"github.com/juju/version"
    18  	gc "gopkg.in/check.v1"
    19  	"gopkg.in/juju/charm.v6-unstable"
    20  
    21  	"github.com/juju/juju/agent"
    22  	"github.com/juju/juju/api"
    23  	"github.com/juju/juju/apiserver/client"
    24  	"github.com/juju/juju/apiserver/common"
    25  	"github.com/juju/juju/apiserver/params"
    26  	"github.com/juju/juju/apiserver/testing"
    27  	"github.com/juju/juju/constraints"
    28  	"github.com/juju/juju/environs"
    29  	"github.com/juju/juju/environs/config"
    30  	"github.com/juju/juju/environs/manual"
    31  	toolstesting "github.com/juju/juju/environs/tools/testing"
    32  	"github.com/juju/juju/instance"
    33  	"github.com/juju/juju/network"
    34  	"github.com/juju/juju/rpc"
    35  	"github.com/juju/juju/state"
    36  	"github.com/juju/juju/state/multiwatcher"
    37  	"github.com/juju/juju/state/presence"
    38  	"github.com/juju/juju/status"
    39  	coretesting "github.com/juju/juju/testing"
    40  	"github.com/juju/juju/testing/factory"
    41  	jujuversion "github.com/juju/juju/version"
    42  )
    43  
    44  type Killer interface {
    45  	Kill() error
    46  }
    47  
    48  type serverSuite struct {
    49  	baseSuite
    50  	client *client.Client
    51  }
    52  
    53  var _ = gc.Suite(&serverSuite{})
    54  
    55  func (s *serverSuite) SetUpTest(c *gc.C) {
    56  	s.baseSuite.SetUpTest(c)
    57  
    58  	var err error
    59  	auth := testing.FakeAuthorizer{
    60  		Tag:            s.AdminUserTag(c),
    61  		EnvironManager: true,
    62  	}
    63  	s.client, err = client.NewClient(s.State, common.NewResources(), auth)
    64  	c.Assert(err, jc.ErrorIsNil)
    65  }
    66  
    67  func (s *serverSuite) setAgentPresence(c *gc.C, machineId string) *presence.Pinger {
    68  	m, err := s.State.Machine(machineId)
    69  	c.Assert(err, jc.ErrorIsNil)
    70  	pinger, err := m.SetAgentPresence()
    71  	c.Assert(err, jc.ErrorIsNil)
    72  	s.State.StartSync()
    73  	err = m.WaitAgentPresence(coretesting.LongWait)
    74  	c.Assert(err, jc.ErrorIsNil)
    75  	return pinger
    76  }
    77  
    78  func (s *serverSuite) TestModelUsersInfo(c *gc.C) {
    79  	testAdmin := s.AdminUserTag(c)
    80  	owner, err := s.State.ModelUser(testAdmin)
    81  	c.Assert(err, jc.ErrorIsNil)
    82  
    83  	localUser1 := s.makeLocalModelUser(c, "ralphdoe", "Ralph Doe")
    84  	localUser2 := s.makeLocalModelUser(c, "samsmith", "Sam Smith")
    85  	remoteUser1 := s.Factory.MakeModelUser(c, &factory.ModelUserParams{User: "bobjohns@ubuntuone", DisplayName: "Bob Johns"})
    86  	remoteUser2 := s.Factory.MakeModelUser(c, &factory.ModelUserParams{User: "nicshaw@idprovider", DisplayName: "Nic Shaw"})
    87  
    88  	results, err := s.client.ModelUserInfo()
    89  	c.Assert(err, jc.ErrorIsNil)
    90  	var expected params.ModelUserInfoResults
    91  	for _, r := range []struct {
    92  		user *state.ModelUser
    93  		info *params.ModelUserInfo
    94  	}{
    95  		{
    96  			owner,
    97  			&params.ModelUserInfo{
    98  				UserName:    owner.UserName(),
    99  				DisplayName: owner.DisplayName(),
   100  				Access:      "write",
   101  			},
   102  		}, {
   103  			localUser1,
   104  			&params.ModelUserInfo{
   105  				UserName:    "ralphdoe@local",
   106  				DisplayName: "Ralph Doe",
   107  				Access:      "write",
   108  			},
   109  		}, {
   110  			localUser2,
   111  			&params.ModelUserInfo{
   112  				UserName:    "samsmith@local",
   113  				DisplayName: "Sam Smith",
   114  				Access:      "write",
   115  			},
   116  		}, {
   117  			remoteUser1,
   118  			&params.ModelUserInfo{
   119  				UserName:    "bobjohns@ubuntuone",
   120  				DisplayName: "Bob Johns",
   121  				Access:      "write",
   122  			},
   123  		}, {
   124  			remoteUser2,
   125  			&params.ModelUserInfo{
   126  				UserName:    "nicshaw@idprovider",
   127  				DisplayName: "Nic Shaw",
   128  				Access:      "write",
   129  			},
   130  		},
   131  	} {
   132  		r.info.LastConnection = lastConnPointer(c, r.user)
   133  		expected.Results = append(expected.Results, params.ModelUserInfoResult{Result: r.info})
   134  	}
   135  
   136  	sort.Sort(ByUserName(expected.Results))
   137  	sort.Sort(ByUserName(results.Results))
   138  	c.Assert(results, jc.DeepEquals, expected)
   139  }
   140  
   141  func lastConnPointer(c *gc.C, modelUser *state.ModelUser) *time.Time {
   142  	lastConn, err := modelUser.LastConnection()
   143  	if err != nil {
   144  		if state.IsNeverConnectedError(err) {
   145  			return nil
   146  		}
   147  		c.Fatal(err)
   148  	}
   149  	return &lastConn
   150  }
   151  
   152  // ByUserName implements sort.Interface for []params.ModelUserInfoResult based on
   153  // the UserName field.
   154  type ByUserName []params.ModelUserInfoResult
   155  
   156  func (a ByUserName) Len() int           { return len(a) }
   157  func (a ByUserName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   158  func (a ByUserName) Less(i, j int) bool { return a[i].Result.UserName < a[j].Result.UserName }
   159  
   160  func (s *serverSuite) makeLocalModelUser(c *gc.C, username, displayname string) *state.ModelUser {
   161  	// factory.MakeUser will create an ModelUser for a local user by defalut
   162  	user := s.Factory.MakeUser(c, &factory.UserParams{Name: username, DisplayName: displayname})
   163  	modelUser, err := s.State.ModelUser(user.UserTag())
   164  	c.Assert(err, jc.ErrorIsNil)
   165  	return modelUser
   166  }
   167  
   168  func (s *serverSuite) TestSetEnvironAgentVersion(c *gc.C) {
   169  	args := params.SetModelAgentVersion{
   170  		Version: version.MustParse("9.8.7"),
   171  	}
   172  	err := s.client.SetModelAgentVersion(args)
   173  	c.Assert(err, jc.ErrorIsNil)
   174  
   175  	envConfig, err := s.State.ModelConfig()
   176  	c.Assert(err, jc.ErrorIsNil)
   177  	agentVersion, found := envConfig.AllAttrs()["agent-version"]
   178  	c.Assert(found, jc.IsTrue)
   179  	c.Assert(agentVersion, gc.Equals, "9.8.7")
   180  }
   181  
   182  type mockEnviron struct {
   183  	environs.Environ
   184  	allInstancesCalled bool
   185  	err                error
   186  }
   187  
   188  func (m *mockEnviron) AllInstances() ([]instance.Instance, error) {
   189  	m.allInstancesCalled = true
   190  	return nil, m.err
   191  }
   192  
   193  func (s *serverSuite) assertCheckProviderAPI(c *gc.C, envError error, expectErr string) {
   194  	env := &mockEnviron{err: envError}
   195  	s.PatchValue(client.GetEnvironment, func(cfg *config.Config) (environs.Environ, error) {
   196  		return env, nil
   197  	})
   198  	args := params.SetModelAgentVersion{
   199  		Version: version.MustParse("9.8.7"),
   200  	}
   201  	err := s.client.SetModelAgentVersion(args)
   202  	c.Assert(env.allInstancesCalled, jc.IsTrue)
   203  	if expectErr != "" {
   204  		c.Assert(err, gc.ErrorMatches, expectErr)
   205  	} else {
   206  		c.Assert(err, jc.ErrorIsNil)
   207  	}
   208  }
   209  
   210  func (s *serverSuite) TestCheckProviderAPISuccess(c *gc.C) {
   211  	s.assertCheckProviderAPI(c, nil, "")
   212  	s.assertCheckProviderAPI(c, environs.ErrPartialInstances, "")
   213  	s.assertCheckProviderAPI(c, environs.ErrNoInstances, "")
   214  }
   215  
   216  func (s *serverSuite) TestCheckProviderAPIFail(c *gc.C) {
   217  	s.assertCheckProviderAPI(c, fmt.Errorf("instances error"), "cannot make API call to provider: instances error")
   218  }
   219  
   220  func (s *serverSuite) assertSetEnvironAgentVersion(c *gc.C) {
   221  	args := params.SetModelAgentVersion{
   222  		Version: version.MustParse("9.8.7"),
   223  	}
   224  	err := s.client.SetModelAgentVersion(args)
   225  	c.Assert(err, jc.ErrorIsNil)
   226  	envConfig, err := s.State.ModelConfig()
   227  	c.Assert(err, jc.ErrorIsNil)
   228  	agentVersion, found := envConfig.AllAttrs()["agent-version"]
   229  	c.Assert(found, jc.IsTrue)
   230  	c.Assert(agentVersion, gc.Equals, "9.8.7")
   231  }
   232  
   233  func (s *serverSuite) assertSetEnvironAgentVersionBlocked(c *gc.C, msg string) {
   234  	args := params.SetModelAgentVersion{
   235  		Version: version.MustParse("9.8.7"),
   236  	}
   237  	err := s.client.SetModelAgentVersion(args)
   238  	s.AssertBlocked(c, err, msg)
   239  }
   240  
   241  func (s *serverSuite) TestBlockDestroySetEnvironAgentVersion(c *gc.C) {
   242  	s.BlockDestroyModel(c, "TestBlockDestroySetEnvironAgentVersion")
   243  	s.assertSetEnvironAgentVersion(c)
   244  }
   245  
   246  func (s *serverSuite) TestBlockRemoveSetEnvironAgentVersion(c *gc.C) {
   247  	s.BlockRemoveObject(c, "TestBlockRemoveSetEnvironAgentVersion")
   248  	s.assertSetEnvironAgentVersion(c)
   249  }
   250  
   251  func (s *serverSuite) TestBlockChangesSetEnvironAgentVersion(c *gc.C) {
   252  	s.BlockAllChanges(c, "TestBlockChangesSetEnvironAgentVersion")
   253  	s.assertSetEnvironAgentVersionBlocked(c, "TestBlockChangesSetEnvironAgentVersion")
   254  }
   255  
   256  func (s *serverSuite) TestAbortCurrentUpgrade(c *gc.C) {
   257  	// Create a provisioned controller.
   258  	machine, err := s.State.AddMachine("series", state.JobManageModel)
   259  	c.Assert(err, jc.ErrorIsNil)
   260  	err = machine.SetProvisioned(instance.Id("i-blah"), "fake-nonce", nil)
   261  	c.Assert(err, jc.ErrorIsNil)
   262  
   263  	// Start an upgrade.
   264  	_, err = s.State.EnsureUpgradeInfo(
   265  		machine.Id(),
   266  		version.MustParse("1.2.3"),
   267  		version.MustParse("9.8.7"),
   268  	)
   269  	c.Assert(err, jc.ErrorIsNil)
   270  	isUpgrading, err := s.State.IsUpgrading()
   271  	c.Assert(err, jc.ErrorIsNil)
   272  	c.Assert(isUpgrading, jc.IsTrue)
   273  
   274  	// Abort it.
   275  	err = s.client.AbortCurrentUpgrade()
   276  	c.Assert(err, jc.ErrorIsNil)
   277  
   278  	isUpgrading, err = s.State.IsUpgrading()
   279  	c.Assert(err, jc.ErrorIsNil)
   280  	c.Assert(isUpgrading, jc.IsFalse)
   281  }
   282  
   283  func (s *serverSuite) assertAbortCurrentUpgradeBlocked(c *gc.C, msg string) {
   284  	err := s.client.AbortCurrentUpgrade()
   285  	s.AssertBlocked(c, err, msg)
   286  }
   287  
   288  func (s *serverSuite) assertAbortCurrentUpgrade(c *gc.C) {
   289  	err := s.client.AbortCurrentUpgrade()
   290  	c.Assert(err, jc.ErrorIsNil)
   291  	isUpgrading, err := s.State.IsUpgrading()
   292  	c.Assert(err, jc.ErrorIsNil)
   293  	c.Assert(isUpgrading, jc.IsFalse)
   294  }
   295  
   296  func (s *serverSuite) setupAbortCurrentUpgradeBlocked(c *gc.C) {
   297  	// Create a provisioned controller.
   298  	machine, err := s.State.AddMachine("series", state.JobManageModel)
   299  	c.Assert(err, jc.ErrorIsNil)
   300  	err = machine.SetProvisioned(instance.Id("i-blah"), "fake-nonce", nil)
   301  	c.Assert(err, jc.ErrorIsNil)
   302  
   303  	// Start an upgrade.
   304  	_, err = s.State.EnsureUpgradeInfo(
   305  		machine.Id(),
   306  		version.MustParse("1.2.3"),
   307  		version.MustParse("9.8.7"),
   308  	)
   309  	c.Assert(err, jc.ErrorIsNil)
   310  	isUpgrading, err := s.State.IsUpgrading()
   311  	c.Assert(err, jc.ErrorIsNil)
   312  	c.Assert(isUpgrading, jc.IsTrue)
   313  }
   314  
   315  func (s *serverSuite) TestBlockDestroyAbortCurrentUpgrade(c *gc.C) {
   316  	s.setupAbortCurrentUpgradeBlocked(c)
   317  	s.BlockDestroyModel(c, "TestBlockDestroyAbortCurrentUpgrade")
   318  	s.assertAbortCurrentUpgrade(c)
   319  }
   320  
   321  func (s *serverSuite) TestBlockRemoveAbortCurrentUpgrade(c *gc.C) {
   322  	s.setupAbortCurrentUpgradeBlocked(c)
   323  	s.BlockRemoveObject(c, "TestBlockRemoveAbortCurrentUpgrade")
   324  	s.assertAbortCurrentUpgrade(c)
   325  }
   326  
   327  func (s *serverSuite) TestBlockChangesAbortCurrentUpgrade(c *gc.C) {
   328  	s.setupAbortCurrentUpgradeBlocked(c)
   329  	s.BlockAllChanges(c, "TestBlockChangesAbortCurrentUpgrade")
   330  	s.assertAbortCurrentUpgradeBlocked(c, "TestBlockChangesAbortCurrentUpgrade")
   331  }
   332  
   333  type clientSuite struct {
   334  	baseSuite
   335  }
   336  
   337  var _ = gc.Suite(&clientSuite{})
   338  
   339  // clearSinceTimes zeros out the updated timestamps inside status
   340  // so we can easily check the results.
   341  func clearSinceTimes(status *params.FullStatus) {
   342  	for serviceId, service := range status.Services {
   343  		for unitId, unit := range service.Units {
   344  			unit.WorkloadStatus.Since = nil
   345  			unit.AgentStatus.Since = nil
   346  			for id, subord := range unit.Subordinates {
   347  				subord.WorkloadStatus.Since = nil
   348  				subord.AgentStatus.Since = nil
   349  				unit.Subordinates[id] = subord
   350  			}
   351  			service.Units[unitId] = unit
   352  		}
   353  		service.Status.Since = nil
   354  		status.Services[serviceId] = service
   355  	}
   356  	for id, machine := range status.Machines {
   357  		machine.AgentStatus.Since = nil
   358  		machine.InstanceStatus.Since = nil
   359  		status.Machines[id] = machine
   360  	}
   361  }
   362  
   363  func (s *clientSuite) TestClientStatus(c *gc.C) {
   364  	s.setUpScenario(c)
   365  	status, err := s.APIState.Client().Status(nil)
   366  	clearSinceTimes(status)
   367  	c.Assert(err, jc.ErrorIsNil)
   368  	c.Assert(status, jc.DeepEquals, scenarioStatus)
   369  }
   370  
   371  func (s *clientSuite) TestClientCharmInfo(c *gc.C) {
   372  	var clientCharmInfoTests = []struct {
   373  		about           string
   374  		charm           string
   375  		url             string
   376  		expectedActions *charm.Actions
   377  		err             string
   378  	}{
   379  		{
   380  			about: "dummy charm which contains an expectedActions spec",
   381  			charm: "dummy",
   382  			url:   "local:quantal/dummy-1",
   383  			expectedActions: &charm.Actions{
   384  				ActionSpecs: map[string]charm.ActionSpec{
   385  					"snapshot": {
   386  						Description: "Take a snapshot of the database.",
   387  						Params: map[string]interface{}{
   388  							"type":        "object",
   389  							"title":       "snapshot",
   390  							"description": "Take a snapshot of the database.",
   391  							"properties": map[string]interface{}{
   392  								"outfile": map[string]interface{}{
   393  									"default":     "foo.bz2",
   394  									"description": "The file to write out to.",
   395  									"type":        "string",
   396  								},
   397  							},
   398  						},
   399  					},
   400  				},
   401  			},
   402  		},
   403  		{
   404  			about: "retrieves charm info",
   405  			// Use wordpress for tests so that we can compare Provides and Requires.
   406  			charm: "wordpress",
   407  			expectedActions: &charm.Actions{ActionSpecs: map[string]charm.ActionSpec{
   408  				"fakeaction": {
   409  					Description: "No description",
   410  					Params: map[string]interface{}{
   411  						"type":        "object",
   412  						"title":       "fakeaction",
   413  						"description": "No description",
   414  						"properties":  map[string]interface{}{},
   415  					},
   416  				},
   417  			}},
   418  			url: "local:quantal/wordpress-3",
   419  		},
   420  		{
   421  			about: "invalid URL",
   422  			charm: "wordpress",
   423  			url:   "not-valid!",
   424  			err:   `URL has invalid charm or bundle name: "not-valid!"`,
   425  		},
   426  		{
   427  			about: "invalid schema",
   428  			charm: "wordpress",
   429  			url:   "not-valid:your-arguments",
   430  			err:   `charm or bundle URL has invalid schema: "not-valid:your-arguments"`,
   431  		},
   432  		{
   433  			about: "unknown charm",
   434  			charm: "wordpress",
   435  			url:   "cs:missing/one-1",
   436  			err:   `charm "cs:missing/one-1" not found \(not found\)`,
   437  		},
   438  	}
   439  
   440  	for i, t := range clientCharmInfoTests {
   441  		c.Logf("test %d. %s", i, t.about)
   442  		charm := s.AddTestingCharm(c, t.charm)
   443  		info, err := s.APIState.Client().CharmInfo(t.url)
   444  		if t.err != "" {
   445  			c.Check(err, gc.ErrorMatches, t.err)
   446  			continue
   447  		}
   448  		c.Assert(err, jc.ErrorIsNil)
   449  		expected := &api.CharmInfo{
   450  			Revision: charm.Revision(),
   451  			URL:      charm.URL().String(),
   452  			Config:   charm.Config(),
   453  			Meta:     charm.Meta(),
   454  			Actions:  charm.Actions(),
   455  		}
   456  		c.Check(info, jc.DeepEquals, expected)
   457  		c.Check(info.Actions, jc.DeepEquals, t.expectedActions)
   458  	}
   459  }
   460  
   461  func (s *clientSuite) TestClientModelInfo(c *gc.C) {
   462  	conf, _ := s.State.ModelConfig()
   463  	info, err := s.APIState.Client().ModelInfo()
   464  	c.Assert(err, jc.ErrorIsNil)
   465  	env, err := s.State.Model()
   466  	c.Assert(err, jc.ErrorIsNil)
   467  	c.Assert(info.DefaultSeries, gc.Equals, config.PreferredSeries(conf))
   468  	c.Assert(info.ProviderType, gc.Equals, conf.Type())
   469  	c.Assert(info.Name, gc.Equals, conf.Name())
   470  	c.Assert(info.UUID, gc.Equals, env.UUID())
   471  	c.Assert(info.ControllerUUID, gc.Equals, env.ControllerUUID())
   472  }
   473  
   474  func assertLife(c *gc.C, entity state.Living, life state.Life) {
   475  	err := entity.Refresh()
   476  	c.Assert(err, jc.ErrorIsNil)
   477  	c.Assert(entity.Life(), gc.Equals, life)
   478  }
   479  
   480  func assertRemoved(c *gc.C, entity state.Living) {
   481  	err := entity.Refresh()
   482  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   483  }
   484  
   485  func assertKill(c *gc.C, killer Killer) {
   486  	c.Assert(killer.Kill(), gc.IsNil)
   487  }
   488  
   489  func (s *clientSuite) setupDestroyMachinesTest(c *gc.C) (*state.Machine, *state.Machine, *state.Machine, *state.Unit) {
   490  	m0, err := s.State.AddMachine("quantal", state.JobManageModel)
   491  	c.Assert(err, jc.ErrorIsNil)
   492  	m1, err := s.State.AddMachine("quantal", state.JobHostUnits)
   493  	c.Assert(err, jc.ErrorIsNil)
   494  	m2, err := s.State.AddMachine("quantal", state.JobHostUnits)
   495  	c.Assert(err, jc.ErrorIsNil)
   496  
   497  	sch := s.AddTestingCharm(c, "wordpress")
   498  	wordpress := s.AddTestingService(c, "wordpress", sch)
   499  	u, err := wordpress.AddUnit()
   500  	c.Assert(err, jc.ErrorIsNil)
   501  	err = u.AssignToMachine(m1)
   502  	c.Assert(err, jc.ErrorIsNil)
   503  
   504  	return m0, m1, m2, u
   505  }
   506  
   507  func (s *clientSuite) TestDestroyMachines(c *gc.C) {
   508  	m0, m1, m2, u := s.setupDestroyMachinesTest(c)
   509  	s.assertDestroyMachineSuccess(c, u, m0, m1, m2)
   510  }
   511  
   512  func (s *clientSuite) TestForceDestroyMachines(c *gc.C) {
   513  	s.assertForceDestroyMachines(c)
   514  }
   515  
   516  func (s *clientSuite) testClientUnitResolved(c *gc.C, retry bool, expectedResolvedMode state.ResolvedMode) {
   517  	// Setup:
   518  	s.setUpScenario(c)
   519  	u, err := s.State.Unit("wordpress/0")
   520  	c.Assert(err, jc.ErrorIsNil)
   521  	err = u.SetAgentStatus(status.StatusError, "gaaah", nil)
   522  	c.Assert(err, jc.ErrorIsNil)
   523  	// Code under test:
   524  	err = s.APIState.Client().Resolved("wordpress/0", retry)
   525  	c.Assert(err, jc.ErrorIsNil)
   526  	// Freshen the unit's state.
   527  	err = u.Refresh()
   528  	c.Assert(err, jc.ErrorIsNil)
   529  	// And now the actual test assertions: we set the unit as resolved via
   530  	// the API so it should have a resolved mode set.
   531  	mode := u.Resolved()
   532  	c.Assert(mode, gc.Equals, expectedResolvedMode)
   533  }
   534  
   535  func (s *clientSuite) TestClientUnitResolved(c *gc.C) {
   536  	s.testClientUnitResolved(c, false, state.ResolvedNoHooks)
   537  }
   538  
   539  func (s *clientSuite) TestClientUnitResolvedRetry(c *gc.C) {
   540  	s.testClientUnitResolved(c, true, state.ResolvedRetryHooks)
   541  }
   542  
   543  func (s *clientSuite) setupResolved(c *gc.C) *state.Unit {
   544  	s.setUpScenario(c)
   545  	u, err := s.State.Unit("wordpress/0")
   546  	c.Assert(err, jc.ErrorIsNil)
   547  	err = u.SetAgentStatus(status.StatusError, "gaaah", nil)
   548  	c.Assert(err, jc.ErrorIsNil)
   549  	return u
   550  }
   551  
   552  func (s *clientSuite) assertResolved(c *gc.C, u *state.Unit) {
   553  	err := s.APIState.Client().Resolved("wordpress/0", true)
   554  	c.Assert(err, jc.ErrorIsNil)
   555  	// Freshen the unit's state.
   556  	err = u.Refresh()
   557  	c.Assert(err, jc.ErrorIsNil)
   558  	// And now the actual test assertions: we set the unit as resolved via
   559  	// the API so it should have a resolved mode set.
   560  	mode := u.Resolved()
   561  	c.Assert(mode, gc.Equals, state.ResolvedRetryHooks)
   562  }
   563  
   564  func (s *clientSuite) assertResolvedBlocked(c *gc.C, u *state.Unit, msg string) {
   565  	err := s.APIState.Client().Resolved("wordpress/0", true)
   566  	s.AssertBlocked(c, err, msg)
   567  }
   568  
   569  func (s *clientSuite) TestBlockDestroyUnitResolved(c *gc.C) {
   570  	u := s.setupResolved(c)
   571  	s.BlockDestroyModel(c, "TestBlockDestroyUnitResolved")
   572  	s.assertResolved(c, u)
   573  }
   574  
   575  func (s *clientSuite) TestBlockRemoveUnitResolved(c *gc.C) {
   576  	u := s.setupResolved(c)
   577  	s.BlockRemoveObject(c, "TestBlockRemoveUnitResolved")
   578  	s.assertResolved(c, u)
   579  }
   580  
   581  func (s *clientSuite) TestBlockChangeUnitResolved(c *gc.C) {
   582  	u := s.setupResolved(c)
   583  	s.BlockAllChanges(c, "TestBlockChangeUnitResolved")
   584  	s.assertResolvedBlocked(c, u, "TestBlockChangeUnitResolved")
   585  }
   586  
   587  type clientRepoSuite struct {
   588  	baseSuite
   589  	testing.CharmStoreSuite
   590  }
   591  
   592  var _ = gc.Suite(&clientRepoSuite{})
   593  
   594  func (s *clientRepoSuite) SetUpSuite(c *gc.C) {
   595  	s.CharmStoreSuite.SetUpSuite(c)
   596  	s.baseSuite.SetUpSuite(c)
   597  
   598  }
   599  
   600  func (s *clientRepoSuite) TearDownSuite(c *gc.C) {
   601  	s.CharmStoreSuite.TearDownSuite(c)
   602  	s.baseSuite.TearDownSuite(c)
   603  }
   604  
   605  func (s *clientRepoSuite) SetUpTest(c *gc.C) {
   606  	s.baseSuite.SetUpTest(c)
   607  	s.CharmStoreSuite.Session = s.baseSuite.Session
   608  	s.CharmStoreSuite.SetUpTest(c)
   609  
   610  	c.Assert(s.APIState, gc.NotNil)
   611  }
   612  
   613  func (s *clientRepoSuite) TearDownTest(c *gc.C) {
   614  	s.CharmStoreSuite.TearDownTest(c)
   615  	s.baseSuite.TearDownTest(c)
   616  }
   617  
   618  func (s *clientSuite) TestClientWatchAll(c *gc.C) {
   619  	// A very simple end-to-end test, because
   620  	// all the logic is tested elsewhere.
   621  	m, err := s.State.AddMachine("quantal", state.JobManageModel)
   622  	c.Assert(err, jc.ErrorIsNil)
   623  	err = m.SetProvisioned("i-0", agent.BootstrapNonce, nil)
   624  	c.Assert(err, jc.ErrorIsNil)
   625  	watcher, err := s.APIState.Client().WatchAll()
   626  	c.Assert(err, jc.ErrorIsNil)
   627  	defer func() {
   628  		err := watcher.Stop()
   629  		c.Assert(err, jc.ErrorIsNil)
   630  	}()
   631  	deltas, err := watcher.Next()
   632  	c.Assert(err, jc.ErrorIsNil)
   633  	c.Assert(len(deltas), gc.Equals, 1)
   634  	d0, ok := deltas[0].Entity.(*multiwatcher.MachineInfo)
   635  	c.Assert(ok, jc.IsTrue)
   636  	d0.JujuStatus.Since = nil
   637  	d0.MachineStatus.Since = nil
   638  	if !c.Check(deltas, gc.DeepEquals, []multiwatcher.Delta{{
   639  		Entity: &multiwatcher.MachineInfo{
   640  			ModelUUID:  s.State.ModelUUID(),
   641  			Id:         m.Id(),
   642  			InstanceId: "i-0",
   643  			JujuStatus: multiwatcher.StatusInfo{
   644  				Current: status.StatusPending,
   645  				Data:    map[string]interface{}{},
   646  			},
   647  			MachineStatus: multiwatcher.StatusInfo{
   648  				Current: status.StatusPending,
   649  				Data:    map[string]interface{}{},
   650  			},
   651  			Life:                    multiwatcher.Life("alive"),
   652  			Series:                  "quantal",
   653  			Jobs:                    []multiwatcher.MachineJob{state.JobManageModel.ToParams()},
   654  			Addresses:               []network.Address{},
   655  			HardwareCharacteristics: &instance.HardwareCharacteristics{},
   656  			HasVote:                 false,
   657  			WantsVote:               true,
   658  		},
   659  	}}) {
   660  		c.Logf("got:")
   661  		for _, d := range deltas {
   662  			c.Logf("%#v\n", d.Entity)
   663  		}
   664  	}
   665  }
   666  
   667  func (s *clientSuite) TestClientSetModelConstraints(c *gc.C) {
   668  	// Set constraints for the model.
   669  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
   670  	c.Assert(err, jc.ErrorIsNil)
   671  	err = s.APIState.Client().SetModelConstraints(cons)
   672  	c.Assert(err, jc.ErrorIsNil)
   673  
   674  	// Ensure the constraints have been correctly updated.
   675  	obtained, err := s.State.ModelConstraints()
   676  	c.Assert(err, jc.ErrorIsNil)
   677  	c.Assert(obtained, gc.DeepEquals, cons)
   678  }
   679  
   680  func (s *clientSuite) assertSetModelConstraints(c *gc.C) {
   681  	// Set constraints for the model.
   682  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
   683  	c.Assert(err, jc.ErrorIsNil)
   684  	err = s.APIState.Client().SetModelConstraints(cons)
   685  	c.Assert(err, jc.ErrorIsNil)
   686  	// Ensure the constraints have been correctly updated.
   687  	obtained, err := s.State.ModelConstraints()
   688  	c.Assert(err, jc.ErrorIsNil)
   689  	c.Assert(obtained, gc.DeepEquals, cons)
   690  }
   691  
   692  func (s *clientSuite) assertSetModelConstraintsBlocked(c *gc.C, msg string) {
   693  	// Set constraints for the model.
   694  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
   695  	c.Assert(err, jc.ErrorIsNil)
   696  	err = s.APIState.Client().SetModelConstraints(cons)
   697  	s.AssertBlocked(c, err, msg)
   698  }
   699  
   700  func (s *clientSuite) TestBlockDestroyClientSetModelConstraints(c *gc.C) {
   701  	s.BlockDestroyModel(c, "TestBlockDestroyClientSetModelConstraints")
   702  	s.assertSetModelConstraints(c)
   703  }
   704  
   705  func (s *clientSuite) TestBlockRemoveClientSetModelConstraints(c *gc.C) {
   706  	s.BlockRemoveObject(c, "TestBlockRemoveClientSetModelConstraints")
   707  	s.assertSetModelConstraints(c)
   708  }
   709  
   710  func (s *clientSuite) TestBlockChangesClientSetModelConstraints(c *gc.C) {
   711  	s.BlockAllChanges(c, "TestBlockChangesClientSetModelConstraints")
   712  	s.assertSetModelConstraintsBlocked(c, "TestBlockChangesClientSetModelConstraints")
   713  }
   714  
   715  func (s *clientSuite) TestClientGetModelConstraints(c *gc.C) {
   716  	// Set constraints for the model.
   717  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
   718  	c.Assert(err, jc.ErrorIsNil)
   719  	err = s.State.SetModelConstraints(cons)
   720  	c.Assert(err, jc.ErrorIsNil)
   721  
   722  	// Check we can get the constraints.
   723  	obtained, err := s.APIState.Client().GetModelConstraints()
   724  	c.Assert(err, jc.ErrorIsNil)
   725  	c.Assert(obtained, gc.DeepEquals, cons)
   726  }
   727  
   728  func (s *clientSuite) TestClientPublicAddressErrors(c *gc.C) {
   729  	s.setUpScenario(c)
   730  	_, err := s.APIState.Client().PublicAddress("wordpress")
   731  	c.Assert(err, gc.ErrorMatches, `unknown unit or machine "wordpress"`)
   732  	_, err = s.APIState.Client().PublicAddress("0")
   733  	c.Assert(err, gc.ErrorMatches, `error fetching address for machine "0": public no address`)
   734  	_, err = s.APIState.Client().PublicAddress("wordpress/0")
   735  	c.Assert(err, gc.ErrorMatches, `error fetching address for unit "wordpress/0": public no address`)
   736  }
   737  
   738  func (s *clientSuite) TestClientPublicAddressMachine(c *gc.C) {
   739  	s.setUpScenario(c)
   740  	network.SetPreferIPv6(false)
   741  
   742  	// Internally, network.SelectPublicAddress is used; the "most public"
   743  	// address is returned.
   744  	m1, err := s.State.Machine("1")
   745  	c.Assert(err, jc.ErrorIsNil)
   746  	cloudLocalAddress := network.NewScopedAddress("cloudlocal", network.ScopeCloudLocal)
   747  	publicAddress := network.NewScopedAddress("public", network.ScopePublic)
   748  	err = m1.SetProviderAddresses(cloudLocalAddress)
   749  	c.Assert(err, jc.ErrorIsNil)
   750  	addr, err := s.APIState.Client().PublicAddress("1")
   751  	c.Assert(err, jc.ErrorIsNil)
   752  	c.Assert(addr, gc.Equals, "cloudlocal")
   753  	err = m1.SetProviderAddresses(cloudLocalAddress, publicAddress)
   754  	addr, err = s.APIState.Client().PublicAddress("1")
   755  	c.Assert(err, jc.ErrorIsNil)
   756  	c.Assert(addr, gc.Equals, "public")
   757  }
   758  
   759  func (s *clientSuite) TestClientPublicAddressUnit(c *gc.C) {
   760  	s.setUpScenario(c)
   761  
   762  	m1, err := s.State.Machine("1")
   763  	publicAddress := network.NewScopedAddress("public", network.ScopePublic)
   764  	err = m1.SetProviderAddresses(publicAddress)
   765  	c.Assert(err, jc.ErrorIsNil)
   766  	addr, err := s.APIState.Client().PublicAddress("wordpress/0")
   767  	c.Assert(err, jc.ErrorIsNil)
   768  	c.Assert(addr, gc.Equals, "public")
   769  }
   770  
   771  func (s *clientSuite) TestClientPrivateAddressErrors(c *gc.C) {
   772  	s.setUpScenario(c)
   773  	_, err := s.APIState.Client().PrivateAddress("wordpress")
   774  	c.Assert(err, gc.ErrorMatches, `unknown unit or machine "wordpress"`)
   775  	_, err = s.APIState.Client().PrivateAddress("0")
   776  	c.Assert(err, gc.ErrorMatches, `error fetching address for machine "0": private no address`)
   777  	_, err = s.APIState.Client().PrivateAddress("wordpress/0")
   778  	c.Assert(err, gc.ErrorMatches, `error fetching address for unit "wordpress/0": private no address`)
   779  }
   780  
   781  func (s *clientSuite) TestClientPrivateAddress(c *gc.C) {
   782  	s.setUpScenario(c)
   783  	network.SetPreferIPv6(false)
   784  
   785  	// Internally, network.SelectInternalAddress is used; the public
   786  	// address if no cloud-local one is available.
   787  	m1, err := s.State.Machine("1")
   788  	c.Assert(err, jc.ErrorIsNil)
   789  	cloudLocalAddress := network.NewScopedAddress("cloudlocal", network.ScopeCloudLocal)
   790  	publicAddress := network.NewScopedAddress("public", network.ScopePublic)
   791  	err = m1.SetProviderAddresses(publicAddress)
   792  	c.Assert(err, jc.ErrorIsNil)
   793  	addr, err := s.APIState.Client().PrivateAddress("1")
   794  	c.Assert(err, jc.ErrorIsNil)
   795  	c.Assert(addr, gc.Equals, "public")
   796  	err = m1.SetProviderAddresses(cloudLocalAddress, publicAddress)
   797  	addr, err = s.APIState.Client().PrivateAddress("1")
   798  	c.Assert(err, jc.ErrorIsNil)
   799  	c.Assert(addr, gc.Equals, "cloudlocal")
   800  }
   801  
   802  func (s *clientSuite) TestClientPrivateAddressUnit(c *gc.C) {
   803  	s.setUpScenario(c)
   804  
   805  	m1, err := s.State.Machine("1")
   806  	privateAddress := network.NewScopedAddress("private", network.ScopeCloudLocal)
   807  	err = m1.SetProviderAddresses(privateAddress)
   808  	c.Assert(err, jc.ErrorIsNil)
   809  	addr, err := s.APIState.Client().PrivateAddress("wordpress/0")
   810  	c.Assert(err, jc.ErrorIsNil)
   811  	c.Assert(addr, gc.Equals, "private")
   812  }
   813  
   814  func (s *serverSuite) TestClientModelGet(c *gc.C) {
   815  	envConfig, err := s.State.ModelConfig()
   816  	c.Assert(err, jc.ErrorIsNil)
   817  	result, err := s.client.ModelGet()
   818  	c.Assert(err, jc.ErrorIsNil)
   819  	c.Assert(result.Config, gc.DeepEquals, envConfig.AllAttrs())
   820  }
   821  
   822  func (s *serverSuite) assertEnvValue(c *gc.C, key string, expected interface{}) {
   823  	envConfig, err := s.State.ModelConfig()
   824  	c.Assert(err, jc.ErrorIsNil)
   825  	value, found := envConfig.AllAttrs()[key]
   826  	c.Assert(found, jc.IsTrue)
   827  	c.Assert(value, gc.Equals, expected)
   828  }
   829  
   830  func (s *serverSuite) assertEnvValueMissing(c *gc.C, key string) {
   831  	envConfig, err := s.State.ModelConfig()
   832  	c.Assert(err, jc.ErrorIsNil)
   833  	_, found := envConfig.AllAttrs()[key]
   834  	c.Assert(found, jc.IsFalse)
   835  }
   836  
   837  func (s *serverSuite) TestClientModelSet(c *gc.C) {
   838  	envConfig, err := s.State.ModelConfig()
   839  	c.Assert(err, jc.ErrorIsNil)
   840  	_, found := envConfig.AllAttrs()["some-key"]
   841  	c.Assert(found, jc.IsFalse)
   842  
   843  	params := params.ModelSet{
   844  		Config: map[string]interface{}{
   845  			"some-key":  "value",
   846  			"other-key": "other value"},
   847  	}
   848  	err = s.client.ModelSet(params)
   849  	c.Assert(err, jc.ErrorIsNil)
   850  	s.assertEnvValue(c, "some-key", "value")
   851  	s.assertEnvValue(c, "other-key", "other value")
   852  }
   853  
   854  func (s *serverSuite) TestClientModelSetImmutable(c *gc.C) {
   855  	// The various immutable config values are tested in
   856  	// environs/config/config_test.go, so just choosing one here.
   857  	params := params.ModelSet{
   858  		Config: map[string]interface{}{"state-port": "1"},
   859  	}
   860  	err := s.client.ModelSet(params)
   861  	c.Check(err, gc.ErrorMatches, `cannot change state-port from .* to 1`)
   862  }
   863  
   864  func (s *serverSuite) assertModelSetBlocked(c *gc.C, args map[string]interface{}, msg string) {
   865  	err := s.client.ModelSet(params.ModelSet{args})
   866  	s.AssertBlocked(c, err, msg)
   867  }
   868  
   869  func (s *serverSuite) TestBlockChangesClientModelSet(c *gc.C) {
   870  	s.BlockAllChanges(c, "TestBlockChangesClientModelSet")
   871  	args := map[string]interface{}{"some-key": "value"}
   872  	s.assertModelSetBlocked(c, args, "TestBlockChangesClientModelSet")
   873  }
   874  
   875  func (s *serverSuite) TestClientModelSetDeprecated(c *gc.C) {
   876  	envConfig, err := s.State.ModelConfig()
   877  	c.Assert(err, jc.ErrorIsNil)
   878  	url := envConfig.AllAttrs()["agent-metadata-url"]
   879  	c.Assert(url, gc.Equals, "")
   880  
   881  	args := params.ModelSet{
   882  		Config: map[string]interface{}{"tools-metadata-url": "value"},
   883  	}
   884  	err = s.client.ModelSet(args)
   885  	c.Assert(err, jc.ErrorIsNil)
   886  	s.assertEnvValue(c, "agent-metadata-url", "value")
   887  	s.assertEnvValue(c, "tools-metadata-url", "value")
   888  }
   889  
   890  func (s *serverSuite) TestClientModelSetCannotChangeAgentVersion(c *gc.C) {
   891  	args := params.ModelSet{
   892  		map[string]interface{}{"agent-version": "9.9.9"},
   893  	}
   894  	err := s.client.ModelSet(args)
   895  	c.Assert(err, gc.ErrorMatches, "agent-version cannot be changed")
   896  
   897  	// It's okay to pass env back with the same agent-version.
   898  	result, err := s.client.ModelGet()
   899  	c.Assert(err, jc.ErrorIsNil)
   900  	c.Assert(result.Config["agent-version"], gc.NotNil)
   901  	args.Config["agent-version"] = result.Config["agent-version"]
   902  	err = s.client.ModelSet(args)
   903  	c.Assert(err, jc.ErrorIsNil)
   904  }
   905  
   906  func (s *serverSuite) TestClientModelUnset(c *gc.C) {
   907  	err := s.State.UpdateModelConfig(map[string]interface{}{"abc": 123}, nil, nil)
   908  	c.Assert(err, jc.ErrorIsNil)
   909  
   910  	args := params.ModelUnset{[]string{"abc"}}
   911  	err = s.client.ModelUnset(args)
   912  	c.Assert(err, jc.ErrorIsNil)
   913  	s.assertEnvValueMissing(c, "abc")
   914  }
   915  
   916  func (s *serverSuite) TestBlockClientModelUnset(c *gc.C) {
   917  	err := s.State.UpdateModelConfig(map[string]interface{}{"abc": 123}, nil, nil)
   918  	c.Assert(err, jc.ErrorIsNil)
   919  	s.BlockAllChanges(c, "TestBlockClientModelUnset")
   920  
   921  	args := params.ModelUnset{[]string{"abc"}}
   922  	err = s.client.ModelUnset(args)
   923  	s.AssertBlocked(c, err, "TestBlockClientModelUnset")
   924  }
   925  
   926  func (s *serverSuite) TestClientModelUnsetMissing(c *gc.C) {
   927  	// It's okay to unset a non-existent attribute.
   928  	args := params.ModelUnset{[]string{"not_there"}}
   929  	err := s.client.ModelUnset(args)
   930  	c.Assert(err, jc.ErrorIsNil)
   931  }
   932  
   933  func (s *serverSuite) TestClientModelUnsetError(c *gc.C) {
   934  	err := s.State.UpdateModelConfig(map[string]interface{}{"abc": 123}, nil, nil)
   935  	c.Assert(err, jc.ErrorIsNil)
   936  
   937  	// "type" may not be removed, and this will cause an error.
   938  	// If any one attribute's removal causes an error, there
   939  	// should be no change.
   940  	args := params.ModelUnset{[]string{"abc", "type"}}
   941  	err = s.client.ModelUnset(args)
   942  	c.Assert(err, gc.ErrorMatches, "type: expected string, got nothing")
   943  	s.assertEnvValue(c, "abc", 123)
   944  }
   945  
   946  func (s *clientSuite) TestClientFindTools(c *gc.C) {
   947  	result, err := s.APIState.Client().FindTools(99, -1, "", "")
   948  	c.Assert(err, jc.ErrorIsNil)
   949  	c.Assert(result.Error, jc.Satisfies, params.IsCodeNotFound)
   950  	toolstesting.UploadToStorage(c, s.DefaultToolsStorage, "released", version.MustParseBinary("2.99.0-precise-amd64"))
   951  	result, err = s.APIState.Client().FindTools(2, 99, "precise", "amd64")
   952  	c.Assert(err, jc.ErrorIsNil)
   953  	c.Assert(result.Error, gc.IsNil)
   954  	c.Assert(result.List, gc.HasLen, 1)
   955  	c.Assert(result.List[0].Version, gc.Equals, version.MustParseBinary("2.99.0-precise-amd64"))
   956  	url := fmt.Sprintf("https://%s/model/%s/tools/%s",
   957  		s.APIState.Addr(), coretesting.ModelTag.Id(), result.List[0].Version)
   958  	c.Assert(result.List[0].URL, gc.Equals, url)
   959  }
   960  
   961  func (s *clientSuite) checkMachine(c *gc.C, id, series, cons string) {
   962  	// Ensure the machine was actually created.
   963  	machine, err := s.BackingState.Machine(id)
   964  	c.Assert(err, jc.ErrorIsNil)
   965  	c.Assert(machine.Series(), gc.Equals, series)
   966  	c.Assert(machine.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobHostUnits})
   967  	machineConstraints, err := machine.Constraints()
   968  	c.Assert(err, jc.ErrorIsNil)
   969  	c.Assert(machineConstraints.String(), gc.Equals, cons)
   970  }
   971  
   972  func (s *clientSuite) TestClientAddMachinesDefaultSeries(c *gc.C) {
   973  	apiParams := make([]params.AddMachineParams, 3)
   974  	for i := 0; i < 3; i++ {
   975  		apiParams[i] = params.AddMachineParams{
   976  			Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
   977  		}
   978  	}
   979  	machines, err := s.APIState.Client().AddMachines(apiParams)
   980  	c.Assert(err, jc.ErrorIsNil)
   981  	c.Assert(len(machines), gc.Equals, 3)
   982  	for i, machineResult := range machines {
   983  		c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
   984  		s.checkMachine(c, machineResult.Machine, series.LatestLts(), apiParams[i].Constraints.String())
   985  	}
   986  }
   987  
   988  func (s *clientSuite) assertAddMachines(c *gc.C) {
   989  	apiParams := make([]params.AddMachineParams, 3)
   990  	for i := 0; i < 3; i++ {
   991  		apiParams[i] = params.AddMachineParams{
   992  			Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
   993  		}
   994  	}
   995  	machines, err := s.APIState.Client().AddMachines(apiParams)
   996  	c.Assert(err, jc.ErrorIsNil)
   997  	c.Assert(len(machines), gc.Equals, 3)
   998  	for i, machineResult := range machines {
   999  		c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
  1000  		s.checkMachine(c, machineResult.Machine, series.LatestLts(), apiParams[i].Constraints.String())
  1001  	}
  1002  }
  1003  
  1004  func (s *clientSuite) assertAddMachinesBlocked(c *gc.C, msg string) {
  1005  	apiParams := make([]params.AddMachineParams, 3)
  1006  	for i := 0; i < 3; i++ {
  1007  		apiParams[i] = params.AddMachineParams{
  1008  			Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  1009  		}
  1010  	}
  1011  	_, err := s.APIState.Client().AddMachines(apiParams)
  1012  	s.AssertBlocked(c, err, msg)
  1013  }
  1014  
  1015  func (s *clientSuite) TestBlockDestroyClientAddMachinesDefaultSeries(c *gc.C) {
  1016  	s.BlockDestroyModel(c, "TestBlockDestroyClientAddMachinesDefaultSeries")
  1017  	s.assertAddMachines(c)
  1018  }
  1019  
  1020  func (s *clientSuite) TestBlockRemoveClientAddMachinesDefaultSeries(c *gc.C) {
  1021  	s.BlockRemoveObject(c, "TestBlockRemoveClientAddMachinesDefaultSeries")
  1022  	s.assertAddMachines(c)
  1023  }
  1024  
  1025  func (s *clientSuite) TestBlockChangesClientAddMachines(c *gc.C) {
  1026  	s.BlockAllChanges(c, "TestBlockChangesClientAddMachines")
  1027  	s.assertAddMachinesBlocked(c, "TestBlockChangesClientAddMachines")
  1028  }
  1029  
  1030  func (s *clientSuite) TestClientAddMachinesWithSeries(c *gc.C) {
  1031  	apiParams := make([]params.AddMachineParams, 3)
  1032  	for i := 0; i < 3; i++ {
  1033  		apiParams[i] = params.AddMachineParams{
  1034  			Series: "quantal",
  1035  			Jobs:   []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  1036  		}
  1037  	}
  1038  	machines, err := s.APIState.Client().AddMachines(apiParams)
  1039  	c.Assert(err, jc.ErrorIsNil)
  1040  	c.Assert(len(machines), gc.Equals, 3)
  1041  	for i, machineResult := range machines {
  1042  		c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
  1043  		s.checkMachine(c, machineResult.Machine, "quantal", apiParams[i].Constraints.String())
  1044  	}
  1045  }
  1046  
  1047  func (s *clientSuite) TestClientAddMachineInsideMachine(c *gc.C) {
  1048  	_, err := s.State.AddMachine("quantal", state.JobHostUnits)
  1049  	c.Assert(err, jc.ErrorIsNil)
  1050  
  1051  	machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{{
  1052  		Jobs:          []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  1053  		ContainerType: instance.LXC,
  1054  		ParentId:      "0",
  1055  		Series:        "quantal",
  1056  	}})
  1057  	c.Assert(err, jc.ErrorIsNil)
  1058  	c.Assert(machines, gc.HasLen, 1)
  1059  	c.Assert(machines[0].Machine, gc.Equals, "0/lxc/0")
  1060  }
  1061  
  1062  // updateConfig sets config variable with given key to a given value
  1063  // Asserts that no errors were encountered.
  1064  func (s *baseSuite) updateConfig(c *gc.C, key string, block bool) {
  1065  	err := s.State.UpdateModelConfig(map[string]interface{}{key: block}, nil, nil)
  1066  	c.Assert(err, jc.ErrorIsNil)
  1067  }
  1068  
  1069  func (s *clientSuite) TestClientAddMachinesWithConstraints(c *gc.C) {
  1070  	apiParams := make([]params.AddMachineParams, 3)
  1071  	for i := 0; i < 3; i++ {
  1072  		apiParams[i] = params.AddMachineParams{
  1073  			Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  1074  		}
  1075  	}
  1076  	// The last machine has some constraints.
  1077  	apiParams[2].Constraints = constraints.MustParse("mem=4G")
  1078  	machines, err := s.APIState.Client().AddMachines(apiParams)
  1079  	c.Assert(err, jc.ErrorIsNil)
  1080  	c.Assert(len(machines), gc.Equals, 3)
  1081  	for i, machineResult := range machines {
  1082  		c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
  1083  		s.checkMachine(c, machineResult.Machine, series.LatestLts(), apiParams[i].Constraints.String())
  1084  	}
  1085  }
  1086  
  1087  func (s *clientSuite) TestClientAddMachinesWithPlacement(c *gc.C) {
  1088  	apiParams := make([]params.AddMachineParams, 4)
  1089  	for i := range apiParams {
  1090  		apiParams[i] = params.AddMachineParams{
  1091  			Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  1092  		}
  1093  	}
  1094  	apiParams[0].Placement = instance.MustParsePlacement("lxc")
  1095  	apiParams[1].Placement = instance.MustParsePlacement("lxc:0")
  1096  	apiParams[1].ContainerType = instance.LXC
  1097  	apiParams[2].Placement = instance.MustParsePlacement("admin:invalid")
  1098  	apiParams[3].Placement = instance.MustParsePlacement("admin:valid")
  1099  	machines, err := s.APIState.Client().AddMachines(apiParams)
  1100  	c.Assert(err, jc.ErrorIsNil)
  1101  	c.Assert(len(machines), gc.Equals, 4)
  1102  	c.Assert(machines[0].Machine, gc.Equals, "0/lxc/0")
  1103  	c.Assert(machines[1].Error, gc.ErrorMatches, "container type and placement are mutually exclusive")
  1104  	c.Assert(machines[2].Error, gc.ErrorMatches, "cannot add a new machine: invalid placement is invalid")
  1105  	c.Assert(machines[3].Machine, gc.Equals, "1")
  1106  
  1107  	m, err := s.BackingState.Machine(machines[3].Machine)
  1108  	c.Assert(err, jc.ErrorIsNil)
  1109  	c.Assert(m.Placement(), gc.DeepEquals, apiParams[3].Placement.Directive)
  1110  }
  1111  
  1112  func (s *clientSuite) TestClientAddMachinesSomeErrors(c *gc.C) {
  1113  	// Here we check that adding a number of containers correctly handles the
  1114  	// case that some adds succeed and others fail and report the errors
  1115  	// accordingly.
  1116  	// We will set up params to the AddMachines API to attempt to create 3 machines.
  1117  	// Machines 0 and 1 will be added successfully.
  1118  	// Remaining machines will fail due to different reasons.
  1119  
  1120  	// Create a machine to host the requested containers.
  1121  	host, err := s.State.AddMachine("quantal", state.JobHostUnits)
  1122  	c.Assert(err, jc.ErrorIsNil)
  1123  	// The host only supports lxc containers.
  1124  	err = host.SetSupportedContainers([]instance.ContainerType{instance.LXC})
  1125  	c.Assert(err, jc.ErrorIsNil)
  1126  
  1127  	// Set up params for adding 3 containers.
  1128  	apiParams := make([]params.AddMachineParams, 3)
  1129  	for i := range apiParams {
  1130  		apiParams[i] = params.AddMachineParams{
  1131  			Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  1132  		}
  1133  	}
  1134  	// This will cause a add-machine to fail due to an unsupported container.
  1135  	apiParams[2].ContainerType = instance.KVM
  1136  	apiParams[2].ParentId = host.Id()
  1137  	machines, err := s.APIState.Client().AddMachines(apiParams)
  1138  	c.Assert(err, jc.ErrorIsNil)
  1139  	c.Assert(len(machines), gc.Equals, 3)
  1140  
  1141  	// Check the results - machines 2 and 3 will have errors.
  1142  	c.Check(machines[0].Machine, gc.Equals, "1")
  1143  	c.Check(machines[0].Error, gc.IsNil)
  1144  	c.Check(machines[1].Machine, gc.Equals, "2")
  1145  	c.Check(machines[1].Error, gc.IsNil)
  1146  	c.Check(machines[2].Error, gc.ErrorMatches, "cannot add a new machine: machine 0 cannot host kvm containers")
  1147  }
  1148  
  1149  func (s *clientSuite) TestClientAddMachinesWithInstanceIdSomeErrors(c *gc.C) {
  1150  	apiParams := make([]params.AddMachineParams, 3)
  1151  	addrs := network.NewAddresses("1.2.3.4")
  1152  	hc := instance.MustParseHardware("mem=4G")
  1153  	for i := 0; i < 3; i++ {
  1154  		apiParams[i] = params.AddMachineParams{
  1155  			Jobs:       []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  1156  			InstanceId: instance.Id(fmt.Sprintf("1234-%d", i)),
  1157  			Nonce:      "foo",
  1158  			HardwareCharacteristics: hc,
  1159  			Addrs: params.FromNetworkAddresses(addrs),
  1160  		}
  1161  	}
  1162  	// This will cause the last add-machine to fail.
  1163  	apiParams[2].Nonce = ""
  1164  	machines, err := s.APIState.Client().AddMachines(apiParams)
  1165  	c.Assert(err, jc.ErrorIsNil)
  1166  	c.Assert(len(machines), gc.Equals, 3)
  1167  	for i, machineResult := range machines {
  1168  		if i == 2 {
  1169  			c.Assert(machineResult.Error, gc.NotNil)
  1170  			c.Assert(machineResult.Error, gc.ErrorMatches, "cannot add a new machine: cannot add a machine with an instance id and no nonce")
  1171  		} else {
  1172  			c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
  1173  			s.checkMachine(c, machineResult.Machine, series.LatestLts(), apiParams[i].Constraints.String())
  1174  			instanceId := fmt.Sprintf("1234-%d", i)
  1175  			s.checkInstance(c, machineResult.Machine, instanceId, "foo", hc, addrs)
  1176  		}
  1177  	}
  1178  }
  1179  
  1180  func (s *clientSuite) checkInstance(c *gc.C, id, instanceId, nonce string,
  1181  	hc instance.HardwareCharacteristics, addr []network.Address) {
  1182  
  1183  	machine, err := s.BackingState.Machine(id)
  1184  	c.Assert(err, jc.ErrorIsNil)
  1185  	machineInstanceId, err := machine.InstanceId()
  1186  	c.Assert(err, jc.ErrorIsNil)
  1187  	c.Assert(machine.CheckProvisioned(nonce), jc.IsTrue)
  1188  	c.Assert(machineInstanceId, gc.Equals, instance.Id(instanceId))
  1189  	machineHardware, err := machine.HardwareCharacteristics()
  1190  	c.Assert(err, jc.ErrorIsNil)
  1191  	c.Assert(machineHardware.String(), gc.Equals, hc.String())
  1192  	c.Assert(machine.Addresses(), gc.DeepEquals, addr)
  1193  }
  1194  
  1195  func (s *clientSuite) TestInjectMachinesStillExists(c *gc.C) {
  1196  	results := new(params.AddMachinesResults)
  1197  	// We need to use Call directly because the client interface
  1198  	// no longer refers to InjectMachine.
  1199  	args := params.AddMachines{
  1200  		MachineParams: []params.AddMachineParams{{
  1201  			Jobs:       []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  1202  			InstanceId: "i-foo",
  1203  			Nonce:      "nonce",
  1204  		}},
  1205  	}
  1206  	err := s.APIState.APICall("Client", 1, "", "AddMachines", args, &results)
  1207  	c.Assert(err, jc.ErrorIsNil)
  1208  	c.Assert(results.Machines, gc.HasLen, 1)
  1209  }
  1210  
  1211  func (s *clientSuite) TestProvisioningScript(c *gc.C) {
  1212  	// Inject a machine and then call the ProvisioningScript API.
  1213  	// The result should be the same as when calling MachineConfig,
  1214  	// converting it to a cloudinit.MachineConfig, and disabling
  1215  	// apt_upgrade.
  1216  	apiParams := params.AddMachineParams{
  1217  		Jobs:       []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  1218  		InstanceId: instance.Id("1234"),
  1219  		Nonce:      "foo",
  1220  		HardwareCharacteristics: instance.MustParseHardware("arch=amd64"),
  1221  	}
  1222  	machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{apiParams})
  1223  	c.Assert(err, jc.ErrorIsNil)
  1224  	c.Assert(len(machines), gc.Equals, 1)
  1225  	machineId := machines[0].Machine
  1226  	// Call ProvisioningScript. Normally ProvisioningScript and
  1227  	// MachineConfig are mutually exclusive; both of them will
  1228  	// allocate a api password for the machine agent.
  1229  	script, err := s.APIState.Client().ProvisioningScript(params.ProvisioningScriptParams{
  1230  		MachineId: machineId,
  1231  		Nonce:     apiParams.Nonce,
  1232  	})
  1233  	c.Assert(err, jc.ErrorIsNil)
  1234  	icfg, err := client.InstanceConfig(s.State, machineId, apiParams.Nonce, "")
  1235  	c.Assert(err, jc.ErrorIsNil)
  1236  	provisioningScript, err := manual.ProvisioningScript(icfg)
  1237  	c.Assert(err, jc.ErrorIsNil)
  1238  	// ProvisioningScript internally calls MachineConfig,
  1239  	// which allocates a new, random password. Everything
  1240  	// about the scripts should be the same other than
  1241  	// the line containing "oldpassword" from agent.conf.
  1242  	scriptLines := strings.Split(script, "\n")
  1243  	provisioningScriptLines := strings.Split(provisioningScript, "\n")
  1244  	c.Assert(scriptLines, gc.HasLen, len(provisioningScriptLines))
  1245  	for i, line := range scriptLines {
  1246  		if strings.Contains(line, "oldpassword") {
  1247  			continue
  1248  		}
  1249  		c.Assert(line, gc.Equals, provisioningScriptLines[i])
  1250  	}
  1251  }
  1252  
  1253  func (s *clientSuite) TestProvisioningScriptDisablePackageCommands(c *gc.C) {
  1254  	apiParams := params.AddMachineParams{
  1255  		Jobs:       []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  1256  		InstanceId: instance.Id("1234"),
  1257  		Nonce:      "foo",
  1258  		HardwareCharacteristics: instance.MustParseHardware("arch=amd64"),
  1259  	}
  1260  	machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{apiParams})
  1261  	c.Assert(err, jc.ErrorIsNil)
  1262  	c.Assert(len(machines), gc.Equals, 1)
  1263  	machineId := machines[0].Machine
  1264  
  1265  	provParams := params.ProvisioningScriptParams{
  1266  		MachineId: machineId,
  1267  		Nonce:     apiParams.Nonce,
  1268  	}
  1269  
  1270  	setUpdateBehavior := func(update, upgrade bool) {
  1271  		s.State.UpdateModelConfig(
  1272  			map[string]interface{}{
  1273  				"enable-os-upgrade":        upgrade,
  1274  				"enable-os-refresh-update": update,
  1275  			},
  1276  			nil,
  1277  			nil,
  1278  		)
  1279  	}
  1280  
  1281  	// Test enabling package commands
  1282  	provParams.DisablePackageCommands = false
  1283  	setUpdateBehavior(true, true)
  1284  	script, err := s.APIState.Client().ProvisioningScript(provParams)
  1285  	c.Assert(err, jc.ErrorIsNil)
  1286  	c.Check(script, jc.Contains, "apt-get update")
  1287  	c.Check(script, jc.Contains, "apt-get upgrade")
  1288  
  1289  	// Test disabling package commands
  1290  	provParams.DisablePackageCommands = true
  1291  	setUpdateBehavior(false, false)
  1292  	script, err = s.APIState.Client().ProvisioningScript(provParams)
  1293  	c.Assert(err, jc.ErrorIsNil)
  1294  	c.Check(script, gc.Not(jc.Contains), "apt-get update")
  1295  	c.Check(script, gc.Not(jc.Contains), "apt-get upgrade")
  1296  
  1297  	// Test client-specified DisablePackageCommands trumps environment
  1298  	// config variables.
  1299  	provParams.DisablePackageCommands = true
  1300  	setUpdateBehavior(true, true)
  1301  	script, err = s.APIState.Client().ProvisioningScript(provParams)
  1302  	c.Assert(err, jc.ErrorIsNil)
  1303  	c.Check(script, gc.Not(jc.Contains), "apt-get update")
  1304  	c.Check(script, gc.Not(jc.Contains), "apt-get upgrade")
  1305  
  1306  	// Test that in the abasence of a client-specified
  1307  	// DisablePackageCommands we use what's set in environment config.
  1308  	provParams.DisablePackageCommands = false
  1309  	setUpdateBehavior(false, false)
  1310  	//provParams.UpdateBehavior = &params.UpdateBehavior{false, false}
  1311  	script, err = s.APIState.Client().ProvisioningScript(provParams)
  1312  	c.Assert(err, jc.ErrorIsNil)
  1313  	c.Check(script, gc.Not(jc.Contains), "apt-get update")
  1314  	c.Check(script, gc.Not(jc.Contains), "apt-get upgrade")
  1315  }
  1316  
  1317  var resolveCharmTests = []struct {
  1318  	about      string
  1319  	url        string
  1320  	resolved   string
  1321  	parseErr   string
  1322  	resolveErr string
  1323  }{{
  1324  	about:    "wordpress resolved",
  1325  	url:      "cs:wordpress",
  1326  	resolved: "cs:trusty/wordpress",
  1327  }, {
  1328  	about:    "mysql resolved",
  1329  	url:      "cs:mysql",
  1330  	resolved: "cs:precise/mysql",
  1331  }, {
  1332  	about:    "riak resolved",
  1333  	url:      "cs:riak",
  1334  	resolved: "cs:trusty/riak",
  1335  }, {
  1336  	about:    "fully qualified char reference",
  1337  	url:      "cs:utopic/riak-5",
  1338  	resolved: "cs:utopic/riak-5",
  1339  }, {
  1340  	about:    "charm with series and no revision",
  1341  	url:      "cs:precise/wordpress",
  1342  	resolved: "cs:precise/wordpress",
  1343  }, {
  1344  	about:      "fully qualified reference not found",
  1345  	url:        "cs:utopic/riak-42",
  1346  	resolveErr: `cannot resolve URL "cs:utopic/riak-42": charm not found`,
  1347  }, {
  1348  	about:      "reference not found",
  1349  	url:        "cs:no-such",
  1350  	resolveErr: `cannot resolve URL "cs:no-such": charm or bundle not found`,
  1351  }, {
  1352  	about:    "invalid charm name",
  1353  	url:      "cs:",
  1354  	parseErr: `URL has invalid charm or bundle name: "cs:"`,
  1355  }, {
  1356  	about:      "local charm",
  1357  	url:        "local:wordpress",
  1358  	resolveErr: `only charm store charm references are supported, with cs: schema`,
  1359  }}
  1360  
  1361  func (s *clientRepoSuite) TestResolveCharm(c *gc.C) {
  1362  	// Add some charms to be resolved later.
  1363  	for _, url := range []string{
  1364  		"precise/wordpress-1",
  1365  		"trusty/wordpress-2",
  1366  		"precise/mysql-3",
  1367  		"trusty/riak-4",
  1368  		"utopic/riak-5",
  1369  	} {
  1370  		s.UploadCharm(c, url, "wordpress")
  1371  	}
  1372  
  1373  	// Run the tests.
  1374  	for i, test := range resolveCharmTests {
  1375  		c.Logf("test %d: %s", i, test.about)
  1376  
  1377  		client := s.APIState.Client()
  1378  		ref, err := charm.ParseURL(test.url)
  1379  		if test.parseErr == "" {
  1380  			if !c.Check(err, jc.ErrorIsNil) {
  1381  				continue
  1382  			}
  1383  		} else {
  1384  			c.Assert(err, gc.NotNil)
  1385  			c.Check(err, gc.ErrorMatches, test.parseErr)
  1386  			continue
  1387  		}
  1388  
  1389  		curl, err := client.ResolveCharm(ref)
  1390  		if test.resolveErr == "" {
  1391  			c.Assert(err, jc.ErrorIsNil)
  1392  			c.Check(curl.String(), gc.Equals, test.resolved)
  1393  			continue
  1394  		}
  1395  		c.Check(err, gc.ErrorMatches, test.resolveErr)
  1396  		c.Check(curl, gc.IsNil)
  1397  	}
  1398  }
  1399  
  1400  func (s *clientSuite) TestRetryProvisioning(c *gc.C) {
  1401  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
  1402  	c.Assert(err, jc.ErrorIsNil)
  1403  	err = machine.SetStatus(status.StatusError, "error", nil)
  1404  	c.Assert(err, jc.ErrorIsNil)
  1405  	_, err = s.APIState.Client().RetryProvisioning(machine.Tag().(names.MachineTag))
  1406  	c.Assert(err, jc.ErrorIsNil)
  1407  
  1408  	statusInfo, err := machine.Status()
  1409  	c.Assert(err, jc.ErrorIsNil)
  1410  	c.Assert(statusInfo.Status, gc.Equals, status.StatusError)
  1411  	c.Assert(statusInfo.Message, gc.Equals, "error")
  1412  	c.Assert(statusInfo.Data["transient"], jc.IsTrue)
  1413  }
  1414  
  1415  func (s *clientSuite) setupRetryProvisioning(c *gc.C) *state.Machine {
  1416  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
  1417  	c.Assert(err, jc.ErrorIsNil)
  1418  	err = machine.SetStatus(status.StatusError, "error", nil)
  1419  	c.Assert(err, jc.ErrorIsNil)
  1420  	return machine
  1421  }
  1422  
  1423  func (s *clientSuite) assertRetryProvisioning(c *gc.C, machine *state.Machine) {
  1424  	_, err := s.APIState.Client().RetryProvisioning(machine.Tag().(names.MachineTag))
  1425  	c.Assert(err, jc.ErrorIsNil)
  1426  	statusInfo, err := machine.Status()
  1427  	c.Assert(err, jc.ErrorIsNil)
  1428  	c.Assert(statusInfo.Status, gc.Equals, status.StatusError)
  1429  	c.Assert(statusInfo.Message, gc.Equals, "error")
  1430  	c.Assert(statusInfo.Data["transient"], jc.IsTrue)
  1431  }
  1432  
  1433  func (s *clientSuite) assertRetryProvisioningBlocked(c *gc.C, machine *state.Machine, msg string) {
  1434  	_, err := s.APIState.Client().RetryProvisioning(machine.Tag().(names.MachineTag))
  1435  	s.AssertBlocked(c, err, msg)
  1436  }
  1437  
  1438  func (s *clientSuite) TestBlockDestroyRetryProvisioning(c *gc.C) {
  1439  	m := s.setupRetryProvisioning(c)
  1440  	s.BlockDestroyModel(c, "TestBlockDestroyRetryProvisioning")
  1441  	s.assertRetryProvisioning(c, m)
  1442  }
  1443  
  1444  func (s *clientSuite) TestBlockRemoveRetryProvisioning(c *gc.C) {
  1445  	m := s.setupRetryProvisioning(c)
  1446  	s.BlockRemoveObject(c, "TestBlockRemoveRetryProvisioning")
  1447  	s.assertRetryProvisioning(c, m)
  1448  }
  1449  
  1450  func (s *clientSuite) TestBlockChangesRetryProvisioning(c *gc.C) {
  1451  	m := s.setupRetryProvisioning(c)
  1452  	s.BlockAllChanges(c, "TestBlockChangesRetryProvisioning")
  1453  	s.assertRetryProvisioningBlocked(c, m, "TestBlockChangesRetryProvisioning")
  1454  }
  1455  
  1456  func (s *clientSuite) TestAPIHostPorts(c *gc.C) {
  1457  	server1Addresses := []network.Address{{
  1458  		Value: "server-1",
  1459  		Type:  network.HostName,
  1460  		Scope: network.ScopePublic,
  1461  	}, {
  1462  		Value: "10.0.0.1",
  1463  		Type:  network.IPv4Address,
  1464  		Scope: network.ScopeCloudLocal,
  1465  	}}
  1466  	server2Addresses := []network.Address{{
  1467  		Value: "::1",
  1468  		Type:  network.IPv6Address,
  1469  		Scope: network.ScopeMachineLocal,
  1470  	}}
  1471  	stateAPIHostPorts := [][]network.HostPort{
  1472  		network.AddressesWithPort(server1Addresses, 123),
  1473  		network.AddressesWithPort(server2Addresses, 456),
  1474  	}
  1475  
  1476  	err := s.State.SetAPIHostPorts(stateAPIHostPorts)
  1477  	c.Assert(err, jc.ErrorIsNil)
  1478  	apiHostPorts, err := s.APIState.Client().APIHostPorts()
  1479  	c.Assert(err, jc.ErrorIsNil)
  1480  	c.Assert(apiHostPorts, gc.DeepEquals, stateAPIHostPorts)
  1481  }
  1482  
  1483  func (s *clientSuite) TestClientAgentVersion(c *gc.C) {
  1484  	current := version.MustParse("1.2.0")
  1485  	s.PatchValue(&jujuversion.Current, current)
  1486  	result, err := s.APIState.Client().AgentVersion()
  1487  	c.Assert(err, jc.ErrorIsNil)
  1488  	c.Assert(result, gc.Equals, current)
  1489  }
  1490  
  1491  func (s *clientSuite) assertDestroyMachineSuccess(c *gc.C, u *state.Unit, m0, m1, m2 *state.Machine) {
  1492  	err := s.APIState.Client().DestroyMachines("0", "1", "2")
  1493  	c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 is required by the model; machine 1 has unit "wordpress/0" assigned`)
  1494  	assertLife(c, m0, state.Alive)
  1495  	assertLife(c, m1, state.Alive)
  1496  	assertLife(c, m2, state.Dying)
  1497  
  1498  	err = u.UnassignFromMachine()
  1499  	c.Assert(err, jc.ErrorIsNil)
  1500  	err = s.APIState.Client().DestroyMachines("0", "1", "2")
  1501  	c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 is required by the model`)
  1502  	assertLife(c, m0, state.Alive)
  1503  	assertLife(c, m1, state.Dying)
  1504  	assertLife(c, m2, state.Dying)
  1505  }
  1506  
  1507  func (s *clientSuite) assertBlockedErrorAndLiveliness(
  1508  	c *gc.C,
  1509  	err error,
  1510  	msg string,
  1511  	living1 state.Living,
  1512  	living2 state.Living,
  1513  	living3 state.Living,
  1514  	living4 state.Living,
  1515  ) {
  1516  	s.AssertBlocked(c, err, msg)
  1517  	assertLife(c, living1, state.Alive)
  1518  	assertLife(c, living2, state.Alive)
  1519  	assertLife(c, living3, state.Alive)
  1520  	assertLife(c, living4, state.Alive)
  1521  }
  1522  
  1523  func (s *clientSuite) AssertBlocked(c *gc.C, err error, msg string) {
  1524  	c.Assert(params.IsCodeOperationBlocked(err), jc.IsTrue, gc.Commentf("error: %#v", err))
  1525  	c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
  1526  		Message: msg,
  1527  		Code:    "operation is blocked",
  1528  	})
  1529  }
  1530  
  1531  func (s *clientSuite) TestBlockRemoveDestroyMachines(c *gc.C) {
  1532  	m0, m1, m2, u := s.setupDestroyMachinesTest(c)
  1533  	s.BlockRemoveObject(c, "TestBlockRemoveDestroyMachines")
  1534  	err := s.APIState.Client().DestroyMachines("0", "1", "2")
  1535  	s.assertBlockedErrorAndLiveliness(c, err, "TestBlockRemoveDestroyMachines", m0, m1, m2, u)
  1536  }
  1537  
  1538  func (s *clientSuite) TestBlockChangesDestroyMachines(c *gc.C) {
  1539  	m0, m1, m2, u := s.setupDestroyMachinesTest(c)
  1540  	s.BlockAllChanges(c, "TestBlockChangesDestroyMachines")
  1541  	err := s.APIState.Client().DestroyMachines("0", "1", "2")
  1542  	s.assertBlockedErrorAndLiveliness(c, err, "TestBlockChangesDestroyMachines", m0, m1, m2, u)
  1543  }
  1544  
  1545  func (s *clientSuite) TestBlockDestoryDestroyMachines(c *gc.C) {
  1546  	m0, m1, m2, u := s.setupDestroyMachinesTest(c)
  1547  	s.BlockDestroyModel(c, "TestBlockDestoryDestroyMachines")
  1548  	s.assertDestroyMachineSuccess(c, u, m0, m1, m2)
  1549  }
  1550  
  1551  func (s *clientSuite) TestAnyBlockForceDestroyMachines(c *gc.C) {
  1552  	// force bypasses all blocks
  1553  	s.BlockAllChanges(c, "TestAnyBlockForceDestroyMachines")
  1554  	s.BlockDestroyModel(c, "TestAnyBlockForceDestroyMachines")
  1555  	s.BlockRemoveObject(c, "TestAnyBlockForceDestroyMachines")
  1556  	s.assertForceDestroyMachines(c)
  1557  }
  1558  
  1559  func (s *clientSuite) assertForceDestroyMachines(c *gc.C) {
  1560  	m0, m1, m2, u := s.setupDestroyMachinesTest(c)
  1561  
  1562  	err := s.APIState.Client().ForceDestroyMachines("0", "1", "2")
  1563  	c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine is required by the model`)
  1564  	assertLife(c, m0, state.Alive)
  1565  	assertLife(c, m1, state.Alive)
  1566  	assertLife(c, m2, state.Alive)
  1567  	assertLife(c, u, state.Alive)
  1568  
  1569  	err = s.State.Cleanup()
  1570  	c.Assert(err, jc.ErrorIsNil)
  1571  	assertLife(c, m0, state.Alive)
  1572  	assertLife(c, m1, state.Dead)
  1573  	assertLife(c, m2, state.Dead)
  1574  	assertRemoved(c, u)
  1575  }
  1576  
  1577  func (s *clientSuite) TestDestroyModel(c *gc.C) {
  1578  	// The full tests for DestroyModel are in modelmanager.
  1579  	// Here we just test that things are hooked up such that we can destroy
  1580  	// the model through the client endpoint to support older juju clients.
  1581  	err := s.APIState.Client().DestroyModel()
  1582  	c.Assert(err, jc.ErrorIsNil)
  1583  
  1584  	env, err := s.State.Model()
  1585  	c.Assert(err, jc.ErrorIsNil)
  1586  	c.Assert(env.Life(), gc.Equals, state.Dying)
  1587  }