github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/client/highavailability/highavailability_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package highavailability_test
     5  
     6  import (
     7  	"fmt"
     8  	stdtesting "testing"
     9  
    10  	"github.com/juju/errors"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  	"gopkg.in/juju/worker.v1"
    14  
    15  	"github.com/juju/juju/apiserver/common"
    16  	commontesting "github.com/juju/juju/apiserver/common/testing"
    17  	"github.com/juju/juju/apiserver/facades/client/highavailability"
    18  	"github.com/juju/juju/apiserver/params"
    19  	apiservertesting "github.com/juju/juju/apiserver/testing"
    20  	"github.com/juju/juju/controller"
    21  	"github.com/juju/juju/core/constraints"
    22  	"github.com/juju/juju/juju/testing"
    23  	"github.com/juju/juju/network"
    24  	"github.com/juju/juju/state"
    25  	"github.com/juju/juju/state/presence"
    26  	coretesting "github.com/juju/juju/testing"
    27  	"github.com/juju/juju/testing/factory"
    28  )
    29  
    30  func TestAll(t *stdtesting.T) {
    31  	coretesting.MgoTestPackage(t)
    32  }
    33  
    34  type clientSuite struct {
    35  	testing.JujuConnSuite
    36  
    37  	resources      *common.Resources
    38  	authoriser     apiservertesting.FakeAuthorizer
    39  	haServer       *highavailability.HighAvailabilityAPI
    40  	machine0Pinger *presence.Pinger
    41  
    42  	commontesting.BlockHelper
    43  }
    44  
    45  var _ = gc.Suite(&clientSuite{})
    46  
    47  var (
    48  	emptyCons      = constraints.Value{}
    49  	controllerCons = constraints.MustParse("mem=16G cores=16")
    50  	defaultSeries  = ""
    51  )
    52  
    53  func (s *clientSuite) SetUpTest(c *gc.C) {
    54  	s.JujuConnSuite.SetUpTest(c)
    55  	s.resources = common.NewResources()
    56  	err := s.resources.RegisterNamed("machineID", common.StringResource("0"))
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	s.AddCleanup(func(_ *gc.C) { s.resources.StopAll() })
    59  
    60  	s.authoriser = apiservertesting.FakeAuthorizer{
    61  		Tag:        s.AdminUserTag(c),
    62  		Controller: true,
    63  	}
    64  
    65  	s.haServer, err = highavailability.NewHighAvailabilityAPI(s.State, s.resources, s.authoriser)
    66  	c.Assert(err, jc.ErrorIsNil)
    67  
    68  	_, err = s.State.AddMachines(state.MachineTemplate{
    69  		Series:      "quantal",
    70  		Jobs:        []state.MachineJob{state.JobManageModel},
    71  		Constraints: controllerCons,
    72  		Addresses: []network.Address{
    73  			network.NewScopedAddress("127.0.0.1", network.ScopeMachineLocal),
    74  			network.NewScopedAddress("cloud-local0.internal", network.ScopeCloudLocal),
    75  			network.NewScopedAddress("fc00::0", network.ScopePublic),
    76  		},
    77  	})
    78  	c.Assert(err, jc.ErrorIsNil)
    79  
    80  	// We have to ensure the agents are alive, or EnableHA will
    81  	// create more to replace them.
    82  	s.machine0Pinger = s.setAgentPresence(c, "0")
    83  	s.BlockHelper = commontesting.NewBlockHelper(s.APIState)
    84  	s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() })
    85  }
    86  
    87  func (s *clientSuite) setAgentPresence(c *gc.C, machineId string) *presence.Pinger {
    88  	m, err := s.State.Machine(machineId)
    89  	c.Assert(err, jc.ErrorIsNil)
    90  	pinger, err := m.SetAgentPresence()
    91  	c.Assert(err, jc.ErrorIsNil)
    92  	s.AddCleanup(func(c *gc.C) {
    93  		c.Assert(worker.Stop(pinger), jc.ErrorIsNil)
    94  	})
    95  
    96  	s.State.StartSync()
    97  	err = m.WaitAgentPresence(coretesting.LongWait)
    98  	c.Assert(err, jc.ErrorIsNil)
    99  	return pinger
   100  }
   101  
   102  func (s *clientSuite) setMachineAddresses(c *gc.C, machineId string) {
   103  	m, err := s.State.Machine(machineId)
   104  	c.Assert(err, jc.ErrorIsNil)
   105  	err = m.SetMachineAddresses(
   106  		network.NewScopedAddress("127.0.0.1", network.ScopeMachineLocal),
   107  		network.NewScopedAddress(fmt.Sprintf("cloud-local%s.internal", machineId), network.ScopeCloudLocal),
   108  		network.NewScopedAddress(fmt.Sprintf("fc0%s::1", machineId), network.ScopePublic),
   109  	)
   110  	c.Assert(err, jc.ErrorIsNil)
   111  }
   112  
   113  func (s *clientSuite) enableHA(
   114  	c *gc.C, numControllers int, cons constraints.Value, series string, placement []string,
   115  ) (params.ControllersChanges, error) {
   116  	return enableHA(c, s.haServer, numControllers, cons, series, placement)
   117  }
   118  
   119  func enableHA(
   120  	c *gc.C,
   121  	haServer *highavailability.HighAvailabilityAPI,
   122  	numControllers int,
   123  	cons constraints.Value,
   124  	series string,
   125  	placement []string,
   126  ) (params.ControllersChanges, error) {
   127  	arg := params.ControllersSpecs{
   128  		Specs: []params.ControllersSpec{{
   129  			NumControllers: numControllers,
   130  			Constraints:    cons,
   131  			Series:         series,
   132  			Placement:      placement,
   133  		}}}
   134  	results, err := haServer.EnableHA(arg)
   135  	c.Assert(err, jc.ErrorIsNil)
   136  	c.Assert(results.Results, gc.HasLen, 1)
   137  	result := results.Results[0]
   138  	// We explicitly return nil here so we can do typed nil checking
   139  	// of the result like normal.
   140  	err = nil
   141  	if result.Error != nil {
   142  		err = result.Error
   143  	}
   144  	return result.Result, err
   145  }
   146  
   147  func (s *clientSuite) TestEnableHASeries(c *gc.C) {
   148  	machines, err := s.State.AllMachines()
   149  	c.Assert(err, jc.ErrorIsNil)
   150  	c.Assert(machines, gc.HasLen, 1)
   151  	c.Assert(machines[0].Series(), gc.Equals, "quantal")
   152  
   153  	enableHAResult, err := s.enableHA(c, 3, emptyCons, defaultSeries, nil)
   154  	c.Assert(err, jc.ErrorIsNil)
   155  	c.Assert(enableHAResult.Maintained, gc.DeepEquals, []string{"machine-0"})
   156  	c.Assert(enableHAResult.Added, gc.DeepEquals, []string{"machine-1", "machine-2"})
   157  	c.Assert(enableHAResult.Removed, gc.HasLen, 0)
   158  	c.Assert(enableHAResult.Converted, gc.HasLen, 0)
   159  
   160  	machines, err = s.State.AllMachines()
   161  	c.Assert(err, jc.ErrorIsNil)
   162  	c.Assert(machines, gc.HasLen, 3)
   163  	c.Assert(machines[0].Series(), gc.Equals, "quantal")
   164  	c.Assert(machines[1].Series(), gc.Equals, "quantal")
   165  	c.Assert(machines[2].Series(), gc.Equals, "quantal")
   166  
   167  	s.setAgentPresence(c, "1")
   168  	s.setAgentPresence(c, "2")
   169  	s.setMachineAddresses(c, "1")
   170  	s.setMachineAddresses(c, "2")
   171  
   172  	enableHAResult, err = s.enableHA(c, 5, emptyCons, "non-default", nil)
   173  	c.Assert(err, jc.ErrorIsNil)
   174  	c.Assert(enableHAResult.Maintained, gc.DeepEquals, []string{"machine-0", "machine-1", "machine-2"})
   175  	c.Assert(enableHAResult.Added, gc.DeepEquals, []string{"machine-3", "machine-4"})
   176  	c.Assert(enableHAResult.Removed, gc.HasLen, 0)
   177  	c.Assert(enableHAResult.Converted, gc.HasLen, 0)
   178  
   179  	c.Assert(err, jc.ErrorIsNil)
   180  	machines, err = s.State.AllMachines()
   181  	c.Assert(err, jc.ErrorIsNil)
   182  	c.Assert(machines, gc.HasLen, 5)
   183  	c.Assert(machines[0].Series(), gc.Equals, "quantal")
   184  	c.Assert(machines[1].Series(), gc.Equals, "quantal")
   185  	c.Assert(machines[2].Series(), gc.Equals, "quantal")
   186  	c.Assert(machines[3].Series(), gc.Equals, "non-default")
   187  	c.Assert(machines[4].Series(), gc.Equals, "non-default")
   188  }
   189  
   190  func (s *clientSuite) TestEnableHAErrorForMultiCloudLocal(c *gc.C) {
   191  	machines, err := s.State.AllMachines()
   192  	c.Assert(err, jc.ErrorIsNil)
   193  	c.Assert(machines, gc.HasLen, 1)
   194  	c.Assert(machines[0].Series(), gc.Equals, "quantal")
   195  
   196  	err = machines[0].SetMachineAddresses(
   197  		network.NewScopedAddress("cloud-local2.internal", network.ScopeCloudLocal),
   198  		network.NewScopedAddress("cloud-local22.internal", network.ScopeCloudLocal),
   199  	)
   200  	c.Assert(err, jc.ErrorIsNil)
   201  
   202  	_, err = s.enableHA(c, 3, emptyCons, defaultSeries, nil)
   203  	c.Assert(err, gc.ErrorMatches,
   204  		"juju-ha-space is not set and a unique usable address was not found for machines: 0"+
   205  			"\nrun \"juju config juju-ha-space=<name>\" to set a space for Mongo peer communication")
   206  }
   207  
   208  func (s *clientSuite) TestEnableHAErrorForNoCloudLocal(c *gc.C) {
   209  	m0, err := s.State.Machine("0")
   210  	c.Assert(err, jc.ErrorIsNil)
   211  	c.Assert(m0.Series(), gc.Equals, "quantal")
   212  
   213  	// remove the extra provider addresses, so we have no valid CloudLocal addresses
   214  	c.Assert(m0.SetProviderAddresses(
   215  		network.NewScopedAddress("127.0.0.1", network.ScopeMachineLocal),
   216  	), jc.ErrorIsNil)
   217  
   218  	_, err = s.enableHA(c, 3, emptyCons, defaultSeries, nil)
   219  	c.Assert(err, gc.ErrorMatches,
   220  		"juju-ha-space is not set and a unique usable address was not found for machines: 0"+
   221  			"\nrun \"juju config juju-ha-space=<name>\" to set a space for Mongo peer communication")
   222  }
   223  
   224  func (s *clientSuite) TestEnableHANoErrorForNoAddresses(c *gc.C) {
   225  	enableHAResult, err := s.enableHA(c, 0, emptyCons, defaultSeries, nil)
   226  	c.Assert(err, jc.ErrorIsNil)
   227  	c.Assert(enableHAResult.Maintained, gc.DeepEquals, []string{"machine-0"})
   228  	c.Assert(enableHAResult.Added, gc.DeepEquals, []string{"machine-1", "machine-2"})
   229  	c.Assert(enableHAResult.Removed, gc.HasLen, 0)
   230  	c.Assert(enableHAResult.Converted, gc.HasLen, 0)
   231  
   232  	s.setMachineAddresses(c, "0")
   233  	s.setMachineAddresses(c, "1")
   234  	// 0 and 1 are up, but 2 hasn't finished booting yet, so has no addresses set
   235  
   236  	_, err = s.enableHA(c, 3, emptyCons, defaultSeries, nil)
   237  	c.Assert(err, jc.ErrorIsNil)
   238  }
   239  
   240  func (s *clientSuite) TestEnableHAAddMachinesErrorForMultiCloudLocal(c *gc.C) {
   241  	machines, err := s.State.AllMachines()
   242  	c.Assert(err, jc.ErrorIsNil)
   243  	c.Assert(machines, gc.HasLen, 1)
   244  	c.Assert(machines[0].Series(), gc.Equals, "quantal")
   245  
   246  	enableHAResult, err := s.enableHA(c, 3, emptyCons, defaultSeries, nil)
   247  	c.Assert(err, jc.ErrorIsNil)
   248  	c.Assert(enableHAResult.Added, gc.DeepEquals, []string{"machine-1", "machine-2"})
   249  
   250  	s.setMachineAddresses(c, "1")
   251  
   252  	m, err := s.State.Machine("2")
   253  	c.Assert(err, jc.ErrorIsNil)
   254  	err = m.SetMachineAddresses(
   255  		network.NewScopedAddress("cloud-local2.internal", network.ScopeCloudLocal),
   256  		network.NewScopedAddress("cloud-local22.internal", network.ScopeCloudLocal),
   257  	)
   258  	c.Assert(err, jc.ErrorIsNil)
   259  
   260  	_, err = s.enableHA(c, 5, emptyCons, defaultSeries, nil)
   261  	c.Assert(err, gc.ErrorMatches,
   262  		"juju-ha-space is not set and a unique usable address was not found for machines: 2"+
   263  			"\nrun \"juju config juju-ha-space=<name>\" to set a space for Mongo peer communication")
   264  }
   265  
   266  func (s *clientSuite) TestEnableHAConstraints(c *gc.C) {
   267  	enableHAResult, err := s.enableHA(c, 3, constraints.MustParse("mem=4G"), defaultSeries, nil)
   268  	c.Assert(err, jc.ErrorIsNil)
   269  	c.Assert(enableHAResult.Maintained, gc.DeepEquals, []string{"machine-0"})
   270  	c.Assert(enableHAResult.Added, gc.DeepEquals, []string{"machine-1", "machine-2"})
   271  	c.Assert(enableHAResult.Removed, gc.HasLen, 0)
   272  	c.Assert(enableHAResult.Converted, gc.HasLen, 0)
   273  
   274  	machines, err := s.State.AllMachines()
   275  	c.Assert(err, jc.ErrorIsNil)
   276  	c.Assert(machines, gc.HasLen, 3)
   277  	expectedCons := []constraints.Value{
   278  		controllerCons,
   279  		constraints.MustParse("mem=4G"),
   280  		constraints.MustParse("mem=4G"),
   281  	}
   282  	for i, m := range machines {
   283  		cons, err := m.Constraints()
   284  		c.Assert(err, jc.ErrorIsNil)
   285  		c.Check(cons, gc.DeepEquals, expectedCons[i])
   286  	}
   287  }
   288  
   289  func (s *clientSuite) TestEnableHAEmptyConstraints(c *gc.C) {
   290  	enableHAResult, err := s.enableHA(c, 3, emptyCons, defaultSeries, nil)
   291  	c.Assert(err, jc.ErrorIsNil)
   292  	c.Assert(enableHAResult.Maintained, gc.DeepEquals, []string{"machine-0"})
   293  	c.Assert(enableHAResult.Added, gc.DeepEquals, []string{"machine-1", "machine-2"})
   294  	c.Assert(enableHAResult.Removed, gc.HasLen, 0)
   295  	c.Assert(enableHAResult.Converted, gc.HasLen, 0)
   296  
   297  	machines, err := s.State.AllMachines()
   298  	c.Assert(err, jc.ErrorIsNil)
   299  	c.Assert(machines, gc.HasLen, 3)
   300  	for _, m := range machines {
   301  		cons, err := m.Constraints()
   302  		c.Assert(err, jc.ErrorIsNil)
   303  		c.Check(cons, gc.DeepEquals, controllerCons)
   304  	}
   305  }
   306  
   307  func (s *clientSuite) TestEnableHAControllerConfigConstraints(c *gc.C) {
   308  	controllerSettings, _ := s.State.ReadSettings("controllers", "controllerSettings")
   309  	controllerSettings.Set(controller.JujuHASpace, "ha-space")
   310  	controllerSettings.Write()
   311  
   312  	enableHAResult, err := s.enableHA(c, 3, constraints.MustParse("spaces=random-space"), defaultSeries, nil)
   313  	c.Assert(err, jc.ErrorIsNil)
   314  	c.Assert(enableHAResult.Maintained, gc.DeepEquals, []string{"machine-0"})
   315  	c.Assert(enableHAResult.Added, gc.DeepEquals, []string{"machine-1", "machine-2"})
   316  	c.Assert(enableHAResult.Removed, gc.HasLen, 0)
   317  	c.Assert(enableHAResult.Converted, gc.HasLen, 0)
   318  
   319  	machines, err := s.State.AllMachines()
   320  	c.Assert(err, jc.ErrorIsNil)
   321  	c.Assert(machines, gc.HasLen, 3)
   322  	expectedCons := []constraints.Value{
   323  		controllerCons,
   324  		constraints.MustParse("spaces=ha-space,random-space"),
   325  		constraints.MustParse("spaces=ha-space,random-space"),
   326  	}
   327  	for i, m := range machines {
   328  		cons, err := m.Constraints()
   329  		c.Assert(err, jc.ErrorIsNil)
   330  		c.Check(cons, gc.DeepEquals, expectedCons[i])
   331  	}
   332  }
   333  
   334  func (s *clientSuite) TestBlockMakeHA(c *gc.C) {
   335  	// Block all changes.
   336  	s.BlockAllChanges(c, "TestBlockEnableHA")
   337  
   338  	enableHAResult, err := s.enableHA(c, 3, constraints.MustParse("mem=4G"), defaultSeries, nil)
   339  	s.AssertBlocked(c, err, "TestBlockEnableHA")
   340  
   341  	c.Assert(enableHAResult.Maintained, gc.HasLen, 0)
   342  	c.Assert(enableHAResult.Added, gc.HasLen, 0)
   343  	c.Assert(enableHAResult.Removed, gc.HasLen, 0)
   344  	c.Assert(enableHAResult.Converted, gc.HasLen, 0)
   345  
   346  	machines, err := s.State.AllMachines()
   347  	c.Assert(err, jc.ErrorIsNil)
   348  	c.Assert(machines, gc.HasLen, 1)
   349  }
   350  
   351  func (s *clientSuite) TestEnableHAPlacement(c *gc.C) {
   352  	placement := []string{"valid"}
   353  	enableHAResult, err := s.enableHA(c, 3, constraints.MustParse("mem=4G tags=foobar"), defaultSeries, placement)
   354  	c.Assert(err, jc.ErrorIsNil)
   355  	c.Assert(enableHAResult.Maintained, gc.DeepEquals, []string{"machine-0"})
   356  	c.Assert(enableHAResult.Added, gc.DeepEquals, []string{"machine-1", "machine-2"})
   357  	c.Assert(enableHAResult.Removed, gc.HasLen, 0)
   358  	c.Assert(enableHAResult.Converted, gc.HasLen, 0)
   359  
   360  	machines, err := s.State.AllMachines()
   361  	c.Assert(err, jc.ErrorIsNil)
   362  	c.Assert(machines, gc.HasLen, 3)
   363  	expectedCons := []constraints.Value{
   364  		controllerCons,
   365  		{},
   366  		constraints.MustParse("mem=4G tags=foobar"),
   367  	}
   368  	expectedPlacement := []string{"", "valid", ""}
   369  	for i, m := range machines {
   370  		cons, err := m.Constraints()
   371  		c.Assert(err, jc.ErrorIsNil)
   372  		c.Check(cons, gc.DeepEquals, expectedCons[i])
   373  		c.Check(m.Placement(), gc.Equals, expectedPlacement[i])
   374  	}
   375  }
   376  
   377  func (s *clientSuite) TestEnableHAPlacementTo(c *gc.C) {
   378  	machine1Cons := constraints.MustParse("mem=8G")
   379  	_, err := s.State.AddMachines(state.MachineTemplate{
   380  		Series:      "quantal",
   381  		Jobs:        []state.MachineJob{state.JobHostUnits},
   382  		Constraints: machine1Cons,
   383  	})
   384  	c.Assert(err, jc.ErrorIsNil)
   385  	s.setAgentPresence(c, "1")
   386  
   387  	_, err = s.State.AddMachine("quantal", state.JobHostUnits)
   388  	c.Assert(err, jc.ErrorIsNil)
   389  	s.setAgentPresence(c, "2")
   390  
   391  	placement := []string{"1", "2"}
   392  	enableHAResult, err := s.enableHA(c, 3, emptyCons, defaultSeries, placement)
   393  	c.Assert(err, jc.ErrorIsNil)
   394  	c.Assert(enableHAResult.Maintained, gc.DeepEquals, []string{"machine-0"})
   395  	c.Assert(enableHAResult.Added, gc.HasLen, 0)
   396  	c.Assert(enableHAResult.Removed, gc.HasLen, 0)
   397  	c.Assert(enableHAResult.Converted, gc.DeepEquals, []string{"machine-1", "machine-2"})
   398  
   399  	machines, err := s.State.AllMachines()
   400  	c.Assert(err, jc.ErrorIsNil)
   401  	c.Assert(machines, gc.HasLen, 3)
   402  	expectedCons := []constraints.Value{
   403  		controllerCons,
   404  		machine1Cons,
   405  		{},
   406  	}
   407  	expectedPlacement := []string{"", "", ""}
   408  	for i, m := range machines {
   409  		cons, err := m.Constraints()
   410  		c.Assert(err, jc.ErrorIsNil)
   411  		c.Check(cons, gc.DeepEquals, expectedCons[i])
   412  		c.Check(m.Placement(), gc.Equals, expectedPlacement[i])
   413  	}
   414  }
   415  
   416  func (s *clientSuite) TestEnableHAPlacementToWithAddressInSpace(c *gc.C) {
   417  	controllerSettings, _ := s.State.ReadSettings("controllers", "controllerSettings")
   418  	controllerSettings.Set(controller.JujuHASpace, "ha-space")
   419  	controllerSettings.Write()
   420  
   421  	m1, err := s.State.AddMachine("quantal", state.JobHostUnits)
   422  	c.Assert(err, jc.ErrorIsNil)
   423  	s.setAgentPresence(c, "1")
   424  	m1.SetProviderAddresses(network.NewAddressOnSpace("ha-space", "192.168.6.6"))
   425  
   426  	m2, err := s.State.AddMachine("quantal", state.JobHostUnits)
   427  	c.Assert(err, jc.ErrorIsNil)
   428  	s.setAgentPresence(c, "2")
   429  	m2.SetProviderAddresses(network.NewAddressOnSpace("ha-space", "192.168.6.7"))
   430  
   431  	placement := []string{"1", "2"}
   432  	_, err = s.enableHA(c, 3, emptyCons, defaultSeries, placement)
   433  	c.Assert(err, jc.ErrorIsNil)
   434  }
   435  
   436  func (s *clientSuite) TestEnableHAPlacementToErrorForInaccessibleSpace(c *gc.C) {
   437  	controllerSettings, _ := s.State.ReadSettings("controllers", "controllerSettings")
   438  	controllerSettings.Set(controller.JujuHASpace, "ha-space")
   439  	controllerSettings.Write()
   440  
   441  	_, err := s.State.AddMachine("quantal", state.JobHostUnits)
   442  	c.Assert(err, jc.ErrorIsNil)
   443  	s.setAgentPresence(c, "1")
   444  
   445  	placement := []string{"1", "2"}
   446  	_, err = s.enableHA(c, 3, emptyCons, defaultSeries, placement)
   447  	c.Assert(err, gc.ErrorMatches, `machine "1" has no addresses in space "ha-space"`)
   448  }
   449  
   450  func (s *clientSuite) TestEnableHA0Preserves(c *gc.C) {
   451  	// A value of 0 says either "if I'm not HA, make me HA" or "preserve my
   452  	// current HA settings".
   453  	enableHAResult, err := s.enableHA(c, 0, emptyCons, defaultSeries, nil)
   454  	c.Assert(err, jc.ErrorIsNil)
   455  	c.Assert(enableHAResult.Maintained, gc.DeepEquals, []string{"machine-0"})
   456  	c.Assert(enableHAResult.Added, gc.DeepEquals, []string{"machine-1", "machine-2"})
   457  	c.Assert(enableHAResult.Removed, gc.HasLen, 0)
   458  	c.Assert(enableHAResult.Converted, gc.HasLen, 0)
   459  
   460  	machines, err := s.State.AllMachines()
   461  	c.Assert(err, jc.ErrorIsNil)
   462  	c.Assert(machines, gc.HasLen, 3)
   463  
   464  	s.setAgentPresence(c, "1")
   465  	s.setMachineAddresses(c, "1")
   466  	s.setMachineAddresses(c, "2")
   467  
   468  	// Now, we keep agent 1 alive, but not agent 2, calling
   469  	// EnableHA(0) again will cause us to start another machine
   470  	c.Assert(machines[2].Destroy(), jc.ErrorIsNil)
   471  	c.Assert(machines[2].Refresh(), jc.ErrorIsNil)
   472  	c.Assert(machines[2].SetHasVote(false), jc.ErrorIsNil)
   473  	c.Assert(s.State.RemoveControllerMachine(machines[2]), jc.ErrorIsNil)
   474  	c.Assert(machines[2].EnsureDead(), jc.ErrorIsNil)
   475  	enableHAResult, err = s.enableHA(c, 0, emptyCons, defaultSeries, nil)
   476  	c.Assert(err, jc.ErrorIsNil)
   477  	c.Assert(enableHAResult.Maintained, gc.DeepEquals, []string{"machine-0", "machine-1"})
   478  	c.Assert(enableHAResult.Added, gc.DeepEquals, []string{"machine-3"})
   479  	c.Assert(enableHAResult.Removed, gc.HasLen, 0)
   480  	c.Assert(enableHAResult.Converted, gc.HasLen, 0)
   481  
   482  	machines, err = s.State.AllMachines()
   483  	c.Assert(err, jc.ErrorIsNil)
   484  	c.Assert(machines, gc.HasLen, 4)
   485  }
   486  
   487  func (s *clientSuite) TestEnableHA0Preserves5(c *gc.C) {
   488  	// Start off with 5 servers
   489  	enableHAResult, err := s.enableHA(c, 5, emptyCons, defaultSeries, nil)
   490  	c.Assert(err, jc.ErrorIsNil)
   491  	c.Assert(enableHAResult.Maintained, gc.DeepEquals, []string{"machine-0"})
   492  	c.Assert(enableHAResult.Added, gc.DeepEquals, []string{"machine-1", "machine-2", "machine-3", "machine-4"})
   493  	c.Assert(enableHAResult.Removed, gc.HasLen, 0)
   494  	c.Assert(enableHAResult.Converted, gc.HasLen, 0)
   495  
   496  	machines, err := s.State.AllMachines()
   497  	c.Assert(err, jc.ErrorIsNil)
   498  	c.Assert(machines, gc.HasLen, 5)
   499  	for _, m := range machines {
   500  		m.SetHasVote(true)
   501  	}
   502  
   503  	s.setMachineAddresses(c, "1")
   504  	s.setMachineAddresses(c, "2")
   505  	s.setMachineAddresses(c, "3")
   506  	s.setMachineAddresses(c, "4")
   507  	c.Assert(machines[4].SetHasVote(false), jc.ErrorIsNil)
   508  	c.Assert(machines[4].Destroy(), jc.ErrorIsNil)
   509  	c.Assert(machines[4].Refresh(), jc.ErrorIsNil)
   510  	c.Assert(s.State.RemoveControllerMachine(machines[4]), jc.ErrorIsNil)
   511  	c.Assert(machines[4].EnsureDead(), jc.ErrorIsNil)
   512  
   513  	// Keeping all alive but one, will bring up 1 more server to preserve 5
   514  	enableHAResult, err = s.enableHA(c, 0, emptyCons, defaultSeries, nil)
   515  	c.Assert(err, jc.ErrorIsNil)
   516  	c.Assert(enableHAResult.Maintained, gc.DeepEquals, []string{"machine-0", "machine-1",
   517  		"machine-2", "machine-3"})
   518  	c.Assert(enableHAResult.Added, gc.DeepEquals, []string{"machine-5"})
   519  	c.Assert(enableHAResult.Removed, gc.HasLen, 0)
   520  	c.Assert(enableHAResult.Converted, gc.HasLen, 0)
   521  
   522  	machines, err = s.State.AllMachines()
   523  	c.Assert(machines, gc.HasLen, 6)
   524  	c.Assert(err, jc.ErrorIsNil)
   525  }
   526  
   527  func (s *clientSuite) TestEnableHAErrors(c *gc.C) {
   528  	enableHAResult, err := s.enableHA(c, -1, emptyCons, defaultSeries, nil)
   529  	c.Assert(err, gc.ErrorMatches, "number of controllers must be odd and non-negative")
   530  
   531  	enableHAResult, err = s.enableHA(c, 3, emptyCons, defaultSeries, nil)
   532  	c.Assert(err, jc.ErrorIsNil)
   533  	c.Assert(enableHAResult.Maintained, gc.DeepEquals, []string{"machine-0"})
   534  	c.Assert(enableHAResult.Added, gc.DeepEquals, []string{"machine-1", "machine-2"})
   535  	c.Assert(enableHAResult.Removed, gc.HasLen, 0)
   536  	c.Assert(enableHAResult.Converted, gc.HasLen, 0)
   537  
   538  	s.setMachineAddresses(c, "1")
   539  	s.setMachineAddresses(c, "2")
   540  
   541  	_, err = s.enableHA(c, 1, emptyCons, defaultSeries, nil)
   542  	c.Assert(err, gc.ErrorMatches, "failed to create new controller machines: cannot reduce controller count")
   543  }
   544  
   545  func (s *clientSuite) TestEnableHAHostedModelErrors(c *gc.C) {
   546  	st2 := s.Factory.MakeModel(c, &factory.ModelParams{ConfigAttrs: coretesting.Attrs{"controller": false}})
   547  	defer st2.Close()
   548  
   549  	haServer, err := highavailability.NewHighAvailabilityAPI(st2, s.resources, s.authoriser)
   550  	c.Assert(err, jc.ErrorIsNil)
   551  
   552  	enableHAResult, err := enableHA(c, haServer, 3, constraints.MustParse("mem=4G"), defaultSeries, nil)
   553  	c.Assert(errors.Cause(err), gc.ErrorMatches, "unsupported with hosted models")
   554  
   555  	c.Assert(enableHAResult.Maintained, gc.HasLen, 0)
   556  	c.Assert(enableHAResult.Added, gc.HasLen, 0)
   557  	c.Assert(enableHAResult.Removed, gc.HasLen, 0)
   558  	c.Assert(enableHAResult.Converted, gc.HasLen, 0)
   559  
   560  	machines, err := st2.AllMachines()
   561  	c.Assert(err, jc.ErrorIsNil)
   562  	c.Assert(machines, gc.HasLen, 0)
   563  }
   564  
   565  func (s *clientSuite) TestEnableHAMultipleSpecs(c *gc.C) {
   566  	arg := params.ControllersSpecs{
   567  		Specs: []params.ControllersSpec{
   568  			{NumControllers: 3},
   569  			{NumControllers: 5},
   570  		},
   571  	}
   572  	results, err := s.haServer.EnableHA(arg)
   573  	c.Check(err, gc.ErrorMatches, "only one controller spec is supported")
   574  	c.Check(results.Results, gc.HasLen, 0)
   575  }
   576  
   577  func (s *clientSuite) TestEnableHANoSpecs(c *gc.C) {
   578  	arg := params.ControllersSpecs{
   579  		Specs: []params.ControllersSpec{},
   580  	}
   581  	results, err := s.haServer.EnableHA(arg)
   582  	c.Check(err, jc.ErrorIsNil)
   583  	c.Check(results.Results, gc.HasLen, 0)
   584  }
   585  
   586  func (s *clientSuite) TestEnableHABootstrap(c *gc.C) {
   587  	// Testing based on lp:1748275 - Juju HA fails due to demotion of Machine 0
   588  	s.machine0Pinger.KillForTesting()
   589  
   590  	machines, err := s.State.AllMachines()
   591  	c.Assert(err, jc.ErrorIsNil)
   592  	c.Assert(machines, gc.HasLen, 1)
   593  
   594  	enableHAResult, err := s.enableHA(c, 3, emptyCons, defaultSeries, nil)
   595  	c.Assert(err, jc.ErrorIsNil)
   596  	c.Assert(enableHAResult.Maintained, gc.DeepEquals, []string{"machine-0"})
   597  	c.Assert(enableHAResult.Added, gc.DeepEquals, []string{"machine-1", "machine-2"})
   598  	c.Assert(enableHAResult.Removed, gc.HasLen, 0)
   599  	c.Assert(enableHAResult.Converted, gc.HasLen, 0)
   600  	c.Assert(enableHAResult.Demoted, gc.HasLen, 0)
   601  }