github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/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/testutil"
    35  )
    36  
    37  type watchdogSuite struct {
    38  	configcoreSuite
    39  
    40  	mockEtcEnvironment string
    41  }
    42  
    43  var _ = Suite(&watchdogSuite{})
    44  
    45  func (s *watchdogSuite) SetUpTest(c *C) {
    46  	s.configcoreSuite.SetUpTest(c)
    47  
    48  	s.mockEtcEnvironment = filepath.Join(dirs.SnapSystemdConfDir, "10-snapd-watchdog.conf")
    49  }
    50  
    51  func (s *watchdogSuite) TestConfigureWatchdog(c *C) {
    52  	for option, val := range map[string]string{"runtime-timeout": "10", "shutdown-timeout": "60"} {
    53  
    54  		err := configcore.Run(coreDev, &mockConf{
    55  			state: s.state,
    56  			conf: map[string]interface{}{
    57  				fmt.Sprintf("watchdog.%s", option): val + "s",
    58  			},
    59  		})
    60  		c.Assert(err, IsNil)
    61  
    62  		var systemdOption string
    63  		switch option {
    64  		case "runtime-timeout":
    65  			systemdOption = "RuntimeWatchdogSec"
    66  		case "shutdown-timeout":
    67  			systemdOption = "ShutdownWatchdogSec"
    68  		}
    69  		c.Check(s.mockEtcEnvironment, testutil.FileEquals,
    70  			fmt.Sprintf("[Manager]\n%s=%s\n", systemdOption, val))
    71  	}
    72  
    73  	c.Check(s.systemctlArgs, DeepEquals, [][]string{
    74  		{"daemon-reexec"},
    75  		{"daemon-reexec"},
    76  	})
    77  }
    78  
    79  func (s *watchdogSuite) TestConfigureWatchdogUnits(c *C) {
    80  	times := []int{56, 432}
    81  	type timeUnit struct {
    82  		unit  string
    83  		toSec int
    84  	}
    85  
    86  	for _, tunit := range []timeUnit{{"s", 1}, {"m", 60}, {"h", 3600}} {
    87  		err := configcore.Run(coreDev, &mockConf{
    88  			state: s.state,
    89  			conf: map[string]interface{}{
    90  				"watchdog.runtime-timeout":  fmt.Sprintf("%d", times[0]) + tunit.unit,
    91  				"watchdog.shutdown-timeout": fmt.Sprintf("%d", times[1]) + tunit.unit,
    92  			},
    93  		})
    94  		c.Assert(err, IsNil)
    95  		c.Check(s.mockEtcEnvironment, testutil.FileEquals, "[Manager]\n"+
    96  			fmt.Sprintf("RuntimeWatchdogSec=%d\n", times[0]*tunit.toSec)+
    97  			fmt.Sprintf("ShutdownWatchdogSec=%d\n", times[1]*tunit.toSec))
    98  	}
    99  }
   100  
   101  func (s *watchdogSuite) TestConfigureWatchdogAll(c *C) {
   102  	times := []int{10, 100}
   103  	err := configcore.Run(coreDev, &mockConf{
   104  		state: s.state,
   105  		conf: map[string]interface{}{
   106  			"watchdog.runtime-timeout":  fmt.Sprintf("%ds", times[0]),
   107  			"watchdog.shutdown-timeout": fmt.Sprintf("%ds", times[1]),
   108  		},
   109  	})
   110  	c.Assert(err, IsNil)
   111  	c.Check(s.mockEtcEnvironment, testutil.FileEquals, "[Manager]\n"+
   112  		fmt.Sprintf("RuntimeWatchdogSec=%d\n", times[0])+
   113  		fmt.Sprintf("ShutdownWatchdogSec=%d\n", times[1]))
   114  
   115  	c.Check(s.systemctlArgs, DeepEquals, [][]string{
   116  		{"daemon-reexec"},
   117  	})
   118  }
   119  
   120  func (s *watchdogSuite) TestConfigureWatchdogAllConfDirExistsAlready(c *C) {
   121  	// make .conf.d directory already
   122  	err := os.MkdirAll(dirs.SnapSystemdConfDir, 0755)
   123  	c.Assert(err, IsNil)
   124  
   125  	times := []int{10, 100}
   126  	err = configcore.Run(coreDev, &mockConf{
   127  		state: s.state,
   128  		conf: map[string]interface{}{
   129  			"watchdog.runtime-timeout":  fmt.Sprintf("%ds", times[0]),
   130  			"watchdog.shutdown-timeout": fmt.Sprintf("%ds", times[1]),
   131  		},
   132  	})
   133  	c.Assert(err, IsNil)
   134  	c.Check(s.mockEtcEnvironment, testutil.FileEquals, "[Manager]\n"+
   135  		fmt.Sprintf("RuntimeWatchdogSec=%d\n", times[0])+
   136  		fmt.Sprintf("ShutdownWatchdogSec=%d\n", times[1]))
   137  
   138  	c.Check(s.systemctlArgs, DeepEquals, [][]string{
   139  		{"daemon-reexec"},
   140  	})
   141  }
   142  
   143  func (s *watchdogSuite) TestConfigureWatchdogBadFormat(c *C) {
   144  	type badValErr struct {
   145  		val string
   146  		err string
   147  	}
   148  	for _, badVal := range []badValErr{{"BAD", ".*invalid duration.*"},
   149  		{"-5s", ".*negative duration.*"},
   150  		{"34k", ".*unknown unit.*"}} {
   151  		err := configcore.Run(coreDev, &mockConf{
   152  			state: s.state,
   153  			conf: map[string]interface{}{
   154  				"watchdog.runtime-timeout": badVal.val,
   155  			},
   156  		})
   157  		c.Assert(err, ErrorMatches, badVal.err)
   158  	}
   159  
   160  	c.Check(s.systemctlArgs, HasLen, 0)
   161  }
   162  
   163  func (s *watchdogSuite) TestConfigureWatchdogNoFileUpdate(c *C) {
   164  	err := os.MkdirAll(dirs.SnapSystemdConfDir, 0755)
   165  	c.Assert(err, IsNil)
   166  	times := []int{10, 100}
   167  	content := "[Manager]\n" +
   168  		fmt.Sprintf("RuntimeWatchdogSec=%d\n", times[0]) +
   169  		fmt.Sprintf("ShutdownWatchdogSec=%d\n", times[1])
   170  	err = ioutil.WriteFile(s.mockEtcEnvironment, []byte(content), 0644)
   171  	c.Assert(err, IsNil)
   172  
   173  	info, err := os.Stat(s.mockEtcEnvironment)
   174  	c.Assert(err, IsNil)
   175  
   176  	fileModTime := info.ModTime()
   177  
   178  	// To make sure the times will defer if the file is newly written
   179  	time.Sleep(100 * time.Millisecond)
   180  
   181  	err = configcore.Run(coreDev, &mockConf{
   182  		state: s.state,
   183  		conf: map[string]interface{}{
   184  			"watchdog.runtime-timeout":  fmt.Sprintf("%ds", times[0]),
   185  			"watchdog.shutdown-timeout": fmt.Sprintf("%ds", times[1]),
   186  		},
   187  	})
   188  	c.Assert(err, IsNil)
   189  	c.Check(s.mockEtcEnvironment, testutil.FileEquals, content)
   190  
   191  	info, err = os.Stat(s.mockEtcEnvironment)
   192  	c.Assert(err, IsNil)
   193  	c.Assert(info.ModTime(), Equals, fileModTime)
   194  
   195  	c.Check(s.systemctlArgs, HasLen, 0)
   196  }
   197  
   198  func (s *watchdogSuite) TestConfigureWatchdogRemovesIfEmpty(c *C) {
   199  	err := os.MkdirAll(dirs.SnapSystemdConfDir, 0755)
   200  	c.Assert(err, IsNil)
   201  	// add canary to ensure we don't touch other files
   202  	canary := filepath.Join(dirs.SnapSystemdConfDir, "05-canary.conf")
   203  	err = ioutil.WriteFile(canary, nil, 0644)
   204  	c.Assert(err, IsNil)
   205  
   206  	content := `[Manager]
   207  RuntimeWatchdogSec=10
   208  ShutdownWatchdogSec=20
   209  `
   210  	err = ioutil.WriteFile(s.mockEtcEnvironment, []byte(content), 0644)
   211  	c.Assert(err, IsNil)
   212  
   213  	err = configcore.Run(coreDev, &mockConf{
   214  		state: s.state,
   215  		conf: map[string]interface{}{
   216  			"watchdog.runtime-timeout":  0,
   217  			"watchdog.shutdown-timeout": 0,
   218  		},
   219  	})
   220  	c.Assert(err, IsNil)
   221  
   222  	// ensure the file got deleted
   223  	c.Check(osutil.FileExists(s.mockEtcEnvironment), Equals, false)
   224  	// but the canary is still here
   225  	c.Check(osutil.FileExists(canary), Equals, true)
   226  
   227  	// apply defaults
   228  	c.Check(s.systemctlArgs, DeepEquals, [][]string{
   229  		{"daemon-reexec"},
   230  	})
   231  }
   232  
   233  func (s *watchdogSuite) TestFilesystemOnlyApply(c *C) {
   234  	conf := configcore.PlainCoreConfig(map[string]interface{}{
   235  		"watchdog.runtime-timeout": "4s",
   236  	})
   237  
   238  	tmpDir := c.MkDir()
   239  	c.Assert(configcore.FilesystemOnlyApply(coreDev, tmpDir, conf), IsNil)
   240  
   241  	watchdogCfg := filepath.Join(tmpDir, "/etc/systemd/system.conf.d/10-snapd-watchdog.conf")
   242  	c.Check(watchdogCfg, testutil.FileEquals, "[Manager]\nRuntimeWatchdogSec=4\n")
   243  }
   244  
   245  func (s *watchdogSuite) TestFilesystemOnlyApplyValidationFails(c *C) {
   246  	conf := configcore.PlainCoreConfig(map[string]interface{}{
   247  		"watchdog.runtime-timeout": "foo",
   248  	})
   249  
   250  	tmpDir := c.MkDir()
   251  	c.Assert(configcore.FilesystemOnlyApply(coreDev, tmpDir, conf), ErrorMatches, `cannot parse "foo": time: invalid duration \"?foo\"?`)
   252  }