mirror of
https://github.com/pgsty/minio.git
synced 2026-11-04 06:42:04 +03:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a880283593 | |||
| e464a5bfbc | |||
| eb6bf454f1 | |||
| ec06089eda | |||
| 0c4be55936 | |||
| 11d21d5d1b | |||
| f9648d3976 | |||
| 2955aae8e4 | |||
| 9fd836e51f | |||
| 518f44908c | |||
| 38f60b3c1d |
+6
-2
@@ -214,8 +214,8 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool)
|
||||
// GetBucketNotification
|
||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||
maxClients(collectAPIStats("getbucketnotification", httpTraceAll(api.GetBucketNotificationHandler)))).Queries("notification", "")
|
||||
// ListenBucketNotification
|
||||
bucket.Methods(http.MethodGet).HandlerFunc(collectAPIStats("listenbucketnotification", httpTraceAll(api.ListenBucketNotificationHandler))).Queries("events", "{events:.*}")
|
||||
// ListenNotification
|
||||
bucket.Methods(http.MethodGet).HandlerFunc(collectAPIStats("listennotification", httpTraceAll(api.ListenNotificationHandler))).Queries("events", "{events:.*}")
|
||||
// ListMultipartUploads
|
||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||
maxClients(collectAPIStats("listmultipartuploads", httpTraceAll(api.ListMultipartUploadsHandler)))).Queries("uploads", "")
|
||||
@@ -282,6 +282,10 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool)
|
||||
|
||||
/// Root operation
|
||||
|
||||
// ListenNotification
|
||||
apiRouter.Methods(http.MethodGet).Path(SlashSeparator).HandlerFunc(
|
||||
collectAPIStats("listennotification", httpTraceAll(api.ListenNotificationHandler))).Queries("events", "{events:.*}")
|
||||
|
||||
// ListBuckets
|
||||
apiRouter.Methods(http.MethodGet).Path(SlashSeparator).HandlerFunc(
|
||||
maxClients(collectAPIStats("listbuckets", httpTraceAll(api.ListBucketsHandler))))
|
||||
|
||||
+15
-15
@@ -52,7 +52,7 @@ func TestGetRequestAuthType(t *testing.T) {
|
||||
"X-Amz-Content-Sha256": []string{streamingContentSHA256},
|
||||
"Content-Encoding": []string{streamingContentEncoding},
|
||||
},
|
||||
Method: "PUT",
|
||||
Method: http.MethodPut,
|
||||
},
|
||||
authT: authTypeStreamingSigned,
|
||||
},
|
||||
@@ -111,7 +111,7 @@ func TestGetRequestAuthType(t *testing.T) {
|
||||
Header: http.Header{
|
||||
"Content-Type": []string{"multipart/form-data"},
|
||||
},
|
||||
Method: "POST",
|
||||
Method: http.MethodPost,
|
||||
},
|
||||
authT: authTypePostPolicy,
|
||||
},
|
||||
@@ -212,7 +212,7 @@ func TestIsRequestPresignedSignatureV2(t *testing.T) {
|
||||
for i, testCase := range testCases {
|
||||
// creating an input HTTP request.
|
||||
// Only the query parameters are relevant for this particular test.
|
||||
inputReq, err := http.NewRequest("GET", "http://example.com", nil)
|
||||
inputReq, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error initializing input HTTP request: %v", err)
|
||||
}
|
||||
@@ -246,7 +246,7 @@ func TestIsRequestPresignedSignatureV4(t *testing.T) {
|
||||
for i, testCase := range testCases {
|
||||
// creating an input HTTP request.
|
||||
// Only the query parameters are relevant for this particular test.
|
||||
inputReq, err := http.NewRequest("GET", "http://example.com", nil)
|
||||
inputReq, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error initializing input HTTP request: %v", err)
|
||||
}
|
||||
@@ -369,15 +369,15 @@ func TestIsReqAuthenticated(t *testing.T) {
|
||||
s3Error APIErrorCode
|
||||
}{
|
||||
// When request is unsigned, access denied is returned.
|
||||
{mustNewRequest("GET", "http://127.0.0.1:9000", 0, nil, t), ErrAccessDenied},
|
||||
{mustNewRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrAccessDenied},
|
||||
// Empty Content-Md5 header.
|
||||
{mustNewSignedEmptyMD5Request("PUT", "http://127.0.0.1:9000/", 5, bytes.NewReader([]byte("hello")), t), ErrInvalidDigest},
|
||||
{mustNewSignedEmptyMD5Request(http.MethodPut, "http://127.0.0.1:9000/", 5, bytes.NewReader([]byte("hello")), t), ErrInvalidDigest},
|
||||
// Short Content-Md5 header.
|
||||
{mustNewSignedShortMD5Request("PUT", "http://127.0.0.1:9000/", 5, bytes.NewReader([]byte("hello")), t), ErrInvalidDigest},
|
||||
{mustNewSignedShortMD5Request(http.MethodPut, "http://127.0.0.1:9000/", 5, bytes.NewReader([]byte("hello")), t), ErrInvalidDigest},
|
||||
// When request is properly signed, but has bad Content-MD5 header.
|
||||
{mustNewSignedBadMD5Request("PUT", "http://127.0.0.1:9000/", 5, bytes.NewReader([]byte("hello")), t), ErrBadDigest},
|
||||
{mustNewSignedBadMD5Request(http.MethodPut, "http://127.0.0.1:9000/", 5, bytes.NewReader([]byte("hello")), t), ErrBadDigest},
|
||||
// When request is properly signed, error is none.
|
||||
{mustNewSignedRequest("GET", "http://127.0.0.1:9000", 0, nil, t), ErrNone},
|
||||
{mustNewSignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrNone},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
@@ -413,11 +413,11 @@ func TestCheckAdminRequestAuthType(t *testing.T) {
|
||||
Request *http.Request
|
||||
ErrCode APIErrorCode
|
||||
}{
|
||||
{Request: mustNewRequest("GET", "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||
{Request: mustNewSignedRequest("GET", "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrNone},
|
||||
{Request: mustNewSignedV2Request("GET", "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||
{Request: mustNewPresignedV2Request("GET", "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||
{Request: mustNewPresignedRequest("GET", "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||
{Request: mustNewRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||
{Request: mustNewSignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrNone},
|
||||
{Request: mustNewSignedV2Request(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||
{Request: mustNewPresignedV2Request(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||
{Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||
}
|
||||
ctx := context.Background()
|
||||
for i, testCase := range testCases {
|
||||
@@ -461,7 +461,7 @@ func TestValidateAdminSignature(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
req := mustNewRequest("GET", "http://localhost:9000/", 0, nil, t)
|
||||
req := mustNewRequest(http.MethodGet, "http://localhost:9000/", 0, nil, t)
|
||||
if err := signRequestV4(req, testCase.AccessKey, testCase.SecretKey); err != nil {
|
||||
t.Fatalf("Unable to inititalized new signed http request %s", err)
|
||||
}
|
||||
|
||||
@@ -578,6 +578,16 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
||||
getObjectLocation(r, globalDomainNames, bucket, ""))
|
||||
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.BucketCreated,
|
||||
BucketName: bucket,
|
||||
ReqParams: extractReqParams(r),
|
||||
RespElements: extractRespElements(w),
|
||||
UserAgent: r.UserAgent(),
|
||||
Host: handlers.GetSourceIP(r),
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
@@ -610,6 +620,15 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
||||
w.Header().Set(xhttp.Location, path.Clean(r.URL.Path)) // Clean any trailing slashes.
|
||||
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.BucketCreated,
|
||||
BucketName: bucket,
|
||||
ReqParams: extractReqParams(r),
|
||||
RespElements: extractRespElements(w),
|
||||
UserAgent: r.UserAgent(),
|
||||
Host: handlers.GetSourceIP(r),
|
||||
})
|
||||
}
|
||||
|
||||
// PostPolicyBucketHandler - POST policy
|
||||
@@ -984,6 +1003,15 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
|
||||
|
||||
// Write success response.
|
||||
writeSuccessNoContent(w)
|
||||
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.BucketRemoved,
|
||||
BucketName: bucket,
|
||||
ReqParams: extractReqParams(r),
|
||||
RespElements: extractRespElements(w),
|
||||
UserAgent: r.UserAgent(),
|
||||
Host: handlers.GetSourceIP(r),
|
||||
})
|
||||
}
|
||||
|
||||
// PutBucketObjectLockConfigHandler - PUT Bucket object lock configuration.
|
||||
|
||||
+22
-22
@@ -45,7 +45,7 @@ func testRemoveBucketHandler(obj ObjectLayer, instanceType, bucketName string, a
|
||||
// initialize httptest Recorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for DELETE bucket.
|
||||
req, err := newTestSignedRequestV4("DELETE", getBucketLocationURL("", bucketName), 0, nil, credentials.AccessKey, credentials.SecretKey, nil)
|
||||
req, err := newTestSignedRequestV4(http.MethodDelete, getBucketLocationURL("", bucketName), 0, nil, credentials.AccessKey, credentials.SecretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %s: Failed to create HTTP request for RemoveBucketHandler: <ERROR> %v", instanceType, err)
|
||||
}
|
||||
@@ -61,7 +61,7 @@ func testRemoveBucketHandler(obj ObjectLayer, instanceType, bucketName string, a
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for DELETE bucket.
|
||||
reqV2, err := newTestSignedRequestV2("DELETE", getBucketLocationURL("", bucketName), 0, nil, credentials.AccessKey, credentials.SecretKey, nil)
|
||||
reqV2, err := newTestSignedRequestV2(http.MethodDelete, getBucketLocationURL("", bucketName), 0, nil, credentials.AccessKey, credentials.SecretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %s: Failed to create HTTP request for RemoveBucketHandler: <ERROR> %v", instanceType, err)
|
||||
}
|
||||
@@ -129,7 +129,7 @@ func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName stri
|
||||
// initialize httptest Recorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for Get bucket location.
|
||||
req, err := newTestSignedRequestV4("GET", getBucketLocationURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
req, err := newTestSignedRequestV4(http.MethodGet, getBucketLocationURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for GetBucketLocationHandler: <ERROR> %v", i+1, instanceType, err)
|
||||
}
|
||||
@@ -161,7 +161,7 @@ func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName stri
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
reqV2, err := newTestSignedRequestV2("GET", getBucketLocationURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
reqV2, err := newTestSignedRequestV2(http.MethodGet, getBucketLocationURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err)
|
||||
@@ -192,7 +192,7 @@ func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName stri
|
||||
|
||||
// Test for Anonymous/unsigned http request.
|
||||
// ListBucketsHandler doesn't support bucket policies, setting the policies shouldn't make any difference.
|
||||
anonReq, err := newTestRequest("GET", getBucketLocationURL("", bucketName), 0, nil)
|
||||
anonReq, err := newTestRequest(http.MethodGet, getBucketLocationURL("", bucketName), 0, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("MinIO %s: Failed to create an anonymous request.", instanceType)
|
||||
}
|
||||
@@ -208,7 +208,7 @@ func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName stri
|
||||
// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
|
||||
|
||||
nilBucket := "dummy-bucket"
|
||||
nilReq, err := newTestRequest("GET", getBucketLocationURL("", nilBucket), 0, nil)
|
||||
nilReq, err := newTestRequest(http.MethodGet, getBucketLocationURL("", nilBucket), 0, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
|
||||
@@ -265,7 +265,7 @@ func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, api
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for HEAD bucket.
|
||||
req, err := newTestSignedRequestV4("HEAD", getHEADBucketURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
req, err := newTestSignedRequestV4(http.MethodHead, getHEADBucketURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for HeadBucketHandler: <ERROR> %v", i+1, instanceType, err)
|
||||
}
|
||||
@@ -280,7 +280,7 @@ func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, api
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
reqV2, err := newTestSignedRequestV2("HEAD", getHEADBucketURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
reqV2, err := newTestSignedRequestV2(http.MethodHead, getHEADBucketURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err)
|
||||
@@ -295,7 +295,7 @@ func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, api
|
||||
}
|
||||
|
||||
// Test for Anonymous/unsigned http request.
|
||||
anonReq, err := newTestRequest("HEAD", getHEADBucketURL("", bucketName), 0, nil)
|
||||
anonReq, err := newTestRequest(http.MethodHead, getHEADBucketURL("", bucketName), 0, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("MinIO %s: Failed to create an anonymous request for bucket \"%s\": <ERROR> %v",
|
||||
@@ -313,7 +313,7 @@ func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, api
|
||||
// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
|
||||
|
||||
nilBucket := "dummy-bucket"
|
||||
nilReq, err := newTestRequest("HEAD", getHEADBucketURL("", nilBucket), 0, nil)
|
||||
nilReq, err := newTestRequest(http.MethodHead, getHEADBucketURL("", nilBucket), 0, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
|
||||
@@ -482,7 +482,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType, bucketName s
|
||||
|
||||
// construct HTTP request for List multipart uploads endpoint.
|
||||
u := getListMultipartUploadsURLWithParams("", testCase.bucket, testCase.prefix, testCase.keyMarker, testCase.uploadIDMarker, testCase.delimiter, testCase.maxUploads)
|
||||
req, gerr := newTestSignedRequestV4("GET", u, 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
req, gerr := newTestSignedRequestV4(http.MethodGet, u, 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
if gerr != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for ListMultipartUploadsHandler: <ERROR> %v", i+1, instanceType, gerr)
|
||||
}
|
||||
@@ -499,7 +499,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType, bucketName s
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
|
||||
// verify response for V2 signed HTTP request.
|
||||
reqV2, err := newTestSignedRequestV2("GET", u, 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
reqV2, err := newTestSignedRequestV2(http.MethodGet, u, 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err)
|
||||
}
|
||||
@@ -516,7 +516,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType, bucketName s
|
||||
|
||||
// construct HTTP request for List multipart uploads endpoint.
|
||||
u := getListMultipartUploadsURLWithParams("", bucketName, "", "", "", "", "")
|
||||
req, err := newTestSignedRequestV4("GET", u, 0, nil, "", "", nil) // Generate an anonymous request.
|
||||
req, err := newTestSignedRequestV4(http.MethodGet, u, 0, nil, "", "", nil) // Generate an anonymous request.
|
||||
if err != nil {
|
||||
t.Fatalf("Test %s: Failed to create HTTP request for ListMultipartUploadsHandler: <ERROR> %v", instanceType, err)
|
||||
}
|
||||
@@ -530,7 +530,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType, bucketName s
|
||||
url := getListMultipartUploadsURLWithParams("", testCases[6].bucket, testCases[6].prefix, testCases[6].keyMarker,
|
||||
testCases[6].uploadIDMarker, testCases[6].delimiter, testCases[6].maxUploads)
|
||||
// Test for Anonymous/unsigned http request.
|
||||
anonReq, err := newTestRequest("GET", url, 0, nil)
|
||||
anonReq, err := newTestRequest(http.MethodGet, url, 0, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("MinIO %s: Failed to create an anonymous request for bucket \"%s\": <ERROR> %v",
|
||||
instanceType, bucketName, err)
|
||||
@@ -550,7 +550,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType, bucketName s
|
||||
url = getListMultipartUploadsURLWithParams("", nilBucket, "dummy-prefix", testCases[6].keyMarker,
|
||||
testCases[6].uploadIDMarker, testCases[6].delimiter, testCases[6].maxUploads)
|
||||
|
||||
nilReq, err := newTestRequest("GET", url, 0, nil)
|
||||
nilReq, err := newTestRequest(http.MethodGet, url, 0, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
|
||||
@@ -596,7 +596,7 @@ func testListBucketsHandler(obj ObjectLayer, instanceType, bucketName string, ap
|
||||
for i, testCase := range testCases {
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
req, lerr := newTestSignedRequestV4("GET", getListBucketURL(""), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
req, lerr := newTestSignedRequestV4(http.MethodGet, getListBucketURL(""), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
if lerr != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for ListBucketsHandler: <ERROR> %v", i+1, instanceType, lerr)
|
||||
}
|
||||
@@ -613,7 +613,7 @@ func testListBucketsHandler(obj ObjectLayer, instanceType, bucketName string, ap
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
|
||||
// verify response for V2 signed HTTP request.
|
||||
reqV2, err := newTestSignedRequestV2("GET", getListBucketURL(""), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
reqV2, err := newTestSignedRequestV2(http.MethodGet, getListBucketURL(""), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err)
|
||||
@@ -628,7 +628,7 @@ func testListBucketsHandler(obj ObjectLayer, instanceType, bucketName string, ap
|
||||
|
||||
// Test for Anonymous/unsigned http request.
|
||||
// ListBucketsHandler doesn't support bucket policies, setting the policies shouldn't make a difference.
|
||||
anonReq, err := newTestRequest("GET", getListBucketURL(""), 0, nil)
|
||||
anonReq, err := newTestRequest(http.MethodGet, getListBucketURL(""), 0, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("MinIO %s: Failed to create an anonymous request.", instanceType)
|
||||
@@ -644,7 +644,7 @@ func testListBucketsHandler(obj ObjectLayer, instanceType, bucketName string, ap
|
||||
// since the `objectLayer==nil` check is performed before any other checks inside the handlers.
|
||||
// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
|
||||
|
||||
nilReq, err := newTestRequest("GET", getListBucketURL(""), 0, nil)
|
||||
nilReq, err := newTestRequest(http.MethodGet, getListBucketURL(""), 0, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
|
||||
@@ -808,10 +808,10 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
||||
|
||||
// Generate a signed or anonymous request based on the testCase
|
||||
if testCase.accessKey != "" {
|
||||
req, err = newTestSignedRequestV4("POST", getDeleteMultipleObjectsURL("", bucketName),
|
||||
req, err = newTestSignedRequestV4(http.MethodPost, getDeleteMultipleObjectsURL("", bucketName),
|
||||
int64(len(testCase.objects)), bytes.NewReader(testCase.objects), testCase.accessKey, testCase.secretKey, nil)
|
||||
} else {
|
||||
req, err = newTestRequest("POST", getDeleteMultipleObjectsURL("", bucketName),
|
||||
req, err = newTestRequest(http.MethodPost, getDeleteMultipleObjectsURL("", bucketName),
|
||||
int64(len(testCase.objects)), bytes.NewReader(testCase.objects))
|
||||
}
|
||||
|
||||
@@ -850,7 +850,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
||||
nilBucket := "dummy-bucket"
|
||||
nilObject := ""
|
||||
|
||||
nilReq, err := newTestSignedRequestV4("POST", getDeleteMultipleObjectsURL("", nilBucket), 0, nil, "", "", nil)
|
||||
nilReq, err := newTestSignedRequestV4(http.MethodPost, getDeleteMultipleObjectsURL("", nilBucket), 0, nil, "", "", nil)
|
||||
if err != nil {
|
||||
t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func testBucketLifecycleHandlersWrongCredentials(obj ObjectLayer, instanceType,
|
||||
}{
|
||||
// GET empty credentials
|
||||
{
|
||||
method: "GET", bucketName: bucketName,
|
||||
method: http.MethodGet, bucketName: bucketName,
|
||||
accessKey: "",
|
||||
secretKey: "",
|
||||
expectedRespStatus: http.StatusForbidden,
|
||||
@@ -64,7 +64,7 @@ func testBucketLifecycleHandlersWrongCredentials(obj ObjectLayer, instanceType,
|
||||
},
|
||||
// GET wrong credentials
|
||||
{
|
||||
method: "GET", bucketName: bucketName,
|
||||
method: http.MethodGet, bucketName: bucketName,
|
||||
accessKey: "abcd",
|
||||
secretKey: "abcd",
|
||||
expectedRespStatus: http.StatusForbidden,
|
||||
@@ -78,7 +78,7 @@ func testBucketLifecycleHandlersWrongCredentials(obj ObjectLayer, instanceType,
|
||||
},
|
||||
// PUT empty credentials
|
||||
{
|
||||
method: "PUT",
|
||||
method: http.MethodPut,
|
||||
bucketName: bucketName,
|
||||
accessKey: "",
|
||||
secretKey: "",
|
||||
@@ -93,7 +93,7 @@ func testBucketLifecycleHandlersWrongCredentials(obj ObjectLayer, instanceType,
|
||||
},
|
||||
// PUT wrong credentials
|
||||
{
|
||||
method: "PUT",
|
||||
method: http.MethodPut,
|
||||
bucketName: bucketName,
|
||||
accessKey: "abcd",
|
||||
secretKey: "abcd",
|
||||
@@ -108,7 +108,7 @@ func testBucketLifecycleHandlersWrongCredentials(obj ObjectLayer, instanceType,
|
||||
},
|
||||
// DELETE empty credentials
|
||||
{
|
||||
method: "DELETE",
|
||||
method: http.MethodDelete,
|
||||
bucketName: bucketName,
|
||||
accessKey: "",
|
||||
secretKey: "",
|
||||
@@ -123,7 +123,7 @@ func testBucketLifecycleHandlersWrongCredentials(obj ObjectLayer, instanceType,
|
||||
},
|
||||
// DELETE wrong credentials
|
||||
{
|
||||
method: "DELETE",
|
||||
method: http.MethodDelete,
|
||||
bucketName: bucketName,
|
||||
accessKey: "abcd",
|
||||
secretKey: "abcd",
|
||||
@@ -168,7 +168,7 @@ func testBucketLifecycleHandlers(obj ObjectLayer, instanceType, bucketName strin
|
||||
// Test case - 1.
|
||||
// Filter contains more than (Prefix,Tag,And) rule
|
||||
{
|
||||
method: "PUT",
|
||||
method: http.MethodPut,
|
||||
bucketName: bucketName,
|
||||
accessKey: creds.AccessKey,
|
||||
secretKey: creds.SecretKey,
|
||||
@@ -185,7 +185,7 @@ func testBucketLifecycleHandlers(obj ObjectLayer, instanceType, bucketName strin
|
||||
},
|
||||
// Date contains wrong format
|
||||
{
|
||||
method: "PUT",
|
||||
method: http.MethodPut,
|
||||
bucketName: bucketName,
|
||||
accessKey: creds.AccessKey,
|
||||
secretKey: creds.SecretKey,
|
||||
@@ -201,7 +201,7 @@ func testBucketLifecycleHandlers(obj ObjectLayer, instanceType, bucketName strin
|
||||
shouldPass: false,
|
||||
},
|
||||
{
|
||||
method: "PUT",
|
||||
method: http.MethodPut,
|
||||
bucketName: bucketName,
|
||||
accessKey: creds.AccessKey,
|
||||
secretKey: creds.SecretKey,
|
||||
@@ -212,7 +212,7 @@ func testBucketLifecycleHandlers(obj ObjectLayer, instanceType, bucketName strin
|
||||
shouldPass: true,
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
method: http.MethodGet,
|
||||
accessKey: creds.AccessKey,
|
||||
secretKey: creds.SecretKey,
|
||||
bucketName: bucketName,
|
||||
@@ -223,7 +223,7 @@ func testBucketLifecycleHandlers(obj ObjectLayer, instanceType, bucketName strin
|
||||
shouldPass: true,
|
||||
},
|
||||
{
|
||||
method: "DELETE",
|
||||
method: http.MethodDelete,
|
||||
accessKey: creds.AccessKey,
|
||||
secretKey: creds.SecretKey,
|
||||
bucketName: bucketName,
|
||||
@@ -234,7 +234,7 @@ func testBucketLifecycleHandlers(obj ObjectLayer, instanceType, bucketName strin
|
||||
shouldPass: true,
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
method: http.MethodGet,
|
||||
accessKey: creds.AccessKey,
|
||||
secretKey: creds.SecretKey,
|
||||
bucketName: bucketName,
|
||||
|
||||
@@ -17,15 +17,12 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
"github.com/minio/minio/pkg/event"
|
||||
@@ -173,137 +170,3 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
|
||||
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
}
|
||||
|
||||
func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListenBucketNotification")
|
||||
|
||||
defer logger.AuditLog(w, r, "ListenBucketNotification", mustGetClaimsFromToken(r))
|
||||
|
||||
// Validate if bucket exists.
|
||||
objAPI := api.ObjectAPI()
|
||||
if objAPI == nil {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
if !objAPI.IsNotificationSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
if !objAPI.IsListenBucketSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucketName := vars["bucket"]
|
||||
|
||||
values := r.URL.Query()
|
||||
values.Set(peerRESTListenBucket, bucketName)
|
||||
|
||||
var prefix string
|
||||
if len(values[peerRESTListenPrefix]) > 1 {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrFilterNamePrefix), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
if len(values[peerRESTListenPrefix]) == 1 {
|
||||
if err := event.ValidateFilterRuleValue(values[peerRESTListenPrefix][0]); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
prefix = values[peerRESTListenPrefix][0]
|
||||
}
|
||||
|
||||
var suffix string
|
||||
if len(values[peerRESTListenSuffix]) > 1 {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrFilterNameSuffix), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
if len(values[peerRESTListenSuffix]) == 1 {
|
||||
if err := event.ValidateFilterRuleValue(values[peerRESTListenSuffix][0]); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
suffix = values[peerRESTListenSuffix][0]
|
||||
}
|
||||
|
||||
pattern := event.NewPattern(prefix, suffix)
|
||||
|
||||
var eventNames []event.Name
|
||||
for _, s := range values[peerRESTListenEvents] {
|
||||
eventName, err := event.ParseName(s)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
eventNames = append(eventNames, eventName)
|
||||
}
|
||||
|
||||
if _, err := objAPI.GetBucketInfo(ctx, bucketName); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
rulesMap := event.NewRulesMap(eventNames, pattern, event.TargetID{ID: mustGetUUID()})
|
||||
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
|
||||
// Listen Publisher and peer-listen-client uses nonblocking send and hence does not wait for slow receivers.
|
||||
// Use buffered channel to take care of burst sends or slow w.Write()
|
||||
listenCh := make(chan interface{}, 4000)
|
||||
|
||||
peers := newPeerRestClients(globalEndpoints)
|
||||
|
||||
globalHTTPListen.Subscribe(listenCh, ctx.Done(), func(evI interface{}) bool {
|
||||
ev, ok := evI.(event.Event)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if ev.S3.Bucket.Name != values.Get(peerRESTListenBucket) {
|
||||
return false
|
||||
}
|
||||
return rulesMap.MatchSimple(ev.EventName, ev.S3.Object.Key)
|
||||
})
|
||||
|
||||
for _, peer := range peers {
|
||||
if peer == nil {
|
||||
continue
|
||||
}
|
||||
peer.Listen(listenCh, ctx.Done(), values)
|
||||
}
|
||||
|
||||
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
|
||||
defer keepAliveTicker.Stop()
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
for {
|
||||
select {
|
||||
case evI := <-listenCh:
|
||||
ev := evI.(event.Event)
|
||||
if len(string(ev.EventName)) > 0 {
|
||||
if err := enc.Encode(struct{ Records []event.Event }{[]event.Event{ev}}); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if _, err := w.Write([]byte(" ")); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
w.(http.Flusher).Flush()
|
||||
case <-keepAliveTicker.C:
|
||||
if _, err := w.Write([]byte(" ")); err != nil {
|
||||
return
|
||||
}
|
||||
w.(http.Flusher).Flush()
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV4 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
reqV4, err := newTestSignedRequestV4("PUT", getPutPolicyURL("", testCase.bucketName),
|
||||
reqV4, err := newTestSignedRequestV4(http.MethodPut, getPutPolicyURL("", testCase.bucketName),
|
||||
int64(testCase.policyLen), testCase.bucketPolicyReader, testCase.accessKey, testCase.secretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err)
|
||||
@@ -266,7 +266,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
reqV2, err := newTestSignedRequestV2("PUT", getPutPolicyURL("", testCase.bucketName),
|
||||
reqV2, err := newTestSignedRequestV2(http.MethodPut, getPutPolicyURL("", testCase.bucketName),
|
||||
int64(testCase.policyLen), testCase.bucketPolicyReader, testCase.accessKey, testCase.secretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err)
|
||||
@@ -283,7 +283,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
// Bucket policy related functions doesn't support anonymous requests, setting policies shouldn't make a difference.
|
||||
bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, bucketName, bucketName)
|
||||
// create unsigned HTTP request for PutBucketPolicyHandler.
|
||||
anonReq, err := newTestRequest("PUT", getPutPolicyURL("", bucketName),
|
||||
anonReq, err := newTestRequest(http.MethodPut, getPutPolicyURL("", bucketName),
|
||||
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)))
|
||||
|
||||
if err != nil {
|
||||
@@ -302,7 +302,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
|
||||
nilBucket := "dummy-bucket"
|
||||
|
||||
nilReq, err := newTestSignedRequestV4("PUT", getPutPolicyURL("", nilBucket),
|
||||
nilReq, err := newTestSignedRequestV4(http.MethodPut, getPutPolicyURL("", nilBucket),
|
||||
0, nil, "", "", nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -344,7 +344,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV4 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
reqV4, err := newTestSignedRequestV4("PUT", getPutPolicyURL("", testPolicy.bucketName),
|
||||
reqV4, err := newTestSignedRequestV4(http.MethodPut, getPutPolicyURL("", testPolicy.bucketName),
|
||||
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
@@ -358,7 +358,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
reqV2, err := newTestSignedRequestV2("PUT", getPutPolicyURL("", testPolicy.bucketName),
|
||||
reqV2, err := newTestSignedRequestV2(http.MethodPut, getPutPolicyURL("", testPolicy.bucketName),
|
||||
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
@@ -415,7 +415,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV4 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
reqV4, err := newTestSignedRequestV4("GET", getGetPolicyURL("", testCase.bucketName),
|
||||
reqV4, err := newTestSignedRequestV4(http.MethodGet, getGetPolicyURL("", testCase.bucketName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -454,7 +454,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
reqV2, err := newTestSignedRequestV2("GET", getGetPolicyURL("", testCase.bucketName),
|
||||
reqV2, err := newTestSignedRequestV2(http.MethodGet, getGetPolicyURL("", testCase.bucketName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for GetBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
@@ -491,7 +491,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
// Test for Anonymous/unsigned http request.
|
||||
// Bucket policy related functions doesn't support anonymous requests, setting policies shouldn't make a difference.
|
||||
// create unsigned HTTP request for PutBucketPolicyHandler.
|
||||
anonReq, err := newTestRequest("GET", getPutPolicyURL("", bucketName), 0, nil)
|
||||
anonReq, err := newTestRequest(http.MethodGet, getPutPolicyURL("", bucketName), 0, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("MinIO %s: Failed to create an anonymous request for bucket \"%s\": <ERROR> %v",
|
||||
@@ -509,7 +509,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
|
||||
nilBucket := "dummy-bucket"
|
||||
|
||||
nilReq, err := newTestSignedRequestV4("GET", getGetPolicyURL("", nilBucket),
|
||||
nilReq, err := newTestSignedRequestV4(http.MethodGet, getGetPolicyURL("", nilBucket),
|
||||
0, nil, "", "", nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -589,7 +589,7 @@ func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName str
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV4 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
reqV4, err := newTestSignedRequestV4("PUT", getPutPolicyURL("", testPolicy.bucketName),
|
||||
reqV4, err := newTestSignedRequestV4(http.MethodPut, getPutPolicyURL("", testPolicy.bucketName),
|
||||
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
@@ -639,7 +639,7 @@ func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName str
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV4 := httptest.NewRecorder()
|
||||
// construct HTTP request for Delete bucket policy endpoint.
|
||||
reqV4, err := newTestSignedRequestV4("DELETE", getDeletePolicyURL("", testCase.bucketName),
|
||||
reqV4, err := newTestSignedRequestV4(http.MethodDelete, getDeletePolicyURL("", testCase.bucketName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for GetBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
@@ -661,7 +661,7 @@ func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName str
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
reqV2, err := newTestSignedRequestV2("PUT", getPutPolicyURL("", testPolicy.bucketName),
|
||||
reqV2, err := newTestSignedRequestV2(http.MethodPut, getPutPolicyURL("", testPolicy.bucketName),
|
||||
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
@@ -678,7 +678,7 @@ func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName str
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for Delete bucket policy endpoint.
|
||||
reqV2, err := newTestSignedRequestV2("DELETE", getDeletePolicyURL("", testCase.bucketName),
|
||||
reqV2, err := newTestSignedRequestV2(http.MethodDelete, getDeletePolicyURL("", testCase.bucketName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for GetBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
@@ -694,7 +694,7 @@ func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName str
|
||||
// Test for Anonymous/unsigned http request.
|
||||
// Bucket policy related functions doesn't support anonymous requests, setting policies shouldn't make a difference.
|
||||
// create unsigned HTTP request for PutBucketPolicyHandler.
|
||||
anonReq, err := newTestRequest("DELETE", getPutPolicyURL("", bucketName), 0, nil)
|
||||
anonReq, err := newTestRequest(http.MethodDelete, getPutPolicyURL("", bucketName), 0, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("MinIO %s: Failed to create an anonymous request for bucket \"%s\": <ERROR> %v",
|
||||
@@ -712,7 +712,7 @@ func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName str
|
||||
// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
|
||||
nilBucket := "dummy-bucket"
|
||||
|
||||
nilReq, err := newTestSignedRequestV4("DELETE", getDeletePolicyURL("", nilBucket),
|
||||
nilReq, err := newTestSignedRequestV4(http.MethodDelete, getDeletePolicyURL("", nilBucket),
|
||||
0, nil, "", "", nil)
|
||||
|
||||
if err != nil {
|
||||
|
||||
+5
-1
@@ -223,7 +223,11 @@ func handleCommonEnvVars() {
|
||||
} else {
|
||||
// Add found interfaces IP address to global domain IPS,
|
||||
// loopback addresses will be naturally dropped.
|
||||
updateDomainIPs(mustGetLocalIP4())
|
||||
domainIPs := mustGetLocalIP4()
|
||||
for _, host := range globalEndpoints.Hostnames() {
|
||||
domainIPs.Add(host)
|
||||
}
|
||||
updateDomainIPs(domainIPs)
|
||||
}
|
||||
|
||||
// In place update is true by default if the MINIO_UPDATE is not set
|
||||
|
||||
@@ -48,8 +48,9 @@ func newCoreDNSMsg(ip string, port string, ttl uint32, t time.Time) ([]byte, err
|
||||
}
|
||||
|
||||
// Close closes the internal etcd client and cannot be used further
|
||||
func (c *CoreDNS) Close() {
|
||||
func (c *CoreDNS) Close() error {
|
||||
c.etcdClient.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// List - Retrieves list of DNS entries for the domain.
|
||||
@@ -259,7 +260,7 @@ func CoreDNSPath(prefix string) Option {
|
||||
}
|
||||
|
||||
// NewCoreDNS - initialize a new coreDNS set/unset values.
|
||||
func NewCoreDNS(cfg clientv3.Config, setters ...Option) (*CoreDNS, error) {
|
||||
func NewCoreDNS(cfg clientv3.Config, setters ...Option) (Store, error) {
|
||||
etcdClient, err := clientv3.New(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dns
|
||||
|
||||
// Store dns record store
|
||||
type Store interface {
|
||||
Put(bucket string) error
|
||||
Get(bucket string) ([]SrvRecord, error)
|
||||
Delete(bucket string) error
|
||||
List() (map[string][]SrvRecord, error)
|
||||
DeleteRecord(record SrvRecord) error
|
||||
Close() error
|
||||
}
|
||||
@@ -206,7 +206,7 @@ func (d *dataUpdateTracker) load(ctx context.Context, drives ...string) {
|
||||
continue
|
||||
}
|
||||
err = d.deserialize(f, d.Saved)
|
||||
if err != nil && err != io.EOF {
|
||||
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
@@ -805,7 +805,7 @@ func DecryptObjectInfo(info *ObjectInfo, r *http.Request) (encrypted bool, err e
|
||||
}
|
||||
|
||||
encrypted = crypto.IsEncrypted(info.UserDefined)
|
||||
if !encrypted && crypto.SSEC.IsRequested(headers) {
|
||||
if !encrypted && crypto.SSEC.IsRequested(headers) && r.Header.Get(xhttp.AmzCopySource) == "" {
|
||||
return false, errInvalidEncryptionParameters
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -239,15 +239,15 @@ func (l EndpointZones) NEndpoints() (count int) {
|
||||
return count
|
||||
}
|
||||
|
||||
// Hosts - returns list of unique hosts
|
||||
func (l EndpointZones) Hosts() []string {
|
||||
// Hostnames - returns list of unique hostnames
|
||||
func (l EndpointZones) Hostnames() []string {
|
||||
foundSet := set.NewStringSet()
|
||||
for _, ep := range l {
|
||||
for _, endpoint := range ep.Endpoints {
|
||||
if foundSet.Contains(endpoint.Host) {
|
||||
if foundSet.Contains(endpoint.Hostname()) {
|
||||
continue
|
||||
}
|
||||
foundSet.Add(endpoint.Host)
|
||||
foundSet.Add(endpoint.Hostname())
|
||||
}
|
||||
}
|
||||
return foundSet.ToSlice()
|
||||
|
||||
@@ -254,8 +254,8 @@ func (er erasureObjects) IsNotificationSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsListenBucketSupported returns whether listen bucket notification is applicable for this layer.
|
||||
func (er erasureObjects) IsListenBucketSupported() bool {
|
||||
// IsListenSupported returns whether listen bucket notification is applicable for this layer.
|
||||
func (er erasureObjects) IsListenSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -212,10 +212,10 @@ func shouldHealObjectOnDisk(erErr, dataErr error, meta FileInfo, quorumModTime t
|
||||
}...) {
|
||||
return true
|
||||
}
|
||||
if meta.XLV1 {
|
||||
if !quorumModTime.Equal(meta.ModTime) {
|
||||
return true
|
||||
}
|
||||
if !quorumModTime.Equal(meta.ModTime) {
|
||||
if meta.XLV1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -368,6 +368,11 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
||||
partsMetadata[i] = cleanFileInfo(latestMeta)
|
||||
}
|
||||
|
||||
dataDir := latestMeta.DataDir
|
||||
if latestMeta.XLV1 {
|
||||
dataDir = migrateDataDir
|
||||
}
|
||||
|
||||
if !latestMeta.Deleted {
|
||||
result.DataBlocks = latestMeta.Erasure.DataBlocks
|
||||
result.ParityBlocks = latestMeta.Erasure.ParityBlocks
|
||||
@@ -399,7 +404,7 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
||||
continue
|
||||
}
|
||||
checksumInfo := partsMetadata[i].Erasure.GetChecksumInfo(partNumber)
|
||||
partPath := pathJoin(object, latestMeta.DataDir, fmt.Sprintf("part.%d", partNumber))
|
||||
partPath := pathJoin(object, dataDir, fmt.Sprintf("part.%d", partNumber))
|
||||
if latestMeta.XLV1 {
|
||||
partPath = pathJoin(object, fmt.Sprintf("part.%d", partNumber))
|
||||
}
|
||||
@@ -410,10 +415,7 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
||||
if disk == OfflineDisk {
|
||||
continue
|
||||
}
|
||||
partPath := pathJoin(tmpID, latestMeta.DataDir, fmt.Sprintf("part.%d", partNumber))
|
||||
if latestMeta.XLV1 {
|
||||
partPath = pathJoin(tmpID, migrateDataDir, fmt.Sprintf("part.%d", partNumber))
|
||||
}
|
||||
partPath := pathJoin(tmpID, dataDir, fmt.Sprintf("part.%d", partNumber))
|
||||
writers[i] = newBitrotWriter(disk, minioMetaTmpBucket, partPath, tillOffset, DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||
}
|
||||
err = erasure.Heal(ctx, readers, writers, partSize)
|
||||
@@ -437,9 +439,7 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
||||
continue
|
||||
}
|
||||
|
||||
if partsMetadata[i].XLV1 {
|
||||
partsMetadata[i].DataDir = migrateDataDir
|
||||
}
|
||||
partsMetadata[i].DataDir = dataDir
|
||||
partsMetadata[i].AddObjectPart(partNumber, "", partSize, partActualSize)
|
||||
partsMetadata[i].Erasure.AddChecksumInfo(ChecksumInfo{
|
||||
PartNumber: partNumber,
|
||||
@@ -465,13 +465,13 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
||||
}
|
||||
|
||||
// Rename from tmp location to the actual location.
|
||||
for _, disk := range outDatedDisks {
|
||||
for i, disk := range outDatedDisks {
|
||||
if disk == OfflineDisk {
|
||||
continue
|
||||
}
|
||||
|
||||
// Attempt a rename now from healed data to final location.
|
||||
if err = disk.RenameData(minioMetaTmpBucket, tmpID, partsMetadata[0].DataDir, bucket, object); err != nil {
|
||||
if err = disk.RenameData(minioMetaTmpBucket, tmpID, partsMetadata[i].DataDir, bucket, object); err != nil {
|
||||
if err != errIsNotRegular && err != errFileNotFound {
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
|
||||
+6
-5
@@ -576,8 +576,8 @@ func (s *erasureSets) IsNotificationSupported() bool {
|
||||
return s.getHashedSet("").IsNotificationSupported()
|
||||
}
|
||||
|
||||
// IsListenBucketSupported returns whether listen bucket notification is applicable for this layer.
|
||||
func (s *erasureSets) IsListenBucketSupported() bool {
|
||||
// IsListenSupported returns whether listen bucket notification is applicable for this layer.
|
||||
func (s *erasureSets) IsListenSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1633,9 +1633,10 @@ func (s *erasureSets) GetMetrics(ctx context.Context) (*Metrics, error) {
|
||||
return &Metrics{}, NotImplemented{}
|
||||
}
|
||||
|
||||
// IsReady - Returns true if atleast n/2 disks (read quorum) are online
|
||||
func (s *erasureSets) IsReady(_ context.Context) bool {
|
||||
return false
|
||||
// Health shouldn't be called directly - will panic
|
||||
func (s *erasureSets) Health(ctx context.Context, _ HealthOptions) HealthResult {
|
||||
logger.CriticalIf(ctx, NotImplemented{})
|
||||
return HealthResult{}
|
||||
}
|
||||
|
||||
// maintainMRFList gathers the list of successful partial uploads
|
||||
|
||||
+74
-32
@@ -677,6 +677,7 @@ func (z *erasureZones) ListObjectsV2(ctx context.Context, bucket, prefix, contin
|
||||
func (z *erasureZones) listObjectsNonSlash(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (loi ListObjectsInfo, err error) {
|
||||
|
||||
var zonesEntryChs [][]FileInfoCh
|
||||
var zonesDrivesPerSet []int
|
||||
|
||||
endWalkCh := make(chan struct{})
|
||||
defer close(endWalkCh)
|
||||
@@ -684,6 +685,7 @@ func (z *erasureZones) listObjectsNonSlash(ctx context.Context, bucket, prefix,
|
||||
for _, zone := range z.zones {
|
||||
zonesEntryChs = append(zonesEntryChs,
|
||||
zone.startMergeWalksN(ctx, bucket, prefix, "", true, endWalkCh, zone.drivesPerSet))
|
||||
zonesDrivesPerSet = append(zonesDrivesPerSet, zone.drivesPerSet)
|
||||
}
|
||||
|
||||
var objInfos []ObjectInfo
|
||||
@@ -702,13 +704,13 @@ func (z *erasureZones) listObjectsNonSlash(ctx context.Context, bucket, prefix,
|
||||
break
|
||||
}
|
||||
|
||||
result, quorumCount, _, ok := lexicallySortedEntryZone(zonesEntryChs, zonesEntriesInfos, zonesEntriesValid)
|
||||
result, quorumCount, zoneIndex, ok := lexicallySortedEntryZone(zonesEntryChs, zonesEntriesInfos, zonesEntriesValid)
|
||||
if !ok {
|
||||
eof = true
|
||||
break
|
||||
}
|
||||
|
||||
if quorumCount < z.zones[0].drivesPerSet/2 {
|
||||
if quorumCount < zonesDrivesPerSet[zoneIndex]/2 {
|
||||
// Skip entries which are not found on upto ndisks/2.
|
||||
continue
|
||||
}
|
||||
@@ -795,6 +797,7 @@ func (z *erasureZones) listObjectsSplunk(ctx context.Context, bucket, prefix, ma
|
||||
|
||||
var zonesEntryChs [][]FileInfoCh
|
||||
var zonesEndWalkCh []chan struct{}
|
||||
var drivesPerSets []int
|
||||
|
||||
for _, zone := range z.zones {
|
||||
entryChs, endWalkCh := zone.poolSplunk.Release(listParams{bucket, recursive, marker, prefix})
|
||||
@@ -804,9 +807,10 @@ func (z *erasureZones) listObjectsSplunk(ctx context.Context, bucket, prefix, ma
|
||||
}
|
||||
zonesEntryChs = append(zonesEntryChs, entryChs)
|
||||
zonesEndWalkCh = append(zonesEndWalkCh, endWalkCh)
|
||||
drivesPerSets = append(drivesPerSets, zone.drivesPerSet)
|
||||
}
|
||||
|
||||
entries := mergeZonesEntriesCh(zonesEntryChs, maxKeys, z.zones[0].drivesPerSet)
|
||||
entries := mergeZonesEntriesCh(zonesEntryChs, maxKeys, drivesPerSets)
|
||||
if len(entries.Files) == 0 {
|
||||
return loi, nil
|
||||
}
|
||||
@@ -885,6 +889,7 @@ func (z *erasureZones) listObjects(ctx context.Context, bucket, prefix, marker,
|
||||
|
||||
var zonesEntryChs [][]FileInfoCh
|
||||
var zonesEndWalkCh []chan struct{}
|
||||
var drivesPerSets []int
|
||||
|
||||
for _, zone := range z.zones {
|
||||
entryChs, endWalkCh := zone.pool.Release(listParams{bucket, recursive, marker, prefix})
|
||||
@@ -894,9 +899,10 @@ func (z *erasureZones) listObjects(ctx context.Context, bucket, prefix, marker,
|
||||
}
|
||||
zonesEntryChs = append(zonesEntryChs, entryChs)
|
||||
zonesEndWalkCh = append(zonesEndWalkCh, endWalkCh)
|
||||
drivesPerSets = append(drivesPerSets, zone.drivesPerSet)
|
||||
}
|
||||
|
||||
entries := mergeZonesEntriesCh(zonesEntryChs, maxKeys, z.zones[0].drivesPerSet)
|
||||
entries := mergeZonesEntriesCh(zonesEntryChs, maxKeys, drivesPerSets)
|
||||
if len(entries.Files) == 0 {
|
||||
return loi, nil
|
||||
}
|
||||
@@ -1100,7 +1106,7 @@ func lexicallySortedEntryZoneVersions(zoneEntryChs [][]FileInfoVersionsCh, zoneE
|
||||
}
|
||||
|
||||
// mergeZonesEntriesVersionsCh - merges FileInfoVersions channel to entries upto maxKeys.
|
||||
func mergeZonesEntriesVersionsCh(zonesEntryChs [][]FileInfoVersionsCh, maxKeys int, ndisks int) (entries FilesInfoVersions) {
|
||||
func mergeZonesEntriesVersionsCh(zonesEntryChs [][]FileInfoVersionsCh, maxKeys int, drivesPerSets []int) (entries FilesInfoVersions) {
|
||||
var i = 0
|
||||
var zonesEntriesInfos [][]FileInfoVersions
|
||||
var zonesEntriesValid [][]bool
|
||||
@@ -1109,13 +1115,13 @@ func mergeZonesEntriesVersionsCh(zonesEntryChs [][]FileInfoVersionsCh, maxKeys i
|
||||
zonesEntriesValid = append(zonesEntriesValid, make([]bool, len(entryChs)))
|
||||
}
|
||||
for {
|
||||
fi, quorumCount, _, ok := lexicallySortedEntryZoneVersions(zonesEntryChs, zonesEntriesInfos, zonesEntriesValid)
|
||||
fi, quorumCount, zoneIndex, ok := lexicallySortedEntryZoneVersions(zonesEntryChs, zonesEntriesInfos, zonesEntriesValid)
|
||||
if !ok {
|
||||
// We have reached EOF across all entryChs, break the loop.
|
||||
break
|
||||
}
|
||||
|
||||
if quorumCount < ndisks/2 {
|
||||
if quorumCount < drivesPerSets[zoneIndex]/2 {
|
||||
// Skip entries which are not found on upto ndisks/2.
|
||||
continue
|
||||
}
|
||||
@@ -1131,7 +1137,7 @@ func mergeZonesEntriesVersionsCh(zonesEntryChs [][]FileInfoVersionsCh, maxKeys i
|
||||
}
|
||||
|
||||
// mergeZonesEntriesCh - merges FileInfo channel to entries upto maxKeys.
|
||||
func mergeZonesEntriesCh(zonesEntryChs [][]FileInfoCh, maxKeys int, ndisks int) (entries FilesInfo) {
|
||||
func mergeZonesEntriesCh(zonesEntryChs [][]FileInfoCh, maxKeys int, drivesPerSets []int) (entries FilesInfo) {
|
||||
var i = 0
|
||||
var zonesEntriesInfos [][]FileInfo
|
||||
var zonesEntriesValid [][]bool
|
||||
@@ -1140,13 +1146,13 @@ func mergeZonesEntriesCh(zonesEntryChs [][]FileInfoCh, maxKeys int, ndisks int)
|
||||
zonesEntriesValid = append(zonesEntriesValid, make([]bool, len(entryChs)))
|
||||
}
|
||||
for {
|
||||
fi, quorumCount, _, ok := lexicallySortedEntryZone(zonesEntryChs, zonesEntriesInfos, zonesEntriesValid)
|
||||
fi, quorumCount, zoneIndex, ok := lexicallySortedEntryZone(zonesEntryChs, zonesEntriesInfos, zonesEntriesValid)
|
||||
if !ok {
|
||||
// We have reached EOF across all entryChs, break the loop.
|
||||
break
|
||||
}
|
||||
|
||||
if quorumCount < ndisks/2 {
|
||||
if quorumCount < drivesPerSets[zoneIndex]/2 {
|
||||
// Skip entries which are not found on upto ndisks/2.
|
||||
continue
|
||||
}
|
||||
@@ -1288,7 +1294,7 @@ func (z *erasureZones) listObjectVersions(ctx context.Context, bucket, prefix, m
|
||||
|
||||
var zonesEntryChs [][]FileInfoVersionsCh
|
||||
var zonesEndWalkCh []chan struct{}
|
||||
|
||||
var drivesPerSets []int
|
||||
for _, zone := range z.zones {
|
||||
entryChs, endWalkCh := zone.poolVersions.Release(listParams{bucket, recursive, marker, prefix})
|
||||
if entryChs == nil {
|
||||
@@ -1297,9 +1303,10 @@ func (z *erasureZones) listObjectVersions(ctx context.Context, bucket, prefix, m
|
||||
}
|
||||
zonesEntryChs = append(zonesEntryChs, entryChs)
|
||||
zonesEndWalkCh = append(zonesEndWalkCh, endWalkCh)
|
||||
drivesPerSets = append(drivesPerSets, zone.drivesPerSet)
|
||||
}
|
||||
|
||||
entries := mergeZonesEntriesVersionsCh(zonesEntryChs, maxKeys, z.zones[0].drivesPerSet)
|
||||
entries := mergeZonesEntriesVersionsCh(zonesEntryChs, maxKeys, drivesPerSets)
|
||||
if len(entries.FilesVersions) == 0 {
|
||||
return loi, nil
|
||||
}
|
||||
@@ -1614,8 +1621,8 @@ func (z *erasureZones) IsNotificationSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsListenBucketSupported returns whether listen bucket notification is applicable for this layer.
|
||||
func (z *erasureZones) IsListenBucketSupported() bool {
|
||||
// IsListenSupported returns whether listen bucket notification is applicable for this layer.
|
||||
func (z *erasureZones) IsListenSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1933,13 +1940,21 @@ func (z *erasureZones) HealObjects(ctx context.Context, bucket, prefix string, o
|
||||
}
|
||||
|
||||
func (z *erasureZones) HealObject(ctx context.Context, bucket, object, versionID string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
|
||||
// Lock the object before healing. Use read lock since healing
|
||||
// will only regenerate parts & xl.meta of outdated disks.
|
||||
lk := z.NewNSLock(ctx, bucket, object)
|
||||
if err := lk.GetRLock(globalHealingTimeout); err != nil {
|
||||
return madmin.HealResultItem{}, err
|
||||
if bucket == minioMetaBucket {
|
||||
// For .minio.sys bucket heals we should hold write locks.
|
||||
if err := lk.GetLock(globalHealingTimeout); err != nil {
|
||||
return madmin.HealResultItem{}, err
|
||||
}
|
||||
defer lk.Unlock()
|
||||
} else {
|
||||
// Lock the object before healing. Use read lock since healing
|
||||
// will only regenerate parts & xl.meta of outdated disks.
|
||||
if err := lk.GetRLock(globalHealingTimeout); err != nil {
|
||||
return madmin.HealResultItem{}, err
|
||||
}
|
||||
defer lk.RUnlock()
|
||||
}
|
||||
defer lk.RUnlock()
|
||||
|
||||
if z.SingleZone() {
|
||||
return z.zones[0].HealObject(ctx, bucket, object, versionID, opts)
|
||||
@@ -2000,29 +2015,49 @@ func (z *erasureZones) getZoneAndSet(id string) (int, int, error) {
|
||||
return 0, 0, fmt.Errorf("DiskID(%s) %w", id, errDiskNotFound)
|
||||
}
|
||||
|
||||
// IsReady - Returns true, when all the erasure sets are writable.
|
||||
func (z *erasureZones) IsReady(ctx context.Context) bool {
|
||||
// HealthOptions takes input options to return sepcific information
|
||||
type HealthOptions struct {
|
||||
Maintenance bool
|
||||
}
|
||||
|
||||
// HealthResult returns the current state of the system, also
|
||||
// additionally with any specific heuristic information which
|
||||
// was queried
|
||||
type HealthResult struct {
|
||||
Healthy bool
|
||||
ZoneID, SetID int
|
||||
WriteQuorum int
|
||||
}
|
||||
|
||||
// Health - returns current status of the object layer health,
|
||||
// provides if write access exists across sets, additionally
|
||||
// can be used to query scenarios if health may be lost
|
||||
// if this node is taken down by an external orchestrator.
|
||||
func (z *erasureZones) Health(ctx context.Context, opts HealthOptions) HealthResult {
|
||||
erasureSetUpCount := make([][]int, len(z.zones))
|
||||
for i := range z.zones {
|
||||
erasureSetUpCount[i] = make([]int, len(z.zones[i].sets))
|
||||
}
|
||||
|
||||
diskIDs := globalNotificationSys.GetLocalDiskIDs(ctx)
|
||||
if !opts.Maintenance {
|
||||
diskIDs = append(diskIDs, getLocalDiskIDs(z))
|
||||
}
|
||||
|
||||
diskIDs = append(diskIDs, getLocalDiskIDs(z)...)
|
||||
|
||||
for _, id := range diskIDs {
|
||||
zoneIdx, setIdx, err := z.getZoneAndSet(id)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
continue
|
||||
for _, localDiskIDs := range diskIDs {
|
||||
for _, id := range localDiskIDs {
|
||||
zoneIdx, setIdx, err := z.getZoneAndSet(id)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
continue
|
||||
}
|
||||
erasureSetUpCount[zoneIdx][setIdx]++
|
||||
}
|
||||
erasureSetUpCount[zoneIdx][setIdx]++
|
||||
}
|
||||
|
||||
for zoneIdx := range erasureSetUpCount {
|
||||
parityDrives := globalStorageClass.GetParityForSC(storageclass.STANDARD)
|
||||
diskCount := len(z.zones[zoneIdx].format.Erasure.Sets[0])
|
||||
diskCount := z.zones[zoneIdx].drivesPerSet
|
||||
if parityDrives == 0 {
|
||||
parityDrives = getDefaultParityBlocks(diskCount)
|
||||
}
|
||||
@@ -2035,11 +2070,18 @@ func (z *erasureZones) IsReady(ctx context.Context) bool {
|
||||
if erasureSetUpCount[zoneIdx][setIdx] < writeQuorum {
|
||||
logger.LogIf(ctx, fmt.Errorf("Write quorum lost on zone: %d, set: %d, expected write quorum: %d",
|
||||
zoneIdx, setIdx, writeQuorum))
|
||||
return false
|
||||
return HealthResult{
|
||||
Healthy: false,
|
||||
ZoneID: zoneIdx,
|
||||
SetID: setIdx,
|
||||
WriteQuorum: writeQuorum,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
return HealthResult{
|
||||
Healthy: true,
|
||||
}
|
||||
}
|
||||
|
||||
// PutObjectTags - replace or add tags to an existing object
|
||||
|
||||
+3
-3
@@ -391,8 +391,8 @@ func (er erasureObjects) crawlAndGetDataUsage(ctx context.Context, buckets []Buc
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsReady - shouldn't be called will panic.
|
||||
func (er erasureObjects) IsReady(ctx context.Context) bool {
|
||||
// Health shouldn't be called directly - will panic
|
||||
func (er erasureObjects) Health(ctx context.Context, _ HealthOptions) HealthResult {
|
||||
logger.CriticalIf(ctx, NotImplemented{})
|
||||
return true
|
||||
return HealthResult{}
|
||||
}
|
||||
|
||||
@@ -131,29 +131,11 @@ func createFormatCache(fsFormatPath string, format *formatCacheV1) error {
|
||||
// of format cache config
|
||||
func initFormatCache(ctx context.Context, drives []string) (formats []*formatCacheV2, err error) {
|
||||
nformats := newFormatCacheV2(drives)
|
||||
for _, drive := range drives {
|
||||
_, err = os.Stat(drive)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
logger.GetReqInfo(ctx).AppendTags("drive", drive)
|
||||
logger.LogIf(ctx, err, logger.Application)
|
||||
return nil, err
|
||||
}
|
||||
if err = os.Mkdir(drive, 0777); err != nil {
|
||||
logger.GetReqInfo(ctx).AppendTags("drive", drive)
|
||||
logger.LogIf(ctx, err, logger.Application)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for i, drive := range drives {
|
||||
if err = os.Mkdir(pathJoin(drive, minioMetaBucket), 0777); err != nil {
|
||||
if !os.IsExist(err) {
|
||||
logger.GetReqInfo(ctx).AppendTags("drive", drive)
|
||||
logger.LogIf(ctx, err)
|
||||
return nil, err
|
||||
}
|
||||
if err = os.MkdirAll(pathJoin(drive, minioMetaBucket), 0777); err != nil {
|
||||
logger.GetReqInfo(ctx).AppendTags("drive", drive)
|
||||
logger.LogIf(ctx, err)
|
||||
return nil, err
|
||||
}
|
||||
cacheFormatPath := pathJoin(drive, minioMetaBucket, formatConfigFile)
|
||||
// Fresh disk - create format.json for this cfs
|
||||
|
||||
+22
-15
@@ -1237,25 +1237,31 @@ func (fs *FSObjects) DeleteObject(ctx context.Context, bucket, object string, op
|
||||
return objInfo, toObjectErr(err, bucket)
|
||||
}
|
||||
|
||||
var rwlk *lock.LockedFile
|
||||
|
||||
minioMetaBucketDir := pathJoin(fs.fsPath, minioMetaBucket)
|
||||
fsMetaPath := pathJoin(minioMetaBucketDir, bucketMetaPrefix, bucket, object, fs.metaJSONFile)
|
||||
if bucket != minioMetaBucket {
|
||||
rwlk, lerr := fs.rwPool.Write(fsMetaPath)
|
||||
if lerr == nil {
|
||||
// This close will allow for fs locks to be synchronized on `fs.json`.
|
||||
defer rwlk.Close()
|
||||
}
|
||||
if lerr != nil && lerr != errFileNotFound {
|
||||
logger.LogIf(ctx, lerr)
|
||||
return objInfo, toObjectErr(lerr, bucket, object)
|
||||
rwlk, err = fs.rwPool.Write(fsMetaPath)
|
||||
if err != nil && err != errFileNotFound {
|
||||
logger.LogIf(ctx, err)
|
||||
return objInfo, toObjectErr(err, bucket, object)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the object.
|
||||
if err = fsDeleteFile(ctx, pathJoin(fs.fsPath, bucket), pathJoin(fs.fsPath, bucket, object)); err != nil {
|
||||
if rwlk != nil {
|
||||
rwlk.Close()
|
||||
}
|
||||
return objInfo, toObjectErr(err, bucket, object)
|
||||
}
|
||||
|
||||
// Close fsMetaPath before deletion
|
||||
if rwlk != nil {
|
||||
rwlk.Close()
|
||||
}
|
||||
|
||||
if bucket != minioMetaBucket {
|
||||
// Delete the metadata object.
|
||||
err = fsDeleteFile(ctx, minioMetaBucketDir, fsMetaPath)
|
||||
@@ -1531,8 +1537,8 @@ func (fs *FSObjects) IsNotificationSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsListenBucketSupported returns whether listen bucket notification is applicable for this layer.
|
||||
func (fs *FSObjects) IsListenBucketSupported() bool {
|
||||
// IsListenSupported returns whether listen bucket notification is applicable for this layer.
|
||||
func (fs *FSObjects) IsListenSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1551,11 +1557,12 @@ func (fs *FSObjects) IsTaggingSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsReady - Check if the backend disk is ready to accept traffic.
|
||||
func (fs *FSObjects) IsReady(_ context.Context) bool {
|
||||
// Health returns health of the object layer
|
||||
func (fs *FSObjects) Health(ctx context.Context, opts HealthOptions) HealthResult {
|
||||
if _, err := os.Stat(fs.fsPath); err != nil {
|
||||
return false
|
||||
return HealthResult{}
|
||||
}
|
||||
return HealthResult{
|
||||
Healthy: newObjectLayerFn() != nil,
|
||||
}
|
||||
|
||||
return newObjectLayerFn() != nil
|
||||
}
|
||||
|
||||
@@ -230,8 +230,8 @@ func (a GatewayUnsupported) IsNotificationSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsListenBucketSupported returns whether listen bucket notification is applicable for this layer.
|
||||
func (a GatewayUnsupported) IsListenBucketSupported() bool {
|
||||
// IsListenSupported returns whether listen bucket notification is applicable for this layer.
|
||||
func (a GatewayUnsupported) IsListenSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ func (a GatewayUnsupported) IsCompressionSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsReady - No Op.
|
||||
func (a GatewayUnsupported) IsReady(_ context.Context) bool {
|
||||
return false
|
||||
// Health - No Op.
|
||||
func (a GatewayUnsupported) Health(_ context.Context, _ HealthOptions) HealthResult {
|
||||
return HealthResult{}
|
||||
}
|
||||
|
||||
@@ -1436,8 +1436,3 @@ func (a *azureObjects) DeleteBucketPolicy(ctx context.Context, bucket string) er
|
||||
func (a *azureObjects) IsCompressionSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsReady returns whether the layer is ready to take requests.
|
||||
func (a *azureObjects) IsReady(ctx context.Context) bool {
|
||||
return minio.IsBackendOnline(ctx, a.httpClient, a.endpoint)
|
||||
}
|
||||
|
||||
@@ -1508,8 +1508,3 @@ func (l *gcsGateway) DeleteBucketPolicy(ctx context.Context, bucket string) erro
|
||||
func (l *gcsGateway) IsCompressionSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsReady returns whether the layer is ready to take requests.
|
||||
func (l *gcsGateway) IsReady(ctx context.Context) bool {
|
||||
return minio.IsBackendOnline(ctx, l.httpClient, "https://storage.googleapis.com")
|
||||
}
|
||||
|
||||
@@ -786,9 +786,3 @@ func (n *hdfsObjects) AbortMultipartUpload(ctx context.Context, bucket, object,
|
||||
}
|
||||
return hdfsToObjectErr(ctx, n.clnt.Remove(n.hdfsPathJoin(minioMetaTmpBucket, uploadID)), bucket, object, uploadID)
|
||||
}
|
||||
|
||||
// IsReady returns whether the layer is ready to take requests.
|
||||
func (n *hdfsObjects) IsReady(ctx context.Context) bool {
|
||||
si, _ := n.StorageInfo(ctx, false)
|
||||
return si.Backend.GatewayOnline
|
||||
}
|
||||
|
||||
@@ -104,8 +104,8 @@ func (g *NAS) Production() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsListenBucketSupported returns whether listen bucket notification is applicable for this gateway.
|
||||
func (n *nasObjects) IsListenBucketSupported() bool {
|
||||
// IsListenSupported returns whether listen bucket notification is applicable for this gateway.
|
||||
func (n *nasObjects) IsListenSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -121,12 +121,6 @@ type nasObjects struct {
|
||||
minio.ObjectLayer
|
||||
}
|
||||
|
||||
// IsReady returns whether the layer is ready to take requests.
|
||||
func (n *nasObjects) IsReady(ctx context.Context) bool {
|
||||
si, _ := n.StorageInfo(ctx, false)
|
||||
return si.Backend.GatewayOnline
|
||||
}
|
||||
|
||||
func (n *nasObjects) IsTaggingSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -755,11 +755,6 @@ func (l *s3Objects) IsEncryptionSupported() bool {
|
||||
return minio.GlobalKMS != nil || len(minio.GlobalGatewaySSE) > 0
|
||||
}
|
||||
|
||||
// IsReady returns whether the layer is ready to take requests.
|
||||
func (l *s3Objects) IsReady(ctx context.Context) bool {
|
||||
return minio.IsBackendOnline(ctx, l.HTTPClient, l.Client.EndpointURL().String())
|
||||
}
|
||||
|
||||
func (l *s3Objects) IsTaggingSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
+1
-1
@@ -234,7 +234,7 @@ var (
|
||||
globalBucketFederation bool
|
||||
|
||||
// Allocated DNS config wrapper over etcd client.
|
||||
globalDNSConfig *dns.CoreDNS
|
||||
globalDNSConfig dns.Store
|
||||
|
||||
// GlobalKMS initialized KMS configuration
|
||||
GlobalKMS crypto.KMS
|
||||
|
||||
+2
-2
@@ -44,8 +44,8 @@ func (t *apiConfig) init(cfg api.Config) {
|
||||
}
|
||||
|
||||
apiRequestsMax := cfg.APIRequestsMax
|
||||
if len(globalEndpoints.Hosts()) > 0 {
|
||||
apiRequestsMax /= len(globalEndpoints.Hosts())
|
||||
if len(globalEndpoints.Hostnames()) > 0 {
|
||||
apiRequestsMax /= len(globalEndpoints.Hostnames())
|
||||
}
|
||||
|
||||
t.requestsPool = make(chan struct{}, apiRequestsMax)
|
||||
|
||||
@@ -35,8 +35,17 @@ func ClusterCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(ctx, globalAPIConfig.getReadyDeadline())
|
||||
defer cancel()
|
||||
|
||||
if !objLayer.IsReady(ctx) {
|
||||
writeResponse(w, http.StatusServiceUnavailable, nil, mimeNone)
|
||||
opts := HealthOptions{Maintenance: r.URL.Query().Get("maintenance") == "true"}
|
||||
result := objLayer.Health(ctx, opts)
|
||||
if !result.Healthy {
|
||||
// As a maintenance call we are purposefully asked to be taken
|
||||
// down, this is for orchestrators to know if we can safely
|
||||
// take this server down, return appropriate error.
|
||||
if opts.Maintenance {
|
||||
writeResponse(w, http.StatusPreconditionFailed, nil, mimeNone)
|
||||
} else {
|
||||
writeResponse(w, http.StatusServiceUnavailable, nil, mimeNone)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
policy "github.com/minio/minio/pkg/bucket/policy"
|
||||
"github.com/minio/minio/pkg/event"
|
||||
)
|
||||
|
||||
func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListenNotification")
|
||||
|
||||
defer logger.AuditLog(w, r, "ListenNotification", mustGetClaimsFromToken(r))
|
||||
|
||||
// Validate if bucket exists.
|
||||
objAPI := api.ObjectAPI()
|
||||
if objAPI == nil {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
if !objAPI.IsNotificationSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
if !objAPI.IsListenSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucketName := vars["bucket"]
|
||||
|
||||
if bucketName == "" {
|
||||
if s3Error := checkRequestAuthType(ctx, r, policy.ListenNotificationAction, bucketName, ""); s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if s3Error := checkRequestAuthType(ctx, r, policy.ListenBucketNotificationAction, bucketName, ""); s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
values := r.URL.Query()
|
||||
|
||||
var prefix string
|
||||
if len(values[peerRESTListenPrefix]) > 1 {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrFilterNamePrefix), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
if len(values[peerRESTListenPrefix]) == 1 {
|
||||
if err := event.ValidateFilterRuleValue(values[peerRESTListenPrefix][0]); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
prefix = values[peerRESTListenPrefix][0]
|
||||
}
|
||||
|
||||
var suffix string
|
||||
if len(values[peerRESTListenSuffix]) > 1 {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrFilterNameSuffix), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
if len(values[peerRESTListenSuffix]) == 1 {
|
||||
if err := event.ValidateFilterRuleValue(values[peerRESTListenSuffix][0]); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
suffix = values[peerRESTListenSuffix][0]
|
||||
}
|
||||
|
||||
pattern := event.NewPattern(prefix, suffix)
|
||||
|
||||
var eventNames []event.Name
|
||||
for _, s := range values[peerRESTListenEvents] {
|
||||
eventName, err := event.ParseName(s)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
eventNames = append(eventNames, eventName)
|
||||
}
|
||||
|
||||
if bucketName != "" {
|
||||
if _, err := objAPI.GetBucketInfo(ctx, bucketName); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rulesMap := event.NewRulesMap(eventNames, pattern, event.TargetID{ID: mustGetUUID()})
|
||||
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
|
||||
// Listen Publisher and peer-listen-client uses nonblocking send and hence does not wait for slow receivers.
|
||||
// Use buffered channel to take care of burst sends or slow w.Write()
|
||||
listenCh := make(chan interface{}, 4000)
|
||||
|
||||
peers := newPeerRestClients(globalEndpoints)
|
||||
|
||||
globalHTTPListen.Subscribe(listenCh, ctx.Done(), func(evI interface{}) bool {
|
||||
ev, ok := evI.(event.Event)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if ev.S3.Bucket.Name != "" && bucketName != "" {
|
||||
if ev.S3.Bucket.Name != bucketName {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return rulesMap.MatchSimple(ev.EventName, ev.S3.Object.Key)
|
||||
})
|
||||
|
||||
for _, peer := range peers {
|
||||
if peer == nil {
|
||||
continue
|
||||
}
|
||||
peer.Listen(listenCh, ctx.Done(), values)
|
||||
}
|
||||
|
||||
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
|
||||
defer keepAliveTicker.Stop()
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
for {
|
||||
select {
|
||||
case evI := <-listenCh:
|
||||
ev := evI.(event.Event)
|
||||
if len(string(ev.EventName)) > 0 {
|
||||
if err := enc.Encode(struct{ Records []event.Event }{[]event.Event{ev}}); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if _, err := w.Write([]byte(" ")); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
w.(http.Flusher).Flush()
|
||||
case <-keepAliveTicker.C:
|
||||
if _, err := w.Write([]byte(" ")); err != nil {
|
||||
return
|
||||
}
|
||||
w.(http.Flusher).Flush()
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
+99
-31
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
|
||||
* MinIO Cloud Storage, (C) 2019, 2020 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,6 +28,7 @@ const (
|
||||
|
||||
// mergeWalkVersions - represents the go routine that does the merge walk versions.
|
||||
type mergeWalkVersions struct {
|
||||
added time.Time
|
||||
entryChs []FileInfoVersionsCh
|
||||
endWalkCh chan struct{} // To signal when mergeWalk go-routine should end.
|
||||
endTimerCh chan<- struct{} // To signal when timer go-routine should end.
|
||||
@@ -88,17 +89,70 @@ func (t *MergeWalkVersionsPool) Set(params listParams, resultChs []FileInfoVersi
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
// If we are above the limit delete at least one entry from the pool.
|
||||
if len(t.pool) > treeWalkEntryLimit {
|
||||
age := time.Now()
|
||||
var oldest listParams
|
||||
for k, v := range t.pool {
|
||||
if len(v) == 0 {
|
||||
delete(t.pool, k)
|
||||
continue
|
||||
}
|
||||
// The first element is the oldest, so we only check that.
|
||||
e := v[0]
|
||||
if e.added.Before(age) {
|
||||
oldest = k
|
||||
age = e.added
|
||||
}
|
||||
}
|
||||
// Invalidate and delete oldest.
|
||||
if walks, ok := t.pool[oldest]; ok && len(walks) > 0 {
|
||||
endCh := walks[0].endTimerCh
|
||||
endWalkCh := walks[0].endWalkCh
|
||||
if len(walks) > 1 {
|
||||
// Move walks forward
|
||||
copy(walks, walks[1:])
|
||||
walks = walks[:len(walks)-1]
|
||||
t.pool[oldest] = walks
|
||||
} else {
|
||||
// Only entry, just delete.
|
||||
delete(t.pool, oldest)
|
||||
}
|
||||
select {
|
||||
case endCh <- struct{}{}:
|
||||
close(endWalkCh)
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
// Shouldn't happen, but just in case.
|
||||
delete(t.pool, oldest)
|
||||
}
|
||||
}
|
||||
|
||||
// Should be a buffered channel so that Release() never blocks.
|
||||
endTimerCh := make(chan struct{}, 1)
|
||||
|
||||
walkInfo := mergeWalkVersions{
|
||||
added: UTCNow(),
|
||||
entryChs: resultChs,
|
||||
endWalkCh: endWalkCh,
|
||||
endTimerCh: endTimerCh,
|
||||
}
|
||||
|
||||
// Append new walk info.
|
||||
t.pool[params] = append(t.pool[params], walkInfo)
|
||||
walks := t.pool[params]
|
||||
if len(walks) < treeWalkSameEntryLimit {
|
||||
t.pool[params] = append(walks, walkInfo)
|
||||
} else {
|
||||
// We are at limit, invalidate oldest, move list down and add new as last.
|
||||
select {
|
||||
case walks[0].endTimerCh <- struct{}{}:
|
||||
close(walks[0].endWalkCh)
|
||||
default:
|
||||
}
|
||||
copy(walks, walks[1:])
|
||||
walks[len(walks)-1] = walkInfo
|
||||
}
|
||||
|
||||
// Timer go-routine which times out after t.timeOut seconds.
|
||||
go func(endTimerCh <-chan struct{}, walkInfo mergeWalkVersions) {
|
||||
@@ -108,6 +162,7 @@ func (t *MergeWalkVersionsPool) Set(params listParams, resultChs []FileInfoVersi
|
||||
// Timeout has expired. Remove the mergeWalk from mergeWalkPool and
|
||||
// end the mergeWalk go-routine.
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
walks, ok := t.pool[params]
|
||||
if ok {
|
||||
// Trick of filtering without allocating
|
||||
@@ -131,7 +186,6 @@ func (t *MergeWalkVersionsPool) Set(params listParams, resultChs []FileInfoVersi
|
||||
}
|
||||
// Signal the mergeWalk go-routine to die.
|
||||
close(endWalkCh)
|
||||
t.Unlock()
|
||||
case <-endTimerCh:
|
||||
return
|
||||
}
|
||||
@@ -166,23 +220,22 @@ func (t *MergeWalkPool) Release(params listParams) ([]FileInfoCh, chan struct{})
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
walks, ok := t.pool[params] // Pick the valid walks.
|
||||
if ok {
|
||||
if len(walks) > 0 {
|
||||
// Pop out the first valid walk entry.
|
||||
walk := walks[0]
|
||||
walks[0] = mergeWalk{} // clear references.
|
||||
walks = walks[1:]
|
||||
if len(walks) > 0 {
|
||||
t.pool[params] = walks
|
||||
} else {
|
||||
delete(t.pool, params)
|
||||
}
|
||||
walk.endTimerCh <- struct{}{}
|
||||
return walk.entryChs, walk.endWalkCh
|
||||
}
|
||||
if !ok || len(walks) == 0 {
|
||||
// Release return nil if params not found.
|
||||
return nil, nil
|
||||
}
|
||||
// Release return nil if params not found.
|
||||
return nil, nil
|
||||
|
||||
// Pop out the first valid walk entry.
|
||||
walk := walks[0]
|
||||
walks[0] = mergeWalk{} // clear references.
|
||||
walks = walks[1:]
|
||||
if len(walks) > 0 {
|
||||
t.pool[params] = walks
|
||||
} else {
|
||||
delete(t.pool, params)
|
||||
}
|
||||
walk.endTimerCh <- struct{}{}
|
||||
return walk.entryChs, walk.endWalkCh
|
||||
}
|
||||
|
||||
// Set - adds a mergeWalk to the mergeWalkPool.
|
||||
@@ -207,27 +260,38 @@ func (t *MergeWalkPool) Set(params listParams, resultChs []FileInfoCh, endWalkCh
|
||||
continue
|
||||
}
|
||||
// The first element is the oldest, so we only check that.
|
||||
if v[0].added.Before(age) {
|
||||
e := v[0]
|
||||
if e.added.Before(age) {
|
||||
oldest = k
|
||||
age = e.added
|
||||
}
|
||||
}
|
||||
// Invalidate and delete oldest.
|
||||
if walks, ok := t.pool[oldest]; ok {
|
||||
walk := walks[0]
|
||||
walks[0] = mergeWalk{} // clear references.
|
||||
walks = walks[1:]
|
||||
if len(walks) > 0 {
|
||||
t.pool[params] = walks
|
||||
if walks, ok := t.pool[oldest]; ok && len(walks) > 0 {
|
||||
endCh := walks[0].endTimerCh
|
||||
endWalkCh := walks[0].endWalkCh
|
||||
if len(walks) > 1 {
|
||||
// Move walks forward
|
||||
copy(walks, walks[1:])
|
||||
walks = walks[:len(walks)-1]
|
||||
t.pool[oldest] = walks
|
||||
} else {
|
||||
delete(t.pool, params)
|
||||
// Only entry, just delete.
|
||||
delete(t.pool, oldest)
|
||||
}
|
||||
walk.endTimerCh <- struct{}{}
|
||||
select {
|
||||
case endCh <- struct{}{}:
|
||||
close(endWalkCh)
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
// Shouldn't happen, but just in case.
|
||||
delete(t.pool, oldest)
|
||||
}
|
||||
}
|
||||
|
||||
// Should be a buffered channel so that Release() never blocks.
|
||||
endTimerCh := make(chan struct{}, 1)
|
||||
|
||||
walkInfo := mergeWalk{
|
||||
added: UTCNow(),
|
||||
entryChs: resultChs,
|
||||
@@ -241,7 +305,11 @@ func (t *MergeWalkPool) Set(params listParams, resultChs []FileInfoCh, endWalkCh
|
||||
t.pool[params] = append(walks, walkInfo)
|
||||
} else {
|
||||
// We are at limit, invalidate oldest, move list down and add new as last.
|
||||
walks[0].endTimerCh <- struct{}{}
|
||||
select {
|
||||
case walks[0].endTimerCh <- struct{}{}:
|
||||
close(walks[0].endWalkCh)
|
||||
default:
|
||||
}
|
||||
copy(walks, walks[1:])
|
||||
walks[len(walks)-1] = walkInfo
|
||||
}
|
||||
@@ -254,6 +322,7 @@ func (t *MergeWalkPool) Set(params listParams, resultChs []FileInfoCh, endWalkCh
|
||||
// Timeout has expired. Remove the mergeWalk from mergeWalkPool and
|
||||
// end the mergeWalk go-routine.
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
walks, ok := t.pool[params]
|
||||
if ok {
|
||||
// Trick of filtering without allocating
|
||||
@@ -277,7 +346,6 @@ func (t *MergeWalkPool) Set(params listParams, resultChs []FileInfoCh, endWalkCh
|
||||
}
|
||||
// Signal the mergeWalk go-routine to die.
|
||||
close(endWalkCh)
|
||||
t.Unlock()
|
||||
case <-endTimerCh:
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
|
||||
* MinIO Cloud Storage, (C) 2019,2020 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,6 +21,42 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Test if tree walker go-routine is removed from the pool after timeout
|
||||
// and that is available in the pool before the timeout.
|
||||
func TestMergeWalkPoolVersionsBasic(t *testing.T) {
|
||||
// Create a treeWalkPool
|
||||
tw := NewMergeWalkVersionsPool(1 * time.Second)
|
||||
|
||||
// Create sample params
|
||||
params := listParams{
|
||||
bucket: "test-bucket",
|
||||
}
|
||||
|
||||
endWalkCh := make(chan struct{})
|
||||
// Add a treeWalk to the pool
|
||||
tw.Set(params, []FileInfoVersionsCh{}, endWalkCh)
|
||||
|
||||
// Wait for treeWalkPool timeout to happen
|
||||
<-time.After(2 * time.Second)
|
||||
if c1, _ := tw.Release(params); c1 != nil {
|
||||
t.Error("treeWalk go-routine must have been freed")
|
||||
}
|
||||
|
||||
// Add the treeWalk back to the pool
|
||||
endWalkCh = make(chan struct{})
|
||||
tw.Set(params, []FileInfoVersionsCh{}, endWalkCh)
|
||||
|
||||
// Release the treeWalk before timeout
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
break
|
||||
default:
|
||||
if c1, _ := tw.Release(params); c1 == nil {
|
||||
t.Error("treeWalk go-routine got freed before timeout")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test if tree walker go-routine is removed from the pool after timeout
|
||||
// and that is available in the pool before the timeout.
|
||||
func TestMergeWalkPoolBasic(t *testing.T) {
|
||||
|
||||
+8
-13
@@ -58,7 +58,7 @@ func (sys *NotificationSys) GetARNList(onlyActive bool) []string {
|
||||
}
|
||||
region := globalServerRegion
|
||||
for targetID, target := range sys.targetList.TargetMap() {
|
||||
// httpclient target is part of ListenBucketNotification
|
||||
// httpclient target is part of ListenNotification
|
||||
// which doesn't need to be listed as part of the ARN list
|
||||
// This list is only meant for external targets, filter
|
||||
// this out pro-actively.
|
||||
@@ -1164,26 +1164,21 @@ func (sys *NotificationSys) ServerInfo() []madmin.ServerProperties {
|
||||
}
|
||||
|
||||
// GetLocalDiskIDs - return disk ids of the local disks of the peers.
|
||||
func (sys *NotificationSys) GetLocalDiskIDs(ctx context.Context) []string {
|
||||
var diskIDs []string
|
||||
var mu sync.Mutex
|
||||
|
||||
func (sys *NotificationSys) GetLocalDiskIDs(ctx context.Context) (localDiskIDs [][]string) {
|
||||
localDiskIDs = make([][]string, len(sys.peerClients))
|
||||
var wg sync.WaitGroup
|
||||
for _, client := range sys.peerClients {
|
||||
for idx, client := range sys.peerClients {
|
||||
if client == nil {
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(client *peerRESTClient) {
|
||||
go func(idx int, client *peerRESTClient) {
|
||||
defer wg.Done()
|
||||
ids := client.GetLocalDiskIDs(ctx)
|
||||
mu.Lock()
|
||||
diskIDs = append(diskIDs, ids...)
|
||||
mu.Unlock()
|
||||
}(client)
|
||||
localDiskIDs[idx] = client.GetLocalDiskIDs(ctx)
|
||||
}(idx, client)
|
||||
}
|
||||
wg.Wait()
|
||||
return diskIDs
|
||||
return localDiskIDs
|
||||
}
|
||||
|
||||
// NewNotificationSys - creates new notification system object.
|
||||
|
||||
@@ -125,7 +125,7 @@ type ObjectLayer interface {
|
||||
|
||||
// Supported operations check
|
||||
IsNotificationSupported() bool
|
||||
IsListenBucketSupported() bool
|
||||
IsListenSupported() bool
|
||||
IsEncryptionSupported() bool
|
||||
IsTaggingSupported() bool
|
||||
IsCompressionSupported() bool
|
||||
@@ -133,8 +133,8 @@ type ObjectLayer interface {
|
||||
// Backend related metrics
|
||||
GetMetrics(ctx context.Context) (*Metrics, error)
|
||||
|
||||
// Check Readiness
|
||||
IsReady(ctx context.Context) bool
|
||||
// Returns health of the backend
|
||||
Health(ctx context.Context, opts HealthOptions) HealthResult
|
||||
|
||||
// ObjectTagging operations
|
||||
PutObjectTags(context.Context, string, string, string, ObjectOptions) error
|
||||
|
||||
+60
-60
@@ -137,7 +137,7 @@ func testAPIHeadObjectHandler(obj ObjectLayer, instanceType, bucketName string,
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for Get Object end point.
|
||||
req, err := newTestSignedRequestV4("HEAD", getHeadObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
req, err := newTestSignedRequestV4(http.MethodHead, getHeadObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for Head Object: <ERROR> %v", i+1, instanceType, err)
|
||||
@@ -155,7 +155,7 @@ func testAPIHeadObjectHandler(obj ObjectLayer, instanceType, bucketName string,
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for Head Object endpoint.
|
||||
reqV2, err := newTestSignedRequestV2("HEAD", getHeadObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
reqV2, err := newTestSignedRequestV2(http.MethodHead, getHeadObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -171,7 +171,7 @@ func testAPIHeadObjectHandler(obj ObjectLayer, instanceType, bucketName string,
|
||||
}
|
||||
|
||||
// Test for Anonymous/unsigned http request.
|
||||
anonReq, err := newTestRequest("HEAD", getHeadObjectURL("", bucketName, objectName), 0, nil)
|
||||
anonReq, err := newTestRequest(http.MethodHead, getHeadObjectURL("", bucketName, objectName), 0, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
|
||||
@@ -190,7 +190,7 @@ func testAPIHeadObjectHandler(obj ObjectLayer, instanceType, bucketName string,
|
||||
|
||||
nilBucket := "dummy-bucket"
|
||||
nilObject := "dummy-object"
|
||||
nilReq, err := newTestSignedRequestV4("HEAD", getGetObjectURL("", nilBucket, nilObject),
|
||||
nilReq, err := newTestSignedRequestV4(http.MethodHead, getGetObjectURL("", nilBucket, nilObject),
|
||||
0, nil, "", "", nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -276,7 +276,7 @@ func testAPIHeadObjectHandlerWithEncryption(obj ObjectLayer, instanceType, bucke
|
||||
// mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for HEAD object.
|
||||
req, err := newTestSignedRequestV4("HEAD", getHeadObjectURL("", bucketName, input.objectName),
|
||||
req, err := newTestSignedRequestV4(http.MethodHead, getHeadObjectURL("", bucketName, input.objectName),
|
||||
0, nil, credentials.AccessKey, credentials.SecretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for Head Object: <ERROR> %v", i+1, instanceType, err)
|
||||
@@ -301,7 +301,7 @@ func testAPIHeadObjectHandlerWithEncryption(obj ObjectLayer, instanceType, bucke
|
||||
// mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for HEAD object.
|
||||
req, err := newTestSignedRequestV4("HEAD", getHeadObjectURL("", bucketName, input.objectName),
|
||||
req, err := newTestSignedRequestV4(http.MethodHead, getHeadObjectURL("", bucketName, input.objectName),
|
||||
0, nil, credentials.AccessKey, credentials.SecretKey, input.metaData)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for Head Object: <ERROR> %v", i+1, instanceType, err)
|
||||
@@ -522,7 +522,7 @@ func testAPIGetObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for Get Object end point.
|
||||
req, err := newTestSignedRequestV4("GET", getGetObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
req, err := newTestSignedRequestV4(http.MethodGet, getGetObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -569,7 +569,7 @@ func testAPIGetObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for GET Object endpoint.
|
||||
reqV2, err := newTestSignedRequestV2("GET", getGetObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
reqV2, err := newTestSignedRequestV2(http.MethodGet, getGetObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -616,7 +616,7 @@ func testAPIGetObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
|
||||
}
|
||||
|
||||
// Test for Anonymous/unsigned http request.
|
||||
anonReq, err := newTestRequest("GET", getGetObjectURL("", bucketName, objectName), 0, nil)
|
||||
anonReq, err := newTestRequest(http.MethodGet, getGetObjectURL("", bucketName, objectName), 0, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
|
||||
@@ -635,7 +635,7 @@ func testAPIGetObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
|
||||
|
||||
nilBucket := "dummy-bucket"
|
||||
nilObject := "dummy-object"
|
||||
nilReq, err := newTestSignedRequestV4("GET", getGetObjectURL("", nilBucket, nilObject),
|
||||
nilReq, err := newTestSignedRequestV4(http.MethodGet, getGetObjectURL("", nilBucket, nilObject),
|
||||
0, nil, "", "", nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -733,7 +733,7 @@ func testAPIGetObjectWithMPHandler(obj ObjectLayer, instanceType, bucketName str
|
||||
mkGetReq := func(oi ObjectInput, byteRange string, i int, mkSignedReq testSignedReqFn) {
|
||||
object := oi.objectName
|
||||
rec := httptest.NewRecorder()
|
||||
req, err := mkSignedReq("GET", getGetObjectURL("", bucketName, object),
|
||||
req, err := mkSignedReq(http.MethodGet, getGetObjectURL("", bucketName, object),
|
||||
0, nil, credentials.AccessKey, credentials.SecretKey, oi.metaData)
|
||||
if err != nil {
|
||||
t.Fatalf("Object: %s Case %d ByteRange: %s: Failed to create HTTP request for Get Object: <ERROR> %v",
|
||||
@@ -832,7 +832,7 @@ func testAPIGetObjectWithMPHandler(obj ObjectLayer, instanceType, bucketName str
|
||||
|
||||
nilBucket := "dummy-bucket"
|
||||
nilObject := "dummy-object"
|
||||
nilReq, err := newTestSignedRequestV4("GET", getGetObjectURL("", nilBucket, nilObject),
|
||||
nilReq, err := newTestSignedRequestV4(http.MethodGet, getGetObjectURL("", nilBucket, nilObject),
|
||||
0, nil, "", "", nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -1080,18 +1080,18 @@ func testAPIPutObjectStreamSigV4Handler(obj ObjectLayer, instanceType, bucketNam
|
||||
// construct HTTP request for Put Object end point.
|
||||
var req *http.Request
|
||||
if testCase.fault == chunkDateMismatch {
|
||||
req, err = newTestStreamingSignedBadChunkDateRequest("PUT",
|
||||
req, err = newTestStreamingSignedBadChunkDateRequest(http.MethodPut,
|
||||
getPutObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
int64(testCase.dataLen), testCase.chunkSize, bytes.NewReader(testCase.data),
|
||||
testCase.accessKey, testCase.secretKey)
|
||||
|
||||
} else if testCase.contentEncoding == "" {
|
||||
req, err = newTestStreamingSignedRequest("PUT",
|
||||
req, err = newTestStreamingSignedRequest(http.MethodPut,
|
||||
getPutObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
int64(testCase.dataLen), testCase.chunkSize, bytes.NewReader(testCase.data),
|
||||
testCase.accessKey, testCase.secretKey)
|
||||
} else if testCase.contentEncoding != "" {
|
||||
req, err = newTestStreamingSignedCustomEncodingRequest("PUT",
|
||||
req, err = newTestStreamingSignedCustomEncodingRequest(http.MethodPut,
|
||||
getPutObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
int64(testCase.dataLen), testCase.chunkSize, bytes.NewReader(testCase.data),
|
||||
testCase.accessKey, testCase.secretKey, testCase.contentEncoding)
|
||||
@@ -1296,7 +1296,7 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for Get Object end point.
|
||||
req, err = newTestSignedRequestV4("PUT", getPutObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
req, err = newTestSignedRequestV4(http.MethodPut, getPutObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
int64(testCase.dataLen), bytes.NewReader(testCase.data), testCase.accessKey, testCase.secretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for Put Object: <ERROR> %v", i+1, err)
|
||||
@@ -1337,7 +1337,7 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT Object endpoint.
|
||||
reqV2, err = newTestSignedRequestV2("PUT", getPutObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
reqV2, err = newTestSignedRequestV2(http.MethodPut, getPutObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
int64(testCase.dataLen), bytes.NewReader(testCase.data), testCase.accessKey, testCase.secretKey, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -1378,7 +1378,7 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
|
||||
}
|
||||
|
||||
// Test for Anonymous/unsigned http request.
|
||||
anonReq, err := newTestRequest("PUT", getPutObjectURL("", bucketName, objectName),
|
||||
anonReq, err := newTestRequest(http.MethodPut, getPutObjectURL("", bucketName, objectName),
|
||||
int64(len("hello")), bytes.NewReader([]byte("hello")))
|
||||
if err != nil {
|
||||
t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
|
||||
@@ -1397,7 +1397,7 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
|
||||
nilBucket := "dummy-bucket"
|
||||
nilObject := "dummy-object"
|
||||
|
||||
nilReq, err := newTestSignedRequestV4("PUT", getPutObjectURL("", nilBucket, nilObject),
|
||||
nilReq, err := newTestSignedRequestV4(http.MethodPut, getPutObjectURL("", nilBucket, nilObject),
|
||||
0, nil, "", "", nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -1476,7 +1476,7 @@ func testAPICopyObjectPartHandlerSanity(obj ObjectLayer, instanceType, bucketNam
|
||||
|
||||
// construct HTTP request for copy object.
|
||||
var req *http.Request
|
||||
req, err = newTestSignedRequestV4("PUT", cpPartURL, 0, nil, credentials.AccessKey, credentials.SecretKey, nil)
|
||||
req, err = newTestSignedRequestV4(http.MethodPut, cpPartURL, 0, nil, credentials.AccessKey, credentials.SecretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test failed to create HTTP request for copy object part: <ERROR> %v", err)
|
||||
}
|
||||
@@ -1775,11 +1775,11 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri
|
||||
rec := httptest.NewRecorder()
|
||||
if !testCase.invalidPartNumber || !testCase.maximumPartNumber {
|
||||
// construct HTTP request for copy object.
|
||||
req, err = newTestSignedRequestV4("PUT", getCopyObjectPartURL("", testCase.bucketName, testObject, testCase.uploadID, "1"), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
req, err = newTestSignedRequestV4(http.MethodPut, getCopyObjectPartURL("", testCase.bucketName, testObject, testCase.uploadID, "1"), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
} else if testCase.invalidPartNumber {
|
||||
req, err = newTestSignedRequestV4("PUT", getCopyObjectPartURL("", testCase.bucketName, testObject, testCase.uploadID, "abc"), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
req, err = newTestSignedRequestV4(http.MethodPut, getCopyObjectPartURL("", testCase.bucketName, testObject, testCase.uploadID, "abc"), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
} else if testCase.maximumPartNumber {
|
||||
req, err = newTestSignedRequestV4("PUT", getCopyObjectPartURL("", testCase.bucketName, testObject, testCase.uploadID, "99999"), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
req, err = newTestSignedRequestV4(http.MethodPut, getCopyObjectPartURL("", testCase.bucketName, testObject, testCase.uploadID, "99999"), 0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for copy Object: <ERROR> %v", i+1, err)
|
||||
@@ -1821,14 +1821,14 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri
|
||||
nilBucket := "dummy-bucket"
|
||||
nilObject := "dummy-object"
|
||||
|
||||
nilReq, err := newTestSignedRequestV4("PUT", getCopyObjectPartURL("", nilBucket, nilObject, "0", "0"),
|
||||
nilReq, err := newTestSignedRequestV4(http.MethodPut, getCopyObjectPartURL("", nilBucket, nilObject, "0", "0"),
|
||||
0, bytes.NewReader([]byte("testNilObjLayer")), "", "", nil)
|
||||
if err != nil {
|
||||
t.Errorf("MinIO %s: Failed to create http request for testing the response when object Layer is set to `nil`.", instanceType)
|
||||
}
|
||||
|
||||
// Below is how CopyObjectPartHandler is registered.
|
||||
// bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(api.CopyObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
||||
// bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(api.CopyObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
||||
// Its necessary to set the "X-Amz-Copy-Source" header for the request to be accepted by the handler.
|
||||
nilReq.Header.Set("X-Amz-Copy-Source", url.QueryEscape(SlashSeparator+nilBucket+SlashSeparator+nilObject))
|
||||
|
||||
@@ -2145,7 +2145,7 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string,
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for copy object.
|
||||
req, err = newTestSignedRequestV4("PUT", getCopyObjectURL("", testCase.bucketName, testCase.newObjectName),
|
||||
req, err = newTestSignedRequestV4(http.MethodPut, getCopyObjectURL("", testCase.bucketName, testCase.newObjectName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -2203,7 +2203,7 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string,
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
|
||||
reqV2, err = newTestRequest("PUT", getCopyObjectURL("", testCase.bucketName, testCase.newObjectName), 0, nil)
|
||||
reqV2, err = newTestRequest(http.MethodPut, getCopyObjectURL("", testCase.bucketName, testCase.newObjectName), 0, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for copy Object: <ERROR> %v", i+1, err)
|
||||
}
|
||||
@@ -2252,11 +2252,11 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string,
|
||||
nilBucket := "dummy-bucket"
|
||||
nilObject := "dummy-object"
|
||||
|
||||
nilReq, err := newTestSignedRequestV4("PUT", getCopyObjectURL("", nilBucket, nilObject),
|
||||
nilReq, err := newTestSignedRequestV4(http.MethodPut, getCopyObjectURL("", nilBucket, nilObject),
|
||||
0, nil, "", "", nil)
|
||||
|
||||
// Below is how CopyObjectHandler is registered.
|
||||
// bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?")
|
||||
// bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?")
|
||||
// Its necessary to set the "X-Amz-Copy-Source" header for the request to be accepted by the handler.
|
||||
nilReq.Header.Set("X-Amz-Copy-Source", url.QueryEscape(SlashSeparator+nilBucket+SlashSeparator+nilObject))
|
||||
if err != nil {
|
||||
@@ -2283,7 +2283,7 @@ func testAPINewMultipartHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
objectName := "test-object-new-multipart"
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for NewMultipart upload.
|
||||
req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, objectName),
|
||||
req, err := newTestSignedRequestV4(http.MethodPost, getNewMultipartURL("", bucketName, objectName),
|
||||
0, nil, credentials.AccessKey, credentials.SecretKey, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -2316,7 +2316,7 @@ func testAPINewMultipartHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
rec = httptest.NewRecorder()
|
||||
// construct HTTP request for NewMultipart upload.
|
||||
// Setting an invalid accessID.
|
||||
req, err = newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, objectName),
|
||||
req, err = newTestSignedRequestV4(http.MethodPost, getNewMultipartURL("", bucketName, objectName),
|
||||
0, nil, "Invalid-AccessID", credentials.SecretKey, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -2335,7 +2335,7 @@ func testAPINewMultipartHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for NewMultipartUpload endpoint.
|
||||
reqV2, err := newTestSignedRequestV2("POST", getNewMultipartURL("", bucketName, objectName),
|
||||
reqV2, err := newTestSignedRequestV2(http.MethodPost, getNewMultipartURL("", bucketName, objectName),
|
||||
0, nil, credentials.AccessKey, credentials.SecretKey, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -2368,7 +2368,7 @@ func testAPINewMultipartHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
recV2 = httptest.NewRecorder()
|
||||
// construct HTTP request for NewMultipartUpload endpoint.
|
||||
// Setting invalid AccessID.
|
||||
reqV2, err = newTestSignedRequestV2("POST", getNewMultipartURL("", bucketName, objectName),
|
||||
reqV2, err = newTestSignedRequestV2(http.MethodPost, getNewMultipartURL("", bucketName, objectName),
|
||||
0, nil, "Invalid-AccessID", credentials.SecretKey, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -2384,7 +2384,7 @@ func testAPINewMultipartHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
}
|
||||
|
||||
// Test for Anonymous/unsigned http request.
|
||||
anonReq, err := newTestRequest("POST", getNewMultipartURL("", bucketName, objectName), 0, nil)
|
||||
anonReq, err := newTestRequest(http.MethodPost, getNewMultipartURL("", bucketName, objectName), 0, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
|
||||
@@ -2403,7 +2403,7 @@ func testAPINewMultipartHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
nilBucket := "dummy-bucket"
|
||||
nilObject := "dummy-object"
|
||||
|
||||
nilReq, err := newTestSignedRequestV4("POST", getNewMultipartURL("", nilBucket, nilObject),
|
||||
nilReq, err := newTestSignedRequestV4(http.MethodPost, getNewMultipartURL("", nilBucket, nilObject),
|
||||
0, nil, "", "", nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -2440,7 +2440,7 @@ func testAPINewMultipartHandlerParallel(obj ObjectLayer, instanceType, bucketNam
|
||||
defer wg.Done()
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request NewMultipartUpload.
|
||||
req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKey, credentials.SecretKey, nil)
|
||||
req, err := newTestSignedRequestV4(http.MethodPost, getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKey, credentials.SecretKey, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create HTTP request for NewMultipart request: <ERROR> %v", err)
|
||||
@@ -2763,7 +2763,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
|
||||
t.Fatalf("Error XML encoding of parts: <ERROR> %s.", err)
|
||||
}
|
||||
// Indicating that all parts are uploaded and initiating CompleteMultipartUpload.
|
||||
req, err = newTestSignedRequestV4("POST", getCompleteMultipartUploadURL("", bucketName, objectName, testCase.uploadID),
|
||||
req, err = newTestSignedRequestV4(http.MethodPost, getCompleteMultipartUploadURL("", bucketName, objectName, testCase.uploadID),
|
||||
int64(len(completeBytes)), bytes.NewReader(completeBytes), testCase.accessKey, testCase.secretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create HTTP request for CompleteMultipartUpload: <ERROR> %v", err)
|
||||
@@ -2819,7 +2819,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
|
||||
}
|
||||
|
||||
// create unsigned HTTP request for CompleteMultipart upload.
|
||||
anonReq, err := newTestRequest("POST", getCompleteMultipartUploadURL("", bucketName, objectName, uploadIDs[1]),
|
||||
anonReq, err := newTestRequest(http.MethodPost, getCompleteMultipartUploadURL("", bucketName, objectName, uploadIDs[1]),
|
||||
int64(len(completeBytes)), bytes.NewReader(completeBytes))
|
||||
if err != nil {
|
||||
t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
|
||||
@@ -2840,7 +2840,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
|
||||
nilBucket := "dummy-bucket"
|
||||
nilObject := "dummy-object"
|
||||
|
||||
nilReq, err := newTestSignedRequestV4("POST", getCompleteMultipartUploadURL("", nilBucket, nilObject, "dummy-uploadID"),
|
||||
nilReq, err := newTestSignedRequestV4(http.MethodPost, getCompleteMultipartUploadURL("", nilBucket, nilObject, "dummy-uploadID"),
|
||||
0, nil, "", "", nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -2964,7 +2964,7 @@ func testAPIAbortMultipartHandler(obj ObjectLayer, instanceType, bucketName stri
|
||||
for i, testCase := range testCases {
|
||||
var req *http.Request
|
||||
// Indicating that all parts are uploaded and initiating abortMultipartUpload.
|
||||
req, err = newTestSignedRequestV4("DELETE", getAbortMultipartUploadURL("", testCase.bucket, testCase.object, testCase.uploadID),
|
||||
req, err = newTestSignedRequestV4(http.MethodDelete, getAbortMultipartUploadURL("", testCase.bucket, testCase.object, testCase.uploadID),
|
||||
0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create HTTP request for AbortMultipartUpload: <ERROR> %v", err)
|
||||
@@ -2982,7 +2982,7 @@ func testAPIAbortMultipartHandler(obj ObjectLayer, instanceType, bucketName stri
|
||||
}
|
||||
|
||||
// create unsigned HTTP request for Abort multipart upload.
|
||||
anonReq, err := newTestRequest("DELETE", getAbortMultipartUploadURL("", bucketName, objectName, uploadIDs[1]),
|
||||
anonReq, err := newTestRequest(http.MethodDelete, getAbortMultipartUploadURL("", bucketName, objectName, uploadIDs[1]),
|
||||
0, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
|
||||
@@ -3003,7 +3003,7 @@ func testAPIAbortMultipartHandler(obj ObjectLayer, instanceType, bucketName stri
|
||||
nilBucket := "dummy-bucket"
|
||||
nilObject := "dummy-object"
|
||||
|
||||
nilReq, err := newTestSignedRequestV4("DELETE", getAbortMultipartUploadURL("", nilBucket, nilObject, "dummy-uploadID"),
|
||||
nilReq, err := newTestSignedRequestV4(http.MethodDelete, getAbortMultipartUploadURL("", nilBucket, nilObject, "dummy-uploadID"),
|
||||
0, nil, "", "", nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -3108,7 +3108,7 @@ func testAPIDeleteObjectHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for Delete Object end point.
|
||||
req, err = newTestSignedRequestV4("DELETE", getDeleteObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
req, err = newTestSignedRequestV4(http.MethodDelete, getDeleteObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -3126,7 +3126,7 @@ func testAPIDeleteObjectHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for Delete Object endpoint.
|
||||
reqV2, err = newTestSignedRequestV2("DELETE", getDeleteObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
reqV2, err = newTestSignedRequestV2(http.MethodDelete, getDeleteObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -3145,7 +3145,7 @@ func testAPIDeleteObjectHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
}
|
||||
|
||||
// Test for Anonymous/unsigned http request.
|
||||
anonReq, err := newTestRequest("DELETE", getDeleteObjectURL("", bucketName, anonObjectName), 0, nil)
|
||||
anonReq, err := newTestRequest(http.MethodDelete, getDeleteObjectURL("", bucketName, anonObjectName), 0, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
|
||||
instanceType, bucketName, anonObjectName, err)
|
||||
@@ -3163,7 +3163,7 @@ func testAPIDeleteObjectHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
nilBucket := "dummy-bucket"
|
||||
nilObject := "dummy-object"
|
||||
|
||||
nilReq, err := newTestSignedRequestV4("DELETE", getDeleteObjectURL("", nilBucket, nilObject),
|
||||
nilReq, err := newTestSignedRequestV4(http.MethodDelete, getDeleteObjectURL("", nilBucket, nilObject),
|
||||
0, nil, "", "", nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -3185,7 +3185,7 @@ func testAPIPutObjectPartHandlerStreaming(obj ObjectLayer, instanceType, bucketN
|
||||
credentials auth.Credentials, t *testing.T) {
|
||||
testObject := "testobject"
|
||||
rec := httptest.NewRecorder()
|
||||
req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, "testobject"),
|
||||
req, err := newTestSignedRequestV4(http.MethodPost, getNewMultipartURL("", bucketName, "testobject"),
|
||||
0, nil, credentials.AccessKey, credentials.SecretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] - Failed to create a signed request to initiate multipart upload for %s/%s: <ERROR> %v",
|
||||
@@ -3219,7 +3219,7 @@ func testAPIPutObjectPartHandlerStreaming(obj ObjectLayer, instanceType, bucketN
|
||||
|
||||
for i, test := range testCases {
|
||||
rec = httptest.NewRecorder()
|
||||
req, err = newTestStreamingSignedRequest("PUT",
|
||||
req, err = newTestStreamingSignedRequest(http.MethodPut,
|
||||
getPutObjectPartURL("", bucketName, testObject, mpartResp.UploadID, "1"),
|
||||
5, 1, bytes.NewReader([]byte("hello")), credentials.AccessKey, credentials.SecretKey)
|
||||
|
||||
@@ -3455,7 +3455,7 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin
|
||||
uploadID = "upload1"
|
||||
}
|
||||
// constructing a v4 signed HTTP request.
|
||||
reqV4, err = newTestSignedRequestV4("PUT",
|
||||
reqV4, err = newTestSignedRequestV4(http.MethodPut,
|
||||
getPutObjectPartURL("", bucketName, test.objectName, uploadID, test.partNumber),
|
||||
0, test.reader, test.accessKey, test.secretKey, nil)
|
||||
if err != nil {
|
||||
@@ -3464,7 +3464,7 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin
|
||||
}
|
||||
// Verify response of the V2 signed HTTP request.
|
||||
// construct HTTP request for PutObject Part Object endpoint.
|
||||
reqV2, err = newTestSignedRequestV2("PUT",
|
||||
reqV2, err = newTestSignedRequestV2(http.MethodPut,
|
||||
getPutObjectPartURL("", bucketName, test.objectName, uploadID, test.partNumber),
|
||||
0, test.reader, test.accessKey, test.secretKey, nil)
|
||||
|
||||
@@ -3548,7 +3548,7 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin
|
||||
}
|
||||
|
||||
// Test for Anonymous/unsigned http request.
|
||||
anonReq, err := newTestRequest("PUT", getPutObjectPartURL("", bucketName, testObject, uploadIDCopy, "1"),
|
||||
anonReq, err := newTestRequest(http.MethodPut, getPutObjectPartURL("", bucketName, testObject, uploadIDCopy, "1"),
|
||||
int64(len("hello")), bytes.NewReader([]byte("hello")))
|
||||
if err != nil {
|
||||
t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
|
||||
@@ -3567,7 +3567,7 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin
|
||||
nilBucket := "dummy-bucket"
|
||||
nilObject := "dummy-object"
|
||||
|
||||
nilReq, err := newTestSignedRequestV4("PUT", getPutObjectPartURL("", nilBucket, nilObject, "0", "0"),
|
||||
nilReq, err := newTestSignedRequestV4(http.MethodPut, getPutObjectPartURL("", nilBucket, nilObject, "0", "0"),
|
||||
0, bytes.NewReader([]byte("testNilObjLayer")), "", "", nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -3590,7 +3590,7 @@ func testAPIListObjectPartsHandlerPreSign(obj ObjectLayer, instanceType, bucketN
|
||||
credentials auth.Credentials, t *testing.T) {
|
||||
testObject := "testobject"
|
||||
rec := httptest.NewRecorder()
|
||||
req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, testObject),
|
||||
req, err := newTestSignedRequestV4(http.MethodPost, getNewMultipartURL("", bucketName, testObject),
|
||||
0, nil, credentials.AccessKey, credentials.SecretKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] - Failed to create a signed request to initiate multipart upload for %s/%s: <ERROR> %v",
|
||||
@@ -3612,7 +3612,7 @@ func testAPIListObjectPartsHandlerPreSign(obj ObjectLayer, instanceType, bucketN
|
||||
|
||||
// Upload a part for listing purposes.
|
||||
rec = httptest.NewRecorder()
|
||||
req, err = newTestSignedRequestV4("PUT",
|
||||
req, err = newTestSignedRequestV4(http.MethodPut,
|
||||
getPutObjectPartURL("", bucketName, testObject, mpartResp.UploadID, "1"),
|
||||
int64(len("hello")), bytes.NewReader([]byte("hello")), credentials.AccessKey, credentials.SecretKey, nil)
|
||||
if err != nil {
|
||||
@@ -3626,7 +3626,7 @@ func testAPIListObjectPartsHandlerPreSign(obj ObjectLayer, instanceType, bucketN
|
||||
instanceType, bucketName, testObject, rec.Code)
|
||||
}
|
||||
rec = httptest.NewRecorder()
|
||||
req, err = newTestRequest("GET",
|
||||
req, err = newTestRequest(http.MethodGet,
|
||||
getListMultipartURLWithParams("", bucketName, testObject, mpartResp.UploadID, "", "", ""),
|
||||
0, nil)
|
||||
if err != nil {
|
||||
@@ -3647,7 +3647,7 @@ func testAPIListObjectPartsHandlerPreSign(obj ObjectLayer, instanceType, bucketN
|
||||
}
|
||||
|
||||
rec = httptest.NewRecorder()
|
||||
req, err = newTestRequest("GET",
|
||||
req, err = newTestRequest(http.MethodGet,
|
||||
getListMultipartURLWithParams("", bucketName, testObject, mpartResp.UploadID, "", "", ""),
|
||||
0, nil)
|
||||
if err != nil {
|
||||
@@ -3772,7 +3772,7 @@ func testAPIListObjectPartsHandler(obj ObjectLayer, instanceType, bucketName str
|
||||
}
|
||||
|
||||
// constructing a v4 signed HTTP request for ListMultipartUploads.
|
||||
reqV4, err = newTestSignedRequestV4("GET",
|
||||
reqV4, err = newTestSignedRequestV4(http.MethodGet,
|
||||
getListMultipartURLWithParams("", bucketName, testObject, uploadID, test.maxParts, test.partNumberMarker, ""),
|
||||
0, nil, credentials.AccessKey, credentials.SecretKey, nil)
|
||||
|
||||
@@ -3782,7 +3782,7 @@ func testAPIListObjectPartsHandler(obj ObjectLayer, instanceType, bucketName str
|
||||
}
|
||||
// Verify response of the V2 signed HTTP request.
|
||||
// construct HTTP request for PutObject Part Object endpoint.
|
||||
reqV2, err = newTestSignedRequestV2("GET",
|
||||
reqV2, err = newTestSignedRequestV2(http.MethodGet,
|
||||
getListMultipartURLWithParams("", bucketName, testObject, uploadID, test.maxParts, test.partNumberMarker, ""),
|
||||
0, nil, credentials.AccessKey, credentials.SecretKey, nil)
|
||||
|
||||
@@ -3851,7 +3851,7 @@ func testAPIListObjectPartsHandler(obj ObjectLayer, instanceType, bucketName str
|
||||
}
|
||||
|
||||
// Test for Anonymous/unsigned http request.
|
||||
anonReq, err := newTestRequest("GET",
|
||||
anonReq, err := newTestRequest(http.MethodGet,
|
||||
getListMultipartURLWithParams("", bucketName, testObject, uploadIDCopy, "", "", ""), 0, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
|
||||
@@ -3870,7 +3870,7 @@ func testAPIListObjectPartsHandler(obj ObjectLayer, instanceType, bucketName str
|
||||
nilBucket := "dummy-bucket"
|
||||
nilObject := "dummy-object"
|
||||
|
||||
nilReq, err := newTestSignedRequestV4("GET",
|
||||
nilReq, err := newTestSignedRequestV4(http.MethodGet,
|
||||
getListMultipartURLWithParams("", nilBucket, nilObject, "dummy-uploadID", "0", "0", ""),
|
||||
0, nil, "", "", nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -897,8 +897,10 @@ func (s *peerRESTServer) ListenHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if ev.S3.Bucket.Name != values.Get(peerRESTListenBucket) {
|
||||
return false
|
||||
if ev.S3.Bucket.Name != "" && values.Get(peerRESTListenBucket) != "" {
|
||||
if ev.S3.Bucket.Name != values.Get(peerRESTListenBucket) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return rulesMap.MatchSimple(ev.EventName, ev.S3.Object.Key)
|
||||
})
|
||||
|
||||
@@ -548,7 +548,7 @@ func newPostRequestV2(endPoint, bucketName, objectName string, accessKey, secret
|
||||
// Set the body equal to the created policy.
|
||||
reader := bytes.NewReader(buf.Bytes())
|
||||
|
||||
req, err := http.NewRequest("POST", makeTestTargetURL(endPoint, bucketName, "", nil), reader)
|
||||
req, err := http.NewRequest(http.MethodPost, makeTestTargetURL(endPoint, bucketName, "", nil), reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -626,7 +626,7 @@ func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData []
|
||||
// Set the body equal to the created policy.
|
||||
reader := bytes.NewReader(buf.Bytes())
|
||||
|
||||
req, err := http.NewRequest("POST", makeTestTargetURL(endPoint, bucketName, "", nil), reader)
|
||||
req, err := http.NewRequest(http.MethodPost, makeTestTargetURL(endPoint, bucketName, "", nil), reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -73,7 +73,6 @@ DIR:
|
||||
FLAGS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
|
||||
EXAMPLES:
|
||||
1. Start minio server on "/home/shared" directory.
|
||||
{{.Prompt}} {{.HelpName}} /home/shared
|
||||
@@ -91,7 +90,6 @@ EXAMPLES:
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_SECRET_KEY{{.AssignmentOperator}}miniostorage
|
||||
{{.Prompt}} {{.HelpName}} http://node{1...16}.example.com/mnt/export{1...32} \
|
||||
http://node{17...64}.example.com/mnt/export{1...64}
|
||||
|
||||
`,
|
||||
}
|
||||
|
||||
|
||||
+154
-154
File diff suppressed because it is too large
Load Diff
@@ -72,7 +72,7 @@ func TestSkipContentSha256Cksum(t *testing.T) {
|
||||
for i, testCase := range testCases {
|
||||
// creating an input HTTP request.
|
||||
// Only the headers are relevant for this particular test.
|
||||
inputReq, err := http.NewRequest("GET", "http://example.com", nil)
|
||||
inputReq, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error initializing input HTTP request: %v", err)
|
||||
}
|
||||
@@ -135,7 +135,7 @@ func TestExtractSignedHeaders(t *testing.T) {
|
||||
expectedTransferEncoding := "gzip"
|
||||
expectedExpect := "100-continue"
|
||||
|
||||
r, err := http.NewRequest("GET", "http://play.min.io:9000", nil)
|
||||
r, err := http.NewRequest(http.MethodGet, "http://play.min.io:9000", nil)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to create http.Request :", err)
|
||||
}
|
||||
@@ -259,7 +259,7 @@ func TestGetContentSha256Cksum(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
r, err := http.NewRequest("GET", "http://localhost/?"+testCase.q, nil)
|
||||
r, err := http.NewRequest(http.MethodGet, "http://localhost/?"+testCase.q, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ func toStorageErr(err error) error {
|
||||
return io.ErrUnexpectedEOF
|
||||
case errDiskStale.Error():
|
||||
return errDiskNotFound
|
||||
case errDiskNotFound.Error():
|
||||
return errDiskNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -114,12 +114,18 @@ func (s *storageRESTServer) IsValid(w http.ResponseWriter, r *http.Request) bool
|
||||
return true
|
||||
}
|
||||
storedDiskID, err := s.storage.GetDiskID()
|
||||
if err == nil && diskID == storedDiskID {
|
||||
// If format.json is available and request sent the right disk-id, we allow the request
|
||||
return true
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return false
|
||||
}
|
||||
s.writeErrorResponse(w, errDiskStale)
|
||||
return false
|
||||
|
||||
if diskID != storedDiskID {
|
||||
s.writeErrorResponse(w, errDiskStale)
|
||||
return false
|
||||
}
|
||||
|
||||
// If format.json is available and request sent the right disk-id, we allow the request
|
||||
return true
|
||||
}
|
||||
|
||||
// HealthHandler handler checks if disk is stale
|
||||
|
||||
+38
-38
@@ -671,7 +671,7 @@ func signStreamingRequest(req *http.Request, accessKey, secretKey string, currTi
|
||||
// Returns new HTTP request object.
|
||||
func newTestStreamingRequest(method, urlStr string, dataLength, chunkSize int64, body io.ReadSeeker) (*http.Request, error) {
|
||||
if method == "" {
|
||||
method = "POST"
|
||||
method = http.MethodPost
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, urlStr, nil)
|
||||
@@ -1036,7 +1036,7 @@ func getMD5HashBase64(data []byte) string {
|
||||
// Returns new HTTP request object.
|
||||
func newTestRequest(method, urlStr string, contentLength int64, body io.ReadSeeker) (*http.Request, error) {
|
||||
if method == "" {
|
||||
method = "POST"
|
||||
method = http.MethodPost
|
||||
}
|
||||
|
||||
// Save for subsequent use
|
||||
@@ -1164,7 +1164,7 @@ func newTestSignedRequestV4(method, urlStr string, contentLength int64, body io.
|
||||
|
||||
// Return new WebRPC request object.
|
||||
func newWebRPCRequest(methodRPC, authorization string, body io.ReadSeeker) (*http.Request, error) {
|
||||
req, err := http.NewRequest("POST", "/minio/webrpc", nil)
|
||||
req, err := http.NewRequest(http.MethodPost, "/minio/webrpc", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1515,7 +1515,7 @@ func getCompleteMultipartUploadURL(endPoint, bucketName, objectName, uploadID st
|
||||
}
|
||||
|
||||
// return URL for listen bucket notification.
|
||||
func getListenBucketNotificationURL(endPoint, bucketName string, prefixes, suffixes, events []string) string {
|
||||
func getListenNotificationURL(endPoint, bucketName string, prefixes, suffixes, events []string) string {
|
||||
queryValue := url.Values{}
|
||||
|
||||
queryValue["prefix"] = prefixes
|
||||
@@ -1693,7 +1693,7 @@ func ExecObjectLayerAPIAnonTest(t *testing.T, obj ObjectLayer, testName, bucketN
|
||||
}
|
||||
|
||||
// HEAD HTTTP request doesn't contain response body.
|
||||
if anonReq.Method != "HEAD" {
|
||||
if anonReq.Method != http.MethodHead {
|
||||
// read the response body.
|
||||
var actualContent []byte
|
||||
actualContent, err = ioutil.ReadAll(rec.Body)
|
||||
@@ -1724,7 +1724,7 @@ func ExecObjectLayerAPIAnonTest(t *testing.T, obj ObjectLayer, testName, bucketN
|
||||
// call the handler using the HTTP Request.
|
||||
apiRouter.ServeHTTP(rec, anonReq)
|
||||
// verify the response body for `ErrAccessDenied` message =.
|
||||
if anonReq.Method != "HEAD" {
|
||||
if anonReq.Method != http.MethodHead {
|
||||
// read the response body.
|
||||
actualContent, err := ioutil.ReadAll(rec.Body)
|
||||
if err != nil {
|
||||
@@ -1781,7 +1781,7 @@ func ExecObjectLayerAPINilTest(t TestErrHandler, bucketName, objectName, instanc
|
||||
|
||||
// HEAD HTTP Request doesn't contain body in its response,
|
||||
// for other type of HTTP requests compare the response body content with the expected one.
|
||||
if req.Method != "HEAD" {
|
||||
if req.Method != http.MethodHead {
|
||||
// read the response body.
|
||||
actualContent, err := ioutil.ReadAll(rec.Body)
|
||||
if err != nil {
|
||||
@@ -1975,76 +1975,76 @@ func registerBucketLevelFunc(bucket *mux.Router, api objectAPIHandlers, apiFunct
|
||||
switch apiFunction {
|
||||
case "PostPolicy":
|
||||
// Register PostPolicy handler.
|
||||
bucket.Methods("POST").HeadersRegexp("Content-Type", "multipart/form-data*").HandlerFunc(api.PostPolicyBucketHandler)
|
||||
bucket.Methods(http.MethodPost).HeadersRegexp("Content-Type", "multipart/form-data*").HandlerFunc(api.PostPolicyBucketHandler)
|
||||
case "HeadObject":
|
||||
// Register HeadObject handler.
|
||||
bucket.Methods("Head").Path("/{object:.+}").HandlerFunc(api.HeadObjectHandler)
|
||||
case "GetObject":
|
||||
// Register GetObject handler.
|
||||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(api.GetObjectHandler)
|
||||
bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(api.GetObjectHandler)
|
||||
case "PutObject":
|
||||
// Register PutObject handler.
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(api.PutObjectHandler)
|
||||
bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(api.PutObjectHandler)
|
||||
case "DeleteObject":
|
||||
// Register Delete Object handler.
|
||||
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(api.DeleteObjectHandler)
|
||||
bucket.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(api.DeleteObjectHandler)
|
||||
case "CopyObject":
|
||||
// Register Copy Object handler.
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(api.CopyObjectHandler)
|
||||
bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(api.CopyObjectHandler)
|
||||
case "PutBucketPolicy":
|
||||
// Register PutBucket Policy handler.
|
||||
bucket.Methods("PUT").HandlerFunc(api.PutBucketPolicyHandler).Queries("policy", "")
|
||||
bucket.Methods(http.MethodPut).HandlerFunc(api.PutBucketPolicyHandler).Queries("policy", "")
|
||||
case "DeleteBucketPolicy":
|
||||
// Register Delete bucket HTTP policy handler.
|
||||
bucket.Methods("DELETE").HandlerFunc(api.DeleteBucketPolicyHandler).Queries("policy", "")
|
||||
bucket.Methods(http.MethodDelete).HandlerFunc(api.DeleteBucketPolicyHandler).Queries("policy", "")
|
||||
case "GetBucketPolicy":
|
||||
// Register Get Bucket policy HTTP Handler.
|
||||
bucket.Methods("GET").HandlerFunc(api.GetBucketPolicyHandler).Queries("policy", "")
|
||||
bucket.Methods(http.MethodGet).HandlerFunc(api.GetBucketPolicyHandler).Queries("policy", "")
|
||||
case "GetBucketLifecycle":
|
||||
bucket.Methods("GET").HandlerFunc(api.GetBucketLifecycleHandler).Queries("lifecycle", "")
|
||||
bucket.Methods(http.MethodGet).HandlerFunc(api.GetBucketLifecycleHandler).Queries("lifecycle", "")
|
||||
case "PutBucketLifecycle":
|
||||
bucket.Methods("PUT").HandlerFunc(api.PutBucketLifecycleHandler).Queries("lifecycle", "")
|
||||
bucket.Methods(http.MethodPut).HandlerFunc(api.PutBucketLifecycleHandler).Queries("lifecycle", "")
|
||||
case "DeleteBucketLifecycle":
|
||||
bucket.Methods("DELETE").HandlerFunc(api.DeleteBucketLifecycleHandler).Queries("lifecycle", "")
|
||||
bucket.Methods(http.MethodDelete).HandlerFunc(api.DeleteBucketLifecycleHandler).Queries("lifecycle", "")
|
||||
case "GetBucketLocation":
|
||||
// Register GetBucketLocation handler.
|
||||
bucket.Methods("GET").HandlerFunc(api.GetBucketLocationHandler).Queries("location", "")
|
||||
bucket.Methods(http.MethodGet).HandlerFunc(api.GetBucketLocationHandler).Queries("location", "")
|
||||
case "HeadBucket":
|
||||
// Register HeadBucket handler.
|
||||
bucket.Methods("HEAD").HandlerFunc(api.HeadBucketHandler)
|
||||
bucket.Methods(http.MethodHead).HandlerFunc(api.HeadBucketHandler)
|
||||
case "DeleteMultipleObjects":
|
||||
// Register DeleteMultipleObjects handler.
|
||||
bucket.Methods("POST").HandlerFunc(api.DeleteMultipleObjectsHandler).Queries("delete", "")
|
||||
bucket.Methods(http.MethodPost).HandlerFunc(api.DeleteMultipleObjectsHandler).Queries("delete", "")
|
||||
case "NewMultipart":
|
||||
// Register New Multipart upload handler.
|
||||
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(api.NewMultipartUploadHandler).Queries("uploads", "")
|
||||
bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(api.NewMultipartUploadHandler).Queries("uploads", "")
|
||||
case "CopyObjectPart":
|
||||
// Register CopyObjectPart handler.
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(api.CopyObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
||||
bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(api.CopyObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
||||
case "PutObjectPart":
|
||||
// Register PutObjectPart handler.
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(api.PutObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
||||
bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(api.PutObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
||||
case "ListObjectParts":
|
||||
// Register ListObjectParts handler.
|
||||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(api.ListObjectPartsHandler).Queries("uploadId", "{uploadId:.*}")
|
||||
bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(api.ListObjectPartsHandler).Queries("uploadId", "{uploadId:.*}")
|
||||
case "ListMultipartUploads":
|
||||
// Register ListMultipartUploads handler.
|
||||
bucket.Methods("GET").HandlerFunc(api.ListMultipartUploadsHandler).Queries("uploads", "")
|
||||
bucket.Methods(http.MethodGet).HandlerFunc(api.ListMultipartUploadsHandler).Queries("uploads", "")
|
||||
case "CompleteMultipart":
|
||||
// Register Complete Multipart Upload handler.
|
||||
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(api.CompleteMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}")
|
||||
bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(api.CompleteMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}")
|
||||
case "AbortMultipart":
|
||||
// Register AbortMultipart Handler.
|
||||
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(api.AbortMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}")
|
||||
bucket.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(api.AbortMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}")
|
||||
case "GetBucketNotification":
|
||||
// Register GetBucketNotification Handler.
|
||||
bucket.Methods("GET").HandlerFunc(api.GetBucketNotificationHandler).Queries("notification", "")
|
||||
bucket.Methods(http.MethodGet).HandlerFunc(api.GetBucketNotificationHandler).Queries("notification", "")
|
||||
case "PutBucketNotification":
|
||||
// Register PutBucketNotification Handler.
|
||||
bucket.Methods("PUT").HandlerFunc(api.PutBucketNotificationHandler).Queries("notification", "")
|
||||
case "ListenBucketNotification":
|
||||
// Register ListenBucketNotification Handler.
|
||||
bucket.Methods("GET").HandlerFunc(api.ListenBucketNotificationHandler).Queries("events", "{events:.*}")
|
||||
bucket.Methods(http.MethodPut).HandlerFunc(api.PutBucketNotificationHandler).Queries("notification", "")
|
||||
case "ListenNotification":
|
||||
// Register ListenNotification Handler.
|
||||
bucket.Methods(http.MethodGet).HandlerFunc(api.ListenNotificationHandler).Queries("events", "{events:.*}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2087,7 +2087,7 @@ func registerAPIFunctions(muxRouter *mux.Router, objLayer ObjectLayer, apiFuncti
|
||||
}
|
||||
|
||||
// Register ListBuckets handler.
|
||||
apiRouter.Methods("GET").HandlerFunc(api.ListBucketsHandler)
|
||||
apiRouter.Methods(http.MethodGet).HandlerFunc(api.ListBucketsHandler)
|
||||
// Register all bucket level handlers.
|
||||
registerBucketLevelFunc(bucketRouter, api, apiFunctions...)
|
||||
}
|
||||
@@ -2306,7 +2306,7 @@ func uploadTestObject(t *testing.T, apiRouter http.Handler, creds auth.Credentia
|
||||
|
||||
if !asMultipart {
|
||||
srcData := NewDummyDataGen(partSizes[0], 0)
|
||||
req, err := newTestSignedRequestV4("PUT", getPutObjectURL("", bucketName, objectName),
|
||||
req, err := newTestSignedRequestV4(http.MethodPut, getPutObjectURL("", bucketName, objectName),
|
||||
partSizes[0], srcData, creds.AccessKey, creds.SecretKey, metadata)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %#v", err)
|
||||
@@ -2320,7 +2320,7 @@ func uploadTestObject(t *testing.T, apiRouter http.Handler, creds auth.Credentia
|
||||
// object when reading).
|
||||
|
||||
// Initiate mp upload
|
||||
reqI, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, objectName),
|
||||
reqI, err := newTestSignedRequestV4(http.MethodPost, getNewMultipartURL("", bucketName, objectName),
|
||||
0, nil, creds.AccessKey, creds.SecretKey, metadata)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %#v", err)
|
||||
@@ -2343,7 +2343,7 @@ func uploadTestObject(t *testing.T, apiRouter http.Handler, creds auth.Credentia
|
||||
partID := i + 1
|
||||
partSrc := NewDummyDataGen(partLen, cumulativeSum)
|
||||
cumulativeSum += partLen
|
||||
req, errP := newTestSignedRequestV4("PUT",
|
||||
req, errP := newTestSignedRequestV4(http.MethodPut,
|
||||
getPutObjectPartURL("", bucketName, objectName, upID, fmt.Sprintf("%d", partID)),
|
||||
partLen, partSrc, creds.AccessKey, creds.SecretKey, metadata)
|
||||
if errP != nil {
|
||||
@@ -2369,7 +2369,7 @@ func uploadTestObject(t *testing.T, apiRouter http.Handler, creds auth.Credentia
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %#v", err)
|
||||
}
|
||||
reqC, errP := newTestSignedRequestV4("POST",
|
||||
reqC, errP := newTestSignedRequestV4(http.MethodPost,
|
||||
getCompleteMultipartUploadURL("", bucketName, objectName, upID),
|
||||
int64(len(compMpBody)), bytes.NewReader(compMpBody),
|
||||
creds.AccessKey, creds.SecretKey, metadata)
|
||||
|
||||
+21
-19
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2016 MinIO, Inc.
|
||||
* MinIO Cloud Storage, (C) 2016-2020 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -80,23 +80,21 @@ func (t *TreeWalkPool) Release(params listParams) (resultCh chan TreeWalkResult,
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
walks, ok := t.pool[params] // Pick the valid walks.
|
||||
if ok {
|
||||
if len(walks) > 0 {
|
||||
// Pop out the first valid walk entry.
|
||||
walk := walks[0]
|
||||
walks[0] = treeWalk{} // clear references.
|
||||
walks = walks[1:]
|
||||
if len(walks) > 0 {
|
||||
t.pool[params] = walks
|
||||
} else {
|
||||
delete(t.pool, params)
|
||||
}
|
||||
walk.endTimerCh <- struct{}{}
|
||||
return walk.resultCh, walk.endWalkCh
|
||||
}
|
||||
if !ok || len(walks) == 0 {
|
||||
// Release return nil if params not found.
|
||||
return nil, nil
|
||||
}
|
||||
// Release return nil if params not found.
|
||||
return nil, nil
|
||||
|
||||
// Pop out the first valid walk entry.
|
||||
walk := walks[0]
|
||||
walks = walks[1:]
|
||||
if len(walks) > 0 {
|
||||
t.pool[params] = walks
|
||||
} else {
|
||||
delete(t.pool, params)
|
||||
}
|
||||
walk.endTimerCh <- struct{}{}
|
||||
return walk.resultCh, walk.endWalkCh
|
||||
}
|
||||
|
||||
// Set - adds a treeWalk to the treeWalkPool.
|
||||
@@ -129,6 +127,7 @@ func (t *TreeWalkPool) Set(params listParams, resultCh chan TreeWalkResult, endW
|
||||
// Invalidate and delete oldest.
|
||||
if walks, ok := t.pool[oldest]; ok && len(walks) > 0 {
|
||||
endCh := walks[0].endTimerCh
|
||||
endWalkCh := walks[0].endWalkCh
|
||||
if len(walks) > 1 {
|
||||
// Move walks forward
|
||||
copy(walks, walks[1:])
|
||||
@@ -140,6 +139,7 @@ func (t *TreeWalkPool) Set(params listParams, resultCh chan TreeWalkResult, endW
|
||||
}
|
||||
select {
|
||||
case endCh <- struct{}{}:
|
||||
close(endWalkCh)
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
@@ -156,6 +156,7 @@ func (t *TreeWalkPool) Set(params listParams, resultCh chan TreeWalkResult, endW
|
||||
endWalkCh: endWalkCh,
|
||||
endTimerCh: endTimerCh,
|
||||
}
|
||||
|
||||
// Append new walk info.
|
||||
walks := t.pool[params]
|
||||
if len(walks) < treeWalkSameEntryLimit {
|
||||
@@ -164,6 +165,7 @@ func (t *TreeWalkPool) Set(params listParams, resultCh chan TreeWalkResult, endW
|
||||
// We are at limit, invalidate oldest, move list down and add new as last.
|
||||
select {
|
||||
case walks[0].endTimerCh <- struct{}{}:
|
||||
close(walks[0].endWalkCh)
|
||||
default:
|
||||
}
|
||||
copy(walks, walks[1:])
|
||||
@@ -171,7 +173,7 @@ func (t *TreeWalkPool) Set(params listParams, resultCh chan TreeWalkResult, endW
|
||||
}
|
||||
|
||||
// Timer go-routine which times out after t.timeOut seconds.
|
||||
go func(endTimerCh <-chan struct{}) {
|
||||
go func(endTimerCh <-chan struct{}, walkInfo treeWalk) {
|
||||
select {
|
||||
// Wait until timeOut
|
||||
case <-time.After(t.timeOut):
|
||||
@@ -205,5 +207,5 @@ func (t *TreeWalkPool) Set(params listParams, resultCh chan TreeWalkResult, endW
|
||||
case <-endTimerCh:
|
||||
return
|
||||
}
|
||||
}(endTimerCh)
|
||||
}(endTimerCh, walkInfo)
|
||||
}
|
||||
|
||||
+2
-2
@@ -225,7 +225,7 @@ func TestCheckURL(t *testing.T) {
|
||||
|
||||
// Testing dumping request function.
|
||||
func TestDumpRequest(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "http://localhost:9000?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=USWUXHGYZQYFYFFIT3RE%2F20170529%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20170529T190139Z&X-Amz-Expires=600&X-Amz-Signature=19b58080999df54b446fc97304eb8dda60d3df1812ae97f3e8783351bfd9781d&X-Amz-SignedHeaders=host&prefix=Hello%2AWorld%2A", nil)
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:9000?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=USWUXHGYZQYFYFFIT3RE%2F20170529%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20170529T190139Z&X-Amz-Expires=600&X-Amz-Signature=19b58080999df54b446fc97304eb8dda60d3df1812ae97f3e8783351bfd9781d&X-Amz-SignedHeaders=host&prefix=Hello%2AWorld%2A", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -244,7 +244,7 @@ func TestDumpRequest(t *testing.T) {
|
||||
}
|
||||
|
||||
// Look for expected method.
|
||||
if res.Method != "GET" {
|
||||
if res.Method != http.MethodGet {
|
||||
t.Fatalf("Unexpected method %s, expected 'GET'", res.Method)
|
||||
}
|
||||
|
||||
|
||||
@@ -183,6 +183,7 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep
|
||||
if err = objectAPI.MakeBucketWithLocation(ctx, args.BucketName, opts); err != nil {
|
||||
return toJSONError(ctx, err)
|
||||
}
|
||||
|
||||
if err = globalDNSConfig.Put(args.BucketName); err != nil {
|
||||
objectAPI.DeleteBucket(ctx, args.BucketName, false)
|
||||
return toJSONError(ctx, err)
|
||||
@@ -201,6 +202,15 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep
|
||||
}
|
||||
|
||||
reply.UIVersion = browser.UIVersion
|
||||
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.BucketCreated,
|
||||
BucketName: args.BucketName,
|
||||
ReqParams: extractReqParams(r),
|
||||
UserAgent: r.UserAgent(),
|
||||
Host: handlers.GetSourceIP(r),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -275,6 +285,14 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs,
|
||||
}
|
||||
}
|
||||
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.BucketRemoved,
|
||||
BucketName: args.BucketName,
|
||||
ReqParams: extractReqParams(r),
|
||||
UserAgent: r.UserAgent(),
|
||||
Host: handlers.GetSourceIP(r),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -752,7 +752,7 @@ func testUploadWebHandler(obj ObjectLayer, instanceType string, t TestErrHandler
|
||||
|
||||
test := func(token string, sendContentLength bool) int {
|
||||
rec := httptest.NewRecorder()
|
||||
req, rErr := http.NewRequest("PUT", "/minio/upload/"+bucketName+SlashSeparator+objectName, nil)
|
||||
req, rErr := http.NewRequest(http.MethodPut, "/minio/upload/"+bucketName+SlashSeparator+objectName, nil)
|
||||
if rErr != nil {
|
||||
t.Fatalf("Cannot create upload request, %v", rErr)
|
||||
}
|
||||
@@ -838,7 +838,7 @@ func testDownloadWebHandler(obj ObjectLayer, instanceType string, t TestErrHandl
|
||||
path = path + token
|
||||
}
|
||||
var req *http.Request
|
||||
req, err = http.NewRequest("GET", path, nil)
|
||||
req, err = http.NewRequest(http.MethodGet, path, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot create upload request, %v", err)
|
||||
@@ -957,7 +957,7 @@ func testWebHandlerDownloadZip(obj ObjectLayer, instanceType string, t TestErrHa
|
||||
return 0, nil
|
||||
}
|
||||
var req *http.Request
|
||||
req, err = http.NewRequest("POST", path, bytes.NewBuffer(argsData))
|
||||
req, err = http.NewRequest(http.MethodPost, path, bytes.NewBuffer(argsData))
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot create upload request, %v", err)
|
||||
@@ -1055,7 +1055,7 @@ func testWebPresignedGetHandler(obj ObjectLayer, instanceType string, t TestErrH
|
||||
// Initialize a new api recorder.
|
||||
arec := httptest.NewRecorder()
|
||||
|
||||
req, err = newTestRequest("GET", presignGetRep.URL, 0, nil)
|
||||
req, err = newTestRequest(http.MethodGet, presignGetRep.URL, 0, nil)
|
||||
req.Header.Del("x-amz-content-sha256")
|
||||
if err != nil {
|
||||
t.Fatal("Failed to initialized a new request", err)
|
||||
@@ -1153,7 +1153,7 @@ func TestWebCheckAuthorization(t *testing.T) {
|
||||
|
||||
rec = httptest.NewRecorder()
|
||||
// Test authorization of Web.Download
|
||||
req, err := http.NewRequest("GET", "/minio/download/bucket/object?token=wrongauth", nil)
|
||||
req, err := http.NewRequest(http.MethodGet, "/minio/download/bucket/object?token=wrongauth", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot create upload request, %v", err)
|
||||
}
|
||||
@@ -1170,7 +1170,7 @@ func TestWebCheckAuthorization(t *testing.T) {
|
||||
rec = httptest.NewRecorder()
|
||||
// Test authorization of Web.Upload
|
||||
content := []byte("temporary file's content")
|
||||
req, err = http.NewRequest("PUT", "/minio/upload/bucket/object", nil)
|
||||
req, err = http.NewRequest(http.MethodPut, "/minio/upload/bucket/object", nil)
|
||||
req.Header.Set("Authorization", "Bearer foo-authorization")
|
||||
req.Header.Set("User-Agent", "Mozilla")
|
||||
req.Header.Set("Content-Length", strconv.Itoa(len(content)))
|
||||
@@ -1288,7 +1288,7 @@ func TestWebObjectLayerFaultyDisks(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test authorization of Web.Download
|
||||
req, err = http.NewRequest("GET", "/minio/download/bucket/object?token="+authorization, nil)
|
||||
req, err = http.NewRequest(http.MethodGet, "/minio/download/bucket/object?token="+authorization, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot create upload request, %v", err)
|
||||
}
|
||||
@@ -1299,7 +1299,7 @@ func TestWebObjectLayerFaultyDisks(t *testing.T) {
|
||||
|
||||
// Test authorization of Web.Upload
|
||||
content := []byte("temporary file's content")
|
||||
req, err = http.NewRequest("PUT", "/minio/upload/bucket/object", nil)
|
||||
req, err = http.NewRequest(http.MethodPut, "/minio/upload/bucket/object", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+authorization)
|
||||
req.Header.Set("Content-Length", strconv.Itoa(len(content)))
|
||||
req.Header.Set("x-amz-date", "20160814T114029Z")
|
||||
|
||||
+4
-4
@@ -83,13 +83,13 @@ func registerWebRouter(router *mux.Router) error {
|
||||
}
|
||||
|
||||
// RPC handler at URI - /minio/webrpc
|
||||
webBrowserRouter.Methods("POST").Path("/webrpc").Handler(webRPC)
|
||||
webBrowserRouter.Methods("PUT").Path("/upload/{bucket}/{object:.+}").HandlerFunc(httpTraceHdrs(web.Upload))
|
||||
webBrowserRouter.Methods(http.MethodPost).Path("/webrpc").Handler(webRPC)
|
||||
webBrowserRouter.Methods(http.MethodPut).Path("/upload/{bucket}/{object:.+}").HandlerFunc(httpTraceHdrs(web.Upload))
|
||||
|
||||
// These methods use short-expiry tokens in the URLs. These tokens may unintentionally
|
||||
// be logged, so a new one must be generated for each request.
|
||||
webBrowserRouter.Methods("GET").Path("/download/{bucket}/{object:.+}").Queries("token", "{token:.*}").HandlerFunc(httpTraceHdrs(web.Download))
|
||||
webBrowserRouter.Methods("POST").Path("/zip").Queries("token", "{token:.*}").HandlerFunc(httpTraceHdrs(web.DownloadZip))
|
||||
webBrowserRouter.Methods(http.MethodGet).Path("/download/{bucket}/{object:.+}").Queries("token", "{token:.*}").HandlerFunc(httpTraceHdrs(web.Download))
|
||||
webBrowserRouter.Methods(http.MethodPost).Path("/zip").Queries("token", "{token:.*}").HandlerFunc(httpTraceHdrs(web.DownloadZip))
|
||||
|
||||
// Create compressed assets handler
|
||||
compressAssets := handlers.CompressHandler(http.StripPrefix(minioReservedBucketPath, http.FileServer(assetFS())))
|
||||
|
||||
+5
-5
@@ -509,7 +509,7 @@ func (s *xlStorage) GetDiskID() (string, error) {
|
||||
if os.IsNotExist(err) {
|
||||
_, err = os.Stat(s.diskPath)
|
||||
if err == nil {
|
||||
// Disk is present by missing `format.json`
|
||||
// Disk is present but missing `format.json`
|
||||
return "", errUnformattedDisk
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
@@ -2112,7 +2112,7 @@ func (s *xlStorage) RenameData(srcVolume, srcPath, dataDir, dstVolume, dstPath s
|
||||
}
|
||||
legacyDataPath := pathJoin(dstVolumeDir, dstPath, legacyDataDir)
|
||||
// legacy data dir means its old content, honor system umask.
|
||||
if err = os.Mkdir(legacyDataPath, 0777); err != nil {
|
||||
if err = os.MkdirAll(legacyDataPath, 0777); err != nil {
|
||||
if isSysErrIO(err) {
|
||||
return errFaultyDisk
|
||||
}
|
||||
@@ -2133,10 +2133,10 @@ func (s *xlStorage) RenameData(srcVolume, srcPath, dataDir, dstVolume, dstPath s
|
||||
}
|
||||
return osErrToFileErr(err)
|
||||
}
|
||||
|
||||
// Sync all the metadata operations once renames are done.
|
||||
globalSync()
|
||||
}
|
||||
|
||||
// Sync all the metadata operations once renames are done.
|
||||
globalSync()
|
||||
}
|
||||
|
||||
var oldDstDataPath string
|
||||
|
||||
@@ -38,5 +38,35 @@ This probe always responds with '200 OK'. When readiness probe fails, Kubernetes
|
||||
```
|
||||
|
||||
### Cluster probe
|
||||
|
||||
This probe is not useful in almost all cases, this is meant for administrators to see if quorum is available in any given cluster. The reply is '200 OK' if cluster has quorum if not it returns '503 Service Unavailable'.
|
||||
|
||||
```
|
||||
curl http://minio1:9001/minio/health/cluster
|
||||
HTTP/1.1 503 Service Unavailable
|
||||
Accept-Ranges: bytes
|
||||
Content-Length: 0
|
||||
Content-Security-Policy: block-all-mixed-content
|
||||
Server: MinIO/GOGET.GOGET
|
||||
Vary: Origin
|
||||
X-Amz-Bucket-Region: us-east-1
|
||||
X-Amz-Request-Id: 16239D6AB80EBECF
|
||||
X-Xss-Protection: 1; mode=block
|
||||
Date: Tue, 21 Jul 2020 00:36:14 GMT
|
||||
```
|
||||
|
||||
#### Checking cluster health for maintenance
|
||||
You may query the cluster probe endpoint to check if the node which received the request can be taken down for maintenance, if the server replies back '412 Precondition Failed' this means you will loose HA. '200 OK' means you are okay to proceed.
|
||||
|
||||
```
|
||||
curl http://minio1:9001/minio/health/cluster?maintenance=true
|
||||
HTTP/1.1 412 Precondition Failed
|
||||
Accept-Ranges: bytes
|
||||
Content-Length: 0
|
||||
Content-Security-Policy: block-all-mixed-content
|
||||
Server: MinIO/GOGET.GOGET
|
||||
Vary: Origin
|
||||
X-Amz-Bucket-Region: us-east-1
|
||||
X-Amz-Request-Id: 16239D63820C6E76
|
||||
X-Xss-Protection: 1; mode=block
|
||||
Date: Tue, 21 Jul 2020 00:35:43 GMT
|
||||
```
|
||||
|
||||
@@ -5,7 +5,7 @@ version: '3.7'
|
||||
# 9001 through 9004.
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2020-07-18T18-48-16Z
|
||||
image: minio/minio:RELEASE.2020-07-20T02-25-16Z
|
||||
volumes:
|
||||
- data1-1:/data1
|
||||
- data1-2:/data2
|
||||
@@ -22,7 +22,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2020-07-18T18-48-16Z
|
||||
image: minio/minio:RELEASE.2020-07-20T02-25-16Z
|
||||
volumes:
|
||||
- data2-1:/data1
|
||||
- data2-2:/data2
|
||||
@@ -39,7 +39,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2020-07-18T18-48-16Z
|
||||
image: minio/minio:RELEASE.2020-07-20T02-25-16Z
|
||||
volumes:
|
||||
- data3-1:/data1
|
||||
- data3-2:/data2
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2020-07-18T18-48-16Z
|
||||
image: minio/minio:RELEASE.2020-07-20T02-25-16Z
|
||||
volumes:
|
||||
- data4-1:/data1
|
||||
- data4-2:/data2
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.7'
|
||||
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2020-07-18T18-48-16Z
|
||||
image: minio/minio:RELEASE.2020-07-20T02-25-16Z
|
||||
hostname: minio1
|
||||
volumes:
|
||||
- minio1-data:/export
|
||||
@@ -29,7 +29,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2020-07-18T18-48-16Z
|
||||
image: minio/minio:RELEASE.2020-07-20T02-25-16Z
|
||||
hostname: minio2
|
||||
volumes:
|
||||
- minio2-data:/export
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2020-07-18T18-48-16Z
|
||||
image: minio/minio:RELEASE.2020-07-20T02-25-16Z
|
||||
hostname: minio3
|
||||
volumes:
|
||||
- minio3-data:/export
|
||||
@@ -83,7 +83,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2020-07-18T18-48-16Z
|
||||
image: minio/minio:RELEASE.2020-07-20T02-25-16Z
|
||||
hostname: minio4
|
||||
volumes:
|
||||
- minio4-data:/export
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.7'
|
||||
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2020-07-18T18-48-16Z
|
||||
image: minio/minio:RELEASE.2020-07-20T02-25-16Z
|
||||
hostname: minio1
|
||||
volumes:
|
||||
- minio1-data:/export
|
||||
@@ -33,7 +33,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2020-07-18T18-48-16Z
|
||||
image: minio/minio:RELEASE.2020-07-20T02-25-16Z
|
||||
hostname: minio2
|
||||
volumes:
|
||||
- minio2-data:/export
|
||||
@@ -64,7 +64,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2020-07-18T18-48-16Z
|
||||
image: minio/minio:RELEASE.2020-07-20T02-25-16Z
|
||||
hostname: minio3
|
||||
volumes:
|
||||
- minio3-data:/export
|
||||
@@ -95,7 +95,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2020-07-18T18-48-16Z
|
||||
image: minio/minio:RELEASE.2020-07-20T02-25-16Z
|
||||
hostname: minio4
|
||||
volumes:
|
||||
- minio4-data:/export
|
||||
|
||||
@@ -282,6 +282,13 @@ func TestComputeActions(t *testing.T) {
|
||||
objectModTime: time.Now().UTC().Add(-10 * 24 * time.Hour), // Created 10 days ago
|
||||
expectedAction: NoneAction,
|
||||
},
|
||||
// Test rule with empty prefix e.g. for whole bucket
|
||||
{
|
||||
inputConfig: `<LifecycleConfiguration><Rule><Filter><Prefix></Prefix></Filter><Status>Enabled</Status><Expiration><Days>5</Days></Expiration></Rule></LifecycleConfiguration>`,
|
||||
objectName: "foxdir/fooobject/foo.txt",
|
||||
objectModTime: time.Now().UTC().Add(-10 * 24 * time.Hour), // Created 10 days ago
|
||||
expectedAction: DeleteAction,
|
||||
},
|
||||
// Too early to remove (test Days)
|
||||
{
|
||||
inputConfig: `<LifecycleConfiguration><Rule><Filter><Prefix>foodir/</Prefix></Filter><Status>Enabled</Status><Expiration><Days>5</Days></Expiration></Rule></LifecycleConfiguration>`,
|
||||
@@ -359,6 +366,22 @@ func TestComputeActions(t *testing.T) {
|
||||
objectModTime: time.Now().UTC().Add(-24 * time.Hour), // Created 1 day ago
|
||||
expectedAction: NoneAction,
|
||||
},
|
||||
// Should remove - empty prefix, tags match, date expiration kicked in
|
||||
{
|
||||
inputConfig: `<LifecycleConfiguration><Rule><Filter><And><Tag><Key>tag1</Key><Value>value1</Value></Tag></And></Filter><Status>Enabled</Status><Expiration><Date>` + time.Now().Truncate(24*time.Hour).UTC().Add(-24*time.Hour).Format(time.RFC3339) + `</Date></Expiration></Rule></LifecycleConfiguration>`,
|
||||
objectName: "foxdir/fooobject",
|
||||
objectTags: "tag1=value1",
|
||||
objectModTime: time.Now().UTC().Add(-24 * time.Hour), // Created 1 day ago
|
||||
expectedAction: DeleteAction,
|
||||
},
|
||||
// Should remove - empty prefix, tags match, object is expired based on specified Days
|
||||
{
|
||||
inputConfig: `<LifecycleConfiguration><Rule><Filter><And><Prefix></Prefix><Tag><Key>tag1</Key><Value>value1</Value></Tag></And></Filter><Status>Enabled</Status><Expiration><Days>1</Days></Expiration></Rule></LifecycleConfiguration>`,
|
||||
objectName: "foxdir/fooobject",
|
||||
objectTags: "tag1=value1",
|
||||
objectModTime: time.Now().UTC().Add(-48 * time.Hour), // Created 2 day ago
|
||||
expectedAction: DeleteAction,
|
||||
},
|
||||
// Should remove, the second rule has expiration kicked in
|
||||
{
|
||||
inputConfig: `<LifecycleConfiguration><Rule><Status>Enabled</Status><Expiration><Date>` + time.Now().Truncate(24*time.Hour).UTC().Add(24*time.Hour).Format(time.RFC3339) + `</Date></Expiration></Rule><Rule><Filter><Prefix>foxdir/</Prefix></Filter><Status>Enabled</Status><Expiration><Date>` + time.Now().Truncate(24*time.Hour).UTC().Add(-24*time.Hour).Format(time.RFC3339) + `</Date></Expiration></Rule></LifecycleConfiguration>`,
|
||||
@@ -400,6 +423,11 @@ func TestHasActiveRules(t *testing.T) {
|
||||
prefix: "foodir/foobject",
|
||||
expectedNonRec: true, expectedRec: true,
|
||||
},
|
||||
{ // empty prefix
|
||||
inputConfig: `<LifecycleConfiguration><Rule><Status>Enabled</Status><Expiration><Days>5</Days></Expiration></Rule></LifecycleConfiguration>`,
|
||||
prefix: "foodir/foobject/foo.txt",
|
||||
expectedNonRec: true, expectedRec: true,
|
||||
},
|
||||
{
|
||||
inputConfig: `<LifecycleConfiguration><Rule><Filter><Prefix>foodir/</Prefix></Filter><Status>Enabled</Status><Expiration><Days>5</Days></Expiration></Rule></LifecycleConfiguration>`,
|
||||
prefix: "zdir/foobject",
|
||||
|
||||
@@ -71,6 +71,10 @@ const (
|
||||
// ListBucketMultipartUploadsAction - ListMultipartUploads Rest API action.
|
||||
ListBucketMultipartUploadsAction = "s3:ListBucketMultipartUploads"
|
||||
|
||||
// ListenNotificationAction - ListenNotification Rest API action.
|
||||
// This is MinIO extension.
|
||||
ListenNotificationAction = "s3:ListenNotification"
|
||||
|
||||
// ListenBucketNotificationAction - ListenBucketNotification Rest API action.
|
||||
// This is MinIO extension.
|
||||
ListenBucketNotificationAction = "s3:ListenBucketNotification"
|
||||
@@ -191,6 +195,7 @@ var supportedActions = map[Action]struct{}{
|
||||
ListAllMyBucketsAction: {},
|
||||
ListBucketAction: {},
|
||||
ListBucketMultipartUploadsAction: {},
|
||||
ListenNotificationAction: {},
|
||||
ListenBucketNotificationAction: {},
|
||||
ListMultipartUploadPartsAction: {},
|
||||
PutBucketNotificationAction: {},
|
||||
@@ -293,6 +298,10 @@ var actionConditionKeyMap = map[Action]condition.KeySet{
|
||||
|
||||
ListBucketMultipartUploadsAction: condition.NewKeySet(condition.CommonKeys...),
|
||||
|
||||
ListenNotificationAction: condition.NewKeySet(condition.CommonKeys...),
|
||||
|
||||
ListenBucketNotificationAction: condition.NewKeySet(condition.CommonKeys...),
|
||||
|
||||
ListMultipartUploadPartsAction: condition.NewKeySet(condition.CommonKeys...),
|
||||
|
||||
PutObjectAction: condition.NewKeySet(
|
||||
|
||||
+19
-2
@@ -23,9 +23,10 @@ import (
|
||||
|
||||
// Name - event type enum.
|
||||
// Refer http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#notification-how-to-event-types-and-destinations
|
||||
// for most basic values we have since extend this and its not really much applicable other than a reference point.
|
||||
type Name int
|
||||
|
||||
// Values of Name
|
||||
// Values of event Name
|
||||
const (
|
||||
ObjectAccessedAll Name = 1 + iota
|
||||
ObjectAccessedGet
|
||||
@@ -42,15 +43,23 @@ const (
|
||||
ObjectRemovedAll
|
||||
ObjectRemovedDelete
|
||||
ObjectRemovedDeleteMarkerCreated
|
||||
|
||||
BucketCreated
|
||||
BucketRemoved
|
||||
)
|
||||
|
||||
// Expand - returns expanded values of abbreviated event type.
|
||||
func (name Name) Expand() []Name {
|
||||
switch name {
|
||||
case BucketCreated:
|
||||
return []Name{BucketCreated}
|
||||
case BucketRemoved:
|
||||
return []Name{BucketRemoved}
|
||||
case ObjectAccessedAll:
|
||||
return []Name{ObjectAccessedGet, ObjectAccessedHead, ObjectAccessedGetRetention, ObjectAccessedGetLegalHold}
|
||||
case ObjectCreatedAll:
|
||||
return []Name{ObjectCreatedCompleteMultipartUpload, ObjectCreatedCopy, ObjectCreatedPost, ObjectCreatedPut, ObjectCreatedPutRetention, ObjectCreatedPutLegalHold}
|
||||
return []Name{ObjectCreatedCompleteMultipartUpload, ObjectCreatedCopy,
|
||||
ObjectCreatedPost, ObjectCreatedPut, ObjectCreatedPutRetention, ObjectCreatedPutLegalHold}
|
||||
case ObjectRemovedAll:
|
||||
return []Name{ObjectRemovedDelete}
|
||||
default:
|
||||
@@ -61,6 +70,10 @@ func (name Name) Expand() []Name {
|
||||
// String - returns string representation of event type.
|
||||
func (name Name) String() string {
|
||||
switch name {
|
||||
case BucketCreated:
|
||||
return "s3:BucketCreated:*"
|
||||
case BucketRemoved:
|
||||
return "s3:BucketRemoved:*"
|
||||
case ObjectAccessedAll:
|
||||
return "s3:ObjectAccessed:*"
|
||||
case ObjectAccessedGet:
|
||||
@@ -141,6 +154,10 @@ func (name *Name) UnmarshalJSON(data []byte) error {
|
||||
// ParseName - parses string to Name.
|
||||
func ParseName(s string) (Name, error) {
|
||||
switch s {
|
||||
case "s3:BucketCreated:*":
|
||||
return BucketCreated, nil
|
||||
case "s3:BucketRemoved:*":
|
||||
return BucketRemoved, nil
|
||||
case "s3:ObjectAccessed:*":
|
||||
return ObjectAccessedAll, nil
|
||||
case "s3:ObjectAccessed:Get":
|
||||
|
||||
@@ -28,8 +28,11 @@ func TestNameExpand(t *testing.T) {
|
||||
name Name
|
||||
expectedResult []Name
|
||||
}{
|
||||
{BucketCreated, []Name{BucketCreated}},
|
||||
{BucketRemoved, []Name{BucketRemoved}},
|
||||
{ObjectAccessedAll, []Name{ObjectAccessedGet, ObjectAccessedHead, ObjectAccessedGetRetention, ObjectAccessedGetLegalHold}},
|
||||
{ObjectCreatedAll, []Name{ObjectCreatedCompleteMultipartUpload, ObjectCreatedCopy, ObjectCreatedPost, ObjectCreatedPut, ObjectCreatedPutRetention, ObjectCreatedPutLegalHold}},
|
||||
{ObjectCreatedAll, []Name{ObjectCreatedCompleteMultipartUpload, ObjectCreatedCopy,
|
||||
ObjectCreatedPost, ObjectCreatedPut, ObjectCreatedPutRetention, ObjectCreatedPutLegalHold}},
|
||||
{ObjectRemovedAll, []Name{ObjectRemovedDelete}},
|
||||
{ObjectAccessedHead, []Name{ObjectAccessedHead}},
|
||||
}
|
||||
@@ -50,6 +53,8 @@ func TestNameString(t *testing.T) {
|
||||
name Name
|
||||
expectedResult string
|
||||
}{
|
||||
{BucketCreated, "s3:BucketCreated:*"},
|
||||
{BucketRemoved, "s3:BucketRemoved:*"},
|
||||
{ObjectAccessedAll, "s3:ObjectAccessed:*"},
|
||||
{ObjectAccessedGet, "s3:ObjectAccessed:Get"},
|
||||
{ObjectAccessedHead, "s3:ObjectAccessed:Head"},
|
||||
|
||||
@@ -70,6 +70,10 @@ const (
|
||||
// ListBucketMultipartUploadsAction - ListMultipartUploads Rest API action.
|
||||
ListBucketMultipartUploadsAction = "s3:ListBucketMultipartUploads"
|
||||
|
||||
// ListenNotificationAction - ListenNotification Rest API action.
|
||||
// This is MinIO extension.
|
||||
ListenNotificationAction = "s3:ListenNotification"
|
||||
|
||||
// ListenBucketNotificationAction - ListenBucketNotification Rest API action.
|
||||
// This is MinIO extension.
|
||||
ListenBucketNotificationAction = "s3:ListenBucketNotification"
|
||||
@@ -175,6 +179,7 @@ var supportedActions = map[Action]struct{}{
|
||||
ListAllMyBucketsAction: {},
|
||||
ListBucketAction: {},
|
||||
ListBucketMultipartUploadsAction: {},
|
||||
ListenNotificationAction: {},
|
||||
ListenBucketNotificationAction: {},
|
||||
ListMultipartUploadPartsAction: {},
|
||||
PutBucketLifecycleAction: {},
|
||||
@@ -283,6 +288,8 @@ var actionConditionKeyMap = map[Action]condition.KeySet{
|
||||
|
||||
ListBucketMultipartUploadsAction: condition.NewKeySet(condition.CommonKeys...),
|
||||
|
||||
ListenNotificationAction: condition.NewKeySet(condition.CommonKeys...),
|
||||
|
||||
ListenBucketNotificationAction: condition.NewKeySet(condition.CommonKeys...),
|
||||
|
||||
ListMultipartUploadPartsAction: condition.NewKeySet(condition.CommonKeys...),
|
||||
|
||||
@@ -465,7 +465,9 @@ func intCast(v *Value) (int64, error) {
|
||||
case string:
|
||||
// Parse as number, truncate floating point if
|
||||
// needed.
|
||||
res, ok := strToInt(x)
|
||||
// String might contain trimming spaces, which
|
||||
// needs to be trimmed.
|
||||
res, ok := strToInt(strings.TrimSpace(x))
|
||||
if !ok {
|
||||
return 0, errCastFailure("could not parse as int")
|
||||
}
|
||||
@@ -473,7 +475,9 @@ func intCast(v *Value) (int64, error) {
|
||||
case []byte:
|
||||
// Parse as number, truncate floating point if
|
||||
// needed.
|
||||
res, ok := strToInt(string(x))
|
||||
// String might contain trimming spaces, which
|
||||
// needs to be trimmed.
|
||||
res, ok := strToInt(strings.TrimSpace(string(x)))
|
||||
if !ok {
|
||||
return 0, errCastFailure("could not parse as int")
|
||||
}
|
||||
@@ -491,13 +495,13 @@ func floatCast(v *Value) (float64, error) {
|
||||
case int:
|
||||
return float64(x), nil
|
||||
case string:
|
||||
f, err := strconv.ParseFloat(x, 64)
|
||||
f, err := strconv.ParseFloat(strings.TrimSpace(x), 64)
|
||||
if err != nil {
|
||||
return 0, errCastFailure("could not parse as float")
|
||||
}
|
||||
return f, nil
|
||||
case []byte:
|
||||
f, err := strconv.ParseFloat(string(x), 64)
|
||||
f, err := strconv.ParseFloat(strings.TrimSpace(string(x)), 64)
|
||||
if err != nil {
|
||||
return 0, errCastFailure("could not parse as float")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user