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