| // +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)) |
| } |
| } |
| } |