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

When first mounting any named volume, copy up #3961

Merged
merged 1 commit into from
Sep 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 68 additions & 32 deletions libpod/container_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/mount"
"github.com/cyphar/filepath-securejoin"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label"
Expand Down Expand Up @@ -1234,43 +1235,82 @@ func (c *Container) mountStorage() (_ string, Err error) {
}()
}

// We need to mount the container before volumes - to ensure the copyup
// works properly.
mountPoint := c.config.Rootfs
if mountPoint == "" {
mountPoint, err = c.mount()
if err != nil {
return "", err
}
defer func() {
if Err != nil {
if err := c.unmount(false); err != nil {
logrus.Errorf("Error unmounting container %s after mount error: %v", c.ID(), err)
}
}
}()
}

// Request a mount of all named volumes
for _, v := range c.config.NamedVolumes {
vol, err := c.runtime.state.Volume(v.Name)
vol, err := c.mountNamedVolume(v, mountPoint)
if err != nil {
return "", errors.Wrapf(err, "error retrieving named volume %s for container %s", v.Name, c.ID())
return "", err
}

if vol.needsMount() {
defer func() {
if Err == nil {
return
}
vol.lock.Lock()
if err := vol.mount(); err != nil {
vol.lock.Unlock()
return "", errors.Wrapf(err, "error mounting volume %s for container %s", vol.Name(), c.ID())
if err := vol.unmount(false); err != nil {
logrus.Errorf("Error unmounting volume %s after error mounting container %s: %v", vol.Name(), c.ID(), err)
}
vol.lock.Unlock()
defer func() {
if Err == nil {
return
}
vol.lock.Lock()
if err := vol.unmount(false); err != nil {
logrus.Errorf("Error unmounting volume %s after error mounting container %s: %v", vol.Name(), c.ID(), err)
}
vol.lock.Unlock()
}()
}
}()
}

// TODO: generalize this mount code so it will mount every mount in ctr.config.Mounts
mountPoint := c.config.Rootfs
if mountPoint == "" {
mountPoint, err = c.mount()
if err != nil {
return "", err
return mountPoint, nil
}

// Mount a single named volume into the container.
// If necessary, copy up image contents into the volume.
// Does not verify that the name volume given is actually present in container
// config.
// Returns the volume that was mounted.
func (c *Container) mountNamedVolume(v *ContainerNamedVolume, mountpoint string) (*Volume, error) {
vol, err := c.runtime.state.Volume(v.Name)
if err != nil {
return nil, errors.Wrapf(err, "error retrieving named volume %s for container %s", v.Name, c.ID())
}

vol.lock.Lock()
defer vol.lock.Unlock()
if vol.needsMount() {
if err := vol.mount(); err != nil {
return nil, errors.Wrapf(err, "error mounting volume %s for container %s", vol.Name(), c.ID())
}
}
// The volume may need a copy-up. Check the state.
if err := vol.update(); err != nil {
return nil, err
}
if vol.state.NeedsCopyUp {
logrus.Debugf("Copying up contents from container %s to volume %s", c.ID(), vol.Name())
srcDir, err := securejoin.SecureJoin(mountpoint, v.Dest)
if err != nil {
return nil, errors.Wrapf(err, "error calculating destination path to copy up container %s volume %s", c.ID(), vol.Name())
}
if err := c.copyWithTarFromImage(srcDir, vol.MountPoint()); err != nil && !os.IsNotExist(err) {
return nil, errors.Wrapf(err, "error copying content from container %s into volume %s", c.ID(), vol.Name())
}

return mountPoint, nil
vol.state.NeedsCopyUp = false
if err := vol.save(); err != nil {
return nil, err
}
}
return vol, nil
}

// cleanupStorage unmounts and cleans up the container's root filesystem
Expand Down Expand Up @@ -1614,15 +1654,11 @@ func (c *Container) unmount(force bool) error {
}

// this should be from chrootarchive.
func (c *Container) copyWithTarFromImage(src, dest string) error {
mountpoint, err := c.mount()
if err != nil {
return err
}
// Container MUST be mounted before calling.
func (c *Container) copyWithTarFromImage(source, dest string) error {
a := archive.NewDefaultArchiver()
source := filepath.Join(mountpoint, src)

if err = c.copyOwnerAndPerms(source, dest); err != nil {
if err := c.copyOwnerAndPerms(source, dest); err != nil {
return err
}
return a.CopyWithTar(source, dest)
Expand Down
4 changes: 3 additions & 1 deletion libpod/container_internal_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ func (c *Container) prepare() (Err error) {
createErr = createNetNSErr
}
if mountStorageErr != nil {
logrus.Errorf("Error preparing container %s: %v", c.ID(), createErr)
if createErr != nil {
logrus.Errorf("Error preparing container %s: %v", c.ID(), createErr)
}
createErr = mountStorageErr
}

Expand Down
4 changes: 0 additions & 4 deletions libpod/runtime_ctr.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,10 +275,6 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (c *Contai
return nil, errors.Wrapf(err, "error creating named volume %q", vol.Name)
}

if err := ctr.copyWithTarFromImage(vol.Dest, newVol.MountPoint()); err != nil && !os.IsNotExist(err) {
return nil, errors.Wrapf(err, "Failed to copy content into new volume mount %q", vol.Name)
}

ctrNamedVolumes = append(ctrNamedVolumes, newVol)
}

Expand Down
7 changes: 7 additions & 0 deletions libpod/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ type VolumeState struct {
// On incrementing from 0, the volume will be mounted on the host.
// On decrementing to 0, the volume will be unmounted on the host.
MountCount uint `json:"mountCount"`
// NeedsCopyUp indicates that the next time the volume is mounted into
// a container, the container will "copy up" the contents of the
// mountpoint into the volume.
// This should only be done once. As such, this is set at container
// create time, then cleared after the copy up is done and never set
// again.
NeedsCopyUp bool `json:"notYetMounted,omitempty"`
}

// Name retrieves the volume's name
Expand Down
2 changes: 2 additions & 0 deletions libpod/volume_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import (
func newVolume(runtime *Runtime) (*Volume, error) {
volume := new(Volume)
volume.config = new(VolumeConfig)
volume.state = new(VolumeState)
volume.runtime = runtime
volume.config.Labels = make(map[string]string)
volume.config.Options = make(map[string]string)
volume.state.NeedsCopyUp = true

return volume, nil
}
Expand Down
21 changes: 21 additions & 0 deletions test/e2e/run_volume_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,4 +249,25 @@ var _ = Describe("Podman run with volumes", func() {
fmt.Printf("Output: %s", mountOut3)
Expect(strings.Contains(mountOut3, volName)).To(BeFalse())
})

It("podman named volume copyup", func() {
baselineSession := podmanTest.Podman([]string{"run", "--rm", "-t", "-i", ALPINE, "ls", "/etc/apk/"})
baselineSession.WaitWithDefaultTimeout()
Expect(baselineSession.ExitCode()).To(Equal(0))
baselineOutput := baselineSession.OutputToString()

inlineVolumeSession := podmanTest.Podman([]string{"run", "--rm", "-t", "-i", "-v", "testvol1:/etc/apk", ALPINE, "ls", "/etc/apk/"})
inlineVolumeSession.WaitWithDefaultTimeout()
Expect(inlineVolumeSession.ExitCode()).To(Equal(0))
Expect(inlineVolumeSession.OutputToString()).To(Equal(baselineOutput))

makeVolumeSession := podmanTest.Podman([]string{"volume", "create", "testvol2"})
makeVolumeSession.WaitWithDefaultTimeout()
Expect(makeVolumeSession.ExitCode()).To(Equal(0))

separateVolumeSession := podmanTest.Podman([]string{"run", "--rm", "-t", "-i", "-v", "testvol2:/etc/apk", ALPINE, "ls", "/etc/apk/"})
separateVolumeSession.WaitWithDefaultTimeout()
Expect(separateVolumeSession.ExitCode()).To(Equal(0))
Expect(separateVolumeSession.OutputToString()).To(Equal(baselineOutput))
})
})