17389: Adds X-Keep-Storage-Classes-Confirmed header to responses on success.
authorLucas Di Pentima <lucas.dipentima@curii.com>
Fri, 18 Jun 2021 11:50:07 +0000 (08:50 -0300)
committerLucas Di Pentima <lucas.dipentima@curii.com>
Fri, 18 Jun 2021 11:50:07 +0000 (08:50 -0300)
Also, don't treat partial success (ie: replicas written > 0) as success to
let the client decide what to do, as this would require a GoSDK API change on
Put methods, or the use of a special new method just for keepproxy that
returns fulfilled storage classes information.
In the case of partial successes from the client point of view, the only
thing that a client can do is retry the request with the same keepproxy, and
that would render the same result.

Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima@curii.com>

services/keepproxy/keepproxy.go
services/keepproxy/keepproxy_test.go

index 538a0612275ec029e448b810f45bcdd08fee74bb..fadb9585ac6a83c8ade8ea06e0e9f92097327740 100644 (file)
@@ -510,9 +510,9 @@ func (h *proxyHandler) Put(resp http.ResponseWriter, req *http.Request) {
        kc.Arvados = &arvclient
 
        // Check if the client specified the number of replicas
-       if req.Header.Get("X-Keep-Desired-Replicas") != "" {
+       if desiredReplicas := req.Header.Get(keepclient.XKeepDesiredReplicas); desiredReplicas != "" {
                var r int
-               _, err := fmt.Sscanf(req.Header.Get(keepclient.XKeepDesiredReplicas), "%d", &r)
+               _, err := fmt.Sscanf(desiredReplicas, "%d", &r)
                if err == nil {
                        kc.Want_replicas = r
                }
@@ -537,23 +537,25 @@ func (h *proxyHandler) Put(resp http.ResponseWriter, req *http.Request) {
        switch err.(type) {
        case nil:
                status = http.StatusOK
+               if len(kc.StorageClasses) > 0 {
+                       hdr := ""
+                       isFirst := true
+                       for _, sc := range kc.StorageClasses {
+                               if isFirst {
+                                       hdr = fmt.Sprintf("%s=%d", sc, wroteReplicas)
+                                       isFirst = false
+                               } else {
+                                       hdr += fmt.Sprintf(", %s=%d", sc, wroteReplicas)
+                               }
+                       }
+                       resp.Header().Set(keepclient.XKeepStorageClassesConfirmed, hdr)
+               }
                _, err = io.WriteString(resp, locatorOut)
-
        case keepclient.OversizeBlockError:
                // Too much data
                status = http.StatusRequestEntityTooLarge
-
        case keepclient.InsufficientReplicasError:
-               if wroteReplicas > 0 {
-                       // At least one write is considered success.  The
-                       // client can decide if getting less than the number of
-                       // replications it asked for is a fatal error.
-                       status = http.StatusOK
-                       _, err = io.WriteString(resp, locatorOut)
-               } else {
-                       status = http.StatusServiceUnavailable
-               }
-
+               status = http.StatusServiceUnavailable
        default:
                status = http.StatusBadGateway
        }
index 6a02ab9bd3a8374dd5c7fed5888edd5c9a4217f8..c569a05e74d970efa98248b7f9ca95ff14657fdb 100644 (file)
@@ -228,6 +228,28 @@ func (s *ServerRequiredSuite) TestStorageClassesHeader(c *C) {
        c.Check(hdr.Get("X-Keep-Storage-Classes"), Equals, "secure")
 }
 
+func (s *ServerRequiredSuite) TestStorageClassesConfirmedHeader(c *C) {
+       runProxy(c, false, false)
+       defer closeListener()
+
+       content := []byte("foo")
+       hash := fmt.Sprintf("%x", md5.Sum(content))
+       client := &http.Client{}
+
+       req, err := http.NewRequest("PUT",
+               fmt.Sprintf("http://%s/%s", listener.Addr().String(), hash),
+               bytes.NewReader(content))
+       c.Assert(err, IsNil)
+       req.Header.Set("X-Keep-Storage-Classes", "default")
+       req.Header.Set("Authorization", "OAuth2 "+arvadostest.ActiveToken)
+       req.Header.Set("Content-Type", "application/octet-stream")
+
+       resp, err := client.Do(req)
+       c.Assert(err, IsNil)
+       c.Assert(resp.StatusCode, Equals, http.StatusOK)
+       c.Assert(resp.Header.Get("X-Keep-Storage-Classes-Confirmed"), Equals, "default=2")
+}
+
 func (s *ServerRequiredSuite) TestDesiredReplicas(c *C) {
        kc := runProxy(c, false, false)
        defer closeListener()