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)