Merge branch '21666-provision-test-improvement'
[arvados.git] / services / api / app / models / link.rb
index e8ac51f9988633a9fc649c3ffcf32aac23b62747..2eb6b88a0c864a5ea33f8dbe7a5a4c9fe4cd2a1f 100644 (file)
@@ -15,12 +15,14 @@ class Link < ArvadosModel
   validate :permission_to_attach_to_objects
   before_update :restrict_alter_permissions
   before_update :apply_max_overlapping_permissions
+  before_create :apply_max_overlapping_permissions
   after_update :delete_overlapping_permissions
-  after_update :call_update_permissions
-  after_create :call_update_permissions
+  after_update :call_update_permissions, :if => Proc.new { @need_update_permissions }
+  after_create :call_update_permissions, :if => Proc.new { @need_update_permissions }
   before_destroy :clear_permissions
   after_destroy :delete_overlapping_permissions
   after_destroy :check_permissions
+  before_save :check_need_update_permissions
 
   api_accessible :user, extend: :common do |t|
     t.add :tail_uuid
@@ -40,10 +42,12 @@ class Link < ArvadosModel
 
   def apply_max_overlapping_permissions
     return if self.link_class != 'permission' || !PermLevel[self.name]
-    Link.where(link_class: 'permission',
-               tail_uuid: self.tail_uuid,
-               head_uuid: self.head_uuid,
-               name: PermLevel.keys).
+    Link.
+      lock. # select ... for update
+      where(link_class: 'permission',
+            tail_uuid: self.tail_uuid,
+            head_uuid: self.head_uuid,
+            name: PermLevel.keys).
       where('uuid <> ?', self.uuid).each do |other|
       if PermLevel[other.name] > PermLevel[self.name]
         self.name = other.name
@@ -53,23 +57,32 @@ class Link < ArvadosModel
 
   def delete_overlapping_permissions
     return if self.link_class != 'permission'
+    redundant = nil
     if PermLevel[self.name]
-      Link.where(link_class: 'permission',
-                 tail_uuid: self.tail_uuid,
-                 head_uuid: self.head_uuid,
-                 name: PermLevel.keys).
-        where('uuid <> ?', self.uuid).
-        delete_all
+      redundant = Link.
+                    lock. # select ... for update
+                    where(link_class: 'permission',
+                          tail_uuid: self.tail_uuid,
+                          head_uuid: self.head_uuid,
+                          name: PermLevel.keys).
+                    where('uuid <> ?', self.uuid)
     elsif self.name == 'can_login' &&
           self.properties.respond_to?(:has_key?) &&
           self.properties.has_key?('username')
-      Link.where(link_class: 'permission',
-                 tail_uuid: self.tail_uuid,
-                 head_uuid: self.head_uuid,
-                 name: 'can_login').
-        where('properties @> ?', SafeJSON.dump({'username' => self.properties['username']})).
-        where('uuid <> ?', self.uuid).
-        delete_all
+      redundant = Link.
+                    lock. # select ... for update
+                    where(link_class: 'permission',
+                          tail_uuid: self.tail_uuid,
+                          head_uuid: self.head_uuid,
+                          name: 'can_login').
+                    where('properties @> ?', SafeJSON.dump({'username' => self.properties['username']})).
+                    where('uuid <> ?', self.uuid)
+    end
+    if redundant
+      redundant.each do |link|
+        link.clear_permissions
+      end
+      redundant.delete_all
     end
   end
 
@@ -177,11 +190,13 @@ class Link < ArvadosModel
     'can_manage' => 3,
   }
 
+  def check_need_update_permissions
+    @need_update_permissions = self.link_class == 'permission' && (name != name_was || new_record?)
+  end
+
   def call_update_permissions
-    if self.link_class == 'permission'
       update_permissions tail_uuid, head_uuid, PERM_LEVEL[name], self.uuid
       current_user.forget_cached_group_perms
-    end
   end
 
   def clear_permissions