18321: Check runtime constraints md5 with/without empty cuda section
[arvados.git] / services / api / app / models / arvados_model.rb
index 6a0a58f08d05e57f10d61b770a04bb6a3760c53d..374c6720f60a790970f9391a3ab14642357814f3 100644 (file)
@@ -386,11 +386,17 @@ class ArvadosModel < ApplicationRecord
 
       links_cond = ""
       if sql_table == "links"
-        # Match any permission link that gives one of the authorized
-        # users some permission _or_ gives anyone else permission to
-        # view one of the authorized users.
+        # 1) Match permission links incoming or outgoing on the
+        # user, i.e. granting permission on the user, or granting
+        # permission to the user.
+        #
+        # 2) Match permission links which grant permission on an
+        # object that this user can_manage.
+        #
         links_cond = "OR (#{sql_table}.link_class IN (:permission_link_classes) AND "+
-                       "(#{sql_table}.head_uuid IN (#{user_uuids_subquery}) OR #{sql_table}.tail_uuid IN (#{user_uuids_subquery})))"
+                     "   ((#{sql_table}.head_uuid IN (#{user_uuids_subquery}) OR #{sql_table}.tail_uuid IN (#{user_uuids_subquery})) OR " +
+                     "    #{sql_table}.head_uuid IN (SELECT target_uuid FROM #{PERMISSION_VIEW} "+
+                     "    WHERE user_uuid IN (#{user_uuids_subquery}) AND perm_level >= 3))) "
       end
 
       sql_conds = "(#{owner_check} #{direct_check} #{links_cond}) #{trashed_check.empty? ? "" : "AND"} #{trashed_check}"
@@ -408,7 +414,7 @@ class ArvadosModel < ApplicationRecord
 
     self.where(sql_conds,
                user_uuids: all_user_uuids.collect{|c| c["target_uuid"]},
-               permission_link_classes: ['permission', 'resources'])
+               permission_link_classes: ['permission'])
   end
 
   def save_with_unique_name!
@@ -695,7 +701,7 @@ class ArvadosModel < ApplicationRecord
     false
   end
 
-  def self.where_serialized(colname, value, md5: false)
+  def self.where_serialized(colname, value, md5: false, multivalue: false)
     colsql = colname.to_s
     if md5
       colsql = "md5(#{colsql})"
@@ -708,7 +714,16 @@ class ArvadosModel < ApplicationRecord
       sql = "#{colsql} IN (?)"
       sorted = deep_sort_hash(value)
     end
-    params = [sorted.to_yaml, SafeJSON.dump(sorted)]
+    params = []
+    if multivalue
+      sorted.each do |v|
+        params << v.to_yaml
+        params << SafeJSON.dump(v)
+      end
+    else
+      params << sorted.to_yaml
+      params << SafeJSON.dump(sorted)
+    end
     if md5
       params = params.map { |x| Digest::MD5.hexdigest(x) }
     end
@@ -855,6 +870,45 @@ class ArvadosModel < ApplicationRecord
     nil
   end
 
+  # Fill in implied zero/false values in database records that were
+  # created before #17014 made them explicit, and reset the Rails
+  # "changed" state so the record doesn't appear to have been modified
+  # after loading.
+  #
+  # Invoked by Container and ContainerRequest models as an after_find
+  # hook.
+  def fill_container_defaults_after_find
+    fill_container_defaults
+    set_attribute_was('runtime_constraints', runtime_constraints)
+    set_attribute_was('scheduling_parameters', scheduling_parameters)
+    clear_changes_information
+  end
+
+  # Fill in implied zero/false values. Invoked by ContainerRequest as
+  # a before_validation hook in order to (a) ensure every key has a
+  # value in the updated database record and (b) ensure the attribute
+  # whitelist doesn't reject a change from an explicit zero/false
+  # value in the database to an implicit zero/false value in an update
+  # request.
+  def fill_container_defaults
+    self.runtime_constraints = {
+      'API' => false,
+      'cuda' => {
+        'device_count' => 0,
+        'driver_version' => '',
+        'hardware_capability' => '',
+      },
+      'keep_cache_ram' => 0,
+      'ram' => 0,
+      'vcpus' => 0,
+    }.merge(attributes['runtime_constraints'] || {})
+    self.scheduling_parameters = {
+      'max_run_time' => 0,
+      'partitions' => [],
+      'preemptible' => false,
+    }.merge(attributes['scheduling_parameters'] || {})
+  end
+
   # ArvadosModel.find_by_uuid needs extra magic to allow it to return
   # an object in any class.
   def self.find_by_uuid uuid