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

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"github.com/juju/mgo/v3/bson"
     8  	jc "github.com/juju/testing/checkers"
     9  	gc "gopkg.in/check.v1"
    10  
    11  	"github.com/juju/juju/state"
    12  )
    13  
    14  type LifeSuite struct {
    15  	ConnSuite
    16  	charm *state.Charm
    17  	app   *state.Application
    18  }
    19  
    20  func (s *LifeSuite) SetUpTest(c *gc.C) {
    21  	s.ConnSuite.SetUpTest(c)
    22  	s.charm = s.AddTestingCharm(c, "dummy")
    23  	s.app = s.AddTestingApplication(c, "dummyapp", s.charm)
    24  }
    25  
    26  var _ = gc.Suite(&LifeSuite{})
    27  
    28  var stateChanges = []struct {
    29  	cached, desired    state.Life
    30  	dbinitial, dbfinal state.Life
    31  }{
    32  	{
    33  		state.Alive, state.Dying,
    34  		state.Alive, state.Dying,
    35  	},
    36  	{
    37  		state.Alive, state.Dying,
    38  		state.Dying, state.Dying,
    39  	},
    40  	{
    41  		state.Alive, state.Dying,
    42  		state.Dead, state.Dead,
    43  	},
    44  	{
    45  		state.Alive, state.Dead,
    46  		state.Alive, state.Dead,
    47  	},
    48  	{
    49  		state.Alive, state.Dead,
    50  		state.Dying, state.Dead,
    51  	},
    52  	{
    53  		state.Alive, state.Dead,
    54  		state.Dead, state.Dead,
    55  	},
    56  	{
    57  		state.Dying, state.Dying,
    58  		state.Dying, state.Dying,
    59  	},
    60  	{
    61  		state.Dying, state.Dying,
    62  		state.Dead, state.Dead,
    63  	},
    64  	{
    65  		state.Dying, state.Dead,
    66  		state.Dying, state.Dead,
    67  	},
    68  	{
    69  		state.Dying, state.Dead,
    70  		state.Dead, state.Dead,
    71  	},
    72  	{
    73  		state.Dead, state.Dying,
    74  		state.Dead, state.Dead,
    75  	},
    76  	{
    77  		state.Dead, state.Dead,
    78  		state.Dead, state.Dead,
    79  	},
    80  }
    81  
    82  type lifeFixture interface {
    83  	id() (coll string, id interface{})
    84  	setup(s *LifeSuite, c *gc.C) state.AgentLiving
    85  	isDying(s *LifeSuite, c *gc.C) bool
    86  }
    87  
    88  type unitLife struct {
    89  	unit *state.Unit
    90  	st   *state.State
    91  }
    92  
    93  func (l *unitLife) id() (coll string, id interface{}) {
    94  	return state.UnitsC, state.DocID(l.st, l.unit.Name())
    95  }
    96  
    97  func (l *unitLife) setup(s *LifeSuite, c *gc.C) state.AgentLiving {
    98  	unit, err := s.app.AddUnit(state.AddUnitParams{})
    99  	c.Assert(err, jc.ErrorIsNil)
   100  	preventUnitDestroyRemove(c, unit)
   101  	l.unit = unit
   102  	return l.unit
   103  }
   104  
   105  func (l *unitLife) isDying(s *LifeSuite, c *gc.C) bool {
   106  	col, id := l.id()
   107  	dying, err := state.IsDying(l.st, col, id)
   108  	c.Assert(err, jc.ErrorIsNil)
   109  	return dying
   110  }
   111  
   112  type machineLife struct {
   113  	machine *state.Machine
   114  	st      *state.State
   115  }
   116  
   117  func (l *machineLife) id() (coll string, id interface{}) {
   118  	return state.MachinesC, state.DocID(l.st, l.machine.Id())
   119  }
   120  
   121  func (l *machineLife) setup(s *LifeSuite, c *gc.C) state.AgentLiving {
   122  	var err error
   123  	l.machine, err = s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   124  	c.Assert(err, jc.ErrorIsNil)
   125  	return l.machine
   126  }
   127  
   128  func (l *machineLife) isDying(s *LifeSuite, c *gc.C) bool {
   129  	col, id := l.id()
   130  	dying, err := state.IsDying(l.st, col, id)
   131  	c.Assert(err, jc.ErrorIsNil)
   132  	return dying
   133  }
   134  
   135  func (s *LifeSuite) prepareFixture(living state.Living, lfix lifeFixture, cached, dbinitial state.Life, c *gc.C) {
   136  	collName, id := lfix.id()
   137  	coll := s.MgoSuite.Session.DB("juju").C(collName)
   138  
   139  	err := coll.UpdateId(id, bson.D{{"$set", bson.D{
   140  		{"life", cached},
   141  	}}})
   142  	c.Assert(err, jc.ErrorIsNil)
   143  	err = living.Refresh()
   144  	c.Assert(err, jc.ErrorIsNil)
   145  
   146  	err = coll.UpdateId(id, bson.D{{"$set", bson.D{
   147  		{"life", dbinitial},
   148  	}}})
   149  	c.Assert(err, jc.ErrorIsNil)
   150  }
   151  
   152  func (s *LifeSuite) TestLifecycleStateChanges(c *gc.C) {
   153  	for i, lfix := range []lifeFixture{&unitLife{st: s.State}, &machineLife{st: s.State}} {
   154  		c.Logf("fixture %d", i)
   155  		for j, v := range stateChanges {
   156  			c.Logf("sequence %d", j)
   157  			living := lfix.setup(s, c)
   158  			s.prepareFixture(living, lfix, v.cached, v.dbinitial, c)
   159  			switch v.desired {
   160  			case state.Dying:
   161  				err := living.Destroy()
   162  				c.Assert(err, jc.ErrorIsNil)
   163  
   164  				// If we're already in the dead state, we can't transition, so
   165  				// don't test that permutation.
   166  				if v.dbinitial != state.Dead {
   167  					ok := lfix.isDying(s, c)
   168  					c.Assert(ok, jc.IsTrue)
   169  				}
   170  			case state.Dead:
   171  				err := living.EnsureDead()
   172  				c.Assert(err, jc.ErrorIsNil)
   173  			default:
   174  				panic("desired lifecycle can only be dying or dead")
   175  			}
   176  			err := living.Refresh()
   177  			c.Assert(err, jc.ErrorIsNil)
   178  			c.Assert(living.Life(), gc.Equals, v.dbfinal)
   179  			err = living.EnsureDead()
   180  			c.Assert(err, jc.ErrorIsNil)
   181  			err = living.Remove()
   182  			c.Assert(err, jc.ErrorIsNil)
   183  		}
   184  	}
   185  }
   186  
   187  func (s *LifeSuite) TestLifeString(c *gc.C) {
   188  	var tests = []struct {
   189  		life state.Life
   190  		want string
   191  	}{
   192  		{state.Alive, "alive"},
   193  		{state.Dying, "dying"},
   194  		{state.Dead, "dead"},
   195  		{42, "unknown"},
   196  	}
   197  	for _, test := range tests {
   198  		got := test.life.String()
   199  		c.Assert(got, gc.Equals, test.want)
   200  	}
   201  }
   202  
   203  const (
   204  	notAliveErr = ".*: .* is not found or not alive"
   205  	deadErr     = ".*: not found or dead"
   206  	noErr       = ""
   207  )
   208  
   209  type lifer interface {
   210  	EnsureDead() error
   211  	Destroy() error
   212  	Life() state.Life
   213  }
   214  
   215  func runLifeChecks(c *gc.C, obj lifer, expectErr string, checks []func() error) {
   216  	for i, check := range checks {
   217  		c.Logf("check %d when %v", i, obj.Life())
   218  		err := check()
   219  		if expectErr == noErr {
   220  			c.Assert(err, jc.ErrorIsNil)
   221  		} else {
   222  			c.Assert(err, gc.ErrorMatches, expectErr)
   223  		}
   224  	}
   225  }
   226  
   227  // testWhenDying sets obj to Dying and Dead in turn, and asserts
   228  // that the errors from the given checks match aliveErr, dyingErr and deadErr
   229  // in each respective life state.
   230  func testWhenDying(c *gc.C, obj lifer, dyingErr, deadErr string, checks ...func() error) {
   231  	c.Logf("checking life of %v (%T)", obj, obj)
   232  	err := obj.Destroy()
   233  	c.Assert(err, jc.ErrorIsNil)
   234  	runLifeChecks(c, obj, dyingErr, checks)
   235  	err = obj.EnsureDead()
   236  	c.Assert(err, jc.ErrorIsNil)
   237  	runLifeChecks(c, obj, deadErr, checks)
   238  }