7121: Replace Get(loc,true) with CompareAndTouch(). Add Compare method to Volume...
[arvados.git] / services / keepstore / handler_test.go
index 6823ad0fc68f9b86b78a298648d204086aa3b95c..a9bf91e842f7178fff900e40fb0c2b75fa4e9fba 100644 (file)
@@ -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)
@@ -231,6 +231,10 @@ func TestPutAndDeleteSkipReadonlyVolumes(t *testing.T) {
                        uri:          "/" + TEST_HASH,
                        request_body: TEST_BLOCK,
                })
+       defer func(orig bool) {
+               never_delete = orig
+       }(never_delete)
+       never_delete = false
        IssueRequest(
                &RequestTester{
                        method:       "DELETE",
@@ -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(
@@ -448,6 +455,8 @@ func TestDeleteHandler(t *testing.T) {
        var user_token = "NOT DATA MANAGER TOKEN"
        data_manager_token = "DATA MANAGER TOKEN"
 
+       never_delete = false
+
        unauth_req := &RequestTester{
                method: "DELETE",
                uri:    "/" + TEST_HASH,
@@ -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)
        }
 }
@@ -803,3 +812,115 @@ func ExpectBody(
                        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:
+       }
+}