Skip to content
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

plugins: container-rootfs-relative paths #26398

Merged
merged 1 commit into from
Dec 9, 2016

Conversation

tiborvass
Copy link
Contributor

@tiborvass tiborvass commented Sep 7, 2016

Legacy plugins expect host-relative paths (such as for Volume.Mount).
However, a containerized plugin cannot respond with a host-relative
path. Therefore, this commit modifies new volume plugins' paths in Mount
and List to prepend the container's rootfs path.

Volume plugins that do mounts and want it to propagate to the host namespace, need to mount inside /mnt.

Signed-off-by: Tibor Vass tibor@docker.com

@@ -58,7 +58,7 @@ func Init(root string, remote libcontainerd.Remote, rs registry.Service, liveRes
root = filepath.Join(root, "plugins")
manager = &Manager{
libRoot: root,
runRoot: "/run/docker",
runRoot: "/run/docker/plugins",
Copy link
Contributor Author

@tiborvass tiborvass Sep 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anusha-ragunathan @vieux I changed this to accommodate legacy plugins that have hardcoded /run/docker/plugins already. I had initially chosen /run/docker because it was shorter. To be clear: this is the path inside the container where we expect the socket to be.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me. It reduces the code change required by the legacy plugins to move to pluginv2.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tiborvass : you dont need to change this for plugins. This is the host directory that gets bind mounted to the plugin. The plugin run time path is defaultPluginRuntimeDestination which is already set to /run/docker/plugins https://github.com/docker/docker/blob/fa7c1a68a68ffb7c6851c59d2c0b41091b9d6e5e/plugin/v2/plugin.go#L29

We could change runRoot back to /run/docker/plugins to indicate that the directories that get created under there are plugins related.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a separate PR, but this should not be hard-coded to /run/docker but instead use the daemon exec root

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason to hard code is that, this holds the plugin socket. There's a socket path limitation on Linux to 108 bytes. The daemon exec root doesnt have any socket and is not bound by this limitation. So we have to choose a path that always works.

@ehazlett ehazlett added status/1-design-review status/failing-ci Indicates that the PR in its current state fails the test suite and removed status/0-triage labels Sep 8, 2016
@@ -205,18 +213,53 @@ func (p *Plugin) GetTypes() []types.PluginInterfaceType {

// InitSpec creates an OCI spec from the plugin's config.
func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs")
p.Rootfs = filepath.Join(libRoot, p.PluginObj.ID, "rootfs")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anusha-ragunathan
Copy link
Contributor

Lets keep this PR specifically for container-rootfs-relative path fix and open new ones for the other bug fixes. Keeps a cleaner history.

Readonly: false, // TODO: all plugins should be readonly? settable in manifest?
}

// TODO: the following should be only if granting device permissions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets fix this todo by adding to Manifest's privilege section. Something like "REQUEST_DEVICE_CREATION".

@@ -113,7 +113,7 @@ func (ps *PluginStore) getAllByCap(capability string) []CompatPlugin {

result := make([]CompatPlugin, 0, 1)
for _, p := range ps.plugins {
if _, err := p.FilterByCap(capability); err == nil {
if _, err := p.FilterByCap(capability); err == nil && p.IsEnabled() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anusha-ragunathan I could not change this in the caller function because I have only access to CompatPlugin interface values, and not *Plugin objects.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough.

@anusha-ragunathan
Copy link
Contributor

anusha-ragunathan commented Sep 9, 2016

@tiborvass

FAIL: docker_cli_daemon_experimental_test.go:177: DockerDaemonSuite.TestVolumePlugin

[d0f844bc9335c] waiting for daemon to start
[d0f844bc9335c] daemon started
docker_cli_daemon_experimental_test.go:223:
    c.Assert(err, checker.IsNil, check.Commentf(out))
... value *exec.ExitError = &exec.ExitError{ProcessState:(*os.ProcessState)(0xc421c5b0e0), Stderr:[]uint8(nil)} ("exit status 127")
... Unable to find image 'busybox:latest' locally
latest: Pulling from library/busybox
8ddc19f16526: Pulling fs layer
8ddc19f16526: Download complete
8ddc19f16526: Pull complete
Digest: sha256:a59906e33509d14c036c8678d687bd4eec81ed7c4b8ce907b888c607f6a1e0e6
Status: Downloaded newer image for busybox:latest
/go/src/github.com/docker/docker/bundles/1.13.0-dev/binary-client/docker: Error response from daemon: oci runtime error: rootfs_linux.go:53: mounting "/tmp/data" to rootfs "/go/src/github.com/docker/docker/bundles/1.13.0-dev/test-integration-cli/d0f844bc9335c/root/vfs/dir/fe75f9766650efb2ecd73bee99b3c9eb67301379cccf3e119582c92016c3a318" caused "stat /go/src/github.com/docker/docker/bundles/1.13.0-dev/test-integration-cli/d0f844bc9335c/root/plugins/2666fee9e7017759705396aa5301f13af1385ca24e8665106e0cdcd687385816/rootfs/data/plugin-volume: no such file or directory".

@ekristen
Copy link
Contributor

Please let me know if I can assist. Looking forward to seeing this get merged for 1.13.

@tiborvass
Copy link
Contributor Author

Thanks, I'm planning to update this soon.

@thaJeztah
Copy link
Member

ping @tiborvass what's the status here?

if typ.Capability == "volumedriver" && typ.Prefix == "docker" && strings.HasPrefix(typ.Version, "1.") {
if p.PluginObj.Config.PropagatedMount != "" {
// TODO: sanitize PropagatedMount and prevent breakout
p.PropagatedMount = filepath.Join(p.Rootfs, p.PluginObj.Config.PropagatedMount)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see #26398 (comment)

c.Log("plugin disabled")
mounts, err := mount.GetMounts()
c.Assert(err, checker.IsNil)
for _, mnt := range mounts {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are simply printing out the mounts, which doesnt test much. Can we use findmnt to see if the mount propagated?

@cpuguy83
Copy link
Member

cpuguy83 commented Dec 3, 2016

Beyond the already commented items, code seems ok.

@@ -32,7 +32,7 @@ func (s *DockerSuite) TestPluginBasicOps(c *check.C) {
id = strings.TrimSpace(id)
c.Assert(err, checker.IsNil)

out, _, err = dockerCmdWithError("plugin", "remove", pNameWithTag)
out, _, _ = dockerCmdWithError("plugin", "remove", pNameWithTag)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be dockerCmd instead ? Doesn't seem right to not care about error at all 😓

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vdemeester I kept err and asserted it non-nil. Realized that dockerCmd will assume non-nil error...

@tiborvass tiborvass force-pushed the plugin-fixes branch 5 times, most recently from 2e89b78 to 46c343f Compare December 7, 2016 18:48
for _, typ := range p.PluginObj.Config.Interface.Types {
if typ.Capability == "volumedriver" && typ.Prefix == "docker" && strings.HasPrefix(typ.Version, "1.") {
if p.PluginObj.Config.PropagatedMount != "" {
// TODO: sanitize PropagatedMount and prevent breakout
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still applicable? When does the mount leak?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

../../../../etc

@@ -21,11 +22,13 @@ type Plugin struct {
PluginObj types.Plugin `json:"plugin"`
PClient *plugins.Client `json:"-"`
RuntimeSourcePath string `json:"-"`
Rootfs string `json:"-"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets not introduce Public fields in this struct. Refer to #29191 is making changes to make this all private and move necessary fields to a controller struct in the manager. Please review that PR and follow similar design on the new fields.

@@ -71,6 +71,10 @@ type PluginConfig struct {
// Required: true
Network PluginConfigNetwork `json:"Network"`

// propagated mount
// Required: true
PropagatedMount string `json:"PropagatedMount"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/docker/docker/blob/master/docs/extend/config.md needs to be updated. Please do that alongside.

@anusha-ragunathan
Copy link
Contributor

LGTM (if tests pass)

if typ.Capability == "volumedriver" && typ.Prefix == "docker" && strings.HasPrefix(typ.Version, "1.") {
if p.PluginObj.Config.PropagatedMount != "" {
// TODO: sanitize PropagatedMount and prevent breakout
p.PropagatedMount = filepath.Join(p.LibRoot, p.PluginObj.Config.PropagatedMount)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filepath.Base(p.PluginObj.Config.PropagatedMount)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making sure I understand this... this is for mounting the propagated path to the plugin root (outside rootfs)?
Maybe even just use mnt instead of the mount path.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tiborvass : You should be using filepath.Join(p.LibRoot, p.PluginObj.ID, p.PluginObj.Config.PropagatedMount). Else mounts from different plugins with the same name will collide. Another option is to fix p.LibRoot to include the pluginID (It should have been this way in the first place)

We dont want to use hard coded mnt. We want plugin devs to specify the mount that needs propagation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cpuguy83 : PropagatedMount is strictly for those plugins (typically volume and graph drivers) that need to setup mounts that need to propagated to the host.
We want plugin devs to specify which mounts need to be propagated, rather than hard coding it to a specific path.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but isn't this a singleton that's the "host" side of the mount?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Having the same dir as the mount in the config could be less confusing. On the other hand, having a standard dir such as mnt is also reasonable. I'll let @tiborvass decide. Also, we dont have to do this in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For start up reasons, this field is also set in plugin/manager.go. And I just realized it's set to something else. So I'll go with p.Rootfs for now.

Legacy plugins expect host-relative paths (such as for Volume.Mount).
However, a containerized plugin cannot respond with a host-relative
path. Therefore, this commit modifies new volume plugins' paths in Mount
and List to prepend the container's rootfs path.

This introduces a new PropagatedMount field in the Plugin Config.
When it is set for volume plugins, RootfsPropagation is set to rshared
and the path specified by PropagatedMount is bind-mounted with rshared
prior to launching the container. This is so that the daemon code can
access the paths returned by the plugin from the host mount namespace.

Signed-off-by: Tibor Vass <tibor@docker.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/plugins priority/P1 Important: P1 issues are a top priority and a must-have for the next release. process/cherry-picked status/2-code-review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet