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