From ccc5562c5b89896ef7c3b9865f7124971444cdb7 Mon Sep 17 00:00:00 2001 From: Alex Lewontin Date: Thu, 4 Jul 2024 17:06:38 -0400 Subject: [PATCH] many: fix iface static attrs not properly updating (#12878) * o/ifacestate: fix typo Signed-off-by: Alex Lewontin * o/ifacestate: autoconnection checking should not be task-bound Signed-off-by: Alex Lewontin * o/ifacestate: update static attrs based on {auto,}connection policy Signed-off-by: Alex Lewontin * tests: add spread test for static attr updating Signed-off-by: Alex Lewontin * tests: add shared-memory static-attr update test Signed-off-by: Alex Lewontin * o/ifacestate: add tests for policy-driven static attr updates Signed-off-by: Alex Lewontin * o/ifacestate: simplify state error handling when reloading connections Signed-off-by: Alex Lewontin * Revert "o/ifacestate: simplify state error handling when reloading connections" This reverts commit d3f67c67e22b463b994a13f50064cef551e86686. * o/i: restore DeviceCtx on cleanup Signed-off-by: Alex Lewontin * o/ifacestate: simpler state handling when reloading connections Signed-off-by: Alex Lewontin * o/ifacestate: also check AutoConnect method when doing policy based reloading Signed-off-by: Alex Lewontin * o/i: use new Sequence helpers in test Signed-off-by: Alex Lewontin * o/ifacestate: add missing appset parameter * tests/main/upgrade-from-2.15: kill the test The test is no longer useful. The oldest version we have in any distribution is 2.38 in Trusty, see https://launchpad.net/snapd/+packages but also 2.15 is very ancient and completely unsupported at this time. Signed-off-by: Maciej Borzecki * overlord: update managers test to account for preserved plug static attributes Signed-off-by: Maciej Borzecki * tests/main/interface-static-attrs-update-on-refresh: clean up after the test Signed-off-by: Maciej Borzecki --------- Signed-off-by: Alex Lewontin Signed-off-by: Maciej Borzecki Co-authored-by: Philip Meulengracht Co-authored-by: Maciej Borzecki --- overlord/ifacestate/handlers.go | 14 +- overlord/ifacestate/helpers.go | 91 ++++-- overlord/ifacestate/helpers_test.go | 22 ++ overlord/ifacestate/ifacestate_test.go | 291 +++++++++++++++++- overlord/managers_test.go | 6 + .../task.yaml | 106 +++++++ .../test-snap-a-v1/bin/sh | 3 + .../test-snap-a-v1/meta/snap.yaml | 21 ++ .../test-snap-a-v2/bin/sh | 3 + .../test-snap-a-v2/meta/snap.yaml | 23 ++ .../test-snap-b/bin/sh | 3 + .../test-snap-b/meta/snap.yaml | 13 + tests/main/upgrade-from-2.15/task.yaml | 83 ----- 13 files changed, 551 insertions(+), 128 deletions(-) create mode 100644 tests/main/interface-static-attrs-update-on-refresh/task.yaml create mode 100755 tests/main/interface-static-attrs-update-on-refresh/test-snap-a-v1/bin/sh create mode 100644 tests/main/interface-static-attrs-update-on-refresh/test-snap-a-v1/meta/snap.yaml create mode 100755 tests/main/interface-static-attrs-update-on-refresh/test-snap-a-v2/bin/sh create mode 100644 tests/main/interface-static-attrs-update-on-refresh/test-snap-a-v2/meta/snap.yaml create mode 100755 tests/main/interface-static-attrs-update-on-refresh/test-snap-b/bin/sh create mode 100644 tests/main/interface-static-attrs-update-on-refresh/test-snap-b/meta/snap.yaml delete mode 100644 tests/main/upgrade-from-2.15/task.yaml diff --git a/overlord/ifacestate/handlers.go b/overlord/ifacestate/handlers.go index 6ec71892eda..99f6bf38b65 100644 --- a/overlord/ifacestate/handlers.go +++ b/overlord/ifacestate/handlers.go @@ -611,7 +611,7 @@ func (m *InterfaceManager) doConnect(task *state.Task, _ *tomb.Tomb) (err error) // policy "connection" rules, other auto-connections obey the // "auto-connection" rules if autoConnect && !byGadget { - autochecker, err := newAutoConnectChecker(st, task, m.repo, deviceCtx) + autochecker, err := newAutoConnectChecker(st, m.repo, deviceCtx) if err != nil { return err } @@ -1360,7 +1360,7 @@ func (m *InterfaceManager) doAutoConnect(task *state.Task, _ *tomb.Tomb) error { snapName := snapsup.InstanceName() - autochecker, err := newAutoConnectChecker(st, task, m.repo, deviceCtx) + autochecker, err := newAutoConnectChecker(st, m.repo, deviceCtx) if err != nil { return err } @@ -1431,7 +1431,7 @@ func (m *InterfaceManager) doAutoConnect(task *state.Task, _ *tomb.Tomb) error { cannotAutoConnectLog := func(plug *snap.PlugInfo, candRefs []string) string { return fmt.Sprintf("cannot auto-connect plug %s, candidates found: %s", plug, strings.Join(candRefs, ", ")) } - if err := autochecker.addAutoConnections(newconns, plugs, nil, conns, cannotAutoConnectLog, conflictError); err != nil { + if err := autochecker.addAutoConnections(task, newconns, plugs, nil, conns, cannotAutoConnectLog, conflictError); err != nil { return err } // Auto-connect all the slots @@ -1444,7 +1444,7 @@ func (m *InterfaceManager) doAutoConnect(task *state.Task, _ *tomb.Tomb) error { cannotAutoConnectLog := func(plug *snap.PlugInfo, candRefs []string) string { return fmt.Sprintf("cannot auto-connect slot %s to plug %s, candidates found: %s", slot, plug, strings.Join(candRefs, ", ")) } - if err := autochecker.addAutoConnections(newconns, candidates, filterForSlot(slot), conns, cannotAutoConnectLog, conflictError); err != nil { + if err := autochecker.addAutoConnections(task, newconns, candidates, filterForSlot(slot), conns, cannotAutoConnectLog, conflictError); err != nil { return err } } @@ -1681,7 +1681,7 @@ func (m *InterfaceManager) doHotplugConnect(task *state.Task, _ *tomb.Tomb) erro conn := conns[id] // device was not unplugged, this is the case if snapd is restarted and we enumerate devices. // note, the situation where device was not unplugged but has changed is handled - // by hotlugDeviceAdded handler - updateDevice. + // by hotplugDeviceAdded handler - updateDevice. if !conn.HotplugGone || conn.Undesired { continue } @@ -1700,7 +1700,7 @@ func (m *InterfaceManager) doHotplugConnect(task *state.Task, _ *tomb.Tomb) erro } // find new auto-connections - autochecker, err := newAutoConnectChecker(st, task, m.repo, deviceCtx) + autochecker, err := newAutoConnectChecker(st, m.repo, deviceCtx) if err != nil { return err } @@ -1713,7 +1713,7 @@ func (m *InterfaceManager) doHotplugConnect(task *state.Task, _ *tomb.Tomb) erro cannotAutoConnectLog := func(plug *snap.PlugInfo, candRefs []string) string { return fmt.Sprintf("cannot auto-connect hotplug slot %s to plug %s, candidates found: %s", slot, plug, strings.Join(candRefs, ", ")) } - if err := autochecker.addAutoConnections(newconns, candidates, filterForSlot(slot), conns, cannotAutoConnectLog, conflictError); err != nil { + if err := autochecker.addAutoConnections(task, newconns, candidates, filterForSlot(slot), conns, cannotAutoConnectLog, conflictError); err != nil { return err } diff --git a/overlord/ifacestate/helpers.go b/overlord/ifacestate/helpers.go index c5041da078d..dc30bbf08fc 100644 --- a/overlord/ifacestate/helpers.go +++ b/overlord/ifacestate/helpers.go @@ -348,6 +348,28 @@ func (m *InterfaceManager) reloadConnections(snapName string) ([]string, error) return nil, err } + var policyChecker interfaces.PolicyFunc + var autoChecker *autoConnectChecker + var connChecker *connectChecker + + deviceCtx, err := snapstate.DeviceCtx(m.state, nil, nil) + if errors.Is(err, state.ErrNoState) { + // everything else is a noop, as no model means no connections + // to reload + return nil, nil + } else if err != nil { + return nil, err + } + autoChecker, err = newAutoConnectChecker(m.state, m.repo, deviceCtx) + if err != nil { + return nil, err + } + + connChecker, err = newConnectChecker(m.state, deviceCtx) + if err != nil { + return nil, err + } + connStateChanged := false affected := make(map[string]bool) ConnsLoop: @@ -402,27 +424,48 @@ ConnsLoop: var updateStaticAttrs bool staticPlugAttrs := connState.StaticPlugAttrs staticSlotAttrs := connState.StaticSlotAttrs + newStaticPlugAttrs := utils.NormalizeInterfaceAttributes(plugInfo.Attrs).(map[string]interface{}) + newStaticSlotAttrs := utils.NormalizeInterfaceAttributes(slotInfo.Attrs).(map[string]interface{}) + + // if the interface was originally autoconnected, update the static attrs if it would + // still be allowed to autoconnect. Otherwise, update the static attrs if it would still + // be allowed to regular connect. + if connState.Auto && !connState.ByGadget { + policyChecker = func(cplug *interfaces.ConnectedPlug, cslot *interfaces.ConnectedSlot) (bool, error) { + iface, err := interfaces.ByName(cplug.Interface()) + if err != nil { + return false, err + } - // XXX: Refresh the copy of the static connection attributes for "content" - // and "system-files" interfaces. - // This is a partial and temporary solution to https://bugs.launchpad.net/snapd/+bug/1825883 - // and https://bugs.launchpad.net/snapd/+bug/1942266. - switch plugInfo.Interface { - case "content": - var plugContent, slotContent string - plugInfo.Attr("content", &plugContent) - slotInfo.Attr("content", &slotContent) - - if plugContent != "" && plugContent == slotContent { - staticPlugAttrs = utils.NormalizeInterfaceAttributes(plugInfo.Attrs).(map[string]interface{}) - staticSlotAttrs = utils.NormalizeInterfaceAttributes(slotInfo.Attrs).(map[string]interface{}) - updateStaticAttrs = true - } else { - logger.Noticef("cannot refresh static attributes of the connection %q", connId) + if !iface.AutoConnect(plugInfo, slotInfo) { + return false, nil + } + + ok, _, err := autoChecker.check(cplug, cslot) + return ok, err } - case "system-files": - staticPlugAttrs = utils.NormalizeInterfaceAttributes(plugInfo.Attrs).(map[string]interface{}) - staticSlotAttrs = utils.NormalizeInterfaceAttributes(slotInfo.Attrs).(map[string]interface{}) + } else { + policyChecker = connChecker.check + } + + plugAppSet, err := interfaces.NewSnapAppSet(plugInfo.Snap, nil) + if err != nil { + return nil, err + } + slotAppSet, err := interfaces.NewSnapAppSet(slotInfo.Snap, nil) + if err != nil { + return nil, err + } + + cplug := interfaces.NewConnectedPlug(plugInfo, plugAppSet, newStaticPlugAttrs, connState.DynamicPlugAttrs) + cslot := interfaces.NewConnectedSlot(slotInfo, slotAppSet, newStaticSlotAttrs, connState.DynamicSlotAttrs) + + ok, err := policyChecker(cplug, cslot) + if !ok || err != nil { + logger.Noticef("cannot refresh static attributes of the connection %q", connId) + } else { + staticPlugAttrs = newStaticPlugAttrs + staticSlotAttrs = newStaticSlotAttrs updateStaticAttrs = true } @@ -704,7 +747,6 @@ var DebugAutoConnectCheck func(*policy.ConnectCandidate, interfaces.SideArity, e type autoConnectChecker struct { st *state.State - task *state.Task repo *interfaces.Repository deviceCtx snapstate.DeviceContext @@ -712,14 +754,13 @@ type autoConnectChecker struct { baseDecl *asserts.BaseDeclaration } -func newAutoConnectChecker(s *state.State, task *state.Task, repo *interfaces.Repository, deviceCtx snapstate.DeviceContext) (*autoConnectChecker, error) { +func newAutoConnectChecker(s *state.State, repo *interfaces.Repository, deviceCtx snapstate.DeviceContext) (*autoConnectChecker, error) { baseDecl, err := assertstate.BaseDeclaration(s) if err != nil { return nil, fmt.Errorf("internal error: cannot find base declaration: %v", err) } return &autoConnectChecker{ st: s, - task: task, repo: repo, deviceCtx: deviceCtx, cache: make(map[string]*asserts.SnapDeclaration), @@ -833,7 +874,7 @@ func filterUbuntuCoreSlots(candidates []*snap.SlotInfo, arities []interfaces.Sid // conns. cannotAutoConnectLog is called to build a log message in // case no applicable pair was found. conflictError is called // to handle checkAutoconnectConflicts errors. -func (c *autoConnectChecker) addAutoConnections(newconns map[string]*interfaces.ConnRef, plugs []*snap.PlugInfo, filter func([]*snap.SlotInfo) []*snap.SlotInfo, conns map[string]*schema.ConnState, cannotAutoConnectLog func(plug *snap.PlugInfo, candRefs []string) string, conflictError func(*state.Retry, error) error) error { +func (c *autoConnectChecker) addAutoConnections(task *state.Task, newconns map[string]*interfaces.ConnRef, plugs []*snap.PlugInfo, filter func([]*snap.SlotInfo) []*snap.SlotInfo, conns map[string]*schema.ConnState, cannotAutoConnectLog func(plug *snap.PlugInfo, candRefs []string) string, conflictError func(*state.Retry, error) error) error { for _, plug := range plugs { candSlots, arities := c.repo.AutoConnectCandidateSlots(plug.Snap.InstanceName(), plug.Name, c.check) @@ -869,12 +910,12 @@ func (c *autoConnectChecker) addAutoConnections(newconns map[string]*interfaces. for i, candidate := range candSlots { crefs[i] = candidate.String() } - c.task.Logf(cannotAutoConnectLog(plug, crefs)) + task.Logf(cannotAutoConnectLog(plug, crefs)) continue } for _, slot := range applicable { - if err := addNewConnection(c.st, c.task, newconns, conns, plug, slot, conflictError); err != nil { + if err := addNewConnection(c.st, task, newconns, conns, plug, slot, conflictError); err != nil { return err } } diff --git a/overlord/ifacestate/helpers_test.go b/overlord/ifacestate/helpers_test.go index 7c2f5fb1fed..8ddedb84a92 100644 --- a/overlord/ifacestate/helpers_test.go +++ b/overlord/ifacestate/helpers_test.go @@ -25,9 +25,12 @@ import ( "os" "path/filepath" "strings" + "time" . "gopkg.in/check.v1" + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/asserts/assertstest" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/ifacetest" @@ -45,6 +48,7 @@ import ( ) type helpersSuite struct { + testutil.BaseTest st *state.State } @@ -53,12 +57,30 @@ var _ = Suite(&helpersSuite{}) func (s *helpersSuite) SetUpTest(c *C) { s.st = state.New(nil) dirs.SetRootDir(c.MkDir()) + + s.MockModel(c, nil) } func (s *helpersSuite) TearDownTest(c *C) { dirs.SetRootDir("") } +func (s *helpersSuite) MockModel(c *C, extraHeaders map[string]interface{}) { + model := assertstest.FakeAssertion(map[string]interface{}{ + "type": "model", + "authority-id": "my-brand", + "series": "16", + "brand-id": "my-brand", + "model": "my-model", + "gadget": "gadget", + "kernel": "krnl", + "architecture": "amd64", + "timestamp": time.Now().Format(time.RFC3339), + }, extraHeaders).(*asserts.Model) + + s.AddCleanup(snapstatetest.MockDeviceModel(model)) +} + func (s *helpersSuite) TestIdentityMapper(c *C) { var m ifacestate.SnapMapper = &ifacestate.IdentityMapper{} diff --git a/overlord/ifacestate/ifacestate_test.go b/overlord/ifacestate/ifacestate_test.go index 52a372a106f..ca89768ce2b 100644 --- a/overlord/ifacestate/ifacestate_test.go +++ b/overlord/ifacestate/ifacestate_test.go @@ -36,12 +36,14 @@ import ( "github.com/snapcore/snapd/asserts/assertstest" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" "github.com/snapcore/snapd/interfaces/hotplug" "github.com/snapcore/snapd/interfaces/ifacetest" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/overlord" "github.com/snapcore/snapd/overlord/assertstate" + "github.com/snapcore/snapd/overlord/devicestate" "github.com/snapcore/snapd/overlord/hookstate" "github.com/snapcore/snapd/overlord/ifacestate" "github.com/snapcore/snapd/overlord/ifacestate/ifacerepo" @@ -243,6 +245,9 @@ func (s *interfaceManagerSuite) SetUpTest(c *C) { s.state.Lock() defer s.state.Unlock() + s.BaseTest.AddCleanup(snapstatetest.ReplaceDeviceCtxHook(devicestate.DeviceCtx)) + s.MockModel(c, nil) + s.privateHookMgr = nil s.privateMgr = nil s.extraIfaces = nil @@ -1847,7 +1852,11 @@ func (s *interfaceManagerSuite) TestForgetUndo(c *C) { // plug3 and slot3 do not exist, so the connection is not in the repository. connState := map[string]interface{}{ - "consumer:plug producer:slot": map[string]interface{}{"interface": "test"}, + "consumer:plug producer:slot": map[string]interface{}{ + "interface": "test", + "plug-static": map[string]interface{}{"attr1": "value1"}, + "slot-static": map[string]interface{}{"attr2": "value2"}, + }, "consumer:plug3 producer:slot3": map[string]interface{}{"interface": "test2"}, } @@ -2024,7 +2033,9 @@ func (s *interfaceManagerSuite) TestStaleConnectionsRemoved(c *C) { func (s *interfaceManagerSuite) testForget(c *C, plugSnap, plugName, slotSnap, slotName string) { s.mockIfaces(&ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"}) + s.MockSnapDecl(c, "consumer", "same-publisher", nil) s.mockSnap(c, consumerYaml) + s.MockSnapDecl(c, "producer", "same-publisher", nil) s.mockSnap(c, producerYaml) s.state.Lock() @@ -2097,7 +2108,11 @@ func (s *interfaceManagerSuite) TestForgetInactiveConnection(c *C) { var conns map[string]interface{} c.Assert(s.state.Get("conns", &conns), IsNil) c.Check(conns, DeepEquals, map[string]interface{}{ - "consumer:plug producer:slot": map[string]interface{}{"interface": "test"}, + "consumer:plug producer:slot": map[string]interface{}{ + "interface": "test", + "plug-static": map[string]interface{}{"attr1": "value1"}, + "slot-static": map[string]interface{}{"attr2": "value2"}, + }, }) mgr := s.manager(c) @@ -3315,6 +3330,7 @@ func (s *interfaceManagerSuite) TestReloadingConnectionsOnStartupUpdatesStaticAt s.state.Set("conns", map[string]interface{}{ "consumer:plug producer:slot": map[string]interface{}{ "interface": "content", + "auto": true, "plug-static": map[string]interface{}{"content": "foo", "attr": "old-plug-attr"}, "slot-static": map[string]interface{}{"content": "foo", "attr": "old-slot-attr"}, }, @@ -3342,7 +3358,9 @@ slots: content: foo attr: new-slot-attr ` + s.MockSnapDecl(c, "producer", "same-publisher", nil) s.mockSnap(c, producerYaml) + s.MockSnapDecl(c, "consumer", "same-publisher", nil) s.mockSnap(c, consumerYaml) // Create a connection reference, it's just verbose and used a few times @@ -3390,7 +3408,7 @@ slots: c.Check(secBackend.SetupCalls, HasLen, 2) } -// LP:#1825883; make sure static attributes in conns state are updated from the snap yaml on snap refresh (content interface only) +// LP:#1825883; make sure static attributes in conns state are updated from the snap yaml on snap refresh func (s *interfaceManagerSuite) testDoSetupProfilesUpdatesStaticAttributes(c *C, snapNameToSetup string) { // Put a connection in the state. The connection binds the two snaps we are // adding below. The connection reflects the snaps as they are now, and @@ -3403,6 +3421,9 @@ func (s *interfaceManagerSuite) testDoSetupProfilesUpdatesStaticAttributes(c *C, "consumer:plug3 producer:slot2": map[string]interface{}{ "interface": "system-files", }, + "consumer:plug4 producer:slot3": map[string]interface{}{ + "interface": "shared-memory", + }, "unrelated-a:plug unrelated-b:slot": map[string]interface{}{ "interface": "unrelated", "plug-static": map[string]interface{}{"attr": "unrelated-stale"}, @@ -3426,6 +3447,9 @@ plugs: content: bar plug3: interface: system-files + plug4: + interface: shared-memory + shared-memory: baz ` const producerV1Yaml = ` name: producer @@ -3436,6 +3460,11 @@ slots: content: foo slot2: interface: system-files + slot3: + interface: shared-memory + shared-memory: baz + read: + - baz ` const consumerV2Yaml = ` name: consumer @@ -3453,6 +3482,9 @@ plugs: interface: system-files read: - /etc/foo + plug4: + interface: shared-memory + shared-memory: baz ` const producerV2Yaml = ` name: producer @@ -3464,6 +3496,12 @@ slots: attr: slot-value slot2: interface: system-files + slot3: + interface: shared-memory + shared-memory: baz + read: + - baz + - qux ` const unrelatedAYaml = ` @@ -3504,6 +3542,9 @@ slots: sysFilesConnRef := &interfaces.ConnRef{ PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug3"}, SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot2"}} + shmConnRef := &interfaces.ConnRef{ + PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug4"}, + SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot3"}} // Add a test security backend and a test interface. We want to use them to // observe the interaction with the security backend and to allow the @@ -3519,11 +3560,15 @@ slots: c.Assert(err, IsNil) sysFilesConn, err2 := repo.Connection(sysFilesConnRef) c.Assert(err2, IsNil) + shmConn, err3 := repo.Connection(shmConnRef) + c.Assert(err3, IsNil) switch appSet.Info().Version { case "1": c.Check(conn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{"content": "foo"}) c.Check(conn.Slot.StaticAttrs(), DeepEquals, map[string]interface{}{"content": "foo"}) c.Check(sysFilesConn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{}) + c.Check(shmConn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{"shared-memory": "baz"}) + c.Check(shmConn.Slot.StaticAttrs(), DeepEquals, map[string]interface{}{"shared-memory": "baz", "read": []interface{}{"baz"}}) case "2": switch snapNameToSetup { case "consumer": @@ -3531,10 +3576,14 @@ slots: c.Check(conn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{"content": "foo", "attr": "plug-value"}) c.Check(conn.Slot.StaticAttrs(), DeepEquals, map[string]interface{}{"content": "foo"}) c.Check(sysFilesConn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{"read": []interface{}{"/etc/foo"}}) + c.Check(shmConn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{"shared-memory": "baz"}) + c.Check(shmConn.Slot.StaticAttrs(), DeepEquals, map[string]interface{}{"shared-memory": "baz", "read": []interface{}{"baz"}}) case "producer": // When the producer has security setup the producer's slot attribute is updated. c.Check(conn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{"content": "foo"}) c.Check(conn.Slot.StaticAttrs(), DeepEquals, map[string]interface{}{"content": "foo", "attr": "slot-value"}) + c.Check(shmConn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{"shared-memory": "baz"}) + c.Check(shmConn.Slot.StaticAttrs(), DeepEquals, map[string]interface{}{"shared-memory": "baz", "read": []interface{}{"baz", "qux"}}) } } return nil @@ -3601,6 +3650,7 @@ func (s *interfaceManagerSuite) TestUpdateStaticAttributesIgnoresContentMismatch s.state.Set("conns", map[string]interface{}{ "consumer:plug producer:slot": map[string]interface{}{ "interface": "content", + "auto": true, "content": "foo", }, }) @@ -3648,7 +3698,9 @@ slots: // NOTE: s.mockSnap sets the state and calls MockSnapInstance internally, // which puts the snap on disk. This gives us all four YAMLs on disk and // just the first version of both in the state. + s.MockSnapDecl(c, "producer", "same-publisher", nil) s.mockSnap(c, producerV1Yaml) + s.MockSnapDecl(c, "consumer", "same-publisher", nil) s.mockSnap(c, consumerV1Yaml) snaptest.MockSnapInstance(c, "", consumerV2Yaml, &snap.SideInfo{Revision: snap.R(2)}) snaptest.MockSnapInstance(c, "", producerV2Yaml, &snap.SideInfo{Revision: snap.R(2)}) @@ -3693,12 +3745,221 @@ slots: c.Check(conns, DeepEquals, map[string]interface{}{ "consumer:plug producer:slot": map[string]interface{}{ "interface": "content", + "auto": true, "plug-static": map[string]interface{}{"content": "foo"}, "slot-static": map[string]interface{}{"content": "foo"}, }, }) } +func (s *interfaceManagerSuite) testUpdateStaticAttributesRespectsSnapDeclaration(c *C, auto, byGadget bool, testUpdateVal string, shouldUpdate bool) { + // allow nothing in the base decl, as we'll later allow certain carve-outs + // in the snap decl + restore := assertstest.MockBuiltinBaseDeclaration([]byte(` +type: base-declaration +authority-id: canonical +series: 16 +slots: + test: + allow-auto-connection: false + allow-connection: false +`)) + defer restore() + + restore = builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"}) + defer restore() + + initialConns := map[string]interface{}{ + "test-consumer:test test-producer:test": map[string]interface{}{ + "interface": "test", + "test-update": "foo", + }, + } + if auto { + initialConns["test-consumer:test test-producer:test"].(map[string]interface{})["auto"] = true + } + if byGadget { + initialConns["test-consumer:test test-producer:test"].(map[string]interface{})["by-gadget"] = true + } + + s.state.Lock() + s.state.Set("conns", initialConns) + s.state.Unlock() + + const consumerV1Yaml = ` +name: test-consumer +version: 1 +plugs: + test: + interface: test + test-update: foo +` + consumerV2Yaml := ` +name: test-consumer +version: 2 +plugs: + test: + interface: test + test-update: ` + testUpdateVal + ` +` + + const producerYaml = ` +name: test-producer +version: 1 +slots: + test: + interface: test +` + + s.MockSnapDecl(c, "test-consumer", "publisher-foo", map[string]interface{}{ + "format": "5", + "plugs": map[string]interface{}{ + "test": map[string]interface{}{ + "allow-auto-connection": map[string]interface{}{ + "plug-attributes": map[string]interface{}{ + "test-update": []interface{}{ + "auto", + "foo", + }, + }, + }, + "allow-connection": map[string]interface{}{ + "plug-attributes": map[string]interface{}{ + "test-update": []interface{}{ + "auto", + "manual", + "foo", + }, + }, + }, + "allow-installation": "true", + }, + }, + }) + + s.MockSnapDecl(c, "test-producer", "publisher-foo", map[string]interface{}{ + "format": "5", + "slots": map[string]interface{}{ + "test": map[string]interface{}{ + "allow-installation": "true", + }, + }, + }) + // NOTE: s.mockSnap sets the state and calls MockSnapInstance internally, + // which puts the snap on disk. This gives us both yamls on disk and + // just the first version in the state. + snapInfo := s.mockSnap(c, consumerV1Yaml) + snaptest.MockSnapInstance(c, "", consumerV2Yaml, &snap.SideInfo{Revision: snap.R(2)}) + + s.mockSnap(c, producerYaml) + + secBackend := &ifacetest.TestSecurityBackend{BackendName: "test"} + s.mockSecBackend(secBackend) + + // Create the interface manager. This indirectly adds the snaps to the + // repository and reloads the connection. + s.manager(c) + + // Alter the state of the test snap to get a new revision. + s.state.Lock() + snapstate.Set(s.state, "test-consumer", &snapstate.SnapState{ + Active: true, + Sequence: snapstatetest.NewSequenceFromSnapSideInfos([]*snap.SideInfo{{Revision: snap.R(1)}, {Revision: snap.R(2)}}), + Current: snap.R(2), + SnapType: string("app"), + }) + s.state.Unlock() + + s.state.Lock() + change := s.state.NewChange("test", "") + task := s.state.NewTask("setup-profiles", "") + task.Set("snap-setup", &snapstate.SnapSetup{ + SideInfo: &snap.SideInfo{RealName: "test-consumer", Revision: snap.R(2), SnapID: snapInfo.SnapID}}) + change.AddTask(task) + s.state.Unlock() + + s.settle(c) + s.state.Lock() + defer s.state.Unlock() + c.Assert(change.Status(), Equals, state.DoneStatus) + + var conns map[string]interface{} + s.state.Get("conns", &conns) + + expectedConns := map[string]interface{}{ + "test-consumer:test test-producer:test": map[string]interface{}{ + "interface": "test", + "plug-static": map[string]interface{}{"test-update": "foo"}, + }, + } + if auto { + expectedConns["test-consumer:test test-producer:test"].(map[string]interface{})["auto"] = true + } + if byGadget { + expectedConns["test-consumer:test test-producer:test"].(map[string]interface{})["by-gadget"] = true + } + if shouldUpdate { + expectedConns["test-consumer:test test-producer:test"].(map[string]interface{})["plug-static"].(map[string]interface{})["test-update"] = testUpdateVal + } + c.Check(conns, DeepEquals, expectedConns) +} + +func (s *interfaceManagerSuite) TestUpdateStaticAttributesRespectsSnapDeclarationAutoHappy(c *C) { + const auto = true + const byGadget = false + const testUpdateVal = "auto" + const shouldUpdate = true + s.testUpdateStaticAttributesRespectsSnapDeclaration(c, auto, byGadget, testUpdateVal, shouldUpdate) +} + +func (s *interfaceManagerSuite) TestUpdateStaticAttributesRespectsSnapDeclarationAutoOnlyAllowManual(c *C) { + const auto = true + const byGadget = false + const testUpdateVal = "manual" + const shouldUpdate = false + s.testUpdateStaticAttributesRespectsSnapDeclaration(c, auto, byGadget, testUpdateVal, shouldUpdate) +} + +func (s *interfaceManagerSuite) TestUpdateStaticAttributesRespectsSnapDeclarationAutoNonsense(c *C) { + const auto = true + const byGadget = false + const testUpdateVal = "bar" + const shouldUpdate = false + s.testUpdateStaticAttributesRespectsSnapDeclaration(c, auto, byGadget, testUpdateVal, shouldUpdate) +} + +func (s *interfaceManagerSuite) TestUpdateStaticAttributesRespectsSnapDeclarationManualHappy(c *C) { + const auto = false + const byGadget = false + const testUpdateVal = "manual" + const shouldUpdate = true + s.testUpdateStaticAttributesRespectsSnapDeclaration(c, auto, byGadget, testUpdateVal, shouldUpdate) +} + +func (s *interfaceManagerSuite) TestUpdateStaticAttributesRespectsSnapDeclarationManualNonsense(c *C) { + const auto = false + const byGadget = false + const testUpdateVal = "bar" + const shouldUpdate = false + s.testUpdateStaticAttributesRespectsSnapDeclaration(c, auto, byGadget, testUpdateVal, shouldUpdate) +} + +func (s *interfaceManagerSuite) TestUpdateStaticAttributesRespectsSnapDeclarationGadgetHappy(c *C) { + const auto = false + const byGadget = false + const testUpdateVal = "manual" + const shouldUpdate = true + s.testUpdateStaticAttributesRespectsSnapDeclaration(c, auto, byGadget, testUpdateVal, shouldUpdate) +} + +func (s *interfaceManagerSuite) TestUpdateStaticAttributesRespectsSnapDeclarationGadgetNonsense(c *C) { + const auto = false + const byGadget = false + const testUpdateVal = "bar" + const shouldUpdate = false + s.testUpdateStaticAttributesRespectsSnapDeclaration(c, auto, byGadget, testUpdateVal, shouldUpdate) +} + func (s *interfaceManagerSuite) TestDoSetupSnapSecurityIgnoresStrayConnection(c *C) { s.MockModel(c, nil) @@ -5083,6 +5344,7 @@ plugs: "consumer:plug core:hotplug-slot": map[string]interface{}{ "interface": "test", "hotplug-gone": true, + "plug-static": map[string]interface{}{"attr": "plug-attr"}, }, "consumer:plug core:slot2": map[string]interface{}{ "interface": "test", @@ -5194,13 +5456,16 @@ slots: content: foo attr: slot-value ` + s.MockSnapDecl(c, "consumer", "same-publisher", nil) s.mockSnap(c, consumerYaml) + s.MockSnapDecl(c, "producer", "same-publisher", nil) s.mockSnap(c, producerYaml) s.state.Lock() s.state.Set("conns", map[string]interface{}{ "consumer:plug producer:slot": map[string]interface{}{ "interface": "content", + "auto": true, "plug-static": map[string]interface{}{ "content": "foo", "attr": "stored-plug-value", @@ -5273,7 +5538,7 @@ func (s *interfaceManagerSuite) setupHotplugSlot(c *C) { }}) } -func (s *interfaceManagerSuite) TestManagerDoesntReloadHotlugGoneConnection(c *C) { +func (s *interfaceManagerSuite) TestManagerDoesntReloadHotplugGoneConnection(c *C) { s.setupHotplugSlot(c) s.state.Lock() @@ -5288,7 +5553,7 @@ func (s *interfaceManagerSuite) TestManagerDoesntReloadHotlugGoneConnection(c *C c.Assert(mgr.Repository().Interfaces().Connections, HasLen, 0) } -func (s *interfaceManagerSuite) TestManagerReloadsHotlugConnection(c *C) { +func (s *interfaceManagerSuite) TestManagerReloadsHotplugConnection(c *C) { s.setupHotplugSlot(c) s.state.Lock() @@ -9546,18 +9811,18 @@ func (s *interfaceManagerSuite) TestFirstTaskAfterBootWhenPreseeding(c *C) { chg := st.NewChange("change", "") setupTask := st.NewTask("some-task", "") - setupTask.Set("snap-setup", &snapstate.SnapSetup{SideInfo: &snap.SideInfo{RealName: "test-snap"}}) + setupTask.Set("snap-setup", &snapstate.SnapSetup{SideInfo: &snap.SideInfo{RealName: "test-consumer"}}) chg.AddTask(setupTask) markPreseeded := st.NewTask("fake-mark-preseeded", "") markPreseeded.WaitFor(setupTask) - _, err := ifacestate.FirstTaskAfterBootWhenPreseeding("test-snap", markPreseeded) + _, err := ifacestate.FirstTaskAfterBootWhenPreseeding("test-consumer", markPreseeded) c.Check(err, ErrorMatches, `internal error: fake-mark-preseeded task not in change`) chg.AddTask(markPreseeded) - _, err = ifacestate.FirstTaskAfterBootWhenPreseeding("test-snap", markPreseeded) - c.Check(err, ErrorMatches, `internal error: cannot find install hook for snap "test-snap"`) + _, err = ifacestate.FirstTaskAfterBootWhenPreseeding("test-consumer", markPreseeded) + c.Check(err, ErrorMatches, `internal error: cannot find install hook for snap "test-consumer"`) // install hook of another snap task1 := st.NewTask("run-hook", "") @@ -9565,16 +9830,16 @@ func (s *interfaceManagerSuite) TestFirstTaskAfterBootWhenPreseeding(c *C) { task1.Set("hook-setup", &hsup) task1.WaitFor(markPreseeded) chg.AddTask(task1) - _, err = ifacestate.FirstTaskAfterBootWhenPreseeding("test-snap", markPreseeded) - c.Check(err, ErrorMatches, `internal error: cannot find install hook for snap "test-snap"`) + _, err = ifacestate.FirstTaskAfterBootWhenPreseeding("test-consumer", markPreseeded) + c.Check(err, ErrorMatches, `internal error: cannot find install hook for snap "test-consumer"`) // add install hook for the correct snap task2 := st.NewTask("run-hook", "") - hsup = hookstate.HookSetup{Hook: "install", Snap: "test-snap"} + hsup = hookstate.HookSetup{Hook: "install", Snap: "test-consumer"} task2.Set("hook-setup", &hsup) task2.WaitFor(markPreseeded) chg.AddTask(task2) - hooktask, err := ifacestate.FirstTaskAfterBootWhenPreseeding("test-snap", markPreseeded) + hooktask, err := ifacestate.FirstTaskAfterBootWhenPreseeding("test-consumer", markPreseeded) c.Assert(err, IsNil) c.Check(hooktask.ID(), Equals, task2.ID()) } diff --git a/overlord/managers_test.go b/overlord/managers_test.go index 0a5b0e5cd38..f96578b38c9 100644 --- a/overlord/managers_test.go +++ b/overlord/managers_test.go @@ -14555,6 +14555,9 @@ func (s *mgrsSuite) testConnectionDurabilityDuringRefreshesAndAutoRefresh(c *C, "snap-with-snapd-control:snapd-control core:snapd-control": map[string]interface{}{ "interface": "snapd-control", "auto": true, + "plug-static": map[string]interface{}{ + "refresh-schedule": "managed", + }, }, }) @@ -14590,6 +14593,9 @@ func (s *mgrsSuite) testConnectionDurabilityDuringRefreshesAndAutoRefresh(c *C, "snap-with-snapd-control:snapd-control core:snapd-control": map[string]interface{}{ "interface": "snapd-control", "auto": true, + "plug-static": map[string]interface{}{ + "refresh-schedule": "managed", + }, }, }) diff --git a/tests/main/interface-static-attrs-update-on-refresh/task.yaml b/tests/main/interface-static-attrs-update-on-refresh/task.yaml new file mode 100644 index 00000000000..d3ea2adaa17 --- /dev/null +++ b/tests/main/interface-static-attrs-update-on-refresh/task.yaml @@ -0,0 +1,106 @@ +summary: check that security profiles get regenerated on refresh + +details: | + This tests that a refresh regenerates AppArmor profiles, even if the set of + slots/plugs hasn't changed. + +# the test relies on a writable rootfs, as we want to use a nonsense path so that +# changes to the base profile or contents of the core snap will never break the test +systems: [-ubuntu-core-*] + +prepare: | + "$TESTSTOOLS"/snaps-state install-local test-snap-a-v1 + "$TESTSTOOLS"/snaps-state install-local test-snap-b + echo "Etc Foo!" > /etc/foo + echo "Etc Bar!" > /etc/bar + echo "Default Shm Foo!" > /dev/shm/foo + echo "Default Shm Bar!" > /dev/shm/bar + tests.cleanup defer rm -rf /etc/foo /etc/bar + +execute: | + if [ "$(snap debug confinement)" = partial ] ; then + exit 0 + fi + + snap info test-snap-a | MATCH "^installed: +version1" + + echo "test-snap-a reading /etc/foo, /etc/bar should fail." + if test-snap-a.sh -c 'cat /etc/foo'; then + echo "Expected error from test-snap-a reading /etc/foo" + exit 1 + fi + if test-snap-a.sh -c 'cat /etc/bar'; then + echo "Expected error from test-snap-a reading /etc/bar" + exit 1 + fi + + echo "test-snap-a writing /dev/shm/foo, /dev/shm/bar should fail." + if test-snap-a.sh -c 'echo "Shm Foo!" > /dev/shm/foo'; then + echo "Expected error from test-snap-a writing /dev/shm/foo" + exit 1 + fi + if test-snap-a.sh -c 'echo "Shm Bar!" > /dev/shm/bar'; then + echo "Expected error from test-snap-a writing /dev/shm/bar" + exit 1 + fi + + echo "test-snap-b reading /dev/shm/foo, /dev/shm/bar should fail." + if test-snap-b.sh -c 'cat /dev/shm/foo'; then + echo "Expected error from test-snap-b reading /dev/shm/foo" + exit 1 + fi + if test-snap-b.sh -c 'cat /dev/shm/bar'; then + echo "Expected error from test-snap-a writing /dev/shm/bar" + exit 1 + fi + + echo "Connecting interfaces..." + snap connect test-snap-a:etc-foo-bar + snap connections test-snap-a | MATCH "^system-files +test-snap-a:etc-foo-bar +:system-files +manual" + snap connect test-snap-b:shm-foo-bar test-snap-a:shm-foo-bar + snap connections test-snap-a | MATCH "^shared-memory +test-snap-b:shm-foo-bar +test-snap-a:shm-foo-bar +manual" + + echo "test-snap-a reading /etc/foo should succeed." + test-snap-a.sh -c 'cat /etc/foo' + + echo "test-snap-a reading /etc/bar should fail." + if test-snap-a.sh -c 'cat /etc/bar'; then + echo "Expected error from test-snap-a reading /etc/bar" + exit 1 + fi + + echo "test-snap-a writing /dev/shm/foo should succeed." + test-snap-a.sh -c 'echo "Shm Foo!" > /dev/shm/foo' + + echo "test-snap-a writing /dev/shm/bar should fail." + if test-snap-a.sh -c 'echo "Shm Bar!" > /dev/shm/bar'; then + echo "Expected error from test-snap-a writing /dev/shm/bar" + exit 1 + fi + + echo "test-snap-b reading /dev/shm/foo should succeed." + test-snap-b.sh -c 'cat /dev/shm/foo' | MATCH "^Shm Foo!" + + echo "test-snap-b reading /dev/shm/bar should fail." + if test-snap-b.sh -c 'cat /dev/shm/bar'; then + echo "Expected error from test-snap-b reading /dev/shm/bar" + exit 1 + fi + + echo "Refreshing test-snap-a to v2..." + "$TESTSTOOLS"/snaps-state install-local test-snap-a-v2 + snap info test-snap-a | MATCH "^installed: +version2" + snap connections test-snap-a | MATCH "^system-files +test-snap-a:etc-foo-bar +:system-files +manual" + snap connections test-snap-a | MATCH "^shared-memory +test-snap-b:shm-foo-bar +test-snap-a:shm-foo-bar +manual" + + echo "test-snap-a reading /etc/foo, /etc/bar should succeed." + test-snap-a.sh -c 'cat /etc/foo' + test-snap-a.sh -c 'cat /etc/bar' + + echo "test-snap-a writing /dev/shm/foo, /dev/shm/bar should succeed." + test-snap-a.sh -c 'echo "Shm Foo!" > /dev/shm/foo' + test-snap-a.sh -c 'echo "Shm Bar!" > /dev/shm/bar' + + echo "test-snap-b reading /dev/shm/foo, /dev/shm/bar should succeed." + test-snap-b.sh -c 'cat /dev/shm/foo' | MATCH "^Shm Foo!" + test-snap-b.sh -c 'cat /dev/shm/bar' | MATCH "^Shm Bar!" diff --git a/tests/main/interface-static-attrs-update-on-refresh/test-snap-a-v1/bin/sh b/tests/main/interface-static-attrs-update-on-refresh/test-snap-a-v1/bin/sh new file mode 100755 index 00000000000..0f845e07c5a --- /dev/null +++ b/tests/main/interface-static-attrs-update-on-refresh/test-snap-a-v1/bin/sh @@ -0,0 +1,3 @@ +#!/bin/sh +PS1='$ ' +exec /bin/sh "$@" diff --git a/tests/main/interface-static-attrs-update-on-refresh/test-snap-a-v1/meta/snap.yaml b/tests/main/interface-static-attrs-update-on-refresh/test-snap-a-v1/meta/snap.yaml new file mode 100644 index 00000000000..277b85e8a73 --- /dev/null +++ b/tests/main/interface-static-attrs-update-on-refresh/test-snap-a-v1/meta/snap.yaml @@ -0,0 +1,21 @@ +name: test-snap-a +version: version1 + +apps: + sh: + command: bin/sh + plugs: + - etc-foo-bar + +plugs: + etc-foo-bar: + interface: system-files + read: + - /etc/foo + +slots: + shm-foo-bar: + interface: shared-memory + shared-memory: shm-foo-bar + read: + - foo diff --git a/tests/main/interface-static-attrs-update-on-refresh/test-snap-a-v2/bin/sh b/tests/main/interface-static-attrs-update-on-refresh/test-snap-a-v2/bin/sh new file mode 100755 index 00000000000..0f845e07c5a --- /dev/null +++ b/tests/main/interface-static-attrs-update-on-refresh/test-snap-a-v2/bin/sh @@ -0,0 +1,3 @@ +#!/bin/sh +PS1='$ ' +exec /bin/sh "$@" diff --git a/tests/main/interface-static-attrs-update-on-refresh/test-snap-a-v2/meta/snap.yaml b/tests/main/interface-static-attrs-update-on-refresh/test-snap-a-v2/meta/snap.yaml new file mode 100644 index 00000000000..0ac6e4ef0ae --- /dev/null +++ b/tests/main/interface-static-attrs-update-on-refresh/test-snap-a-v2/meta/snap.yaml @@ -0,0 +1,23 @@ +name: test-snap-a +version: version2 + +apps: + sh: + command: bin/sh + plugs: + - etc-foo-bar + +plugs: + etc-foo-bar: + interface: system-files + read: + - /etc/foo + - /etc/bar + +slots: + shm-foo-bar: + interface: shared-memory + shared-memory: shm-foo-bar + read: + - foo + - bar diff --git a/tests/main/interface-static-attrs-update-on-refresh/test-snap-b/bin/sh b/tests/main/interface-static-attrs-update-on-refresh/test-snap-b/bin/sh new file mode 100755 index 00000000000..0f845e07c5a --- /dev/null +++ b/tests/main/interface-static-attrs-update-on-refresh/test-snap-b/bin/sh @@ -0,0 +1,3 @@ +#!/bin/sh +PS1='$ ' +exec /bin/sh "$@" diff --git a/tests/main/interface-static-attrs-update-on-refresh/test-snap-b/meta/snap.yaml b/tests/main/interface-static-attrs-update-on-refresh/test-snap-b/meta/snap.yaml new file mode 100644 index 00000000000..39e41d3dcb0 --- /dev/null +++ b/tests/main/interface-static-attrs-update-on-refresh/test-snap-b/meta/snap.yaml @@ -0,0 +1,13 @@ +name: test-snap-b +version: version1 + +apps: + sh: + command: bin/sh + plugs: + - shm-foo-bar + +plugs: + shm-foo-bar: + interface: shared-memory + shared-memory: shm-foo-bar diff --git a/tests/main/upgrade-from-2.15/task.yaml b/tests/main/upgrade-from-2.15/task.yaml deleted file mode 100644 index f0656747e06..00000000000 --- a/tests/main/upgrade-from-2.15/task.yaml +++ /dev/null @@ -1,83 +0,0 @@ -summary: Ensure upgrades from snapd 2.15 work - -details: | - The test ensures that users starting with that snapd 2.15 can successfully - update to a locally built Debian package, and end up with functioning snapd. - This checks the transition of so-called conf-files handling application - permission profiles for snap-confine, which could end up outdated if the - version from snapd 2.15 is retained after the update. - -systems: [ubuntu-16.04-64] - -prepare: | - #shellcheck source=tests/lib/pkgdb.sh - . "$TESTSLIB/pkgdb.sh" - distro_purge_package snapd - -restore: | - #shellcheck source=tests/lib/pkgdb.sh - . "$TESTSLIB/pkgdb.sh" - if [ -e old_install_done ]; then - echo "Ensure core is gone and we have ubuntu-core instead" - distro_purge_package snapd - fi - distro_install_build_snapd - - # snap-confine, in the old version of snapd, leaves behind junk in - # /tmp/snap.rootfs_* This is now detected by invariant detector, which uses - # it as a sign of possible snap-confine crash. To avoid the false positive, - # remove those leftovers. - rm -rf /tmp/snap.rootfs_* - - # Remove file with wget history - rm -f /root/.wget-hsts - -execute: | - echo "download snapd 2.15.2ubuntu1 and the matching ubuntu-core-launcher" - wget https://launchpad.net/ubuntu/+source/snap-confine/1.0.38-0ubuntu0.16.04.8/+build/10606388/+files/ubuntu-core-launcher_1.0.38-0ubuntu0.16.04.8_amd64.deb - wget https://launchpad.net/ubuntu/+source/snap-confine/1.0.38-0ubuntu0.16.04.8/+build/10606388/+files/snap-confine_1.0.38-0ubuntu0.16.04.8_amd64.deb - wget https://launchpad.net/ubuntu/+source/snapd/2.15.2ubuntu1/+build/10939171/+files/snapd_2.15.2ubuntu1_amd64.deb - echo "Install snapd 2.15.2" - apt install -y ./ubuntu-core-launcher_1.0.38-0ubuntu0.16.04.8_amd64.deb ./snap-confine_1.0.38-0ubuntu0.16.04.8_amd64.deb ./snapd_2.15.2ubuntu1_amd64.deb - - echo "Installation completed" - touch old_install_done - - echo "install a service snap and check its active" - snap install go-example-webserver - - # google ships 4.15 in (some?) of their cloud instances for 16.04 - if [[ "$(uname -r)" != 4.4.* ]]; then - # snapd version 2.15 will not work with kernels newer than - # 4.4 because later kernels require snap-exec to be mmapable - # add this missing rule here - sed -i 's#^}$#/usr/lib/snapd/snap-exec m,\n}#' /var/lib/snapd/apparmor/profiles/snap.go-example-webserver.webserver - apparmor_parser -r /var/lib/snapd/apparmor/profiles/snap.go-example-webserver.webserver - systemctl start snap.go-example-webserver.webserver - fi - tests.systemd wait-for-service -n 30 --state active snap.go-example-webserver.webserver.service - - echo "Install a test snap" - snap install test-snapd-sh - - echo "upgrade to current snapd" - apt install -y "$GOHOME"/snapd*.deb - - echo "and ensure the snap service is still active" - tests.systemd wait-for-service -n 30 --state active snap.go-example-webserver.webserver.service - - echo "wait for ubuntu-core->core transition" - snap debug ensure-state-soon - retry -n 240 --wait 1 sh -c 'snap changes | MATCH ".*Done.*Transition ubuntu-core to core"' - - echo "check that snap confine profiles are fine after upgrade" - test -f /etc/apparmor.d/usr.lib.snapd.snap-confine.real - test "$(find /var/lib/snapd/apparmor/profiles/ -name "snap-confine.core.*" | wc -l)" -eq 1 - MATCH "^/usr/lib/snapd/snap-confine \\(enforce\\)" < /sys/kernel/security/apparmor/profiles - MATCH "^/snap/core/.*/usr/lib/snapd/snap-confine \\(enforce\\)" < /sys/kernel/security/apparmor/profiles - - echo "ensure that the old/obsolete snap-confine appamor profile got removed" - test ! -f /etc/apparmor.d/usr.lib.snapd.snap-confine - - echo "Smoke test, this should fail if profiles were wrong" - test-snapd-sh.sh -c 'echo hello' | MATCH hello