123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- package volume
- import (
- "net/http"
- "os"
- "path/filepath"
- "strings"
- "testing"
- "time"
- containertypes "github.com/docker/docker/api/types/container"
- "github.com/docker/docker/api/types/filters"
- "github.com/docker/docker/api/types/volume"
- clientpkg "github.com/docker/docker/client"
- "github.com/docker/docker/errdefs"
- "github.com/docker/docker/integration/internal/build"
- "github.com/docker/docker/integration/internal/container"
- "github.com/docker/docker/testutil"
- "github.com/docker/docker/testutil/daemon"
- "github.com/docker/docker/testutil/fakecontext"
- "github.com/docker/docker/testutil/request"
- "github.com/google/go-cmp/cmp/cmpopts"
- "gotest.tools/v3/assert"
- is "gotest.tools/v3/assert/cmp"
- "gotest.tools/v3/skip"
- )
- func TestVolumesCreateAndList(t *testing.T) {
- ctx := setupTest(t)
- client := testEnv.APIClient()
- name := t.Name()
- // Windows file system is case insensitive
- if testEnv.DaemonInfo.OSType == "windows" {
- name = strings.ToLower(name)
- }
- vol, err := client.VolumeCreate(ctx, volume.CreateOptions{
- Name: name,
- })
- assert.NilError(t, err)
- expected := volume.Volume{
- // Ignore timestamp of CreatedAt
- CreatedAt: vol.CreatedAt,
- Driver: "local",
- Scope: "local",
- Name: name,
- Mountpoint: filepath.Join(testEnv.DaemonInfo.DockerRootDir, "volumes", name, "_data"),
- }
- assert.Check(t, is.DeepEqual(vol, expected, cmpopts.EquateEmpty()))
- volList, err := client.VolumeList(ctx, volume.ListOptions{})
- assert.NilError(t, err)
- assert.Assert(t, len(volList.Volumes) > 0)
- volumes := volList.Volumes[:0]
- for _, v := range volList.Volumes {
- if v.Name == vol.Name {
- volumes = append(volumes, v)
- }
- }
- assert.Check(t, is.Equal(len(volumes), 1))
- assert.Check(t, volumes[0] != nil)
- assert.Check(t, is.DeepEqual(*volumes[0], expected, cmpopts.EquateEmpty()))
- }
- func TestVolumesRemove(t *testing.T) {
- ctx := setupTest(t)
- client := testEnv.APIClient()
- prefix, slash := getPrefixAndSlashFromDaemonPlatform()
- id := container.Create(ctx, t, client, container.WithVolume(prefix+slash+"foo"))
- c, err := client.ContainerInspect(ctx, id)
- assert.NilError(t, err)
- vname := c.Mounts[0].Name
- t.Run("volume in use", func(t *testing.T) {
- err = client.VolumeRemove(ctx, vname, false)
- assert.Check(t, is.ErrorType(err, errdefs.IsConflict))
- assert.Check(t, is.ErrorContains(err, "volume is in use"))
- })
- t.Run("volume not in use", func(t *testing.T) {
- err = client.ContainerRemove(ctx, id, containertypes.RemoveOptions{
- Force: true,
- })
- assert.NilError(t, err)
- err = client.VolumeRemove(ctx, vname, false)
- assert.NilError(t, err)
- })
- t.Run("non-existing volume", func(t *testing.T) {
- err = client.VolumeRemove(ctx, "no_such_volume", false)
- assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
- })
- t.Run("non-existing volume force", func(t *testing.T) {
- err = client.VolumeRemove(ctx, "no_such_volume", true)
- assert.NilError(t, err)
- })
- }
- // TestVolumesRemoveSwarmEnabled tests that an error is returned if a volume
- // is in use, also if swarm is enabled (and cluster volumes are supported).
- //
- // Regression test for https://github.com/docker/cli/issues/4082
- func TestVolumesRemoveSwarmEnabled(t *testing.T) {
- skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
- skip.If(t, testEnv.DaemonInfo.OSType == "windows", "TODO enable on windows")
- t.Parallel()
- ctx := setupTest(t)
- // Spin up a new daemon, so that we can run this test in parallel (it's a slow test)
- d := daemon.New(t)
- d.StartAndSwarmInit(ctx, t)
- defer d.Stop(t)
- client := d.NewClientT(t)
- prefix, slash := getPrefixAndSlashFromDaemonPlatform()
- id := container.Create(ctx, t, client, container.WithVolume(prefix+slash+"foo"))
- c, err := client.ContainerInspect(ctx, id)
- assert.NilError(t, err)
- vname := c.Mounts[0].Name
- t.Run("volume in use", func(t *testing.T) {
- err = client.VolumeRemove(ctx, vname, false)
- assert.Check(t, is.ErrorType(err, errdefs.IsConflict))
- assert.Check(t, is.ErrorContains(err, "volume is in use"))
- })
- t.Run("volume not in use", func(t *testing.T) {
- err = client.ContainerRemove(ctx, id, containertypes.RemoveOptions{
- Force: true,
- })
- assert.NilError(t, err)
- err = client.VolumeRemove(ctx, vname, false)
- assert.NilError(t, err)
- })
- t.Run("non-existing volume", func(t *testing.T) {
- err = client.VolumeRemove(ctx, "no_such_volume", false)
- assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
- })
- t.Run("non-existing volume force", func(t *testing.T) {
- err = client.VolumeRemove(ctx, "no_such_volume", true)
- assert.NilError(t, err)
- })
- }
- func TestVolumesInspect(t *testing.T) {
- ctx := setupTest(t)
- client := testEnv.APIClient()
- now := time.Now()
- vol, err := client.VolumeCreate(ctx, volume.CreateOptions{})
- assert.NilError(t, err)
- inspected, err := client.VolumeInspect(ctx, vol.Name)
- assert.NilError(t, err)
- assert.Check(t, is.DeepEqual(inspected, vol, cmpopts.EquateEmpty()))
- // comparing CreatedAt field time for the new volume to now. Truncate to 1 minute precision to avoid false positive
- createdAt, err := time.Parse(time.RFC3339, strings.TrimSpace(inspected.CreatedAt))
- assert.NilError(t, err)
- assert.Check(t, createdAt.Unix()-now.Unix() < 60, "CreatedAt (%s) exceeds creation time (%s) 60s", createdAt, now)
- // update atime and mtime for the "_data" directory (which would happen during volume initialization)
- modifiedAt := time.Now().Local().Add(5 * time.Hour)
- err = os.Chtimes(inspected.Mountpoint, modifiedAt, modifiedAt)
- assert.NilError(t, err)
- inspected, err = client.VolumeInspect(ctx, vol.Name)
- assert.NilError(t, err)
- createdAt2, err := time.Parse(time.RFC3339, strings.TrimSpace(inspected.CreatedAt))
- assert.NilError(t, err)
- // Check that CreatedAt didn't change after updating atime and mtime of the "_data" directory
- // Related issue: #38274
- assert.Equal(t, createdAt, createdAt2)
- }
- // TestVolumesInvalidJSON tests that POST endpoints that expect a body return
- // the correct error when sending invalid JSON requests.
- func TestVolumesInvalidJSON(t *testing.T) {
- ctx := setupTest(t)
- // POST endpoints that accept / expect a JSON body;
- endpoints := []string{"/volumes/create"}
- for _, ep := range endpoints {
- ep := ep
- t.Run(ep[1:], func(t *testing.T) {
- t.Parallel()
- ctx := testutil.StartSpan(ctx, t)
- t.Run("invalid content type", func(t *testing.T) {
- ctx := testutil.StartSpan(ctx, t)
- res, body, err := request.Post(ctx, ep, request.RawString("{}"), request.ContentType("text/plain"))
- assert.NilError(t, err)
- assert.Check(t, is.Equal(res.StatusCode, http.StatusBadRequest))
- buf, err := request.ReadBody(body)
- assert.NilError(t, err)
- assert.Check(t, is.Contains(string(buf), "unsupported Content-Type header (text/plain): must be 'application/json'"))
- })
- t.Run("invalid JSON", func(t *testing.T) {
- ctx := testutil.StartSpan(ctx, t)
- res, body, err := request.Post(ctx, ep, request.RawString("{invalid json"), request.JSON)
- assert.NilError(t, err)
- assert.Check(t, is.Equal(res.StatusCode, http.StatusBadRequest))
- buf, err := request.ReadBody(body)
- assert.NilError(t, err)
- assert.Check(t, is.Contains(string(buf), "invalid JSON: invalid character 'i' looking for beginning of object key string"))
- })
- t.Run("extra content after JSON", func(t *testing.T) {
- ctx := testutil.StartSpan(ctx, t)
- res, body, err := request.Post(ctx, ep, request.RawString(`{} trailing content`), request.JSON)
- assert.NilError(t, err)
- assert.Check(t, is.Equal(res.StatusCode, http.StatusBadRequest))
- buf, err := request.ReadBody(body)
- assert.NilError(t, err)
- assert.Check(t, is.Contains(string(buf), "unexpected content after JSON"))
- })
- t.Run("empty body", func(t *testing.T) {
- ctx := testutil.StartSpan(ctx, t)
- // empty body should not produce an 500 internal server error, or
- // any 5XX error (this is assuming the request does not produce
- // an internal server error for another reason, but it shouldn't)
- res, _, err := request.Post(ctx, ep, request.RawString(``), request.JSON)
- assert.NilError(t, err)
- assert.Check(t, res.StatusCode < http.StatusInternalServerError)
- })
- })
- }
- }
- func getPrefixAndSlashFromDaemonPlatform() (prefix, slash string) {
- if testEnv.DaemonInfo.OSType == "windows" {
- return "c:", `\`
- }
- return "", "/"
- }
- func TestVolumePruneAnonymous(t *testing.T) {
- ctx := setupTest(t)
- client := testEnv.APIClient()
- // Create an anonymous volume
- v, err := client.VolumeCreate(ctx, volume.CreateOptions{})
- assert.NilError(t, err)
- // Create a named volume
- vNamed, err := client.VolumeCreate(ctx, volume.CreateOptions{
- Name: "test",
- })
- assert.NilError(t, err)
- // Prune anonymous volumes
- pruneReport, err := client.VolumesPrune(ctx, filters.Args{})
- assert.NilError(t, err)
- assert.Check(t, is.Equal(len(pruneReport.VolumesDeleted), 1))
- assert.Check(t, is.Equal(pruneReport.VolumesDeleted[0], v.Name))
- _, err = client.VolumeInspect(ctx, vNamed.Name)
- assert.NilError(t, err)
- // Prune all volumes
- _, err = client.VolumeCreate(ctx, volume.CreateOptions{})
- assert.NilError(t, err)
- pruneReport, err = client.VolumesPrune(ctx, filters.NewArgs(filters.Arg("all", "1")))
- assert.NilError(t, err)
- assert.Check(t, is.Equal(len(pruneReport.VolumesDeleted), 2))
- // Validate that older API versions still have the old behavior of pruning all local volumes
- clientOld, err := clientpkg.NewClientWithOpts(clientpkg.FromEnv, clientpkg.WithVersion("1.41"))
- assert.NilError(t, err)
- defer clientOld.Close()
- assert.Equal(t, clientOld.ClientVersion(), "1.41")
- v, err = client.VolumeCreate(ctx, volume.CreateOptions{})
- assert.NilError(t, err)
- vNamed, err = client.VolumeCreate(ctx, volume.CreateOptions{Name: "test-api141"})
- assert.NilError(t, err)
- pruneReport, err = clientOld.VolumesPrune(ctx, filters.Args{})
- assert.NilError(t, err)
- assert.Check(t, is.Equal(len(pruneReport.VolumesDeleted), 2))
- assert.Check(t, is.Contains(pruneReport.VolumesDeleted, v.Name))
- assert.Check(t, is.Contains(pruneReport.VolumesDeleted, vNamed.Name))
- }
- func TestVolumePruneAnonFromImage(t *testing.T) {
- ctx := setupTest(t)
- client := testEnv.APIClient()
- volDest := "/foo"
- if testEnv.DaemonInfo.OSType == "windows" {
- volDest = `c:\\foo`
- }
- dockerfile := `FROM busybox
- VOLUME ` + volDest
- img := build.Do(ctx, t, client, fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile)))
- id := container.Create(ctx, t, client, container.WithImage(img))
- defer client.ContainerRemove(ctx, id, containertypes.RemoveOptions{})
- inspect, err := client.ContainerInspect(ctx, id)
- assert.NilError(t, err)
- assert.Assert(t, is.Len(inspect.Mounts, 1))
- volumeName := inspect.Mounts[0].Name
- assert.Assert(t, volumeName != "")
- err = client.ContainerRemove(ctx, id, containertypes.RemoveOptions{})
- assert.NilError(t, err)
- pruneReport, err := client.VolumesPrune(ctx, filters.Args{})
- assert.NilError(t, err)
- assert.Assert(t, is.Contains(pruneReport.VolumesDeleted, volumeName))
- }
|