Merge branch 'main' into 20831-user-table-locks
[arvados.git] / lib / controller / handler_test.go
index 4a7e1ad78acaf655748190a7bcc7f4a38d9aef10..0c50a6c4be59b85fc23140d93c4346e1f8e8a9ae 100644 (file)
@@ -131,13 +131,12 @@ func (s *HandlerSuite) TestDiscoveryDocCache(c *check.C) {
                return &wg
        }
        clearCache := func() {
-               for path := range s.handler.cache {
-                       s.handler.cache[path] = &cacheEnt{}
-               }
-       }
-       expireCache := func() {
                for _, ent := range s.handler.cache {
-                       ent.refreshAfter = time.Now()
+                       ent.refreshLock.Lock()
+                       ent.mtx.Lock()
+                       ent.body, ent.header, ent.refreshAfter = nil, nil, time.Time{}
+                       ent.mtx.Unlock()
+                       ent.refreshLock.Unlock()
                }
        }
        waitPendingUpdates := func() {
@@ -148,6 +147,18 @@ func (s *HandlerSuite) TestDiscoveryDocCache(c *check.C) {
                        defer ent.mtx.Unlock()
                }
        }
+       refreshNow := func() {
+               waitPendingUpdates()
+               for _, ent := range s.handler.cache {
+                       ent.refreshAfter = time.Now()
+               }
+       }
+       expireNow := func() {
+               waitPendingUpdates()
+               for _, ent := range s.handler.cache {
+                       ent.expireAfter = time.Now()
+               }
+       }
 
        // Easy path: first req fetches, subsequent reqs use cache.
        c.Check(countRailsReqs(), check.Equals, 0)
