github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/cleanup_test.go (about) 1 // Copyright 2014-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state_test 5 6 import ( 7 "bytes" 8 "sort" 9 "strconv" 10 "time" 11 12 "github.com/juju/charm/v12" 13 "github.com/juju/errors" 14 "github.com/juju/names/v5" 15 jc "github.com/juju/testing/checkers" 16 gc "gopkg.in/check.v1" 17 18 "github.com/juju/juju/caas" 19 k8sprovider "github.com/juju/juju/caas/kubernetes/provider" 20 k8stesting "github.com/juju/juju/caas/kubernetes/provider/testing" 21 "github.com/juju/juju/core/constraints" 22 "github.com/juju/juju/core/crossmodel" 23 "github.com/juju/juju/core/instance" 24 resourcetesting "github.com/juju/juju/core/resources/testing" 25 "github.com/juju/juju/core/status" 26 "github.com/juju/juju/state" 27 "github.com/juju/juju/state/stateenvirons" 28 "github.com/juju/juju/state/storage" 29 "github.com/juju/juju/state/testing" 30 corestorage "github.com/juju/juju/storage" 31 "github.com/juju/juju/testing/factory" 32 ) 33 34 type CleanupSuite struct { 35 ConnSuite 36 storageBackend *state.StorageBackend 37 } 38 39 var _ = gc.Suite(&CleanupSuite{}) 40 41 func (s *CleanupSuite) SetUpTest(c *gc.C) { 42 s.ConnSuite.SetUpTest(c) 43 s.assertDoesNotNeedCleanup(c) 44 var err error 45 s.storageBackend, err = state.NewStorageBackend(s.State) 46 c.Assert(err, jc.ErrorIsNil) 47 48 } 49 50 func (s *CleanupSuite) TestCleanupDyingApplicationNoUnits(c *gc.C) { 51 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 52 c.Assert(mysql.Destroy(), jc.ErrorIsNil) 53 c.Assert(mysql.Refresh(), jc.Satisfies, errors.IsNotFound) 54 } 55 56 func (s *CleanupSuite) TestCleanupDyingApplicationUnits(c *gc.C) { 57 // Create a application with some units. 58 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 59 units := make([]*state.Unit, 3) 60 for i := range units { 61 unit, err := mysql.AddUnit(state.AddUnitParams{}) 62 c.Assert(err, jc.ErrorIsNil) 63 units[i] = unit 64 } 65 preventUnitDestroyRemove(c, units[0]) 66 s.assertDoesNotNeedCleanup(c) 67 68 // Destroy the application and check the units are unaffected, but a cleanup 69 // has been scheduled. 70 err := mysql.Destroy() 71 c.Assert(err, jc.ErrorIsNil) 72 for _, unit := range units { 73 err := unit.Refresh() 74 c.Assert(err, jc.ErrorIsNil) 75 } 76 s.assertNeedsCleanup(c) 77 78 // Run the cleanup, and check that units are all destroyed as appropriate. 79 s.assertCleanupRuns(c) 80 err = units[0].Refresh() 81 c.Assert(err, jc.ErrorIsNil) 82 c.Assert(units[0].Life(), gc.Equals, state.Dying) 83 err = units[1].Refresh() 84 c.Assert(err, jc.Satisfies, errors.IsNotFound) 85 err = units[2].Refresh() 86 c.Assert(err, jc.Satisfies, errors.IsNotFound) 87 88 // Run a final cleanup to clear the cleanup scheduled for the unit that 89 // became dying. 90 s.assertCleanupCount(c, 1) 91 } 92 93 func (s *CleanupSuite) TestCleanupDyingApplicationCharm(c *gc.C) { 94 // Create a application and a charm. 95 ch := s.AddTestingCharm(c, "mysql") 96 mysql := s.AddTestingApplication(c, "mysql", ch) 97 98 // Create a dummy archive blob. 99 stor := storage.NewStorage(s.State.ModelUUID(), s.State.MongoSession()) 100 storagePath := "dummy-path" 101 err := stor.Put(storagePath, bytes.NewReader([]byte("data")), 4) 102 c.Assert(err, jc.ErrorIsNil) 103 104 // Destroy the application and check that a cleanup has been scheduled. 105 err = mysql.Destroy() 106 c.Assert(err, jc.ErrorIsNil) 107 s.assertNeedsCleanup(c) 108 109 // Run the cleanup, and check that the charm is removed. 110 s.assertCleanupRuns(c) 111 _, _, err = stor.Get(storagePath) 112 c.Assert(err, jc.Satisfies, errors.IsNotFound) 113 } 114 115 func (s *CleanupSuite) TestCleanupRemoteApplication(c *gc.C) { 116 app, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{ 117 Name: "remote-app", 118 SourceModel: names.NewModelTag("test"), 119 Token: "token", 120 }) 121 c.Assert(err, jc.ErrorIsNil) 122 123 err = app.Destroy() 124 c.Assert(err, jc.ErrorIsNil) 125 // Removed immediately since there are no relations yet. 126 s.assertDoesNotNeedCleanup(c) 127 _, err = s.State.RemoteApplication("remote-app") 128 c.Assert(err, jc.Satisfies, errors.IsNotFound) 129 } 130 131 func (s *CleanupSuite) TestCleanupRemoteApplicationWithRelations(c *gc.C) { 132 mysqlEps := []charm.Relation{ 133 { 134 Interface: "mysql", 135 Name: "db", 136 Role: charm.RoleProvider, 137 Scope: charm.ScopeGlobal, 138 }, 139 } 140 remoteApp, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{ 141 Name: "mysql", 142 SourceModel: s.Model.ModelTag(), 143 Token: "t0", 144 Endpoints: mysqlEps, 145 }) 146 c.Assert(err, jc.ErrorIsNil) 147 148 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 149 eps, err := s.State.InferEndpoints("wordpress", "mysql") 150 c.Assert(err, jc.ErrorIsNil) 151 _, err = s.State.AddRelation(eps[0], eps[1]) 152 c.Assert(err, jc.ErrorIsNil) 153 c.Assert(remoteApp.Refresh(), jc.ErrorIsNil) 154 c.Assert(wordpress.Refresh(), jc.ErrorIsNil) 155 156 err = remoteApp.Destroy() 157 c.Assert(err, jc.ErrorIsNil) 158 s.assertNeedsCleanup(c) 159 160 // Run the cleanup, and check that the remote app is removed. 161 s.assertCleanupRuns(c) 162 _, err = s.State.RemoteApplication("mysql") 163 c.Assert(err, jc.Satisfies, errors.IsNotFound) 164 } 165 166 func (s *CleanupSuite) TestCleanupControllerModels(c *gc.C) { 167 s.assertDoesNotNeedCleanup(c) 168 169 // Create a non-empty hosted model. 170 otherSt := s.Factory.MakeModel(c, nil) 171 defer otherSt.Close() 172 factory.NewFactory(otherSt, s.StatePool).MakeApplication(c, nil) 173 otherModel, err := otherSt.Model() 174 c.Assert(err, jc.ErrorIsNil) 175 176 s.assertDoesNotNeedCleanup(c) 177 178 // Destroy the controller and check the model is unaffected, but a 179 // cleanup for the model and applications has been scheduled. 180 controllerModel, err := s.State.Model() 181 c.Assert(err, jc.ErrorIsNil) 182 err = controllerModel.Destroy(state.DestroyModelParams{ 183 DestroyHostedModels: true, 184 }) 185 c.Assert(err, jc.ErrorIsNil) 186 187 // Two cleanups should be scheduled. One to destroy the hosted 188 // models, the other to destroy the controller model's 189 // applications. 190 s.assertCleanupCount(c, 1) 191 err = otherModel.Refresh() 192 c.Assert(err, jc.ErrorIsNil) 193 c.Assert(otherModel.Life(), gc.Equals, state.Dying) 194 195 s.assertDoesNotNeedCleanup(c) 196 } 197 198 func (s *CleanupSuite) TestCleanupModelMachinesForce(c *gc.C) { 199 s.testCleanupModelMachines(c, true) 200 } 201 202 func (s *CleanupSuite) TestCleanupModelMachines(c *gc.C) { 203 s.testCleanupModelMachines(c, false) 204 } 205 206 func (s *CleanupSuite) testCleanupModelMachines(c *gc.C, force bool) { 207 // Create a controller machine, and manual and non-manual 208 // workload machine, the latter with a container workload machine. 209 stateMachine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobManageModel) 210 c.Assert(err, jc.ErrorIsNil) 211 modelMachine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 212 c.Assert(err, jc.ErrorIsNil) 213 container, err := s.State.AddMachineInsideMachine(state.MachineTemplate{ 214 Base: state.UbuntuBase("12.10"), 215 Jobs: []state.MachineJob{state.JobHostUnits}, 216 }, modelMachine.Id(), instance.LXD) 217 c.Assert(err, jc.ErrorIsNil) 218 manualMachine, err := s.State.AddOneMachine(state.MachineTemplate{ 219 Base: state.UbuntuBase("12.10"), 220 Jobs: []state.MachineJob{state.JobHostUnits}, 221 InstanceId: "inst-ance", 222 Nonce: "manual:foo", 223 }) 224 c.Assert(err, jc.ErrorIsNil) 225 226 // Create a relation with a unit in scope and assigned to the hosted machine. 227 pr := newPeerRelation(c, s.State) 228 err = pr.u0.AssignToMachine(modelMachine) 229 c.Assert(err, jc.ErrorIsNil) 230 preventPeerUnitsDestroyRemove(c, pr) 231 232 err = pr.ru0.EnterScope(nil) 233 c.Assert(err, jc.ErrorIsNil) 234 s.assertDoesNotNeedCleanup(c) 235 236 // Destroy model, check cleanup queued. 237 model, err := s.State.Model() 238 c.Assert(err, jc.ErrorIsNil) 239 err = model.Destroy(state.DestroyModelParams{Force: &force}) 240 c.Assert(err, jc.ErrorIsNil) 241 s.assertNeedsCleanup(c) 242 243 // Clean up, and check that the unit has been removed... 244 if force { 245 // There are 4 jobs for the destroy and then the model 246 // cleanup task queues another set because force is used. 247 s.assertCleanupCountDirty(c, 4) 248 assertRemoved(c, pr.u0) 249 // ...and the unit has departed relation scope... 250 assertNotJoined(c, pr.ru0) 251 // ...and the machine has been removed (since model destroy does a 252 // force-destroy on the machine). 253 c.Assert(modelMachine.Refresh(), jc.Satisfies, errors.IsNotFound) 254 c.Assert(container.Refresh(), jc.Satisfies, errors.IsNotFound) 255 } else { 256 // Without force, in this test, the machines are not marked Dead, 257 // as no call is made to EnsureDead here, but from the machiner. 258 s.assertCleanupCountDirty(c, 2) 259 assertLife(c, modelMachine, state.Dying) 260 assertLife(c, container, state.Dying) 261 } 262 263 assertLife(c, manualMachine, state.Dying) 264 assertLife(c, stateMachine, state.Alive) 265 } 266 267 func (s *CleanupSuite) TestCleanupModelApplications(c *gc.C) { 268 s.assertDoesNotNeedCleanup(c) 269 270 // Create a application with some units. 271 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 272 units := make([]*state.Unit, 3) 273 for i := range units { 274 unit, err := mysql.AddUnit(state.AddUnitParams{}) 275 c.Assert(err, jc.ErrorIsNil) 276 units[i] = unit 277 } 278 s.assertDoesNotNeedCleanup(c) 279 280 // Destroy the model and check the application and units are 281 // unaffected, but a cleanup for the application has been scheduled. 282 model, err := s.State.Model() 283 c.Assert(err, jc.ErrorIsNil) 284 err = model.Destroy(state.DestroyModelParams{}) 285 c.Assert(err, jc.ErrorIsNil) 286 s.assertNeedsCleanup(c) 287 s.assertCleanupRuns(c) 288 err = mysql.Refresh() 289 c.Assert(err, jc.ErrorIsNil) 290 c.Assert(mysql.Life(), gc.Equals, state.Dying) 291 for _, unit := range units { 292 err = unit.Refresh() 293 c.Assert(err, jc.ErrorIsNil) 294 c.Assert(unit.Life(), gc.Equals, state.Alive) 295 } 296 297 // The first cleanup removes the units, which schedules 298 // the application to be removed. This removes the application 299 // queing up a change for actions and charms. 300 s.assertCleanupCount(c, 3) 301 for _, unit := range units { 302 err = unit.Refresh() 303 c.Assert(err, jc.Satisfies, errors.IsNotFound) 304 } 305 306 // Now we should have all the cleanups done 307 s.assertDoesNotNeedCleanup(c) 308 } 309 310 func (s *CleanupSuite) TestCleanupModelOffers(c *gc.C) { 311 wordpressEps := []charm.Relation{ 312 { 313 Interface: "mysql", 314 Name: "database", 315 Role: charm.RoleRequirer, 316 Scope: charm.ScopeGlobal, 317 }, 318 } 319 remoteApp, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{ 320 Name: "remote-wordpress", 321 SourceModel: s.Model.ModelTag(), 322 IsConsumerProxy: true, 323 Token: "t0", 324 Endpoints: wordpressEps, 325 }) 326 c.Assert(err, jc.ErrorIsNil) 327 328 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 329 units := make([]*state.Unit, 3) 330 for i := range units { 331 unit, err := mysql.AddUnit(state.AddUnitParams{}) 332 c.Assert(err, jc.ErrorIsNil) 333 units[i] = unit 334 } 335 336 eps, err := s.State.InferEndpoints("remote-wordpress", "mysql") 337 c.Assert(err, jc.ErrorIsNil) 338 rel, err := s.State.AddRelation(eps[0], eps[1]) 339 c.Assert(err, jc.ErrorIsNil) 340 c.Assert(remoteApp.Refresh(), jc.ErrorIsNil) 341 c.Assert(mysql.Refresh(), jc.ErrorIsNil) 342 343 // Prevent short circuit of offer removal. 344 ru, err := rel.Unit(units[0]) 345 c.Assert(err, jc.ErrorIsNil) 346 err = ru.EnterScope(nil) 347 c.Assert(err, jc.ErrorIsNil) 348 349 offers := state.NewApplicationOffers(s.State) 350 offer, err := offers.AddOffer(crossmodel.AddApplicationOfferArgs{ 351 OfferName: "hosted-mysql", 352 ApplicationName: "mysql", 353 Endpoints: map[string]string{"database": "server"}, 354 Owner: s.Owner.Name(), 355 }) 356 c.Assert(err, jc.ErrorIsNil) 357 358 _, err = s.State.AddOfferConnection(state.AddOfferConnectionParams{ 359 OfferUUID: offer.OfferUUID, 360 RelationId: rel.Id(), 361 RelationKey: rel.Tag().Id(), 362 Username: s.Owner.Name(), 363 SourceModelUUID: s.State.ModelUUID(), 364 }) 365 c.Assert(err, jc.ErrorIsNil) 366 367 s.assertDoesNotNeedCleanup(c) 368 369 // Destroy the model and check the application and units are 370 // unaffected, but a cleanup for the application has been scheduled, 371 // and the offer has been removed. 372 model, err := s.State.Model() 373 c.Assert(err, jc.ErrorIsNil) 374 err = model.Destroy(state.DestroyModelParams{}) 375 c.Assert(err, jc.ErrorIsNil) 376 s.assertNeedsCleanup(c) 377 s.assertCleanupRuns(c) 378 err = mysql.Refresh() 379 c.Assert(err, jc.ErrorIsNil) 380 c.Assert(mysql.Life(), gc.Equals, state.Dying) 381 for _, unit := range units { 382 err = unit.Refresh() 383 c.Assert(err, jc.ErrorIsNil) 384 c.Assert(unit.Life(), gc.Equals, state.Alive) 385 } 386 387 s.assertCleanupCount(c, 3) 388 allOffers, err := offers.ListOffers() 389 c.Assert(err, jc.ErrorIsNil) 390 c.Assert(allOffers, gc.HasLen, 0) 391 _, err = s.State.RemoteApplication("remote-wordpress") 392 c.Assert(err, jc.Satisfies, errors.IsNotFound) 393 } 394 395 func (s *CleanupSuite) TestCleanupRelationSettings(c *gc.C) { 396 // Create a relation with a unit in scope. 397 pr := newPeerRelation(c, s.State) 398 preventPeerUnitsDestroyRemove(c, pr) 399 rel := pr.ru0.Relation() 400 err := pr.ru0.EnterScope(map[string]interface{}{"some": "settings"}) 401 c.Assert(err, jc.ErrorIsNil) 402 s.assertDoesNotNeedCleanup(c) 403 404 // Destroy the application, check the relation's still around. 405 err = pr.app.Destroy() 406 c.Assert(err, jc.ErrorIsNil) 407 s.assertCleanupCount(c, 2) 408 err = rel.Refresh() 409 c.Assert(err, jc.ErrorIsNil) 410 c.Assert(rel.Life(), gc.Equals, state.Dying) 411 412 // The unit leaves scope, triggering relation removal. 413 err = pr.ru0.LeaveScope() 414 c.Assert(err, jc.ErrorIsNil) 415 s.assertNeedsCleanup(c) 416 417 // Settings are not destroyed yet... 418 settings, err := pr.ru1.ReadSettings("riak/0") 419 c.Assert(err, jc.ErrorIsNil) 420 c.Assert(settings, gc.DeepEquals, map[string]interface{}{"some": "settings"}) 421 422 // ...but they are on cleanup. 423 s.assertCleanupCount(c, 1) 424 _, err = pr.ru1.ReadSettings("riak/0") 425 c.Assert(err, gc.ErrorMatches, `cannot read settings for unit "riak/0" in relation "riak:ring": unit "riak/0": settings not found`) 426 } 427 428 func (s *CleanupSuite) TestCleanupModelBranches(c *gc.C) { 429 s.assertDoesNotNeedCleanup(c) 430 431 // Create a branch. 432 c.Assert(s.Model.AddBranch(newBranchName, newBranchCreator), jc.ErrorIsNil) 433 branches, err := s.State.Branches() 434 c.Assert(err, jc.ErrorIsNil) 435 c.Check(branches, gc.HasLen, 1) 436 s.assertDoesNotNeedCleanup(c) 437 438 // Destroy the model and check the branches unaffected, but a cleanup for 439 // the branches has been scheduled. 440 model, err := s.State.Model() 441 c.Assert(err, jc.ErrorIsNil) 442 err = model.Destroy(state.DestroyModelParams{}) 443 c.Assert(err, jc.ErrorIsNil) 444 s.assertNeedsCleanup(c) 445 s.assertCleanupCount(c, 1) 446 447 s.assertCleanupRuns(c) 448 err = model.Refresh() 449 c.Assert(err, jc.ErrorIsNil) 450 s.assertCleanupCount(c, 0) 451 452 _, err = s.Model.Branch(newBranchName) 453 c.Assert(err, jc.Satisfies, errors.IsNotFound) 454 455 branches, err = s.State.Branches() 456 c.Assert(err, jc.ErrorIsNil) 457 c.Check(branches, gc.HasLen, 0) 458 459 // Now we should have all the cleanups done 460 s.assertDoesNotNeedCleanup(c) 461 } 462 463 func (s *CleanupSuite) TestDestroyControllerMachineErrors(c *gc.C) { 464 manager, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobManageModel) 465 c.Assert(err, jc.ErrorIsNil) 466 node, err := s.State.ControllerNode(manager.Id()) 467 c.Assert(err, jc.ErrorIsNil) 468 node.SetHasVote(true) 469 c.Assert(err, jc.ErrorIsNil) 470 s.assertDoesNotNeedCleanup(c) 471 err = manager.Destroy() 472 c.Assert(err, gc.ErrorMatches, "controller 0 is the only controller") 473 s.assertDoesNotNeedCleanup(c) 474 assertLife(c, manager, state.Alive) 475 } 476 477 func (s *CleanupSuite) TestDestroyControllerMachineHAWithControllerCharm(c *gc.C) { 478 cons := constraints.Value{ 479 Mem: newUint64(100), 480 } 481 changes, err := s.State.EnableHA(3, cons, state.UbuntuBase("12.04"), nil) 482 c.Assert(err, jc.ErrorIsNil) 483 c.Assert(changes.Added, gc.HasLen, 3) 484 485 ch := s.AddTestingCharmWithSeries(c, "juju-controller", "") 486 app := s.AddTestingApplicationForBase(c, state.UbuntuBase("12.04"), "controller", ch) 487 488 var machines []*state.Machine 489 var units []*state.Unit 490 for i := 0; i < 3; i++ { 491 m, err := s.State.Machine(strconv.Itoa(i)) 492 c.Assert(err, jc.ErrorIsNil) 493 c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{ 494 state.JobHostUnits, 495 state.JobManageModel, 496 }) 497 498 if i == 0 { 499 node, err := s.State.ControllerNode(m.Id()) 500 c.Assert(err, jc.ErrorIsNil) 501 node.SetHasVote(true) 502 } 503 504 u, err := app.AddUnit(state.AddUnitParams{}) 505 c.Assert(err, jc.ErrorIsNil) 506 err = u.SetCharmURL(ch.URL()) 507 c.Assert(err, jc.ErrorIsNil) 508 err = u.AssignToMachine(m) 509 c.Assert(err, jc.ErrorIsNil) 510 511 machines = append(machines, m) 512 units = append(units, u) 513 } 514 515 for _, m := range machines { 516 assertLife(c, m, state.Alive) 517 } 518 for _, u := range units { 519 assertLife(c, u, state.Alive) 520 } 521 522 s.assertDoesNotNeedCleanup(c) 523 err = machines[2].Destroy() 524 c.Assert(err, jc.ErrorIsNil) 525 526 s.assertNeedsCleanup(c) 527 s.assertNextCleanup(c, "evacuateMachine(2)") 528 s.assertNeedsCleanup(c) 529 s.assertNextCleanup(c, `removedUnit("controller/2")`) 530 s.assertNeedsCleanup(c) 531 s.assertNextCleanup(c, "evacuateMachine(2)") 532 s.assertDoesNotNeedCleanup(c) 533 } 534 535 const dontWait = time.Duration(0) 536 537 func (s *CleanupSuite) TestCleanupForceDestroyedMachineUnit(c *gc.C) { 538 // Create a machine. 539 machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 540 c.Assert(err, jc.ErrorIsNil) 541 err = machine.SetProvisioned("inst-id", "", "fake_nonce", nil) 542 c.Assert(err, jc.ErrorIsNil) 543 544 // Create a relation with a unit in scope and assigned to the machine. 545 pr := newPeerRelation(c, s.State) 546 err = pr.u0.AssignToMachine(machine) 547 c.Assert(err, jc.ErrorIsNil) 548 preventPeerUnitsDestroyRemove(c, pr) 549 err = pr.ru0.EnterScope(nil) 550 c.Assert(err, jc.ErrorIsNil) 551 s.assertDoesNotNeedCleanup(c) 552 553 // Force machine destruction, check cleanup queued. 554 err = machine.ForceDestroy(time.Minute) 555 c.Assert(err, jc.ErrorIsNil) 556 s.assertNeedsCleanup(c) 557 558 // Clean up, and check that the unit has been removed... 559 s.assertCleanupCountDirty(c, 2) 560 assertRemoved(c, pr.u0) 561 562 // ...and the unit has departed relation scope... 563 assertNotJoined(c, pr.ru0) 564 565 // ...but that the machine remains, and is Dead, ready for removal by the 566 // provisioner. 567 assertLife(c, machine, state.Dead) 568 } 569 570 func (s *CleanupSuite) TestCleanupForceDestroyedControllerMachine(c *gc.C) { 571 machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobManageModel) 572 c.Assert(err, jc.ErrorIsNil) 573 node, err := s.State.ControllerNode(machine.Id()) 574 c.Assert(err, jc.ErrorIsNil) 575 err = node.SetHasVote(true) 576 c.Assert(err, jc.ErrorIsNil) 577 changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("12.04"), nil) 578 c.Assert(err, jc.ErrorIsNil) 579 c.Check(changes.Added, gc.HasLen, 2) 580 c.Check(changes.Removed, gc.HasLen, 0) 581 c.Check(changes.Maintained, gc.HasLen, 1) 582 c.Check(changes.Converted, gc.HasLen, 0) 583 for _, mid := range changes.Added { 584 m, err := s.State.Machine(mid) 585 c.Assert(err, jc.ErrorIsNil) 586 node, err := s.State.ControllerNode(m.Id()) 587 c.Assert(err, jc.ErrorIsNil) 588 c.Assert(node.SetHasVote(true), jc.ErrorIsNil) 589 } 590 s.assertDoesNotNeedCleanup(c) 591 err = machine.ForceDestroy(time.Minute) 592 c.Assert(err, jc.ErrorIsNil) 593 // The machine should no longer want the vote, should be forced to not have the vote, and forced to not be a 594 // controller member anymore 595 c.Assert(machine.Refresh(), jc.ErrorIsNil) 596 c.Check(machine.Life(), gc.Equals, state.Dying) 597 node, err = s.State.ControllerNode(machine.Id()) 598 c.Assert(err, jc.ErrorIsNil) 599 c.Check(node.WantsVote(), jc.IsFalse) 600 c.Check(node.HasVote(), jc.IsTrue) 601 c.Check(machine.Jobs(), jc.DeepEquals, []state.MachineJob{state.JobManageModel}) 602 controllerIds, err := s.State.ControllerIds() 603 c.Assert(err, jc.ErrorIsNil) 604 c.Check(controllerIds, gc.DeepEquals, append([]string{machine.Id()}, changes.Added...)) 605 // ForceDestroy still won't kill the controller if it is flagged as having a vote 606 // We don't see the error because it is logged, but not returned. 607 s.assertCleanupRuns(c) 608 c.Assert(node.SetHasVote(false), jc.ErrorIsNil) 609 // However, if we remove the vote, it can be cleaned up. 610 // ForceDestroy sets up a cleanupForceDestroyedMachine, which 611 // calls advanceLifecycle(Dead) which sets up a 612 // cleanupDyingMachine, which in turn creates a delayed 613 // cleanupForceRemoveMachine. 614 // Run the first two. 615 s.assertCleanupCountDirty(c, 2) 616 // After we've run the cleanup for the controller machine, the machine should be dead, and it should not be 617 // present in the other documents. 618 assertLife(c, machine, state.Dead) 619 controllerIds, err = s.State.ControllerIds() 620 c.Assert(err, jc.ErrorIsNil) 621 sort.Strings(controllerIds) 622 sort.Strings(changes.Added) 623 // Only the machines that were added should still be part of the controller 624 c.Check(controllerIds, gc.DeepEquals, changes.Added) 625 } 626 627 func (s *CleanupSuite) TestCleanupForceDestroyMachineCleansStorageAttachments(c *gc.C) { 628 machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 629 c.Assert(err, jc.ErrorIsNil) 630 s.assertDoesNotNeedCleanup(c) 631 632 err = machine.SetProvisioned("inst-id", "", "fake_nonce", nil) 633 c.Assert(err, jc.ErrorIsNil) 634 635 ch := s.AddTestingCharm(c, "storage-block") 636 storage := map[string]state.StorageConstraints{ 637 "data": makeStorageCons("loop", 1024, 1), 638 } 639 application := s.AddTestingApplicationWithStorage(c, "storage-block", ch, storage) 640 u, err := application.AddUnit(state.AddUnitParams{}) 641 c.Assert(err, jc.ErrorIsNil) 642 err = u.AssignToMachine(machine) 643 c.Assert(err, jc.ErrorIsNil) 644 645 // check no cleanups 646 s.assertDoesNotNeedCleanup(c) 647 648 // this tag matches the storage instance created for the unit above. 649 storageTag := names.NewStorageTag("data/0") 650 651 sa, err := s.storageBackend.StorageAttachment(storageTag, u.UnitTag()) 652 c.Assert(err, jc.ErrorIsNil) 653 c.Assert(sa.Life(), gc.Equals, state.Alive) 654 655 // destroy machine and run cleanups 656 err = machine.ForceDestroy(time.Minute) 657 c.Assert(err, jc.ErrorIsNil) 658 659 // Run cleanups to remove the unit and make the machine dead. 660 s.assertCleanupCountDirty(c, 2) 661 662 // After running the cleanups, the storage attachment should 663 // have been removed; the storage instance should be floating, 664 // and will be removed along with the machine. 665 _, err = s.storageBackend.StorageAttachment(storageTag, u.UnitTag()) 666 c.Assert(err, jc.Satisfies, errors.IsNotFound) 667 si, err := s.storageBackend.StorageInstance(storageTag) 668 c.Assert(err, jc.ErrorIsNil) 669 _, hasOwner := si.Owner() 670 c.Assert(hasOwner, jc.IsFalse) 671 672 // Check that the unit has been removed. 673 assertRemoved(c, u) 674 675 s.Clock.Advance(time.Minute) 676 // Check that the last cleanup to remove the machine runs. 677 s.assertCleanupCount(c, 1) 678 } 679 680 func (s *CleanupSuite) TestCleanupForceDestroyedMachineWithContainer(c *gc.C) { 681 // Create a machine with a container. 682 machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 683 c.Assert(err, jc.ErrorIsNil) 684 err = machine.SetProvisioned("inst-id", "", "fake_nonce", nil) 685 c.Assert(err, jc.ErrorIsNil) 686 container, err := s.State.AddMachineInsideMachine(state.MachineTemplate{ 687 Base: state.UbuntuBase("12.10"), 688 Jobs: []state.MachineJob{state.JobHostUnits}, 689 }, machine.Id(), instance.LXD) 690 c.Assert(err, jc.ErrorIsNil) 691 err = container.SetProvisioned("inst-id", "", "fake_nonce", nil) 692 c.Assert(err, jc.ErrorIsNil) 693 694 // Create active units (in relation scope, with subordinates). 695 prr := newProReqRelation(c, &s.ConnSuite, charm.ScopeContainer, machine, container) 696 prr.allEnterScope(c) 697 698 preventProReqUnitsDestroyRemove(c, prr) 699 s.assertDoesNotNeedCleanup(c) 700 701 // Force removal of the top-level machine. 702 err = machine.ForceDestroy(time.Minute) 703 c.Assert(err, jc.ErrorIsNil) 704 s.assertNeedsCleanup(c) 705 706 // And do it again, just to check that the second cleanup doc for the same 707 // machine doesn't cause problems down the line. 708 err = machine.ForceDestroy(time.Minute) 709 c.Assert(err, jc.ErrorIsNil) 710 s.assertNeedsCleanup(c) 711 712 // Clean up, and check that the container has been removed... 713 s.assertCleanupCountDirty(c, 2) 714 err = container.Refresh() 715 c.Assert(err, jc.Satisfies, errors.IsNotFound) 716 717 // ...and so have all the units... 718 assertRemoved(c, prr.pu0) 719 assertRemoved(c, prr.pu1) 720 assertRemoved(c, prr.ru0) 721 assertRemoved(c, prr.ru1) 722 723 // ...and none of the units have left relation scopes occupied... 724 assertNotInScope(c, prr.pru0) 725 assertNotInScope(c, prr.pru1) 726 assertNotInScope(c, prr.rru0) 727 assertNotInScope(c, prr.rru1) 728 729 // ...but that the machine remains, and is Dead, ready for removal by the 730 // provisioner. 731 assertLife(c, machine, state.Dead) 732 } 733 734 func (s *CleanupSuite) TestForceDestroyMachineSchedulesRemove(c *gc.C) { 735 machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 736 c.Assert(err, jc.ErrorIsNil) 737 err = machine.SetProvisioned("inst-id", "", "fake_nonce", nil) 738 c.Assert(err, jc.ErrorIsNil) 739 740 s.assertDoesNotNeedCleanup(c) 741 742 err = machine.ForceDestroy(time.Minute) 743 c.Assert(err, jc.ErrorIsNil) 744 s.assertNeedsCleanup(c) 745 746 s.assertCleanupRuns(c) 747 748 assertLifeIs(c, machine, state.Dead) 749 750 // Running a cleanup pass succeeds but doesn't get rid of cleanups 751 // because there's a scheduled one. 752 s.assertCleanupRuns(c) 753 assertLifeIs(c, machine, state.Dead) 754 s.assertNeedsCleanup(c) 755 756 s.Clock.Advance(time.Minute) 757 s.assertCleanupCount(c, 1) 758 err = machine.Refresh() 759 c.Assert(err, jc.Satisfies, errors.IsNotFound) 760 } 761 762 func (s *CleanupSuite) TestRemoveApplicationRemovesAllCleanUps(c *gc.C) { 763 ch := s.AddTestingCharm(c, "dummy") 764 app := s.AddTestingApplication(c, "dummy", ch) 765 unit, err := app.AddUnit(state.AddUnitParams{}) 766 c.Assert(err, jc.ErrorIsNil) 767 c.Assert(app.Refresh(), jc.ErrorIsNil) 768 c.Assert(app.UnitCount(), gc.Equals, 1) 769 s.assertDoesNotNeedCleanup(c) 770 771 // app `dummyfoo` and its units should not be impacted after app `dummy` was destroyed. 772 appfoo := s.AddTestingApplication(c, "dummyfoo", ch) 773 unitfoo, err := appfoo.AddUnit(state.AddUnitParams{}) 774 c.Assert(err, jc.ErrorIsNil) 775 c.Assert(appfoo.Refresh(), jc.ErrorIsNil) 776 c.Assert(appfoo.UnitCount(), gc.Equals, 1) 777 s.assertDoesNotNeedCleanup(c) 778 779 s.State.ScheduleForceCleanup(state.CleanupForceDestroyedUnit, unit.Name(), 1*time.Minute) 780 s.State.ScheduleForceCleanup(state.CleanupForceRemoveUnit, unit.Name(), 1*time.Minute) 781 s.State.ScheduleForceCleanup(state.CleanupForceApplication, app.Name(), 1*time.Minute) 782 s.assertNeedsCleanup(c) 783 784 op := app.DestroyOperation() 785 op.DestroyStorage = false 786 op.Force = true 787 op.MaxWait = 1 * time.Minute 788 err = s.State.ApplyOperation(op) 789 c.Assert(err, jc.ErrorIsNil) 790 791 s.assertNeedsCleanup(c) 792 s.assertCleanupCount(c, 3) 793 794 c.Assert(unit.Refresh(), jc.Satisfies, errors.IsNotFound) 795 c.Assert(app.Refresh(), jc.Satisfies, errors.IsNotFound) 796 797 c.Assert(unitfoo.Refresh(), jc.ErrorIsNil) 798 c.Assert(appfoo.Refresh(), jc.ErrorIsNil) 799 c.Assert(appfoo.UnitCount(), gc.Equals, 1) 800 } 801 802 func (s *CleanupSuite) TestForceDestroyMachineRemovesUpgradeSeriesLock(c *gc.C) { 803 machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 804 c.Assert(err, jc.ErrorIsNil) 805 806 err = machine.SetProvisioned("inst-id", "", "fake_nonce", nil) 807 c.Assert(err, jc.ErrorIsNil) 808 s.assertDoesNotNeedCleanup(c) 809 810 err = machine.CreateUpgradeSeriesLock(nil, state.UbuntuBase("16.04")) 811 c.Assert(err, jc.ErrorIsNil) 812 813 err = machine.ForceDestroy(time.Minute) 814 c.Assert(err, jc.ErrorIsNil) 815 s.assertNeedsCleanup(c) 816 s.assertCleanupRuns(c) 817 818 assertLifeIs(c, machine, state.Dead) 819 820 // Running a cleanup pass succeeds but doesn't get rid of cleanups 821 // because there's a scheduled one. 822 s.assertCleanupRuns(c) 823 assertLifeIs(c, machine, state.Dead) 824 s.assertNeedsCleanup(c) 825 826 locked, err := machine.IsLockedForSeriesUpgrade() 827 c.Assert(err, jc.ErrorIsNil) 828 c.Assert(locked, jc.IsFalse) 829 830 s.Clock.Advance(time.Minute) 831 s.assertCleanupCount(c, 1) 832 err = machine.Refresh() 833 c.Assert(err, jc.Satisfies, errors.IsNotFound) 834 } 835 836 func (s *CleanupSuite) TestDestroyMachineAssertsNoUpgradeSeriesLock(c *gc.C) { 837 machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 838 c.Assert(err, jc.ErrorIsNil) 839 840 err = machine.SetProvisioned("inst-id", "", "fake_nonce", nil) 841 c.Assert(err, jc.ErrorIsNil) 842 s.assertDoesNotNeedCleanup(c) 843 844 // Simulate a race by adding a lock after the check has been run, 845 // but before the destruction transaction assertions execute. 846 defer state.SetBeforeHooks(c, s.State, func() { 847 c.Assert(machine.CreateUpgradeSeriesLock(nil, state.UbuntuBase("16.04")), gc.IsNil) 848 }).Check() 849 850 // Check that we get an error, but for the transaction assertion failure, 851 // and not for the initial check, which passes. 852 err = machine.Destroy() 853 c.Assert(err, gc.NotNil) 854 c.Assert(err, gc.Not(gc.ErrorMatches), `machine 1 is locked for series upgrade`) 855 856 assertLifeIs(c, machine, state.Alive) 857 } 858 859 func (s *CleanupSuite) TestForceDestroyMachineRemovesLinkLayerDevices(c *gc.C) { 860 machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 861 c.Assert(err, jc.ErrorIsNil) 862 863 err = machine.SetProvisioned("inst-id", "", "fake_nonce", nil) 864 c.Assert(err, jc.ErrorIsNil) 865 s.assertDoesNotNeedCleanup(c) 866 867 // Add a NIC with an address. 868 ops, err := machine.AddLinkLayerDeviceOps( 869 state.LinkLayerDeviceArgs{ 870 Name: "eth0", 871 }, 872 state.LinkLayerDeviceAddress{ 873 DeviceName: "eth0", 874 CIDRAddress: "192.168.0.9/24", 875 }, 876 ) 877 c.Assert(err, jc.ErrorIsNil) 878 state.RunTransaction(c, s.State, ops) 879 880 nics, err := machine.AllLinkLayerDevices() 881 c.Assert(err, jc.ErrorIsNil) 882 c.Assert(nics, gc.HasLen, 1) 883 884 err = machine.ForceDestroy(time.Minute) 885 c.Assert(err, jc.ErrorIsNil) 886 s.assertNeedsCleanup(c) 887 s.assertCleanupRuns(c) 888 889 assertLifeIs(c, machine, state.Dead) 890 891 // Nics should be gone. 892 nics, err = machine.AllLinkLayerDevices() 893 c.Assert(err, jc.ErrorIsNil) 894 c.Assert(nics, gc.HasLen, 0) 895 } 896 897 func (s *CleanupSuite) TestCleanupDyingUnit(c *gc.C) { 898 // Create active unit, in a relation. 899 prr := newProReqRelation(c, &s.ConnSuite, charm.ScopeGlobal) 900 preventProReqUnitsDestroyRemove(c, prr) 901 err := prr.pru0.EnterScope(nil) 902 c.Assert(err, jc.ErrorIsNil) 903 904 // Destroy provider unit 0; check it's Dying, and a cleanup has been scheduled. 905 err = prr.pu0.Destroy() 906 c.Assert(err, jc.ErrorIsNil) 907 err = prr.pu0.Refresh() 908 c.Assert(err, jc.ErrorIsNil) 909 assertLife(c, prr.pu0, state.Dying) 910 s.assertNeedsCleanup(c) 911 912 // Check it's reported in scope until cleaned up. 913 assertJoined(c, prr.pru0) 914 s.assertCleanupCount(c, 1) 915 assertInScope(c, prr.pru0) 916 assertNotJoined(c, prr.pru0) 917 918 // Destroy the relation, and check it sticks around... 919 err = prr.rel.Refresh() 920 c.Assert(err, jc.ErrorIsNil) 921 err = prr.rel.Destroy() 922 c.Assert(err, jc.ErrorIsNil) 923 assertLife(c, prr.rel, state.Dying) 924 925 // ...until the unit is removed, and really leaves scope. 926 err = prr.pu0.EnsureDead() 927 c.Assert(err, jc.ErrorIsNil) 928 err = prr.pu0.Remove() 929 c.Assert(err, jc.ErrorIsNil) 930 assertNotInScope(c, prr.pru0) 931 assertRemoved(c, prr.rel) 932 } 933 934 func (s *CleanupSuite) TestCleanupDyingUnitAlreadyRemoved(c *gc.C) { 935 // Create active unit, in a relation. 936 prr := newProReqRelation(c, &s.ConnSuite, charm.ScopeGlobal) 937 preventProReqUnitsDestroyRemove(c, prr) 938 err := prr.pru0.EnterScope(nil) 939 c.Assert(err, jc.ErrorIsNil) 940 941 // Destroy provider unit 0; check it's Dying, and a cleanup has been scheduled. 942 err = prr.pu0.Destroy() 943 c.Assert(err, jc.ErrorIsNil) 944 err = prr.pu0.Refresh() 945 c.Assert(err, jc.ErrorIsNil) 946 assertLife(c, prr.pu0, state.Dying) 947 s.assertNeedsCleanup(c) 948 949 // Remove the unit, and the relation. 950 err = prr.pu0.EnsureDead() 951 c.Assert(err, jc.ErrorIsNil) 952 err = prr.pu0.Remove() 953 c.Assert(err, jc.ErrorIsNil) 954 err = prr.rel.Destroy() 955 c.Assert(err, jc.ErrorIsNil) 956 assertRemoved(c, prr.rel) 957 958 // Check the cleanup still runs happily. 959 s.assertCleanupCount(c, 1) 960 s.assertCleanupRuns(c) 961 } 962 963 func (s *CleanupSuite) TestCleanupActions(c *gc.C) { 964 // Create a application with a unit. 965 dummy := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 966 unit, err := dummy.AddUnit(state.AddUnitParams{}) 967 c.Assert(err, jc.ErrorIsNil) 968 969 // check no cleanups 970 s.assertDoesNotNeedCleanup(c) 971 972 operationID, err := s.Model.EnqueueOperation("a test", 2) 973 c.Assert(err, jc.ErrorIsNil) 974 // Add a couple actions to the unit 975 _, err = s.Model.AddAction(unit, operationID, "snapshot", nil, nil, nil) 976 c.Assert(err, jc.ErrorIsNil) 977 _, err = s.Model.AddAction(unit, operationID, "snapshot", nil, nil, nil) 978 c.Assert(err, jc.ErrorIsNil) 979 980 // make sure unit still has actions 981 actions, err := unit.PendingActions() 982 c.Assert(err, jc.ErrorIsNil) 983 c.Assert(len(actions), gc.Equals, 2) 984 985 // destroy unit and run cleanups 986 err = dummy.Destroy() 987 c.Assert(err, jc.ErrorIsNil) 988 s.assertCleanupRuns(c) 989 990 // make sure unit still has actions, after first cleanup pass 991 actions, err = unit.PendingActions() 992 c.Assert(err, jc.ErrorIsNil) 993 c.Assert(len(actions), gc.Equals, 2) 994 995 // second cleanup pass 996 s.assertCleanupRuns(c) 997 998 // make sure unit has no actions, after second cleanup pass 999 actions, err = unit.PendingActions() 1000 c.Assert(err, jc.ErrorIsNil) 1001 c.Assert(len(actions), gc.Equals, 0) 1002 1003 // Application has been cleaned up, but now we cleanup the charm 1004 c.Assert(dummy.Refresh(), jc.Satisfies, errors.IsNotFound) 1005 s.assertCleanupRuns(c) 1006 1007 // check no cleanups 1008 s.assertDoesNotNeedCleanup(c) 1009 } 1010 1011 func (s *CleanupSuite) TestCleanupWithCompletedActions(c *gc.C) { 1012 for _, status := range []state.ActionStatus{ 1013 state.ActionCompleted, 1014 state.ActionCancelled, 1015 state.ActionAborted, 1016 state.ActionFailed, 1017 } { 1018 // Create a application with a unit. 1019 dummy := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 1020 unit, err := dummy.AddUnit(state.AddUnitParams{}) 1021 c.Assert(err, jc.ErrorIsNil) 1022 s.assertDoesNotNeedCleanup(c) 1023 1024 // Add a completed action to the unit. 1025 operationID, err := s.Model.EnqueueOperation("a test", 1) 1026 c.Assert(err, jc.ErrorIsNil) 1027 action, err := s.Model.AddAction(unit, operationID, "snapshot", nil, nil, nil) 1028 c.Assert(err, jc.ErrorIsNil) 1029 action, err = action.Finish(state.ActionResults{ 1030 Status: status, 1031 Message: "done", 1032 }) 1033 c.Assert(err, jc.ErrorIsNil) 1034 c.Assert(action.Status(), gc.Equals, status) 1035 1036 // Destroy application and run cleanups. 1037 err = dummy.Destroy() 1038 c.Assert(err, jc.ErrorIsNil) 1039 // First cleanup marks all units of the application as dying. 1040 // Second cleanup clear pending actions. 1041 s.assertCleanupCount(c, 3) 1042 } 1043 } 1044 1045 func (s *CleanupSuite) TestCleanupStorageAttachments(c *gc.C) { 1046 s.assertDoesNotNeedCleanup(c) 1047 1048 ch := s.AddTestingCharm(c, "storage-block") 1049 storage := map[string]state.StorageConstraints{ 1050 "data": makeStorageCons("loop", 1024, 1), 1051 } 1052 application := s.AddTestingApplicationWithStorage(c, "storage-block", ch, storage) 1053 u, err := application.AddUnit(state.AddUnitParams{}) 1054 c.Assert(err, jc.ErrorIsNil) 1055 1056 // check no cleanups 1057 s.assertDoesNotNeedCleanup(c) 1058 1059 // this tag matches the storage instance created for the unit above. 1060 storageTag := names.NewStorageTag("data/0") 1061 1062 sa, err := s.storageBackend.StorageAttachment(storageTag, u.UnitTag()) 1063 c.Assert(err, jc.ErrorIsNil) 1064 c.Assert(sa.Life(), gc.Equals, state.Alive) 1065 1066 // destroy unit and run cleanups; the storage should be detached 1067 err = u.Destroy() 1068 c.Assert(err, jc.ErrorIsNil) 1069 s.assertCleanupRuns(c) 1070 1071 // After running the cleanup, the attachment should be removed 1072 // (short-circuited, because volume was never attached). 1073 _, err = s.storageBackend.StorageAttachment(storageTag, u.UnitTag()) 1074 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1075 1076 // check no cleanups 1077 s.assertDoesNotNeedCleanup(c) 1078 } 1079 1080 func (s *CleanupSuite) TestCleanupStorageInstances(c *gc.C) { 1081 ch := s.AddTestingCharm(c, "storage-block") 1082 storage := map[string]state.StorageConstraints{ 1083 "allecto": makeStorageCons("modelscoped-block", 1024, 1), 1084 } 1085 application := s.AddTestingApplicationWithStorage(c, "storage-block", ch, storage) 1086 u, err := application.AddUnit(state.AddUnitParams{}) 1087 c.Assert(err, jc.ErrorIsNil) 1088 1089 // check no cleanups 1090 s.assertDoesNotNeedCleanup(c) 1091 1092 // this tag matches the storage instance created for the unit above. 1093 storageTag := names.NewStorageTag("allecto/0") 1094 1095 si, err := s.storageBackend.StorageInstance(storageTag) 1096 c.Assert(err, jc.ErrorIsNil) 1097 c.Assert(si.Life(), gc.Equals, state.Alive) 1098 1099 // destroy storage instance and run cleanups 1100 err = s.storageBackend.DestroyStorageInstance(storageTag, true, false, dontWait) 1101 c.Assert(err, jc.ErrorIsNil) 1102 si, err = s.storageBackend.StorageInstance(storageTag) 1103 c.Assert(err, jc.ErrorIsNil) 1104 c.Assert(si.Life(), gc.Equals, state.Dying) 1105 sa, err := s.storageBackend.StorageAttachment(storageTag, u.UnitTag()) 1106 c.Assert(err, jc.ErrorIsNil) 1107 c.Assert(sa.Life(), gc.Equals, state.Alive) 1108 s.assertCleanupRuns(c) 1109 1110 // After running the cleanup, the attachment should be removed 1111 // (short-circuited, because volume was never attached). 1112 _, err = s.storageBackend.StorageAttachment(storageTag, u.UnitTag()) 1113 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1114 1115 // check no cleanups 1116 s.assertDoesNotNeedCleanup(c) 1117 } 1118 1119 func (s *CleanupSuite) TestCleanupMachineStorage(c *gc.C) { 1120 ch := s.AddTestingCharm(c, "storage-block") 1121 storage := map[string]state.StorageConstraints{ 1122 "data": makeStorageCons("modelscoped", 1024, 1), 1123 } 1124 application := s.AddTestingApplicationWithStorage(c, "storage-block", ch, storage) 1125 unit, err := application.AddUnit(state.AddUnitParams{}) 1126 c.Assert(err, jc.ErrorIsNil) 1127 err = s.State.AssignUnit(unit, state.AssignCleanEmpty) 1128 c.Assert(err, jc.ErrorIsNil) 1129 machineId, err := unit.AssignedMachineId() 1130 c.Assert(err, jc.ErrorIsNil) 1131 machine, err := s.State.Machine(machineId) 1132 c.Assert(err, jc.ErrorIsNil) 1133 1134 // Destroy the application, so we can destroy the machine. 1135 err = unit.Destroy() 1136 c.Assert(err, jc.ErrorIsNil) 1137 s.assertCleanupRuns(c) 1138 err = application.Destroy() 1139 c.Assert(err, jc.ErrorIsNil) 1140 s.assertCleanupRuns(c) 1141 1142 // check no cleanups 1143 s.assertDoesNotNeedCleanup(c) 1144 1145 // destroy machine and run cleanups; the volume attachment 1146 // should be marked dying. 1147 err = machine.Destroy() 1148 c.Assert(err, jc.ErrorIsNil) 1149 s.assertCleanupRuns(c) 1150 1151 vas, err := s.storageBackend.MachineVolumeAttachments(machine.MachineTag()) 1152 c.Assert(err, jc.ErrorIsNil) 1153 c.Assert(vas, gc.HasLen, 1) 1154 c.Assert(vas[0].Life(), gc.Equals, state.Dying) 1155 1156 // check no cleanups 1157 s.assertDoesNotNeedCleanup(c) 1158 } 1159 1160 func (s *CleanupSuite) TestCleanupCAASApplicationWithStorage(c *gc.C) { 1161 s.assertCleanupCAASEntityWithStorage(c, func(st *state.State, app *state.Application) error { 1162 op := app.DestroyOperation() 1163 op.DestroyStorage = true 1164 return st.ApplyOperation(op) 1165 }) 1166 } 1167 1168 func (s *CleanupSuite) TestCleanupCAASUnitWithStorage(c *gc.C) { 1169 s.assertCleanupCAASEntityWithStorage(c, func(st *state.State, app *state.Application) error { 1170 units, err := app.AllUnits() 1171 if err != nil { 1172 return err 1173 } 1174 op := units[0].DestroyOperation() 1175 op.DestroyStorage = true 1176 return st.ApplyOperation(op) 1177 }) 1178 } 1179 1180 func (s *CleanupSuite) assertCleanupCAASEntityWithStorage(c *gc.C, deleteOp func(*state.State, *state.Application) error) { 1181 s.PatchValue(&k8sprovider.NewK8sClients, k8stesting.NoopFakeK8sClients) 1182 st := s.Factory.MakeCAASModel(c, nil) 1183 defer st.Close() 1184 1185 assertCleanups := func(n int) { 1186 for i := 0; i < 4; i++ { 1187 err := st.Cleanup() 1188 c.Assert(err, jc.ErrorIsNil) 1189 } 1190 state.AssertNoCleanups(c, st) 1191 } 1192 1193 sb, err := state.NewStorageBackend(st) 1194 c.Assert(err, jc.ErrorIsNil) 1195 model, err := st.Model() 1196 c.Assert(err, jc.ErrorIsNil) 1197 broker, err := stateenvirons.GetNewCAASBrokerFunc(caas.New)(model) 1198 c.Assert(err, jc.ErrorIsNil) 1199 registry := stateenvirons.NewStorageProviderRegistry(broker) 1200 s.policy = testing.MockPolicy{ 1201 GetStorageProviderRegistry: func() (corestorage.ProviderRegistry, error) { 1202 return registry, nil 1203 }, 1204 } 1205 1206 ch := state.AddTestingCharmForSeries(c, st, "kubernetes", "storage-filesystem") 1207 storCons := map[string]state.StorageConstraints{ 1208 "data": makeStorageCons("", 1024, 1), 1209 } 1210 application := state.AddTestingApplicationWithStorage(c, st, "storage-filesystem", ch, storCons) 1211 unit, err := application.AddUnit(state.AddUnitParams{}) 1212 c.Assert(err, jc.ErrorIsNil) 1213 c.Assert(application.Refresh(), jc.ErrorIsNil) 1214 1215 fs, err := sb.AllFilesystems() 1216 c.Assert(err, jc.ErrorIsNil) 1217 c.Assert(fs, gc.HasLen, 1) 1218 fas, err := sb.UnitFilesystemAttachments(unit.UnitTag()) 1219 c.Assert(err, jc.ErrorIsNil) 1220 c.Assert(fas, gc.HasLen, 1) 1221 1222 vols, err := sb.AllVolumes() 1223 c.Assert(err, jc.ErrorIsNil) 1224 c.Assert(vols, gc.HasLen, 1) 1225 1226 c.Log("provision storage") 1227 err = sb.SetVolumeInfo(vols[0].VolumeTag(), state.VolumeInfo{ 1228 Size: 1024, 1229 VolumeId: "cloud-vol-id-1234", 1230 }) 1231 c.Assert(err, jc.ErrorIsNil) 1232 err = sb.SetVolumeAttachmentInfo(unit.UnitTag(), vols[0].VolumeTag(), state.VolumeAttachmentInfo{}) 1233 c.Assert(err, jc.ErrorIsNil) 1234 err = sb.SetFilesystemInfo(fs[0].FilesystemTag(), state.FilesystemInfo{ 1235 Size: 1024, 1236 Pool: "", 1237 FilesystemId: "cloud-fs-id-123", 1238 }) 1239 c.Assert(err, jc.ErrorIsNil) 1240 err = sb.SetFilesystemAttachmentInfo(unit.UnitTag(), fs[0].FilesystemTag(), state.FilesystemAttachmentInfo{ 1241 MountPoint: "/abc", 1242 }) 1243 c.Assert(err, jc.ErrorIsNil) 1244 1245 c.Log("delete op") 1246 err = deleteOp(st, application) 1247 c.Assert(err, jc.ErrorIsNil) 1248 c.Assert(application.Refresh(), jc.ErrorIsNil) 1249 1250 c.Log("destroy app") 1251 err = application.Destroy() 1252 c.Assert(err, jc.ErrorIsNil) 1253 1254 assertCleanups(4) 1255 c.Assert(application.Life(), gc.Equals, state.Dying) 1256 c.Assert(unit.Refresh(), jc.ErrorIsNil) 1257 c.Assert(unit.Life(), gc.Equals, state.Dying) 1258 fas, err = sb.UnitFilesystemAttachments(unit.UnitTag()) 1259 c.Assert(err, jc.ErrorIsNil) 1260 c.Assert(fas, gc.HasLen, 1) 1261 fs, err = sb.AllFilesystems() 1262 c.Assert(err, jc.ErrorIsNil) 1263 c.Assert(fs, gc.HasLen, 1) 1264 sas, err := sb.AllStorageInstances() 1265 c.Assert(err, jc.ErrorIsNil) 1266 c.Assert(sas, gc.HasLen, 1) 1267 1268 c.Log("unit dying") 1269 // RemoveStorageAttachment is called after storage-detaching hook. 1270 err = sb.RemoveStorageAttachment(names.NewStorageTag("data/0"), unit.UnitTag(), false) 1271 c.Assert(err, jc.ErrorIsNil) 1272 assertCleanups(1) 1273 1274 err = unit.EnsureDead() 1275 c.Assert(err, jc.ErrorIsNil) 1276 err = unit.Remove() 1277 c.Assert(err, jc.ErrorIsNil) 1278 sas, err = sb.AllStorageInstances() 1279 c.Assert(err, jc.ErrorIsNil) 1280 c.Assert(sas, gc.HasLen, 0) 1281 1282 assertCleanups(1) 1283 fs, err = sb.AllFilesystems() 1284 c.Assert(err, jc.ErrorIsNil) 1285 c.Assert(fs, gc.HasLen, 0) 1286 1287 // A storage provisioner would call this. 1288 err = sb.RemoveVolumeAttachment(unit.UnitTag(), vols[0].VolumeTag(), false) 1289 c.Assert(err, jc.ErrorIsNil) 1290 err = sb.RemoveVolume(vols[0].VolumeTag()) 1291 c.Assert(err, jc.ErrorIsNil) 1292 1293 assertCleanups(1) 1294 vols, err = sb.AllVolumes() 1295 c.Assert(err, jc.ErrorIsNil) 1296 c.Assert(vols, gc.HasLen, 0) 1297 } 1298 1299 func (s *CleanupSuite) TestCleanupVolumeAttachments(c *gc.C) { 1300 _, err := s.State.AddOneMachine(state.MachineTemplate{ 1301 Base: state.UbuntuBase("12.10"), 1302 Jobs: []state.MachineJob{state.JobHostUnits}, 1303 Volumes: []state.HostVolumeParams{{ 1304 Volume: state.VolumeParams{Pool: "loop", Size: 1024}, 1305 }}, 1306 }) 1307 c.Assert(err, jc.ErrorIsNil) 1308 s.assertDoesNotNeedCleanup(c) 1309 1310 err = s.storageBackend.DestroyVolume(names.NewVolumeTag("0/0"), false) 1311 c.Assert(err, jc.ErrorIsNil) 1312 s.assertCleanupRuns(c) 1313 1314 attachment, err := s.storageBackend.VolumeAttachment(names.NewMachineTag("0"), names.NewVolumeTag("0/0")) 1315 c.Assert(err, jc.ErrorIsNil) 1316 c.Assert(attachment.Life(), gc.Equals, state.Dying) 1317 } 1318 1319 func (s *CleanupSuite) TestCleanupFilesystemAttachments(c *gc.C) { 1320 _, err := s.State.AddOneMachine(state.MachineTemplate{ 1321 Base: state.UbuntuBase("12.10"), 1322 Jobs: []state.MachineJob{state.JobHostUnits}, 1323 Filesystems: []state.HostFilesystemParams{{ 1324 Filesystem: state.FilesystemParams{Pool: "rootfs", Size: 1024}, 1325 }}, 1326 }) 1327 c.Assert(err, jc.ErrorIsNil) 1328 s.assertDoesNotNeedCleanup(c) 1329 1330 err = s.storageBackend.DestroyFilesystem(names.NewFilesystemTag("0/0"), false) 1331 c.Assert(err, jc.ErrorIsNil) 1332 s.assertCleanupRuns(c) 1333 1334 attachment, err := s.storageBackend.FilesystemAttachment(names.NewMachineTag("0"), names.NewFilesystemTag("0/0")) 1335 c.Assert(err, jc.ErrorIsNil) 1336 c.Assert(attachment.Life(), gc.Equals, state.Dying) 1337 } 1338 1339 func (s *CleanupSuite) TestCleanupResourceBlob(c *gc.C) { 1340 app := s.AddTestingApplication(c, "wp", s.AddTestingCharm(c, "wordpress")) 1341 data := "ancient-debris" 1342 res := resourcetesting.NewResource(c, nil, "mug", "wp", data).Resource 1343 resources := s.State.Resources() 1344 _, err := resources.SetResource("wp", res.Username, res.Resource, bytes.NewBufferString(data), state.IncrementCharmModifiedVersion) 1345 c.Assert(err, jc.ErrorIsNil) 1346 1347 err = app.Destroy() 1348 c.Assert(err, jc.ErrorIsNil) 1349 1350 path := "application-wp/resources/mug" 1351 stateStorage := storage.NewStorage(s.State.ModelUUID(), s.State.MongoSession()) 1352 closer, _, err := stateStorage.Get(path) 1353 c.Assert(err, jc.ErrorIsNil) 1354 err = closer.Close() 1355 c.Assert(err, jc.ErrorIsNil) 1356 1357 s.assertCleanupRuns(c) 1358 1359 _, _, err = stateStorage.Get(path) 1360 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1361 } 1362 1363 func (s *CleanupSuite) TestCleanupResourceBlobHandlesMissing(c *gc.C) { 1364 app := s.AddTestingApplication(c, "wp", s.AddTestingCharm(c, "wordpress")) 1365 data := "ancient-debris" 1366 res := resourcetesting.NewResource(c, nil, "mug", "wp", data).Resource 1367 resources := s.State.Resources() 1368 _, err := resources.SetResource("wp", res.Username, res.Resource, bytes.NewBufferString(data), state.IncrementCharmModifiedVersion) 1369 c.Assert(err, jc.ErrorIsNil) 1370 1371 err = app.Destroy() 1372 c.Assert(err, jc.ErrorIsNil) 1373 1374 path := "application-wp/resources/mug" 1375 stateStorage := storage.NewStorage(s.State.ModelUUID(), s.State.MongoSession()) 1376 err = stateStorage.Remove(path) 1377 c.Assert(err, jc.ErrorIsNil) 1378 1379 s.assertCleanupRuns(c) 1380 // Make sure the cleanup completed successfully. 1381 s.assertDoesNotNeedCleanup(c) 1382 } 1383 1384 func (s *CleanupSuite) TestNothingToCleanup(c *gc.C) { 1385 s.assertDoesNotNeedCleanup(c) 1386 s.assertCleanupRuns(c) 1387 s.assertDoesNotNeedCleanup(c) 1388 } 1389 1390 func (s *CleanupSuite) TestCleanupIDSanity(c *gc.C) { 1391 // Cleanup IDs shouldn't be ObjectIdHex("blah") 1392 app := s.AddTestingApplication(c, "wp", s.AddTestingCharm(c, "wordpress")) 1393 err := app.Destroy() 1394 c.Assert(err, jc.ErrorIsNil) 1395 1396 coll := s.Session.DB("juju").C("cleanups") 1397 var ids []struct { 1398 ID string `bson:"_id"` 1399 } 1400 err = coll.Find(nil).All(&ids) 1401 c.Assert(err, jc.ErrorIsNil) 1402 for _, item := range ids { 1403 c.Assert(item.ID, gc.Not(gc.Matches), `.*ObjectIdHex\(.*`) 1404 } 1405 s.assertCleanupRuns(c) 1406 } 1407 1408 func (s *CleanupSuite) TestDyingUnitWithForceSchedulesForceFallback(c *gc.C) { 1409 ch := s.AddTestingCharm(c, "mysql") 1410 application := s.AddTestingApplication(c, "mysql", ch) 1411 unit, err := application.AddUnit(state.AddUnitParams{}) 1412 c.Assert(err, jc.ErrorIsNil) 1413 err = s.State.AssignUnit(unit, state.AssignCleanEmpty) 1414 c.Assert(err, jc.ErrorIsNil) 1415 1416 err = unit.SetAgentStatus(status.StatusInfo{ 1417 Status: status.Idle, 1418 }) 1419 c.Assert(err, jc.ErrorIsNil) 1420 1421 opErrs, err := unit.DestroyWithForce(true, time.Minute) 1422 c.Assert(err, jc.ErrorIsNil) 1423 c.Assert(opErrs, gc.IsNil) 1424 1425 // The unit should be dying, and there's a deferred cleanup to 1426 // endeaden it. 1427 assertLifeIs(c, unit, state.Dying) 1428 1429 s.assertNeedsCleanup(c) 1430 // dyingUnit 1431 s.assertCleanupRuns(c) 1432 1433 s.Clock.Advance(time.Minute) 1434 // forceDestroyedUnit 1435 s.assertCleanupRuns(c) 1436 1437 assertLifeIs(c, unit, state.Dead) 1438 1439 s.Clock.Advance(time.Minute) 1440 // forceRemoveUnit 1441 s.assertCleanupRuns(c) 1442 1443 assertUnitRemoved(c, unit) 1444 // After this there are two cleanups remaining: removedUnit, forceRemoveMachine 1445 //(but the last is delayed a minute). 1446 s.assertCleanupCountDirty(c, 2) 1447 1448 s.Clock.Advance(time.Minute) 1449 s.assertCleanupCount(c, 1) 1450 } 1451 1452 func (s *CleanupSuite) TestForceDestroyUnitDestroysSubordinates(c *gc.C) { 1453 prr := newProReqRelation(c, &s.ConnSuite, charm.ScopeContainer) 1454 prr.allEnterScope(c) 1455 for _, principal := range []*state.Unit{prr.pu0, prr.pu1} { 1456 err := s.State.AssignUnit(principal, state.AssignCleanEmpty) 1457 c.Assert(err, jc.ErrorIsNil) 1458 } 1459 for _, unit := range []*state.Unit{prr.pu0, prr.pu1, prr.ru0, prr.ru1} { 1460 err := unit.SetAgentStatus(status.StatusInfo{ 1461 Status: status.Idle, 1462 }) 1463 c.Assert(err, jc.ErrorIsNil) 1464 } 1465 1466 unit := prr.pu0 1467 subordinate := prr.ru0 1468 1469 opErrs, err := unit.DestroyWithForce(true, time.Duration(0)) 1470 c.Assert(err, jc.ErrorIsNil) 1471 c.Assert(opErrs, gc.IsNil) 1472 1473 assertLifeIs(c, unit, state.Dying) 1474 assertLifeIs(c, subordinate, state.Alive) 1475 1476 s.assertNeedsCleanup(c) 1477 // dyingUnit(mysql/0) 1478 s.assertNextCleanup(c, "dyingUnit(mysql/0)") 1479 1480 // forceDestroyUnit(mysql/0) triggers destruction of the subordinate that 1481 // needs to run - it fails because the subordinates haven't yet 1482 // been removed. It will be pending until near the end of this test when it 1483 // finally succeeds. 1484 s.assertNextCleanup(c, "forceDestroyUnit(mysql/0)") 1485 1486 assertLifeIs(c, subordinate, state.Dying) 1487 assertLifeIs(c, unit, state.Dying) 1488 1489 // dyingUnit(logging/0) runs and schedules the force cleanup. 1490 s.assertNextCleanup(c, "dyingUnit(logging/0)") 1491 // forceDestroyUnit(logging/0) sets it to dead. 1492 s.assertNextCleanup(c, "forceDestroyUnit(logging/0)") 1493 1494 assertLifeIs(c, subordinate, state.Dead) 1495 assertLifeIs(c, unit, state.Dying) 1496 1497 // forceRemoveUnit(logging/0) runs 1498 s.assertNextCleanup(c, "forceRemoveUnit(logging/0)") 1499 assertUnitRemoved(c, subordinate) 1500 1501 // Now forceDestroyUnit(mysql/0) can run successfully and make the unit dead 1502 s.assertNextCleanup(c, "forceRemoveUnit(mysql/0)") 1503 assertLifeIs(c, unit, state.Dead) 1504 1505 // forceRemoveUnit 1506 s.assertNextCleanup(c, "forceRemoveUnit") 1507 1508 assertUnitRemoved(c, unit) 1509 // After this there are two cleanups remaining: removedUnit, forceRemoveMachine. 1510 s.assertCleanupCount(c, 2) 1511 } 1512 1513 func (s *CleanupSuite) TestForceDestroyUnitLeavesRelations(c *gc.C) { 1514 prr := newProReqRelation(c, &s.ConnSuite, charm.ScopeGlobal) 1515 prr.allEnterScope(c) 1516 for _, unit := range []*state.Unit{prr.pu0, prr.pu1, prr.ru0, prr.ru1} { 1517 err := s.State.AssignUnit(unit, state.AssignCleanEmpty) 1518 c.Assert(err, jc.ErrorIsNil) 1519 err = unit.SetAgentStatus(status.StatusInfo{ 1520 Status: status.Idle, 1521 }) 1522 c.Assert(err, jc.ErrorIsNil) 1523 } 1524 1525 unit := prr.pu0 1526 opErrs, err := unit.DestroyWithForce(true, dontWait) 1527 c.Assert(err, jc.ErrorIsNil) 1528 c.Assert(opErrs, gc.IsNil) 1529 1530 assertLifeIs(c, unit, state.Dying) 1531 assertUnitInScope(c, unit, prr.rel, true) 1532 1533 // dyingUnit schedules forceDestroyedUnit 1534 s.assertCleanupRuns(c) 1535 // ...which leaves the scope for its relations. 1536 s.assertCleanupRuns(c) 1537 1538 assertLifeIs(c, unit, state.Dead) 1539 assertUnitInScope(c, unit, prr.rel, false) 1540 1541 // forceRemoveUnit 1542 s.assertCleanupRuns(c) 1543 1544 assertUnitRemoved(c, unit) 1545 // After this there are two cleanups remaining: removedUnit, forceRemoveMachine. 1546 s.assertCleanupCount(c, 2) 1547 } 1548 1549 func (s *CleanupSuite) TestForceDestroyUnitRemovesStorageAttachments(c *gc.C) { 1550 s.assertDoesNotNeedCleanup(c) 1551 1552 ch := s.AddTestingCharm(c, "storage-block") 1553 storage := map[string]state.StorageConstraints{ 1554 "data": makeStorageCons("loop", 1024, 1), 1555 } 1556 application := s.AddTestingApplicationWithStorage(c, "storage-block", ch, storage) 1557 u, err := application.AddUnit(state.AddUnitParams{}) 1558 c.Assert(err, jc.ErrorIsNil) 1559 1560 machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 1561 c.Assert(err, jc.ErrorIsNil) 1562 err = u.AssignToMachine(machine) 1563 c.Assert(err, jc.ErrorIsNil) 1564 err = u.SetAgentStatus(status.StatusInfo{ 1565 Status: status.Idle, 1566 }) 1567 c.Assert(err, jc.ErrorIsNil) 1568 1569 // this tag matches the storage instance created for the unit above. 1570 storageTag := names.NewStorageTag("data/0") 1571 1572 sa, err := s.storageBackend.StorageAttachment(storageTag, u.UnitTag()) 1573 c.Assert(err, jc.ErrorIsNil) 1574 c.Assert(sa.Life(), gc.Equals, state.Alive) 1575 1576 // Ensure there's a volume on the machine hosting the unit so the 1577 // attachment removal can't be short-circuited. 1578 err = machine.SetProvisioned("inst-id", "", "fake_nonce", nil) 1579 c.Assert(err, jc.ErrorIsNil) 1580 volume, err := s.storageBackend.StorageInstanceVolume(storageTag) 1581 c.Assert(err, jc.ErrorIsNil) 1582 err = s.storageBackend.SetVolumeInfo( 1583 volume.VolumeTag(), state.VolumeInfo{VolumeId: "vol-123"}) 1584 c.Assert(err, jc.ErrorIsNil) 1585 err = s.storageBackend.SetVolumeAttachmentInfo( 1586 machine.MachineTag(), 1587 volume.VolumeTag(), 1588 state.VolumeAttachmentInfo{DeviceName: "sdc"}, 1589 ) 1590 c.Assert(err, jc.ErrorIsNil) 1591 1592 // destroy unit and run cleanups 1593 opErrs, err := u.DestroyWithForce(true, dontWait) 1594 c.Assert(opErrs, gc.IsNil) 1595 c.Assert(err, jc.ErrorIsNil) 1596 s.assertCleanupRuns(c) 1597 1598 // After running the cleanup, the attachment should still be 1599 // around because volume was attached. 1600 _, err = s.storageBackend.StorageAttachment(storageTag, u.UnitTag()) 1601 c.Assert(err, jc.ErrorIsNil) 1602 1603 // So now run the forceDestroyedUnit cleanup... 1604 s.assertCleanupRuns(c) 1605 1606 // ...and the storage instance should be gone. 1607 // After running the cleanup, the attachment should still be 1608 // around because volume was attached. 1609 _, err = s.storageBackend.StorageAttachment(storageTag, u.UnitTag()) 1610 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1611 1612 // forceRemoveUnit 1613 s.assertCleanupRuns(c) 1614 1615 assertUnitRemoved(c, u) 1616 // After this there are two cleanups remaining: removedUnit, forceRemoveMachine. 1617 s.assertCleanupCount(c, 2) 1618 } 1619 1620 func (s *CleanupSuite) TestForceDestroyApplicationRemovesUnitsThatAreAlreadyDying(c *gc.C) { 1621 // If you remove an application when it has a unit in error, and 1622 // then you try to force-remove it, it should get cleaned up 1623 // correctly. 1624 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 1625 unit, err := mysql.AddUnit(state.AddUnitParams{}) 1626 c.Assert(err, jc.ErrorIsNil) 1627 preventUnitDestroyRemove(c, unit) 1628 s.assertDoesNotNeedCleanup(c) 1629 1630 err = mysql.Destroy() 1631 c.Assert(err, jc.ErrorIsNil) 1632 err = mysql.Refresh() 1633 c.Assert(err, jc.ErrorIsNil) 1634 1635 // The application is dying and there's a cleanup to make the unit 1636 // dying but it hasn't run yet. 1637 s.assertNeedsCleanup(c) 1638 assertLife(c, mysql, state.Dying) 1639 assertLife(c, unit, state.Alive) 1640 1641 // cleanupUnitsForDyingApplication 1642 s.assertCleanupRuns(c) 1643 assertLife(c, unit, state.Dying) 1644 // dyingUnit 1645 s.assertCleanupRuns(c) 1646 assertLife(c, unit, state.Dying) 1647 1648 // Simulate the unit being in error by never coming back and 1649 // reporting the unit dead. The user eventually gets tired of 1650 // waiting and force-removes the application. 1651 op := mysql.DestroyOperation() 1652 op.Force = true 1653 err = s.State.ApplyOperation(op) 1654 c.Assert(err, jc.ErrorIsNil) 1655 c.Assert(op.Errors, gc.HasLen, 0) 1656 1657 // cleanupUnitsForDyingApplication 1658 s.assertNeedsCleanup(c) 1659 s.assertCleanupRuns(c) 1660 assertLifeIs(c, unit, state.Dying) 1661 1662 // Even though the unit was already dying, because we're forcing 1663 // we rerun the destroy operation so that fallback-scheduling can 1664 // happen. 1665 1666 // dyingUnit 1667 s.assertNeedsCleanup(c) 1668 s.assertCleanupRuns(c) 1669 assertLifeIs(c, unit, state.Dying) 1670 1671 // forceDestroyedUnit 1672 s.assertNeedsCleanup(c) 1673 s.assertCleanupRuns(c) 1674 assertLifeIs(c, unit, state.Dead) 1675 1676 // forceRemoveUnit 1677 s.assertNeedsCleanup(c) 1678 s.assertCleanupRuns(c) 1679 assertUnitRemoved(c, unit) 1680 1681 // application 1682 s.assertNeedsCleanup(c) 1683 s.assertCleanupRuns(c) 1684 assertRemoved(c, mysql) 1685 } 1686 1687 func (s *CleanupSuite) TestForceDestroyRelationIncorrectUnitCount(c *gc.C) { 1688 prr := newProReqRelation(c, &s.ConnSuite, charm.ScopeGlobal) 1689 prr.allEnterScope(c) 1690 1691 rel := prr.rel 1692 state.RemoveUnitRelations(c, rel) 1693 err := rel.Refresh() 1694 c.Assert(err, jc.ErrorIsNil) 1695 c.Assert(rel.UnitCount(), gc.Not(gc.Equals), 0) 1696 1697 opErrs, err := rel.DestroyWithForce(true, dontWait) 1698 c.Assert(err, jc.ErrorIsNil) 1699 c.Assert(opErrs, gc.IsNil) 1700 1701 // dyingRelation schedules cleanupForceDestroyedRelation 1702 s.assertCleanupRuns(c) 1703 err = rel.Refresh() 1704 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1705 1706 s.assertCleanupCount(c, 0) 1707 } 1708 1709 func (s *CleanupSuite) assertCleanupRuns(c *gc.C) { 1710 err := s.State.Cleanup() 1711 c.Assert(err, jc.ErrorIsNil) 1712 } 1713 1714 func (s *CleanupSuite) assertNeedsCleanup(c *gc.C) { 1715 actual, err := s.State.NeedsCleanup() 1716 c.Assert(err, jc.ErrorIsNil) 1717 c.Assert(actual, jc.IsTrue, gc.Commentf("NeedsCleanup returned false, expected true")) 1718 } 1719 1720 func (s *CleanupSuite) assertDoesNotNeedCleanup(c *gc.C) { 1721 state.AssertNoCleanups(c, s.State) 1722 } 1723 1724 // assertCleanupCount is useful because certain cleanups cause other cleanups 1725 // to be queued; it makes more sense to just run cleanup again than to unpick 1726 // object destruction so that we run the cleanups inline while running cleanups. 1727 func (s *CleanupSuite) assertCleanupCount(c *gc.C, count int) { 1728 for i := 0; i < count; i++ { 1729 c.Logf("checking cleanups %d", i) 1730 s.assertNeedsCleanup(c) 1731 s.assertCleanupRuns(c) 1732 } 1733 s.assertDoesNotNeedCleanup(c) 1734 } 1735 1736 // assertCleanupCountDirty is the same as assertCleanupCount, but it 1737 // checks that there are still cleanups to run. 1738 func (s *CleanupSuite) assertCleanupCountDirty(c *gc.C, count int) { 1739 for i := 0; i < count; i++ { 1740 c.Logf("checking cleanups %d", i) 1741 s.assertNeedsCleanup(c) 1742 s.assertCleanupRuns(c) 1743 } 1744 s.assertNeedsCleanup(c) 1745 } 1746 1747 // assertNextCleanup tracks that the next cleanup runs, and logs what cleanup we are expecting. 1748 func (s *CleanupSuite) assertNextCleanup(c *gc.C, message string) { 1749 c.Logf("expect cleanup: %s", message) 1750 s.assertNeedsCleanup(c) 1751 s.assertCleanupRuns(c) 1752 } 1753 1754 type lifeChecker interface { 1755 Refresh() error 1756 Life() state.Life 1757 } 1758 1759 func assertLifeIs(c *gc.C, thing lifeChecker, expected state.Life) { 1760 err := thing.Refresh() 1761 c.Assert(err, jc.ErrorIsNil) 1762 c.Assert(thing.Life(), gc.Equals, expected) 1763 } 1764 1765 func assertUnitRemoved(c *gc.C, thing lifeChecker) { 1766 err := thing.Refresh() 1767 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1768 } 1769 1770 func assertUnitInScope(c *gc.C, unit *state.Unit, rel *state.Relation, expected bool) { 1771 ru, err := rel.Unit(unit) 1772 c.Assert(err, jc.ErrorIsNil) 1773 inscope, err := ru.InScope() 1774 c.Assert(err, jc.ErrorIsNil) 1775 c.Assert(inscope, gc.Equals, expected) 1776 }