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  }