github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/snapstate/refresh_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 snapstate_test 21 22 import ( 23 "io/ioutil" 24 "os" 25 "path/filepath" 26 27 . "gopkg.in/check.v1" 28 29 "github.com/snapcore/snapd/cmd/snaplock/runinhibit" 30 "github.com/snapcore/snapd/dirs" 31 "github.com/snapcore/snapd/overlord/snapstate" 32 "github.com/snapcore/snapd/overlord/state" 33 "github.com/snapcore/snapd/snap" 34 "github.com/snapcore/snapd/snap/snaptest" 35 "github.com/snapcore/snapd/testutil" 36 ) 37 38 type refreshSuite struct { 39 testutil.BaseTest 40 state *state.State 41 info *snap.Info 42 pids map[string][]int 43 } 44 45 var _ = Suite(&refreshSuite{}) 46 47 func (s *refreshSuite) SetUpTest(c *C) { 48 dirs.SetRootDir(c.MkDir()) 49 yamlText := ` 50 name: pkg 51 version: 1 52 apps: 53 daemon: 54 command: dummy 55 daemon: simple 56 app: 57 command: dummy 58 hooks: 59 configure: 60 ` 61 s.info = snaptest.MockInfo(c, yamlText, nil) 62 s.pids = nil 63 restore := snapstate.MockPidsOfSnap(func(instanceName string) (map[string][]int, error) { 64 c.Assert(instanceName, Equals, s.info.InstanceName()) 65 return s.pids, nil 66 }) 67 s.AddCleanup(restore) 68 s.AddCleanup(func() { dirs.SetRootDir("") }) 69 s.state = state.New(nil) 70 } 71 72 func (s *refreshSuite) TestSoftNothingRunningRefreshCheck(c *C) { 73 // Services are not blocking soft refresh check, 74 // they will be stopped before refresh. 75 s.pids = map[string][]int{ 76 "snap.pkg.daemon": {100}, 77 } 78 err := snapstate.SoftNothingRunningRefreshCheck(s.info) 79 c.Check(err, IsNil) 80 81 // Apps are blocking soft refresh check. They are not stopped by 82 // snapd, unless the app is running for longer than the maximum 83 // duration allowed for postponing refreshes. 84 s.pids = map[string][]int{ 85 "snap.pkg.daemon": {100}, 86 "snap.pkg.app": {101}, 87 } 88 err = snapstate.SoftNothingRunningRefreshCheck(s.info) 89 c.Assert(err, NotNil) 90 c.Check(err.Error(), Equals, `snap "pkg" has running apps (app)`) 91 c.Check(err.(*snapstate.BusySnapError).Pids(), DeepEquals, []int{101}) 92 93 // Hooks behave just like apps. IDEA: perhaps hooks should not be 94 // killed this way? They have their own life-cycle management. 95 s.pids = map[string][]int{ 96 "snap.pkg.hook.configure": {105}, 97 } 98 err = snapstate.SoftNothingRunningRefreshCheck(s.info) 99 c.Assert(err, NotNil) 100 c.Check(err.Error(), Equals, `snap "pkg" has running hooks (configure)`) 101 c.Check(err.(*snapstate.BusySnapError).Pids(), DeepEquals, []int{105}) 102 103 // Both apps and hooks can be running. 104 s.pids = map[string][]int{ 105 "snap.pkg.hook.configure": {105}, 106 "snap.pkg.app": {106}, 107 } 108 err = snapstate.SoftNothingRunningRefreshCheck(s.info) 109 c.Assert(err, NotNil) 110 c.Check(err.Error(), Equals, `snap "pkg" has running apps (app) and hooks (configure)`) 111 c.Check(err.(*snapstate.BusySnapError).Pids(), DeepEquals, []int{105, 106}) 112 } 113 114 func (s *refreshSuite) TestHardNothingRunningRefreshCheck(c *C) { 115 // Regular services are blocking hard refresh check. 116 // We were expecting them to be gone by now. 117 s.pids = map[string][]int{ 118 "snap.pkg.daemon": {100}, 119 } 120 err := snapstate.HardNothingRunningRefreshCheck(s.info) 121 c.Assert(err, NotNil) 122 c.Check(err.Error(), Equals, `snap "pkg" has running apps (daemon)`) 123 c.Check(err.(*snapstate.BusySnapError).Pids(), DeepEquals, []int{100}) 124 125 // When the service is supposed to endure refreshes it will not be 126 // stopped. As such such services cannot block refresh. 127 s.info.Apps["daemon"].RefreshMode = "endure" 128 err = snapstate.HardNothingRunningRefreshCheck(s.info) 129 c.Check(err, IsNil) 130 s.info.Apps["daemon"].RefreshMode = "" 131 132 // Applications are also blocking hard refresh check. 133 s.pids = map[string][]int{ 134 "snap.pkg.app": {101}, 135 } 136 err = snapstate.HardNothingRunningRefreshCheck(s.info) 137 c.Assert(err, NotNil) 138 c.Check(err.Error(), Equals, `snap "pkg" has running apps (app)`) 139 c.Assert(err, FitsTypeOf, &snapstate.BusySnapError{}) 140 c.Check(err.(*snapstate.BusySnapError).Pids(), DeepEquals, []int{101}) 141 142 // Hooks are equally blocking hard refresh check. 143 s.pids = map[string][]int{ 144 "snap.pkg.hook.configure": {105}, 145 } 146 err = snapstate.HardNothingRunningRefreshCheck(s.info) 147 c.Assert(err, NotNil) 148 c.Check(err.Error(), Equals, `snap "pkg" has running hooks (configure)`) 149 c.Check(err.(*snapstate.BusySnapError).Pids(), DeepEquals, []int{105}) 150 } 151 152 func (s *refreshSuite) TestPendingSnapRefreshInfo(c *C) { 153 err := snapstate.NewBusySnapError(s.info, nil, nil, nil) 154 refreshInfo := err.PendingSnapRefreshInfo() 155 c.Check(refreshInfo.InstanceName, Equals, s.info.InstanceName()) 156 // The information about a busy app is not populated because 157 // the original error did not have the corresponding information. 158 c.Check(refreshInfo.BusyAppName, Equals, "") 159 c.Check(refreshInfo.BusyAppDesktopEntry, Equals, "") 160 161 // If we create a matching desktop entry then relevant meta-data is added. 162 err = snapstate.NewBusySnapError(s.info, nil, []string{"app"}, nil) 163 desktopFile := s.info.Apps["app"].DesktopFile() 164 c.Assert(os.MkdirAll(filepath.Dir(desktopFile), 0755), IsNil) 165 c.Assert(ioutil.WriteFile(desktopFile, nil, 0644), IsNil) 166 refreshInfo = err.PendingSnapRefreshInfo() 167 c.Check(refreshInfo.InstanceName, Equals, s.info.InstanceName()) 168 c.Check(refreshInfo.BusyAppName, Equals, "app") 169 c.Check(refreshInfo.BusyAppDesktopEntry, Equals, "pkg_app") 170 } 171 172 func (s *refreshSuite) addInstalledSnap(snapst *snapstate.SnapState) (*snapstate.SnapState, *snap.Info) { 173 snapName := snapst.Sequence[0].RealName 174 snapstate.Set(s.state, snapName, snapst) 175 info := &snap.Info{SideInfo: snap.SideInfo{RealName: snapName, Revision: snapst.Current}} 176 return snapst, info 177 } 178 179 func (s *refreshSuite) addDummyInstalledSnap() (*snapstate.SnapState, *snap.Info) { 180 return s.addInstalledSnap(&snapstate.SnapState{ 181 Active: true, 182 Sequence: []*snap.SideInfo{ 183 {RealName: "pkg", Revision: snap.R(5), SnapID: "pkg-snap-id"}, 184 }, 185 Current: snap.R(5), 186 SnapType: "app", 187 UserID: 1, 188 }) 189 } 190 191 func (s *refreshSuite) TestDoSoftRefreshCheckAllowed(c *C) { 192 // Pretend we have a snap 193 s.state.Lock() 194 defer s.state.Unlock() 195 snapst, info := s.addDummyInstalledSnap() 196 197 // Pretend that snaps can refresh normally. 198 restore := snapstate.MockGenericRefreshCheck(func(info *snap.Info, canAppRunDuringRefresh func(app *snap.AppInfo) bool) error { 199 return nil 200 }) 201 defer restore() 202 203 // Soft refresh should not fail. 204 err := snapstate.SoftCheckNothingRunningForRefresh(s.state, snapst, info) 205 c.Assert(err, IsNil) 206 207 // In addition, the inhibition lock is not set. 208 hint, err := runinhibit.IsLocked(info.InstanceName()) 209 c.Assert(err, IsNil) 210 c.Check(hint, Equals, runinhibit.HintNotInhibited) 211 } 212 213 func (s *refreshSuite) TestDoSoftRefreshCheckDisallowed(c *C) { 214 // Pretend we have a snap 215 s.state.Lock() 216 defer s.state.Unlock() 217 snapst, info := s.addDummyInstalledSnap() 218 219 // Pretend that snaps cannot refresh. 220 restore := snapstate.MockGenericRefreshCheck(func(info *snap.Info, canAppRunDuringRefresh func(app *snap.AppInfo) bool) error { 221 return &snapstate.BusySnapError{SnapInfo: info} 222 }) 223 defer restore() 224 225 // Soft refresh should fail with a proper error. 226 err := snapstate.SoftCheckNothingRunningForRefresh(s.state, snapst, info) 227 c.Assert(err, ErrorMatches, `snap "pkg" has running apps or hooks`) 228 229 // Sanity check: the inhibition lock was not set. 230 hint, err := runinhibit.IsLocked(info.InstanceName()) 231 c.Assert(err, IsNil) 232 c.Check(hint, Equals, runinhibit.HintNotInhibited) 233 } 234 235 func (s *refreshSuite) TestDoHardRefreshFlowRefreshAllowed(c *C) { 236 backend := &fakeSnappyBackend{} 237 // Pretend we have a snap 238 s.state.Lock() 239 defer s.state.Unlock() 240 snapst, info := s.addDummyInstalledSnap() 241 242 // Pretend that snaps can refresh normally. 243 restore := snapstate.MockGenericRefreshCheck(func(info *snap.Info, canAppRunDuringRefresh func(app *snap.AppInfo) bool) error { 244 return nil 245 }) 246 defer restore() 247 248 // Hard refresh should not fail and return a valid lock. 249 lock, err := snapstate.HardEnsureNothingRunningDuringRefresh(backend, s.state, snapst, info) 250 c.Assert(err, IsNil) 251 c.Assert(lock, NotNil) 252 defer lock.Close() 253 254 // We should be able to unlock the lock without an error because 255 // it was acquired in the same process by the tested logic. 256 c.Assert(lock.Unlock(), IsNil) 257 258 // In addition, the fake backend recorded that a lock was established. 259 op := backend.ops.MustFindOp(c, "run-inhibit-snap-for-unlink") 260 c.Check(op.inhibitHint, Equals, runinhibit.Hint("refresh")) 261 } 262 263 func (s *refreshSuite) TestDoHardRefreshFlowRefreshDisallowed(c *C) { 264 backend := &fakeSnappyBackend{} 265 // Pretend we have a snap 266 s.state.Lock() 267 defer s.state.Unlock() 268 snapst, info := s.addDummyInstalledSnap() 269 270 // Pretend that snaps cannot refresh. 271 restore := snapstate.MockGenericRefreshCheck(func(info *snap.Info, canAppRunDuringRefresh func(app *snap.AppInfo) bool) error { 272 return &snapstate.BusySnapError{SnapInfo: info} 273 }) 274 defer restore() 275 276 // Hard refresh should fail and not return a lock. 277 lock, err := snapstate.HardEnsureNothingRunningDuringRefresh(backend, s.state, snapst, info) 278 c.Assert(err, ErrorMatches, `snap "pkg" has running apps or hooks`) 279 c.Assert(lock, IsNil) 280 281 // Sanity check: the inhibition lock was not set. 282 op := backend.ops.MustFindOp(c, "run-inhibit-snap-for-unlink") 283 c.Check(op.inhibitHint, Equals, runinhibit.Hint("refresh")) 284 }