forked from TrueCloudLab/distribution
fbdfd1ac35
This simple change mainly affects the distribution client. By respecting the context the caller passes in, timeouts and cancellations will work as expected. Also, transports which rely on the context (such as tracing transports that retrieve a span from the context) will work properly. Signed-off-by: Aaron Lehmann <alehmann@netflix.com>
499 lines
13 KiB
Go
499 lines
13 KiB
Go
package client
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/distribution/distribution/v3"
|
|
"github.com/distribution/distribution/v3/registry/api/errcode"
|
|
v2 "github.com/distribution/distribution/v3/registry/api/v2"
|
|
"github.com/distribution/distribution/v3/testutil"
|
|
)
|
|
|
|
// Test implements distribution.BlobWriter
|
|
var _ distribution.BlobWriter = &httpBlobUpload{}
|
|
|
|
func TestUploadReadFrom(t *testing.T) {
|
|
_, b := newRandomBlob(64)
|
|
repo := "test/upload/readfrom"
|
|
locationPath := fmt.Sprintf("/v2/%s/uploads/testid", repo)
|
|
|
|
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "GET",
|
|
Route: "/v2/",
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusOK,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Distribution-API-Version": {"registry/2.0"},
|
|
}),
|
|
},
|
|
},
|
|
// Test Valid case
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "PATCH",
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusAccepted,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Upload-UUID": {"46603072-7a1b-4b41-98f9-fd8a7da89f9b"},
|
|
"Location": {locationPath},
|
|
"Range": {"0-63"},
|
|
}),
|
|
},
|
|
},
|
|
// Test invalid range
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "PATCH",
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusAccepted,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Upload-UUID": {"46603072-7a1b-4b41-98f9-fd8a7da89f9b"},
|
|
"Location": {locationPath},
|
|
"Range": {""},
|
|
}),
|
|
},
|
|
},
|
|
// Test 404
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "PATCH",
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusNotFound,
|
|
},
|
|
},
|
|
// Test 400 valid json
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "PATCH",
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusBadRequest,
|
|
Body: []byte(`
|
|
{ "errors":
|
|
[
|
|
{
|
|
"code": "BLOB_UPLOAD_INVALID",
|
|
"message": "blob upload invalid",
|
|
"detail": "more detail"
|
|
}
|
|
]
|
|
} `),
|
|
},
|
|
},
|
|
// Test 400 invalid json
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "PATCH",
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusBadRequest,
|
|
Body: []byte("something bad happened"),
|
|
},
|
|
},
|
|
// Test 500
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "PATCH",
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusInternalServerError,
|
|
},
|
|
},
|
|
})
|
|
|
|
e, c := testServer(m)
|
|
defer c()
|
|
|
|
blobUpload := &httpBlobUpload{
|
|
ctx: context.Background(),
|
|
client: &http.Client{},
|
|
}
|
|
|
|
// Valid case
|
|
blobUpload.location = e + locationPath
|
|
n, err := blobUpload.ReadFrom(bytes.NewReader(b))
|
|
if err != nil {
|
|
t.Fatalf("Error calling ReadFrom: %s", err)
|
|
}
|
|
if n != 64 {
|
|
t.Fatalf("Wrong length returned from ReadFrom: %d, expected 64", n)
|
|
}
|
|
|
|
// Bad range
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.ReadFrom(bytes.NewReader(b))
|
|
if err == nil {
|
|
t.Fatalf("Expected error when bad range received")
|
|
}
|
|
|
|
// 404
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.ReadFrom(bytes.NewReader(b))
|
|
if err == nil {
|
|
t.Fatalf("Expected error when not found")
|
|
}
|
|
if err != distribution.ErrBlobUploadUnknown {
|
|
t.Fatalf("Wrong error thrown: %s, expected %s", err, distribution.ErrBlobUploadUnknown)
|
|
}
|
|
|
|
// 400 valid json
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.ReadFrom(bytes.NewReader(b))
|
|
if err == nil {
|
|
t.Fatalf("Expected error when not found")
|
|
}
|
|
if uploadErr, ok := err.(errcode.Errors); !ok {
|
|
t.Fatalf("Wrong error type %T: %s", err, err)
|
|
} else if len(uploadErr) != 1 {
|
|
t.Fatalf("Unexpected number of errors: %d, expected 1", len(uploadErr))
|
|
} else {
|
|
v2Err, ok := uploadErr[0].(errcode.Error)
|
|
if !ok {
|
|
t.Fatalf("Not an 'Error' type: %#v", uploadErr[0])
|
|
}
|
|
if v2Err.Code != v2.ErrorCodeBlobUploadInvalid {
|
|
t.Fatalf("Unexpected error code: %s, expected %d", v2Err.Code.String(), v2.ErrorCodeBlobUploadInvalid)
|
|
}
|
|
if expected := "blob upload invalid"; v2Err.Message != expected {
|
|
t.Fatalf("Unexpected error message: %q, expected %q", v2Err.Message, expected)
|
|
}
|
|
if expected := "more detail"; v2Err.Detail.(string) != expected {
|
|
t.Fatalf("Unexpected error message: %q, expected %q", v2Err.Detail.(string), expected)
|
|
}
|
|
}
|
|
|
|
// 400 invalid json
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.ReadFrom(bytes.NewReader(b))
|
|
if err == nil {
|
|
t.Fatalf("Expected error when not found")
|
|
}
|
|
if uploadErr, ok := err.(*UnexpectedHTTPResponseError); !ok {
|
|
t.Fatalf("Wrong error type %T: %s", err, err)
|
|
} else {
|
|
respStr := string(uploadErr.Response)
|
|
if expected := "something bad happened"; respStr != expected {
|
|
t.Fatalf("Unexpected response string: %s, expected: %s", respStr, expected)
|
|
}
|
|
}
|
|
|
|
// 500
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.ReadFrom(bytes.NewReader(b))
|
|
if err == nil {
|
|
t.Fatalf("Expected error when not found")
|
|
}
|
|
if uploadErr, ok := err.(*UnexpectedHTTPStatusError); !ok {
|
|
t.Fatalf("Wrong error type %T: %s", err, err)
|
|
} else if expected := "500 " + http.StatusText(http.StatusInternalServerError); uploadErr.Status != expected {
|
|
t.Fatalf("Unexpected response status: %s, expected %s", uploadErr.Status, expected)
|
|
}
|
|
}
|
|
|
|
func TestUploadSize(t *testing.T) {
|
|
_, b := newRandomBlob(64)
|
|
readFromLocationPath := "/v2/test/upload/readfrom/uploads/testid"
|
|
writeLocationPath := "/v2/test/upload/readfrom/uploads/testid"
|
|
|
|
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "GET",
|
|
Route: "/v2/",
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusOK,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Distribution-API-Version": {"registry/2.0"},
|
|
}),
|
|
},
|
|
},
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "PATCH",
|
|
Route: readFromLocationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusAccepted,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Upload-UUID": {"46603072-7a1b-4b41-98f9-fd8a7da89f9b"},
|
|
"Location": {readFromLocationPath},
|
|
"Range": {"0-63"},
|
|
}),
|
|
},
|
|
},
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "PATCH",
|
|
Route: writeLocationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusAccepted,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Upload-UUID": {"46603072-7a1b-4b41-98f9-fd8a7da89f9b"},
|
|
"Location": {writeLocationPath},
|
|
"Range": {"0-63"},
|
|
}),
|
|
},
|
|
},
|
|
})
|
|
|
|
e, c := testServer(m)
|
|
defer c()
|
|
|
|
// Writing with ReadFrom
|
|
blobUpload := &httpBlobUpload{
|
|
ctx: context.Background(),
|
|
client: &http.Client{},
|
|
location: e + readFromLocationPath,
|
|
}
|
|
|
|
if blobUpload.Size() != 0 {
|
|
t.Fatalf("Wrong size returned from Size: %d, expected 0", blobUpload.Size())
|
|
}
|
|
|
|
_, err := blobUpload.ReadFrom(bytes.NewReader(b))
|
|
if err != nil {
|
|
t.Fatalf("Error calling ReadFrom: %s", err)
|
|
}
|
|
|
|
if blobUpload.Size() != 64 {
|
|
t.Fatalf("Wrong size returned from Size: %d, expected 64", blobUpload.Size())
|
|
}
|
|
|
|
// Writing with Write
|
|
blobUpload = &httpBlobUpload{
|
|
ctx: context.Background(),
|
|
client: &http.Client{},
|
|
location: e + writeLocationPath,
|
|
}
|
|
|
|
_, err = blobUpload.Write(b)
|
|
if err != nil {
|
|
t.Fatalf("Error calling Write: %s", err)
|
|
}
|
|
|
|
if blobUpload.Size() != 64 {
|
|
t.Fatalf("Wrong size returned from Size: %d, expected 64", blobUpload.Size())
|
|
}
|
|
}
|
|
|
|
func TestUploadWrite(t *testing.T) {
|
|
_, b := newRandomBlob(64)
|
|
repo := "test/upload/write"
|
|
locationPath := fmt.Sprintf("/v2/%s/uploads/testid", repo)
|
|
|
|
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "GET",
|
|
Route: "/v2/",
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusOK,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Distribution-API-Version": {"registry/2.0"},
|
|
}),
|
|
},
|
|
},
|
|
// Test Valid case
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "PATCH",
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusAccepted,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Upload-UUID": {"46603072-7a1b-4b41-98f9-fd8a7da89f9b"},
|
|
"Location": {locationPath},
|
|
"Range": {"0-63"},
|
|
}),
|
|
},
|
|
},
|
|
// Test invalid range
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "PATCH",
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusAccepted,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Upload-UUID": {"46603072-7a1b-4b41-98f9-fd8a7da89f9b"},
|
|
"Location": {locationPath},
|
|
"Range": {""},
|
|
}),
|
|
},
|
|
},
|
|
// Test 404
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "PATCH",
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusNotFound,
|
|
},
|
|
},
|
|
// Test 400 valid json
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "PATCH",
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusBadRequest,
|
|
Body: []byte(`
|
|
{ "errors":
|
|
[
|
|
{
|
|
"code": "BLOB_UPLOAD_INVALID",
|
|
"message": "blob upload invalid",
|
|
"detail": "more detail"
|
|
}
|
|
]
|
|
} `),
|
|
},
|
|
},
|
|
// Test 400 invalid json
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "PATCH",
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusBadRequest,
|
|
Body: []byte("something bad happened"),
|
|
},
|
|
},
|
|
// Test 500
|
|
{
|
|
Request: testutil.Request{
|
|
Method: "PATCH",
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusInternalServerError,
|
|
},
|
|
},
|
|
})
|
|
|
|
e, c := testServer(m)
|
|
defer c()
|
|
|
|
blobUpload := &httpBlobUpload{
|
|
ctx: context.Background(),
|
|
client: &http.Client{},
|
|
}
|
|
|
|
// Valid case
|
|
blobUpload.location = e + locationPath
|
|
n, err := blobUpload.Write(b)
|
|
if err != nil {
|
|
t.Fatalf("Error calling Write: %s", err)
|
|
}
|
|
if n != 64 {
|
|
t.Fatalf("Wrong length returned from Write: %d, expected 64", n)
|
|
}
|
|
|
|
// Bad range
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.Write(b)
|
|
if err == nil {
|
|
t.Fatalf("Expected error when bad range received")
|
|
}
|
|
|
|
// 404
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.Write(b)
|
|
if err == nil {
|
|
t.Fatalf("Expected error when not found")
|
|
}
|
|
if err != distribution.ErrBlobUploadUnknown {
|
|
t.Fatalf("Wrong error thrown: %s, expected %s", err, distribution.ErrBlobUploadUnknown)
|
|
}
|
|
|
|
// 400 valid json
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.Write(b)
|
|
if err == nil {
|
|
t.Fatalf("Expected error when not found")
|
|
}
|
|
if uploadErr, ok := err.(errcode.Errors); !ok {
|
|
t.Fatalf("Wrong error type %T: %s", err, err)
|
|
} else if len(uploadErr) != 1 {
|
|
t.Fatalf("Unexpected number of errors: %d, expected 1", len(uploadErr))
|
|
} else {
|
|
v2Err, ok := uploadErr[0].(errcode.Error)
|
|
if !ok {
|
|
t.Fatalf("Not an 'Error' type: %#v", uploadErr[0])
|
|
}
|
|
if v2Err.Code != v2.ErrorCodeBlobUploadInvalid {
|
|
t.Fatalf("Unexpected error code: %s, expected %d", v2Err.Code.String(), v2.ErrorCodeBlobUploadInvalid)
|
|
}
|
|
if expected := "blob upload invalid"; v2Err.Message != expected {
|
|
t.Fatalf("Unexpected error message: %q, expected %q", v2Err.Message, expected)
|
|
}
|
|
if expected := "more detail"; v2Err.Detail.(string) != expected {
|
|
t.Fatalf("Unexpected error message: %q, expected %q", v2Err.Detail.(string), expected)
|
|
}
|
|
}
|
|
|
|
// 400 invalid json
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.Write(b)
|
|
if err == nil {
|
|
t.Fatalf("Expected error when not found")
|
|
}
|
|
if uploadErr, ok := err.(*UnexpectedHTTPResponseError); !ok {
|
|
t.Fatalf("Wrong error type %T: %s", err, err)
|
|
} else {
|
|
respStr := string(uploadErr.Response)
|
|
if expected := "something bad happened"; respStr != expected {
|
|
t.Fatalf("Unexpected response string: %s, expected: %s", respStr, expected)
|
|
}
|
|
}
|
|
|
|
// 500
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.Write(b)
|
|
if err == nil {
|
|
t.Fatalf("Expected error when not found")
|
|
}
|
|
if uploadErr, ok := err.(*UnexpectedHTTPStatusError); !ok {
|
|
t.Fatalf("Wrong error type %T: %s", err, err)
|
|
} else if expected := "500 " + http.StatusText(http.StatusInternalServerError); uploadErr.Status != expected {
|
|
t.Fatalf("Unexpected response status: %s, expected %s", uploadErr.Status, expected)
|
|
}
|
|
}
|