summaryrefslogtreecommitdiff
diff options
authorMichael Vogt <mvo@ubuntu.com>2016-12-14 07:56:58 +0100
committerMichael Vogt <mvo@ubuntu.com>2016-12-14 07:56:58 +0100
commitfd29123efe51bf9fff654409774503918a145fdd (patch)
treeb5a36735631ef48e9a4f914fcd87ad14b3fe2903
parent6fae7360738f0a65c0536ac971d5ce3d722f877a (diff)
parent9817ae246cb0d567549d65b5b9b29722348a0101 (diff)
Merge remote-tracking branch 'upstream/master' into bugfix/autopkgtest-downgradesbugfix/autopkgtest-downgrades
-rw-r--r--interfaces/apparmor/template.go2
-rw-r--r--interfaces/builtin/all.go3
-rw-r--r--interfaces/builtin/all_test.go2
-rw-r--r--interfaces/builtin/basedeclaration.go16
-rw-r--r--interfaces/builtin/basedeclaration_test.go4
-rw-r--r--interfaces/builtin/bootconfig.go39
-rw-r--r--interfaces/builtin/bootconfig_test.go74
-rw-r--r--interfaces/builtin/libvirt.go3
-rw-r--r--interfaces/builtin/openvswitch.go46
-rw-r--r--interfaces/builtin/openvswitch_support.go37
-rw-r--r--interfaces/builtin/openvswitch_support_test.go86
-rw-r--r--interfaces/builtin/openvswitch_test.go90
-rw-r--r--interfaces/builtin/process_control.go3
-rw-r--r--interfaces/systemd/backend.go10
-rw-r--r--interfaces/systemd/backend_test.go30
-rw-r--r--overlord/managers_test.go68
-rw-r--r--overlord/snapstate/aliases.go116
-rw-r--r--overlord/snapstate/aliases_test.go376
-rw-r--r--snap/implicit.go2
-rw-r--r--systemd/systemd.go14
-rw-r--r--systemd/systemd_test.go12
-rwxr-xr-xtests/lib/prepare.sh1
22 files changed, 909 insertions, 125 deletions
diff --git a/interfaces/apparmor/template.go b/interfaces/apparmor/template.go
index aaa1385d4e..f8c07714a8 100644
--- a/interfaces/apparmor/template.go
+++ b/interfaces/apparmor/template.go
@@ -152,6 +152,7 @@ var defaultTemplate = []byte(`
/{,usr/}bin/rev ixr,
/{,usr/}bin/rm ixr,
/{,usr/}bin/rmdir ixr,
+ /{,usr/}bin/run-parts ixr,
/{,usr/}bin/sed ixr,
/{,usr/}bin/seq ixr,
/{,usr/}bin/sha{1,224,256,384,512}sum ixr,
@@ -235,6 +236,7 @@ var defaultTemplate = []byte(`
# match until AppArmor kernel var is available to solve this properly (see
# LP: #1546825 for details)
owner @{PROC}/@{pid}/cmdline r,
+ owner @{PROC}/@{pid}/comm r,
# Miscellaneous accesses
/dev/{,u}random w,
diff --git a/interfaces/builtin/all.go b/interfaces/builtin/all.go
index 4563064fe4..d74c2fc5d7 100644
--- a/interfaces/builtin/all.go
+++ b/interfaces/builtin/all.go
@@ -50,6 +50,7 @@ var allInterfaces = []interfaces.Interface{
NewAlsaInterface(),
NewAvahiObserveInterface(),
NewBluetoothControlInterface(),
+ NewBootConfigInterface(),
NewCameraInterface(),
NewCupsControlInterface(),
NewDcdbasControlInterface(),
@@ -69,6 +70,8 @@ var allInterfaces = []interfaces.Interface{
NewNetworkObserveInterface(),
NewNetworkSetupObserveInterface(),
NewOpenglInterface(),
+ NewOpenvSwitchInterface(),
+ NewOpenvSwitchSupportInterface(),
NewOpticalDriveInterface(),
NewProcessControlInterface(),
NewRawUsbInterface(),
diff --git a/interfaces/builtin/all_test.go b/interfaces/builtin/all_test.go
index 4411ec5c7d..53b6733c09 100644
--- a/interfaces/builtin/all_test.go
+++ b/interfaces/builtin/all_test.go
@@ -68,6 +68,8 @@ func (s *AllSuite) TestInterfaces(c *C) {
c.Check(all, DeepContains, builtin.NewNetworkInterface())
c.Check(all, DeepContains, builtin.NewNetworkObserveInterface())
c.Check(all, DeepContains, builtin.NewOpenglInterface())
+ c.Check(all, DeepContains, builtin.NewOpenvSwitchInterface())
+ c.Check(all, DeepContains, builtin.NewOpenvSwitchSupportInterface())
c.Check(all, DeepContains, builtin.NewOpticalDriveInterface())
c.Check(all, DeepContains, builtin.NewProcessControlInterface())
c.Check(all, DeepContains, builtin.NewRawUsbInterface())
diff --git a/interfaces/builtin/basedeclaration.go b/interfaces/builtin/basedeclaration.go
index 02abc20cf0..6823757e13 100644
--- a/interfaces/builtin/basedeclaration.go
+++ b/interfaces/builtin/basedeclaration.go
@@ -178,6 +178,12 @@ slots:
- core
- gadget
deny-auto-connection: true
+ boot-config:
+ allow-installation:
+ slot-snap-type:
+ - gadget
+ deny-connection: true
+ deny-auto-connection: true
browser-support:
allow-installation:
slot-snap-type:
@@ -376,6 +382,16 @@ slots:
allow-installation:
slot-snap-type:
- core
+ openvswitch:
+ allow-installation:
+ slot-snap-type:
+ - core
+ deny-auto-connection: true
+ openvswitch-support:
+ allow-installation:
+ slot-snap-type:
+ - core
+ deny-auto-connection: true
optical-drive:
allow-installation:
slot-snap-type:
diff --git a/interfaces/builtin/basedeclaration_test.go b/interfaces/builtin/basedeclaration_test.go
index 675cfc71c0..3bc6694eb6 100644
--- a/interfaces/builtin/basedeclaration_test.go
+++ b/interfaces/builtin/basedeclaration_test.go
@@ -155,7 +155,7 @@ func (s *baseDeclSuite) TestAutoConnection(c *C) {
continue
}
expected := autoconnect[iface.Name()]
- comm := Commentf(iface.Name())
+ comm := Commentf("%s: %v", iface.Name(), expected)
// check base declaration
cand := s.connectCand(c, iface.Name(), "", "")
@@ -347,6 +347,7 @@ var (
// other
"bluez": {"app"},
"bool-file": {"core", "gadget"},
+ "boot-config": {"gadget"},
"browser-support": {"core"},
"content": {"app", "gadget"},
"docker-support": {"core"},
@@ -467,6 +468,7 @@ func (s *baseDeclSuite) TestConnection(c *C) {
// case-by-case basis
noconnect := map[string]bool{
"bluez": true,
+ "boot-config": true,
"docker": true,
"fwupd": true,
"location-control": true,
diff --git a/interfaces/builtin/bootconfig.go b/interfaces/builtin/bootconfig.go
new file mode 100644
index 0000000000..5b1c4dcecb
--- /dev/null
+++ b/interfaces/builtin/bootconfig.go
@@ -0,0 +1,39 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 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 builtin
+
+import (
+ "github.com/snapcore/snapd/interfaces"
+)
+
+const bootConfigConnectedPlugAppArmor = `
+# Description: Can access boot config files amd brick the system
+# Usage: reserved (very much so!)
+
+# Allow read/write access to the pi2 boot config.txt
+owner /boot/uboot/config.txt rwk,
+`
+
+func NewBootConfigInterface() interfaces.Interface {
+ return &commonInterface{
+ name: "boot-config",
+ connectedPlugAppArmor: bootConfigConnectedPlugAppArmor,
+ }
+}
diff --git a/interfaces/builtin/bootconfig_test.go b/interfaces/builtin/bootconfig_test.go
new file mode 100644
index 0000000000..190dcde624
--- /dev/null
+++ b/interfaces/builtin/bootconfig_test.go
@@ -0,0 +1,74 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 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 builtin_test
+
+import (
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/interfaces/builtin"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type bootConfigInterfaceSuite struct {
+ iface interfaces.Interface
+ slot *interfaces.Slot
+ plug *interfaces.Plug
+}
+
+var _ = Suite(&bootConfigInterfaceSuite{
+ iface: builtin.NewBootConfigInterface(),
+ slot: &interfaces.Slot{
+ SlotInfo: &snap.SlotInfo{
+ Snap: &snap.Info{SuggestedName: "pi2", Type: snap.TypeGadget},
+ Name: "boot-config",
+ Interface: "boot-config",
+ },
+ },
+ plug: &interfaces.Plug{
+ PlugInfo: &snap.PlugInfo{
+ Snap: &snap.Info{SuggestedName: "other"},
+ Name: "boot-config",
+ Interface: "boot-config",
+ },
+ },
+})
+
+func (s *bootConfigInterfaceSuite) TestName(c *C) {
+ c.Assert(s.iface.Name(), Equals, "boot-config")
+}
+
+func (s *bootConfigInterfaceSuite) TestUsedSecuritySystems(c *C) {
+ // connected plugs have a non-nil security snippet for apparmor
+ snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityAppArmor)
+ c.Assert(err, IsNil)
+ c.Assert(string(snippet), testutil.Contains, "/boot/uboot/config.txt")
+}
+
+func (s *bootConfigInterfaceSuite) TestSanitizeSlot(c *C) {
+ err := s.iface.SanitizeSlot(s.slot)
+ c.Assert(err, IsNil)
+}
+
+func (s *bootConfigInterfaceSuite) TestSanitizePlug(c *C) {
+ err := s.iface.SanitizePlug(s.plug)
+ c.Assert(err, IsNil)
+}
diff --git a/interfaces/builtin/libvirt.go b/interfaces/builtin/libvirt.go
index 93ae32d7bb..bcf7c0a05a 100644
--- a/interfaces/builtin/libvirt.go
+++ b/interfaces/builtin/libvirt.go
@@ -23,6 +23,7 @@ import "github.com/snapcore/snapd/interfaces"
const libvirtConnectedPlugAppArmor = `
/run/libvirt/libvirt-sock rw,
+/etc/libvirt/* r,
`
const libvirtConnectedPlugSecComp = `
@@ -35,6 +36,8 @@ sendto
sendmsg
socket
socketpair
+listen
+accept
`
func NewLibvirtInterface() interfaces.Interface {
diff --git a/interfaces/builtin/openvswitch.go b/interfaces/builtin/openvswitch.go
new file mode 100644
index 0000000000..847b70eb03
--- /dev/null
+++ b/interfaces/builtin/openvswitch.go
@@ -0,0 +1,46 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 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 builtin
+
+import "github.com/snapcore/snapd/interfaces"
+
+const openvswitchConnectedPlugAppArmor = `
+/run/openvswitch/db.sock rw,
+`
+
+const openvswitchConnectedPlugSecComp = `
+connect
+recv
+recvmsg
+send
+sendto
+sendmsg
+socket
+socketpair
+`
+
+func NewOpenvSwitchInterface() interfaces.Interface {
+ return &commonInterface{
+ name: "openvswitch",
+ connectedPlugAppArmor: openvswitchConnectedPlugAppArmor,
+ connectedPlugSecComp: openvswitchConnectedPlugSecComp,
+ reservedForOS: true,
+ }
+}
diff --git a/interfaces/builtin/openvswitch_support.go b/interfaces/builtin/openvswitch_support.go
new file mode 100644
index 0000000000..3d57ed623b
--- /dev/null
+++ b/interfaces/builtin/openvswitch_support.go
@@ -0,0 +1,37 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 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 builtin
+
+import (
+ "github.com/snapcore/snapd/interfaces"
+)
+
+const openvswitchSupportConnectedPlugKmod = `
+openvswitch
+`
+
+// NewOpenvSwitchSupportInterface returns a new "openvswitch-support" interface.
+func NewOpenvSwitchSupportInterface() interfaces.Interface {
+ return &commonInterface{
+ name: "openvswitch-support",
+ connectedPlugKMod: openvswitchSupportConnectedPlugKmod,
+ reservedForOS: true,
+ }
+}
diff --git a/interfaces/builtin/openvswitch_support_test.go b/interfaces/builtin/openvswitch_support_test.go
new file mode 100644
index 0000000000..3d5f502455
--- /dev/null
+++ b/interfaces/builtin/openvswitch_support_test.go
@@ -0,0 +1,86 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 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 builtin_test
+
+import (
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/interfaces/builtin"
+ "github.com/snapcore/snapd/snap"
+)
+
+type OpenvSwitchSupportInterfaceSuite struct {
+ iface interfaces.Interface
+ slot *interfaces.Slot
+ plug *interfaces.Plug
+}
+
+var _ = Suite(&OpenvSwitchSupportInterfaceSuite{
+ iface: builtin.NewOpenvSwitchSupportInterface(),
+ slot: &interfaces.Slot{
+ SlotInfo: &snap.SlotInfo{
+ Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS},
+ Name: "openvswitch-support",
+ Interface: "openvswitch-support",
+ },
+ },
+ plug: &interfaces.Plug{
+ PlugInfo: &snap.PlugInfo{
+ Snap: &snap.Info{SuggestedName: "other"},
+ Name: "openvswitch-support",
+ Interface: "openvswitch-support",
+ },
+ },
+})
+
+func (s *OpenvSwitchSupportInterfaceSuite) TestName(c *C) {
+ c.Assert(s.iface.Name(), Equals, "openvswitch-support")
+}
+
+func (s *OpenvSwitchSupportInterfaceSuite) TestSanitizeSlot(c *C) {
+ err := s.iface.SanitizeSlot(s.slot)
+ c.Assert(err, IsNil)
+ err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{
+ Snap: &snap.Info{SuggestedName: "some-snap"},
+ Name: "openvswitch-support",
+ Interface: "openvswitch-support",
+ }})
+ c.Assert(err, ErrorMatches, "openvswitch-support slots are reserved for the operating system snap")
+}
+
+func (s *OpenvSwitchSupportInterfaceSuite) TestSanitizePlug(c *C) {
+ err := s.iface.SanitizePlug(s.plug)
+ c.Assert(err, IsNil)
+}
+
+func (s *OpenvSwitchSupportInterfaceSuite) TestSanitizeIncorrectInterface(c *C) {
+ c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) },
+ PanicMatches, `slot is not of interface "openvswitch-support"`)
+ c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) },
+ PanicMatches, `plug is not of interface "openvswitch-support"`)
+}
+
+func (s *OpenvSwitchSupportInterfaceSuite) TestUsedSecuritySystems(c *C) {
+ // connected plugs have a non-nil security snippet for kmod
+ snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityKMod)
+ c.Assert(err, IsNil)
+ c.Assert(snippet, Not(IsNil))
+}
diff --git a/interfaces/builtin/openvswitch_test.go b/interfaces/builtin/openvswitch_test.go
new file mode 100644
index 0000000000..18ae434731
--- /dev/null
+++ b/interfaces/builtin/openvswitch_test.go
@@ -0,0 +1,90 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 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 builtin_test
+
+import (
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/interfaces/builtin"
+ "github.com/snapcore/snapd/snap"
+)
+
+type OpenvSwitchInterfaceSuite struct {
+ iface interfaces.Interface
+ slot *interfaces.Slot
+ plug *interfaces.Plug
+}
+
+var _ = Suite(&OpenvSwitchInterfaceSuite{
+ iface: builtin.NewOpenvSwitchInterface(),
+ slot: &interfaces.Slot{
+ SlotInfo: &snap.SlotInfo{
+ Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS},
+ Name: "openvswitch",
+ Interface: "openvswitch",
+ },
+ },
+ plug: &interfaces.Plug{
+ PlugInfo: &snap.PlugInfo{
+ Snap: &snap.Info{SuggestedName: "other"},
+ Name: "openvswitch",
+ Interface: "openvswitch",
+ },
+ },
+})
+
+func (s *OpenvSwitchInterfaceSuite) TestName(c *C) {
+ c.Assert(s.iface.Name(), Equals, "openvswitch")
+}
+
+func (s *OpenvSwitchInterfaceSuite) TestSanitizeSlot(c *C) {
+ err := s.iface.SanitizeSlot(s.slot)
+ c.Assert(err, IsNil)
+ err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{
+ Snap: &snap.Info{SuggestedName: "some-snap"},
+ Name: "openvswitch",
+ Interface: "openvswitch",
+ }})
+ c.Assert(err, ErrorMatches, "openvswitch slots are reserved for the operating system snap")
+}
+
+func (s *OpenvSwitchInterfaceSuite) TestSanitizePlug(c *C) {
+ err := s.iface.SanitizePlug(s.plug)
+ c.Assert(err, IsNil)
+}
+
+func (s *OpenvSwitchInterfaceSuite) TestSanitizeIncorrectInterface(c *C) {
+ c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) },
+ PanicMatches, `slot is not of interface "openvswitch"`)
+ c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) },
+ PanicMatches, `plug is not of interface "openvswitch"`)
+}
+
+func (s *OpenvSwitchInterfaceSuite) TestUsedSecuritySystems(c *C) {
+ // connected plugs have a non-nil security snippet for apparmor
+ snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityAppArmor)
+ c.Assert(err, IsNil)
+ c.Assert(snippet, Not(IsNil))
+ // connected plugs have a non-nil security snippet for seccomp
+ snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecuritySecComp)
+ c.Assert(err, IsNil)
+ c.Assert(snippet, Not(IsNil))
+}
diff --git a/interfaces/builtin/process_control.go b/interfaces/builtin/process_control.go
index 265cbac191..8e1e3e5d30 100644
--- a/interfaces/builtin/process_control.go
+++ b/interfaces/builtin/process_control.go
@@ -29,6 +29,9 @@ const processControlConnectedPlugAppArmor = `
# all processes under root or processes running under the same UID otherwise.
# Usage: reserved
+/{,usr/}bin/nice ixr,
+
+capability sys_resource,
capability sys_nice,
signal,
diff --git a/interfaces/systemd/backend.go b/interfaces/systemd/backend.go
index 3d0f33c783..a202b671a9 100644
--- a/interfaces/systemd/backend.go
+++ b/interfaces/systemd/backend.go
@@ -55,9 +55,12 @@ func disableRemovedServices(systemd sysd.Systemd, dir, glob string, content map[
for _, path := range paths {
service := filepath.Base(path)
if content[service] == nil {
- if err := systemd.DisableNow(service); err != nil {
+ if err := systemd.Disable(service); err != nil {
logger.Noticef("cannot disable service %q: %s", service, err)
}
+ if err := systemd.Stop(service, 5*time.Second); err != nil {
+ logger.Noticef("cannot stop service %q: %s", service, err)
+ }
}
}
return nil
@@ -122,9 +125,12 @@ func (b *Backend) Remove(snapName string) error {
glob := interfaces.InterfaceServiceName(snapName, "*")
_, removed, errEnsure := osutil.EnsureDirState(dirs.SnapServicesDir, glob, nil)
for _, service := range removed {
- if err := systemd.DisableNow(service); err != nil {
+ if err := systemd.Disable(service); err != nil {
logger.Noticef("cannot disable service %q: %s", service, err)
}
+ if err := systemd.Stop(service, 5*time.Second); err != nil {
+ logger.Noticef("cannot stop service %q: %s", service, err)
+ }
}
// Reload systemd whenever something is removed
if len(removed) > 0 {
diff --git a/interfaces/systemd/backend_test.go b/interfaces/systemd/backend_test.go
index a28b1cfeb5..21805c28c2 100644
--- a/interfaces/systemd/backend_test.go
+++ b/interfaces/systemd/backend_test.go
@@ -52,7 +52,7 @@ var testedConfinementOpts = []interfaces.ConfinementOptions{
func (s *backendSuite) SetUpTest(c *C) {
s.BackendSuite.SetUpTest(c)
s.Backend = &systemd.Backend{}
- s.systemctlCmd = testutil.MockCommand(c, "systemctl", "")
+ s.systemctlCmd = testutil.MockCommand(c, "systemctl", "echo ActiveState=inactive")
}
func (s *backendSuite) TearDownTest(c *C) {
@@ -196,14 +196,12 @@ func (s *backendSuite) TestRemovingSnapRemovesAndStopsServices(c *C) {
_, err := os.Stat(service)
c.Check(os.IsNotExist(err), Equals, true)
// the service was stopped
- calls := s.systemctlCmd.Calls()
- c.Check(calls[0], DeepEquals, []string{"systemctl", "--root", dirs.GlobalRootDir, "--now", "disable", "snap.samba.interface.foo.service"})
- for i, call := range calls {
- if i > 0 && i < len(calls)-1 {
- c.Check(call, DeepEquals, []string{"systemctl", "show", "--property=ActiveState", "snap.samba.interface.foo.service"})
- }
- }
- c.Check(calls[len(calls)-1], DeepEquals, []string{"systemctl", "daemon-reload"})
+ c.Check(s.systemctlCmd.Calls(), DeepEquals, [][]string{
+ {"systemctl", "--root", dirs.GlobalRootDir, "disable", "snap.samba.interface.foo.service"},
+ {"systemctl", "stop", "snap.samba.interface.foo.service"},
+ {"systemctl", "show", "--property=ActiveState", "snap.samba.interface.foo.service"},
+ {"systemctl", "daemon-reload"},
+ })
}
}
@@ -228,12 +226,10 @@ func (s *backendSuite) TestSettingUpSecurityWithFewerServices(c *C) {
// Update over to the same snap to regenerate security
s.UpdateSnap(c, snapInfo, interfaces.ConfinementOptions{}, backendtest.SambaYamlV1, 0)
// The bar service should have been stopped
- calls := s.systemctlCmd.Calls()
- c.Check(calls[0], DeepEquals, []string{"systemctl", "--root", dirs.GlobalRootDir, "--now", "disable", "snap.samba.interface.bar.service"})
- c.Check(calls[1], DeepEquals, []string{"systemctl", "daemon-reload"})
- for i, call := range calls {
- if i > 1 {
- c.Check(call, DeepEquals, []string{"systemctl", "show", "--property=ActiveState", "snap.samba.interface.bar.service"})
- }
- }
+ c.Check(s.systemctlCmd.Calls(), DeepEquals, [][]string{
+ {"systemctl", "--root", dirs.GlobalRootDir, "disable", "snap.samba.interface.bar.service"},
+ {"systemctl", "stop", "snap.samba.interface.bar.service"},
+ {"systemctl", "show", "--property=ActiveState", "snap.samba.interface.bar.service"},
+ {"systemctl", "daemon-reload"},
+ })
}
diff --git a/overlord/managers_test.go b/overlord/managers_test.go
index e2cd5403d4..2ed329af29 100644
--- a/overlord/managers_test.go
+++ b/overlord/managers_test.go
@@ -1032,6 +1032,74 @@ apps:
c.Check(allAliases, HasLen, 0)
}
+func (ms *mgrsSuite) TestHappyUnalias(c *C) {
+ st := ms.o.State()
+ st.Lock()
+ defer st.Unlock()
+
+ fooYaml := `name: foo
+version: 1.0
+apps:
+ foo:
+ command: bin/foo
+ aliases: [foo_]
+`
+ ms.installLocalTestSnap(c, fooYaml)
+
+ ts, err := snapstate.Alias(st, "foo", []string{"foo_"})
+ c.Assert(err, IsNil)
+ chg := st.NewChange("alias", "...")
+ chg.AddAll(ts)
+
+ st.Unlock()
+ err = ms.o.Settle()
+ st.Lock()
+ c.Assert(err, IsNil)
+
+ c.Assert(chg.Err(), IsNil)
+ c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("alias change failed with: %v", chg.Err()))
+
+ foo_Alias := filepath.Join(dirs.SnapBinariesDir, "foo_")
+ dest, err := os.Readlink(foo_Alias)
+ c.Assert(err, IsNil)
+
+ c.Check(dest, Equals, "foo")
+
+ var allAliases map[string]map[string]string
+ err = st.Get("aliases", &allAliases)
+ c.Assert(err, IsNil)
+ c.Check(allAliases, DeepEquals, map[string]map[string]string{
+ "foo": {
+ "foo_": "enabled",
+ },
+ })
+
+ ts, err = snapstate.Unalias(st, "foo", []string{"foo_"})
+ c.Assert(err, IsNil)
+ chg = st.NewChange("unalias", "...")
+ chg.AddAll(ts)
+
+ st.Unlock()
+ err = ms.o.Settle()
+ st.Lock()
+ c.Assert(err, IsNil)
+
+ c.Assert(chg.Err(), IsNil)
+ c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("unalias change failed with: %v", chg.Err()))
+
+ c.Check(osutil.IsSymlink(foo_Alias), Equals, false)
+
+ allAliases = nil
+
+ err = st.Get("aliases", &allAliases)
+ c.Assert(err, IsNil)
+ c.Check(allAliases, DeepEquals, map[string]map[string]string{
+ "foo": {
+ "foo_": "disabled",
+ },
+ })
+}
+
type authContextSetupSuite struct {
o *overlord.Overlord
ac auth.AuthContext
diff --git a/overlord/snapstate/aliases.go b/overlord/snapstate/aliases.go
index 94ac54de33..4782e652ef 100644
--- a/overlord/snapstate/aliases.go
+++ b/overlord/snapstate/aliases.go
@@ -105,6 +105,38 @@ func Alias(st *state.State, snapName string, aliases []string) (*state.TaskSet,
return state.NewTaskSet(alias), nil
}
+// Unalias explicitly disables the provided aliases for the snap with the given name.
+func Unalias(st *state.State, snapName string, aliases []string) (*state.TaskSet, error) {
+ var snapst SnapState
+ err := Get(st, snapName, &snapst)
+ if err == state.ErrNoState {
+ return nil, fmt.Errorf("cannot find snap %q", snapName)
+ }
+ if err != nil {
+ return nil, err
+ }
+ if !snapst.Active {
+ return nil, fmt.Errorf("disabling aliases for disabled snap %q not supported", snapName)
+ }
+ if err := checkChangeConflict(st, snapName, nil); err != nil {
+ return nil, err
+ }
+
+ snapsup := &SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: snapName},
+ }
+
+ alias := st.NewTask("alias", fmt.Sprintf(i18n.G("Disable aliases for snap %q"), snapsup.Name()))
+ alias.Set("snap-setup", &snapsup)
+ toDisable := map[string]string{}
+ for _, alias := range aliases {
+ toDisable[alias] = "disabled"
+ }
+ alias.Set("aliases", toDisable)
+
+ return state.NewTaskSet(alias), nil
+}
+
func (m *SnapManager) doAlias(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
@@ -113,8 +145,8 @@ func (m *SnapManager) doAlias(t *state.Task, _ *tomb.Tomb) error {
if err != nil {
return err
}
- var toEnable map[string]string
- err = t.Get("aliases", &toEnable)
+ var changes map[string]string
+ err = t.Get("aliases", &changes)
if err != nil {
return err
}
@@ -132,27 +164,43 @@ func (m *SnapManager) doAlias(t *state.Task, _ *tomb.Tomb) error {
aliasStatuses = make(map[string]string)
}
var add []*backend.Alias
- for alias := range toEnable {
+ var remove []*backend.Alias
+ for alias, newStatus := range changes {
aliasApp := curInfo.Aliases[alias]
if aliasApp == nil {
- return fmt.Errorf("cannot enable alias %q for %q, no such alias", alias, snapName)
+ var action string
+ switch newStatus {
+ case "enabled":
+ action = "enable"
+ case "disabled":
+ action = "disable"
+ }
+ return fmt.Errorf("cannot %s alias %q for %q, no such alias", action, alias, snapName)
}
- if aliasStatuses[alias] == "enabled" {
+ if aliasStatuses[alias] == newStatus {
// nothing to do
continue
}
- err := checkAliasConflict(st, snapName, alias)
- if err != nil {
- return err
- }
- aliasStatuses[alias] = "enabled"
- add = append(add, &backend.Alias{
+ beAlias := &backend.Alias{
Name: alias,
Target: filepath.Base(aliasApp.WrapperPath()),
- })
+ }
+ switch newStatus {
+ case "enabled":
+ err := checkAliasConflict(st, snapName, alias)
+ if err != nil {
+ return err
+ }
+ add = append(add, beAlias)
+ case "disabled":
+ if aliasStatuses[alias] != "" {
+ remove = append(remove, beAlias)
+ }
+ }
+ aliasStatuses[alias] = newStatus
}
st.Unlock()
- err = m.backend.UpdateAliases(add, nil)
+ err = m.backend.UpdateAliases(add, remove)
st.Lock()
if err != nil {
return err
@@ -174,8 +222,8 @@ func (m *SnapManager) undoAlias(t *state.Task, _ *tomb.Tomb) error {
if err != nil {
return err
}
- var toEnable map[string]string
- err = t.Get("aliases", &toEnable)
+ var changes map[string]string
+ err = t.Get("aliases", &changes)
if err != nil {
return err
}
@@ -184,21 +232,41 @@ func (m *SnapManager) undoAlias(t *state.Task, _ *tomb.Tomb) error {
if err != nil {
return err
}
+ var add []*backend.Alias
var remove []*backend.Alias
- for alias := range toEnable {
- if oldStatuses[alias] == "enabled" {
+Next:
+ for alias, newStatus := range changes {
+ if oldStatuses[alias] == newStatus {
// nothing to undo
continue
}
aliasApp := curInfo.Aliases[alias]
if aliasApp == nil {
// unexpected
- return fmt.Errorf("internal error: cannot re-disable alias %q for %q, no such alias", alias, snapName)
+ return fmt.Errorf("internal error: cannot re-toggle alias %q for %q, no such alias", alias, snapName)
}
- remove = append(remove, &backend.Alias{
+ beAlias := &backend.Alias{
Name: alias,
Target: filepath.Base(aliasApp.WrapperPath()),
- })
+ }
+ switch newStatus {
+ case "enabled":
+ remove = append(remove, beAlias)
+ case "disabled":
+ if oldStatuses[alias] != "" {
+ // can actually be reinstated only if it doesn't conflict
+ err := checkAliasConflict(st, snapName, alias)
+ if err != nil {
+ if _, ok := err.(*aliasConflictError); ok {
+ delete(oldStatuses, alias)
+ t.Errorf("%v", err)
+ continue Next
+ }
+ return err
+ }
+ add = append(add, beAlias)
+ }
+ }
}
st.Unlock()
remove, err = m.backend.MatchingAliases(remove)
@@ -207,7 +275,13 @@ func (m *SnapManager) undoAlias(t *state.Task, _ *tomb.Tomb) error {
return fmt.Errorf("cannot list aliases for snap %q: %v", snapName, err)
}
st.Unlock()
- err = m.backend.UpdateAliases(nil, remove)
+ add, err = m.backend.MissingAliases(add)
+ st.Lock()
+ if err != nil {
+ return fmt.Errorf("cannot list aliases for snap %q: %v", snapName, err)
+ }
+ st.Unlock()
+ err = m.backend.UpdateAliases(add, remove)
st.Lock()
if err != nil {
return err
diff --git a/overlord/snapstate/aliases_test.go b/overlord/snapstate/aliases_test.go
index e96e578926..42fbc66bca 100644
--- a/overlord/snapstate/aliases_test.go
+++ b/overlord/snapstate/aliases_test.go
@@ -211,7 +211,7 @@ func (s *snapmgrTestSuite) TestUpdateAliasChangeConflict(c *C) {
c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`)
}
-func (s *snapmgrTestSuite) TestAliasUpdateChangeConflict(c *C) {
+func (s *snapmgrTestSuite) TestUpdateUnaliasChangeConflict(c *C) {
s.state.Lock()
defer s.state.Unlock()
@@ -222,76 +222,33 @@ func (s *snapmgrTestSuite) TestAliasUpdateChangeConflict(c *C) {
SnapType: "app",
})
- ts, err := snapstate.Alias(s.state, "some-snap", []string{"alias1"})
+ ts, err := snapstate.Update(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{})
c.Assert(err, IsNil)
// need a change to make the tasks visible
- s.state.NewChange("alias", "...").AddAll(ts)
+ s.state.NewChange("update", "...").AddAll(ts)
- _, err = snapstate.Update(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{})
+ _, err = snapstate.Unalias(s.state, "some-snap", []string{"alias1"})
c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`)
}
-func (s *snapmgrTestSuite) TestAliasTotalUndoRunthrough(c *C) {
+func (s *snapmgrTestSuite) TestAliasUpdateChangeConflict(c *C) {
s.state.Lock()
defer s.state.Unlock()
- snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
- Sequence: []*snap.SideInfo{
- {RealName: "alias-snap", Revision: snap.R(11)},
- },
- Current: snap.R(11),
- Active: true,
- })
- s.state.Set("aliases", map[string]map[string]string{
- "other-snap": {"alias7": "enabled"},
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}},
+ Current: snap.R(7),
+ SnapType: "app",
})
- chg := s.state.NewChange("alias", "enable an alias")
- ts, err := snapstate.Alias(s.state, "alias-snap", []string{"alias1"})
+ ts, err := snapstate.Alias(s.state, "some-snap", []string{"alias1"})
c.Assert(err, IsNil)
- chg.AddAll(ts)
-
- tasks := ts.Tasks()
- last := tasks[len(tasks)-1]
-
- terr := s.state.NewTask("error-trigger", "provoking total undo")
- terr.WaitFor(last)
- chg.AddTask(terr)
-
- s.state.Unlock()
-
- for i := 0; i < 3; i++ {
- s.snapmgr.Ensure()
- s.snapmgr.Wait()
- }
-
- s.state.Lock()
-
- c.Check(chg.Status(), Equals, state.ErrorStatus, Commentf("%v", chg.Err()))
- expected := fakeOps{
- {
- op: "update-aliases",
- aliases: []*backend.Alias{{"alias1", "alias-snap.cmd1"}},
- },
- {
- op: "matching-aliases",
- aliases: []*backend.Alias{{"alias1", "alias-snap.cmd1"}},
- },
- {
- op: "update-aliases",
- rmAliases: []*backend.Alias{{"alias1", "alias-snap.cmd1"}},
- },
- }
- // start with an easier-to-read error if this fails:
- c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
- c.Check(s.fakeBackend.ops, DeepEquals, expected)
+ // need a change to make the tasks visible
+ s.state.NewChange("alias", "...").AddAll(ts)
- var allAliases map[string]map[string]string
- err = s.state.Get("aliases", &allAliases)
- c.Assert(err, IsNil)
- c.Check(allAliases, DeepEquals, map[string]map[string]string{
- "other-snap": {"alias7": "enabled"},
- })
+ _, err = snapstate.Update(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{})
+ c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`)
}
func (s *snapmgrTestSuite) TestAliasNoAlias(c *C) {
@@ -538,3 +495,306 @@ func (s *snapmgrTestSuite) TestDoUndoClearAliasesConflict(c *C) {
c.Check(t.Log(), HasLen, 1)
c.Check(t.Log()[0], Matches, `.* ERROR cannot enable alias "alias9" for "alias-snap", already enabled for "other-snap"`)
}
+
+var statusesMatrix = []struct {
+ beforeStatus string
+ action string
+ status string
+ mutation string
+}{
+ {"", "alias", "enabled", "add"},
+ {"enabled", "alias", "enabled", "-"},
+ {"disabled", "alias", "enabled", "add"},
+ {"", "unalias", "disabled", "-"},
+ {"enabled", "unalias", "disabled", "rm"},
+ {"disabled", "unalias", "disabled", "-"},
+}
+
+func (s *snapmgrTestSuite) TestAliasMatrixRunThrough(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ })
+
+ defer s.snapmgr.Stop()
+ for _, scenario := range statusesMatrix {
+ if scenario.beforeStatus != "" {
+ s.state.Set("aliases", map[string]map[string]string{
+ "alias-snap": {
+ "alias1": scenario.beforeStatus,
+ },
+ })
+ } else {
+ s.state.Set("aliases", nil)
+ }
+
+ chg := s.state.NewChange("scenario", "...")
+ var err error
+ var ts *state.TaskSet
+ switch scenario.action {
+ case "alias":
+ ts, err = snapstate.Alias(s.state, "alias-snap", []string{"alias1"})
+ case "unalias":
+ ts, err = snapstate.Unalias(s.state, "alias-snap", []string{"alias1"})
+ }
+ c.Assert(err, IsNil)
+
+ chg.AddAll(ts)
+
+ s.state.Unlock()
+ s.settle()
+ s.state.Lock()
+
+ c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("%#v: %v", scenario, chg.Err()))
+ var aliases []*backend.Alias
+ var rmAliases []*backend.Alias
+ switch scenario.mutation {
+ case "-":
+ case "add":
+ aliases = []*backend.Alias{{"alias1", "alias-snap.cmd1"}}
+ case "rm":
+ rmAliases = []*backend.Alias{{"alias1", "alias-snap.cmd1"}}
+ }
+
+ comm := Commentf("%#v", scenario)
+ expected := fakeOps{
+ {
+ op: "update-aliases",
+ aliases: aliases,
+ rmAliases: rmAliases,
+ },
+ }
+ // start with an easier-to-read error if this fails:
+ c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops(), comm)
+ c.Assert(s.fakeBackend.ops, DeepEquals, expected, comm)
+
+ var allAliases map[string]map[string]string
+ err = s.state.Get("aliases", &allAliases)
+ c.Assert(err, IsNil)
+ if scenario.status != "" {
+ c.Check(allAliases, DeepEquals, map[string]map[string]string{
+ "alias-snap": {"alias1": scenario.status},
+ }, comm)
+ } else {
+ c.Check(allAliases, HasLen, 0, Commentf("%#v", scenario), comm)
+ }
+
+ s.fakeBackend.ops = nil
+ }
+}
+
+func (s *snapmgrTestSuite) TestAliasMatrixTotalUndoRunThrough(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ })
+
+ defer s.snapmgr.Stop()
+ for _, scenario := range statusesMatrix {
+ if scenario.beforeStatus != "" {
+ s.state.Set("aliases", map[string]map[string]string{
+ "alias-snap": {
+ "alias1": scenario.beforeStatus,
+ },
+ })
+ } else {
+ s.state.Set("aliases", nil)
+ }
+
+ chg := s.state.NewChange("scenario", "...")
+ var err error
+ var ts *state.TaskSet
+ switch scenario.action {
+ case "alias":
+ ts, err = snapstate.Alias(s.state, "alias-snap", []string{"alias1"})
+ case "unalias":
+ ts, err = snapstate.Unalias(s.state, "alias-snap", []string{"alias1"})
+ }
+ c.Assert(err, IsNil)
+
+ chg.AddAll(ts)
+
+ tasks := ts.Tasks()
+ last := tasks[len(tasks)-1]
+
+ terr := s.state.NewTask("error-trigger", "provoking total undo")
+ terr.WaitFor(last)
+ chg.AddTask(terr)
+
+ s.state.Unlock()
+ for i := 0; i < 3; i++ {
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+ }
+ s.state.Lock()
+
+ c.Assert(chg.Status(), Equals, state.ErrorStatus, Commentf("%#v: %v", scenario, chg.Err()))
+ var aliases []*backend.Alias
+ var rmAliases []*backend.Alias
+ switch scenario.mutation {
+ case "-":
+ case "add":
+ aliases = []*backend.Alias{{"alias1", "alias-snap.cmd1"}}
+ case "rm":
+ rmAliases = []*backend.Alias{{"alias1", "alias-snap.cmd1"}}
+ }
+
+ comm := Commentf("%#v", scenario)
+ expected := fakeOps{
+ {
+ op: "update-aliases",
+ aliases: aliases,
+ rmAliases: rmAliases,
+ },
+ {
+ op: "matching-aliases",
+ aliases: aliases,
+ },
+ {
+ op: "missing-aliases",
+ aliases: rmAliases,
+ },
+ {
+ op: "update-aliases",
+ aliases: rmAliases,
+ rmAliases: aliases,
+ },
+ }
+ // start with an easier-to-read error if this fails:
+ c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops(), comm)
+ c.Assert(s.fakeBackend.ops, DeepEquals, expected, comm)
+
+ var allAliases map[string]map[string]string
+ err = s.state.Get("aliases", &allAliases)
+ c.Assert(err, IsNil)
+ if scenario.beforeStatus != "" {
+ c.Check(allAliases, DeepEquals, map[string]map[string]string{
+ "alias-snap": {"alias1": scenario.beforeStatus},
+ }, comm)
+ } else {
+ c.Check(allAliases, HasLen, 0, comm)
+ }
+
+ s.fakeBackend.ops = nil
+ }
+}
+
+func (s *snapmgrTestSuite) TestUnliasTotalUndoRunThroughAliasConflict(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ })
+
+ defer s.snapmgr.Stop()
+ s.state.Set("aliases", map[string]map[string]string{
+ "alias-snap": {
+ "alias1": "enabled",
+ },
+ })
+
+ chg := s.state.NewChange("scenario", "...")
+ ts, err := snapstate.Unalias(s.state, "alias-snap", []string{"alias1"})
+ c.Assert(err, IsNil)
+
+ chg.AddAll(ts)
+
+ tasks := ts.Tasks()
+ last := tasks[len(tasks)-1]
+
+ grabAlias1 := func(t *state.Task, _ *tomb.Tomb) error {
+ st := t.State()
+ st.Lock()
+ defer st.Unlock()
+
+ var allAliases map[string]map[string]string
+ err := st.Get("aliases", &allAliases)
+ c.Assert(err, IsNil)
+ c.Assert(allAliases, DeepEquals, map[string]map[string]string{
+ "alias-snap": {
+ "alias1": "disabled",
+ },
+ })
+
+ st.Set("aliases", map[string]map[string]string{
+ "alias-snap": {
+ "alias1": "disabled",
+ },
+ "other-snap": {
+ "alias1": "enabled",
+ },
+ })
+ return nil
+ }
+
+ s.snapmgr.AddAdhocTaskHandler("grab-alias1", grabAlias1, nil)
+
+ tgrab1 := s.state.NewTask("grab-alias1", "grab alias1 for other-snap")
+ tgrab1.WaitFor(last)
+ chg.AddTask(tgrab1)
+
+ terr := s.state.NewTask("error-trigger", "provoking total undo")
+ terr.WaitFor(tgrab1)
+ chg.AddTask(terr)
+
+ s.state.Unlock()
+
+ for i := 0; i < 5; i++ {
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Assert(chg.Status(), Equals, state.ErrorStatus, Commentf("%v", chg.Err()))
+ rmAliases := []*backend.Alias{{"alias1", "alias-snap.cmd1"}}
+
+ expected := fakeOps{
+ {
+ op: "update-aliases",
+ rmAliases: rmAliases,
+ },
+ {
+ op: "matching-aliases",
+ },
+ {
+ op: "missing-aliases",
+ },
+ {
+ op: "update-aliases",
+ },
+ }
+ // start with an easier-to-read error if this fails:
+ c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
+ c.Assert(s.fakeBackend.ops, DeepEquals, expected)
+
+ var allAliases map[string]map[string]string
+ err = s.state.Get("aliases", &allAliases)
+ c.Assert(err, IsNil)
+ c.Check(allAliases, DeepEquals, map[string]map[string]string{
+ "other-snap": {
+ "alias1": "enabled",
+ },
+ })
+
+ c.Check(last.Log(), HasLen, 1)
+ c.Check(last.Log()[0], Matches, `.* ERROR cannot enable alias "alias1" for "alias-snap", already enabled for "other-snap"`)
+
+}
diff --git a/snap/implicit.go b/snap/implicit.go
index af9e87da67..b24bbd1b90 100644
--- a/snap/implicit.go
+++ b/snap/implicit.go
@@ -48,6 +48,7 @@ var implicitSlots = []string{
"network-observe",
"network-setup-observe",
"opengl",
+ "openvswitch-support",
"ppp",
"process-control",
"raw-usb",
@@ -71,6 +72,7 @@ var implicitClassicSlots = []string{
"modem-manager",
"network-manager",
"ofono",
+ "openvswitch",
"optical-drive",
"pulseaudio",
"screen-inhibit-control",
diff --git a/systemd/systemd.go b/systemd/systemd.go
index 309ccc5bb2..366d40569a 100644
--- a/systemd/systemd.go
+++ b/systemd/systemd.go
@@ -86,9 +86,7 @@ var JournalctlCmd = jctl
type Systemd interface {
DaemonReload() error
Enable(service string) error
- EnableNow(service string) error
Disable(service string) error
- DisableNow(service string) error
Start(service string) error
Stop(service string, timeout time.Duration) error
Kill(service, signal string) error
@@ -186,24 +184,12 @@ func (s *systemd) Enable(serviceName string) error {
return err
}
-// Enable the given service and start it
-func (s *systemd) EnableNow(serviceName string) error {
- _, err := SystemctlCmd("--root", s.rootDir, "--now", "enable", serviceName)
- return err
-}
-
// Disable the given service
func (s *systemd) Disable(serviceName string) error {
_, err := SystemctlCmd("--root", s.rootDir, "disable", serviceName)
return err
}
-// Disable the given service and stop it
-func (s *systemd) DisableNow(serviceName string) error {
- _, err := SystemctlCmd("--root", s.rootDir, "--now", "disable", serviceName)
- return err
-}
-
// Start the given service
func (*systemd) Start(serviceName string) error {
_, err := SystemctlCmd("start", serviceName)
diff --git a/systemd/systemd_test.go b/systemd/systemd_test.go
index 88713cdc27..30643fd630 100644
--- a/systemd/systemd_test.go
+++ b/systemd/systemd_test.go
@@ -191,24 +191,12 @@ func (s *SystemdTestSuite) TestDisable(c *C) {
c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "disable", "foo"}})
}
-func (s *SystemdTestSuite) TestDisableNow(c *C) {
- err := New("xyzzy", s.rep).DisableNow("foo")
- c.Assert(err, IsNil)
- c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "--now", "disable", "foo"}})
-}
-
func (s *SystemdTestSuite) TestEnable(c *C) {
err := New("xyzzy", s.rep).Enable("foo")
c.Assert(err, IsNil)
c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "enable", "foo"}})
}
-func (s *SystemdTestSuite) TestEnableNow(c *C) {
- err := New("xyzzy", s.rep).EnableNow("foo")
- c.Assert(err, IsNil)
- c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "--now", "enable", "foo"}})
-}
-
func (s *SystemdTestSuite) TestRestart(c *C) {
restore := MockStopDelays(time.Millisecond, 25*time.Second)
defer restore()
diff --git a/tests/lib/prepare.sh b/tests/lib/prepare.sh
index b2bd69dec2..631c1a8c20 100755
--- a/tests/lib/prepare.sh
+++ b/tests/lib/prepare.sh
@@ -138,6 +138,7 @@ setup_reflash_magic() {
# the image
# unpack our freshly build snapd into the new core snap
dpkg-deb -x ${SPREAD_PATH}/../snapd_*.deb $UNPACKD
+ dpkg-deb -x ${SPREAD_PATH}/../snap-confine_*.deb $UNPACKD
# add a gpio slot
cat >> $UNPACKD/meta/snap.yaml <<-EOF