github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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 "bytes" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/dirs" 32 "github.com/snapcore/snapd/overlord/snapstate" 33 "github.com/snapcore/snapd/overlord/state" 34 "github.com/snapcore/snapd/snap" 35 "github.com/snapcore/snapd/snap/snaptest" 36 ) 37 38 type refreshSuite struct { 39 state *state.State 40 info *snap.Info 41 42 // paths of the PID cgroup of each app or hook. 43 daemonPath string 44 appPath string 45 hookPath string 46 } 47 48 var _ = Suite(&refreshSuite{}) 49 50 func (s *refreshSuite) SetUpTest(c *C) { 51 dirs.SetRootDir(c.MkDir()) 52 yamlText := ` 53 name: foo 54 version: 1 55 apps: 56 daemon: 57 command: dummy 58 daemon: simple 59 app: 60 command: dummy 61 hooks: 62 configure: 63 ` 64 s.info = snaptest.MockInfo(c, yamlText, nil) 65 s.daemonPath = filepath.Join(dirs.PidsCgroupDir, s.info.Apps["daemon"].SecurityTag()) 66 s.appPath = filepath.Join(dirs.PidsCgroupDir, s.info.Apps["app"].SecurityTag()) 67 s.hookPath = filepath.Join(dirs.PidsCgroupDir, s.info.Hooks["configure"].SecurityTag()) 68 } 69 70 func (s *refreshSuite) TearDownTest(c *C) { 71 dirs.SetRootDir("") 72 } 73 74 func writePids(c *C, dir string, pids []int) { 75 err := os.MkdirAll(dir, 0755) 76 c.Assert(err, IsNil) 77 var buf bytes.Buffer 78 for _, pid := range pids { 79 fmt.Fprintf(&buf, "%d\n", pid) 80 } 81 err = ioutil.WriteFile(filepath.Join(dir, "cgroup.procs"), buf.Bytes(), 0644) 82 c.Assert(err, IsNil) 83 } 84 85 func (s *refreshSuite) TestSoftNothingRunningRefreshCheck(c *C) { 86 // There are no errors when PID cgroup is absent. 87 err := snapstate.SoftNothingRunningRefreshCheck(s.info) 88 c.Check(err, IsNil) 89 90 // Services are not blocking soft refresh check, 91 // they will be stopped before refresh. 92 writePids(c, s.daemonPath, []int{100}) 93 err = snapstate.SoftNothingRunningRefreshCheck(s.info) 94 c.Check(err, IsNil) 95 96 // Apps are blocking soft refresh check. They are not stopped by 97 // snapd, unless the app is running for longer than the maximum 98 // duration allowed for postponing refreshes. 99 writePids(c, s.daemonPath, []int{100}) 100 writePids(c, s.appPath, []int{101}) 101 err = snapstate.SoftNothingRunningRefreshCheck(s.info) 102 c.Assert(err, NotNil) 103 c.Check(err.Error(), Equals, `snap "foo" has running apps (app)`) 104 c.Check(err.(*snapstate.BusySnapError).Pids(), DeepEquals, []int{101}) 105 106 // Hooks behave just like apps. IDEA: perhaps hooks should not be 107 // killed this way? They have their own life-cycle management. 108 writePids(c, s.daemonPath, []int{}) 109 writePids(c, s.appPath, []int{}) 110 writePids(c, s.hookPath, []int{105}) 111 err = snapstate.SoftNothingRunningRefreshCheck(s.info) 112 c.Assert(err, NotNil) 113 c.Check(err.Error(), Equals, `snap "foo" has running hooks (configure)`) 114 c.Check(err.(*snapstate.BusySnapError).Pids(), DeepEquals, []int{105}) 115 116 // Both apps and hooks can be running. 117 writePids(c, s.daemonPath, []int{100}) 118 writePids(c, s.appPath, []int{101}) 119 writePids(c, s.hookPath, []int{105}) 120 err = snapstate.SoftNothingRunningRefreshCheck(s.info) 121 c.Assert(err, NotNil) 122 c.Check(err.Error(), Equals, `snap "foo" has running apps (app) and hooks (configure)`) 123 c.Check(err.(*snapstate.BusySnapError).Pids(), DeepEquals, []int{101, 105}) 124 } 125 126 func (s *refreshSuite) TestHardNothingRunningRefreshCheck(c *C) { 127 // There are no errors when PID cgroup is absent. 128 err := snapstate.HardNothingRunningRefreshCheck(s.info) 129 c.Check(err, IsNil) 130 131 // Regular services are blocking hard refresh check. 132 // We were expecting them to be gone by now. 133 writePids(c, s.daemonPath, []int{100}) 134 writePids(c, s.appPath, []int{}) 135 err = snapstate.HardNothingRunningRefreshCheck(s.info) 136 c.Assert(err, NotNil) 137 c.Check(err.Error(), Equals, `snap "foo" has running apps (daemon)`) 138 c.Check(err.(*snapstate.BusySnapError).Pids(), DeepEquals, []int{100}) 139 140 // When the service is supposed to endure refreshes it will not be 141 // stopped. As such such services cannot block refresh. 142 s.info.Apps["daemon"].RefreshMode = "endure" 143 err = snapstate.HardNothingRunningRefreshCheck(s.info) 144 c.Check(err, IsNil) 145 s.info.Apps["daemon"].RefreshMode = "" 146 147 // Applications are also blocking hard refresh check. 148 writePids(c, s.daemonPath, []int{}) 149 writePids(c, s.appPath, []int{101}) 150 err = snapstate.HardNothingRunningRefreshCheck(s.info) 151 c.Assert(err, NotNil) 152 c.Check(err.Error(), Equals, `snap "foo" has running apps (app)`) 153 c.Check(err.(*snapstate.BusySnapError).Pids(), DeepEquals, []int{101}) 154 155 // Hooks are equally blocking hard refresh check. 156 writePids(c, s.daemonPath, []int{}) 157 writePids(c, s.appPath, []int{}) 158 writePids(c, s.hookPath, []int{105}) 159 err = snapstate.HardNothingRunningRefreshCheck(s.info) 160 c.Assert(err, NotNil) 161 c.Check(err.Error(), Equals, `snap "foo" has running hooks (configure)`) 162 c.Check(err.(*snapstate.BusySnapError).Pids(), DeepEquals, []int{105}) 163 }