github.com/adityamillind98/moby@v23.0.0-rc.4+incompatible/integration/volume/volume_test.go (about)

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