Files
snapd/systemd/systemd_test.go
Miguel Pires 0bf3e39e00 systemd: strip pre-release suffixes when comparing versions
Some tests were failing on debian-sid because we weren't coping with
systemd versions that contained a release candidate suffix. This deals
with it simply by ignoring which should be enough given that we only
compare them roughly to check if systemd is not too old.
```
error: cannot get logs: cannot get systemd version: cannot convert systemd
       version to number: 256~rc3
```

Signed-off-by: Miguel Pires <miguel.pires@canonical.com>
2024-05-29 12:23:16 +02:00

2625 lines
80 KiB
Go

// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2014-2015 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package systemd_test
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"testing"
"time"
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/gadget/quantity"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/osutil/squashfs"
"github.com/snapcore/snapd/sandbox/selinux"
"github.com/snapcore/snapd/systemd"
. "github.com/snapcore/snapd/systemd"
"github.com/snapcore/snapd/testutil"
)
type testreporter struct {
msgs []string
}
func (tr *testreporter) Notify(msg string) {
tr.msgs = append(tr.msgs, msg)
}
// Hook up check.v1 into the "go test" runner
func Test(t *testing.T) { TestingT(t) }
// systemd's testsuite
type SystemdTestSuite struct {
// 'systemctl' command except for stop. These all
// run synchronously so they can share attributes.
i int
argses [][]string
errors []error
outs [][]byte
// 'systemctl stop' command requires some special
// mocking because it runs in its own thread. We
// have to track its own set of attributes otherwise
// the unit test arguments checks will intermittendly
// fail due to the random completion ordering. We
// do not care about output for 'systemctl stop'.
stopDelays []time.Duration
stopArgses [][]string
stopErrors []error
stopIter int
j int
jns []string
jsvcs [][]string
jouts [][]byte
jerrs []error
jfollows []bool
jnamespaces []bool
rep *testreporter
restoreSystemctl func()
restoreJournalctl func()
restoreSELinux func()
}
var _ = Suite(&SystemdTestSuite{})
func (s *SystemdTestSuite) SetUpTest(c *C) {
dirs.SetRootDir(c.MkDir())
err := os.MkdirAll(dirs.SnapServicesDir, 0755)
c.Assert(err, IsNil)
err = os.MkdirAll(dirs.SnapRuntimeServicesDir, 0755)
c.Assert(err, IsNil)
c.Assert(os.MkdirAll(filepath.Join(dirs.SnapServicesDir, "snapd.mounts.target.wants"), 0755), IsNil)
// This is only to handle older version of snapd
c.Assert(os.MkdirAll(filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants"), 0755), IsNil)
// force UTC timezone, for reproducible timestamps
os.Setenv("TZ", "")
s.restoreSystemctl = MockSystemctlWithDelay(s.myRun)
s.i = 0
s.argses = nil
s.errors = nil
s.outs = nil
s.stopDelays = nil
s.stopArgses = nil
s.stopErrors = nil
s.stopIter = 0
s.restoreJournalctl = MockJournalctl(s.myJctl)
s.j = 0
s.jns = nil
s.jsvcs = nil
s.jouts = nil
s.jerrs = nil
s.jfollows = nil
s.jnamespaces = nil
s.rep = new(testreporter)
s.restoreSELinux = selinux.MockIsEnabled(func() (bool, error) { return false, nil })
}
func (s *SystemdTestSuite) TearDownTest(c *C) {
s.restoreSystemctl()
s.restoreJournalctl()
s.restoreSELinux()
}
func (s *SystemdTestSuite) myRun(args ...string) (out []byte, delay time.Duration, err error) {
// Default completion time for 'systemctl' operations
delayReq := time.Duration(0)
// We deal with 'systemctl stop' separately because this is a
// blocking call running inside a go routine (separate thread)
if args[0] == "stop" {
// 'systemctl stop' cases
s.stopArgses = append(s.stopArgses, args)
if s.stopIter < len(s.stopErrors) {
err = s.stopErrors[s.stopIter]
}
if s.stopIter < len(s.stopDelays) {
delayReq = s.stopDelays[s.stopIter]
}
s.stopIter++
} else {
// Other 'systemctl' cases
s.argses = append(s.argses, args)
if s.i < len(s.outs) {
out = s.outs[s.i]
}
if s.i < len(s.errors) {
err = s.errors[s.i]
}
s.i++
}
return out, delayReq, err
}
func (s *SystemdTestSuite) myJctl(svcs []string, n int, follow, namespaces bool) (io.ReadCloser, error) {
var err error
var out []byte
s.jns = append(s.jns, strconv.Itoa(n))
s.jsvcs = append(s.jsvcs, svcs)
s.jfollows = append(s.jfollows, follow)
s.jnamespaces = append(s.jnamespaces, namespaces)
if s.j < len(s.jouts) {
out = s.jouts[s.j]
}
if s.j < len(s.jerrs) {
err = s.jerrs[s.j]
}
s.j++
if out == nil {
return nil, err
}
return io.NopCloser(bytes.NewReader(out)), err
}
func (s *SystemdTestSuite) TestMockVersion(c *C) {
for _, tc := range []struct {
version int
err error
}{
{0, errors.New("some error")},
{123, nil},
} {
restore := MockSystemdVersion(tc.version, tc.err)
defer restore()
version, err := Version()
c.Check(version, Equals, tc.version)
c.Check(err, Equals, tc.err)
}
}
func (s *SystemdTestSuite) TestEnsureAtLeastFail(c *C) {
for _, tc := range []struct {
requiredVersion int
mockedVersion int
mockedErr error
expectedErr string
}{
{123, 0, errors.New("some error"), "some error"},
{140, 139, nil, `systemd version 139 is too old \(expected at least 140\)`},
{149, 150, nil, ""},
} {
restore := MockSystemdVersion(tc.mockedVersion, tc.mockedErr)
defer restore()
err := EnsureAtLeast(tc.requiredVersion)
if tc.expectedErr != "" {
c.Check(err, ErrorMatches, tc.expectedErr)
} else {
c.Check(err, IsNil)
}
}
}
func (s *SystemdTestSuite) TestBackend(c *C) {
c.Check(New(SystemMode, s.rep).Backend(), Equals, RunningSystemdBackend)
}
func (s *SystemdTestSuite) TestDaemonReload(c *C) {
err := New(SystemMode, s.rep).DaemonReload()
c.Assert(err, IsNil)
c.Assert(s.argses, DeepEquals, [][]string{{"daemon-reload"}})
}
func (s *SystemdTestSuite) TestDaemonReexec(c *C) {
err := New(SystemMode, s.rep).DaemonReexec()
c.Assert(err, IsNil)
c.Assert(s.argses, DeepEquals, [][]string{{"daemon-reexec"}})
}
func (s *SystemdTestSuite) TestStart(c *C) {
sysd := New(SystemMode, s.rep)
err := sysd.Start([]string{"foo"})
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"start", "foo"}})
s.argses = nil
err = sysd.Start([]string{"foo", "bar", "baz"})
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"start", "foo", "bar", "baz"}})
}
func (s *SystemdTestSuite) TestStartNoBlock(c *C) {
sysd := New(SystemMode, s.rep)
err := sysd.StartNoBlock([]string{"foo"})
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"start", "--no-block", "foo"}})
s.argses = nil
err = sysd.StartNoBlock([]string{"foo", "bar"})
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"start", "--no-block", "foo", "bar"}})
}
// Stop failed because systemctl stop returned an error
func (s *SystemdTestSuite) TestStopSystemCtlStopError(c *C) {
// Poll rate = 2ms, Silence timeout = 4ms
restore := MockStopDelays(2*time.Millisecond, 4*time.Millisecond)
defer restore()
s.stopErrors = []error{errors.New("mock error")}
err := New(SystemMode, s.rep).Stop([]string{"foo"})
c.Assert(err, ErrorMatches, "mock error")
c.Assert(s.stopArgses, HasLen, 1)
c.Check(s.stopArgses[0], DeepEquals, []string{"stop", "foo"})
// No notifications
c.Assert(len(s.rep.msgs), Equals, 0)
}
// Stop failed because systemctl show returned an error
func (s *SystemdTestSuite) TestSystemCtlShowError(c *C) {
// Poll rate = 2ms, Silence timeout = 4ms
restore := MockStopDelays(2*time.Millisecond, 4*time.Millisecond)
defer restore()
// 'systemctl stop'
s.stopDelays = []time.Duration{
// The delay we set here applies to the systemctl stop
// command. The purpose is to simulate how long systemd
// tries to stop the supplied services before returning.
50 * time.Millisecond,
}
// 'systemctl show'
s.errors = []error{errors.New("mock error")}
err := New(SystemMode, s.rep).Stop([]string{"foo"})
c.Assert(err, ErrorMatches, "mock error")
c.Assert(s.stopArgses, HasLen, 1)
c.Check(s.stopArgses[0], DeepEquals, []string{"stop", "foo"})
c.Assert(s.argses, HasLen, 1)
c.Check(s.argses[0], DeepEquals, []string{"show", "--property=ActiveState", "foo"})
// No notifications
c.Assert(len(s.rep.msgs), Equals, 0)
}
// The stop completed before the notification silence period expired so
// no output (notification) is expected
func (s *SystemdTestSuite) TestStopBeforeNotify(c *C) {
// Poll rate = 2ms, Silence timeout = 4ms
restore := MockStopDelays(2*time.Millisecond, 4*time.Millisecond)
defer restore()
// 'systemctl stop'
s.stopDelays = []time.Duration{
// The delay we set here applies to the systemctl stop
// command. The purpose is to simulate how long systemd
// tries to stop the supplied services before returning.
50 * time.Millisecond,
}
s.stopErrors = []error{nil, errors.New("mock error")}
// 'systemctl show'
s.outs = [][]byte{
[]byte("ActiveState=inactive\n"),
}
s.errors = []error{nil, errors.New("mock error")}
err := New(SystemMode, s.rep).Stop([]string{"foo"})
c.Assert(err, IsNil)
// 'systemctl stop'
c.Assert(s.stopArgses, HasLen, 1)
c.Check(s.stopArgses[0], DeepEquals, []string{"stop", "foo"})
// 'systemctl show'
c.Assert(s.argses, HasLen, 1)
c.Check(s.argses[0], DeepEquals, []string{"show", "--property=ActiveState", "foo"})
// No notifications
c.Assert(len(s.rep.msgs), Equals, 0)
}
// The stop completed after the notification silence period expired
func (s *SystemdTestSuite) TestStopAfterNotify(c *C) {
// Poll rate = 2ms, Silence timeout = 4ms
restore := MockStopDelays(2*time.Millisecond, 4*time.Millisecond)
defer restore()
// 'systemctl stop'
s.stopDelays = []time.Duration{
// The delay we set here applies to the systemctl stop
// command. The purpose is to simulate how long systemd
// tries to stop the supplied services before returning.
50 * time.Millisecond,
}
s.stopErrors = []error{nil, errors.New("mock error")}
// 'systemctl show'
s.outs = [][]byte{
[]byte("ActiveState=whatever\n"),
[]byte("ActiveState=whatever\n"),
[]byte("ActiveState=whatever\n"),
[]byte("ActiveState=active\n"),
[]byte("ActiveState=inactive\n"),
}
// The timeout error output should never by reached because the 's.outs'
// entry before this slot indicates the unit has stopped
s.errors = []error{nil, nil, nil, nil, nil, errors.New("mock error")}
err := New(SystemMode, s.rep).Stop([]string{"foo"})
c.Assert(err, IsNil)
// 'systemctl stop'
c.Assert(s.stopArgses, HasLen, 1)
c.Check(s.stopArgses[0], DeepEquals, []string{"stop", "foo"})
// 'systemctl show'
c.Assert(s.argses, HasLen, 5)
c.Check(s.argses[0], DeepEquals, []string{"show", "--property=ActiveState", "foo"})
c.Check(s.argses[0], DeepEquals, s.argses[1])
c.Check(s.argses[0], DeepEquals, s.argses[2])
c.Check(s.argses[0], DeepEquals, s.argses[3])
c.Check(s.argses[0], DeepEquals, s.argses[4])
// Only the initial notification will be shown because no
// list update are possible with only a single service unit
c.Assert(len(s.rep.msgs), Equals, 1)
c.Check(s.rep.msgs[0], Equals, `Waiting for "foo" to stop.`)
}
func (s *SystemdTestSuite) TestStopMany(c *C) {
// Poll rate = 2ms, Silence timeout = 4ms
restore := MockStopDelays(2*time.Millisecond, 4*time.Millisecond)
defer restore()
// 'systemctl stop'
s.stopDelays = []time.Duration{
// The delay we set here applies to the systemctl stop
// command. The purpose is to simulate how long systemd
// tries to stop the supplied services before returning.
50 * time.Millisecond,
}
s.stopErrors = []error{nil, errors.New("mock error")}
// 'systemctl show'
s.outs = [][]byte{
[]byte("ActiveState=whatever\n"), // foo
[]byte("ActiveState=whatever\n"), // bar
[]byte("ActiveState=whatever\n"), // foo
[]byte("ActiveState=whatever\n"), // bar
[]byte("ActiveState=active\n"), // foo
[]byte("ActiveState=whatever\n"), // bar
[]byte("ActiveState=inactive\n"), // foo
[]byte("ActiveState=active\n"), // bar
[]byte("ActiveState=inactive\n"), // bar
}
// The timeout error output should never by reached because the 's.outs'
// entry before this slot indicates the unit has stopped
s.errors = []error{nil, nil, nil, nil, nil, nil, nil, nil, nil, errors.New("mock error")}
err := New(SystemMode, s.rep).Stop([]string{"foo", "bar"})
c.Assert(err, IsNil)
// 'systemctl stop'
c.Assert(s.stopArgses, HasLen, 1)
c.Check(s.stopArgses[0], DeepEquals, []string{"stop", "foo", "bar"})
// 'systemctl show'
c.Assert(s.argses, HasLen, 9)
c.Check(s.argses[0], DeepEquals, []string{"show", "--property=ActiveState", "foo"})
c.Check(s.argses[1], DeepEquals, []string{"show", "--property=ActiveState", "bar"})
c.Check(s.argses[2], DeepEquals, []string{"show", "--property=ActiveState", "foo"})
c.Check(s.argses[3], DeepEquals, []string{"show", "--property=ActiveState", "bar"})
c.Check(s.argses[4], DeepEquals, []string{"show", "--property=ActiveState", "foo"})
c.Check(s.argses[5], DeepEquals, []string{"show", "--property=ActiveState", "bar"})
c.Check(s.argses[6], DeepEquals, []string{"show", "--property=ActiveState", "foo"})
c.Check(s.argses[7], DeepEquals, []string{"show", "--property=ActiveState", "bar"})
c.Check(s.argses[8], DeepEquals, []string{"show", "--property=ActiveState", "bar"})
c.Assert(len(s.rep.msgs), Equals, 2)
c.Check(s.rep.msgs[0], Equals, `Waiting for "foo", "bar" to stop.`)
c.Check(s.rep.msgs[1], Equals, `Waiting for "bar" to stop.`)
}
func (s *SystemdTestSuite) TestStatus(c *C) {
s.outs = [][]byte{
[]byte(`
Type=simple
Id=foo.service
Names=foo.service
ActiveState=active
UnitFileState=enabled
NeedDaemonReload=no
Type=simple
Id=bar.service
Names=bar.service
ActiveState=reloading
UnitFileState=static
NeedDaemonReload=no
Type=potato
Id=baz.service
Names=baz.service
ActiveState=inactive
UnitFileState=disabled
NeedDaemonReload=yes
Type=
Id=missing.service
Names=missing.service
ActiveState=inactive
UnitFileState=
NeedDaemonReload=no
`[1:]),
[]byte(`
Id=some.timer
Names=some.timer
ActiveState=active
UnitFileState=enabled
NeedDaemonReload=yes
Id=other.socket
Names=other.socket
ActiveState=active
UnitFileState=disabled
NeedDaemonReload=yes
Id=reboot.target
Names=reboot.target ctrl-alt-del.target
ActiveState=inactive
UnitFileState=enabled
NeedDaemonReload=yes
Id=reboot.target
Names=reboot.target ctrl-alt-del.target
ActiveState=inactive
UnitFileState=enabled
NeedDaemonReload=yes
`[1:]),
}
s.errors = []error{nil}
units := []string{
"foo.service", "bar.service", "baz.service",
"missing.service", "some.timer", "other.socket",
"reboot.target", "ctrl-alt-del.target",
}
out, err := New(SystemMode, s.rep).Status(units)
c.Assert(err, IsNil)
c.Check(out, DeepEquals, []*UnitStatus{
{
Daemon: "simple",
Name: "foo.service",
Names: []string{"foo.service"},
Active: true,
Enabled: true,
Installed: true,
Id: "foo.service",
NeedDaemonReload: false,
}, {
Daemon: "simple",
Name: "bar.service",
Names: []string{"bar.service"},
Active: true,
Enabled: true,
Installed: true,
Id: "bar.service",
NeedDaemonReload: false,
}, {
Daemon: "potato",
Name: "baz.service",
Names: []string{"baz.service"},
Active: false,
Enabled: false,
Installed: true,
Id: "baz.service",
NeedDaemonReload: true,
}, {
Daemon: "",
Name: "missing.service",
Names: []string{"missing.service"},
Active: false,
Enabled: false,
Installed: false,
Id: "missing.service",
NeedDaemonReload: false,
}, {
Name: "some.timer",
Names: []string{"some.timer"},
Active: true,
Enabled: true,
Installed: true,
Id: "some.timer",
NeedDaemonReload: true,
}, {
Name: "other.socket",
Names: []string{"other.socket"},
Active: true,
Enabled: false,
Installed: true,
Id: "other.socket",
NeedDaemonReload: true,
}, {
Name: "reboot.target",
Names: []string{"reboot.target", "ctrl-alt-del.target"},
Active: false,
Enabled: true,
Installed: true,
Id: "reboot.target",
NeedDaemonReload: true,
}, {
Name: "ctrl-alt-del.target",
Names: []string{"reboot.target", "ctrl-alt-del.target"},
Active: false,
Enabled: true,
Installed: true,
Id: "reboot.target",
NeedDaemonReload: true,
},
})
c.Check(s.rep.msgs, IsNil)
c.Assert(s.argses, DeepEquals, [][]string{
{"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", "foo.service", "bar.service", "baz.service", "missing.service"},
{"show", "--property=Id,ActiveState,UnitFileState,Names", "some.timer", "other.socket", "reboot.target", "ctrl-alt-del.target"},
})
}
func (s *SystemdTestSuite) TestStatusTooManyNumberOfValues(c *C) {
s.outs = [][]byte{
[]byte(`
Type=simple
Id=foo.service
Names=foo.service
ActiveState=active
UnitFileState=enabled
NeedDaemonReload=no
Type=simple
Id=foo.service
Names=foo.service
ActiveState=active
UnitFileState=enabled
NeedDaemonReload=no
`[1:]),
}
s.errors = []error{nil}
out, err := New(SystemMode, s.rep).Status([]string{"foo.service"})
c.Check(err, ErrorMatches, "cannot get unit status: got more results than expected")
c.Check(out, IsNil)
c.Check(s.rep.msgs, IsNil)
}
func (s *SystemdTestSuite) TestStatusTooFewNumberOfValues(c *C) {
s.outs = [][]byte{
[]byte(`
Type=simple
Id=foo.service
Names=foo.service
ActiveState=active
UnitFileState=enabled
NeedDaemonReload=no
Type=simple
Id=bar.service
Names=foo.service
ActiveState=active
UnitFileState=enabled
NeedDaemonReload=no
`[1:]),
}
s.errors = []error{nil}
units := []string{"foo.service", "bar.service", "test.service"}
out, err := New(SystemMode, s.rep).Status(units)
c.Check(err, ErrorMatches, "cannot get unit status: expected 3 results, got 2")
c.Check(out, IsNil)
c.Check(s.rep.msgs, IsNil)
}
func (s *SystemdTestSuite) TestStatusBadLine(c *C) {
s.outs = [][]byte{
[]byte(`
Type=simple
Id=foo.service
Names=foo.service
ActiveState=active
UnitFileState=enabled
Potatoes
`[1:]),
}
s.errors = []error{nil}
out, err := New(SystemMode, s.rep).Status([]string{"foo.service"})
c.Assert(err, ErrorMatches, `.* bad line "Potatoes" .*`)
c.Check(out, IsNil)
}
func (s *SystemdTestSuite) TestStatusBadId(c *C) {
s.outs = [][]byte{
[]byte(`
Type=simple
Id=bar.service
Names=bar.service
ActiveState=active
UnitFileState=enabled
NeedDaemonReload=no
`[1:]),
}
s.errors = []error{nil}
out, err := New(SystemMode, s.rep).Status([]string{"foo.service"})
c.Assert(err, ErrorMatches, `.* queried status of "foo.service" but got status of "bar.service"`)
c.Check(out, IsNil)
}
func (s *SystemdTestSuite) TestStatusBadField(c *C) {
s.outs = [][]byte{
[]byte(`
Type=simple
Id=foo.service
Names=foo.service
ActiveState=active
UnitFileState=enabled
Potatoes=false
NeedDaemonReload=no
`[1:]),
}
s.errors = []error{nil}
out, err := New(SystemMode, s.rep).Status([]string{"foo.service"})
c.Assert(err, ErrorMatches, `.* unexpected field "Potatoes" .*`)
c.Check(out, IsNil)
}
func (s *SystemdTestSuite) TestStatusMissingRequiredFieldService(c *C) {
s.outs = [][]byte{
[]byte(`
Id=foo.service
ActiveState=active
NeedDaemonReload=no
`[1:]),
}
s.errors = []error{nil}
out, err := New(SystemMode, s.rep).Status([]string{"foo.service"})
c.Assert(err, ErrorMatches, `.* missing UnitFileState, Type.*`)
c.Check(out, IsNil)
}
func (s *SystemdTestSuite) TestStatusMissingRequiredFieldTimer(c *C) {
s.outs = [][]byte{
[]byte(`
Id=foo.timer
ActiveState=active
`[1:]),
}
s.errors = []error{nil}
out, err := New(SystemMode, s.rep).Status([]string{"foo.timer"})
c.Assert(err, ErrorMatches, `.* missing UnitFileState, Names.*`)
c.Check(out, IsNil)
}
func (s *SystemdTestSuite) TestStatusMissingRequiredFieldTarget(c *C) {
s.outs = [][]byte{
[]byte(`
Id=reboot.target
ActiveState=active
`[1:]),
}
s.errors = []error{nil}
out, err := New(SystemMode, s.rep).Status([]string{"reboot.target"})
c.Assert(err, ErrorMatches, `.* missing UnitFileState, Names.*`)
c.Check(out, IsNil)
}
func (s *SystemdTestSuite) TestStatusDupeField(c *C) {
s.outs = [][]byte{
[]byte(`
Type=simple
Id=foo.service
Names=foo.service
ActiveState=active
ActiveState=active
UnitFileState=enabled
NeedDaemonReload=no
`[1:]),
}
s.errors = []error{nil}
out, err := New(SystemMode, s.rep).Status([]string{"foo.service"})
c.Assert(err, ErrorMatches, `.* duplicate field "ActiveState" .*`)
c.Check(out, IsNil)
}
func (s *SystemdTestSuite) TestStatusEmptyField(c *C) {
s.outs = [][]byte{
[]byte(`
Type=simple
Id=
Names=foo.service
ActiveState=active
UnitFileState=enabled
NeedDaemonReload=no
`[1:]),
}
s.errors = []error{nil}
out, err := New(SystemMode, s.rep).Status([]string{"foo.service"})
c.Assert(err, ErrorMatches, `.* empty field "Id" .*`)
c.Check(out, IsNil)
}
func (s *SystemdTestSuite) TestDisable(c *C) {
sysd := New(SystemMode, s.rep)
err := sysd.DisableNoReload([]string{"foo"})
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"--no-reload", "disable", "foo"}})
s.argses = nil
err = sysd.DisableNoReload([]string{"foo", "bar"})
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"--no-reload", "disable", "foo", "bar"}})
}
func (s *SystemdTestSuite) TestUnderRootDisable(c *C) {
err := NewUnderRoot("xyzzy", SystemMode, s.rep).DisableNoReload([]string{"foo"})
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "disable", "foo"}})
}
func (s *SystemdTestSuite) TestAvailable(c *C) {
err := Available()
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"--version"}})
}
func (s *SystemdTestSuite) TestVersion(c *C) {
s.outs = [][]byte{
[]byte("systemd 223\n+PAM\n"),
[]byte("systemd 245 (245.4-4ubuntu3)\n+PAM +AUDIT +SELINUX +IMA\n"),
[]byte("systemd 255~rc3\n+PAM"),
[]byte("systemd 256~rc3-foo\n+PAM"),
// error cases
[]byte("foo 223\n+PAM\n"),
[]byte(""),
[]byte("systemd abc\n+PAM\n"),
}
v, err := Version()
c.Assert(err, IsNil)
c.Check(v, Equals, 223)
v, err = Version()
c.Assert(err, IsNil)
c.Check(v, Equals, 245)
v, err = Version()
c.Assert(err, IsNil)
c.Check(v, Equals, 255)
v, err = Version()
c.Assert(err, IsNil)
c.Check(v, Equals, 256)
_, err = Version()
c.Assert(err, ErrorMatches, `cannot parse systemd version: expected "systemd", got "foo"`)
_, err = Version()
c.Assert(err, ErrorMatches, `cannot read systemd version: <nil>`)
_, err = Version()
c.Assert(err, ErrorMatches, `cannot convert systemd version to number: abc`)
c.Check(s.argses, DeepEquals, [][]string{
{"--version"},
{"--version"},
{"--version"},
{"--version"},
{"--version"},
{"--version"},
{"--version"},
})
}
func (s *SystemdTestSuite) TestEnable(c *C) {
sysd := New(SystemMode, s.rep)
err := sysd.EnableNoReload([]string{"foo"})
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"--no-reload", "enable", "foo"}})
s.argses = nil
err = sysd.EnableNoReload([]string{"foo", "bar"})
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"--no-reload", "enable", "foo", "bar"}})
}
func (s *SystemdTestSuite) TestEnableUnderRoot(c *C) {
err := NewUnderRoot("xyzzy", SystemMode, s.rep).EnableNoReload([]string{"foo"})
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "enable", "foo"}})
}
func (s *SystemdTestSuite) TestMask(c *C) {
err := New(SystemMode, s.rep).Mask("foo")
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"mask", "foo"}})
}
func (s *SystemdTestSuite) TestMaskUnderRoot(c *C) {
err := NewUnderRoot("xyzzy", SystemMode, s.rep).Mask("foo")
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "mask", "foo"}})
}
func (s *SystemdTestSuite) TestUnmask(c *C) {
err := New(SystemMode, s.rep).Unmask("foo")
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"unmask", "foo"}})
}
func (s *SystemdTestSuite) TestUnmaskUnderRoot(c *C) {
err := NewUnderRoot("xyzzy", SystemMode, s.rep).Unmask("foo")
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "unmask", "foo"}})
}
func (s *SystemdTestSuite) TestRestart(c *C) {
restore := MockStopDelays(2*time.Millisecond, 4*time.Millisecond)
defer restore()
// 'systemctl stop'
s.stopDelays = []time.Duration{
// The delay we set here applies to the systemctl stop
// command. The purpose is to simulate how long systemd
// tries to stop the supplied services before returning.
50 * time.Millisecond,
}
s.outs = [][]byte{
[]byte("ActiveState=inactive\n"),
nil, // for the "start"
}
s.errors = []error{nil, nil, errors.New("mock error")}
err := New(SystemMode, s.rep).Restart([]string{"foo"})
c.Assert(err, IsNil)
c.Check(s.stopArgses, HasLen, 1)
c.Check(s.stopArgses[0], DeepEquals, []string{"stop", "foo"})
c.Check(s.argses, HasLen, 2)
c.Check(s.argses[0], DeepEquals, []string{"show", "--property=ActiveState", "foo"})
c.Check(s.argses[1], DeepEquals, []string{"start", "foo"})
}
func (s *SystemdTestSuite) TestRestartMany(c *C) {
restore := MockStopDelays(2*time.Millisecond, 4*time.Millisecond)
defer restore()
// 'systemctl stop'
s.stopDelays = []time.Duration{
// The delay we set here applies to the systemctl stop
// command. The purpose is to simulate how long systemd
// tries to stop the supplied services before returning.
50 * time.Millisecond,
}
s.outs = [][]byte{
[]byte("ActiveState=inactive\n"), // foo
[]byte("ActiveState=inactive\n"), // bar
nil, // for the "start"
}
s.errors = []error{nil, nil, nil, errors.New("mock error")}
err := New(SystemMode, s.rep).Restart([]string{"foo", "bar"})
c.Assert(err, IsNil)
c.Check(s.stopArgses, HasLen, 1)
c.Check(s.stopArgses[0], DeepEquals, []string{"stop", "foo", "bar"})
c.Check(s.argses, HasLen, 3)
c.Check(s.argses[0], DeepEquals, []string{"show", "--property=ActiveState", "foo"})
c.Check(s.argses[1], DeepEquals, []string{"show", "--property=ActiveState", "bar"})
c.Check(s.argses[2], DeepEquals, []string{"start", "foo", "bar"})
}
func (s *SystemdTestSuite) TestKill(c *C) {
c.Assert(New(SystemMode, s.rep).Kill("foo", "HUP", ""), IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"kill", "foo", "-s", "HUP", "--kill-who=all"}})
}
func (s *SystemdTestSuite) TestLogErrJctl(c *C) {
s.jerrs = []error{errors.New("mock journalctl error")}
reader, err := New(SystemMode, s.rep).LogReader([]string{"foo"}, 24, false, false)
c.Check(err, NotNil)
c.Check(reader, IsNil)
c.Check(s.jns, DeepEquals, []string{"24"})
c.Check(s.jsvcs, DeepEquals, [][]string{{"foo"}})
c.Check(s.jfollows, DeepEquals, []bool{false})
c.Check(s.jnamespaces, DeepEquals, []bool{false})
c.Check(s.j, Equals, 1)
}
func (s *SystemdTestSuite) TestLogs(c *C) {
expected := `{"a": 1}
{"a": 2}
`
s.jouts = [][]byte{[]byte(expected)}
reader, err := New(SystemMode, s.rep).LogReader([]string{"foo"}, 24, false, false)
c.Check(err, IsNil)
logs, err := io.ReadAll(reader)
c.Assert(err, IsNil)
c.Check(string(logs), Equals, expected)
c.Check(s.jns, DeepEquals, []string{"24"})
c.Check(s.jsvcs, DeepEquals, [][]string{{"foo"}})
c.Check(s.jfollows, DeepEquals, []bool{false})
c.Check(s.jnamespaces, DeepEquals, []bool{false})
c.Check(s.j, Equals, 1)
}
// mustJSONMarshal panic's if the value cannot be marshaled
func mustJSONMarshal(v interface{}) *json.RawMessage {
b, err := json.Marshal(v)
if err != nil {
panic(fmt.Sprintf("couldn't marshal json in test fixture: %v", err))
}
msg := json.RawMessage(b)
return &msg
}
func (s *SystemdTestSuite) TestLogPIDWithNonTrivialKeyValues(c *C) {
l1 := Log{
"_PID": mustJSONMarshal([]string{}),
}
l2 := Log{
"_PID": mustJSONMarshal(6),
}
l3 := Log{
"_PID": mustJSONMarshal([]string{"pid1", "pid2", "pid3"}),
}
l4 := Log{
"SYSLOG_PID": mustJSONMarshal([]string{"pid1", "pid2", "pid3"}),
}
l5 := Log{
"_PID": mustJSONMarshal("42"),
"SYSLOG_PID": mustJSONMarshal([]string{"pid1", "pid2", "pid3"}),
}
l6 := Log{
"_PID": mustJSONMarshal([]string{"42"}),
"SYSLOG_PID": mustJSONMarshal([]string{"pid1", "pid2", "pid3"}),
}
l7 := Log{
"_PID": mustJSONMarshal([]string{"42", "42"}),
"SYSLOG_PID": mustJSONMarshal([]string{"singlepid"}),
}
c.Check(Log{}.PID(), Equals, "-")
c.Check(l1.PID(), Equals, "-")
c.Check(l2.PID(), Equals, "-")
c.Check(l3.PID(), Equals, "-")
c.Check(l4.PID(), Equals, "-")
// things starting with underscore are "trusted", so we trust
// them more than the user-settable ones:
c.Check(l5.PID(), Equals, "42")
c.Check(l6.PID(), Equals, "42")
c.Check(l7.PID(), Equals, "singlepid")
}
func (s *SystemdTestSuite) TestLogsMessageWithNonUniqueKeys(c *C) {
tt := []struct {
msg *json.RawMessage
exp string
comment string
}{
{
mustJSONMarshal("m1"),
"m1",
"simple string",
},
{
mustJSONMarshal("Я"),
"Я",
"simple utf-8 string",
},
{
mustJSONMarshal([]rune{65, 66, 67, 192, 69}),
"ABC\xc0E",
"invalid utf-8 bytes",
},
{
mustJSONMarshal(""),
"",
"empty string",
},
{
mustJSONMarshal([]string{"m1", "m2", "m3"}),
"m1\nm2\nm3",
"slice of strings",
},
{
// this is just "hello" in ascii
mustJSONMarshal([]rune{104, 101, 108, 108, 111}),
"hello",
"rune arrays are converted to strings",
},
{
// this is "hello\r" in ascii, the \r char is unprintable
mustJSONMarshal([]rune{104, 101, 108, 108, 111, 13}),
"hello\r",
"rune arrays are converted to strings",
},
{
// this is "hel" and "lo" in ascii
mustJSONMarshal([][]rune{
{104, 101, 108},
{108, 111},
}),
"hel\nlo",
"arrays of rune arrays are converted to arrays of strings",
},
{
// this is invalid utf-8 string followed by a valid one
mustJSONMarshal([][]byte{
{65, 66, 67, 192, 69},
{104, 101, 108, 108, 111},
}),
"ABC\xc0E\nhello",
"arrays of bytes, some are invalid utf-8 strings",
},
{
mustJSONMarshal(5),
"- (error decoding original message: unsupported JSON encoding format)",
"invalid message format of raw scalar number",
},
{
mustJSONMarshal(map[string]int{"hello": 1}),
"- (error decoding original message: unsupported JSON encoding format)",
"invalid message format of map object",
},
}
// trivial case
c.Check(Log{}.Message(), Equals, "-")
// case where the JSON has a "null" JSON value for the key, which happens if
// the actual message is too large for journald to send
// we can't use the mustJSONMarshal helper for this in the test table
// because that gets decoded by Go differently than a verbatim nil here, it
// gets interpreted as the empty string rather than nil directly
c.Check(Log{"MESSAGE": nil}.Message(), Equals, "- (error decoding original message: message key \"MESSAGE\" truncated)")
for _, t := range tt {
if t.msg == nil {
}
c.Check(Log{
"MESSAGE": t.msg,
}.Message(), Equals, t.exp, Commentf(t.comment))
}
}
func (s *SystemdTestSuite) TestLogSID(c *C) {
c.Check(Log{}.SID(), Equals, "-")
c.Check(Log{"SYSLOG_IDENTIFIER": mustJSONMarshal("abcdef")}.SID(), Equals, "abcdef")
c.Check(Log{"SYSLOG_IDENTIFIER": mustJSONMarshal([]string{"abcdef"})}.SID(), Equals, "abcdef")
// multiple string values are not supported
c.Check(Log{"SYSLOG_IDENTIFIER": mustJSONMarshal([]string{"abc", "def"})}.SID(), Equals, "-")
}
func (s *SystemdTestSuite) TestLogPID(c *C) {
c.Check(Log{}.PID(), Equals, "-")
c.Check(Log{"_PID": mustJSONMarshal("99")}.PID(), Equals, "99")
c.Check(Log{"SYSLOG_PID": mustJSONMarshal("99")}.PID(), Equals, "99")
// things starting with underscore are "trusted", so we trust
// them more than the user-settable ones:
c.Check(Log{
"_PID": mustJSONMarshal("42"),
"SYSLOG_PID": mustJSONMarshal("99"),
}.PID(), Equals, "42")
}
func (s *SystemdTestSuite) TestTime(c *C) {
t, err := Log{}.Time()
c.Check(t.IsZero(), Equals, true)
c.Check(err, ErrorMatches, "key \"__REALTIME_TIMESTAMP\" missing from message")
// multiple timestampe keys mean we don't have a timestamp
t, err = Log{"__REALTIME_TIMESTAMP": mustJSONMarshal([]string{"1", "2"})}.Time()
c.Check(t.IsZero(), Equals, true)
c.Check(err, ErrorMatches, `no timestamp`)
t, err = Log{"__REALTIME_TIMESTAMP": mustJSONMarshal("what")}.Time()
c.Check(t.IsZero(), Equals, true)
c.Check(err, ErrorMatches, `timestamp not a decimal number: "what"`)
t, err = Log{"__REALTIME_TIMESTAMP": mustJSONMarshal("0")}.Time()
c.Check(err, IsNil)
c.Check(t.String(), Equals, "1970-01-01 00:00:00 +0000 UTC")
t, err = Log{"__REALTIME_TIMESTAMP": mustJSONMarshal("42")}.Time()
c.Check(err, IsNil)
c.Check(t.String(), Equals, "1970-01-01 00:00:00.000042 +0000 UTC")
}
func (s *SystemdTestSuite) TestMountUnitPath(c *C) {
c.Assert(MountUnitPath("/apps/hello/1.1"), Equals, filepath.Join(dirs.SnapServicesDir, "apps-hello-1.1.mount"))
}
func makeMockFile(c *C, path string) {
err := os.MkdirAll(filepath.Dir(path), 0755)
c.Assert(err, IsNil)
err = os.WriteFile(path, nil, 0644)
c.Assert(err, IsNil)
}
func (s *SystemdTestSuite) TestAddMountUnit(c *C) {
rootDir := dirs.GlobalRootDir
restore := squashfs.MockNeedsFuse(false)
defer restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
makeMockFile(c, mockSnapPath)
mountUnitName, err := NewUnderRoot(rootDir, SystemMode, nil).EnsureMountUnitFile("Mount unit for foo, revision 42", mockSnapPath, "/snap/snapname/123", "squashfs", systemd.EnsureMountUnitFlags{})
c.Assert(err, IsNil)
defer os.Remove(mountUnitName)
c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(`
[Unit]
Description=Mount unit for foo, revision 42
After=snapd.mounts-pre.target
Before=snapd.mounts.target
[Mount]
What=%s
Where=/snap/snapname/123
Type=squashfs
Options=nodev,ro,x-gdu.hide,x-gvfs-hide
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
`[1:], mockSnapPath))
c.Assert(s.argses, DeepEquals, [][]string{
{"daemon-reload"},
{"--root", rootDir, "enable", "snap-snapname-123.mount"},
{"restart", "snap-snapname-123.mount"},
})
}
func (s *SystemdTestSuite) TestEnsureMountUnitUnchanged(c *C) {
rootDir := dirs.GlobalRootDir
restore := squashfs.MockNeedsFuse(false)
defer restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
makeMockFile(c, mockSnapPath)
// Now we create the mount unit already
err := os.MkdirAll(dirs.SnapServicesDir, 0755)
c.Assert(err, IsNil)
content := fmt.Sprintf(`
[Unit]
Description=Mount unit for foo, revision 42
After=snapd.mounts-pre.target
Before=snapd.mounts.target
[Mount]
What=%s
Where=/snap/snapname/123
Type=squashfs
Options=nodev,ro,x-gdu.hide,x-gvfs-hide
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
`[1:], mockSnapPath)
err = os.WriteFile(filepath.Join(dirs.SnapServicesDir, "snap-snapname-123.mount"), []byte(content), 0644)
c.Assert(err, IsNil)
mountUnitName, err := NewUnderRoot(rootDir, SystemMode, nil).EnsureMountUnitFile("Mount unit for foo, revision 42", mockSnapPath, "/snap/snapname/123", "squashfs", systemd.EnsureMountUnitFlags{})
c.Assert(err, IsNil)
// Should still be the same file
c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(`
[Unit]
Description=Mount unit for foo, revision 42
After=snapd.mounts-pre.target
Before=snapd.mounts.target
[Mount]
What=%s
Where=/snap/snapname/123
Type=squashfs
Options=nodev,ro,x-gdu.hide,x-gvfs-hide
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
`[1:], mockSnapPath))
// No call systemctl
c.Assert(s.argses, DeepEquals, [][]string(nil))
}
func (s *SystemdTestSuite) TestEnsureMountUnitChanged(c *C) {
s.testEnsureMountUnitChanged(c, systemd.EnsureMountUnitFlags{})
}
func (s *SystemdTestSuite) TestEnsureMountUnitChangedIgnoreChanged(c *C) {
s.testEnsureMountUnitChanged(c,
systemd.EnsureMountUnitFlags{PreventRestartIfModified: true})
}
func (s *SystemdTestSuite) testEnsureMountUnitChanged(c *C, mountFlags systemd.EnsureMountUnitFlags) {
rootDir := dirs.GlobalRootDir
restore := squashfs.MockNeedsFuse(false)
defer restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
makeMockFile(c, mockSnapPath)
// Now we create the mount unit already
err := os.MkdirAll(dirs.SnapServicesDir, 0755)
c.Assert(err, IsNil)
// Note the "someotheroption" that should be removed
content := fmt.Sprintf(`
[Unit]
Description=Mount unit for foo, revision 42
Before=snapd.service
[Mount]
What=%s
Where=/snap/snapname/123
Type=squashfs
Options=nodev,ro,x-gdu.hide,x-gvfs-hide,someotheroption
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
`[1:], mockSnapPath)
err = os.WriteFile(filepath.Join(dirs.SnapServicesDir, "snap-snapname-123.mount"), []byte(content), 0644)
c.Assert(err, IsNil)
mountUnitName, err := NewUnderRoot(rootDir, SystemMode, nil).EnsureMountUnitFile("Mount unit for foo, revision 42", mockSnapPath, "/snap/snapname/123", "squashfs", mountFlags)
c.Assert(err, IsNil)
// Should still be the same file
c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(`
[Unit]
Description=Mount unit for foo, revision 42
After=snapd.mounts-pre.target
Before=snapd.mounts.target
[Mount]
What=%s
Where=/snap/snapname/123
Type=squashfs
Options=nodev,ro,x-gdu.hide,x-gvfs-hide
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
`[1:], mockSnapPath))
// No call systemctl
expectedCalls := [][]string{
{"daemon-reload"},
{"--root", rootDir, "enable", "snap-snapname-123.mount"}}
if !mountFlags.PreventRestartIfModified {
expectedCalls = append(expectedCalls, []string{"restart", "snap-snapname-123.mount"})
}
c.Assert(s.argses, DeepEquals, expectedCalls)
}
func (s *SystemdTestSuite) TestAddMountUnitForDirs(c *C) {
restore := squashfs.MockNeedsFuse(false)
defer restore()
// a directory instead of a file produces a different output
snapDir := c.MkDir()
mountUnitName, err := New(SystemMode, nil).EnsureMountUnitFile("Mount unit for foodir, revision x1", snapDir, "/snap/snapname/x1", "squashfs", systemd.EnsureMountUnitFlags{})
c.Assert(err, IsNil)
defer os.Remove(mountUnitName)
c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(`
[Unit]
Description=Mount unit for foodir, revision x1
After=snapd.mounts-pre.target
Before=snapd.mounts.target
[Mount]
What=%s
Where=/snap/snapname/x1
Type=none
Options=nodev,ro,x-gdu.hide,x-gvfs-hide,bind
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
`[1:], snapDir))
c.Assert(s.argses, DeepEquals, [][]string{
{"daemon-reload"},
{"--no-reload", "enable", "snap-snapname-x1.mount"},
{"restart", "snap-snapname-x1.mount"},
})
}
func (s *SystemdTestSuite) TestAddMountUnitStartBeforeDriversLoad(c *C) {
restore := squashfs.MockNeedsFuse(false)
defer restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
makeMockFile(c, mockSnapPath)
mountUnitName, err := New(SystemMode, nil).EnsureMountUnitFile("Mount unit for foo, revision x1", mockSnapPath, "/snap/snapname/x1", "squashfs", systemd.EnsureMountUnitFlags{StartBeforeDriversLoad: true})
c.Assert(err, IsNil)
defer os.Remove(mountUnitName)
c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(`
[Unit]
Description=Mount unit for foo, revision x1
After=snapd.mounts-pre.target
Before=snapd.mounts.target
Before=systemd-udevd.service systemd-modules-load.service
[Mount]
What=%s
Where=/snap/snapname/x1
Type=squashfs
Options=nodev,ro,x-gdu.hide,x-gvfs-hide
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
`[1:], mockSnapPath))
c.Assert(s.argses, DeepEquals, [][]string{
{"daemon-reload"},
{"--no-reload", "enable", "snap-snapname-x1.mount"},
{"restart", "snap-snapname-x1.mount"},
})
}
func (s *SystemdTestSuite) TestAddMountUnitTransient(c *C) {
rootDir := dirs.GlobalRootDir
restore := squashfs.MockNeedsFuse(false)
defer restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
makeMockFile(c, mockSnapPath)
addMountUnitOptions := &MountUnitOptions{
Lifetime: Transient,
Description: "Mount unit for foo via bar",
What: mockSnapPath,
Where: "/snap/snapname/345",
Fstype: "squashfs",
Options: []string{"remount,ro"},
Origin: "bar",
}
mountUnitName, err := NewUnderRoot(rootDir, SystemMode, nil).EnsureMountUnitFileWithOptions(addMountUnitOptions)
c.Assert(err, IsNil)
defer os.Remove(mountUnitName)
c.Assert(filepath.Join(dirs.SnapRuntimeServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(`
[Unit]
Description=Mount unit for foo via bar
After=snapd.mounts-pre.target
Before=snapd.mounts.target
[Mount]
What=%s
Where=/snap/snapname/345
Type=squashfs
Options=remount,ro
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
X-SnapdOrigin=bar
`[1:], mockSnapPath))
c.Assert(s.argses, DeepEquals, [][]string{
{"daemon-reload"},
{"--root", rootDir, "enable", "snap-snapname-345.mount"},
{"restart", "snap-snapname-345.mount"},
})
}
func (s *SystemdTestSuite) TestAddKernelModulesMountUnit(c *C) {
rootDir := dirs.GlobalRootDir
restore := squashfs.MockNeedsFuse(false)
defer restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snapd/snaps/mykernel+mykmod_11.comp")
makeMockFile(c, mockSnapPath)
addMountUnitOptions := &MountUnitOptions{
MountUnitType: BeforeDriversLoadMountUnit,
Lifetime: Persistent,
Description: "Mount unit for wifi kernel modules component",
What: mockSnapPath,
Where: "/run/mnt/kernel-modules/5.15.0-91-generic/mykmod/",
Fstype: "squashfs",
Options: []string{"nodev,ro,x-gdu.hide,x-gvfs-hide"},
Origin: "",
}
mountUnitName, err := NewUnderRoot(rootDir, SystemMode, nil).EnsureMountUnitFileWithOptions(addMountUnitOptions)
c.Assert(err, IsNil)
defer os.Remove(mountUnitName)
c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(`[Unit]
Description=Mount unit for wifi kernel modules component
After=snapd.mounts-pre.target
Before=snapd.mounts.target
Before=systemd-udevd.service systemd-modules-load.service
[Mount]
What=%s
Where=/run/mnt/kernel-modules/5.15.0-91-generic/mykmod/
Type=squashfs
Options=nodev,ro,x-gdu.hide,x-gvfs-hide
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
`, mockSnapPath))
escapedUnit := "run-mnt-kernel\\x2dmodules-5.15.0\\x2d91\\x2dgeneric-mykmod.mount"
c.Assert(s.argses, DeepEquals, [][]string{
{"daemon-reload"},
{"--root", rootDir, "enable", escapedUnit},
{"restart", escapedUnit},
})
}
func (s *SystemdTestSuite) TestAddKernelTreeMountUnit(c *C) {
rootDir := dirs.GlobalRootDir
restore := squashfs.MockNeedsFuse(false)
defer restore()
// systemd would automatically add a dependency for this unit on the mount unit for
// /run/mnt/kernel-modules/5.15.0-91-generic/mykmod
addMountUnitOptions := &MountUnitOptions{
MountUnitType: BeforeDriversLoadMountUnit,
Lifetime: Persistent,
Description: "Mount unit for kernel modules in kernel tree",
What: "/run/mnt/kernel-modules/5.15.0-91-generic/mykmod/modules/5.15.0-91-generic",
Where: "/usr/lib/modules/5.15.0-91-generic/updates/mykmod/",
Fstype: "none",
Options: []string{"bind"},
Origin: "",
}
mountUnitName, err := NewUnderRoot(rootDir, SystemMode, nil).EnsureMountUnitFileWithOptions(addMountUnitOptions)
c.Assert(err, IsNil)
defer os.Remove(mountUnitName)
c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(`[Unit]
Description=Mount unit for kernel modules in kernel tree
After=snapd.mounts-pre.target
Before=snapd.mounts.target
Before=systemd-udevd.service systemd-modules-load.service
[Mount]
What=/run/mnt/kernel-modules/5.15.0-91-generic/mykmod/modules/5.15.0-91-generic
Where=/usr/lib/modules/5.15.0-91-generic/updates/mykmod/
Type=none
Options=bind
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
`))
escapedUnit := "usr-lib-modules-5.15.0\\x2d91\\x2dgeneric-updates-mykmod.mount"
c.Assert(s.argses, DeepEquals, [][]string{
{"daemon-reload"},
{"--root", rootDir, "enable", escapedUnit},
{"restart", escapedUnit},
})
}
func (s *SystemdTestSuite) TestWriteSELinuxMountUnit(c *C) {
restore := selinux.MockIsEnabled(func() (bool, error) { return true, nil })
defer restore()
restore = selinux.MockIsEnforcing(func() (bool, error) { return true, nil })
defer restore()
restore = squashfs.MockNeedsFuse(false)
defer restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
err := os.MkdirAll(filepath.Dir(mockSnapPath), 0755)
c.Assert(err, IsNil)
err = os.WriteFile(mockSnapPath, nil, 0644)
c.Assert(err, IsNil)
mountUnitName, err := New(SystemMode, nil).EnsureMountUnitFile("Mount unit for foo, revision 42", mockSnapPath, "/snap/snapname/123", "squashfs", systemd.EnsureMountUnitFlags{})
c.Assert(err, IsNil)
defer os.Remove(mountUnitName)
c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(`
[Unit]
Description=Mount unit for foo, revision 42
After=snapd.mounts-pre.target
Before=snapd.mounts.target
[Mount]
What=%s
Where=/snap/snapname/123
Type=squashfs
Options=nodev,context=system_u:object_r:snappy_snap_t:s0,ro,x-gdu.hide,x-gvfs-hide
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
`[1:], mockSnapPath))
}
func (s *SystemdTestSuite) TestFuseInContainer(c *C) {
if !osutil.FileExists("/dev/fuse") {
c.Skip("No /dev/fuse on the system")
}
systemdCmd := testutil.MockCommand(c, "systemd-detect-virt", `
echo lxc
exit 0
`)
defer systemdCmd.Restore()
fuseCmd := testutil.MockCommand(c, "squashfuse", `
exit 0
`)
defer fuseCmd.Restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
err := os.MkdirAll(filepath.Dir(mockSnapPath), 0755)
c.Assert(err, IsNil)
err = os.WriteFile(mockSnapPath, nil, 0644)
c.Assert(err, IsNil)
mountUnitName, err := New(SystemMode, nil).EnsureMountUnitFile("Mount unit for foo, revision x1", mockSnapPath, "/snap/snapname/123", "squashfs", systemd.EnsureMountUnitFlags{})
c.Assert(err, IsNil)
defer os.Remove(mountUnitName)
c.Check(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(`
[Unit]
Description=Mount unit for foo, revision x1
After=snapd.mounts-pre.target
Before=snapd.mounts.target
[Mount]
What=%s
Where=/snap/snapname/123
Type=fuse.squashfuse
Options=nodev,ro,x-gdu.hide,x-gvfs-hide,allow_other
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
`[1:], mockSnapPath))
}
func (s *SystemdTestSuite) TestFuseOutsideContainer(c *C) {
systemdCmd := testutil.MockCommand(c, "systemd-detect-virt", `
echo none
exit 0
`)
defer systemdCmd.Restore()
fuseCmd := testutil.MockCommand(c, "squashfuse", `
exit 0
`)
defer fuseCmd.Restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
err := os.MkdirAll(filepath.Dir(mockSnapPath), 0755)
c.Assert(err, IsNil)
err = os.WriteFile(mockSnapPath, nil, 0644)
c.Assert(err, IsNil)
mountUnitName, err := New(SystemMode, nil).EnsureMountUnitFile("Mount unit for foo, revision x1", mockSnapPath, "/snap/snapname/123", "squashfs", systemd.EnsureMountUnitFlags{})
c.Assert(err, IsNil)
defer os.Remove(mountUnitName)
c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(`
[Unit]
Description=Mount unit for foo, revision x1
After=snapd.mounts-pre.target
Before=snapd.mounts.target
[Mount]
What=%s
Where=/snap/snapname/123
Type=squashfs
Options=nodev,ro,x-gdu.hide,x-gvfs-hide
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
`[1:], mockSnapPath))
}
func (s *SystemdTestSuite) TestJctl(c *C) {
var args []string
var err error
MockOsutilStreamCommand(func(name string, myargs ...string) (io.ReadCloser, error) {
c.Check(cap(myargs) <= len(myargs)+3, Equals, true, Commentf("cap:%d, len:%d", cap(myargs), len(myargs)))
args = myargs
return nil, nil
})
_, err = Jctl([]string{"foo", "bar"}, 10, false, false)
c.Assert(err, IsNil)
c.Check(args, DeepEquals, []string{"-o", "json", "--no-pager", "-n", "10", "-u", "foo", "-u", "bar"})
_, err = Jctl([]string{"foo", "bar", "baz"}, 99, true, false)
c.Assert(err, IsNil)
c.Check(args, DeepEquals, []string{"-o", "json", "--no-pager", "-n", "99", "-f", "-u", "foo", "-u", "bar", "-u", "baz"})
_, err = Jctl([]string{"foo", "bar"}, -1, false, false)
c.Assert(err, IsNil)
c.Check(args, DeepEquals, []string{"-o", "json", "--no-pager", "--no-tail", "-u", "foo", "-u", "bar"})
_, err = Jctl([]string{"foo", "bar"}, -1, false, true)
c.Assert(err, IsNil)
c.Check(args, DeepEquals, []string{"-o", "json", "--no-pager", "--no-tail", "--namespace=*", "-u", "foo", "-u", "bar"})
}
func (s *SystemdTestSuite) TestIsActiveUnderRoot(c *C) {
sysErr := &Error{}
// manpage states that systemctl returns exit code 3 for inactive
// services, however we should check any non-0 exit status
sysErr.SetExitCode(1)
sysErr.SetMsg([]byte("inactive\n"))
s.errors = []error{sysErr}
_, err := NewUnderRoot("xyzzy", SystemMode, s.rep).IsActive("foo")
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "is-active", "foo"}})
}
func (s *SystemdTestSuite) TestIsActiveIsInactive(c *C) {
sysErr := &Error{}
// manpage states that systemctl returns exit code 3 for inactive
// services, however we should check any non-0 exit status
sysErr.SetExitCode(1)
sysErr.SetMsg([]byte("inactive\n"))
s.errors = []error{sysErr}
active, err := New(SystemMode, s.rep).IsActive("foo")
c.Assert(active, Equals, false)
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"is-active", "foo"}})
}
func (s *SystemdTestSuite) TestIsActiveIsInactiveAlternativeMessage(c *C) {
sysErr := &Error{}
// on Centos 7, with systemd 219 we see "unknown" returned when querying the
// active state for a slice unit which does not exist, check that we handle
// this case properly as well
sysErr.SetExitCode(3)
sysErr.SetMsg([]byte("unknown\n"))
s.errors = []error{sysErr}
active, err := New(SystemMode, s.rep).IsActive("foo")
c.Assert(active, Equals, false)
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"is-active", "foo"}})
}
func (s *SystemdTestSuite) TestIsActiveIsFailed(c *C) {
sysErr := &Error{}
// seen in the wild to be reported for a 'failed' service
sysErr.SetExitCode(3)
sysErr.SetMsg([]byte("failed\n"))
s.errors = []error{sysErr}
active, err := New(SystemMode, s.rep).IsActive("foo")
c.Assert(active, Equals, false)
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"is-active", "foo"}})
}
func (s *SystemdTestSuite) TestIsActiveIsActive(c *C) {
s.errors = []error{nil}
active, err := New(SystemMode, s.rep).IsActive("foo")
c.Assert(active, Equals, true)
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"is-active", "foo"}})
}
func (s *SystemdTestSuite) TestIsActiveUnexpectedErr(c *C) {
sysErr := &Error{}
sysErr.SetExitCode(1)
sysErr.SetMsg([]byte("random-failure\n"))
s.errors = []error{sysErr}
active, err := NewUnderRoot("xyzzy", SystemMode, s.rep).IsActive("foo")
c.Assert(active, Equals, false)
c.Assert(err, ErrorMatches, ".* failed with exit status 1: random-failure\n")
}
func makeMockMountUnit(c *C, mountDir string) string {
mountUnit := MountUnitPath(dirs.StripRootDir(mountDir))
err := os.WriteFile(mountUnit, nil, 0644)
c.Assert(err, IsNil)
return mountUnit
}
// FIXME: also test for the "IsMounted" case
func (s *SystemdTestSuite) TestRemoveMountUnit(c *C) {
rootDir := dirs.GlobalRootDir
restore := osutil.MockMountInfo("")
defer restore()
mountDir := rootDir + "/snap/foo/42"
mountUnit := makeMockMountUnit(c, mountDir)
err := NewUnderRoot(rootDir, SystemMode, nil).RemoveMountUnitFile(mountDir)
c.Assert(err, IsNil)
// the file is gone
c.Check(osutil.FileExists(mountUnit), Equals, false)
// and the unit is disabled and the daemon reloaded
c.Check(s.argses, DeepEquals, [][]string{
{"--root", rootDir, "disable", "snap-foo-42.mount"},
{"daemon-reload"},
})
}
func (s *SystemdTestSuite) TestDaemonReloadMutex(c *C) {
s.testDaemonOpWithMutex(c, Systemd.DaemonReload)
}
func (s *SystemdTestSuite) testDaemonOpWithMutex(c *C, testFunc func(Systemd) error) {
rootDir := dirs.GlobalRootDir
sysd := NewUnderRoot(rootDir, SystemMode, nil)
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
makeMockFile(c, mockSnapPath)
// create a go-routine that will try to daemon-reload like crazy
stopCh := make(chan bool, 1)
stoppedCh := make(chan bool, 1)
go func() {
for {
testFunc(sysd)
select {
case <-stopCh:
close(stoppedCh)
return
default:
//pass
}
}
}()
// And now add a mount unit file while the go-routine tries to
// daemon-reload. This will be serialized, if not this would
// panic because systemd.daemonReloadNoLock ensures the lock is
// taken when this happens.
_, err := sysd.EnsureMountUnitFile("42", mockSnapPath, "/snap/foo/42", "squashfs", systemd.EnsureMountUnitFlags{})
c.Assert(err, IsNil)
close(stopCh)
<-stoppedCh
}
func (s *SystemdTestSuite) TestDaemonReexecMutex(c *C) {
s.testDaemonOpWithMutex(c, Systemd.DaemonReexec)
}
func (s *SystemdTestSuite) TestUserMode(c *C) {
rootDir := dirs.GlobalRootDir
sysd := NewUnderRoot(rootDir, UserMode, nil)
c.Assert(sysd.EnableNoReload([]string{"foo"}), IsNil)
c.Check(s.argses[0], DeepEquals, []string{"--user", "--root", rootDir, "enable", "foo"})
c.Assert(sysd.Start([]string{"foo"}), IsNil)
c.Check(s.argses[1], DeepEquals, []string{"--user", "start", "foo"})
}
func (s *SystemdTestSuite) TestGlobalUserMode(c *C) {
rootDir := dirs.GlobalRootDir
sysd := NewUnderRoot(rootDir, GlobalUserMode, nil)
c.Assert(sysd.EnableNoReload([]string{"foo"}), IsNil)
c.Check(s.argses[0], DeepEquals, []string{"--user", "--global", "--root", rootDir, "enable", "foo"})
c.Assert(sysd.DisableNoReload([]string{"foo"}), IsNil)
c.Check(s.argses[1], DeepEquals, []string{"--user", "--global", "--root", rootDir, "disable", "foo"})
c.Assert(sysd.Mask("foo"), IsNil)
c.Check(s.argses[2], DeepEquals, []string{"--user", "--global", "--root", rootDir, "mask", "foo"})
c.Assert(sysd.Unmask("foo"), IsNil)
c.Check(s.argses[3], DeepEquals, []string{"--user", "--global", "--root", rootDir, "unmask", "foo"})
_, err := sysd.IsEnabled("foo")
c.Check(err, IsNil)
c.Check(s.argses[4], DeepEquals, []string{"--user", "--global", "--root", rootDir, "is-enabled", "foo"})
// Commands that don't make sense for GlobalUserMode panic
c.Check(sysd.DaemonReload, Panics, "cannot call daemon-reload with GlobalUserMode")
c.Check(sysd.DaemonReexec, Panics, "cannot call daemon-reexec with GlobalUserMode")
c.Check(func() { sysd.Start([]string{"foo"}) }, Panics, "cannot call start with GlobalUserMode")
c.Check(func() { sysd.StartNoBlock([]string{"foo"}) }, Panics, "cannot call start with GlobalUserMode")
c.Check(func() { sysd.Stop([]string{"foo"}) }, Panics, "cannot call stop with GlobalUserMode")
c.Check(func() { sysd.Restart([]string{"foo"}) }, Panics, "cannot call restart with GlobalUserMode")
c.Check(func() { sysd.Kill("foo", "HUP", "") }, Panics, "cannot call kill with GlobalUserMode")
c.Check(func() { sysd.IsActive("foo") }, Panics, "cannot call is-active with GlobalUserMode")
}
func (s *SystemdTestSuite) TestStatusGlobalUserMode(c *C) {
output := []byte("enabled\ndisabled\nstatic\n")
sysdErr := &Error{}
sysdErr.SetExitCode(1)
sysdErr.SetMsg(output)
s.outs = [][]byte{output, nil, output}
s.errors = []error{nil, sysdErr, nil}
rootDir := dirs.GlobalRootDir
sysd := NewUnderRoot(rootDir, GlobalUserMode, nil)
sts, err := sysd.Status([]string{"foo", "bar", "baz"})
c.Check(err, IsNil)
c.Check(sts, DeepEquals, []*UnitStatus{
{Name: "foo", Enabled: true},
{Name: "bar", Enabled: false},
{Name: "baz", Enabled: true},
})
c.Check(s.argses[0], DeepEquals, []string{"--user", "--global", "--root", rootDir, "is-enabled", "foo", "bar", "baz"})
// Output is collected if systemctl has a non-zero exit status
sts, err = sysd.Status([]string{"one", "two", "three"})
c.Check(err, IsNil)
c.Check(sts, DeepEquals, []*UnitStatus{
{Name: "one", Enabled: true},
{Name: "two", Enabled: false},
{Name: "three", Enabled: true},
})
c.Check(s.argses[1], DeepEquals, []string{"--user", "--global", "--root", rootDir, "is-enabled", "one", "two", "three"})
// An error is returned if the wrong number of statuses are returned
sts, err = sysd.Status([]string{"one"})
c.Check(err, ErrorMatches, "cannot get enabled status of services: expected 1 results, got 3")
c.Check(sts, IsNil)
c.Check(s.argses[2], DeepEquals, []string{"--user", "--global", "--root", rootDir, "is-enabled", "one"})
}
func (s *SystemdTestSuite) TestEmulationModeBackend(c *C) {
sysd := NewEmulationMode(dirs.GlobalRootDir)
c.Check(sysd.Backend(), Equals, EmulationModeBackend)
}
const unitTemplate = `
[Unit]
Description=Mount unit for foo, revision 42
After=snapd.mounts-pre.target
Before=snapd.mounts.target
[Mount]
What=%s
Where=/snap/snapname/123
Type=%s
Options=%s
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
`
func (s *SystemdTestSuite) TestPreseedModeAddMountUnit(c *C) {
sysd := NewEmulationMode(dirs.GlobalRootDir)
restore := squashfs.MockNeedsFuse(false)
defer restore()
mockMountCmd := testutil.MockCommand(c, "mount", "")
defer mockMountCmd.Restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
makeMockFile(c, mockSnapPath)
mountUnitName, err := sysd.EnsureMountUnitFile("Mount unit for foo, revision 42", mockSnapPath, "/snap/snapname/123", "squashfs", systemd.EnsureMountUnitFlags{})
c.Assert(err, IsNil)
defer os.Remove(mountUnitName)
c.Check(s.argses, DeepEquals, [][]string{{"--root", dirs.GlobalRootDir, "enable", "snap-snapname-123.mount"}})
// mount was called
c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "squashfs", mockSnapPath, "/snap/snapname/123", "-o", "nodev,ro,x-gdu.hide,x-gvfs-hide"})
c.Check(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(unitTemplate[1:], mockSnapPath, "squashfs", "nodev,ro,x-gdu.hide,x-gvfs-hide"))
}
func (s *SystemdTestSuite) TestPreseedModeAddMountUnitUnchanged(c *C) {
sysd := NewEmulationMode(dirs.GlobalRootDir)
restore := squashfs.MockNeedsFuse(false)
defer restore()
mockMountCmd := testutil.MockCommand(c, "mount", "")
defer mockMountCmd.Restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
makeMockFile(c, mockSnapPath)
err := os.MkdirAll(dirs.SnapServicesDir, 0755)
c.Assert(err, IsNil)
content := fmt.Sprintf(`
[Unit]
Description=Mount unit for foo, revision 42
After=snapd.mounts-pre.target
Before=snapd.mounts.target
[Mount]
What=%s
Where=/snap/snapname/123
Type=squashfs
Options=nodev,ro,x-gdu.hide,x-gvfs-hide
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
`[1:], mockSnapPath)
err = os.WriteFile(filepath.Join(dirs.SnapServicesDir, "snap-snapname-123.mount"), []byte(content), 0644)
c.Assert(err, IsNil)
_, err = sysd.EnsureMountUnitFile("Mount unit for foo, revision 42", mockSnapPath, "/snap/snapname/123", "squashfs", systemd.EnsureMountUnitFlags{})
c.Assert(err, IsNil)
// systemd was not called
c.Check(s.argses, HasLen, 0)
// no mount was called
c.Check(mockMountCmd.Calls(), HasLen, 0)
}
func (s *SystemdTestSuite) TestPreseedModeAddMountUniModified(c *C) {
sysd := NewEmulationMode(dirs.GlobalRootDir)
restore := squashfs.MockNeedsFuse(false)
defer restore()
mockMountCmd := testutil.MockCommand(c, "mount", "")
defer mockMountCmd.Restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
makeMockFile(c, mockSnapPath)
err := os.MkdirAll(dirs.SnapServicesDir, 0755)
c.Assert(err, IsNil)
// Note the "anotheroption"
content := fmt.Sprintf(`
[Unit]
Description=Mount unit for foo, revision 42
After=snapd.mounts-pre.target
Before=snapd.mounts.target
[Mount]
What=%s
Where=/snap/snapname/123
Type=squashfs
Options=nodev,ro,x-gdu.hide,x-gvfs-hide,anotheroption
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
`[1:], mockSnapPath)
err = os.WriteFile(filepath.Join(dirs.SnapServicesDir, "snap-snapname-123.mount"), []byte(content), 0644)
c.Assert(err, IsNil)
mountUnitName, err := sysd.EnsureMountUnitFile("Mount unit for foo, revision 42", mockSnapPath, "/snap/snapname/123", "squashfs", systemd.EnsureMountUnitFlags{})
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"--root", dirs.GlobalRootDir, "enable", "snap-snapname-123.mount"}})
// mount was called with remount option
c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "squashfs", mockSnapPath, "/snap/snapname/123", "-o", "nodev,ro,x-gdu.hide,x-gvfs-hide,remount"})
c.Check(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(unitTemplate[1:], mockSnapPath, "squashfs", "nodev,ro,x-gdu.hide,x-gvfs-hide"))
}
func (s *SystemdTestSuite) TestPreseedModeAddMountUnitWithFuse(c *C) {
sysd := NewEmulationMode(dirs.GlobalRootDir)
restore := MockSquashFsType(func() (string, []string) { return "fuse.squashfuse", []string{"a,b,c"} })
defer restore()
mockMountCmd := testutil.MockCommand(c, "mount", "")
defer mockMountCmd.Restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
makeMockFile(c, mockSnapPath)
mountUnitName, err := sysd.EnsureMountUnitFile("Mount unit for foo, revision 42", mockSnapPath, "/snap/snapname/123", "squashfs", systemd.EnsureMountUnitFlags{})
c.Assert(err, IsNil)
defer os.Remove(mountUnitName)
c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "fuse.squashfuse", mockSnapPath, "/snap/snapname/123", "-o", "nodev,a,b,c"})
c.Check(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(unitTemplate[1:], mockSnapPath, "squashfs", "nodev,ro,x-gdu.hide,x-gvfs-hide"))
}
func (s *SystemdTestSuite) TestPreseedModeAddMountUnitWithOptions(c *C) {
sysd := NewEmulationMode(dirs.GlobalRootDir)
restore := MockSquashFsType(func() (string, []string) { return "fuse.squashfuse", []string{"a,b,c"} })
defer restore()
mockMountCmd := testutil.MockCommand(c, "mount", "")
defer mockMountCmd.Restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/pc-kernel_1.0.snap")
makeMockFile(c, mockSnapPath)
mountUnitOptions := &systemd.MountUnitOptions{
MountUnitType: systemd.BeforeDriversLoadMountUnit,
Lifetime: systemd.Persistent,
Description: "Early mount unit for kernel snap",
What: mockSnapPath,
Where: "/run/mnt/kernel-snaps/pc-kernel/1",
Fstype: "squashfs",
Options: []string{"nodev,ro,x-gdu.hide,x-gvfs-hide"},
}
mountUnitName, err := sysd.EnsureMountUnitFileWithOptions(mountUnitOptions)
c.Assert(err, IsNil)
defer os.Remove(mountUnitName)
c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "fuse.squashfuse", mockSnapPath, "/run/mnt/kernel-snaps/pc-kernel/1", "-o", "nodev,a,b,c"})
where := filepath.Join(dirs.RunDir, "mnt/kernel-snaps/pc-kernel/1")
c.Check(osutil.IsDirectory(where), Equals, true)
c.Check(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals,
fmt.Sprintf(`[Unit]
Description=Early mount unit for kernel snap
After=snapd.mounts-pre.target
Before=snapd.mounts.target
Before=systemd-udevd.service systemd-modules-load.service
[Mount]
What=%s
Where=/run/mnt/kernel-snaps/pc-kernel/1
Type=squashfs
Options=nodev,ro,x-gdu.hide,x-gvfs-hide
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
`, mockSnapPath))
}
func (s *SystemdTestSuite) TestPreseedModeMountError(c *C) {
sysd := NewEmulationMode(dirs.GlobalRootDir)
restore := squashfs.MockNeedsFuse(false)
defer restore()
mockMountCmd := testutil.MockCommand(c, "mount", `echo "some failure"; exit 1`)
defer mockMountCmd.Restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
makeMockFile(c, mockSnapPath)
_, err := sysd.EnsureMountUnitFile("42", mockSnapPath, "/snap/snapname/123", "squashfs", systemd.EnsureMountUnitFlags{})
c.Assert(err, ErrorMatches, `cannot mount .*/var/lib/snappy/snaps/foo_1.0.snap \(squashfs\) at /snap/snapname/123 in preseed mode: exit status 1; some failure\n`)
}
func (s *SystemdTestSuite) TestPreseedModeRemoveMountUnit(c *C) {
mountDir := dirs.GlobalRootDir + "/snap/foo/42"
restore := MockOsutilIsMounted(func(path string) (bool, error) {
c.Check(path, Equals, mountDir)
return true, nil
})
defer restore()
mockUmountCmd := testutil.MockCommand(c, "umount", "")
defer mockUmountCmd.Restore()
sysd := NewEmulationMode(dirs.GlobalRootDir)
mountUnit := makeMockMountUnit(c, mountDir)
c.Assert(sysd.RemoveMountUnitFile(mountDir), IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"--root", dirs.GlobalRootDir, "disable", "snap-foo-42.mount"}})
// the file is gone
c.Check(osutil.FileExists(mountUnit), Equals, false)
// umount was called
c.Check(mockUmountCmd.Calls(), DeepEquals, [][]string{{"umount", "-d", "-l", mountDir}})
}
func (s *SystemdTestSuite) TestPreseedModeRemoveMountUnitUnmounted(c *C) {
mountDir := dirs.GlobalRootDir + "/snap/foo/42"
restore := MockOsutilIsMounted(func(path string) (bool, error) {
c.Check(path, Equals, mountDir)
return false, nil
})
defer restore()
mockUmountCmd := testutil.MockCommand(c, "umount", "")
defer mockUmountCmd.Restore()
sysd := NewEmulationMode(dirs.GlobalRootDir)
mountUnit := makeMockMountUnit(c, mountDir)
symlinkPath := filepath.Join(dirs.SnapServicesDir, "snapd.mounts.target.wants", filepath.Base(mountUnit))
c.Assert(os.Symlink(mountUnit, symlinkPath), IsNil)
// This is only to handle older version of snapd
oldSymlinkPath := filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants", filepath.Base(mountUnit))
c.Assert(os.Symlink(mountUnit, oldSymlinkPath), IsNil)
c.Assert(sysd.RemoveMountUnitFile(mountDir), IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"--root", dirs.GlobalRootDir, "disable", "snap-foo-42.mount"}})
// umount was not called
c.Check(mockUmountCmd.Calls(), HasLen, 0)
}
func (s *SystemdTestSuite) TestPreseedModeBindmountNotSupported(c *C) {
sysd := NewEmulationMode(dirs.GlobalRootDir)
restore := squashfs.MockNeedsFuse(false)
defer restore()
mockSnapPath := c.MkDir()
_, err := sysd.EnsureMountUnitFile("42", mockSnapPath, "/snap/snapname/123", "", systemd.EnsureMountUnitFlags{})
c.Assert(err, ErrorMatches, `bind-mounted directory is not supported in emulation mode`)
}
func (s *SystemdTestSuite) TestEnableInEmulationMode(c *C) {
sysd := NewEmulationMode("/path")
c.Assert(sysd.EnableNoReload([]string{"foo"}), IsNil)
sysd = NewEmulationMode("")
c.Assert(sysd.EnableNoReload([]string{"bar"}), IsNil)
c.Check(s.argses, DeepEquals, [][]string{
{"--root", "/path", "enable", "foo"},
{"--root", dirs.GlobalRootDir, "enable", "bar"}})
}
func (s *SystemdTestSuite) TestDisableInEmulationMode(c *C) {
sysd := NewEmulationMode("/path")
c.Assert(sysd.DisableNoReload([]string{"foo"}), IsNil)
c.Check(s.argses, DeepEquals, [][]string{
{"--root", "/path", "disable", "foo"}})
}
func (s *SystemdTestSuite) TestMaskInEmulationMode(c *C) {
sysd := NewEmulationMode("/path")
c.Assert(sysd.Mask("foo"), IsNil)
c.Check(s.argses, DeepEquals, [][]string{
{"--root", "/path", "mask", "foo"}})
}
func (s *SystemdTestSuite) TestUnmaskInEmulationMode(c *C) {
sysd := NewEmulationMode("/path")
c.Assert(sysd.Unmask("foo"), IsNil)
c.Check(s.argses, DeepEquals, [][]string{
{"--root", "/path", "unmask", "foo"}})
}
func (s *SystemdTestSuite) TestListMountUnitsEmpty(c *C) {
s.outs = [][]byte{
[]byte("\n"),
}
sysd := New(SystemMode, nil)
units, err := sysd.ListMountUnits("some-snap", "")
c.Check(units, HasLen, 0)
c.Check(err, IsNil)
}
func (s *SystemdTestSuite) TestListMountUnitsMalformed(c *C) {
s.outs = [][]byte{
[]byte(`Description=Mount unit for some-snap, revision x1
Where=/somewhere/here
FragmentPath=/etc/systemd/system/somewhere-here.mount
HereIsOneLineWithoutAnEqualSign
`),
}
sysd := New(SystemMode, nil)
units, err := sysd.ListMountUnits("some-snap", "")
c.Check(units, HasLen, 0)
c.Check(err, ErrorMatches, "cannot parse systemctl output:.*")
}
func (s *SystemdTestSuite) TestListMountUnitsHappy(c *C) {
tmpDir, err := os.MkdirTemp("/tmp", "snapd-systemd-test-list-mounts-*")
c.Assert(err, IsNil)
defer os.RemoveAll(tmpDir)
var systemctlOutput string
createFakeUnit := func(fileName, snapName, where, origin string) error {
path := filepath.Join(tmpDir, fileName)
if len(systemctlOutput) > 0 {
systemctlOutput += "\n\n"
}
systemctlOutput += fmt.Sprintf(`Description=Mount unit for %s, revision x1
Where=%s
FragmentPath=%s
`, snapName, where, path)
contents := fmt.Sprintf(`[Unit]
Description=Mount unit for %s, revision x1
[Mount]
What=/does/not/matter
Where=%s
Type=doesntmatter
Options=do,not,matter,either
[Install]
WantedBy=doesntmatter.target
X-SnapdOrigin=%s
`, snapName, where, origin)
return os.WriteFile(path, []byte(contents), 0644)
}
// Prepare the unit files
err = createFakeUnit("somepath-somedir.mount", "some-snap", "/somepath/somedir", "module1")
c.Assert(err, IsNil)
err = createFakeUnit("somewhere-here.mount", "some-other-snap", "/somewhere/here", "module2")
c.Assert(err, IsNil)
err = createFakeUnit("somewhere-there.mount", "some-snap", "/somewhere/there", "module3")
c.Assert(err, IsNil)
s.outs = [][]byte{
[]byte(systemctlOutput),
}
sysd := New(SystemMode, nil)
// First, get all mount units for some-snap, without filter on the origin module
units, err := sysd.ListMountUnits("some-snap", "")
c.Check(units, DeepEquals, []string{"/somepath/somedir", "/somewhere/there"})
c.Check(err, IsNil)
// Now repeat the same, filtering on the origin module
s.i = 0 // this resets the systemctl output iterator back to the beginning
units, err = sysd.ListMountUnits("some-snap", "module3")
c.Check(units, DeepEquals, []string{"/somewhere/there"})
c.Check(err, IsNil)
}
func (s *SystemdTestSuite) TestMountHappy(c *C) {
sysd := New(SystemMode, nil)
cmd := testutil.MockCommand(c, "systemd-mount", "")
defer cmd.Restore()
c.Assert(sysd.Mount("foo", "bar"), IsNil)
c.Check(cmd.Calls(), DeepEquals, [][]string{
{"systemd-mount", "foo", "bar"},
})
cmd.ForgetCalls()
c.Assert(sysd.Mount("foo", "bar", "-o", "bind"), IsNil)
c.Check(cmd.Calls(), DeepEquals, [][]string{
{"systemd-mount", "-o", "bind", "foo", "bar"},
})
}
func (s *SystemdTestSuite) TestMountErr(c *C) {
sysd := New(SystemMode, nil)
cmd := testutil.MockCommand(c, "systemd-mount", `echo "failed"; exit 111`)
defer cmd.Restore()
err := sysd.Mount("foo", "bar")
c.Assert(err, ErrorMatches, "failed")
c.Check(cmd.Calls(), DeepEquals, [][]string{
{"systemd-mount", "foo", "bar"},
})
}
func (s *SystemdTestSuite) TestUmountHappy(c *C) {
sysd := New(SystemMode, nil)
cmd := testutil.MockCommand(c, "systemd-mount", "")
defer cmd.Restore()
c.Assert(sysd.Umount("bar"), IsNil)
c.Check(cmd.Calls(), DeepEquals, [][]string{
{"systemd-mount", "--umount", "bar"},
})
}
func (s *SystemdTestSuite) TestUmountErr(c *C) {
sysd := New(SystemMode, nil)
cmd := testutil.MockCommand(c, "systemd-mount", `echo "failed"; exit 111`)
defer cmd.Restore()
err := sysd.Umount("bar")
c.Assert(err, ErrorMatches, "failed")
c.Check(cmd.Calls(), DeepEquals, [][]string{
{"systemd-mount", "--umount", "bar"},
})
}
func (s *SystemdTestSuite) TestCurrentUsageFamilyReallyInvalid(c *C) {
s.outs = [][]byte{
[]byte(`gahstringsarehard`),
[]byte(`gahstringsarehard`),
}
sysd := New(SystemMode, s.rep)
_, err := sysd.CurrentMemoryUsage("bar.service")
c.Assert(err, ErrorMatches, `invalid property format from systemd for MemoryCurrent \(got gahstringsarehard\)`)
_, err = sysd.CurrentTasksCount("bar.service")
c.Assert(err, ErrorMatches, `invalid property format from systemd for TasksCurrent \(got gahstringsarehard\)`)
c.Check(s.argses, DeepEquals, [][]string{
{"show", "--property", "MemoryCurrent", "bar.service"},
{"show", "--property", "TasksCurrent", "bar.service"},
})
}
func (s *SystemdTestSuite) TestCurrentUsageFamilyInactive(c *C) {
s.outs = [][]byte{
[]byte(`MemoryCurrent=[not set]`),
[]byte(`TasksCurrent=[not set]`),
}
sysd := New(SystemMode, s.rep)
_, err := sysd.CurrentMemoryUsage("bar.service")
c.Assert(err, ErrorMatches, "memory usage unavailable")
_, err = sysd.CurrentTasksCount("bar.service")
c.Assert(err, ErrorMatches, "tasks count unavailable")
c.Check(s.argses, DeepEquals, [][]string{
{"show", "--property", "MemoryCurrent", "bar.service"},
{"show", "--property", "TasksCurrent", "bar.service"},
})
}
func (s *SystemdTestSuite) TestCurrentUsageFamilyInvalid(c *C) {
s.outs = [][]byte{
[]byte(`MemoryCurrent=blahhhhhhhhhhhhhh`),
[]byte(`TasksCurrent=blahhhhhhhhhhhhhh`),
}
sysd := New(SystemMode, s.rep)
_, err := sysd.CurrentMemoryUsage("bar.service")
c.Assert(err, ErrorMatches, `invalid property value from systemd for MemoryCurrent: cannot parse "blahhhhhhhhhhhhhh" as an integer`)
_, err = sysd.CurrentTasksCount("bar.service")
c.Assert(err, ErrorMatches, `invalid property value from systemd for TasksCurrent: cannot parse "blahhhhhhhhhhhhhh" as an integer`)
c.Check(s.argses, DeepEquals, [][]string{
{"show", "--property", "MemoryCurrent", "bar.service"},
{"show", "--property", "TasksCurrent", "bar.service"},
})
}
func (s *SystemdTestSuite) TestCurrentUsageFamilyHappy(c *C) {
s.outs = [][]byte{
[]byte(`MemoryCurrent=1024`),
[]byte(`MemoryCurrent=18446744073709551615`), // special value from systemd bug
[]byte(`TasksCurrent=10`),
}
sysd := New(SystemMode, s.rep)
memUsage, err := sysd.CurrentMemoryUsage("bar.service")
c.Assert(err, IsNil)
c.Assert(memUsage, Equals, quantity.SizeKiB)
memUsage, err = sysd.CurrentMemoryUsage("bar.service")
c.Assert(err, IsNil)
const sixteenExb = quantity.Size(1<<64 - 1)
c.Assert(memUsage, Equals, sixteenExb)
tasksUsage, err := sysd.CurrentTasksCount("bar.service")
c.Assert(tasksUsage, Equals, uint64(10))
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{
{"show", "--property", "MemoryCurrent", "bar.service"},
{"show", "--property", "MemoryCurrent", "bar.service"},
{"show", "--property", "TasksCurrent", "bar.service"},
})
}
func (s *SystemdTestSuite) TestInactiveEnterTimestampZero(c *C) {
s.outs = [][]byte{
[]byte(`InactiveEnterTimestamp=`),
}
sysd := New(SystemMode, s.rep)
stamp, err := sysd.InactiveEnterTimestamp("bar.service")
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{
{"show", "--property", "InactiveEnterTimestamp", "bar.service"},
})
c.Check(stamp.IsZero(), Equals, true)
}
func (s *SystemdTestSuite) TestInactiveEnterTimestampValidWhitespace(c *C) {
s.outs = [][]byte{
[]byte(`InactiveEnterTimestamp=Fri 2021-04-16 15:32:21 UTC
`),
}
stamp, err := New(SystemMode, s.rep).InactiveEnterTimestamp("bar.service")
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{
{"show", "--property", "InactiveEnterTimestamp", "bar.service"},
})
c.Check(stamp.Equal(time.Date(2021, time.April, 16, 15, 32, 21, 0, time.UTC)), Equals, true)
}
func (s *SystemdTestSuite) TestInactiveEnterTimestampValid(c *C) {
s.outs = [][]byte{
[]byte(`InactiveEnterTimestamp=Fri 2021-04-16 15:32:21 UTC`),
}
stamp, err := New(SystemMode, s.rep).InactiveEnterTimestamp("bar.service")
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{
{"show", "--property", "InactiveEnterTimestamp", "bar.service"},
})
c.Check(stamp.Equal(time.Date(2021, time.April, 16, 15, 32, 21, 0, time.UTC)), Equals, true)
}
func (s *SystemdTestSuite) TestInactiveEnterTimestampFailure(c *C) {
s.outs = [][]byte{
[]byte(`mocked failure`),
}
s.errors = []error{
fmt.Errorf("mocked failure"),
}
stamp, err := New(SystemMode, s.rep).InactiveEnterTimestamp("bar.service")
c.Assert(err, ErrorMatches, "mocked failure")
c.Check(stamp.IsZero(), Equals, true)
}
func (s *SystemdTestSuite) TestInactiveEnterTimestampMalformed(c *C) {
s.outs = [][]byte{
[]byte(`InactiveEnterTimestamp`),
[]byte(``),
[]byte(`some random garbage
with newlines`),
}
sysd := New(SystemMode, s.rep)
for i := 0; i < len(s.outs); i++ {
s.argses = nil
stamp, err := sysd.InactiveEnterTimestamp("bar.service")
c.Assert(err.Error(), testutil.Contains, `invalid property format from systemd for InactiveEnterTimestamp (got`)
c.Check(s.argses, DeepEquals, [][]string{
{"show", "--property", "InactiveEnterTimestamp", "bar.service"},
})
c.Check(stamp.IsZero(), Equals, true)
}
}
func (s *SystemdTestSuite) TestInactiveEnterTimestampMalformedMore(c *C) {
s.outs = [][]byte{
[]byte(`InactiveEnterTimestamp=0`), // 0 is valid for InactiveEnterTimestampMonotonic
}
sysd := New(SystemMode, s.rep)
stamp, err := sysd.InactiveEnterTimestamp("bar.service")
c.Assert(err, ErrorMatches, `internal error: systemctl time output \(0\) is malformed`)
c.Check(s.argses, DeepEquals, [][]string{
{"show", "--property", "InactiveEnterTimestamp", "bar.service"},
})
c.Check(stamp.IsZero(), Equals, true)
}
func (s *SystemdTestSuite) TestSystemdRunError(c *C) {
sr := testutil.MockCommand(c, "systemd-run", `echo "fail"; exit 11`)
defer sr.Restore()
sysd := New(SystemMode, s.rep)
output, err := sysd.Run([]string{"bad-cmd", "arg1"}, nil)
c.Check(output, IsNil)
c.Assert(err, ErrorMatches, `cannot run \["bad-cmd" "arg1"\]: fail`)
}
func (s *SystemdTestSuite) TestSystemdRunHappyNoStderr(c *C) {
sr := testutil.MockCommand(c, "systemd-run", `echo "happy output" && >&2 echo "to stderr"`)
defer sr.Restore()
sysd := New(SystemMode, s.rep)
output, err := sysd.Run([]string{"happy-cmd", "arg1"}, nil)
c.Check(string(output), Equals, "happy output\n")
c.Check(err, IsNil)
c.Check(sr.Calls(), DeepEquals, [][]string{
{"systemd-run", "--wait", "--pipe", "--collect", "--service-type=exec", "--quiet", "--", "happy-cmd", "arg1"},
})
}
func (s *SystemdTestSuite) TestSystemdRunHappyWithStdin(c *C) {
sr := testutil.MockCommand(c, "systemd-run", `echo "some output" && cat - `)
defer sr.Restore()
sysd := New(SystemMode, s.rep)
opts := &RunOptions{Stdin: bytes.NewBufferString("stdin input\n")}
output, err := sysd.Run([]string{"cmd-with-stdin", "arg1"}, opts)
c.Check(string(output), Equals, "some output\nstdin input\n")
c.Check(err, IsNil)
c.Check(sr.Calls(), DeepEquals, [][]string{
{"systemd-run", "--wait", "--pipe", "--collect", "--service-type=exec", "--quiet", "--", "cmd-with-stdin", "arg1"},
})
}
func (s *SystemdTestSuite) TestSystemdRunKeyringMode(c *C) {
sr := testutil.MockCommand(c, "systemd-run", `echo "happy output"`)
defer sr.Restore()
sysd := New(SystemMode, s.rep)
opts := &RunOptions{KeyringMode: KeyringModePrivate}
output, err := sysd.Run([]string{"happy-cmd", "arg1"}, opts)
c.Check(string(output), Equals, "happy output\n")
c.Check(err, IsNil)
c.Check(sr.Calls(), DeepEquals, [][]string{
{"systemd-run", "--wait", "--pipe", "--collect", "--service-type=exec", "--quiet", "--property=KeyringMode=private", "--", "happy-cmd", "arg1"},
})
}
type systemdErrorSuite struct{}
var _ = Suite(&systemdErrorSuite{})
func (s *systemdErrorSuite) TestErrorStringNormalError(c *C) {
systemctl := testutil.MockCommand(c, "systemctl", `echo "I fail"; exit 11`)
defer systemctl.Restore()
_, err := Version()
c.Check(err, ErrorMatches, `systemctl command \[--version\] failed with exit status 11: I fail`)
}
func (s *systemdErrorSuite) TestErrorStringNoOutput(c *C) {
systemctl := testutil.MockCommand(c, "systemctl", `exit 22`)
defer systemctl.Restore()
_, err := Version()
c.Check(err, ErrorMatches, `systemctl command \[--version\] failed with exit status 22`)
}
func (s *systemdErrorSuite) TestErrorStringNoSystemctl(c *C) {
oldPath := os.Getenv("PATH")
os.Setenv("PATH", "/xxx")
defer func() { os.Setenv("PATH", oldPath) }()
_, err := Version()
c.Check(err, ErrorMatches, `systemctl command \[--version\] failed with: exec: "systemctl": executable file not found in \$PATH`)
}
func (s *systemdErrorSuite) TestEnsureMountUnitFileEnsureFileStateErr(c *C) {
dirs.SetRootDir(c.MkDir())
defer dirs.SetRootDir("")
restore := osutil.MockMountInfo("")
defer restore()
mockSnapPath := filepath.Join(dirs.GlobalRootDir, "/var/lib/snappy/snaps/foo_1.0.snap")
makeMockFile(c, mockSnapPath)
// trigger an error below by creating a directory with the
// same name as the mount unit file
err := os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap-snapname-123.mount"), 0755)
c.Assert(err, IsNil)
_, err = New(SystemMode, nil).EnsureMountUnitFile("42", mockSnapPath, "/snap/snapname/123", "squashfs", systemd.EnsureMountUnitFlags{})
c.Assert(err, ErrorMatches, fmt.Sprintf("internal error: only regular files are supported, got %q instead", os.ModeDir))
}