7121: Replace Get(loc,true) with CompareAndTouch(). Add Compare method to Volume...
[arvados.git] / services / keepstore / handler_test.go
index 1765ddfa0334e0cf6f4ae4dee2b787df1ff0b43b..a9bf91e842f7178fff900e40fb0c2b75fa4e9fba 100644 (file)
@@ -54,11 +54,11 @@ func TestGetHandler(t *testing.T) {
        // Turn on permission settings so we can generate signed locators.
        enforce_permissions = true
        PermissionSecret = []byte(known_key)
-       permission_ttl = time.Duration(300) * time.Second
+       blob_signature_ttl = 300 * time.Second
 
        var (
                unsigned_locator  = "/" + TEST_HASH
-               valid_timestamp   = time.Now().Add(permission_ttl)
+               valid_timestamp   = time.Now().Add(blob_signature_ttl)
                expired_timestamp = time.Now().Add(-time.Hour)
                signed_locator    = "/" + SignLocator(TEST_HASH, known_token, valid_timestamp)
                expired_locator   = "/" + SignLocator(TEST_HASH, known_token, expired_timestamp)
@@ -176,7 +176,7 @@ func TestPutHandler(t *testing.T) {
        // With a server key.
 
        PermissionSecret = []byte(known_key)
-       permission_ttl = time.Duration(300) * time.Second
+       blob_signature_ttl = 300 * time.Second
 
        // When a permission key is available, the locator returned
        // from an authenticated PUT request will be signed.
@@ -195,7 +195,7 @@ func TestPutHandler(t *testing.T) {
                "Authenticated PUT, signed locator, with server key",
                http.StatusOK, response)
        response_locator := strings.TrimSpace(response.Body.String())
-       if !VerifySignature(response_locator, known_token) {
+       if VerifySignature(response_locator, known_token) != nil {
                t.Errorf("Authenticated PUT, signed locator, with server key:\n"+
                        "response '%s' does not contain a valid signature",
                        response_locator)
@@ -228,13 +228,17 @@ func TestPutAndDeleteSkipReadonlyVolumes(t *testing.T) {
        IssueRequest(
                &RequestTester{
                        method:       "PUT",
-                       uri:          "/"+TEST_HASH,
+                       uri:          "/" + TEST_HASH,
                        request_body: TEST_BLOCK,
                })
+       defer func(orig bool) {
+               never_delete = orig
+       }(never_delete)
+       never_delete = false
        IssueRequest(
                &RequestTester{
                        method:       "DELETE",
-                       uri:          "/"+TEST_HASH,
+                       uri:          "/" + TEST_HASH,
                        request_body: TEST_BLOCK,
                        api_token:    data_manager_token,
                })
@@ -245,10 +249,13 @@ func TestPutAndDeleteSkipReadonlyVolumes(t *testing.T) {
        }
        for _, e := range []expect{
                {0, "Get", 0},
+               {0, "Compare", 0},
                {0, "Touch", 0},
                {0, "Put", 0},
                {0, "Delete", 0},
-               {1, "Get", 1},
+               {1, "Get", 0},
+               {1, "Compare", 1},
+               {1, "Touch", 1},
                {1, "Put", 1},
                {1, "Delete", 1},
        } {
@@ -377,7 +384,7 @@ func TestIndexHandler(t *testing.T) {
                response)
 
        expected := `^` + TEST_HASH + `\+\d+ \d+\n` +
-               TEST_HASH_2 + `\+\d+ \d+\n$`
+               TEST_HASH_2 + `\+\d+ \d+\n\n$`
        match, _ := regexp.MatchString(expected, response.Body.String())
        if !match {
                t.Errorf(
@@ -393,7 +400,7 @@ func TestIndexHandler(t *testing.T) {
                http.StatusOK,
                response)
 
-       expected = `^` + TEST_HASH + `\+\d+ \d+\n$`
+       expected = `^` + TEST_HASH + `\+\d+ \d+\n\n$`
        match, _ = regexp.MatchString(expected, response.Body.String())
        if !match {
                t.Errorf(
@@ -440,14 +447,16 @@ func TestDeleteHandler(t *testing.T) {
        vols := KeepVM.AllWritable()
        vols[0].Put(TEST_HASH, TEST_BLOCK)
 
-       // Explicitly set the permission_ttl to 0 for these
+       // Explicitly set the blob_signature_ttl to 0 for these
        // tests, to ensure the MockVolume deletes the blocks
        // even though they have just been created.
-       permission_ttl = time.Duration(0)
+       blob_signature_ttl = time.Duration(0)
 
        var user_token = "NOT DATA MANAGER TOKEN"
        data_manager_token = "DATA MANAGER TOKEN"
 
+       never_delete = false
+
        unauth_req := &RequestTester{
                method: "DELETE",
                uri:    "/" + TEST_HASH,
@@ -528,10 +537,10 @@ func TestDeleteHandler(t *testing.T) {
                t.Error("superuser_existing_block_req: block not deleted")
        }
 
-       // A DELETE request on a block newer than permission_ttl should return
-       // success but leave the block on the volume.
+       // A DELETE request on a block newer than blob_signature_ttl
+       // should return success but leave the block on the volume.
        vols[0].Put(TEST_HASH, TEST_BLOCK)
-       permission_ttl = time.Duration(1) * time.Hour
+       blob_signature_ttl = time.Hour
 
        response = IssueRequest(superuser_existing_block_req)
        ExpectStatusCode(t,
@@ -636,7 +645,7 @@ func TestPullHandler(t *testing.T) {
                        "Invalid pull request from the data manager",
                        RequestTester{"/pull", data_manager_token, "PUT", bad_json},
                        http.StatusBadRequest,
-                       "Bad Request\n",
+                       "",
                },
        }
 
@@ -740,7 +749,7 @@ func TestTrashHandler(t *testing.T) {
                        "Invalid trash list from the data manager",
                        RequestTester{"/trash", data_manager_token, "PUT", bad_json},
                        http.StatusBadRequest,
-                       "Bad Request\n",
+                       "",
                },
        }
 
@@ -788,7 +797,7 @@ func ExpectStatusCode(
        expected_status int,
        response *httptest.ResponseRecorder) {
        if response.Code != expected_status {
-               t.Errorf("%s: expected status %s, got %+v",
+               t.Errorf("%s: expected status %d, got %+v",
                        testname, expected_status, response)
        }
 }
@@ -798,8 +807,120 @@ func ExpectBody(
        testname string,
        expected_body string,
        response *httptest.ResponseRecorder) {
-       if response.Body.String() != expected_body {
+       if expected_body != "" && response.Body.String() != expected_body {
                t.Errorf("%s: expected response body '%s', got %+v",
                        testname, expected_body, response)
        }
 }
+
+// See #7121
+func TestPutNeedsOnlyOneBuffer(t *testing.T) {
+       defer teardown()
+       KeepVM = MakeTestVolumeManager(1)
+       defer KeepVM.Close()
+
+       defer func(orig *bufferPool) {
+               bufs = orig
+       }(bufs)
+       bufs = newBufferPool(1, BLOCKSIZE)
+
+       ok := make(chan struct{})
+       go func() {
+               for i := 0; i < 2; i++ {
+                       response := IssueRequest(
+                               &RequestTester{
+                                       method:       "PUT",
+                                       uri:          "/" + TEST_HASH,
+                                       request_body: TEST_BLOCK,
+                               })
+                       ExpectStatusCode(t,
+                               "TestPutNeedsOnlyOneBuffer", http.StatusOK, response)
+               }
+               ok <- struct{}{}
+       }()
+
+       select {
+       case <-ok:
+       case <-time.After(time.Second):
+               t.Fatal("PUT deadlocks with maxBuffers==1")
+       }
+}
+
+// Invoke the PutBlockHandler a bunch of times to test for bufferpool resource
+// leak.
+func TestPutHandlerNoBufferleak(t *testing.T) {
+       defer teardown()
+
+       // Prepare two test Keep volumes.
+       KeepVM = MakeTestVolumeManager(2)
+       defer KeepVM.Close()
+
+       ok := make(chan bool)
+       go func() {
+               for i := 0; i < maxBuffers+1; i += 1 {
+                       // Unauthenticated request, no server key
+                       // => OK (unsigned response)
+                       unsigned_locator := "/" + TEST_HASH
+                       response := IssueRequest(
+                               &RequestTester{
+                                       method:       "PUT",
+                                       uri:          unsigned_locator,
+                                       request_body: TEST_BLOCK,
+                               })
+                       ExpectStatusCode(t,
+                               "TestPutHandlerBufferleak", http.StatusOK, response)
+                       ExpectBody(t,
+                               "TestPutHandlerBufferleak",
+                               TEST_HASH_PUT_RESPONSE, response)
+               }
+               ok <- true
+       }()
+       select {
+       case <-time.After(20 * time.Second):
+               // If the buffer pool leaks, the test goroutine hangs.
+               t.Fatal("test did not finish, assuming pool leaked")
+       case <-ok:
+       }
+}
+
+// Invoke the GetBlockHandler a bunch of times to test for bufferpool resource
+// leak.
+func TestGetHandlerNoBufferleak(t *testing.T) {
+       defer teardown()
+
+       // Prepare two test Keep volumes. Our block is stored on the second volume.
+       KeepVM = MakeTestVolumeManager(2)
+       defer KeepVM.Close()
+
+       vols := KeepVM.AllWritable()
+       if err := vols[0].Put(TEST_HASH, TEST_BLOCK); err != nil {
+               t.Error(err)
+       }
+
+       ok := make(chan bool)
+       go func() {
+               for i := 0; i < maxBuffers+1; i += 1 {
+                       // Unauthenticated request, unsigned locator
+                       // => OK
+                       unsigned_locator := "/" + TEST_HASH
+                       response := IssueRequest(
+                               &RequestTester{
+                                       method: "GET",
+                                       uri:    unsigned_locator,
+                               })
+                       ExpectStatusCode(t,
+                               "Unauthenticated request, unsigned locator", http.StatusOK, response)
+                       ExpectBody(t,
+                               "Unauthenticated request, unsigned locator",
+                               string(TEST_BLOCK),
+                               response)
+               }
+               ok <- true
+       }()
+       select {
+       case <-time.After(20 * time.Second):
+               // If the buffer pool leaks, the test goroutine hangs.
+               t.Fatal("test did not finish, assuming pool leaked")
+       case <-ok:
+       }
+}