Merge pull request #12712 from flouthoc/volume_overlay_advanced

volume: add support for non-volatile `upperdir`,`workdir` for overlay volumes
This commit is contained in:
OpenShift Merge Robot
2022-01-28 07:06:02 -05:00
committed by GitHub
4 changed files with 109 additions and 3 deletions

View File

@ -1394,6 +1394,10 @@ directory will be the lower, and the container storage directory will be the
upper. Modifications to the mount point are destroyed when the container upper. Modifications to the mount point are destroyed when the container
finishes executing, similar to a tmpfs mount point being unmounted. finishes executing, similar to a tmpfs mount point being unmounted.
For advanced users overlay option also supports custom non-volatile `upperdir` and `workdir`
for the overlay mount. Custom `upperdir` and `workdir` can be fully managed by the users themselves
and `podman` will not remove it on lifecycle completion. Example `:O,upperdir=/some/upper,workdir=/some/work`
Subsequent executions of the container will see the original source directory Subsequent executions of the container will see the original source directory
content, any changes from previous container executions no longer exist. content, any changes from previous container executions no longer exist.

View File

@ -391,18 +391,52 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
} }
overlayFlag := false overlayFlag := false
upperDir := ""
workDir := ""
for _, o := range namedVol.Options { for _, o := range namedVol.Options {
if o == "O" { if o == "O" {
overlayFlag = true overlayFlag = true
} }
if overlayFlag && strings.Contains(o, "upperdir") {
splitOpt := strings.SplitN(o, "=", 2)
if len(splitOpt) > 1 {
upperDir = splitOpt[1]
if upperDir == "" {
return nil, errors.New("cannot accept empty value for upperdir")
}
}
}
if overlayFlag && strings.Contains(o, "workdir") {
splitOpt := strings.SplitN(o, "=", 2)
if len(splitOpt) > 1 {
workDir = splitOpt[1]
if workDir == "" {
return nil, errors.New("cannot accept empty value for workdir")
}
}
}
} }
if overlayFlag { if overlayFlag {
var overlayMount spec.Mount
var overlayOpts *overlay.Options
contentDir, err := overlay.TempDir(c.config.StaticDir, c.RootUID(), c.RootGID()) contentDir, err := overlay.TempDir(c.config.StaticDir, c.RootUID(), c.RootGID())
if err != nil { if err != nil {
return nil, err return nil, err
} }
overlayMount, err := overlay.Mount(contentDir, mountPoint, namedVol.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions())
if (upperDir != "" && workDir == "") || (upperDir == "" && workDir != "") {
return nil, errors.Wrapf(err, "must specify both upperdir and workdir")
}
overlayOpts = &overlay.Options{RootUID: c.RootUID(),
RootGID: c.RootGID(),
UpperDirOptionFragment: upperDir,
WorkDirOptionFragment: workDir,
GraphOpts: c.runtime.store.GraphOptions(),
}
overlayMount, err = overlay.MountWithOptions(contentDir, mountPoint, namedVol.Dest, overlayOpts)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "mounting overlay failed %q", mountPoint) return nil, errors.Wrapf(err, "mounting overlay failed %q", mountPoint)
} }

View File

@ -25,16 +25,30 @@ type defaultMountOptions struct {
// The sourcePath variable, if not empty, contains a bind mount source. // The sourcePath variable, if not empty, contains a bind mount source.
func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string, error) { func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string, error) {
var ( var (
foundWrite, foundSize, foundProp, foundMode, foundExec, foundSuid, foundDev, foundCopyUp, foundBind, foundZ, foundU bool foundWrite, foundSize, foundProp, foundMode, foundExec, foundSuid, foundDev, foundCopyUp, foundBind, foundZ, foundU, foundOverlay bool
) )
newOptions := make([]string, 0, len(options)) newOptions := make([]string, 0, len(options))
for _, opt := range options { for _, opt := range options {
// Some options have parameters - size, mode // Some options have parameters - size, mode
splitOpt := strings.SplitN(opt, "=", 2) splitOpt := strings.SplitN(opt, "=", 2)
// add advanced options such as upperdir=/path and workdir=/path, when overlay is specified
if foundOverlay {
if strings.Contains(opt, "upperdir") {
newOptions = append(newOptions, opt)
continue
}
if strings.Contains(opt, "workdir") {
newOptions = append(newOptions, opt)
continue
}
}
switch splitOpt[0] { switch splitOpt[0] {
case "idmap":
case "O": case "O":
foundOverlay = true
case "idmap":
if len(options) > 1 { if len(options) > 1 {
return nil, errors.Wrapf(ErrDupeMntOption, "'O' option can not be used with other options") return nil, errors.Wrapf(ErrDupeMntOption, "'O' option can not be used with other options")
} }

View File

@ -260,6 +260,60 @@ var _ = Describe("Podman run with volumes", func() {
}) })
It("podman support overlay on named volume with custom upperdir and workdir", func() {
SkipIfRemote("Overlay volumes only work locally")
if os.Getenv("container") != "" {
Skip("Overlay mounts not supported when running in a container")
}
if rootless.IsRootless() {
if _, err := exec.LookPath("fuse-overlayfs"); err != nil {
Skip("Fuse-Overlayfs required for rootless overlay mount test")
}
}
// create persistent upperdir on host
upperDir := filepath.Join(tempdir, "upper")
err := os.Mkdir(upperDir, 0755)
Expect(err).To(BeNil(), "mkdir "+upperDir)
// create persistent workdir on host
workDir := filepath.Join(tempdir, "work")
err = os.Mkdir(workDir, 0755)
Expect(err).To(BeNil(), "mkdir "+workDir)
overlayOpts := fmt.Sprintf("upperdir=%s,workdir=%s", upperDir, workDir)
session := podmanTest.Podman([]string{"volume", "create", "myvolume"})
session.WaitWithDefaultTimeout()
volName := session.OutputToString()
Expect(session).Should(Exit(0))
// create file on actual volume
session = podmanTest.Podman([]string{"run", "--volume", volName + ":/data", ALPINE, "sh", "-c", "echo hello >> " + "/data/test"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
// create file on overlay volume
session = podmanTest.Podman([]string{"run", "--volume", volName + ":/data:O," + overlayOpts, ALPINE, "sh", "-c", "echo hello >> " + "/data/overlay"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
session = podmanTest.Podman([]string{"run", "--volume", volName + ":/data:O," + overlayOpts, ALPINE, "sh", "-c", "ls /data"})
session.WaitWithDefaultTimeout()
// must contain `overlay` file since it should be persistent on specified upper and workdir
Expect(session.OutputToString()).To(ContainSubstring("overlay"))
// this should be there since `test` was written on actual volume not on any overlay
Expect(session.OutputToString()).To(ContainSubstring("test"))
session = podmanTest.Podman([]string{"run", "--volume", volName + ":/data:O", ALPINE, "sh", "-c", "ls /data"})
session.WaitWithDefaultTimeout()
// must not contain `overlay` file which was on custom upper and workdir since we have not specified any upper or workdir
Expect(session.OutputToString()).To(Not(ContainSubstring("overlay")))
// this should be there since `test` was written on actual volume not on any overlay
Expect(session.OutputToString()).To(ContainSubstring("test"))
})
It("podman run with noexec can't exec", func() { It("podman run with noexec can't exec", func() {
session := podmanTest.Podman([]string{"run", "--rm", "-v", "/bin:/hostbin:noexec", ALPINE, "/hostbin/ls", "/"}) session := podmanTest.Podman([]string{"run", "--rm", "-v", "/bin:/hostbin:noexec", ALPINE, "/hostbin/ls", "/"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()