3036: API server tests pass
[arvados.git] / services / api / app / models / collection.rb
index a50c47b6a30fd778a77a5717df2b41cb8d31d526..2bf781013bce167ae1a376740ad100cb1c73abf9 100644 (file)
@@ -3,15 +3,50 @@ class Collection < ArvadosModel
   include KindAndEtag
   include CommonApiTemplate
 
+  before_validation :set_portable_data_hash
+  validate :ensure_hash_matches_manifest_text
+
   api_accessible :user, extend: :common do |t|
     t.add :data_size
     t.add :files
+    t.add :name
+    t.add :description
+    t.add :properties
+    t.add :portable_data_hash
   end
 
   api_accessible :with_data, extend: :user do |t|
     t.add :manifest_text
   end
 
+  def set_portable_data_hash
+    if (self.portable_data_hash.nil? or (self.portable_data_hash == "") or (manifest_text_changed? and !portable_data_hash_changed?))
+      self.portable_data_hash = "#{Digest::MD5.hexdigest(manifest_text)}+#{manifest_text.length}"
+    elsif portable_data_hash_changed?
+      begin
+        loc = Locator.parse!(self.portable_data_hash)
+        loc.strip_hints!
+        self.portable_data_hash = loc.to_s
+      rescue ArgumentError => e
+        errors.add(:portable_data_hash, "#{e}")
+        return false
+      end
+    end
+    true
+  end
+
+  def ensure_hash_matches_manifest_text
+    if manifest_text_changed? or portable_data_hash_changed?
+      computed_hash = "#{Digest::MD5.hexdigest(manifest_text)}+#{manifest_text.length}"
+      unless computed_hash == portable_data_hash
+        logger.debug "(computed) '#{computed_hash}' != '#{portable_data_hash}' (provided)"
+        errors.add(:portable_data_hash, "does not match hash of manifest_text")
+        return false
+      end
+    end
+    true
+  end
+
   def redundancy_status
     if redundancy_confirmed_as.nil?
       'unconfirmed'
@@ -28,39 +63,6 @@ class Collection < ArvadosModel
     end
   end
 
-  def assign_uuid
-    if not self.manifest_text
-      errors.add :manifest_text, 'not supplied'
-      return false
-    end
-    expect_uuid = Digest::MD5.hexdigest(self.manifest_text)
-    if self.uuid
-      self.uuid.gsub! /\+.*/, ''
-      if self.uuid != expect_uuid
-        errors.add :uuid, 'must match checksum of manifest_text'
-        return false
-      end
-    else
-      self.uuid = expect_uuid
-    end
-    self.uuid.gsub! /$/, '+' + self.manifest_text.length.to_s
-    true
-  end
-
-  # TODO (#3036/tom) replace above assign_uuid method with below assign_uuid and self.generate_uuid
-  # def assign_uuid
-  #   # Even admins cannot assign collection uuids.
-  #   self.uuid = self.class.generate_uuid
-  # end
-  # def self.generate_uuid
-  #   # The last 10 characters of a collection uuid are the last 10
-  #   # characters of the base-36 SHA256 digest of manifest_text.
-  #   [Server::Application.config.uuid_prefix,
-  #    self.uuid_prefix,
-  #    rand(2**256).to_s(36)[-5..-1] + Digest::SHA256.hexdigest(self.manifest_text).to_i(16).to_s(36)[-10..-1],
-  #   ].join '-'
-  # end
-
   def data_size
     inspect_manifest_text if @data_size.nil? or manifest_text_changed?
     @data_size
@@ -131,10 +133,6 @@ class Collection < ArvadosModel
     end
   end
 
-  def self.uuid_like_pattern
-    "________________________________+%"
-  end
-
   def self.normalize_uuid uuid
     hash_part = nil
     size_part = nil
@@ -159,12 +157,20 @@ class Collection < ArvadosModel
       joins("JOIN collections ON links.head_uuid = collections.uuid").
       order("links.created_at DESC")
 
-    # If the search term is a Collection locator with an associated
-    # Docker image hash link, return that Collection.
-    coll_matches = base_search.
-      where(link_class: "docker_image_hash", collections: {uuid: search_term})
-    if match = coll_matches.first
-      return [match.head_uuid]
+    # If the search term is a Collection locator that contains one file
+    # that looks like a Docker image, return it.
+    if loc = Locator.parse(search_term)
+      loc.strip_hints!
+      coll_match = readable_by(*readers).where(portable_data_hash: loc.to_s).limit(1).first
+      if coll_match and (coll_match.files.size == 1) and
+          (coll_match.files[0][1] =~ /^[0-9A-Fa-f]{64}\.tar$/)
+        return [coll_match.uuid]
+      end
+    end
+
+    if search_tag.nil? and (n = search_term.index(":"))
+      search_tag = search_term[n+1..-1]
+      search_term = search_term[0..n-1]
     end
 
     # Find Collections with matching Docker image repository+tag pairs.
@@ -175,7 +181,7 @@ class Collection < ArvadosModel
     # If that didn't work, find Collections with matching Docker image hashes.
     if matches.empty?
       matches = base_search.
-        where("link_class = ? and name LIKE ?",
+        where("link_class = ? and links.name LIKE ?",
               "docker_image_hash", "#{search_term}%")
     end
 
@@ -184,7 +190,8 @@ class Collection < ArvadosModel
     # anything without; then we use the link's created_at as a tiebreaker.
     uuid_timestamps = {}
     matches.find_each do |link|
-      uuid_timestamps[link.head_uuid] =
+      c = Collection.find_by_uuid(link.head_uuid)
+      uuid_timestamps[c.uuid] =
         [(-link.properties["image_timestamp"].to_datetime.to_i rescue 0),
          -link.created_at.to_i]
     end