github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/names"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  	"gopkg.in/juju/charm.v6-unstable"
    14  
    15  	"github.com/juju/juju/instance"
    16  	"github.com/juju/juju/state"
    17  	"github.com/juju/juju/storage/provider"
    18  	"github.com/juju/juju/storage/provider/registry"
    19  	"github.com/juju/juju/testing/factory"
    20  )
    21  
    22  type CleanupSuite struct {
    23  	ConnSuite
    24  }
    25  
    26  var _ = gc.Suite(&CleanupSuite{})
    27  
    28  func (s *CleanupSuite) SetUpSuite(c *gc.C) {
    29  	s.ConnSuite.SetUpSuite(c)
    30  	registry.RegisterEnvironStorageProviders("someprovider", provider.LoopProviderType)
    31  }
    32  
    33  func (s *CleanupSuite) SetUpTest(c *gc.C) {
    34  	s.ConnSuite.SetUpTest(c)
    35  	s.assertDoesNotNeedCleanup(c)
    36  }
    37  
    38  func (s *CleanupSuite) TestCleanupDyingServiceUnits(c *gc.C) {
    39  	// Create a service with some units.
    40  	mysql := s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql"))
    41  	units := make([]*state.Unit, 3)
    42  	for i := range units {
    43  		unit, err := mysql.AddUnit()
    44  		c.Assert(err, jc.ErrorIsNil)
    45  		units[i] = unit
    46  	}
    47  	preventUnitDestroyRemove(c, units[0])
    48  	s.assertDoesNotNeedCleanup(c)
    49  
    50  	// Destroy the service and check the units are unaffected, but a cleanup
    51  	// has been scheduled.
    52  	err := mysql.Destroy()
    53  	c.Assert(err, jc.ErrorIsNil)
    54  	for _, unit := range units {
    55  		err := unit.Refresh()
    56  		c.Assert(err, jc.ErrorIsNil)
    57  	}
    58  	s.assertNeedsCleanup(c)
    59  
    60  	// Run the cleanup, and check that units are all destroyed as appropriate.
    61  	s.assertCleanupRuns(c)
    62  	err = units[0].Refresh()
    63  	c.Assert(err, jc.ErrorIsNil)
    64  	c.Assert(units[0].Life(), gc.Equals, state.Dying)
    65  	err = units[1].Refresh()
    66  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
    67  	err = units[2].Refresh()
    68  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
    69  
    70  	// Run a final cleanup to clear the cleanup scheduled for the unit that
    71  	// became dying.
    72  	s.assertCleanupCount(c, 1)
    73  }
    74  
    75  func (s *CleanupSuite) TestCleanupControllerModels(c *gc.C) {
    76  	s.assertDoesNotNeedCleanup(c)
    77  
    78  	// Create a non-empty hosted model.
    79  	otherSt := s.Factory.MakeModel(c, nil)
    80  	defer otherSt.Close()
    81  	factory.NewFactory(otherSt).MakeService(c, nil)
    82  	otherEnv, err := otherSt.Model()
    83  	c.Assert(err, jc.ErrorIsNil)
    84  
    85  	s.assertDoesNotNeedCleanup(c)
    86  
    87  	// Destroy the controller and check the model is unaffected, but a
    88  	// cleanup for the model and services has been scheduled.
    89  	controllerEnv, err := s.State.Model()
    90  	c.Assert(err, jc.ErrorIsNil)
    91  	err = controllerEnv.DestroyIncludingHosted()
    92  	c.Assert(err, jc.ErrorIsNil)
    93  
    94  	// Two cleanups should be scheduled. One to destroy the hosted
    95  	// models, the other to destroy the controller model's
    96  	// services.
    97  	s.assertCleanupCount(c, 1)
    98  	err = otherEnv.Refresh()
    99  	c.Assert(err, jc.ErrorIsNil)
   100  	c.Assert(otherEnv.Life(), gc.Equals, state.Dying)
   101  
   102  	s.assertDoesNotNeedCleanup(c)
   103  }
   104  
   105  func (s *CleanupSuite) TestCleanupModelMachines(c *gc.C) {
   106  	// Create a state and hosted machine.
   107  	stateMachine, err := s.State.AddMachine("quantal", state.JobManageModel)
   108  	c.Assert(err, jc.ErrorIsNil)
   109  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
   110  	c.Assert(err, jc.ErrorIsNil)
   111  
   112  	// Create a relation with a unit in scope and assigned to the hosted machine.
   113  	pr := NewPeerRelation(c, s.State, s.Owner)
   114  	err = pr.u0.AssignToMachine(machine)
   115  	c.Assert(err, jc.ErrorIsNil)
   116  	err = pr.ru0.EnterScope(nil)
   117  	c.Assert(err, jc.ErrorIsNil)
   118  	s.assertDoesNotNeedCleanup(c)
   119  
   120  	// Destroy model, check cleanup queued.
   121  	env, err := s.State.Model()
   122  	c.Assert(err, jc.ErrorIsNil)
   123  	err = env.Destroy()
   124  	c.Assert(err, jc.ErrorIsNil)
   125  	s.assertNeedsCleanup(c)
   126  
   127  	// Clean up, and check that the unit has been removed...
   128  	s.assertCleanupCount(c, 3)
   129  	assertRemoved(c, pr.u0)
   130  
   131  	// ...and the unit has departed relation scope...
   132  	assertNotJoined(c, pr.ru0)
   133  
   134  	// ...but that the machine remains, and is Dead, ready for removal by the
   135  	// provisioner.
   136  	assertLife(c, machine, state.Dead)
   137  	assertLife(c, stateMachine, state.Alive)
   138  }
   139  
   140  func (s *CleanupSuite) TestCleanupModelServices(c *gc.C) {
   141  	s.assertDoesNotNeedCleanup(c)
   142  
   143  	// Create a service with some units.
   144  	mysql := s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql"))
   145  	units := make([]*state.Unit, 3)
   146  	for i := range units {
   147  		unit, err := mysql.AddUnit()
   148  		c.Assert(err, jc.ErrorIsNil)
   149  		units[i] = unit
   150  	}
   151  	s.assertDoesNotNeedCleanup(c)
   152  
   153  	// Destroy the model and check the service and units are
   154  	// unaffected, but a cleanup for the service has been scheduled.
   155  	env, err := s.State.Model()
   156  	c.Assert(err, jc.ErrorIsNil)
   157  	err = env.Destroy()
   158  	c.Assert(err, jc.ErrorIsNil)
   159  	s.assertNeedsCleanup(c)
   160  	s.assertCleanupRuns(c)
   161  	err = mysql.Refresh()
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	c.Assert(mysql.Life(), gc.Equals, state.Dying)
   164  	for _, unit := range units {
   165  		err = unit.Refresh()
   166  		c.Assert(err, jc.ErrorIsNil)
   167  		c.Assert(unit.Life(), gc.Equals, state.Alive)
   168  	}
   169  
   170  	// The first cleanup Destroys the service, which
   171  	// schedules another cleanup to destroy the units,
   172  	// then we need another pass for the actions cleanup
   173  	// which is queued on the next pass
   174  	s.assertCleanupCount(c, 2)
   175  	for _, unit := range units {
   176  		err = unit.Refresh()
   177  		c.Assert(err, jc.Satisfies, errors.IsNotFound)
   178  	}
   179  
   180  	// Now we should have all the cleanups done
   181  	s.assertDoesNotNeedCleanup(c)
   182  }
   183  
   184  func (s *CleanupSuite) TestCleanupRelationSettings(c *gc.C) {
   185  	// Create a relation with a unit in scope.
   186  	pr := NewPeerRelation(c, s.State, s.Owner)
   187  	rel := pr.ru0.Relation()
   188  	err := pr.ru0.EnterScope(map[string]interface{}{"some": "settings"})
   189  	c.Assert(err, jc.ErrorIsNil)
   190  	s.assertDoesNotNeedCleanup(c)
   191  
   192  	// Destroy the service, check the relation's still around.
   193  	err = pr.svc.Destroy()
   194  	c.Assert(err, jc.ErrorIsNil)
   195  	s.assertCleanupCount(c, 2)
   196  	err = rel.Refresh()
   197  	c.Assert(err, jc.ErrorIsNil)
   198  	c.Assert(rel.Life(), gc.Equals, state.Dying)
   199  
   200  	// The unit leaves scope, triggering relation removal.
   201  	err = pr.ru0.LeaveScope()
   202  	c.Assert(err, jc.ErrorIsNil)
   203  	s.assertNeedsCleanup(c)
   204  
   205  	// Settings are not destroyed yet...
   206  	settings, err := pr.ru1.ReadSettings("riak/0")
   207  	c.Assert(err, jc.ErrorIsNil)
   208  	c.Assert(settings, gc.DeepEquals, map[string]interface{}{"some": "settings"})
   209  
   210  	// ...but they are on cleanup.
   211  	s.assertCleanupCount(c, 1)
   212  	_, err = pr.ru1.ReadSettings("riak/0")
   213  	c.Assert(err, gc.ErrorMatches, `cannot read settings for unit "riak/0" in relation "riak:ring": settings not found`)
   214  }
   215  
   216  func (s *CleanupSuite) TestForceDestroyMachineErrors(c *gc.C) {
   217  	manager, err := s.State.AddMachine("quantal", state.JobManageModel)
   218  	c.Assert(err, jc.ErrorIsNil)
   219  	s.assertDoesNotNeedCleanup(c)
   220  	err = manager.ForceDestroy()
   221  	expect := fmt.Sprintf("machine is required by the model")
   222  	c.Assert(err, gc.ErrorMatches, expect)
   223  	s.assertDoesNotNeedCleanup(c)
   224  	assertLife(c, manager, state.Alive)
   225  }
   226  
   227  func (s *CleanupSuite) TestCleanupForceDestroyedMachineUnit(c *gc.C) {
   228  	// Create a machine.
   229  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
   230  	c.Assert(err, jc.ErrorIsNil)
   231  
   232  	// Create a relation with a unit in scope and assigned to the machine.
   233  	pr := NewPeerRelation(c, s.State, s.Owner)
   234  	err = pr.u0.AssignToMachine(machine)
   235  	c.Assert(err, jc.ErrorIsNil)
   236  	err = pr.ru0.EnterScope(nil)
   237  	c.Assert(err, jc.ErrorIsNil)
   238  	s.assertDoesNotNeedCleanup(c)
   239  
   240  	// Force machine destruction, check cleanup queued.
   241  	err = machine.ForceDestroy()
   242  	c.Assert(err, jc.ErrorIsNil)
   243  	s.assertNeedsCleanup(c)
   244  
   245  	// Clean up, and check that the unit has been removed...
   246  	s.assertCleanupCount(c, 2)
   247  	assertRemoved(c, pr.u0)
   248  
   249  	// ...and the unit has departed relation scope...
   250  	assertNotJoined(c, pr.ru0)
   251  
   252  	// ...but that the machine remains, and is Dead, ready for removal by the
   253  	// provisioner.
   254  	assertLife(c, machine, state.Dead)
   255  }
   256  
   257  func (s *CleanupSuite) TestCleanupForceDestroyedMachineWithContainer(c *gc.C) {
   258  	// Create a machine with a container.
   259  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
   260  	c.Assert(err, jc.ErrorIsNil)
   261  	container, err := s.State.AddMachineInsideMachine(state.MachineTemplate{
   262  		Series: "quantal",
   263  		Jobs:   []state.MachineJob{state.JobHostUnits},
   264  	}, machine.Id(), instance.LXC)
   265  	c.Assert(err, jc.ErrorIsNil)
   266  
   267  	// Create active units (in relation scope, with subordinates).
   268  	prr := NewProReqRelation(c, &s.ConnSuite, charm.ScopeContainer)
   269  	err = prr.pru0.EnterScope(nil)
   270  	c.Assert(err, jc.ErrorIsNil)
   271  	err = prr.pru1.EnterScope(nil)
   272  	c.Assert(err, jc.ErrorIsNil)
   273  	err = prr.rru0.EnterScope(nil)
   274  	c.Assert(err, jc.ErrorIsNil)
   275  	err = prr.rru1.EnterScope(nil)
   276  	c.Assert(err, jc.ErrorIsNil)
   277  
   278  	// Assign the various units to machines.
   279  	err = prr.pu0.AssignToMachine(machine)
   280  	c.Assert(err, jc.ErrorIsNil)
   281  	err = prr.pu1.AssignToMachine(container)
   282  	c.Assert(err, jc.ErrorIsNil)
   283  	s.assertDoesNotNeedCleanup(c)
   284  
   285  	// Force removal of the top-level machine.
   286  	err = machine.ForceDestroy()
   287  	c.Assert(err, jc.ErrorIsNil)
   288  	s.assertNeedsCleanup(c)
   289  
   290  	// And do it again, just to check that the second cleanup doc for the same
   291  	// machine doesn't cause problems down the line.
   292  	err = machine.ForceDestroy()
   293  	c.Assert(err, jc.ErrorIsNil)
   294  	s.assertNeedsCleanup(c)
   295  
   296  	// Clean up, and check that the container has been removed...
   297  	s.assertCleanupCount(c, 2)
   298  	err = container.Refresh()
   299  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   300  
   301  	// ...and so have all the units...
   302  	assertRemoved(c, prr.pu0)
   303  	assertRemoved(c, prr.pu1)
   304  	assertRemoved(c, prr.ru0)
   305  	assertRemoved(c, prr.ru1)
   306  
   307  	// ...and none of the units have left relation scopes occupied...
   308  	assertNotInScope(c, prr.pru0)
   309  	assertNotInScope(c, prr.pru1)
   310  	assertNotInScope(c, prr.rru0)
   311  	assertNotInScope(c, prr.rru1)
   312  
   313  	// ...but that the machine remains, and is Dead, ready for removal by the
   314  	// provisioner.
   315  	assertLife(c, machine, state.Dead)
   316  }
   317  
   318  func (s *CleanupSuite) TestCleanupDyingUnit(c *gc.C) {
   319  	// Create active unit, in a relation.
   320  	prr := NewProReqRelation(c, &s.ConnSuite, charm.ScopeGlobal)
   321  	err := prr.pru0.EnterScope(nil)
   322  	c.Assert(err, jc.ErrorIsNil)
   323  
   324  	// Destroy provider unit 0; check it's Dying, and a cleanup has been scheduled.
   325  	err = prr.pu0.Destroy()
   326  	c.Assert(err, jc.ErrorIsNil)
   327  	err = prr.pu0.Refresh()
   328  	c.Assert(err, jc.ErrorIsNil)
   329  	assertLife(c, prr.pu0, state.Dying)
   330  	s.assertNeedsCleanup(c)
   331  
   332  	// Check it's reported in scope until cleaned up.
   333  	assertJoined(c, prr.pru0)
   334  	s.assertCleanupCount(c, 1)
   335  	assertInScope(c, prr.pru0)
   336  	assertNotJoined(c, prr.pru0)
   337  
   338  	// Destroy the relation, and check it sticks around...
   339  	err = prr.rel.Destroy()
   340  	c.Assert(err, jc.ErrorIsNil)
   341  	assertLife(c, prr.rel, state.Dying)
   342  
   343  	// ...until the unit is removed, and really leaves scope.
   344  	err = prr.pu0.EnsureDead()
   345  	c.Assert(err, jc.ErrorIsNil)
   346  	err = prr.pu0.Remove()
   347  	c.Assert(err, jc.ErrorIsNil)
   348  	assertNotInScope(c, prr.pru0)
   349  	assertRemoved(c, prr.rel)
   350  }
   351  
   352  func (s *CleanupSuite) TestCleanupDyingUnitAlreadyRemoved(c *gc.C) {
   353  	// Create active unit, in a relation.
   354  	prr := NewProReqRelation(c, &s.ConnSuite, charm.ScopeGlobal)
   355  	err := prr.pru0.EnterScope(nil)
   356  	c.Assert(err, jc.ErrorIsNil)
   357  
   358  	// Destroy provider unit 0; check it's Dying, and a cleanup has been scheduled.
   359  	err = prr.pu0.Destroy()
   360  	c.Assert(err, jc.ErrorIsNil)
   361  	err = prr.pu0.Refresh()
   362  	c.Assert(err, jc.ErrorIsNil)
   363  	assertLife(c, prr.pu0, state.Dying)
   364  	s.assertNeedsCleanup(c)
   365  
   366  	// Remove the unit, and the relation.
   367  	err = prr.pu0.EnsureDead()
   368  	c.Assert(err, jc.ErrorIsNil)
   369  	err = prr.pu0.Remove()
   370  	c.Assert(err, jc.ErrorIsNil)
   371  	err = prr.rel.Destroy()
   372  	c.Assert(err, jc.ErrorIsNil)
   373  	assertRemoved(c, prr.rel)
   374  
   375  	// Check the cleanup still runs happily.
   376  	s.assertCleanupCount(c, 1)
   377  }
   378  
   379  func (s *CleanupSuite) TestCleanupActions(c *gc.C) {
   380  	// Create a service with a unit.
   381  	dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy"))
   382  	unit, err := dummy.AddUnit()
   383  	c.Assert(err, jc.ErrorIsNil)
   384  
   385  	// check no cleanups
   386  	s.assertDoesNotNeedCleanup(c)
   387  
   388  	// Add a couple actions to the unit
   389  	_, err = unit.AddAction("snapshot", nil)
   390  	c.Assert(err, jc.ErrorIsNil)
   391  	_, err = unit.AddAction("snapshot", nil)
   392  	c.Assert(err, jc.ErrorIsNil)
   393  
   394  	// make sure unit still has actions
   395  	actions, err := unit.PendingActions()
   396  	c.Assert(err, jc.ErrorIsNil)
   397  	c.Assert(len(actions), gc.Equals, 2)
   398  
   399  	// destroy unit and run cleanups
   400  	err = dummy.Destroy()
   401  	c.Assert(err, jc.ErrorIsNil)
   402  	s.assertCleanupRuns(c)
   403  
   404  	// make sure unit still has actions, after first cleanup pass
   405  	actions, err = unit.PendingActions()
   406  	c.Assert(err, jc.ErrorIsNil)
   407  	c.Assert(len(actions), gc.Equals, 2)
   408  
   409  	// second cleanup pass
   410  	s.assertCleanupRuns(c)
   411  
   412  	// make sure unit has no actions, after second cleanup pass
   413  	actions, err = unit.PendingActions()
   414  	c.Assert(err, jc.ErrorIsNil)
   415  	c.Assert(len(actions), gc.Equals, 0)
   416  
   417  	// check no cleanups
   418  	s.assertDoesNotNeedCleanup(c)
   419  }
   420  
   421  func (s *CleanupSuite) TestCleanupStorageAttachments(c *gc.C) {
   422  	s.assertDoesNotNeedCleanup(c)
   423  
   424  	ch := s.AddTestingCharm(c, "storage-block")
   425  	storage := map[string]state.StorageConstraints{
   426  		"data": makeStorageCons("loop", 1024, 1),
   427  	}
   428  	service := s.AddTestingServiceWithStorage(c, "storage-block", ch, storage)
   429  	u, err := service.AddUnit()
   430  	c.Assert(err, jc.ErrorIsNil)
   431  
   432  	// check no cleanups
   433  	s.assertDoesNotNeedCleanup(c)
   434  
   435  	// this tag matches the storage instance created for the unit above.
   436  	storageTag := names.NewStorageTag("data/0")
   437  
   438  	sa, err := s.State.StorageAttachment(storageTag, u.UnitTag())
   439  	c.Assert(err, jc.ErrorIsNil)
   440  	c.Assert(sa.Life(), gc.Equals, state.Alive)
   441  
   442  	// destroy unit and run cleanups; the attachment should be marked dying
   443  	err = u.Destroy()
   444  	c.Assert(err, jc.ErrorIsNil)
   445  	s.assertCleanupRuns(c)
   446  
   447  	// After running the cleanup, the attachment should be dying.
   448  	sa, err = s.State.StorageAttachment(storageTag, u.UnitTag())
   449  	c.Assert(err, jc.ErrorIsNil)
   450  	c.Assert(sa.Life(), gc.Equals, state.Dying)
   451  
   452  	// check no cleanups
   453  	s.assertDoesNotNeedCleanup(c)
   454  }
   455  
   456  func (s *CleanupSuite) TestCleanupStorageInstances(c *gc.C) {
   457  	ch := s.AddTestingCharm(c, "storage-block")
   458  	registry.RegisterEnvironStorageProviders("someprovider", provider.LoopProviderType)
   459  	storage := map[string]state.StorageConstraints{
   460  		"data": makeStorageCons("loop", 1024, 1),
   461  	}
   462  	service := s.AddTestingServiceWithStorage(c, "storage-block", ch, storage)
   463  	u, err := service.AddUnit()
   464  	c.Assert(err, jc.ErrorIsNil)
   465  
   466  	// check no cleanups
   467  	s.assertDoesNotNeedCleanup(c)
   468  
   469  	// this tag matches the storage instance created for the unit above.
   470  	storageTag := names.NewStorageTag("data/0")
   471  
   472  	si, err := s.State.StorageInstance(storageTag)
   473  	c.Assert(err, jc.ErrorIsNil)
   474  	c.Assert(si.Life(), gc.Equals, state.Alive)
   475  
   476  	// destroy storage instance and run cleanups
   477  	err = s.State.DestroyStorageInstance(storageTag)
   478  	c.Assert(err, jc.ErrorIsNil)
   479  	si, err = s.State.StorageInstance(storageTag)
   480  	c.Assert(err, jc.ErrorIsNil)
   481  	c.Assert(si.Life(), gc.Equals, state.Dying)
   482  	sa, err := s.State.UnitStorageAttachments(u.UnitTag())
   483  	c.Assert(err, jc.ErrorIsNil)
   484  	c.Assert(sa, gc.HasLen, 1)
   485  	c.Assert(sa[0].Life(), gc.Equals, state.Alive)
   486  	s.assertCleanupRuns(c)
   487  
   488  	// After running the cleanup, the attachment should be dying.
   489  	sa, err = s.State.UnitStorageAttachments(u.UnitTag())
   490  	c.Assert(err, jc.ErrorIsNil)
   491  	c.Assert(sa, gc.HasLen, 1)
   492  	c.Assert(sa[0].Life(), gc.Equals, state.Dying)
   493  
   494  	// check no cleanups
   495  	s.assertDoesNotNeedCleanup(c)
   496  }
   497  
   498  func (s *CleanupSuite) TestCleanupMachineStorage(c *gc.C) {
   499  	ch := s.AddTestingCharm(c, "storage-filesystem")
   500  	storage := map[string]state.StorageConstraints{
   501  		"data": makeStorageCons("loop", 1024, 1),
   502  	}
   503  	service := s.AddTestingServiceWithStorage(c, "storage-filesystem", ch, storage)
   504  	unit, err := service.AddUnit()
   505  	c.Assert(err, jc.ErrorIsNil)
   506  	err = s.State.AssignUnit(unit, state.AssignCleanEmpty)
   507  	c.Assert(err, jc.ErrorIsNil)
   508  	machineId, err := unit.AssignedMachineId()
   509  	c.Assert(err, jc.ErrorIsNil)
   510  	machine, err := s.State.Machine(machineId)
   511  	c.Assert(err, jc.ErrorIsNil)
   512  
   513  	// Destroy the service, so we can destroy the machine.
   514  	err = unit.Destroy()
   515  	s.assertCleanupRuns(c)
   516  	err = service.Destroy()
   517  	c.Assert(err, jc.ErrorIsNil)
   518  	s.assertCleanupRuns(c)
   519  
   520  	// check no cleanups
   521  	s.assertDoesNotNeedCleanup(c)
   522  
   523  	// destroy machine and run cleanups; the filesystem attachment
   524  	// should be marked dying, but the volume attachment should not
   525  	// since it contains the filesystem.
   526  	err = machine.Destroy()
   527  	c.Assert(err, jc.ErrorIsNil)
   528  	s.assertCleanupRuns(c)
   529  
   530  	fas, err := s.State.MachineFilesystemAttachments(machine.MachineTag())
   531  	c.Assert(err, jc.ErrorIsNil)
   532  	c.Assert(fas, gc.HasLen, 1)
   533  	c.Assert(fas[0].Life(), gc.Equals, state.Dying)
   534  	vas, err := s.State.MachineVolumeAttachments(machine.MachineTag())
   535  	c.Assert(err, jc.ErrorIsNil)
   536  	c.Assert(vas, gc.HasLen, 1)
   537  	c.Assert(vas[0].Life(), gc.Equals, state.Alive)
   538  
   539  	// check no cleanups
   540  	s.assertDoesNotNeedCleanup(c)
   541  }
   542  
   543  func (s *CleanupSuite) TestCleanupVolumeAttachments(c *gc.C) {
   544  	_, err := s.State.AddOneMachine(state.MachineTemplate{
   545  		Series: "quantal",
   546  		Jobs:   []state.MachineJob{state.JobHostUnits},
   547  		Volumes: []state.MachineVolumeParams{{
   548  			Volume: state.VolumeParams{Pool: "loop", Size: 1024},
   549  		}},
   550  	})
   551  	c.Assert(err, jc.ErrorIsNil)
   552  	s.assertDoesNotNeedCleanup(c)
   553  
   554  	err = s.State.DestroyVolume(names.NewVolumeTag("0/0"))
   555  	c.Assert(err, jc.ErrorIsNil)
   556  	s.assertCleanupRuns(c)
   557  
   558  	attachment, err := s.State.VolumeAttachment(names.NewMachineTag("0"), names.NewVolumeTag("0/0"))
   559  	c.Assert(err, jc.ErrorIsNil)
   560  	c.Assert(attachment.Life(), gc.Equals, state.Dying)
   561  }
   562  
   563  func (s *CleanupSuite) TestCleanupFilesystemAttachments(c *gc.C) {
   564  	_, err := s.State.AddOneMachine(state.MachineTemplate{
   565  		Series: "quantal",
   566  		Jobs:   []state.MachineJob{state.JobHostUnits},
   567  		Filesystems: []state.MachineFilesystemParams{{
   568  			Filesystem: state.FilesystemParams{Pool: "rootfs", Size: 1024},
   569  		}},
   570  	})
   571  	c.Assert(err, jc.ErrorIsNil)
   572  	s.assertDoesNotNeedCleanup(c)
   573  
   574  	err = s.State.DestroyFilesystem(names.NewFilesystemTag("0/0"))
   575  	c.Assert(err, jc.ErrorIsNil)
   576  	s.assertCleanupRuns(c)
   577  
   578  	attachment, err := s.State.FilesystemAttachment(names.NewMachineTag("0"), names.NewFilesystemTag("0/0"))
   579  	c.Assert(err, jc.ErrorIsNil)
   580  	c.Assert(attachment.Life(), gc.Equals, state.Dying)
   581  }
   582  
   583  func (s *CleanupSuite) TestNothingToCleanup(c *gc.C) {
   584  	s.assertDoesNotNeedCleanup(c)
   585  	s.assertCleanupRuns(c)
   586  	s.assertDoesNotNeedCleanup(c)
   587  }
   588  
   589  func (s *CleanupSuite) assertCleanupRuns(c *gc.C) {
   590  	err := s.State.Cleanup()
   591  	c.Assert(err, jc.ErrorIsNil)
   592  }
   593  
   594  func (s *CleanupSuite) assertNeedsCleanup(c *gc.C) {
   595  	actual, err := s.State.NeedsCleanup()
   596  	c.Assert(err, jc.ErrorIsNil)
   597  	c.Assert(actual, jc.IsTrue)
   598  }
   599  
   600  func (s *CleanupSuite) assertDoesNotNeedCleanup(c *gc.C) {
   601  	actual, err := s.State.NeedsCleanup()
   602  	c.Assert(err, jc.ErrorIsNil)
   603  	c.Assert(actual, jc.IsFalse)
   604  }
   605  
   606  // assertCleanupCount is useful because certain cleanups cause other cleanups
   607  // to be queued; it makes more sense to just run cleanup again than to unpick
   608  // object destruction so that we run the cleanups inline while running cleanups.
   609  func (s *CleanupSuite) assertCleanupCount(c *gc.C, count int) {
   610  	for i := 0; i < count; i++ {
   611  		c.Logf("checking cleanups %d", i)
   612  		s.assertNeedsCleanup(c)
   613  		s.assertCleanupRuns(c)
   614  	}
   615  	s.assertDoesNotNeedCleanup(c)
   616  }