Merge branch 'master' into 3036-collection-uuids
authorPeter Amstutz <peter.amstutz@curoverse.com>
Tue, 19 Aug 2014 15:36:15 +0000 (11:36 -0400)
committerPeter Amstutz <peter.amstutz@curoverse.com>
Tue, 19 Aug 2014 15:36:15 +0000 (11:36 -0400)
Conflicts:
services/api/app/controllers/arvados/v1/collections_controller.rb
services/api/app/models/collection.rb

36 files changed:
sdk/python/arvados/commands/keepdocker.py
services/api/app/controllers/application_controller.rb
services/api/app/controllers/arvados/v1/collections_controller.rb
services/api/app/controllers/arvados/v1/groups_controller.rb
services/api/app/controllers/arvados/v1/jobs_controller.rb
services/api/app/helpers/collections_helper.rb
services/api/app/models/arvados_model.rb
services/api/app/models/collection.rb
services/api/app/models/group.rb
services/api/app/models/job.rb
services/api/app/models/link.rb
services/api/app/models/locator.rb
services/api/app/models/pipeline_instance.rb
services/api/app/views/api_client_authorizations/index.html.erb [deleted file]
services/api/app/views/collections/index.html.erb [deleted file]
services/api/app/views/nodes/index.html.erb [deleted file]
services/api/app/views/pipeline_instances/index.html.erb [deleted file]
services/api/db/migrate/20140811184643_collection_use_regular_uuids.rb [new file with mode: 0644]
services/api/db/migrate/20140815171049_add_name_description_columns.rb [new file with mode: 0644]
services/api/db/migrate/20140817035914_add_unique_name_constraints.rb [new file with mode: 0644]
services/api/db/migrate/20140818125735_add_not_null_constraint_to_group_name.rb [new file with mode: 0644]
services/api/db/structure.sql
services/api/lib/has_uuid.rb
services/api/test/fixtures/collections.yml
services/api/test/fixtures/groups.yml
services/api/test/fixtures/links.yml
services/api/test/fixtures/logs.yml
services/api/test/functional/arvados/v1/collections_controller_test.rb
services/api/test/functional/arvados/v1/groups_controller_test.rb
services/api/test/functional/arvados/v1/links_controller_test.rb
services/api/test/integration/collections_api_test.rb
services/api/test/unit/arvados_model_test.rb
services/api/test/unit/group_test.rb
services/api/test/unit/job_test.rb
services/api/test/unit/link_test.rb
services/api/test/unit/owner_test.rb

index 236d9fc0043c3438401d496116cd33e7ef7f8da8..c6bfc868d3e96c27408164eca51a5289525d12a8 100644 (file)
@@ -203,7 +203,6 @@ def main(arguments=None):
 
     make_link('docker_image_hash', image_hash, **link_base)
     if not image_hash.startswith(args.image.lower()):
-        make_link('docker_image_repository', args.image, **link_base)
         make_link('docker_image_repo+tag', '{}:{}'.format(args.image, args.tag),
                   **link_base)
 
index d3b5c6b14725d9549deb1423ac443757366b5abe..c9c78794d62ca652e756e087f1840b875eec6d1e 100644 (file)
@@ -104,7 +104,9 @@ class ApplicationController < ActionController::Base
     if e.respond_to? :backtrace and e.backtrace
       logger.error e.backtrace.collect { |x| x + "\n" }.join('')
     end
-    if @object and @object.errors and @object.errors.full_messages and not @object.errors.full_messages.empty?
+    if (@object and @object.respond_to? :errors and
+        @object.errors and @object.errors.full_messages and
+        not @object.errors.full_messages.empty?)
       errors = @object.errors.full_messages
       logger.error errors.inspect
     else
index b65fa5b962ab76b9c26e52a0443cb4586f90a043..6b2bd64be93d1211100fd44d5767b16ac5631ea5 100644 (file)
@@ -1,14 +1,8 @@
 class Arvados::V1::CollectionsController < ApplicationController
   def create
-    # Collections are owned by system_user. Creating a collection has
-    # two effects: The collection is added if it doesn't already
-    # exist, and a "permission" Link is added (if one doesn't already
-    # exist) giving the current user (or specified owner_uuid)
-    # permission to read it.
-    owner_uuid = resource_attrs.delete(:owner_uuid) || current_user.uuid
-    unless current_user.can? write: owner_uuid
-      logger.warn "User #{current_user.andand.uuid} tried to set collection owner_uuid to #{owner_uuid}"
-      raise ArvadosModel::PermissionDeniedError
+    if !resource_attrs[:manifest_text]
+      return send_error("'manifest_text' attribute must be specified",
+                        status: :unprocessable_entity)
     end
 
     # Check permissions on the collection manifest.
@@ -49,38 +43,24 @@ class Arvados::V1::CollectionsController < ApplicationController
       loc.without_signature.to_s
     end
 
-    # Save the collection with the stripped manifest.
-    act_as_system_user do
-      @object = model_class.new resource_attrs.reject { |k,v| k == :owner_uuid }
-      begin
-        @object.save!
-      rescue ActiveRecord::RecordNotUnique
-        logger.debug resource_attrs.inspect
-        if @object.manifest_text and @object.uuid
-          @existing_object = model_class.
-            where('uuid=? and manifest_text=?',
-                  @object.uuid,
-                  @object.manifest_text).
-            first
-          @object = @existing_object || @object
-        end
-      end
-      if @object
-        link_attrs = {
-          owner_uuid: owner_uuid,
-          link_class: 'permission',
-          name: 'can_read',
-          head_uuid: @object.uuid,
-          tail_uuid: owner_uuid
+    super
+  end
+
+  def find_object_by_uuid
+    if loc = Locator.parse(params[:id])
+      loc.strip_hints!
+      if c = Collection.readable_by(*@read_users).where({ portable_data_hash: loc.to_s }).limit(1).first
+        @object = {
+          portable_data_hash: c.portable_data_hash,
+          manifest_text: c.manifest_text,
+          files: c.files,
+          data_size: c.data_size
         }
-        ActiveRecord::Base.transaction do
-          if Link.where(link_attrs).empty?
-            Link.create! link_attrs
-          end
-        end
       end
+    else
+      super
     end
-    show
+    true
   end
 
   def show
@@ -93,15 +73,6 @@ class Arvados::V1::CollectionsController < ApplicationController
     super
   end
 
-  def collection_uuid(uuid)
-    m = /([a-f0-9]{32}(\+[0-9]+)?)(\+.*)?/.match(uuid)
-    if m
-      m[1]
-    else
-      nil
-    end
-  end
-
   def script_param_edges(visited, sp)
     case sp
     when Hash
@@ -114,108 +85,72 @@ class Arvados::V1::CollectionsController < ApplicationController
       end
     when String
       return if sp.empty?
-      m = collection_uuid(sp)
-      if m
-        generate_provenance_edges(visited, m)
+      if loc = Locator.parse(sp)
+        search_edges(visited, loc.to_s, :search_up)
       end
     end
   end
 
-  def generate_provenance_edges(visited, uuid)
-    m = collection_uuid(uuid)
-    uuid = m if m
+  def search_edges(visited, uuid, direction)
+    if uuid.nil? or uuid.empty? or visited[uuid]
+      return
+    end
 
-    if not uuid or uuid.empty? or visited[uuid]
-      return ""
+    if loc = Locator.parse(uuid)
+      loc.strip_hints!
+      return if visited[loc.to_s]
     end
 
     logger.debug "visiting #{uuid}"
 
-    if m
-      # uuid is a collection
-      Collection.readable_by(current_user).where(uuid: uuid).each do |c|
-        visited[uuid] = c.as_api_response
-        visited[uuid][:files] = []
-        c.files.each do |f|
-          visited[uuid][:files] << f
-        end
-      end
-
-      Job.readable_by(current_user).where(output: uuid).each do |job|
-        generate_provenance_edges(visited, job.uuid)
+    if loc
+      # uuid is a portable_data_hash
+      if c = Collection.readable_by(*@read_users).where(portable_data_hash: loc.to_s).limit(1).first
+        visited[loc.to_s] = {
+          portable_data_hash: c.portable_data_hash,
+          files: c.files,
+          data_size: c.data_size
+        }
       end
 
-      Job.readable_by(current_user).where(log: uuid).each do |job|
-        generate_provenance_edges(visited, job.uuid)
-      end
+      if direction == :search_up
+        # Search upstream for jobs where this locator is the output of some job
+        Job.readable_by(*@read_users).where(output: loc.to_s).each do |job|
+          search_edges(visited, job.uuid, :search_up)
+        end
 
-    else
-      # uuid is something else
-      rsc = ArvadosModel::resource_class_for_uuid uuid
-      if rsc == Job
-        Job.readable_by(current_user).where(uuid: uuid).each do |job|
-          visited[uuid] = job.as_api_response
-          script_param_edges(visited, job.script_parameters)
+        Job.readable_by(*@read_users).where(log: loc.to_s).each do |job|
+          search_edges(visited, job.uuid, :search_up)
         end
-      elsif rsc != nil
-        rsc.where(uuid: uuid).each do |r|
-          visited[uuid] = r.as_api_response
+      elsif direction == :search_down
+        if loc.to_s == "d41d8cd98f00b204e9800998ecf8427e+0"
+          # Special case, don't follow the empty collection.
+          return
         end
-      end
-    end
-
-    Link.readable_by(current_user).
-      where(head_uuid: uuid, link_class: "provenance").
-      each do |link|
-      visited[link.uuid] = link.as_api_response
-      generate_provenance_edges(visited, link.tail_uuid)
-    end
-
-    #puts "finished #{uuid}"
-  end
-
-  def provenance
-    visited = {}
-    generate_provenance_edges(visited, @object[:uuid])
-    render json: visited
-  end
-
-  def generate_used_by_edges(visited, uuid)
-    m = collection_uuid(uuid)
-    uuid = m if m
 
-    if not uuid or uuid.empty? or visited[uuid]
-      return ""
-    end
-
-    logger.debug "visiting #{uuid}"
-
-    if m
-      # uuid is a collection
-      Collection.readable_by(current_user).where(uuid: uuid).each do |c|
-        visited[uuid] = c.as_api_response
-        visited[uuid][:files] = []
-        c.files.each do |f|
-          visited[uuid][:files] << f
+        # Search downstream for jobs where this locator is in script_parameters
+        Job.readable_by(*@read_users).where(["jobs.script_parameters like ?", "%#{loc.to_s}%"]).each do |job|
+          search_edges(visited, job.uuid, :search_down)
         end
       end
-
-      if uuid == "d41d8cd98f00b204e9800998ecf8427e+0"
-        # special case for empty collection
-        return
-      end
-
-      Job.readable_by(current_user).where(["jobs.script_parameters like ?", "%#{uuid}%"]).each do |job|
-        generate_used_by_edges(visited, job.uuid)
-      end
-
     else
-      # uuid is something else
+      # uuid is a regular Arvados UUID
       rsc = ArvadosModel::resource_class_for_uuid uuid
       if rsc == Job
-        Job.readable_by(current_user).where(uuid: uuid).each do |job|
+        Job.readable_by(*@read_users).where(uuid: uuid).each do |job|
           visited[uuid] = job.as_api_response
-          generate_used_by_edges(visited, job.output)
+          if direction == :search_up
+            # Follow upstream collections referenced in the script parameters
+            script_param_edges(visited, job.script_parameters)
+          elsif direction == :search_down
+            # Follow downstream job output
+            search_edges(visited, job.output, direction)
+          end
+        end
+      elsif rsc == Collection
+        if c = Collection.readable_by(*@read_users).where(uuid: uuid).limit(1).first
+          search_edges(visited, c.portable_data_hash, direction)
+          visited[c.portable_data_hash] = c.as_api_response
         end
       elsif rsc != nil
         rsc.where(uuid: uuid).each do |r|
@@ -224,19 +159,34 @@ class Arvados::V1::CollectionsController < ApplicationController
       end
     end
 
-    Link.readable_by(current_user).
-      where(tail_uuid: uuid, link_class: "provenance").
-      each do |link|
-      visited[link.uuid] = link.as_api_response
-      generate_used_by_edges(visited, link.head_uuid)
+    if direction == :search_up
+      # Search for provenance links pointing to the current uuid
+      Link.readable_by(*@read_users).
+        where(head_uuid: uuid, link_class: "provenance").
+        each do |link|
+        visited[link.uuid] = link.as_api_response
+        search_edges(visited, link.tail_uuid, direction)
+      end
+    elsif direction == :search_down
+      # Search for provenance links emanating from the current uuid
+      Link.readable_by(current_user).
+        where(tail_uuid: uuid, link_class: "provenance").
+        each do |link|
+        visited[link.uuid] = link.as_api_response
+        search_edges(visited, link.head_uuid, direction)
+      end
     end
+  end
 
-    #puts "finished #{uuid}"
+  def provenance
+    visited = {}
+    search_edges(visited, @object[:uuid] || @object[:portable_data_hash], :search_up)
+    render json: visited
   end
 
   def used_by
     visited = {}
-    generate_used_by_edges(visited, @object[:uuid])
+    search_edges(visited, @object[:uuid] || @object[:portable_data_hash], :search_down)
     render json: visited
   end
 
index 17be40a57ee449a6a0902409ed1aa9ae7853be8a..7ee4e555ae88ce405308cb9e83677f4f0689667b 100644 (file)
@@ -82,12 +82,6 @@ class Arvados::V1::GroupsController < ApplicationController
         cond_params = []
         conds << "#{klass.table_name}.owner_uuid = ?"
         cond_params << opts[:owner_uuid]
-        if opts[:include_linked]
-          haslink = "#{klass.table_name}.uuid IN (SELECT head_uuid FROM links WHERE link_class=#{klass.sanitize 'name'}"
-          haslink += " AND links.tail_uuid=#{klass.sanitize opts[:owner_uuid]}"
-          haslink += ")"
-          conds << haslink
-        end
         if conds.any?
           cond_sql = '(' + conds.join(') OR (') + ')'
           @objects = @objects.where(cond_sql, *cond_params)
index 69778293a4096138cba2607599a2c9d47efafe6b..5045e078c358d3bd1352721c425063d92f862047 100644 (file)
@@ -71,7 +71,7 @@ class Arvados::V1::JobsController < ApplicationController
             # We'll use this if we don't find a job that has completed
             incomplete_job ||= j
           else
-            if Collection.readable_by(current_user).find_by_uuid(j.output)
+            if Collection.readable_by(current_user).find_by_portable_data_hash(j.output)
               # Record the first job in the list
               if !@object
                 @object = j
@@ -214,7 +214,9 @@ class Arvados::V1::JobsController < ApplicationController
         search_list = filter[2].is_a?(Enumerable) ? filter[2] : [filter[2]]
         filter[2] = search_list.flat_map do |search_term|
           image_search, image_tag = search_term.split(':', 2)
-          Collection.uuids_for_docker_image(image_search, image_tag, @read_users)
+          Collection.uuids_for_docker_image(image_search, image_tag, @read_users).map do |uuid|
+            Collection.find_by_uuid(uuid).portable_data_hash
+          end
         end
         true
       else
index 30179854a2ae434d7f20f0a6613b2b1d90ab5e6e..020bdbe020f58211b55f6f83478ec34191392bc8 100644 (file)
@@ -1,2 +1,10 @@
 module CollectionsHelper
+  def stripped_portable_data_hash(uuid)
+    m = /([a-f0-9]{32}(\+[0-9]+)?)(\+.*)?/.match(uuid)
+    if m
+      m[1]
+    else
+      nil
+    end
+  end
 end
index 539f69d6cabd5afac1e0d3e4b19a8337bb231897..f95a35e739d470dfa57e0d753ef1ea864ac9a193 100644 (file)
@@ -21,8 +21,8 @@ class ArvadosModel < ActiveRecord::Base
   after_update :log_update
   after_destroy :log_destroy
   after_find :convert_serialized_symbols_to_strings
+  before_validation :normalize_collection_uuids
   validate :ensure_serialized_attribute_type
-  validate :normalize_collection_uuids
   validate :ensure_valid_uuids
 
   # Note: This only returns permission links. It does not account for
@@ -168,12 +168,6 @@ class ArvadosModel < ActiveRecord::Base
         sql_params += [uuid_list]
       end
 
-      if sql_table == "collections" and users_list.any?
-        # There is a 'name' link going from a readable group to the collection.
-        name_links = "(SELECT head_uuid FROM links WHERE link_class='name' AND tail_uuid IN (#{sanitized_uuid_list}))"
-        sql_conds += ["#{sql_table}.uuid IN #{name_links}"]
-      end
-
       # Link head points to this row, or to the owner of this row (the thing to be read)
       #
       # Link tail originates from this user, or a group that is readable by this
@@ -204,7 +198,7 @@ class ArvadosModel < ActiveRecord::Base
     if new_record? or owner_uuid_changed?
       uuid_in_path = {owner_uuid => true, uuid => true}
       x = owner_uuid
-      while (owner_class = self.class.resource_class_for_uuid(x)) != User
+      while (owner_class = ArvadosModel::resource_class_for_uuid(x)) != User
         begin
           if x == uuid
             # Test for cycles with the new version, not the DB contents
@@ -234,12 +228,29 @@ class ArvadosModel < ActiveRecord::Base
 
   def ensure_owner_uuid_is_permitted
     raise PermissionDeniedError if !current_user
+
     if new_record? and respond_to? :owner_uuid=
       self.owner_uuid ||= current_user.uuid
     end
-    # Verify permission to write to old owner (unless owner_uuid was
-    # nil -- or hasn't changed, in which case the following
-    # "permission to write to new owner" block will take care of us)
+
+    if self.owner_uuid.nil?
+      errors.add :owner_uuid, "cannot be nil"
+      raise PermissionDeniedError
+    end
+
+    rsc_class = ArvadosModel::resource_class_for_uuid owner_uuid
+    unless rsc_class == User or rsc_class == Group
+      errors.add :owner_uuid, "can only be set to User or Group"
+      raise PermissionDeniedError
+    end
+
+    # Verify "write" permission on old owner
+    # default fail unless one of:
+    # owner_uuid did not change
+    # previous owner_uuid is nil
+    # current user is the old owner
+    # current user is this object
+    # current user can_write old owner
     unless !owner_uuid_changed? or
         owner_uuid_was.nil? or
         current_user.uuid == self.owner_uuid_was or
@@ -249,12 +260,18 @@ class ArvadosModel < ActiveRecord::Base
       errors.add :owner_uuid, "cannot be changed without write permission on old owner"
       raise PermissionDeniedError
     end
-    # Verify permission to write to new owner
+
+    # Verify "write" permission on new owner
+    # default fail unless one of:
+    # current_user is this object
+    # current user can_write new owner
     unless current_user == self or current_user.can? write: owner_uuid
       logger.warn "User #{current_user.uuid} tried to modify #{self.class.to_s} #{uuid} but does not have permission to write new owner_uuid #{owner_uuid}"
       errors.add :owner_uuid, "cannot be changed without write permission on new owner"
       raise PermissionDeniedError
     end
+
+    true
   end
 
   def ensure_permission_to_save
@@ -398,8 +415,6 @@ class ArvadosModel < ActiveRecord::Base
     end
   end
 
-  @@UUID_REGEX = /^[0-9a-z]{5}-([0-9a-z]{5})-[0-9a-z]{15}$/
-
   @@prefixes_hash = nil
   def self.uuid_prefixes
     unless @@prefixes_hash
@@ -418,7 +433,7 @@ class ArvadosModel < ActiveRecord::Base
   end
 
   def ensure_valid_uuids
-    specials = [system_user_uuid, 'd41d8cd98f00b204e9800998ecf8427e+0']
+    specials = [system_user_uuid]
 
     foreign_key_attributes.each do |attr|
       if new_record? or send (attr + "_changed?")
@@ -464,13 +479,10 @@ class ArvadosModel < ActiveRecord::Base
     unless uuid.is_a? String
       return nil
     end
-    if uuid.match /^[0-9a-f]{32}(\+[^,]+)*(,[0-9a-f]{32}(\+[^,]+)*)*$/
-      return Collection
-    end
     resource_class = nil
 
     Rails.application.eager_load!
-    uuid.match @@UUID_REGEX do |re|
+    uuid.match HasUuid::UUID_REGEX do |re|
       return uuid_prefixes[re[1]] if uuid_prefixes[re[1]]
     end
 
index ac845f50adc71e5e95d4eded09779be72ffdca46..c0c2b7ef7d3af5c21e67fb51bebde066907d7276 100644 (file)
@@ -3,9 +3,16 @@ 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
     t.add :manifest_text
   end
 
@@ -15,6 +22,34 @@ class Collection < ArvadosModel
                 })
   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'
