github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/configstate/configcore/watchdog_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2018 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 configcore_test 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "time" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/dirs" 32 "github.com/snapcore/snapd/osutil" 33 "github.com/snapcore/snapd/overlord/configstate/configcore" 34 "github.com/snapcore/snapd/release" 35 "github.com/snapcore/snapd/testutil" 36 ) 37 38 type watchdogSuite struct { 39 configcoreSuite 40 41 mockEtcEnvironment string 42 } 43 44 var _ = Suite(&watchdogSuite{}) 45 46 func (s *watchdogSuite) SetUpTest(c *C) { 47 s.configcoreSuite.SetUpTest(c) 48 49 s.mockEtcEnvironment = filepath.Join(dirs.SnapSystemdConfDir, "10-snapd-watchdog.conf") 50 } 51 52 func (s *watchdogSuite) TestConfigureWatchdog(c *C) { 53 restore := release.MockOnClassic(false) 54 defer restore() 55 56 for option, val := range map[string]string{"runtime-timeout": "10", "shutdown-timeout": "60"} { 57 58 err := configcore.Run(&mockConf{ 59 state: s.state, 60 conf: map[string]interface{}{ 61 fmt.Sprintf("watchdog.%s", option): val + "s", 62 }, 63 }) 64 c.Assert(err, IsNil) 65 66 var systemdOption string 67 switch option { 68 case "runtime-timeout": 69 systemdOption = "RuntimeWatchdogSec" 70 case "shutdown-timeout": 71 systemdOption = "ShutdownWatchdogSec" 72 } 73 c.Check(s.mockEtcEnvironment, testutil.FileEquals, 74 fmt.Sprintf("[Manager]\n%s=%s\n", systemdOption, val)) 75 } 76 77 c.Check(s.systemctlArgs, DeepEquals, [][]string{ 78 {"daemon-reexec"}, 79 {"daemon-reexec"}, 80 }) 81 } 82 83 func (s *watchdogSuite) TestConfigureWatchdogUnits(c *C) { 84 restore := release.MockOnClassic(false) 85 defer restore() 86 87 times := []int{56, 432} 88 type timeUnit struct { 89 unit string 90 toSec int 91 } 92 93 for _, tunit := range []timeUnit{{"s", 1}, {"m", 60}, {"h", 3600}} { 94 err := configcore.Run(&mockConf{ 95 state: s.state, 96 conf: map[string]interface{}{ 97 "watchdog.runtime-timeout": fmt.Sprintf("%d", times[0]) + tunit.unit, 98 "watchdog.shutdown-timeout": fmt.Sprintf("%d", times[1]) + tunit.unit, 99 }, 100 }) 101 c.Assert(err, IsNil) 102 c.Check(s.mockEtcEnvironment, testutil.FileEquals, "[Manager]\n"+ 103 fmt.Sprintf("RuntimeWatchdogSec=%d\n", times[0]*tunit.toSec)+ 104 fmt.Sprintf("ShutdownWatchdogSec=%d\n", times[1]*tunit.toSec)) 105 } 106 } 107 108 func (s *watchdogSuite) TestConfigureWatchdogAll(c *C) { 109 restore := release.MockOnClassic(false) 110 defer restore() 111 112 times := []int{10, 100} 113 err := configcore.Run(&mockConf{ 114 state: s.state, 115 conf: map[string]interface{}{ 116 "watchdog.runtime-timeout": fmt.Sprintf("%ds", times[0]), 117 "watchdog.shutdown-timeout": fmt.Sprintf("%ds", times[1]), 118 }, 119 }) 120 c.Assert(err, IsNil) 121 c.Check(s.mockEtcEnvironment, testutil.FileEquals, "[Manager]\n"+ 122 fmt.Sprintf("RuntimeWatchdogSec=%d\n", times[0])+ 123 fmt.Sprintf("ShutdownWatchdogSec=%d\n", times[1])) 124 125 c.Check(s.systemctlArgs, DeepEquals, [][]string{ 126 {"daemon-reexec"}, 127 }) 128 } 129 130 func (s *watchdogSuite) TestConfigureWatchdogAllConfDirExistsAlready(c *C) { 131 // make .conf.d directory already 132 err := os.MkdirAll(dirs.SnapSystemdConfDir, 0755) 133 c.Assert(err, IsNil) 134 135 restore := release.MockOnClassic(false) 136 defer restore() 137 138 times := []int{10, 100} 139 err = configcore.Run(&mockConf{ 140 state: s.state, 141 conf: map[string]interface{}{ 142 "watchdog.runtime-timeout": fmt.Sprintf("%ds", times[0]), 143 "watchdog.shutdown-timeout": fmt.Sprintf("%ds", times[1]), 144 }, 145 }) 146 c.Assert(err, IsNil) 147 c.Check(s.mockEtcEnvironment, testutil.FileEquals, "[Manager]\n"+ 148 fmt.Sprintf("RuntimeWatchdogSec=%d\n", times[0])+ 149 fmt.Sprintf("ShutdownWatchdogSec=%d\n", times[1])) 150 151 c.Check(s.systemctlArgs, DeepEquals, [][]string{ 152 {"daemon-reexec"}, 153 }) 154 } 155 156 func (s *watchdogSuite) TestConfigureWatchdogBadFormat(c *C) { 157 restore := release.MockOnClassic(false) 158 defer restore() 159 160 type badValErr struct { 161 val string 162 err string 163 } 164 for _, badVal := range []badValErr{{"BAD", ".*invalid duration.*"}, 165 {"-5s", ".*negative duration.*"}, 166 {"34k", ".*unknown unit.*"}} { 167 err := configcore.Run(&mockConf{ 168 state: s.state, 169 conf: map[string]interface{}{ 170 "watchdog.runtime-timeout": badVal.val, 171 }, 172 }) 173 c.Assert(err, ErrorMatches, badVal.err) 174 } 175 176 c.Check(s.systemctlArgs, HasLen, 0) 177 } 178 179 func (s *watchdogSuite) TestConfigureWatchdogNoFileUpdate(c *C) { 180 restore := release.MockOnClassic(false) 181 defer restore() 182 183 err := os.MkdirAll(dirs.SnapSystemdConfDir, 0755) 184 c.Assert(err, IsNil) 185 times := []int{10, 100} 186 content := "[Manager]\n" + 187 fmt.Sprintf("RuntimeWatchdogSec=%d\n", times[0]) + 188 fmt.Sprintf("ShutdownWatchdogSec=%d\n", times[1]) 189 err = ioutil.WriteFile(s.mockEtcEnvironment, []byte(content), 0644) 190 c.Assert(err, IsNil) 191 192 info, err := os.Stat(s.mockEtcEnvironment) 193 c.Assert(err, IsNil) 194 195 fileModTime := info.ModTime() 196 197 // To make sure the times will defer if the file is newly written 198 time.Sleep(100 * time.Millisecond) 199 200 err = configcore.Run(&mockConf{ 201 state: s.state, 202 conf: map[string]interface{}{ 203 "watchdog.runtime-timeout": fmt.Sprintf("%ds", times[0]), 204 "watchdog.shutdown-timeout": fmt.Sprintf("%ds", times[1]), 205 }, 206 }) 207 c.Assert(err, IsNil) 208 c.Check(s.mockEtcEnvironment, testutil.FileEquals, content) 209 210 info, err = os.Stat(s.mockEtcEnvironment) 211 c.Assert(err, IsNil) 212 c.Assert(info.ModTime(), Equals, fileModTime) 213 214 c.Check(s.systemctlArgs, HasLen, 0) 215 } 216 217 func (s *watchdogSuite) TestConfigureWatchdogRemovesIfEmpty(c *C) { 218 restore := release.MockOnClassic(false) 219 defer restore() 220 221 err := os.MkdirAll(dirs.SnapSystemdConfDir, 0755) 222 c.Assert(err, IsNil) 223 // add canary to ensure we don't touch other files 224 canary := filepath.Join(dirs.SnapSystemdConfDir, "05-canary.conf") 225 err = ioutil.WriteFile(canary, nil, 0644) 226 c.Assert(err, IsNil) 227 228 content := `[Manager] 229 RuntimeWatchdogSec=10 230 ShutdownWatchdogSec=20 231 ` 232 err = ioutil.WriteFile(s.mockEtcEnvironment, []byte(content), 0644) 233 c.Assert(err, IsNil) 234 235 err = configcore.Run(&mockConf{ 236 state: s.state, 237 conf: map[string]interface{}{ 238 "watchdog.runtime-timeout": 0, 239 "watchdog.shutdown-timeout": 0, 240 }, 241 }) 242 c.Assert(err, IsNil) 243 244 // ensure the file got deleted 245 c.Check(osutil.FileExists(s.mockEtcEnvironment), Equals, false) 246 // but the canary is still here 247 c.Check(osutil.FileExists(canary), Equals, true) 248 249 // apply defaults 250 c.Check(s.systemctlArgs, DeepEquals, [][]string{ 251 {"daemon-reexec"}, 252 }) 253 } 254 255 func (s *watchdogSuite) TestFilesystemOnlyApply(c *C) { 256 conf := configcore.PlainCoreConfig(map[string]interface{}{ 257 "watchdog.runtime-timeout": "4s", 258 }) 259 260 tmpDir := c.MkDir() 261 c.Assert(configcore.FilesystemOnlyApply(tmpDir, conf, nil), IsNil) 262 263 watchdogCfg := filepath.Join(tmpDir, "/etc/systemd/system.conf.d/10-snapd-watchdog.conf") 264 c.Check(watchdogCfg, testutil.FileEquals, "[Manager]\nRuntimeWatchdogSec=4\n") 265 } 266 267 func (s *watchdogSuite) TestFilesystemOnlyApplyValidationFails(c *C) { 268 conf := configcore.PlainCoreConfig(map[string]interface{}{ 269 "watchdog.runtime-timeout": "foo", 270 }) 271 272 tmpDir := c.MkDir() 273 c.Assert(configcore.FilesystemOnlyApply(tmpDir, conf, nil), ErrorMatches, `cannot parse "foo": time: invalid duration \"?foo\"?`) 274 }