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 }