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  }