github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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 captainHook 102 ) 103 104 func (s *healthSuite) TestHealthNoHook(c *check.C) { 105 s.testHealth(c, noHook) 106 } 107 108 func (s *healthSuite) TestHealthFailingHook(c *check.C) { 109 s.testHealth(c, badHook) 110 } 111 112 func (s *healthSuite) TestHealth(c *check.C) { 113 s.testHealth(c, goodHook) 114 } 115 116 func (s *healthSuite) testHealth(c *check.C, cond healthHookTestCondition) { 117 var cmd *testutil.MockCmd 118 switch cond { 119 case badHook: 120 cmd = testutil.MockCommand(c, "snap", "exit 1") 121 default: 122 cmd = testutil.MockCommand(c, "snap", "exit 0") 123 } 124 125 if cond != noHook { 126 hookFn := filepath.Join(s.info.MountDir(), "meta", "hooks", "check-health") 127 c.Assert(os.MkdirAll(filepath.Dir(hookFn), 0755), check.IsNil) 128 // the hook won't actually be called, but needs to exist 129 c.Assert(ioutil.WriteFile(hookFn, nil, 0755), check.IsNil) 130 } 131 132 s.state.Lock() 133 task := healthstate.Hook(s.state, "test-snap", snap.R(42)) 134 change := s.state.NewChange("kind", "summary") 135 change.AddTask(task) 136 s.state.Unlock() 137 138 c.Assert(task.Kind(), check.Equals, "run-hook") 139 var hooksup hookstate.HookSetup 140 141 s.state.Lock() 142 err := task.Get("hook-setup", &hooksup) 143 s.state.Unlock() 144 c.Check(err, check.IsNil) 145 146 c.Check(hooksup, check.DeepEquals, hookstate.HookSetup{ 147 Snap: "test-snap", 148 Hook: "check-health", 149 Revision: snap.R(42), 150 Optional: true, 151 Timeout: time.Second, 152 IgnoreError: false, 153 TrackError: false, 154 }) 155 156 t0 := time.Now() 157 s.se.Ensure() 158 s.se.Wait() 159 tf := time.Now() 160 var healths map[string]*healthstate.HealthState 161 var health *healthstate.HealthState 162 var err2 error 163 s.state.Lock() 164 status := change.Status() 165 err = s.state.Get("health", &healths) 166 health, err2 = healthstate.Get(s.state, "test-snap") 167 s.state.Unlock() 168 c.Assert(err2, check.IsNil) 169 170 switch cond { 171 case badHook: 172 c.Assert(status, check.Equals, state.ErrorStatus) 173 default: 174 c.Assert(status, check.Equals, state.DoneStatus) 175 } 176 if cond != noHook { 177 c.Assert(err, check.IsNil) 178 c.Assert(healths, check.HasLen, 1) 179 c.Assert(healths["test-snap"], check.NotNil) 180 c.Check(health, check.DeepEquals, healths["test-snap"]) 181 c.Check(health.Revision, check.Equals, snap.R(42)) 182 c.Check(health.Status, check.Equals, healthstate.UnknownStatus) 183 if cond == badHook { 184 c.Check(health.Message, check.Equals, "hook failed") 185 c.Check(health.Code, check.Equals, "snapd-hook-failed") 186 } else { 187 c.Check(health.Message, check.Equals, "hook did not call set-health") 188 c.Check(health.Code, check.Equals, "snapd-hook-no-health-set") 189 } 190 com := check.Commentf("%s ⩼ %s ⩼ %s", t0.Format(time.StampNano), health.Timestamp.Format(time.StampNano), tf.Format(time.StampNano)) 191 c.Check(health.Timestamp.After(t0) && health.Timestamp.Before(tf), check.Equals, true, com) 192 c.Check(cmd.Calls(), check.DeepEquals, [][]string{{"snap", "run", "--hook", "check-health", "-r", "42", "test-snap"}}) 193 } else { 194 // no script -> no health 195 c.Assert(err, check.Equals, state.ErrNoState) 196 c.Check(healths, check.IsNil) 197 c.Check(health, check.IsNil) 198 c.Check(cmd.Calls(), check.HasLen, 0) 199 } 200 } 201 202 func (*healthSuite) TestStatusHappy(c *check.C) { 203 for i, str := range healthstate.KnownStatuses { 204 status, err := healthstate.StatusLookup(str) 205 c.Check(err, check.IsNil, check.Commentf("%v", str)) 206 c.Check(status, check.Equals, healthstate.HealthStatus(i), check.Commentf("%v", str)) 207 c.Check(healthstate.HealthStatus(i).String(), check.Equals, str, check.Commentf("%v", str)) 208 } 209 } 210 211 func (*healthSuite) TestStatusUnhappy(c *check.C) { 212 status, err := healthstate.StatusLookup("rabbits") 213 c.Check(status, check.Equals, healthstate.HealthStatus(-1)) 214 c.Check(err, check.ErrorMatches, `invalid status "rabbits".*`) 215 c.Check(status.String(), check.Equals, "invalid (-1)") 216 } 217 218 func (s *healthSuite) TestSetFromHookContext(c *check.C) { 219 ctx, err := hookstate.NewContext(nil, s.state, &hookstate.HookSetup{Snap: "foo"}, nil, "") 220 c.Assert(err, check.IsNil) 221 222 ctx.Lock() 223 defer ctx.Unlock() 224 225 var hs map[string]*healthstate.HealthState 226 c.Check(s.state.Get("health", &hs), check.Equals, state.ErrNoState) 227 228 ctx.Set("health", &healthstate.HealthState{Status: 42}) 229 230 err = healthstate.SetFromHookContext(ctx) 231 c.Assert(err, check.IsNil) 232 233 hs, err = healthstate.All(s.state) 234 c.Check(err, check.IsNil) 235 c.Check(hs, check.DeepEquals, map[string]*healthstate.HealthState{ 236 "foo": {Status: 42}, 237 }) 238 } 239 240 func (s *healthSuite) TestSetFromHookContextEmpty(c *check.C) { 241 ctx, err := hookstate.NewContext(nil, s.state, &hookstate.HookSetup{Snap: "foo"}, nil, "") 242 c.Assert(err, check.IsNil) 243 244 ctx.Lock() 245 defer ctx.Unlock() 246 247 var hs map[string]healthstate.HealthState 248 c.Check(s.state.Get("health", &hs), check.Equals, state.ErrNoState) 249 250 err = healthstate.SetFromHookContext(ctx) 251 c.Assert(err, check.IsNil) 252 253 // no health in the context -> no health in state 254 c.Check(s.state.Get("health", &hs), check.Equals, state.ErrNoState) 255 }