Merge lp:~mvo/snappy/snappy-lp1460152-workaround into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by Michael Vogt
Status: Work in progress
Proposed branch: lp:~mvo/snappy/snappy-lp1460152-workaround
Merge into: lp:~snappy-dev/snappy/snappy-moved-to-github
Diff against target: 157 lines (+78/-6)
5 files modified
helpers/helpers.go (+17/-0)
helpers/helpers_test.go (+11/-1)
snappy/dirs.go (+4/-0)
snappy/systemimage.go (+16/-5)
snappy/systemimage_test.go (+30/-0)
To merge this branch: bzr merge lp:~mvo/snappy/snappy-lp1460152-workaround
Reviewer Review Type Date Requested Status
Ricardo Salveti (community) Needs Information
Review via email: mp+261144@code.launchpad.net

Commit message

This branch adds a workaround for LP: #1460152 to prevent that the
apparmor profile and cache get out of sync.

Description of the change

This branch adds a workaround for LP: #1460152 to prevent that the
apparmor profile and cache get out of sync.

To post a comment you must log in.
Revision history for this message
Sergio Schvezov (sergiusens) wrote :

I linked the bug, we can add a task for a proper implementation.

Revision history for this message
Ricardo Salveti (rsalveti) wrote :

Wouldn't this only be triggered after doing a second update? The major problem we have is that we're currently over a stable/older image, and we want to update to edge, and make sure that the new cache is not broken on the next reboot, but we're still updating to edge using the tools available at stable (not using the extra logic you added here).

So the workaround would need to be either something triggered by the update process coming from stable, or something done as part of the first boot after updating the image.

review: Needs Information
Revision history for this message
Sergio Schvezov (sergiusens) wrote :

Tears aside ;-) this is lacking some sort of systemd unit to trigger apparmor cache cleaning on boot (we can probably leverage the one apparmor has itself).

Revision history for this message
Michael Vogt (mvo) wrote :

@Ricardo: Indeed you are right :/ Sorry for overlooking this, at some point we need to add the ability for the upgrader to upgrade itself. So the best way seems to be going a systemd unit route (as Sergio suggested).

@Sergio: I pushed an alternative solution based on systemd to http://bazaar.launchpad.net/~mvo/ubuntu/vivid/ubuntu-core-config/lp1460152-workaround/revision/28 and then http://bazaar.launchpad.net/~mvo/ubuntu/vivid/ubuntu-core-config/lp1460152-workaround/revision/29. My original mtime based approach did not work, the md5sum one looks promising though.

The full diff is here:
https://code.launchpad.net/~mvo/ubuntu/vivid/ubuntu-core-config/lp1460152-workaround/+merge/261179

If the approach looks good this needs to go to the ppa and get a real-world test (i.e. 15.04 stable->edge upgrade). Please keep me updated via mail or telegram as I won't be on irc today.

Unmerged revisions

486. By Michael Vogt

Remove /etc/apparmor.d/cache/* on upgrade to workaround lp1460152

This works around the issue that the way apparmor creates the cache
is based on the mtime of the profile. So if the mtime of the profile
is older than the mtime of the cache file the cache is not re-generated.

This is a problem because:
- boot stable, /etc/apparmor.d/cache/usr.bin.ubuntu-core-launcher is mtime of now because we generate the cache on boot
- upgrade to edge, /etc/apparmor.d/usr.bin.ubuntu-core-launcher is updated and has the mtime of T (yesterday) when the file was put into the package
- on the next reboot the apparmor_parser compares the mtime of the cache/usr.bin.ubuntu-core-launcher (very very recent) with the mtime of the souce usr.bin.ubuntu-core-launcher (much older)
-> cache does is *not* re-generate

The real fix is IMO that apparmor adds the mtime of the profile into
the header of the cache file (or makes the mtime of the cache file)
the mtime of the profile and re-generated if they get out of sync
(instead of checking for newer).

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'helpers/helpers.go'
2--- helpers/helpers.go 2015-06-04 12:55:54 +0000
3+++ helpers/helpers.go 2015-06-04 20:40:35 +0000
4@@ -26,6 +26,7 @@
5 "encoding/hex"
6 "fmt"
7 "io"
8+ "io/ioutil"
9 "math/rand"
10 "os"
11 "os/exec"
12@@ -436,3 +437,19 @@
13 "SNAPP_APP_USER_DATA_PATH={{.Home}}{{.AppPath}}",
14 })
15 }
16+
17+// CleanDir cleans all regular files in the given directory
18+func CleanDir(dir string) error {
19+ dents, err := ioutil.ReadDir(dir)
20+ if err != nil {
21+ return err
22+ }
23+ for _, dent := range dents {
24+ if dent.Mode().IsRegular() {
25+ if err := os.Remove(filepath.Join(dir, dent.Name())); err != nil {
26+ return err
27+ }
28+ }
29+ }
30+ return nil
31+}
32
33=== modified file 'helpers/helpers_test.go'
34--- helpers/helpers_test.go 2015-06-04 12:55:54 +0000
35+++ helpers/helpers_test.go 2015-06-04 20:40:35 +0000
36@@ -326,5 +326,15 @@
37 err = UnpackTar(f, c.MkDir(), nil)
38 c.Assert(err, IsNil)
39 c.Assert(mknodWasCalled, Equals, true)
40-
41+}
42+
43+func (ts *HTestSuite) TestCleanDir(c *C) {
44+ d := c.MkDir()
45+ canary := filepath.Join(d, "foo")
46+ err := ioutil.WriteFile(canary, []byte(nil), 0644)
47+ c.Assert(err, IsNil)
48+
49+ err = CleanDir(d)
50+ c.Assert(err, IsNil)
51+ c.Assert(FileExists(canary), Equals, false)
52 }
53
54=== modified file 'snappy/dirs.go'
55--- snappy/dirs.go 2015-05-15 13:33:27 +0000
56+++ snappy/dirs.go 2015-06-04 20:40:35 +0000
57@@ -33,6 +33,8 @@
58 snapSeccompDir string
59 snapUdevRulesDir string
60
61+ systemApparmorProfileCacheDir string
62+
63 snapBinariesDir string
64 snapServicesDir string
65 snapBusPolicyDir string
66@@ -62,4 +64,6 @@
67 cloudMetaDataFile = filepath.Join(rootdir, "/var/lib/cloud/seed/nocloud-net/meta-data")
68
69 snapUdevRulesDir = filepath.Join(rootdir, "/etc/udev/rules.d")
70+
71+ systemApparmorProfileCacheDir = filepath.Join(rootdir, "/etc/apparmor.d/cache")
72 }
73
74=== modified file 'snappy/systemimage.go'
75--- snappy/systemimage.go 2015-05-20 17:24:29 +0000
76+++ snappy/systemimage.go 2015-06-04 20:40:35 +0000
77@@ -222,16 +222,27 @@
78 }
79
80 // Check that the final system state is as expected.
81- if err = s.verifyUpgradeWasApplied(); err != nil {
82- return "", err
83- }
84-
85- if err = s.partition.ToggleNextBoot(); err != nil {
86+ if err := s.verifyUpgradeWasApplied(); err != nil {
87+ return "", err
88+ }
89+
90+ if err := s.postUpgradeHacks(); err != nil {
91+ return "", err
92+ }
93+
94+ if err := s.partition.ToggleNextBoot(); err != nil {
95 return "", err
96 }
97 return systemImagePartName, nil
98 }
99
100+// this makes me cry
101+func (s *SystemImagePart) postUpgradeHacks() error {
102+ // Apply HACK to deal with bug #1460152 where the apparmor
103+ // cache is confused after the upgrade
104+ return helpers.CleanDir(systemApparmorProfileCacheDir)
105+}
106+
107 // Ensure the expected version update was applied to the expected partition.
108 func (s *SystemImagePart) verifyUpgradeWasApplied() error {
109 // The upgrade has now been applied, so check that the expected
110
111=== modified file 'snappy/systemimage_test.go'
112--- snappy/systemimage_test.go 2015-06-02 20:53:10 +0000
113+++ snappy/systemimage_test.go 2015-06-04 20:40:35 +0000
114@@ -58,6 +58,13 @@
115 // setup fake /other partition
116 makeFakeSystemImageChannelConfig(c, filepath.Join(tempdir, "other", systemImageChannelConfig), "2")
117
118+ // fake the apparmor.d/cache
119+ systemApparmorProfileCacheDir = filepath.Join(systemImageRoot, "etc", "apparmor.d", "cache")
120+ err := os.MkdirAll(systemApparmorProfileCacheDir, 0755)
121+ c.Assert(err, IsNil)
122+ err = ioutil.WriteFile(filepath.Join(systemApparmorProfileCacheDir, "some-cache"), []byte(nil), 0644)
123+ c.Assert(err, IsNil)
124+
125 // run test webserver instead of talking to the real one
126 //
127 // The mock webserver versions "1" and "2"
128@@ -192,6 +199,29 @@
129 c.Assert(pb.progress, DeepEquals, []float64{20.0, 40.0, 60.0, 80.0, 100.0})
130 }
131
132+func (s *SITestSuite) TestSystemImagePartInstalAppliesHackLp1460152(c *C) {
133+ // add a update
134+ mockSystemImageIndexJSON = fmt.Sprintf(mockSystemImageIndexJSONTemplate, "2")
135+ parts, err := s.systemImage.Updates()
136+
137+ sp := parts[0].(*SystemImagePart)
138+ mockPartition := MockPartition{}
139+ sp.partition = &mockPartition
140+
141+ dents, err := ioutil.ReadDir(systemApparmorProfileCacheDir)
142+ c.Assert(err, IsNil)
143+ c.Assert(dents, HasLen, 1)
144+
145+ pb := &MockProgressMeter{}
146+ // do the install
147+ _, err = sp.Install(pb, 0)
148+ c.Assert(err, IsNil)
149+
150+ dents, err = ioutil.ReadDir(systemApparmorProfileCacheDir)
151+ c.Assert(err, IsNil)
152+ c.Assert(dents, HasLen, 0)
153+}
154+
155 func (s *SITestSuite) TestSystemImagePartInstallUpdatesBroken(c *C) {
156 // fake a broken upgrade
157 scriptContent := `#!/bin/sh

Subscribers

People subscribed via source and target branches