Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
cmd/snap-update-ns: teach update logic to handle synthetic changes #4224
Conversation
codecov-io
commented
Nov 15, 2017
•
Codecov Report
@@ Coverage Diff @@
## master #4224 +/- ##
========================================
+ Coverage 76.09% 76.2% +0.1%
========================================
Files 442 444 +2
Lines 38675 38721 +46
========================================
+ Hits 29431 29508 +77
+ Misses 7222 7196 -26
+ Partials 2022 2017 -5
Continue to review full report at Codecov.
|
| + // Collect the IDs of desired changes. | ||
| + // We need that below to keep implicit changes from the current profile. | ||
| + for i := range desired { | ||
| + desiredIDs[XSnapdEntryID(&desired[i])] = true |
stolowski
Nov 17, 2017
Contributor
It's slightly baffling that XSnapdEntryID and XSnapdParentID can return empty strings if missing, but we don't seem to handle that here. Is this guaranteed not to happen? What happens if we have an antry with snapd.parent-id=e1, but "e1" doesn't exist? Would it make sense to have a test for this?
zyga
Nov 20, 2017
Contributor
I changed the approach to make mount IDs implicit. This is actually both easier to handle and works better in practice where existing desired mount profiles don't have them.
zyga
added some commits
Nov 20, 2017
jdstrand
requested changes
Nov 21, 2017
I clearly have a lot of questions with this PR. I think this is partly because this is an intermediate PR where other pieces are missing and I don't have the full context. It seems there might be opportunities to improve comments, variable names and the use of the term 'parent'.
I'm going to mark this as 'request changes' for the code comments. Please consider the number of questions as reason to clarify things. I'm happy to review again after my questions are answered and code clarified.
| continue | ||
| } | ||
| skipDir = "" // reset skip prefix as it no longer applies | ||
| + | ||
| + // Reuse synthetic entries if their parent is desired. | ||
| + if XSnapdSynthetic(¤t[i]) && desiredIDs[XSnapdParentID(¤t[i])] { |
jdstrand
Nov 21, 2017
Contributor
Can you add a comment on why synthetic entries should be reused only if the parent is desired? AIUI, a synthetic can't exist without the parent and so in that case it can't be reused. I think part of the problem for understanding is that a syntethic mount is described as "entries [that] are created by snap-update-ns itself, separately from what snapd instructed. Such entries are needed to make other things possible", but that isn't very specific and I'm not sure what that means wrt this code. Maybe a more specific comment in entry.go is warranted....
I don't see where snapd is setting x-snapd.parent-id or x-snapd.id-- I only see the helpers to parse these. Is this being done in a follow-up PR?
zyga
Nov 28, 2017
•
Contributor
The assignments of needed-by (nee parent-id) and id thing is indeed a follow up.
| +// Unused bind mount farms are unmounted. | ||
| +func (s *changeSuite) TestNeededChangesTmpfsBindMountFarmUnused(c *C) { | ||
| + current := &mount.Profile{Entries: []mount.Entry{{ | ||
| + // The tmpfs that lets us write into immutable squashfs. |
jdstrand
Nov 21, 2017
Contributor
Perhaps:
// The tmpfs that lets us write into immutable squashfs. We mock x-snapd.parent-id
// to the squashfs mount. Mark it synthetic since it is a helper mount that is needed
// to facilitate the following mounts.
| +func (s *changeSuite) TestNeededChangesTmpfsBindMountFarmUnused(c *C) { | ||
| + current := &mount.Profile{Entries: []mount.Entry{{ | ||
| + // The tmpfs that lets us write into immutable squashfs. | ||
| + Name: "none", |
| + Type: "tmpfs", | ||
| + Options: []string{"x-snapd.parent-id=/snap/name/42/subdir", "x-snapd.synthetic"}, | ||
| + }, { | ||
| + // A bind mount to preserve a directory hidden by the tmpfs. |
jdstrand
Nov 21, 2017
Contributor
Perhaps:
// A bind mount to preserve a directory hidden by the tmpfs (the mountpoint
// is created elsewhere). We mock x-snapd.parent-id to the tmpfs mount
What is interesting here is that the parent-id of tmpfs mount in the previous profile is for the squashfs mount and the parent-id of this bind mount is for the tmpfs, but they both have the same parent-id. Is this intended? Might this cause confusion?
| + }, { | ||
| + // A bind mount to put some content from another snap. The bind mount | ||
| + // is nothing special but the fact that it is possible is the reason | ||
| + // the two entries above exist. |
jdstrand
Nov 21, 2017
Contributor
Perhaps append to the comment: (the mountpoint ('created') is created elsewhere
| + // the two entries above exist. | ||
| + Name: "/snap/other/123/libs", | ||
| + Dir: "/snap/name/42/subdir/created", | ||
| + Options: []string{"bind", "ro"}, |
jdstrand
Nov 21, 2017
Contributor
Based on the placement within the current profiles list, I was expecting that this would have a parent-id on tmpfs.
Also, why don't these (and the other) example mount profiles use x-snapd.id? I was thinking every mount would have x-snapd.id, most would have x-snapd.parent-id and some would have x-snapd.synthetic....
| + Type: "tmpfs", | ||
| + Options: []string{"x-snapd.parent-id=/snap/name/42/subdir", "x-snapd.synthetic"}, | ||
| + }, Action: update.Unmount}, | ||
| + }) |
jdstrand
Nov 21, 2017
Contributor
I thought the unmounts were supposed to happen in the reverse order. The current has: 'tmpfs, existing, created', but the unmount is 'existing, created, tmpfs'. Is this intended?
| + | ||
| +func (s *changeSuite) TestNeededChangesTmpfsBindMountFarmUsed(c *C) { | ||
| + current := &mount.Profile{Entries: []mount.Entry{{ | ||
| + // The tmpfs that lets us write into immutable squashfs. |
jdstrand
Nov 21, 2017
Contributor
Perhaps more detail on '.../created' would make this test clearer.
| +func (s *changeSuite) TestNeededChangesTmpfsBindMountFarmUsed(c *C) { | ||
| + current := &mount.Profile{Entries: []mount.Entry{{ | ||
| + // The tmpfs that lets us write into immutable squashfs. | ||
| + Name: "none", |
| + Type: "tmpfs", | ||
| + Options: []string{"x-snapd.parent-id=/snap/name/42/subdir/created", "x-snapd.synthetic"}, | ||
| + }, Action: update.Keep}, | ||
| + }) |
jdstrand
Nov 21, 2017
Contributor
Why are tmpfs, existing and created all kept (and again, not in the expected order) when only created is desired? I don't see anything in 'created' about a parent-id. If it is because the prefix is the same, can you add a comment stating this?
| + if val, ok := e.OptStr("x-snapd.id"); ok { | ||
| + return val | ||
| + } | ||
| + return e.Dir |
jdstrand
Nov 21, 2017
Contributor
Ok, this clarifies some of my questions on why x-snapd.id isn't being set in the testsuite, but then, why is x-snapd.id needed at all if we are just going to use e.Dir here? When is x-snapd.id set to the hash? How is that hash calculated?
zyga
Nov 22, 2017
Contributor
This is something I was experimenting with. Initially it was just an opaque ID that snapd would generate. Then I realized it cannot be so because that identifier has to be stable so I resorted to hashing the whole entry. Then I realized it actually needs to be implicitly defined so that existing installations upgrade correctly (and by correctly I mean that there are no spurious changes). In the end this just became the mount point. I think that we can simplify this aspect and just use the e.Dir (ill-named for files but still) directly.
jdstrand
Nov 28, 2017
Contributor
"I think that we can simplify this aspect and just use the e.Dir (ill-named for files but still) directly."
Will this happen in a follow-up PR?
| + // | ||
| + // Note that if you compare this to the code that plans a writable mimic | ||
| + // you will see that there are additional changes that are _not_ | ||
| + // represented here. The changes are have only one goal: tell |
| + // you will see that there are additional changes that are _not_ | ||
| + // represented here. The changes are have only one goal: tell | ||
| + // snap-update-ns how the mimic can be undone in case it is no longer | ||
| + // needed. |
jdstrand
Nov 21, 2017
Contributor
This comment is illuminating. I think it deserves to be in the actual code, not just the testsuite.
| + Name: "/snap/mysnap/42/usr/share/mysnap", | ||
| + Dir: "/usr/share/mysnap", Type: "none", | ||
| + Options: []string{"bind", "ro"}}}) | ||
| + syntetic := []*update.Change{ |
| + // read only) was hidden with a tmpfs. | ||
| + {Action: update.Mount, Entry: mount.Entry{ | ||
| + Dir: "/usr/share", Name: "none", Type: "tmpfs", | ||
| + Options: []string{"x-snapd.synthetic", "x-snapd.parent-id=/usr/share/mysnap"}}}, |
jdstrand
Nov 21, 2017
Contributor
Why is /usr/share/mysnap the parent-id? Is the parent-id intended to be the thing that caused this mount? (sorry, I don't see where we set parent-id anywhere so I'm not sure what it is supposed to be). If parent-id is meant to be the thing that caused this mount, I would suggest not calling it 'parent' since the term is overloaded with the term 'parent dir'. Ie, '/usr/share/mysnap' is not the parent dir of '/usr/share', but within the context of this code, it is the parent operation. I think this is the cause of some of my confusion and why I had so many questions up above.
zyga
Nov 22, 2017
Contributor
I agree, it should not be called parent. Perhaps needed-by= would suffice?
| + // constructed using a temporary bind mount that contained the | ||
| + // original mount entries of /usr/share but this fact was lost. | ||
| + // Again, the only point of this entry is to correctly perform an | ||
| + // undo operation when /usr/share/mysnap is no longer needed. |
jdstrand
Nov 21, 2017
Contributor
Again, this comment is illuminating. I think discussing this in the code that does the operation would be worthwhile.
zyga
added some commits
Nov 28, 2017
zyga
referenced this pull request
Nov 28, 2017
Merged
cmd/snap-update-ns: add execWritableMimic #4315
jdstrand
approved these changes
Nov 28, 2017
I'm going to mark this as approved, but please change 'parent-id' to 'needed-by' before committing. There is also one open question inline regarding x-snapd.id and e.Dir and when you would perform that cleanup. It would be nice to have here but can be in a follow-up PR.
|
@jdstrand I changed all |
zyga commentedNov 15, 2017
This patch teaches the mount namespace update logic to cope with
synthetic changes. Such changes are are tagged with x-snapd.synethetic
field. Each syntethic change is tied via the x-snapd.parent-id field
to a non-synthetic change matching the x-snapd.id field.
As long as the non synthetic change is around all synthetic changes
that refer to it are preserved during updates. When the non-synthetic
change is no longer desired all the synthetic changes are undone in the
right order.
Signed-off-by: Zygmunt Krynicki zygmunt.krynicki@canonical.com