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