forked from TrueCloudLab/frostfs-s3-gw
[#466] Implement PATCH for multipart objects
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
This commit is contained in:
parent
a2e0b92575
commit
d6b506f6d9
8 changed files with 468 additions and 40 deletions
|
@ -3,6 +3,7 @@ package handler
|
|||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/xml"
|
||||
|
@ -107,6 +108,237 @@ func TestPatch(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPatchMultipartObject(t *testing.T) {
|
||||
tc := prepareHandlerContextWithMinCache(t)
|
||||
tc.config.md5Enabled = true
|
||||
|
||||
bktName, objName, partSize := "bucket-for-multipart-patch", "object-for-multipart-patch", 5*1024*1024
|
||||
createTestBucket(tc, bktName)
|
||||
|
||||
t.Run("patch beginning of the first part", func(t *testing.T) {
|
||||
multipartInfo := createMultipartUpload(tc, bktName, objName, map[string]string{})
|
||||
etag1, data1 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 1, partSize)
|
||||
etag2, data2 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 2, partSize)
|
||||
etag3, data3 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 3, partSize)
|
||||
completeMultipartUpload(tc, bktName, objName, multipartInfo.UploadID, []string{etag1, etag2, etag3})
|
||||
|
||||
patchSize := partSize / 2
|
||||
patchBody := make([]byte, patchSize)
|
||||
_, err := rand.Read(patchBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
patchObject(t, tc, bktName, objName, "bytes 0-"+strconv.Itoa(patchSize-1)+"/*", patchBody, nil)
|
||||
object, header := getObject(tc, bktName, objName)
|
||||
contentLen, err := strconv.Atoi(header.Get(api.ContentLength))
|
||||
require.NoError(t, err)
|
||||
equalDataSlices(t, bytes.Join([][]byte{patchBody, data1[patchSize:], data2, data3}, []byte("")), object)
|
||||
require.Equal(t, partSize*3, contentLen)
|
||||
require.True(t, strings.HasSuffix(data.UnQuote(header.Get(api.ETag)), "-3"))
|
||||
})
|
||||
|
||||
t.Run("patch middle of the first part", func(t *testing.T) {
|
||||
multipartInfo := createMultipartUpload(tc, bktName, objName, map[string]string{})
|
||||
etag1, data1 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 1, partSize)
|
||||
etag2, data2 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 2, partSize)
|
||||
etag3, data3 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 3, partSize)
|
||||
completeMultipartUpload(tc, bktName, objName, multipartInfo.UploadID, []string{etag1, etag2, etag3})
|
||||
|
||||
patchSize := partSize / 2
|
||||
patchBody := make([]byte, patchSize)
|
||||
_, err := rand.Read(patchBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
patchObject(t, tc, bktName, objName, "bytes "+strconv.Itoa(partSize/4)+"-"+strconv.Itoa(partSize*3/4-1)+"/*", patchBody, nil)
|
||||
object, header := getObject(tc, bktName, objName)
|
||||
contentLen, err := strconv.Atoi(header.Get(api.ContentLength))
|
||||
require.NoError(t, err)
|
||||
equalDataSlices(t, bytes.Join([][]byte{data1[:partSize/4], patchBody, data1[partSize*3/4:], data2, data3}, []byte("")), object)
|
||||
require.Equal(t, partSize*3, contentLen)
|
||||
require.True(t, strings.HasSuffix(data.UnQuote(header.Get(api.ETag)), "-3"))
|
||||
})
|
||||
|
||||
t.Run("patch first and second parts", func(t *testing.T) {
|
||||
multipartInfo := createMultipartUpload(tc, bktName, objName, map[string]string{})
|
||||
etag1, data1 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 1, partSize)
|
||||
etag2, data2 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 2, partSize)
|
||||
etag3, data3 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 3, partSize)
|
||||
completeMultipartUpload(tc, bktName, objName, multipartInfo.UploadID, []string{etag1, etag2, etag3})
|
||||
|
||||
patchSize := partSize / 2
|
||||
patchBody := make([]byte, patchSize)
|
||||
_, err := rand.Read(patchBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
patchObject(t, tc, bktName, objName, "bytes "+strconv.Itoa(partSize*3/4)+"-"+strconv.Itoa(partSize*5/4-1)+"/*", patchBody, nil)
|
||||
object, header := getObject(tc, bktName, objName)
|
||||
contentLen, err := strconv.Atoi(header.Get(api.ContentLength))
|
||||
require.NoError(t, err)
|
||||
equalDataSlices(t, bytes.Join([][]byte{data1[:partSize*3/4], patchBody, data2[partSize/4:], data3}, []byte("")), object)
|
||||
require.Equal(t, partSize*3, contentLen)
|
||||
require.True(t, strings.HasSuffix(data.UnQuote(header.Get(api.ETag)), "-3"))
|
||||
})
|
||||
|
||||
t.Run("patch all parts", func(t *testing.T) {
|
||||
multipartInfo := createMultipartUpload(tc, bktName, objName, map[string]string{})
|
||||
etag1, data1 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 1, partSize)
|
||||
etag2, _ := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 2, partSize)
|
||||
etag3, data3 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 3, partSize)
|
||||
completeMultipartUpload(tc, bktName, objName, multipartInfo.UploadID, []string{etag1, etag2, etag3})
|
||||
|
||||
patchSize := partSize * 2
|
||||
patchBody := make([]byte, patchSize)
|
||||
_, err := rand.Read(patchBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
patchObject(t, tc, bktName, objName, "bytes "+strconv.Itoa(partSize/2-1)+"-"+strconv.Itoa(partSize/2+patchSize-2)+"/*", patchBody, nil)
|
||||
object, header := getObject(tc, bktName, objName)
|
||||
contentLen, err := strconv.Atoi(header.Get(api.ContentLength))
|
||||
require.NoError(t, err)
|
||||
equalDataSlices(t, bytes.Join([][]byte{data1[:partSize/2-1], patchBody, data3[partSize/2-1:]}, []byte("")), object)
|
||||
require.Equal(t, partSize*3, contentLen)
|
||||
require.True(t, strings.HasSuffix(data.UnQuote(header.Get(api.ETag)), "-3"))
|
||||
})
|
||||
|
||||
t.Run("patch all parts and append bytes", func(t *testing.T) {
|
||||
multipartInfo := createMultipartUpload(tc, bktName, objName, map[string]string{})
|
||||
etag1, data1 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 1, partSize)
|
||||
etag2, _ := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 2, partSize)
|
||||
etag3, _ := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 3, partSize)
|
||||
completeMultipartUpload(tc, bktName, objName, multipartInfo.UploadID, []string{etag1, etag2, etag3})
|
||||
|
||||
patchSize := partSize * 3
|
||||
patchBody := make([]byte, patchSize)
|
||||
_, err := rand.Read(patchBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
patchObject(t, tc, bktName, objName, "bytes "+strconv.Itoa(partSize/2)+"-"+strconv.Itoa(partSize/2+patchSize-1)+"/*", patchBody, nil)
|
||||
object, header := getObject(tc, bktName, objName)
|
||||
contentLen, err := strconv.Atoi(header.Get(api.ContentLength))
|
||||
require.NoError(t, err)
|
||||
equalDataSlices(t, bytes.Join([][]byte{data1[:partSize/2], patchBody}, []byte("")), object)
|
||||
require.Equal(t, partSize*7/2, contentLen)
|
||||
require.True(t, strings.HasSuffix(data.UnQuote(header.Get(api.ETag)), "-3"))
|
||||
})
|
||||
|
||||
t.Run("patch second part", func(t *testing.T) {
|
||||
multipartInfo := createMultipartUpload(tc, bktName, objName, map[string]string{})
|
||||
etag1, data1 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 1, partSize)
|
||||
etag2, _ := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 2, partSize)
|
||||
etag3, data3 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 3, partSize)
|
||||
completeMultipartUpload(tc, bktName, objName, multipartInfo.UploadID, []string{etag1, etag2, etag3})
|
||||
|
||||
patchBody := make([]byte, partSize)
|
||||
_, err := rand.Read(patchBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
patchObject(t, tc, bktName, objName, "bytes "+strconv.Itoa(partSize)+"-"+strconv.Itoa(partSize*2-1)+"/*", patchBody, nil)
|
||||
object, header := getObject(tc, bktName, objName)
|
||||
contentLen, err := strconv.Atoi(header.Get(api.ContentLength))
|
||||
require.NoError(t, err)
|
||||
equalDataSlices(t, bytes.Join([][]byte{data1, patchBody, data3}, []byte("")), object)
|
||||
require.Equal(t, partSize*3, contentLen)
|
||||
require.True(t, strings.HasSuffix(data.UnQuote(header.Get(api.ETag)), "-3"))
|
||||
})
|
||||
|
||||
t.Run("patch last part, equal size", func(t *testing.T) {
|
||||
multipartInfo := createMultipartUpload(tc, bktName, objName, map[string]string{})
|
||||
etag1, data1 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 1, partSize)
|
||||
etag2, data2 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 2, partSize)
|
||||
etag3, _ := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 3, partSize)
|
||||
completeMultipartUpload(tc, bktName, objName, multipartInfo.UploadID, []string{etag1, etag2, etag3})
|
||||
|
||||
patchBody := make([]byte, partSize)
|
||||
_, err := rand.Read(patchBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
patchObject(t, tc, bktName, objName, "bytes "+strconv.Itoa(partSize*2)+"-"+strconv.Itoa(partSize*3-1)+"/*", patchBody, nil)
|
||||
object, header := getObject(tc, bktName, objName)
|
||||
contentLen, err := strconv.Atoi(header.Get(api.ContentLength))
|
||||
require.NoError(t, err)
|
||||
equalDataSlices(t, bytes.Join([][]byte{data1, data2, patchBody}, []byte("")), object)
|
||||
require.Equal(t, partSize*3, contentLen)
|
||||
require.True(t, strings.HasSuffix(data.UnQuote(header.Get(api.ETag)), "-3"))
|
||||
})
|
||||
|
||||
t.Run("patch last part, increase size", func(t *testing.T) {
|
||||
multipartInfo := createMultipartUpload(tc, bktName, objName, map[string]string{})
|
||||
etag1, data1 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 1, partSize)
|
||||
etag2, data2 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 2, partSize)
|
||||
etag3, _ := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 3, partSize)
|
||||
completeMultipartUpload(tc, bktName, objName, multipartInfo.UploadID, []string{etag1, etag2, etag3})
|
||||
|
||||
patchBody := make([]byte, partSize+1)
|
||||
_, err := rand.Read(patchBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
patchObject(t, tc, bktName, objName, "bytes "+strconv.Itoa(partSize*2)+"-"+strconv.Itoa(partSize*3)+"/*", patchBody, nil)
|
||||
object, header := getObject(tc, bktName, objName)
|
||||
contentLen, err := strconv.Atoi(header.Get(api.ContentLength))
|
||||
require.NoError(t, err)
|
||||
equalDataSlices(t, bytes.Join([][]byte{data1, data2, patchBody}, []byte("")), object)
|
||||
require.Equal(t, partSize*3+1, contentLen)
|
||||
require.True(t, strings.HasSuffix(data.UnQuote(header.Get(api.ETag)), "-3"))
|
||||
})
|
||||
|
||||
t.Run("patch last part with offset and append bytes", func(t *testing.T) {
|
||||
multipartInfo := createMultipartUpload(tc, bktName, objName, map[string]string{})
|
||||
etag1, data1 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 1, partSize)
|
||||
etag2, data2 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 2, partSize)
|
||||
etag3, data3 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 3, partSize)
|
||||
completeMultipartUpload(tc, bktName, objName, multipartInfo.UploadID, []string{etag1, etag2, etag3})
|
||||
|
||||
patchBody := make([]byte, partSize)
|
||||
_, err := rand.Read(patchBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
patchObject(t, tc, bktName, objName, "bytes "+strconv.Itoa(partSize*2+3)+"-"+strconv.Itoa(partSize*3+2)+"/*", patchBody, nil)
|
||||
object, header := getObject(tc, bktName, objName)
|
||||
contentLen, err := strconv.Atoi(header.Get(api.ContentLength))
|
||||
require.NoError(t, err)
|
||||
equalDataSlices(t, bytes.Join([][]byte{data1, data2, data3[:3], patchBody}, []byte("")), object)
|
||||
require.Equal(t, partSize*3+3, contentLen)
|
||||
require.True(t, strings.HasSuffix(data.UnQuote(header.Get(api.ETag)), "-3"))
|
||||
})
|
||||
|
||||
t.Run("append bytes", func(t *testing.T) {
|
||||
multipartInfo := createMultipartUpload(tc, bktName, objName, map[string]string{})
|
||||
etag1, data1 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 1, partSize)
|
||||
etag2, data2 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 2, partSize)
|
||||
etag3, data3 := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 3, partSize)
|
||||
completeMultipartUpload(tc, bktName, objName, multipartInfo.UploadID, []string{etag1, etag2, etag3})
|
||||
|
||||
patchBody := make([]byte, partSize)
|
||||
_, err := rand.Read(patchBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
patchObject(t, tc, bktName, objName, "bytes "+strconv.Itoa(partSize*3)+"-"+strconv.Itoa(partSize*4-1)+"/*", patchBody, nil)
|
||||
object, header := getObject(tc, bktName, objName)
|
||||
contentLen, err := strconv.Atoi(header.Get(api.ContentLength))
|
||||
require.NoError(t, err)
|
||||
equalDataSlices(t, bytes.Join([][]byte{data1, data2, data3, patchBody}, []byte("")), object)
|
||||
require.Equal(t, partSize*4, contentLen)
|
||||
require.True(t, strings.HasSuffix(data.UnQuote(header.Get(api.ETag)), "-3"))
|
||||
})
|
||||
|
||||
t.Run("patch empty multipart", func(t *testing.T) {
|
||||
multipartInfo := createMultipartUpload(tc, bktName, objName, map[string]string{})
|
||||
etag, _ := uploadPart(tc, bktName, objName, multipartInfo.UploadID, 1, 0)
|
||||
completeMultipartUpload(tc, bktName, objName, multipartInfo.UploadID, []string{etag})
|
||||
|
||||
patchBody := make([]byte, partSize)
|
||||
_, err := rand.Read(patchBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
patchObject(t, tc, bktName, objName, "bytes 0-"+strconv.Itoa(partSize-1)+"/*", patchBody, nil)
|
||||
object, header := getObject(tc, bktName, objName)
|
||||
contentLen, err := strconv.Atoi(header.Get(api.ContentLength))
|
||||
require.NoError(t, err)
|
||||
equalDataSlices(t, patchBody, object)
|
||||
require.Equal(t, partSize, contentLen)
|
||||
require.True(t, strings.HasSuffix(data.UnQuote(header.Get(api.ETag)), "-1"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestPatchWithVersion(t *testing.T) {
|
||||
hc := prepareHandlerContextWithMinCache(t)
|
||||
bktName, objName := "bucket", "obj"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue