gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/healthstate/healthstate_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package healthstate_test 21 22 import ( 23 "io/ioutil" 24 "os" 25 "path/filepath" 26 "testing" 27 "time" 28 29 "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/dirs" 32 "github.com/snapcore/snapd/overlord" 33 "github.com/snapcore/snapd/overlord/healthstate" 34 "github.com/snapcore/snapd/overlord/hookstate" 35 "github.com/snapcore/snapd/overlord/snapstate" 36 "github.com/snapcore/snapd/overlord/state" 37 "github.com/snapcore/snapd/snap" 38 "github.com/snapcore/snapd/snap/snaptest" 39 "github.com/snapcore/snapd/store/storetest" 40 "github.com/snapcore/snapd/testutil" 41 ) 42 43 func TestHealthState(t *testing.T) { check.TestingT(t) } 44 45 type healthSuite struct { 46 testutil.BaseTest 47 o *overlord.Overlord 48 se *overlord.StateEngine 49 state *state.State 50 hookMgr *hookstate.HookManager 51 info *snap.Info 52 } 53 54 var _ = check.Suite(&healthSuite{}) 55 56 func (s *healthSuite) SetUpTest(c *check.C) { 57 s.BaseTest.SetUpTest(c) 58 s.AddCleanup(healthstate.MockCheckTimeout(time.Second)) 59 dirs.SetRootDir(c.MkDir()) 60 61 s.o = overlord.Mock() 62 s.state = s.o.State() 63 64 var err error 65 s.hookMgr, err = hookstate.Manager(s.state, s.o.TaskRunner()) 66 c.Assert(err, check.IsNil) 67 s.se = s.o.StateEngine() 68 s.o.AddManager(s.hookMgr) 69 s.o.AddManager(s.o.TaskRunner()) 70 71 healthstate.Init(s.hookMgr) 72 73 c.Assert(s.o.StartUp(), check.IsNil) 74 75 s.state.Lock() 76 defer s.state.Unlock() 77 78 snapstate.ReplaceStore(s.state, storetest.Store{}) 79 sideInfo := &snap.SideInfo{RealName: "test-snap", Revision: snap.R(42)} 80 snapstate.Set(s.state, "test-snap", &snapstate.SnapState{ 81 Sequence: []*snap.SideInfo{sideInfo}, 82 Current: snap.R(42), 83 Active: true, 84 SnapType: "app", 85 }) 86 s.info = snaptest.MockSnapCurrent(c, "{name: test-snap, version: v1}", sideInfo) 87 } 88 89 func (s *healthSuite) TearDownTest(c *check.C) { 90 s.hookMgr.StopHooks() 91 s.se.Stop() 92 s.BaseTest.TearDownTest(c) 93 } 94 95 type healthHookTestCondition int 96 97 const ( 98 noHook = iota 99 badHook 100 goodHook 101 ) 102 103 func (s *healthSuite) TestHealthNoHook(c *check.C) { 104 s.testHealth(c, noHook) 105 } 106 107 func (s *healthSuite) TestHealthFailingHook(c *check.C) { 108 s.testHealth(c, badHook) 109 } 110 111 func (s *healthSuite) TestHealth(c *check.C) { 112 s.testHealth(c, goodHook) 113 } 114 115 func (s *healthSuite) testHealth(c *check.C, cond healthHookTestCondition) { 116 var cmd *testutil.MockCmd 117 switch cond { 118 case badHook: 119 cmd = testutil.MockCommand(c, "snap", "exit 1") 120 default: 121 cmd = testutil.MockCommand(c, "snap", "exit 0") 122 } 123 124 if cond != noHook { 125 hookFn := filepath.Join(s.info.MountDir(), "meta", "hooks", "check-health") 126 c.Assert(os.MkdirAll(filepath.Dir(hookFn), 0755), check.IsNil) 127 // the hook won't actually be called, but needs to exist 128 c.Assert(ioutil.WriteFile(hookFn, nil, 0755), check.IsNil) 129 } 130 131 s.state.Lock() 132 task := healthstate.Hook(s.state, "test-snap", snap.R(42)) 133 change := s.state.NewChange("kind", "summary") 134 change.AddTask(task) 135 s.state.Unlock() 136 137 c.Assert(task.Kind(), check.Equals, "run-hook") 138 var hooksup hookstate.HookSetup 139 140 s.state.Lock() 141 err := task.Get("hook-setup", &hooksup) 142 s.state.Unlock() 143 c.Check(err, check.IsNil) 144 145 c.Check(hooksup, check.DeepEquals, hookstate.HookSetup{ 146 Snap: "test-snap", 147 Hook: "check-health", 148 Revision: snap.R(42), 149 Optional: true, 150 Timeout: time.Second, 151 IgnoreError: false, 152 TrackError: false, 153 }) 154 155 t0 := time.Now() 156 s.se.Ensure() 157 s.se.Wait() 158 tf := time.Now() 159 var healths map[string]*healthstate.HealthState 160 var health *healthstate.HealthState 161 var err2 error 162 s.state.Lock() 163 status := change.Status() 164 err = s.state.Get("health", &healths) 165 health, err2 = healthstate.Get(s.state, "test-snap") 166 s.state.Unlock() 167 c.Assert(err2, check.IsNil) 168 169 switch cond { 170 case badHook: 171 c.Assert(status, check.Equals, state.ErrorStatus) 172 default: 173 c.Assert(status, check.Equals, state.DoneStatus) 174 } 175 if cond != noHook { 176 c.Assert(err, check.IsNil) 177 c.Assert(healths, check.HasLen, 1) 178 c.Assert(healths["test-snap"], check.NotNil) 179 c.Check(health, check.DeepEquals, healths["test-snap"]) 180 c.Check(health.Revision, check.Equals, snap.R(42)) 181 c.Check(health.Status, check.Equals, healthstate.UnknownStatus) 182 if cond == badHook { 183 c.Check(health.Message, check.Equals, "hook failed") 184 c.Check(health.Code, check.Equals, "snapd-hook-failed") 185 } else { 186 c.Check(health.Message, check.Equals, "hook did not call set-health") 187 c.Check(health.Code, check.Equals, "snapd-hook-no-health-set") 188 } 189 com := check.Commentf("%s ⩼ %s ⩼ %s", t0.Format(time.StampNano), health.Timestamp.Format(time.StampNano), tf.Format(time.StampNano)) 190 c.Check(health.Timestamp.After(t0) && health.Timestamp.Before(tf), check.Equals, true, com) 191 c.Check(cmd.Calls(), check.DeepEquals, [][]string{{"snap", "run", "--hook", "check-health", "-r", "42", "test-snap"}}) 192 } else { 193 // no script -> no health 194 c.Assert(err, check.Equals, state.ErrNoState) 195 c.Check(healths, check.IsNil) 196 c.Check(health, check.IsNil) 197 c.Check(cmd.Calls(), check.HasLen, 0) 198 } 199 } 200 201 func (*healthSuite) TestStatusHappy(c *check.C) { 202 for i, str := range healthstate.KnownStatuses { 203 status, err := healthstate.StatusLookup(str) 204 c.Check(err, check.IsNil, check.Commentf("%v", str)) 205 c.Check(status, check.Equals, healthstate.HealthStatus(i), check.Commentf("%v", str)) 206 c.Check(healthstate.HealthStatus(i).String(), check.Equals, str, check.Commentf("%v", str)) 207 } 208 } 209 210 func (*healthSuite) TestStatusUnhappy(c *check.C) { 211 status, err := healthstate.StatusLookup("rabbits") 212 c.Check(status, check.Equals, healthstate.HealthStatus(-1)) 213 c.Check(err, check.ErrorMatches, `invalid status "rabbits".*`) 214 c.Check(status.String(), check.Equals, "invalid (-1)") 215 } 216 217 func (s *healthSuite) TestSetFromHookContext(c *check.C) { 218 ctx, err := hookstate.NewContext(nil, s.state, &hookstate.HookSetup{Snap: "foo"}, nil, "") 219 c.Assert(err, check.IsNil) 220 221 ctx.Lock() 222 defer ctx.Unlock() 223 224 var hs map[string]*healthstate.HealthState 225 c.Check(s.state.Get("health", &hs), check.Equals, state.ErrNoState) 226 227 ctx.Set("health", &healthstate.HealthState{Status: 42}) 228 229 err = healthstate.SetFromHookContext(ctx) 230 c.Assert(err, check.IsNil) 231 232 hs, err = healthstate.All(s.state) 233 c.Check(err, check.IsNil) 234 c.Check(hs, check.DeepEquals, map[string]*healthstate.HealthState{ 235 "foo": {Status: 42}, 236 }) 237 } 238 239 func (s *healthSuite) TestSetFromHookContextEmpty(c *check.C) { 240 ctx, err := hookstate.NewContext(nil, s.state, &hookstate.HookSetup{Snap: "foo"}, nil, "") 241 c.Assert(err, check.IsNil) 242 243 ctx.Lock() 244 defer ctx.Unlock() 245 246 var hs map[string]healthstate.HealthState 247 c.Check(s.state.Get("health", &hs), check.Equals, state.ErrNoState) 248 249 err = healthstate.SetFromHookContext(ctx) 250 c.Assert(err, check.IsNil) 251 252 // no health in the context -> no health in state 253 c.Check(s.state.Get("health", &hs), check.Equals, state.ErrNoState) 254 }