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  }