Merge branch '18076-stale-cached-users-handling' into main. Closes #18076
authorLucas Di Pentima <lucas.dipentima@curii.com>
Thu, 2 Sep 2021 20:07:28 +0000 (17:07 -0300)
committerLucas Di Pentima <lucas.dipentima@curii.com>
Thu, 2 Sep 2021 20:07:28 +0000 (17:07 -0300)
Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima@curii.com>

lib/controller/integration_test.go
services/api/app/controllers/arvados/v1/users_controller.rb

index 70c2e9e437248f8fadd504a84b5e5213a170342d..7cad7d5f7a040a7165810f629406461f1bb8c39a 100644 (file)
@@ -662,6 +662,87 @@ func (s *IntegrationSuite) TestIntermediateCluster(c *check.C) {
        }
 }
 
+// Test for bug #18076
+func (s *IntegrationSuite) TestStaleCachedUserRecord(c *check.C) {
+       rootctx1, _, _ := s.testClusters["z1111"].RootClients()
+       conn1 := s.testClusters["z1111"].Conn()
+       conn3 := s.testClusters["z3333"].Conn()
+
+       // Make sure LoginCluster is properly configured
+       for cls := range s.testClusters {
+               if cls == "z1111" || cls == "z3333" {
+                       c.Check(
+                               s.testClusters[cls].Config.Clusters[cls].Login.LoginCluster,
+                               check.Equals, "z1111",
+                               check.Commentf("incorrect LoginCluster config on cluster %q", cls))
+               }
+       }
+
+       // Create some users, request them on the federated cluster so they're cached.
+       var users []arvados.User
+       for userNr := 0; userNr < 2; userNr++ {
+               _, _, _, user := s.testClusters["z1111"].UserClients(
+                       rootctx1,
+                       c,
+                       conn1,
+                       fmt.Sprintf("user%d@example.com", userNr),
+                       true)
+               c.Assert(user.Username, check.Not(check.Equals), "")
+               users = append(users, user)
+
+               lst, err := conn3.UserList(rootctx1, arvados.ListOptions{Limit: -1})
+               c.Assert(err, check.Equals, nil)
+               userFound := false
+               for _, fedUser := range lst.Items {
+                       if fedUser.UUID == user.UUID {
+                               c.Assert(fedUser.Username, check.Equals, user.Username)
+                               userFound = true
+                               break
+                       }
+               }
+               c.Assert(userFound, check.Equals, true)
+       }
+
+       // Swap the usernames
+       _, err := conn1.UserUpdate(rootctx1, arvados.UpdateOptions{
+               UUID: users[0].UUID,
+               Attrs: map[string]interface{}{
+                       "username": "",
+               },
+       })
+       c.Assert(err, check.Equals, nil)
+       _, err = conn1.UserUpdate(rootctx1, arvados.UpdateOptions{
+               UUID: users[1].UUID,
+               Attrs: map[string]interface{}{
+                       "username": users[0].Username,
+               },
+       })
+       c.Assert(err, check.Equals, nil)
+       _, err = conn1.UserUpdate(rootctx1, arvados.UpdateOptions{
+               UUID: users[0].UUID,
+               Attrs: map[string]interface{}{
+                       "username": users[1].Username,
+               },
+       })
+       c.Assert(err, check.Equals, nil)
+
+       // Re-request the list on the federated cluster & check for updates
+       lst, err := conn3.UserList(rootctx1, arvados.ListOptions{Limit: -1})
+       c.Assert(err, check.Equals, nil)
+       var user0Found, user1Found bool
+       for _, user := range lst.Items {
+               if user.UUID == users[0].UUID {
+                       user0Found = true
+                       c.Assert(user.Username, check.Equals, users[1].Username)
+               } else if user.UUID == users[1].UUID {
+                       user1Found = true
+                       c.Assert(user.Username, check.Equals, users[0].Username)
+               }
+       }
+       c.Assert(user0Found, check.Equals, true)
+       c.Assert(user1Found, check.Equals, true)
+}
+
 // Test for bug #16263
 func (s *IntegrationSuite) TestListUsers(c *check.C) {
        rootctx1, _, _ := s.testClusters["z1111"].RootClients()
index f4d42edf6c1891e69e644d4a0d0c86cd952a0aa1..82594c1eb3fb0996c4055b1f7a65120dc42b2524 100644 (file)
@@ -29,7 +29,22 @@ class Arvados::V1::UsersController < ApplicationController
         end
       end
       if needupdate.length > 0
-        u.update_attributes!(needupdate)
+        begin
+          u.update_attributes!(needupdate)
+        rescue ActiveRecord::RecordInvalid
+          loginCluster = Rails.configuration.Login.LoginCluster
+          if u.uuid[0..4] == loginCluster && !needupdate[:username].nil?
+            local_user = User.find_by_username(needupdate[:username])
+            # A cached user record from the LoginCluster is stale, reset its username
+            # and retry the update operation.
+            if local_user.andand.uuid[0..4] == loginCluster && local_user.uuid != u.uuid
+              Rails.logger.warn("cached username '#{needupdate[:username]}' collision with user '#{local_user.uuid}' - resetting")
+              local_user.update_attributes!({username: nil})
+              retry
+            end
+          end
+          raise # Not the issue we're handling above
+        end
       end
       @objects << u
     end