-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for mounting volumes with local driver and options #3931
Add support for mounting volumes with local driver and options #3931
Conversation
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: mheon The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
@rhatdan PTAL - you said this was blocking your work on overlay volumes |
I think not, but thought I'd ask. Any anticipated db upgrade issues? |
The first commit should address most of them. The bigger issue was the locks PR that went in earlier - we'll need a |
Thanks @mheon. A quick browse and the code LGTM. The |
libpod/boltdb_state.go
Outdated
if err != nil { | ||
return errors.Wrapf(err, "error marshalling volume %s state to JSON", volume.Name()) | ||
} | ||
volStateJSON = stateJSON |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any reason not to volStateJSON, err = json.Marshal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think json.Marshal
on a nil pointer makes angry errors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wait, i see what you're saying. Probably will work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only works when changing the short decl :=
to an assignment =
. Using the short decl :=
any variable on the left will be created unless already being declared in the same scope. Since volStateJSON
is declared in an outer scope, it won't work unless we declare err
before and change it to an assignment.
lgtm other than nit question ... have you measured any perf impact to overall container running with this? |
Should be 0 on containers that do not mount shared volumes. On those with volumes requiring a full mount call, on first mount, it's pretty substantial - around 1s extra for a tmpfs, in my measurements. This about matches what I saw with Docker, though - was how I figured out they had to be doing the mount on container launch. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work, @mheon. Just a couple of nits.
I looked into the man pages and noticed that there's no information about supported drivers. The only information I found is that the default is local.
pkg/varlinkapi/volumes.go
Outdated
if len(options.Options) > 0 { | ||
volumeOptions = append(volumeOptions, libpod.WithVolumeOptions(options.Options)) | ||
return call.ReplyErrorOccurred("not yet implemented") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could use define.ErrNotImplemented.Error()
here.
libpod/volume_internal.go
Outdated
// Volumes with options set, or a filesystem type, or a device to mount need to | ||
// be mounted and unmounted. | ||
func (v *Volume) needsMount() bool { | ||
return len(v.config.Options) > 0 && v.config.Driver == "local" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use a constant instead of "local"
?
// The location the volume is mounted at. | ||
MountPoint string `json:"mountPoint"` | ||
// Time the volume was created. | ||
CreatedTime time.Time `json:"createdAt,omitempty"` | ||
// Options to pass to the volume driver. For the local driver, this is | ||
// a list of mount options. For other drivers, they are passed to the | ||
// volume driver handling the volume. | ||
Options map[string]string `json:"options"` | ||
Options map[string]string `json:"volumeOptions,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we changing the identifiers of the options and the driver? Prefixing with volume
feels redundant since it's a VolumeConfig.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to discard anything already in the database. Previously we were doing absolutely no validation on these fields - now we're properly vetting their contents. We can't go back and make previous containers safe, so the next best option is to ignore them (they were all acting like volumes without options prior to this PR anyways)
libpod/boltdb_state.go
Outdated
if err != nil { | ||
return errors.Wrapf(err, "error marshalling volume %s state to JSON", volume.Name()) | ||
} | ||
volStateJSON = stateJSON |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only works when changing the short decl :=
to an assignment =
. Using the short decl :=
any variable on the left will be created unless already being declared in the same scope. Since volStateJSON
is declared in an outer scope, it won't work unless we declare err
before and change it to an assignment.
libpod/container_internal.go
Outdated
return | ||
} | ||
vol.lock.Lock() | ||
defer vol.lock.Unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we move it down a few lines, we can spare the defer
and save a few cycles.
libpod/runtime_volume_linux.go
Outdated
@@ -48,6 +48,19 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption) | |||
} | |||
volume.config.CreatedTime = time.Now() | |||
|
|||
if volume.config.Driver == "local" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here: can we use a constant for "local"?
libpod/volume_internal_linux.go
Outdated
|
||
errPipe, err := mountCmd.StderrPipe() | ||
if err != nil { | ||
return errors.Wrapf(err, "getting stderr pipe") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"error getting ..."?
// Convincing unix.Mount to use the same semantics as the mount command | ||
// itself seems prohibitively difficult. | ||
// TODO: might want to cache this path in the runtime? | ||
mountPath, err := exec.LookPath("mount") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer calling the syscall directly. I am no mount expert, so I may be totally unaware of the difficulties.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My initial attempt tried that, but I abandoned it pretty quickly due to difficulties syncing up the defaults. Docker allows semi-arbitrary options to be passed, does almost no authentication, and pops them straight into a call to mount
. We would need to reverse-engineer somewhat large tracts of the functionality of Coreutils mount
to make this work properly without it.
pkg/varlinkapi/volumes.go
Outdated
@@ -67,7 +68,8 @@ func (i *LibpodAPI) GetVolumes(call iopodman.VarlinkCall, args []string, all boo | |||
Labels: v.Labels(), | |||
MountPoint: v.MountPoint(), | |||
Name: v.Name(), | |||
Options: v.Options(), | |||
// TODO change types here to be correct | |||
//Options: v.Options(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we need the options as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, oops, that one's a mistake, reverting
I would like to see @vrothberg questions addressed, as well as squash the PRs, and add some tests. Examples in man pages would also be nice. Looks very good. |
4267b38
to
29941a9
Compare
Addressed review comments |
@mheon can you squash your commits. |
Sure |
f177d64
to
5b150b6
Compare
In upcoming commits, we're going to turn on the backends for these fields. Volumes with these set will act fundamentally differently from other volumes. There will probably be validation required for each field. Until now, though, we've freely allowed creation of volumes with these set - they just did nothing. So we have no idea what could be in the DB with old volumes. Change the struct tags so we don't have to worry about old, unvalidated data. We'll start fresh with new volumes. Signed-off-by: Matthew Heon <[email protected]>
We need to be able to track the number of times a volume has been mounted for tmpfs/nfs/etc volumes. As such, we need a mutable state for volumes. Add one, with the expected update/save methods in both states. There is backwards compat here, in that older volumes without a state will still be accepted. Signed-off-by: Matthew Heon <[email protected]>
cc80069
to
f6ca089
Compare
Added tests |
7c7e94e
to
d168f57
Compare
Assuming things go green, I think we're good for final review and merge |
d168f57
to
f24fa83
Compare
/retest |
cfdd268
to
951ecb3
Compare
When volume options and the local volume driver are specified, the volume is intended to be mounted using the 'mount' command. Supported options will be used to volume the volume before the first container using it starts, and unmount the volume after the last container using it dies. This should work for any local filesystem, though at present I've only tested with tmpfs and btrfs. Signed-off-by: Matthew Heon <[email protected]>
When we fail to remove a container's SHM, that's an error, and we need to report it as such. This may be part of our lingering storage woes. Also, remove MNT_DETACH. It may be another cause of the storage removal failures. Signed-off-by: Matthew Heon <[email protected]>
951ecb3
to
de9a394
Compare
Going green. @rhatdan @baude @vrothberg @TomSweeneyRedHat @giuseppe PTAL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
/lgtm |
Volumes with the
local
driver and mount options specified are mounted using themount
command, instead of being plain directories. The volumes are mounted as the first container using them is started, and unmounted when the last container using them exits.Given the mount/unmount behavior, we need to add a mount counter to volumes to know when to mount/unmount. This requires adding a mutable state to volumes, with associated methods in the DB to update and save the state.
Missing tests right now, but I've tested locally with tmpfs and btrfs with positive results.