github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/integration/volume/volume_test.go (about) 1 package volume 2 3 import ( 4 "context" 5 "net/http" 6 "path/filepath" 7 "strings" 8 "testing" 9 "time" 10 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/filters" 13 "github.com/docker/docker/api/types/volume" 14 clientpkg "github.com/docker/docker/client" 15 "github.com/docker/docker/integration/internal/container" 16 "github.com/docker/docker/testutil/request" 17 "github.com/google/go-cmp/cmp/cmpopts" 18 "gotest.tools/v3/assert" 19 "gotest.tools/v3/assert/cmp" 20 is "gotest.tools/v3/assert/cmp" 21 ) 22 23 func TestVolumesCreateAndList(t *testing.T) { 24 defer setupTest(t)() 25 client := testEnv.APIClient() 26 ctx := context.Background() 27 28 name := t.Name() 29 // Windows file system is case insensitive 30 if testEnv.OSType == "windows" { 31 name = strings.ToLower(name) 32 } 33 vol, err := client.VolumeCreate(ctx, volume.CreateOptions{ 34 Name: name, 35 }) 36 assert.NilError(t, err) 37 38 expected := volume.Volume{ 39 // Ignore timestamp of CreatedAt 40 CreatedAt: vol.CreatedAt, 41 Driver: "local", 42 Scope: "local", 43 Name: name, 44 Mountpoint: filepath.Join(testEnv.DaemonInfo.DockerRootDir, "volumes", name, "_data"), 45 } 46 assert.Check(t, is.DeepEqual(vol, expected, cmpopts.EquateEmpty())) 47 48 volList, err := client.VolumeList(ctx, volume.ListOptions{}) 49 assert.NilError(t, err) 50 assert.Assert(t, len(volList.Volumes) > 0) 51 52 volumes := volList.Volumes[:0] 53 for _, v := range volList.Volumes { 54 if v.Name == vol.Name { 55 volumes = append(volumes, v) 56 } 57 } 58 59 assert.Check(t, is.Equal(len(volumes), 1)) 60 assert.Check(t, volumes[0] != nil) 61 assert.Check(t, is.DeepEqual(*volumes[0], expected, cmpopts.EquateEmpty())) 62 } 63 64 func TestVolumesRemove(t *testing.T) { 65 defer setupTest(t)() 66 client := testEnv.APIClient() 67 ctx := context.Background() 68 69 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 70 71 id := container.Create(ctx, t, client, container.WithVolume(prefix+slash+"foo")) 72 73 c, err := client.ContainerInspect(ctx, id) 74 assert.NilError(t, err) 75 vname := c.Mounts[0].Name 76 77 err = client.VolumeRemove(ctx, vname, false) 78 assert.Check(t, is.ErrorContains(err, "volume is in use")) 79 80 err = client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{ 81 Force: true, 82 }) 83 assert.NilError(t, err) 84 85 err = client.VolumeRemove(ctx, vname, false) 86 assert.NilError(t, err) 87 } 88 89 func TestVolumesInspect(t *testing.T) { 90 defer setupTest(t)() 91 client := testEnv.APIClient() 92 ctx := context.Background() 93 94 now := time.Now() 95 vol, err := client.VolumeCreate(ctx, volume.CreateOptions{}) 96 assert.NilError(t, err) 97 98 inspected, err := client.VolumeInspect(ctx, vol.Name) 99 assert.NilError(t, err) 100 101 assert.Check(t, is.DeepEqual(inspected, vol, cmpopts.EquateEmpty())) 102 103 // comparing CreatedAt field time for the new volume to now. Truncate to 1 minute precision to avoid false positive 104 createdAt, err := time.Parse(time.RFC3339, strings.TrimSpace(inspected.CreatedAt)) 105 assert.NilError(t, err) 106 assert.Check(t, createdAt.Unix()-now.Unix() < 60, "CreatedAt (%s) exceeds creation time (%s) 60s", createdAt, now) 107 } 108 109 // TestVolumesInvalidJSON tests that POST endpoints that expect a body return 110 // the correct error when sending invalid JSON requests. 111 func TestVolumesInvalidJSON(t *testing.T) { 112 defer setupTest(t)() 113 114 // POST endpoints that accept / expect a JSON body; 115 endpoints := []string{"/volumes/create"} 116 117 for _, ep := range endpoints { 118 ep := ep 119 t.Run(ep[1:], func(t *testing.T) { 120 t.Parallel() 121 122 t.Run("invalid content type", func(t *testing.T) { 123 res, body, err := request.Post(ep, request.RawString("{}"), request.ContentType("text/plain")) 124 assert.NilError(t, err) 125 assert.Check(t, is.Equal(res.StatusCode, http.StatusBadRequest)) 126 127 buf, err := request.ReadBody(body) 128 assert.NilError(t, err) 129 assert.Check(t, is.Contains(string(buf), "unsupported Content-Type header (text/plain): must be 'application/json'")) 130 }) 131 132 t.Run("invalid JSON", func(t *testing.T) { 133 res, body, err := request.Post(ep, request.RawString("{invalid json"), request.JSON) 134 assert.NilError(t, err) 135 assert.Check(t, is.Equal(res.StatusCode, http.StatusBadRequest)) 136 137 buf, err := request.ReadBody(body) 138 assert.NilError(t, err) 139 assert.Check(t, is.Contains(string(buf), "invalid JSON: invalid character 'i' looking for beginning of object key string")) 140 }) 141 142 t.Run("extra content after JSON", func(t *testing.T) { 143 res, body, err := request.Post(ep, request.RawString(`{} trailing content`), request.JSON) 144 assert.NilError(t, err) 145 assert.Check(t, is.Equal(res.StatusCode, http.StatusBadRequest)) 146 147 buf, err := request.ReadBody(body) 148 assert.NilError(t, err) 149 assert.Check(t, is.Contains(string(buf), "unexpected content after JSON")) 150 }) 151 152 t.Run("empty body", func(t *testing.T) { 153 // empty body should not produce an 500 internal server error, or 154 // any 5XX error (this is assuming the request does not produce 155 // an internal server error for another reason, but it shouldn't) 156 res, _, err := request.Post(ep, request.RawString(``), request.JSON) 157 assert.NilError(t, err) 158 assert.Check(t, res.StatusCode < http.StatusInternalServerError) 159 }) 160 }) 161 } 162 } 163 164 func getPrefixAndSlashFromDaemonPlatform() (prefix, slash string) { 165 if testEnv.OSType == "windows" { 166 return "c:", `\` 167 } 168 return "", "/" 169 } 170 171 func TestVolumePruneAnonymous(t *testing.T) { 172 defer setupTest(t)() 173 174 client := testEnv.APIClient() 175 ctx := context.Background() 176 177 // Create an anonymous volume 178 v, err := client.VolumeCreate(ctx, volume.CreateOptions{}) 179 assert.NilError(t, err) 180 181 // Create a named volume 182 vNamed, err := client.VolumeCreate(ctx, volume.CreateOptions{ 183 Name: "test", 184 }) 185 assert.NilError(t, err) 186 187 // Prune anonymous volumes 188 pruneReport, err := client.VolumesPrune(ctx, filters.Args{}) 189 assert.NilError(t, err) 190 assert.Check(t, is.Equal(len(pruneReport.VolumesDeleted), 1)) 191 assert.Check(t, is.Equal(pruneReport.VolumesDeleted[0], v.Name)) 192 193 _, err = client.VolumeInspect(ctx, vNamed.Name) 194 assert.NilError(t, err) 195 196 // Prune all volumes 197 _, err = client.VolumeCreate(ctx, volume.CreateOptions{}) 198 assert.NilError(t, err) 199 200 pruneReport, err = client.VolumesPrune(ctx, filters.NewArgs(filters.Arg("all", "1"))) 201 assert.NilError(t, err) 202 assert.Check(t, is.Equal(len(pruneReport.VolumesDeleted), 2)) 203 204 // Validate that older API versions still have the old behavior of pruning all local volumes 205 clientOld, err := clientpkg.NewClientWithOpts(clientpkg.FromEnv, clientpkg.WithVersion("1.41")) 206 assert.NilError(t, err) 207 defer clientOld.Close() 208 assert.Equal(t, clientOld.ClientVersion(), "1.41") 209 210 v, err = client.VolumeCreate(ctx, volume.CreateOptions{}) 211 assert.NilError(t, err) 212 vNamed, err = client.VolumeCreate(ctx, volume.CreateOptions{Name: "test-api141"}) 213 assert.NilError(t, err) 214 215 pruneReport, err = clientOld.VolumesPrune(ctx, filters.Args{}) 216 assert.NilError(t, err) 217 assert.Check(t, is.Equal(len(pruneReport.VolumesDeleted), 2)) 218 assert.Check(t, cmp.Contains(pruneReport.VolumesDeleted, v.Name)) 219 assert.Check(t, cmp.Contains(pruneReport.VolumesDeleted, vNamed.Name)) 220 }