github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/configstate/configcore/services_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 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 28 . "gopkg.in/check.v1" 29 30 "github.com/snapcore/snapd/dirs" 31 "github.com/snapcore/snapd/osutil" 32 "github.com/snapcore/snapd/overlord/configstate/configcore" 33 "github.com/snapcore/snapd/release" 34 "github.com/snapcore/snapd/snap" 35 "github.com/snapcore/snapd/testutil" 36 ) 37 38 type servicesSuite struct { 39 configcoreSuite 40 serviceInstalled bool 41 } 42 43 var _ = Suite(&servicesSuite{}) 44 45 func (s *servicesSuite) SetUpTest(c *C) { 46 s.configcoreSuite.SetUpTest(c) 47 s.systemctlOutput = func(args ...string) []byte { 48 var output []byte 49 if args[0] == "show" { 50 if args[1] == "--property=ActiveState" { 51 output = []byte("ActiveState=inactive") 52 } else { 53 if s.serviceInstalled { 54 output = []byte(fmt.Sprintf("Id=%s\nType=daemon\nActiveState=inactive\nUnitFileState=enabled\n", args[2])) 55 } else { 56 output = []byte(fmt.Sprintf("Id=%s\nType=\nActiveState=inactive\nUnitFileState=\n", args[2])) 57 } 58 } 59 } 60 return output 61 } 62 63 c.Assert(os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc"), 0755), IsNil) 64 s.serviceInstalled = true 65 s.systemctlArgs = nil 66 s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})) 67 } 68 69 func (s *servicesSuite) TestConfigureServiceInvalidValue(c *C) { 70 restore := release.MockOnClassic(false) 71 defer restore() 72 73 err := configcore.Run(&mockConf{ 74 state: s.state, 75 changes: map[string]interface{}{ 76 "service.ssh.disable": "xxx", 77 }, 78 }) 79 c.Check(err, ErrorMatches, `option "service.ssh.disable" has invalid value "xxx"`) 80 } 81 82 func (s *servicesSuite) TestConfigureServiceNotDisabled(c *C) { 83 err := configcore.SwitchDisableService("sshd.service", false, nil) 84 c.Assert(err, IsNil) 85 c.Check(s.systemctlArgs, DeepEquals, [][]string{ 86 {"show", "--property=Id,ActiveState,UnitFileState,Type", "sshd.service"}, 87 {"unmask", "sshd.service"}, 88 {"enable", "sshd.service"}, 89 {"start", "sshd.service"}, 90 }) 91 } 92 93 func (s *servicesSuite) TestConfigureServiceDisabled(c *C) { 94 err := configcore.SwitchDisableService("sshd.service", true, nil) 95 c.Assert(err, IsNil) 96 c.Check(s.systemctlArgs, DeepEquals, [][]string{ 97 {"show", "--property=Id,ActiveState,UnitFileState,Type", "sshd.service"}, 98 {"disable", "sshd.service"}, 99 {"mask", "sshd.service"}, 100 {"stop", "sshd.service"}, 101 {"show", "--property=ActiveState", "sshd.service"}, 102 }) 103 } 104 105 func (s *servicesSuite) TestConfigureServiceDisabledIntegration(c *C) { 106 restore := release.MockOnClassic(false) 107 defer restore() 108 109 err := os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "/etc/ssh"), 0755) 110 c.Assert(err, IsNil) 111 112 for _, service := range []struct { 113 cfgName string 114 systemdName string 115 installed bool 116 }{ 117 {"ssh", "ssh.service", true}, // no installed check for ssh 118 {"ssh", "ssh.service", false}, // no installed check for ssh 119 {"rsyslog", "rsyslog.service", true}, 120 {"rsyslog", "rsyslog.service", false}, 121 {"systemd-resolved", "systemd-resolved.service", true}, 122 {"systemd-resolved", "systemd-resolved.service", false}, 123 } { 124 s.systemctlArgs = nil 125 s.serviceInstalled = service.installed 126 err := configcore.Run(&mockConf{ 127 state: s.state, 128 conf: map[string]interface{}{ 129 fmt.Sprintf("service.%s.disable", service.cfgName): true, 130 }, 131 }) 132 c.Assert(err, IsNil) 133 srv := service.systemdName 134 switch service.cfgName { 135 case "ssh": 136 sshCanary := filepath.Join(dirs.GlobalRootDir, "/etc/ssh/sshd_not_to_be_run") 137 _, err := os.Stat(sshCanary) 138 c.Assert(err, IsNil) 139 c.Check(s.systemctlArgs, DeepEquals, [][]string{ 140 {"stop", srv}, 141 {"show", "--property=ActiveState", srv}, 142 }) 143 default: 144 if service.installed { 145 c.Check(s.systemctlArgs, DeepEquals, [][]string{ 146 {"show", "--property=Id,ActiveState,UnitFileState,Type", srv}, 147 {"disable", srv}, 148 {"mask", srv}, 149 {"stop", srv}, 150 {"show", "--property=ActiveState", srv}, 151 }) 152 } else { 153 c.Check(s.systemctlArgs, DeepEquals, [][]string{ 154 {"show", "--property=Id,ActiveState,UnitFileState,Type", srv}, 155 }) 156 } 157 } 158 } 159 } 160 161 func (s *servicesSuite) TestConfigureConsoleConfDisableFSOnly(c *C) { 162 restore := release.MockOnClassic(false) 163 defer restore() 164 165 conf := configcore.PlainCoreConfig(map[string]interface{}{ 166 "service.console-conf.disable": true, 167 }) 168 169 tmpDir := c.MkDir() 170 c.Assert(configcore.FilesystemOnlyApply(tmpDir, conf, nil), IsNil) 171 172 consoleConfDisabled := filepath.Join(tmpDir, "/var/lib/console-conf/complete") 173 c.Check(consoleConfDisabled, testutil.FileEquals, "console-conf has been disabled by the snapd system configuration\n") 174 } 175 176 func (s *servicesSuite) TestConfigureConsoleConfEnabledFSOnly(c *C) { 177 restore := release.MockOnClassic(false) 178 defer restore() 179 180 conf := configcore.PlainCoreConfig(map[string]interface{}{ 181 "service.console-conf.disable": false, 182 }) 183 184 tmpDir := c.MkDir() 185 c.Assert(configcore.FilesystemOnlyApply(tmpDir, conf, nil), IsNil) 186 187 consoleConfDisabled := filepath.Join(tmpDir, "/var/lib/console-conf/complete") 188 c.Check(consoleConfDisabled, testutil.FileAbsent) 189 } 190 191 func (s *servicesSuite) TestConfigureConsoleConfEnableNotAtRuntime(c *C) { 192 restore := release.MockOnClassic(false) 193 defer restore() 194 195 // pretend that console-conf is disabled 196 canary := filepath.Join(dirs.GlobalRootDir, "/var/lib/console-conf/complete") 197 err := os.MkdirAll(filepath.Dir(canary), 0755) 198 c.Assert(err, IsNil) 199 err = ioutil.WriteFile(canary, nil, 0644) 200 c.Assert(err, IsNil) 201 202 // now enable it 203 err = configcore.Run(&mockConf{ 204 state: s.state, 205 conf: map[string]interface{}{ 206 "service.console-conf.disable": false, 207 }, 208 }) 209 c.Assert(err, ErrorMatches, "cannot toggle console-conf at runtime, but only initially via gadget defaults") 210 } 211 212 func (s *servicesSuite) TestConfigureConsoleConfDisableNotAtRuntime(c *C) { 213 restore := release.MockOnClassic(false) 214 defer restore() 215 216 // console-conf is not disabled, i.e. there is no 217 // "/var/lib/console-conf/complete" file 218 219 // now try to enable it 220 err := configcore.Run(&mockConf{ 221 state: s.state, 222 conf: map[string]interface{}{ 223 "service.console-conf.disable": true, 224 }, 225 }) 226 c.Assert(err, ErrorMatches, "cannot toggle console-conf at runtime, but only initially via gadget defaults") 227 } 228 229 func (s *servicesSuite) TestConfigureConsoleConfEnableAlreadyEnabledIsFine(c *C) { 230 restore := release.MockOnClassic(false) 231 defer restore() 232 233 // Note that we have no 234 // /var/lib/console-conf/complete 235 // file. So console-conf is already enabled 236 err := configcore.Run(&mockConf{ 237 state: s.state, 238 conf: map[string]interface{}{ 239 "service.console-conf.disable": false, 240 }, 241 }) 242 c.Assert(err, IsNil) 243 } 244 245 func (s *servicesSuite) TestConfigureConsoleConfDisableAlreadyDisabledIsFine(c *C) { 246 restore := release.MockOnClassic(false) 247 defer restore() 248 249 // pretend that console-conf is disabled 250 canary := filepath.Join(dirs.GlobalRootDir, "/var/lib/console-conf/complete") 251 err := os.MkdirAll(filepath.Dir(canary), 0755) 252 c.Assert(err, IsNil) 253 err = ioutil.WriteFile(canary, nil, 0644) 254 c.Assert(err, IsNil) 255 256 err = configcore.Run(&mockConf{ 257 state: s.state, 258 conf: map[string]interface{}{ 259 "service.console-conf.disable": true, 260 }, 261 }) 262 c.Assert(err, IsNil) 263 } 264 265 func (s *servicesSuite) TestConfigureConsoleConfEnableDuringInstallMode(c *C) { 266 restore := release.MockOnClassic(false) 267 defer restore() 268 269 mockProcCmdline := filepath.Join(c.MkDir(), "cmdline") 270 err := ioutil.WriteFile(mockProcCmdline, []byte("snapd_recovery_mode=install snapd_recovery_system=20201212\n"), 0644) 271 c.Assert(err, IsNil) 272 restore = osutil.MockProcCmdline(mockProcCmdline) 273 defer restore() 274 275 err = configcore.Run(&mockConf{ 276 state: s.state, 277 conf: map[string]interface{}{ 278 "service.console-conf.disable": true, 279 }, 280 }) 281 // no error because we are in install mode 282 c.Assert(err, IsNil) 283 } 284 285 func (s *servicesSuite) TestConfigureServiceEnableIntegration(c *C) { 286 restore := release.MockOnClassic(false) 287 defer restore() 288 289 err := os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "/etc/ssh"), 0755) 290 c.Assert(err, IsNil) 291 292 for _, service := range []struct { 293 cfgName string 294 systemdName string 295 installed bool 296 }{ 297 {"ssh", "ssh.service", true}, // no installed check for ssh 298 {"ssh", "ssh.service", false}, // no installed check for ssh 299 {"rsyslog", "rsyslog.service", true}, 300 {"rsyslog", "rsyslog.service", false}, 301 {"systemd-resolved", "systemd-resolved.service", true}, 302 {"systemd-resolved", "systemd-resolved.service", false}, 303 } { 304 s.systemctlArgs = nil 305 s.serviceInstalled = service.installed 306 err := configcore.Run(&mockConf{ 307 state: s.state, 308 conf: map[string]interface{}{ 309 fmt.Sprintf("service.%s.disable", service.cfgName): false, 310 }, 311 }) 312 313 c.Assert(err, IsNil) 314 srv := service.systemdName 315 switch service.cfgName { 316 case "ssh": 317 c.Check(s.systemctlArgs, DeepEquals, [][]string{ 318 {"unmask", "sshd.service"}, 319 {"unmask", "ssh.service"}, 320 {"start", srv}, 321 }) 322 sshCanary := filepath.Join(dirs.GlobalRootDir, "/etc/ssh/sshd_not_to_be_run") 323 _, err := os.Stat(sshCanary) 324 c.Assert(err, ErrorMatches, ".* no such file or directory") 325 default: 326 if service.installed { 327 c.Check(s.systemctlArgs, DeepEquals, [][]string{ 328 {"show", "--property=Id,ActiveState,UnitFileState,Type", srv}, 329 {"unmask", srv}, 330 {"enable", srv}, 331 {"start", srv}, 332 }) 333 } else { 334 c.Check(s.systemctlArgs, DeepEquals, [][]string{ 335 {"show", "--property=Id,ActiveState,UnitFileState,Type", srv}, 336 }) 337 } 338 } 339 } 340 } 341 342 func (s *servicesSuite) TestConfigureServiceUnsupportedService(c *C) { 343 restore := release.MockOnClassic(false) 344 defer restore() 345 346 err := configcore.Run(&mockConf{ 347 state: s.state, 348 conf: map[string]interface{}{ 349 "service.snapd.disable": true, 350 }, 351 }) 352 c.Assert(err, IsNil) 353 354 // ensure nothing gets enabled/disabled when an unsupported 355 // service is set for disable 356 c.Check(s.systemctlArgs, IsNil) 357 } 358 359 func (s *servicesSuite) TestFilesystemOnlyApply(c *C) { 360 tmpDir := c.MkDir() 361 c.Assert(os.MkdirAll(filepath.Join(tmpDir, "etc", "ssh"), 0755), IsNil) 362 363 conf := configcore.PlainCoreConfig(map[string]interface{}{ 364 "service.ssh.disable": "true", 365 "service.rsyslog.disable": "true", 366 }) 367 c.Assert(configcore.FilesystemOnlyApply(tmpDir, conf, nil), IsNil) 368 c.Check(s.systemctlArgs, DeepEquals, [][]string{ 369 {"--root", tmpDir, "mask", "rsyslog.service"}, 370 }) 371 }