github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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  	"io"
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/juju/errors"
    15  	"github.com/juju/names"
    16  	jc "github.com/juju/testing/checkers"
    17  	"github.com/juju/utils"
    18  	"github.com/juju/utils/featureflag"
    19  	gc "gopkg.in/check.v1"
    20  	"gopkg.in/juju/charm.v4"
    21  	charmtesting "gopkg.in/juju/charm.v4/testing"
    22  	"gopkg.in/mgo.v2"
    23  
    24  	"github.com/juju/juju/agent"
    25  	"github.com/juju/juju/api"
    26  	"github.com/juju/juju/apiserver/client"
    27  	"github.com/juju/juju/apiserver/common"
    28  	"github.com/juju/juju/apiserver/params"
    29  	"github.com/juju/juju/apiserver/testing"
    30  	"github.com/juju/juju/constraints"
    31  	"github.com/juju/juju/environs/config"
    32  	"github.com/juju/juju/environs/manual"
    33  	toolstesting "github.com/juju/juju/environs/tools/testing"
    34  	"github.com/juju/juju/instance"
    35  	"github.com/juju/juju/juju/osenv"
    36  	"github.com/juju/juju/network"
    37  	"github.com/juju/juju/provider/dummy"
    38  	"github.com/juju/juju/state"
    39  	"github.com/juju/juju/state/multiwatcher"
    40  	"github.com/juju/juju/state/presence"
    41  	statestorage "github.com/juju/juju/state/storage"
    42  	"github.com/juju/juju/storage"
    43  	"github.com/juju/juju/testcharms"
    44  	coretesting "github.com/juju/juju/testing"
    45  	"github.com/juju/juju/testing/factory"
    46  	"github.com/juju/juju/version"
    47  )
    48  
    49  type clientSuite struct {
    50  	baseSuite
    51  }
    52  
    53  type Killer interface {
    54  	Kill() error
    55  }
    56  
    57  type serverSuite struct {
    58  	baseSuite
    59  	client *client.Client
    60  }
    61  
    62  var _ = gc.Suite(&serverSuite{})
    63  
    64  func (s *serverSuite) SetUpTest(c *gc.C) {
    65  	s.baseSuite.SetUpTest(c)
    66  
    67  	var err error
    68  	auth := testing.FakeAuthorizer{
    69  		Tag:            s.AdminUserTag(c),
    70  		EnvironManager: true,
    71  	}
    72  	s.client, err = client.NewClient(s.State, common.NewResources(), auth)
    73  	c.Assert(err, jc.ErrorIsNil)
    74  }
    75  
    76  func (s *serverSuite) setAgentPresence(c *gc.C, machineId string) *presence.Pinger {
    77  	m, err := s.State.Machine(machineId)
    78  	c.Assert(err, jc.ErrorIsNil)
    79  	pinger, err := m.SetAgentPresence()
    80  	c.Assert(err, jc.ErrorIsNil)
    81  	s.State.StartSync()
    82  	err = m.WaitAgentPresence(coretesting.LongWait)
    83  	c.Assert(err, jc.ErrorIsNil)
    84  	return pinger
    85  }
    86  
    87  func (s *serverSuite) TestEnsureAvailabilityDeprecated(c *gc.C) {
    88  	_, err := s.State.AddMachine("quantal", state.JobManageEnviron)
    89  	c.Assert(err, jc.ErrorIsNil)
    90  	// We have to ensure the agents are alive, or EnsureAvailability will
    91  	// create more to replace them.
    92  	pingerA := s.setAgentPresence(c, "0")
    93  	defer assertKill(c, pingerA)
    94  
    95  	machines, err := s.State.AllMachines()
    96  	c.Assert(err, jc.ErrorIsNil)
    97  	c.Assert(machines, gc.HasLen, 1)
    98  	c.Assert(machines[0].Series(), gc.Equals, "quantal")
    99  
   100  	arg := params.StateServersSpecs{[]params.StateServersSpec{{NumStateServers: 3}}}
   101  	results, err := s.client.EnsureAvailability(arg)
   102  	c.Assert(err, jc.ErrorIsNil)
   103  	c.Assert(results.Results, gc.HasLen, 1)
   104  	result := results.Results[0]
   105  	c.Assert(result.Error, gc.IsNil)
   106  	ensureAvailabilityResult := result.Result
   107  	c.Assert(ensureAvailabilityResult.Maintained, gc.DeepEquals, []string{"machine-0"})
   108  	c.Assert(ensureAvailabilityResult.Added, gc.DeepEquals, []string{"machine-1", "machine-2"})
   109  	c.Assert(ensureAvailabilityResult.Removed, gc.HasLen, 0)
   110  
   111  	machines, err = s.State.AllMachines()
   112  	c.Assert(err, jc.ErrorIsNil)
   113  	c.Assert(machines, gc.HasLen, 3)
   114  	c.Assert(machines[0].Series(), gc.Equals, "quantal")
   115  	c.Assert(machines[1].Series(), gc.Equals, "quantal")
   116  	c.Assert(machines[2].Series(), gc.Equals, "quantal")
   117  }
   118  
   119  func (s *serverSuite) TestBlockEnsureAvailabilityDeprecated(c *gc.C) {
   120  	_, err := s.State.AddMachine("quantal", state.JobManageEnviron)
   121  	c.Assert(err, jc.ErrorIsNil)
   122  
   123  	s.blockAllChanges(c)
   124  
   125  	arg := params.StateServersSpecs{[]params.StateServersSpec{{NumStateServers: 3}}}
   126  	results, err := s.client.EnsureAvailability(arg)
   127  	c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
   128  	c.Assert(results.Results, gc.HasLen, 0)
   129  
   130  	machines, err := s.State.AllMachines() //there
   131  	c.Assert(err, jc.ErrorIsNil)
   132  	c.Assert(machines, gc.HasLen, 1)
   133  }
   134  
   135  func (s *serverSuite) TestShareEnvironmentAddMissingLocalFails(c *gc.C) {
   136  	args := params.ModifyEnvironUsers{
   137  		Changes: []params.ModifyEnvironUser{{
   138  			UserTag: names.NewLocalUserTag("foobar").String(),
   139  			Action:  params.AddEnvUser,
   140  		}}}
   141  
   142  	result, err := s.client.ShareEnvironment(args)
   143  	c.Assert(err, jc.ErrorIsNil)
   144  	expectedErr := `could not share environment: user "foobar" does not exist locally: user "foobar" not found`
   145  	c.Assert(result.OneError(), gc.ErrorMatches, expectedErr)
   146  	c.Assert(result.Results, gc.HasLen, 1)
   147  	c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr)
   148  }
   149  
   150  func (s *serverSuite) TestUnshareEnvironment(c *gc.C) {
   151  	user := s.Factory.MakeEnvUser(c, nil)
   152  	_, err := s.State.EnvironmentUser(user.UserTag())
   153  	c.Assert(err, jc.ErrorIsNil)
   154  
   155  	args := params.ModifyEnvironUsers{
   156  		Changes: []params.ModifyEnvironUser{{
   157  			UserTag: user.UserTag().String(),
   158  			Action:  params.RemoveEnvUser,
   159  		}}}
   160  
   161  	result, err := s.client.ShareEnvironment(args)
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	c.Assert(result.OneError(), gc.IsNil)
   164  	c.Assert(result.Results, gc.HasLen, 1)
   165  	c.Assert(result.Results[0].Error, gc.IsNil)
   166  
   167  	_, err = s.State.EnvironmentUser(user.UserTag())
   168  	c.Assert(errors.IsNotFound(err), jc.IsTrue)
   169  }
   170  
   171  func (s *serverSuite) TestShareEnvironmentAddLocalUser(c *gc.C) {
   172  	user := s.Factory.MakeUser(c, &factory.UserParams{Name: "foobar", NoEnvUser: true})
   173  	args := params.ModifyEnvironUsers{
   174  		Changes: []params.ModifyEnvironUser{{
   175  			UserTag: user.Tag().String(),
   176  			Action:  params.AddEnvUser,
   177  		}}}
   178  
   179  	result, err := s.client.ShareEnvironment(args)
   180  	c.Assert(err, jc.ErrorIsNil)
   181  	c.Assert(result.OneError(), gc.IsNil)
   182  	c.Assert(result.Results, gc.HasLen, 1)
   183  	c.Assert(result.Results[0].Error, gc.IsNil)
   184  
   185  	envUser, err := s.State.EnvironmentUser(user.UserTag())
   186  	c.Assert(err, jc.ErrorIsNil)
   187  	c.Assert(envUser.UserName(), gc.Equals, user.UserTag().Username())
   188  	c.Assert(envUser.CreatedBy(), gc.Equals, dummy.AdminUserTag().Username())
   189  	c.Assert(envUser.LastConnection(), gc.IsNil)
   190  }
   191  
   192  func (s *serverSuite) TestShareEnvironmentAddRemoteUser(c *gc.C) {
   193  	user := names.NewUserTag("foobar@ubuntuone")
   194  	args := params.ModifyEnvironUsers{
   195  		Changes: []params.ModifyEnvironUser{{
   196  			UserTag: user.String(),
   197  			Action:  params.AddEnvUser,
   198  		}}}
   199  
   200  	result, err := s.client.ShareEnvironment(args)
   201  	c.Assert(err, jc.ErrorIsNil)
   202  	c.Assert(result.OneError(), gc.IsNil)
   203  	c.Assert(result.Results, gc.HasLen, 1)
   204  	c.Assert(result.Results[0].Error, gc.IsNil)
   205  
   206  	envUser, err := s.State.EnvironmentUser(user)
   207  	c.Assert(err, jc.ErrorIsNil)
   208  	c.Assert(envUser.UserName(), gc.Equals, user.Username())
   209  	c.Assert(envUser.CreatedBy(), gc.Equals, dummy.AdminUserTag().Username())
   210  	c.Assert(envUser.LastConnection(), gc.IsNil)
   211  }
   212  
   213  func (s *serverSuite) TestShareEnvironmentInvalidTags(c *gc.C) {
   214  	for _, testParam := range []struct {
   215  		tag      string
   216  		validTag bool
   217  	}{{
   218  		tag:      "unit-foo/0",
   219  		validTag: true,
   220  	}, {
   221  		tag:      "service-foo",
   222  		validTag: true,
   223  	}, {
   224  		tag:      "relation-wordpress:db mysql:db",
   225  		validTag: true,
   226  	}, {
   227  		tag:      "machine-0",
   228  		validTag: true,
   229  	}, {
   230  		tag:      "user@local",
   231  		validTag: false,
   232  	}, {
   233  		tag:      "user-Mua^h^h^h^arh",
   234  		validTag: true,
   235  	}, {
   236  		tag:      "user@",
   237  		validTag: false,
   238  	}, {
   239  		tag:      "user@ubuntuone",
   240  		validTag: false,
   241  	}, {
   242  		tag:      "user@ubuntuone",
   243  		validTag: false,
   244  	}, {
   245  		tag:      "@ubuntuone",
   246  		validTag: false,
   247  	}, {
   248  		tag:      "in^valid.",
   249  		validTag: false,
   250  	}, {
   251  		tag:      "",
   252  		validTag: false,
   253  	},
   254  	} {
   255  		var expectedErr string
   256  		errPart := `could not share environment: "` + regexp.QuoteMeta(testParam.tag) + `" is not a valid `
   257  
   258  		if testParam.validTag {
   259  
   260  			// The string is a valid tag, but not a user tag.
   261  			expectedErr = errPart + `user tag`
   262  		} else {
   263  
   264  			// The string is not a valid tag of any kind.
   265  			expectedErr = errPart + `tag`
   266  		}
   267  
   268  		args := params.ModifyEnvironUsers{
   269  			Changes: []params.ModifyEnvironUser{{
   270  				UserTag: testParam.tag,
   271  				Action:  params.AddEnvUser,
   272  			}}}
   273  
   274  		_, err := s.client.ShareEnvironment(args)
   275  		result, err := s.client.ShareEnvironment(args)
   276  		c.Assert(err, jc.ErrorIsNil)
   277  		c.Assert(result.OneError(), gc.ErrorMatches, expectedErr)
   278  		c.Assert(result.Results, gc.HasLen, 1)
   279  		c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr)
   280  	}
   281  }
   282  
   283  func (s *serverSuite) TestShareEnvironmentZeroArgs(c *gc.C) {
   284  	args := params.ModifyEnvironUsers{Changes: []params.ModifyEnvironUser{{}}}
   285  
   286  	_, err := s.client.ShareEnvironment(args)
   287  	result, err := s.client.ShareEnvironment(args)
   288  	c.Assert(err, jc.ErrorIsNil)
   289  	expectedErr := `could not share environment: "" is not a valid tag`
   290  	c.Assert(result.OneError(), gc.ErrorMatches, expectedErr)
   291  	c.Assert(result.Results, gc.HasLen, 1)
   292  	c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr)
   293  }
   294  
   295  func (s *serverSuite) TestShareEnvironmentInvalidAction(c *gc.C) {
   296  	var dance params.EnvironAction = "dance"
   297  	args := params.ModifyEnvironUsers{
   298  		Changes: []params.ModifyEnvironUser{{
   299  			UserTag: "user-user@local",
   300  			Action:  dance,
   301  		}}}
   302  
   303  	_, err := s.client.ShareEnvironment(args)
   304  	result, err := s.client.ShareEnvironment(args)
   305  	c.Assert(err, jc.ErrorIsNil)
   306  	expectedErr := `unknown action "dance"`
   307  	c.Assert(result.OneError(), gc.ErrorMatches, expectedErr)
   308  	c.Assert(result.Results, gc.HasLen, 1)
   309  	c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr)
   310  }
   311  
   312  func (s *serverSuite) TestSetEnvironAgentVersion(c *gc.C) {
   313  	args := params.SetEnvironAgentVersion{
   314  		Version: version.MustParse("9.8.7"),
   315  	}
   316  	err := s.client.SetEnvironAgentVersion(args)
   317  	c.Assert(err, jc.ErrorIsNil)
   318  
   319  	envConfig, err := s.State.EnvironConfig()
   320  	c.Assert(err, jc.ErrorIsNil)
   321  	agentVersion, found := envConfig.AllAttrs()["agent-version"]
   322  	c.Assert(found, jc.IsTrue)
   323  	c.Assert(agentVersion, gc.Equals, "9.8.7")
   324  }
   325  
   326  func (s *serverSuite) assertSetEnvironAgentVersionBlocked(c *gc.C, blocked bool) {
   327  	args := params.SetEnvironAgentVersion{
   328  		Version: version.MustParse("9.8.7"),
   329  	}
   330  	err := s.client.SetEnvironAgentVersion(args)
   331  	if blocked {
   332  		c.Assert(errors.Cause(err), gc.Equals, common.ErrOperationBlocked)
   333  	} else {
   334  		c.Assert(err, jc.ErrorIsNil)
   335  		envConfig, err := s.State.EnvironConfig()
   336  		c.Assert(err, jc.ErrorIsNil)
   337  		agentVersion, found := envConfig.AllAttrs()["agent-version"]
   338  		c.Assert(found, jc.IsTrue)
   339  		c.Assert(agentVersion, gc.Equals, "9.8.7")
   340  	}
   341  }
   342  
   343  func (s *serverSuite) TestBlockDestroySetEnvironAgentVersion(c *gc.C) {
   344  	s.blockDestroyEnvironment(c)
   345  	s.assertSetEnvironAgentVersionBlocked(c, false)
   346  }
   347  
   348  func (s *serverSuite) TestBlockRemoveSetEnvironAgentVersion(c *gc.C) {
   349  	s.blockRemoveObject(c)
   350  	s.assertSetEnvironAgentVersionBlocked(c, false)
   351  }
   352  
   353  func (s *serverSuite) TestBlockChangesSetEnvironAgentVersion(c *gc.C) {
   354  	s.blockAllChanges(c)
   355  	s.assertSetEnvironAgentVersionBlocked(c, true)
   356  }
   357  
   358  func (s *serverSuite) TestAbortCurrentUpgrade(c *gc.C) {
   359  	// Create a provisioned state server.
   360  	machine, err := s.State.AddMachine("series", state.JobManageEnviron)
   361  	c.Assert(err, jc.ErrorIsNil)
   362  	err = machine.SetProvisioned(instance.Id("i-blah"), "fake-nonce", nil)
   363  	c.Assert(err, jc.ErrorIsNil)
   364  
   365  	// Start an upgrade.
   366  	_, err = s.State.EnsureUpgradeInfo(
   367  		machine.Id(),
   368  		version.MustParse("1.2.3"),
   369  		version.MustParse("9.8.7"),
   370  	)
   371  	c.Assert(err, jc.ErrorIsNil)
   372  	isUpgrading, err := s.State.IsUpgrading()
   373  	c.Assert(err, jc.ErrorIsNil)
   374  	c.Assert(isUpgrading, jc.IsTrue)
   375  
   376  	// Abort it.
   377  	err = s.client.AbortCurrentUpgrade()
   378  	c.Assert(err, jc.ErrorIsNil)
   379  
   380  	isUpgrading, err = s.State.IsUpgrading()
   381  	c.Assert(err, jc.ErrorIsNil)
   382  	c.Assert(isUpgrading, jc.IsFalse)
   383  }
   384  
   385  func (s *serverSuite) assertAbortCurrentUpgradeBlocked(c *gc.C, blocked bool) {
   386  	err := s.client.AbortCurrentUpgrade()
   387  
   388  	if blocked {
   389  		c.Assert(errors.Cause(err), gc.Equals, common.ErrOperationBlocked)
   390  	} else {
   391  		c.Assert(err, jc.ErrorIsNil)
   392  		isUpgrading, err := s.State.IsUpgrading()
   393  		c.Assert(err, jc.ErrorIsNil)
   394  		c.Assert(isUpgrading, jc.IsFalse)
   395  	}
   396  }
   397  
   398  func (s *serverSuite) setupAbortCurrentUpgradeBlocked(c *gc.C) {
   399  	// Create a provisioned state server.
   400  	machine, err := s.State.AddMachine("series", state.JobManageEnviron)
   401  	c.Assert(err, jc.ErrorIsNil)
   402  	err = machine.SetProvisioned(instance.Id("i-blah"), "fake-nonce", nil)
   403  	c.Assert(err, jc.ErrorIsNil)
   404  
   405  	// Start an upgrade.
   406  	_, err = s.State.EnsureUpgradeInfo(
   407  		machine.Id(),
   408  		version.MustParse("1.2.3"),
   409  		version.MustParse("9.8.7"),
   410  	)
   411  	c.Assert(err, jc.ErrorIsNil)
   412  	isUpgrading, err := s.State.IsUpgrading()
   413  	c.Assert(err, jc.ErrorIsNil)
   414  	c.Assert(isUpgrading, jc.IsTrue)
   415  }
   416  
   417  func (s *serverSuite) TestBlockDestroyAbortCurrentUpgrade(c *gc.C) {
   418  	s.setupAbortCurrentUpgradeBlocked(c)
   419  	s.blockDestroyEnvironment(c)
   420  	s.assertAbortCurrentUpgradeBlocked(c, false)
   421  }
   422  
   423  func (s *serverSuite) TestBlockRemoveAbortCurrentUpgrade(c *gc.C) {
   424  	s.setupAbortCurrentUpgradeBlocked(c)
   425  	s.blockRemoveObject(c)
   426  	s.assertAbortCurrentUpgradeBlocked(c, false)
   427  }
   428  
   429  func (s *serverSuite) TestBlockChangesAbortCurrentUpgrade(c *gc.C) {
   430  	s.setupAbortCurrentUpgradeBlocked(c)
   431  	s.blockAllChanges(c)
   432  	s.assertAbortCurrentUpgradeBlocked(c, true)
   433  }
   434  
   435  var _ = gc.Suite(&clientSuite{})
   436  
   437  func (s *clientSuite) TestClientStatus(c *gc.C) {
   438  	s.setUpScenario(c)
   439  	status, err := s.APIState.Client().Status(nil)
   440  	c.Assert(err, jc.ErrorIsNil)
   441  	c.Assert(status, jc.DeepEquals, scenarioStatus)
   442  }
   443  
   444  func (s *clientSuite) TestCompatibleSettingsParsing(c *gc.C) {
   445  	// Test the exported settings parsing in a compatible way.
   446  	s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   447  	service, err := s.State.Service("dummy")
   448  	c.Assert(err, jc.ErrorIsNil)
   449  	ch, _, err := service.Charm()
   450  	c.Assert(err, jc.ErrorIsNil)
   451  	c.Assert(ch.URL().String(), gc.Equals, "local:quantal/dummy-1")
   452  
   453  	// Empty string will be returned as nil.
   454  	options := map[string]string{
   455  		"title":    "foobar",
   456  		"username": "",
   457  	}
   458  	settings, err := client.ParseSettingsCompatible(ch, options)
   459  	c.Assert(err, jc.ErrorIsNil)
   460  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   461  		"title":    "foobar",
   462  		"username": nil,
   463  	})
   464  
   465  	// Illegal settings lead to an error.
   466  	options = map[string]string{
   467  		"yummy": "didgeridoo",
   468  	}
   469  	settings, err = client.ParseSettingsCompatible(ch, options)
   470  	c.Assert(err, gc.ErrorMatches, `unknown option "yummy"`)
   471  }
   472  
   473  var (
   474  	validSetTestValue     = "a value with spaces\nand newline\nand UTF-8 characters: \U0001F604 / \U0001F44D"
   475  	invalidSetTestValue   = "a value with an invalid UTF-8 sequence: " + string([]byte{0xFF, 0xFF})
   476  	correctedSetTestValue = "a value with an invalid UTF-8 sequence: \ufffd\ufffd"
   477  )
   478  
   479  func (s *clientSuite) TestClientServiceSet(c *gc.C) {
   480  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   481  
   482  	err := s.APIState.Client().ServiceSet("dummy", map[string]string{
   483  		"title":    "foobar",
   484  		"username": validSetTestValue,
   485  	})
   486  	c.Assert(err, jc.ErrorIsNil)
   487  	settings, err := dummy.ConfigSettings()
   488  	c.Assert(err, jc.ErrorIsNil)
   489  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   490  		"title":    "foobar",
   491  		"username": validSetTestValue,
   492  	})
   493  
   494  	// Test doesn't fail because Go JSON marshalling converts invalid
   495  	// UTF-8 sequences transparently to U+FFFD. The test demonstrates
   496  	// this behavior. It's a currently accepted behavior as it never has
   497  	// been a real-life issue.
   498  	err = s.APIState.Client().ServiceSet("dummy", map[string]string{
   499  		"title":    "foobar",
   500  		"username": invalidSetTestValue,
   501  	})
   502  	c.Assert(err, jc.ErrorIsNil)
   503  	settings, err = dummy.ConfigSettings()
   504  	c.Assert(err, jc.ErrorIsNil)
   505  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   506  		"title":    "foobar",
   507  		"username": correctedSetTestValue,
   508  	})
   509  
   510  	err = s.APIState.Client().ServiceSet("dummy", map[string]string{
   511  		"title":    "barfoo",
   512  		"username": "",
   513  	})
   514  	c.Assert(err, jc.ErrorIsNil)
   515  	settings, err = dummy.ConfigSettings()
   516  	c.Assert(err, jc.ErrorIsNil)
   517  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   518  		"title":    "barfoo",
   519  		"username": "",
   520  	})
   521  }
   522  
   523  func (s *serverSuite) assertServiceSetBlocked(c *gc.C, blocked bool, dummy *state.Service) {
   524  	err := s.client.ServiceSet(params.ServiceSet{
   525  		ServiceName: "dummy",
   526  		Options: map[string]string{
   527  			"title":    "foobar",
   528  			"username": validSetTestValue}})
   529  	if blocked {
   530  		c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
   531  	} else {
   532  		c.Assert(err, jc.ErrorIsNil)
   533  		settings, err := dummy.ConfigSettings()
   534  		c.Assert(err, jc.ErrorIsNil)
   535  		c.Assert(settings, gc.DeepEquals, charm.Settings{
   536  			"title":    "foobar",
   537  			"username": validSetTestValue,
   538  		})
   539  	}
   540  }
   541  func (s *serverSuite) TestBlockDestroyServiceSet(c *gc.C) {
   542  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   543  	s.blockDestroyEnvironment(c)
   544  	s.assertServiceSetBlocked(c, false, dummy)
   545  }
   546  
   547  func (s *serverSuite) TestBlockRemoveServiceSet(c *gc.C) {
   548  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   549  	s.blockRemoveObject(c)
   550  	s.assertServiceSetBlocked(c, false, dummy)
   551  }
   552  
   553  func (s *serverSuite) TestBlockChangesServiceSet(c *gc.C) {
   554  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   555  	s.blockAllChanges(c)
   556  	s.assertServiceSetBlocked(c, true, dummy)
   557  }
   558  
   559  func (s *clientSuite) TestClientServerUnset(c *gc.C) {
   560  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   561  
   562  	err := s.APIState.Client().ServiceSet("dummy", map[string]string{
   563  		"title":    "foobar",
   564  		"username": "user name",
   565  	})
   566  	c.Assert(err, jc.ErrorIsNil)
   567  	settings, err := dummy.ConfigSettings()
   568  	c.Assert(err, jc.ErrorIsNil)
   569  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   570  		"title":    "foobar",
   571  		"username": "user name",
   572  	})
   573  
   574  	err = s.APIState.Client().ServiceUnset("dummy", []string{"username"})
   575  	c.Assert(err, jc.ErrorIsNil)
   576  	settings, err = dummy.ConfigSettings()
   577  	c.Assert(err, jc.ErrorIsNil)
   578  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   579  		"title": "foobar",
   580  	})
   581  }
   582  
   583  func (s *serverSuite) setupServerUnsetBlocked(c *gc.C) *state.Service {
   584  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   585  
   586  	err := s.client.ServiceSet(params.ServiceSet{
   587  		ServiceName: "dummy",
   588  		Options: map[string]string{
   589  			"title":    "foobar",
   590  			"username": "user name",
   591  		}})
   592  	c.Assert(err, jc.ErrorIsNil)
   593  	settings, err := dummy.ConfigSettings()
   594  	c.Assert(err, jc.ErrorIsNil)
   595  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   596  		"title":    "foobar",
   597  		"username": "user name",
   598  	})
   599  	return dummy
   600  }
   601  
   602  func (s *serverSuite) assertServerUnsetBlocked(c *gc.C, blocked bool, dummy *state.Service) {
   603  	err := s.client.ServiceUnset(params.ServiceUnset{
   604  		ServiceName: "dummy",
   605  		Options:     []string{"username"},
   606  	})
   607  	if blocked {
   608  		c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
   609  	} else {
   610  		c.Assert(err, jc.ErrorIsNil)
   611  		settings, err := dummy.ConfigSettings()
   612  		c.Assert(err, jc.ErrorIsNil)
   613  		c.Assert(settings, gc.DeepEquals, charm.Settings{
   614  			"title": "foobar",
   615  		})
   616  	}
   617  }
   618  
   619  func (s *serverSuite) TestBlockDestroyServerUnset(c *gc.C) {
   620  	dummy := s.setupServerUnsetBlocked(c)
   621  	s.blockDestroyEnvironment(c)
   622  	s.assertServerUnsetBlocked(c, false, dummy)
   623  }
   624  
   625  func (s *serverSuite) TestBlockRemoveServerUnset(c *gc.C) {
   626  	dummy := s.setupServerUnsetBlocked(c)
   627  	s.blockRemoveObject(c)
   628  	s.assertServerUnsetBlocked(c, false, dummy)
   629  }
   630  
   631  func (s *serverSuite) TestBlockChangesServerUnset(c *gc.C) {
   632  	dummy := s.setupServerUnsetBlocked(c)
   633  	s.blockAllChanges(c)
   634  	s.assertServerUnsetBlocked(c, true, dummy)
   635  }
   636  
   637  func (s *clientSuite) TestClientServiceSetYAML(c *gc.C) {
   638  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   639  
   640  	err := s.APIState.Client().ServiceSetYAML("dummy", "dummy:\n  title: foobar\n  username: user name\n")
   641  	c.Assert(err, jc.ErrorIsNil)
   642  	settings, err := dummy.ConfigSettings()
   643  	c.Assert(err, jc.ErrorIsNil)
   644  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   645  		"title":    "foobar",
   646  		"username": "user name",
   647  	})
   648  
   649  	err = s.APIState.Client().ServiceSetYAML("dummy", "dummy:\n  title: barfoo\n  username: \n")
   650  	c.Assert(err, jc.ErrorIsNil)
   651  	settings, err = dummy.ConfigSettings()
   652  	c.Assert(err, jc.ErrorIsNil)
   653  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   654  		"title": "barfoo",
   655  	})
   656  }
   657  
   658  func (s *clientSuite) assertServiceSetYAMLBlocked(c *gc.C, blocked bool, dummy *state.Service) {
   659  	err := s.APIState.Client().ServiceSetYAML("dummy", "dummy:\n  title: foobar\n  username: user name\n")
   660  	if blocked {
   661  		c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
   662  	} else {
   663  		c.Assert(err, jc.ErrorIsNil)
   664  		settings, err := dummy.ConfigSettings()
   665  		c.Assert(err, jc.ErrorIsNil)
   666  		c.Assert(settings, gc.DeepEquals, charm.Settings{
   667  			"title":    "foobar",
   668  			"username": "user name",
   669  		})
   670  	}
   671  }
   672  
   673  func (s *clientSuite) TestBlockDestroyServiceSetYAML(c *gc.C) {
   674  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   675  	s.blockDestroyEnvironment(c)
   676  	s.assertServiceSetYAMLBlocked(c, false, dummy)
   677  }
   678  
   679  func (s *clientSuite) TestBlockRemoveServiceSetYAML(c *gc.C) {
   680  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   681  	s.blockRemoveObject(c)
   682  	s.assertServiceSetYAMLBlocked(c, false, dummy)
   683  }
   684  
   685  func (s *clientSuite) TestBlockChangesServiceSetYAML(c *gc.C) {
   686  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   687  	s.blockAllChanges(c)
   688  	s.assertServiceSetYAMLBlocked(c, true, dummy)
   689  }
   690  
   691  var clientAddServiceUnitsTests = []struct {
   692  	about    string
   693  	service  string // if not set, defaults to 'dummy'
   694  	expected []string
   695  	to       string
   696  	err      string
   697  }{
   698  	{
   699  		about:    "returns unit names",
   700  		expected: []string{"dummy/0", "dummy/1", "dummy/2"},
   701  	},
   702  	{
   703  		about: "fails trying to add zero units",
   704  		err:   "must add at least one unit",
   705  	},
   706  	{
   707  		about:    "cannot mix to when adding multiple units",
   708  		err:      "cannot use NumUnits with ToMachineSpec",
   709  		expected: []string{"dummy/0", "dummy/1"},
   710  		to:       "0",
   711  	},
   712  	{
   713  		// Note: chained-state, we add 1 unit here, but the 3 units
   714  		// from the first condition still exist
   715  		about:    "force the unit onto bootstrap machine",
   716  		expected: []string{"dummy/3"},
   717  		to:       "0",
   718  	},
   719  	{
   720  		about:   "unknown service name",
   721  		service: "unknown-service",
   722  		err:     `service "unknown-service" not found`,
   723  	},
   724  }
   725  
   726  func (s *clientSuite) TestClientAddServiceUnits(c *gc.C) {
   727  	s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   728  	for i, t := range clientAddServiceUnitsTests {
   729  		c.Logf("test %d. %s", i, t.about)
   730  		serviceName := t.service
   731  		if serviceName == "" {
   732  			serviceName = "dummy"
   733  		}
   734  		units, err := s.APIState.Client().AddServiceUnits(serviceName, len(t.expected), t.to)
   735  		if t.err != "" {
   736  			c.Assert(err, gc.ErrorMatches, t.err)
   737  			continue
   738  		}
   739  		c.Assert(err, jc.ErrorIsNil)
   740  		c.Assert(units, gc.DeepEquals, t.expected)
   741  	}
   742  	// Test that we actually assigned the unit to machine 0
   743  	forcedUnit, err := s.BackingState.Unit("dummy/3")
   744  	c.Assert(err, jc.ErrorIsNil)
   745  	assignedMachine, err := forcedUnit.AssignedMachineId()
   746  	c.Assert(err, jc.ErrorIsNil)
   747  	c.Assert(assignedMachine, gc.Equals, "0")
   748  }
   749  
   750  func (s *clientSuite) assertAddServiceUnitsBlocked(c *gc.C, blocked bool) {
   751  	units, err := s.APIState.Client().AddServiceUnits("dummy", 3, "")
   752  	if blocked {
   753  		c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
   754  	} else {
   755  		c.Assert(err, jc.ErrorIsNil)
   756  		c.Assert(units, gc.DeepEquals, []string{"dummy/0", "dummy/1", "dummy/2"})
   757  
   758  		// Test that we actually assigned the unit to machine 0
   759  		forcedUnit, err := s.BackingState.Unit("dummy/0")
   760  		c.Assert(err, jc.ErrorIsNil)
   761  		assignedMachine, err := forcedUnit.AssignedMachineId()
   762  		c.Assert(err, jc.ErrorIsNil)
   763  		c.Assert(assignedMachine, gc.Equals, "0")
   764  	}
   765  }
   766  
   767  func (s *clientSuite) TestBlockDestroyAddServiceUnits(c *gc.C) {
   768  	s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   769  	s.blockDestroyEnvironment(c)
   770  	s.assertAddServiceUnitsBlocked(c, false)
   771  }
   772  
   773  func (s *clientSuite) TestBlockRemoveAddServiceUnits(c *gc.C) {
   774  	s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   775  	s.blockRemoveObject(c)
   776  	s.assertAddServiceUnitsBlocked(c, false)
   777  }
   778  
   779  func (s *clientSuite) TestBlockChangeAddServiceUnits(c *gc.C) {
   780  	s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   781  	s.blockAllChanges(c)
   782  	s.assertAddServiceUnitsBlocked(c, true)
   783  }
   784  
   785  func (s *clientSuite) TestClientAddUnitToMachineNotFound(c *gc.C) {
   786  	s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   787  	_, err := s.APIState.Client().AddServiceUnits("dummy", 1, "42")
   788  	c.Assert(err, gc.ErrorMatches, `cannot add units for service "dummy" to machine 42: machine 42 not found`)
   789  }
   790  
   791  func (s *clientSuite) TestClientCharmInfo(c *gc.C) {
   792  	var clientCharmInfoTests = []struct {
   793  		about           string
   794  		charm           string
   795  		url             string
   796  		expectedActions *charm.Actions
   797  		err             string
   798  	}{
   799  		{
   800  			about: "dummy charm which contains an expectedActions spec",
   801  			charm: "dummy",
   802  			url:   "local:quantal/dummy-1",
   803  			expectedActions: &charm.Actions{
   804  				ActionSpecs: map[string]charm.ActionSpec{
   805  					"snapshot": {
   806  						Description: "Take a snapshot of the database.",
   807  						Params: map[string]interface{}{
   808  							"type":        "object",
   809  							"title":       "snapshot",
   810  							"description": "Take a snapshot of the database.",
   811  							"properties": map[string]interface{}{
   812  								"outfile": map[string]interface{}{
   813  									"default":     "foo.bz2",
   814  									"description": "The file to write out to.",
   815  									"type":        "string",
   816  								},
   817  							},
   818  						},
   819  					},
   820  				},
   821  			},
   822  		},
   823  		{
   824  			about: "retrieves charm info",
   825  			// Use wordpress for tests so that we can compare Provides and Requires.
   826  			charm: "wordpress",
   827  			expectedActions: &charm.Actions{ActionSpecs: map[string]charm.ActionSpec{
   828  				"fakeaction": {
   829  					Description: "No description",
   830  					Params: map[string]interface{}{
   831  						"type":        "object",
   832  						"title":       "fakeaction",
   833  						"description": "No description",
   834  						"properties":  map[string]interface{}{},
   835  					},
   836  				},
   837  			}},
   838  			url: "local:quantal/wordpress-3",
   839  		},
   840  		{
   841  			about:           "invalid URL",
   842  			charm:           "wordpress",
   843  			expectedActions: &charm.Actions{ActionSpecs: nil},
   844  			url:             "not-valid",
   845  			err:             "charm url series is not resolved",
   846  		},
   847  		{
   848  			about:           "invalid schema",
   849  			charm:           "wordpress",
   850  			expectedActions: &charm.Actions{ActionSpecs: nil},
   851  			url:             "not-valid:your-arguments",
   852  			err:             `charm URL has invalid schema: "not-valid:your-arguments"`,
   853  		},
   854  		{
   855  			about:           "unknown charm",
   856  			charm:           "wordpress",
   857  			expectedActions: &charm.Actions{ActionSpecs: nil},
   858  			url:             "cs:missing/one-1",
   859  			err:             `charm "cs:missing/one-1" not found`,
   860  		},
   861  	}
   862  
   863  	for i, t := range clientCharmInfoTests {
   864  		c.Logf("test %d. %s", i, t.about)
   865  		charm := s.AddTestingCharm(c, t.charm)
   866  		info, err := s.APIState.Client().CharmInfo(t.url)
   867  		if t.err != "" {
   868  			c.Check(err, gc.ErrorMatches, t.err)
   869  			continue
   870  		}
   871  		c.Assert(err, jc.ErrorIsNil)
   872  		expected := &api.CharmInfo{
   873  			Revision: charm.Revision(),
   874  			URL:      charm.URL().String(),
   875  			Config:   charm.Config(),
   876  			Meta:     charm.Meta(),
   877  			Actions:  charm.Actions(),
   878  		}
   879  		c.Check(info, jc.DeepEquals, expected)
   880  		c.Check(info.Actions, jc.DeepEquals, t.expectedActions)
   881  	}
   882  }
   883  
   884  func (s *clientSuite) TestClientEnvironmentInfo(c *gc.C) {
   885  	conf, _ := s.State.EnvironConfig()
   886  	info, err := s.APIState.Client().EnvironmentInfo()
   887  	c.Assert(err, jc.ErrorIsNil)
   888  	env, err := s.State.Environment()
   889  	c.Assert(err, jc.ErrorIsNil)
   890  	c.Assert(info.DefaultSeries, gc.Equals, config.PreferredSeries(conf))
   891  	c.Assert(info.ProviderType, gc.Equals, conf.Type())
   892  	c.Assert(info.Name, gc.Equals, conf.Name())
   893  	c.Assert(info.UUID, gc.Equals, env.UUID())
   894  }
   895  
   896  var clientAnnotationsTests = []struct {
   897  	about    string
   898  	initial  map[string]string
   899  	input    map[string]string
   900  	expected map[string]string
   901  	err      string
   902  }{
   903  	{
   904  		about:    "test setting an annotation",
   905  		input:    map[string]string{"mykey": "myvalue"},
   906  		expected: map[string]string{"mykey": "myvalue"},
   907  	},
   908  	{
   909  		about:    "test setting multiple annotations",
   910  		input:    map[string]string{"key1": "value1", "key2": "value2"},
   911  		expected: map[string]string{"key1": "value1", "key2": "value2"},
   912  	},
   913  	{
   914  		about:    "test overriding annotations",
   915  		initial:  map[string]string{"mykey": "myvalue"},
   916  		input:    map[string]string{"mykey": "another-value"},
   917  		expected: map[string]string{"mykey": "another-value"},
   918  	},
   919  	{
   920  		about: "test setting an invalid annotation",
   921  		input: map[string]string{"invalid.key": "myvalue"},
   922  		err:   `cannot update annotations on .*: invalid key "invalid.key"`,
   923  	},
   924  }
   925  
   926  func (s *clientSuite) TestClientAnnotations(c *gc.C) {
   927  	// Set up entities.
   928  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   929  	unit, err := service.AddUnit()
   930  	c.Assert(err, jc.ErrorIsNil)
   931  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
   932  	c.Assert(err, jc.ErrorIsNil)
   933  	environment, err := s.State.Environment()
   934  	c.Assert(err, jc.ErrorIsNil)
   935  	type taggedAnnotator interface {
   936  		state.Entity
   937  	}
   938  	entities := []taggedAnnotator{service, unit, machine, environment}
   939  	for i, t := range clientAnnotationsTests {
   940  		for _, entity := range entities {
   941  			id := entity.Tag().String() // this is WRONG, it should be Tag().Id() but the code is wrong.
   942  			c.Logf("test %d. %s. entity %s", i, t.about, id)
   943  			// Set initial entity annotations.
   944  			err := s.APIState.Client().SetAnnotations(id, t.initial)
   945  			c.Assert(err, jc.ErrorIsNil)
   946  			// Add annotations using the API call.
   947  			err = s.APIState.Client().SetAnnotations(id, t.input)
   948  			if t.err != "" {
   949  				c.Assert(err, gc.ErrorMatches, t.err)
   950  				continue
   951  			}
   952  			// Retrieve annotations using the API call.
   953  			ann, err := s.APIState.Client().GetAnnotations(id)
   954  			c.Assert(err, jc.ErrorIsNil)
   955  			// Check annotations are correctly returned.
   956  			c.Assert(ann, gc.DeepEquals, t.input)
   957  			// Clean up annotations on the current entity.
   958  			cleanup := make(map[string]string)
   959  			for key := range ann {
   960  				cleanup[key] = ""
   961  			}
   962  			err = s.APIState.Client().SetAnnotations(id, cleanup)
   963  			c.Assert(err, jc.ErrorIsNil)
   964  		}
   965  	}
   966  }
   967  
   968  func (s *clientSuite) TestCharmAnnotationsUnsupported(c *gc.C) {
   969  	// Set up charm.
   970  	charm := s.AddTestingCharm(c, "dummy")
   971  	id := charm.Tag().Id()
   972  	for i, t := range clientAnnotationsTests {
   973  		c.Logf("test %d. %s. entity %s", i, t.about, id)
   974  		// Add annotations using the API call.
   975  		err := s.APIState.Client().SetAnnotations(id, t.input)
   976  		// Should not be able to annotate charm with this client
   977  		c.Assert(err.Error(), gc.Matches, ".*is not a valid tag.*")
   978  
   979  		// Retrieve annotations using the API call.
   980  		ann, err := s.APIState.Client().GetAnnotations(id)
   981  		// Should not be able to get annotations from charm using this client
   982  		c.Assert(err.Error(), gc.Matches, ".*is not a valid tag.*")
   983  		c.Assert(ann, gc.IsNil)
   984  	}
   985  }
   986  
   987  func (s *clientSuite) TestClientAnnotationsBadEntity(c *gc.C) {
   988  	bad := []string{"", "machine", "-foo", "foo-", "---", "machine-jim", "unit-123", "unit-foo", "service-", "service-foo/bar"}
   989  	expected := `".*" is not a valid( [a-z]+)? tag`
   990  	for _, id := range bad {
   991  		err := s.APIState.Client().SetAnnotations(id, map[string]string{"mykey": "myvalue"})
   992  		c.Assert(err, gc.ErrorMatches, expected)
   993  		_, err = s.APIState.Client().GetAnnotations(id)
   994  		c.Assert(err, gc.ErrorMatches, expected)
   995  	}
   996  }
   997  
   998  var serviceExposeTests = []struct {
   999  	about   string
  1000  	service string
  1001  	err     string
  1002  	exposed bool
  1003  }{
  1004  	{
  1005  		about:   "unknown service name",
  1006  		service: "unknown-service",
  1007  		err:     `service "unknown-service" not found`,
  1008  	},
  1009  	{
  1010  		about:   "expose a service",
  1011  		service: "dummy-service",
  1012  		exposed: true,
  1013  	},
  1014  	{
  1015  		about:   "expose an already exposed service",
  1016  		service: "exposed-service",
  1017  		exposed: true,
  1018  	},
  1019  }
  1020  
  1021  func (s *clientSuite) TestClientServiceExpose(c *gc.C) {
  1022  	charm := s.AddTestingCharm(c, "dummy")
  1023  	serviceNames := []string{"dummy-service", "exposed-service"}
  1024  	svcs := make([]*state.Service, len(serviceNames))
  1025  	var err error
  1026  	for i, name := range serviceNames {
  1027  		svcs[i] = s.AddTestingService(c, name, charm)
  1028  		c.Assert(svcs[i].IsExposed(), jc.IsFalse)
  1029  	}
  1030  	err = svcs[1].SetExposed()
  1031  	c.Assert(err, jc.ErrorIsNil)
  1032  	c.Assert(svcs[1].IsExposed(), jc.IsTrue)
  1033  	for i, t := range serviceExposeTests {
  1034  		c.Logf("test %d. %s", i, t.about)
  1035  		err = s.APIState.Client().ServiceExpose(t.service)
  1036  		if t.err != "" {
  1037  			c.Assert(err, gc.ErrorMatches, t.err)
  1038  		} else {
  1039  			c.Assert(err, jc.ErrorIsNil)
  1040  			service, err := s.State.Service(t.service)
  1041  			c.Assert(err, jc.ErrorIsNil)
  1042  			c.Assert(service.IsExposed(), gc.Equals, t.exposed)
  1043  		}
  1044  	}
  1045  }
  1046  
  1047  func (s *clientSuite) setupServiceExpose(c *gc.C) {
  1048  	charm := s.AddTestingCharm(c, "dummy")
  1049  	serviceNames := []string{"dummy-service", "exposed-service"}
  1050  	svcs := make([]*state.Service, len(serviceNames))
  1051  	var err error
  1052  	for i, name := range serviceNames {
  1053  		svcs[i] = s.AddTestingService(c, name, charm)
  1054  		c.Assert(svcs[i].IsExposed(), jc.IsFalse)
  1055  	}
  1056  	err = svcs[1].SetExposed()
  1057  	c.Assert(err, jc.ErrorIsNil)
  1058  	c.Assert(svcs[1].IsExposed(), jc.IsTrue)
  1059  }
  1060  
  1061  func (s *clientSuite) assertServiceExposeBlocked(c *gc.C, blocked bool) {
  1062  	for i, t := range serviceExposeTests {
  1063  		c.Logf("test %d. %s", i, t.about)
  1064  		err := s.APIState.Client().ServiceExpose(t.service)
  1065  		if blocked {
  1066  			c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
  1067  		} else {
  1068  			if t.err != "" {
  1069  				c.Assert(err, gc.ErrorMatches, t.err)
  1070  			} else {
  1071  				c.Assert(err, jc.ErrorIsNil)
  1072  				service, err := s.State.Service(t.service)
  1073  				c.Assert(err, jc.ErrorIsNil)
  1074  				c.Assert(service.IsExposed(), gc.Equals, t.exposed)
  1075  			}
  1076  		}
  1077  	}
  1078  }
  1079  
  1080  func (s *clientSuite) TestBlockDestroyServiceExpose(c *gc.C) {
  1081  	s.setupServiceExpose(c)
  1082  	s.blockDestroyEnvironment(c)
  1083  	s.assertServiceExposeBlocked(c, false)
  1084  }
  1085  
  1086  func (s *clientSuite) TestBlockRemoveServiceExpose(c *gc.C) {
  1087  	s.setupServiceExpose(c)
  1088  	s.blockRemoveObject(c)
  1089  	s.assertServiceExposeBlocked(c, false)
  1090  }
  1091  
  1092  func (s *clientSuite) TestBlockChangesServiceExpose(c *gc.C) {
  1093  	s.setupServiceExpose(c)
  1094  	s.blockAllChanges(c)
  1095  	s.assertServiceExposeBlocked(c, true)
  1096  }
  1097  
  1098  var serviceUnexposeTests = []struct {
  1099  	about    string
  1100  	service  string
  1101  	err      string
  1102  	initial  bool
  1103  	expected bool
  1104  }{
  1105  	{
  1106  		about:   "unknown service name",
  1107  		service: "unknown-service",
  1108  		err:     `service "unknown-service" not found`,
  1109  	},
  1110  	{
  1111  		about:    "unexpose a service",
  1112  		service:  "dummy-service",
  1113  		initial:  true,
  1114  		expected: false,
  1115  	},
  1116  	{
  1117  		about:    "unexpose an already unexposed service",
  1118  		service:  "dummy-service",
  1119  		initial:  false,
  1120  		expected: false,
  1121  	},
  1122  }
  1123  
  1124  func (s *clientSuite) TestClientServiceUnexpose(c *gc.C) {
  1125  	charm := s.AddTestingCharm(c, "dummy")
  1126  	for i, t := range serviceUnexposeTests {
  1127  		c.Logf("test %d. %s", i, t.about)
  1128  		svc := s.AddTestingService(c, "dummy-service", charm)
  1129  		if t.initial {
  1130  			svc.SetExposed()
  1131  		}
  1132  		c.Assert(svc.IsExposed(), gc.Equals, t.initial)
  1133  		err := s.APIState.Client().ServiceUnexpose(t.service)
  1134  		if t.err == "" {
  1135  			c.Assert(err, jc.ErrorIsNil)
  1136  			svc.Refresh()
  1137  			c.Assert(svc.IsExposed(), gc.Equals, t.expected)
  1138  		} else {
  1139  			c.Assert(err, gc.ErrorMatches, t.err)
  1140  		}
  1141  		err = svc.Destroy()
  1142  		c.Assert(err, jc.ErrorIsNil)
  1143  	}
  1144  }
  1145  
  1146  func (s *clientSuite) setupServiceUnexpose(c *gc.C) *state.Service {
  1147  	charm := s.AddTestingCharm(c, "dummy")
  1148  	svc := s.AddTestingService(c, "dummy-service", charm)
  1149  	svc.SetExposed()
  1150  	c.Assert(svc.IsExposed(), gc.Equals, true)
  1151  	return svc
  1152  }
  1153  
  1154  func (s *clientSuite) assertServiceUnexposeBlocked(c *gc.C, blocked bool, svc *state.Service) {
  1155  	err := s.APIState.Client().ServiceUnexpose("dummy-service")
  1156  	if blocked {
  1157  		c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
  1158  	} else {
  1159  		c.Assert(err, jc.ErrorIsNil)
  1160  		svc.Refresh()
  1161  		c.Assert(svc.IsExposed(), gc.Equals, false)
  1162  	}
  1163  	err = svc.Destroy()
  1164  	c.Assert(err, jc.ErrorIsNil)
  1165  }
  1166  
  1167  func (s *clientSuite) TestBlockDestroyServiceUnexpose(c *gc.C) {
  1168  	svc := s.setupServiceUnexpose(c)
  1169  	s.blockDestroyEnvironment(c)
  1170  	s.assertServiceUnexposeBlocked(c, false, svc)
  1171  }
  1172  
  1173  func (s *clientSuite) TestBlockRemoveServiceUnexpose(c *gc.C) {
  1174  	svc := s.setupServiceUnexpose(c)
  1175  	s.blockRemoveObject(c)
  1176  	s.assertServiceUnexposeBlocked(c, false, svc)
  1177  }
  1178  
  1179  func (s *clientSuite) TestBlockChangesServiceUnexpose(c *gc.C) {
  1180  	svc := s.setupServiceUnexpose(c)
  1181  	s.blockAllChanges(c)
  1182  	s.assertServiceUnexposeBlocked(c, true, svc)
  1183  }
  1184  
  1185  var serviceDestroyTests = []struct {
  1186  	about   string
  1187  	service string
  1188  	err     string
  1189  }{
  1190  	{
  1191  		about:   "unknown service name",
  1192  		service: "unknown-service",
  1193  		err:     `service "unknown-service" not found`,
  1194  	},
  1195  	{
  1196  		about:   "destroy a service",
  1197  		service: "dummy-service",
  1198  	},
  1199  	{
  1200  		about:   "destroy an already destroyed service",
  1201  		service: "dummy-service",
  1202  		err:     `service "dummy-service" not found`,
  1203  	},
  1204  }
  1205  
  1206  func (s *clientSuite) TestClientServiceDestroy(c *gc.C) {
  1207  	s.AddTestingService(c, "dummy-service", s.AddTestingCharm(c, "dummy"))
  1208  	for i, t := range serviceDestroyTests {
  1209  		c.Logf("test %d. %s", i, t.about)
  1210  		err := s.APIState.Client().ServiceDestroy(t.service)
  1211  		if t.err != "" {
  1212  			c.Assert(err, gc.ErrorMatches, t.err)
  1213  		} else {
  1214  			c.Assert(err, jc.ErrorIsNil)
  1215  		}
  1216  	}
  1217  
  1218  	// Now do ServiceDestroy on a service with units. Destroy will
  1219  	// cause the service to be not-Alive, but will not remove its
  1220  	// document.
  1221  	s.setUpScenario(c)
  1222  	serviceName := "wordpress"
  1223  	service, err := s.State.Service(serviceName)
  1224  	c.Assert(err, jc.ErrorIsNil)
  1225  	err = s.APIState.Client().ServiceDestroy(serviceName)
  1226  	c.Assert(err, jc.ErrorIsNil)
  1227  	err = service.Refresh()
  1228  	c.Assert(err, jc.ErrorIsNil)
  1229  	c.Assert(service.Life(), gc.Not(gc.Equals), state.Alive)
  1230  }
  1231  
  1232  func assertLife(c *gc.C, entity state.Living, life state.Life) {
  1233  	err := entity.Refresh()
  1234  	c.Assert(err, jc.ErrorIsNil)
  1235  	c.Assert(entity.Life(), gc.Equals, life)
  1236  }
  1237  
  1238  func assertRemoved(c *gc.C, entity state.Living) {
  1239  	err := entity.Refresh()
  1240  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1241  }
  1242  
  1243  func assertKill(c *gc.C, killer Killer) {
  1244  	c.Assert(killer.Kill(), gc.IsNil)
  1245  }
  1246  
  1247  func (s *clientSuite) setupDestroyMachinesTest(c *gc.C) (*state.Machine, *state.Machine, *state.Machine, *state.Unit) {
  1248  	m0, err := s.State.AddMachine("quantal", state.JobManageEnviron)
  1249  	c.Assert(err, jc.ErrorIsNil)
  1250  	m1, err := s.State.AddMachine("quantal", state.JobHostUnits)
  1251  	c.Assert(err, jc.ErrorIsNil)
  1252  	m2, err := s.State.AddMachine("quantal", state.JobHostUnits)
  1253  	c.Assert(err, jc.ErrorIsNil)
  1254  
  1255  	sch := s.AddTestingCharm(c, "wordpress")
  1256  	wordpress := s.AddTestingService(c, "wordpress", sch)
  1257  	u, err := wordpress.AddUnit()
  1258  	c.Assert(err, jc.ErrorIsNil)
  1259  	err = u.AssignToMachine(m1)
  1260  	c.Assert(err, jc.ErrorIsNil)
  1261  
  1262  	return m0, m1, m2, u
  1263  }
  1264  
  1265  func (s *clientSuite) TestDestroyMachines(c *gc.C) {
  1266  	m0, m1, m2, u := s.setupDestroyMachinesTest(c)
  1267  	s.assertDestroyMachineSuccess(c, u, m0, m1, m2)
  1268  }
  1269  
  1270  func (s *clientSuite) TestForceDestroyMachines(c *gc.C) {
  1271  	s.assertForceDestroyMachines(c)
  1272  }
  1273  
  1274  func (s *clientSuite) TestDestroyPrincipalUnits(c *gc.C) {
  1275  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1276  	units := make([]*state.Unit, 5)
  1277  	for i := range units {
  1278  		unit, err := wordpress.AddUnit()
  1279  		c.Assert(err, jc.ErrorIsNil)
  1280  		err = unit.SetStatus(state.StatusActive, "", nil)
  1281  		c.Assert(err, jc.ErrorIsNil)
  1282  		units[i] = unit
  1283  	}
  1284  	s.assertDestroyPrincipalUnits(c, units)
  1285  }
  1286  
  1287  func (s *clientSuite) TestDestroySubordinateUnits(c *gc.C) {
  1288  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1289  	wordpress0, err := wordpress.AddUnit()
  1290  	c.Assert(err, jc.ErrorIsNil)
  1291  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
  1292  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  1293  	c.Assert(err, jc.ErrorIsNil)
  1294  	rel, err := s.State.AddRelation(eps...)
  1295  	c.Assert(err, jc.ErrorIsNil)
  1296  	ru, err := rel.Unit(wordpress0)
  1297  	c.Assert(err, jc.ErrorIsNil)
  1298  	err = ru.EnterScope(nil)
  1299  	c.Assert(err, jc.ErrorIsNil)
  1300  	logging0, err := s.State.Unit("logging/0")
  1301  	c.Assert(err, jc.ErrorIsNil)
  1302  
  1303  	// Try to destroy the subordinate alone; check it fails.
  1304  	err = s.APIState.Client().DestroyServiceUnits("logging/0")
  1305  	c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`)
  1306  	assertLife(c, logging0, state.Alive)
  1307  
  1308  	s.assertDestroySubordinateUnits(c, wordpress0, logging0)
  1309  }
  1310  
  1311  func (s *clientSuite) testClientUnitResolved(c *gc.C, retry bool, expectedResolvedMode state.ResolvedMode) {
  1312  	// Setup:
  1313  	s.setUpScenario(c)
  1314  	u, err := s.State.Unit("wordpress/0")
  1315  	c.Assert(err, jc.ErrorIsNil)
  1316  	err = u.SetStatus(state.StatusError, "gaaah", nil)
  1317  	c.Assert(err, jc.ErrorIsNil)
  1318  	// Code under test:
  1319  	err = s.APIState.Client().Resolved("wordpress/0", retry)
  1320  	c.Assert(err, jc.ErrorIsNil)
  1321  	// Freshen the unit's state.
  1322  	err = u.Refresh()
  1323  	c.Assert(err, jc.ErrorIsNil)
  1324  	// And now the actual test assertions: we set the unit as resolved via
  1325  	// the API so it should have a resolved mode set.
  1326  	mode := u.Resolved()
  1327  	c.Assert(mode, gc.Equals, expectedResolvedMode)
  1328  }
  1329  
  1330  func (s *clientSuite) TestClientUnitResolved(c *gc.C) {
  1331  	s.testClientUnitResolved(c, false, state.ResolvedNoHooks)
  1332  }
  1333  
  1334  func (s *clientSuite) TestClientUnitResolvedRetry(c *gc.C) {
  1335  	s.testClientUnitResolved(c, true, state.ResolvedRetryHooks)
  1336  }
  1337  
  1338  func (s *clientSuite) setupResolved(c *gc.C) *state.Unit {
  1339  	s.setUpScenario(c)
  1340  	u, err := s.State.Unit("wordpress/0")
  1341  	c.Assert(err, jc.ErrorIsNil)
  1342  	err = u.SetStatus(state.StatusError, "gaaah", nil)
  1343  	c.Assert(err, jc.ErrorIsNil)
  1344  	return u
  1345  }
  1346  
  1347  func (s *clientSuite) assertResolvedBlocked(c *gc.C, blocked bool, u *state.Unit) {
  1348  	err := s.APIState.Client().Resolved("wordpress/0", true)
  1349  	if blocked {
  1350  		c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
  1351  	} else {
  1352  		c.Assert(err, jc.ErrorIsNil)
  1353  		// Freshen the unit's state.
  1354  		err = u.Refresh()
  1355  		c.Assert(err, jc.ErrorIsNil)
  1356  		// And now the actual test assertions: we set the unit as resolved via
  1357  		// the API so it should have a resolved mode set.
  1358  		mode := u.Resolved()
  1359  		c.Assert(mode, gc.Equals, state.ResolvedRetryHooks)
  1360  	}
  1361  }
  1362  
  1363  func (s *clientSuite) TestBlockDestroyUnitResolved(c *gc.C) {
  1364  	u := s.setupResolved(c)
  1365  	s.blockDestroyEnvironment(c)
  1366  	s.assertResolvedBlocked(c, false, u)
  1367  }
  1368  
  1369  func (s *clientSuite) TestBlockRemoveUnitResolved(c *gc.C) {
  1370  	u := s.setupResolved(c)
  1371  	s.blockRemoveObject(c)
  1372  	s.assertResolvedBlocked(c, false, u)
  1373  }
  1374  
  1375  func (s *clientSuite) TestBlockChangeUnitResolved(c *gc.C) {
  1376  	u := s.setupResolved(c)
  1377  	s.blockAllChanges(c)
  1378  	s.assertResolvedBlocked(c, true, u)
  1379  }
  1380  
  1381  func (s *clientSuite) TestClientServiceDeployCharmErrors(c *gc.C) {
  1382  	s.makeMockCharmStore()
  1383  	for url, expect := range map[string]string{
  1384  		"wordpress":                   "charm url series is not resolved",
  1385  		"cs:wordpress":                "charm url series is not resolved",
  1386  		"cs:precise/wordpress":        "charm url must include revision",
  1387  		"cs:precise/wordpress-999999": `cannot download charm ".*": charm not found in mock store: cs:precise/wordpress-999999`,
  1388  	} {
  1389  		c.Logf("test %s", url)
  1390  		err := s.APIState.Client().ServiceDeploy(
  1391  			url, "service", 1, "", constraints.Value{}, "",
  1392  		)
  1393  		c.Check(err, gc.ErrorMatches, expect)
  1394  		_, err = s.State.Service("service")
  1395  		c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1396  	}
  1397  }
  1398  
  1399  func (s *clientSuite) TestClientServiceDeployWithNetworks(c *gc.C) {
  1400  	s.makeMockCharmStore()
  1401  	curl, bundle := addCharm(c, "dummy")
  1402  	cons := constraints.MustParse("mem=4G networks=^net3")
  1403  
  1404  	// Check for invalid network tags handling.
  1405  	err := s.APIState.Client().ServiceDeployWithNetworks(
  1406  		curl.String(), "service", 3, "", cons, "",
  1407  		[]string{"net1", "net2"},
  1408  		nil,
  1409  	)
  1410  	c.Assert(err, gc.ErrorMatches, `"net1" is not a valid tag`)
  1411  
  1412  	err = s.APIState.Client().ServiceDeployWithNetworks(
  1413  		curl.String(), "service", 3, "", cons, "",
  1414  		[]string{"network-net1", "network-net2"},
  1415  		nil,
  1416  	)
  1417  	c.Assert(err, jc.ErrorIsNil)
  1418  	service := s.assertPrincipalDeployed(c, "service", curl, false, bundle, cons)
  1419  
  1420  	networks, err := service.Networks()
  1421  	c.Assert(err, jc.ErrorIsNil)
  1422  	c.Assert(networks, gc.DeepEquals, []string{"net1", "net2"})
  1423  	serviceCons, err := service.Constraints()
  1424  	c.Assert(err, jc.ErrorIsNil)
  1425  	c.Assert(serviceCons, gc.DeepEquals, cons)
  1426  }
  1427  
  1428  func (s *clientSuite) TestClientServiceDeployWithStorage(c *gc.C) {
  1429  	s.PatchEnvironment(osenv.JujuFeatureFlagEnvKey, "storage")
  1430  	featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey)
  1431  	s.testClientServiceDeployWithStorage(c, true)
  1432  }
  1433  
  1434  func (s *clientSuite) TestClientServiceDeployWithStorageWithoutFeature(c *gc.C) {
  1435  	s.testClientServiceDeployWithStorage(c, false)
  1436  }
  1437  
  1438  func (s *clientSuite) testClientServiceDeployWithStorage(c *gc.C, expectConstraints bool) {
  1439  	s.makeMockCharmStore()
  1440  	curl, bundle := addCharm(c, "storage-block")
  1441  	storageConstraints := map[string]storage.Constraints{
  1442  		"data": {
  1443  			Count: 1,
  1444  			Size:  1024,
  1445  		},
  1446  	}
  1447  
  1448  	var cons constraints.Value
  1449  	err := s.APIState.Client().ServiceDeployWithNetworks(
  1450  		curl.String(), "service", 1, "", cons, "", nil,
  1451  		storageConstraints,
  1452  	)
  1453  	c.Assert(err, jc.ErrorIsNil)
  1454  	service := s.assertPrincipalDeployed(c, "service", curl, false, bundle, cons)
  1455  	storageConstraintsOut, err := service.StorageConstraints()
  1456  	c.Assert(err, jc.ErrorIsNil)
  1457  
  1458  	if expectConstraints {
  1459  		c.Assert(storageConstraintsOut, gc.DeepEquals, map[string]state.StorageConstraints{
  1460  			"data": {
  1461  				Count: 1,
  1462  				Size:  1024,
  1463  			},
  1464  		})
  1465  	} else {
  1466  		c.Assert(storageConstraintsOut, gc.HasLen, 0)
  1467  	}
  1468  }
  1469  
  1470  func (s *clientSuite) setupServiceDeploy(c *gc.C, args string) (*charm.URL, charm.Charm, constraints.Value) {
  1471  	s.makeMockCharmStore()
  1472  	curl, bundle := addCharm(c, "dummy")
  1473  	cons := constraints.MustParse(args)
  1474  	return curl, bundle, cons
  1475  }
  1476  
  1477  func (s *clientSuite) assertServiceDeployWithNetworksBlocked(c *gc.C, blocked bool, curl *charm.URL, bundle charm.Charm, cons constraints.Value) {
  1478  	err := s.APIState.Client().ServiceDeployWithNetworks(
  1479  		curl.String(), "service", 3, "", cons, "",
  1480  		[]string{"network-net1", "network-net2"},
  1481  		nil,
  1482  	)
  1483  	if blocked {
  1484  		c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
  1485  	} else {
  1486  		c.Assert(err, jc.ErrorIsNil)
  1487  		service := s.assertPrincipalDeployed(c, "service", curl, false, bundle, cons)
  1488  		networks, err := service.Networks()
  1489  		c.Assert(err, jc.ErrorIsNil)
  1490  		c.Assert(networks, gc.DeepEquals, []string{"net1", "net2"})
  1491  		serviceCons, err := service.Constraints()
  1492  		c.Assert(err, jc.ErrorIsNil)
  1493  		c.Assert(serviceCons, gc.DeepEquals, cons)
  1494  	}
  1495  }
  1496  
  1497  func (s *clientSuite) TestBlockDestroyServiceDeployWithNetworks(c *gc.C) {
  1498  	curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G networks=^net3")
  1499  	s.blockDestroyEnvironment(c)
  1500  	s.assertServiceDeployWithNetworksBlocked(c, false, curl, bundle, cons)
  1501  }
  1502  
  1503  func (s *clientSuite) TestBlockRemoveServiceDeployWithNetworks(c *gc.C) {
  1504  	curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G networks=^net3")
  1505  	s.blockRemoveObject(c)
  1506  	s.assertServiceDeployWithNetworksBlocked(c, false, curl, bundle, cons)
  1507  }
  1508  
  1509  func (s *clientSuite) TestBlockChangeServiceDeployWithNetworks(c *gc.C) {
  1510  	curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G networks=^net3")
  1511  	s.blockAllChanges(c)
  1512  	s.assertServiceDeployWithNetworksBlocked(c, true, curl, bundle, cons)
  1513  }
  1514  
  1515  func (s *clientSuite) assertPrincipalDeployed(c *gc.C, serviceName string, curl *charm.URL, forced bool, bundle charm.Charm, cons constraints.Value) *state.Service {
  1516  	service, err := s.State.Service(serviceName)
  1517  	c.Assert(err, jc.ErrorIsNil)
  1518  	charm, force, err := service.Charm()
  1519  	c.Assert(err, jc.ErrorIsNil)
  1520  	c.Assert(force, gc.Equals, forced)
  1521  	c.Assert(charm.URL(), gc.DeepEquals, curl)
  1522  	c.Assert(charm.Meta(), gc.DeepEquals, bundle.Meta())
  1523  	c.Assert(charm.Config(), gc.DeepEquals, bundle.Config())
  1524  
  1525  	serviceCons, err := service.Constraints()
  1526  	c.Assert(err, jc.ErrorIsNil)
  1527  	c.Assert(serviceCons, gc.DeepEquals, cons)
  1528  	units, err := service.AllUnits()
  1529  	c.Assert(err, jc.ErrorIsNil)
  1530  	for _, unit := range units {
  1531  		mid, err := unit.AssignedMachineId()
  1532  		c.Assert(err, jc.ErrorIsNil)
  1533  		machine, err := s.State.Machine(mid)
  1534  		c.Assert(err, jc.ErrorIsNil)
  1535  		machineCons, err := machine.Constraints()
  1536  		c.Assert(err, jc.ErrorIsNil)
  1537  		c.Assert(machineCons, gc.DeepEquals, cons)
  1538  	}
  1539  	return service
  1540  }
  1541  
  1542  func (s *clientSuite) TestClientServiceDeployPrincipal(c *gc.C) {
  1543  	// TODO(fwereade): test ToMachineSpec directly on srvClient, when we
  1544  	// manage to extract it as a package and can thus do it conveniently.
  1545  	s.makeMockCharmStore()
  1546  	curl, bundle := addCharm(c, "dummy")
  1547  	mem4g := constraints.MustParse("mem=4G")
  1548  	err := s.APIState.Client().ServiceDeploy(
  1549  		curl.String(), "service", 3, "", mem4g, "",
  1550  	)
  1551  	c.Assert(err, jc.ErrorIsNil)
  1552  	s.assertPrincipalDeployed(c, "service", curl, false, bundle, mem4g)
  1553  }
  1554  
  1555  func (s *clientSuite) assertServiceDeployPrincipalBlocked(c *gc.C, blocked bool, curl *charm.URL, bundle charm.Charm, mem4g constraints.Value) {
  1556  	err := s.APIState.Client().ServiceDeploy(
  1557  		curl.String(), "service", 3, "", mem4g, "",
  1558  	)
  1559  	if blocked {
  1560  		c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
  1561  	} else {
  1562  		c.Assert(err, jc.ErrorIsNil)
  1563  		s.assertPrincipalDeployed(c, "service", curl, false, bundle, mem4g)
  1564  	}
  1565  }
  1566  
  1567  func (s *clientSuite) TestBlockDestroyServiceDeployPrincipal(c *gc.C) {
  1568  	curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G")
  1569  	s.blockDestroyEnvironment(c)
  1570  	s.assertServiceDeployPrincipalBlocked(c, false, curl, bundle, cons)
  1571  }
  1572  
  1573  func (s *clientSuite) TestBlockRemoveServiceDeployPrincipal(c *gc.C) {
  1574  	curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G")
  1575  	s.blockRemoveObject(c)
  1576  	s.assertServiceDeployPrincipalBlocked(c, false, curl, bundle, cons)
  1577  }
  1578  
  1579  func (s *clientSuite) TestBlockChangesServiceDeployPrincipal(c *gc.C) {
  1580  	curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G")
  1581  	s.blockAllChanges(c)
  1582  	s.assertServiceDeployPrincipalBlocked(c, true, curl, bundle, cons)
  1583  }
  1584  
  1585  func (s *clientSuite) TestClientServiceDeploySubordinate(c *gc.C) {
  1586  	s.makeMockCharmStore()
  1587  	curl, bundle := addCharm(c, "logging")
  1588  	err := s.APIState.Client().ServiceDeploy(
  1589  		curl.String(), "service-name", 0, "", constraints.Value{}, "",
  1590  	)
  1591  	service, err := s.State.Service("service-name")
  1592  	c.Assert(err, jc.ErrorIsNil)
  1593  	charm, force, err := service.Charm()
  1594  	c.Assert(err, jc.ErrorIsNil)
  1595  	c.Assert(force, jc.IsFalse)
  1596  	c.Assert(charm.URL(), gc.DeepEquals, curl)
  1597  	c.Assert(charm.Meta(), gc.DeepEquals, bundle.Meta())
  1598  	c.Assert(charm.Config(), gc.DeepEquals, bundle.Config())
  1599  
  1600  	units, err := service.AllUnits()
  1601  	c.Assert(err, jc.ErrorIsNil)
  1602  	c.Assert(units, gc.HasLen, 0)
  1603  }
  1604  
  1605  func (s *clientSuite) TestClientServiceDeployConfig(c *gc.C) {
  1606  	// TODO(fwereade): test Config/ConfigYAML handling directly on srvClient.
  1607  	// Can't be done cleanly until it's extracted similarly to Machiner.
  1608  	s.makeMockCharmStore()
  1609  	curl, _ := addCharm(c, "dummy")
  1610  	err := s.APIState.Client().ServiceDeploy(
  1611  		curl.String(), "service-name", 1, "service-name:\n  username: fred", constraints.Value{}, "",
  1612  	)
  1613  	c.Assert(err, jc.ErrorIsNil)
  1614  	service, err := s.State.Service("service-name")
  1615  	c.Assert(err, jc.ErrorIsNil)
  1616  	settings, err := service.ConfigSettings()
  1617  	c.Assert(err, jc.ErrorIsNil)
  1618  	c.Assert(settings, gc.DeepEquals, charm.Settings{"username": "fred"})
  1619  }
  1620  
  1621  func (s *clientSuite) TestClientServiceDeployConfigError(c *gc.C) {
  1622  	// TODO(fwereade): test Config/ConfigYAML handling directly on srvClient.
  1623  	// Can't be done cleanly until it's extracted similarly to Machiner.
  1624  	s.makeMockCharmStore()
  1625  	curl, _ := addCharm(c, "dummy")
  1626  	err := s.APIState.Client().ServiceDeploy(
  1627  		curl.String(), "service-name", 1, "service-name:\n  skill-level: fred", constraints.Value{}, "",
  1628  	)
  1629  	c.Assert(err, gc.ErrorMatches, `option "skill-level" expected int, got "fred"`)
  1630  	_, err = s.State.Service("service-name")
  1631  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1632  }
  1633  
  1634  func (s *clientSuite) TestClientServiceDeployToMachine(c *gc.C) {
  1635  	s.makeMockCharmStore()
  1636  	curl, bundle := addCharm(c, "dummy")
  1637  
  1638  	machine, err := s.State.AddMachine("precise", state.JobHostUnits)
  1639  	c.Assert(err, jc.ErrorIsNil)
  1640  	err = s.APIState.Client().ServiceDeploy(
  1641  		curl.String(), "service-name", 1, "service-name:\n  username: fred", constraints.Value{}, machine.Id(),
  1642  	)
  1643  	c.Assert(err, jc.ErrorIsNil)
  1644  
  1645  	service, err := s.State.Service("service-name")
  1646  	c.Assert(err, jc.ErrorIsNil)
  1647  	charm, force, err := service.Charm()
  1648  	c.Assert(err, jc.ErrorIsNil)
  1649  	c.Assert(force, jc.IsFalse)
  1650  	c.Assert(charm.URL(), gc.DeepEquals, curl)
  1651  	c.Assert(charm.Meta(), gc.DeepEquals, bundle.Meta())
  1652  	c.Assert(charm.Config(), gc.DeepEquals, bundle.Config())
  1653  
  1654  	units, err := service.AllUnits()
  1655  	c.Assert(err, jc.ErrorIsNil)
  1656  	c.Assert(units, gc.HasLen, 1)
  1657  	mid, err := units[0].AssignedMachineId()
  1658  	c.Assert(err, jc.ErrorIsNil)
  1659  	c.Assert(mid, gc.Equals, machine.Id())
  1660  }
  1661  
  1662  func (s *clientSuite) TestClientServiceDeployToMachineNotFound(c *gc.C) {
  1663  	err := s.APIState.Client().ServiceDeploy(
  1664  		"cs:precise/service-name-1", "service-name", 1, "", constraints.Value{}, "42",
  1665  	)
  1666  	c.Assert(err, gc.ErrorMatches, `cannot deploy "service-name" to machine 42: machine 42 not found`)
  1667  
  1668  	_, err = s.State.Service("service-name")
  1669  	c.Assert(err, gc.ErrorMatches, `service "service-name" not found`)
  1670  }
  1671  
  1672  func (s *clientSuite) TestClientServiceDeployServiceOwner(c *gc.C) {
  1673  	s.makeMockCharmStore()
  1674  	curl, _ := addCharm(c, "dummy")
  1675  
  1676  	user := s.Factory.MakeUser(c, &factory.UserParams{Password: "password"})
  1677  	s.APIState = s.OpenAPIAs(c, user.Tag(), "password")
  1678  
  1679  	err := s.APIState.Client().ServiceDeploy(
  1680  		curl.String(), "service", 3, "", constraints.Value{}, "",
  1681  	)
  1682  	c.Assert(err, jc.ErrorIsNil)
  1683  
  1684  	service, err := s.State.Service("service")
  1685  	c.Assert(err, jc.ErrorIsNil)
  1686  	c.Assert(service.GetOwnerTag(), gc.Equals, user.Tag().String())
  1687  }
  1688  
  1689  func (s *clientSuite) deployServiceForTests(c *gc.C) {
  1690  	curl, _ := addCharm(c, "dummy")
  1691  	err := s.APIState.Client().ServiceDeploy(curl.String(),
  1692  		"service", 1, "", constraints.Value{}, "",
  1693  	)
  1694  	c.Assert(err, jc.ErrorIsNil)
  1695  }
  1696  
  1697  func (s *clientSuite) checkClientServiceUpdateSetCharm(c *gc.C, forceCharmUrl bool) {
  1698  	s.makeMockCharmStore()
  1699  	s.deployServiceForTests(c)
  1700  	addCharm(c, "wordpress")
  1701  
  1702  	// Update the charm for the service.
  1703  	args := params.ServiceUpdate{
  1704  		ServiceName:   "service",
  1705  		CharmUrl:      "cs:precise/wordpress-3",
  1706  		ForceCharmUrl: forceCharmUrl,
  1707  	}
  1708  	err := s.APIState.Client().ServiceUpdate(args)
  1709  	c.Assert(err, jc.ErrorIsNil)
  1710  
  1711  	// Ensure the charm has been updated and and the force flag correctly set.
  1712  	service, err := s.State.Service("service")
  1713  	c.Assert(err, jc.ErrorIsNil)
  1714  	ch, force, err := service.Charm()
  1715  	c.Assert(err, jc.ErrorIsNil)
  1716  	c.Assert(ch.URL().String(), gc.Equals, "cs:precise/wordpress-3")
  1717  	c.Assert(force, gc.Equals, forceCharmUrl)
  1718  }
  1719  
  1720  func (s *clientSuite) TestClientServiceUpdateSetCharm(c *gc.C) {
  1721  	s.checkClientServiceUpdateSetCharm(c, false)
  1722  }
  1723  
  1724  func (s *clientSuite) TestBlockDestroyServiceUpdate(c *gc.C) {
  1725  	s.blockDestroyEnvironment(c)
  1726  	s.checkClientServiceUpdateSetCharm(c, false)
  1727  }
  1728  
  1729  func (s *clientSuite) TestBlockRemoveServiceUpdate(c *gc.C) {
  1730  	s.blockRemoveObject(c)
  1731  	s.checkClientServiceUpdateSetCharm(c, false)
  1732  }
  1733  
  1734  func (s *clientSuite) setupServiceUpdate(c *gc.C) {
  1735  	s.makeMockCharmStore()
  1736  	s.deployServiceForTests(c)
  1737  	addCharm(c, "wordpress")
  1738  }
  1739  
  1740  func (s *clientSuite) TestBlockChangeServiceUpdate(c *gc.C) {
  1741  	s.setupServiceUpdate(c)
  1742  	s.blockAllChanges(c)
  1743  	// Update the charm for the service.
  1744  	args := params.ServiceUpdate{
  1745  		ServiceName:   "service",
  1746  		CharmUrl:      "cs:precise/wordpress-3",
  1747  		ForceCharmUrl: false,
  1748  	}
  1749  	err := s.APIState.Client().ServiceUpdate(args)
  1750  	c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
  1751  }
  1752  
  1753  func (s *clientSuite) TestClientServiceUpdateForceSetCharm(c *gc.C) {
  1754  	s.checkClientServiceUpdateSetCharm(c, true)
  1755  }
  1756  
  1757  func (s *clientSuite) TestBlockServiceUpdateForced(c *gc.C) {
  1758  	s.setupServiceUpdate(c)
  1759  
  1760  	// block all changes. Force should ignore block :)
  1761  	s.blockAllChanges(c)
  1762  	s.blockDestroyEnvironment(c)
  1763  	s.blockRemoveObject(c)
  1764  
  1765  	// Update the charm for the service.
  1766  	args := params.ServiceUpdate{
  1767  		ServiceName:   "service",
  1768  		CharmUrl:      "cs:precise/wordpress-3",
  1769  		ForceCharmUrl: true,
  1770  	}
  1771  	err := s.APIState.Client().ServiceUpdate(args)
  1772  	c.Assert(err, jc.ErrorIsNil)
  1773  
  1774  	// Ensure the charm has been updated and and the force flag correctly set.
  1775  	service, err := s.State.Service("service")
  1776  	c.Assert(err, jc.ErrorIsNil)
  1777  	ch, force, err := service.Charm()
  1778  	c.Assert(err, jc.ErrorIsNil)
  1779  	c.Assert(ch.URL().String(), gc.Equals, "cs:precise/wordpress-3")
  1780  	c.Assert(force, jc.IsTrue)
  1781  }
  1782  
  1783  func (s *clientSuite) TestClientServiceUpdateSetCharmErrors(c *gc.C) {
  1784  	s.makeMockCharmStore()
  1785  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1786  	for charmUrl, expect := range map[string]string{
  1787  		"wordpress":                   "charm url series is not resolved",
  1788  		"cs:wordpress":                "charm url series is not resolved",
  1789  		"cs:precise/wordpress":        "charm url must include revision",
  1790  		"cs:precise/wordpress-999999": `cannot download charm ".*": charm not found in mock store: cs:precise/wordpress-999999`,
  1791  	} {
  1792  		c.Logf("test %s", charmUrl)
  1793  		args := params.ServiceUpdate{
  1794  			ServiceName: "wordpress",
  1795  			CharmUrl:    charmUrl,
  1796  		}
  1797  		err := s.APIState.Client().ServiceUpdate(args)
  1798  		c.Check(err, gc.ErrorMatches, expect)
  1799  	}
  1800  }
  1801  
  1802  func (s *clientSuite) TestClientServiceUpdateSetMinUnits(c *gc.C) {
  1803  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1804  
  1805  	// Set minimum units for the service.
  1806  	minUnits := 2
  1807  	args := params.ServiceUpdate{
  1808  		ServiceName: "dummy",
  1809  		MinUnits:    &minUnits,
  1810  	}
  1811  	err := s.APIState.Client().ServiceUpdate(args)
  1812  	c.Assert(err, jc.ErrorIsNil)
  1813  
  1814  	// Ensure the minimum number of units has been set.
  1815  	c.Assert(service.Refresh(), gc.IsNil)
  1816  	c.Assert(service.MinUnits(), gc.Equals, minUnits)
  1817  }
  1818  
  1819  func (s *clientSuite) TestClientServiceUpdateSetMinUnitsError(c *gc.C) {
  1820  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1821  
  1822  	// Set a negative minimum number of units for the service.
  1823  	minUnits := -1
  1824  	args := params.ServiceUpdate{
  1825  		ServiceName: "dummy",
  1826  		MinUnits:    &minUnits,
  1827  	}
  1828  	err := s.APIState.Client().ServiceUpdate(args)
  1829  	c.Assert(err, gc.ErrorMatches,
  1830  		`cannot set minimum units for service "dummy": cannot set a negative minimum number of units`)
  1831  
  1832  	// Ensure the minimum number of units has not been set.
  1833  	c.Assert(service.Refresh(), gc.IsNil)
  1834  	c.Assert(service.MinUnits(), gc.Equals, 0)
  1835  }
  1836  
  1837  func (s *clientSuite) TestClientServiceUpdateSetSettingsStrings(c *gc.C) {
  1838  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1839  
  1840  	// Update settings for the service.
  1841  	args := params.ServiceUpdate{
  1842  		ServiceName:     "dummy",
  1843  		SettingsStrings: map[string]string{"title": "s-title", "username": "s-user"},
  1844  	}
  1845  	err := s.APIState.Client().ServiceUpdate(args)
  1846  	c.Assert(err, jc.ErrorIsNil)
  1847  
  1848  	// Ensure the settings have been correctly updated.
  1849  	expected := charm.Settings{"title": "s-title", "username": "s-user"}
  1850  	obtained, err := service.ConfigSettings()
  1851  	c.Assert(err, jc.ErrorIsNil)
  1852  	c.Assert(obtained, gc.DeepEquals, expected)
  1853  }
  1854  
  1855  func (s *clientSuite) TestClientServiceUpdateSetSettingsYAML(c *gc.C) {
  1856  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1857  
  1858  	// Update settings for the service.
  1859  	args := params.ServiceUpdate{
  1860  		ServiceName:  "dummy",
  1861  		SettingsYAML: "dummy:\n  title: y-title\n  username: y-user",
  1862  	}
  1863  	err := s.APIState.Client().ServiceUpdate(args)
  1864  	c.Assert(err, jc.ErrorIsNil)
  1865  
  1866  	// Ensure the settings have been correctly updated.
  1867  	expected := charm.Settings{"title": "y-title", "username": "y-user"}
  1868  	obtained, err := service.ConfigSettings()
  1869  	c.Assert(err, jc.ErrorIsNil)
  1870  	c.Assert(obtained, gc.DeepEquals, expected)
  1871  }
  1872  
  1873  func (s *clientSuite) TestClientServiceUpdateSetConstraints(c *gc.C) {
  1874  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  1875  
  1876  	// Update constraints for the service.
  1877  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
  1878  	c.Assert(err, jc.ErrorIsNil)
  1879  	args := params.ServiceUpdate{
  1880  		ServiceName: "dummy",
  1881  		Constraints: &cons,
  1882  	}
  1883  	err = s.APIState.Client().ServiceUpdate(args)
  1884  	c.Assert(err, jc.ErrorIsNil)
  1885  
  1886  	// Ensure the constraints have been correctly updated.
  1887  	obtained, err := service.Constraints()
  1888  	c.Assert(err, jc.ErrorIsNil)
  1889  	c.Assert(obtained, gc.DeepEquals, cons)
  1890  }
  1891  
  1892  func (s *clientSuite) TestClientServiceUpdateAllParams(c *gc.C) {
  1893  	s.makeMockCharmStore()
  1894  	s.deployServiceForTests(c)
  1895  	addCharm(c, "wordpress")
  1896  
  1897  	// Update all the service attributes.
  1898  	minUnits := 3
  1899  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
  1900  	c.Assert(err, jc.ErrorIsNil)
  1901  	args := params.ServiceUpdate{
  1902  		ServiceName:     "service",
  1903  		CharmUrl:        "cs:precise/wordpress-3",
  1904  		ForceCharmUrl:   true,
  1905  		MinUnits:        &minUnits,
  1906  		SettingsStrings: map[string]string{"blog-title": "string-title"},
  1907  		SettingsYAML:    "service:\n  blog-title: yaml-title\n",
  1908  		Constraints:     &cons,
  1909  	}
  1910  	err = s.APIState.Client().ServiceUpdate(args)
  1911  	c.Assert(err, jc.ErrorIsNil)
  1912  
  1913  	// Ensure the service has been correctly updated.
  1914  	service, err := s.State.Service("service")
  1915  	c.Assert(err, jc.ErrorIsNil)
  1916  
  1917  	// Check the charm.
  1918  	ch, force, err := service.Charm()
  1919  	c.Assert(err, jc.ErrorIsNil)
  1920  	c.Assert(ch.URL().String(), gc.Equals, "cs:precise/wordpress-3")
  1921  	c.Assert(force, jc.IsTrue)
  1922  
  1923  	// Check the minimum number of units.
  1924  	c.Assert(service.MinUnits(), gc.Equals, minUnits)
  1925  
  1926  	// Check the settings: also ensure the YAML settings take precedence
  1927  	// over strings ones.
  1928  	expectedSettings := charm.Settings{"blog-title": "yaml-title"}
  1929  	obtainedSettings, err := service.ConfigSettings()
  1930  	c.Assert(err, jc.ErrorIsNil)
  1931  	c.Assert(obtainedSettings, gc.DeepEquals, expectedSettings)
  1932  
  1933  	// Check the constraints.
  1934  	obtainedConstraints, err := service.Constraints()
  1935  	c.Assert(err, jc.ErrorIsNil)
  1936  	c.Assert(obtainedConstraints, gc.DeepEquals, cons)
  1937  }
  1938  
  1939  func (s *clientSuite) TestClientServiceUpdateNoParams(c *gc.C) {
  1940  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1941  
  1942  	// Calling ServiceUpdate with no parameters set is a no-op.
  1943  	args := params.ServiceUpdate{ServiceName: "wordpress"}
  1944  	err := s.APIState.Client().ServiceUpdate(args)
  1945  	c.Assert(err, jc.ErrorIsNil)
  1946  }
  1947  
  1948  func (s *clientSuite) TestClientServiceUpdateNoService(c *gc.C) {
  1949  	err := s.APIState.Client().ServiceUpdate(params.ServiceUpdate{})
  1950  	c.Assert(err, gc.ErrorMatches, `"" is not a valid service name`)
  1951  }
  1952  
  1953  func (s *clientSuite) TestClientServiceUpdateInvalidService(c *gc.C) {
  1954  	args := params.ServiceUpdate{ServiceName: "no-such-service"}
  1955  	err := s.APIState.Client().ServiceUpdate(args)
  1956  	c.Assert(err, gc.ErrorMatches, `service "no-such-service" not found`)
  1957  }
  1958  
  1959  func (s *clientSuite) TestClientServiceSetCharm(c *gc.C) {
  1960  	s.makeMockCharmStore()
  1961  	curl, _ := addCharm(c, "dummy")
  1962  	err := s.APIState.Client().ServiceDeploy(
  1963  		curl.String(), "service", 3, "", constraints.Value{}, "",
  1964  	)
  1965  	c.Assert(err, jc.ErrorIsNil)
  1966  	addCharm(c, "wordpress")
  1967  	err = s.APIState.Client().ServiceSetCharm(
  1968  		"service", "cs:precise/wordpress-3", false,
  1969  	)
  1970  	c.Assert(err, jc.ErrorIsNil)
  1971  
  1972  	// Ensure that the charm is not marked as forced.
  1973  	service, err := s.State.Service("service")
  1974  	c.Assert(err, jc.ErrorIsNil)
  1975  	charm, force, err := service.Charm()
  1976  	c.Assert(err, jc.ErrorIsNil)
  1977  	c.Assert(charm.URL().String(), gc.Equals, "cs:precise/wordpress-3")
  1978  	c.Assert(force, jc.IsFalse)
  1979  }
  1980  
  1981  func (s *clientSuite) setupServiceSetCharm(c *gc.C) {
  1982  	s.makeMockCharmStore()
  1983  	curl, _ := addCharm(c, "dummy")
  1984  	err := s.APIState.Client().ServiceDeploy(
  1985  		curl.String(), "service", 3, "", constraints.Value{}, "",
  1986  	)
  1987  	c.Assert(err, jc.ErrorIsNil)
  1988  	addCharm(c, "wordpress")
  1989  }
  1990  
  1991  func (s *clientSuite) assertServiceSetCharmBlocked(c *gc.C, blocked bool, force bool) {
  1992  	err := s.APIState.Client().ServiceSetCharm(
  1993  		"service", "cs:precise/wordpress-3", force,
  1994  	)
  1995  	if blocked {
  1996  		c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
  1997  	} else {
  1998  		c.Assert(err, jc.ErrorIsNil)
  1999  		// Ensure that the charm is not marked as forced.
  2000  		service, err := s.State.Service("service")
  2001  		c.Assert(err, jc.ErrorIsNil)
  2002  		charm, _, err := service.Charm()
  2003  		c.Assert(err, jc.ErrorIsNil)
  2004  		c.Assert(charm.URL().String(), gc.Equals, "cs:precise/wordpress-3")
  2005  	}
  2006  }
  2007  
  2008  func (s *clientSuite) TestBlockDestroyServiceSetCharm(c *gc.C) {
  2009  	s.setupServiceSetCharm(c)
  2010  	s.blockDestroyEnvironment(c)
  2011  	s.assertServiceSetCharmBlocked(c, false, false)
  2012  }
  2013  
  2014  func (s *clientSuite) TestBlockRemoveServiceSetCharm(c *gc.C) {
  2015  	s.setupServiceSetCharm(c)
  2016  	s.blockRemoveObject(c)
  2017  	s.assertServiceSetCharmBlocked(c, false, false)
  2018  }
  2019  
  2020  func (s *clientSuite) TestBlockChangesServiceSetCharm(c *gc.C) {
  2021  	s.setupServiceSetCharm(c)
  2022  	s.blockAllChanges(c)
  2023  	s.assertServiceSetCharmBlocked(c, true, false)
  2024  }
  2025  
  2026  func (s *clientSuite) TestClientServiceSetCharmForce(c *gc.C) {
  2027  	s.makeMockCharmStore()
  2028  	curl, _ := addCharm(c, "dummy")
  2029  	err := s.APIState.Client().ServiceDeploy(
  2030  		curl.String(), "service", 3, "", constraints.Value{}, "",
  2031  	)
  2032  	c.Assert(err, jc.ErrorIsNil)
  2033  	addCharm(c, "wordpress")
  2034  	err = s.APIState.Client().ServiceSetCharm(
  2035  		"service", "cs:precise/wordpress-3", true,
  2036  	)
  2037  	c.Assert(err, jc.ErrorIsNil)
  2038  
  2039  	// Ensure that the charm is marked as forced.
  2040  	service, err := s.State.Service("service")
  2041  	c.Assert(err, jc.ErrorIsNil)
  2042  	charm, force, err := service.Charm()
  2043  	c.Assert(err, jc.ErrorIsNil)
  2044  	c.Assert(charm.URL().String(), gc.Equals, "cs:precise/wordpress-3")
  2045  	c.Assert(force, jc.IsTrue)
  2046  }
  2047  
  2048  func (s *clientSuite) TestBlockServiceSetCharmForce(c *gc.C) {
  2049  	s.setupServiceSetCharm(c)
  2050  
  2051  	// block all changes
  2052  	s.blockAllChanges(c)
  2053  	s.blockRemoveObject(c)
  2054  	s.blockDestroyEnvironment(c)
  2055  
  2056  	s.assertServiceSetCharmBlocked(c, false, true)
  2057  }
  2058  
  2059  func (s *clientSuite) TestClientServiceSetCharmInvalidService(c *gc.C) {
  2060  	s.makeMockCharmStore()
  2061  	err := s.APIState.Client().ServiceSetCharm(
  2062  		"badservice", "cs:precise/wordpress-3", true,
  2063  	)
  2064  	c.Assert(err, gc.ErrorMatches, `service "badservice" not found`)
  2065  }
  2066  
  2067  func (s *clientSuite) TestClientServiceSetCharmErrors(c *gc.C) {
  2068  	s.makeMockCharmStore()
  2069  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  2070  	for url, expect := range map[string]string{
  2071  		// TODO(fwereade,Makyo) make these errors consistent one day.
  2072  		"wordpress":                   "charm url series is not resolved",
  2073  		"cs:wordpress":                "charm url series is not resolved",
  2074  		"cs:precise/wordpress":        "charm url must include revision",
  2075  		"cs:precise/wordpress-999999": `cannot download charm ".*": charm not found in mock store: cs:precise/wordpress-999999`,
  2076  	} {
  2077  		c.Logf("test %s", url)
  2078  		err := s.APIState.Client().ServiceSetCharm(
  2079  			"wordpress", url, false,
  2080  		)
  2081  		c.Check(err, gc.ErrorMatches, expect)
  2082  	}
  2083  }
  2084  
  2085  func (s *clientSuite) makeMockCharmStore() (store *charmtesting.MockCharmStore) {
  2086  	mockStore := charmtesting.NewMockCharmStore()
  2087  	origStore := client.CharmStore
  2088  	client.CharmStore = mockStore
  2089  	s.AddCleanup(func(_ *gc.C) { client.CharmStore = origStore })
  2090  	return mockStore
  2091  }
  2092  
  2093  func addCharm(c *gc.C, name string) (*charm.URL, charm.Charm) {
  2094  	return addSeriesCharm(c, "precise", name)
  2095  }
  2096  
  2097  func addSeriesCharm(c *gc.C, series, name string) (*charm.URL, charm.Charm) {
  2098  	bundle := testcharms.Repo.CharmArchive(c.MkDir(), name)
  2099  	scurl := fmt.Sprintf("cs:%s/%s-%d", series, name, bundle.Revision())
  2100  	curl := charm.MustParseURL(scurl)
  2101  	err := client.CharmStore.(*charmtesting.MockCharmStore).SetCharm(curl, bundle)
  2102  	c.Assert(err, jc.ErrorIsNil)
  2103  	return curl, bundle
  2104  }
  2105  
  2106  func (s *clientSuite) checkEndpoints(c *gc.C, endpoints map[string]charm.Relation) {
  2107  	c.Assert(endpoints["wordpress"], gc.DeepEquals, charm.Relation{
  2108  		Name:      "db",
  2109  		Role:      charm.RelationRole("requirer"),
  2110  		Interface: "mysql",
  2111  		Optional:  false,
  2112  		Limit:     1,
  2113  		Scope:     charm.RelationScope("global"),
  2114  	})
  2115  	c.Assert(endpoints["mysql"], gc.DeepEquals, charm.Relation{
  2116  		Name:      "server",
  2117  		Role:      charm.RelationRole("provider"),
  2118  		Interface: "mysql",
  2119  		Optional:  false,
  2120  		Limit:     0,
  2121  		Scope:     charm.RelationScope("global"),
  2122  	})
  2123  }
  2124  
  2125  func (s *clientSuite) assertAddRelation(c *gc.C, endpoints []string) {
  2126  	s.setUpScenario(c)
  2127  	res, err := s.APIState.Client().AddRelation(endpoints...)
  2128  	c.Assert(err, jc.ErrorIsNil)
  2129  	s.checkEndpoints(c, res.Endpoints)
  2130  	// Show that the relation was added.
  2131  	wpSvc, err := s.State.Service("wordpress")
  2132  	c.Assert(err, jc.ErrorIsNil)
  2133  	rels, err := wpSvc.Relations()
  2134  	// There are 2 relations - the logging-wordpress one set up in the
  2135  	// scenario and the one created in this test.
  2136  	c.Assert(len(rels), gc.Equals, 2)
  2137  	mySvc, err := s.State.Service("mysql")
  2138  	c.Assert(err, jc.ErrorIsNil)
  2139  	rels, err = mySvc.Relations()
  2140  	c.Assert(len(rels), gc.Equals, 1)
  2141  }
  2142  
  2143  func (s *clientSuite) TestSuccessfullyAddRelation(c *gc.C) {
  2144  	endpoints := []string{"wordpress", "mysql"}
  2145  	s.assertAddRelation(c, endpoints)
  2146  }
  2147  
  2148  func (s *clientSuite) TestBlockDestroyAddRelation(c *gc.C) {
  2149  	s.blockDestroyEnvironment(c)
  2150  	s.assertAddRelation(c, []string{"wordpress", "mysql"})
  2151  }
  2152  func (s *clientSuite) TestBlockRemoveAddRelation(c *gc.C) {
  2153  	s.blockRemoveObject(c)
  2154  	s.assertAddRelation(c, []string{"wordpress", "mysql"})
  2155  }
  2156  
  2157  func (s *clientSuite) TestBlockChangesAddRelation(c *gc.C) {
  2158  	s.setUpScenario(c)
  2159  	s.blockAllChanges(c)
  2160  	_, err := s.APIState.Client().AddRelation([]string{"wordpress", "mysql"}...)
  2161  	c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
  2162  }
  2163  
  2164  func (s *clientSuite) TestSuccessfullyAddRelationSwapped(c *gc.C) {
  2165  	// Show that the order of the services listed in the AddRelation call
  2166  	// does not matter.  This is a repeat of the previous test with the service
  2167  	// names swapped.
  2168  	endpoints := []string{"mysql", "wordpress"}
  2169  	s.assertAddRelation(c, endpoints)
  2170  }
  2171  
  2172  func (s *clientSuite) TestCallWithOnlyOneEndpoint(c *gc.C) {
  2173  	s.setUpScenario(c)
  2174  	endpoints := []string{"wordpress"}
  2175  	_, err := s.APIState.Client().AddRelation(endpoints...)
  2176  	c.Assert(err, gc.ErrorMatches, "no relations found")
  2177  }
  2178  
  2179  func (s *clientSuite) TestCallWithOneEndpointTooMany(c *gc.C) {
  2180  	s.setUpScenario(c)
  2181  	endpoints := []string{"wordpress", "mysql", "logging"}
  2182  	_, err := s.APIState.Client().AddRelation(endpoints...)
  2183  	c.Assert(err, gc.ErrorMatches, "cannot relate 3 endpoints")
  2184  }
  2185  
  2186  func (s *clientSuite) TestAddAlreadyAddedRelation(c *gc.C) {
  2187  	s.setUpScenario(c)
  2188  	// Add a relation between wordpress and mysql.
  2189  	endpoints := []string{"wordpress", "mysql"}
  2190  	eps, err := s.State.InferEndpoints(endpoints...)
  2191  	c.Assert(err, jc.ErrorIsNil)
  2192  	_, err = s.State.AddRelation(eps...)
  2193  	c.Assert(err, jc.ErrorIsNil)
  2194  	// And try to add it again.
  2195  	_, err = s.APIState.Client().AddRelation(endpoints...)
  2196  	c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": relation already exists`)
  2197  }
  2198  
  2199  func (s *clientSuite) setupRelationScenario(c *gc.C, endpoints []string) *state.Relation {
  2200  	s.setUpScenario(c)
  2201  	// Add a relation between the endpoints.
  2202  	eps, err := s.State.InferEndpoints(endpoints...)
  2203  	c.Assert(err, jc.ErrorIsNil)
  2204  	relation, err := s.State.AddRelation(eps...)
  2205  	c.Assert(err, jc.ErrorIsNil)
  2206  	return relation
  2207  }
  2208  
  2209  func (s *clientSuite) assertDestroyRelation(c *gc.C, endpoints []string) {
  2210  	s.assertDestroyRelationSuccess(
  2211  		c,
  2212  		s.setupRelationScenario(c, endpoints),
  2213  		endpoints)
  2214  }
  2215  
  2216  func (s *clientSuite) assertDestroyRelationSuccess(c *gc.C, relation *state.Relation, endpoints []string) {
  2217  	err := s.APIState.Client().DestroyRelation(endpoints...)
  2218  	c.Assert(err, jc.ErrorIsNil)
  2219  	// Show that the relation was removed.
  2220  	c.Assert(relation.Refresh(), jc.Satisfies, errors.IsNotFound)
  2221  }
  2222  
  2223  func (s *clientSuite) TestSuccessfulDestroyRelation(c *gc.C) {
  2224  	endpoints := []string{"wordpress", "mysql"}
  2225  	s.assertDestroyRelation(c, endpoints)
  2226  }
  2227  
  2228  func (s *clientSuite) TestSuccessfullyDestroyRelationSwapped(c *gc.C) {
  2229  	// Show that the order of the services listed in the DestroyRelation call
  2230  	// does not matter.  This is a repeat of the previous test with the service
  2231  	// names swapped.
  2232  	endpoints := []string{"mysql", "wordpress"}
  2233  	s.assertDestroyRelation(c, endpoints)
  2234  }
  2235  
  2236  func (s *clientSuite) TestNoRelation(c *gc.C) {
  2237  	s.setUpScenario(c)
  2238  	endpoints := []string{"wordpress", "mysql"}
  2239  	err := s.APIState.Client().DestroyRelation(endpoints...)
  2240  	c.Assert(err, gc.ErrorMatches, `relation "wordpress:db mysql:server" not found`)
  2241  }
  2242  
  2243  func (s *clientSuite) TestAttemptDestroyingNonExistentRelation(c *gc.C) {
  2244  	s.setUpScenario(c)
  2245  	s.AddTestingService(c, "riak", s.AddTestingCharm(c, "riak"))
  2246  	endpoints := []string{"riak", "wordpress"}
  2247  	err := s.APIState.Client().DestroyRelation(endpoints...)
  2248  	c.Assert(err, gc.ErrorMatches, "no relations found")
  2249  }
  2250  
  2251  func (s *clientSuite) TestAttemptDestroyingWithOnlyOneEndpoint(c *gc.C) {
  2252  	s.setUpScenario(c)
  2253  	endpoints := []string{"wordpress"}
  2254  	err := s.APIState.Client().DestroyRelation(endpoints...)
  2255  	c.Assert(err, gc.ErrorMatches, "no relations found")
  2256  }
  2257  
  2258  func (s *clientSuite) TestAttemptDestroyingPeerRelation(c *gc.C) {
  2259  	s.setUpScenario(c)
  2260  	s.AddTestingService(c, "riak", s.AddTestingCharm(c, "riak"))
  2261  
  2262  	endpoints := []string{"riak:ring"}
  2263  	err := s.APIState.Client().DestroyRelation(endpoints...)
  2264  	c.Assert(err, gc.ErrorMatches, `cannot destroy relation "riak:ring": is a peer relation`)
  2265  }
  2266  
  2267  func (s *clientSuite) TestAttemptDestroyingAlreadyDestroyedRelation(c *gc.C) {
  2268  	s.setUpScenario(c)
  2269  
  2270  	// Add a relation between wordpress and mysql.
  2271  	eps, err := s.State.InferEndpoints("wordpress", "mysql")
  2272  	c.Assert(err, jc.ErrorIsNil)
  2273  	rel, err := s.State.AddRelation(eps...)
  2274  	c.Assert(err, jc.ErrorIsNil)
  2275  
  2276  	endpoints := []string{"wordpress", "mysql"}
  2277  	err = s.APIState.Client().DestroyRelation(endpoints...)
  2278  	// Show that the relation was removed.
  2279  	c.Assert(rel.Refresh(), jc.Satisfies, errors.IsNotFound)
  2280  
  2281  	// And try to destroy it again.
  2282  	err = s.APIState.Client().DestroyRelation(endpoints...)
  2283  	c.Assert(err, gc.ErrorMatches, `relation "wordpress:db mysql:server" not found`)
  2284  }
  2285  
  2286  func (s *clientSuite) TestClientWatchAll(c *gc.C) {
  2287  	// A very simple end-to-end test, because
  2288  	// all the logic is tested elsewhere.
  2289  	m, err := s.State.AddMachine("quantal", state.JobManageEnviron)
  2290  	c.Assert(err, jc.ErrorIsNil)
  2291  	err = m.SetProvisioned("i-0", agent.BootstrapNonce, nil)
  2292  	c.Assert(err, jc.ErrorIsNil)
  2293  	watcher, err := s.APIState.Client().WatchAll()
  2294  	c.Assert(err, jc.ErrorIsNil)
  2295  	defer func() {
  2296  		err := watcher.Stop()
  2297  		c.Assert(err, jc.ErrorIsNil)
  2298  	}()
  2299  	deltas, err := watcher.Next()
  2300  	c.Assert(err, jc.ErrorIsNil)
  2301  	if !c.Check(deltas, gc.DeepEquals, []multiwatcher.Delta{{
  2302  		Entity: &multiwatcher.MachineInfo{
  2303  			Id:                      m.Id(),
  2304  			InstanceId:              "i-0",
  2305  			Status:                  multiwatcher.Status("pending"),
  2306  			Life:                    multiwatcher.Life("alive"),
  2307  			Series:                  "quantal",
  2308  			Jobs:                    []multiwatcher.MachineJob{state.JobManageEnviron.ToParams()},
  2309  			Addresses:               []network.Address{},
  2310  			HardwareCharacteristics: &instance.HardwareCharacteristics{},
  2311  			HasVote:                 false,
  2312  			WantsVote:               true,
  2313  		},
  2314  	}}) {
  2315  		c.Logf("got:")
  2316  		for _, d := range deltas {
  2317  			c.Logf("%#v\n", d.Entity)
  2318  		}
  2319  	}
  2320  }
  2321  
  2322  func (s *clientSuite) TestClientSetServiceConstraints(c *gc.C) {
  2323  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  2324  
  2325  	// Update constraints for the service.
  2326  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
  2327  	c.Assert(err, jc.ErrorIsNil)
  2328  	err = s.APIState.Client().SetServiceConstraints("dummy", cons)
  2329  	c.Assert(err, jc.ErrorIsNil)
  2330  
  2331  	// Ensure the constraints have been correctly updated.
  2332  	obtained, err := service.Constraints()
  2333  	c.Assert(err, jc.ErrorIsNil)
  2334  	c.Assert(obtained, gc.DeepEquals, cons)
  2335  }
  2336  
  2337  func (s *clientSuite) setupSetServiceConstraints(c *gc.C) (*state.Service, constraints.Value) {
  2338  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  2339  	// Update constraints for the service.
  2340  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
  2341  	c.Assert(err, jc.ErrorIsNil)
  2342  	return service, cons
  2343  }
  2344  
  2345  func (s *clientSuite) assertSetServiceConstraints(c *gc.C, blocked bool, service *state.Service, cons constraints.Value) {
  2346  	err := s.APIState.Client().SetServiceConstraints("dummy", cons)
  2347  	if blocked {
  2348  		c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
  2349  	} else {
  2350  		c.Assert(err, jc.ErrorIsNil)
  2351  		// Ensure the constraints have been correctly updated.
  2352  		obtained, err := service.Constraints()
  2353  		c.Assert(err, jc.ErrorIsNil)
  2354  		c.Assert(obtained, gc.DeepEquals, cons)
  2355  	}
  2356  }
  2357  
  2358  func (s *clientSuite) TestBlockDestroySetServiceConstraints(c *gc.C) {
  2359  	svc, cons := s.setupSetServiceConstraints(c)
  2360  	s.blockDestroyEnvironment(c)
  2361  	s.assertSetServiceConstraints(c, false, svc, cons)
  2362  }
  2363  
  2364  func (s *clientSuite) TestBlockRemoveSetServiceConstraints(c *gc.C) {
  2365  	svc, cons := s.setupSetServiceConstraints(c)
  2366  	s.blockRemoveObject(c)
  2367  	s.assertSetServiceConstraints(c, false, svc, cons)
  2368  }
  2369  
  2370  func (s *clientSuite) TestBlockChangesSetServiceConstraints(c *gc.C) {
  2371  	svc, cons := s.setupSetServiceConstraints(c)
  2372  	s.blockAllChanges(c)
  2373  	s.assertSetServiceConstraints(c, true, svc, cons)
  2374  }
  2375  
  2376  func (s *clientSuite) TestClientGetServiceConstraints(c *gc.C) {
  2377  	service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
  2378  
  2379  	// Set constraints for the service.
  2380  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
  2381  	c.Assert(err, jc.ErrorIsNil)
  2382  	err = service.SetConstraints(cons)
  2383  	c.Assert(err, jc.ErrorIsNil)
  2384  
  2385  	// Check we can get the constraints.
  2386  	obtained, err := s.APIState.Client().GetServiceConstraints("dummy")
  2387  	c.Assert(err, jc.ErrorIsNil)
  2388  	c.Assert(obtained, gc.DeepEquals, cons)
  2389  }
  2390  
  2391  func (s *clientSuite) TestClientSetEnvironmentConstraints(c *gc.C) {
  2392  	// Set constraints for the environment.
  2393  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
  2394  	c.Assert(err, jc.ErrorIsNil)
  2395  	err = s.APIState.Client().SetEnvironmentConstraints(cons)
  2396  	c.Assert(err, jc.ErrorIsNil)
  2397  
  2398  	// Ensure the constraints have been correctly updated.
  2399  	obtained, err := s.State.EnvironConstraints()
  2400  	c.Assert(err, jc.ErrorIsNil)
  2401  	c.Assert(obtained, gc.DeepEquals, cons)
  2402  }
  2403  
  2404  func (s *clientSuite) assertSetEnvironmentConstraintsBlocked(c *gc.C, blocked bool) {
  2405  	// Set constraints for the environment.
  2406  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
  2407  	c.Assert(err, jc.ErrorIsNil)
  2408  	err = s.APIState.Client().SetEnvironmentConstraints(cons)
  2409  	if blocked {
  2410  		c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
  2411  	} else {
  2412  		c.Assert(err, jc.ErrorIsNil)
  2413  		// Ensure the constraints have been correctly updated.
  2414  		obtained, err := s.State.EnvironConstraints()
  2415  		c.Assert(err, jc.ErrorIsNil)
  2416  		c.Assert(obtained, gc.DeepEquals, cons)
  2417  	}
  2418  }
  2419  
  2420  func (s *clientSuite) TestBlockDestroyClientSetEnvironmentConstraints(c *gc.C) {
  2421  	s.blockDestroyEnvironment(c)
  2422  	s.assertSetEnvironmentConstraintsBlocked(c, false)
  2423  }
  2424  
  2425  func (s *clientSuite) TestBlockRemoveClientSetEnvironmentConstraints(c *gc.C) {
  2426  	s.blockRemoveObject(c)
  2427  	s.assertSetEnvironmentConstraintsBlocked(c, false)
  2428  }
  2429  
  2430  func (s *clientSuite) TestBlockChangesClientSetEnvironmentConstraints(c *gc.C) {
  2431  	s.blockAllChanges(c)
  2432  	s.assertSetEnvironmentConstraintsBlocked(c, true)
  2433  }
  2434  
  2435  func (s *clientSuite) TestClientGetEnvironmentConstraints(c *gc.C) {
  2436  	// Set constraints for the environment.
  2437  	cons, err := constraints.Parse("mem=4096", "cpu-cores=2")
  2438  	c.Assert(err, jc.ErrorIsNil)
  2439  	err = s.State.SetEnvironConstraints(cons)
  2440  	c.Assert(err, jc.ErrorIsNil)
  2441  
  2442  	// Check we can get the constraints.
  2443  	obtained, err := s.APIState.Client().GetEnvironmentConstraints()
  2444  	c.Assert(err, jc.ErrorIsNil)
  2445  	c.Assert(obtained, gc.DeepEquals, cons)
  2446  }
  2447  
  2448  func (s *clientSuite) TestClientServiceCharmRelations(c *gc.C) {
  2449  	s.setUpScenario(c)
  2450  	_, err := s.APIState.Client().ServiceCharmRelations("blah")
  2451  	c.Assert(err, gc.ErrorMatches, `service "blah" not found`)
  2452  
  2453  	relations, err := s.APIState.Client().ServiceCharmRelations("wordpress")
  2454  	c.Assert(err, jc.ErrorIsNil)
  2455  	c.Assert(relations, gc.DeepEquals, []string{
  2456  		"cache", "db", "juju-info", "logging-dir", "monitoring-port", "url",
  2457  	})
  2458  }
  2459  
  2460  func (s *clientSuite) TestClientPublicAddressErrors(c *gc.C) {
  2461  	s.setUpScenario(c)
  2462  	_, err := s.APIState.Client().PublicAddress("wordpress")
  2463  	c.Assert(err, gc.ErrorMatches, `unknown unit or machine "wordpress"`)
  2464  	_, err = s.APIState.Client().PublicAddress("0")
  2465  	c.Assert(err, gc.ErrorMatches, `machine "0" has no public address`)
  2466  	_, err = s.APIState.Client().PublicAddress("wordpress/0")
  2467  	c.Assert(err, gc.ErrorMatches, `unit "wordpress/0" has no public address`)
  2468  }
  2469  
  2470  func (s *clientSuite) TestClientPublicAddressMachine(c *gc.C) {
  2471  	s.setUpScenario(c)
  2472  
  2473  	// Internally, network.SelectPublicAddress is used; the "most public"
  2474  	// address is returned.
  2475  	m1, err := s.State.Machine("1")
  2476  	c.Assert(err, jc.ErrorIsNil)
  2477  	cloudLocalAddress := network.NewAddress("cloudlocal", network.ScopeCloudLocal)
  2478  	publicAddress := network.NewAddress("public", network.ScopePublic)
  2479  	err = m1.SetAddresses(cloudLocalAddress)
  2480  	c.Assert(err, jc.ErrorIsNil)
  2481  	addr, err := s.APIState.Client().PublicAddress("1")
  2482  	c.Assert(err, jc.ErrorIsNil)
  2483  	c.Assert(addr, gc.Equals, "cloudlocal")
  2484  	err = m1.SetAddresses(cloudLocalAddress, publicAddress)
  2485  	addr, err = s.APIState.Client().PublicAddress("1")
  2486  	c.Assert(err, jc.ErrorIsNil)
  2487  	c.Assert(addr, gc.Equals, "public")
  2488  }
  2489  
  2490  func (s *clientSuite) TestClientPublicAddressUnit(c *gc.C) {
  2491  	s.setUpScenario(c)
  2492  
  2493  	m1, err := s.State.Machine("1")
  2494  	publicAddress := network.NewAddress("public", network.ScopePublic)
  2495  	err = m1.SetAddresses(publicAddress)
  2496  	c.Assert(err, jc.ErrorIsNil)
  2497  	addr, err := s.APIState.Client().PublicAddress("wordpress/0")
  2498  	c.Assert(err, jc.ErrorIsNil)
  2499  	c.Assert(addr, gc.Equals, "public")
  2500  }
  2501  
  2502  func (s *clientSuite) TestClientPrivateAddressErrors(c *gc.C) {
  2503  	s.setUpScenario(c)
  2504  	_, err := s.APIState.Client().PrivateAddress("wordpress")
  2505  	c.Assert(err, gc.ErrorMatches, `unknown unit or machine "wordpress"`)
  2506  	_, err = s.APIState.Client().PrivateAddress("0")
  2507  	c.Assert(err, gc.ErrorMatches, `machine "0" has no internal address`)
  2508  	_, err = s.APIState.Client().PrivateAddress("wordpress/0")
  2509  	c.Assert(err, gc.ErrorMatches, `unit "wordpress/0" has no internal address`)
  2510  }
  2511  
  2512  func (s *clientSuite) TestClientPrivateAddress(c *gc.C) {
  2513  	s.setUpScenario(c)
  2514  
  2515  	// Internally, network.SelectInternalAddress is used; the public
  2516  	// address if no cloud-local one is available.
  2517  	m1, err := s.State.Machine("1")
  2518  	c.Assert(err, jc.ErrorIsNil)
  2519  	cloudLocalAddress := network.NewAddress("cloudlocal", network.ScopeCloudLocal)
  2520  	publicAddress := network.NewAddress("public", network.ScopePublic)
  2521  	err = m1.SetAddresses(publicAddress)
  2522  	c.Assert(err, jc.ErrorIsNil)
  2523  	addr, err := s.APIState.Client().PrivateAddress("1")
  2524  	c.Assert(err, jc.ErrorIsNil)
  2525  	c.Assert(addr, gc.Equals, "public")
  2526  	err = m1.SetAddresses(cloudLocalAddress, publicAddress)
  2527  	addr, err = s.APIState.Client().PrivateAddress("1")
  2528  	c.Assert(err, jc.ErrorIsNil)
  2529  	c.Assert(addr, gc.Equals, "cloudlocal")
  2530  }
  2531  
  2532  func (s *clientSuite) TestClientPrivateAddressUnit(c *gc.C) {
  2533  	s.setUpScenario(c)
  2534  
  2535  	m1, err := s.State.Machine("1")
  2536  	privateAddress := network.NewAddress("private", network.ScopeCloudLocal)
  2537  	err = m1.SetAddresses(privateAddress)
  2538  	c.Assert(err, jc.ErrorIsNil)
  2539  	addr, err := s.APIState.Client().PrivateAddress("wordpress/0")
  2540  	c.Assert(err, jc.ErrorIsNil)
  2541  	c.Assert(addr, gc.Equals, "private")
  2542  }
  2543  
  2544  func (s *serverSuite) TestClientEnvironmentGet(c *gc.C) {
  2545  	envConfig, err := s.State.EnvironConfig()
  2546  	c.Assert(err, jc.ErrorIsNil)
  2547  	result, err := s.client.EnvironmentGet()
  2548  	c.Assert(err, jc.ErrorIsNil)
  2549  	c.Assert(result.Config, gc.DeepEquals, envConfig.AllAttrs())
  2550  }
  2551  
  2552  func (s *serverSuite) assertEnvValue(c *gc.C, key string, expected interface{}) {
  2553  	envConfig, err := s.State.EnvironConfig()
  2554  	c.Assert(err, jc.ErrorIsNil)
  2555  	value, found := envConfig.AllAttrs()[key]
  2556  	c.Assert(found, jc.IsTrue)
  2557  	c.Assert(value, gc.Equals, expected)
  2558  }
  2559  
  2560  func (s *serverSuite) assertEnvValueMissing(c *gc.C, key string) {
  2561  	envConfig, err := s.State.EnvironConfig()
  2562  	c.Assert(err, jc.ErrorIsNil)
  2563  	_, found := envConfig.AllAttrs()[key]
  2564  	c.Assert(found, jc.IsFalse)
  2565  }
  2566  
  2567  func (s *serverSuite) TestClientEnvironmentSet(c *gc.C) {
  2568  	envConfig, err := s.State.EnvironConfig()
  2569  	c.Assert(err, jc.ErrorIsNil)
  2570  	_, found := envConfig.AllAttrs()["some-key"]
  2571  	c.Assert(found, jc.IsFalse)
  2572  
  2573  	params := params.EnvironmentSet{
  2574  		Config: map[string]interface{}{
  2575  			"some-key":  "value",
  2576  			"other-key": "other value"},
  2577  	}
  2578  	err = s.client.EnvironmentSet(params)
  2579  	c.Assert(err, jc.ErrorIsNil)
  2580  	s.assertEnvValue(c, "some-key", "value")
  2581  	s.assertEnvValue(c, "other-key", "other value")
  2582  }
  2583  
  2584  func (s *serverSuite) TestClientEnvironmentSetImmutable(c *gc.C) {
  2585  	// The various immutable config values are tested in
  2586  	// environs/config/config_test.go, so just choosing one here.
  2587  	params := params.EnvironmentSet{
  2588  		Config: map[string]interface{}{"state-port": "1"},
  2589  	}
  2590  	err := s.client.EnvironmentSet(params)
  2591  	c.Check(err, gc.ErrorMatches, `cannot change state-port from .* to 1`)
  2592  }
  2593  
  2594  func (s *serverSuite) assertEnvironmentSetBlocked(c *gc.C, args map[string]interface{}) {
  2595  	err := s.client.EnvironmentSet(params.EnvironmentSet{args})
  2596  	c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
  2597  }
  2598  
  2599  func (s *serverSuite) assertEnvironmentSetNotBlocked(c *gc.C, args map[string]interface{}) {
  2600  	err := s.client.EnvironmentSet(params.EnvironmentSet{args})
  2601  	c.Assert(err, jc.ErrorIsNil)
  2602  	s.assertEnvValue(c, "some-key", "value")
  2603  }
  2604  
  2605  func (s *serverSuite) TestBlockChangesClientEnvironmentSet(c *gc.C) {
  2606  	s.blockAllChanges(c)
  2607  	args := map[string]interface{}{"some-key": "value"}
  2608  	s.assertEnvironmentSetBlocked(c, args)
  2609  
  2610  	// Make sure just mentioning variable does not unblock env.
  2611  	// Need right value to unblock properly.
  2612  	args[config.PreventAllChangesKey] = true
  2613  	s.assertEnvironmentSetBlocked(c, args)
  2614  
  2615  	// But make sure that can unblock block-changes with right value.
  2616  	args[config.PreventAllChangesKey] = false
  2617  	s.assertEnvironmentSetNotBlocked(c, args)
  2618  	s.assertEnvValue(c, config.PreventAllChangesKey, false)
  2619  }
  2620  
  2621  func (s *serverSuite) TestClientEnvironmentSetDeprecated(c *gc.C) {
  2622  	envConfig, err := s.State.EnvironConfig()
  2623  	c.Assert(err, jc.ErrorIsNil)
  2624  	url := envConfig.AllAttrs()["agent-metadata-url"]
  2625  	c.Assert(url, gc.Equals, "")
  2626  
  2627  	args := params.EnvironmentSet{
  2628  		Config: map[string]interface{}{"tools-metadata-url": "value"},
  2629  	}
  2630  	err = s.client.EnvironmentSet(args)
  2631  	c.Assert(err, jc.ErrorIsNil)
  2632  	s.assertEnvValue(c, "agent-metadata-url", "value")
  2633  	s.assertEnvValue(c, "tools-metadata-url", "value")
  2634  }
  2635  
  2636  func (s *serverSuite) TestClientEnvironmentSetCannotChangeAgentVersion(c *gc.C) {
  2637  	args := params.EnvironmentSet{
  2638  		map[string]interface{}{"agent-version": "9.9.9"},
  2639  	}
  2640  	err := s.client.EnvironmentSet(args)
  2641  	c.Assert(err, gc.ErrorMatches, "agent-version cannot be changed")
  2642  
  2643  	// It's okay to pass env back with the same agent-version.
  2644  	result, err := s.client.EnvironmentGet()
  2645  	c.Assert(err, jc.ErrorIsNil)
  2646  	c.Assert(result.Config["agent-version"], gc.NotNil)
  2647  	args.Config["agent-version"] = result.Config["agent-version"]
  2648  	err = s.client.EnvironmentSet(args)
  2649  	c.Assert(err, jc.ErrorIsNil)
  2650  }
  2651  
  2652  func (s *serverSuite) TestClientEnvironmentUnset(c *gc.C) {
  2653  	err := s.State.UpdateEnvironConfig(map[string]interface{}{"abc": 123}, nil, nil)
  2654  	c.Assert(err, jc.ErrorIsNil)
  2655  
  2656  	args := params.EnvironmentUnset{[]string{"abc"}}
  2657  	err = s.client.EnvironmentUnset(args)
  2658  	c.Assert(err, jc.ErrorIsNil)
  2659  	s.assertEnvValueMissing(c, "abc")
  2660  }
  2661  
  2662  func (s *serverSuite) TestBlockClientEnvironmentUnset(c *gc.C) {
  2663  	err := s.State.UpdateEnvironConfig(map[string]interface{}{"abc": 123}, nil, nil)
  2664  	c.Assert(err, jc.ErrorIsNil)
  2665  	s.blockAllChanges(c)
  2666  
  2667  	args := params.EnvironmentUnset{[]string{"abc"}}
  2668  	err = s.client.EnvironmentUnset(args)
  2669  	c.Assert(errors.Cause(err), gc.Equals, common.ErrOperationBlocked)
  2670  }
  2671  
  2672  func (s *serverSuite) TestClientEnvironmentUnsetMissing(c *gc.C) {
  2673  	// It's okay to unset a non-existent attribute.
  2674  	args := params.EnvironmentUnset{[]string{"not_there"}}
  2675  	err := s.client.EnvironmentUnset(args)
  2676  	c.Assert(err, jc.ErrorIsNil)
  2677  }
  2678  
  2679  func (s *serverSuite) TestClientEnvironmentUnsetError(c *gc.C) {
  2680  	err := s.State.UpdateEnvironConfig(map[string]interface{}{"abc": 123}, nil, nil)
  2681  	c.Assert(err, jc.ErrorIsNil)
  2682  
  2683  	// "type" may not be removed, and this will cause an error.
  2684  	// If any one attribute's removal causes an error, there
  2685  	// should be no change.
  2686  	args := params.EnvironmentUnset{[]string{"abc", "type"}}
  2687  	err = s.client.EnvironmentUnset(args)
  2688  	c.Assert(err, gc.ErrorMatches, "type: expected string, got nothing")
  2689  	s.assertEnvValue(c, "abc", 123)
  2690  }
  2691  
  2692  func (s *clientSuite) TestClientFindTools(c *gc.C) {
  2693  	result, err := s.APIState.Client().FindTools(2, -1, "", "")
  2694  	c.Assert(err, jc.ErrorIsNil)
  2695  	c.Assert(result.Error, jc.Satisfies, params.IsCodeNotFound)
  2696  	toolstesting.UploadToStorage(c, s.DefaultToolsStorage, "released", version.MustParseBinary("2.12.0-precise-amd64"))
  2697  	result, err = s.APIState.Client().FindTools(2, 12, "precise", "amd64")
  2698  	c.Assert(err, jc.ErrorIsNil)
  2699  	c.Assert(result.Error, gc.IsNil)
  2700  	c.Assert(result.List, gc.HasLen, 1)
  2701  	c.Assert(result.List[0].Version, gc.Equals, version.MustParseBinary("2.12.0-precise-amd64"))
  2702  	url := fmt.Sprintf("https://%s/environment/%s/tools/%s",
  2703  		s.APIState.Addr(), coretesting.EnvironmentTag.Id(), result.List[0].Version)
  2704  	c.Assert(result.List[0].URL, gc.Equals, url)
  2705  }
  2706  
  2707  func (s *clientSuite) checkMachine(c *gc.C, id, series, cons string) {
  2708  	// Ensure the machine was actually created.
  2709  	machine, err := s.BackingState.Machine(id)
  2710  	c.Assert(err, jc.ErrorIsNil)
  2711  	c.Assert(machine.Series(), gc.Equals, series)
  2712  	c.Assert(machine.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobHostUnits})
  2713  	machineConstraints, err := machine.Constraints()
  2714  	c.Assert(err, jc.ErrorIsNil)
  2715  	c.Assert(machineConstraints.String(), gc.Equals, cons)
  2716  }
  2717  
  2718  func (s *clientSuite) TestClientAddMachinesDefaultSeries(c *gc.C) {
  2719  	apiParams := make([]params.AddMachineParams, 3)
  2720  	for i := 0; i < 3; i++ {
  2721  		apiParams[i] = params.AddMachineParams{
  2722  			Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  2723  		}
  2724  	}
  2725  	machines, err := s.APIState.Client().AddMachines(apiParams)
  2726  	c.Assert(err, jc.ErrorIsNil)
  2727  	c.Assert(len(machines), gc.Equals, 3)
  2728  	for i, machineResult := range machines {
  2729  		c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
  2730  		s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String())
  2731  	}
  2732  }
  2733  
  2734  func (s *clientSuite) assertAddMachinesBlocked(c *gc.C, blocked bool) {
  2735  	apiParams := make([]params.AddMachineParams, 3)
  2736  	for i := 0; i < 3; i++ {
  2737  		apiParams[i] = params.AddMachineParams{
  2738  			Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  2739  		}
  2740  	}
  2741  	machines, err := s.APIState.Client().AddMachines(apiParams)
  2742  	if blocked {
  2743  		c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
  2744  	} else {
  2745  		c.Assert(err, jc.ErrorIsNil)
  2746  		c.Assert(len(machines), gc.Equals, 3)
  2747  		for i, machineResult := range machines {
  2748  			c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
  2749  			s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String())
  2750  		}
  2751  	}
  2752  }
  2753  
  2754  func (s *clientSuite) TestBlockDestroyClientAddMachinesDefaultSeries(c *gc.C) {
  2755  	s.blockDestroyEnvironment(c)
  2756  	s.assertAddMachinesBlocked(c, false)
  2757  }
  2758  
  2759  func (s *clientSuite) TestBlockRemoveClientAddMachinesDefaultSeries(c *gc.C) {
  2760  	s.blockRemoveObject(c)
  2761  	s.assertAddMachinesBlocked(c, false)
  2762  }
  2763  
  2764  func (s *clientSuite) TestBlockChangesClientAddMachines(c *gc.C) {
  2765  	s.blockAllChanges(c)
  2766  	s.assertAddMachinesBlocked(c, true)
  2767  }
  2768  
  2769  func (s *clientSuite) TestClientAddMachinesWithSeries(c *gc.C) {
  2770  	apiParams := make([]params.AddMachineParams, 3)
  2771  	for i := 0; i < 3; i++ {
  2772  		apiParams[i] = params.AddMachineParams{
  2773  			Series: "quantal",
  2774  			Jobs:   []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  2775  		}
  2776  	}
  2777  	machines, err := s.APIState.Client().AddMachines(apiParams)
  2778  	c.Assert(err, jc.ErrorIsNil)
  2779  	c.Assert(len(machines), gc.Equals, 3)
  2780  	for i, machineResult := range machines {
  2781  		c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
  2782  		s.checkMachine(c, machineResult.Machine, "quantal", apiParams[i].Constraints.String())
  2783  	}
  2784  }
  2785  
  2786  func (s *clientSuite) TestClientAddMachineInsideMachine(c *gc.C) {
  2787  	_, err := s.State.AddMachine("quantal", state.JobHostUnits)
  2788  	c.Assert(err, jc.ErrorIsNil)
  2789  
  2790  	machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{{
  2791  		Jobs:          []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  2792  		ContainerType: instance.LXC,
  2793  		ParentId:      "0",
  2794  		Series:        "quantal",
  2795  	}})
  2796  	c.Assert(err, jc.ErrorIsNil)
  2797  	c.Assert(machines, gc.HasLen, 1)
  2798  	c.Assert(machines[0].Machine, gc.Equals, "0/lxc/0")
  2799  }
  2800  
  2801  // updateConfig sets config variable with given key to a given value
  2802  // Asserts that no errors were encountered.
  2803  func (s *baseSuite) updateConfig(c *gc.C, key string, block bool) {
  2804  	err := s.State.UpdateEnvironConfig(map[string]interface{}{key: block}, nil, nil)
  2805  	c.Assert(err, jc.ErrorIsNil)
  2806  }
  2807  
  2808  // setBlockAllChanges blocks all operations that could change environment -
  2809  // setting block-all-changes to true.
  2810  func (s *baseSuite) setBlockAllChanges(c *gc.C, block bool) {
  2811  	s.updateConfig(c, "block-all-changes", block)
  2812  }
  2813  
  2814  func (s *baseSuite) blockAllChanges(c *gc.C) {
  2815  	s.setBlockAllChanges(c, true)
  2816  }
  2817  
  2818  // setBlockRemoveObject blocks all operations that remove
  2819  // machines, services, units or relations -
  2820  // setting block-remove-object to true.
  2821  func (s *baseSuite) setBlockRemoveObject(c *gc.C, block bool) {
  2822  	s.updateConfig(c, "block-remove-object", block)
  2823  }
  2824  
  2825  func (s *baseSuite) blockRemoveObject(c *gc.C) {
  2826  	s.setBlockRemoveObject(c, true)
  2827  }
  2828  
  2829  // setBlockDestroyEnvironment blocks destroy-environment -
  2830  // setting block-destroy-environment to true.
  2831  func (s *baseSuite) setBlockDestroyEnvironment(c *gc.C, block bool) {
  2832  	s.updateConfig(c, "block-destroy-environment", block)
  2833  }
  2834  
  2835  func (s *baseSuite) blockDestroyEnvironment(c *gc.C) {
  2836  	s.setBlockDestroyEnvironment(c, true)
  2837  }
  2838  
  2839  func (s *clientSuite) TestClientAddMachinesWithConstraints(c *gc.C) {
  2840  	apiParams := make([]params.AddMachineParams, 3)
  2841  	for i := 0; i < 3; i++ {
  2842  		apiParams[i] = params.AddMachineParams{
  2843  			Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  2844  		}
  2845  	}
  2846  	// The last machine has some constraints.
  2847  	apiParams[2].Constraints = constraints.MustParse("mem=4G")
  2848  	machines, err := s.APIState.Client().AddMachines(apiParams)
  2849  	c.Assert(err, jc.ErrorIsNil)
  2850  	c.Assert(len(machines), gc.Equals, 3)
  2851  	for i, machineResult := range machines {
  2852  		c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
  2853  		s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String())
  2854  	}
  2855  }
  2856  
  2857  func (s *clientSuite) TestClientAddMachinesWithPlacement(c *gc.C) {
  2858  	apiParams := make([]params.AddMachineParams, 4)
  2859  	for i := range apiParams {
  2860  		apiParams[i] = params.AddMachineParams{
  2861  			Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  2862  		}
  2863  	}
  2864  	apiParams[0].Placement = instance.MustParsePlacement("lxc")
  2865  	apiParams[1].Placement = instance.MustParsePlacement("lxc:0")
  2866  	apiParams[1].ContainerType = instance.LXC
  2867  	apiParams[2].Placement = instance.MustParsePlacement("dummyenv:invalid")
  2868  	apiParams[3].Placement = instance.MustParsePlacement("dummyenv:valid")
  2869  	machines, err := s.APIState.Client().AddMachines(apiParams)
  2870  	c.Assert(err, jc.ErrorIsNil)
  2871  	c.Assert(len(machines), gc.Equals, 4)
  2872  	c.Assert(machines[0].Machine, gc.Equals, "0/lxc/0")
  2873  	c.Assert(machines[1].Error, gc.ErrorMatches, "container type and placement are mutually exclusive")
  2874  	c.Assert(machines[2].Error, gc.ErrorMatches, "cannot add a new machine: invalid placement is invalid")
  2875  	c.Assert(machines[3].Machine, gc.Equals, "1")
  2876  
  2877  	m, err := s.BackingState.Machine(machines[3].Machine)
  2878  	c.Assert(err, jc.ErrorIsNil)
  2879  	c.Assert(m.Placement(), gc.DeepEquals, apiParams[3].Placement.Directive)
  2880  }
  2881  
  2882  func (s *clientSuite) TestClientAddMachinesWithDisks(c *gc.C) {
  2883  	s.PatchEnvironment(osenv.JujuFeatureFlagEnvKey, "storage")
  2884  	featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey)
  2885  
  2886  	apiParams := make([]params.AddMachineParams, 3)
  2887  	for i := range apiParams {
  2888  		apiParams[i] = params.AddMachineParams{
  2889  			Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  2890  		}
  2891  	}
  2892  	apiParams[0].Disks = []storage.Constraints{{Size: 1, Count: 2}, {Size: 2, Count: 1}}
  2893  	apiParams[1].Disks = []storage.Constraints{{Size: 1, Count: 2, Pool: "three"}}
  2894  	apiParams[2].Disks = []storage.Constraints{{Size: 0, Count: 0}}
  2895  	machines, err := s.APIState.Client().AddMachines(apiParams)
  2896  	c.Assert(err, jc.ErrorIsNil)
  2897  	c.Assert(len(machines), gc.Equals, 3)
  2898  	c.Assert(machines[0].Machine, gc.Equals, "0")
  2899  	c.Assert(machines[1].Error, gc.ErrorMatches, "storage pools not implemented")
  2900  	c.Assert(machines[2].Error, gc.ErrorMatches, "invalid size 0")
  2901  
  2902  	m, err := s.BackingState.Machine(machines[0].Machine)
  2903  	c.Assert(err, jc.ErrorIsNil)
  2904  	blockDevices, err := m.BlockDevices()
  2905  	c.Assert(err, jc.ErrorIsNil)
  2906  	expectParams := []state.BlockDeviceParams{{Size: 1}, {Size: 1}, {Size: 2}}
  2907  	c.Assert(blockDevices, gc.HasLen, len(expectParams))
  2908  	for i, dev := range blockDevices {
  2909  		params, ok := dev.Params()
  2910  		c.Assert(ok, jc.IsTrue)
  2911  		c.Assert(params, gc.DeepEquals, expectParams[i])
  2912  	}
  2913  }
  2914  
  2915  func (s *clientSuite) TestClientAddMachinesWithDisksNoFeatureFlag(c *gc.C) {
  2916  	// If the storage feature flag is not set, then Disks should be ignored.
  2917  	apiParams := make([]params.AddMachineParams, 2)
  2918  	for i := range apiParams {
  2919  		apiParams[i] = params.AddMachineParams{
  2920  			Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  2921  		}
  2922  	}
  2923  	apiParams[0].Disks = []storage.Constraints{{Size: 1, Count: 2}, {Size: 2, Count: 1}}
  2924  	apiParams[1].Disks = []storage.Constraints{{Size: 1, Count: 0, Pool: "three"}}
  2925  	machines, err := s.APIState.Client().AddMachines(apiParams)
  2926  	c.Assert(err, jc.ErrorIsNil)
  2927  	c.Assert(len(machines), gc.Equals, 2)
  2928  	c.Assert(machines[0].Machine, gc.Equals, "0")
  2929  	c.Assert(machines[1].Machine, gc.Equals, "1")
  2930  	m, err := s.BackingState.Machine(machines[0].Machine)
  2931  	c.Assert(err, jc.ErrorIsNil)
  2932  	blockDevices, err := m.BlockDevices()
  2933  	c.Assert(err, jc.ErrorIsNil)
  2934  	c.Assert(blockDevices, gc.HasLen, 0)
  2935  }
  2936  
  2937  func (s *clientSuite) TestClientAddMachines1dot18(c *gc.C) {
  2938  	apiParams := make([]params.AddMachineParams, 2)
  2939  	for i := range apiParams {
  2940  		apiParams[i] = params.AddMachineParams{
  2941  			Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  2942  		}
  2943  	}
  2944  	apiParams[1].ContainerType = instance.LXC
  2945  	apiParams[1].ParentId = "0"
  2946  	machines, err := s.APIState.Client().AddMachines1dot18(apiParams)
  2947  	c.Assert(err, jc.ErrorIsNil)
  2948  	c.Assert(len(machines), gc.Equals, 2)
  2949  	c.Assert(machines[0].Machine, gc.Equals, "0")
  2950  	c.Assert(machines[1].Machine, gc.Equals, "0/lxc/0")
  2951  }
  2952  
  2953  func (s *clientSuite) TestClientAddMachines1dot18SomeErrors(c *gc.C) {
  2954  	apiParams := []params.AddMachineParams{{
  2955  		Jobs:     []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  2956  		ParentId: "123",
  2957  	}}
  2958  	machines, err := s.APIState.Client().AddMachines1dot18(apiParams)
  2959  	c.Assert(err, jc.ErrorIsNil)
  2960  	c.Assert(len(machines), gc.Equals, 1)
  2961  	c.Check(machines[0].Error, gc.ErrorMatches, "parent machine specified without container type")
  2962  }
  2963  
  2964  func (s *clientSuite) TestClientAddMachinesSomeErrors(c *gc.C) {
  2965  	// Here we check that adding a number of containers correctly handles the
  2966  	// case that some adds succeed and others fail and report the errors
  2967  	// accordingly.
  2968  	// We will set up params to the AddMachines API to attempt to create 3 machines.
  2969  	// Machines 0 and 1 will be added successfully.
  2970  	// Remaining machines will fail due to different reasons.
  2971  
  2972  	// Create a machine to host the requested containers.
  2973  	host, err := s.State.AddMachine("quantal", state.JobHostUnits)
  2974  	c.Assert(err, jc.ErrorIsNil)
  2975  	// The host only supports lxc containers.
  2976  	err = host.SetSupportedContainers([]instance.ContainerType{instance.LXC})
  2977  	c.Assert(err, jc.ErrorIsNil)
  2978  
  2979  	// Set up params for adding 3 containers.
  2980  	apiParams := make([]params.AddMachineParams, 3)
  2981  	for i := range apiParams {
  2982  		apiParams[i] = params.AddMachineParams{
  2983  			Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  2984  		}
  2985  	}
  2986  	// This will cause a machine add to fail due to an unsupported container.
  2987  	apiParams[2].ContainerType = instance.KVM
  2988  	apiParams[2].ParentId = host.Id()
  2989  	machines, err := s.APIState.Client().AddMachines(apiParams)
  2990  	c.Assert(err, jc.ErrorIsNil)
  2991  	c.Assert(len(machines), gc.Equals, 3)
  2992  
  2993  	// Check the results - machines 2 and 3 will have errors.
  2994  	c.Check(machines[0].Machine, gc.Equals, "1")
  2995  	c.Check(machines[0].Error, gc.IsNil)
  2996  	c.Check(machines[1].Machine, gc.Equals, "2")
  2997  	c.Check(machines[1].Error, gc.IsNil)
  2998  	c.Check(machines[2].Error, gc.ErrorMatches, "cannot add a new machine: machine 0 cannot host kvm containers")
  2999  }
  3000  
  3001  func (s *clientSuite) TestClientAddMachinesWithInstanceIdSomeErrors(c *gc.C) {
  3002  	apiParams := make([]params.AddMachineParams, 3)
  3003  	addrs := []network.Address{network.NewAddress("1.2.3.4", network.ScopeUnknown)}
  3004  	hc := instance.MustParseHardware("mem=4G")
  3005  	for i := 0; i < 3; i++ {
  3006  		apiParams[i] = params.AddMachineParams{
  3007  			Jobs:       []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  3008  			InstanceId: instance.Id(fmt.Sprintf("1234-%d", i)),
  3009  			Nonce:      "foo",
  3010  			HardwareCharacteristics: hc,
  3011  			Addrs: addrs,
  3012  		}
  3013  	}
  3014  	// This will cause the last machine add to fail.
  3015  	apiParams[2].Nonce = ""
  3016  	machines, err := s.APIState.Client().AddMachines(apiParams)
  3017  	c.Assert(err, jc.ErrorIsNil)
  3018  	c.Assert(len(machines), gc.Equals, 3)
  3019  	for i, machineResult := range machines {
  3020  		if i == 2 {
  3021  			c.Assert(machineResult.Error, gc.NotNil)
  3022  			c.Assert(machineResult.Error, gc.ErrorMatches, "cannot add a new machine: cannot add a machine with an instance id and no nonce")
  3023  		} else {
  3024  			c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
  3025  			s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String())
  3026  			instanceId := fmt.Sprintf("1234-%d", i)
  3027  			s.checkInstance(c, machineResult.Machine, instanceId, "foo", hc, addrs)
  3028  		}
  3029  	}
  3030  }
  3031  
  3032  func (s *clientSuite) checkInstance(c *gc.C, id, instanceId, nonce string,
  3033  	hc instance.HardwareCharacteristics, addr []network.Address) {
  3034  
  3035  	machine, err := s.BackingState.Machine(id)
  3036  	c.Assert(err, jc.ErrorIsNil)
  3037  	machineInstanceId, err := machine.InstanceId()
  3038  	c.Assert(err, jc.ErrorIsNil)
  3039  	c.Assert(machine.CheckProvisioned(nonce), jc.IsTrue)
  3040  	c.Assert(machineInstanceId, gc.Equals, instance.Id(instanceId))
  3041  	machineHardware, err := machine.HardwareCharacteristics()
  3042  	c.Assert(err, jc.ErrorIsNil)
  3043  	c.Assert(machineHardware.String(), gc.Equals, hc.String())
  3044  	c.Assert(machine.Addresses(), gc.DeepEquals, addr)
  3045  }
  3046  
  3047  func (s *clientSuite) TestInjectMachinesStillExists(c *gc.C) {
  3048  	results := new(params.AddMachinesResults)
  3049  	// We need to use Call directly because the client interface
  3050  	// no longer refers to InjectMachine.
  3051  	args := params.AddMachines{
  3052  		MachineParams: []params.AddMachineParams{{
  3053  			Jobs:       []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  3054  			InstanceId: "i-foo",
  3055  			Nonce:      "nonce",
  3056  		}},
  3057  	}
  3058  	err := s.APIState.APICall("Client", 0, "", "AddMachines", args, &results)
  3059  	c.Assert(err, jc.ErrorIsNil)
  3060  	c.Assert(results.Machines, gc.HasLen, 1)
  3061  }
  3062  
  3063  func (s *clientSuite) TestProvisioningScript(c *gc.C) {
  3064  	// Inject a machine and then call the ProvisioningScript API.
  3065  	// The result should be the same as when calling MachineConfig,
  3066  	// converting it to a cloudinit.MachineConfig, and disabling
  3067  	// apt_upgrade.
  3068  	apiParams := params.AddMachineParams{
  3069  		Jobs:       []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  3070  		InstanceId: instance.Id("1234"),
  3071  		Nonce:      "foo",
  3072  		HardwareCharacteristics: instance.MustParseHardware("arch=amd64"),
  3073  	}
  3074  	machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{apiParams})
  3075  	c.Assert(err, jc.ErrorIsNil)
  3076  	c.Assert(len(machines), gc.Equals, 1)
  3077  	machineId := machines[0].Machine
  3078  	// Call ProvisioningScript. Normally ProvisioningScript and
  3079  	// MachineConfig are mutually exclusive; both of them will
  3080  	// allocate a api password for the machine agent.
  3081  	script, err := s.APIState.Client().ProvisioningScript(params.ProvisioningScriptParams{
  3082  		MachineId: machineId,
  3083  		Nonce:     apiParams.Nonce,
  3084  	})
  3085  	c.Assert(err, jc.ErrorIsNil)
  3086  	mcfg, err := client.MachineConfig(s.State, machineId, apiParams.Nonce, "")
  3087  	c.Assert(err, jc.ErrorIsNil)
  3088  	sshinitScript, err := manual.ProvisioningScript(mcfg)
  3089  	c.Assert(err, jc.ErrorIsNil)
  3090  	// ProvisioningScript internally calls MachineConfig,
  3091  	// which allocates a new, random password. Everything
  3092  	// about the scripts should be the same other than
  3093  	// the line containing "oldpassword" from agent.conf.
  3094  	scriptLines := strings.Split(script, "\n")
  3095  	sshinitScriptLines := strings.Split(sshinitScript, "\n")
  3096  	c.Assert(scriptLines, gc.HasLen, len(sshinitScriptLines))
  3097  	for i, line := range scriptLines {
  3098  		if strings.Contains(line, "oldpassword") {
  3099  			continue
  3100  		}
  3101  		c.Assert(line, gc.Equals, sshinitScriptLines[i])
  3102  	}
  3103  }
  3104  
  3105  func (s *clientSuite) TestProvisioningScriptDisablePackageCommands(c *gc.C) {
  3106  	apiParams := params.AddMachineParams{
  3107  		Jobs:       []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  3108  		InstanceId: instance.Id("1234"),
  3109  		Nonce:      "foo",
  3110  		HardwareCharacteristics: instance.MustParseHardware("arch=amd64"),
  3111  	}
  3112  	machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{apiParams})
  3113  	c.Assert(err, jc.ErrorIsNil)
  3114  	c.Assert(len(machines), gc.Equals, 1)
  3115  	machineId := machines[0].Machine
  3116  
  3117  	provParams := params.ProvisioningScriptParams{
  3118  		MachineId: machineId,
  3119  		Nonce:     apiParams.Nonce,
  3120  	}
  3121  
  3122  	setUpdateBehavior := func(update, upgrade bool) {
  3123  		s.State.UpdateEnvironConfig(
  3124  			map[string]interface{}{
  3125  				"enable-os-upgrade":        upgrade,
  3126  				"enable-os-refresh-update": update,
  3127  			},
  3128  			nil,
  3129  			nil,
  3130  		)
  3131  	}
  3132  
  3133  	// Test enabling package commands
  3134  	provParams.DisablePackageCommands = false
  3135  	setUpdateBehavior(true, true)
  3136  	script, err := s.APIState.Client().ProvisioningScript(provParams)
  3137  	c.Assert(err, jc.ErrorIsNil)
  3138  	c.Check(script, jc.Contains, "apt-get update")
  3139  	c.Check(script, jc.Contains, "apt-get upgrade")
  3140  
  3141  	// Test disabling package commands
  3142  	provParams.DisablePackageCommands = true
  3143  	setUpdateBehavior(false, false)
  3144  	script, err = s.APIState.Client().ProvisioningScript(provParams)
  3145  	c.Assert(err, jc.ErrorIsNil)
  3146  	c.Check(script, gc.Not(jc.Contains), "apt-get update")
  3147  	c.Check(script, gc.Not(jc.Contains), "apt-get upgrade")
  3148  
  3149  	// Test client-specified DisablePackageCommands trumps environment
  3150  	// config variables.
  3151  	provParams.DisablePackageCommands = true
  3152  	setUpdateBehavior(true, true)
  3153  	script, err = s.APIState.Client().ProvisioningScript(provParams)
  3154  	c.Assert(err, jc.ErrorIsNil)
  3155  	c.Check(script, gc.Not(jc.Contains), "apt-get update")
  3156  	c.Check(script, gc.Not(jc.Contains), "apt-get upgrade")
  3157  
  3158  	// Test that in the abasence of a client-specified
  3159  	// DisablePackageCommands we use what's set in environments.yaml.
  3160  	provParams.DisablePackageCommands = false
  3161  	setUpdateBehavior(false, false)
  3162  	//provParams.UpdateBehavior = &params.UpdateBehavior{false, false}
  3163  	script, err = s.APIState.Client().ProvisioningScript(provParams)
  3164  	c.Assert(err, jc.ErrorIsNil)
  3165  	c.Check(script, gc.Not(jc.Contains), "apt-get update")
  3166  	c.Check(script, gc.Not(jc.Contains), "apt-get upgrade")
  3167  }
  3168  
  3169  func (s *clientSuite) TestClientSpecializeStoreOnDeployServiceSetCharmAndAddCharm(c *gc.C) {
  3170  	store := s.makeMockCharmStore()
  3171  
  3172  	attrs := map[string]interface{}{"charm-store-auth": "token=value",
  3173  		"test-mode": true}
  3174  	err := s.State.UpdateEnvironConfig(attrs, nil, nil)
  3175  	c.Assert(err, jc.ErrorIsNil)
  3176  
  3177  	curl, _ := addCharm(c, "dummy")
  3178  	err = s.APIState.Client().ServiceDeploy(
  3179  		curl.String(), "service", 3, "", constraints.Value{}, "",
  3180  	)
  3181  	c.Assert(err, jc.ErrorIsNil)
  3182  
  3183  	// check that the store's auth attributes were set
  3184  	c.Assert(store.AuthAttrs(), gc.Equals, "token=value")
  3185  	c.Assert(store.TestMode(), jc.IsTrue)
  3186  
  3187  	store.SetAuthAttrs("")
  3188  
  3189  	curl, _ = addCharm(c, "wordpress")
  3190  	err = s.APIState.Client().ServiceSetCharm(
  3191  		"service", curl.String(), false,
  3192  	)
  3193  
  3194  	// check that the store's auth attributes were set
  3195  	c.Assert(store.AuthAttrs(), gc.Equals, "token=value")
  3196  
  3197  	curl, _ = addCharm(c, "riak")
  3198  	err = s.APIState.Client().AddCharm(curl)
  3199  
  3200  	// check that the store's auth attributes were set
  3201  	c.Assert(store.AuthAttrs(), gc.Equals, "token=value")
  3202  }
  3203  
  3204  func (s *clientSuite) TestAddCharm(c *gc.C) {
  3205  	s.makeMockCharmStore()
  3206  
  3207  	var blobs blobs
  3208  	s.PatchValue(client.NewStateStorage, func(uuid string, session *mgo.Session) statestorage.Storage {
  3209  		storage := statestorage.NewStorage(uuid, session)
  3210  		return &recordingStorage{Storage: storage, blobs: &blobs}
  3211  	})
  3212  
  3213  	client := s.APIState.Client()
  3214  	// First test the sanity checks.
  3215  	err := client.AddCharm(&charm.URL{Name: "nonsense"})
  3216  	c.Assert(err, gc.ErrorMatches, `charm URL has invalid schema: ":nonsense-0"`)
  3217  	err = client.AddCharm(charm.MustParseURL("local:precise/dummy"))
  3218  	c.Assert(err, gc.ErrorMatches, "only charm store charm URLs are supported, with cs: schema")
  3219  	err = client.AddCharm(charm.MustParseURL("cs:precise/wordpress"))
  3220  	c.Assert(err, gc.ErrorMatches, "charm URL must include revision")
  3221  
  3222  	// Add a charm, without uploading it to storage, to
  3223  	// check that AddCharm does not try to do it.
  3224  	charmDir := testcharms.Repo.CharmDir("dummy")
  3225  	ident := fmt.Sprintf("%s-%d", charmDir.Meta().Name, charmDir.Revision())
  3226  	curl := charm.MustParseURL("cs:quantal/" + ident)
  3227  	sch, err := s.State.AddCharm(charmDir, curl, "", ident+"-sha256")
  3228  	c.Assert(err, jc.ErrorIsNil)
  3229  
  3230  	// AddCharm should see the charm in state and not upload it.
  3231  	err = client.AddCharm(sch.URL())
  3232  	c.Assert(err, jc.ErrorIsNil)
  3233  
  3234  	c.Assert(blobs.m, gc.HasLen, 0)
  3235  
  3236  	// Now try adding another charm completely.
  3237  	curl, _ = addCharm(c, "wordpress")
  3238  	err = client.AddCharm(curl)
  3239  	c.Assert(err, jc.ErrorIsNil)
  3240  
  3241  	// Verify it's in state and it got uploaded.
  3242  	storage := statestorage.NewStorage(s.State.EnvironUUID(), s.State.MongoSession())
  3243  	sch, err = s.State.Charm(curl)
  3244  	c.Assert(err, jc.ErrorIsNil)
  3245  	s.assertUploaded(c, storage, sch.StoragePath(), sch.BundleSha256())
  3246  }
  3247  
  3248  var resolveCharmCases = []struct {
  3249  	schema, defaultSeries, charmName string
  3250  	parseErr                         string
  3251  	resolveErr                       string
  3252  }{
  3253  	{"cs", "precise", "wordpress", "", ""},
  3254  	{"cs", "trusty", "wordpress", "", ""},
  3255  	{"cs", "", "wordpress", "", `charm url series is not resolved`},
  3256  	{"cs", "trusty", "", `charm URL has invalid charm name: "cs:"`, ""},
  3257  	{"local", "trusty", "wordpress", "", `only charm store charm references are supported, with cs: schema`},
  3258  	{"cs", "precise", "hl3", "", ""},
  3259  	{"cs", "trusty", "hl3", "", ""},
  3260  	{"cs", "", "hl3", "", `charm url series is not resolved`},
  3261  }
  3262  
  3263  func (s *clientSuite) TestResolveCharm(c *gc.C) {
  3264  	store := s.makeMockCharmStore()
  3265  
  3266  	for i, test := range resolveCharmCases {
  3267  		c.Logf("test %d: %#v", i, test)
  3268  		// Mock charm store will use this to resolve a charm reference.
  3269  		store.SetDefaultSeries(test.defaultSeries)
  3270  
  3271  		client := s.APIState.Client()
  3272  		ref, err := charm.ParseReference(fmt.Sprintf("%s:%s", test.schema, test.charmName))
  3273  		if test.parseErr == "" {
  3274  			if !c.Check(err, jc.ErrorIsNil) {
  3275  				continue
  3276  			}
  3277  		} else {
  3278  			c.Assert(err, gc.NotNil)
  3279  			c.Check(err, gc.ErrorMatches, test.parseErr)
  3280  			continue
  3281  		}
  3282  		c.Check(ref.String(), gc.Equals, fmt.Sprintf("%s:%s", test.schema, test.charmName))
  3283  
  3284  		curl, err := client.ResolveCharm(ref)
  3285  		if err == nil {
  3286  			c.Assert(curl, gc.NotNil)
  3287  			// Only cs: schema should make it through here
  3288  			c.Check(curl.String(), gc.Equals, fmt.Sprintf("cs:%s/%s", test.defaultSeries, test.charmName))
  3289  			c.Check(test.resolveErr, gc.Equals, "")
  3290  		} else {
  3291  			c.Check(curl, gc.IsNil)
  3292  			c.Check(err, gc.ErrorMatches, test.resolveErr)
  3293  		}
  3294  	}
  3295  }
  3296  
  3297  type blobs struct {
  3298  	sync.Mutex
  3299  	m map[string]bool // maps path to added (true), or deleted (false)
  3300  }
  3301  
  3302  // Add adds a path to the list of known paths.
  3303  func (b *blobs) Add(path string) {
  3304  	b.Lock()
  3305  	defer b.Unlock()
  3306  	b.check()
  3307  	b.m[path] = true
  3308  }
  3309  
  3310  // Remove marks a path as deleted, even if it was not previously Added.
  3311  func (b *blobs) Remove(path string) {
  3312  	b.Lock()
  3313  	defer b.Unlock()
  3314  	b.check()
  3315  	b.m[path] = false
  3316  }
  3317  
  3318  func (b *blobs) check() {
  3319  	if b.m == nil {
  3320  		b.m = make(map[string]bool)
  3321  	}
  3322  }
  3323  
  3324  type recordingStorage struct {
  3325  	statestorage.Storage
  3326  	putBarrier *sync.WaitGroup
  3327  	blobs      *blobs
  3328  }
  3329  
  3330  func (s *recordingStorage) Put(path string, r io.Reader, size int64) error {
  3331  	if s.putBarrier != nil {
  3332  		// This goroutine has gotten to Put() so mark it Done() and
  3333  		// wait for the other goroutines to get to this point.
  3334  		s.putBarrier.Done()
  3335  		s.putBarrier.Wait()
  3336  	}
  3337  	if err := s.Storage.Put(path, r, size); err != nil {
  3338  		return errors.Trace(err)
  3339  	}
  3340  	s.blobs.Add(path)
  3341  	return nil
  3342  }
  3343  
  3344  func (s *recordingStorage) Remove(path string) error {
  3345  	if err := s.Storage.Remove(path); err != nil {
  3346  		return errors.Trace(err)
  3347  	}
  3348  	s.blobs.Remove(path)
  3349  	return nil
  3350  }
  3351  
  3352  func (s *clientSuite) TestAddCharmConcurrently(c *gc.C) {
  3353  	s.makeMockCharmStore()
  3354  
  3355  	var putBarrier sync.WaitGroup
  3356  	var blobs blobs
  3357  	s.PatchValue(client.NewStateStorage, func(uuid string, session *mgo.Session) statestorage.Storage {
  3358  		storage := statestorage.NewStorage(uuid, session)
  3359  		return &recordingStorage{Storage: storage, blobs: &blobs, putBarrier: &putBarrier}
  3360  	})
  3361  
  3362  	client := s.APIState.Client()
  3363  	curl, _ := addCharm(c, "wordpress")
  3364  
  3365  	// Try adding the same charm concurrently from multiple goroutines
  3366  	// to test no "duplicate key errors" are reported (see lp bug
  3367  	// #1067979) and also at the end only one charm document is
  3368  	// created.
  3369  
  3370  	var wg sync.WaitGroup
  3371  	// We don't add them 1-by-1 because that would allow each goroutine to
  3372  	// finish separately without actually synchronizing between them
  3373  	putBarrier.Add(10)
  3374  	for i := 0; i < 10; i++ {
  3375  		wg.Add(1)
  3376  		go func(index int) {
  3377  			defer wg.Done()
  3378  
  3379  			c.Assert(client.AddCharm(curl), gc.IsNil, gc.Commentf("goroutine %d", index))
  3380  			sch, err := s.State.Charm(curl)
  3381  			c.Assert(err, gc.IsNil, gc.Commentf("goroutine %d", index))
  3382  			c.Assert(sch.URL(), jc.DeepEquals, curl, gc.Commentf("goroutine %d", index))
  3383  		}(i)
  3384  	}
  3385  	wg.Wait()
  3386  
  3387  	blobs.Lock()
  3388  
  3389  	c.Assert(blobs.m, gc.HasLen, 10)
  3390  
  3391  	// Verify there is only a single uploaded charm remains and it
  3392  	// contains the correct data.
  3393  	sch, err := s.State.Charm(curl)
  3394  	c.Assert(err, jc.ErrorIsNil)
  3395  	storagePath := sch.StoragePath()
  3396  	c.Assert(blobs.m[storagePath], jc.IsTrue)
  3397  	for path, exists := range blobs.m {
  3398  		if path != storagePath {
  3399  			c.Assert(exists, jc.IsFalse)
  3400  		}
  3401  	}
  3402  
  3403  	storage := statestorage.NewStorage(s.State.EnvironUUID(), s.State.MongoSession())
  3404  	s.assertUploaded(c, storage, sch.StoragePath(), sch.BundleSha256())
  3405  }
  3406  
  3407  func (s *clientSuite) TestAddCharmOverwritesPlaceholders(c *gc.C) {
  3408  	s.makeMockCharmStore()
  3409  
  3410  	client := s.APIState.Client()
  3411  	curl, _ := addCharm(c, "wordpress")
  3412  
  3413  	// Add a placeholder with the same charm URL.
  3414  	err := s.State.AddStoreCharmPlaceholder(curl)
  3415  	c.Assert(err, jc.ErrorIsNil)
  3416  	_, err = s.State.Charm(curl)
  3417  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  3418  
  3419  	// Now try to add the charm, which will convert the placeholder to
  3420  	// a pending charm.
  3421  	err = client.AddCharm(curl)
  3422  	c.Assert(err, jc.ErrorIsNil)
  3423  
  3424  	// Make sure the document's flags were reset as expected.
  3425  	sch, err := s.State.Charm(curl)
  3426  	c.Assert(err, jc.ErrorIsNil)
  3427  	c.Assert(sch.URL(), jc.DeepEquals, curl)
  3428  	c.Assert(sch.IsPlaceholder(), jc.IsFalse)
  3429  	c.Assert(sch.IsUploaded(), jc.IsTrue)
  3430  }
  3431  
  3432  func (s *clientSuite) assertUploaded(c *gc.C, storage statestorage.Storage, storagePath, expectedSHA256 string) {
  3433  	reader, _, err := storage.Get(storagePath)
  3434  	c.Assert(err, jc.ErrorIsNil)
  3435  	defer reader.Close()
  3436  	downloadedSHA256, _, err := utils.ReadSHA256(reader)
  3437  	c.Assert(err, jc.ErrorIsNil)
  3438  	c.Assert(downloadedSHA256, gc.Equals, expectedSHA256)
  3439  }
  3440  
  3441  func (s *clientSuite) TestRetryProvisioning(c *gc.C) {
  3442  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
  3443  	c.Assert(err, jc.ErrorIsNil)
  3444  	err = machine.SetStatus(state.StatusError, "error", nil)
  3445  	c.Assert(err, jc.ErrorIsNil)
  3446  	_, err = s.APIState.Client().RetryProvisioning(machine.Tag().(names.MachineTag))
  3447  	c.Assert(err, jc.ErrorIsNil)
  3448  
  3449  	status, info, data, err := machine.Status()
  3450  	c.Assert(err, jc.ErrorIsNil)
  3451  	c.Assert(status, gc.Equals, state.StatusError)
  3452  	c.Assert(info, gc.Equals, "error")
  3453  	c.Assert(data["transient"], jc.IsTrue)
  3454  }
  3455  
  3456  func (s *clientSuite) setupRetryProvisioning(c *gc.C) *state.Machine {
  3457  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
  3458  	c.Assert(err, jc.ErrorIsNil)
  3459  	err = machine.SetStatus(state.StatusError, "error", nil)
  3460  	c.Assert(err, jc.ErrorIsNil)
  3461  	return machine
  3462  }
  3463  
  3464  func (s *clientSuite) assertRetryProvisioningBlocked(c *gc.C, blocked bool, machine *state.Machine) {
  3465  	_, err := s.APIState.Client().RetryProvisioning(machine.Tag().(names.MachineTag))
  3466  	if blocked {
  3467  		c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
  3468  	} else {
  3469  		c.Assert(err, jc.ErrorIsNil)
  3470  		status, info, data, err := machine.Status()
  3471  		c.Assert(err, jc.ErrorIsNil)
  3472  		c.Assert(status, gc.Equals, state.StatusError)
  3473  		c.Assert(info, gc.Equals, "error")
  3474  		c.Assert(data["transient"], jc.IsTrue)
  3475  	}
  3476  }
  3477  
  3478  func (s *clientSuite) TestBlockDestroyRetryProvisioning(c *gc.C) {
  3479  	m := s.setupRetryProvisioning(c)
  3480  	s.blockDestroyEnvironment(c)
  3481  	s.assertRetryProvisioningBlocked(c, false, m)
  3482  }
  3483  
  3484  func (s *clientSuite) TestBlockRemoveRetryProvisioning(c *gc.C) {
  3485  	m := s.setupRetryProvisioning(c)
  3486  	s.blockRemoveObject(c)
  3487  	s.assertRetryProvisioningBlocked(c, false, m)
  3488  }
  3489  
  3490  func (s *clientSuite) TestBlockChangesRetryProvisioning(c *gc.C) {
  3491  	m := s.setupRetryProvisioning(c)
  3492  	s.blockAllChanges(c)
  3493  	s.assertRetryProvisioningBlocked(c, true, m)
  3494  }
  3495  
  3496  func (s *clientSuite) TestAPIHostPorts(c *gc.C) {
  3497  	server1Addresses := []network.Address{{
  3498  		Value: "server-1",
  3499  		Type:  network.HostName,
  3500  		Scope: network.ScopePublic,
  3501  	}, {
  3502  		Value:       "10.0.0.1",
  3503  		Type:        network.IPv4Address,
  3504  		NetworkName: "internal",
  3505  		Scope:       network.ScopeCloudLocal,
  3506  	}}
  3507  	server2Addresses := []network.Address{{
  3508  		Value:       "::1",
  3509  		Type:        network.IPv6Address,
  3510  		NetworkName: "loopback",
  3511  		Scope:       network.ScopeMachineLocal,
  3512  	}}
  3513  	stateAPIHostPorts := [][]network.HostPort{
  3514  		network.AddressesWithPort(server1Addresses, 123),
  3515  		network.AddressesWithPort(server2Addresses, 456),
  3516  	}
  3517  
  3518  	err := s.State.SetAPIHostPorts(stateAPIHostPorts)
  3519  	c.Assert(err, jc.ErrorIsNil)
  3520  	apiHostPorts, err := s.APIState.Client().APIHostPorts()
  3521  	c.Assert(err, jc.ErrorIsNil)
  3522  	c.Assert(apiHostPorts, gc.DeepEquals, stateAPIHostPorts)
  3523  }
  3524  
  3525  func (s *clientSuite) TestClientAgentVersion(c *gc.C) {
  3526  	current := version.MustParse("1.2.0")
  3527  	s.PatchValue(&version.Current.Number, current)
  3528  	result, err := s.APIState.Client().AgentVersion()
  3529  	c.Assert(err, jc.ErrorIsNil)
  3530  	c.Assert(result, gc.Equals, current)
  3531  }
  3532  
  3533  func (s *clientSuite) TestMachineJobFromParams(c *gc.C) {
  3534  	var tests = []struct {
  3535  		name multiwatcher.MachineJob
  3536  		want state.MachineJob
  3537  		err  string
  3538  	}{{
  3539  		name: multiwatcher.JobHostUnits,
  3540  		want: state.JobHostUnits,
  3541  	}, {
  3542  		name: multiwatcher.JobManageEnviron,
  3543  		want: state.JobManageEnviron,
  3544  	}, {
  3545  		name: multiwatcher.JobManageNetworking,
  3546  		want: state.JobManageNetworking,
  3547  	}, {
  3548  		name: multiwatcher.JobManageStateDeprecated,
  3549  		want: state.JobManageStateDeprecated,
  3550  	}, {
  3551  		name: "invalid",
  3552  		want: -1,
  3553  		err:  `invalid machine job "invalid"`,
  3554  	}}
  3555  	for _, test := range tests {
  3556  		got, err := client.MachineJobFromParams(test.name)
  3557  		if err != nil {
  3558  			c.Check(err, gc.ErrorMatches, test.err)
  3559  		}
  3560  		c.Check(got, gc.Equals, test.want)
  3561  	}
  3562  }
  3563  
  3564  func (s *serverSuite) TestBlockServiceDestroy(c *gc.C) {
  3565  	s.AddTestingService(c, "dummy-service", s.AddTestingCharm(c, "dummy"))
  3566  	// block remove-objects
  3567  	s.blockRemoveObject(c)
  3568  
  3569  	for i, t := range serviceDestroyTests {
  3570  		c.Logf("test %d. %s", i, t.about)
  3571  		err := s.APIState.Client().ServiceDestroy(t.service)
  3572  		c.Assert(errors.Cause(err), gc.ErrorMatches, common.ErrOperationBlocked.Error())
  3573  		// Tests may have invalid service names.
  3574  		service, err := s.State.Service(t.service)
  3575  		if err == nil {
  3576  			// For valid service names, check that service is alive :-)
  3577  			assertLife(c, service, state.Alive)
  3578  		}
  3579  	}
  3580  }
  3581  
  3582  func (s *clientSuite) assertDestroyMachineSuccess(c *gc.C, u *state.Unit, m0, m1, m2 *state.Machine) {
  3583  	err := s.APIState.Client().DestroyMachines("0", "1", "2")
  3584  	c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 is required by the environment; machine 1 has unit "wordpress/0" assigned`)
  3585  	assertLife(c, m0, state.Alive)
  3586  	assertLife(c, m1, state.Alive)
  3587  	assertLife(c, m2, state.Dying)
  3588  
  3589  	err = u.UnassignFromMachine()
  3590  	c.Assert(err, jc.ErrorIsNil)
  3591  	err = s.APIState.Client().DestroyMachines("0", "1", "2")
  3592  	c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 is required by the environment`)
  3593  	assertLife(c, m0, state.Alive)
  3594  	assertLife(c, m1, state.Dying)
  3595  	assertLife(c, m2, state.Dying)
  3596  }
  3597  
  3598  func (s *clientSuite) assertBlockedErrorAndLiveliness(c *gc.C, err error,
  3599  	living1 state.Living,
  3600  	living2 state.Living,
  3601  	living3 state.Living,
  3602  	living4 state.Living) {
  3603  	c.Assert(errors.Cause(err), gc.ErrorMatches, common.ErrOperationBlocked.Error())
  3604  	assertLife(c, living1, state.Alive)
  3605  	assertLife(c, living2, state.Alive)
  3606  	assertLife(c, living3, state.Alive)
  3607  	assertLife(c, living4, state.Alive)
  3608  }
  3609  
  3610  func (s *clientSuite) TestBlockRemoveDestroyMachines(c *gc.C) {
  3611  	m0, m1, m2, u := s.setupDestroyMachinesTest(c)
  3612  	s.blockRemoveObject(c)
  3613  	err := s.APIState.Client().DestroyMachines("0", "1", "2")
  3614  	s.assertBlockedErrorAndLiveliness(c, err, m0, m1, m2, u)
  3615  }
  3616  
  3617  func (s *clientSuite) TestBlockChangesDestroyMachines(c *gc.C) {
  3618  	m0, m1, m2, u := s.setupDestroyMachinesTest(c)
  3619  	s.blockAllChanges(c)
  3620  	err := s.APIState.Client().DestroyMachines("0", "1", "2")
  3621  	s.assertBlockedErrorAndLiveliness(c, err, m0, m1, m2, u)
  3622  }
  3623  
  3624  func (s *clientSuite) TestBlockDestoryDestroyMachines(c *gc.C) {
  3625  	m0, m1, m2, u := s.setupDestroyMachinesTest(c)
  3626  	s.blockDestroyEnvironment(c)
  3627  	s.assertDestroyMachineSuccess(c, u, m0, m1, m2)
  3628  }
  3629  
  3630  func (s *clientSuite) TestAnyBlockForceDestroyMachines(c *gc.C) {
  3631  	// force bypasses all blocks
  3632  	s.blockAllChanges(c)
  3633  	s.blockDestroyEnvironment(c)
  3634  	s.blockRemoveObject(c)
  3635  	s.assertForceDestroyMachines(c)
  3636  }
  3637  
  3638  func (s *clientSuite) assertForceDestroyMachines(c *gc.C) {
  3639  	m0, m1, m2, u := s.setupDestroyMachinesTest(c)
  3640  
  3641  	err := s.APIState.Client().ForceDestroyMachines("0", "1", "2")
  3642  	c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 is required by the environment`)
  3643  	assertLife(c, m0, state.Alive)
  3644  	assertLife(c, m1, state.Alive)
  3645  	assertLife(c, m2, state.Alive)
  3646  	assertLife(c, u, state.Alive)
  3647  
  3648  	err = s.State.Cleanup()
  3649  	c.Assert(err, jc.ErrorIsNil)
  3650  	assertLife(c, m0, state.Alive)
  3651  	assertLife(c, m1, state.Dead)
  3652  	assertLife(c, m2, state.Dead)
  3653  	assertRemoved(c, u)
  3654  }
  3655  
  3656  func (s *clientSuite) assertDestroyPrincipalUnits(c *gc.C, units []*state.Unit) {
  3657  	// Destroy 2 of them; check they become Dying.
  3658  	err := s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/1")
  3659  	c.Assert(err, jc.ErrorIsNil)
  3660  	assertLife(c, units[0], state.Dying)
  3661  	assertLife(c, units[1], state.Dying)
  3662  
  3663  	// Try to destroy an Alive one and a Dying one; check
  3664  	// it destroys the Alive one and ignores the Dying one.
  3665  	err = s.APIState.Client().DestroyServiceUnits("wordpress/2", "wordpress/0")
  3666  	c.Assert(err, jc.ErrorIsNil)
  3667  	assertLife(c, units[2], state.Dying)
  3668  
  3669  	// Try to destroy an Alive one along with a nonexistent one; check that
  3670  	// the valid instruction is followed but the invalid one is warned about.
  3671  	err = s.APIState.Client().DestroyServiceUnits("boojum/123", "wordpress/3")
  3672  	c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`)
  3673  	assertLife(c, units[3], state.Dying)
  3674  
  3675  	// Make one Dead, and destroy an Alive one alongside it; check no errors.
  3676  	wp0, err := s.State.Unit("wordpress/0")
  3677  	c.Assert(err, jc.ErrorIsNil)
  3678  	err = wp0.EnsureDead()
  3679  	c.Assert(err, jc.ErrorIsNil)
  3680  	err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/4")
  3681  	c.Assert(err, jc.ErrorIsNil)
  3682  	assertLife(c, units[0], state.Dead)
  3683  	assertLife(c, units[4], state.Dying)
  3684  }
  3685  
  3686  func (s *clientSuite) setupDestroyPrincipalUnits(c *gc.C) []*state.Unit {
  3687  	units := make([]*state.Unit, 5)
  3688  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  3689  	for i := range units {
  3690  		unit, err := wordpress.AddUnit()
  3691  		c.Assert(err, jc.ErrorIsNil)
  3692  		err = unit.SetStatus(state.StatusActive, "", nil)
  3693  		c.Assert(err, jc.ErrorIsNil)
  3694  		units[i] = unit
  3695  	}
  3696  	return units
  3697  }
  3698  func (s *clientSuite) TestBlockChangesDestroyPrincipalUnits(c *gc.C) {
  3699  	units := s.setupDestroyPrincipalUnits(c)
  3700  	s.blockAllChanges(c)
  3701  	err := s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/1")
  3702  	s.assertBlockedErrorAndLiveliness(c, err, units[0], units[1], units[2], units[3])
  3703  }
  3704  
  3705  func (s *clientSuite) TestBlockRemoveDestroyPrincipalUnits(c *gc.C) {
  3706  	units := s.setupDestroyPrincipalUnits(c)
  3707  	s.blockRemoveObject(c)
  3708  	err := s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/1")
  3709  	s.assertBlockedErrorAndLiveliness(c, err, units[0], units[1], units[2], units[3])
  3710  }
  3711  
  3712  func (s *clientSuite) TestBlockDestroyDestroyPrincipalUnits(c *gc.C) {
  3713  	units := s.setupDestroyPrincipalUnits(c)
  3714  	s.blockDestroyEnvironment(c)
  3715  	err := s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/1")
  3716  	c.Assert(err, jc.ErrorIsNil)
  3717  	assertLife(c, units[0], state.Dying)
  3718  	assertLife(c, units[1], state.Dying)
  3719  }
  3720  
  3721  func (s *clientSuite) assertDestroySubordinateUnits(c *gc.C, wordpress0, logging0 *state.Unit) {
  3722  	// Try to destroy the principal and the subordinate together; check it warns
  3723  	// about the subordinate, but destroys the one it can. (The principal unit
  3724  	// agent will be resposible for destroying the subordinate.)
  3725  	err := s.APIState.Client().DestroyServiceUnits("wordpress/0", "logging/0")
  3726  	c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate`)
  3727  	assertLife(c, wordpress0, state.Dying)
  3728  	assertLife(c, logging0, state.Alive)
  3729  }
  3730  
  3731  func (s *clientSuite) TestBlockRemoveDestroySubordinateUnits(c *gc.C) {
  3732  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  3733  	wordpress0, err := wordpress.AddUnit()
  3734  	c.Assert(err, jc.ErrorIsNil)
  3735  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
  3736  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  3737  	c.Assert(err, jc.ErrorIsNil)
  3738  	rel, err := s.State.AddRelation(eps...)
  3739  	c.Assert(err, jc.ErrorIsNil)
  3740  	ru, err := rel.Unit(wordpress0)
  3741  	c.Assert(err, jc.ErrorIsNil)
  3742  	err = ru.EnterScope(nil)
  3743  	c.Assert(err, jc.ErrorIsNil)
  3744  	logging0, err := s.State.Unit("logging/0")
  3745  	c.Assert(err, jc.ErrorIsNil)
  3746  
  3747  	s.blockRemoveObject(c)
  3748  	// Try to destroy the subordinate alone; check it fails.
  3749  	err = s.APIState.Client().DestroyServiceUnits("logging/0")
  3750  	c.Assert(errors.Cause(err), gc.ErrorMatches, common.ErrOperationBlocked.Error())
  3751  	assertLife(c, rel, state.Alive)
  3752  	assertLife(c, wordpress0, state.Alive)
  3753  	assertLife(c, logging0, state.Alive)
  3754  
  3755  	err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "logging/0")
  3756  	c.Assert(errors.Cause(err), gc.ErrorMatches, common.ErrOperationBlocked.Error())
  3757  	assertLife(c, wordpress0, state.Alive)
  3758  	assertLife(c, logging0, state.Alive)
  3759  	assertLife(c, rel, state.Alive)
  3760  }
  3761  
  3762  func (s *clientSuite) TestBlockChangesDestroySubordinateUnits(c *gc.C) {
  3763  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  3764  	wordpress0, err := wordpress.AddUnit()
  3765  	c.Assert(err, jc.ErrorIsNil)
  3766  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
  3767  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  3768  	c.Assert(err, jc.ErrorIsNil)
  3769  	rel, err := s.State.AddRelation(eps...)
  3770  	c.Assert(err, jc.ErrorIsNil)
  3771  	ru, err := rel.Unit(wordpress0)
  3772  	c.Assert(err, jc.ErrorIsNil)
  3773  	err = ru.EnterScope(nil)
  3774  	c.Assert(err, jc.ErrorIsNil)
  3775  	logging0, err := s.State.Unit("logging/0")
  3776  	c.Assert(err, jc.ErrorIsNil)
  3777  
  3778  	s.blockAllChanges(c)
  3779  	// Try to destroy the subordinate alone; check it fails.
  3780  	err = s.APIState.Client().DestroyServiceUnits("logging/0")
  3781  	c.Assert(errors.Cause(err), gc.ErrorMatches, common.ErrOperationBlocked.Error())
  3782  	assertLife(c, rel, state.Alive)
  3783  	assertLife(c, wordpress0, state.Alive)
  3784  	assertLife(c, logging0, state.Alive)
  3785  
  3786  	err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "logging/0")
  3787  	c.Assert(errors.Cause(err), gc.ErrorMatches, common.ErrOperationBlocked.Error())
  3788  	assertLife(c, wordpress0, state.Alive)
  3789  	assertLife(c, logging0, state.Alive)
  3790  	assertLife(c, rel, state.Alive)
  3791  }
  3792  
  3793  func (s *clientSuite) TestBlockDestroyDestroySubordinateUnits(c *gc.C) {
  3794  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  3795  	wordpress0, err := wordpress.AddUnit()
  3796  	c.Assert(err, jc.ErrorIsNil)
  3797  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
  3798  	eps, err := s.State.InferEndpoints("logging", "wordpress")
  3799  	c.Assert(err, jc.ErrorIsNil)
  3800  	rel, err := s.State.AddRelation(eps...)
  3801  	c.Assert(err, jc.ErrorIsNil)
  3802  	ru, err := rel.Unit(wordpress0)
  3803  	c.Assert(err, jc.ErrorIsNil)
  3804  	err = ru.EnterScope(nil)
  3805  	c.Assert(err, jc.ErrorIsNil)
  3806  	logging0, err := s.State.Unit("logging/0")
  3807  	c.Assert(err, jc.ErrorIsNil)
  3808  
  3809  	s.blockDestroyEnvironment(c)
  3810  	// Try to destroy the subordinate alone; check it fails.
  3811  	err = s.APIState.Client().DestroyServiceUnits("logging/0")
  3812  	c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`)
  3813  	assertLife(c, logging0, state.Alive)
  3814  
  3815  	s.assertDestroySubordinateUnits(c, wordpress0, logging0)
  3816  }
  3817  
  3818  func (s *clientSuite) TestBlockRemoveDestroyRelation(c *gc.C) {
  3819  	endpoints := []string{"wordpress", "mysql"}
  3820  	relation := s.setupRelationScenario(c, endpoints)
  3821  	// block remove-objects
  3822  	s.blockRemoveObject(c)
  3823  	err := s.APIState.Client().DestroyRelation(endpoints...)
  3824  	c.Assert(errors.Cause(err), gc.ErrorMatches, common.ErrOperationBlocked.Error())
  3825  	assertLife(c, relation, state.Alive)
  3826  }
  3827  
  3828  func (s *clientSuite) TestBlockChangeDestroyRelation(c *gc.C) {
  3829  	endpoints := []string{"wordpress", "mysql"}
  3830  	relation := s.setupRelationScenario(c, endpoints)
  3831  	s.blockAllChanges(c)
  3832  	err := s.APIState.Client().DestroyRelation(endpoints...)
  3833  	c.Assert(errors.Cause(err), gc.ErrorMatches, common.ErrOperationBlocked.Error())
  3834  	assertLife(c, relation, state.Alive)
  3835  }
  3836  
  3837  func (s *clientSuite) TestBlockDestroyDestroyRelation(c *gc.C) {
  3838  	s.blockDestroyEnvironment(c)
  3839  	endpoints := []string{"wordpress", "mysql"}
  3840  	s.assertDestroyRelation(c, endpoints)
  3841  }