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)
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
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.
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
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
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|
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
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)
# 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
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
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
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
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
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
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
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
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
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?")
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
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
})
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'
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
end
end
- def self.uuid_like_pattern
- "________________________________+%"
- end
-
def self.normalize_uuid uuid
hash_part = nil
size_part = nil
# 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",
# 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
# 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
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
# 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
t.add :repository
t.add :supplied_script_version
t.add :docker_image_locator
+ t.add :name
+ t.add :description
end
def assert_finished
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}")
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
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.
# 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]
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
+++ /dev/null
-<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>
+++ /dev/null
-<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>
+++ /dev/null
-<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>
+++ /dev/null
-<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>
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--
SET statement_timeout = 0;
-SET lock_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SET check_function_bodies = false;
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),
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
);
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)
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
);
updated_at timestamp without time zone NOT NULL,
properties text,
state character varying(255),
- components_summary text
+ components_summary text,
+ description text
);
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:
--
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:
--
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:
--
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
tail_uuid: zzzzz-tpzed-000000000000000
link_class: signature
name: require
- head_uuid: b519d9cb706a29fc7ea24dbea2f05851+249025
+ head_uuid: 4n8aq-4zz18-t68oksiu9m80s4y
properties: {}
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:
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
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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
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: {}
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:
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
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:
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:
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"
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"
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"
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
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
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) %>
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) %>
./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
assert_nil assigns(:objects)
get :show, {
- id: test_collection[:uuid]
+ id: test_collection[:portable_data_hash]
}
assert_response :success
assert_not_nil assigns(:object)
authorize_with :active
test_collection = {
manifest_text: "",
- uuid: "d41d8cd98f00b204e9800998ecf8427e+0"
+ portable_data_hash: "d41d8cd98f00b204e9800998ecf8427e+0"
}
post :create, {
collection: test_collection
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
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
collection: {
owner_uuid: 'zzzzz-j7d0g-it30l961gq3t0oi',
manifest_text: manifest_text,
- uuid: "d30fe8ae534397864cb96c544f4cf102"
+ portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
}
}
assert_response :success
collection: <<-EOS
{
"manifest_text":". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n",\
- "uuid":"d30fe8ae534397864cb96c544f4cf102"\
+ "portable_data_hash":"d30fe8ae534397864cb96c544f4cf102+47"\
}
EOS
}
collection: <<-EOS
{
"manifest_text":". d41d8cd98f00b204e9800998ecf8427e 0:0:bar.txt\n",\
- "uuid":"d30fe8ae534397864cb96c544f4cf102"\
+ "portable_data_hash":"d30fe8ae534397864cb96c544f4cf102+47"\
}
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
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
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|
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|
post :create, {
collection: {
manifest_text: bad_manifest,
- uuid: manifest_uuid
+ portable_data_hash: manifest_uuid
}
}
post :create, {
collection: {
manifest_text: signed_manifest,
- uuid: manifest_uuid
+ portable_data_hash: manifest_uuid
}
}
test_collection = {
manifest_text: manifest_text,
- uuid: manifest_uuid,
+ portable_data_hash: manifest_uuid,
}
post_collection = Marshal.load(Marshal.dump(test_collection))
post :create, {
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.
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"
post :create, {
collection: {
manifest_text: unsigned_manifest,
- uuid: manifest_uuid,
+ portable_data_hash: manifest_uuid,
}
}
assert_response 403,
assert_empty Collection.where('uuid like ?', manifest_uuid+'%'),
"Collection should not exist in database after failed create"
end
+
end
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
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
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
@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,
}
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
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
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
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
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
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'},
# 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
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 }
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')
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
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
{'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,
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',
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)
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,
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"
[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),
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")
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
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")
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")