@@ -177,7 +188,7 @@ func (s *HandlerSuite) TestDiscoveryDocCache(c *check.C) {
 
        // Race after expiry: concurrent reqs return the cached data
        // but initiate a new fetch in the background.
-       expireCache()
+       refreshNow()
        holdReqs = make(chan struct{})
        wg = getDDConcurrently(5, http.StatusOK, check.Commentf("race after expiry"))
        reqsBefore = countRailsReqs()
@@ -188,11 +199,15 @@ func (s *HandlerSuite) TestDiscoveryDocCache(c *check.C) {
        }
        c.Check(countRailsReqs(), check.Equals, reqsBefore+1)
 
-       // Configure railsSpy to return an error when wantError==true.
-       var wantError bool
+       // Configure railsSpy to return an error or bad content
+       // depending on flags.
+       var wantError, wantBadContent bool
        s.railsSpy.Director = func(req *http.Request) {
                if wantError {
                        req.Method = "MAKE-COFFEE"
+               } else if wantBadContent {
+                       req.URL.Path = "/_health/ping"
+                       req.Header.Set("Authorization", "Bearer "+arvadostest.ManagementToken)
                }
        }
 
@@ -200,7 +215,7 @@ func (s *HandlerSuite) TestDiscoveryDocCache(c *check.C) {
        // make an upstream attempt for each incoming request because
        // we have nothing better to return
        clearCache()
-       wantError = true
+       wantError, wantBadContent = true, false
        reqsBefore = countRailsReqs()
        holdReqs = make(chan struct{})
        wg = getDDConcurrently(5, http.StatusBadGateway, check.Commentf("error at startup"))
@@ -208,39 +223,75 @@ func (s *HandlerSuite) TestDiscoveryDocCache(c *check.C) {
        wg.Wait()
        c.Check(countRailsReqs(), check.Equals, reqsBefore+5)
 
+       // Response status is OK but body is not a discovery document
+       wantError, wantBadContent = false, true
+       reqsBefore = countRailsReqs()
+       c.Check(getDD(), check.Equals, http.StatusBadGateway)
+       c.Check(countRailsReqs(), check.Equals, reqsBefore+1)
+
        // Error condition clears => caller gets OK, cache is warmed
        // up
-       wantError = false
+       wantError, wantBadContent = false, false
        reqsBefore = countRailsReqs()
        getDDConcurrently(5, http.StatusOK, check.Commentf("success after errors at startup")).Wait()
        c.Check(countRailsReqs(), check.Equals, reqsBefore+1)
 
        // Error with warm cache => caller gets OK (with no attempt to
        // re-fetch)
-       wantError = true
+       wantError, wantBadContent = true, false
        reqsBefore = countRailsReqs()
        getDDConcurrently(5, http.StatusOK, check.Commentf("error with warm cache")).Wait()
        c.Check(countRailsReqs(), check.Equals, reqsBefore)
-       expireCache()
 
-       // Error with expired cache => caller gets OK with stale data
+       // Error with stale cache => caller gets OK with stale data
        // while the re-fetch is attempted in the background
+       refreshNow()
+       wantError, wantBadContent = true, false
        reqsBefore = countRailsReqs()
        holdReqs = make(chan struct{})
-       getDDConcurrently(5, http.StatusOK, check.Commentf("error with expired cache")).Wait()
+       getDDConcurrently(5, http.StatusOK, check.Commentf("error with stale cache")).Wait()
        close(holdReqs)
        // Only one attempt to re-fetch (holdReqs ensured the first
        // update took long enough for the last incoming request to
        // arrive)
        c.Check(countRailsReqs(), check.Equals, reqsBefore+1)
 
-       waitPendingUpdates()
-       expireCache()
-       wantError = false
+       refreshNow()
+       wantError, wantBadContent = false, false
        reqsBefore = countRailsReqs()
        holdReqs = make(chan struct{})
        getDDConcurrently(5, http.StatusOK, check.Commentf("refresh cache after error condition clears")).Wait()
        close(holdReqs)
+       waitPendingUpdates()
+       c.Check(countRailsReqs(), check.Equals, reqsBefore+1)
+
+       // Make sure expireAfter is getting set
+       waitPendingUpdates()
+       exp := s.handler.cache["/discovery/v1/apis/arvados/v1/rest"].expireAfter.Sub(time.Now())
+       c.Check(exp > cacheTTL, check.Equals, true)
+       c.Check(exp < cacheExpire, check.Equals, true)
+
+       // After the cache *expires* it behaves as if uninitialized:
+       // each incoming request does a new upstream request until one
+       // succeeds.
+       //
+       // First check failure after expiry:
+       expireNow()
+       wantError, wantBadContent = true, false
+       reqsBefore = countRailsReqs()
+       holdReqs = make(chan struct{})
+       wg = getDDConcurrently(5, http.StatusBadGateway, check.Commentf("error after expiry"))
+       close(holdReqs)
+       wg.Wait()
+       c.Check(countRailsReqs(), check.Equals, reqsBefore+5)
+
+       // Success after expiry:
+       wantError, wantBadContent = false, false
+       reqsBefore = countRailsReqs()
+       holdReqs = make(chan struct{})
+       wg = getDDConcurrently(5, http.StatusOK, check.Commentf("success after expiry"))
+       close(holdReqs)
+       wg.Wait()
        c.Check(countRailsReqs(), check.Equals, reqsBefore+1)
 }
 
@@ -588,7 +639,7 @@ func (s *HandlerSuite) TestGetObjects(c *check.C) {
        testCases := map[string]map[string]bool{
                "api_clients/" + arvadostest.TrustedWorkbenchAPIClientUUID:     nil,
                "api_client_authorizations/" + auth.UUID:                       {"href": true, "modified_by_client_uuid": true, "modified_by_user_uuid": true},
-               "authorized_keys/" + arvadostest.AdminAuthorizedKeysUUID:       nil,
+               "authorized_keys/" + arvadostest.AdminAuthorizedKeysUUID:       {"href": true},
                "collections/" + arvadostest.CollectionWithUniqueWordsUUID:     {"href": true},
                "containers/" + arvadostest.RunningContainerUUID:               nil,
                "container_requests/" + arvadostest.QueuedContainerRequestUUID: nil,