github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/enableha_test.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     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/core/constraints"
    15  	"github.com/juju/juju/core/controller"
    16  	"github.com/juju/juju/state"
    17  	statetesting "github.com/juju/juju/state/testing"
    18  )
    19  
    20  type EnableHASuite struct {
    21  	ConnSuite
    22  }
    23  
    24  var _ = gc.Suite(&EnableHASuite{})
    25  
    26  func (s *EnableHASuite) TestHasVote(c *gc.C) {
    27  	controller, err := s.State.AddMachine(state.UbuntuBase("18.04"), state.JobHostUnits, state.JobManageModel)
    28  	c.Assert(err, jc.ErrorIsNil)
    29  
    30  	node, err := s.State.ControllerNode(controller.Id())
    31  	c.Assert(err, jc.ErrorIsNil)
    32  	c.Assert(node.SetHasVote(false), jc.ErrorIsNil)
    33  	c.Assert(node.HasVote(), jc.IsFalse)
    34  
    35  	// Make another node value so that
    36  	// it won't have the cached HasVote value.
    37  	nodeCopy, err := s.State.ControllerNode(controller.Id())
    38  	c.Assert(err, jc.ErrorIsNil)
    39  
    40  	err = node.SetHasVote(true)
    41  	c.Assert(err, jc.ErrorIsNil)
    42  	c.Assert(node.Refresh(), jc.ErrorIsNil)
    43  	c.Assert(node.HasVote(), jc.IsTrue)
    44  	c.Assert(nodeCopy.HasVote(), jc.IsFalse)
    45  
    46  	err = nodeCopy.Refresh()
    47  	c.Assert(err, jc.ErrorIsNil)
    48  	c.Assert(nodeCopy.HasVote(), jc.IsTrue)
    49  
    50  	err = nodeCopy.SetHasVote(false)
    51  	c.Assert(err, jc.ErrorIsNil)
    52  	c.Assert(nodeCopy.Refresh(), jc.ErrorIsNil)
    53  	c.Assert(nodeCopy.HasVote(), jc.IsFalse)
    54  
    55  	c.Assert(node.HasVote(), jc.IsTrue)
    56  	err = node.Refresh()
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	c.Assert(node.HasVote(), jc.IsFalse)
    59  }
    60  
    61  func (s *EnableHASuite) TestEnableHAFailsWithBadCount(c *gc.C) {
    62  	for _, n := range []int{-1, 2, 6} {
    63  		changes, err := s.State.EnableHA(n, constraints.Value{}, state.Base{}, nil)
    64  		c.Assert(err, gc.ErrorMatches, "number of controllers must be odd and non-negative")
    65  		c.Assert(changes.Added, gc.HasLen, 0)
    66  	}
    67  	_, err := s.State.EnableHA(controller.MaxPeers+2, constraints.Value{}, state.Base{}, nil)
    68  	c.Assert(err, gc.ErrorMatches, `controller count is too large \(allowed \d+\)`)
    69  }
    70  
    71  func (s *EnableHASuite) TestEnableHAAddsNewMachines(c *gc.C) {
    72  	ids := make([]string, 3)
    73  	m0, err := s.State.AddMachine(state.UbuntuBase("18.04"), state.JobHostUnits, state.JobManageModel)
    74  	c.Assert(err, jc.ErrorIsNil)
    75  	ids[0] = m0.Id()
    76  
    77  	// Add a non-controller machine just to make sure.
    78  	_, err = s.State.AddMachine(state.UbuntuBase("18.04"), state.JobHostUnits)
    79  	c.Assert(err, jc.ErrorIsNil)
    80  
    81  	s.assertControllerInfo(c, []string{"0"}, []string{"0"}, nil)
    82  
    83  	cons := constraints.Value{
    84  		Mem: newUint64(100),
    85  	}
    86  	changes, err := s.State.EnableHA(3, cons, state.UbuntuBase("18.04"), nil)
    87  	c.Assert(err, jc.ErrorIsNil)
    88  	c.Assert(changes.Added, gc.HasLen, 2)
    89  
    90  	for i := 1; i < 3; i++ {
    91  		m, err := s.State.Machine(fmt.Sprint(i + 1))
    92  		c.Assert(err, jc.ErrorIsNil)
    93  		c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{
    94  			state.JobHostUnits,
    95  			state.JobManageModel,
    96  		})
    97  		gotCons, err := m.Constraints()
    98  		c.Assert(err, jc.ErrorIsNil)
    99  		c.Assert(gotCons, gc.DeepEquals, cons)
   100  		node, err := s.State.ControllerNode(m0.Id())
   101  		c.Assert(err, jc.ErrorIsNil)
   102  		c.Check(node.HasVote(), jc.IsFalse)
   103  		ids[i] = m.Id()
   104  	}
   105  	s.assertControllerInfo(c, ids, ids, nil)
   106  }
   107  
   108  func (s *EnableHASuite) TestEnableHAAddsControllerCharm(c *gc.C) {
   109  	state.AddTestingApplicationForBase(c, s.State, state.UbuntuBase("20.04"), "controller",
   110  		state.AddTestingCharmMultiSeries(c, s.State, "juju-controller"))
   111  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   112  	c.Assert(err, jc.ErrorIsNil)
   113  	c.Assert(changes.Added, gc.HasLen, 3)
   114  	for i := 0; i < 3; i++ {
   115  		unitName := fmt.Sprintf("controller/%d", i)
   116  		m, err := s.State.Machine(fmt.Sprint(i))
   117  		c.Assert(err, jc.ErrorIsNil)
   118  		c.Assert(m.Principals(), jc.DeepEquals, []string{unitName})
   119  		u, err := s.State.Unit(unitName)
   120  		c.Assert(err, jc.ErrorIsNil)
   121  		mID, err := u.AssignedMachineId()
   122  		c.Assert(err, jc.ErrorIsNil)
   123  		c.Assert(mID, gc.Equals, fmt.Sprint(i))
   124  	}
   125  }
   126  
   127  func (s *EnableHASuite) TestEnableHAAddsControllerCharmToPromoted(c *gc.C) {
   128  	state.AddTestingApplicationForBase(c, s.State, state.UbuntuBase("20.04"), "controller",
   129  		state.AddTestingCharmMultiSeries(c, s.State, "juju-controller"))
   130  	m0, err := s.State.AddMachine(state.UbuntuBase("18.04"), state.JobHostUnits)
   131  	c.Assert(err, jc.ErrorIsNil)
   132  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), []string{"0"})
   133  	c.Assert(err, jc.ErrorIsNil)
   134  	c.Assert(changes.Added, gc.HasLen, 2)
   135  	c.Assert(changes.Converted, gc.HasLen, 1)
   136  	for i := 0; i < 3; i++ {
   137  		unitName := fmt.Sprintf("controller/%d", i)
   138  		m, err := s.State.Machine(fmt.Sprint(i))
   139  		c.Assert(err, jc.ErrorIsNil)
   140  		c.Assert(m.Principals(), jc.DeepEquals, []string{unitName})
   141  		u, err := s.State.Unit(unitName)
   142  		c.Assert(err, jc.ErrorIsNil)
   143  		mID, err := u.AssignedMachineId()
   144  		c.Assert(err, jc.ErrorIsNil)
   145  		c.Assert(mID, gc.Equals, fmt.Sprint(i))
   146  	}
   147  	err = m0.Refresh()
   148  	c.Assert(err, jc.ErrorIsNil)
   149  	c.Assert(m0.Principals(), gc.DeepEquals, []string{"controller/0"})
   150  }
   151  
   152  func (s *EnableHASuite) TestEnableHATo(c *gc.C) {
   153  	ids := make([]string, 3)
   154  	m0, err := s.State.AddMachine(state.UbuntuBase("18.04"), state.JobHostUnits, state.JobManageModel)
   155  	c.Assert(err, jc.ErrorIsNil)
   156  	ids[0] = m0.Id()
   157  
   158  	// Add two non-controller machines.
   159  	_, err = s.State.AddMachine(state.UbuntuBase("18.04"), state.JobHostUnits)
   160  	c.Assert(err, jc.ErrorIsNil)
   161  
   162  	_, err = s.State.AddMachine(state.UbuntuBase("18.04"), state.JobHostUnits)
   163  	c.Assert(err, jc.ErrorIsNil)
   164  
   165  	s.assertControllerInfo(c, []string{"0"}, []string{"0"}, nil)
   166  
   167  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), []string{"1", "2"})
   168  	c.Assert(err, jc.ErrorIsNil)
   169  	c.Assert(changes.Added, gc.HasLen, 0)
   170  	c.Assert(changes.Converted, gc.HasLen, 2)
   171  
   172  	for i := 1; i < 3; i++ {
   173  		m, err := s.State.Machine(fmt.Sprint(i))
   174  		c.Assert(err, jc.ErrorIsNil)
   175  		c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{
   176  			state.JobHostUnits,
   177  			state.JobManageModel,
   178  		})
   179  		gotCons, err := m.Constraints()
   180  		c.Assert(err, jc.ErrorIsNil)
   181  		c.Assert(gotCons, gc.DeepEquals, constraints.Value{})
   182  		node, err := s.State.ControllerNode(m0.Id())
   183  		c.Assert(err, jc.ErrorIsNil)
   184  		c.Check(node.HasVote(), jc.IsFalse)
   185  		ids[i] = m.Id()
   186  	}
   187  	s.assertControllerInfo(c, ids, ids, nil)
   188  }
   189  
   190  func (s *EnableHASuite) TestEnableHAToPartial(c *gc.C) {
   191  	ids := make([]string, 3)
   192  	m0, err := s.State.AddMachine(state.UbuntuBase("18.04"), state.JobHostUnits, state.JobManageModel)
   193  	c.Assert(err, jc.ErrorIsNil)
   194  	ids[0] = m0.Id()
   195  
   196  	// Add one non-controller machine.
   197  	_, err = s.State.AddMachine(state.UbuntuBase("18.04"), state.JobHostUnits)
   198  	c.Assert(err, jc.ErrorIsNil)
   199  
   200  	s.assertControllerInfo(c, []string{"0"}, []string{"0"}, nil)
   201  
   202  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), []string{"1"})
   203  	c.Assert(err, jc.ErrorIsNil)
   204  
   205  	// One machine is converted (existing machine with placement),
   206  	// and another is added to make up the 3.
   207  	c.Assert(changes.Converted, gc.HasLen, 1)
   208  	c.Assert(changes.Added, gc.HasLen, 1)
   209  
   210  	for i := 1; i < 3; i++ {
   211  		m, err := s.State.Machine(fmt.Sprint(i))
   212  		c.Assert(err, jc.ErrorIsNil)
   213  		c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{
   214  			state.JobHostUnits,
   215  			state.JobManageModel,
   216  		})
   217  		gotCons, err := m.Constraints()
   218  		c.Assert(err, jc.ErrorIsNil)
   219  		c.Assert(gotCons, gc.DeepEquals, constraints.Value{})
   220  		node, err := s.State.ControllerNode(m0.Id())
   221  		c.Assert(err, jc.ErrorIsNil)
   222  		c.Check(node.HasVote(), jc.IsFalse)
   223  		ids[i] = m.Id()
   224  	}
   225  	s.assertControllerInfo(c, ids, ids, nil)
   226  }
   227  
   228  func newUint64(i uint64) *uint64 {
   229  	return &i
   230  }
   231  
   232  func (s *EnableHASuite) assertControllerInfo(c *gc.C, expectedIds []string, wantVoteMachineIds []string, placement []string) {
   233  	controllerIds, err := s.State.ControllerIds()
   234  	c.Assert(err, jc.ErrorIsNil)
   235  	c.Check(controllerIds, jc.SameContents, expectedIds)
   236  
   237  	foundVoting := make([]string, 0)
   238  	for i, id := range expectedIds {
   239  		m, err := s.State.Machine(id)
   240  		c.Assert(err, jc.ErrorIsNil)
   241  		if len(placement) == 0 || i >= len(placement) {
   242  			c.Check(m.Placement(), gc.Equals, "")
   243  		} else {
   244  			c.Check(m.Placement(), gc.Equals, placement[i])
   245  		}
   246  		node, err := s.State.ControllerNode(id)
   247  		c.Assert(err, jc.ErrorIsNil)
   248  		if node.WantsVote() {
   249  			foundVoting = append(foundVoting, m.Id())
   250  		}
   251  	}
   252  	c.Check(foundVoting, gc.DeepEquals, wantVoteMachineIds)
   253  }
   254  
   255  func (s *EnableHASuite) TestEnableHASamePlacementAsNewCount(c *gc.C) {
   256  	placement := []string{"p1", "p2", "p3"}
   257  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), placement)
   258  	c.Assert(err, jc.ErrorIsNil)
   259  	c.Assert(changes.Added, gc.HasLen, 3)
   260  	s.assertControllerInfo(c, []string{"0", "1", "2"}, []string{"0", "1", "2"}, []string{"p1", "p2", "p3"})
   261  }
   262  
   263  func (s *EnableHASuite) TestEnableHAMorePlacementThanNewCount(c *gc.C) {
   264  	placement := []string{"p1", "p2", "p3", "p4"}
   265  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), placement)
   266  	c.Assert(err, jc.ErrorIsNil)
   267  	c.Assert(changes.Added, gc.HasLen, 3)
   268  	s.assertControllerInfo(c, []string{"0", "1", "2"}, []string{"0", "1", "2"}, []string{"p1", "p2", "p3"})
   269  }
   270  
   271  func (s *EnableHASuite) TestEnableHALessPlacementThanNewCount(c *gc.C) {
   272  	placement := []string{"p1", "p2"}
   273  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), placement)
   274  	c.Assert(err, jc.ErrorIsNil)
   275  	c.Assert(changes.Added, gc.HasLen, 3)
   276  	s.assertControllerInfo(c, []string{"0", "1", "2"}, []string{"0", "1", "2"}, []string{"p1", "p2"})
   277  }
   278  
   279  func (s *EnableHASuite) TestEnableHAMockBootstrap(c *gc.C) {
   280  	// Testing based on lp:1748275 - Juju HA fails due to demotion of Machine 0
   281  	m0, err := s.State.AddMachine(state.UbuntuBase("18.04"), state.JobHostUnits, state.JobManageModel)
   282  	c.Assert(err, jc.ErrorIsNil)
   283  	node, err := s.State.ControllerNode(m0.Id())
   284  	c.Assert(err, jc.ErrorIsNil)
   285  	err = node.SetHasVote(true)
   286  	c.Assert(err, jc.ErrorIsNil)
   287  
   288  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   289  	c.Assert(err, jc.ErrorIsNil)
   290  	c.Assert(changes.Added, gc.HasLen, 2)
   291  	c.Assert(changes.Maintained, gc.DeepEquals, []string{"0"})
   292  	s.assertControllerInfo(c, []string{"0", "1", "2"}, []string{"0", "1", "2"}, nil)
   293  }
   294  
   295  func (s *EnableHASuite) TestEnableHADefaultsTo3(c *gc.C) {
   296  	changes, err := s.State.EnableHA(0, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   297  	c.Assert(err, jc.ErrorIsNil)
   298  	c.Assert(changes.Added, gc.HasLen, 3)
   299  	s.assertControllerInfo(c, []string{"0", "1", "2"}, []string{"0", "1", "2"}, nil)
   300  	// Mark machine 0 as being removed, and then run it again
   301  	s.progressControllerToDead(c, "0")
   302  	changes, err = s.State.EnableHA(0, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   303  	c.Assert(err, jc.ErrorIsNil)
   304  	c.Assert(changes.Added, gc.DeepEquals, []string{"3"})
   305  
   306  	// New controller machine "3" is created
   307  	s.assertControllerInfo(c, []string{"1", "2", "3"}, []string{"1", "2", "3"}, nil)
   308  	m0, err := s.State.Machine("0")
   309  	c.Assert(err, jc.ErrorIsNil)
   310  	node, err := s.State.ControllerNode(m0.Id())
   311  	c.Assert(err, jc.ErrorIsNil)
   312  	c.Assert(node.WantsVote(), jc.IsFalse)
   313  	c.Assert(m0.IsManager(), jc.IsFalse) // job still intact for now
   314  	m3, err := s.State.Machine("3")
   315  	c.Assert(err, jc.ErrorIsNil)
   316  	node, err = s.State.ControllerNode(m3.Id())
   317  	c.Assert(err, jc.ErrorIsNil)
   318  	c.Check(node.HasVote(), jc.IsFalse) // No vote yet.
   319  	c.Assert(m3.IsManager(), jc.IsTrue)
   320  }
   321  
   322  // progressControllerToDead starts the machine as dying, and then does what the normal workers would do
   323  // (like peergrouper), and runs all the cleanups to progress the machine all the way to dead.
   324  func (s *EnableHASuite) progressControllerToDead(c *gc.C, id string) {
   325  	m, err := s.State.Machine(id)
   326  	c.Assert(err, jc.ErrorIsNil)
   327  	node, err := s.State.ControllerNode(id)
   328  	c.Assert(err, jc.ErrorIsNil)
   329  	c.Logf("destroying machine 0")
   330  	c.Assert(m.Destroy(), jc.ErrorIsNil)
   331  	c.Assert(node.Refresh(), jc.ErrorIsNil)
   332  	c.Check(node.WantsVote(), jc.IsFalse)
   333  	// Pretend to be the peergrouper, notice the machine doesn't want to vote, so get rid of its vote, and remove it
   334  	// as a controller machine.
   335  	c.Check(node.SetHasVote(false), jc.ErrorIsNil)
   336  	// TODO(HA) - no longer need to refresh once HasVote is moved off machine
   337  	c.Assert(node.Refresh(), jc.ErrorIsNil)
   338  	c.Assert(s.State.RemoveControllerReference(node), jc.ErrorIsNil)
   339  	c.Assert(s.State.Cleanup(), jc.ErrorIsNil)
   340  	c.Assert(m.EnsureDead(), jc.ErrorIsNil)
   341  }
   342  
   343  func (s *EnableHASuite) TestEnableHAGoesToNextOdd(c *gc.C) {
   344  	changes, err := s.State.EnableHA(0, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   345  	c.Assert(err, jc.ErrorIsNil)
   346  	c.Assert(changes.Added, gc.HasLen, 3)
   347  	s.assertControllerInfo(c, []string{"0", "1", "2"}, []string{"0", "1", "2"}, nil)
   348  	// "run the peergrouper" and give all the controllers the vote
   349  	for _, id := range []string{"0", "1", "2"} {
   350  		node, err := s.State.ControllerNode(id)
   351  		c.Assert(err, jc.ErrorIsNil)
   352  		c.Assert(node.SetHasVote(true), jc.ErrorIsNil)
   353  	}
   354  	// Remove machine 0, so that we are down to 2 machines that want to vote. Requesting a count of '0' should
   355  	// still bring us back to 3
   356  	s.progressControllerToDead(c, "0")
   357  	s.assertControllerInfo(c, []string{"1", "2"}, []string{"1", "2"}, nil)
   358  	changes, err = s.State.EnableHA(0, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   359  	c.Assert(err, jc.ErrorIsNil)
   360  	// We should try to get back to 3 again, since we only have 2 voting machines
   361  	c.Check(changes.Added, gc.DeepEquals, []string{"3"})
   362  	s.assertControllerInfo(c, []string{"1", "2", "3"}, []string{"1", "2", "3"}, nil)
   363  	// Doing it again with 0, should be a no-op, still going to '3'
   364  	changes, err = s.State.EnableHA(0, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   365  	c.Assert(err, jc.ErrorIsNil)
   366  	c.Check(changes.Added, gc.HasLen, 0)
   367  	s.assertControllerInfo(c, []string{"1", "2", "3"}, []string{"1", "2", "3"}, nil)
   368  	// Now if we go up to 5, and drop down to 4, we should again go to 5
   369  	changes, err = s.State.EnableHA(5, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   370  	c.Assert(err, jc.ErrorIsNil)
   371  	sort.Strings(changes.Added)
   372  	c.Check(changes.Added, gc.DeepEquals, []string{"4", "5"})
   373  	s.assertControllerInfo(c, []string{"1", "2", "3", "4", "5"}, []string{"1", "2", "3", "4", "5"}, nil)
   374  	s.progressControllerToDead(c, "1")
   375  	s.assertControllerInfo(c, []string{"2", "3", "4", "5"}, []string{"2", "3", "4", "5"}, nil)
   376  	changes, err = s.State.EnableHA(0, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   377  	c.Assert(err, jc.ErrorIsNil)
   378  	c.Check(changes.Added, gc.DeepEquals, []string{"6"})
   379  	s.assertControllerInfo(c, []string{"2", "3", "4", "5", "6"}, []string{"2", "3", "4", "5", "6"}, nil)
   380  	// And again 0 should be treated as 5, and thus a no-op
   381  	changes, err = s.State.EnableHA(0, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   382  	c.Assert(err, jc.ErrorIsNil)
   383  	c.Check(changes.Added, gc.HasLen, 0)
   384  	s.assertControllerInfo(c, []string{"2", "3", "4", "5", "6"}, []string{"2", "3", "4", "5", "6"}, nil)
   385  }
   386  
   387  func (s *EnableHASuite) TestEnableHAConcurrentSame(c *gc.C) {
   388  	defer state.SetBeforeHooks(c, s.State, func() {
   389  		changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   390  		c.Assert(err, jc.ErrorIsNil)
   391  		// The outer EnableHA call will allocate IDs 0..2,
   392  		// and the inner one 3..5.
   393  		c.Assert(changes.Added, gc.HasLen, 3)
   394  		expected := []string{"3", "4", "5"}
   395  		s.assertControllerInfo(c, expected, expected, nil)
   396  	}).Check()
   397  
   398  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   399  	c.Assert(err, jc.ErrorIsNil)
   400  	c.Assert(changes.Added, gc.DeepEquals, []string{"0", "1", "2"})
   401  	s.assertControllerInfo(c, []string{"3", "4", "5"}, []string{"3", "4", "5"}, nil)
   402  
   403  	// Machine 0 should never have been created.
   404  	_, err = s.State.Machine("0")
   405  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   406  }
   407  
   408  func (s *EnableHASuite) TestEnableHAConcurrentLess(c *gc.C) {
   409  	defer state.SetBeforeHooks(c, s.State, func() {
   410  		changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   411  		c.Assert(err, jc.ErrorIsNil)
   412  		c.Assert(changes.Added, gc.HasLen, 3)
   413  		// The outer EnableHA call will initially allocate IDs 0..4,
   414  		// and the inner one 5..7.
   415  		expected := []string{"5", "6", "7"}
   416  		s.assertControllerInfo(c, expected, expected, nil)
   417  	}).Check()
   418  
   419  	// This call to EnableHA will initially attempt to allocate
   420  	// machines 0..4, and fail due to the concurrent change. It will then
   421  	// allocate machines 8..9 to make up the difference from the concurrent
   422  	// EnableHA call.
   423  	changes, err := s.State.EnableHA(5, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   424  	c.Assert(err, jc.ErrorIsNil)
   425  	c.Assert(changes.Added, gc.HasLen, 2)
   426  	expected := []string{"5", "6", "7", "8", "9"}
   427  	s.assertControllerInfo(c, expected, expected, nil)
   428  
   429  	// Machine 0 should never have been created.
   430  	_, err = s.State.Machine("0")
   431  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   432  }
   433  
   434  func (s *EnableHASuite) TestEnableHAConcurrentMore(c *gc.C) {
   435  	defer state.SetBeforeHooks(c, s.State, func() {
   436  		changes, err := s.State.EnableHA(5, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   437  		c.Assert(err, jc.ErrorIsNil)
   438  		c.Assert(changes.Added, gc.HasLen, 5)
   439  		// The outer EnableHA call will allocate IDs 0..2,
   440  		// and the inner one 3..7.
   441  		expected := []string{"3", "4", "5", "6", "7"}
   442  		s.assertControllerInfo(c, expected, expected, nil)
   443  	}).Check()
   444  
   445  	// This call to EnableHA will initially attempt to allocate
   446  	// machines 0..2, and fail due to the concurrent change. It will then
   447  	// find that the number of voting machines in state is greater than
   448  	// what we're attempting to ensure, and fail.
   449  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   450  	c.Assert(err, gc.ErrorMatches, "failed to enable HA with 3 controllers: cannot remove controllers with enable-ha, use remove-machine and chose the controller\\(s\\) to remove")
   451  	c.Assert(changes.Added, gc.HasLen, 0)
   452  
   453  	// Machine 0 should never have been created.
   454  	_, err = s.State.Machine("0")
   455  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   456  }
   457  
   458  func (s *EnableHASuite) TestWatchControllerInfo(c *gc.C) {
   459  	_, err := s.State.AddMachine(state.UbuntuBase("18.04"), state.JobManageModel)
   460  	c.Assert(err, jc.ErrorIsNil)
   461  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
   462  
   463  	w := s.State.WatchControllerInfo()
   464  	defer statetesting.AssertStop(c, w)
   465  
   466  	// Initial event.
   467  	wc := statetesting.NewStringsWatcherC(c, w)
   468  	wc.AssertChange("0")
   469  
   470  	info, err := s.State.ControllerInfo()
   471  	c.Assert(err, jc.ErrorIsNil)
   472  	c.Assert(info, jc.DeepEquals, &state.ControllerInfo{
   473  		CloudName:     "dummy",
   474  		ModelTag:      s.modelTag,
   475  		ControllerIds: []string{"0"},
   476  	})
   477  
   478  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   479  	c.Assert(err, jc.ErrorIsNil)
   480  	c.Assert(changes.Added, gc.HasLen, 2)
   481  
   482  	wc.AssertChange("1", "2")
   483  
   484  	info, err = s.State.ControllerInfo()
   485  	c.Assert(err, jc.ErrorIsNil)
   486  	c.Assert(info, jc.DeepEquals, &state.ControllerInfo{
   487  		CloudName:     "dummy",
   488  		ModelTag:      s.modelTag,
   489  		ControllerIds: []string{"0", "1", "2"},
   490  	})
   491  }
   492  
   493  func (s *EnableHASuite) TestDestroyFromHA(c *gc.C) {
   494  	m0, err := s.State.AddMachine(state.UbuntuBase("18.04"), state.JobHostUnits, state.JobManageModel)
   495  	c.Assert(err, jc.ErrorIsNil)
   496  	err = m0.Destroy()
   497  	c.Assert(err, gc.ErrorMatches, "controller 0 is the only controller")
   498  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   499  	c.Assert(err, jc.ErrorIsNil)
   500  	c.Assert(changes.Added, gc.HasLen, 2)
   501  	s.assertControllerInfo(c, []string{"0", "1", "2"}, []string{"0", "1", "2"}, nil)
   502  	err = m0.Destroy()
   503  	c.Assert(err, jc.ErrorIsNil)
   504  	c.Assert(m0.Refresh(), jc.ErrorIsNil)
   505  	c.Check(m0.Life(), gc.Equals, state.Dying)
   506  	node, err := s.State.ControllerNode(m0.Id())
   507  	c.Assert(err, jc.ErrorIsNil)
   508  	c.Assert(node.WantsVote(), jc.IsFalse)
   509  }
   510  
   511  func (s *EnableHASuite) TestForceDestroyFromHA(c *gc.C) {
   512  	m0, err := s.State.AddMachine(state.UbuntuBase("18.04"), state.JobHostUnits, state.JobManageModel)
   513  	c.Assert(err, jc.ErrorIsNil)
   514  	node, err := s.State.ControllerNode(m0.Id())
   515  	c.Assert(err, jc.ErrorIsNil)
   516  	err = node.SetHasVote(true)
   517  	c.Assert(err, jc.ErrorIsNil)
   518  	// ForceDestroy must be blocked if there is only 1 machine.
   519  	err = m0.ForceDestroy(dontWait)
   520  	c.Assert(err, gc.ErrorMatches, "controller 0 is the only controller")
   521  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   522  	c.Assert(err, jc.ErrorIsNil)
   523  	c.Assert(changes.Added, gc.HasLen, 2)
   524  	s.assertControllerInfo(c, []string{"0", "1", "2"}, []string{"0", "1", "2"}, nil)
   525  	err = m0.ForceDestroy(dontWait)
   526  	c.Assert(err, jc.ErrorIsNil)
   527  	c.Assert(m0.Refresh(), jc.ErrorIsNil)
   528  	// Could this actually get all the way to Dead?
   529  	c.Check(m0.Life(), gc.Equals, state.Dying)
   530  	c.Assert(node.Refresh(), jc.ErrorIsNil)
   531  	c.Assert(node.WantsVote(), jc.IsFalse)
   532  }
   533  
   534  func (s *EnableHASuite) TestDestroyRaceLastController(c *gc.C) {
   535  	m0, err := s.State.AddMachine(state.UbuntuBase("18.04"), state.JobHostUnits, state.JobManageModel)
   536  	c.Assert(err, jc.ErrorIsNil)
   537  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   538  	c.Assert(err, jc.ErrorIsNil)
   539  	c.Assert(changes.Added, gc.HasLen, 2)
   540  	s.assertControllerInfo(c, []string{"0", "1", "2"}, []string{"0", "1", "2"}, nil)
   541  	for _, id := range []string{"0", "1", "2"} {
   542  		node, err := s.State.ControllerNode(id)
   543  		c.Assert(err, jc.ErrorIsNil)
   544  		c.Assert(node.SetHasVote(true), jc.ErrorIsNil)
   545  	}
   546  
   547  	defer state.SetBeforeHooks(c, s.State, func() {
   548  		// We remove the other 2 controllers just before controller "0" would be destroyed
   549  		for _, id := range []string{"1", "2"} {
   550  			c.Check(state.SetWantsVote(s.State, id, false), jc.ErrorIsNil)
   551  			node, err := s.State.ControllerNode(id)
   552  			c.Assert(err, jc.ErrorIsNil)
   553  			c.Check(node.SetHasVote(false), jc.ErrorIsNil)
   554  			c.Check(node.Refresh(), jc.ErrorIsNil)
   555  			c.Check(s.State.RemoveControllerReference(node), jc.ErrorIsNil)
   556  			c.Logf("removed machine %s", id)
   557  			c.Assert(m0.Refresh(), jc.ErrorIsNil)
   558  			c.Assert(node.Refresh(), jc.ErrorIsNil)
   559  			c.Logf("machine 0: %s wants %t has %t", m0.Life(), node.WantsVote(), node.HasVote())
   560  		}
   561  	}).Check()
   562  	c.Logf("destroying machine 0")
   563  	err = m0.Destroy()
   564  	c.Check(err, gc.ErrorMatches, "controller 0 is the only controller")
   565  	c.Logf("attempted to destroy machine 0 finished")
   566  	c.Assert(m0.Refresh(), jc.ErrorIsNil)
   567  	c.Check(m0.Life(), gc.Equals, state.Alive)
   568  	node, err := s.State.ControllerNode(m0.Id())
   569  	c.Assert(err, jc.ErrorIsNil)
   570  	c.Check(node.HasVote(), jc.IsTrue)
   571  	c.Check(node.WantsVote(), jc.IsTrue)
   572  }
   573  
   574  func (s *EnableHASuite) TestRemoveControllerMachineOneMachine(c *gc.C) {
   575  	m0, err := s.State.AddMachine(state.UbuntuBase("18.04"), state.JobManageModel)
   576  	c.Assert(err, jc.ErrorIsNil)
   577  	node, err := s.State.ControllerNode(m0.Id())
   578  	c.Assert(err, jc.ErrorIsNil)
   579  	c.Assert(node.SetHasVote(true), jc.ErrorIsNil)
   580  	err = s.State.RemoveControllerReference(node)
   581  	c.Assert(err, gc.ErrorMatches, "controller 0 cannot be removed as it still wants to vote")
   582  	c.Assert(state.SetWantsVote(s.State, m0.Id(), false), jc.ErrorIsNil)
   583  	// TODO(HA) - no longer need to refresh once HasVote is moved off machine
   584  	c.Assert(node.Refresh(), jc.ErrorIsNil)
   585  	err = s.State.RemoveControllerReference(node)
   586  	c.Assert(err, gc.ErrorMatches, "controller 0 cannot be removed as it still has a vote")
   587  	c.Assert(node.SetHasVote(false), jc.ErrorIsNil)
   588  	c.Assert(node.Refresh(), jc.ErrorIsNil)
   589  	// it seems odd that we would end up the last controller but not have a vote, but we care about the DB integrity
   590  	err = s.State.RemoveControllerReference(node)
   591  	c.Assert(err, gc.ErrorMatches, "controller 0 cannot be removed as it is the last controller")
   592  }
   593  
   594  func (s *EnableHASuite) TestRemoveControllerMachine(c *gc.C) {
   595  	m0, err := s.State.AddMachine(state.UbuntuBase("18.04"), state.JobManageModel)
   596  	c.Assert(err, jc.ErrorIsNil)
   597  	node, err := s.State.ControllerNode(m0.Id())
   598  	c.Assert(err, jc.ErrorIsNil)
   599  	c.Assert(node.SetHasVote(true), jc.ErrorIsNil)
   600  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   601  	c.Assert(err, jc.ErrorIsNil)
   602  	c.Check(changes.Added, gc.HasLen, 2)
   603  	s.assertControllerInfo(c, []string{"0", "1", "2"}, []string{"0", "1", "2"}, nil)
   604  	c.Assert(m0.Destroy(), jc.ErrorIsNil)
   605  	c.Assert(node.SetHasVote(false), jc.ErrorIsNil)
   606  	c.Assert(node.Refresh(), jc.ErrorIsNil)
   607  	err = s.State.RemoveControllerReference(node)
   608  	c.Assert(err, jc.ErrorIsNil)
   609  	s.assertControllerInfo(c, []string{"1", "2"}, []string{"1", "2"}, nil)
   610  	c.Assert(m0.Refresh(), jc.ErrorIsNil)
   611  	c.Check(m0.Jobs(), jc.DeepEquals, []state.MachineJob{})
   612  }
   613  
   614  func (s *EnableHASuite) TestRemoveControllerMachineVoteRace(c *gc.C) {
   615  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   616  	c.Assert(err, jc.ErrorIsNil)
   617  	c.Assert(changes.Added, gc.HasLen, 3)
   618  	s.assertControllerInfo(c, []string{"0", "1", "2"}, []string{"0", "1", "2"}, nil)
   619  	m0, err := s.State.Machine("0")
   620  	c.Assert(err, jc.ErrorIsNil)
   621  	c.Assert(state.SetWantsVote(s.State, m0.Id(), false), jc.ErrorIsNil)
   622  	node, err := s.State.ControllerNode(m0.Id())
   623  	c.Assert(err, jc.ErrorIsNil)
   624  	c.Assert(node.SetHasVote(false), jc.ErrorIsNil)
   625  	// It no longer wants the vote, but does have the JobManageModel
   626  	s.assertControllerInfo(c, []string{"0", "1", "2"}, []string{"1", "2"}, nil)
   627  	defer state.SetBeforeHooks(c, s.State, func() {
   628  		// we sneakily add the vote back to machine 1 just before it would be removed
   629  		m0, err := s.State.Machine("0")
   630  		c.Check(err, jc.ErrorIsNil)
   631  		c.Check(state.SetWantsVote(s.State, m0.Id(), true), jc.ErrorIsNil)
   632  	}).Check()
   633  	err = s.State.RemoveControllerReference(node)
   634  	c.Check(err, gc.ErrorMatches, "controller 0 cannot be removed as it still wants to vote")
   635  	c.Assert(m0.Refresh(), jc.ErrorIsNil)
   636  	c.Check(m0.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobHostUnits, state.JobManageModel})
   637  	c.Assert(node.Refresh(), jc.ErrorIsNil)
   638  	c.Check(node.HasVote(), jc.IsFalse)
   639  	s.assertControllerInfo(c, []string{"0", "1", "2"}, []string{"0", "1", "2"}, nil)
   640  }
   641  
   642  func (s *EnableHASuite) TestRemoveControllerMachineRace(c *gc.C) {
   643  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("18.04"), nil)
   644  	c.Assert(err, jc.ErrorIsNil)
   645  	c.Assert(changes.Added, gc.HasLen, 3)
   646  	s.assertControllerInfo(c, []string{"0", "1", "2"}, []string{"0", "1", "2"}, nil)
   647  	m0, err := s.State.Machine("0")
   648  	c.Assert(err, jc.ErrorIsNil)
   649  	c.Assert(state.SetWantsVote(s.State, m0.Id(), false), jc.ErrorIsNil)
   650  	node, err := s.State.ControllerNode(m0.Id())
   651  	c.Assert(err, jc.ErrorIsNil)
   652  	c.Assert(node.SetHasVote(false), jc.ErrorIsNil)
   653  	removeOne := func(id string) {
   654  		c.Check(state.SetWantsVote(s.State, id, false), jc.ErrorIsNil)
   655  		node, err := s.State.ControllerNode(id)
   656  		c.Assert(err, jc.ErrorIsNil)
   657  		c.Check(s.State.RemoveControllerReference(node), jc.ErrorIsNil)
   658  	}
   659  	defer state.SetBeforeHooks(c, s.State, func() {
   660  		// we sneakily remove machine 1 just before 0 can be removed, this causes the removal of m0 to be retried
   661  		removeOne("1")
   662  	}, func() {
   663  		// then we remove machine 2, leaving 0 as the last machine, and that aborts the removal
   664  		removeOne("2")
   665  	}).Check()
   666  	err = s.State.RemoveControllerReference(node)
   667  	c.Assert(err, gc.ErrorMatches, "controller 0 cannot be removed as it is the last controller")
   668  	c.Assert(node.Refresh(), jc.ErrorIsNil)
   669  	c.Check(node.WantsVote(), jc.IsFalse)
   670  	c.Check(node.HasVote(), jc.IsFalse)
   671  	c.Assert(m0.Refresh(), jc.ErrorIsNil)
   672  	c.Check(m0.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobHostUnits, state.JobManageModel})
   673  }