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 }