From 49ee00a953538645525f3eba690d9257755ac1ff Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Fri, 12 Apr 2019 08:46:12 -0400 Subject: [PATCH] Support nfs local volume mounts Allow users to create volumes with nfs shares. These volumes will get mounted when the first container gets started and then will remain mounted until the system reboots or the volume is removed. Signed-off-by: Daniel J Walsh --- cmd/podman/volume_create.go | 4 +-- docs/podman-volume-create.1.md | 10 +++++- libpod/runtime_ctr.go | 7 ++++ libpod/volume.go | 62 ++++++++++++++++++++++++++++++++++ libpod/volume_internal.go | 1 + 5 files changed, 81 insertions(+), 3 deletions(-) diff --git a/cmd/podman/volume_create.go b/cmd/podman/volume_create.go index 84f6bba9413..7342d9110a7 100644 --- a/cmd/podman/volume_create.go +++ b/cmd/podman/volume_create.go @@ -25,8 +25,8 @@ var ( return volumeCreateCmd(&volumeCreateCommand) }, Example: `podman volume create myvol - podman volume create - podman volume create --label foo=bar myvol`, + podman volume create --label foo=bar + podman volume create --opt type=nfs --opt o=addr=192.168.0.2,rw --opt device=:/nfsshare mynfsvol`, } ) diff --git a/docs/podman-volume-create.1.md b/docs/podman-volume-create.1.md index 795d7b44942..5cf04720765 100644 --- a/docs/podman-volume-create.1.md +++ b/docs/podman-volume-create.1.md @@ -29,7 +29,13 @@ Set metadata for a volume (e.g., --label mykey=value). **-o**, **--opt**=[] -Set driver specific options. +Set driver specific options. To setup NFS volume you need to specify: + + type: `-o type=nfs` To indicate the nfs mount. + + o: `-o o=addr=nfsserver.example.com,rw` Options including the address of the nfs server. + + device: `-o device=/nfsshare`, the remote nfs share. ## EXAMPLES @@ -39,6 +45,8 @@ $ podman volume create myvol $ podman volume create $ podman volume create --label foo=bar myvol + +# podman volume create --opt type=nfs --opt o=addr=192.168.0.2,rw --opt device=/nfsshare mynfsvol ``` ## SEE ALSO diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index c7758055fe9..d90a3493a06 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -191,6 +191,13 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. return nil, errors.Wrapf(err, "error creating named volume %q", vol.Name) } + options := newVol.Options() + if len(options) > 0 { + if err := newVol.Mount(); err != nil { + return nil, err + } + } + 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) } diff --git a/libpod/volume.go b/libpod/volume.go index 0b37d44efef..b179db75dd0 100644 --- a/libpod/volume.go +++ b/libpod/volume.go @@ -1,5 +1,13 @@ package libpod +import ( + "net" + "strings" + + "github.com/containers/storage/pkg/mount" + "github.com/pkg/errors" +) + // Volume is the type used to create named volumes // TODO: all volumes should be created using this and the Volume API type Volume struct { @@ -70,3 +78,57 @@ func (v *Volume) Scope() string { func (v *Volume) IsCtrSpecific() bool { return v.config.IsCtrSpecific } + +// Mount the volume +func (v *Volume) Mount() error { + if v.MountPoint() == "" { + return errors.Errorf("missing device in volume options") + } + mounted, err := mount.Mounted(v.MountPoint()) + if err != nil { + return errors.Wrapf(err, "failed to determine if %v is mounted", v.Name()) + } + if mounted { + return nil + } + options := v.Options() + if len(options) == 0 { + return errors.Errorf("volume %v is not mountable, no options available", v.Name()) + } + mountOpts := options["o"] + device := options["device"] + if options["type"] == "nfs" { + if addrValue := getAddress(mountOpts); addrValue != "" && net.ParseIP(addrValue).To4() == nil { + ipAddr, err := net.ResolveIPAddr("ip", addrValue) + if err != nil { + return errors.Wrapf(err, "error resolving passed in nfs address") + } + mountOpts = strings.Replace(mountOpts, "addr="+addrValue, "addr="+ipAddr.String(), 1) + } + if device[0] != ':' { + device = ":" + device + } + } + err = mount.Mount(device, v.MountPoint(), options["type"], mountOpts) + return errors.Wrap(err, "failed to mount local volume") +} + +// Unmount the volume from the system +func (v *Volume) Unmount() error { + if v.MountPoint() == "" { + return errors.Errorf("missing device in volume options") + } + return mount.Unmount(v.MountPoint()) +} + +// getAddress finds out address/hostname from options +func getAddress(opts string) string { + optsList := strings.Split(opts, ",") + for i := 0; i < len(optsList); i++ { + if strings.HasPrefix(optsList[i], "addr=") { + addr := strings.SplitN(optsList[i], "=", 2)[1] + return addr + } + } + return "" +} diff --git a/libpod/volume_internal.go b/libpod/volume_internal.go index 35f0ca19d60..96f8e471b02 100644 --- a/libpod/volume_internal.go +++ b/libpod/volume_internal.go @@ -18,5 +18,6 @@ func newVolume(runtime *Runtime) (*Volume, error) { // teardownStorage deletes the volume from volumePath func (v *Volume) teardownStorage() error { + v.Unmount() return os.RemoveAll(filepath.Join(v.runtime.config.VolumePath, v.Name())) }