github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/wrappers/services_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-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 wrappers_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"regexp"
    28  	"sort"
    29  	"strings"
    30  	"time"
    31  
    32  	. "gopkg.in/check.v1"
    33  
    34  	"github.com/snapcore/snapd/dirs"
    35  	"github.com/snapcore/snapd/gadget/quantity"
    36  
    37  	// imported to ensure actual interfaces are defined (in production this is guaranteed by ifacestate)
    38  	_ "github.com/snapcore/snapd/interfaces/builtin"
    39  	"github.com/snapcore/snapd/osutil"
    40  	"github.com/snapcore/snapd/progress"
    41  	"github.com/snapcore/snapd/snap"
    42  	"github.com/snapcore/snapd/snap/quota"
    43  	"github.com/snapcore/snapd/snap/snaptest"
    44  	"github.com/snapcore/snapd/strutil"
    45  	"github.com/snapcore/snapd/systemd"
    46  	"github.com/snapcore/snapd/systemd/systemdtest"
    47  	"github.com/snapcore/snapd/testutil"
    48  	"github.com/snapcore/snapd/timings"
    49  	"github.com/snapcore/snapd/usersession/agent"
    50  	"github.com/snapcore/snapd/wrappers"
    51  )
    52  
    53  type servicesTestSuite struct {
    54  	testutil.DBusTest
    55  
    56  	tempdir string
    57  
    58  	sysdLog [][]string
    59  
    60  	systemctlRestorer, delaysRestorer func()
    61  
    62  	perfTimings timings.Measurer
    63  
    64  	agent *agent.SessionAgent
    65  }
    66  
    67  var _ = Suite(&servicesTestSuite{})
    68  
    69  func (s *servicesTestSuite) SetUpTest(c *C) {
    70  	s.DBusTest.SetUpTest(c)
    71  	s.tempdir = c.MkDir()
    72  	s.sysdLog = nil
    73  	dirs.SetRootDir(s.tempdir)
    74  
    75  	s.systemctlRestorer = systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
    76  		s.sysdLog = append(s.sysdLog, cmd)
    77  		return []byte("ActiveState=inactive\n"), nil
    78  	})
    79  	s.delaysRestorer = systemd.MockStopDelays(time.Millisecond, 25*time.Second)
    80  	s.perfTimings = timings.New(nil)
    81  
    82  	xdgRuntimeDir := fmt.Sprintf("%s/%d", dirs.XdgRuntimeDirBase, os.Getuid())
    83  	err := os.MkdirAll(xdgRuntimeDir, 0700)
    84  	c.Assert(err, IsNil)
    85  	s.agent, err = agent.New()
    86  	c.Assert(err, IsNil)
    87  	s.agent.Start()
    88  }
    89  
    90  func (s *servicesTestSuite) TearDownTest(c *C) {
    91  	if s.agent != nil {
    92  		err := s.agent.Stop()
    93  		c.Check(err, IsNil)
    94  	}
    95  	s.systemctlRestorer()
    96  	s.delaysRestorer()
    97  	dirs.SetRootDir("")
    98  	s.DBusTest.TearDownTest(c)
    99  }
   100  
   101  func (s *servicesTestSuite) TestAddSnapServicesAndRemove(c *C) {
   102  	info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
   103  	svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
   104  
   105  	err := wrappers.AddSnapServices(info, nil, progress.Null)
   106  	c.Assert(err, IsNil)
   107  	c.Check(s.sysdLog, DeepEquals, [][]string{
   108  		{"daemon-reload"},
   109  	})
   110  
   111  	s.sysdLog = nil
   112  
   113  	flags := &wrappers.StartServicesFlags{Enable: true}
   114  	err = wrappers.StartServices(info.Services(), nil, flags, progress.Null, s.perfTimings)
   115  	c.Assert(err, IsNil)
   116  	c.Check(s.sysdLog, DeepEquals, [][]string{
   117  		{"enable", filepath.Base(svcFile)},
   118  		{"start", filepath.Base(svcFile)},
   119  	})
   120  
   121  	dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
   122  	c.Assert(svcFile, testutil.FileEquals, fmt.Sprintf(`[Unit]
   123  # Auto-generated, DO NOT EDIT
   124  Description=Service for snap application hello-snap.svc1
   125  Requires=%[1]s
   126  Wants=network.target
   127  After=%[1]s network.target snapd.apparmor.service
   128  X-Snappy=yes
   129  
   130  [Service]
   131  EnvironmentFile=-/etc/environment
   132  ExecStart=/usr/bin/snap run hello-snap.svc1
   133  SyslogIdentifier=hello-snap.svc1
   134  Restart=on-failure
   135  WorkingDirectory=%[2]s/var/snap/hello-snap/12
   136  ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1
   137  ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1
   138  TimeoutStopSec=30
   139  Type=forking
   140  
   141  [Install]
   142  WantedBy=multi-user.target
   143  `,
   144  		systemd.EscapeUnitNamePath(dir),
   145  		dirs.GlobalRootDir,
   146  	))
   147  
   148  	s.sysdLog = nil
   149  	err = wrappers.StopServices(info.Services(), nil, "", progress.Null, s.perfTimings)
   150  	c.Assert(err, IsNil)
   151  	c.Assert(s.sysdLog, HasLen, 2)
   152  	c.Check(s.sysdLog, DeepEquals, [][]string{
   153  		{"stop", filepath.Base(svcFile)},
   154  		{"show", "--property=ActiveState", "snap.hello-snap.svc1.service"},
   155  	})
   156  
   157  	s.sysdLog = nil
   158  	err = wrappers.RemoveSnapServices(info, progress.Null)
   159  	c.Assert(err, IsNil)
   160  	c.Check(svcFile, testutil.FileAbsent)
   161  	c.Assert(s.sysdLog, DeepEquals, [][]string{
   162  		{"disable", filepath.Base(svcFile)},
   163  		{"daemon-reload"},
   164  	})
   165  }
   166  func (s *servicesTestSuite) TestEnsureSnapServicesAdds(c *C) {
   167  	// map unit -> new
   168  	seen := make(map[string]bool)
   169  	cb := func(app *snap.AppInfo, grp *quota.Group, unitType, name string, old, new string) {
   170  		seen[fmt.Sprintf("%s:%s:%s:%s", app.Snap.InstanceName(), app.Name, unitType, name)] = old == ""
   171  	}
   172  
   173  	info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
   174  	svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
   175  
   176  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
   177  		info: nil,
   178  	}
   179  
   180  	err := wrappers.EnsureSnapServices(m, nil, cb, progress.Null)
   181  	c.Assert(err, IsNil)
   182  	c.Check(s.sysdLog, DeepEquals, [][]string{
   183  		{"daemon-reload"},
   184  	})
   185  	c.Check(seen, DeepEquals, map[string]bool{
   186  		"hello-snap:svc1:service:svc1": true,
   187  	})
   188  
   189  	dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
   190  	c.Assert(svcFile, testutil.FileEquals, fmt.Sprintf(`[Unit]
   191  # Auto-generated, DO NOT EDIT
   192  Description=Service for snap application hello-snap.svc1
   193  Requires=%[1]s
   194  Wants=network.target
   195  After=%[1]s network.target snapd.apparmor.service
   196  X-Snappy=yes
   197  
   198  [Service]
   199  EnvironmentFile=-/etc/environment
   200  ExecStart=/usr/bin/snap run hello-snap.svc1
   201  SyslogIdentifier=hello-snap.svc1
   202  Restart=on-failure
   203  WorkingDirectory=%[2]s/var/snap/hello-snap/12
   204  ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1
   205  ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1
   206  TimeoutStopSec=30
   207  Type=forking
   208  
   209  [Install]
   210  WantedBy=multi-user.target
   211  `,
   212  		systemd.EscapeUnitNamePath(dir),
   213  		dirs.GlobalRootDir,
   214  	))
   215  }
   216  
   217  func (s *servicesTestSuite) TestEnsureSnapServicesWithQuotas(c *C) {
   218  	info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
   219  	svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
   220  
   221  	memLimit := quantity.SizeGiB
   222  	grp, err := quota.NewGroup("foogroup", memLimit)
   223  	c.Assert(err, IsNil)
   224  
   225  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
   226  		info: {QuotaGroup: grp},
   227  	}
   228  
   229  	dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
   230  	svcContent := fmt.Sprintf(`[Unit]
   231  # Auto-generated, DO NOT EDIT
   232  Description=Service for snap application hello-snap.svc1
   233  Requires=%[1]s
   234  Wants=network.target
   235  After=%[1]s network.target snapd.apparmor.service
   236  X-Snappy=yes
   237  
   238  [Service]
   239  EnvironmentFile=-/etc/environment
   240  ExecStart=/usr/bin/snap run hello-snap.svc1
   241  SyslogIdentifier=hello-snap.svc1
   242  Restart=on-failure
   243  WorkingDirectory=%[2]s/var/snap/hello-snap/12
   244  ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1
   245  ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1
   246  TimeoutStopSec=30
   247  Type=forking
   248  Slice=snap.foogroup.slice
   249  
   250  [Install]
   251  WantedBy=multi-user.target
   252  `,
   253  		systemd.EscapeUnitNamePath(dir),
   254  		dirs.GlobalRootDir,
   255  	)
   256  
   257  	sliceTempl := `[Unit]
   258  Description=Slice for snap quota group %s
   259  Before=slices.target
   260  X-Snappy=yes
   261  
   262  [Slice]
   263  # Always enable memory accounting otherwise the MemoryMax setting does nothing.
   264  MemoryAccounting=true
   265  MemoryMax=%[2]s
   266  # for compatibility with older versions of systemd
   267  MemoryLimit=%[2]s
   268  
   269  # Always enable task accounting in order to be able to count the processes/
   270  # threads, etc for a slice
   271  TasksAccounting=true
   272  `
   273  
   274  	sliceContent := fmt.Sprintf(sliceTempl, grp.Name, memLimit.String())
   275  
   276  	exp := []changesObservation{
   277  		{
   278  			snapName: "hello-snap",
   279  			unitType: "service",
   280  			name:     "svc1",
   281  			old:      "",
   282  			new:      svcContent,
   283  		},
   284  		{
   285  			grp:      grp,
   286  			unitType: "slice",
   287  			new:      sliceContent,
   288  			old:      "",
   289  			name:     "foogroup",
   290  		},
   291  	}
   292  	r, observe := expChangeObserver(c, exp)
   293  	defer r()
   294  
   295  	err = wrappers.EnsureSnapServices(m, nil, observe, progress.Null)
   296  	c.Assert(err, IsNil)
   297  	c.Check(s.sysdLog, DeepEquals, [][]string{
   298  		{"daemon-reload"},
   299  	})
   300  
   301  	c.Assert(svcFile, testutil.FileEquals, svcContent)
   302  }
   303  
   304  type changesObservation struct {
   305  	snapName string
   306  	grp      *quota.Group
   307  	unitType string
   308  	name     string
   309  	old      string
   310  	new      string
   311  }
   312  
   313  func expChangeObserver(c *C, exp []changesObservation) (restore func(), obs wrappers.ObserveChangeCallback) {
   314  	changesObserved := []changesObservation{}
   315  	f := func(app *snap.AppInfo, grp *quota.Group, unitType, name, old, new string) {
   316  		snapName := ""
   317  		if app != nil {
   318  			snapName = app.Snap.SnapName()
   319  		}
   320  		changesObserved = append(changesObserved, changesObservation{
   321  			snapName: snapName,
   322  			grp:      grp,
   323  			unitType: unitType,
   324  			name:     name,
   325  			old:      old,
   326  			new:      new,
   327  		})
   328  	}
   329  
   330  	r := func() {
   331  		// sort the changesObserved by snapName, with all services being
   332  		// observed first, then all groups secondly
   333  		groupObservations := make([]changesObservation, 0, len(changesObserved))
   334  		svcObservations := make([]changesObservation, 0, len(changesObserved))
   335  
   336  		for _, chg := range changesObserved {
   337  			if chg.unitType == "slice" {
   338  				groupObservations = append(groupObservations, chg)
   339  			} else {
   340  				svcObservations = append(svcObservations, chg)
   341  			}
   342  		}
   343  		// sort svcObservations, note we do not need to sort groups, since
   344  		// quota groups are iterated over in a stable sorted order via
   345  		// QuotaGroupSet.AllQuotaGroups
   346  		sort.SliceStable(svcObservations, func(i, j int) bool {
   347  			return svcObservations[i].snapName < svcObservations[j].snapName
   348  		})
   349  		finalSortChangesObserved := make([]changesObservation, 0, len(changesObserved))
   350  		finalSortChangesObserved = append(finalSortChangesObserved, svcObservations...)
   351  		finalSortChangesObserved = append(finalSortChangesObserved, groupObservations...)
   352  		c.Assert(finalSortChangesObserved, DeepEquals, exp)
   353  	}
   354  
   355  	return r, f
   356  }
   357  
   358  func (s *servicesTestSuite) TestEnsureSnapServicesRewritesQuotaSlices(c *C) {
   359  	info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
   360  	svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
   361  
   362  	memLimit1 := quantity.SizeGiB
   363  	memLimit2 := quantity.SizeGiB * 2
   364  
   365  	// write both the unit file and a slice with a different memory limit
   366  	// setting than will be provided below
   367  	sliceTempl := `[Unit]
   368  Description=Slice for snap quota group %s
   369  Before=slices.target
   370  X-Snappy=yes
   371  
   372  [Slice]
   373  # Always enable memory accounting otherwise the MemoryMax setting does nothing.
   374  MemoryAccounting=true
   375  MemoryMax=%[2]s
   376  # for compatibility with older versions of systemd
   377  MemoryLimit=%[2]s
   378  
   379  # Always enable task accounting in order to be able to count the processes/
   380  # threads, etc for a slice
   381  TasksAccounting=true
   382  `
   383  	sliceFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.foogroup.slice")
   384  
   385  	dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
   386  	svcContent := fmt.Sprintf(`[Unit]
   387  # Auto-generated, DO NOT EDIT
   388  Description=Service for snap application hello-snap.svc1
   389  Requires=%[1]s
   390  Wants=network.target
   391  After=%[1]s network.target snapd.apparmor.service
   392  X-Snappy=yes
   393  
   394  [Service]
   395  EnvironmentFile=-/etc/environment
   396  ExecStart=/usr/bin/snap run hello-snap.svc1
   397  SyslogIdentifier=hello-snap.svc1
   398  Restart=on-failure
   399  WorkingDirectory=%[2]s/var/snap/hello-snap/12
   400  ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1
   401  ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1
   402  TimeoutStopSec=30
   403  Type=forking
   404  Slice=snap.foogroup.slice
   405  
   406  [Install]
   407  WantedBy=multi-user.target
   408  `,
   409  		systemd.EscapeUnitNamePath(dir),
   410  		dirs.GlobalRootDir,
   411  	)
   412  
   413  	err := os.MkdirAll(filepath.Dir(sliceFile), 0755)
   414  	c.Assert(err, IsNil)
   415  
   416  	oldContent := fmt.Sprintf(sliceTempl, "foogroup", memLimit1.String())
   417  	err = ioutil.WriteFile(sliceFile, []byte(oldContent), 0644)
   418  	c.Assert(err, IsNil)
   419  
   420  	err = ioutil.WriteFile(svcFile, []byte(svcContent), 0644)
   421  	c.Assert(err, IsNil)
   422  
   423  	// use new memory limit
   424  	grp, err := quota.NewGroup("foogroup", memLimit2)
   425  	c.Assert(err, IsNil)
   426  
   427  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
   428  		info: {QuotaGroup: grp},
   429  	}
   430  
   431  	newContent := fmt.Sprintf(sliceTempl, "foogroup", memLimit2.String())
   432  	exp := []changesObservation{
   433  		{
   434  			grp:      grp,
   435  			unitType: "slice",
   436  			new:      newContent,
   437  			old:      oldContent,
   438  			name:     "foogroup",
   439  		},
   440  	}
   441  	r, observe := expChangeObserver(c, exp)
   442  	defer r()
   443  
   444  	err = wrappers.EnsureSnapServices(m, nil, observe, progress.Null)
   445  	c.Assert(err, IsNil)
   446  	c.Check(s.sysdLog, DeepEquals, [][]string{
   447  		{"daemon-reload"},
   448  	})
   449  
   450  	c.Assert(svcFile, testutil.FileEquals, svcContent)
   451  
   452  	c.Assert(sliceFile, testutil.FileEquals, newContent)
   453  
   454  }
   455  
   456  func (s *servicesTestSuite) TestEnsureSnapServicesDoesNotRewriteQuotaSlicesOnNoop(c *C) {
   457  	info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
   458  	svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
   459  
   460  	memLimit := quantity.SizeGiB
   461  
   462  	// write both the unit file and a slice before running the ensure
   463  	sliceTempl := `[Unit]
   464  Description=Slice for snap quota group %s
   465  Before=slices.target
   466  X-Snappy=yes
   467  
   468  [Slice]
   469  # Always enable memory accounting otherwise the MemoryMax setting does nothing.
   470  MemoryAccounting=true
   471  MemoryMax=%[2]s
   472  # for compatibility with older versions of systemd
   473  MemoryLimit=%[2]s
   474  
   475  # Always enable task accounting in order to be able to count the processes/
   476  # threads, etc for a slice
   477  TasksAccounting=true
   478  `
   479  	sliceFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.foogroup.slice")
   480  
   481  	dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
   482  	svcContent := fmt.Sprintf(`[Unit]
   483  # Auto-generated, DO NOT EDIT
   484  Description=Service for snap application hello-snap.svc1
   485  Requires=%[1]s
   486  Wants=network.target
   487  After=%[1]s network.target snapd.apparmor.service
   488  X-Snappy=yes
   489  
   490  [Service]
   491  EnvironmentFile=-/etc/environment
   492  ExecStart=/usr/bin/snap run hello-snap.svc1
   493  SyslogIdentifier=hello-snap.svc1
   494  Restart=on-failure
   495  WorkingDirectory=%[2]s/var/snap/hello-snap/12
   496  ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1
   497  ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1
   498  TimeoutStopSec=30
   499  Type=forking
   500  Slice=snap.foogroup.slice
   501  
   502  [Install]
   503  WantedBy=multi-user.target
   504  `,
   505  		systemd.EscapeUnitNamePath(dir),
   506  		dirs.GlobalRootDir,
   507  	)
   508  
   509  	err := os.MkdirAll(filepath.Dir(sliceFile), 0755)
   510  	c.Assert(err, IsNil)
   511  
   512  	err = ioutil.WriteFile(sliceFile, []byte(fmt.Sprintf(sliceTempl, "foogroup", memLimit.String())), 0644)
   513  	c.Assert(err, IsNil)
   514  
   515  	err = ioutil.WriteFile(svcFile, []byte(svcContent), 0644)
   516  	c.Assert(err, IsNil)
   517  
   518  	grp, err := quota.NewGroup("foogroup", memLimit)
   519  	c.Assert(err, IsNil)
   520  
   521  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
   522  		info: {QuotaGroup: grp},
   523  	}
   524  
   525  	observe := func(app *snap.AppInfo, grp *quota.Group, unitType, name, old, new string) {
   526  		c.Error("unexpected call to observe function")
   527  	}
   528  
   529  	err = wrappers.EnsureSnapServices(m, nil, observe, progress.Null)
   530  	c.Assert(err, IsNil)
   531  	// no daemon restart since the files didn't change
   532  	c.Check(s.sysdLog, HasLen, 0)
   533  
   534  	c.Assert(svcFile, testutil.FileEquals, svcContent)
   535  
   536  	c.Assert(sliceFile, testutil.FileEquals, fmt.Sprintf(sliceTempl, "foogroup", memLimit.String()))
   537  }
   538  
   539  func (s *servicesTestSuite) TestRemoveQuotaGroup(c *C) {
   540  	// create the group
   541  	grp, err := quota.NewGroup("foogroup", quantity.SizeKiB)
   542  	c.Assert(err, IsNil)
   543  
   544  	sliceFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.foogroup.slice")
   545  	c.Assert(sliceFile, testutil.FileAbsent)
   546  
   547  	// removing the group when the slice file doesn't exist is not an error
   548  	err = wrappers.RemoveQuotaGroup(grp, progress.Null)
   549  	c.Assert(err, IsNil)
   550  
   551  	c.Assert(s.sysdLog, HasLen, 0)
   552  
   553  	c.Assert(sliceFile, testutil.FileAbsent)
   554  
   555  	// now write slice file and ensure it is deleted
   556  	sliceTempl := `[Unit]
   557  Description=Slice for snap quota group %s
   558  Before=slices.target
   559  X-Snappy=yes
   560  
   561  [Slice]
   562  # Always enable memory accounting otherwise the MemoryMax setting does nothing.
   563  MemoryAccounting=true
   564  MemoryMax=1024
   565  # for compatibility with older versions of systemd
   566  MemoryLimit=1024
   567  `
   568  
   569  	err = os.MkdirAll(filepath.Dir(sliceFile), 0755)
   570  	c.Assert(err, IsNil)
   571  
   572  	err = ioutil.WriteFile(sliceFile, []byte(fmt.Sprintf(sliceTempl, "foogroup")), 0644)
   573  	c.Assert(err, IsNil)
   574  
   575  	// removing it deletes it
   576  	err = wrappers.RemoveQuotaGroup(grp, progress.Null)
   577  	c.Assert(err, IsNil)
   578  
   579  	c.Assert(s.sysdLog, DeepEquals, [][]string{
   580  		{"daemon-reload"},
   581  	})
   582  
   583  	c.Assert(sliceFile, testutil.FileAbsent)
   584  }
   585  
   586  func (s *servicesTestSuite) TestEnsureSnapServicesWithSubGroupQuotaGroupsForSnaps(c *C) {
   587  	info1 := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
   588  	info2 := snaptest.MockSnap(c, `
   589  name: hello-other-snap
   590  version: 1.10
   591  summary: hello
   592  description: Hello...
   593  apps:
   594   hello:
   595     command: bin/hello
   596   world:
   597     command: bin/world
   598     completer: world-completer.sh
   599   svc1:
   600    command: bin/hello
   601    stop-command: bin/goodbye
   602    post-stop-command: bin/missya
   603    daemon: forking
   604  `, &snap.SideInfo{Revision: snap.R(12)})
   605  	svcFile1 := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
   606  	svcFile2 := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-other-snap.svc1.service")
   607  
   608  	var err error
   609  	memLimit := quantity.SizeGiB
   610  	// make a root quota group and add the first snap to it
   611  	grp, err := quota.NewGroup("foogroup", memLimit)
   612  	c.Assert(err, IsNil)
   613  
   614  	// the second group is a sub-group with the same limit, but is for the
   615  	// second snap
   616  	subgrp, err := grp.NewSubGroup("subgroup", memLimit)
   617  	c.Assert(err, IsNil)
   618  
   619  	sliceFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.foogroup.slice")
   620  	subSliceFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.foogroup-subgroup.slice")
   621  
   622  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
   623  		info1: {QuotaGroup: grp},
   624  		info2: {QuotaGroup: subgrp},
   625  	}
   626  
   627  	sliceTempl := `[Unit]
   628  Description=Slice for snap quota group %s
   629  Before=slices.target
   630  X-Snappy=yes
   631  
   632  [Slice]
   633  # Always enable memory accounting otherwise the MemoryMax setting does nothing.
   634  MemoryAccounting=true
   635  MemoryMax=%[2]s
   636  # for compatibility with older versions of systemd
   637  MemoryLimit=%[2]s
   638  
   639  # Always enable task accounting in order to be able to count the processes/
   640  # threads, etc for a slice
   641  TasksAccounting=true
   642  `
   643  
   644  	sliceContent := fmt.Sprintf(sliceTempl, "foogroup", memLimit.String())
   645  	subSliceContent := fmt.Sprintf(sliceTempl, "subgroup", memLimit.String())
   646  
   647  	svcTemplate := `[Unit]
   648  # Auto-generated, DO NOT EDIT
   649  Description=Service for snap application %[1]s.svc1
   650  Requires=%[2]s
   651  Wants=network.target
   652  After=%[2]s network.target snapd.apparmor.service
   653  X-Snappy=yes
   654  
   655  [Service]
   656  EnvironmentFile=-/etc/environment
   657  ExecStart=/usr/bin/snap run %[1]s.svc1
   658  SyslogIdentifier=%[1]s.svc1
   659  Restart=on-failure
   660  WorkingDirectory=%[3]s/var/snap/%[1]s/12
   661  ExecStop=/usr/bin/snap run --command=stop %[1]s.svc1
   662  ExecStopPost=/usr/bin/snap run --command=post-stop %[1]s.svc1
   663  TimeoutStopSec=30
   664  Type=forking
   665  Slice=%[4]s
   666  
   667  [Install]
   668  WantedBy=multi-user.target
   669  `
   670  
   671  	dir1 := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
   672  	dir2 := filepath.Join(dirs.SnapMountDir, "hello-other-snap", "12.mount")
   673  
   674  	helloSnapContent := fmt.Sprintf(svcTemplate,
   675  		"hello-snap",
   676  		systemd.EscapeUnitNamePath(dir1),
   677  		dirs.GlobalRootDir,
   678  		"snap.foogroup.slice",
   679  	)
   680  
   681  	helloOtherSnapContent := fmt.Sprintf(svcTemplate,
   682  		"hello-other-snap",
   683  		systemd.EscapeUnitNamePath(dir2),
   684  		dirs.GlobalRootDir,
   685  		"snap.foogroup-subgroup.slice",
   686  	)
   687  
   688  	exp := []changesObservation{
   689  		{
   690  			snapName: "hello-other-snap",
   691  			unitType: "service",
   692  			name:     "svc1",
   693  			old:      "",
   694  			new:      helloOtherSnapContent,
   695  		},
   696  		{
   697  			snapName: "hello-snap",
   698  			unitType: "service",
   699  			name:     "svc1",
   700  			old:      "",
   701  			new:      helloSnapContent,
   702  		},
   703  		{
   704  			grp:      grp,
   705  			unitType: "slice",
   706  			new:      sliceContent,
   707  			old:      "",
   708  			name:     "foogroup",
   709  		},
   710  		{
   711  			grp:      subgrp,
   712  			unitType: "slice",
   713  			new:      subSliceContent,
   714  			old:      "",
   715  			name:     "subgroup",
   716  		},
   717  	}
   718  	r, observe := expChangeObserver(c, exp)
   719  	defer r()
   720  
   721  	err = wrappers.EnsureSnapServices(m, nil, observe, progress.Null)
   722  	c.Assert(err, IsNil)
   723  	c.Check(s.sysdLog, DeepEquals, [][]string{
   724  		{"daemon-reload"},
   725  	})
   726  
   727  	c.Assert(svcFile1, testutil.FileEquals, helloSnapContent)
   728  
   729  	c.Assert(svcFile2, testutil.FileEquals, helloOtherSnapContent)
   730  
   731  	// check that the slice units were also generated
   732  
   733  	c.Assert(sliceFile, testutil.FileEquals, sliceContent)
   734  	c.Assert(subSliceFile, testutil.FileEquals, subSliceContent)
   735  }
   736  
   737  func (s *servicesTestSuite) TestEnsureSnapServicesWithSubGroupQuotaGroupsGeneratesParentGroups(c *C) {
   738  	info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
   739  	svcFile1 := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
   740  
   741  	var err error
   742  	memLimit := quantity.SizeGiB
   743  	// make a root quota group without any snaps in it
   744  	grp, err := quota.NewGroup("foogroup", memLimit)
   745  	c.Assert(err, IsNil)
   746  
   747  	// the second group is a sub-group with the same limit, but it is the one
   748  	// with the snap in it
   749  	subgrp, err := grp.NewSubGroup("subgroup", memLimit)
   750  	c.Assert(err, IsNil)
   751  
   752  	sliceFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.foogroup.slice")
   753  	subSliceFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.foogroup-subgroup.slice")
   754  
   755  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
   756  		info: {QuotaGroup: subgrp},
   757  	}
   758  
   759  	err = wrappers.EnsureSnapServices(m, nil, nil, progress.Null)
   760  	c.Assert(err, IsNil)
   761  	c.Check(s.sysdLog, DeepEquals, [][]string{
   762  		{"daemon-reload"},
   763  	})
   764  
   765  	svcTemplate := `[Unit]
   766  # Auto-generated, DO NOT EDIT
   767  Description=Service for snap application %[1]s.svc1
   768  Requires=%[2]s
   769  Wants=network.target
   770  After=%[2]s network.target snapd.apparmor.service
   771  X-Snappy=yes
   772  
   773  [Service]
   774  EnvironmentFile=-/etc/environment
   775  ExecStart=/usr/bin/snap run %[1]s.svc1
   776  SyslogIdentifier=%[1]s.svc1
   777  Restart=on-failure
   778  WorkingDirectory=%[3]s/var/snap/%[1]s/12
   779  ExecStop=/usr/bin/snap run --command=stop %[1]s.svc1
   780  ExecStopPost=/usr/bin/snap run --command=post-stop %[1]s.svc1
   781  TimeoutStopSec=30
   782  Type=forking
   783  Slice=%[4]s
   784  
   785  [Install]
   786  WantedBy=multi-user.target
   787  `
   788  
   789  	dir1 := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
   790  
   791  	c.Assert(svcFile1, testutil.FileEquals, fmt.Sprintf(svcTemplate,
   792  		"hello-snap",
   793  		systemd.EscapeUnitNamePath(dir1),
   794  		dirs.GlobalRootDir,
   795  		"snap.foogroup-subgroup.slice",
   796  	))
   797  
   798  	// check that both the parent and sub-group slice units were generated
   799  	templ := `[Unit]
   800  Description=Slice for snap quota group %s
   801  Before=slices.target
   802  X-Snappy=yes
   803  
   804  [Slice]
   805  # Always enable memory accounting otherwise the MemoryMax setting does nothing.
   806  MemoryAccounting=true
   807  MemoryMax=%[2]s
   808  # for compatibility with older versions of systemd
   809  MemoryLimit=%[2]s
   810  
   811  # Always enable task accounting in order to be able to count the processes/
   812  # threads, etc for a slice
   813  TasksAccounting=true
   814  `
   815  
   816  	c.Assert(sliceFile, testutil.FileEquals, fmt.Sprintf(templ, "foogroup", memLimit.String()))
   817  	c.Assert(subSliceFile, testutil.FileEquals, fmt.Sprintf(templ, "subgroup", memLimit.String()))
   818  }
   819  
   820  func (s *servicesTestSuite) TestEnsureSnapServiceEnsureError(c *C) {
   821  	info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
   822  	svcFileDir := filepath.Join(s.tempdir, "/etc/systemd/system")
   823  
   824  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
   825  		info: nil,
   826  	}
   827  
   828  	// make the directory where the service file is written not writable, this
   829  	// will make EnsureFileState return an error
   830  	err := os.MkdirAll(svcFileDir, 0755)
   831  	c.Assert(err, IsNil)
   832  
   833  	err = os.Chmod(svcFileDir, 0644)
   834  	c.Assert(err, IsNil)
   835  
   836  	err = wrappers.EnsureSnapServices(m, nil, nil, progress.Null)
   837  	c.Assert(err, ErrorMatches, ".* permission denied")
   838  	// we don't issue a daemon-reload since we didn't actually end up making any
   839  	// changes (there was nothing to rollback to)
   840  	c.Check(s.sysdLog, HasLen, 0)
   841  
   842  	// we didn't write any files
   843  	c.Assert(filepath.Join(svcFileDir, "snap.hello-snap.svc1.service"), testutil.FileAbsent)
   844  }
   845  
   846  func (s *servicesTestSuite) TestEnsureSnapServicesPreseedingHappy(c *C) {
   847  	// map unit -> new
   848  	seen := make(map[string]bool)
   849  	cb := func(app *snap.AppInfo, grp *quota.Group, unitType, name string, old, new string) {
   850  		seen[fmt.Sprintf("%s:%s:%s:%s", app.Snap.InstanceName(), app.Name, unitType, name)] = old == ""
   851  	}
   852  
   853  	info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
   854  	svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
   855  
   856  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
   857  		info: nil,
   858  	}
   859  
   860  	// we provide globally applicable Preseeding option via
   861  	// EnsureSnapServiceOptions
   862  	globalOpts := &wrappers.EnsureSnapServicesOptions{
   863  		Preseeding: true,
   864  	}
   865  	err := wrappers.EnsureSnapServices(m, globalOpts, cb, progress.Null)
   866  	c.Assert(err, IsNil)
   867  	// no daemon-reload's since we are preseeding
   868  	c.Check(s.sysdLog, HasLen, 0)
   869  	c.Check(seen, DeepEquals, map[string]bool{
   870  		"hello-snap:svc1:service:svc1": true,
   871  	})
   872  
   873  	dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
   874  	c.Assert(svcFile, testutil.FileEquals, fmt.Sprintf(`[Unit]
   875  # Auto-generated, DO NOT EDIT
   876  Description=Service for snap application hello-snap.svc1
   877  Requires=%[1]s
   878  Wants=network.target
   879  After=%[1]s network.target snapd.apparmor.service
   880  X-Snappy=yes
   881  
   882  [Service]
   883  EnvironmentFile=-/etc/environment
   884  ExecStart=/usr/bin/snap run hello-snap.svc1
   885  SyslogIdentifier=hello-snap.svc1
   886  Restart=on-failure
   887  WorkingDirectory=%[2]s/var/snap/hello-snap/12
   888  ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1
   889  ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1
   890  TimeoutStopSec=30
   891  Type=forking
   892  
   893  [Install]
   894  WantedBy=multi-user.target
   895  `,
   896  		systemd.EscapeUnitNamePath(dir),
   897  		dirs.GlobalRootDir,
   898  	))
   899  }
   900  
   901  func (s *servicesTestSuite) TestEnsureSnapServicesRequireMountedSnapdSnapOptionsHappy(c *C) {
   902  	// map unit -> new
   903  	seen := make(map[string]bool)
   904  	cb := func(app *snap.AppInfo, grp *quota.Group, unitType, name string, old, new string) {
   905  		seen[fmt.Sprintf("%s:%s:%s:%s", app.Snap.InstanceName(), app.Name, unitType, name)] = old == ""
   906  	}
   907  
   908  	// use two snaps one with per-snap options and one without to demonstrate
   909  	// that the global options apply to all snaps
   910  	info1 := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
   911  	info2 := snaptest.MockSnap(c, `
   912  name: hello-other-snap
   913  version: 1.10
   914  summary: hello
   915  description: Hello...
   916  apps:
   917   hello:
   918     command: bin/hello
   919   world:
   920     command: bin/world
   921     completer: world-completer.sh
   922   svc1:
   923    command: bin/hello
   924    stop-command: bin/goodbye
   925    post-stop-command: bin/missya
   926    daemon: forking
   927  `, &snap.SideInfo{Revision: snap.R(12)})
   928  	svcFile1 := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
   929  	svcFile2 := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-other-snap.svc1.service")
   930  
   931  	// some options per-snap
   932  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
   933  		info1: {VitalityRank: 1},
   934  		info2: nil,
   935  	}
   936  
   937  	// and also a global option that should propagate to unit generation too
   938  	globalOpts := &wrappers.EnsureSnapServicesOptions{
   939  		RequireMountedSnapdSnap: true,
   940  	}
   941  	err := wrappers.EnsureSnapServices(m, globalOpts, cb, progress.Null)
   942  	c.Assert(err, IsNil)
   943  	// no daemon-reload's since we are preseeding
   944  	c.Check(s.sysdLog, DeepEquals, [][]string{
   945  		{"daemon-reload"},
   946  	})
   947  	c.Check(seen, DeepEquals, map[string]bool{
   948  		"hello-snap:svc1:service:svc1":       true,
   949  		"hello-other-snap:svc1:service:svc1": true,
   950  	})
   951  
   952  	template := `[Unit]
   953  # Auto-generated, DO NOT EDIT
   954  Description=Service for snap application %[1]s.svc1
   955  Requires=%[2]s
   956  Wants=network.target
   957  After=%[2]s network.target snapd.apparmor.service
   958  Wants=usr-lib-snapd.mount
   959  After=usr-lib-snapd.mount
   960  X-Snappy=yes
   961  
   962  [Service]
   963  EnvironmentFile=-/etc/environment
   964  ExecStart=/usr/bin/snap run %[1]s.svc1
   965  SyslogIdentifier=%[1]s.svc1
   966  Restart=on-failure
   967  WorkingDirectory=%[3]s/var/snap/%[1]s/12
   968  ExecStop=/usr/bin/snap run --command=stop %[1]s.svc1
   969  ExecStopPost=/usr/bin/snap run --command=post-stop %[1]s.svc1
   970  TimeoutStopSec=30
   971  Type=forking
   972  %[4]s
   973  [Install]
   974  WantedBy=multi-user.target
   975  `
   976  
   977  	dir1 := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
   978  	dir2 := filepath.Join(dirs.SnapMountDir, "hello-other-snap", "12.mount")
   979  
   980  	c.Assert(svcFile1, testutil.FileEquals, fmt.Sprintf(template,
   981  		"hello-snap",
   982  		systemd.EscapeUnitNamePath(dir1),
   983  		dirs.GlobalRootDir,
   984  		"OOMScoreAdjust=-899\n", // VitalityRank in effect
   985  	))
   986  
   987  	c.Assert(svcFile2, testutil.FileEquals, fmt.Sprintf(template,
   988  		"hello-other-snap",
   989  		systemd.EscapeUnitNamePath(dir2),
   990  		dirs.GlobalRootDir,
   991  		"", // no VitalityRank in effect
   992  	))
   993  }
   994  
   995  func (s *servicesTestSuite) TestEnsureSnapServicesCallback(c *C) {
   996  	// hava a 2nd new service definition
   997  	info := snaptest.MockSnap(c, packageHello+` svc2:
   998    command: bin/hello
   999    stop-command: bin/goodbye
  1000    post-stop-command: bin/missya
  1001    daemon: forking
  1002  `, &snap.SideInfo{Revision: snap.R(12)})
  1003  	svc1File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
  1004  	svc2File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc2.service")
  1005  
  1006  	dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
  1007  	template := `[Unit]
  1008  # Auto-generated, DO NOT EDIT
  1009  Description=Service for snap application hello-snap.%[1]s
  1010  Requires=%[2]s
  1011  Wants=network.target
  1012  After=%[2]s network.target snapd.apparmor.service
  1013  X-Snappy=yes
  1014  
  1015  [Service]
  1016  EnvironmentFile=-/etc/environment
  1017  ExecStart=/usr/bin/snap run hello-snap.%[1]s
  1018  SyslogIdentifier=hello-snap.%[1]s
  1019  Restart=on-failure
  1020  WorkingDirectory=%[3]s/var/snap/hello-snap/12
  1021  ExecStop=/usr/bin/snap run --command=stop hello-snap.%[1]s
  1022  ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.%[1]s
  1023  TimeoutStopSec=30
  1024  Type=forking
  1025  %[4]s
  1026  [Install]
  1027  WantedBy=multi-user.target
  1028  `
  1029  	svc1Content := fmt.Sprintf(template,
  1030  		"svc1",
  1031  		systemd.EscapeUnitNamePath(dir),
  1032  		dirs.GlobalRootDir,
  1033  		"",
  1034  	)
  1035  
  1036  	err := os.MkdirAll(filepath.Dir(svc1File), 0755)
  1037  	c.Assert(err, IsNil)
  1038  	err = ioutil.WriteFile(svc1File, []byte(svc1Content), 0644)
  1039  	c.Assert(err, IsNil)
  1040  
  1041  	// both will be written, one is new
  1042  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
  1043  		info: {VitalityRank: 1},
  1044  	}
  1045  
  1046  	seen := make(map[string][]string)
  1047  	cb := func(app *snap.AppInfo, grp *quota.Group, unitType, name string, old, new string) {
  1048  		seen[fmt.Sprintf("%s:%s:%s:%s", app.Snap.InstanceName(), app.Name, unitType, name)] = []string{old, new}
  1049  	}
  1050  
  1051  	err = wrappers.EnsureSnapServices(m, nil, cb, progress.Null)
  1052  	c.Assert(err, IsNil)
  1053  	c.Check(s.sysdLog, DeepEquals, [][]string{
  1054  		{"daemon-reload"},
  1055  	})
  1056  
  1057  	// svc2 was written as expected
  1058  	svc2New := fmt.Sprintf(template,
  1059  		"svc2",
  1060  		systemd.EscapeUnitNamePath(dir),
  1061  		dirs.GlobalRootDir,
  1062  		"OOMScoreAdjust=-899\n",
  1063  	)
  1064  	c.Assert(svc2File, testutil.FileEquals, svc2New)
  1065  
  1066  	// and svc1 was changed as well
  1067  	svc1New := fmt.Sprintf(template,
  1068  		"svc1",
  1069  		systemd.EscapeUnitNamePath(dir),
  1070  		dirs.GlobalRootDir,
  1071  		"OOMScoreAdjust=-899\n",
  1072  	)
  1073  	c.Assert(svc1File, testutil.FileEquals, svc1New)
  1074  
  1075  	c.Check(seen, DeepEquals, map[string][]string{
  1076  		"hello-snap:svc1:service:svc1": {svc1Content, svc1New},
  1077  		"hello-snap:svc2:service:svc2": {"", svc2New},
  1078  	})
  1079  }
  1080  
  1081  func (s *servicesTestSuite) TestEnsureSnapServicesAddsNewSvc(c *C) {
  1082  	// map unit -> new
  1083  	seen := make(map[string]bool)
  1084  	cb := func(app *snap.AppInfo, grp *quota.Group, unitType, name string, old, new string) {
  1085  		seen[fmt.Sprintf("%s:%s:%s:%s", app.Snap.InstanceName(), app.Name, unitType, name)] = old == ""
  1086  	}
  1087  
  1088  	// test that with an existing service unit definition, it is not changed
  1089  	// but we do add the new one
  1090  	info := snaptest.MockSnap(c, packageHello+` svc2:
  1091    command: bin/hello
  1092    stop-command: bin/goodbye
  1093    post-stop-command: bin/missya
  1094    daemon: forking
  1095  `, &snap.SideInfo{Revision: snap.R(12)})
  1096  	svc1File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
  1097  	svc2File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc2.service")
  1098  
  1099  	dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
  1100  	template := `[Unit]
  1101  # Auto-generated, DO NOT EDIT
  1102  Description=Service for snap application hello-snap.%[1]s
  1103  Requires=%[2]s
  1104  Wants=network.target
  1105  After=%[2]s network.target snapd.apparmor.service
  1106  X-Snappy=yes
  1107  
  1108  [Service]
  1109  EnvironmentFile=-/etc/environment
  1110  ExecStart=/usr/bin/snap run hello-snap.%[1]s
  1111  SyslogIdentifier=hello-snap.%[1]s
  1112  Restart=on-failure
  1113  WorkingDirectory=%[3]s/var/snap/hello-snap/12
  1114  ExecStop=/usr/bin/snap run --command=stop hello-snap.%[1]s
  1115  ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.%[1]s
  1116  TimeoutStopSec=30
  1117  Type=forking
  1118  %[4]s
  1119  [Install]
  1120  WantedBy=multi-user.target
  1121  `
  1122  	svc1Content := fmt.Sprintf(template,
  1123  		"svc1",
  1124  		systemd.EscapeUnitNamePath(dir),
  1125  		dirs.GlobalRootDir,
  1126  		"",
  1127  	)
  1128  
  1129  	err := os.MkdirAll(filepath.Dir(svc1File), 0755)
  1130  	c.Assert(err, IsNil)
  1131  	err = ioutil.WriteFile(svc1File, []byte(svc1Content), 0644)
  1132  	c.Assert(err, IsNil)
  1133  
  1134  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
  1135  		info: nil,
  1136  	}
  1137  
  1138  	err = wrappers.EnsureSnapServices(m, nil, cb, progress.Null)
  1139  	c.Assert(err, IsNil)
  1140  	c.Check(s.sysdLog, DeepEquals, [][]string{
  1141  		{"daemon-reload"},
  1142  	})
  1143  	// we only added svc2
  1144  	c.Check(seen, DeepEquals, map[string]bool{
  1145  		"hello-snap:svc2:service:svc2": true,
  1146  	})
  1147  
  1148  	// svc2 was written as expected
  1149  	c.Assert(svc2File, testutil.FileEquals, fmt.Sprintf(template,
  1150  		"svc2",
  1151  		systemd.EscapeUnitNamePath(dir),
  1152  		dirs.GlobalRootDir,
  1153  		"",
  1154  	))
  1155  
  1156  	// and svc1 didn't change
  1157  	c.Assert(svc1File, testutil.FileEquals, fmt.Sprintf(template,
  1158  		"svc1",
  1159  		systemd.EscapeUnitNamePath(dir),
  1160  		dirs.GlobalRootDir,
  1161  		"",
  1162  	))
  1163  }
  1164  
  1165  func (s *servicesTestSuite) TestEnsureSnapServicesNoChangeNoop(c *C) {
  1166  	info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
  1167  
  1168  	// pretend we already have a unit file setup
  1169  	svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
  1170  
  1171  	dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
  1172  	template := `[Unit]
  1173  # Auto-generated, DO NOT EDIT
  1174  Description=Service for snap application hello-snap.svc1
  1175  Requires=%[1]s
  1176  Wants=network.target
  1177  After=%[1]s network.target snapd.apparmor.service
  1178  X-Snappy=yes
  1179  
  1180  [Service]
  1181  EnvironmentFile=-/etc/environment
  1182  ExecStart=/usr/bin/snap run hello-snap.svc1
  1183  SyslogIdentifier=hello-snap.svc1
  1184  Restart=on-failure
  1185  WorkingDirectory=%[2]s/var/snap/hello-snap/12
  1186  ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1
  1187  ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1
  1188  TimeoutStopSec=30
  1189  Type=forking
  1190  %s
  1191  [Install]
  1192  WantedBy=multi-user.target
  1193  `
  1194  	origContent := fmt.Sprintf(template,
  1195  		systemd.EscapeUnitNamePath(dir),
  1196  		dirs.GlobalRootDir,
  1197  		"",
  1198  	)
  1199  
  1200  	err := os.MkdirAll(filepath.Dir(svcFile), 0755)
  1201  	c.Assert(err, IsNil)
  1202  	err = ioutil.WriteFile(svcFile, []byte(origContent), 0644)
  1203  	c.Assert(err, IsNil)
  1204  
  1205  	// now ensuring with no options will not modify anything or trigger a
  1206  	// daemon-reload
  1207  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
  1208  		info: nil,
  1209  	}
  1210  
  1211  	cbCalled := 0
  1212  	cb := func(app *snap.AppInfo, grp *quota.Group, unitType, name string, old, new string) {
  1213  		cbCalled++
  1214  	}
  1215  
  1216  	err = wrappers.EnsureSnapServices(m, nil, cb, progress.Null)
  1217  	c.Assert(err, IsNil)
  1218  	c.Check(s.sysdLog, HasLen, 0)
  1219  
  1220  	// the file is not changed
  1221  	c.Assert(svcFile, testutil.FileEquals, origContent)
  1222  
  1223  	// callback is not called if no change
  1224  	c.Check(cbCalled, Equals, 0)
  1225  }
  1226  
  1227  func (s *servicesTestSuite) TestEnsureSnapServicesChanges(c *C) {
  1228  	// map unit -> new
  1229  	seen := make(map[string]bool)
  1230  	cb := func(app *snap.AppInfo, grp *quota.Group, unitType, name string, old, new string) {
  1231  		seen[fmt.Sprintf("%s:%s:%s:%s", app.Snap.InstanceName(), app.Name, unitType, name)] = old == ""
  1232  	}
  1233  
  1234  	info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
  1235  
  1236  	// pretend we already have a unit file with no VitalityRank options set
  1237  	svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
  1238  
  1239  	dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
  1240  	template := `[Unit]
  1241  # Auto-generated, DO NOT EDIT
  1242  Description=Service for snap application hello-snap.svc1
  1243  Requires=%[1]s
  1244  Wants=network.target
  1245  After=%[1]s network.target snapd.apparmor.service
  1246  X-Snappy=yes
  1247  
  1248  [Service]
  1249  EnvironmentFile=-/etc/environment
  1250  ExecStart=/usr/bin/snap run hello-snap.svc1
  1251  SyslogIdentifier=hello-snap.svc1
  1252  Restart=on-failure
  1253  WorkingDirectory=%[2]s/var/snap/hello-snap/12
  1254  ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1
  1255  ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1
  1256  TimeoutStopSec=30
  1257  Type=forking
  1258  %s
  1259  [Install]
  1260  WantedBy=multi-user.target
  1261  `
  1262  	origContent := fmt.Sprintf(template,
  1263  		systemd.EscapeUnitNamePath(dir),
  1264  		dirs.GlobalRootDir,
  1265  		"",
  1266  	)
  1267  
  1268  	err := os.MkdirAll(filepath.Dir(svcFile), 0755)
  1269  	c.Assert(err, IsNil)
  1270  	err = ioutil.WriteFile(svcFile, []byte(origContent), 0644)
  1271  	c.Assert(err, IsNil)
  1272  
  1273  	// now ensuring with the VitalityRank set will modify the file
  1274  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
  1275  		info: {VitalityRank: 1},
  1276  	}
  1277  
  1278  	err = wrappers.EnsureSnapServices(m, nil, cb, progress.Null)
  1279  	c.Assert(err, IsNil)
  1280  	c.Check(s.sysdLog, DeepEquals, [][]string{
  1281  		{"daemon-reload"},
  1282  	})
  1283  
  1284  	// only modified
  1285  	c.Check(seen, DeepEquals, map[string]bool{
  1286  		"hello-snap:svc1:service:svc1": false,
  1287  	})
  1288  
  1289  	// now the file has been modified to have OOMScoreAdjust set for it
  1290  	c.Assert(svcFile, testutil.FileEquals, fmt.Sprintf(template,
  1291  		systemd.EscapeUnitNamePath(dir),
  1292  		dirs.GlobalRootDir,
  1293  		"OOMScoreAdjust=-899\n",
  1294  	))
  1295  }
  1296  
  1297  func (s *servicesTestSuite) TestEnsureSnapServicesRollsback(c *C) {
  1298  	info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
  1299  
  1300  	svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
  1301  
  1302  	// pretend we already have a unit file with no VitalityRank options set
  1303  	dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
  1304  	template := `[Unit]
  1305  # Auto-generated, DO NOT EDIT
  1306  Description=Service for snap application hello-snap.svc1
  1307  Requires=%[1]s
  1308  Wants=network.target
  1309  After=%[1]s network.target snapd.apparmor.service
  1310  X-Snappy=yes
  1311  
  1312  [Service]
  1313  EnvironmentFile=-/etc/environment
  1314  ExecStart=/usr/bin/snap run hello-snap.svc1
  1315  SyslogIdentifier=hello-snap.svc1
  1316  Restart=on-failure
  1317  WorkingDirectory=%[2]s/var/snap/hello-snap/12
  1318  ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1
  1319  ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1
  1320  TimeoutStopSec=30
  1321  Type=forking
  1322  %s
  1323  [Install]
  1324  WantedBy=multi-user.target
  1325  `
  1326  	origContent := fmt.Sprintf(template,
  1327  		systemd.EscapeUnitNamePath(dir),
  1328  		dirs.GlobalRootDir,
  1329  		"",
  1330  	)
  1331  
  1332  	err := os.MkdirAll(filepath.Dir(svcFile), 0755)
  1333  	c.Assert(err, IsNil)
  1334  	err = ioutil.WriteFile(svcFile, []byte(origContent), 0644)
  1335  	c.Assert(err, IsNil)
  1336  
  1337  	// make systemctl fail the first time when we try to do a daemon-reload,
  1338  	// then the next time don't return an error
  1339  	systemctlCalls := 0
  1340  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  1341  		systemctlCalls++
  1342  		switch systemctlCalls {
  1343  		case 1:
  1344  			// check that the file has been modified to have OOMScoreAdjust set
  1345  			// for it
  1346  			c.Assert(svcFile, testutil.FileEquals, fmt.Sprintf(template,
  1347  				systemd.EscapeUnitNamePath(dir),
  1348  				dirs.GlobalRootDir,
  1349  				"OOMScoreAdjust=-899\n",
  1350  			))
  1351  
  1352  			// now return an error to trigger a rollback
  1353  			return nil, fmt.Errorf("oops")
  1354  		case 2:
  1355  			// check that the rollback happened to restore the original content
  1356  			c.Assert(svcFile, testutil.FileEquals, origContent)
  1357  
  1358  			return nil, nil
  1359  		default:
  1360  			c.Errorf("unexpected call (number %d) to systemctl: %+v", systemctlCalls, cmd)
  1361  			return nil, fmt.Errorf("broken test")
  1362  		}
  1363  	})
  1364  	defer r()
  1365  
  1366  	// now ensuring with the VitalityRank set will modify the file
  1367  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
  1368  		info: {VitalityRank: 1},
  1369  	}
  1370  
  1371  	err = wrappers.EnsureSnapServices(m, nil, nil, progress.Null)
  1372  	c.Assert(err, ErrorMatches, "oops")
  1373  	c.Assert(systemctlCalls, Equals, 2)
  1374  
  1375  	// double-check that after the function is done, the file is back to what we
  1376  	// had before (this check duplicates the one in MockSystemctl but doesn't
  1377  	// hurt anything to do again)
  1378  	c.Assert(svcFile, testutil.FileEquals, origContent)
  1379  }
  1380  
  1381  func (s *servicesTestSuite) TestEnsureSnapServicesRemovesNewAddOnRollback(c *C) {
  1382  	info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
  1383  
  1384  	svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
  1385  
  1386  	// pretend we already have a unit file with no VitalityRank options set
  1387  	dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
  1388  	template := `[Unit]
  1389  # Auto-generated, DO NOT EDIT
  1390  Description=Service for snap application hello-snap.svc1
  1391  Requires=%[1]s
  1392  Wants=network.target
  1393  After=%[1]s network.target snapd.apparmor.service
  1394  X-Snappy=yes
  1395  
  1396  [Service]
  1397  EnvironmentFile=-/etc/environment
  1398  ExecStart=/usr/bin/snap run hello-snap.svc1
  1399  SyslogIdentifier=hello-snap.svc1
  1400  Restart=on-failure
  1401  WorkingDirectory=%[2]s/var/snap/hello-snap/12
  1402  ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1
  1403  ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1
  1404  TimeoutStopSec=30
  1405  Type=forking
  1406  %s
  1407  [Install]
  1408  WantedBy=multi-user.target
  1409  `
  1410  	// make systemctl fail the first time when we try to do a daemon-reload,
  1411  	// then the next time don't return an error
  1412  	systemctlCalls := 0
  1413  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  1414  		systemctlCalls++
  1415  		switch systemctlCalls {
  1416  		case 1:
  1417  			// double check that we wrote the new file here before calling
  1418  			// daemon reload
  1419  			c.Assert(svcFile, testutil.FileEquals, fmt.Sprintf(template,
  1420  				systemd.EscapeUnitNamePath(dir),
  1421  				dirs.GlobalRootDir,
  1422  				"",
  1423  			))
  1424  
  1425  			// now return an error to trigger a rollback
  1426  			return nil, fmt.Errorf("oops")
  1427  		case 2:
  1428  			// after the rollback, check that the new file was deleted
  1429  			c.Assert(svcFile, testutil.FileAbsent)
  1430  
  1431  			return nil, nil
  1432  		default:
  1433  			c.Errorf("unexpected call (number %d) to systemctl: %+v", systemctlCalls, cmd)
  1434  			return nil, fmt.Errorf("broken test")
  1435  		}
  1436  	})
  1437  	defer r()
  1438  
  1439  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
  1440  		info: nil,
  1441  	}
  1442  
  1443  	err := wrappers.EnsureSnapServices(m, nil, nil, progress.Null)
  1444  	c.Assert(err, ErrorMatches, "oops")
  1445  	c.Assert(systemctlCalls, Equals, 2)
  1446  
  1447  	// double-check that after the function is done, the file is gone again
  1448  	c.Assert(svcFile, testutil.FileAbsent)
  1449  }
  1450  
  1451  func (s *servicesTestSuite) TestEnsureSnapServicesOnlyRemovesNewAddOnRollback(c *C) {
  1452  	info := snaptest.MockSnap(c, packageHello+` svc2:
  1453    command: bin/hello
  1454    stop-command: bin/goodbye
  1455    post-stop-command: bin/missya
  1456    daemon: forking
  1457  `, &snap.SideInfo{Revision: snap.R(12)})
  1458  
  1459  	// we won't delete existing files, but we will delete new files, so mock an
  1460  	// existing file to check that it doesn't get deleted
  1461  	svc1File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
  1462  	svc2File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc2.service")
  1463  
  1464  	// pretend we already have a unit file with no VitalityRank options set
  1465  	dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
  1466  	template := `[Unit]
  1467  # Auto-generated, DO NOT EDIT
  1468  Description=Service for snap application hello-snap.%[1]s
  1469  Requires=%[2]s
  1470  Wants=network.target
  1471  After=%[2]s network.target snapd.apparmor.service
  1472  X-Snappy=yes
  1473  
  1474  [Service]
  1475  EnvironmentFile=-/etc/environment
  1476  ExecStart=/usr/bin/snap run hello-snap.%[1]s
  1477  SyslogIdentifier=hello-snap.%[1]s
  1478  Restart=on-failure
  1479  WorkingDirectory=%[3]s/var/snap/hello-snap/12
  1480  ExecStop=/usr/bin/snap run --command=stop hello-snap.%[1]s
  1481  ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.%[1]s
  1482  TimeoutStopSec=30
  1483  Type=forking
  1484  %[4]s
  1485  [Install]
  1486  WantedBy=multi-user.target
  1487  `
  1488  
  1489  	svc1Content := fmt.Sprintf(template,
  1490  		"svc1",
  1491  		systemd.EscapeUnitNamePath(dir),
  1492  		dirs.GlobalRootDir,
  1493  		"",
  1494  	)
  1495  	svc2Content := fmt.Sprintf(template,
  1496  		"svc2",
  1497  		systemd.EscapeUnitNamePath(dir),
  1498  		dirs.GlobalRootDir,
  1499  		"",
  1500  	)
  1501  
  1502  	err := os.MkdirAll(filepath.Dir(svc1File), 0755)
  1503  	c.Assert(err, IsNil)
  1504  	err = ioutil.WriteFile(svc1File, []byte(svc1Content), 0644)
  1505  	c.Assert(err, IsNil)
  1506  
  1507  	// make systemctl fail the first time when we try to do a daemon-reload,
  1508  	// then the next time don't return an error
  1509  	systemctlCalls := 0
  1510  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  1511  		systemctlCalls++
  1512  		switch systemctlCalls {
  1513  		case 1:
  1514  			// double check that we wrote the new file here before calling
  1515  			// daemon reload
  1516  			c.Assert(svc2File, testutil.FileEquals, svc2Content)
  1517  
  1518  			// and the existing file is still the same
  1519  			c.Assert(svc1File, testutil.FileEquals, svc1Content)
  1520  
  1521  			// now return error to trigger a rollback
  1522  			return nil, fmt.Errorf("oops")
  1523  		case 2:
  1524  			// after the rollback, check that the new file was deleted
  1525  			c.Assert(svc2File, testutil.FileAbsent)
  1526  
  1527  			return nil, nil
  1528  		default:
  1529  			c.Errorf("unexpected call (number %d) to systemctl: %+v", systemctlCalls, cmd)
  1530  			return nil, fmt.Errorf("broken test")
  1531  		}
  1532  	})
  1533  	defer r()
  1534  
  1535  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
  1536  		info: nil,
  1537  	}
  1538  
  1539  	err = wrappers.EnsureSnapServices(m, nil, nil, progress.Null)
  1540  	c.Assert(err, ErrorMatches, "oops")
  1541  	c.Assert(systemctlCalls, Equals, 2)
  1542  
  1543  	// double-check that after the function, svc2 (the new one) is missing, but
  1544  	// svc1 is still the same
  1545  	c.Assert(svc2File, testutil.FileAbsent)
  1546  	c.Assert(svc1File, testutil.FileEquals, svc1Content)
  1547  }
  1548  
  1549  func (s *servicesTestSuite) TestEnsureSnapServicesSubunits(c *C) {
  1550  	// map unit -> new
  1551  	seen := make(map[string]bool)
  1552  	cb := func(app *snap.AppInfo, grp *quota.Group, unitType, name string, old, new string) {
  1553  		seen[fmt.Sprintf("%s:%s:%s:%s", app.Snap.InstanceName(), app.Name, unitType, name)] = old == ""
  1554  	}
  1555  
  1556  	info := snaptest.MockSnap(c, packageHello+`
  1557    timer: 10:00-12:00
  1558  `, &snap.SideInfo{Revision: snap.R(11)})
  1559  
  1560  	m := map[*snap.Info]*wrappers.SnapServiceOptions{
  1561  		info: nil,
  1562  	}
  1563  	err := wrappers.EnsureSnapServices(m, nil, cb, progress.Null)
  1564  	c.Assert(err, IsNil)
  1565  
  1566  	c.Check(seen, DeepEquals, map[string]bool{
  1567  		"hello-snap:svc1:service:svc1": true,
  1568  		"hello-snap:svc1:timer:":       true,
  1569  	})
  1570  	// reset
  1571  	seen = make(map[string]bool)
  1572  
  1573  	// change vitality, timer, add socket
  1574  	info = snaptest.MockSnap(c, packageHello+`
  1575    plugs: [network-bind]
  1576    timer: 10:00-12:00,20:00-22:00
  1577    sockets:
  1578      sock1:
  1579        listen-stream: $SNAP_DATA/sock1.socket
  1580  `, &snap.SideInfo{Revision: snap.R(12)})
  1581  
  1582  	m = map[*snap.Info]*wrappers.SnapServiceOptions{
  1583  		info: {VitalityRank: 1},
  1584  	}
  1585  	err = wrappers.EnsureSnapServices(m, nil, cb, progress.Null)
  1586  	c.Assert(err, IsNil)
  1587  
  1588  	c.Check(seen, DeepEquals, map[string]bool{
  1589  		"hello-snap:svc1:service:svc1": false,
  1590  		"hello-snap:svc1:timer:":       false,
  1591  		"hello-snap:svc1:socket:sock1": true,
  1592  	})
  1593  }
  1594  
  1595  func (s *servicesTestSuite) TestAddSnapServicesWithInterfaceSnippets(c *C) {
  1596  	tt := []struct {
  1597  		comment     string
  1598  		plugSnippet string
  1599  	}{
  1600  		// just single bare interfaces with no attributes
  1601  		{
  1602  			"docker-support",
  1603  			`
  1604    plugs:
  1605     - docker-support`,
  1606  		},
  1607  		{
  1608  			"k8s-support",
  1609  			`
  1610    plugs:
  1611     - kubernetes-support`,
  1612  		},
  1613  		{
  1614  			"lxd-support",
  1615  			`
  1616    plugs:
  1617     - lxd-support
  1618  `,
  1619  		},
  1620  		{
  1621  			"greengrass-support",
  1622  			`
  1623    plugs:
  1624     - greengrass-support
  1625  `,
  1626  		},
  1627  
  1628  		// multiple interfaces that require Delegate=true, but only one is
  1629  		// generated
  1630  
  1631  		{
  1632  			"multiple interfaces that require Delegate=true",
  1633  			`
  1634    plugs:
  1635     - docker-support
  1636     - kubernetes-support`,
  1637  		},
  1638  
  1639  		// interfaces with flavor attributes
  1640  
  1641  		{
  1642  			"k8s-support with kubelet",
  1643  			`
  1644    plugs:
  1645     - kubelet
  1646  plugs:
  1647   kubelet:
  1648    interface: kubernetes-support
  1649    flavor: kubelet
  1650  `,
  1651  		},
  1652  		{
  1653  			"k8s-support with kubeproxy",
  1654  			`
  1655    plugs:
  1656     - kubeproxy
  1657  plugs:
  1658   kubeproxy:
  1659    interface: kubernetes-support
  1660    flavor: kubeproxy
  1661  `,
  1662  		},
  1663  		{
  1664  			"greengrass-support with legacy-container flavor",
  1665  			`
  1666    plugs:
  1667     - greengrass
  1668  plugs:
  1669   greengrass:
  1670    interface: greengrass-support
  1671    flavor: legacy-container
  1672  `,
  1673  		},
  1674  	}
  1675  
  1676  	for _, t := range tt {
  1677  		comment := Commentf(t.comment)
  1678  		info := snaptest.MockSnap(c, packageHello+`
  1679   svc1:
  1680    daemon: simple
  1681  `+t.plugSnippet,
  1682  			&snap.SideInfo{Revision: snap.R(12)})
  1683  		svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
  1684  
  1685  		err := wrappers.AddSnapServices(info, nil, progress.Null)
  1686  		c.Assert(err, IsNil, comment)
  1687  		c.Check(s.sysdLog, DeepEquals, [][]string{
  1688  			{"daemon-reload"},
  1689  		}, comment)
  1690  
  1691  		dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount")
  1692  		c.Assert(svcFile, testutil.FileEquals, fmt.Sprintf(`[Unit]
  1693  # Auto-generated, DO NOT EDIT
  1694  Description=Service for snap application hello-snap.svc1
  1695  Requires=%[1]s
  1696  Wants=network.target
  1697  After=%[1]s network.target snapd.apparmor.service
  1698  X-Snappy=yes
  1699  
  1700  [Service]
  1701  EnvironmentFile=-/etc/environment
  1702  ExecStart=/usr/bin/snap run hello-snap.svc1
  1703  SyslogIdentifier=hello-snap.svc1
  1704  Restart=on-failure
  1705  WorkingDirectory=%[2]s/var/snap/hello-snap/12
  1706  TimeoutStopSec=30
  1707  Type=simple
  1708  Delegate=true
  1709  
  1710  [Install]
  1711  WantedBy=multi-user.target
  1712  `,
  1713  			systemd.EscapeUnitNamePath(dir),
  1714  			dirs.GlobalRootDir,
  1715  		), comment)
  1716  
  1717  		s.sysdLog = nil
  1718  		err = wrappers.StopServices(info.Services(), nil, "", progress.Null, s.perfTimings)
  1719  		c.Assert(err, IsNil, comment)
  1720  		c.Assert(s.sysdLog, HasLen, 2, comment)
  1721  		c.Check(s.sysdLog, DeepEquals, [][]string{
  1722  			{"stop", filepath.Base(svcFile)},
  1723  			{"show", "--property=ActiveState", "snap.hello-snap.svc1.service"},
  1724  		}, comment)
  1725  
  1726  		s.sysdLog = nil
  1727  		err = wrappers.RemoveSnapServices(info, progress.Null)
  1728  		c.Assert(err, IsNil, comment)
  1729  		c.Check(osutil.FileExists(svcFile), Equals, false, comment)
  1730  		c.Assert(s.sysdLog, HasLen, 2, comment)
  1731  		c.Check(s.sysdLog, DeepEquals, [][]string{
  1732  			{"disable", filepath.Base(svcFile)},
  1733  			{"daemon-reload"},
  1734  		}, comment)
  1735  
  1736  		s.sysdLog = nil
  1737  	}
  1738  }
  1739  
  1740  func (s *servicesTestSuite) TestAddSnapServicesAndRemoveUserDaemons(c *C) {
  1741  	info := snaptest.MockSnap(c, packageHello+`
  1742   svc1:
  1743    daemon: simple
  1744    daemon-scope: user
  1745  `, &snap.SideInfo{Revision: snap.R(12)})
  1746  	svcFile := filepath.Join(s.tempdir, "/etc/systemd/user/snap.hello-snap.svc1.service")
  1747  
  1748  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  1749  	c.Assert(err, IsNil)
  1750  	c.Check(s.sysdLog, DeepEquals, [][]string{
  1751  		{"--user", "daemon-reload"},
  1752  	})
  1753  
  1754  	expected := "ExecStart=/usr/bin/snap run hello-snap.svc1"
  1755  	c.Check(svcFile, testutil.FileMatches, "(?ms).*^"+regexp.QuoteMeta(expected)) // check.v1 adds ^ and $ around the regexp provided
  1756  
  1757  	s.sysdLog = nil
  1758  	err = wrappers.StopServices(info.Services(), nil, "", progress.Null, s.perfTimings)
  1759  	c.Assert(err, IsNil)
  1760  	c.Assert(s.sysdLog, HasLen, 2)
  1761  	c.Check(s.sysdLog, DeepEquals, [][]string{
  1762  		{"--user", "stop", filepath.Base(svcFile)},
  1763  		{"--user", "show", "--property=ActiveState", "snap.hello-snap.svc1.service"},
  1764  	})
  1765  
  1766  	s.sysdLog = nil
  1767  	err = wrappers.RemoveSnapServices(info, progress.Null)
  1768  	c.Assert(err, IsNil)
  1769  	c.Check(osutil.FileExists(svcFile), Equals, false)
  1770  	c.Assert(s.sysdLog, HasLen, 2)
  1771  	c.Check(s.sysdLog, DeepEquals, [][]string{
  1772  		{"--user", "--global", "disable", filepath.Base(svcFile)},
  1773  		{"--user", "daemon-reload"},
  1774  	})
  1775  }
  1776  
  1777  var snapdYaml = `name: snapd
  1778  version: 1.0
  1779  type: snapd
  1780  `
  1781  
  1782  func (s *servicesTestSuite) TestRemoveSnapWithSocketsRemovesSocketsService(c *C) {
  1783  	info := snaptest.MockSnap(c, packageHello+`
  1784   svc1:
  1785    daemon: simple
  1786    plugs: [network-bind]
  1787    sockets:
  1788      sock1:
  1789        listen-stream: $SNAP_DATA/sock1.socket
  1790        socket-mode: 0666
  1791      sock2:
  1792        listen-stream: $SNAP_COMMON/sock2.socket
  1793  `, &snap.SideInfo{Revision: snap.R(12)})
  1794  
  1795  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  1796  	c.Assert(err, IsNil)
  1797  
  1798  	err = wrappers.StopServices(info.Services(), nil, "", &progress.Null, s.perfTimings)
  1799  	c.Assert(err, IsNil)
  1800  
  1801  	err = wrappers.RemoveSnapServices(info, &progress.Null)
  1802  	c.Assert(err, IsNil)
  1803  
  1804  	app := info.Apps["svc1"]
  1805  	c.Assert(app.Sockets, HasLen, 2)
  1806  	for _, socket := range app.Sockets {
  1807  		c.Check(osutil.FileExists(socket.File()), Equals, false)
  1808  	}
  1809  }
  1810  
  1811  func (s *servicesTestSuite) TestRemoveSnapPackageFallbackToKill(c *C) {
  1812  	restore := wrappers.MockKillWait(time.Millisecond)
  1813  	defer restore()
  1814  
  1815  	var sysdLog [][]string
  1816  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  1817  		// filter out the "systemctl show" that
  1818  		// StopServices generates
  1819  		if cmd[0] != "show" {
  1820  			sysdLog = append(sysdLog, cmd)
  1821  		}
  1822  		return []byte("ActiveState=active\n"), nil
  1823  	})
  1824  	defer r()
  1825  
  1826  	info := snaptest.MockSnap(c, `name: wat
  1827  version: 42
  1828  apps:
  1829   wat:
  1830     command: wat
  1831     stop-timeout: 20ms
  1832     daemon: forking
  1833  `, &snap.SideInfo{Revision: snap.R(11)})
  1834  
  1835  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  1836  	c.Assert(err, IsNil)
  1837  
  1838  	sysdLog = nil
  1839  
  1840  	svcFName := "snap.wat.wat.service"
  1841  
  1842  	err = wrappers.StopServices(info.Services(), nil, "", progress.Null, s.perfTimings)
  1843  	c.Assert(err, IsNil)
  1844  
  1845  	c.Check(sysdLog, DeepEquals, [][]string{
  1846  		{"stop", svcFName},
  1847  		// check kill invocations
  1848  		{"kill", svcFName, "-s", "TERM", "--kill-who=all"},
  1849  		{"kill", svcFName, "-s", "KILL", "--kill-who=all"},
  1850  	})
  1851  }
  1852  
  1853  func (s *servicesTestSuite) TestRemoveSnapPackageUserDaemonStopFailure(c *C) {
  1854  	var sysdLog [][]string
  1855  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  1856  		// filter out the "systemctl --user show" that
  1857  		// StopServices generates
  1858  		if cmd[0] == "--user" && cmd[1] != "show" {
  1859  			sysdLog = append(sysdLog, cmd)
  1860  		}
  1861  		if cmd[0] == "--user" && cmd[1] == "stop" {
  1862  			return nil, fmt.Errorf("user unit stop failed")
  1863  		}
  1864  		return []byte("ActiveState=active\n"), nil
  1865  	})
  1866  	defer r()
  1867  
  1868  	info := snaptest.MockSnap(c, `name: wat
  1869  version: 42
  1870  apps:
  1871   wat:
  1872     command: wat
  1873     stop-timeout: 20ms
  1874     daemon: forking
  1875     daemon-scope: user
  1876  `, &snap.SideInfo{Revision: snap.R(11)})
  1877  
  1878  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  1879  	c.Assert(err, IsNil)
  1880  
  1881  	sysdLog = nil
  1882  
  1883  	svcFName := "snap.wat.wat.service"
  1884  
  1885  	err = wrappers.StopServices(info.Services(), nil, "", progress.Null, s.perfTimings)
  1886  	c.Check(err, ErrorMatches, "some user services failed to stop")
  1887  	c.Check(sysdLog, DeepEquals, [][]string{
  1888  		{"--user", "stop", svcFName},
  1889  	})
  1890  }
  1891  
  1892  func (s *servicesTestSuite) TestServicesEnableState(c *C) {
  1893  	info := snaptest.MockSnap(c, packageHello+`
  1894   svc2:
  1895    command: bin/hello
  1896    daemon: forking
  1897   svc3:
  1898    command: bin/hello
  1899    daemon: simple
  1900    daemon-scope: user
  1901  `, &snap.SideInfo{Revision: snap.R(12)})
  1902  	svc1File := "snap.hello-snap.svc1.service"
  1903  	svc2File := "snap.hello-snap.svc2.service"
  1904  
  1905  	s.systemctlRestorer()
  1906  	r := testutil.MockCommand(c, "systemctl", `#!/bin/sh
  1907  	if [ "$1" = "--root" ]; then
  1908  		# shifting by 2 also drops the temp dir arg to --root
  1909  	    shift 2
  1910  	fi
  1911  
  1912  	case "$1" in
  1913  		is-enabled)
  1914  			case "$2" in 
  1915  			"snap.hello-snap.svc1.service")
  1916  				echo "disabled"
  1917  				exit 1
  1918  				;;
  1919  			"snap.hello-snap.svc2.service")
  1920  				echo "enabled"
  1921  				exit 0
  1922  				;;
  1923  			*)
  1924  				echo "unexpected is-enabled of service $2"
  1925  				exit 2
  1926  				;;
  1927  			esac
  1928  	        ;;
  1929  	    *)
  1930  	        echo "unexpected op $*"
  1931  	        exit 2
  1932  	esac
  1933  
  1934  	exit 1
  1935  	`)
  1936  	defer r.Restore()
  1937  
  1938  	states, err := wrappers.ServicesEnableState(info, progress.Null)
  1939  	c.Assert(err, IsNil)
  1940  
  1941  	c.Assert(states, DeepEquals, map[string]bool{
  1942  		"svc1": false,
  1943  		"svc2": true,
  1944  	})
  1945  
  1946  	// the calls could be out of order in the list, since iterating over a map
  1947  	// is non-deterministic, so manually check each call
  1948  	c.Assert(r.Calls(), HasLen, 2)
  1949  	for _, call := range r.Calls() {
  1950  		c.Assert(call, HasLen, 3)
  1951  		c.Assert(call[:2], DeepEquals, []string{"systemctl", "is-enabled"})
  1952  		switch call[2] {
  1953  		case svc1File, svc2File:
  1954  		default:
  1955  			c.Errorf("unknown service for systemctl call: %s", call[2])
  1956  		}
  1957  	}
  1958  }
  1959  
  1960  func (s *servicesTestSuite) TestServicesEnableStateFail(c *C) {
  1961  	info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
  1962  	svc1File := "snap.hello-snap.svc1.service"
  1963  
  1964  	s.systemctlRestorer()
  1965  	r := testutil.MockCommand(c, "systemctl", `#!/bin/sh
  1966  	if [ "$1" = "--root" ]; then
  1967  		# shifting by 2 also drops the temp dir arg to --root
  1968  	    shift 2
  1969  	fi
  1970  
  1971  	case "$1" in
  1972  		is-enabled)
  1973  			case "$2" in
  1974  			"snap.hello-snap.svc1.service")
  1975  				echo "whoops"
  1976  				exit 1
  1977  				;;
  1978  			*)
  1979  				echo "unexpected is-enabled of service $2"
  1980  				exit 2
  1981  				;;
  1982  			esac
  1983  	        ;;
  1984  	    *)
  1985  	        echo "unexpected op $*"
  1986  	        exit 2
  1987  	esac
  1988  
  1989  	exit 1
  1990  	`)
  1991  	defer r.Restore()
  1992  
  1993  	_, err := wrappers.ServicesEnableState(info, progress.Null)
  1994  	c.Assert(err, ErrorMatches, ".*is-enabled snap.hello-snap.svc1.service\\] failed with exit status 1: whoops\n.*")
  1995  
  1996  	c.Assert(r.Calls(), DeepEquals, [][]string{
  1997  		{"systemctl", "is-enabled", svc1File},
  1998  	})
  1999  }
  2000  
  2001  func (s *servicesTestSuite) TestAddSnapServicesWithDisabledServices(c *C) {
  2002  	info := snaptest.MockSnap(c, packageHello+`
  2003   svc2:
  2004    command: bin/hello
  2005    daemon: forking
  2006  `, &snap.SideInfo{Revision: snap.R(12)})
  2007  
  2008  	s.systemctlRestorer()
  2009  	r := testutil.MockCommand(c, "systemctl", `#!/bin/sh
  2010  	if [ "$1" = "--root" ]; then
  2011  	    shift 2
  2012  	fi
  2013  
  2014  	case "$1" in
  2015  		enable)
  2016  			case "$2" in 
  2017  				"snap.hello-snap.svc1.service")
  2018  					echo "unexpected enable of disabled service $2"
  2019  					exit 1
  2020  					;;
  2021  				"snap.hello-snap.svc2.service")
  2022  					exit 0
  2023  					;;
  2024  				*)
  2025  					echo "unexpected enable of service $2"
  2026  					exit 1
  2027  					;;
  2028  			esac
  2029  			;;
  2030  		start)
  2031  			case "$2" in
  2032  				"snap.hello-snap.svc2.service")
  2033  					exit 0
  2034  					;;
  2035  			*)
  2036  					echo "unexpected start of service $2"
  2037  					exit 1
  2038  					;;
  2039  			esac
  2040  			;;
  2041  		daemon-reload)
  2042  			exit 0
  2043  			;;
  2044  	    *)
  2045  	        echo "unexpected op $*"
  2046  	        exit 2
  2047  	esac
  2048  	exit 2
  2049  	`)
  2050  	defer r.Restore()
  2051  
  2052  	// svc1 will be disabled
  2053  	disabledSvcs := []string{"svc1"}
  2054  
  2055  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  2056  	c.Assert(err, IsNil)
  2057  
  2058  	c.Assert(r.Calls(), DeepEquals, [][]string{
  2059  		{"systemctl", "daemon-reload"},
  2060  	})
  2061  
  2062  	r.ForgetCalls()
  2063  
  2064  	flags := &wrappers.StartServicesFlags{Enable: true}
  2065  	err = wrappers.StartServices(info.Services(), disabledSvcs, flags, progress.Null, s.perfTimings)
  2066  	c.Assert(err, IsNil)
  2067  
  2068  	// only svc2 should be enabled
  2069  	c.Assert(r.Calls(), DeepEquals, [][]string{
  2070  		{"systemctl", "enable", "snap.hello-snap.svc2.service"},
  2071  		{"systemctl", "start", "snap.hello-snap.svc2.service"},
  2072  	})
  2073  }
  2074  
  2075  func (s *servicesTestSuite) TestAddSnapServicesWithPreseed(c *C) {
  2076  	opts := &wrappers.AddSnapServicesOptions{Preseeding: true}
  2077  
  2078  	info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
  2079  
  2080  	s.systemctlRestorer()
  2081  	r := testutil.MockCommand(c, "systemctl", "exit 1")
  2082  	defer r.Restore()
  2083  
  2084  	err := wrappers.AddSnapServices(info, opts, progress.Null)
  2085  	c.Assert(err, IsNil)
  2086  
  2087  	// file was created
  2088  	svcFiles, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.*.service"))
  2089  	c.Check(svcFiles, HasLen, 1)
  2090  
  2091  	// but systemctl was not called
  2092  	c.Assert(r.Calls(), HasLen, 0)
  2093  }
  2094  
  2095  func (s *servicesTestSuite) TestStopServicesWithSockets(c *C) {
  2096  	var sysServices, userServices []string
  2097  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  2098  		if cmd[0] == "stop" {
  2099  			sysServices = append(sysServices, cmd[1])
  2100  		} else if cmd[0] == "--user" && cmd[1] == "stop" {
  2101  			userServices = append(userServices, cmd[2])
  2102  		}
  2103  		return []byte("ActiveState=inactive\n"), nil
  2104  	})
  2105  	defer r()
  2106  
  2107  	info := snaptest.MockSnap(c, packageHello+`
  2108   svc1:
  2109    daemon: simple
  2110    plugs: [network-bind]
  2111    sockets:
  2112      sock1:
  2113        listen-stream: $SNAP_COMMON/sock1.socket
  2114        socket-mode: 0666
  2115      sock2:
  2116        listen-stream: $SNAP_DATA/sock2.socket
  2117   svc2:
  2118    daemon: simple
  2119    daemon-scope: user
  2120    plugs: [network-bind]
  2121    sockets:
  2122      sock1:
  2123        listen-stream: $SNAP_USER_COMMON/sock1.socket
  2124        socket-mode: 0666
  2125      sock2:
  2126        listen-stream: $SNAP_USER_DATA/sock2.socket
  2127  `, &snap.SideInfo{Revision: snap.R(12)})
  2128  
  2129  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  2130  	c.Assert(err, IsNil)
  2131  
  2132  	sysServices = nil
  2133  	userServices = nil
  2134  
  2135  	err = wrappers.StopServices(info.Services(), nil, "", &progress.Null, s.perfTimings)
  2136  	c.Assert(err, IsNil)
  2137  
  2138  	sort.Strings(sysServices)
  2139  	c.Check(sysServices, DeepEquals, []string{
  2140  		"snap.hello-snap.svc1.service", "snap.hello-snap.svc1.sock1.socket", "snap.hello-snap.svc1.sock2.socket"})
  2141  	sort.Strings(userServices)
  2142  	c.Check(userServices, DeepEquals, []string{
  2143  		"snap.hello-snap.svc2.service", "snap.hello-snap.svc2.sock1.socket", "snap.hello-snap.svc2.sock2.socket"})
  2144  }
  2145  
  2146  func (s *servicesTestSuite) TestStartServices(c *C) {
  2147  	info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
  2148  	svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
  2149  
  2150  	flags := &wrappers.StartServicesFlags{Enable: true}
  2151  	err := wrappers.StartServices(info.Services(), nil, flags, &progress.Null, s.perfTimings)
  2152  	c.Assert(err, IsNil)
  2153  
  2154  	c.Check(s.sysdLog, DeepEquals, [][]string{
  2155  		{"enable", filepath.Base(svcFile)},
  2156  		{"start", filepath.Base(svcFile)},
  2157  	})
  2158  }
  2159  
  2160  func (s *servicesTestSuite) TestStartServicesUserDaemons(c *C) {
  2161  	info := snaptest.MockSnap(c, packageHello+`
  2162   svc1:
  2163    daemon: simple
  2164    daemon-scope: user
  2165  `, &snap.SideInfo{Revision: snap.R(12)})
  2166  	svcFile := filepath.Join(s.tempdir, "/etc/systemd/user/snap.hello-snap.svc1.service")
  2167  
  2168  	flags := &wrappers.StartServicesFlags{Enable: true}
  2169  	err := wrappers.StartServices(info.Services(), nil, flags, &progress.Null, s.perfTimings)
  2170  	c.Assert(err, IsNil)
  2171  
  2172  	c.Assert(s.sysdLog, DeepEquals, [][]string{
  2173  		{"--user", "--global", "enable", filepath.Base(svcFile)},
  2174  		{"--user", "start", filepath.Base(svcFile)},
  2175  	})
  2176  }
  2177  
  2178  func (s *servicesTestSuite) TestStartServicesEnabledConditional(c *C) {
  2179  	info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
  2180  	svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service")
  2181  
  2182  	flags := &wrappers.StartServicesFlags{}
  2183  	c.Check(wrappers.StartServices(info.Services(), nil, flags, progress.Null, s.perfTimings), IsNil)
  2184  	c.Check(s.sysdLog, DeepEquals, [][]string{{"start", filepath.Base(svcFile)}})
  2185  }
  2186  
  2187  func (s *servicesTestSuite) TestNoStartDisabledServices(c *C) {
  2188  	svc2Name := "snap.hello-snap.svc2.service"
  2189  
  2190  	info := snaptest.MockSnap(c, packageHello+`
  2191   svc2:
  2192    command: bin/hello
  2193    daemon: simple
  2194  `, &snap.SideInfo{Revision: snap.R(12)})
  2195  
  2196  	s.systemctlRestorer()
  2197  	r := testutil.MockCommand(c, "systemctl", `#!/bin/sh
  2198  	if [ "$1" = "--root" ]; then
  2199  	    shift 2
  2200  	fi
  2201  
  2202  	case "$1" in
  2203  		start)
  2204  			if [ "$2" = "snap.hello-snap.svc2.service" ]; then
  2205  				exit 0
  2206  			fi
  2207  			echo "unexpected start of service $2"
  2208  			exit 1
  2209  			;;
  2210  		enable)
  2211  			if [ "$2" = "snap.hello-snap.svc2.service" ]; then
  2212  				exit 0
  2213  			fi
  2214  			echo "unexpected enable of service $2"
  2215  			exit 1
  2216  			;;
  2217  	    *)
  2218  	        echo "unexpected call $*"
  2219  	        exit 2
  2220  	esac
  2221  	`)
  2222  	defer r.Restore()
  2223  
  2224  	flags := &wrappers.StartServicesFlags{Enable: true}
  2225  	err := wrappers.StartServices(info.Services(), []string{"svc1"}, flags, &progress.Null, s.perfTimings)
  2226  	c.Assert(err, IsNil)
  2227  	c.Assert(r.Calls(), DeepEquals, [][]string{
  2228  		{"systemctl", "enable", svc2Name},
  2229  		{"systemctl", "start", svc2Name},
  2230  	})
  2231  }
  2232  
  2233  func (s *servicesTestSuite) TestAddSnapMultiServicesFailCreateCleanup(c *C) {
  2234  	// sanity check: there are no service files
  2235  	svcFiles, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service"))
  2236  	c.Check(svcFiles, HasLen, 0)
  2237  
  2238  	info := snaptest.MockSnap(c, packageHello+`
  2239   svc2:
  2240    daemon: potato
  2241  `, &snap.SideInfo{Revision: snap.R(12)})
  2242  
  2243  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  2244  	c.Assert(err, ErrorMatches, ".*potato.*")
  2245  
  2246  	// the services are cleaned up
  2247  	svcFiles, _ = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service"))
  2248  	c.Check(svcFiles, HasLen, 0)
  2249  
  2250  	// *either* the first service failed validation, and nothing
  2251  	// was done, *or* the second one failed, and the first one was
  2252  	// enabled before the second failed, and disabled after.
  2253  	if len(s.sysdLog) > 0 {
  2254  		// the second service failed validation
  2255  		c.Check(s.sysdLog, DeepEquals, [][]string{
  2256  			{"daemon-reload"},
  2257  		})
  2258  	}
  2259  }
  2260  
  2261  func (s *servicesTestSuite) TestMultiServicesFailEnableCleanup(c *C) {
  2262  	var sysdLog [][]string
  2263  	svc1Name := "snap.hello-snap.svc1.service"
  2264  	svc2Name := "snap.hello-snap.svc2.service"
  2265  	numEnables := 0
  2266  
  2267  	// sanity check: there are no service files
  2268  	svcFiles, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service"))
  2269  	c.Check(svcFiles, HasLen, 0)
  2270  
  2271  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  2272  		sysdLog = append(sysdLog, cmd)
  2273  		sdcmd := cmd[0]
  2274  		if sdcmd == "show" {
  2275  			return []byte("ActiveState=inactive"), nil
  2276  		}
  2277  		if len(cmd) >= 2 {
  2278  			sdcmd = cmd[len(cmd)-2]
  2279  		}
  2280  		switch sdcmd {
  2281  		case "enable":
  2282  			numEnables++
  2283  			switch numEnables {
  2284  			case 1:
  2285  				if cmd[len(cmd)-1] == svc2Name {
  2286  					// the services are being iterated in the "wrong" order
  2287  					svc1Name, svc2Name = svc2Name, svc1Name
  2288  				}
  2289  				return nil, nil
  2290  			case 2:
  2291  				return nil, fmt.Errorf("failed")
  2292  			default:
  2293  				panic("expected no more than 2 enables")
  2294  			}
  2295  		case "disable", "daemon-reload", "stop":
  2296  			return nil, nil
  2297  		default:
  2298  			panic("unexpected systemctl command " + sdcmd)
  2299  		}
  2300  	})
  2301  	defer r()
  2302  
  2303  	info := snaptest.MockSnap(c, packageHello+`
  2304   svc2:
  2305    command: bin/hello
  2306    daemon: simple
  2307  `, &snap.SideInfo{Revision: snap.R(12)})
  2308  
  2309  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  2310  	c.Assert(err, IsNil)
  2311  
  2312  	svcFiles, _ = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service"))
  2313  	c.Check(svcFiles, HasLen, 2)
  2314  
  2315  	flags := &wrappers.StartServicesFlags{Enable: true}
  2316  	err = wrappers.StartServices(info.Services(), nil, flags, progress.Null, s.perfTimings)
  2317  	c.Assert(err, ErrorMatches, "failed")
  2318  
  2319  	c.Check(sysdLog, DeepEquals, [][]string{
  2320  		{"daemon-reload"}, // from AddSnapServices
  2321  		{"enable", svc1Name},
  2322  		{"enable", svc2Name}, // this one fails
  2323  		{"disable", svc1Name},
  2324  	})
  2325  }
  2326  
  2327  func (s *servicesTestSuite) TestAddSnapMultiServicesStartFailOnSystemdReloadCleanup(c *C) {
  2328  	// this test might be overdoing it (it's mostly covering the same ground as the previous one), but ... :-)
  2329  	var sysdLog [][]string
  2330  
  2331  	// sanity check: there are no service files
  2332  	svcFiles, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service"))
  2333  	c.Check(svcFiles, HasLen, 0)
  2334  
  2335  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  2336  		sysdLog = append(sysdLog, cmd)
  2337  		if cmd[0] == "daemon-reload" {
  2338  			return nil, fmt.Errorf("failed")
  2339  		}
  2340  		c.Fatalf("unexpected systemctl call")
  2341  		return nil, nil
  2342  
  2343  	})
  2344  	defer r()
  2345  
  2346  	info := snaptest.MockSnap(c, packageHello+`
  2347   svc2:
  2348    command: bin/hello
  2349    daemon: simple
  2350  `, &snap.SideInfo{Revision: snap.R(12)})
  2351  
  2352  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  2353  	c.Assert(err, ErrorMatches, "failed")
  2354  
  2355  	// the services are cleaned up
  2356  	svcFiles, _ = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service"))
  2357  	c.Check(svcFiles, HasLen, 0)
  2358  	c.Check(sysdLog, DeepEquals, [][]string{
  2359  		{"daemon-reload"}, // this one fails
  2360  		{"daemon-reload"}, // reload as part of cleanup after removal
  2361  	})
  2362  }
  2363  
  2364  func (s *servicesTestSuite) TestAddSnapMultiUserServicesFailEnableCleanup(c *C) {
  2365  	var sysdLog [][]string
  2366  
  2367  	// sanity check: there are no service files
  2368  	svcFiles, _ := filepath.Glob(filepath.Join(dirs.SnapUserServicesDir, "snap.hello-snap.*.service"))
  2369  	c.Check(svcFiles, HasLen, 0)
  2370  
  2371  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  2372  		sysdLog = append(sysdLog, cmd)
  2373  		if len(cmd) >= 1 && cmd[0] == "--user" {
  2374  			cmd = cmd[1:]
  2375  		}
  2376  		if len(cmd) >= 1 && cmd[0] == "--global" {
  2377  			cmd = cmd[1:]
  2378  		}
  2379  		sdcmd := cmd[0]
  2380  		if len(cmd) >= 2 {
  2381  			sdcmd = cmd[len(cmd)-2]
  2382  		}
  2383  		switch sdcmd {
  2384  		case "daemon-reload":
  2385  			return nil, fmt.Errorf("failed")
  2386  		default:
  2387  			panic("unexpected systemctl command " + sdcmd)
  2388  		}
  2389  	})
  2390  	defer r()
  2391  
  2392  	info := snaptest.MockSnap(c, packageHello+`
  2393   svc1:
  2394    command: bin/hello
  2395    daemon: simple
  2396    daemon-scope: user
  2397   svc2:
  2398    command: bin/hello
  2399    daemon: simple
  2400    daemon-scope: user
  2401  `, &snap.SideInfo{Revision: snap.R(12)})
  2402  
  2403  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  2404  	c.Assert(err, ErrorMatches, "cannot reload daemon: failed")
  2405  
  2406  	// the services are cleaned up
  2407  	svcFiles, _ = filepath.Glob(filepath.Join(dirs.SnapUserServicesDir, "snap.hello-snap.*.service"))
  2408  	c.Check(svcFiles, HasLen, 0)
  2409  	c.Check(sysdLog, DeepEquals, [][]string{
  2410  		{"--user", "daemon-reload"},
  2411  		{"--user", "daemon-reload"},
  2412  	})
  2413  }
  2414  
  2415  func (s *servicesTestSuite) TestAddSnapMultiUserServicesStartFailOnSystemdReloadCleanup(c *C) {
  2416  	// this test might be overdoing it (it's mostly covering the same ground as the previous one), but ... :-)
  2417  	var sysdLog [][]string
  2418  	svc1Name := "snap.hello-snap.svc1.service"
  2419  	svc2Name := "snap.hello-snap.svc2.service"
  2420  
  2421  	// sanity check: there are no service files
  2422  	svcFiles, _ := filepath.Glob(filepath.Join(dirs.SnapUserServicesDir, "snap.hello-snap.*.service"))
  2423  	c.Check(svcFiles, HasLen, 0)
  2424  
  2425  	first := true
  2426  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  2427  		sysdLog = append(sysdLog, cmd)
  2428  		if len(cmd) < 3 {
  2429  			return nil, fmt.Errorf("failed")
  2430  		}
  2431  		if first {
  2432  			first = false
  2433  			if cmd[len(cmd)-1] == svc2Name {
  2434  				// the services are being iterated in the "wrong" order
  2435  				svc1Name, svc2Name = svc2Name, svc1Name
  2436  			}
  2437  		}
  2438  		return nil, nil
  2439  
  2440  	})
  2441  	defer r()
  2442  
  2443  	info := snaptest.MockSnap(c, packageHello+`
  2444   svc1:
  2445    command: bin/hello
  2446    daemon: simple
  2447    daemon-scope: user
  2448   svc2:
  2449    command: bin/hello
  2450    daemon: simple
  2451    daemon-scope: user
  2452  `, &snap.SideInfo{Revision: snap.R(12)})
  2453  
  2454  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  2455  	c.Assert(err, ErrorMatches, "cannot reload daemon: failed")
  2456  
  2457  	// the services are cleaned up
  2458  	svcFiles, _ = filepath.Glob(filepath.Join(dirs.SnapUserServicesDir, "snap.hello-snap.*.service"))
  2459  	c.Check(svcFiles, HasLen, 0)
  2460  	c.Check(sysdLog, DeepEquals, [][]string{
  2461  		{"--user", "daemon-reload"}, // this one fails
  2462  		{"--user", "daemon-reload"}, // so does this one :-)
  2463  	})
  2464  }
  2465  
  2466  func (s *servicesTestSuite) TestAddSnapSocketFiles(c *C) {
  2467  	info := snaptest.MockSnap(c, packageHello+`
  2468   svc1:
  2469    daemon: simple
  2470    plugs: [network-bind]
  2471    sockets:
  2472      sock1:
  2473        listen-stream: $SNAP_COMMON/sock1.socket
  2474        socket-mode: 0666
  2475      sock2:
  2476        listen-stream: $SNAP_DATA/sock2.socket
  2477      sock3:
  2478        listen-stream: $XDG_RUNTIME_DIR/sock3.socket
  2479  
  2480  `, &snap.SideInfo{Revision: snap.R(12)})
  2481  
  2482  	sock1File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.sock1.socket")
  2483  	sock2File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.sock2.socket")
  2484  	sock3File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.sock3.socket")
  2485  
  2486  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  2487  	c.Assert(err, IsNil)
  2488  
  2489  	expected := fmt.Sprintf(
  2490  		`[Socket]
  2491  Service=snap.hello-snap.svc1.service
  2492  FileDescriptorName=sock1
  2493  ListenStream=%s
  2494  SocketMode=0666
  2495  
  2496  `, filepath.Join(s.tempdir, "/var/snap/hello-snap/common/sock1.socket"))
  2497  	c.Check(sock1File, testutil.FileContains, expected)
  2498  
  2499  	expected = fmt.Sprintf(
  2500  		`[Socket]
  2501  Service=snap.hello-snap.svc1.service
  2502  FileDescriptorName=sock2
  2503  ListenStream=%s
  2504  
  2505  `, filepath.Join(s.tempdir, "/var/snap/hello-snap/12/sock2.socket"))
  2506  	c.Check(sock2File, testutil.FileContains, expected)
  2507  
  2508  	expected = fmt.Sprintf(
  2509  		`[Socket]
  2510  Service=snap.hello-snap.svc1.service
  2511  FileDescriptorName=sock3
  2512  ListenStream=%s
  2513  
  2514  `, filepath.Join(s.tempdir, "/run/user/0/snap.hello-snap/sock3.socket"))
  2515  	c.Check(sock3File, testutil.FileContains, expected)
  2516  }
  2517  
  2518  func (s *servicesTestSuite) TestAddSnapUserSocketFiles(c *C) {
  2519  	info := snaptest.MockSnap(c, packageHello+`
  2520   svc1:
  2521    daemon: simple
  2522    daemon-scope: user
  2523    plugs: [network-bind]
  2524    sockets:
  2525      sock1:
  2526        listen-stream: $SNAP_USER_COMMON/sock1.socket
  2527        socket-mode: 0666
  2528      sock2:
  2529        listen-stream: $SNAP_USER_DATA/sock2.socket
  2530      sock3:
  2531        listen-stream: $XDG_RUNTIME_DIR/sock3.socket
  2532  `, &snap.SideInfo{Revision: snap.R(12)})
  2533  
  2534  	sock1File := filepath.Join(s.tempdir, "/etc/systemd/user/snap.hello-snap.svc1.sock1.socket")
  2535  	sock2File := filepath.Join(s.tempdir, "/etc/systemd/user/snap.hello-snap.svc1.sock2.socket")
  2536  	sock3File := filepath.Join(s.tempdir, "/etc/systemd/user/snap.hello-snap.svc1.sock3.socket")
  2537  
  2538  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  2539  	c.Assert(err, IsNil)
  2540  
  2541  	expected := `[Socket]
  2542  Service=snap.hello-snap.svc1.service
  2543  FileDescriptorName=sock1
  2544  ListenStream=%h/snap/hello-snap/common/sock1.socket
  2545  SocketMode=0666
  2546  
  2547  `
  2548  	c.Check(sock1File, testutil.FileContains, expected)
  2549  
  2550  	expected = `[Socket]
  2551  Service=snap.hello-snap.svc1.service
  2552  FileDescriptorName=sock2
  2553  ListenStream=%h/snap/hello-snap/12/sock2.socket
  2554  
  2555  `
  2556  	c.Check(sock2File, testutil.FileContains, expected)
  2557  
  2558  	expected = `[Socket]
  2559  Service=snap.hello-snap.svc1.service
  2560  FileDescriptorName=sock3
  2561  ListenStream=%t/snap.hello-snap/sock3.socket
  2562  
  2563  `
  2564  	c.Check(sock3File, testutil.FileContains, expected)
  2565  }
  2566  
  2567  func (s *servicesTestSuite) TestStartSnapMultiServicesFailStartCleanup(c *C) {
  2568  	var sysdLog [][]string
  2569  	svc1Name := "snap.hello-snap.svc1.service"
  2570  	svc2Name := "snap.hello-snap.svc2.service"
  2571  
  2572  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  2573  		sysdLog = append(sysdLog, cmd)
  2574  		if len(cmd) >= 2 && cmd[0] == "start" {
  2575  			name := cmd[len(cmd)-1]
  2576  			if name == svc2Name {
  2577  				return nil, fmt.Errorf("failed")
  2578  			}
  2579  		}
  2580  		return []byte("ActiveState=inactive\n"), nil
  2581  	})
  2582  	defer r()
  2583  
  2584  	info := snaptest.MockSnap(c, packageHello+`
  2585   svc2:
  2586    command: bin/hello
  2587    daemon: simple
  2588  `, &snap.SideInfo{Revision: snap.R(12)})
  2589  
  2590  	svcs := info.Services()
  2591  	c.Assert(svcs, HasLen, 2)
  2592  	if svcs[0].Name == "svc2" {
  2593  		svcs[0], svcs[1] = svcs[1], svcs[0]
  2594  	}
  2595  
  2596  	flags := &wrappers.StartServicesFlags{Enable: true}
  2597  	err := wrappers.StartServices(svcs, nil, flags, &progress.Null, s.perfTimings)
  2598  	c.Assert(err, ErrorMatches, "failed")
  2599  	c.Assert(sysdLog, HasLen, 10, Commentf("len: %v calls: %v", len(sysdLog), sysdLog))
  2600  	c.Check(sysdLog, DeepEquals, [][]string{
  2601  		{"enable", svc1Name},
  2602  		{"enable", svc2Name},
  2603  		{"start", svc1Name},
  2604  		{"start", svc2Name}, // one of the services fails
  2605  		{"stop", svc2Name},
  2606  		{"show", "--property=ActiveState", svc2Name},
  2607  		{"stop", svc1Name},
  2608  		{"show", "--property=ActiveState", svc1Name},
  2609  		{"disable", svc1Name},
  2610  		{"disable", svc2Name},
  2611  	}, Commentf("calls: %v", sysdLog))
  2612  }
  2613  
  2614  func (s *servicesTestSuite) TestStartSnapMultiServicesFailStartCleanupWithSockets(c *C) {
  2615  	var sysdLog [][]string
  2616  	svc1Name := "snap.hello-snap.svc1.service"
  2617  	svc2Name := "snap.hello-snap.svc2.service"
  2618  	svc2SocketName := "snap.hello-snap.svc2.sock1.socket"
  2619  	svc3Name := "snap.hello-snap.svc3.service"
  2620  	svc3SocketName := "snap.hello-snap.svc3.sock1.socket"
  2621  
  2622  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  2623  		sysdLog = append(sysdLog, cmd)
  2624  		c.Logf("call: %v", cmd)
  2625  		if len(cmd) >= 2 && cmd[0] == "start" && cmd[1] == svc3SocketName {
  2626  			// svc2 socket fails
  2627  			return nil, fmt.Errorf("failed")
  2628  		}
  2629  		return []byte("ActiveState=inactive\n"), nil
  2630  	})
  2631  	defer r()
  2632  
  2633  	info := snaptest.MockSnap(c, packageHello+`
  2634   svc2:
  2635    command: bin/hello
  2636    daemon: simple
  2637    sockets:
  2638      sock1:
  2639        listen-stream: $SNAP_COMMON/sock1.socket
  2640        socket-mode: 0666
  2641   svc3:
  2642    command: bin/hello
  2643    daemon: simple
  2644    sockets:
  2645      sock1:
  2646        listen-stream: $SNAP_COMMON/sock1.socket
  2647        socket-mode: 0666
  2648  `, &snap.SideInfo{Revision: snap.R(12)})
  2649  
  2650  	// ensure desired order
  2651  	apps := []*snap.AppInfo{info.Apps["svc1"], info.Apps["svc2"], info.Apps["svc3"]}
  2652  
  2653  	flags := &wrappers.StartServicesFlags{Enable: true}
  2654  	err := wrappers.StartServices(apps, nil, flags, &progress.Null, s.perfTimings)
  2655  	c.Assert(err, ErrorMatches, "failed")
  2656  	c.Logf("sysdlog: %v", sysdLog)
  2657  	c.Assert(sysdLog, HasLen, 18, Commentf("len: %v calls: %v", len(sysdLog), sysdLog))
  2658  	c.Check(sysdLog, DeepEquals, [][]string{
  2659  		{"enable", svc1Name},
  2660  		{"enable", svc2SocketName},
  2661  		{"start", svc2SocketName},
  2662  		{"enable", svc3SocketName},
  2663  		{"start", svc3SocketName}, // start failed, what follows is the cleanup
  2664  		{"stop", svc3SocketName},
  2665  		{"show", "--property=ActiveState", svc3SocketName},
  2666  		{"stop", svc3Name},
  2667  		{"show", "--property=ActiveState", svc3Name},
  2668  		{"disable", svc3SocketName},
  2669  		{"stop", svc2SocketName},
  2670  		{"show", "--property=ActiveState", svc2SocketName},
  2671  		{"stop", svc2Name},
  2672  		{"show", "--property=ActiveState", svc2Name},
  2673  		{"disable", svc2SocketName},
  2674  		{"stop", svc1Name},
  2675  		{"show", "--property=ActiveState", svc1Name},
  2676  		{"disable", svc1Name},
  2677  	}, Commentf("calls: %v", sysdLog))
  2678  }
  2679  
  2680  func (s *servicesTestSuite) TestStartSnapMultiUserServicesFailStartCleanup(c *C) {
  2681  	var sysdLog [][]string
  2682  	svc1Name := "snap.hello-snap.svc1.service"
  2683  	svc2Name := "snap.hello-snap.svc2.service"
  2684  
  2685  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  2686  		sysdLog = append(sysdLog, cmd)
  2687  		if len(cmd) >= 3 && cmd[0] == "--user" && cmd[1] == "start" {
  2688  			name := cmd[len(cmd)-1]
  2689  			if name == svc2Name {
  2690  				return nil, fmt.Errorf("failed")
  2691  			}
  2692  		}
  2693  		return []byte("ActiveState=inactive\n"), nil
  2694  	})
  2695  	defer r()
  2696  
  2697  	info := snaptest.MockSnap(c, packageHello+`
  2698   svc1:
  2699    command: bin/hello
  2700    daemon: simple
  2701    daemon-scope: user
  2702   svc2:
  2703    command: bin/hello
  2704    daemon: simple
  2705    daemon-scope: user
  2706  `, &snap.SideInfo{Revision: snap.R(12)})
  2707  
  2708  	svcs := info.Services()
  2709  	c.Assert(svcs, HasLen, 2)
  2710  	if svcs[0].Name == "svc2" {
  2711  		svcs[0], svcs[1] = svcs[1], svcs[0]
  2712  	}
  2713  	flags := &wrappers.StartServicesFlags{Enable: true}
  2714  	err := wrappers.StartServices(svcs, nil, flags, &progress.Null, s.perfTimings)
  2715  	c.Assert(err, ErrorMatches, "some user services failed to start")
  2716  	c.Assert(sysdLog, HasLen, 12, Commentf("len: %v calls: %v", len(sysdLog), sysdLog))
  2717  	c.Check(sysdLog, DeepEquals, [][]string{
  2718  		{"--user", "--global", "enable", svc1Name},
  2719  		{"--user", "--global", "enable", svc2Name},
  2720  		{"--user", "start", svc1Name},
  2721  		{"--user", "start", svc2Name}, // one of the services fails
  2722  		// session agent attempts to stop the non-failed services
  2723  		{"--user", "stop", svc1Name},
  2724  		{"--user", "show", "--property=ActiveState", svc1Name},
  2725  		// StartServices ensures everything is stopped
  2726  		{"--user", "stop", svc2Name},
  2727  		{"--user", "show", "--property=ActiveState", svc2Name},
  2728  		{"--user", "stop", svc1Name},
  2729  		{"--user", "show", "--property=ActiveState", svc1Name},
  2730  		{"--user", "--global", "disable", svc1Name},
  2731  		{"--user", "--global", "disable", svc2Name},
  2732  	}, Commentf("calls: %v", sysdLog))
  2733  }
  2734  
  2735  func (s *servicesTestSuite) TestStartSnapServicesKeepsOrder(c *C) {
  2736  	var sysdLog [][]string
  2737  	svc1Name := "snap.services-snap.svc1.service"
  2738  	svc2Name := "snap.services-snap.svc2.service"
  2739  	svc3Name := "snap.services-snap.svc3.service"
  2740  
  2741  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  2742  		sysdLog = append(sysdLog, cmd)
  2743  		return []byte("ActiveState=inactive\n"), nil
  2744  	})
  2745  	defer r()
  2746  
  2747  	info := snaptest.MockSnap(c, `name: services-snap
  2748  apps:
  2749    svc1:
  2750      daemon: simple
  2751      before: [svc3]
  2752    svc2:
  2753      daemon: simple
  2754      after: [svc1]
  2755    svc3:
  2756      daemon: simple
  2757      before: [svc2]
  2758  `, &snap.SideInfo{Revision: snap.R(12)})
  2759  
  2760  	svcs := info.Services()
  2761  	c.Assert(svcs, HasLen, 3)
  2762  
  2763  	sorted, err := snap.SortServices(svcs)
  2764  	c.Assert(err, IsNil)
  2765  
  2766  	flags := &wrappers.StartServicesFlags{Enable: true}
  2767  	err = wrappers.StartServices(sorted, nil, flags, &progress.Null, s.perfTimings)
  2768  	c.Assert(err, IsNil)
  2769  	c.Assert(sysdLog, HasLen, 6, Commentf("len: %v calls: %v", len(sysdLog), sysdLog))
  2770  	c.Check(sysdLog, DeepEquals, [][]string{
  2771  		{"enable", svc1Name},
  2772  		{"enable", svc3Name},
  2773  		{"enable", svc2Name},
  2774  		{"start", svc1Name},
  2775  		{"start", svc3Name},
  2776  		{"start", svc2Name},
  2777  	}, Commentf("calls: %v", sysdLog))
  2778  
  2779  	// change the order
  2780  	sorted[1], sorted[0] = sorted[0], sorted[1]
  2781  
  2782  	// we should observe the calls done in the same order as services
  2783  	err = wrappers.StartServices(sorted, nil, flags, &progress.Null, s.perfTimings)
  2784  	c.Assert(err, IsNil)
  2785  	c.Assert(sysdLog, HasLen, 12, Commentf("len: %v calls: %v", len(sysdLog), sysdLog))
  2786  	c.Check(sysdLog[6:], DeepEquals, [][]string{
  2787  		{"enable", svc3Name},
  2788  		{"enable", svc1Name},
  2789  		{"enable", svc2Name},
  2790  		{"start", svc3Name},
  2791  		{"start", svc1Name},
  2792  		{"start", svc2Name},
  2793  	}, Commentf("calls: %v", sysdLog))
  2794  }
  2795  
  2796  func (s *servicesTestSuite) TestServiceAfterBefore(c *C) {
  2797  	snapYaml := packageHello + `
  2798   svc2:
  2799     daemon: forking
  2800     after: [svc1]
  2801   svc3:
  2802     daemon: forking
  2803     before: [svc4]
  2804     after:  [svc2]
  2805   svc4:
  2806     daemon: forking
  2807     after:
  2808       - svc1
  2809       - svc2
  2810       - svc3
  2811  `
  2812  	info := snaptest.MockSnap(c, snapYaml, &snap.SideInfo{Revision: snap.R(12)})
  2813  
  2814  	checks := []struct {
  2815  		file    string
  2816  		kind    string
  2817  		matches []string
  2818  	}{{
  2819  		file:    filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc2.service"),
  2820  		kind:    "After",
  2821  		matches: []string{info.Apps["svc1"].ServiceName()},
  2822  	}, {
  2823  		file:    filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc3.service"),
  2824  		kind:    "After",
  2825  		matches: []string{info.Apps["svc2"].ServiceName()},
  2826  	}, {
  2827  		file:    filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc3.service"),
  2828  		kind:    "Before",
  2829  		matches: []string{info.Apps["svc4"].ServiceName()},
  2830  	}, {
  2831  		file: filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc4.service"),
  2832  		kind: "After",
  2833  		matches: []string{
  2834  			info.Apps["svc1"].ServiceName(),
  2835  			info.Apps["svc2"].ServiceName(),
  2836  			info.Apps["svc3"].ServiceName(),
  2837  		},
  2838  	}}
  2839  
  2840  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  2841  	c.Assert(err, IsNil)
  2842  
  2843  	for _, check := range checks {
  2844  		for _, m := range check.matches {
  2845  			c.Check(check.file, testutil.FileMatches,
  2846  				// match:
  2847  				//   ...
  2848  				//   After=other.mount some.target foo.service bar.service
  2849  				//   Before=foo.service bar.service
  2850  				//   ...
  2851  				// but not:
  2852  				//   Foo=something After=foo.service Bar=something else
  2853  				// or:
  2854  				//   After=foo.service
  2855  				//   bar.service
  2856  				// or:
  2857  				//   After=  foo.service    bar.service
  2858  				"(?ms).*^(?U)"+check.kind+"=.*\\s?"+regexp.QuoteMeta(m)+"\\s?[^=]*$")
  2859  		}
  2860  	}
  2861  }
  2862  
  2863  func (s *servicesTestSuite) TestServiceWatchdog(c *C) {
  2864  	snapYaml := packageHello + `
  2865   svc2:
  2866     daemon: forking
  2867     watchdog-timeout: 12s
  2868   svc3:
  2869     daemon: forking
  2870     watchdog-timeout: 0s
  2871   svc4:
  2872     daemon: forking
  2873  `
  2874  	info := snaptest.MockSnap(c, snapYaml, &snap.SideInfo{Revision: snap.R(12)})
  2875  
  2876  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  2877  	c.Assert(err, IsNil)
  2878  
  2879  	content, err := ioutil.ReadFile(filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc2.service"))
  2880  	c.Assert(err, IsNil)
  2881  	c.Check(strings.Contains(string(content), "\nWatchdogSec=12\n"), Equals, true)
  2882  
  2883  	noWatchdog := []string{
  2884  		filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc3.service"),
  2885  		filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc4.service"),
  2886  	}
  2887  	for _, svcPath := range noWatchdog {
  2888  		content, err := ioutil.ReadFile(svcPath)
  2889  		c.Assert(err, IsNil)
  2890  		c.Check(strings.Contains(string(content), "WatchdogSec="), Equals, false)
  2891  	}
  2892  }
  2893  
  2894  func (s *servicesTestSuite) TestStopServiceEndure(c *C) {
  2895  	const surviveYaml = `name: survive-snap
  2896  version: 1.0
  2897  apps:
  2898   survivor:
  2899    command: bin/survivor
  2900    refresh-mode: endure
  2901    daemon: simple
  2902  `
  2903  	info := snaptest.MockSnap(c, surviveYaml, &snap.SideInfo{Revision: snap.R(1)})
  2904  	survivorFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.survive-snap.survivor.service")
  2905  
  2906  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  2907  	c.Assert(err, IsNil)
  2908  	c.Check(s.sysdLog, DeepEquals, [][]string{
  2909  		{"daemon-reload"},
  2910  	})
  2911  	s.sysdLog = nil
  2912  
  2913  	apps := []*snap.AppInfo{info.Apps["survivor"]}
  2914  	flags := &wrappers.StartServicesFlags{Enable: true}
  2915  	err = wrappers.StartServices(apps, nil, flags, progress.Null, s.perfTimings)
  2916  	c.Assert(err, IsNil)
  2917  
  2918  	c.Check(s.sysdLog, DeepEquals, [][]string{
  2919  		{"enable", filepath.Base(survivorFile)},
  2920  		{"start", filepath.Base(survivorFile)},
  2921  	})
  2922  
  2923  	s.sysdLog = nil
  2924  	err = wrappers.StopServices(info.Services(), nil, snap.StopReasonRefresh, progress.Null, s.perfTimings)
  2925  	c.Assert(err, IsNil)
  2926  	c.Assert(s.sysdLog, HasLen, 0)
  2927  
  2928  	s.sysdLog = nil
  2929  	err = wrappers.StopServices(info.Services(), nil, snap.StopReasonRemove, progress.Null, s.perfTimings)
  2930  	c.Assert(err, IsNil)
  2931  	c.Check(s.sysdLog, DeepEquals, [][]string{
  2932  		{"stop", filepath.Base(survivorFile)},
  2933  		{"show", "--property=ActiveState", "snap.survive-snap.survivor.service"},
  2934  	})
  2935  
  2936  }
  2937  
  2938  func (s *servicesTestSuite) TestStopServiceSigs(c *C) {
  2939  	r := wrappers.MockKillWait(1 * time.Millisecond)
  2940  	defer r()
  2941  
  2942  	survivorFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.survive-snap.srv.service")
  2943  	for _, t := range []struct {
  2944  		mode        string
  2945  		expectedSig string
  2946  		expectedWho string
  2947  	}{
  2948  		{mode: "sigterm", expectedSig: "TERM", expectedWho: "main"},
  2949  		{mode: "sigterm-all", expectedSig: "TERM", expectedWho: "all"},
  2950  		{mode: "sighup", expectedSig: "HUP", expectedWho: "main"},
  2951  		{mode: "sighup-all", expectedSig: "HUP", expectedWho: "all"},
  2952  		{mode: "sigusr1", expectedSig: "USR1", expectedWho: "main"},
  2953  		{mode: "sigusr1-all", expectedSig: "USR1", expectedWho: "all"},
  2954  		{mode: "sigusr2", expectedSig: "USR2", expectedWho: "main"},
  2955  		{mode: "sigusr2-all", expectedSig: "USR2", expectedWho: "all"},
  2956  	} {
  2957  		surviveYaml := fmt.Sprintf(`name: survive-snap
  2958  version: 1.0
  2959  apps:
  2960   srv:
  2961    command: bin/survivor
  2962    stop-mode: %s
  2963    daemon: simple
  2964  `, t.mode)
  2965  		info := snaptest.MockSnap(c, surviveYaml, &snap.SideInfo{Revision: snap.R(1)})
  2966  
  2967  		s.sysdLog = nil
  2968  		err := wrappers.AddSnapServices(info, nil, progress.Null)
  2969  		c.Assert(err, IsNil)
  2970  
  2971  		c.Check(s.sysdLog, DeepEquals, [][]string{
  2972  			{"daemon-reload"},
  2973  		})
  2974  		s.sysdLog = nil
  2975  
  2976  		var apps []*snap.AppInfo
  2977  		for _, a := range info.Apps {
  2978  			apps = append(apps, a)
  2979  		}
  2980  		flags := &wrappers.StartServicesFlags{Enable: true}
  2981  		err = wrappers.StartServices(apps, nil, flags, progress.Null, s.perfTimings)
  2982  		c.Assert(err, IsNil)
  2983  		c.Check(s.sysdLog, DeepEquals, [][]string{
  2984  			{"enable", filepath.Base(survivorFile)},
  2985  			{"start", filepath.Base(survivorFile)},
  2986  		})
  2987  
  2988  		s.sysdLog = nil
  2989  		err = wrappers.StopServices(info.Services(), nil, snap.StopReasonRefresh, progress.Null, s.perfTimings)
  2990  		c.Assert(err, IsNil)
  2991  		c.Check(s.sysdLog, DeepEquals, [][]string{
  2992  			{"stop", filepath.Base(survivorFile)},
  2993  			{"show", "--property=ActiveState", "snap.survive-snap.srv.service"},
  2994  		}, Commentf("failure in %s", t.mode))
  2995  
  2996  		s.sysdLog = nil
  2997  		err = wrappers.StopServices(info.Services(), nil, snap.StopReasonRemove, progress.Null, s.perfTimings)
  2998  		c.Assert(err, IsNil)
  2999  		switch t.expectedWho {
  3000  		case "all":
  3001  			c.Check(s.sysdLog, DeepEquals, [][]string{
  3002  				{"stop", filepath.Base(survivorFile)},
  3003  				{"show", "--property=ActiveState", "snap.survive-snap.srv.service"},
  3004  			})
  3005  		case "main":
  3006  			c.Check(s.sysdLog, DeepEquals, [][]string{
  3007  				{"stop", filepath.Base(survivorFile)},
  3008  				{"show", "--property=ActiveState", "snap.survive-snap.srv.service"},
  3009  				{"kill", filepath.Base(survivorFile), "-s", "TERM", "--kill-who=all"},
  3010  				{"kill", filepath.Base(survivorFile), "-s", "KILL", "--kill-who=all"},
  3011  			})
  3012  		default:
  3013  			panic("not reached")
  3014  		}
  3015  	}
  3016  
  3017  }
  3018  
  3019  func (s *servicesTestSuite) TestStartSnapSocketEnableStart(c *C) {
  3020  	svc1Name := "snap.hello-snap.svc1.service"
  3021  	// svc2Name := "snap.hello-snap.svc2.service"
  3022  	svc2Sock := "snap.hello-snap.svc2.sock.socket"
  3023  	svc3Sock := "snap.hello-snap.svc3.sock.socket"
  3024  
  3025  	info := snaptest.MockSnap(c, packageHello+`
  3026   svc2:
  3027    command: bin/hello
  3028    daemon: simple
  3029    sockets:
  3030      sock:
  3031        listen-stream: $SNAP_COMMON/sock1.socket
  3032   svc3:
  3033    command: bin/hello
  3034    daemon: simple
  3035    daemon-scope: user
  3036    sockets:
  3037      sock:
  3038        listen-stream: $SNAP_USER_COMMON/sock1.socket
  3039  `, &snap.SideInfo{Revision: snap.R(12)})
  3040  
  3041  	// fix the apps order to make the test stable
  3042  	apps := []*snap.AppInfo{info.Apps["svc1"], info.Apps["svc2"], info.Apps["svc3"]}
  3043  	flags := &wrappers.StartServicesFlags{Enable: true}
  3044  	err := wrappers.StartServices(apps, nil, flags, &progress.Null, s.perfTimings)
  3045  	c.Assert(err, IsNil)
  3046  	c.Assert(s.sysdLog, HasLen, 6, Commentf("len: %v calls: %v", len(s.sysdLog), s.sysdLog))
  3047  	c.Check(s.sysdLog, DeepEquals, [][]string{
  3048  		{"enable", svc1Name},
  3049  		{"enable", svc2Sock},
  3050  		{"start", svc2Sock},
  3051  		{"--user", "--global", "enable", svc3Sock},
  3052  		{"--user", "start", svc3Sock},
  3053  		{"start", svc1Name},
  3054  	}, Commentf("calls: %v", s.sysdLog))
  3055  }
  3056  
  3057  func (s *servicesTestSuite) TestStartSnapTimerEnableStart(c *C) {
  3058  	svc1Name := "snap.hello-snap.svc1.service"
  3059  	// svc2Name := "snap.hello-snap.svc2.service"
  3060  	svc2Timer := "snap.hello-snap.svc2.timer"
  3061  	svc3Timer := "snap.hello-snap.svc3.timer"
  3062  
  3063  	info := snaptest.MockSnap(c, packageHello+`
  3064   svc2:
  3065    command: bin/hello
  3066    daemon: simple
  3067    timer: 10:00-12:00
  3068   svc3:
  3069    command: bin/hello
  3070    daemon: simple
  3071    daemon-scope: user
  3072    timer: 10:00-12:00
  3073  `, &snap.SideInfo{Revision: snap.R(12)})
  3074  
  3075  	// fix the apps order to make the test stable
  3076  	apps := []*snap.AppInfo{info.Apps["svc1"], info.Apps["svc2"], info.Apps["svc3"]}
  3077  	flags := &wrappers.StartServicesFlags{Enable: true}
  3078  	err := wrappers.StartServices(apps, nil, flags, &progress.Null, s.perfTimings)
  3079  	c.Assert(err, IsNil)
  3080  	c.Assert(s.sysdLog, HasLen, 6, Commentf("len: %v calls: %v", len(s.sysdLog), s.sysdLog))
  3081  	c.Check(s.sysdLog, DeepEquals, [][]string{
  3082  		{"enable", svc1Name},
  3083  		{"enable", svc2Timer},
  3084  		{"start", svc2Timer},
  3085  		{"--user", "--global", "enable", svc3Timer},
  3086  		{"--user", "start", svc3Timer},
  3087  		{"start", svc1Name},
  3088  	}, Commentf("calls: %v", s.sysdLog))
  3089  }
  3090  
  3091  func (s *servicesTestSuite) TestStartSnapTimerCleanup(c *C) {
  3092  	var sysdLog [][]string
  3093  	svc1Name := "snap.hello-snap.svc1.service"
  3094  	svc2Name := "snap.hello-snap.svc2.service"
  3095  	svc2Timer := "snap.hello-snap.svc2.timer"
  3096  
  3097  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  3098  		sysdLog = append(sysdLog, cmd)
  3099  		if len(cmd) >= 2 && cmd[0] == "start" && cmd[1] == svc2Timer {
  3100  			return nil, fmt.Errorf("failed")
  3101  		}
  3102  		return []byte("ActiveState=inactive\n"), nil
  3103  	})
  3104  	defer r()
  3105  
  3106  	info := snaptest.MockSnap(c, packageHello+`
  3107   svc2:
  3108    command: bin/hello
  3109    daemon: simple
  3110    timer: 10:00-12:00
  3111  `, &snap.SideInfo{Revision: snap.R(12)})
  3112  
  3113  	// fix the apps order to make the test stable
  3114  	apps := []*snap.AppInfo{info.Apps["svc1"], info.Apps["svc2"]}
  3115  	flags := &wrappers.StartServicesFlags{Enable: true}
  3116  	err := wrappers.StartServices(apps, nil, flags, &progress.Null, s.perfTimings)
  3117  	c.Assert(err, ErrorMatches, "failed")
  3118  	c.Assert(sysdLog, HasLen, 11, Commentf("len: %v calls: %v", len(sysdLog), sysdLog))
  3119  	c.Check(sysdLog, DeepEquals, [][]string{
  3120  		{"enable", svc1Name},
  3121  		{"enable", svc2Timer},
  3122  		{"start", svc2Timer}, // this call fails
  3123  		{"stop", svc2Timer},
  3124  		{"show", "--property=ActiveState", svc2Timer},
  3125  		{"stop", svc2Name},
  3126  		{"show", "--property=ActiveState", svc2Name},
  3127  		{"disable", svc2Timer},
  3128  		{"stop", svc1Name},
  3129  		{"show", "--property=ActiveState", svc1Name},
  3130  		{"disable", svc1Name},
  3131  	}, Commentf("calls: %v", sysdLog))
  3132  }
  3133  
  3134  func (s *servicesTestSuite) TestAddRemoveSnapWithTimersAddsRemovesTimerFiles(c *C) {
  3135  	info := snaptest.MockSnap(c, packageHello+`
  3136   svc2:
  3137    command: bin/hello
  3138    daemon: simple
  3139    timer: 10:00-12:00
  3140  `, &snap.SideInfo{Revision: snap.R(12)})
  3141  
  3142  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  3143  	c.Assert(err, IsNil)
  3144  
  3145  	app := info.Apps["svc2"]
  3146  	c.Assert(app.Timer, NotNil)
  3147  
  3148  	c.Check(osutil.FileExists(app.Timer.File()), Equals, true)
  3149  	c.Check(osutil.FileExists(app.ServiceFile()), Equals, true)
  3150  
  3151  	err = wrappers.StopServices(info.Services(), nil, "", &progress.Null, s.perfTimings)
  3152  	c.Assert(err, IsNil)
  3153  
  3154  	err = wrappers.RemoveSnapServices(info, &progress.Null)
  3155  	c.Assert(err, IsNil)
  3156  
  3157  	c.Check(osutil.FileExists(app.Timer.File()), Equals, false)
  3158  	c.Check(osutil.FileExists(app.ServiceFile()), Equals, false)
  3159  }
  3160  
  3161  func (s *servicesTestSuite) TestFailedAddSnapCleansUp(c *C) {
  3162  	info := snaptest.MockSnap(c, packageHello+`
  3163   svc2:
  3164    command: bin/hello
  3165    daemon: simple
  3166    timer: 10:00-12:00
  3167   svc3:
  3168    command: bin/hello
  3169    daemon: simple
  3170    plugs: [network-bind]
  3171    sockets:
  3172      sock1:
  3173        listen-stream: $SNAP_COMMON/sock1.socket
  3174        socket-mode: 0666
  3175  `, &snap.SideInfo{Revision: snap.R(12)})
  3176  
  3177  	calls := 0
  3178  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  3179  		if len(cmd) == 1 && cmd[0] == "daemon-reload" && calls == 0 {
  3180  			// only fail the first systemd daemon-reload call, the
  3181  			// second one is at the end of cleanup
  3182  			calls += 1
  3183  			return nil, fmt.Errorf("failed")
  3184  		}
  3185  		return []byte("ActiveState=inactive\n"), nil
  3186  	})
  3187  	defer r()
  3188  
  3189  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  3190  	c.Assert(err, NotNil)
  3191  
  3192  	c.Logf("services dir: %v", dirs.SnapServicesDir)
  3193  	matches, err := filepath.Glob(dirs.SnapServicesDir + "/*")
  3194  	c.Assert(err, IsNil)
  3195  	c.Assert(matches, HasLen, 0, Commentf("the following autogenerated files were left behind: %v", matches))
  3196  }
  3197  
  3198  func (s *servicesTestSuite) TestAddServicesDidReload(c *C) {
  3199  	const base = `name: hello-snap
  3200  version: 1.10
  3201  summary: hello
  3202  description: Hello...
  3203  apps:
  3204  `
  3205  	onlyServices := snaptest.MockSnap(c, base+`
  3206   svc1:
  3207    command: bin/hello
  3208    daemon: simple
  3209  `, &snap.SideInfo{Revision: snap.R(12)})
  3210  
  3211  	onlySockets := snaptest.MockSnap(c, base+`
  3212   svc1:
  3213    command: bin/hello
  3214    daemon: simple
  3215    plugs: [network-bind]
  3216    sockets:
  3217      sock1:
  3218        listen-stream: $SNAP_COMMON/sock1.socket
  3219        socket-mode: 0666
  3220  `, &snap.SideInfo{Revision: snap.R(12)})
  3221  
  3222  	onlyTimers := snaptest.MockSnap(c, base+`
  3223   svc1:
  3224    command: bin/hello
  3225    daemon: oneshot
  3226    timer: 10:00-12:00
  3227  `, &snap.SideInfo{Revision: snap.R(12)})
  3228  
  3229  	for i, info := range []*snap.Info{onlyServices, onlySockets, onlyTimers} {
  3230  		s.sysdLog = nil
  3231  		err := wrappers.AddSnapServices(info, nil, progress.Null)
  3232  		c.Assert(err, IsNil)
  3233  		reloads := 0
  3234  		c.Logf("calls: %v", s.sysdLog)
  3235  		for _, call := range s.sysdLog {
  3236  			if strutil.ListContains(call, "daemon-reload") {
  3237  				reloads += 1
  3238  			}
  3239  		}
  3240  		c.Check(reloads >= 1, Equals, true, Commentf("test-case %v did not reload services as expected", i))
  3241  	}
  3242  }
  3243  
  3244  func (s *servicesTestSuite) TestSnapServicesActivation(c *C) {
  3245  	const snapYaml = `name: hello-snap
  3246  version: 1.10
  3247  summary: hello
  3248  description: Hello...
  3249  apps:
  3250   svc1:
  3251    command: bin/hello
  3252    daemon: simple
  3253    plugs: [network-bind]
  3254    sockets:
  3255      sock1:
  3256        listen-stream: $SNAP_COMMON/sock1.socket
  3257        socket-mode: 0666
  3258   svc2:
  3259    command: bin/hello
  3260    daemon: oneshot
  3261    timer: 10:00-12:00
  3262   svc3:
  3263    command: bin/hello
  3264    daemon: simple
  3265  `
  3266  	svc1Socket := "snap.hello-snap.svc1.sock1.socket"
  3267  	svc2Timer := "snap.hello-snap.svc2.timer"
  3268  	svc3Name := "snap.hello-snap.svc3.service"
  3269  
  3270  	info := snaptest.MockSnap(c, snapYaml, &snap.SideInfo{Revision: snap.R(12)})
  3271  
  3272  	// fix the apps order to make the test stable
  3273  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  3274  	c.Assert(err, IsNil)
  3275  	c.Check(s.sysdLog, DeepEquals, [][]string{
  3276  		{"daemon-reload"},
  3277  	})
  3278  	s.sysdLog = nil
  3279  
  3280  	apps := []*snap.AppInfo{info.Apps["svc1"], info.Apps["svc2"], info.Apps["svc3"]}
  3281  	flags := &wrappers.StartServicesFlags{Enable: true}
  3282  	err = wrappers.StartServices(apps, nil, flags, progress.Null, s.perfTimings)
  3283  	c.Assert(err, IsNil)
  3284  
  3285  	c.Assert(s.sysdLog, HasLen, 6, Commentf("len: %v calls: %v", len(s.sysdLog), s.sysdLog))
  3286  	c.Check(s.sysdLog, DeepEquals, [][]string{
  3287  		{"enable", svc3Name},
  3288  		{"enable", svc1Socket},
  3289  		{"start", svc1Socket},
  3290  		{"enable", svc2Timer},
  3291  		{"start", svc2Timer},
  3292  		{"start", svc3Name},
  3293  	}, Commentf("calls: %v", s.sysdLog))
  3294  }
  3295  
  3296  func (s *servicesTestSuite) TestServiceRestartDelay(c *C) {
  3297  	snapYaml := packageHello + `
  3298   svc2:
  3299     daemon: forking
  3300     restart-delay: 12s
  3301   svc3:
  3302     daemon: forking
  3303  `
  3304  	info := snaptest.MockSnap(c, snapYaml, &snap.SideInfo{Revision: snap.R(12)})
  3305  
  3306  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  3307  	c.Assert(err, IsNil)
  3308  
  3309  	content, err := ioutil.ReadFile(filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc2.service"))
  3310  	c.Assert(err, IsNil)
  3311  	c.Check(strings.Contains(string(content), "\nRestartSec=12\n"), Equals, true)
  3312  
  3313  	content, err = ioutil.ReadFile(filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc3.service"))
  3314  	c.Assert(err, IsNil)
  3315  	c.Check(strings.Contains(string(content), "RestartSec="), Equals, false)
  3316  }
  3317  
  3318  func (s *servicesTestSuite) TestAddRemoveSnapServiceWithSnapd(c *C) {
  3319  	info := makeMockSnapdSnap(c)
  3320  
  3321  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  3322  	c.Check(err, ErrorMatches, "internal error: adding explicit services for snapd snap is unexpected")
  3323  
  3324  	err = wrappers.RemoveSnapServices(info, progress.Null)
  3325  	c.Check(err, ErrorMatches, "internal error: removing explicit services for snapd snap is unexpected")
  3326  }
  3327  
  3328  func (s *servicesTestSuite) TestReloadOrRestart(c *C) {
  3329  	const surviveYaml = `name: test-snap
  3330  version: 1.0
  3331  apps:
  3332    foo:
  3333      command: bin/foo
  3334      daemon: simple
  3335  `
  3336  	info := snaptest.MockSnap(c, surviveYaml, &snap.SideInfo{Revision: snap.R(1)})
  3337  	srvFile := "snap.test-snap.foo.service"
  3338  
  3339  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  3340  		s.sysdLog = append(s.sysdLog, cmd)
  3341  		if out := systemdtest.HandleMockAllUnitsActiveOutput(cmd, nil); out != nil {
  3342  			return out, nil
  3343  		}
  3344  		return []byte("ActiveState=inactive\n"), nil
  3345  	})
  3346  	defer r()
  3347  
  3348  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  3349  	c.Assert(err, IsNil)
  3350  
  3351  	s.sysdLog = nil
  3352  	flags := &wrappers.RestartServicesFlags{Reload: true}
  3353  	c.Assert(wrappers.RestartServices(info.Services(), nil, flags, progress.Null, s.perfTimings), IsNil)
  3354  	c.Assert(err, IsNil)
  3355  	c.Check(s.sysdLog, DeepEquals, [][]string{
  3356  		{"show", "--property=Id,ActiveState,UnitFileState,Type", srvFile},
  3357  		{"reload-or-restart", srvFile},
  3358  	})
  3359  
  3360  	s.sysdLog = nil
  3361  	flags.Reload = false
  3362  	c.Assert(wrappers.RestartServices(info.Services(), nil, flags, progress.Null, s.perfTimings), IsNil)
  3363  	c.Check(s.sysdLog, DeepEquals, [][]string{
  3364  		{"show", "--property=Id,ActiveState,UnitFileState,Type", srvFile},
  3365  		{"stop", srvFile},
  3366  		{"show", "--property=ActiveState", srvFile},
  3367  		{"start", srvFile},
  3368  	})
  3369  
  3370  	s.sysdLog = nil
  3371  	c.Assert(wrappers.RestartServices(info.Services(), nil, nil, progress.Null, s.perfTimings), IsNil)
  3372  	c.Check(s.sysdLog, DeepEquals, [][]string{
  3373  		{"show", "--property=Id,ActiveState,UnitFileState,Type", srvFile},
  3374  		{"stop", srvFile},
  3375  		{"show", "--property=ActiveState", srvFile},
  3376  		{"start", srvFile},
  3377  	})
  3378  }
  3379  
  3380  func (s *servicesTestSuite) TestRestartInDifferentStates(c *C) {
  3381  	const manyServicesYaml = `name: test-snap
  3382  version: 1.0
  3383  apps:
  3384    svc1:
  3385      command: bin/foo
  3386      daemon: simple
  3387    svc2:
  3388      command: bin/foo
  3389      daemon: simple
  3390    svc3:
  3391      command: bin/foo
  3392      daemon: simple
  3393    svc4:
  3394      command: bin/foo
  3395      daemon: simple
  3396  `
  3397  	srvFile1 := "snap.test-snap.svc1.service"
  3398  	srvFile2 := "snap.test-snap.svc2.service"
  3399  	srvFile3 := "snap.test-snap.svc3.service"
  3400  	srvFile4 := "snap.test-snap.svc4.service"
  3401  
  3402  	info := snaptest.MockSnap(c, manyServicesYaml, &snap.SideInfo{Revision: snap.R(1)})
  3403  
  3404  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
  3405  		s.sysdLog = append(s.sysdLog, cmd)
  3406  		states := map[string]systemdtest.ServiceState{
  3407  			srvFile1: {ActiveState: "active", UnitFileState: "enabled"},
  3408  			srvFile2: {ActiveState: "inactive", UnitFileState: "enabled"},
  3409  			srvFile3: {ActiveState: "active", UnitFileState: "disabled"},
  3410  			srvFile4: {ActiveState: "inactive", UnitFileState: "disabled"},
  3411  		}
  3412  		if out := systemdtest.HandleMockAllUnitsActiveOutput(cmd, states); out != nil {
  3413  			return out, nil
  3414  		}
  3415  		return []byte("ActiveState=inactive\n"), nil
  3416  	})
  3417  	defer r()
  3418  
  3419  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  3420  	c.Assert(err, IsNil)
  3421  
  3422  	s.sysdLog = nil
  3423  	services := info.Services()
  3424  	sort.Sort(snap.AppInfoBySnapApp(services))
  3425  	c.Assert(wrappers.RestartServices(services, nil, nil, progress.Null, s.perfTimings), IsNil)
  3426  	c.Check(s.sysdLog, DeepEquals, [][]string{
  3427  		{"show", "--property=Id,ActiveState,UnitFileState,Type",
  3428  			srvFile1, srvFile2, srvFile3, srvFile4},
  3429  		{"stop", srvFile1},
  3430  		{"show", "--property=ActiveState", srvFile1},
  3431  		{"start", srvFile1},
  3432  		{"stop", srvFile3},
  3433  		{"show", "--property=ActiveState", srvFile3},
  3434  		{"start", srvFile3},
  3435  	})
  3436  
  3437  	// Verify that explicitly mentioning a service causes it to restart,
  3438  	// regardless of its state
  3439  	s.sysdLog = nil
  3440  	c.Assert(wrappers.RestartServices(services, []string{srvFile2}, nil, progress.Null, s.perfTimings), IsNil)
  3441  	c.Check(s.sysdLog, DeepEquals, [][]string{
  3442  		{"show", "--property=Id,ActiveState,UnitFileState,Type",
  3443  			srvFile1, srvFile2, srvFile3, srvFile4},
  3444  		{"stop", srvFile1},
  3445  		{"show", "--property=ActiveState", srvFile1},
  3446  		{"start", srvFile1},
  3447  		{"stop", srvFile2},
  3448  		{"show", "--property=ActiveState", srvFile2},
  3449  		{"start", srvFile2},
  3450  		{"stop", srvFile3},
  3451  		{"show", "--property=ActiveState", srvFile3},
  3452  		{"start", srvFile3},
  3453  	})
  3454  }
  3455  
  3456  func (s *servicesTestSuite) TestStopAndDisableServices(c *C) {
  3457  	info := snaptest.MockSnap(c, packageHello+`
  3458   svc1:
  3459    daemon: simple
  3460  `, &snap.SideInfo{Revision: snap.R(12)})
  3461  	svcFile := "snap.hello-snap.svc1.service"
  3462  
  3463  	err := wrappers.AddSnapServices(info, nil, progress.Null)
  3464  	c.Assert(err, IsNil)
  3465  
  3466  	s.sysdLog = nil
  3467  	flags := &wrappers.StopServicesFlags{Disable: true}
  3468  	err = wrappers.StopServices(info.Services(), flags, "", progress.Null, s.perfTimings)
  3469  	c.Assert(err, IsNil)
  3470  	c.Check(s.sysdLog, DeepEquals, [][]string{
  3471  		{"stop", svcFile},
  3472  		{"show", "--property=ActiveState", svcFile},
  3473  		{"disable", svcFile},
  3474  	})
  3475  }