moby/daemon/volumes_unix_test.go
Brian Goff 3cf18596e9 Fix issue backporting mount spec to pre-1.13 obj
In some cases a mount spec would not be properly backported which could
lead to accidental removal of the underlying volume on container remove
(which should never happen with named volumes).

Adds unit tests for this as well. Unfortunately I had to add a daemon
depdency for the backport function due to looking up `VolumesFrom`
specs.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2017-05-11 12:31:53 -04:00

259 lines
7.6 KiB
Go

// +build !windows
package daemon
import (
"encoding/json"
"fmt"
"reflect"
"testing"
containertypes "github.com/docker/docker/api/types/container"
mounttypes "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/container"
"github.com/docker/docker/volume"
)
func TestBackportMountSpec(t *testing.T) {
d := Daemon{containers: container.NewMemoryStore()}
c := &container.Container{
CommonContainer: container.CommonContainer{
State: &container.State{},
MountPoints: map[string]*volume.MountPoint{
"/apple": {Destination: "/apple", Source: "/var/lib/docker/volumes/12345678", Name: "12345678", RW: true, CopyData: true}, // anonymous volume
"/banana": {Destination: "/banana", Source: "/var/lib/docker/volumes/data", Name: "data", RW: true, CopyData: true}, // named volume
"/cherry": {Destination: "/cherry", Source: "/var/lib/docker/volumes/data", Name: "data", CopyData: true}, // RO named volume
"/dates": {Destination: "/dates", Source: "/var/lib/docker/volumes/data", Name: "data"}, // named volume nocopy
"/elderberry": {Destination: "/elderberry", Source: "/var/lib/docker/volumes/data", Name: "data"}, // masks anon vol
"/fig": {Destination: "/fig", Source: "/data", RW: true}, // RW bind
"/guava": {Destination: "/guava", Source: "/data", RW: false, Propagation: "shared"}, // RO bind + propagation
"/kumquat": {Destination: "/kumquat", Name: "data", RW: false, CopyData: true}, // volumes-from
// partially configured mountpoint due to #32613
// specifically, `mp.Spec.Source` is not set
"/honeydew": {
Type: mounttypes.TypeVolume,
Destination: "/honeydew",
Name: "data",
Source: "/var/lib/docker/volumes/data",
Spec: mounttypes.Mount{Type: mounttypes.TypeVolume, Target: "/honeydew", VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}},
},
// from hostconfig.Mounts
"/jambolan": {
Type: mounttypes.TypeVolume,
Destination: "/jambolan",
Source: "/var/lib/docker/volumes/data",
RW: true,
Name: "data",
Spec: mounttypes.Mount{Type: mounttypes.TypeVolume, Target: "/jambolan", Source: "data"},
},
},
HostConfig: &containertypes.HostConfig{
Binds: []string{
"data:/banana",
"data:/cherry:ro",
"data:/dates:ro,nocopy",
"data:/elderberry:ro,nocopy",
"/data:/fig",
"/data:/guava:ro,shared",
"data:/honeydew:nocopy",
},
VolumesFrom: []string{"1:ro"},
Mounts: []mounttypes.Mount{
{Type: mounttypes.TypeVolume, Target: "/jambolan"},
},
},
Config: &containertypes.Config{Volumes: map[string]struct{}{
"/apple": {},
"/elderberry": {},
}},
}}
d.containers.Add("1", &container.Container{
CommonContainer: container.CommonContainer{
State: &container.State{},
ID: "1",
MountPoints: map[string]*volume.MountPoint{
"/kumquat": {Destination: "/kumquat", Name: "data", RW: false, CopyData: true},
},
HostConfig: &containertypes.HostConfig{
Binds: []string{
"data:/kumquat:ro",
},
},
},
})
type expected struct {
mp *volume.MountPoint
comment string
}
pretty := func(mp *volume.MountPoint) string {
b, err := json.MarshalIndent(mp, "\t", " ")
if err != nil {
return fmt.Sprintf("%#v", mp)
}
return string(b)
}
for _, x := range []expected{
{
mp: &volume.MountPoint{
Type: mounttypes.TypeVolume,
Destination: "/apple",
RW: true,
Name: "12345678",
Source: "/var/lib/docker/volumes/12345678",
CopyData: true,
Spec: mounttypes.Mount{
Type: mounttypes.TypeVolume,
Source: "",
Target: "/apple",
},
},
comment: "anonymous volume",
},
{
mp: &volume.MountPoint{
Type: mounttypes.TypeVolume,
Destination: "/banana",
RW: true,
Name: "data",
Source: "/var/lib/docker/volumes/data",
CopyData: true,
Spec: mounttypes.Mount{
Type: mounttypes.TypeVolume,
Source: "data",
Target: "/banana",
},
},
comment: "named volume",
},
{
mp: &volume.MountPoint{
Type: mounttypes.TypeVolume,
Destination: "/cherry",
Name: "data",
Source: "/var/lib/docker/volumes/data",
CopyData: true,
Spec: mounttypes.Mount{
Type: mounttypes.TypeVolume,
Source: "data",
Target: "/cherry",
ReadOnly: true,
},
},
comment: "read-only named volume",
},
{
mp: &volume.MountPoint{
Type: mounttypes.TypeVolume,
Destination: "/dates",
Name: "data",
Source: "/var/lib/docker/volumes/data",
Spec: mounttypes.Mount{
Type: mounttypes.TypeVolume,
Source: "data",
Target: "/dates",
ReadOnly: true,
VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true},
},
},
comment: "named volume with nocopy",
},
{
mp: &volume.MountPoint{
Type: mounttypes.TypeVolume,
Destination: "/elderberry",
Name: "data",
Source: "/var/lib/docker/volumes/data",
Spec: mounttypes.Mount{
Type: mounttypes.TypeVolume,
Source: "data",
Target: "/elderberry",
ReadOnly: true,
VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true},
},
},
comment: "masks an anonymous volume",
},
{
mp: &volume.MountPoint{
Type: mounttypes.TypeBind,
Destination: "/fig",
Source: "/data",
RW: true,
Spec: mounttypes.Mount{
Type: mounttypes.TypeBind,
Source: "/data",
Target: "/fig",
},
},
comment: "bind mount with read/write",
},
{
mp: &volume.MountPoint{
Type: mounttypes.TypeBind,
Destination: "/guava",
Source: "/data",
RW: false,
Propagation: "shared",
Spec: mounttypes.Mount{
Type: mounttypes.TypeBind,
Source: "/data",
Target: "/guava",
ReadOnly: true,
BindOptions: &mounttypes.BindOptions{Propagation: "shared"},
},
},
comment: "bind mount with read/write + shared propgation",
},
{
mp: &volume.MountPoint{
Type: mounttypes.TypeVolume,
Destination: "/honeydew",
Source: "/var/lib/docker/volumes/data",
RW: true,
Propagation: "shared",
Spec: mounttypes.Mount{
Type: mounttypes.TypeVolume,
Source: "data",
Target: "/honeydew",
VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true},
},
},
comment: "partially configured named volume caused by #32613",
},
{
mp: &(*c.MountPoints["/jambolan"]), // copy the mountpoint, expect no changes
comment: "volume defined in mounts API",
},
{
mp: &volume.MountPoint{
Type: mounttypes.TypeVolume,
Destination: "/kumquat",
Source: "/var/lib/docker/volumes/data",
RW: false,
Name: "data",
Spec: mounttypes.Mount{
Type: mounttypes.TypeVolume,
Source: "data",
Target: "/kumquat",
ReadOnly: true,
},
},
comment: "partially configured named volume caused by #32613",
},
} {
mp := c.MountPoints[x.mp.Destination]
d.backportMountSpec(c)
if !reflect.DeepEqual(mp.Spec, x.mp.Spec) {
t.Fatalf("%s\nexpected:\n\t%s\n\ngot:\n\t%s", x.comment, pretty(x.mp), pretty(mp))
}
}
}