gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/servicestate/servicemgr_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 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 servicestate_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/asserts"
    32  	"github.com/snapcore/snapd/asserts/assertstest"
    33  	"github.com/snapcore/snapd/dirs"
    34  	"github.com/snapcore/snapd/logger"
    35  	"github.com/snapcore/snapd/overlord"
    36  	"github.com/snapcore/snapd/overlord/configstate/config"
    37  	"github.com/snapcore/snapd/overlord/servicestate"
    38  	"github.com/snapcore/snapd/overlord/snapstate"
    39  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    40  	"github.com/snapcore/snapd/overlord/state"
    41  	"github.com/snapcore/snapd/snap"
    42  	"github.com/snapcore/snapd/snap/snaptest"
    43  	"github.com/snapcore/snapd/systemd"
    44  	"github.com/snapcore/snapd/testutil"
    45  	"github.com/snapcore/snapd/wrappers"
    46  )
    47  
    48  type baseServiceMgrTestSuite struct {
    49  	testutil.BaseTest
    50  
    51  	mgr *servicestate.ServiceManager
    52  
    53  	o     *overlord.Overlord
    54  	se    *overlord.StateEngine
    55  	state *state.State
    56  
    57  	restartRequests []state.RestartType
    58  	restartObserve  func()
    59  
    60  	uc18Model *asserts.Model
    61  	uc16Model *asserts.Model
    62  
    63  	testSnapState    *snapstate.SnapState
    64  	testSnapSideInfo *snap.SideInfo
    65  }
    66  
    67  func (s *baseServiceMgrTestSuite) SetUpTest(c *C) {
    68  	s.BaseTest.SetUpTest(c)
    69  
    70  	dirs.SetRootDir(c.MkDir())
    71  	s.AddCleanup(func() { dirs.SetRootDir("") })
    72  
    73  	s.restartRequests = nil
    74  
    75  	s.restartObserve = nil
    76  	s.o = overlord.MockWithStateAndRestartHandler(nil, func(req state.RestartType) {
    77  		s.restartRequests = append(s.restartRequests, req)
    78  		if s.restartObserve != nil {
    79  			s.restartObserve()
    80  		}
    81  	})
    82  
    83  	s.state = s.o.State()
    84  	s.state.Lock()
    85  	s.state.VerifyReboot("boot-id-0")
    86  	s.state.Unlock()
    87  	s.se = s.o.StateEngine()
    88  
    89  	s.mgr = servicestate.Manager(s.state, s.o.TaskRunner())
    90  	s.o.AddManager(s.mgr)
    91  	s.o.AddManager(s.o.TaskRunner())
    92  
    93  	err := s.o.StartUp()
    94  	c.Assert(err, IsNil)
    95  
    96  	// by default we are seeded
    97  	s.state.Lock()
    98  	s.state.Set("seeded", true)
    99  	s.state.Unlock()
   100  
   101  	s.uc18Model = assertstest.FakeAssertion(map[string]interface{}{
   102  		"type":         "model",
   103  		"authority-id": "canonical",
   104  		"series":       "16",
   105  		"brand-id":     "canonical",
   106  		"model":        "pc",
   107  		"gadget":       "pc",
   108  		"kernel":       "kernel",
   109  		"architecture": "amd64",
   110  		"base":         "core18",
   111  	}).(*asserts.Model)
   112  
   113  	s.uc16Model = assertstest.FakeAssertion(map[string]interface{}{
   114  		"type":         "model",
   115  		"authority-id": "canonical",
   116  		"series":       "16",
   117  		"brand-id":     "canonical",
   118  		"model":        "pc",
   119  		"gadget":       "pc",
   120  		"kernel":       "kernel",
   121  		"architecture": "amd64",
   122  		// no base
   123  	}).(*asserts.Model)
   124  
   125  	// by default mock that we are uc18
   126  	s.AddCleanup(snapstatetest.MockDeviceModel(s.uc18Model))
   127  
   128  	// setup a test-snap with a service that can be easily injected into
   129  	// snapstate to be setup as needed
   130  	s.testSnapSideInfo = &snap.SideInfo{RealName: "test-snap", Revision: snap.R(42)}
   131  	s.testSnapState = &snapstate.SnapState{
   132  		Sequence: []*snap.SideInfo{s.testSnapSideInfo},
   133  		Current:  snap.R(42),
   134  		Active:   true,
   135  		SnapType: "app",
   136  	}
   137  }
   138  
   139  type expectedSystemctl struct {
   140  	expArgs []string
   141  	output  string
   142  	err     error
   143  }
   144  
   145  type ensureSnapServiceSuite struct {
   146  	baseServiceMgrTestSuite
   147  }
   148  
   149  var (
   150  	unitTempl = `[Unit]
   151  # Auto-generated, DO NOT EDIT
   152  Description=Service for snap application test-snap.svc1
   153  Requires=%[1]s
   154  Wants=network.target
   155  After=%[1]s network.target snapd.apparmor.service
   156  %[3]sX-Snappy=yes
   157  
   158  [Service]
   159  EnvironmentFile=-/etc/environment
   160  ExecStart=/usr/bin/snap run test-snap.svc1
   161  SyslogIdentifier=test-snap.svc1
   162  Restart=on-failure
   163  WorkingDirectory=%[2]s/var/snap/test-snap/42
   164  TimeoutStopSec=30
   165  Type=simple
   166  %[4]s
   167  [Install]
   168  WantedBy=multi-user.target
   169  `
   170  
   171  	testYaml = `name: test-snap
   172  version: v1
   173  apps:
   174    svc1:
   175      command: bin.sh
   176      daemon: simple
   177  `
   178  	testYaml2 = `name: test-snap2
   179  version: v1
   180  apps:
   181    svc1:
   182      command: bin.sh
   183      daemon: simple
   184  `
   185  
   186  	systemdTimeFormat = "Mon 2006-01-02 15:04:05 MST"
   187  )
   188  
   189  type unitOptions struct {
   190  	usrLibSnapdOrderVerb string
   191  	snapName             string
   192  	snapRev              string
   193  	oomScore             string
   194  }
   195  
   196  func mkUnitFile(c *C, opts *unitOptions) string {
   197  	if opts == nil {
   198  		opts = &unitOptions{}
   199  	}
   200  	usrLibSnapdSnippet := ""
   201  	if opts.usrLibSnapdOrderVerb != "" {
   202  		usrLibSnapdSnippet = fmt.Sprintf(`%[1]s=usr-lib-snapd.mount
   203  After=usr-lib-snapd.mount
   204  `,
   205  			opts.usrLibSnapdOrderVerb)
   206  	}
   207  	oomScoreAdjust := ""
   208  	if opts.oomScore != "" {
   209  		oomScoreAdjust = fmt.Sprintf(`OOMScoreAdjust=%s
   210  `,
   211  			opts.oomScore,
   212  		)
   213  	}
   214  
   215  	return fmt.Sprintf(unitTempl,
   216  		systemd.EscapeUnitNamePath(filepath.Join(dirs.SnapMountDir, opts.snapName, opts.snapRev+".mount")),
   217  		dirs.GlobalRootDir,
   218  		usrLibSnapdSnippet,
   219  		oomScoreAdjust,
   220  	)
   221  }
   222  
   223  var _ = Suite(&ensureSnapServiceSuite{})
   224  
   225  func (s *baseServiceMgrTestSuite) mockSystemctlCalls(c *C, expCalls []expectedSystemctl) (restore func()) {
   226  	allSystemctlCalls := [][]string{}
   227  	r := systemd.MockSystemctl(func(args ...string) ([]byte, error) {
   228  		systemctlCalls := len(allSystemctlCalls)
   229  		allSystemctlCalls = append(allSystemctlCalls, args)
   230  		if systemctlCalls < len(expCalls) {
   231  			res := expCalls[systemctlCalls]
   232  			c.Check(args, DeepEquals, res.expArgs)
   233  			return []byte(res.output), res.err
   234  		}
   235  		c.Errorf("unexpected and unhandled systemctl command: %+v", args)
   236  		return nil, fmt.Errorf("broken test")
   237  	})
   238  
   239  	return func() {
   240  		r()
   241  		// double-check at the end of the test that we got as many systemctl calls
   242  		// as were mocked and that we didn't get less, then re-set it for the next
   243  		// test
   244  		expArgCalls := make([][]string, 0, len(expCalls))
   245  		for _, call := range expCalls {
   246  			expArgCalls = append(expArgCalls, call.expArgs)
   247  		}
   248  		c.Assert(allSystemctlCalls, DeepEquals, expArgCalls)
   249  	}
   250  }
   251  
   252  func (s *ensureSnapServiceSuite) SetUpTest(c *C) {
   253  	s.baseServiceMgrTestSuite.SetUpTest(c)
   254  }
   255  
   256  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesNoSnapsDoesNothing(c *C) {
   257  	// don't mock any snaps in snapstate
   258  	err := s.mgr.Ensure()
   259  	c.Assert(err, IsNil)
   260  
   261  	// we didn't write any services
   262  	c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service"), testutil.FileAbsent)
   263  
   264  	// we did not request a restart
   265  	c.Assert(s.restartRequests, HasLen, 0)
   266  }
   267  
   268  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesNotSeeded(c *C) {
   269  	s.state.Lock()
   270  	// we are not seeded but we do have a service which needs to be generated
   271  	s.state.Set("seeded", false)
   272  	snapstate.Set(s.state, "test-snap", s.testSnapState)
   273  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
   274  	s.state.Unlock()
   275  
   276  	err := s.mgr.Ensure()
   277  	c.Assert(err, IsNil)
   278  
   279  	// we didn't write any services
   280  	c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service"), testutil.FileAbsent)
   281  
   282  	// we did not request a restart
   283  	c.Assert(s.restartRequests, HasLen, 0)
   284  }
   285  
   286  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesSimpleWritesServicesFilesUC16(c *C) {
   287  	s.state.Lock()
   288  	// there is a snap in snap state that needs a service generated for it
   289  	snapstate.Set(s.state, "test-snap", s.testSnapState)
   290  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
   291  	// mock the device context as uc16
   292  	s.AddCleanup(snapstatetest.MockDeviceModel(s.uc16Model))
   293  
   294  	s.state.Unlock()
   295  
   296  	// don't add a usr-lib-snapd.mount unit since we won't read it, since we are
   297  	// on uc16
   298  
   299  	// we will only trigger a daemon-reload once after generating the service
   300  	// file
   301  	r := s.mockSystemctlCalls(c, []expectedSystemctl{
   302  		{
   303  			expArgs: []string{"daemon-reload"},
   304  		},
   305  	})
   306  	defer r()
   307  
   308  	err := s.mgr.Ensure()
   309  	c.Assert(err, IsNil)
   310  
   311  	// we wrote a service unit file
   312  	content := mkUnitFile(c, &unitOptions{
   313  		snapName: "test-snap",
   314  		snapRev:  "42",
   315  	})
   316  	c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service"), testutil.FileEquals, content)
   317  
   318  	// we did not request a restart
   319  	c.Assert(s.restartRequests, HasLen, 0)
   320  }
   321  
   322  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesSkipsSnapdSnap(c *C) {
   323  	s.state.Lock()
   324  	// add an unexpected snapd snap which has services in it, but we
   325  	// specifically skip the snapd snap when considering services to add since
   326  	// it is special
   327  	sideInfo := &snap.SideInfo{RealName: "snapd", Revision: snap.R(42)}
   328  	snapstate.Set(s.state, "snapd", &snapstate.SnapState{
   329  		Sequence: []*snap.SideInfo{sideInfo},
   330  		Current:  snap.R(42),
   331  		Active:   true,
   332  		SnapType: string(snap.TypeSnapd),
   333  	})
   334  	snaptest.MockSnapCurrent(c, `name: snapd
   335  type: snapd
   336  version: v1
   337  apps:
   338    svc1:
   339      command: bin.sh
   340      daemon: simple
   341  `, sideInfo)
   342  
   343  	s.state.Unlock()
   344  
   345  	// don't need to mock usr-lib-snapd.mount since we will skip before that
   346  	// with snapd as the only snap
   347  
   348  	err := s.mgr.Ensure()
   349  	c.Assert(err, IsNil)
   350  
   351  	// we didn't write a snap service file for snapd
   352  	c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.snapd.svc1.service"), testutil.FileAbsent)
   353  
   354  	// we did not request a restart
   355  	c.Assert(s.restartRequests, HasLen, 0)
   356  }
   357  
   358  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesUC18(c *C) {
   359  	s.state.Lock()
   360  	// there is a snap in snap state that needs a service generated for it
   361  	snapstate.Set(s.state, "test-snap", s.testSnapState)
   362  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
   363  
   364  	s.state.Unlock()
   365  
   366  	// add the usr-lib-snapd.mount unit
   367  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
   368  	c.Assert(err, IsNil)
   369  	usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit)
   370  	err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644)
   371  	c.Assert(err, IsNil)
   372  
   373  	r := s.mockSystemctlCalls(c, []expectedSystemctl{
   374  		{
   375  			expArgs: []string{"daemon-reload"},
   376  		},
   377  	})
   378  	defer r()
   379  
   380  	err = s.mgr.Ensure()
   381  	c.Assert(err, IsNil)
   382  
   383  	// we wrote the service unit file
   384  	content := mkUnitFile(c, &unitOptions{
   385  		usrLibSnapdOrderVerb: "Wants",
   386  		snapName:             "test-snap",
   387  		snapRev:              "42",
   388  	})
   389  	c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service"), testutil.FileEquals, content)
   390  
   391  	// we did not request a restart
   392  	c.Assert(s.restartRequests, HasLen, 0)
   393  }
   394  
   395  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesVitalityRankUC18(c *C) {
   396  	s.state.Lock()
   397  	// there is a snap in snap state that needs a service generated for it
   398  	snapstate.Set(s.state, "test-snap", s.testSnapState)
   399  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
   400  
   401  	// also set vitality-hint for this snap
   402  	t := config.NewTransaction(s.state)
   403  	err := t.Set("core", "resilience.vitality-hint", "bar,test-snap")
   404  	c.Assert(err, IsNil)
   405  	t.Commit()
   406  
   407  	s.state.Unlock()
   408  
   409  	// add the usr-lib-snapd.mount unit
   410  	err = os.MkdirAll(dirs.SnapServicesDir, 0755)
   411  	c.Assert(err, IsNil)
   412  	usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit)
   413  	err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644)
   414  	c.Assert(err, IsNil)
   415  
   416  	r := s.mockSystemctlCalls(c, []expectedSystemctl{
   417  		{
   418  			expArgs: []string{"daemon-reload"},
   419  		},
   420  	})
   421  	defer r()
   422  
   423  	err = s.mgr.Ensure()
   424  	c.Assert(err, IsNil)
   425  
   426  	// we wrote the service unit file
   427  	content := mkUnitFile(c, &unitOptions{
   428  		usrLibSnapdOrderVerb: "Wants",
   429  		snapName:             "test-snap",
   430  		snapRev:              "42",
   431  		oomScore:             "-898",
   432  	})
   433  	c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service"), testutil.FileEquals, content)
   434  
   435  	// we did not request a restart
   436  	c.Assert(s.restartRequests, HasLen, 0)
   437  }
   438  
   439  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesAndDoesNotRestartIfBootTimeAfterModTime(c *C) {
   440  	s.state.Lock()
   441  	// there is a snap in snap state that needs a service generated for it
   442  	snapstate.Set(s.state, "test-snap", s.testSnapState)
   443  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
   444  
   445  	s.state.Unlock()
   446  
   447  	// add the usr-lib-snapd.mount unit
   448  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
   449  	c.Assert(err, IsNil)
   450  	usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit)
   451  	err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644)
   452  	c.Assert(err, IsNil)
   453  
   454  	now := time.Now()
   455  	err = os.Chtimes(usrLibSnapdMountFile, now, now)
   456  	c.Assert(err, IsNil)
   457  
   458  	logbuf, r := logger.MockLogger()
   459  	defer r()
   460  
   461  	// TZ's are important for the boot time specifically, we need to output the
   462  	// UTC time from the uptime script below, otherwise using local time here
   463  	// but not elsewhere leads to errors
   464  	future := now.Add(30 * time.Minute).UTC()
   465  
   466  	// we won't try to start services if the current boot time is ahead of the
   467  	// modification time
   468  
   469  	// mock the uptime command
   470  	cmd := testutil.MockCommand(c, "uptime", fmt.Sprintf(`
   471  #!/bin/sh
   472  
   473  if [ "$TZ" != "UTC" ]; then
   474  	echo "unexpected TZ env value: $TZ (expected UTC)"
   475  	exit 1
   476  fi
   477  
   478  if [ "$*" != "-s" ]; then
   479  	echo "arguments $* were unexpected"
   480  	exit 1
   481  fi
   482  
   483  echo %[1]q
   484  `, future.Format("2006-01-02 15:04:05")))
   485  	defer cmd.Restore()
   486  
   487  	svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service")
   488  
   489  	// add the initial state of the service file using Requires
   490  	requiresContent := mkUnitFile(c, &unitOptions{
   491  		usrLibSnapdOrderVerb: "Requires",
   492  		snapName:             "test-snap",
   493  		snapRev:              "42",
   494  	})
   495  	err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644)
   496  	c.Assert(err, IsNil)
   497  
   498  	r = s.mockSystemctlCalls(c, []expectedSystemctl{
   499  		{
   500  			expArgs: []string{"daemon-reload"},
   501  		},
   502  	})
   503  	defer r()
   504  
   505  	err = s.mgr.Ensure()
   506  	c.Assert(err, IsNil)
   507  
   508  	// we wrote the service unit file
   509  	content := mkUnitFile(c, &unitOptions{
   510  		usrLibSnapdOrderVerb: "Wants",
   511  		snapName:             "test-snap",
   512  		snapRev:              "42",
   513  	})
   514  	c.Assert(svcFile, testutil.FileEquals, content)
   515  
   516  	c.Assert(cmd.Calls(), DeepEquals, [][]string{
   517  		{"uptime", "-s"},
   518  	})
   519  
   520  	c.Assert(logbuf.String(), Equals, "")
   521  
   522  	// we did not request a restart
   523  	c.Assert(s.restartRequests, HasLen, 0)
   524  }
   525  
   526  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesAndIgnoresBootTimeErrors(c *C) {
   527  	s.state.Lock()
   528  	// there is a snap in snap state that needs a service generated for it
   529  	snapstate.Set(s.state, "test-snap", s.testSnapState)
   530  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
   531  
   532  	s.state.Unlock()
   533  
   534  	// add the usr-lib-snapd.mount unit
   535  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
   536  	c.Assert(err, IsNil)
   537  	usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit)
   538  	err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644)
   539  	c.Assert(err, IsNil)
   540  
   541  	now := time.Now()
   542  	err = os.Chtimes(usrLibSnapdMountFile, now, now)
   543  	c.Assert(err, IsNil)
   544  
   545  	// if the boot time can't be determined, we log a message and continue on
   546  	// considering whether or not the service should be restarted based on when
   547  	// it exited
   548  	cmd := testutil.MockCommand(c, "uptime", `
   549  #!/bin/sh
   550  echo "boot time broken"
   551  exit 1
   552  `)
   553  	defer cmd.Restore()
   554  
   555  	logbuf, r := logger.MockLogger()
   556  	defer r()
   557  
   558  	svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service")
   559  
   560  	// add the initial state of the service file using Requires
   561  	requiresContent := mkUnitFile(c, &unitOptions{
   562  		usrLibSnapdOrderVerb: "Requires",
   563  		snapName:             "test-snap",
   564  		snapRev:              "42",
   565  	})
   566  	err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644)
   567  	c.Assert(err, IsNil)
   568  
   569  	r = s.mockSystemctlCalls(c, []expectedSystemctl{
   570  		{
   571  			expArgs: []string{"daemon-reload"},
   572  		},
   573  		{
   574  			// usr-lib-snapd.mount has never been stopped though so we skip out
   575  			// anyways
   576  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"},
   577  			output:  "InactiveEnterTimestamp=",
   578  		},
   579  	})
   580  	defer r()
   581  
   582  	err = s.mgr.Ensure()
   583  	c.Assert(err, IsNil)
   584  
   585  	// we wrote the service unit file
   586  	content := mkUnitFile(c, &unitOptions{
   587  		usrLibSnapdOrderVerb: "Wants",
   588  		snapName:             "test-snap",
   589  		snapRev:              "42",
   590  	})
   591  	c.Assert(svcFile, testutil.FileEquals, content)
   592  
   593  	c.Assert(cmd.Calls(), DeepEquals, [][]string{
   594  		{"uptime", "-s"},
   595  	})
   596  
   597  	c.Assert(logbuf.String(), Matches, ".*error getting boot time: boot time broken\n")
   598  
   599  	// we did not request a restart
   600  	c.Assert(s.restartRequests, HasLen, 0)
   601  }
   602  
   603  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesAndRestarts(c *C) {
   604  	s.state.Lock()
   605  	// there is a snap in snap state that needs a service generated for it
   606  	snapstate.Set(s.state, "test-snap", s.testSnapState)
   607  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
   608  
   609  	s.state.Unlock()
   610  
   611  	// add the usr-lib-snapd.mount unit
   612  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
   613  	c.Assert(err, IsNil)
   614  	usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit)
   615  	err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644)
   616  	c.Assert(err, IsNil)
   617  
   618  	now := time.Now()
   619  	err = os.Chtimes(usrLibSnapdMountFile, now, now)
   620  	c.Assert(err, IsNil)
   621  
   622  	svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service")
   623  
   624  	// add the initial state of the service file using Requires
   625  	requiresContent := mkUnitFile(c, &unitOptions{
   626  		usrLibSnapdOrderVerb: "Requires",
   627  		snapName:             "test-snap",
   628  		snapRev:              "42",
   629  	})
   630  	err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644)
   631  	c.Assert(err, IsNil)
   632  
   633  	slightFuture := now.Add(30 * time.Minute).Format(systemdTimeFormat)
   634  	theFuture := now.Add(1 * time.Hour).Format(systemdTimeFormat)
   635  
   636  	r := s.mockSystemctlCalls(c, []expectedSystemctl{
   637  		{
   638  			expArgs: []string{"daemon-reload"},
   639  		},
   640  		{
   641  			// usr-lib-snapd.mount was stopped "far in the future"
   642  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"},
   643  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", theFuture),
   644  		},
   645  		{
   646  			// but the snap.test-snap.svc1 was stopped only slightly in the
   647  			// future (hence before the usr-lib-snapd.mount unit was stopped and
   648  			// after usr-lib-snapd.mount file was modified)
   649  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"},
   650  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", slightFuture),
   651  		},
   652  		{
   653  			expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"},
   654  			output:  "enabled",
   655  		},
   656  		{
   657  			expArgs: []string{"start", "snap.test-snap.svc1.service"},
   658  		},
   659  	})
   660  	defer r()
   661  
   662  	err = s.mgr.Ensure()
   663  	c.Assert(err, IsNil)
   664  
   665  	// we wrote the service unit file
   666  	content := mkUnitFile(c, &unitOptions{
   667  		usrLibSnapdOrderVerb: "Wants",
   668  		snapName:             "test-snap",
   669  		snapRev:              "42",
   670  	})
   671  	c.Assert(svcFile, testutil.FileEquals, content)
   672  
   673  	// we did not request a restart
   674  	c.Assert(s.restartRequests, HasLen, 0)
   675  }
   676  
   677  type systemctlDisabledServiceError struct{}
   678  
   679  func (s systemctlDisabledServiceError) Msg() []byte   { return []byte("disabled") }
   680  func (s systemctlDisabledServiceError) ExitCode() int { return 1 }
   681  func (s systemctlDisabledServiceError) Error() string { return "disabled service" }
   682  
   683  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesButDoesNotRestartDisabledServices(c *C) {
   684  	s.state.Lock()
   685  	// there is a snap in snap state that needs a service generated for it
   686  	snapstate.Set(s.state, "test-snap", s.testSnapState)
   687  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
   688  
   689  	s.state.Unlock()
   690  
   691  	// add the usr-lib-snapd.mount unit
   692  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
   693  	c.Assert(err, IsNil)
   694  	usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit)
   695  	err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644)
   696  	c.Assert(err, IsNil)
   697  
   698  	now := time.Now()
   699  	err = os.Chtimes(usrLibSnapdMountFile, now, now)
   700  	c.Assert(err, IsNil)
   701  
   702  	svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service")
   703  
   704  	// add the initial state of the service file using Requires
   705  	requiresContent := mkUnitFile(c, &unitOptions{
   706  		usrLibSnapdOrderVerb: "Requires",
   707  		snapName:             "test-snap",
   708  		snapRev:              "42",
   709  	})
   710  	err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644)
   711  	c.Assert(err, IsNil)
   712  
   713  	slightFuture := now.Add(30 * time.Minute).Format(systemdTimeFormat)
   714  	theFuture := now.Add(1 * time.Hour).Format(systemdTimeFormat)
   715  
   716  	r := s.mockSystemctlCalls(c, []expectedSystemctl{
   717  		{
   718  			expArgs: []string{"daemon-reload"},
   719  		},
   720  		{
   721  			// usr-lib-snapd.mount was stopped "far in the future"
   722  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"},
   723  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", theFuture),
   724  		},
   725  		{
   726  			// but the snap.test-snap.svc1 was stopped only slightly in the
   727  			// future (hence before the usr-lib-snapd.mount unit was stopped and
   728  			// after usr-lib-snapd.mount file was modified)
   729  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"},
   730  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", slightFuture),
   731  		},
   732  		// the service is disabled
   733  		{
   734  			expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"},
   735  			output:  "disabled",
   736  			err:     systemctlDisabledServiceError{},
   737  		},
   738  		// then we don't restart the service even though it was killed
   739  	})
   740  	defer r()
   741  
   742  	err = s.mgr.Ensure()
   743  	c.Assert(err, IsNil)
   744  
   745  	// we wrote the service unit file
   746  	content := mkUnitFile(c, &unitOptions{
   747  		usrLibSnapdOrderVerb: "Wants",
   748  		snapName:             "test-snap",
   749  		snapRev:              "42",
   750  	})
   751  	c.Assert(svcFile, testutil.FileEquals, content)
   752  
   753  	// we did not request a restart
   754  	c.Assert(s.restartRequests, HasLen, 0)
   755  }
   756  
   757  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesDoesNotRestartServicesKilledBeforeSnapdRefresh(c *C) {
   758  	s.state.Lock()
   759  	// there is a snap in snap state that needs a service generated for it
   760  	snapstate.Set(s.state, "test-snap", s.testSnapState)
   761  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
   762  
   763  	s.state.Unlock()
   764  
   765  	// add the usr-lib-snapd.mount unit
   766  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
   767  	c.Assert(err, IsNil)
   768  	usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit)
   769  	err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644)
   770  	c.Assert(err, IsNil)
   771  
   772  	now := time.Now()
   773  	err = os.Chtimes(usrLibSnapdMountFile, now, now)
   774  	c.Assert(err, IsNil)
   775  
   776  	svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service")
   777  
   778  	// add the initial state of the service file using Requires
   779  	requiresContent := mkUnitFile(c, &unitOptions{
   780  		usrLibSnapdOrderVerb: "Requires",
   781  		snapName:             "test-snap",
   782  		snapRev:              "42",
   783  	})
   784  	err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644)
   785  	c.Assert(err, IsNil)
   786  
   787  	theFuture := now.Add(1 * time.Hour).Format(systemdTimeFormat)
   788  	thePast := now.Add(-30 * time.Minute).Format(systemdTimeFormat)
   789  
   790  	r := s.mockSystemctlCalls(c, []expectedSystemctl{
   791  		{
   792  			expArgs: []string{"daemon-reload"},
   793  		},
   794  		{
   795  			// usr-lib-snapd.mount was stopped "far in the future"
   796  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"},
   797  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", theFuture),
   798  		},
   799  		{
   800  			// but the snap.test-snap.svc1 was stopped before that, so it isn't
   801  			// restarted
   802  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"},
   803  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", thePast),
   804  		},
   805  	})
   806  	defer r()
   807  
   808  	err = s.mgr.Ensure()
   809  	c.Assert(err, IsNil)
   810  
   811  	// we wrote the service unit file
   812  	content := mkUnitFile(c, &unitOptions{
   813  		usrLibSnapdOrderVerb: "Wants",
   814  		snapName:             "test-snap",
   815  		snapRev:              "42",
   816  	})
   817  	c.Assert(svcFile, testutil.FileEquals, content)
   818  
   819  	// we did not request a restart
   820  	c.Assert(s.restartRequests, HasLen, 0)
   821  }
   822  
   823  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesDoesNotRestartServicesKilledAfterSnapdRefresh(c *C) {
   824  	s.state.Lock()
   825  	// there is a snap in snap state that needs a service generated for it
   826  	snapstate.Set(s.state, "test-snap", s.testSnapState)
   827  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
   828  
   829  	s.state.Unlock()
   830  
   831  	// add the usr-lib-snapd.mount unit
   832  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
   833  	c.Assert(err, IsNil)
   834  	usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit)
   835  	err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644)
   836  	c.Assert(err, IsNil)
   837  
   838  	now := time.Now()
   839  	err = os.Chtimes(usrLibSnapdMountFile, now, now)
   840  	c.Assert(err, IsNil)
   841  
   842  	svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service")
   843  
   844  	// add the initial state of the service file using Requires
   845  	requiresContent := mkUnitFile(c, &unitOptions{
   846  		usrLibSnapdOrderVerb: "Requires",
   847  		snapName:             "test-snap",
   848  		snapRev:              "42",
   849  	})
   850  	err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644)
   851  	c.Assert(err, IsNil)
   852  
   853  	theFuture := now.Add(1 * time.Hour).Format(systemdTimeFormat)
   854  	thePast := now.Add(-30 * time.Minute).Format(systemdTimeFormat)
   855  
   856  	r := s.mockSystemctlCalls(c, []expectedSystemctl{
   857  		{
   858  			expArgs: []string{"daemon-reload"},
   859  		},
   860  		{
   861  			// usr-lib-snapd.mount was stopped in the past
   862  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"},
   863  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", thePast),
   864  		},
   865  		{
   866  			// but the snap.test-snap.svc1 was stopped after that, so it isn't
   867  			// restarted
   868  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"},
   869  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", theFuture),
   870  		},
   871  	})
   872  	defer r()
   873  
   874  	err = s.mgr.Ensure()
   875  	c.Assert(err, IsNil)
   876  
   877  	// we wrote the service unit file
   878  	content := mkUnitFile(c, &unitOptions{
   879  		usrLibSnapdOrderVerb: "Wants",
   880  		snapName:             "test-snap",
   881  		snapRev:              "42",
   882  	})
   883  	c.Assert(svcFile, testutil.FileEquals, content)
   884  
   885  	// we did not request a restart
   886  	c.Assert(s.restartRequests, HasLen, 0)
   887  }
   888  
   889  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesSimpleRewritesServicesFilesAndRestartsTimePrecisionSilly(c *C) {
   890  	s.state.Lock()
   891  	// there is a snap in snap state that needs a service generated for it
   892  	snapstate.Set(s.state, "test-snap", s.testSnapState)
   893  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
   894  
   895  	s.state.Unlock()
   896  
   897  	// add the usr-lib-snapd.mount unit
   898  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
   899  	c.Assert(err, IsNil)
   900  	usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit)
   901  	err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644)
   902  	c.Assert(err, IsNil)
   903  
   904  	// this test is about the specific scenario we have now when using systemctl
   905  	// show --property where the time precision of the InactiveEnterTimestamp's
   906  	// is much lower than that of the modification file time, so we need to
   907  	// set the inactive enter time for both the usr-lib-snapd.mount and the snap
   908  	// service to be the same time, which is actually _in the past_ compared to
   909  	// the file modification time
   910  
   911  	// truncate the current time and add 500 milliseconds
   912  	t0 := time.Now().Truncate(time.Second).Add(500 * time.Millisecond)
   913  	err = os.Chtimes(usrLibSnapdMountFile, t0, t0)
   914  	c.Assert(err, IsNil)
   915  
   916  	// drop the milliseconds
   917  	t1 := t0.Truncate(time.Second)
   918  	t1Str := t1.Format(systemdTimeFormat)
   919  
   920  	// double check our math for the times is correct
   921  	c.Assert(t1.Before(t0), Equals, true)
   922  	c.Assert(t0.After(t1), Equals, true)
   923  	c.Assert(t1.Equal(t0), Equals, false)
   924  
   925  	svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service")
   926  
   927  	// add the initial state of the service file using Requires
   928  	requiresContent := mkUnitFile(c, &unitOptions{
   929  		usrLibSnapdOrderVerb: "Requires",
   930  		snapName:             "test-snap",
   931  		snapRev:              "42",
   932  	})
   933  	err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644)
   934  	c.Assert(err, IsNil)
   935  
   936  	r := s.mockSystemctlCalls(c, []expectedSystemctl{
   937  		{
   938  			expArgs: []string{"daemon-reload"},
   939  		},
   940  		{
   941  			// usr-lib-snapd.mount was stopped "far in the future"
   942  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"},
   943  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", t1Str),
   944  		},
   945  		{
   946  			// but the snap.test-snap.svc1 was stopped only slightly in the
   947  			// future
   948  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"},
   949  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", t1Str),
   950  		},
   951  		{
   952  			expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"},
   953  			output:  "enabled",
   954  		},
   955  		{
   956  			expArgs: []string{"start", "snap.test-snap.svc1.service"},
   957  		},
   958  	})
   959  	defer r()
   960  
   961  	err = s.mgr.Ensure()
   962  	c.Assert(err, IsNil)
   963  
   964  	// the file was rewritten to use Wants instead now
   965  	wantsContent := mkUnitFile(c, &unitOptions{
   966  		usrLibSnapdOrderVerb: "Wants",
   967  		snapName:             "test-snap",
   968  		snapRev:              "42",
   969  	})
   970  	c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service"), testutil.FileEquals, wantsContent)
   971  
   972  	// we did not request a restart
   973  	c.Assert(s.restartRequests, HasLen, 0)
   974  }
   975  
   976  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesSimpleRewritesServicesFilesAndRestartsTimePrecisionMoreSilly(c *C) {
   977  	s.state.Lock()
   978  	// there is a snap in snap state that needs a service generated for it
   979  	snapstate.Set(s.state, "test-snap", s.testSnapState)
   980  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
   981  
   982  	s.state.Unlock()
   983  
   984  	// add the usr-lib-snapd.mount unit
   985  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
   986  	c.Assert(err, IsNil)
   987  	usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit)
   988  	err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644)
   989  	c.Assert(err, IsNil)
   990  
   991  	// this test is like TestEnsureSnapServicesSimpleRewritesServicesFilesAndRestartsTimePrecisionSilly,
   992  	// but more extreme, in that we don't have precision problems of less than a
   993  	// second, we have some more critical error where the lower timestamp range
   994  	// is somehow way in the future and we want our system to act rationally and
   995  	// pick the upper time bound as the lower time bound when the initially
   996  	// identified lower time bound is nonsensical
   997  
   998  	// truncate the current time and add 500 minutes
   999  	now := time.Now().Truncate(time.Second)
  1000  	t0 := now.Add(500 * time.Minute)
  1001  	err = os.Chtimes(usrLibSnapdMountFile, t0, t0)
  1002  	c.Assert(err, IsNil)
  1003  
  1004  	t1 := now
  1005  	t1Str := t1.Format(systemdTimeFormat)
  1006  
  1007  	// double check our math for the times is correct
  1008  	c.Assert(t1.Before(t0), Equals, true)
  1009  	c.Assert(t0.After(t1), Equals, true)
  1010  	c.Assert(t1.Equal(t0), Equals, false)
  1011  
  1012  	svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service")
  1013  
  1014  	// add the initial state of the service file using Requires
  1015  	requiresContent := mkUnitFile(c, &unitOptions{
  1016  		usrLibSnapdOrderVerb: "Requires",
  1017  		snapName:             "test-snap",
  1018  		snapRev:              "42",
  1019  	})
  1020  	err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644)
  1021  	c.Assert(err, IsNil)
  1022  
  1023  	r := s.mockSystemctlCalls(c, []expectedSystemctl{
  1024  		{
  1025  			expArgs: []string{"daemon-reload"},
  1026  		},
  1027  		{
  1028  			// usr-lib-snapd.mount was stopped "far in the future"
  1029  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"},
  1030  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", t1Str),
  1031  		},
  1032  		{
  1033  			// but the snap.test-snap.svc1 was stopped only slightly in the
  1034  			// future
  1035  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"},
  1036  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", t1Str),
  1037  		},
  1038  		{
  1039  			expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"},
  1040  			output:  "enabled",
  1041  		},
  1042  		{
  1043  			expArgs: []string{"start", "snap.test-snap.svc1.service"},
  1044  		},
  1045  	})
  1046  	defer r()
  1047  
  1048  	err = s.mgr.Ensure()
  1049  	c.Assert(err, IsNil)
  1050  
  1051  	// the file was rewritten to use Wants instead now
  1052  	wantsContent := mkUnitFile(c, &unitOptions{
  1053  		usrLibSnapdOrderVerb: "Wants",
  1054  		snapName:             "test-snap",
  1055  		snapRev:              "42",
  1056  	})
  1057  	c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service"), testutil.FileEquals, wantsContent)
  1058  
  1059  	// we did not request a restart
  1060  	c.Assert(s.restartRequests, HasLen, 0)
  1061  }
  1062  
  1063  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesSimpleRewritesServicesFilesAndRestartsUC18(c *C) {
  1064  	s.state.Lock()
  1065  	// there is a snap in snap state that needs a service generated for it
  1066  	snapstate.Set(s.state, "test-snap", s.testSnapState)
  1067  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
  1068  
  1069  	s.state.Unlock()
  1070  
  1071  	// add the usr-lib-snapd.mount unit
  1072  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
  1073  	c.Assert(err, IsNil)
  1074  	usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit)
  1075  	err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644)
  1076  	c.Assert(err, IsNil)
  1077  
  1078  	now := time.Now()
  1079  	err = os.Chtimes(usrLibSnapdMountFile, now, now)
  1080  	c.Assert(err, IsNil)
  1081  
  1082  	slightFuture := now.Add(30 * time.Minute).Format(systemdTimeFormat)
  1083  	theFuture := now.Add(1 * time.Hour).Format(systemdTimeFormat)
  1084  
  1085  	svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service")
  1086  
  1087  	// add the initial state of the service file using Requires
  1088  	requiresContent := mkUnitFile(c, &unitOptions{
  1089  		usrLibSnapdOrderVerb: "Requires",
  1090  		snapName:             "test-snap",
  1091  		snapRev:              "42",
  1092  	})
  1093  	err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644)
  1094  	c.Assert(err, IsNil)
  1095  
  1096  	r := s.mockSystemctlCalls(c, []expectedSystemctl{
  1097  		{
  1098  			expArgs: []string{"daemon-reload"},
  1099  		},
  1100  		{
  1101  			// usr-lib-snapd.mount was stopped "far in the future"
  1102  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"},
  1103  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", theFuture),
  1104  		},
  1105  		{
  1106  			// but the snap.test-snap.svc1 was stopped only slightly in the
  1107  			// future
  1108  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"},
  1109  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", slightFuture),
  1110  		},
  1111  		{
  1112  			expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"},
  1113  			output:  "enabled",
  1114  		},
  1115  		{
  1116  			expArgs: []string{"start", "snap.test-snap.svc1.service"},
  1117  		},
  1118  	})
  1119  	defer r()
  1120  
  1121  	err = s.mgr.Ensure()
  1122  	c.Assert(err, IsNil)
  1123  
  1124  	// the file was rewritten to use Wants instead now
  1125  	wantsContent := mkUnitFile(c, &unitOptions{
  1126  		usrLibSnapdOrderVerb: "Wants",
  1127  		snapName:             "test-snap",
  1128  		snapRev:              "42",
  1129  	})
  1130  	c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service"), testutil.FileEquals, wantsContent)
  1131  
  1132  	// we did not request a restart
  1133  	c.Assert(s.restartRequests, HasLen, 0)
  1134  }
  1135  
  1136  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesNoChangeServiceFileDoesNothingUC18(c *C) {
  1137  	s.state.Lock()
  1138  	// there is a snap in snap state that needs a service generated for it
  1139  	snapstate.Set(s.state, "test-snap", s.testSnapState)
  1140  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
  1141  
  1142  	s.state.Unlock()
  1143  
  1144  	// add the usr-lib-snapd.mount unit
  1145  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
  1146  	c.Assert(err, IsNil)
  1147  	usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit)
  1148  	err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644)
  1149  	c.Assert(err, IsNil)
  1150  
  1151  	now := time.Now()
  1152  	err = os.Chtimes(usrLibSnapdMountFile, now, now)
  1153  	c.Assert(err, IsNil)
  1154  
  1155  	svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service")
  1156  
  1157  	// add the initial state of the service file using Wants
  1158  	content := mkUnitFile(c, &unitOptions{
  1159  		usrLibSnapdOrderVerb: "Wants",
  1160  		snapName:             "test-snap",
  1161  		snapRev:              "42",
  1162  	})
  1163  	err = ioutil.WriteFile(svcFile, []byte(content), 0644)
  1164  	c.Assert(err, IsNil)
  1165  
  1166  	// we don't use systemctl at all because we didn't change anything
  1167  	// s.systemctlReturns = []expectedSystemctl{}
  1168  
  1169  	err = s.mgr.Ensure()
  1170  	c.Assert(err, IsNil)
  1171  
  1172  	// the file was not modified
  1173  	c.Assert(svcFile, testutil.FileEquals, content)
  1174  
  1175  	// we did not request a restart
  1176  	c.Assert(s.restartRequests, HasLen, 0)
  1177  
  1178  }
  1179  
  1180  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesDoesNotRestartServicesWhenUsrLibSnapdWasNeverInactive(c *C) {
  1181  	s.state.Lock()
  1182  	// there is a snap in snap state that needs a service generated for it
  1183  	snapstate.Set(s.state, "test-snap", s.testSnapState)
  1184  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
  1185  
  1186  	s.state.Unlock()
  1187  
  1188  	// add the usr-lib-snapd.mount unit
  1189  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
  1190  	c.Assert(err, IsNil)
  1191  	usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit)
  1192  	err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644)
  1193  	c.Assert(err, IsNil)
  1194  
  1195  	now := time.Now()
  1196  	os.Chtimes(usrLibSnapdMountFile, now, now)
  1197  
  1198  	svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service")
  1199  
  1200  	// add the initial state of the service file using Requires
  1201  	requiresContent := mkUnitFile(c, &unitOptions{
  1202  		usrLibSnapdOrderVerb: "Requires",
  1203  		snapName:             "test-snap",
  1204  		snapRev:              "42",
  1205  	})
  1206  	err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644)
  1207  	c.Assert(err, IsNil)
  1208  
  1209  	r := s.mockSystemctlCalls(c, []expectedSystemctl{
  1210  		{
  1211  			expArgs: []string{"daemon-reload"},
  1212  		},
  1213  		{
  1214  			// usr-lib-snapd.mount has never been stopped this boot, thus has
  1215  			// always been active
  1216  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"},
  1217  			output:  "InactiveEnterTimestamp=",
  1218  		},
  1219  	})
  1220  	defer r()
  1221  
  1222  	err = s.mgr.Ensure()
  1223  	c.Assert(err, IsNil)
  1224  
  1225  	content := mkUnitFile(c, &unitOptions{
  1226  		usrLibSnapdOrderVerb: "Wants",
  1227  		snapName:             "test-snap",
  1228  		snapRev:              "42",
  1229  	})
  1230  	c.Assert(svcFile, testutil.FileEquals, content)
  1231  
  1232  	// we did not request a restart
  1233  	c.Assert(s.restartRequests, HasLen, 0)
  1234  }
  1235  
  1236  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesAndRestartsButThenFallsbackToReboot(c *C) {
  1237  	s.state.Lock()
  1238  	// there is a snap in snap state that needs a service generated for it
  1239  	snapstate.Set(s.state, "test-snap", s.testSnapState)
  1240  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
  1241  
  1242  	s.state.Unlock()
  1243  
  1244  	// add the usr-lib-snapd.mount unit
  1245  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
  1246  	c.Assert(err, IsNil)
  1247  	usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit)
  1248  	err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644)
  1249  	c.Assert(err, IsNil)
  1250  
  1251  	now := time.Now()
  1252  	err = os.Chtimes(usrLibSnapdMountFile, now, now)
  1253  	c.Assert(err, IsNil)
  1254  
  1255  	svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service")
  1256  
  1257  	// add the initial state of the service file using Requires
  1258  	requiresContent := mkUnitFile(c, &unitOptions{
  1259  		usrLibSnapdOrderVerb: "Requires",
  1260  		snapName:             "test-snap",
  1261  		snapRev:              "42",
  1262  	})
  1263  	err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644)
  1264  	c.Assert(err, IsNil)
  1265  
  1266  	slightFuture := now.Add(30 * time.Minute).Format(systemdTimeFormat)
  1267  	theFuture := now.Add(1 * time.Hour).Format(systemdTimeFormat)
  1268  
  1269  	r := s.mockSystemctlCalls(c, []expectedSystemctl{
  1270  		{
  1271  			expArgs: []string{"daemon-reload"},
  1272  		},
  1273  		{
  1274  			// usr-lib-snapd.mount was stopped "far in the future"
  1275  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"},
  1276  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", theFuture),
  1277  		},
  1278  		{
  1279  			// but the snap.test-snap.svc1 was stopped only slightly in the
  1280  			// future (hence before the usr-lib-snapd.mount unit was stopped and
  1281  			// after usr-lib-snapd.mount file was modified)
  1282  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"},
  1283  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", slightFuture),
  1284  		},
  1285  		{
  1286  			expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"},
  1287  			output:  "enabled",
  1288  		},
  1289  		{
  1290  			expArgs: []string{"start", "snap.test-snap.svc1.service"},
  1291  			err:     fmt.Errorf("this service is having a bad day"),
  1292  		},
  1293  		{
  1294  			expArgs: []string{"stop", "snap.test-snap.svc1.service"},
  1295  			err:     fmt.Errorf("this service is still having a bad day"),
  1296  		},
  1297  	})
  1298  	defer r()
  1299  
  1300  	err = s.mgr.Ensure()
  1301  	c.Assert(err, ErrorMatches, "error trying to restart killed services, immediately rebooting: this service is having a bad day")
  1302  
  1303  	// we did write the service unit file
  1304  	content := mkUnitFile(c, &unitOptions{
  1305  		usrLibSnapdOrderVerb: "Wants",
  1306  		snapName:             "test-snap",
  1307  		snapRev:              "42",
  1308  	})
  1309  	c.Assert(svcFile, testutil.FileEquals, content)
  1310  
  1311  	// we requested a restart
  1312  	c.Assert(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
  1313  }
  1314  
  1315  func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesAndTriesRestartButFailsButThenFallsbackToReboot(c *C) {
  1316  	s.state.Lock()
  1317  	// there is a snap in snap state that needs a service generated for it
  1318  	snapstate.Set(s.state, "test-snap", s.testSnapState)
  1319  	snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo)
  1320  
  1321  	s.state.Unlock()
  1322  
  1323  	// add the usr-lib-snapd.mount unit
  1324  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
  1325  	c.Assert(err, IsNil)
  1326  	usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit)
  1327  	err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644)
  1328  	c.Assert(err, IsNil)
  1329  
  1330  	now := time.Now()
  1331  	err = os.Chtimes(usrLibSnapdMountFile, now, now)
  1332  	c.Assert(err, IsNil)
  1333  
  1334  	svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service")
  1335  
  1336  	// add the initial state of the service file using Requires
  1337  	requiresContent := mkUnitFile(c, &unitOptions{
  1338  		usrLibSnapdOrderVerb: "Requires",
  1339  		snapName:             "test-snap",
  1340  		snapRev:              "42",
  1341  	})
  1342  	err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644)
  1343  	c.Assert(err, IsNil)
  1344  
  1345  	slightFuture := now.Add(30 * time.Minute).Format(systemdTimeFormat)
  1346  	theFuture := now.Add(1 * time.Hour).Format(systemdTimeFormat)
  1347  
  1348  	r := s.mockSystemctlCalls(c, []expectedSystemctl{
  1349  		{
  1350  			expArgs: []string{"daemon-reload"},
  1351  		},
  1352  		{
  1353  			// usr-lib-snapd.mount was stopped "far in the future"
  1354  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"},
  1355  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", theFuture),
  1356  		},
  1357  		{
  1358  			// but the snap.test-snap.svc1 was stopped only slightly in the
  1359  			// future (hence before the usr-lib-snapd.mount unit was stopped and
  1360  			// after usr-lib-snapd.mount file was modified)
  1361  			expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"},
  1362  			output:  fmt.Sprintf("InactiveEnterTimestamp=%s", slightFuture),
  1363  		},
  1364  		{
  1365  			expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"},
  1366  			err:     fmt.Errorf("systemd is having a bad day"),
  1367  		},
  1368  	})
  1369  	defer r()
  1370  
  1371  	err = s.mgr.Ensure()
  1372  	c.Assert(err, ErrorMatches, "error trying to restart killed services, immediately rebooting: systemd is having a bad day")
  1373  
  1374  	// we did write the service unit file
  1375  	content := mkUnitFile(c, &unitOptions{
  1376  		usrLibSnapdOrderVerb: "Wants",
  1377  		snapName:             "test-snap",
  1378  		snapRev:              "42",
  1379  	})
  1380  	c.Assert(svcFile, testutil.FileEquals, content)
  1381  
  1382  	// we requested a restart
  1383  	c.Assert(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
  1384  }