interfaces,overlord: support unversioned data #1095

Merged
merged 8 commits into from Apr 30, 2016

Conversation

Projects
None yet
6 participants
Member

kyrofa commented Apr 28, 2016

The current versioned data directories are an important aspect of the upgrade/rollback strategy, but they don't scale well for applications that need to hold a vast amount of non-version-specific data (LP: #1559248). This PR is an attempt to implement support for unversioned data (i.e. data that is shared between versions of a given snap), conforming to Jamie's suggestion (note that the naming is of course up for discussion). The shared directories are removed by overlord only once the final version is removed.

Note that this will also require changes to ubuntu-core-launcher to create the user-specific shared data directory (as it creates the versioned data directory). That can come once this PR is approved.

interfaces,overlord: Support unversioned data.
Signed-off-by: Kyle Fazzari <kyle@canonical.com>
overlord/snapstate/backend_test.go
+func (f *fakeSnappyBackend) RemoveSnapSharedData(info *snap.Info) error {
+ f.ops = append(f.ops, fakeOp{
+ op: "remove-snap-shared-data",
+ name: info.MountDir(),
@zyga

zyga Apr 28, 2016

Contributor

MountDir feels odd here

@kyrofa

kyrofa Apr 28, 2016

Member

You mean because it includes the version? Perhaps this is reason enough to create an info.BaseDir() function, then.

@zyga

zyga Apr 29, 2016

Contributor

My comment was based on just the name: ... MountDir connection but I see that other tests use this as well.

@kyrofa

kyrofa Apr 29, 2016

Member

Yeah, it's nice for testing that a given operation happens on a given revision of the snap. We may want to leave this as MountDir(), then.

@niemeyer

niemeyer Apr 29, 2016

Contributor

Yeah, let's not overthink this too much for now. It's just a way to print out what the backend had at hand when the given method was called.

snap/snapenv/snapenv.go
@@ -63,6 +67,7 @@ func GetBasicSnapEnvVars(desc interface{}) []string {
return fillSnapEnvVars(desc, []string{
"SNAP={{.SnapPath}}",
"SNAP_DATA=/var{{.SnapPath}}",
+ "SNAP_SHARED_DATA={{CleanPath (printf \"/var%s/../shared\" .SnapPath)}}",
@zyga

zyga Apr 28, 2016

Contributor

Hmmm, I'm not sure this looks nice.

@kyrofa

kyrofa Apr 28, 2016

Member

Yeah I wasn't a huge fan either, but the only nice alternative I could think of was to stop being clever using SnapPath for everything and actually specify each of these explicitly, which is quite a bit more invasive (e.g. create an info.BasePath() to get /snap/snapname without the version, and so on). Would that be better? I'll see if I can simplify this.

@kyrofa

kyrofa Apr 29, 2016

Member

Fixed.

snap/snapenv/snapenv.go
@@ -78,5 +83,6 @@ func GetBasicSnapEnvVars(desc interface{}) []string {
func GetUserSnapEnvVars(desc interface{}) []string {
return fillSnapEnvVars(desc, []string{
"SNAP_USER_DATA={{.Home}}{{.SnapPath}}",
+ "SNAP_USER_SHARED_DATA={{CleanPath (printf \"%s%s/../shared\" .Home .SnapPath)}}",
@zyga

zyga Apr 28, 2016

Contributor

Ditto

@kyrofa

kyrofa Apr 29, 2016

Member

Also fixed.

snappy/snapdata.go
+ if err := os.RemoveAll(dir); err != nil && !os.IsNotExist(err) {
+ return err
+ }
+ os.Remove(filepath.Dir(dir))
@zyga

zyga Apr 28, 2016

Contributor

Based on

zyga@zyga-thinkpad-w510:~$ go doc os.RemoveAll
func RemoveAll(path string) error
    RemoveAll removes path and any children it contains. It removes everything
    it can but returns the first error it encounters. If the path does not
    exist, RemoveAll returns nil (no error).

I don't think you have to check for the os.IsNotExist or call os.Remove again later.

@kyrofa

kyrofa Apr 28, 2016

Member

Agreed, this was taken from RemoveSnapData(). They should use the same function anyway, so I'll refactor them both.

@kyrofa

kyrofa Apr 29, 2016

Member

Done. Though note: the later os.Remove() call is what removes the parent directories when they're empty, so I've left that, but added a comment to clarify.

@@ -211,8 +211,8 @@ X-Snappy=yes
[Service]
ExecStart=/usr/bin/ubuntu-core-launcher app aa-profile /apps/app/1.0/bin/start
Restart=on-failure
-WorkingDirectory=/var/apps/app/1.0/
-Environment="SNAP=/apps/app/1.0/" "SNAP_DATA=/var/apps/app/1.0/" "SNAP_NAME=app" "SNAP_VERSION=1.0" "SNAP_REVISION=44" "SNAP_ARCH=%[3]s" "SNAP_LIBRARY_PATH=/var/lib/snapd/lib/gl:" "SNAP_USER_DATA=/root/apps/app/1.0/"
@zyga

zyga Apr 28, 2016

Contributor

Wow, this stuff is stale! /apps/app/1.0 Where did that come from?

In either case I don't think /var/appps is what we want today. I'd look at something in /var/lib/snapd/

@kyrofa

kyrofa Apr 28, 2016

Member

Indeed, lots of test cruft here. Note that it's using version instead of revision as well. I thought cleaning it would make for a noisy diff, though. Should I anyway?

@zyga

zyga Apr 29, 2016

Contributor

Perhaps as a side branch. I think it's well worth not adding more /apps test data.

@kyrofa

kyrofa Apr 29, 2016

Member

Alright, I'll leave this alone here, then.

Contributor

zyga commented Apr 28, 2016

Some comments inline.

interfaces/apparmor/template.go
@@ -233,13 +233,15 @@ var defaultTemplate = []byte(`
# Writable home area for this version.
owner @{HOME}/snap/@{SNAP_NAME}/@{SNAP_REVISION}/** wl,
+ owner @{HOME}/snap/@{SNAP_NAME}/shared/** wl,
@niemeyer

niemeyer Apr 29, 2016

Contributor

Although I'd like to sync up with everyone on this, the approach looks nice and simple.

One initial comment: shared is perhaps not the best word. It also means "make it public" (share with whom?), and it may imply concurrent use which will never be the case here (only the active snap will touch it). As a preliminary strawman, I'd suggest "common".

@kyrofa

kyrofa Apr 29, 2016

Member

shared is perhaps not the best word. It also means "make it public" (share with whom?), and it may imply concurrent use which will never be the case here (only the active snap will touch it).

Great point, I agree.

As a preliminary strawman, I'd suggest "common".

I'm good with "common." How about the variable name format: SNAP_COMMON_DATA and SNAP_USER_COMMON_DATA? I'm concerned the latter gives the impression that the data is common among users, which of course isn't the case. Can you think of something more clear?

@kyrofa

kyrofa Apr 29, 2016

Member

I guess "unversioned" is an option as well.

@niemeyer

niemeyer Apr 29, 2016

Contributor

Quickly discussed this with the team and the response was positive.

So let's go ahead with the proposal, just replacing the "shared" term by "common" everywhere please.

@didrocks

didrocks May 2, 2016

Contributor

Reading about this, "common" makes sense from an internal code perspective for sure, I'm less sure from a developer perspective it would though.
"common" is implicit (as for "shared") that multiple entities can access to it at the same time, which isn't the case as @niemeyer suggested.

What you do actually end up with are versioned and version-independant areas. It's even more obvious when you ls in the data directory as you will have:
version1/, version2/, …, current. Why not then rather using unversioned which I guess will live better next to those versions and the current symlink? Thoughts?

@niemeyer

niemeyer May 2, 2016

Contributor

"common" doesn't really have any access-related concurrency connotation that I'm aware of, and searching for these terms seems to agree with that. Do you have any reference pointing out otherwise?

The fact multiple entities can access it is intended, though. That's exactly what "not versioned" means, and it's always more pleasing to label something after what it means than after what it does not mean.

@didrocks

didrocks May 2, 2016

Contributor

I partially agree with it's more pleasing to label something in a non negative way. However, I do fell that "unversioned" underly a fact that "common" doesn't hilight well enough: you need to ensure data you put there are forward compatible.

The "unversioned" term will be a little bit more scary to developers for this and it's a good reason: you really need to ensure that data you put there are working with any available version of your application. You don't want non forward-compatible schema of your database for instance (I'm afraid that this directory will be used a lot for databases in particular).
"common" is a little bit more gentle in this regard and don't underline this risk… at least from a non native speaker perspective (so take that with a grain of salt ;))

kyrofa added some commits Apr 29, 2016

snap: Add info.BaseDir() function to get basedir of all versions.
Signed-off-by: Kyle Fazzari <kyle@canonical.com>
snappy: Refactor RemoveSnapData and RemoveSnapSharedData.
Signed-off-by: Kyle Fazzari <kyle@canonical.com>
overlord/snapstate/snapmgr.go
+ return err
+ }
+
+ // Only remove data shared between versions if this is the last version
@niemeyer

niemeyer Apr 29, 2016

Contributor

This is a bit problematic, and makes me want to think further about the whole approach we're taking. Imagine a button that says "Reset snap data".. if we keep the shared data around we're not actually resetting the data. We'll restart the snap, and its data is still there, partially! How do we reset the data for a snap, ever, once we have a shared directory?

@kyrofa

kyrofa Apr 29, 2016

Member

That sounds like new functionality, though (i.e. not involving this particular bit of logic)-- do we currently support clearing data out from under an installed snap? As part of that feature I expect we'd also clear the shared data.

@niemeyer

niemeyer Apr 29, 2016

Contributor

Okay, the answer is that we'll need a separate task that kills the data for all snaps at once. If we intentionally want to reset the data for a snap, it should do so for all snaps rather than just the current revision. The experience would probably be awkward otherwise.

So this is fine for now, thanks.

@kyrofa

kyrofa Apr 29, 2016

Member

Agreed.

snap/info.go
@@ -36,7 +36,10 @@ type PlaceInfo interface {
// Name returns the name of the snap.
Name() string
- // MountDir returns the base directory of the snap.
+ // BaseDir returns the base directory for all versions of the snap.
+ BaseDir() string
@niemeyer

niemeyer Apr 29, 2016

Contributor

We had a BaseDir very recently, which got renamed because it was unclear. It doesn't feel much more clear now. Which base dir?

@kyrofa

kyrofa Apr 29, 2016

Member

Agreed. This is part of why I used filepath.Clean initially-- this directory really has no meaning except for building paths. ParentDir()? MountsDir()? MountParentDir()?

@niemeyer

niemeyer Apr 29, 2016

Contributor

Let's please drop all BaseDir entries. The use case in the template is better handled by exposing App to the template directly, and using the proper method names instead of rebuilding the paths inside the template.

snap/info.go
}
// MinimalPlaceInfo returns a PlaceInfo with just the location information for a snap of the given name and revision.
func MinimalPlaceInfo(name string, revision int) PlaceInfo {
return &Info{SideInfo: SideInfo{OfficialName: name, Revision: revision}}
}
+// BaseDir returns the base directory for all versions of the snap.
+func BaseDir(name string) string {
@niemeyer

niemeyer Apr 29, 2016

Contributor

Same as prior point above.

snap/info.go
@@ -142,6 +156,11 @@ func (s *Info) strRevno() string {
return strconv.Itoa(s.Revision)
}
+// BaseDir returns the base directory for all versions of the snap.
+func (s *Info) BaseDir() string {
@niemeyer

niemeyer Apr 29, 2016

Contributor

Same as prior point above.

snap/snapenv/snapenv.go
@@ -63,6 +63,7 @@ func GetBasicSnapEnvVars(desc interface{}) []string {
return fillSnapEnvVars(desc, []string{
"SNAP={{.SnapPath}}",
"SNAP_DATA=/var{{.SnapPath}}",
+ "SNAP_SHARED_DATA=/var{{.SnapBasePath}}/shared",
@niemeyer

niemeyer Apr 29, 2016

Contributor

Why are we building this variable here when we have a method in info that returns the exact correct path? We should have the info here and use its methods (templates can do that) instead of inventing a new way to get the same data for each one of these variables.

@kyrofa

kyrofa Apr 29, 2016

Member

That would involve adding stripGlobalRootDir as a template function which makes this a little less pretty. Is that okay?

@kyrofa

kyrofa Apr 29, 2016

Member

Note that touches a lot of tests as well. It may be better suited for another PR.

@niemeyer

niemeyer Apr 29, 2016

Contributor

Per prior note, let's expose the App to the template and use the existing methods directly here instead of rebuilding the path locally.

@kyrofa

kyrofa Apr 29, 2016

Member

Sounds good.

snappy/binaries.go
- Home: "$HOME",
- Target: actualBinPath,
- AaProfile: app.SecurityTag(),
+ SnapName: app.Snap.Name(),
@niemeyer

niemeyer Apr 29, 2016

Contributor

Let's please have a single field here, "App", and kill all of these fields which are simply accessing it by doing so in the template itself.

@kyrofa

kyrofa Apr 29, 2016

Member

+1, but ditto my comment about stripGlobalRootDir and the fact that this will add a lot of diff noise. Do you want me to do that here, or in another PR?

@zyga

zyga Apr 29, 2016

Contributor

I can start a parallel branch that does that and we can see where it leads.

snappy/binaries.go
+ Version: app.Snap.Version,
+ Revision: app.Snap.Revision,
+ UdevAppName: app.SecurityTag(),
+ Home: "$HOME",
@niemeyer

niemeyer Apr 29, 2016

Contributor

This also shouldn't be here. We can have the string in the template itself.

@kyrofa

kyrofa Apr 29, 2016

Member

Well, it changes to /root if it's a service, though.

@zyga

zyga Apr 29, 2016

Contributor

That is still a "$HOME" constant in the template.

@kyrofa

kyrofa Apr 29, 2016

Member

I'm talking about https://github.com/ubuntu-core/snappy/blob/master/systemd/systemd.go#L388 . You can't use $HOME or %h in the service file.

kyrofa added some commits Apr 29, 2016

snap: Don't name shared data directories yet.
Signed-off-by: Kyle Fazzari <kyle@canonical.com>
interfaces,overload: Rename "shared" to "common."
Signed-off-by: Kyle Fazzari <kyle@canonical.com>
Member

kyrofa commented Apr 29, 2016

As agreed in Telegram, I've pushed updates renaming "shared" to "common," and removed the variables from the launchers.

@niemeyer niemeyer changed the title from interfaces,overlord: Support unversioned data. to interfaces,overlord: support unversioned data Apr 29, 2016

Contributor

niemeyer commented Apr 29, 2016

LGTM

# Read-only system area for other versions
/var/snap/@{SNAP_NAME}/ r,
/var/snap/@{SNAP_NAME}/** mrkix,
# Writable system area only for this version
/var/snap/@{SNAP_NAME}/@{SNAP_REVISION}/** wl,
+ /var/snap/@{SNAP_NAME}/common/** wl,
@jdstrand

jdstrand Apr 29, 2016

Contributor

FYI, these changes look fine security policy-wise.

snap/info.go
@@ -45,8 +45,14 @@ type PlaceInfo interface {
// DataDir returns the data directory of the snap.
DataDir() string
+ // CommonDataDir returns the data directory common between versions of the snap.
@zyga

zyga Apr 29, 2016

Contributor

s/versions/revisions/

@niemeyer

niemeyer Apr 29, 2016

Contributor

Good catch. Even better: "common across revisions"

snap/info.go
// DataHomeDir returns the per user data directory of the snap.
DataHomeDir() string
+
+ // CommonDataHomeDir returns the per user data directory common between versions of the snap.
@zyga

zyga Apr 29, 2016

Contributor

ditto (revisions)

snap/info.go
@@ -157,11 +163,21 @@ func (s *Info) DataDir() string {
return filepath.Join(dirs.SnapDataDir, s.Name(), s.strRevno())
}
+// CommonDataDir returns the data directory common between versions of the snap.
@zyga

zyga Apr 29, 2016

Contributor

revisions

snap/info.go
// DataHomeDir returns the per user data directory of the snap.
func (s *Info) DataHomeDir() string {
return filepath.Join(dirs.SnapDataHomeGlob, s.Name(), s.strRevno())
}
+// CommonDataHomeDir returns the per user data directory common between versions of the snap.
+func (s *Info) CommonDataHomeDir() string {
@zyga

zyga Apr 29, 2016

Contributor

revisions

snap/info.go
// DataHomeDir returns the per user data directory of the snap.
func (s *Info) DataHomeDir() string {
return filepath.Join(dirs.SnapDataHomeGlob, s.Name(), s.strRevno())
}
+// CommonDataHomeDir returns the per user data directory common between versions of the snap.
+func (s *Info) CommonDataHomeDir() string {
+ return filepath.Join(dirs.SnapDataHomeGlob, s.Name(), "common")
@zyga

zyga Apr 29, 2016

Contributor

This would return /home/*/snap/$snap/common. This is not a new problem but it's not clear that the returned value is a glob.

@kyrofa

kyrofa Apr 29, 2016

Member

Indeed. I think both this and DataHomeDir() should have "Glob" at the end of their names. I can do that here if you like. I actually already did it once and decided it made too much noise that didn't relate to the PR, but it's really not that invasive.

@niemeyer

niemeyer Apr 29, 2016

Contributor

Instead of having a Glob in the name, let's please make it take a username parameter instead which can be set to "*" in current use cases.

@kyrofa

kyrofa Apr 29, 2016

Member

I actually did that once as well. You run into problems when you try to use '$HOME' with a global root. I couldn't make it pretty.

@kyrofa

kyrofa Apr 29, 2016

Member

Also, you actually need the globs for removal, so that would require new functions (probably part of @zyga's refactor).

@pedronis

pedronis Apr 29, 2016

Contributor

DataHomeDir is my fault refactoring away and not noticing that it involved actually a glob, not sure one method and one dirs pkg variable can cover all our use cases though, it seems we need to produce:
/home/*/snap/... "/root/snap/..." and "$HOME/snap/..."

@niemeyer

niemeyer Apr 30, 2016

Contributor

We already have that problem today, so let's merge this and tackle that separately.

+func (s *Info) CommonDataDir() string {
+ return filepath.Join(dirs.SnapDataDir, s.Name(), "common")
+}
+
// DataHomeDir returns the per user data directory of the snap.
func (s *Info) DataHomeDir() string {
return filepath.Join(dirs.SnapDataHomeGlob, s.Name(), s.strRevno())
@zyga

zyga Apr 29, 2016

Contributor

FYI: note the glob remark on CommonDataHomeDir below

s/version/revision/
Signed-off-by: Kyle Fazzari <kyle@canonical.com>
Contributor

zyga commented Apr 29, 2016

LGTM

@niemeyer niemeyer merged commit 4927a90 into snapcore:master Apr 30, 2016

4 checks passed

Integration tests Success
Details
autopkgtest Success
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
coverage/coveralls Coverage decreased (-0.2%) to 79.275%
Details

@kyrofa kyrofa deleted the kyrofa:feature/1559248/unversioned_data branch May 6, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment