From fc783f84077bfe60e5f5d620fdf877cb527d4666 Mon Sep 17 00:00:00 2001 From: Anis Elleuch Date: Fri, 23 Sep 2016 00:35:12 +0100 Subject: [PATCH] More tests for web handlers (#2755) * Return negative values of Total and Free in StorageInfo() when we fail to get disk info * Return consistent messages in web handlers when the server is not initialized --- cmd/web-handlers.go | 22 +-- cmd/web-handlers_test.go | 294 ++++++++++++++++++++++++++++++++++++++- cmd/xl-v1.go | 6 + 3 files changed, 309 insertions(+), 13 deletions(-) diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index 5f1762a2f..2fbfa6ca2 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -127,7 +127,7 @@ func (web *webAPIHandlers) StorageInfo(r *http.Request, args *GenericArgs, reply reply.UIVersion = miniobrowser.UIVersion objectAPI := web.ObjectAPI() if objectAPI == nil { - return &json2.Error{Message: "Volume not found"} + return &json2.Error{Message: "Server not initialized"} } reply.StorageInfo = objectAPI.StorageInfo() return nil @@ -146,7 +146,7 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep reply.UIVersion = miniobrowser.UIVersion objectAPI := web.ObjectAPI() if objectAPI == nil { - return &json2.Error{Message: "Volume not found"} + return &json2.Error{Message: "Server not initialized"} } if err := objectAPI.MakeBucket(args.BucketName); err != nil { return &json2.Error{Message: err.Error()} @@ -175,7 +175,7 @@ func (web *webAPIHandlers) ListBuckets(r *http.Request, args *WebGenericArgs, re } objectAPI := web.ObjectAPI() if objectAPI == nil { - return &json2.Error{Message: "Volume not found"} + return &json2.Error{Message: "Server not initialized"} } buckets, err := objectAPI.ListBuckets() if err != nil { @@ -227,7 +227,7 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r for { objectAPI := web.ObjectAPI() if objectAPI == nil { - return &json2.Error{Message: "Volume not found"} + return &json2.Error{Message: "Server not initialized"} } lo, err := objectAPI.ListObjects(args.BucketName, args.Prefix, marker, "/", 1000) if err != nil { @@ -269,7 +269,7 @@ func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs, reply.UIVersion = miniobrowser.UIVersion objectAPI := web.ObjectAPI() if objectAPI == nil { - return &json2.Error{Message: "Volume not found"} + return &json2.Error{Message: "Server not initialized"} } if err := objectAPI.DeleteObject(args.BucketName, args.ObjectName); err != nil { return &json2.Error{Message: err.Error()} @@ -412,7 +412,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { objectAPI := web.ObjectAPI() if objectAPI == nil { - writeWebErrorResponse(w, errors.New("Volume not found")) + writeWebErrorResponse(w, errors.New("Server not initialized")) return } if _, err := objectAPI.PutObject(bucket, object, -1, r.Body, metadata); err != nil { @@ -468,7 +468,7 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) { objectAPI := web.ObjectAPI() if objectAPI == nil { - writeWebErrorResponse(w, errors.New("Volume not found")) + writeWebErrorResponse(w, errors.New("Server not initialized")) return } objInfo, err := objectAPI.GetObjectInfo(bucket, object) @@ -594,16 +594,16 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic if !isJWTReqAuthenticated(r) { return &json2.Error{Message: "Unauthorized request"} } + objectAPI := web.ObjectAPI() + if objectAPI == nil { + return &json2.Error{Message: "Server not initialized"} + } bucketPolicy := policy.BucketPolicy(args.Policy) if !bucketPolicy.IsValidBucketPolicy() { return &json2.Error{Message: "Invalid policy " + args.Policy} } - objectAPI := web.ObjectAPI() - if objectAPI == nil { - return &json2.Error{Message: "Server not initialized"} - } policyInfo, err := readBucketAccessPolicy(objectAPI, args.BucketName) if err != nil { return &json2.Error{Message: err.Error()} diff --git a/cmd/web-handlers_test.go b/cmd/web-handlers_test.go index 1a0c2f72c..f9e09c137 100644 --- a/cmd/web-handlers_test.go +++ b/cmd/web-handlers_test.go @@ -23,8 +23,10 @@ import ( "net/http" "net/http/httptest" "strconv" + "strings" "testing" + router "github.com/gorilla/mux" "github.com/minio/minio-go/pkg/policy" ) @@ -742,7 +744,12 @@ func testWebGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestE rec := httptest.NewRecorder() bucketName := getRandomBucketName() - if err = obj.MakeBucket(bucketName); err != nil { + if err := obj.MakeBucket(bucketName); err != nil { + t.Fatal("Unexpected error: ", err) + } + + policyDoc := "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Resource\":[\"arn:aws:s3:::" + bucketName + "\"],\"Sid\":\"\"},{\"Action\":[\"s3:GetObject\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Resource\":[\"arn:aws:s3:::" + bucketName + "/*\"],\"Sid\":\"\"}]}" + if err := writeBucketPolicy(bucketName, obj, bytes.NewReader([]byte(policyDoc)), int64(len(policyDoc))); err != nil { t.Fatal("Unexpected error: ", err) } @@ -751,7 +758,7 @@ func testWebGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestE prefix string expectedResult policy.BucketPolicy }{ - {bucketName, "", policy.BucketPolicyNone}, + {bucketName, "", policy.BucketPolicyReadOnly}, } for i, testCase := range testCases { @@ -846,3 +853,286 @@ func testWebSetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestE } } } + +// TestWebCheckAuthorization - Test Authorization for all web handlers +func TestWebCheckAuthorization(t *testing.T) { + // Prepare XL backend + obj, fsDirs, e := prepareXL() + if e != nil { + t.Fatalf("Initialization of object layer failed for XL setup: %s", e) + } + // Executing the object layer tests for XL. + defer removeRoots(fsDirs) + + // Register the API end points with XL/FS object layer. + apiRouter := initTestWebRPCEndPoint(obj) + // initialize the server and obtain the credentials and root. + // credentials are necessary to sign the HTTP request. + rootPath, e := newTestConfig("us-east-1") + if e != nil { + t.Fatalf("Init Test config failed") + } + // remove the root folder after the test ends. + defer removeAll(rootPath) + + rec := httptest.NewRecorder() + + // Check if web rpc calls return unauthorized request with an incorrect token + webRPCs := []string{"ServerInfo", "StorageInfo", "MakeBucket", "ListBuckets", "ListObjects", "RemoveObject", "GenerateAuth", + "SetAuth", "GetAuth", "GetBucketPolicy", "SetBucketPolicy"} + for _, rpcCall := range webRPCs { + args := &GenericArgs{} + reply := &WebGenericRep{} + req, err := newTestWebRPCRequest("Web."+rpcCall, "Bearer fooauthorization", args) + if err != nil { + t.Fatalf("Test %s: Failed to create HTTP request: %v", rpcCall, err) + } + apiRouter.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("Test %s: Expected the response status to be 200, but instead found `%d`", rpcCall, rec.Code) + } + err = getTestWebRPCResponse(rec, &reply) + if err == nil { + t.Fatalf("Test %s: Should fail", rpcCall) + } else { + if !strings.Contains(err.Error(), "Unauthorized request") { + t.Fatalf("Test %s: should fail with Unauthorized request. Found error: %v", rpcCall, err) + } + } + } + + // Test authorization of Web.Download + req, err := http.NewRequest("GET", "/minio/download/bucket/object?token=wrongauth", nil) + if err != nil { + t.Fatalf("Cannot create upload request, %v", err) + } + apiRouter.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("Expected the response status to be 200, but instead found `%d`", rec.Code) + } + resp := string(rec.Body.Bytes()) + if !strings.Contains(resp, "Invalid token") { + t.Fatalf("Unexpected error message, expected: `Invalid token`, found: `%s`", resp) + } + + // Test authorization of Web.Upload + content := []byte("temporary file's content") + req, err = http.NewRequest("PUT", "/minio/upload/bucket/object", nil) + req.Header.Set("Authorization", "Bearer foo-authorization") + req.Header.Set("Content-Length", strconv.Itoa(len(content))) + req.Header.Set("x-amz-date", "20160814T114029Z") + req.Header.Set("Accept", "*/*") + req.Body = ioutil.NopCloser(bytes.NewReader(content)) + if err != nil { + t.Fatalf("Cannot create upload request, %v", err) + } + apiRouter.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("Expected the response status to be 200, but instead found `%d`", rec.Code) + } + resp = string(rec.Body.Bytes()) + if !strings.Contains(resp, "Invalid token") { + t.Fatalf("Unexpected error message, expected: `Invalid token`, found: `%s`", resp) + } +} + +// TestWebObjectLayerNotReady - Test RPCs responses when disks are not ready +func TestWebObjectLayerNotReady(t *testing.T) { + webHandlers := &webAPIHandlers{ + ObjectAPI: func() ObjectLayer { return nil }, + } + // Initialize router. + apiRouter := router.NewRouter() + registerWebRouter(apiRouter, webHandlers) + + // initialize the server and obtain the credentials and root. + // credentials are necessary to sign the HTTP request. + rootPath, e := newTestConfig("us-east-1") + if e != nil { + t.Fatalf("Init Test config failed") + } + // remove the root folder after the test ends. + defer removeAll(rootPath) + + rec := httptest.NewRecorder() + + credentials := serverConfig.GetCredential() + authorization, e := getWebRPCToken(apiRouter, credentials.AccessKeyID, credentials.SecretAccessKey) + if e != nil { + t.Fatal("Cannot authenticate") + } + + // Check if web rpc calls return Server not initialized. ServerInfo, GenerateAuth, + // SetAuth and GetAuth are not concerned + webRPCs := []string{"StorageInfo", "MakeBucket", "ListBuckets", "ListObjects", "RemoveObject", + "GetBucketPolicy", "SetBucketPolicy"} + for _, rpcCall := range webRPCs { + args := &GenericArgs{} + reply := &WebGenericRep{} + req, err := newTestWebRPCRequest("Web."+rpcCall, authorization, args) + if err != nil { + t.Fatalf("Test %s: Failed to create HTTP request: %v", rpcCall, err) + } + apiRouter.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("Test %s: Expected the response status to be 200, but instead found `%d`", rpcCall, rec.Code) + } + err = getTestWebRPCResponse(rec, &reply) + if err == nil { + t.Fatalf("Test %s: Should fail", rpcCall) + } else { + if !strings.Contains(err.Error(), "Server not initialized") { + t.Fatalf("Test %s: should fail with Unauthorized request. Found error: %v", rpcCall, err) + } + } + } + + // Test authorization of Web.Download + req, err := http.NewRequest("GET", "/minio/download/bucket/object?token="+authorization, nil) + if err != nil { + t.Fatalf("Cannot create upload request, %v", err) + } + apiRouter.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("Expected the response status to be 200, but instead found `%d`", rec.Code) + } + resp := string(rec.Body.Bytes()) + if !strings.Contains(resp, "We encountered an internal error, please try again.") { + t.Fatalf("Unexpected error message, expected: `Invalid token`, found: `%s`", resp) + } + + // Test authorization of Web.Upload + content := []byte("temporary file's content") + req, err = http.NewRequest("PUT", "/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") + req.Header.Set("Accept", "*/*") + req.Body = ioutil.NopCloser(bytes.NewReader(content)) + if err != nil { + t.Fatalf("Cannot create upload request, %v", err) + } + apiRouter.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("Expected the response status to be 200, but instead found `%d`", rec.Code) + } + resp = string(rec.Body.Bytes()) + if !strings.Contains(resp, "We encountered an internal error, please try again.") { + t.Fatalf("Unexpected error message, expected: `Invalid token`, found: `%s`", resp) + } +} + +// TestWebObjectLayerFaultyDisks - Test Web RPC responses with faulty disks +func TestWebObjectLayerFaultyDisks(t *testing.T) { + // Prepare XL backend + obj, fsDirs, e := prepareXL() + if e != nil { + t.Fatalf("Initialization of object layer failed for XL setup: %s", e) + } + // Executing the object layer tests for XL. + defer removeRoots(fsDirs) + + // Set faulty disks to XL backend + xl := obj.(xlObjects) + for i, d := range xl.storageDisks { + xl.storageDisks[i] = newNaughtyDisk(d.(*posix), nil, errFaultyDisk) + } + + webHandlers := &webAPIHandlers{ + ObjectAPI: func() ObjectLayer { return obj }, + } + // Initialize router. + apiRouter := router.NewRouter() + registerWebRouter(apiRouter, webHandlers) + + // initialize the server and obtain the credentials and root. + // credentials are necessary to sign the HTTP request. + rootPath, e := newTestConfig("us-east-1") + if e != nil { + t.Fatalf("Init Test config failed") + } + // remove the root folder after the test ends. + defer removeAll(rootPath) + + rec := httptest.NewRecorder() + + credentials := serverConfig.GetCredential() + authorization, e := getWebRPCToken(apiRouter, credentials.AccessKeyID, credentials.SecretAccessKey) + if e != nil { + t.Fatal("Cannot authenticate") + } + + // Check if web rpc calls return errors with faulty disks. ServerInfo, GenerateAuth, SetAuth, GetAuth are not concerned + webRPCs := []string{"MakeBucket", "ListBuckets", "ListObjects", "RemoveObject", + "GetBucketPolicy", "SetBucketPolicy"} + + for _, rpcCall := range webRPCs { + args := &GenericArgs{} + reply := &WebGenericRep{} + req, err := newTestWebRPCRequest("Web."+rpcCall, authorization, args) + if err != nil { + t.Fatalf("Test %s: Failed to create HTTP request: %v", rpcCall, err) + } + apiRouter.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("Test %s: Expected the response status to be 200, but instead found `%d`", rpcCall, rec.Code) + } + err = getTestWebRPCResponse(rec, &reply) + if err == nil { + t.Errorf("Test %s: Should fail", rpcCall) + } + } + + // Test Web.StorageInfo + storageInfoRequest := GenericArgs{} + storageInfoReply := &StorageInfoRep{} + req, err := newTestWebRPCRequest("Web.StorageInfo", authorization, storageInfoRequest) + if err != nil { + t.Fatalf("Failed to create HTTP request: %v", err) + } + apiRouter.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("Expected the response status to be 200, but instead found `%d`", rec.Code) + } + err = getTestWebRPCResponse(rec, &storageInfoReply) + if err != nil { + t.Fatalf("Failed %v", err) + } + if storageInfoReply.StorageInfo.Total != -1 || storageInfoReply.StorageInfo.Free != -1 { + t.Fatalf("Should get negative values of Total and Free since disks are faulty ") + } + + // Test authorization of Web.Download + req, err = http.NewRequest("GET", "/minio/download/bucket/object?token="+authorization, nil) + if err != nil { + t.Fatalf("Cannot create upload request, %v", err) + } + apiRouter.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("Expected the response status to be 200, but instead found `%d`", rec.Code) + } + resp := string(rec.Body.Bytes()) + if !strings.Contains(resp, "We encountered an internal error, please try again.") { + t.Fatalf("Unexpected error message, expected: `Invalid token`, found: `%s`", resp) + } + + // Test authorization of Web.Upload + content := []byte("temporary file's content") + req, err = http.NewRequest("PUT", "/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") + req.Header.Set("Accept", "*/*") + req.Body = ioutil.NopCloser(bytes.NewReader(content)) + if err != nil { + t.Fatalf("Cannot create upload request, %v", err) + } + apiRouter.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("Expected the response status to be 200, but instead found `%d`", rec.Code) + } + resp = string(rec.Body.Bytes()) + if !strings.Contains(resp, "We encountered an internal error, please try again.") { + t.Fatalf("Unexpected error message, expected: `Invalid token`, found: `%s`", resp) + } +} diff --git a/cmd/xl-v1.go b/cmd/xl-v1.go index 352bc5930..c217a8913 100644 --- a/cmd/xl-v1.go +++ b/cmd/xl-v1.go @@ -224,6 +224,12 @@ func (xl xlObjects) StorageInfo() StorageInfo { // Sort so that the first element is the smallest. sort.Sort(byDiskTotal(disksInfo)) + if len(disksInfo) == 0 { + return StorageInfo{ + Total: -1, + Free: -1, + } + } // Return calculated storage info, choose the lowest Total and // Free as the total aggregated values. Total capacity is always // the multiple of smallest disk among the disk list.