diff --git a/cmd/bucket-notification-handlers_test.go b/cmd/bucket-notification-handlers_test.go index befea9049..e82dced58 100644 --- a/cmd/bucket-notification-handlers_test.go +++ b/cmd/bucket-notification-handlers_test.go @@ -324,3 +324,375 @@ func testGetBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te func TestGetBucketNotificationHandler(t *testing.T) { ExecObjectLayerTest(t, testGetBucketNotificationHandler) } + +func testPutBucketNotificationHandler(obj ObjectLayer, instanceType string, t TestErrHandler) { + invalidBucket := "Invalid^Bucket" + // get random bucket name. + randBucket := getRandomBucketName() + + err := obj.MakeBucket(randBucket) + if err != nil { + // failed to create randBucket, abort. + t.Fatalf("Failed to create bucket %s %s : %s", randBucket, + instanceType, err) + } + + sampleNotificationBytes := []byte("" + + "s3:ObjectCreated:*s3:ObjectRemoved:*" + + "arn:minio:sns:us-east-1:1474332374:listen" + + "") + + // Register the API end points with XL/FS object layer. + apiRouter := initTestAPIEndPoints(obj, []string{ + "GetBucketNotificationHandler", + "PutBucketNotificationHandler", + }) + + // initialize the server and obtain the credentials and root. + // credentials are necessary to sign the HTTP request. + rootPath, err := newTestConfig("us-east-1") + if err != nil { + t.Fatalf("Init Test config failed") + } + // remove the root folder after the test ends. + defer removeAll(rootPath) + + credentials := serverConfig.GetCredential() + + //Initialize global event notifier with mock queue targets. + err = initMockEventNotifier(obj) + if err != nil { + t.Fatalf("Test %s: Failed to initialize mock event notifier %v", + instanceType, err) + } + + signatureMismatchError := getAPIError(ErrContentSHA256Mismatch) + missingContentLengthError := getAPIError(ErrMissingContentLength) + type testKind int + const ( + CompareBytes testKind = iota + CheckStatus + InvalidAuth + MissingContentLength + ChunkedEncoding + ) + testCases := []struct { + bucketName string + kind testKind + expectedNotificationBytes []byte + expectedHTTPCode int + expectedAPIError string + }{ + {randBucket, CompareBytes, sampleNotificationBytes, http.StatusOK, ""}, + {randBucket, ChunkedEncoding, sampleNotificationBytes, http.StatusOK, ""}, + {randBucket, InvalidAuth, nil, signatureMismatchError.HTTPStatusCode, signatureMismatchError.Code}, + {randBucket, MissingContentLength, nil, missingContentLengthError.HTTPStatusCode, missingContentLengthError.Code}, + {invalidBucket, CheckStatus, nil, http.StatusBadRequest, ""}, + } + for i, test := range testCases { + testRec := httptest.NewRecorder() + testReq, tErr := newTestSignedRequest("PUT", getPutBucketNotificationURL("", test.bucketName), + int64(len(test.expectedNotificationBytes)), bytes.NewReader(test.expectedNotificationBytes), + credentials.AccessKeyID, credentials.SecretAccessKey) + if tErr != nil { + t.Fatalf("Test %d: %s: Failed to create HTTP testRequest for PutBucketNotification: %v", + i+1, instanceType, tErr) + } + + // Set X-Amz-Content-SHA256 in header different from what was used to calculate Signature. + switch test.kind { + case InvalidAuth: + // Triggering a authentication type check failure. + testReq.Header.Set("x-amz-content-sha256", "somethingElse") + case MissingContentLength: + testReq.ContentLength = -1 + case ChunkedEncoding: + testReq.ContentLength = -1 + testReq.TransferEncoding = append(testReq.TransferEncoding, "chunked") + } + + apiRouter.ServeHTTP(testRec, testReq) + + switch test.kind { + case CompareBytes: + + testReq, tErr = newTestSignedRequest("GET", getGetBucketNotificationURL("", test.bucketName), + int64(0), nil, credentials.AccessKeyID, credentials.SecretAccessKey) + if tErr != nil { + t.Fatalf("Test %d: %s: Failed to create HTTP testRequest for GetBucketNotification: %v", + i+1, instanceType, tErr) + } + apiRouter.ServeHTTP(testRec, testReq) + + rspBytes, rErr := ioutil.ReadAll(testRec.Body) + if rErr != nil { + t.Errorf("Test %d: %s: Failed to read response body: %v", i+1, instanceType, rErr) + } + if !bytes.Equal(rspBytes, test.expectedNotificationBytes) { + t.Errorf("Test %d: %s: Notification config doesn't match expected value %s: %v", + i+1, instanceType, string(test.expectedNotificationBytes), err) + } + case MissingContentLength, InvalidAuth: + rspBytes, rErr := ioutil.ReadAll(testRec.Body) + if rErr != nil { + t.Errorf("Test %d: %s: Failed to read response body: %v", i+1, instanceType, rErr) + } + var errCode APIError + xErr := xml.Unmarshal(rspBytes, &errCode) + if xErr != nil { + t.Errorf("Test %d: %s: Failed to unmarshal error XML: %v", i+1, instanceType, xErr) + + } + + if errCode.Code != test.expectedAPIError { + t.Errorf("Test %d: %s: Expected error code %s but received %s: %v", i+1, + instanceType, test.expectedAPIError, errCode.Code, err) + + } + fallthrough + case CheckStatus: + if testRec.Code != test.expectedHTTPCode { + t.Errorf("Test %d: %s: expected HTTP code %d, but received %d: %v", + i+1, instanceType, test.expectedHTTPCode, testRec.Code, err) + } + } + } + + // Nil Object layer + nilAPIRouter := initTestAPIEndPoints(nil, []string{ + "GetBucketNotificationHandler", + "PutBucketNotificationHandler", + }) + testRec := httptest.NewRecorder() + testReq, tErr := newTestSignedRequest("PUT", getPutBucketNotificationURL("", randBucket), + int64(len(sampleNotificationBytes)), bytes.NewReader(sampleNotificationBytes), + credentials.AccessKeyID, credentials.SecretAccessKey) + if tErr != nil { + t.Fatalf("Test %d: %s: Failed to create HTTP testRequest for PutBucketNotification: %v", + len(testCases)+1, instanceType, tErr) + } + nilAPIRouter.ServeHTTP(testRec, testReq) + if testRec.Code != http.StatusServiceUnavailable { + t.Errorf("Test %d: %s: expected HTTP code %d, but received %d: %v", + len(testCases)+1, instanceType, http.StatusServiceUnavailable, testRec.Code, err) + } +} + +func TestPutBucketNotificationHandler(t *testing.T) { + ExecObjectLayerTest(t, testPutBucketNotificationHandler) +} + +func testListenBucketNotificationHandler(obj ObjectLayer, instanceType string, t TestErrHandler) { + invalidBucket := "Invalid^Bucket" + noNotificationBucket := "nonotificationbucket" + // get random bucket name. + randBucket := getRandomBucketName() + for _, bucket := range []string{randBucket, noNotificationBucket} { + err := obj.MakeBucket(bucket) + if err != nil { + // failed to create bucket, abort. + t.Fatalf("Failed to create bucket %s %s : %s", bucket, + instanceType, err) + } + } + + sampleNotificationBytes := []byte("" + + "s3:ObjectCreated:*s3:ObjectRemoved:*" + + "arn:minio:sns:us-east-1:1474332374:listen" + + "") + + // Register the API end points with XL/FS object layer. + apiRouter := initTestAPIEndPoints(obj, []string{ + "PutBucketNotificationHandler", + "ListenBucketNotificationHandler", + "PutObject", + }) + + // initialize the server and obtain the credentials and root. + // credentials are necessary to sign the HTTP request. + rootPath, err := newTestConfig("us-east-1") + if err != nil { + t.Fatalf("Init Test config failed") + } + // remove the root folder after the test ends. + defer removeAll(rootPath) + + credentials := serverConfig.GetCredential() + + //Initialize global event notifier with mock queue targets. + err = initMockEventNotifier(obj) + if err != nil { + t.Fatalf("Test %s: Failed to initialize mock event notifier %v", + instanceType, err) + } + testRec := httptest.NewRecorder() + testReq, tErr := newTestSignedRequest("PUT", getPutBucketNotificationURL("", randBucket), + int64(len(sampleNotificationBytes)), bytes.NewReader(sampleNotificationBytes), + credentials.AccessKeyID, credentials.SecretAccessKey) + if tErr != nil { + t.Fatalf("%s: Failed to create HTTP testRequest for PutBucketNotification: %v", instanceType, tErr) + } + apiRouter.ServeHTTP(testRec, testReq) + + signatureMismatchError := getAPIError(ErrContentSHA256Mismatch) + type testKind int + const ( + CheckStatus testKind = iota + InvalidAuth + AsyncHandler + ) + tooBigPrefix := string(bytes.Repeat([]byte("a"), 1025)) + validEvents := []string{"s3:ObjectCreated:*", "s3:ObjectRemoved:*"} + invalidEvents := []string{"invalidEvent"} + testCases := []struct { + bucketName string + prefix string + suffix string + events []string + kind testKind + expectedHTTPCode int + expectedAPIError string + }{ + // FIXME: Need to find a way to run valid listen bucket notification test case without blocking the unit test. + {randBucket, "", "", invalidEvents, CheckStatus, signatureMismatchError.HTTPStatusCode, ""}, + {randBucket, tooBigPrefix, "", validEvents, CheckStatus, http.StatusBadRequest, ""}, + {invalidBucket, "", "", nil, CheckStatus, http.StatusBadRequest, ""}, + {randBucket, "", "", nil, InvalidAuth, signatureMismatchError.HTTPStatusCode, signatureMismatchError.Code}, + } + + for i, test := range testCases { + testRec = httptest.NewRecorder() + testReq, tErr = newTestSignedRequest("GET", + getListenBucketNotificationURL("", test.bucketName, test.prefix, test.suffix, test.events), + 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) + if tErr != nil { + t.Fatalf("%s: Failed to create HTTP testRequest for ListenBucketNotification: %v", instanceType, tErr) + } + // Set X-Amz-Content-SHA256 in header different from what was used to calculate Signature. + if test.kind == InvalidAuth { + // Triggering a authentication type check failure. + testReq.Header.Set("x-amz-content-sha256", "somethingElse") + } + if test.kind == AsyncHandler { + go apiRouter.ServeHTTP(testRec, testReq) + } else { + apiRouter.ServeHTTP(testRec, testReq) + switch test.kind { + case InvalidAuth: + rspBytes, rErr := ioutil.ReadAll(testRec.Body) + if rErr != nil { + t.Errorf("Test %d: %s: Failed to read response body: %v", i+1, instanceType, rErr) + } + var errCode APIError + xErr := xml.Unmarshal(rspBytes, &errCode) + if xErr != nil { + t.Errorf("Test %d: %s: Failed to unmarshal error XML: %v", i+1, instanceType, xErr) + + } + + if errCode.Code != test.expectedAPIError { + t.Errorf("Test %d: %s: Expected error code %s but received %s: %v", i+1, + instanceType, test.expectedAPIError, errCode.Code, err) + + } + fallthrough + case CheckStatus: + if testRec.Code != test.expectedHTTPCode { + t.Errorf("Test %d: %s: expected HTTP code %d, but received %d: %v", + i+1, instanceType, test.expectedHTTPCode, testRec.Code, err) + } + } + } + } + + // Nil Object layer + nilAPIRouter := initTestAPIEndPoints(nil, []string{ + "PutBucketNotificationHandler", + "ListenBucketNotificationHandler", + }) + testRec = httptest.NewRecorder() + testReq, tErr = newTestSignedRequest("GET", + getListenBucketNotificationURL("", randBucket, "", "*.jpg", []string{"s3:ObjectCreated:*", "s3:ObjectRemoved:*"}), + 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) + if tErr != nil { + t.Fatalf("%s: Failed to create HTTP testRequest for ListenBucketNotification: %v", instanceType, tErr) + } + nilAPIRouter.ServeHTTP(testRec, testReq) + if testRec.Code != http.StatusServiceUnavailable { + t.Errorf("Test %d: %s: expected HTTP code %d, but received %d: %v", + 1, instanceType, http.StatusServiceUnavailable, testRec.Code, err) + } +} + +func TestListenBucketNotificationHandler(t *testing.T) { + ExecObjectLayerTest(t, testListenBucketNotificationHandler) +} + +func testRemoveNotificationConfig(obj ObjectLayer, instanceType string, t TestErrHandler) { + invalidBucket := "Invalid^Bucket" + // get random bucket name. + randBucket := getRandomBucketName() + + err := obj.MakeBucket(randBucket) + if err != nil { + // failed to create bucket, abort. + t.Fatalf("Failed to create bucket %s %s : %s", randBucket, + instanceType, err) + } + + sampleNotificationBytes := []byte("" + + "s3:ObjectCreated:*s3:ObjectRemoved:*" + + "arn:minio:sns:us-east-1:1474332374:listen" + + "") + + // Register the API end points with XL/FS object layer. + apiRouter := initTestAPIEndPoints(obj, []string{ + "PutBucketNotificationHandler", + "ListenBucketNotificationHandler", + }) + + // initialize the server and obtain the credentials and root. + // credentials are necessary to sign the HTTP request. + rootPath, err := newTestConfig("us-east-1") + if err != nil { + t.Fatalf("Init Test config failed") + } + // remove the root folder after the test ends. + defer removeAll(rootPath) + + credentials := serverConfig.GetCredential() + + //Initialize global event notifier with mock queue targets. + err = initMockEventNotifier(obj) + if err != nil { + t.Fatalf("Test %s: Failed to initialize mock event notifier %v", + instanceType, err) + } + // Set sample bucket notification on randBucket. + testRec := httptest.NewRecorder() + testReq, tErr := newTestSignedRequest("PUT", getPutBucketNotificationURL("", randBucket), + int64(len(sampleNotificationBytes)), bytes.NewReader(sampleNotificationBytes), + credentials.AccessKeyID, credentials.SecretAccessKey) + if tErr != nil { + t.Fatalf("%s: Failed to create HTTP testRequest for PutBucketNotification: %v", instanceType, tErr) + } + apiRouter.ServeHTTP(testRec, testReq) + + testCases := []struct { + bucketName string + expectedErr error + }{ + {invalidBucket, BucketNameInvalid{Bucket: invalidBucket}}, + {randBucket, nil}, + } + for i, test := range testCases { + tErr := removeNotificationConfig(test.bucketName, obj) + if tErr != test.expectedErr { + t.Errorf("Test %d: %s expected error %v, but received %v", i+1, instanceType, test.expectedErr, tErr) + } + } +} + +func TestRemoveNotificationConfig(t *testing.T) { + ExecObjectLayerTest(t, testRemoveNotificationConfig) +} diff --git a/cmd/test-utils_test.go b/cmd/test-utils_test.go index eeed3ce3d..4156e3f25 100644 --- a/cmd/test-utils_test.go +++ b/cmd/test-utils_test.go @@ -1108,6 +1108,15 @@ func getGetBucketNotificationURL(endPoint, bucketName string) string { return makeTestTargetURL(endPoint, bucketName, "", queryValue) } +// return URL for listen bucket notification. +func getListenBucketNotificationURL(endPoint, bucketName, prefix, suffix string, events []string) string { + queryValue := url.Values{} + queryValue.Set("prefix", prefix) + queryValue.Set("suffix", suffix) + queryValue["events"] = events + return makeTestTargetURL(endPoint, bucketName, "", queryValue) +} + // returns temp root directory. ` func getTestRoot() (string, error) { return ioutil.TempDir(os.TempDir(), "api-") @@ -1321,7 +1330,10 @@ func initTestAPIEndPoints(objLayer ObjectLayer, apiFunctions []string) http.Hand // Register GetObject handler. case "GetObject": bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(api.GetObjectHandler) - // Register Delete Object handler. + // Register PutObject handler. + case "PutObject": + bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(api.PutObjectHandler) + // Register Delete Object handler. case "DeleteObject": bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(api.DeleteObjectHandler) // Register Copy Object handler. @@ -1358,6 +1370,9 @@ func initTestAPIEndPoints(objLayer ObjectLayer, apiFunctions []string) http.Hand // Register PutBucketNotification Handler. case "PutBucketNotification": bucket.Methods("PUT").HandlerFunc(api.PutBucketNotificationHandler).Queries("notification", "") + // Register ListenBucketNotification Handler. + case "ListenBucketNotification": + bucket.Methods("GET").HandlerFunc(api.ListenBucketNotificationHandler).Queries("events", "{events:.*}") // Register all api endpoints by default. default: registerAPIRouter(muxRouter, api)