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