@@ -31,39 +66,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
@@ -134,10 +136,6 @@ class Collection < ArvadosModel
     end
   end
 
-  def self.uuid_like_pattern
-    "________________________________+%"
-  end
-
   def self.normalize_uuid uuid
     hash_part = nil
     size_part = nil
@@ -166,13 +164,18 @@ class Collection < ArvadosModel
     # that looks like a Docker image, return it.
     if loc = Locator.parse(search_term)
       loc.strip_hints!
-      coll_match = readable_by(*readers).where(uuid: loc.to_s).first
+      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 [loc.to_s]
+        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.
     matches = base_search.
       where(link_class: "docker_image_repo+tag",
@@ -181,7 +184,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
 
@@ -190,7 +193,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
index d3802441317f21f5be00ccc194d16c2c7338037f..0e857ad15c22101d0d93e4b497479f3e14a6055d 100644 (file)
@@ -7,6 +7,7 @@ class Group < ArvadosModel
   include CanBeAnOwner
   after_create :invalidate_permissions_cache
   after_update :maybe_invalidate_permissions_cache
+  before_create :assign_name
 
   api_accessible :user, extend: :common do |t|
     t.add :name
@@ -28,4 +29,12 @@ class Group < ArvadosModel
     # immediately after being created.
     User.invalidate_permissions_cache
   end
+
+  def assign_name
+    if self.new_record? and (self.name.nil? or self.name.empty?)
+      self.name = self.uuid
+    end
+    true
+  end
+
 end
index 1ef0b797c044574a2b9eba72e1fcbd0aeba77e16..f56b57ac7b42361a216d05f93b07278fea7e1479 100644 (file)
@@ -40,6 +40,8 @@ class Job < ArvadosModel
     t.add :repository
     t.add :supplied_script_version
     t.add :docker_image_locator
+    t.add :name
+    t.add :description
   end
 
   def assert_finished
@@ -118,7 +120,7 @@ class Job < ArvadosModel
       self.docker_image_locator = nil
       true
     elsif coll = Collection.for_latest_docker_image(image_search, image_tag)
-      self.docker_image_locator = coll.uuid
+      self.docker_image_locator = coll.portable_data_hash
       true
     else
       errors.add(:docker_image_locator, "not found for #{image_search}")
index 3058081c1e85b46f29060e35b138c205255d8545..e319190534ba498ef645ac902de7fbbc6ebf3864 100644 (file)
@@ -9,8 +9,7 @@ class Link < ArvadosModel
   after_create :maybe_invalidate_permissions_cache
   after_destroy :maybe_invalidate_permissions_cache
   attr_accessor :head_kind, :tail_kind
-  validate :name_link_has_valid_name
-  validate :name_link_owner_is_tail
+  validate :name_links_are_obsolete
 
   api_accessible :user, extend: :common do |t|
     t.add :tail_uuid
@@ -71,23 +70,14 @@ class Link < ArvadosModel
     end
   end
 
-  def name_link_has_valid_name
+  def name_links_are_obsolete
     if link_class == 'name'
-      unless name.is_a? String and !name.empty?
-        errors.add('name', 'must be a non-empty string')
-      end
+        errors.add('name', 'Name links are obsolete')
     else
       true
     end
   end
 
-  def name_link_owner_is_tail
-    if link_class == 'name'
-      self.owner_uuid = tail_uuid
-      ensure_owner_uuid_is_permitted
-    end
-  end
-
   # A user is permitted to create, update or modify a permission link
   # if and only if they have "manage" permission on the destination
   # object.
index d01c9dcbc00f9825e86356e88a4dacb527d33abf..39a7aaf6c301ab8bfbda1aba1ffa417fe96a6685 100644 (file)
@@ -35,9 +35,16 @@ class Locator
   # Locator.parse! returns a Locator object parsed from the string tok,
   # raising an ArgumentError if tok cannot be parsed.
   def self.parse!(tok)
+    if tok.nil? or tok.empty?
+      raise ArgumentError.new "locator is nil or empty"
+    end
+
     m = /^([[:xdigit:]]{32})(\+([[:digit:]]+))?(\+([[:upper:]][[:alnum:]+@_-]*))?$/.match(tok.strip)
     unless m
-      raise ArgumentError.new "could not parse #{tok}"
+      raise ArgumentError.new "not a valid locator #{tok}"
+    end
+    unless m[2]
+      raise ArgumentError.new "missing size hint on #{tok}"
     end
 
     tokhash, _, toksize, _, trailer = m[1..5]
index 354c8924e78f691cce4f327edb75ca37ec261f71..f2c930c43bfa85c38f728b26dafd6911f27005b7 100644 (file)
@@ -17,6 +17,7 @@ class PipelineInstance < ArvadosModel
     t.add :pipeline_template_uuid
     t.add :pipeline_template, :if => :pipeline_template
     t.add :name
+    t.add :description
     t.add :components
     t.add :dependencies
     t.add :properties
diff --git a/services/api/app/views/api_client_authorizations/index.html.erb b/services/api/app/views/api_client_authorizations/index.html.erb
deleted file mode 100644 (file)
index f80ecef..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-<table style="width:100%">
-  <tr class="contain-align-left">
-    <th>
-      API client
-    </th><th>
-      Token
-    </th><th>
-      Created at
-    </th><th>
-      Used at
-    </th><th>
-      Expires
-    </th>
-  </tr>
-
-  <% @objects.each do |o| %>
-
-  <tr>
-    <td>
-      <%= o.api_client.name || o.api_client.url_prefix || o.api_client.uuid %>
-    </td><td>
-      <%= o.api_token %>
-    </td><td>
-      <%= o.created_at %>
-    </td><td>
-      <%= o.last_used_at %>
-      /
-      <%= o.last_used_by_ip_address %>
-    </td><td>
-      <%= o.expires_at %>
-    </td>
-  </tr>
-
-  <% end %>
-
-</table>
diff --git a/services/api/app/views/collections/index.html.erb b/services/api/app/views/collections/index.html.erb
deleted file mode 100644 (file)
index a73c74b..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-<table style="width:100%">
-  <tr class="contain-align-left">
-    <th>
-      redundancy
-    </th><th>
-      uuid
-    </th><th>
-      name
-    </th><th>
-      locator
-    </th><th>
-      last updated
-    </th>
-  </tr>
-
-  <% @objects.each do |o| %>
-
-  <tr class="collection-redundancy-status collection-redundancy-status-<%= o.redundancy_status %>" data-showhide-selector="tr#extra-info-<%= o.uuid %>" style="cursor:pointer">
-    <td>
-      <%= o.redundancy_status %> (<%= o.redundancy %>)
-    </td><td>
-      <%= o.uuid %>
-    </td><td>
-      <%= o.name %>
-    </td><td>
-      <%= o.locator %>
-    </td><td>
-      <%= distance_of_time_in_words(o.updated_at, Time.now, true) + ' ago' if o.updated_at %>
-    </td>
-  </tr>
-
-  <% if  %>
-  <tr id="extra-info-<%= o.uuid %>" data-showhide-default>
-    <td colspan="5">
-      <table>
-       <tr>
-         <td>
-           (file list not available)
-         </td>
-       </tr>
-      </table>
-    </td>
-  </tr>
-
-  <% end %>
-  <% end %>
-</table>
diff --git a/services/api/app/views/nodes/index.html.erb b/services/api/app/views/nodes/index.html.erb
deleted file mode 100644 (file)
index bb346e9..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-<table style="width:100%">
-  <tr class="contain-align-left">
-    <th>
-      status
-    </th><th>
-      sinfo
-    </th><th>
-      uuid
-    </th><th>
-      hostname
-    </th><th>
-      domain
-    </th><th>
-      ip address
-    </th><th>
-      created
-    </th><th>
-      startup delay
-    </th><th>
-      last ping
-    </th><th>
-      instance_id
-    </th>
-  </tr>
-
-  <% @objects.each do |o| %>
-
-  <tr class="node-status-<%= o.status %>">
-    <td class="node-status" data-showhide-selector="tr#extra-info-<%= o.uuid %>" style="cursor:pointer">
-      <%= o.status %>
-    </td><td class="node-slurm-state node-slurm-state-<%= @slurm_state[o.hostname] %>">
-      <%= @slurm_state[o.hostname] %>
-    </td><td>
-      <%= o.uuid %>
-    </td><td>
-      <%= o.hostname %>
-    </td><td>
-      <%= o.domain %>
-    </td><td>
-      <%= o.ip_address %>
-    </td><td>
-      <%= o.created_at %>
-    </td><td>
-      <%= distance_of_time_in_words(o.first_ping_at, o.created_at, true) if o.first_ping_at %>
-    </td><td>
-      <%= distance_of_time_in_words(o.last_ping_at, Time.now, true) + ' ago' if o.last_ping_at %>
-    </td><td>
-      <%= o.info[:ec2_instance_id] %>
-    </td>
-  </tr>
-
-  <% if  %>
-  <tr id="extra-info-<%= o.uuid %>" <%= 'data-showhide-default' unless o.info[:ec2_start_command] and !o.first_ping_at %>>
-    <td colspan="9">
-      <dl>
-      <% o.info.each do |k,v| %>
-      <dt><em><%= k %></em></dt>
-      <dd><%= v %></dd>
-      <% end %>
-      </dl>
-    </td>
-  </tr>
-
-  <% end %>
-  <% end %>
-</table>
diff --git a/services/api/app/views/pipeline_instances/index.html.erb b/services/api/app/views/pipeline_instances/index.html.erb
deleted file mode 100644 (file)
index cfadd26..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-<table style="width:100%">
-  <tr class="contain-align-left">
-    <th>
-      success
-    </th><th>
-      active
-    </th><th>
-      % complete
-    </th><th>
-      uuid
-    </th><th>
-      pipeline template
-    </th><th>
-      name
-    </th><th>
-      last updated
-    </th>
-  </tr>
-
-  <% @objects.each do |o| %>
-
-  <% status = (o.state == 'Complete') ? 'success' : ((o.state == 'Failed') ? 'failure' : 'pending') %>
-
-  <tr class="pipeline-instance-status pipeline-instance-status-<%= status %>" data-showhide-selector="tr#extra-info-<%= o.uuid %>" style="cursor:pointer">
-    <td>
-      <%= status %>
-    </td><td>
-      <%= (o.state == 'RunningOnServer') ? 'yes' : '-' %>
-    </td><td>
-      <%= (o.progress_ratio * 1000).floor / 10 %>
-    </td><td>
-      <%= o.uuid %>
-    </td><td>
-      <%= o.pipeline_template_uuid %>
-    </td><td>
-      <%= o.name %>
-    </td><td>
-      <%= distance_of_time_in_words(o.updated_at, Time.now, true) + ' ago' if o.updated_at %>
-    </td>
-  </tr>
-
-  <% if  %>
-  <tr id="extra-info-<%= o.uuid %>" data-showhide-default>
-    <td colspan="7">
-      <table>
-       <% o.progress_table.each do |r| %>
-       <tr>
-         <% r[2] = "#{(r[2]*100).floor}%" %>
-         <% r[4] = r[4][0..5] rescue '' %>
-         <% r.each do |c| %>
-         <td>
-           <%= (c.is_a? Time) ? distance_of_time_in_words(c, Time.now, true) + ' ago' : c %>
-         </td>
-         <% end %>
-       </tr>
-       <% end %>
-      </table>
-    </td>
-  </tr>
-
-  <% end %>
-  <% end %>
-</table>
diff --git a/services/api/db/migrate/20140811184643_collection_use_regular_uuids.rb b/services/api/db/migrate/20140811184643_collection_use_regular_uuids.rb
new file mode 100644 (file)
index 0000000..8755ee3
--- /dev/null
@@ -0,0 +1,170 @@
+class CollectionUseRegularUuids < ActiveRecord::Migration
+  def up
+    add_column :collections, :name, :string
+    add_column :collections, :description, :string
+    add_column :collections, :properties, :text
+    add_column :collections, :expire_time, :date
+    remove_column :collections, :locator
+
+    say_with_time "Step 1. Move manifest hashes into portable_data_hash field" do
+      ActiveRecord::Base.connection.execute("update collections set portable_data_hash=uuid, uuid=null")
+    end
+
+    say_with_time "Step 2. Create new collection objects from the name links in the table." do
+      from_clause = %{
+from links inner join collections on head_uuid=collections.portable_data_hash
+where link_class='name' and collections.uuid is null
+}
+      links = ActiveRecord::Base.connection.select_all %{
+select links.uuid, head_uuid, tail_uuid, links.name,
+manifest_text, links.created_at, links.modified_at, links.modified_by
+#{from_clause}
+}
+      links.each do |d|
+        ActiveRecord::Base.connection.execute %{
+insert into collections (uuid, portable_data_hash, owner_uuid, name, manifest_text, created_at, modified_at, modified_by)
+values (#{ActiveRecord::Base.connection.quote Collection.generate_uuid},
+#{ActiveRecord::Base.connection.quote d['head_uuid']},
+#{ActiveRecord::Base.connection.quote d['tail_uuid']},
+#{ActiveRecord::Base.connection.quote d['name']},
+#{ActiveRecord::Base.connection.quote d['manifest_text']},
+#{ActiveRecord::Base.connection.quote d['created_at']},
+#{ActiveRecord::Base.connection.quote d['modified_at']},
+#{ActiveRecord::Base.connection.quote d['modified_by']})
+}
+      end
+      ActiveRecord::Base.connection.execute "delete from links where links.uuid in (select links.uuid #{from_clause})"
+    end
+
+    say_with_time "Step 3. Create new collection objects from the can_read links in the table." do
+      from_clause = %{
+from links inner join collections on head_uuid=collections.portable_data_hash
+where link_class='permission' and links.name='can_read' and collections.uuid is null
+}
+      links = ActiveRecord::Base.connection.select_all %{
+select links.uuid, head_uuid, tail_uuid, manifest_text, links.created_at, links.modified_at
+#{from_clause}
+}
+      links.each do |d|
+        ActiveRecord::Base.connection.execute %{
+insert into collections (uuid, portable_data_hash, owner_uuid, manifest_text, created_at, modified_at, modified_by)
+values (#{ActiveRecord::Base.connection.quote Collection.generate_uuid},
+#{ActiveRecord::Base.connection.quote d['head_uuid']},
+#{ActiveRecord::Base.connection.quote d['tail_uuid']},
+#{ActiveRecord::Base.connection.quote d['manifest_text']},
+#{ActiveRecord::Base.connection.quote d['created_at']},
+#{ActiveRecord::Base.connection.quote d['modified_at']},
+#{ActiveRecord::Base.connection.quote d['modified_by']})
+}
+      end
+      ActiveRecord::Base.connection.execute "delete from links where links.uuid in (select links.uuid #{from_clause})"
+    end
+
+    say_with_time "Step 4. Migrate remaining orphan collection objects" do
+      links = ActiveRecord::Base.connection.select_all %{
+select portable_data_hash, owner_uuid, manifest_text, created_at, modified_at
+from collections
+where uuid is null and portable_data_hash not in (select portable_data_hash from collections where uuid is not null)
+}
+      links.each do |d|
+        ActiveRecord::Base.connection.execute %{
+insert into collections (uuid, portable_data_hash, owner_uuid, manifest_text, created_at, modified_at, modified_by)
+values (#{ActiveRecord::Base.connection.quote Collection.generate_uuid},
+#{ActiveRecord::Base.connection.quote d['portable_data_hash']},
+#{ActiveRecord::Base.connection.quote d['owner_uuid']},
+#{ActiveRecord::Base.connection.quote d['manifest_text']},
+#{ActiveRecord::Base.connection.quote d['created_at']},
+#{ActiveRecord::Base.connection.quote d['modified_at']},
+#{ActiveRecord::Base.connection.quote d['modified_by']})
+}
+      end
+    end
+
+    say_with_time "Step 5. Delete old collection objects." do
+      ActiveRecord::Base.connection.execute("delete from collections where uuid is null")
+    end
+
+    say_with_time "Step 6. Delete permission links where tail_uuid is a collection (invalid records)" do
+      ActiveRecord::Base.connection.execute %{
+delete from links where links.uuid in (select links.uuid
+from links
+where tail_uuid like '________________________________+%' and link_class='permission' )
+}
+    end
+
+    say_with_time "Step 7. Migrate collection -> collection provenance links to jobs" do
+      from_clause = %{
+from links
+where head_uuid like '________________________________+%' and tail_uuid like '________________________________+%' and links.link_class = 'provenance'
+}
+      links = ActiveRecord::Base.connection.select_all %{
+select links.uuid, head_uuid, tail_uuid, links.created_at, links.modified_at, links.modified_by, links.owner_uuid
+#{from_clause}
+}
+      links.each do |d|
+        newuuid = Job.generate_uuid
+        ActiveRecord::Base.connection.execute %{
+insert into jobs (uuid, script_parameters, output, running, success, created_at, modified_at, modified_by, owner_uuid)
+values (#{ActiveRecord::Base.connection.quote newuuid},
+#{ActiveRecord::Base.connection.quote "---\ninput: "+d['tail_uuid']},
+#{ActiveRecord::Base.connection.quote d['head_uuid']},
+#{ActiveRecord::Base.connection.quote false},
+#{ActiveRecord::Base.connection.quote true},
+#{ActiveRecord::Base.connection.quote d['created_at']},
+#{ActiveRecord::Base.connection.quote d['modified_at']},
+#{ActiveRecord::Base.connection.quote d['modified_by']},
+#{ActiveRecord::Base.connection.quote d['owner_uuid']})
+}
+      end
+      ActiveRecord::Base.connection.execute "delete from links where links.uuid in (select links.uuid #{from_clause})"
+    end
+
+    say_with_time "Step 8. Migrate remaining links with head_uuid pointing to collections" do
+      from_clause = %{
+from links inner join collections on links.head_uuid=portable_data_hash
+where collections.uuid is not null
+}
+      links = ActiveRecord::Base.connection.select_all %{
+select links.uuid, collections.uuid as collectionuuid, tail_uuid, link_class, links.properties,
+links.name, links.created_at, links.modified_at, links.modified_by, links.owner_uuid
+#{from_clause}
+}
+      links.each do |d|
+        ActiveRecord::Base.connection.execute %{
+insert into links (uuid, head_uuid, tail_uuid, link_class, name, properties, created_at, modified_at, modified_by, owner_uuid)
+values (#{ActiveRecord::Base.connection.quote Link.generate_uuid},
+#{ActiveRecord::Base.connection.quote d['collectionuuid']},
+#{ActiveRecord::Base.connection.quote d['tail_uuid']},
+#{ActiveRecord::Base.connection.quote d['link_class']},
+#{ActiveRecord::Base.connection.quote d['name']},
+#{ActiveRecord::Base.connection.quote d['properties']},
+#{ActiveRecord::Base.connection.quote d['created_at']},
+#{ActiveRecord::Base.connection.quote d['modified_at']},
+#{ActiveRecord::Base.connection.quote d['modified_by']},
+#{ActiveRecord::Base.connection.quote d['owner_uuid']})
+}
+      end
+      ActiveRecord::Base.connection.execute "delete from links where links.uuid in (select links.uuid #{from_clause})"
+    end
+
+    say_with_time "Step 9. Delete any remaining name links" do
+      ActiveRecord::Base.connection.execute("delete from links where link_class='name'")
+    end
+
+    say_with_time "Step 10. Validate links table" do
+      links = ActiveRecord::Base.connection.select_all %{
+select links.uuid, head_uuid, tail_uuid, link_class, name
+from links
+where head_uuid like '________________________________+%' or tail_uuid like '________________________________+%'
+}
+      links.each do |d|
+        raise "Bad row #{d}"
+      end
+    end
+
+  end
+
+  def down
+    # Not gonna happen.
+  end
+end
diff --git a/services/api/db/migrate/20140815171049_add_name_description_columns.rb b/services/api/db/migrate/20140815171049_add_name_description_columns.rb
new file mode 100644 (file)
index 0000000..c0c8f6a
--- /dev/null
@@ -0,0 +1,13 @@
+class AddNameDescriptionColumns < ActiveRecord::Migration
+  def up
+    add_column :jobs, :name, :string
+    add_column :jobs, :description, :text
+    add_column :pipeline_instances, :description, :text
+  end
+
+  def down
+    remove_column :jobs, :name
+    remove_column :jobs, :description
+    remove_column :pipeline_instances, :description
+  end
+end
diff --git a/services/api/db/migrate/20140817035914_add_unique_name_constraints.rb b/services/api/db/migrate/20140817035914_add_unique_name_constraints.rb
new file mode 100644 (file)
index 0000000..f84c635
--- /dev/null
@@ -0,0 +1,34 @@
+class AddUniqueNameConstraints < ActiveRecord::Migration
+  def change
+    # Ensure uniqueness before adding constraints.
+    ["collections", "pipeline_templates", "pipeline_instances", "jobs", "groups"].each do |table|
+      rows = ActiveRecord::Base.connection.select_all %{
+select uuid, owner_uuid, name from #{table} order by owner_uuid, name
+}
+      prev = {}
+      n = 1
+      rows.each do |r|
+        if r["owner_uuid"] == prev["owner_uuid"] and !r["name"].nil? and r["name"] == prev["name"]
+          n += 1
+          ActiveRecord::Base.connection.execute %{
+update #{table} set name='#{r["name"]} #{n}' where uuid='#{r["uuid"]}'
+}
+        else
+          n = 1
+        end
+        prev = r
+      end
+    end
+
+    add_index(:collections, [:owner_uuid, :name], unique: true,
+              name: 'collection_owner_uuid_name_unique')
+    add_index(:pipeline_templates, [:owner_uuid, :name], unique: true,
+              name: 'pipeline_template_owner_uuid_name_unique')
+    add_index(:pipeline_instances, [:owner_uuid, :name], unique: true,
+              name: 'pipeline_instance_owner_uuid_name_unique')
+    add_index(:jobs, [:owner_uuid, :name], unique: true,
+              name: 'jobs_owner_uuid_name_unique')
+    add_index(:groups, [:owner_uuid, :name], unique: true,
+              name: 'groups_owner_uuid_name_unique')
+  end
+end
diff --git a/services/api/db/migrate/20140818125735_add_not_null_constraint_to_group_name.rb b/services/api/db/migrate/20140818125735_add_not_null_constraint_to_group_name.rb
new file mode 100644 (file)
index 0000000..1b07470
--- /dev/null
@@ -0,0 +1,6 @@
+class AddNotNullConstraintToGroupName < ActiveRecord::Migration
+  def change
+    ActiveRecord::Base.connection.execute("update groups set name=uuid where name is null or name=''")
+    change_column_null :groups, :name, false
+  end
+end
index aa05031fe0ec8c45a73d1fd41320f1c3913e10d5..de293326e690b25265ccb5711588fc8c3ab60d34 100644 (file)
@@ -3,7 +3,6 @@
 --
 
 SET statement_timeout = 0;
-SET lock_timeout = 0;
 SET client_encoding = 'UTF8';
 SET standard_conforming_strings = on;
 SET check_function_bodies = false;
@@ -154,7 +153,6 @@ ALTER SEQUENCE authorized_keys_id_seq OWNED BY authorized_keys.id;
 
 CREATE TABLE collections (
     id integer NOT NULL,
-    locator character varying(255),
     owner_uuid character varying(255),
     created_at timestamp without time zone NOT NULL,
     modified_by_client_uuid character varying(255),
@@ -167,7 +165,11 @@ CREATE TABLE collections (
     redundancy_confirmed_as integer,
     updated_at timestamp without time zone NOT NULL,
     uuid character varying(255),
-    manifest_text text
+    manifest_text text,
+    name character varying(255),
+    description character varying(255),
+    properties text,
+    expire_time date
 );
 
 
@@ -269,7 +271,7 @@ CREATE TABLE groups (
     modified_by_client_uuid character varying(255),
     modified_by_user_uuid character varying(255),
     modified_at timestamp without time zone,
-    name character varying(255),
+    name character varying(255) NOT NULL,
     description text,
     updated_at timestamp without time zone NOT NULL,
     group_class character varying(255)
@@ -427,7 +429,9 @@ CREATE TABLE jobs (
     repository character varying(255),
     output_is_persistent boolean DEFAULT false NOT NULL,
     supplied_script_version character varying(255),
-    docker_image_locator character varying(255)
+    docker_image_locator character varying(255),
+    name character varying(255),
+    description text
 );
 
 
@@ -676,7 +680,8 @@ CREATE TABLE pipeline_instances (
     updated_at timestamp without time zone NOT NULL,
     properties text,
     state character varying(255),
-    components_summary text
+    components_summary text,
+    description text
 );
 
 
@@ -1267,6 +1272,20 @@ ALTER TABLE ONLY virtual_machines
     ADD CONSTRAINT virtual_machines_pkey PRIMARY KEY (id);
 
 
+--
+-- Name: collection_owner_uuid_name_unique; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX collection_owner_uuid_name_unique ON collections USING btree (owner_uuid, name);
+
+
+--
+-- Name: groups_owner_uuid_name_unique; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX groups_owner_uuid_name_unique ON groups USING btree (owner_uuid, name);
+
+
 --
 -- Name: index_api_client_authorizations_on_api_client_id; Type: INDEX; Schema: public; Owner: -; Tablespace: 
 --
@@ -1778,6 +1797,13 @@ CREATE INDEX index_virtual_machines_on_hostname ON virtual_machines USING btree
 CREATE UNIQUE INDEX index_virtual_machines_on_uuid ON virtual_machines USING btree (uuid);
 
 
+--
+-- Name: jobs_owner_uuid_name_unique; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX jobs_owner_uuid_name_unique ON jobs USING btree (owner_uuid, name);
+
+
 --
 -- Name: links_tail_name_unique_if_link_class_name; Type: INDEX; Schema: public; Owner: -; Tablespace: 
 --
@@ -1785,6 +1811,20 @@ CREATE UNIQUE INDEX index_virtual_machines_on_uuid ON virtual_machines USING btr
 CREATE UNIQUE INDEX links_tail_name_unique_if_link_class_name ON links USING btree (tail_uuid, name) WHERE ((link_class)::text = 'name'::text);
 
 
+--
+-- Name: pipeline_instance_owner_uuid_name_unique; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX pipeline_instance_owner_uuid_name_unique ON pipeline_instances USING btree (owner_uuid, name);
+
+
+--
+-- Name: pipeline_template_owner_uuid_name_unique; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX pipeline_template_owner_uuid_name_unique ON pipeline_templates USING btree (owner_uuid, name);
+
+
 --
 -- Name: unique_schema_migrations; Type: INDEX; Schema: public; Owner: -; Tablespace: 
 --
@@ -1976,4 +2016,12 @@ INSERT INTO schema_migrations (version) VALUES ('20140627210837');
 
 INSERT INTO schema_migrations (version) VALUES ('20140709172343');
 
-INSERT INTO schema_migrations (version) VALUES ('20140714184006');
\ No newline at end of file
+INSERT INTO schema_migrations (version) VALUES ('20140714184006');
+
+INSERT INTO schema_migrations (version) VALUES ('20140811184643');
+
+INSERT INTO schema_migrations (version) VALUES ('20140815171049');
+
+INSERT INTO schema_migrations (version) VALUES ('20140817035914');
+
+INSERT INTO schema_migrations (version) VALUES ('20140818125735');
\ No newline at end of file
index 481d27a037ed4036561bf499e2ae94af6c71011d..df175f8602851a638db0a2b7727ccb1d489a792a 100644 (file)
@@ -1,7 +1,10 @@
 module HasUuid
 
+  UUID_REGEX = /^[0-9a-z]{5}-([0-9a-z]{5})-[0-9a-z]{15}$/
+
   def self.included(base)
     base.extend(ClassMethods)
+    base.validate :validate_uuid
     base.before_create :assign_uuid
     base.before_destroy :destroy_permission_links
     base.has_many :links_via_head, class_name: 'Link', foreign_key: :head_uuid, primary_key: :uuid, conditions: "not (link_class = 'permission')", dependent: :restrict
@@ -26,13 +29,38 @@ module HasUuid
     self.respond_to? :uuid
   end
 
-  def assign_uuid
-    return true if !self.respond_to_uuid?
-    if (uuid.is_a?(String) and uuid.length>0 and
-        current_user and current_user.is_admin)
+  def validate_uuid
+    if self.respond_to_uuid? and self.uuid_changed?
+      if current_user.andand.is_admin and self.uuid.is_a?(String)
+        if (re = self.uuid.match HasUuid::UUID_REGEX)
+          if re[1] == self.class.uuid_prefix
+            return true
+          else
+            self.errors.add(:uuid, "Matched uuid type '#{re[1]}', expected '#{self.class.uuid_prefix}'")
+            return false
+          end
+        else
+          self.errors.add(:uuid, "'#{self.uuid}' is not a valid Arvados UUID")
+          return false
+        end
+      else
+        if self.new_record?
+          self.errors.add(:uuid, "Not permitted to specify uuid")
+        else
+          self.errors.add(:uuid, "Not permitted to change uuid")
+        end
+        return false
+      end
+    else
       return true
     end
-    self.uuid = self.class.generate_uuid
+  end
+
+  def assign_uuid
+    if self.respond_to_uuid? and self.uuid.nil? or self.uuid.empty?
+      self.uuid = self.class.generate_uuid
+    end
+    true
   end
 
   def destroy_permission_links
index fbd150cdf7887f7f164ea34521acec9d4f051b5e..c8144c2c88884e80b443add693c16ae60165d764 100644 (file)
@@ -1,5 +1,6 @@
 user_agreement:
-  uuid: b519d9cb706a29fc7ea24dbea2f05851+249025
+  uuid: 4n8aq-4zz18-t68oksiu9m80s4y
+  portable_data_hash: b519d9cb706a29fc7ea24dbea2f05851+249025
   owner_uuid: zzzzz-tpzed-000000000000000
   created_at: 2013-12-26T19:22:54Z
   modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
@@ -7,9 +8,11 @@ user_agreement:
   modified_at: 2013-12-26T19:22:54Z
   updated_at: 2013-12-26T19:22:54Z
   manifest_text: ". 6a4ff0499484c6c79c95cd8c566bd25f+249025 0:249025:GNU_General_Public_License,_version_3.pdf\n"
+  name: user_agreement
 
 foo_file:
-  uuid: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
+  uuid: 4n8aq-4zz18-znfnqtbbv4spc3w
+  portable_data_hash: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
   owner_uuid: zzzzz-tpzed-000000000000000
   created_at: 2014-02-03T17:22:54Z
   modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
@@ -17,9 +20,11 @@ foo_file:
   modified_at: 2014-02-03T17:22:54Z
   updated_at: 2014-02-03T17:22:54Z
   manifest_text: ". acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo\n"
+  name: foo_file
 
 bar_file:
-  uuid: fa7aeb5140e2848d39b416daeef4ffc5+45
+  uuid: 4n8aq-4zz18-ehbhgtheo8909or
+  portable_data_hash: fa7aeb5140e2848d39b416daeef4ffc5+45
   owner_uuid: zzzzz-tpzed-000000000000000
   created_at: 2014-02-03T17:22:54Z
   modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
@@ -27,9 +32,11 @@ bar_file:
   modified_at: 2014-02-03T17:22:54Z
   updated_at: 2014-02-03T17:22:54Z
   manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
+  name: bar_file
 
 baz_file:
-  uuid: ea10d51bcf88862dbcc36eb292017dfd+45
+  uuid: 4n8aq-4zz18-y9vne9npefyxh8g
+  portable_data_hash: ea10d51bcf88862dbcc36eb292017dfd+45
   owner_uuid: zzzzz-tpzed-000000000000000
   created_at: 2014-02-03T17:22:54Z
   modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
@@ -37,9 +44,11 @@ baz_file:
   modified_at: 2014-02-03T17:22:54Z
   updated_at: 2014-02-03T17:22:54Z
   manifest_text: ". 73feffa4b7f6bb68e44cf984c85f6e88+3 0:3:baz\n"
+  name: baz_file
 
 multilevel_collection_1:
-  uuid: 1fd08fc162a5c6413070a8bd0bffc818+150
+  uuid: 4n8aq-4zz18-pyw8yp9g3pr7irn
+  portable_data_hash: 1fd08fc162a5c6413070a8bd0bffc818+150
   owner_uuid: qr1hi-tpzed-000000000000000
   created_at: 2014-02-03T17:22:54Z
   modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
@@ -47,10 +56,12 @@ multilevel_collection_1:
   modified_at: 2014-02-03T17:22:54Z
   updated_at: 2014-02-03T17:22:54Z
   manifest_text: ". 0:0:file1 0:0:file2 0:0:file3\n./dir1 0:0:file1 0:0:file2 0:0:file3\n./dir1/subdir 0:0:file1 0:0:file2 0:0:file3\n./dir2 0:0:file1 0:0:file2 0:0:file3\n"
+  name: multilevel_collection_1
 
 multilevel_collection_2:
+  uuid: 4n8aq-4zz18-45xf9hw1sxkhl6q
   # All of this collection's files are deep in subdirectories.
-  uuid: 80cf6dd2cf079dd13f272ec4245cb4a8+48
+  portable_data_hash: 80cf6dd2cf079dd13f272ec4245cb4a8+48
   owner_uuid: qr1hi-tpzed-000000000000000
   created_at: 2014-02-03T17:22:54Z
   modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
@@ -58,10 +69,12 @@ multilevel_collection_2:
   modified_at: 2014-02-03T17:22:54Z
   updated_at: 2014-02-03T17:22:54Z
   manifest_text: "./dir1/sub1 0:0:a 0:0:b\n./dir2/sub2 0:0:c 0:0:d\n"
+  name: multilevel_collection_2
 
 docker_image:
+  uuid: 4n8aq-4zz18-1v45jub259sjjgb
   # This Collection has links with Docker image metadata.
-  uuid: fa3c1a9cb6783f85f2ecda037e07b8c3+167
+  portable_data_hash: fa3c1a9cb6783f85f2ecda037e07b8c3+167
   owner_uuid: qr1hi-tpzed-000000000000000
   created_at: 2014-06-11T17:22:54Z
   modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
@@ -69,11 +82,13 @@ docker_image:
   modified_at: 2014-06-11T17:22:54Z
   updated_at: 2014-06-11T17:22:54Z
   manifest_text: ". d21353cfe035e3e384563ee55eadbb2f+67108864 5c77a43e329b9838cbec18ff42790e57+55605760 0:122714624:d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678.tar\n"
+  name: docker_image
 
 unlinked_docker_image:
+  uuid: 4n8aq-4zz18-d0d8z5wofvfgwad
   # This Collection contains a file that looks like a Docker image,
   # but has no Docker metadata links pointing to it.
-  uuid: 9ae44d5792468c58bcf85ce7353c7027+124
+  portable_data_hash: 9ae44d5792468c58bcf85ce7353c7027+124
   owner_uuid: qr1hi-tpzed-000000000000000
   created_at: 2014-06-11T17:22:54Z
   modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
@@ -81,10 +96,12 @@ unlinked_docker_image:
   modified_at: 2014-06-11T17:22:54Z
   updated_at: 2014-06-11T17:22:54Z
   manifest_text: ". fca529cfe035e3e384563ee55eadbb2f+67108863 0:67108863:bcd02158b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678.tar\n"
+  name: unlinked_docker_image
 
 empty:
+  uuid: 4n8aq-4zz18-gs9ooj1h9sd5mde
   # Empty collection owned by anonymous_group is added with rake db:seed.
-  uuid: d41d8cd98f00b204e9800998ecf8427e+0
+  portable_data_hash: d41d8cd98f00b204e9800998ecf8427e+0
   owner_uuid: zzzzz-tpzed-000000000000000
   created_at: 2014-06-11T17:22:54Z
   modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
@@ -92,3 +109,55 @@ empty:
   modified_at: 2014-06-11T17:22:54Z
   updated_at: 2014-06-11T17:22:54Z
   manifest_text: ""
+  name: empty_collection
+
+foo_collection_in_aproject:
+  uuid: 4n8aq-4zz18-fy296fx3hot09f7
+  portable_data_hash: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
+  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
+  created_at: 2014-04-21 15:37:48 -0400
+  modified_at: 2014-04-21 15:37:48 -0400
+  updated_at: 2014-04-21 15:37:48 -0400
+  manifest_text: ". acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo\n"
+  name: "4n8aq-4zz18-znfnqtbbv4spc3w added sometime"
+
+user_agreement_in_anonymously_accessible_project:
+  uuid: 4n8aq-4zz18-uukreo9rbgwsujr
+  portable_data_hash: b519d9cb706a29fc7ea24dbea2f05851+249025
+  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
+  created_at: 2014-06-13 20:42:26 -0800
+  modified_at: 2014-06-13 20:42:26 -0800
+  updated_at: 2014-06-13 20:42:26 -0800
+  manifest_text: ". 6a4ff0499484c6c79c95cd8c566bd25f+249025 0:249025:GNU_General_Public_License,_version_3.pdf\n"
+  name: GNU General Public License, version 3
+
+baz_collection_name_in_asubproject:
+  uuid: 4n8aq-4zz18-lsitwcf548ui4oe
+  portable_data_hash: ea10d51bcf88862dbcc36eb292017dfd+45
+  owner_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
+  created_at: 2014-04-21 15:37:48 -0400
+  modified_at: 2014-04-21 15:37:48 -0400
+  updated_at: 2014-04-21 15:37:48 -0400
+  manifest_text: ". 73feffa4b7f6bb68e44cf984c85f6e88+3 0:3:baz\n"
+  name: "4n8aq-4zz18-lsitwcf548ui4oe added sometime"
+
+empty_collection_name_in_active_user_home_project:
+  uuid: 4n8aq-4zz18-5qa38qghh1j3nvv
+  portable_data_hash: d41d8cd98f00b204e9800998ecf8427e+0
+  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  created_at: 2014-08-06 22:11:51.242392533 Z
+  modified_at: 2014-08-06 22:11:51.242150425 Z
+  manifest_text: ""
+  name: Empty collection
+
+baz_file_in_asubproject:
+  uuid: 4n8aq-4zz18-0mri2x4u7ftngez
+  portable_data_hash: ea10d51bcf88862dbcc36eb292017dfd+45
+  owner_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
+  created_at: 2014-02-03T17:22:54Z
+  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+  modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
+  modified_at: 2014-02-03T17:22:54Z
+  updated_at: 2014-02-03T17:22:54Z
+  manifest_text: ". 73feffa4b7f6bb68e44cf984c85f6e88+3 0:3:baz\n"
+  name: baz_file
index c4ed1335ea4fb243d6e40cbccd3e1bbad204837f..34adfd500d2e97aed47a5b2a80cf17cf853a6ae5 100644 (file)
@@ -16,12 +16,6 @@ private_and_can_read_foofile:
   name: Private and Can Read Foofile
   description: Another Private Group
 
-system_owned_group:
-  uuid: zzzzz-j7d0g-8ulrifv67tve5sx
-  owner_uuid: zzzzz-tpzed-000000000000000
-  name: System Private
-  description: System-owned Group
-
 system_group:
   uuid: zzzzz-j7d0g-000000000000000
   owner_uuid: zzzzz-tpzed-000000000000000
@@ -111,3 +105,8 @@ anonymously_accessible_project:
   name: Unrestricted public data
   group_class: project
   description: An anonymously accessible project
+
+active_user_has_can_manage:
+  uuid: zzzzz-j7d0g-ptt1ou6a9lxrv07
+  owner_uuid: zzzzz-tpzed-d9tiejq69daie8f
+  name: Active user has can_manage
index 4125757b9d63968bbb3bc0ed2ed4aa0a48051ef5..3a0b0635b0f6ce1e2187d7a7d6837943bc248e78 100644 (file)
@@ -9,7 +9,7 @@ user_agreement_required:
   tail_uuid: zzzzz-tpzed-000000000000000
   link_class: signature
   name: require
-  head_uuid: b519d9cb706a29fc7ea24dbea2f05851+249025
+  head_uuid: 4n8aq-4zz18-t68oksiu9m80s4y
   properties: {}
 
 user_agreement_readable:
@@ -23,7 +23,7 @@ user_agreement_readable:
   tail_uuid: zzzzz-j7d0g-fffffffffffffff
   link_class: permission
   name: can_read
-  head_uuid: b519d9cb706a29fc7ea24dbea2f05851+249025
+  head_uuid: 4n8aq-4zz18-t68oksiu9m80s4y
   properties: {}
 
 active_user_member_of_all_users_group:
@@ -40,7 +40,7 @@ active_user_member_of_all_users_group:
   head_uuid: zzzzz-j7d0g-fffffffffffffff
   properties: {}
 
-active_user_can_manage_system_owned_group:
+active_user_can_manage_group:
   uuid: zzzzz-o0j2j-3sa30nd3bqn1msh
   owner_uuid: zzzzz-tpzed-000000000000000
   created_at: 2014-02-03 15:42:26 -0800
@@ -51,7 +51,7 @@ active_user_can_manage_system_owned_group:
   tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   link_class: permission
   name: can_manage
-  head_uuid: zzzzz-j7d0g-8ulrifv67tve5sx
+  head_uuid: zzzzz-j7d0g-ptt1ou6a9lxrv07
   properties: {}
 
 user_agreement_signed_by_active:
@@ -65,7 +65,7 @@ user_agreement_signed_by_active:
   tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   link_class: signature
   name: click
-  head_uuid: b519d9cb706a29fc7ea24dbea2f05851+249025
+  head_uuid: 4n8aq-4zz18-t68oksiu9m80s4y
   properties: {}
 
 user_agreement_signed_by_inactive:
@@ -79,7 +79,7 @@ user_agreement_signed_by_inactive:
   tail_uuid: zzzzz-tpzed-7sg468ezxwnodxs
   link_class: signature
   name: click
-  head_uuid: b519d9cb706a29fc7ea24dbea2f05851+249025
+  head_uuid: 4n8aq-4zz18-t68oksiu9m80s4y
   properties: {}
 
 spectator_user_member_of_all_users_group:
@@ -135,7 +135,7 @@ foo_file_readable_by_active:
   tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   link_class: permission
   name: can_read
-  head_uuid: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
+  head_uuid: 4n8aq-4zz18-znfnqtbbv4spc3w
   properties: {}
 
 foo_file_readable_by_active_duplicate_permission:
@@ -149,7 +149,7 @@ foo_file_readable_by_active_duplicate_permission:
   tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   link_class: permission
   name: can_read
-  head_uuid: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
+  head_uuid: 4n8aq-4zz18-znfnqtbbv4spc3w
   properties: {}
 
 foo_file_readable_by_active_redundant_permission_via_private_group:
@@ -163,7 +163,7 @@ foo_file_readable_by_active_redundant_permission_via_private_group:
   tail_uuid: zzzzz-j7d0g-22xp1wpjul508rk
   link_class: permission
   name: can_read
-  head_uuid: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
+  head_uuid: 4n8aq-4zz18-znfnqtbbv4spc3w
   properties: {}
 
 foo_file_readable_by_aproject:
@@ -177,7 +177,7 @@ foo_file_readable_by_aproject:
   tail_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
   link_class: permission
   name: can_read
-  head_uuid: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
+  head_uuid: 4n8aq-4zz18-znfnqtbbv4spc3w
   properties: {}
 
 bar_file_readable_by_active:
@@ -191,7 +191,7 @@ bar_file_readable_by_active:
   tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   link_class: permission
   name: can_read
-  head_uuid: fa7aeb5140e2848d39b416daeef4ffc5+45
+  head_uuid: 4n8aq-4zz18-ehbhgtheo8909or
   properties: {}
 
 bar_file_readable_by_spectator:
@@ -205,7 +205,7 @@ bar_file_readable_by_spectator:
   tail_uuid: zzzzz-tpzed-l1s2piq4t4mps8r
   link_class: permission
   name: can_read
-  head_uuid: fa7aeb5140e2848d39b416daeef4ffc5+45
+  head_uuid: 4n8aq-4zz18-ehbhgtheo8909or
   properties: {}
 
 baz_file_publicly_readable:
@@ -219,7 +219,7 @@ baz_file_publicly_readable:
   tail_uuid: zzzzz-j7d0g-fffffffffffffff
   link_class: permission
   name: can_read
-  head_uuid: ea10d51bcf88862dbcc36eb292017dfd+45
+  head_uuid: 4n8aq-4zz18-y9vne9npefyxh8g
   properties: {}
 
 barbaz_job_readable_by_spectator:
@@ -349,64 +349,6 @@ project_viewer_can_read_project:
   head_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
   properties: {}
 
-specimen_is_in_two_projects:
-  uuid: zzzzz-o0j2j-ryhm1bn83ni03sn
-  owner_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
-  created_at: 2014-04-21 15:37:48 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  modified_at: 2014-04-21 15:37:48 -0400
-  updated_at: 2014-04-21 15:37:48 -0400
-  tail_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
-  head_uuid: zzzzz-j58dm-5gid26432uujf79
-  link_class: name
-  name: "I'm in a subproject, too"
-  properties: {}
-
-template_name_in_aproject:
-  uuid: zzzzz-o0j2j-4kpwf3d6rwkeqhl
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-04-29 16:47:26 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  modified_at: 2014-04-29 16:47:26 -0400
-  updated_at: 2014-04-29 16:47:26 -0400
-  tail_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  head_uuid: zzzzz-p5p6p-aox0k0ofxrystgw
-  link_class: name
-  name: "I'm a template in a project"
-  properties: {}
-
-job_name_in_aproject:
-  uuid: zzzzz-o0j2j-1kt6dppqcxbl1yt
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-04-29 16:47:26 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  modified_at: 2014-04-29 16:47:26 -0400
-  updated_at: 2014-04-29 16:47:26 -0400
-  tail_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  head_uuid: zzzzz-8i9sb-pshmckwoma9plh7
-  link_class: name
-  name: "I'm a job in a project"
-  properties: {}
-
-foo_collection_name_in_aproject:
-  uuid: zzzzz-o0j2j-fooprojectname1
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-04-21 15:37:48 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  modified_at: 2014-04-21 15:37:48 -0400
-  updated_at: 2014-04-21 15:37:48 -0400
-  tail_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  head_uuid: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
-  link_class: name
-  # This should resemble the default name assigned when a
-  # Collection is added to a Project.
-  name: "1f4b0bc7583c2a7f9102c395f4ffc5e3+45 added sometime"
-  properties: {}
-
 foo_collection_tag:
   uuid: zzzzz-o0j2j-eedahfaho8aphiv
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
@@ -416,7 +358,7 @@ foo_collection_tag:
   modified_at: 2014-04-21 15:37:48 -0400
   updated_at: 2014-04-21 15:37:48 -0400
   tail_uuid: ~
-  head_uuid: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
+  head_uuid: 4n8aq-4zz18-znfnqtbbv4spc3w
   link_class: tag
   name: foo_tag
   properties: {}
@@ -446,7 +388,7 @@ multilevel_collection_1_readable_by_active:
   tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   link_class: permission
   name: can_read
-  head_uuid: 1fd08fc162a5c6413070a8bd0bffc818+150
+  head_uuid: 4n8aq-4zz18-pyw8yp9g3pr7irn
   properties: {}
 
 has_symbol_keys_in_database_somehow:
@@ -500,20 +442,6 @@ anonymous_group_can_read_anonymously_accessible_project:
   head_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
   properties: {}
 
-user_agreement_in_anonymously_accessible_project:
-  uuid: zzzzz-o0j2j-k0ukddp35mt6ok1
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  created_at: 2014-06-13 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
-  modified_at: 2014-06-13 20:42:26 -0800
-  updated_at: 2014-06-13 20:42:26 -0800
-  link_class: name
-  name: GNU General Public License, version 3
-  tail_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  head_uuid: b519d9cb706a29fc7ea24dbea2f05851+249025
-  properties: {}
-
 user_agreement_readable_by_anonymously_accessible_project:
   uuid: zzzzz-o0j2j-o5ds5gvhkztdc8h
   owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
@@ -536,7 +464,7 @@ active_user_permission_to_docker_image_collection:
   tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   link_class: permission
   name: can_read
-  head_uuid: fa3c1a9cb6783f85f2ecda037e07b8c3+167
+  head_uuid: 4n8aq-4zz18-1v45jub259sjjgb
   properties: {}
 
 active_user_permission_to_unlinked_docker_image_collection:
@@ -550,7 +478,7 @@ active_user_permission_to_unlinked_docker_image_collection:
   tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   link_class: permission
   name: can_read
-  head_uuid: 9ae44d5792468c58bcf85ce7353c7027+124
+  head_uuid: 4n8aq-4zz18-d0d8z5wofvfgwad
   properties: {}
 
 docker_image_collection_hash:
@@ -564,22 +492,7 @@ docker_image_collection_hash:
   link_class: docker_image_hash
   name: d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678
   tail_uuid: ~
-  head_uuid: fa3c1a9cb6783f85f2ecda037e07b8c3+167
-  properties:
-    image_timestamp: "2014-06-10T14:30:00.184019565Z"
-
-docker_image_collection_repository:
-  uuid: zzzzz-o0j2j-dockercollrepos
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-06-11 14:30:00.184389725 Z
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-06-11 14:30:00.184019565 Z
-  updated_at: 2014-06-11 14:30:00.183829316 Z
-  link_class: docker_image_repository
-  name: arvados/apitestfixture
-  tail_uuid: ~
-  head_uuid: fa3c1a9cb6783f85f2ecda037e07b8c3+167
+  head_uuid: 4n8aq-4zz18-1v45jub259sjjgb
   properties:
     image_timestamp: "2014-06-10T14:30:00.184019565Z"
 
@@ -594,7 +507,7 @@ docker_image_collection_tag:
   link_class: docker_image_repo+tag
   name: arvados/apitestfixture:latest
   tail_uuid: ~
-  head_uuid: fa3c1a9cb6783f85f2ecda037e07b8c3+167
+  head_uuid: 4n8aq-4zz18-1v45jub259sjjgb
   properties:
     image_timestamp: "2014-06-10T14:30:00.184019565Z"
 
@@ -609,7 +522,7 @@ docker_image_collection_tag2:
   link_class: docker_image_repo+tag
   name: arvados/apitestfixture:june10
   tail_uuid: ~
-  head_uuid: fa3c1a9cb6783f85f2ecda037e07b8c3+167
+  head_uuid: 4n8aq-4zz18-1v45jub259sjjgb
   properties:
     image_timestamp: "2014-06-10T14:30:00.184019565Z"
 
@@ -628,26 +541,10 @@ ancient_docker_image_collection_hash:
   link_class: docker_image_hash
   name: d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678
   tail_uuid: ~
-  head_uuid: b519d9cb706a29fc7ea24dbea2f05851+249025
+  head_uuid: 4n8aq-4zz18-t68oksiu9m80s4y
   properties:
     image_timestamp: "2010-06-10T14:30:00.184019565Z"
 
-anonymous_group_can_read_empty_collection:
-  # Permission link giving anonymous_group permission to read the
-  # empty collection. This link is added in production by the
-  # empty_collection helper.
-  uuid: zzzzz-o0j2j-emptycollection
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-06-13 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-tpzed-000000000000000
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-06-13 20:42:26 -0800
-  updated_at: 2014-06-13 20:42:26 -0800
-  link_class: permission
-  name: can_read
-  tail_uuid: zzzzz-j7d0g-anonymouspublic
-  head_uuid: d41d8cd98f00b204e9800998ecf8427e+0
-
 job_reader_can_read_previous_job_run:
   # Permission link giving job_reader permission
   # to read previous_job_run
@@ -677,33 +574,3 @@ job_reader_can_read_foo_repo:
   name: can_read
   tail_uuid: zzzzz-tpzed-905b42d1dd4a354
   head_uuid: zzzzz-s0uqq-382brsig8rp3666
-
-baz_collection_name_in_asubproject:
-  uuid: zzzzz-o0j2j-bazprojectname2
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-04-21 15:37:48 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  modified_at: 2014-04-21 15:37:48 -0400
-  updated_at: 2014-04-21 15:37:48 -0400
-  tail_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
-  head_uuid: ea10d51bcf88862dbcc36eb292017dfd+45
-  link_class: name
-  # This should resemble the default name assigned when a
-  # Collection is added to a Project.
-  name: "ea10d51bcf88862dbcc36eb292017dfd+45 added sometime"
-  properties: {}
-
-empty_collection_name_in_active_user_home_project:
-  uuid: zzzzz-o0j2j-i3n6m552x6tmoi4
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-08-06 22:11:51.242392533 Z
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  modified_at: 2014-08-06 22:11:51.242150425 Z
-  tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  link_class: name
-  name: Empty collection
-  head_uuid: d41d8cd98f00b204e9800998ecf8427e+0
-  properties: {}
-  updated_at: 2014-08-06 22:11:51.242010312 Z
index d95177a91aee6d047efa78d3ca1f912d09bc1138..c737bb13843647d9b03cf81d2d9b0a1e141b11b6 100644 (file)
@@ -24,7 +24,7 @@ log4: # foo collection added, readable by active through link
   id: 4
   uuid: zzzzz-xxxxx-pshmckwoma00004
   owner_uuid: zzzzz-tpzed-000000000000000 # system user
-  object_uuid: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45 # foo file
+  object_uuid: 4n8aq-4zz18-znfnqtbbv4spc3w # foo file
   object_owner_uuid: zzzzz-tpzed-000000000000000 # system user
   event_at: <%= 4.minute.ago.to_s(:db) %>
 
@@ -32,6 +32,6 @@ log5: # baz collection added, readable by active and spectator through group 'al
   id: 5
   uuid: zzzzz-xxxxx-pshmckwoma00005
   owner_uuid: zzzzz-tpzed-000000000000000 # system user
-  object_uuid: ea10d51bcf88862dbcc36eb292017dfd+45 # baz file
+  object_uuid: 4n8aq-4zz18-y9vne9npefyxh8g # baz file
   object_owner_uuid: zzzzz-tpzed-000000000000000 # system user
   event_at: <%= 5.minute.ago.to_s(:db) %>
index 9c7f4886affa7eeb559443e6364210ee80254700..78d3d748a204083877ad61ee7f2caffc5e142371 100644 (file)
@@ -113,7 +113,7 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase
 ./baz acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:bar.txt
 EOS
     }
-    test_collection[:uuid] =
+    test_collection[:portable_data_hash] =
       Digest::MD5.hexdigest(test_collection[:manifest_text]) +
       '+' +
       test_collection[:manifest_text].length.to_s
@@ -129,7 +129,7 @@ EOS
     assert_nil assigns(:objects)
 
     get :show, {
-      id: test_collection[:uuid]
+      id: test_collection[:portable_data_hash]
     }
     assert_response :success
     assert_not_nil assigns(:object)
@@ -150,7 +150,7 @@ EOS
     authorize_with :active
     test_collection = {
       manifest_text: "",
-      uuid: "d41d8cd98f00b204e9800998ecf8427e+0"
+      portable_data_hash: "d41d8cd98f00b204e9800998ecf8427e+0"
     }
     post :create, {
       collection: test_collection
@@ -173,12 +173,27 @@ EOS
       collection: {
         owner_uuid: 'zzzzz-j7d0g-rew6elm53kancon',
         manifest_text: manifest_text,
-        uuid: "d30fe8ae534397864cb96c544f4cf102"
+        portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
       }
     }
     assert_response :success
     resp = JSON.parse(@response.body)
-    assert_equal 'zzzzz-tpzed-000000000000000', resp['owner_uuid']
+    assert_equal 'zzzzz-j7d0g-rew6elm53kancon', resp['owner_uuid']
+  end
+
+  test "create fails with duplicate name" do
+    permit_unsigned_manifests
+    authorize_with :admin
+    manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
+    post :create, {
+      collection: {
+        owner_uuid: 'zzzzz-tpzed-000000000000000',
+        manifest_text: manifest_text,
+        portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47",
+        name: "foo_file"
+      }
+    }
+    assert_response 422
   end
 
   test "create with owner_uuid set to group i can_manage" do
@@ -187,30 +202,44 @@ EOS
     manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
     post :create, {
       collection: {
-        owner_uuid: 'zzzzz-j7d0g-8ulrifv67tve5sx',
+        owner_uuid: groups(:active_user_has_can_manage).uuid,
         manifest_text: manifest_text,
-        uuid: "d30fe8ae534397864cb96c544f4cf102"
+        portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
       }
     }
     assert_response :success
     resp = JSON.parse(@response.body)
-    assert_equal 'zzzzz-tpzed-000000000000000', resp['owner_uuid']
+    assert_equal groups(:active_user_has_can_manage).uuid, resp['owner_uuid']
   end
 
-  test "create with owner_uuid set to group with no can_manage permission" do
+  test "create with owner_uuid fails on group with only can_read permission" do
     permit_unsigned_manifests
     authorize_with :active
     manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
     post :create, {
       collection: {
-        owner_uuid: 'zzzzz-j7d0g-it30l961gq3t0oi',
+        owner_uuid: groups(:all_users).uuid,
         manifest_text: manifest_text,
-        uuid: "d30fe8ae534397864cb96c544f4cf102"
+        portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
       }
     }
     assert_response 403
   end
 
+  test "create with owner_uuid fails on group with no permission" do
+    permit_unsigned_manifests
+    authorize_with :active
+    manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
+    post :create, {
+      collection: {
+        owner_uuid: groups(:public).uuid,
+        manifest_text: manifest_text,
+        portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
+      }
+    }
+    assert_response 422
+  end
+
   test "admin create with owner_uuid set to group with no permission" do
     permit_unsigned_manifests
     authorize_with :admin
@@ -219,7 +248,7 @@ EOS
       collection: {
         owner_uuid: 'zzzzz-j7d0g-it30l961gq3t0oi',
         manifest_text: manifest_text,
-        uuid: "d30fe8ae534397864cb96c544f4cf102"
+        portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
       }
     }
     assert_response :success
@@ -232,7 +261,7 @@ EOS
       collection: <<-EOS
       {
         "manifest_text":". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n",\
-        "uuid":"d30fe8ae534397864cb96c544f4cf102"\
+        "portable_data_hash":"d30fe8ae534397864cb96c544f4cf102+47"\
       }
       EOS
     }
@@ -246,7 +275,7 @@ EOS
       collection: <<-EOS
       {
         "manifest_text":". d41d8cd98f00b204e9800998ecf8427e 0:0:bar.txt\n",\
-        "uuid":"d30fe8ae534397864cb96c544f4cf102"\
+        "portable_data_hash":"d30fe8ae534397864cb96c544f4cf102+47"\
       }
       EOS
     }
@@ -259,13 +288,13 @@ EOS
     post :create, {
       collection: {
         manifest_text: ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n",
-        uuid: "d30fe8ae534397864cb96c544f4cf102+47+Khint+Xhint+Zhint"
+        portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47+Khint+Xhint+Zhint"
       }
     }
     assert_response :success
     assert_not_nil assigns(:object)
     resp = JSON.parse(@response.body)
-    assert_equal "d30fe8ae534397864cb96c544f4cf102+47", resp['uuid']
+    assert_equal "d30fe8ae534397864cb96c544f4cf102+47", resp['portable_data_hash']
   end
 
   test "get full provenance for baz file" do
@@ -306,8 +335,8 @@ EOS
       where: { any: ['contains', '7f9102c395f4ffc5e3'] }
     }
     assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal 1, found.count
+    found = assigns(:objects).collect(&:portable_data_hash)
+    assert_equal 2, found.count
     assert_equal true, !!found.index('1f4b0bc7583c2a7f9102c395f4ffc5e3+45')
   end
 
@@ -350,13 +379,13 @@ EOS
       post :create, {
         collection: {
           manifest_text: signed_manifest,
-          uuid: manifest_uuid,
+          portable_data_hash: manifest_uuid,
         }
       }
       assert_response :success
       assert_not_nil assigns(:object)
       resp = JSON.parse(@response.body)
-      assert_equal manifest_uuid, resp['uuid']
+      assert_equal manifest_uuid, resp['portable_data_hash']
       assert_equal 48, resp['data_size']
       # All of the locators in the output must be signed.
       resp['manifest_text'].lines.each do |entry|
@@ -398,13 +427,13 @@ EOS
     post :create, {
       collection: {
         manifest_text: signed_manifest,
-        uuid: manifest_uuid,
+        portable_data_hash: manifest_uuid,
       }
     }
     assert_response :success
     assert_not_nil assigns(:object)
     resp = JSON.parse(@response.body)
-    assert_equal manifest_uuid, resp['uuid']
+    assert_equal manifest_uuid, resp['portable_data_hash']
     assert_equal 48, resp['data_size']
     # All of the locators in the output must be signed.
     resp['manifest_text'].lines.each do |entry|
@@ -438,7 +467,7 @@ EOS
     post :create, {
       collection: {
         manifest_text: bad_manifest,
-        uuid: manifest_uuid
+        portable_data_hash: manifest_uuid
       }
     }
 
@@ -462,7 +491,7 @@ EOS
     post :create, {
       collection: {
         manifest_text: signed_manifest,
-        uuid: manifest_uuid
+        portable_data_hash: manifest_uuid
       }
     }
 
@@ -484,7 +513,7 @@ EOS
 
     test_collection = {
       manifest_text: manifest_text,
-      uuid: manifest_uuid,
+      portable_data_hash: manifest_uuid,
     }
     post_collection = Marshal.load(Marshal.dump(test_collection))
     post :create, {
@@ -493,7 +522,7 @@ EOS
     assert_response :success
     assert_not_nil assigns(:object)
     resp = JSON.parse(@response.body)
-    assert_equal manifest_uuid, resp['uuid']
+    assert_equal manifest_uuid, resp['portable_data_hash']
     assert_equal 48, resp['data_size']
 
     # The manifest in the response will have had permission hints added.
@@ -526,13 +555,13 @@ EOS
     post :create, {
       collection: {
         manifest_text: signed_manifest,
-        uuid: manifest_uuid,
+        portable_data_hash: manifest_uuid,
       }
     }
     assert_response :success
     assert_not_nil assigns(:object)
     resp = JSON.parse(@response.body)
-    assert_equal manifest_uuid, resp['uuid']
+    assert_equal manifest_uuid, resp['portable_data_hash']
     assert_equal 48, resp['data_size']
     # All of the locators in the output must be signed.
     # Each line is of the form "path locator locator ... 0:0:file.txt"
@@ -554,7 +583,7 @@ EOS
     post :create, {
       collection: {
         manifest_text: unsigned_manifest,
-        uuid: manifest_uuid,
+        portable_data_hash: manifest_uuid,
       }
     }
     assert_response 403,
@@ -562,4 +591,5 @@ EOS
     assert_empty Collection.where('uuid like ?', manifest_uuid+'%'),
     "Collection should not exist in database after failed create"
   end
+
 end
index 217809ae6569cf55b066cd776fd187405b5519b4..e8ce6fd6df58c2bdc17cdd9170c1d4c264a9ec48 100644 (file)
@@ -90,44 +90,35 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     check_project_contents_response
   end
 
-  [false, true].each do |include_linked|
-    test "list objects across projects, include_linked=#{include_linked}" do
-      authorize_with :project_viewer
-      get :contents, {
-        format: :json,
-        include_linked: include_linked,
-        filters: [['uuid', 'is_a', 'arvados#specimen']]
-      }
-      assert_response :success
-      found_uuids = json_response['items'].collect { |i| i['uuid'] }
-      [[:in_aproject, true],
-       [:in_asubproject, true],
-       [:owned_by_private_group, false]].each do |specimen_fixture, should_find|
-        if should_find
-          assert_includes found_uuids, specimens(specimen_fixture).uuid, "did not find specimen fixture '#{specimen_fixture}'"
-        else
-          refute_includes found_uuids, specimens(specimen_fixture).uuid, "found specimen fixture '#{specimen_fixture}'"
-        end
+  test "list objects across projects" do
+    authorize_with :project_viewer
+    get :contents, {
+      format: :json,
+      filters: [['uuid', 'is_a', 'arvados#specimen']]
+    }
+    assert_response :success
+    found_uuids = json_response['items'].collect { |i| i['uuid'] }
+    [[:in_aproject, true],
+     [:in_asubproject, true],
+     [:owned_by_private_group, false]].each do |specimen_fixture, should_find|
+      if should_find
+        assert_includes found_uuids, specimens(specimen_fixture).uuid, "did not find specimen fixture '#{specimen_fixture}'"
+      else
+        refute_includes found_uuids, specimens(specimen_fixture).uuid, "found specimen fixture '#{specimen_fixture}'"
       end
     end
   end
 
-  [false, true].each do |include_linked|
-    test "list objects in home project, include_linked=#{include_linked}" do
-      authorize_with :active
-      get :contents, {
-        format: :json,
-        id: users(:active).uuid,
-        include_linked: include_linked,
-      }
-      assert_response :success
-      found_uuids = json_response['items'].collect { |i| i['uuid'] }
-      if include_linked
-        assert_includes found_uuids, collections(:empty).uuid, "empty collection did not appear in home project"
-      end
-      assert_includes found_uuids, specimens(:owned_by_active_user).uuid, "specimen did not appear in home project"
-      refute_includes found_uuids, specimens(:in_asubproject).uuid, "specimen appeared unexpectedly in home project"
-    end
+  test "list objects in home project" do
+    authorize_with :active
+    get :contents, {
+      format: :json,
+      id: users(:active).uuid
+    }
+    assert_response :success
+    found_uuids = json_response['items'].collect { |i| i['uuid'] }
+    assert_includes found_uuids, specimens(:owned_by_active_user).uuid, "specimen did not appear in home project"
+    refute_includes found_uuids, specimens(:in_asubproject).uuid, "specimen appeared unexpectedly in home project"
   end
 
   test "user with project read permission can see project collections" do
@@ -135,10 +126,9 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     get :contents, {
       id: groups(:asubproject).uuid,
       format: :json,
-      include_linked: true,
     }
     ids = json_response['items'].map { |item| item["uuid"] }
-    assert_includes ids, collections(:baz_file).uuid
+    assert_includes ids, collections(:baz_file_in_asubproject).uuid
   end
 
   test 'list objects across multiple projects' do
@@ -188,8 +178,8 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     authorize_with :project_viewer
     @controller = Arvados::V1::LinksController.new
     post :update, {
-      id: links(:job_name_in_aproject).uuid,
-      link: {name: "Denied test name"},
+      id: jobs(:running).uuid,
+      name: "Denied test name",
     }
     assert_includes(403..404, response.status)
   end
@@ -198,7 +188,7 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     @controller = Arvados::V1::PipelineTemplatesController.new
     authorize_with :project_viewer
     post :update, {
-      id: links(:template_name_in_aproject).head_uuid,
+      id: pipeline_templates(:two_part).uuid,
       pipeline_template: {
         owner_uuid: users(:project_viewer).uuid,
       }
@@ -261,77 +251,42 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     assert_equal nil, uuids.index(unexpected_uuid)
   end
 
-  test 'get group-owned objects with include_linked' do
-    expected_uuid = specimens(:in_aproject_linked_from_asubproject).uuid
-    authorize_with :active
-    get :contents, {
-      id: groups(:asubproject).uuid,
-      include_linked: true,
-      format: :json,
-    }
-    assert_response :success
-    uuids = json_response['items'].collect { |i| i['uuid'] }
-    assert_includes uuids, expected_uuid, "Did not get #{expected_uuid}"
 
-    expected_name = links(:specimen_is_in_two_projects).name
-    found_specimen_name = false
-    assert(json_response['links'].any?,
-           "Expected a non-empty array of links in response")
-    json_response['links'].each do |link|
-      if link['head_uuid'] == expected_uuid
-        if link['name'] == expected_name
-          found_specimen_name = true
-        end
-      end
-    end
-    assert(found_specimen_name,
-           "Expected to find name '#{expected_name}' in response")
-  end
-
-  [false, true].each do |inc_ind|
-    test "get all pages of group-owned #{'and -linked ' if inc_ind}objects" do
-      authorize_with :active
-      limit = 5
-      offset = 0
-      items_available = nil
-      uuid_received = {}
-      owner_received = {}
-      while true
-        # Behaving badly here, using the same controller multiple
-        # times within a test.
-        @json_response = nil
-        get :contents, {
-          id: groups(:aproject).uuid,
-          include_linked: inc_ind,
-          limit: limit,
-          offset: offset,
-          format: :json,
-        }
-        assert_response :success
-        assert_operator(0, :<, json_response['items'].count,
-                        "items_available=#{items_available} but received 0 "\
-                        "items with offset=#{offset}")
-        items_available ||= json_response['items_available']
-        assert_equal(items_available, json_response['items_available'],
-                     "items_available changed between page #{offset/limit} "\
-                     "and page #{1+offset/limit}")
-        json_response['items'].each do |item|
-          uuid = item['uuid']
-          assert_equal(nil, uuid_received[uuid],
-                       "Received '#{uuid}' again on page #{1+offset/limit}")
-          uuid_received[uuid] = true
-          owner_received[item['owner_uuid']] = true
-          offset += 1
-          if not inc_ind
-            assert_equal groups(:aproject).uuid, item['owner_uuid']
-          end
-        end
-        break if offset >= items_available
-      end
-      if inc_ind
-        assert_operator 0, :<, (json_response.keys - [users(:active).uuid]).count,
-        "Set include_linked=true but did not receive any non-owned items"
+  test "get all pages of group-owned objects" do
+    authorize_with :active
+    limit = 5
+    offset = 0
+    items_available = nil
+    uuid_received = {}
+    owner_received = {}
+    while true
+      # Behaving badly here, using the same controller multiple
+      # times within a test.
+      @json_response = nil
+      get :contents, {
+        id: groups(:aproject).uuid,
+        limit: limit,
+        offset: offset,
+        format: :json,
+      }
+      assert_response :success
+      assert_operator(0, :<, json_response['items'].count,
+                      "items_available=#{items_available} but received 0 "\
+                      "items with offset=#{offset}")
+      items_available ||= json_response['items_available']
+      assert_equal(items_available, json_response['items_available'],
+                   "items_available changed between page #{offset/limit} "\
+                   "and page #{1+offset/limit}")
+      json_response['items'].each do |item|
+        uuid = item['uuid']
+        assert_equal(nil, uuid_received[uuid],
+                     "Received '#{uuid}' again on page #{1+offset/limit}")
+        uuid_received[uuid] = true
+        owner_received[item['owner_uuid']] = true
+        offset += 1
+        assert_equal groups(:aproject).uuid, item['owner_uuid']
       end
+      break if offset >= items_available
     end
   end
 
index b131947bc939f603168839635cb81255a1ddaad7..faeaae00a488b9d823b9884b07b0ac28803362c1 100644 (file)
@@ -165,7 +165,7 @@ class Arvados::V1::LinksControllerTest < ActionController::TestCase
     assert_response :success
     found = assigns(:objects)
     assert_not_equal 0, found.count
-    assert_equal found.count, (found.select { |f| f.head_uuid.match /[a-f0-9]{32}\+\d+/}).count
+    assert_equal found.count, (found.select { |f| f.head_uuid.match /.....-4zz18-.............../}).count
   end
 
   test "test can still use where tail_kind" do
@@ -270,20 +270,6 @@ class Arvados::V1::LinksControllerTest < ActionController::TestCase
     assert_response :success
   end
 
-  test "refuse duplicate name" do
-    the_name = links(:job_name_in_aproject).name
-    the_project = links(:job_name_in_aproject).tail_uuid
-    authorize_with :active
-    post :create, link: {
-      tail_uuid: the_project,
-      head_uuid: specimens(:owned_by_active_user).uuid,
-      link_class: 'name',
-      name: the_name,
-      properties: {this_s: "a duplicate name"}
-    }
-    assert_response 422
-  end
-
   test "project owner can show a project permission" do
     uuid = links(:project_viewer_can_read_project).uuid
     authorize_with :active
index bc89c00bf63f90cbebe0bc5f2de95691061e1c70..5d06c0a3d5de8f904b80e8ce9f7f6023f7b351a8 100644 (file)
@@ -80,9 +80,25 @@ class CollectionsApiTest < ActionDispatch::IntegrationTest
                                        signing_opts)
     post "/arvados/v1/collections", {
       format: :json,
-      collection: "{\"manifest_text\":\". #{signed_locator} 0:44:md5sum.txt\\n\",\"uuid\":\"ad02e37b6a7f45bbe2ead3c29a109b8a+54\"}"
+      collection: "{\"manifest_text\":\". #{signed_locator} 0:44:md5sum.txt\\n\",\"portable_data_hash\":\"ad02e37b6a7f45bbe2ead3c29a109b8a+54\"}"
     }, auth(:active)
     assert_response 200
-    assert_equal 'ad02e37b6a7f45bbe2ead3c29a109b8a+54', json_response['uuid']
+    assert_equal 'ad02e37b6a7f45bbe2ead3c29a109b8a+54', json_response['portable_data_hash']
   end
+
+  test "store collection with manifest_text only" do
+    signing_opts = {
+      key: Rails.configuration.blob_signing_key,
+      api_token: api_token(:active),
+    }
+    signed_locator = Blob.sign_locator('bad42fa702ae3ea7d888fef11b46f450+44',
+                                       signing_opts)
+    post "/arvados/v1/collections", {
+      format: :json,
+      collection: "{\"manifest_text\":\". #{signed_locator} 0:44:md5sum.txt\\n\"}"
+    }, auth(:active)
+    assert_response 200
+    assert_equal 'ad02e37b6a7f45bbe2ead3c29a109b8a+54', json_response['portable_data_hash']
+  end
+
 end
index 85d07a5746afce9e85c4287ed2d487c5d465663f..e47aa3b8921fc067a8ea5d24c0b7f59eaa231853 100644 (file)
@@ -12,8 +12,7 @@ class ArvadosModelTest < ActiveSupport::TestCase
     set_user_from_auth :active_trustedclient
     want_uuid = Specimen.generate_uuid
     a = create_with_attrs(uuid: want_uuid)
-    assert_not_equal want_uuid, a.uuid, "Non-admin should not assign uuid."
-    assert a.uuid.length==27, "Auto assigned uuid length is wrong."
+    assert_nil a, "Non-admin should not assign uuid."
   end
 
   test 'admin can assign valid uuid' do
@@ -24,11 +23,23 @@ class ArvadosModelTest < ActiveSupport::TestCase
     assert a.uuid.length==27, "Auto assigned uuid length is wrong."
   end
 
+  test 'admin cannot assign uuid with wrong object type' do
+    set_user_from_auth :admin_trustedclient
+    want_uuid = Human.generate_uuid
+    a = create_with_attrs(uuid: want_uuid)
+    assert_nil a, "Admin should not be able to assign invalid uuid."
+  end
+
+  test 'admin cannot assign badly formed uuid' do
+    set_user_from_auth :admin_trustedclient
+    a = create_with_attrs(uuid: "ntoheunthaoesunhasoeuhtnsaoeunhtsth")
+    assert_nil a, "Admin should not be able to assign invalid uuid."
+  end
+
   test 'admin cannot assign empty uuid' do
     set_user_from_auth :admin_trustedclient
     a = create_with_attrs(uuid: "")
-    assert_not_equal "", a.uuid, "Admin should not assign empty uuid."
-    assert a.uuid.length==27, "Auto assigned uuid length is wrong."
+    assert_nil a, "Admin cannot assign empty uuid."
   end
 
   [ {:a => 'foo'},
index 97977a5d56be7ba87bd8c2c93938b41ec36902b2..8c40d3aa476b3327a3abf5235e6982ceaa5a1df0 100644 (file)
@@ -14,7 +14,8 @@ class GroupTest < ActiveSupport::TestCase
     # Use the group as the owner of a new object
     s = Specimen.
       create(owner_uuid: groups(:bad_group_has_ownership_cycle_b).uuid)
-    assert s.valid?, "ownership should pass validation"
+    puts s.errors.messages
+    assert s.valid?, "ownership should pass validation #{s.errors.messages}"
     assert_equal false, s.save, "should not save object with #{g.uuid} as owner"
 
     # Use the group as the new owner of an existing object
@@ -27,11 +28,8 @@ class GroupTest < ActiveSupport::TestCase
   test "cannot create a new ownership cycle" do
     set_user_from_auth :active_trustedclient
 
-    g_foo = Group.create(name: "foo")
-    g_foo.save!
-
-    g_bar = Group.create(name: "bar")
-    g_bar.save!
+    g_foo = Group.create!(name: "foo")
+    g_bar = Group.create!(name: "bar")
 
     g_foo.owner_uuid = g_bar.uuid
     assert g_foo.save, lambda { g_foo.errors.messages }
@@ -44,11 +42,11 @@ class GroupTest < ActiveSupport::TestCase
   test "cannot create a single-object ownership cycle" do
     set_user_from_auth :active_trustedclient
 
-    g_foo = Group.create(name: "foo")
+    g_foo = Group.create!(name: "foo")
     assert g_foo.save
 
     # Ensure I have permission to manage this group even when its owner changes
-    perm_link = Link.create(tail_uuid: users(:active).uuid,
+    perm_link = Link.create!(tail_uuid: users(:active).uuid,
                             head_uuid: g_foo.uuid,
                             link_class: 'permission',
                             name: 'can_manage')
index 73a676f2fd3276143d5f1b2c007c5d84555c304a..730eb0634a5cc8f18373eb1b9ff219ca072bbeb2 100644 (file)
@@ -25,16 +25,16 @@ class JobTest < ActiveSupport::TestCase
     assert_nil job.docker_image_locator
   end
 
-  { 'name' => [:links, :docker_image_collection_repository, :name],
+  { 'name' => [:links, :docker_image_collection_tag, :name],
     'hash' => [:links, :docker_image_collection_hash, :name],
-    'locator' => [:collections, :docker_image, :uuid],
+    'locator' => [:collections, :docker_image, :portable_data_hash],
   }.each_pair do |spec_type, (fixture_type, fixture_name, fixture_attr)|
     test "Job initialized with Docker image #{spec_type} gets locator" do
       image_spec = send(fixture_type, fixture_name).send(fixture_attr)
       job = Job.new job_attrs(runtime_constraints:
                               {'docker_image' => image_spec})
       assert job.valid?, job.errors.full_messages.to_s
-      assert_equal(collections(:docker_image).uuid, job.docker_image_locator)
+      assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
     end
 
     test "Job modified with Docker image #{spec_type} gets locator" do
@@ -44,12 +44,12 @@ class JobTest < ActiveSupport::TestCase
       image_spec = send(fixture_type, fixture_name).send(fixture_attr)
       job.runtime_constraints['docker_image'] = image_spec
       assert job.valid?, job.errors.full_messages.to_s
-      assert_equal(collections(:docker_image).uuid, job.docker_image_locator)
+      assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
     end
   end
 
   test "removing a Docker runtime constraint removes the locator" do
-    image_locator = collections(:docker_image).uuid
+    image_locator = collections(:docker_image).portable_data_hash
     job = Job.new job_attrs(runtime_constraints:
                             {'docker_image' => image_locator})
     assert job.valid?, job.errors.full_messages.to_s
@@ -66,11 +66,11 @@ class JobTest < ActiveSupport::TestCase
                             {'docker_image' => image_repo,
                               'docker_image_tag' => image_tag})
     assert job.valid?, job.errors.full_messages.to_s
-    assert_equal(collections(:docker_image).uuid, job.docker_image_locator)
+    assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
   end
 
   test "can't locate a Docker image with a nonexistent tag" do
-    image_repo = links(:docker_image_collection_repository).name
+    image_repo = links(:docker_image_collection_tag).name
     image_tag = '__nonexistent tag__'
     job = Job.new job_attrs(runtime_constraints:
                             {'docker_image' => image_repo,
@@ -83,7 +83,7 @@ class JobTest < ActiveSupport::TestCase
     job = Job.new job_attrs(runtime_constraints:
                             {'docker_image' => image_hash})
     assert job.valid?, job.errors.full_messages.to_s + " with partial hash #{image_hash}"
-    assert_equal(collections(:docker_image).uuid, job.docker_image_locator)
+    assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
   end
 
   { 'name' => 'arvados_test_nonexistent',
@@ -104,7 +104,7 @@ class JobTest < ActiveSupport::TestCase
   end
 
   test "can create Job with Docker image Collection without Docker links" do
-    image_uuid = collections(:unlinked_docker_image).uuid
+    image_uuid = collections(:unlinked_docker_image).portable_data_hash
     job = Job.new job_attrs(runtime_constraints: {"docker_image" => image_uuid})
     assert(job.valid?, "Job created with unlinked Docker image was invalid")
     assert_equal(image_uuid, job.docker_image_locator)
index 640b26c64d29fcf5a101e279045be0371225108a..ff7e8eaa645c1c10f5f0abb41811b37095d167b7 100644 (file)
@@ -7,48 +7,6 @@ class LinkTest < ActiveSupport::TestCase
     set_user_from_auth :admin_trustedclient
   end
 
-  test 'name links with the same tail_uuid must be unique' do
-    a = Link.create!(tail_uuid: groups(:aproject).uuid,
-                     head_uuid: specimens(:owned_by_active_user).uuid,
-                     link_class: 'name',
-                     name: 'foo')
-    assert a.valid?, a.errors.to_s
-    assert_equal groups(:aproject).uuid, a.owner_uuid
-    assert_raises ActiveRecord::RecordNotUnique do
-      b = Link.create!(tail_uuid: groups(:aproject).uuid,
-                       head_uuid: specimens(:owned_by_active_user).uuid,
-                       link_class: 'name',
-                       name: 'foo')
-    end
-  end
-
-  test 'name links with different tail_uuid need not be unique' do
-    a = Link.create!(tail_uuid: groups(:aproject).uuid,
-                     head_uuid: specimens(:owned_by_active_user).uuid,
-                     link_class: 'name',
-                     name: 'foo')
-    assert a.valid?, a.errors.to_s
-    assert_equal groups(:aproject).uuid, a.owner_uuid
-    b = Link.create!(tail_uuid: groups(:asubproject).uuid,
-                     head_uuid: specimens(:owned_by_active_user).uuid,
-                     link_class: 'name',
-                     name: 'foo')
-    assert b.valid?, b.errors.to_s
-    assert_equal groups(:asubproject).uuid, b.owner_uuid
-    assert_not_equal(a.uuid, b.uuid,
-                     "created two links and got the same uuid back.")
-  end
-
-  [nil, '', false].each do |name|
-    test "name links cannot have name=#{name.inspect}" do
-      a = Link.create(tail_uuid: groups(:aproject).uuid,
-                      head_uuid: specimens(:owned_by_active_user).uuid,
-                      link_class: 'name',
-                      name: name)
-      assert a.invalid?, "invalid name was accepted as valid?"
-    end
-  end
-
   test "cannot delete an object referenced by links" do
     ob = Specimen.create
     link = Link.create(tail_uuid: users(:active).uuid,
index c177bc3901cdc74db85d448b32222eb586bedcb1..c7f9776ac6a98c36f8ab8a3e002773c03603ea6a 100644 (file)
@@ -17,7 +17,7 @@ class OwnerTest < ActiveSupport::TestCase
   Group.all
   [User, Group].each do |o_class|
     test "create object with legit #{o_class} owner" do
-      o = o_class.create
+      o = o_class.create!
       i = Specimen.create(owner_uuid: o.uuid)
       assert i.valid?, "new item should pass validation"
       assert i.uuid, "new item should have an ID"
@@ -40,9 +40,9 @@ class OwnerTest < ActiveSupport::TestCase
 
     [User, Group].each do |new_o_class|
       test "change owner from legit #{o_class} to legit #{new_o_class} owner" do
-        o = o_class.create
-        i = Specimen.create(owner_uuid: o.uuid)
-        new_o = new_o_class.create
+        o = o_class.create!
+        i = Specimen.create!(owner_uuid: o.uuid)
+        new_o = new_o_class.create!
         assert(Specimen.where(uuid: i.uuid).any?,
                "new item should really be in DB")
         assert(i.update_attributes(owner_uuid: new_o.uuid),
@@ -51,7 +51,7 @@ class OwnerTest < ActiveSupport::TestCase
     end
 
     test "delete #{o_class} that owns nothing" do
-      o = o_class.create
+      o = o_class.create!
       assert(o_class.where(uuid: o.uuid).any?,
              "new #{o_class} should really be in DB")
       assert(o.destroy, "should delete #{o_class} that owns nothing")
@@ -61,7 +61,7 @@ class OwnerTest < ActiveSupport::TestCase
 
     test "change uuid of #{o_class} that owns nothing" do
       # (we're relying on our admin credentials here)
-      o = o_class.create
+      o = o_class.create!
       assert(o_class.where(uuid: o.uuid).any?,
              "new #{o_class} should really be in DB")
       old_uuid = o.uuid
@@ -97,7 +97,7 @@ class OwnerTest < ActiveSupport::TestCase
   end
 
   test "delete User that owns self" do
-    o = User.create
+    o = User.create!
     assert User.where(uuid: o.uuid).any?, "new User should really be in DB"
     assert_equal(true, o.update_attributes(owner_uuid: o.uuid),
                  "setting owner to self should work")
@@ -107,7 +107,7 @@ class OwnerTest < ActiveSupport::TestCase
   end
 
   test "change uuid of User that owns self" do
-    o = User.create
+    o = User.create!
     assert User.where(uuid: o.uuid).any?, "new User should really be in DB"
     assert_equal(true, o.update_attributes(owner_uuid: o.uuid),
                  "setting owner to self should work")