3cf18596e9
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>
259 lines
7.6 KiB
Go
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))
|
|
}
|
|
}
|
|
}
|